Merge pull request #61501 from home-assistant/rc

This commit is contained in:
Franck Nijhof 2021-12-11 19:06:29 +01:00 committed by GitHub
commit 604a2ac327
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4372 changed files with 117583 additions and 33776 deletions

120
.core_files.yaml Normal file
View File

@ -0,0 +1,120 @@
# 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.yaml
- 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/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_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/common.py
- tests/conftest.py
- tests/ignore_uncaught_exceptions.py
- tests/mock/*
- tests/test_util/*
- tests/testing_config/**
other: &other
- .github/workflows/*
- homeassistant/scripts/**
any:
- *base_platforms
- *components
- *core
- *other
- *tests

View File

@ -90,6 +90,8 @@ omit =
homeassistant/components/azure_devops/sensor.py homeassistant/components/azure_devops/sensor.py
homeassistant/components/azure_service_bus/* homeassistant/components/azure_service_bus/*
homeassistant/components/baidu/tts.py homeassistant/components/baidu/tts.py
homeassistant/components/balboa/__init__.py
homeassistant/components/balboa/entity.py
homeassistant/components/beewi_smartclim/sensor.py homeassistant/components/beewi_smartclim/sensor.py
homeassistant/components/bbb_gpio/* homeassistant/components/bbb_gpio/*
homeassistant/components/bbox/device_tracker.py homeassistant/components/bbox/device_tracker.py
@ -123,6 +125,7 @@ omit =
homeassistant/components/bosch_shc/__init__.py homeassistant/components/bosch_shc/__init__.py
homeassistant/components/bosch_shc/binary_sensor.py homeassistant/components/bosch_shc/binary_sensor.py
homeassistant/components/bosch_shc/const.py homeassistant/components/bosch_shc/const.py
homeassistant/components/bosch_shc/cover.py
homeassistant/components/bosch_shc/entity.py homeassistant/components/bosch_shc/entity.py
homeassistant/components/bosch_shc/sensor.py homeassistant/components/bosch_shc/sensor.py
homeassistant/components/braviatv/__init__.py homeassistant/components/braviatv/__init__.py
@ -137,7 +140,9 @@ omit =
homeassistant/components/broadlink/updater.py homeassistant/components/broadlink/updater.py
homeassistant/components/brottsplatskartan/sensor.py homeassistant/components/brottsplatskartan/sensor.py
homeassistant/components/browser/* homeassistant/components/browser/*
homeassistant/components/brunt/__init__.py
homeassistant/components/brunt/cover.py homeassistant/components/brunt/cover.py
homeassistant/components/brunt/const.py
homeassistant/components/bsblan/climate.py homeassistant/components/bsblan/climate.py
homeassistant/components/bt_home_hub_5/device_tracker.py homeassistant/components/bt_home_hub_5/device_tracker.py
homeassistant/components/bt_smarthub/device_tracker.py homeassistant/components/bt_smarthub/device_tracker.py
@ -281,6 +286,7 @@ omit =
homeassistant/components/eq3btsmart/climate.py homeassistant/components/eq3btsmart/climate.py
homeassistant/components/esphome/__init__.py homeassistant/components/esphome/__init__.py
homeassistant/components/esphome/binary_sensor.py homeassistant/components/esphome/binary_sensor.py
homeassistant/components/esphome/button.py
homeassistant/components/esphome/camera.py homeassistant/components/esphome/camera.py
homeassistant/components/esphome/climate.py homeassistant/components/esphome/climate.py
homeassistant/components/esphome/cover.py homeassistant/components/esphome/cover.py
@ -369,7 +375,6 @@ omit =
homeassistant/components/fritzbox_callmonitor/const.py homeassistant/components/fritzbox_callmonitor/const.py
homeassistant/components/fritzbox_callmonitor/base.py homeassistant/components/fritzbox_callmonitor/base.py
homeassistant/components/fritzbox_callmonitor/sensor.py homeassistant/components/fritzbox_callmonitor/sensor.py
homeassistant/components/fronius/sensor.py
homeassistant/components/frontier_silicon/media_player.py homeassistant/components/frontier_silicon/media_player.py
homeassistant/components/futurenow/light.py homeassistant/components/futurenow/light.py
homeassistant/components/garadget/cover.py homeassistant/components/garadget/cover.py
@ -386,10 +391,6 @@ omit =
homeassistant/components/glances/sensor.py homeassistant/components/glances/sensor.py
homeassistant/components/gntp/notify.py homeassistant/components/gntp/notify.py
homeassistant/components/goalfeed/* homeassistant/components/goalfeed/*
homeassistant/components/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/*
homeassistant/components/google_cloud/tts.py homeassistant/components/google_cloud/tts.py
homeassistant/components/google_maps/device_tracker.py homeassistant/components/google_maps/device_tracker.py
@ -399,8 +400,6 @@ omit =
homeassistant/components/google_travel_time/sensor.py homeassistant/components/google_travel_time/sensor.py
homeassistant/components/gpmdp/media_player.py homeassistant/components/gpmdp/media_player.py
homeassistant/components/gpsd/sensor.py homeassistant/components/gpsd/sensor.py
homeassistant/components/greeneye_monitor/*
homeassistant/components/greeneye_monitor/sensor.py
homeassistant/components/greenwave/light.py homeassistant/components/greenwave/light.py
homeassistant/components/group/notify.py homeassistant/components/group/notify.py
homeassistant/components/growatt_server/sensor.py homeassistant/components/growatt_server/sensor.py
@ -421,9 +420,6 @@ omit =
homeassistant/components/harmony/data.py homeassistant/components/harmony/data.py
homeassistant/components/harmony/remote.py homeassistant/components/harmony/remote.py
homeassistant/components/harmony/util.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/haveibeenpwned/sensor.py
homeassistant/components/hdmi_cec/* homeassistant/components/hdmi_cec/*
homeassistant/components/heatmiser/climate.py homeassistant/components/heatmiser/climate.py
@ -433,8 +429,9 @@ omit =
homeassistant/components/hisense_aehw4a1/climate.py homeassistant/components/hisense_aehw4a1/climate.py
homeassistant/components/hitron_coda/device_tracker.py homeassistant/components/hitron_coda/device_tracker.py
homeassistant/components/hive/__init__.py homeassistant/components/hive/__init__.py
homeassistant/components/hive/climate.py homeassistant/components/hive/alarm_control_panel.py
homeassistant/components/hive/binary_sensor.py homeassistant/components/hive/binary_sensor.py
homeassistant/components/hive/climate.py
homeassistant/components/hive/light.py homeassistant/components/hive/light.py
homeassistant/components/hive/sensor.py homeassistant/components/hive/sensor.py
homeassistant/components/hive/switch.py homeassistant/components/hive/switch.py
@ -502,7 +499,6 @@ omit =
homeassistant/components/incomfort/* homeassistant/components/incomfort/*
homeassistant/components/intesishome/* homeassistant/components/intesishome/*
homeassistant/components/ios/* homeassistant/components/ios/*
homeassistant/components/iota/*
homeassistant/components/iperf3/* homeassistant/components/iperf3/*
homeassistant/components/iqvia/* homeassistant/components/iqvia/*
homeassistant/components/irish_rail_transport/sensor.py homeassistant/components/irish_rail_transport/sensor.py
@ -521,6 +517,8 @@ omit =
homeassistant/components/isy994/switch.py homeassistant/components/isy994/switch.py
homeassistant/components/itach/remote.py homeassistant/components/itach/remote.py
homeassistant/components/itunes/media_player.py homeassistant/components/itunes/media_player.py
homeassistant/components/jellyfin/__init__.py
homeassistant/components/jellyfin/media_source.py
homeassistant/components/joaoapps_join/* homeassistant/components/joaoapps_join/*
homeassistant/components/juicenet/__init__.py homeassistant/components/juicenet/__init__.py
homeassistant/components/juicenet/const.py homeassistant/components/juicenet/const.py
@ -541,7 +539,14 @@ omit =
homeassistant/components/keyboard_remote/* homeassistant/components/keyboard_remote/*
homeassistant/components/kira/* homeassistant/components/kira/*
homeassistant/components/kiwi/lock.py homeassistant/components/kiwi/lock.py
homeassistant/components/knx/* homeassistant/components/knx/__init__.py
homeassistant/components/knx/climate.py
homeassistant/components/knx/cover.py
homeassistant/components/knx/expose.py
homeassistant/components/knx/knx_entity.py
homeassistant/components/knx/light.py
homeassistant/components/knx/notify.py
homeassistant/components/knx/schema.py
homeassistant/components/kodi/__init__.py homeassistant/components/kodi/__init__.py
homeassistant/components/kodi/browse_media.py homeassistant/components/kodi/browse_media.py
homeassistant/components/kodi/const.py homeassistant/components/kodi/const.py
@ -551,7 +556,9 @@ omit =
homeassistant/components/kostal_plenticore/__init__.py homeassistant/components/kostal_plenticore/__init__.py
homeassistant/components/kostal_plenticore/const.py homeassistant/components/kostal_plenticore/const.py
homeassistant/components/kostal_plenticore/helper.py homeassistant/components/kostal_plenticore/helper.py
homeassistant/components/kostal_plenticore/select.py
homeassistant/components/kostal_plenticore/sensor.py homeassistant/components/kostal_plenticore/sensor.py
homeassistant/components/kostal_plenticore/switch.py
homeassistant/components/kwb/sensor.py homeassistant/components/kwb/sensor.py
homeassistant/components/lacrosse/sensor.py homeassistant/components/lacrosse/sensor.py
homeassistant/components/lametric/* homeassistant/components/lametric/*
@ -590,7 +597,6 @@ omit =
homeassistant/components/lookin/models.py homeassistant/components/lookin/models.py
homeassistant/components/lookin/sensor.py homeassistant/components/lookin/sensor.py
homeassistant/components/lookin/climate.py homeassistant/components/lookin/climate.py
homeassistant/components/loopenergy/sensor.py
homeassistant/components/luci/device_tracker.py homeassistant/components/luci/device_tracker.py
homeassistant/components/luftdaten/__init__.py homeassistant/components/luftdaten/__init__.py
homeassistant/components/luftdaten/sensor.py homeassistant/components/luftdaten/sensor.py
@ -640,7 +646,6 @@ omit =
homeassistant/components/miflora/sensor.py homeassistant/components/miflora/sensor.py
homeassistant/components/mikrotik/hub.py homeassistant/components/mikrotik/hub.py
homeassistant/components/mikrotik/device_tracker.py homeassistant/components/mikrotik/device_tracker.py
homeassistant/components/mill/__init__.py
homeassistant/components/mill/climate.py homeassistant/components/mill/climate.py
homeassistant/components/mill/const.py homeassistant/components/mill/const.py
homeassistant/components/mill/sensor.py homeassistant/components/mill/sensor.py
@ -669,7 +674,6 @@ omit =
homeassistant/components/mutesync/binary_sensor.py homeassistant/components/mutesync/binary_sensor.py
homeassistant/components/nest/const.py homeassistant/components/nest/const.py
homeassistant/components/mvglive/sensor.py homeassistant/components/mvglive/sensor.py
homeassistant/components/mychevy/*
homeassistant/components/mycroft/* homeassistant/components/mycroft/*
homeassistant/components/mysensors/__init__.py homeassistant/components/mysensors/__init__.py
homeassistant/components/mysensors/binary_sensor.py homeassistant/components/mysensors/binary_sensor.py
@ -692,6 +696,8 @@ omit =
homeassistant/components/myq/light.py homeassistant/components/myq/light.py
homeassistant/components/nad/media_player.py homeassistant/components/nad/media_player.py
homeassistant/components/nanoleaf/__init__.py homeassistant/components/nanoleaf/__init__.py
homeassistant/components/nanoleaf/button.py
homeassistant/components/nanoleaf/entity.py
homeassistant/components/nanoleaf/light.py homeassistant/components/nanoleaf/light.py
homeassistant/components/neato/__init__.py homeassistant/components/neato/__init__.py
homeassistant/components/neato/api.py homeassistant/components/neato/api.py
@ -871,6 +877,8 @@ omit =
homeassistant/components/remote_rpi_gpio/* homeassistant/components/remote_rpi_gpio/*
homeassistant/components/rest/notify.py homeassistant/components/rest/notify.py
homeassistant/components/rest/switch.py homeassistant/components/rest/switch.py
homeassistant/components/ridwell/__init__.py
homeassistant/components/ridwell/sensor.py
homeassistant/components/ring/camera.py homeassistant/components/ring/camera.py
homeassistant/components/ripple/sensor.py homeassistant/components/ripple/sensor.py
homeassistant/components/rocketchat/notify.py homeassistant/components/rocketchat/notify.py
@ -906,6 +914,7 @@ omit =
homeassistant/components/screenlogic/binary_sensor.py homeassistant/components/screenlogic/binary_sensor.py
homeassistant/components/screenlogic/climate.py homeassistant/components/screenlogic/climate.py
homeassistant/components/screenlogic/light.py homeassistant/components/screenlogic/light.py
homeassistant/components/screenlogic/number.py
homeassistant/components/screenlogic/sensor.py homeassistant/components/screenlogic/sensor.py
homeassistant/components/screenlogic/services.py homeassistant/components/screenlogic/services.py
homeassistant/components/screenlogic/switch.py homeassistant/components/screenlogic/switch.py
@ -926,6 +935,7 @@ omit =
homeassistant/components/shodan/sensor.py homeassistant/components/shodan/sensor.py
homeassistant/components/shelly/__init__.py homeassistant/components/shelly/__init__.py
homeassistant/components/shelly/binary_sensor.py homeassistant/components/shelly/binary_sensor.py
homeassistant/components/shelly/climate.py
homeassistant/components/shelly/entity.py homeassistant/components/shelly/entity.py
homeassistant/components/shelly/light.py homeassistant/components/shelly/light.py
homeassistant/components/shelly/sensor.py homeassistant/components/shelly/sensor.py
@ -1081,6 +1091,14 @@ omit =
homeassistant/components/todoist/calendar.py homeassistant/components/todoist/calendar.py
homeassistant/components/todoist/const.py homeassistant/components/todoist/const.py
homeassistant/components/tof/sensor.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/tomato/device_tracker.py
homeassistant/components/toon/__init__.py homeassistant/components/toon/__init__.py
homeassistant/components/toon/binary_sensor.py homeassistant/components/toon/binary_sensor.py
@ -1115,6 +1133,7 @@ omit =
homeassistant/components/tradfri/sensor.py homeassistant/components/tradfri/sensor.py
homeassistant/components/tradfri/switch.py homeassistant/components/tradfri/switch.py
homeassistant/components/trafikverket_train/sensor.py homeassistant/components/trafikverket_train/sensor.py
homeassistant/components/trafikverket_weatherstation/__init__.py
homeassistant/components/trafikverket_weatherstation/sensor.py homeassistant/components/trafikverket_weatherstation/sensor.py
homeassistant/components/transmission/sensor.py homeassistant/components/transmission/sensor.py
homeassistant/components/transmission/switch.py homeassistant/components/transmission/switch.py
@ -1124,6 +1143,7 @@ omit =
homeassistant/components/tuya/__init__.py homeassistant/components/tuya/__init__.py
homeassistant/components/tuya/base.py homeassistant/components/tuya/base.py
homeassistant/components/tuya/binary_sensor.py homeassistant/components/tuya/binary_sensor.py
homeassistant/components/tuya/button.py
homeassistant/components/tuya/camera.py homeassistant/components/tuya/camera.py
homeassistant/components/tuya/climate.py homeassistant/components/tuya/climate.py
homeassistant/components/tuya/const.py homeassistant/components/tuya/const.py
@ -1139,8 +1159,6 @@ omit =
homeassistant/components/tuya/switch.py homeassistant/components/tuya/switch.py
homeassistant/components/tuya/util.py homeassistant/components/tuya/util.py
homeassistant/components/tuya/vacuum.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_call/notify.py
homeassistant/components/twilio_sms/notify.py homeassistant/components/twilio_sms/notify.py
homeassistant/components/twitter/notify.py homeassistant/components/twitter/notify.py
@ -1169,7 +1187,9 @@ omit =
homeassistant/components/velbus/switch.py homeassistant/components/velbus/switch.py
homeassistant/components/velux/* homeassistant/components/velux/*
homeassistant/components/venstar/__init__.py homeassistant/components/venstar/__init__.py
homeassistant/components/venstar/binary_sensor.py
homeassistant/components/venstar/climate.py homeassistant/components/venstar/climate.py
homeassistant/components/venstar/sensor.py
homeassistant/components/verisure/__init__.py homeassistant/components/verisure/__init__.py
homeassistant/components/verisure/alarm_control_panel.py homeassistant/components/verisure/alarm_control_panel.py
homeassistant/components/verisure/binary_sensor.py homeassistant/components/verisure/binary_sensor.py
@ -1186,7 +1206,12 @@ omit =
homeassistant/components/vesync/light.py homeassistant/components/vesync/light.py
homeassistant/components/vesync/switch.py homeassistant/components/vesync/switch.py
homeassistant/components/viaggiatreno/sensor.py homeassistant/components/viaggiatreno/sensor.py
homeassistant/components/vicare/* homeassistant/components/vicare/binary_sensor.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/vilfo/__init__.py homeassistant/components/vilfo/__init__.py
homeassistant/components/vilfo/sensor.py homeassistant/components/vilfo/sensor.py
homeassistant/components/vilfo/const.py homeassistant/components/vilfo/const.py
@ -1263,6 +1288,7 @@ omit =
homeassistant/components/yale_smart_alarm/coordinator.py homeassistant/components/yale_smart_alarm/coordinator.py
homeassistant/components/yamaha_musiccast/__init__.py homeassistant/components/yamaha_musiccast/__init__.py
homeassistant/components/yamaha_musiccast/media_player.py homeassistant/components/yamaha_musiccast/media_player.py
homeassistant/components/yamaha_musiccast/number.py
homeassistant/components/yandex_transport/* homeassistant/components/yandex_transport/*
homeassistant/components/yeelightsunflower/light.py homeassistant/components/yeelightsunflower/light.py
homeassistant/components/yi/camera.py homeassistant/components/yi/camera.py

View File

@ -23,12 +23,12 @@ jobs:
publish: ${{ steps.version.outputs.publish }} publish: ${{ steps.version.outputs.publish }}
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v2.3.5 uses: actions/checkout@v2.4.0
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v2.2.2 uses: actions/setup-python@v2.3.1
with: with:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}
@ -56,9 +56,7 @@ jobs:
uses: home-assistant/actions/helpers/codenotary@master uses: home-assistant/actions/helpers/codenotary@master
with: with:
source: file://${{ github.workspace }}/OFFICIAL_IMAGE source: file://${{ github.workspace }}/OFFICIAL_IMAGE
user: ${{ secrets.VCN_USER }} token: ${{ secrets.CAS_TOKEN }}
password: ${{ secrets.VCN_PASSWORD }}
organisation: home-assistant.io
build_python: build_python:
name: Build PyPi package name: Build PyPi package
@ -67,10 +65,10 @@ jobs:
if: needs.init.outputs.publish == 'true' if: needs.init.outputs.publish == 'true'
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v2.3.5 uses: actions/checkout@v2.4.0
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v2.2.2 uses: actions/setup-python@v2.3.1
with: with:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}
@ -97,11 +95,11 @@ jobs:
arch: ${{ fromJson(needs.init.outputs.architectures) }} arch: ${{ fromJson(needs.init.outputs.architectures) }}
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v2.3.5 uses: actions/checkout@v2.4.0
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
if: needs.init.outputs.channel == 'dev' if: needs.init.outputs.channel == 'dev'
uses: actions/setup-python@v2.2.2 uses: actions/setup-python@v2.3.1
with: with:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}
@ -133,15 +131,15 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Build base image - name: Build base image
uses: home-assistant/builder@2021.09.0 uses: home-assistant/builder@2021.11.4
with: with:
args: | args: |
$BUILD_ARGS \ $BUILD_ARGS \
--${{ matrix.arch }} \ --${{ matrix.arch }} \
--target /data \ --target /data \
--with-codenotary "${{ secrets.VCN_USER }}" "${{ secrets.VCN_PASSWORD }}" "${{ secrets.VCN_ORG }}" \
--validate-from "${{ secrets.VCN_ORG }}" \
--generic ${{ needs.init.outputs.version }} --generic ${{ needs.init.outputs.version }}
env:
CAS_API_KEY: ${{ secrets.CAS_TOKEN }}
build_machine: build_machine:
name: Build ${{ matrix.machine }} machine core image name: Build ${{ matrix.machine }} machine core image
@ -170,7 +168,7 @@ jobs:
- tinker - tinker
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v2.3.5 uses: actions/checkout@v2.4.0
- name: Login to DockerHub - name: Login to DockerHub
uses: docker/login-action@v1.10.0 uses: docker/login-action@v1.10.0
@ -186,14 +184,14 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Build base image - name: Build base image
uses: home-assistant/builder@2021.09.0 uses: home-assistant/builder@2021.11.4
with: with:
args: | args: |
$BUILD_ARGS \ $BUILD_ARGS \
--target /data/machine \ --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 }}" --machine "${{ needs.init.outputs.version }}=${{ matrix.machine }}"
env:
CAS_API_KEY: ${{ secrets.CAS_TOKEN }}
publish_ha: publish_ha:
name: Publish version files name: Publish version files
@ -201,7 +199,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v2.3.5 uses: actions/checkout@v2.4.0
- name: Initialize git - name: Initialize git
uses: home-assistant/actions/helpers/git-init@master uses: home-assistant/actions/helpers/git-init@master
@ -233,7 +231,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v2.3.5 uses: actions/checkout@v2.4.0
- name: Login to DockerHub - name: Login to DockerHub
uses: docker/login-action@v1.10.0 uses: docker/login-action@v1.10.0
@ -248,8 +246,8 @@ jobs:
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Install VCN tools - name: Install CAS tools
uses: home-assistant/actions/helpers/vcn@master uses: home-assistant/actions/helpers/cas@master
- name: Build Meta Image - name: Build Meta Image
shell: bash shell: bash
@ -293,8 +291,7 @@ jobs:
function validate_image() { function validate_image() {
local image=${1} local image=${1}
state="$(vcn authenticate --org home-assistant.io --output json docker://${image} | jq '.verification.status // 2')" if ! cas authenticate --signerID notary@home-assistant.io "docker://${image}"; then
if [[ "${state}" != "0" ]]; then
echo "Invalid signature!" echo "Invalid signature!"
exit 1 exit 1
fi fi

View File

@ -15,7 +15,115 @@ env:
PRE_COMMIT_CACHE: ~/.cache/pre-commit PRE_COMMIT_CACHE: ~/.cache/pre-commit
SQLALCHEMY_WARN_20: 1 SQLALCHEMY_WARN_20: 1
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs: jobs:
changes:
name: Determine what has changed
outputs:
# In case of issues with the partial run, use the following line instead:
# test_full_suite: 'true'
test_full_suite: ${{ steps.info.outputs.test_full_suite }}
core: ${{ steps.core.outputs.changes }}
integrations: ${{ steps.integrations.outputs.changes }}
integrations_glob: ${{ steps.info.outputs.integrations_glob }}
tests: ${{ steps.info.outputs.tests }}
tests_glob: ${{ steps.info.outputs.tests_glob }}
test_groups: ${{ steps.info.outputs.test_groups }}
test_group_count: ${{ steps.info.outputs.test_group_count }}
runs-on: ubuntu-latest
steps:
- name: Check out code from GitHub
uses: actions/checkout@v2.4.0
- name: Filter for core changes
uses: dorny/paths-filter@v2.10.2
id: core
with:
filters: .core_files.yaml
- name: Create a list of integrations to filter for changes
run: |
integrations=$(ls -Ad ./homeassistant/components/[!_]* | xargs -n 1 basename)
touch .integration_paths.yaml
for integration in $integrations; do
echo "${integration}: [homeassistant/components/${integration}/*, tests/components/${integration}/*]" \
>> .integration_paths.yaml;
done
echo "Result:"
cat .integration_paths.yaml
- name: Filter for integration changes
uses: dorny/paths-filter@v2.10.2
id: integrations
with:
filters: .integration_paths.yaml
- name: Collect additional information
id: info
run: |
# Defaults
integrations_glob=""
test_full_suite="true"
test_groups="[1, 2, 3, 4, 5, 6]"
test_group_count=6
tests="[]"
tests_glob=""
if [[ "${{ steps.integrations.outputs.changes }}" != "[]" ]];
then
# Create a file glob for the integrations
integrations_glob=$(echo '${{ steps.integrations.outputs.changes }}' | jq -cSr '. | join(",")')
[[ "${integrations_glob}" == *","* ]] && integrations_glob="{${integrations_glob}}"
# Create list of testable integrations
possible_integrations=$(echo '${{ steps.integrations.outputs.changes }}' | jq -cSr '.[]')
tests=$(
for integration in ${possible_integrations};
do
if [[ -d "tests/components/${integration}" ]]; then
echo -n "\"${integration}\",";
fi;
done
)
[[ ! -z "${tests}" ]] && tests="${tests::-1}"
tests="[${tests}]"
test_groups="${tests}"
# Test group count should be 1, we don't split partial tests
test_group_count=1
# Create a file glob for the integrations tests
tests_glob=$(echo "${tests}" | jq -cSr '. | join(",")')
[[ "${tests_glob}" == *","* ]] && tests_glob="{${tests_glob}}"
test_full_suite="false"
fi
# We need to run the full suite on certain branches.
# Or, in case core files are touched, for the full suite as well.
if [[ "${{ github.ref }}" == "refs/heads/dev" ]] \
|| [[ "${{ github.ref }}" == "refs/heads/master" ]] \
|| [[ "${{ github.ref }}" == "refs/heads/rc" ]] \
|| [[ "${{ steps.core.outputs.any }}" == "true" ]];
then
test_groups="[1, 2, 3, 4, 5, 6]"
test_group_count=6
test_full_suite="true"
fi
# Output & sent to GitHub Actions
echo "test_full_suite: ${test_full_suite}"
echo "::set-output name=test_full_suite::${test_full_suite}"
echo "integrations_glob: ${integrations_glob}"
echo "::set-output name=integrations_glob::${integrations_glob}"
echo "test_group_count: ${test_group_count}"
echo "::set-output name=test_group_count::${test_group_count}"
echo "test_groups: ${test_groups}"
echo "::set-output name=test_groups::${test_groups}"
echo "tests: ${tests}"
echo "::set-output name=tests::${tests}"
echo "tests_glob: ${tests_glob}"
echo "::set-output name=tests_glob::${tests_glob}"
# Separate job to pre-populate the base dependency cache # Separate job to pre-populate the base dependency cache
# This prevent upcoming jobs to do the same individually # This prevent upcoming jobs to do the same individually
prepare-base: prepare-base:
@ -26,10 +134,10 @@ jobs:
pre-commit-key: ${{ steps.generate-pre-commit-key.outputs.key }} pre-commit-key: ${{ steps.generate-pre-commit-key.outputs.key }}
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v2.3.5 uses: actions/checkout@v2.4.0
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python id: python
uses: actions/setup-python@v2.2.2 uses: actions/setup-python@v2.3.1
with: with:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}
- name: Generate partial Python venv restore key - name: Generate partial Python venv restore key
@ -41,7 +149,7 @@ jobs:
hashFiles('homeassistant/package_constraints.txt') }}" hashFiles('homeassistant/package_constraints.txt') }}"
- name: Restore base Python virtual environment - name: Restore base Python virtual environment
id: cache-venv id: cache-venv
uses: actions/cache@v2.1.6 uses: actions/cache@v2.1.7
with: with:
path: venv path: venv
key: >- key: >-
@ -65,7 +173,7 @@ jobs:
hashFiles('.pre-commit-config.yaml') }}" hashFiles('.pre-commit-config.yaml') }}"
- name: Restore pre-commit environment from cache - name: Restore pre-commit environment from cache
id: cache-precommit id: cache-precommit
uses: actions/cache@v2.1.6 uses: actions/cache@v2.1.7
with: with:
path: ${{ env.PRE_COMMIT_CACHE }} path: ${{ env.PRE_COMMIT_CACHE }}
key: >- key: >-
@ -78,61 +186,23 @@ jobs:
. venv/bin/activate . venv/bin/activate
pre-commit install-hooks pre-commit install-hooks
lint-bandit:
name: Check bandit
runs-on: ubuntu-latest
needs: prepare-base
steps:
- name: Check out code from GitHub
uses: actions/checkout@v2.3.5
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v2.2.2
id: python
with:
python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore base Python virtual environment
id: cache-venv
uses: actions/cache@v2.1.6
with:
path: venv
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
needs.prepare-base.outputs.python-key }}
- name: Fail job if Python cache restore failed
if: steps.cache-venv.outputs.cache-hit != 'true'
run: |
echo "Failed to restore Python virtual environment from cache"
exit 1
- name: Restore pre-commit environment from cache
id: cache-precommit
uses: actions/cache@v2.1.6
with:
path: ${{ env.PRE_COMMIT_CACHE }}
key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }}
- name: Fail job if pre-commit cache restore failed
if: steps.cache-precommit.outputs.cache-hit != 'true'
run: |
echo "Failed to restore pre-commit environment from cache"
exit 1
- name: Run bandit
run: |
. venv/bin/activate
pre-commit run --hook-stage manual bandit --all-files --show-diff-on-failure
lint-black: lint-black:
name: Check black name: Check black
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: prepare-base needs:
- changes
- prepare-base
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v2.3.5 uses: actions/checkout@v2.4.0
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v2.2.2 uses: actions/setup-python@v2.3.1
id: python id: python
with: with:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore base Python virtual environment - name: Restore base Python virtual environment
id: cache-venv id: cache-venv
uses: actions/cache@v2.1.6 uses: actions/cache@v2.1.7
with: with:
path: venv path: venv
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
@ -144,7 +214,7 @@ jobs:
exit 1 exit 1
- name: Restore pre-commit environment from cache - name: Restore pre-commit environment from cache
id: cache-precommit id: cache-precommit
uses: actions/cache@v2.1.6 uses: actions/cache@v2.1.7
with: with:
path: ${{ env.PRE_COMMIT_CACHE }} path: ${{ env.PRE_COMMIT_CACHE }}
key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }} key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }}
@ -153,131 +223,35 @@ jobs:
run: | run: |
echo "Failed to restore pre-commit environment from cache" echo "Failed to restore pre-commit environment from cache"
exit 1 exit 1
- name: Run black - name: Run black (fully)
if: needs.changes.outputs.test_full_suite == 'true'
run: | run: |
. venv/bin/activate . venv/bin/activate
pre-commit run --hook-stage manual black --all-files --show-diff-on-failure pre-commit run --hook-stage manual black --all-files --show-diff-on-failure
- name: Run black (partially)
lint-codespell: if: needs.changes.outputs.test_full_suite == 'false'
name: Check codespell shell: bash
runs-on: ubuntu-latest
needs: prepare-base
steps:
- name: Check out code from GitHub
uses: actions/checkout@v2.3.5
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v2.2.2
id: python
with:
python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore base Python virtual environment
id: cache-venv
uses: actions/cache@v2.1.6
with:
path: venv
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
needs.prepare-base.outputs.python-key }}
- name: Fail job if Python cache restore failed
if: steps.cache-venv.outputs.cache-hit != 'true'
run: |
echo "Failed to restore Python virtual environment from cache"
exit 1
- name: Restore pre-commit environment from cache
id: cache-precommit
uses: actions/cache@v2.1.6
with:
path: ${{ env.PRE_COMMIT_CACHE }}
key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }}
- name: Fail job if pre-commit cache restore failed
if: steps.cache-precommit.outputs.cache-hit != 'true'
run: |
echo "Failed to restore pre-commit environment from cache"
exit 1
- name: Register codespell problem matcher
run: |
echo "::add-matcher::.github/workflows/matchers/codespell.json"
- name: Run codespell
run: | run: |
. venv/bin/activate . venv/bin/activate
pre-commit run --show-diff-on-failure --hook-stage manual codespell --all-files pre-commit run --hook-stage manual black --files {homeassistant,tests}/components/${{ needs.changes.outputs.integrations_glob }}/* --show-diff-on-failure
lint-dockerfile:
name: Check Dockerfile
runs-on: ubuntu-latest
needs: prepare-base
steps:
- name: Check out code from GitHub
uses: actions/checkout@v2.3.5
- name: Register hadolint problem matcher
run: |
echo "::add-matcher::.github/workflows/matchers/hadolint.json"
- name: Check Dockerfile
uses: docker://hadolint/hadolint:v1.18.2
with:
args: hadolint Dockerfile
- name: Check Dockerfile.dev
uses: docker://hadolint/hadolint:v1.18.2
with:
args: hadolint Dockerfile.dev
lint-executable-shebangs:
name: Check executables
runs-on: ubuntu-latest
needs: prepare-base
steps:
- name: Check out code from GitHub
uses: actions/checkout@v2.3.5
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v2.2.2
id: python
with:
python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore base Python virtual environment
id: cache-venv
uses: actions/cache@v2.1.6
with:
path: venv
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
needs.prepare-base.outputs.python-key }}
- name: Fail job if Python cache restore failed
if: steps.cache-venv.outputs.cache-hit != 'true'
run: |
echo "Failed to restore Python virtual environment from cache"
exit 1
- name: Restore pre-commit environment from cache
id: cache-precommit
uses: actions/cache@v2.1.6
with:
path: ${{ env.PRE_COMMIT_CACHE }}
key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }}
- name: Fail job if pre-commit cache restore failed
if: steps.cache-precommit.outputs.cache-hit != 'true'
run: |
echo "Failed to restore pre-commit environment from cache"
exit 1
- name: Register check executables problem matcher
run: |
echo "::add-matcher::.github/workflows/matchers/check-executables-have-shebangs.json"
- name: Run executables check
run: |
. venv/bin/activate
pre-commit run --hook-stage manual check-executables-have-shebangs --all-files
lint-flake8: lint-flake8:
name: Check flake8 name: Check flake8
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: prepare-base needs:
- changes
- prepare-base
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v2.3.5 uses: actions/checkout@v2.4.0
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v2.2.2 uses: actions/setup-python@v2.3.1
id: python id: python
with: with:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore base Python virtual environment - name: Restore base Python virtual environment
id: cache-venv id: cache-venv
uses: actions/cache@v2.1.6 uses: actions/cache@v2.1.7
with: with:
path: venv path: venv
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
@ -289,7 +263,7 @@ jobs:
exit 1 exit 1
- name: Restore pre-commit environment from cache - name: Restore pre-commit environment from cache
id: cache-precommit id: cache-precommit
uses: actions/cache@v2.1.6 uses: actions/cache@v2.1.7
with: with:
path: ${{ env.PRE_COMMIT_CACHE }} path: ${{ env.PRE_COMMIT_CACHE }}
key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }} key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }}
@ -301,10 +275,17 @@ jobs:
- name: Register flake8 problem matcher - name: Register flake8 problem matcher
run: | run: |
echo "::add-matcher::.github/workflows/matchers/flake8.json" echo "::add-matcher::.github/workflows/matchers/flake8.json"
- name: Run flake8 - name: Run flake8 (fully)
if: needs.changes.outputs.test_full_suite == 'true'
run: | run: |
. venv/bin/activate . venv/bin/activate
pre-commit run --hook-stage manual flake8 --all-files pre-commit run --hook-stage manual flake8 --all-files
- name: Run flake8 (partially)
if: needs.changes.outputs.test_full_suite == 'false'
shell: bash
run: |
. venv/bin/activate
pre-commit run --hook-stage manual flake8 --files {homeassistant,tests}/components/${{ needs.changes.outputs.integrations_glob }}/*
lint-isort: lint-isort:
name: Check isort name: Check isort
@ -312,15 +293,15 @@ jobs:
needs: prepare-base needs: prepare-base
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v2.3.5 uses: actions/checkout@v2.4.0
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v2.2.2 uses: actions/setup-python@v2.3.1
id: python id: python
with: with:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore base Python virtual environment - name: Restore base Python virtual environment
id: cache-venv id: cache-venv
uses: actions/cache@v2.1.6 uses: actions/cache@v2.1.7
with: with:
path: venv path: venv
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
@ -332,7 +313,7 @@ jobs:
exit 1 exit 1
- name: Restore pre-commit environment from cache - name: Restore pre-commit environment from cache
id: cache-precommit id: cache-precommit
uses: actions/cache@v2.1.6 uses: actions/cache@v2.1.7
with: with:
path: ${{ env.PRE_COMMIT_CACHE }} path: ${{ env.PRE_COMMIT_CACHE }}
key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }} key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }}
@ -346,21 +327,23 @@ jobs:
. venv/bin/activate . venv/bin/activate
pre-commit run --hook-stage manual isort --all-files --show-diff-on-failure pre-commit run --hook-stage manual isort --all-files --show-diff-on-failure
lint-json: lint-other:
name: Check JSON name: Check other linters
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: prepare-base needs:
- changes
- prepare-base
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v2.3.5 uses: actions/checkout@v2.4.0
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v2.2.2 uses: actions/setup-python@v2.3.1
id: python id: python
with: with:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore base Python virtual environment - name: Restore base Python virtual environment
id: cache-venv id: cache-venv
uses: actions/cache@v2.1.6 uses: actions/cache@v2.1.7
with: with:
path: venv path: venv
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
@ -372,7 +355,7 @@ jobs:
exit 1 exit 1
- name: Restore pre-commit environment from cache - name: Restore pre-commit environment from cache
id: cache-precommit id: cache-precommit
uses: actions/cache@v2.1.6 uses: actions/cache@v2.1.7
with: with:
path: ${{ env.PRE_COMMIT_CACHE }} path: ${{ env.PRE_COMMIT_CACHE }}
key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }} key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }}
@ -381,6 +364,27 @@ jobs:
run: | run: |
echo "Failed to restore pre-commit environment from cache" echo "Failed to restore pre-commit environment from cache"
exit 1 exit 1
- name: Run pyupgrade (fully)
if: needs.changes.outputs.test_full_suite == 'true'
run: |
. venv/bin/activate
pre-commit run --hook-stage manual pyupgrade --all-files --show-diff-on-failure
- name: Run pyupgrade (partially)
if: needs.changes.outputs.test_full_suite == 'false'
shell: bash
run: |
. venv/bin/activate
pre-commit run --hook-stage manual pyupgrade --files {homeassistant,tests}/components/${{ needs.changes.outputs.integrations_glob }}/* --show-diff-on-failure
- name: Register yamllint problem matcher
run: |
echo "::add-matcher::.github/workflows/matchers/yamllint.json"
- name: Run yamllint
run: |
. venv/bin/activate
pre-commit run --hook-stage manual yamllint --all-files --show-diff-on-failure
- name: Register check-json problem matcher - name: Register check-json problem matcher
run: | run: |
echo "::add-matcher::.github/workflows/matchers/check-json.json" echo "::add-matcher::.github/workflows/matchers/check-json.json"
@ -389,99 +393,45 @@ jobs:
. venv/bin/activate . venv/bin/activate
pre-commit run --hook-stage manual check-json --all-files pre-commit run --hook-stage manual check-json --all-files
lint-pyupgrade: - name: Register check executables problem matcher
name: Check pyupgrade
runs-on: ubuntu-latest
needs: prepare-base
steps:
- name: Check out code from GitHub
uses: actions/checkout@v2.3.5
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v2.2.2
id: python
with:
python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore base Python virtual environment
id: cache-venv
uses: actions/cache@v2.1.6
with:
path: venv
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
needs.prepare-base.outputs.python-key }}
- name: Fail job if Python cache restore failed
if: steps.cache-venv.outputs.cache-hit != 'true'
run: | run: |
echo "Failed to restore Python virtual environment from cache" echo "::add-matcher::.github/workflows/matchers/check-executables-have-shebangs.json"
exit 1 - name: Run executables check
- name: Restore pre-commit environment from cache
id: cache-precommit
uses: actions/cache@v2.1.6
with:
path: ${{ env.PRE_COMMIT_CACHE }}
key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }}
- name: Fail job if pre-commit cache restore failed
if: steps.cache-precommit.outputs.cache-hit != 'true'
run: |
echo "Failed to restore pre-commit environment from cache"
exit 1
- name: Run pyupgrade
run: | run: |
. venv/bin/activate . venv/bin/activate
pre-commit run --hook-stage manual pyupgrade --all-files --show-diff-on-failure pre-commit run --hook-stage manual check-executables-have-shebangs --all-files
# Disabled until we have the existing issues fixed - name: Register codespell problem matcher
# lint-shellcheck:
# name: Check ShellCheck
# runs-on: ubuntu-latest
# needs: prepare-base
# steps:
# - name: Check out code from GitHub
# uses: actions/checkout@v2.3.5
# - name: Run ShellCheck
# uses: ludeeus/action-shellcheck@0.3.0
lint-yaml:
name: Check YAML
runs-on: ubuntu-latest
needs: prepare-base
steps:
- name: Check out code from GitHub
uses: actions/checkout@v2.3.5
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v2.2.2
id: python
with:
python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore base Python virtual environment
id: cache-venv
uses: actions/cache@v2.1.6
with:
path: venv
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
needs.prepare-base.outputs.python-key }}
- name: Fail job if Python cache restore failed
if: steps.cache-venv.outputs.cache-hit != 'true'
run: | run: |
echo "Failed to restore Python virtual environment from cache" echo "::add-matcher::.github/workflows/matchers/codespell.json"
exit 1 - name: Run codespell
- name: Restore pre-commit environment from cache
id: cache-precommit
uses: actions/cache@v2.1.6
with:
path: ${{ env.PRE_COMMIT_CACHE }}
key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }}
- name: Fail job if pre-commit cache restore failed
if: steps.cache-precommit.outputs.cache-hit != 'true'
run: |
echo "Failed to restore pre-commit environment from cache"
exit 1
- name: Register yamllint problem matcher
run: |
echo "::add-matcher::.github/workflows/matchers/yamllint.json"
- name: Run yamllint
run: | run: |
. venv/bin/activate . venv/bin/activate
pre-commit run --hook-stage manual yamllint --all-files --show-diff-on-failure pre-commit run --show-diff-on-failure --hook-stage manual codespell --all-files
- name: Register hadolint problem matcher
run: |
echo "::add-matcher::.github/workflows/matchers/hadolint.json"
- name: Check Dockerfile
uses: docker://hadolint/hadolint:v1.18.2
with:
args: hadolint Dockerfile
- name: Check Dockerfile.dev
uses: docker://hadolint/hadolint:v1.18.2
with:
args: hadolint Dockerfile.dev
- name: Run bandit (fully)
if: needs.changes.outputs.test_full_suite == 'true'
run: |
. venv/bin/activate
pre-commit run --hook-stage manual bandit --all-files --show-diff-on-failure
- name: Run bandit (partially)
if: needs.changes.outputs.test_full_suite == 'false'
shell: bash
run: |
. venv/bin/activate
pre-commit run --hook-stage manual bandit --files {homeassistant,tests}/components/${{ needs.changes.outputs.integrations_glob }}/* --show-diff-on-failure
hassfest: hassfest:
name: Check hassfest name: Check hassfest
@ -493,10 +443,10 @@ jobs:
container: homeassistant/ci-azure:${{ matrix.python-version }} container: homeassistant/ci-azure:${{ matrix.python-version }}
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v2.3.5 uses: actions/checkout@v2.4.0
- name: Restore full Python ${{ matrix.python-version }} virtual environment - name: Restore full Python ${{ matrix.python-version }} virtual environment
id: cache-venv id: cache-venv
uses: actions/cache@v2.1.6 uses: actions/cache@v2.1.7
with: with:
path: venv path: venv
key: ${{ runner.os }}-${{ matrix.python-version }}-${{ key: ${{ runner.os }}-${{ matrix.python-version }}-${{
@ -517,15 +467,15 @@ jobs:
needs: prepare-base needs: prepare-base
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v2.3.5 uses: actions/checkout@v2.4.0
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v2.2.2 uses: actions/setup-python@v2.3.1
id: python id: python
with: with:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore base Python virtual environment - name: Restore base Python virtual environment
id: cache-venv id: cache-venv
uses: actions/cache@v2.1.6 uses: actions/cache@v2.1.7
with: with:
path: venv path: venv
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
@ -551,7 +501,7 @@ jobs:
container: homeassistant/ci-azure:${{ matrix.python-version }} container: homeassistant/ci-azure:${{ matrix.python-version }}
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v2.3.5 uses: actions/checkout@v2.4.0
- name: Generate partial Python venv restore key - name: Generate partial Python venv restore key
id: generate-python-key id: generate-python-key
run: >- run: >-
@ -561,7 +511,7 @@ jobs:
hashFiles('homeassistant/package_constraints.txt') }}" hashFiles('homeassistant/package_constraints.txt') }}"
- name: Restore full Python ${{ matrix.python-version }} virtual environment - name: Restore full Python ${{ matrix.python-version }} virtual environment
id: cache-venv id: cache-venv
uses: actions/cache@v2.1.6 uses: actions/cache@v2.1.7
with: with:
path: venv path: venv
key: >- key: >-
@ -588,17 +538,19 @@ jobs:
pylint: pylint:
name: Check pylint name: Check pylint
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: prepare-tests needs:
- changes
- prepare-tests
strategy: strategy:
matrix: matrix:
python-version: [3.8] python-version: [3.8]
container: homeassistant/ci-azure:${{ matrix.python-version }} container: homeassistant/ci-azure:${{ matrix.python-version }}
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v2.3.5 uses: actions/checkout@v2.4.0
- name: Restore full Python ${{ matrix.python-version }} virtual environment - name: Restore full Python ${{ matrix.python-version }} virtual environment
id: cache-venv id: cache-venv
uses: actions/cache@v2.1.6 uses: actions/cache@v2.1.7
with: with:
path: venv path: venv
key: ${{ runner.os }}-${{ matrix.python-version }}-${{ key: ${{ runner.os }}-${{ matrix.python-version }}-${{
@ -611,25 +563,34 @@ jobs:
- name: Register pylint problem matcher - name: Register pylint problem matcher
run: | run: |
echo "::add-matcher::.github/workflows/matchers/pylint.json" echo "::add-matcher::.github/workflows/matchers/pylint.json"
- name: Run pylint - name: Run pylint (fully)
if: needs.changes.outputs.test_full_suite == 'true'
run: | run: |
. venv/bin/activate . venv/bin/activate
pylint homeassistant pylint homeassistant
- name: Run pylint (partially)
if: needs.changes.outputs.test_full_suite == 'false'
shell: bash
run: |
. venv/bin/activate
pylint homeassistant/components/${{ needs.changes.outputs.integrations_glob }}
mypy: mypy:
name: Check mypy name: Check mypy
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: prepare-tests needs:
- changes
- prepare-tests
strategy: strategy:
matrix: matrix:
python-version: [3.8] python-version: [3.8]
container: homeassistant/ci-azure:${{ matrix.python-version }} container: homeassistant/ci-azure:${{ matrix.python-version }}
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v2.3.5 uses: actions/checkout@v2.4.0
- name: Restore full Python ${{ matrix.python-version }} virtual environment - name: Restore full Python ${{ matrix.python-version }} virtual environment
id: cache-venv id: cache-venv
uses: actions/cache@v2.1.6 uses: actions/cache@v2.1.7
with: with:
path: venv path: venv
key: ${{ runner.os }}-${{ matrix.python-version }}-${{ key: ${{ runner.os }}-${{ matrix.python-version }}-${{
@ -642,28 +603,44 @@ jobs:
- name: Register mypy problem matcher - name: Register mypy problem matcher
run: | run: |
echo "::add-matcher::.github/workflows/matchers/mypy.json" echo "::add-matcher::.github/workflows/matchers/mypy.json"
- name: Run mypy - name: Run mypy (fully)
if: needs.changes.outputs.test_full_suite == 'true'
run: | run: |
. venv/bin/activate . venv/bin/activate
mypy homeassistant mypy homeassistant
- name: Run mypy (partially)
if: needs.changes.outputs.test_full_suite == 'false'
shell: bash
run: |
. venv/bin/activate
mypy homeassistant/components/${{ needs.changes.outputs.integrations_glob }}
pytest: pytest:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: prepare-tests if: needs.changes.outputs.test_full_suite == 'true' || needs.changes.outputs.tests_glob
needs:
- changes
- gen-requirements-all
- hassfest
- lint-black
- lint-other
- lint-isort
- mypy
- prepare-tests
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
group: [1, 2, 3, 4] group: ${{ fromJson(needs.changes.outputs.test_groups) }}
python-version: [3.8, 3.9] python-version: [3.8, 3.9]
name: >- name: >-
Run tests Python ${{ matrix.python-version }} (group ${{ matrix.group }}) Run tests Python ${{ matrix.python-version }} (${{ matrix.group }})
container: homeassistant/ci-azure:${{ matrix.python-version }} container: homeassistant/ci-azure:${{ matrix.python-version }}
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v2.3.5 uses: actions/checkout@v2.4.0
- name: Restore full Python ${{ matrix.python-version }} virtual environment - name: Restore full Python ${{ matrix.python-version }} virtual environment
id: cache-venv id: cache-venv
uses: actions/cache@v2.1.6 uses: actions/cache@v2.1.7
with: with:
path: venv path: venv
key: ${{ runner.os }}-${{ matrix.python-version }}-${{ key: ${{ runner.os }}-${{ matrix.python-version }}-${{
@ -683,61 +660,82 @@ jobs:
# However this plugin is fairly new and doesn't run correctly # However this plugin is fairly new and doesn't run correctly
# on a non-GitHub environment. # on a non-GitHub environment.
pip install pytest-github-actions-annotate-failures==0.1.3 pip install pytest-github-actions-annotate-failures==0.1.3
- name: Run pytest - name: Register pytest slow test problem matcher
run: |
echo "::add-matcher::.github/workflows/matchers/pytest-slow.json"
- name: Run pytest (fully)
if: needs.changes.outputs.test_full_suite == 'true'
run: | run: |
. venv/bin/activate . venv/bin/activate
python3 -X dev -bb -m pytest \ python3 -X dev -m pytest \
-qq \ -qq \
--timeout=9 \ --timeout=9 \
--durations=10 \ --durations=10 \
-n auto \ -n auto \
--dist=loadfile \ --dist=loadfile \
--test-group-count 4 \ --test-group-count ${{ needs.changes.outputs.test_group_count }} \
--test-group=${{ matrix.group }} \ --test-group=${{ matrix.group }} \
--cov homeassistant \ --cov homeassistant \
--cov-report= \ --cov-report=xml \
-o console_output_style=count \ -o console_output_style=count \
-p no:sugar \ -p no:sugar \
tests tests
- name: Run pytest (partially)
if: needs.changes.outputs.test_full_suite == 'false' && matrix.python-version != '3.8'
run: |
. venv/bin/activate
python3 -X dev -m pytest \
-qq \
--timeout=9 \
--durations=10 \
-n auto \
--cov homeassistant.components.${{ matrix.group }} \
--cov-report=xml \
--cov-report=term-missing \
-o console_output_style=count \
--durations=0 \
--durations-min=1 \
-p no:sugar \
tests/components/${{ matrix.group }}
- name: Run pytest (partially); no coverage
if: needs.changes.outputs.test_full_suite == 'false' && matrix.python-version == '3.8'
run: |
. venv/bin/activate
python3 -X dev -m pytest \
-qq \
--timeout=9 \
--durations=10 \
-n auto \
-o console_output_style=count \
--durations=0 \
--durations-min=1 \
-p no:sugar \
tests/components/${{ matrix.group }}
- name: Upload coverage artifact - name: Upload coverage artifact
uses: actions/upload-artifact@v2.2.4 uses: actions/upload-artifact@v2.2.4
with: with:
name: coverage-${{ matrix.python-version }}-group${{ matrix.group }} name: coverage-${{ matrix.python-version }}-${{ matrix.group }}
path: .coverage path: coverage.xml
- name: Check dirty - name: Check dirty
run: | run: |
./script/check_dirty ./script/check_dirty
coverage: coverage:
name: Process test coverage name: Upload test coverage to Codecov
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: ["prepare-tests", "pytest"] needs:
strategy: - changes
matrix: - pytest
python-version: [3.8]
container: homeassistant/ci-azure:${{ matrix.python-version }}
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v2.3.5 uses: actions/checkout@v2.4.0
- name: Restore full Python ${{ matrix.python-version }} virtual environment
id: cache-venv
uses: actions/cache@v2.1.6
with:
path: venv
key: ${{ runner.os }}-${{ matrix.python-version }}-${{
needs.prepare-tests.outputs.python-key }}
- name: Fail job if Python cache restore failed
if: steps.cache-venv.outputs.cache-hit != 'true'
run: |
echo "Failed to restore Python virtual environment from cache"
exit 1
- name: Download all coverage artifacts - name: Download all coverage artifacts
uses: actions/download-artifact@v2 uses: actions/download-artifact@v2
- name: Combine coverage results - name: Upload coverage to Codecov (full coverage)
run: | if: needs.changes.outputs.test_full_suite == 'true'
. venv/bin/activate uses: codecov/codecov-action@v2.1.0
coverage combine coverage*/.coverage* with:
coverage report --fail-under=94 flags: full-suite
coverage xml - name: Upload coverage to Codecov (partial coverage)
- name: Upload coverage to Codecov if: needs.changes.outputs.test_full_suite == 'false'
uses: codecov/codecov-action@v2.1.0 uses: codecov/codecov-action@v2.1.0

View File

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

View File

@ -20,10 +20,10 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v2.3.5 uses: actions/checkout@v2.4.0
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v2.2.2 uses: actions/setup-python@v2.3.1
with: with:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}
@ -39,10 +39,10 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v2.3.5 uses: actions/checkout@v2.4.0
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v2.2.2 uses: actions/setup-python@v2.3.1
with: with:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}

View File

@ -21,7 +21,7 @@ jobs:
architectures: ${{ steps.info.outputs.architectures }} architectures: ${{ steps.info.outputs.architectures }}
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v2.3.5 uses: actions/checkout@v2.4.0
- name: Get information - name: Get information
id: info id: info
@ -68,7 +68,7 @@ jobs:
- "3.9-alpine3.14" - "3.9-alpine3.14"
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v2.3.5 uses: actions/checkout@v2.4.0
- name: Download env_file - name: Download env_file
uses: actions/download-artifact@v2 uses: actions/download-artifact@v2
@ -108,7 +108,7 @@ jobs:
- "3.9-alpine3.14" - "3.9-alpine3.14"
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v2.3.5 uses: actions/checkout@v2.4.0
- name: Download env_file - name: Download env_file
uses: actions/download-artifact@v2 uses: actions/download-artifact@v2

View File

@ -1,11 +1,11 @@
repos: repos:
- repo: https://github.com/asottile/pyupgrade - repo: https://github.com/asottile/pyupgrade
rev: v2.27.0 rev: v2.29.0
hooks: hooks:
- id: pyupgrade - id: pyupgrade
args: [--py38-plus] args: [--py38-plus]
- repo: https://github.com/psf/black - repo: https://github.com/psf/black
rev: 21.9b0 rev: 21.11b1
hooks: hooks:
- id: black - id: black
args: args:
@ -17,7 +17,7 @@ repos:
hooks: hooks:
- id: codespell - id: codespell
args: args:
- --ignore-words-list=hass,alot,datas,dof,dur,ether,farenheit,hist,iff,ines,ist,lightsensor,mut,nd,pres,referer,ser,serie,te,technik,ue,uint,visability,wan,wanna,withing,iam,incomfort,ba - --ignore-words-list=hass,alot,datas,dof,dur,ether,farenheit,hist,iff,ines,ist,lightsensor,mut,nd,pres,referer,ser,serie,te,technik,ue,uint,visability,wan,wanna,withing,iam,incomfort,ba,haa
- --skip="./.*,*.csv,*.json" - --skip="./.*,*.csv,*.json"
- --quiet-level=2 - --quiet-level=2
exclude_types: [csv, json] exclude_types: [csv, json]
@ -45,7 +45,7 @@ repos:
- --configfile=tests/bandit.yaml - --configfile=tests/bandit.yaml
files: ^(homeassistant|script|tests)/.+\.py$ files: ^(homeassistant|script|tests)/.+\.py$
- repo: https://github.com/PyCQA/isort - repo: https://github.com/PyCQA/isort
rev: 5.9.3 rev: 5.10.0
hooks: hooks:
- id: isort - id: isort
- repo: https://github.com/pre-commit/pre-commit-hooks - repo: https://github.com/pre-commit/pre-commit-hooks
@ -61,7 +61,7 @@ repos:
- --branch=master - --branch=master
- --branch=rc - --branch=rc
- repo: https://github.com/adrienverge/yamllint.git - repo: https://github.com/adrienverge/yamllint.git
rev: v1.26.1 rev: v1.26.3
hooks: hooks:
- id: yamllint - id: yamllint
- repo: https://github.com/pre-commit/mirrors-prettier - repo: https://github.com/pre-commit/mirrors-prettier

View File

@ -24,6 +24,7 @@ homeassistant.components.bmw_connected_drive.*
homeassistant.components.bond.* homeassistant.components.bond.*
homeassistant.components.braviatv.* homeassistant.components.braviatv.*
homeassistant.components.brother.* homeassistant.components.brother.*
homeassistant.components.button.*
homeassistant.components.calendar.* homeassistant.components.calendar.*
homeassistant.components.camera.* homeassistant.components.camera.*
homeassistant.components.canary.* homeassistant.components.canary.*
@ -32,6 +33,7 @@ homeassistant.components.crownstone.*
homeassistant.components.device_automation.* homeassistant.components.device_automation.*
homeassistant.components.device_tracker.* homeassistant.components.device_tracker.*
homeassistant.components.devolo_home_control.* homeassistant.components.devolo_home_control.*
homeassistant.components.devolo_home_network.*
homeassistant.components.dlna_dmr.* homeassistant.components.dlna_dmr.*
homeassistant.components.dnsip.* homeassistant.components.dnsip.*
homeassistant.components.dsmr.* homeassistant.components.dsmr.*
@ -40,17 +42,20 @@ homeassistant.components.efergy.*
homeassistant.components.elgato.* homeassistant.components.elgato.*
homeassistant.components.esphome.* homeassistant.components.esphome.*
homeassistant.components.energy.* homeassistant.components.energy.*
homeassistant.components.evil_genius_labs.*
homeassistant.components.fastdotcom.* homeassistant.components.fastdotcom.*
homeassistant.components.fitbit.* homeassistant.components.fitbit.*
homeassistant.components.flunearyou.* homeassistant.components.flunearyou.*
homeassistant.components.flux_led.* homeassistant.components.flux_led.*
homeassistant.components.forecast_solar.* homeassistant.components.forecast_solar.*
homeassistant.components.fritzbox.* homeassistant.components.fritzbox.*
homeassistant.components.fronius.*
homeassistant.components.frontend.* homeassistant.components.frontend.*
homeassistant.components.fritz.* homeassistant.components.fritz.*
homeassistant.components.geo_location.* homeassistant.components.geo_location.*
homeassistant.components.gios.* homeassistant.components.gios.*
homeassistant.components.goalzero.* homeassistant.components.goalzero.*
homeassistant.components.greeneye_monitor.*
homeassistant.components.group.* homeassistant.components.group.*
homeassistant.components.guardian.* homeassistant.components.guardian.*
homeassistant.components.history.* homeassistant.components.history.*
@ -62,6 +67,7 @@ homeassistant.components.image_processing.*
homeassistant.components.input_select.* homeassistant.components.input_select.*
homeassistant.components.integration.* homeassistant.components.integration.*
homeassistant.components.iqvia.* homeassistant.components.iqvia.*
homeassistant.components.jellyfin.*
homeassistant.components.jewish_calendar.* homeassistant.components.jewish_calendar.*
homeassistant.components.knx.* homeassistant.components.knx.*
homeassistant.components.kraken.* homeassistant.components.kraken.*
@ -93,12 +99,14 @@ homeassistant.components.persistent_notification.*
homeassistant.components.pi_hole.* homeassistant.components.pi_hole.*
homeassistant.components.proximity.* homeassistant.components.proximity.*
homeassistant.components.rainmachine.* homeassistant.components.rainmachine.*
homeassistant.components.rdw.*
homeassistant.components.recollect_waste.* homeassistant.components.recollect_waste.*
homeassistant.components.recorder.purge homeassistant.components.recorder.purge
homeassistant.components.recorder.repack homeassistant.components.recorder.repack
homeassistant.components.recorder.statistics homeassistant.components.recorder.statistics
homeassistant.components.remote.* homeassistant.components.remote.*
homeassistant.components.renault.* homeassistant.components.renault.*
homeassistant.components.ridwell.*
homeassistant.components.rituals_perfume_genie.* homeassistant.components.rituals_perfume_genie.*
homeassistant.components.rpi_power.* homeassistant.components.rpi_power.*
homeassistant.components.samsungtv.* homeassistant.components.samsungtv.*
@ -119,18 +127,24 @@ homeassistant.components.switcher_kis.*
homeassistant.components.synology_dsm.* homeassistant.components.synology_dsm.*
homeassistant.components.systemmonitor.* homeassistant.components.systemmonitor.*
homeassistant.components.tag.* homeassistant.components.tag.*
homeassistant.components.tailscale.*
homeassistant.components.tautulli.* homeassistant.components.tautulli.*
homeassistant.components.tcp.* homeassistant.components.tcp.*
homeassistant.components.tile.* homeassistant.components.tile.*
homeassistant.components.tplink.* homeassistant.components.tplink.*
homeassistant.components.tolo.*
homeassistant.components.tractive.* homeassistant.components.tractive.*
homeassistant.components.tradfri.* homeassistant.components.tradfri.*
homeassistant.components.tts.* homeassistant.components.tts.*
homeassistant.components.twentemilieu.*
homeassistant.components.upcloud.* homeassistant.components.upcloud.*
homeassistant.components.uptime.* homeassistant.components.uptime.*
homeassistant.components.uptimerobot.* homeassistant.components.uptimerobot.*
homeassistant.components.vacuum.* homeassistant.components.vacuum.*
homeassistant.components.vallox.* homeassistant.components.vallox.*
homeassistant.components.velbus.*
homeassistant.components.vlc_telnet.*
homeassistant.components.wallbox.*
homeassistant.components.water_heater.* homeassistant.components.water_heater.*
homeassistant.components.watttime.* homeassistant.components.watttime.*
homeassistant.components.weather.* homeassistant.components.weather.*

2
.vscode/tasks.json vendored
View File

@ -64,7 +64,7 @@
"label": "Code Coverage", "label": "Code Coverage",
"detail": "Generate code coverage report for a given integration.", "detail": "Generate code coverage report for a given integration.",
"type": "shell", "type": "shell",
"command": "pytest ./tests/components/${input:integrationName}/ --cov=homeassistant.components.${input:integrationName} --cov-report term-missing", "command": "pytest ./tests/components/${input:integrationName}/ --cov=homeassistant.components.${input:integrationName} --cov-report term-missing --durations-min=1 --durations=0",
"group": { "group": {
"kind": "test", "kind": "test",
"isDefault": true "isDefault": true

View File

@ -67,12 +67,14 @@ homeassistant/components/axis/* @Kane610
homeassistant/components/azure_devops/* @timmo001 homeassistant/components/azure_devops/* @timmo001
homeassistant/components/azure_event_hub/* @eavanvalkenburg homeassistant/components/azure_event_hub/* @eavanvalkenburg
homeassistant/components/azure_service_bus/* @hfurubotten homeassistant/components/azure_service_bus/* @hfurubotten
homeassistant/components/balboa/* @garbled1
homeassistant/components/beewi_smartclim/* @alemuro homeassistant/components/beewi_smartclim/* @alemuro
homeassistant/components/bitcoin/* @fabaff homeassistant/components/bitcoin/* @fabaff
homeassistant/components/bizkaibus/* @UgaitzEtxebarria homeassistant/components/bizkaibus/* @UgaitzEtxebarria
homeassistant/components/blebox/* @bbx-a @bbx-jp homeassistant/components/blebox/* @bbx-a @bbx-jp
homeassistant/components/blink/* @fronzbot homeassistant/components/blink/* @fronzbot
homeassistant/components/blueprint/* @home-assistant/core homeassistant/components/blueprint/* @home-assistant/core
homeassistant/components/bluesound/* @thrawnarn
homeassistant/components/bmp280/* @belidzs homeassistant/components/bmp280/* @belidzs
homeassistant/components/bmw_connected_drive/* @gerard33 @rikroe homeassistant/components/bmw_connected_drive/* @gerard33 @rikroe
homeassistant/components/bond/* @bdraco @prystupa @joshs85 homeassistant/components/bond/* @bdraco @prystupa @joshs85
@ -84,6 +86,7 @@ homeassistant/components/brunt/* @eavanvalkenburg
homeassistant/components/bsblan/* @liudger homeassistant/components/bsblan/* @liudger
homeassistant/components/bt_smarthub/* @jxwolstenholme homeassistant/components/bt_smarthub/* @jxwolstenholme
homeassistant/components/buienradar/* @mjj4791 @ties @Robbie1221 homeassistant/components/buienradar/* @mjj4791 @ties @Robbie1221
homeassistant/components/button/* @home-assistant/core
homeassistant/components/cast/* @emontnemery homeassistant/components/cast/* @emontnemery
homeassistant/components/cert_expiry/* @Cereal2nd @jjlawren homeassistant/components/cert_expiry/* @Cereal2nd @jjlawren
homeassistant/components/circuit/* @braam homeassistant/components/circuit/* @braam
@ -118,6 +121,7 @@ homeassistant/components/denonavr/* @ol-iver @starkillerOG
homeassistant/components/derivative/* @afaucogney homeassistant/components/derivative/* @afaucogney
homeassistant/components/device_automation/* @home-assistant/core homeassistant/components/device_automation/* @home-assistant/core
homeassistant/components/devolo_home_control/* @2Fake @Shutgun homeassistant/components/devolo_home_control/* @2Fake @Shutgun
homeassistant/components/devolo_home_network/* @2Fake @Shutgun
homeassistant/components/dexcom/* @gagebenne homeassistant/components/dexcom/* @gagebenne
homeassistant/components/dhcp/* @bdraco homeassistant/components/dhcp/* @bdraco
homeassistant/components/dht/* @thegardenmonkey homeassistant/components/dht/* @thegardenmonkey
@ -157,6 +161,7 @@ homeassistant/components/epson/* @pszafer
homeassistant/components/epsonworkforce/* @ThaStealth homeassistant/components/epsonworkforce/* @ThaStealth
homeassistant/components/eq3btsmart/* @rytilahti homeassistant/components/eq3btsmart/* @rytilahti
homeassistant/components/esphome/* @OttoWinter @jesserockz homeassistant/components/esphome/* @OttoWinter @jesserockz
homeassistant/components/evil_genius_labs/* @balloob
homeassistant/components/evohome/* @zxdavb homeassistant/components/evohome/* @zxdavb
homeassistant/components/ezviz/* @RenierM26 @baqs homeassistant/components/ezviz/* @RenierM26 @baqs
homeassistant/components/faa_delays/* @ntilley905 homeassistant/components/faa_delays/* @ntilley905
@ -182,7 +187,7 @@ homeassistant/components/freebox/* @hacf-fr @Quentame
homeassistant/components/freedompro/* @stefano055415 homeassistant/components/freedompro/* @stefano055415
homeassistant/components/fritz/* @mammuth @AaronDavidSchneider @chemelli74 homeassistant/components/fritz/* @mammuth @AaronDavidSchneider @chemelli74
homeassistant/components/fritzbox/* @mib1185 @flabbamann homeassistant/components/fritzbox/* @mib1185 @flabbamann
homeassistant/components/fronius/* @nielstron homeassistant/components/fronius/* @nielstron @farmio
homeassistant/components/frontend/* @home-assistant/frontend homeassistant/components/frontend/* @home-assistant/frontend
homeassistant/components/garages_amsterdam/* @klaasnicolaas homeassistant/components/garages_amsterdam/* @klaasnicolaas
homeassistant/components/gdacs/* @exxamalte homeassistant/components/gdacs/* @exxamalte
@ -227,7 +232,7 @@ homeassistant/components/homematic/* @pvizeli @danielperna84
homeassistant/components/honeywell/* @rdfurman homeassistant/components/honeywell/* @rdfurman
homeassistant/components/http/* @home-assistant/core homeassistant/components/http/* @home-assistant/core
homeassistant/components/huawei_lte/* @scop @fphammerle homeassistant/components/huawei_lte/* @scop @fphammerle
homeassistant/components/hue/* @balloob @frenck homeassistant/components/hue/* @balloob @marcelveldt
homeassistant/components/huisbaasje/* @dennisschroer homeassistant/components/huisbaasje/* @dennisschroer
homeassistant/components/humidifier/* @home-assistant/core @Shulyaka homeassistant/components/humidifier/* @home-assistant/core @Shulyaka
homeassistant/components/hunterdouglas_powerview/* @bdraco homeassistant/components/hunterdouglas_powerview/* @bdraco
@ -261,6 +266,7 @@ homeassistant/components/irish_rail_transport/* @ttroy50
homeassistant/components/islamic_prayer_times/* @engrbm87 homeassistant/components/islamic_prayer_times/* @engrbm87
homeassistant/components/isy994/* @bdraco @shbatm homeassistant/components/isy994/* @bdraco @shbatm
homeassistant/components/izone/* @Swamp-Ig homeassistant/components/izone/* @Swamp-Ig
homeassistant/components/jellyfin/* @j-stienstra
homeassistant/components/jewish_calendar/* @tsvi homeassistant/components/jewish_calendar/* @tsvi
homeassistant/components/juicenet/* @jesserockz homeassistant/components/juicenet/* @jesserockz
homeassistant/components/kaiterra/* @Michsior14 homeassistant/components/kaiterra/* @Michsior14
@ -287,7 +293,6 @@ homeassistant/components/local_ip/* @issacg
homeassistant/components/logger/* @home-assistant/core homeassistant/components/logger/* @home-assistant/core
homeassistant/components/logi_circle/* @evanjd homeassistant/components/logi_circle/* @evanjd
homeassistant/components/lookin/* @ANMalko homeassistant/components/lookin/* @ANMalko
homeassistant/components/loopenergy/* @pavoni
homeassistant/components/lovelace/* @home-assistant/frontend homeassistant/components/lovelace/* @home-assistant/frontend
homeassistant/components/luci/* @mzdrale homeassistant/components/luci/* @mzdrale
homeassistant/components/luftdaten/* @fabaff homeassistant/components/luftdaten/* @fabaff
@ -422,6 +427,7 @@ homeassistant/components/raincloud/* @vanstinator
homeassistant/components/rainforest_eagle/* @gtdiehl @jcalbert homeassistant/components/rainforest_eagle/* @gtdiehl @jcalbert
homeassistant/components/rainmachine/* @bachya homeassistant/components/rainmachine/* @bachya
homeassistant/components/random/* @fabaff homeassistant/components/random/* @fabaff
homeassistant/components/rdw/* @frenck
homeassistant/components/recollect_waste/* @bachya homeassistant/components/recollect_waste/* @bachya
homeassistant/components/recorder/* @home-assistant/core homeassistant/components/recorder/* @home-assistant/core
homeassistant/components/rejseplanen/* @DarkFox homeassistant/components/rejseplanen/* @DarkFox
@ -429,6 +435,7 @@ homeassistant/components/renault/* @epenet
homeassistant/components/repetier/* @MTrab homeassistant/components/repetier/* @MTrab
homeassistant/components/rflink/* @javicalle homeassistant/components/rflink/* @javicalle
homeassistant/components/rfxtrx/* @danielhiversen @elupus @RobBie1221 homeassistant/components/rfxtrx/* @danielhiversen @elupus @RobBie1221
homeassistant/components/ridwell/* @bachya
homeassistant/components/ring/* @balloob homeassistant/components/ring/* @balloob
homeassistant/components/risco/* @OnFreund homeassistant/components/risco/* @OnFreund
homeassistant/components/rituals_perfume_genie/* @milanmeu homeassistant/components/rituals_perfume_genie/* @milanmeu
@ -520,12 +527,14 @@ homeassistant/components/system_bridge/* @timmo001
homeassistant/components/tado/* @michaelarnauts @noltari homeassistant/components/tado/* @michaelarnauts @noltari
homeassistant/components/tag/* @balloob @dmulcahey homeassistant/components/tag/* @balloob @dmulcahey
homeassistant/components/tahoma/* @philklei homeassistant/components/tahoma/* @philklei
homeassistant/components/tailscale/* @frenck
homeassistant/components/tankerkoenig/* @guillempages homeassistant/components/tankerkoenig/* @guillempages
homeassistant/components/tapsaff/* @bazwilliams homeassistant/components/tapsaff/* @bazwilliams
homeassistant/components/tasmota/* @emontnemery homeassistant/components/tasmota/* @emontnemery
homeassistant/components/tautulli/* @ludeeus homeassistant/components/tautulli/* @ludeeus
homeassistant/components/tellduslive/* @fredrike homeassistant/components/tellduslive/* @fredrike
homeassistant/components/template/* @PhracturedBlue @tetienne @home-assistant/core homeassistant/components/template/* @PhracturedBlue @tetienne @home-assistant/core
homeassistant/components/tesla_wall_connector/* @einarhauks
homeassistant/components/tfiac/* @fredrike @mellado homeassistant/components/tfiac/* @fredrike @mellado
homeassistant/components/thethingsnetwork/* @fabaff homeassistant/components/thethingsnetwork/* @fabaff
homeassistant/components/threshold/* @fabaff homeassistant/components/threshold/* @fabaff
@ -534,12 +543,12 @@ homeassistant/components/tile/* @bachya
homeassistant/components/time_date/* @fabaff homeassistant/components/time_date/* @fabaff
homeassistant/components/tmb/* @alemuro homeassistant/components/tmb/* @alemuro
homeassistant/components/todoist/* @boralyl homeassistant/components/todoist/* @boralyl
homeassistant/components/tolo/* @MatthiasLohr
homeassistant/components/totalconnect/* @austinmroczek homeassistant/components/totalconnect/* @austinmroczek
homeassistant/components/tplink/* @rytilahti @thegardenmonkey homeassistant/components/tplink/* @rytilahti @thegardenmonkey
homeassistant/components/traccar/* @ludeeus homeassistant/components/traccar/* @ludeeus
homeassistant/components/trace/* @home-assistant/core homeassistant/components/trace/* @home-assistant/core
homeassistant/components/tractive/* @Danielhiversen @zhulik @bieniu homeassistant/components/tractive/* @Danielhiversen @zhulik @bieniu
homeassistant/components/tradfri/* @janiversen
homeassistant/components/trafikverket_train/* @endor-force homeassistant/components/trafikverket_train/* @endor-force
homeassistant/components/trafikverket_weatherstation/* @endor-force homeassistant/components/trafikverket_weatherstation/* @endor-force
homeassistant/components/transmission/* @engrbm87 @JPHutchins homeassistant/components/transmission/* @engrbm87 @JPHutchins
@ -575,6 +584,7 @@ homeassistant/components/vizio/* @raman325
homeassistant/components/vlc_telnet/* @rodripf @dmcc @MartinHjelmare homeassistant/components/vlc_telnet/* @rodripf @dmcc @MartinHjelmare
homeassistant/components/volkszaehler/* @fabaff homeassistant/components/volkszaehler/* @fabaff
homeassistant/components/volumio/* @OnFreund homeassistant/components/volumio/* @OnFreund
homeassistant/components/volvooncall/* @molobrakos @decompil3d
homeassistant/components/wake_on_lan/* @ntilley905 homeassistant/components/wake_on_lan/* @ntilley905
homeassistant/components/wallbox/* @hesselonline homeassistant/components/wallbox/* @hesselonline
homeassistant/components/waqi/* @andrey-git homeassistant/components/waqi/* @andrey-git

View File

@ -7,12 +7,21 @@ ENV \
WORKDIR /usr/src WORKDIR /usr/src
## Setup Home Assistant ## 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
COPY . homeassistant/ COPY . homeassistant/
RUN \ RUN \
pip3 install --no-cache-dir --no-index --only-binary=:all: --find-links "${WHEELS_LINKS}" \ 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 \ -e ./homeassistant \
&& python3 -m compileall homeassistant/homeassistant && python3 -m compileall homeassistant/homeassistant

View File

@ -30,11 +30,12 @@ RUN git clone --depth 1 https://github.com/home-assistant/hass-release \
WORKDIR /workspaces WORKDIR /workspaces
# Install Python dependencies from requirements # Install Python dependencies from requirements
COPY requirements.txt requirements_test.txt requirements_test_pre_commit.txt ./ COPY requirements.txt ./
COPY homeassistant/package_constraints.txt homeassistant/package_constraints.txt COPY homeassistant/package_constraints.txt homeassistant/package_constraints.txt
RUN pip3 install -r requirements.txt \ RUN pip3 install -r requirements.txt
&& pip3 install -r requirements_test.txt \ COPY requirements_test.txt requirements_test_pre_commit.txt ./
&& rm -rf requirements.txt requirements_test.txt requirements_test_pre_commit.txt homeassistant/ RUN pip3 install -r requirements_test.txt
RUN rm -rf requirements.txt requirements_test.txt requirements_test_pre_commit.txt homeassistant/
# Set the default shell to bash instead of sh # Set the default shell to bash instead of sh
ENV SHELL /bin/bash ENV SHELL /bin/bash

View File

@ -1,22 +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"
},
"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
}

20
build.yaml Normal file
View File

@ -0,0 +1,20 @@
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,4 +6,28 @@ coverage:
default: default:
target: 90 target: 90
threshold: 0.09 threshold: 0.09
config-flows:
target: auto
threshold: 0
paths:
- homeassistant/components/*/config_flow.py
patch:
default:
target: auto
config-flows:
target: 100
threshold: 0
paths:
- homeassistant/components/*/config_flow.py
comment: false 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

View File

@ -0,0 +1,42 @@
"""Provide backwards compat for async_timeout."""
from __future__ import annotations
import asyncio
from typing import Any
import async_timeout
from homeassistant.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.2",
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.2; 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

@ -214,11 +214,19 @@ class AuthManager:
return None return None
async def async_create_system_user( async def async_create_system_user(
self, name: str, group_ids: list[str] | None = None self,
name: str,
*,
group_ids: list[str] | None = None,
local_only: bool | None = None,
) -> models.User: ) -> models.User:
"""Create a system user.""" """Create a system user."""
user = await self._store.async_create_user( user = await self._store.async_create_user(
name=name, system_generated=True, is_active=True, group_ids=group_ids or [] name=name,
system_generated=True,
is_active=True,
group_ids=group_ids or [],
local_only=local_only,
) )
self.hass.bus.async_fire(EVENT_USER_ADDED, {"user_id": user.id}) self.hass.bus.async_fire(EVENT_USER_ADDED, {"user_id": user.id})
@ -226,13 +234,18 @@ class AuthManager:
return user return user
async def async_create_user( async def async_create_user(
self, name: str, group_ids: list[str] | None = None self,
name: str,
*,
group_ids: list[str] | None = None,
local_only: bool | None = None,
) -> models.User: ) -> models.User:
"""Create a user.""" """Create a user."""
kwargs: dict[str, Any] = { kwargs: dict[str, Any] = {
"name": name, "name": name,
"is_active": True, "is_active": True,
"group_ids": group_ids or [], "group_ids": group_ids or [],
"local_only": local_only,
} }
if await self._user_should_be_owner(): if await self._user_should_be_owner():
@ -304,13 +317,18 @@ class AuthManager:
name: str | None = None, name: str | None = None,
is_active: bool | None = None, is_active: bool | None = None,
group_ids: list[str] | None = None, group_ids: list[str] | None = None,
local_only: bool | None = None,
) -> None: ) -> None:
"""Update a user.""" """Update a user."""
kwargs: dict[str, Any] = {} kwargs: dict[str, Any] = {}
if name is not None:
kwargs["name"] = name for attr_name, value in (
if group_ids is not None: ("name", name),
kwargs["group_ids"] = group_ids ("group_ids", group_ids),
("local_only", local_only),
):
if value is not None:
kwargs[attr_name] = value
await self._store.async_update_user(user, **kwargs) await self._store.async_update_user(user, **kwargs)
if is_active is not None: if is_active is not None:

View File

@ -42,7 +42,7 @@ class AuthStore:
self._groups: dict[str, models.Group] | None = None self._groups: dict[str, models.Group] | None = None
self._perm_lookup: PermissionLookup | None = None self._perm_lookup: PermissionLookup | None = None
self._store = hass.helpers.storage.Store( self._store = hass.helpers.storage.Store(
STORAGE_VERSION, STORAGE_KEY, private=True STORAGE_VERSION, STORAGE_KEY, private=True, atomic_writes=True
) )
self._lock = asyncio.Lock() self._lock = asyncio.Lock()
@ -86,6 +86,7 @@ class AuthStore:
system_generated: bool | None = None, system_generated: bool | None = None,
credentials: models.Credentials | None = None, credentials: models.Credentials | None = None,
group_ids: list[str] | None = None, group_ids: list[str] | None = None,
local_only: bool | None = None,
) -> models.User: ) -> models.User:
"""Create a new user.""" """Create a new user."""
if self._users is None: if self._users is None:
@ -108,14 +109,14 @@ class AuthStore:
"perm_lookup": self._perm_lookup, "perm_lookup": self._perm_lookup,
} }
if is_owner is not None: for attr_name, value in (
kwargs["is_owner"] = is_owner ("is_owner", is_owner),
("is_active", is_active),
if is_active is not None: ("local_only", local_only),
kwargs["is_active"] = is_active ("system_generated", system_generated),
):
if system_generated is not None: if value is not None:
kwargs["system_generated"] = system_generated kwargs[attr_name] = value
new_user = models.User(**kwargs) new_user = models.User(**kwargs)
@ -152,6 +153,7 @@ class AuthStore:
name: str | None = None, name: str | None = None,
is_active: bool | None = None, is_active: bool | None = None,
group_ids: list[str] | None = None, group_ids: list[str] | None = None,
local_only: bool | None = None,
) -> None: ) -> None:
"""Update a user.""" """Update a user."""
assert self._groups is not None assert self._groups is not None
@ -166,7 +168,11 @@ class AuthStore:
user.groups = groups user.groups = groups
user.invalidate_permission_cache() user.invalidate_permission_cache()
for attr_name, value in (("name", name), ("is_active", is_active)): for attr_name, value in (
("name", name),
("is_active", is_active),
("local_only", local_only),
):
if value is not None: if value is not None:
setattr(user, attr_name, value) setattr(user, attr_name, value)
@ -417,6 +423,8 @@ class AuthStore:
is_active=user_dict["is_active"], is_active=user_dict["is_active"],
system_generated=user_dict["system_generated"], system_generated=user_dict["system_generated"],
perm_lookup=perm_lookup, perm_lookup=perm_lookup,
# New in 2021.11
local_only=user_dict.get("local_only", False),
) )
for cred_dict in data["credentials"]: for cred_dict in data["credentials"]:
@ -502,6 +510,7 @@ class AuthStore:
"is_active": user.is_active, "is_active": user.is_active,
"name": user.name, "name": user.name,
"system_generated": user.system_generated, "system_generated": user.system_generated,
"local_only": user.local_only,
} }
for user in self._users.values() for user in self._users.values()
] ]

View File

@ -100,7 +100,7 @@ class NotifyAuthModule(MultiFactorAuthModule):
super().__init__(hass, config) super().__init__(hass, config)
self._user_settings: _UsersDict | None = None self._user_settings: _UsersDict | None = None
self._user_store = hass.helpers.storage.Store( self._user_store = hass.helpers.storage.Store(
STORAGE_VERSION, STORAGE_KEY, private=True STORAGE_VERSION, STORAGE_KEY, private=True, atomic_writes=True
) )
self._include = config.get(CONF_INCLUDE, []) self._include = config.get(CONF_INCLUDE, [])
self._exclude = config.get(CONF_EXCLUDE, []) self._exclude = config.get(CONF_EXCLUDE, [])

View File

@ -77,7 +77,7 @@ class TotpAuthModule(MultiFactorAuthModule):
super().__init__(hass, config) super().__init__(hass, config)
self._users: dict[str, str] | None = None self._users: dict[str, str] | None = None
self._user_store = hass.helpers.storage.Store( self._user_store = hass.helpers.storage.Store(
STORAGE_VERSION, STORAGE_KEY, private=True STORAGE_VERSION, STORAGE_KEY, private=True, atomic_writes=True
) )
self._init_lock = asyncio.Lock() self._init_lock = asyncio.Lock()

View File

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

View File

@ -63,7 +63,7 @@ class Data:
"""Initialize the user data store.""" """Initialize the user data store."""
self.hass = hass self.hass = hass
self._store = hass.helpers.storage.Store( self._store = hass.helpers.storage.Store(
STORAGE_VERSION, STORAGE_KEY, private=True STORAGE_VERSION, STORAGE_KEY, private=True, atomic_writes=True
) )
self._data: dict[str, Any] | None = None self._data: dict[str, Any] | None = None
# Legacy mode will allow usernames to start/end with whitespace # Legacy mode will allow usernames to start/end with whitespace

View File

@ -102,7 +102,7 @@ class ExampleLoginFlow(LoginFlow):
self, user_input: dict[str, str] | None = None self, user_input: dict[str, str] | None = None
) -> FlowResult: ) -> FlowResult:
"""Handle the step of the form.""" """Handle the step of the form."""
errors = {} errors = None
if user_input is not None: if user_input is not None:
try: try:
@ -110,7 +110,7 @@ class ExampleLoginFlow(LoginFlow):
user_input["username"], user_input["password"] user_input["username"], user_input["password"]
) )
except InvalidAuthError: except InvalidAuthError:
errors["base"] = "invalid_auth" errors = {"base": "invalid_auth"}
if not errors: if not errors:
user_input.pop("password") user_input.pop("password")

View File

@ -194,6 +194,12 @@ class TrustedNetworksAuthProvider(AuthProvider):
if any(ip_addr in trusted_proxy for trusted_proxy in self.trusted_proxies): if any(ip_addr in trusted_proxy for trusted_proxy in self.trusted_proxies):
raise InvalidAuthError("Can't allow access from a proxy server") 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 @callback
def async_validate_refresh_token( def async_validate_refresh_token(
self, refresh_token: RefreshToken, remote_ip: str | None = None self, refresh_token: RefreshToken, remote_ip: str | None = None

View File

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

View File

@ -0,0 +1,33 @@
"""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) # type: ignore[call-overload,no-any-return]
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

@ -252,8 +252,7 @@ async def async_from_config_dict(
f"{'.'.join(str(x) for x in sys.version_info[:3])} is deprecated and will " 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 Home Assistant {REQUIRED_NEXT_PYTHON_HA_RELEASE}. "
"Please upgrade Python to " "Please upgrade Python to "
f"{'.'.join(str(x) for x in REQUIRED_NEXT_PYTHON_VER)} or " f"{'.'.join(str(x) for x in REQUIRED_NEXT_PYTHON_VER[:2])}."
"higher."
) )
_LOGGER.warning(msg) _LOGGER.warning(msg)
hass.components.persistent_notification.async_create( hass.components.persistent_notification.async_create(

View File

@ -16,6 +16,7 @@ from homeassistant.const import (
CONF_PASSWORD, CONF_PASSWORD,
CONF_USERNAME, CONF_USERNAME,
EVENT_HOMEASSISTANT_STOP, EVENT_HOMEASSISTANT_STOP,
Platform,
) )
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers import config_validation as cv from homeassistant.helpers import config_validation as cv
@ -53,14 +54,14 @@ CAPTURE_IMAGE_SCHEMA = vol.Schema({ATTR_ENTITY_ID: cv.entity_ids})
AUTOMATION_SCHEMA = vol.Schema({ATTR_ENTITY_ID: cv.entity_ids}) AUTOMATION_SCHEMA = vol.Schema({ATTR_ENTITY_ID: cv.entity_ids})
PLATFORMS = [ PLATFORMS = [
"alarm_control_panel", Platform.ALARM_CONTROL_PANEL,
"binary_sensor", Platform.BINARY_SENSOR,
"lock", Platform.LOCK,
"switch", Platform.SWITCH,
"cover", Platform.COVER,
"camera", Platform.CAMERA,
"light", Platform.LIGHT,
"sensor", Platform.SENSOR,
] ]

View File

@ -0,0 +1,35 @@
{
"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,7 +27,8 @@
"data": { "data": {
"password": "Parola", "password": "Parola",
"username": "E-posta" "username": "E-posta"
} },
"title": "Abode giri\u015f bilgilerinizi doldurun"
} }
} }
} }

View File

@ -11,7 +11,7 @@ from aiohttp.client_exceptions import ClientConnectorError
from async_timeout import timeout from async_timeout import timeout
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_API_KEY from homeassistant.const import CONF_API_KEY, Platform
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
@ -20,7 +20,7 @@ from .const import ATTR_FORECAST, CONF_FORECAST, DOMAIN
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
PLATFORMS = ["sensor", "weather"] PLATFORMS = [Platform.SENSOR, Platform.WEATHER]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:

View File

@ -3,7 +3,7 @@ from __future__ import annotations
from typing import Final from typing import Final
from homeassistant.components.sensor import STATE_CLASS_MEASUREMENT from homeassistant.components.sensor import SensorStateClass
from homeassistant.components.weather import ( from homeassistant.components.weather import (
ATTR_CONDITION_CLEAR_NIGHT, ATTR_CONDITION_CLEAR_NIGHT,
ATTR_CONDITION_CLOUDY, ATTR_CONDITION_CLOUDY,
@ -220,7 +220,7 @@ SENSOR_TYPES: Final[tuple[AccuWeatherSensorDescription, ...]] = (
unit_metric=TEMP_CELSIUS, unit_metric=TEMP_CELSIUS,
unit_imperial=TEMP_FAHRENHEIT, unit_imperial=TEMP_FAHRENHEIT,
entity_registry_enabled_default=False, entity_registry_enabled_default=False,
state_class=STATE_CLASS_MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
), ),
AccuWeatherSensorDescription( AccuWeatherSensorDescription(
key="Ceiling", key="Ceiling",
@ -228,7 +228,7 @@ SENSOR_TYPES: Final[tuple[AccuWeatherSensorDescription, ...]] = (
name="Cloud Ceiling", name="Cloud Ceiling",
unit_metric=LENGTH_METERS, unit_metric=LENGTH_METERS,
unit_imperial=LENGTH_FEET, unit_imperial=LENGTH_FEET,
state_class=STATE_CLASS_MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
), ),
AccuWeatherSensorDescription( AccuWeatherSensorDescription(
key="CloudCover", key="CloudCover",
@ -237,7 +237,7 @@ SENSOR_TYPES: Final[tuple[AccuWeatherSensorDescription, ...]] = (
unit_metric=PERCENTAGE, unit_metric=PERCENTAGE,
unit_imperial=PERCENTAGE, unit_imperial=PERCENTAGE,
entity_registry_enabled_default=False, entity_registry_enabled_default=False,
state_class=STATE_CLASS_MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
), ),
AccuWeatherSensorDescription( AccuWeatherSensorDescription(
key="DewPoint", key="DewPoint",
@ -246,7 +246,7 @@ SENSOR_TYPES: Final[tuple[AccuWeatherSensorDescription, ...]] = (
unit_metric=TEMP_CELSIUS, unit_metric=TEMP_CELSIUS,
unit_imperial=TEMP_FAHRENHEIT, unit_imperial=TEMP_FAHRENHEIT,
entity_registry_enabled_default=False, entity_registry_enabled_default=False,
state_class=STATE_CLASS_MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
), ),
AccuWeatherSensorDescription( AccuWeatherSensorDescription(
key="RealFeelTemperature", key="RealFeelTemperature",
@ -254,7 +254,7 @@ SENSOR_TYPES: Final[tuple[AccuWeatherSensorDescription, ...]] = (
name="RealFeel Temperature", name="RealFeel Temperature",
unit_metric=TEMP_CELSIUS, unit_metric=TEMP_CELSIUS,
unit_imperial=TEMP_FAHRENHEIT, unit_imperial=TEMP_FAHRENHEIT,
state_class=STATE_CLASS_MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
), ),
AccuWeatherSensorDescription( AccuWeatherSensorDescription(
key="RealFeelTemperatureShade", key="RealFeelTemperatureShade",
@ -263,7 +263,7 @@ SENSOR_TYPES: Final[tuple[AccuWeatherSensorDescription, ...]] = (
unit_metric=TEMP_CELSIUS, unit_metric=TEMP_CELSIUS,
unit_imperial=TEMP_FAHRENHEIT, unit_imperial=TEMP_FAHRENHEIT,
entity_registry_enabled_default=False, entity_registry_enabled_default=False,
state_class=STATE_CLASS_MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
), ),
AccuWeatherSensorDescription( AccuWeatherSensorDescription(
key="Precipitation", key="Precipitation",
@ -271,7 +271,7 @@ SENSOR_TYPES: Final[tuple[AccuWeatherSensorDescription, ...]] = (
name="Precipitation", name="Precipitation",
unit_metric=LENGTH_MILLIMETERS, unit_metric=LENGTH_MILLIMETERS,
unit_imperial=LENGTH_INCHES, unit_imperial=LENGTH_INCHES,
state_class=STATE_CLASS_MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
), ),
AccuWeatherSensorDescription( AccuWeatherSensorDescription(
key="PressureTendency", key="PressureTendency",
@ -287,7 +287,7 @@ SENSOR_TYPES: Final[tuple[AccuWeatherSensorDescription, ...]] = (
name="UV Index", name="UV Index",
unit_metric=UV_INDEX, unit_metric=UV_INDEX,
unit_imperial=UV_INDEX, unit_imperial=UV_INDEX,
state_class=STATE_CLASS_MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
), ),
AccuWeatherSensorDescription( AccuWeatherSensorDescription(
key="WetBulbTemperature", key="WetBulbTemperature",
@ -296,7 +296,7 @@ SENSOR_TYPES: Final[tuple[AccuWeatherSensorDescription, ...]] = (
unit_metric=TEMP_CELSIUS, unit_metric=TEMP_CELSIUS,
unit_imperial=TEMP_FAHRENHEIT, unit_imperial=TEMP_FAHRENHEIT,
entity_registry_enabled_default=False, entity_registry_enabled_default=False,
state_class=STATE_CLASS_MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
), ),
AccuWeatherSensorDescription( AccuWeatherSensorDescription(
key="WindChillTemperature", key="WindChillTemperature",
@ -305,7 +305,7 @@ SENSOR_TYPES: Final[tuple[AccuWeatherSensorDescription, ...]] = (
unit_metric=TEMP_CELSIUS, unit_metric=TEMP_CELSIUS,
unit_imperial=TEMP_FAHRENHEIT, unit_imperial=TEMP_FAHRENHEIT,
entity_registry_enabled_default=False, entity_registry_enabled_default=False,
state_class=STATE_CLASS_MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
), ),
AccuWeatherSensorDescription( AccuWeatherSensorDescription(
key="Wind", key="Wind",
@ -313,7 +313,7 @@ SENSOR_TYPES: Final[tuple[AccuWeatherSensorDescription, ...]] = (
name="Wind", name="Wind",
unit_metric=SPEED_KILOMETERS_PER_HOUR, unit_metric=SPEED_KILOMETERS_PER_HOUR,
unit_imperial=SPEED_MILES_PER_HOUR, unit_imperial=SPEED_MILES_PER_HOUR,
state_class=STATE_CLASS_MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
), ),
AccuWeatherSensorDescription( AccuWeatherSensorDescription(
key="WindGust", key="WindGust",
@ -322,6 +322,6 @@ SENSOR_TYPES: Final[tuple[AccuWeatherSensorDescription, ...]] = (
unit_metric=SPEED_KILOMETERS_PER_HOUR, unit_metric=SPEED_KILOMETERS_PER_HOUR,
unit_imperial=SPEED_MILES_PER_HOUR, unit_imperial=SPEED_MILES_PER_HOUR,
entity_registry_enabled_default=False, entity_registry_enabled_default=False,
state_class=STATE_CLASS_MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
), ),
) )

View File

@ -7,6 +7,7 @@ from homeassistant.components.sensor import SensorEntity
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_NAME, DEVICE_CLASS_TEMPERATURE from homeassistant.const import CONF_NAME, DEVICE_CLASS_TEMPERATURE
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.device_registry import DeviceEntryType
from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType from homeassistant.helpers.typing import StateType
@ -95,7 +96,7 @@ class AccuWeatherSensor(CoordinatorEntity, SensorEntity):
self._unit_system = API_IMPERIAL self._unit_system = API_IMPERIAL
self._attr_native_unit_of_measurement = description.unit_imperial self._attr_native_unit_of_measurement = description.unit_imperial
self._attr_device_info = DeviceInfo( self._attr_device_info = DeviceInfo(
entry_type="service", entry_type=DeviceEntryType.SERVICE,
identifiers={(DOMAIN, coordinator.location_key)}, identifiers={(DOMAIN, coordinator.location_key)},
manufacturer=MANUFACTURER, manufacturer=MANUFACTURER,
name=NAME, name=NAME,

View File

@ -1,7 +1,22 @@
{ {
"config": { "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": { "error": {
"cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0445 \u043f\u0440\u0438 \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435" "cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\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

@ -0,0 +1,41 @@
{
"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\u306b\u5230\u9054",
"remaining_requests": "\u6b8b\u308a\u306e\u8a31\u53ef\u3055\u308c\u305f\u30ea\u30af\u30a8\u30b9\u30c8"
}
}
}

View File

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

View File

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

View File

@ -5,7 +5,8 @@
}, },
"error": { "error": {
"cannot_connect": "Ba\u011flanma hatas\u0131", "cannot_connect": "Ba\u011flanma hatas\u0131",
"invalid_api_key": "Ge\u00e7ersiz API anahtar\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."
}, },
"step": { "step": {
"user": { "user": {
@ -15,6 +16,7 @@
"longitude": "Boylam", "longitude": "Boylam",
"name": "Ad" "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" "title": "AccuWeather"
} }
} }
@ -25,6 +27,7 @@
"data": { "data": {
"forecast": "Hava Durumu tahmini" "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" "title": "AccuWeather Se\u00e7enekleri"
} }
} }

View File

@ -19,6 +19,7 @@ from homeassistant.components.weather import (
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_NAME, TEMP_CELSIUS, TEMP_FAHRENHEIT from homeassistant.const import CONF_NAME, TEMP_CELSIUS, TEMP_FAHRENHEIT
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceEntryType
from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.helpers.update_coordinator import CoordinatorEntity
@ -68,10 +69,16 @@ class AccuWeatherEntity(CoordinatorEntity, WeatherEntity):
) )
self._attr_attribution = ATTRIBUTION self._attr_attribution = ATTRIBUTION
self._attr_device_info = DeviceInfo( self._attr_device_info = DeviceInfo(
entry_type="service", entry_type=DeviceEntryType.SERVICE,
identifiers={(DOMAIN, coordinator.location_key)}, identifiers={(DOMAIN, coordinator.location_key)},
manufacturer=MANUFACTURER, manufacturer=MANUFACTURER,
name=NAME, 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}/",
) )
@property @property

View File

@ -111,8 +111,7 @@ class AcerSwitch(SwitchEntity):
"""Write msg, obtain answer and format output.""" """Write msg, obtain answer and format output."""
# answers are formatted as ***\answer\r*** # answers are formatted as ***\answer\r***
awns = self._write_read(msg) awns = self._write_read(msg)
match = re.search(r"\r(.+)\r", awns) if match := re.search(r"\r(.+)\r", awns):
if match:
return match.group(1) return match.group(1)
return STATE_UNKNOWN return STATE_UNKNOWN

View File

@ -1,13 +1,14 @@
"""The Rollease Acmeda Automate integration.""" """The Rollease Acmeda Automate integration."""
from homeassistant import config_entries, core from homeassistant import config_entries, core
from homeassistant.const import Platform
from .const import DOMAIN from .const import DOMAIN
from .hub import PulseHub from .hub import PulseHub
CONF_HUBS = "hubs" CONF_HUBS = "hubs"
PLATFORMS = ["cover", "sensor"] PLATFORMS = [Platform.COVER, Platform.SENSOR]
async def async_setup_entry( async def async_setup_entry(

View File

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

View File

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

View File

@ -0,0 +1,15 @@
{
"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,10 +1,14 @@
{ {
"config": { "config": {
"abort": {
"no_devices_found": "A\u011fda cihaz bulunamad\u0131"
},
"step": { "step": {
"user": { "user": {
"data": { "data": {
"id": "Ana bilgisayar kimli\u011fi" "id": "Ana bilgisayar kimli\u011fi"
} },
"title": "Eklemek i\u00e7in bir merkez se\u00e7in"
} }
} }
} }

View File

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

View File

@ -2,9 +2,10 @@
from __future__ import annotations from __future__ import annotations
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
PLATFORMS = ["climate"] PLATFORMS = [Platform.CLIMATE]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:

View File

@ -4,7 +4,7 @@
"config_flow": true, "config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/adax", "documentation": "https://www.home-assistant.io/integrations/adax",
"requirements": [ "requirements": [
"adax==0.1.1" "adax==0.2.0"
], ],
"codeowners": [ "codeowners": [
"@danielhiversen" "@danielhiversen"

View File

@ -4,7 +4,7 @@
"already_configured": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u0442\u043e \u0432\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u043e" "already_configured": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u0442\u043e \u0432\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u043e"
}, },
"error": { "error": {
"cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0445 \u043f\u0440\u0438 \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435", "cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435",
"invalid_auth": "\u041d\u0435\u0432\u0430\u043b\u0438\u0434\u043d\u043e \u0443\u0434\u043e\u0441\u0442\u043e\u0432\u0435\u0440\u044f\u0432\u0430\u043d\u0435" "invalid_auth": "\u041d\u0435\u0432\u0430\u043b\u0438\u0434\u043d\u043e \u0443\u0434\u043e\u0441\u0442\u043e\u0432\u0435\u0440\u044f\u0432\u0430\u043d\u0435"
}, },
"step": { "step": {

View File

@ -0,0 +1,20 @@
{
"config": {
"abort": {
"already_configured": "\u30c7\u30d0\u30a4\u30b9\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059"
},
"error": {
"cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f",
"invalid_auth": "\u7121\u52b9\u306a\u8a8d\u8a3c"
},
"step": {
"user": {
"data": {
"account_id": "\u30a2\u30ab\u30a6\u30f3\u30c8ID",
"host": "\u30db\u30b9\u30c8",
"password": "\u30d1\u30b9\u30ef\u30fc\u30c9"
}
}
}
}
}

View File

@ -0,0 +1,20 @@
{
"config": {
"abort": {
"already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f"
},
"error": {
"cannot_connect": "Ba\u011flanma hatas\u0131",
"invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama"
},
"step": {
"user": {
"data": {
"account_id": "Hesap Kimli\u011fi",
"host": "Ana bilgisayar",
"password": "Parola"
}
}
}
}
}

View File

@ -6,7 +6,7 @@ import logging
from adguardhome import AdGuardHome, AdGuardHomeConnectionError, AdGuardHomeError from adguardhome import AdGuardHome, AdGuardHomeConnectionError, AdGuardHomeError
import voluptuous as vol import voluptuous as vol
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import SOURCE_HASSIO, ConfigEntry
from homeassistant.const import ( from homeassistant.const import (
CONF_HOST, CONF_HOST,
CONF_NAME, CONF_NAME,
@ -16,11 +16,13 @@ from homeassistant.const import (
CONF_URL, CONF_URL,
CONF_USERNAME, CONF_USERNAME,
CONF_VERIFY_SSL, CONF_VERIFY_SSL,
Platform,
) )
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import config_validation as cv from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.device_registry import DeviceEntryType
from homeassistant.helpers.entity import DeviceInfo, Entity from homeassistant.helpers.entity import DeviceInfo, Entity
from .const import ( from .const import (
@ -45,7 +47,7 @@ SERVICE_REFRESH_SCHEMA = vol.Schema(
{vol.Optional(CONF_FORCE, default=False): cv.boolean} {vol.Optional(CONF_FORCE, default=False): cv.boolean}
) )
PLATFORMS = ["sensor", "switch"] PLATFORMS = [Platform.SENSOR, Platform.SWITCH]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
@ -196,8 +198,16 @@ class AdGuardHomeDeviceEntity(AdGuardHomeEntity):
@property @property
def device_info(self) -> DeviceInfo: def device_info(self) -> DeviceInfo:
"""Return device information about this AdGuard Home instance.""" """Return device information about this AdGuard Home instance."""
if self._entry.source == SOURCE_HASSIO:
config_url = "homeassistant://hassio/ingress/a0d7b954_adguard"
else:
if self.adguard.tls:
config_url = f"https://{self.adguard.host}:{self.adguard.port}"
else:
config_url = f"http://{self.adguard.host}:{self.adguard.port}"
return DeviceInfo( return DeviceInfo(
entry_type="service", entry_type=DeviceEntryType.SERVICE,
identifiers={ identifiers={
(DOMAIN, self.adguard.host, self.adguard.port, self.adguard.base_path) # type: ignore (DOMAIN, self.adguard.host, self.adguard.port, self.adguard.base_path) # type: ignore
}, },
@ -206,4 +216,5 @@ class AdGuardHomeDeviceEntity(AdGuardHomeEntity):
sw_version=self.hass.data[DOMAIN][self._entry.entry_id].get( sw_version=self.hass.data[DOMAIN][self._entry.entry_id].get(
DATA_ADGUARD_VERSION DATA_ADGUARD_VERSION
), ),
configuration_url=config_url,
) )

View File

@ -6,6 +6,7 @@ from typing import Any
from adguardhome import AdGuardHome, AdGuardHomeConnectionError from adguardhome import AdGuardHome, AdGuardHomeConnectionError
import voluptuous as vol import voluptuous as vol
from homeassistant.components.hassio import HassioServiceInfo
from homeassistant.config_entries import ConfigFlow from homeassistant.config_entries import ConfigFlow
from homeassistant.const import ( from homeassistant.const import (
CONF_HOST, CONF_HOST,
@ -104,14 +105,14 @@ class AdGuardHomeFlowHandler(ConfigFlow, domain=DOMAIN):
}, },
) )
async def async_step_hassio(self, discovery_info: dict[str, Any]) -> FlowResult: async def async_step_hassio(self, discovery_info: HassioServiceInfo) -> FlowResult:
"""Prepare configuration for a Hass.io AdGuard Home add-on. """Prepare configuration for a Hass.io AdGuard Home add-on.
This flow is triggered by the discovery component. This flow is triggered by the discovery component.
""" """
await self._async_handle_discovery_without_unique_id() await self._async_handle_discovery_without_unique_id()
self._hassio_discovery = discovery_info self._hassio_discovery = discovery_info.config
return await self.async_step_hassio_confirm() return await self.async_step_hassio_confirm()
async def async_step_hassio_confirm( async def async_step_hassio_confirm(

View File

@ -0,0 +1,28 @@
{
"config": {
"abort": {
"already_configured": "\u30b5\u30fc\u30d3\u30b9\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059",
"existing_instance_updated": "\u65e2\u5b58\u306e\u8a2d\u5b9a\u3092\u66f4\u65b0\u3057\u307e\u3057\u305f\u3002"
},
"error": {
"cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f"
},
"step": {
"hassio_confirm": {
"description": "\u30a2\u30c9\u30aa\u30f3 {addon} \u304c\u3001\u63d0\u4f9b\u3059\u308bAdGuard Home\u306b\u63a5\u7d9a\u3059\u308b\u3088\u3046\u306bHome Assistant\u3092\u8a2d\u5b9a\u3057\u307e\u3059\u304b\uff1f",
"title": "Home Assistant\u30a2\u30c9\u30aa\u30f3\u7d4c\u7531\u306eAdGuard Home"
},
"user": {
"data": {
"host": "\u30db\u30b9\u30c8",
"password": "\u30d1\u30b9\u30ef\u30fc\u30c9",
"port": "\u30dd\u30fc\u30c8",
"ssl": "SSL\u8a3c\u660e\u66f8\u3092\u4f7f\u7528\u3059\u308b",
"username": "\u30e6\u30fc\u30b6\u30fc\u540d",
"verify_ssl": "SSL\u8a3c\u660e\u66f8\u3092\u78ba\u8a8d\u3059\u308b"
},
"description": "\u76e3\u8996\u3068\u5236\u5fa1\u304c\u3067\u304d\u308b\u3088\u3046\u306b\u3001AdGuardHome\u30a4\u30f3\u30b9\u30bf\u30f3\u30b9\u306e\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3092\u3057\u307e\u3059\u3002"
}
}
}
}

View File

@ -1,16 +1,27 @@
{ {
"config": { "config": {
"abort": {
"already_configured": "Hizmet zaten yap\u0131land\u0131r\u0131lm\u0131\u015f",
"existing_instance_updated": "Mevcut yap\u0131land\u0131rma g\u00fcncellendi."
},
"error": { "error": {
"cannot_connect": "Ba\u011flanma hatas\u0131" "cannot_connect": "Ba\u011flanma hatas\u0131"
}, },
"step": { "step": {
"hassio_confirm": {
"description": "{addon} taraf\u0131ndan sa\u011flanan AdGuard Home'a ba\u011flanmak i\u00e7in Home Assistant'\u0131 yap\u0131land\u0131rmak istiyor musunuz?",
"title": "Home Assistant eklentisi arac\u0131l\u0131\u011f\u0131yla AdGuard Home"
},
"user": { "user": {
"data": { "data": {
"host": "Ana Bilgisayar", "host": "Ana bilgisayar",
"password": "Parola", "password": "Parola",
"port": "Port", "port": "Port",
"username": "Kullan\u0131c\u0131 Ad\u0131" "ssl": "SSL sertifikas\u0131 kullan\u0131r",
} "username": "Kullan\u0131c\u0131 Ad\u0131",
"verify_ssl": "SSL sertifikas\u0131n\u0131 do\u011frulay\u0131n"
},
"description": "AdGuard Home \u00f6rne\u011finizi, izleme ve kontrole izin verecek \u015fekilde ayarlay\u0131n."
} }
} }
} }

View File

@ -307,7 +307,7 @@ class AdsEntity(Entity):
self._ads_hub.add_device_notification, ads_var, plctype, update self._ads_hub.add_device_notification, ads_var, plctype, update
) )
try: try:
with async_timeout.timeout(10): async with async_timeout.timeout(10):
await self._event.wait() await self._event.wait()
except asyncio.TimeoutError: except asyncio.TimeoutError:
_LOGGER.debug("Variable %s: Timeout during first update", ads_var) _LOGGER.debug("Variable %s: Timeout during first update", ads_var)

View File

@ -5,14 +5,21 @@ import logging
from advantage_air import ApiError, advantage_air from advantage_air import ApiError, advantage_air
from homeassistant.const import CONF_IP_ADDRESS, CONF_PORT from homeassistant.const import CONF_IP_ADDRESS, CONF_PORT, Platform
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import ADVANTAGE_AIR_RETRY, DOMAIN from .const import ADVANTAGE_AIR_RETRY, DOMAIN
ADVANTAGE_AIR_SYNC_INTERVAL = 15 ADVANTAGE_AIR_SYNC_INTERVAL = 15
PLATFORMS = ["binary_sensor", "climate", "cover", "select", "sensor", "switch"] PLATFORMS = [
Platform.BINARY_SENSOR,
Platform.CLIMATE,
Platform.COVER,
Platform.SELECT,
Platform.SENSOR,
Platform.SWITCH,
]
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)

View File

@ -10,6 +10,7 @@ from homeassistant.components.climate.const import (
HVAC_MODE_DRY, HVAC_MODE_DRY,
HVAC_MODE_FAN_ONLY, HVAC_MODE_FAN_ONLY,
HVAC_MODE_HEAT, HVAC_MODE_HEAT,
HVAC_MODE_HEAT_COOL,
HVAC_MODE_OFF, HVAC_MODE_OFF,
SUPPORT_FAN_MODE, SUPPORT_FAN_MODE,
SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_TEMPERATURE,
@ -53,7 +54,7 @@ HASS_FAN_MODES = {v: k for k, v in ADVANTAGE_AIR_FAN_MODES.items()}
FAN_SPEEDS = {FAN_LOW: 30, FAN_MEDIUM: 60, FAN_HIGH: 100} FAN_SPEEDS = {FAN_LOW: 30, FAN_MEDIUM: 60, FAN_HIGH: 100}
ADVANTAGE_AIR_SERVICE_SET_MYZONE = "set_myzone" ADVANTAGE_AIR_SERVICE_SET_MYZONE = "set_myzone"
ZONE_HVAC_MODES = [HVAC_MODE_OFF, HVAC_MODE_FAN_ONLY] ZONE_HVAC_MODES = [HVAC_MODE_OFF, HVAC_MODE_HEAT_COOL]
PARALLEL_UPDATES = 0 PARALLEL_UPDATES = 0
@ -169,7 +170,7 @@ class AdvantageAirZone(AdvantageAirClimateEntity):
def hvac_mode(self): def hvac_mode(self):
"""Return the current state as HVAC mode.""" """Return the current state as HVAC mode."""
if self._zone["state"] == ADVANTAGE_AIR_STATE_OPEN: if self._zone["state"] == ADVANTAGE_AIR_STATE_OPEN:
return HVAC_MODE_FAN_ONLY return HVAC_MODE_HEAT_COOL
return HVAC_MODE_OFF return HVAC_MODE_OFF
@property @property

View File

@ -3,8 +3,8 @@ import voluptuous as vol
from homeassistant.components.sensor import ( from homeassistant.components.sensor import (
DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_MEASUREMENT,
SensorEntity, SensorEntity,
SensorStateClass,
) )
from homeassistant.const import ENTITY_CATEGORY_DIAGNOSTIC, PERCENTAGE, TEMP_CELSIUS from homeassistant.const import ENTITY_CATEGORY_DIAGNOSTIC, PERCENTAGE, TEMP_CELSIUS
from homeassistant.helpers import config_validation as cv, entity_platform from homeassistant.helpers import config_validation as cv, entity_platform
@ -84,7 +84,7 @@ class AdvantageAirZoneVent(AdvantageAirEntity, SensorEntity):
"""Representation of Advantage Air Zone Vent Sensor.""" """Representation of Advantage Air Zone Vent Sensor."""
_attr_native_unit_of_measurement = PERCENTAGE _attr_native_unit_of_measurement = PERCENTAGE
_attr_state_class = STATE_CLASS_MEASUREMENT _attr_state_class = SensorStateClass.MEASUREMENT
_attr_entity_category = ENTITY_CATEGORY_DIAGNOSTIC _attr_entity_category = ENTITY_CATEGORY_DIAGNOSTIC
def __init__(self, instance, ac_key, zone_key): def __init__(self, instance, ac_key, zone_key):
@ -114,7 +114,7 @@ class AdvantageAirZoneSignal(AdvantageAirEntity, SensorEntity):
"""Representation of Advantage Air Zone wireless signal sensor.""" """Representation of Advantage Air Zone wireless signal sensor."""
_attr_native_unit_of_measurement = PERCENTAGE _attr_native_unit_of_measurement = PERCENTAGE
_attr_state_class = STATE_CLASS_MEASUREMENT _attr_state_class = SensorStateClass.MEASUREMENT
_attr_entity_category = ENTITY_CATEGORY_DIAGNOSTIC _attr_entity_category = ENTITY_CATEGORY_DIAGNOSTIC
def __init__(self, instance, ac_key, zone_key): def __init__(self, instance, ac_key, zone_key):
@ -149,7 +149,7 @@ class AdvantageAirZoneTemp(AdvantageAirEntity, SensorEntity):
_attr_native_unit_of_measurement = TEMP_CELSIUS _attr_native_unit_of_measurement = TEMP_CELSIUS
_attr_device_class = DEVICE_CLASS_TEMPERATURE _attr_device_class = DEVICE_CLASS_TEMPERATURE
_attr_state_class = STATE_CLASS_MEASUREMENT _attr_state_class = SensorStateClass.MEASUREMENT
_attr_entity_registry_enabled_default = False _attr_entity_registry_enabled_default = False
_attr_entity_category = ENTITY_CATEGORY_DIAGNOSTIC _attr_entity_category = ENTITY_CATEGORY_DIAGNOSTIC

View File

@ -0,0 +1,20 @@
{
"config": {
"abort": {
"already_configured": "\u30c7\u30d0\u30a4\u30b9\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059"
},
"error": {
"cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f"
},
"step": {
"user": {
"data": {
"ip_address": "IP\u30a2\u30c9\u30ec\u30b9",
"port": "\u30dd\u30fc\u30c8"
},
"description": "Advantage Air wall mounted tablet\u306eAPI\u306b\u63a5\u7d9a\u3057\u307e\u3059\u3002",
"title": "\u63a5\u7d9a"
}
}
}
}

View File

@ -9,9 +9,10 @@
"step": { "step": {
"user": { "user": {
"data": { "data": {
"ip_address": "\u0130p Adresi", "ip_address": "IP Adresi",
"port": "Port" "port": "Port"
}, },
"description": "Advantage Air duvara monte tabletinizin API'sine ba\u011flan\u0131n.",
"title": "Ba\u011flan" "title": "Ba\u011flan"
} }
} }

View File

@ -1,10 +1,7 @@
"""Constant values for the AEMET OpenData component.""" """Constant values for the AEMET OpenData component."""
from __future__ import annotations from __future__ import annotations
from homeassistant.components.sensor import ( from homeassistant.components.sensor import SensorEntityDescription, SensorStateClass
STATE_CLASS_MEASUREMENT,
SensorEntityDescription,
)
from homeassistant.components.weather import ( from homeassistant.components.weather import (
ATTR_CONDITION_CLEAR_NIGHT, ATTR_CONDITION_CLEAR_NIGHT,
ATTR_CONDITION_CLOUDY, ATTR_CONDITION_CLOUDY,
@ -36,11 +33,12 @@ from homeassistant.const import (
PRESSURE_HPA, PRESSURE_HPA,
SPEED_KILOMETERS_PER_HOUR, SPEED_KILOMETERS_PER_HOUR,
TEMP_CELSIUS, TEMP_CELSIUS,
Platform,
) )
ATTRIBUTION = "Powered by AEMET OpenData" ATTRIBUTION = "Powered by AEMET OpenData"
CONF_STATION_UPDATES = "station_updates" CONF_STATION_UPDATES = "station_updates"
PLATFORMS = ["sensor", "weather"] PLATFORMS = [Platform.SENSOR, Platform.WEATHER]
DEFAULT_NAME = "AEMET" DEFAULT_NAME = "AEMET"
DOMAIN = "aemet" DOMAIN = "aemet"
ENTRY_NAME = "name" ENTRY_NAME = "name"
@ -255,14 +253,14 @@ WEATHER_SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
name="Humidity", name="Humidity",
native_unit_of_measurement=PERCENTAGE, native_unit_of_measurement=PERCENTAGE,
device_class=DEVICE_CLASS_HUMIDITY, device_class=DEVICE_CLASS_HUMIDITY,
state_class=STATE_CLASS_MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
), ),
SensorEntityDescription( SensorEntityDescription(
key=ATTR_API_PRESSURE, key=ATTR_API_PRESSURE,
name="Pressure", name="Pressure",
native_unit_of_measurement=PRESSURE_HPA, native_unit_of_measurement=PRESSURE_HPA,
device_class=DEVICE_CLASS_PRESSURE, device_class=DEVICE_CLASS_PRESSURE,
state_class=STATE_CLASS_MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
), ),
SensorEntityDescription( SensorEntityDescription(
key=ATTR_API_RAIN, key=ATTR_API_RAIN,
@ -273,7 +271,7 @@ WEATHER_SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
key=ATTR_API_RAIN_PROB, key=ATTR_API_RAIN_PROB,
name="Rain probability", name="Rain probability",
native_unit_of_measurement=PERCENTAGE, native_unit_of_measurement=PERCENTAGE,
state_class=STATE_CLASS_MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
), ),
SensorEntityDescription( SensorEntityDescription(
key=ATTR_API_SNOW, key=ATTR_API_SNOW,
@ -284,7 +282,7 @@ WEATHER_SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
key=ATTR_API_SNOW_PROB, key=ATTR_API_SNOW_PROB,
name="Snow probability", name="Snow probability",
native_unit_of_measurement=PERCENTAGE, native_unit_of_measurement=PERCENTAGE,
state_class=STATE_CLASS_MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
), ),
SensorEntityDescription( SensorEntityDescription(
key=ATTR_API_STATION_ID, key=ATTR_API_STATION_ID,
@ -303,21 +301,21 @@ WEATHER_SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
key=ATTR_API_STORM_PROB, key=ATTR_API_STORM_PROB,
name="Storm probability", name="Storm probability",
native_unit_of_measurement=PERCENTAGE, native_unit_of_measurement=PERCENTAGE,
state_class=STATE_CLASS_MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
), ),
SensorEntityDescription( SensorEntityDescription(
key=ATTR_API_TEMPERATURE, key=ATTR_API_TEMPERATURE,
name="Temperature", name="Temperature",
native_unit_of_measurement=TEMP_CELSIUS, native_unit_of_measurement=TEMP_CELSIUS,
device_class=DEVICE_CLASS_TEMPERATURE, device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
), ),
SensorEntityDescription( SensorEntityDescription(
key=ATTR_API_TEMPERATURE_FEELING, key=ATTR_API_TEMPERATURE_FEELING,
name="Temperature feeling", name="Temperature feeling",
native_unit_of_measurement=TEMP_CELSIUS, native_unit_of_measurement=TEMP_CELSIUS,
device_class=DEVICE_CLASS_TEMPERATURE, device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
), ),
SensorEntityDescription( SensorEntityDescription(
key=ATTR_API_TOWN_ID, key=ATTR_API_TOWN_ID,
@ -336,7 +334,7 @@ WEATHER_SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
key=ATTR_API_WIND_BEARING, key=ATTR_API_WIND_BEARING,
name="Wind bearing", name="Wind bearing",
native_unit_of_measurement=DEGREE, native_unit_of_measurement=DEGREE,
state_class=STATE_CLASS_MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
), ),
SensorEntityDescription( SensorEntityDescription(
key=ATTR_API_WIND_MAX_SPEED, key=ATTR_API_WIND_MAX_SPEED,
@ -347,7 +345,7 @@ WEATHER_SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
key=ATTR_API_WIND_SPEED, key=ATTR_API_WIND_SPEED,
name="Wind speed", name="Wind speed",
native_unit_of_measurement=SPEED_KILOMETERS_PER_HOUR, native_unit_of_measurement=SPEED_KILOMETERS_PER_HOUR,
state_class=STATE_CLASS_MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
), ),
) )

View File

@ -0,0 +1,14 @@
{
"config": {
"error": {
"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"
}
}
}
}
}

View File

@ -0,0 +1,31 @@
{
"config": {
"abort": {
"already_configured": "\u30ed\u30b1\u30fc\u30b7\u30e7\u30f3\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059"
},
"error": {
"invalid_api_key": "\u7121\u52b9\u306aAPI\u30ad\u30fc"
},
"step": {
"user": {
"data": {
"api_key": "API\u30ad\u30fc",
"latitude": "\u7def\u5ea6",
"longitude": "\u7d4c\u5ea6",
"name": "\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u306e\u540d\u524d"
},
"description": "AEMET OpenData\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3057\u307e\u3059\u3002 API\u30ad\u30fc\u3092\u751f\u6210\u3059\u308b\u306b\u306f\u3001https://opendata.aemet.es/centrodedescargas/altaUsuario \u306b\u30a2\u30af\u30bb\u30b9\u3057\u3066\u304f\u3060\u3055\u3044",
"title": "AEMET OpenData"
}
}
},
"options": {
"step": {
"init": {
"data": {
"station_updates": "AEMET weather station\u304b\u3089\u30c7\u30fc\u30bf\u3092\u53ce\u96c6\u3059\u308b"
}
}
}
}
}

View File

@ -14,7 +14,7 @@
"longitude": "\u0414\u043e\u043b\u0433\u043e\u0442\u0430", "longitude": "\u0414\u043e\u043b\u0433\u043e\u0442\u0430",
"name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435" "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435"
}, },
"description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u0441 AEMET OpenData. \u0427\u0442\u043e\u0431\u044b \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043a\u043b\u044e\u0447 API, \u043f\u0435\u0440\u0435\u0439\u0434\u0438\u0442\u0435 \u043d\u0430 https://opendata.aemet.es/centrodedescargas/altaUsuario.", "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 Home Assistant \u0434\u043b\u044f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u0441 AEMET OpenData. \u0427\u0442\u043e\u0431\u044b \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043a\u043b\u044e\u0447 API, \u043f\u0435\u0440\u0435\u0439\u0434\u0438\u0442\u0435 \u043d\u0430 https://opendata.aemet.es/centrodedescargas/altaUsuario.",
"title": "AEMET OpenData" "title": "AEMET OpenData"
} }
} }

View File

@ -0,0 +1,31 @@
{
"config": {
"abort": {
"already_configured": "Konum zaten yap\u0131land\u0131r\u0131lm\u0131\u015f"
},
"error": {
"invalid_api_key": "Ge\u00e7ersiz API anahtar\u0131"
},
"step": {
"user": {
"data": {
"api_key": "API Anahtar\u0131",
"latitude": "Enlem",
"longitude": "Boylam",
"name": "Entegrasyonun ad\u0131"
},
"description": "AEMET OpenData entegrasyonunu ayarlay\u0131n. API anahtar\u0131 olu\u015fturmak i\u00e7in https://opendata.aemet.es/centrodedescargas/altaUsuario adresine gidin.",
"title": "AEMET OpenData"
}
}
},
"options": {
"step": {
"init": {
"data": {
"station_updates": "AEMET hava istasyonlar\u0131ndan veri toplay\u0131n"
}
}
}
}
}

View File

@ -140,7 +140,7 @@ class WeatherUpdateCoordinator(DataUpdateCoordinator):
async def _async_update_data(self): async def _async_update_data(self):
data = {} data = {}
with async_timeout.timeout(120): async with async_timeout.timeout(120):
weather_response = await self._get_aemet_weather() weather_response = await self._get_aemet_weather()
data = self._convert_weather_response(weather_response) data = self._convert_weather_response(weather_response)
return data return data
@ -398,8 +398,7 @@ class WeatherUpdateCoordinator(DataUpdateCoordinator):
return None return None
def _convert_forecast_day(self, date, day): def _convert_forecast_day(self, date, day):
condition = self._get_condition_day(day) if not (condition := self._get_condition_day(day)):
if not condition:
return None return None
return { return {
@ -415,8 +414,7 @@ class WeatherUpdateCoordinator(DataUpdateCoordinator):
} }
def _convert_forecast_hour(self, date, day, hour): def _convert_forecast_hour(self, date, day, hour):
condition = self._get_condition(day, hour) if not (condition := self._get_condition(day, hour)):
if not condition:
return None return None
forecast_dt = date.replace(hour=hour, minute=0, second=0) forecast_dt = date.replace(hour=hour, minute=0, second=0)
@ -435,13 +433,8 @@ class WeatherUpdateCoordinator(DataUpdateCoordinator):
def _calc_precipitation(self, day, hour): def _calc_precipitation(self, day, hour):
"""Calculate the precipitation.""" """Calculate the precipitation."""
rain_value = self._get_rain(day, hour) rain_value = self._get_rain(day, hour) or 0
if not rain_value: snow_value = self._get_snow(day, hour) or 0
rain_value = 0
snow_value = self._get_snow(day, hour)
if not snow_value:
snow_value = 0
if round(rain_value + snow_value, 1) == 0: if round(rain_value + snow_value, 1) == 0:
return None return None
@ -449,13 +442,8 @@ class WeatherUpdateCoordinator(DataUpdateCoordinator):
def _calc_precipitation_prob(self, day, hour): def _calc_precipitation_prob(self, day, hour):
"""Calculate the precipitation probability (hour).""" """Calculate the precipitation probability (hour)."""
rain_value = self._get_rain_prob(day, hour) rain_value = self._get_rain_prob(day, hour) or 0
if not rain_value: snow_value = self._get_snow_prob(day, hour) or 0
rain_value = 0
snow_value = self._get_snow_prob(day, hour)
if not snow_value:
snow_value = 0
if rain_value == 0 and snow_value == 0: if rain_value == 0 and snow_value == 0:
return None return None

View File

@ -3,6 +3,7 @@
from agent import AgentError from agent import AgentError
from agent.a import Agent from agent.a import Agent
from homeassistant.const import Platform
from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import device_registry as dr from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
@ -12,7 +13,7 @@ from .const import CONNECTION, DOMAIN as AGENT_DOMAIN, SERVER_URL
ATTRIBUTION = "ispyconnect.com" ATTRIBUTION = "ispyconnect.com"
DEFAULT_BRAND = "Agent DVR by ispyconnect.com" DEFAULT_BRAND = "Agent DVR by ispyconnect.com"
FORWARDS = ["alarm_control_panel", "camera"] PLATFORMS = [Platform.ALARM_CONTROL_PANEL, Platform.CAMERA]
async def async_setup_entry(hass, config_entry): async def async_setup_entry(hass, config_entry):
@ -35,7 +36,7 @@ async def async_setup_entry(hass, config_entry):
hass.data[AGENT_DOMAIN][config_entry.entry_id] = {CONNECTION: agent_client} hass.data[AGENT_DOMAIN][config_entry.entry_id] = {CONNECTION: agent_client}
device_registry = await dr.async_get_registry(hass) device_registry = dr.async_get(hass)
device_registry.async_get_or_create( device_registry.async_get_or_create(
config_entry_id=config_entry.entry_id, config_entry_id=config_entry.entry_id,
@ -46,14 +47,16 @@ async def async_setup_entry(hass, config_entry):
sw_version=agent_client.version, sw_version=agent_client.version,
) )
hass.config_entries.async_setup_platforms(config_entry, FORWARDS) hass.config_entries.async_setup_platforms(config_entry, PLATFORMS)
return True return True
async def async_unload_entry(hass, config_entry): async def async_unload_entry(hass, config_entry):
"""Unload a config entry.""" """Unload a config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(config_entry, FORWARDS) unload_ok = await hass.config_entries.async_unload_platforms(
config_entry, PLATFORMS
)
await hass.data[AGENT_DOMAIN][config_entry.entry_id][CONNECTION].close() await hass.data[AGENT_DOMAIN][config_entry.entry_id][CONNECTION].close()

View File

@ -0,0 +1,20 @@
{
"config": {
"abort": {
"already_configured": "\u30c7\u30d0\u30a4\u30b9\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059"
},
"error": {
"already_in_progress": "\u69cb\u6210\u30d5\u30ed\u30fc\u306f\u3059\u3067\u306b\u9032\u884c\u4e2d\u3067\u3059",
"cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f"
},
"step": {
"user": {
"data": {
"host": "\u30db\u30b9\u30c8",
"port": "\u30dd\u30fc\u30c8"
},
"title": "Agent DVR\u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7"
}
}
}
}

View File

@ -10,7 +10,7 @@
"step": { "step": {
"user": { "user": {
"data": { "data": {
"host": "Ana Bilgisayar", "host": "Ana bilgisayar",
"port": "Port" "port": "Port"
}, },
"title": "Agent DVR'\u0131 kurun" "title": "Agent DVR'\u0131 kurun"

View File

@ -137,8 +137,7 @@ class AirQualityEntity(Entity):
data: dict[str, str | int | float] = {} data: dict[str, str | int | float] = {}
for prop, attr in PROP_TO_ATTR.items(): for prop, attr in PROP_TO_ATTR.items():
value = getattr(self, prop) if (value := getattr(self, prop)) is not None:
if value is not None:
data[attr] = value data[attr] = value
return data return data

View File

@ -13,7 +13,7 @@ import async_timeout
from homeassistant.components.air_quality import DOMAIN as AIR_QUALITY_PLATFORM from homeassistant.components.air_quality import DOMAIN as AIR_QUALITY_PLATFORM
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, Platform
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry from homeassistant.helpers import entity_registry
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
@ -33,7 +33,7 @@ from .const import (
NO_AIRLY_SENSORS, NO_AIRLY_SENSORS,
) )
PLATFORMS = ["sensor"] PLATFORMS = [Platform.SENSOR]
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -167,7 +167,7 @@ class AirlyDataUpdateCoordinator(DataUpdateCoordinator):
measurements = self.airly.create_measurements_session_point( measurements = self.airly.create_measurements_session_point(
self.latitude, self.longitude self.latitude, self.longitude
) )
with async_timeout.timeout(20): async with async_timeout.timeout(20):
try: try:
await measurements.update() await measurements.update()
except (AirlyError, ClientConnectorError) as error: except (AirlyError, ClientConnectorError) as error:

View File

@ -103,7 +103,7 @@ async def test_location(
measurements = airly.create_measurements_session_point( measurements = airly.create_measurements_session_point(
latitude=latitude, longitude=longitude latitude=latitude, longitude=longitude
) )
with async_timeout.timeout(10): async with async_timeout.timeout(10):
await measurements.update() await measurements.update()
current = measurements.current current = measurements.current

View File

@ -27,6 +27,7 @@ from homeassistant.const import (
TEMP_CELSIUS, TEMP_CELSIUS,
) )
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceEntryType
from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType from homeassistant.helpers.typing import StateType
@ -154,7 +155,7 @@ class AirlySensor(CoordinatorEntity, SensorEntity):
"""Initialize.""" """Initialize."""
super().__init__(coordinator) super().__init__(coordinator)
self._attr_device_info = DeviceInfo( self._attr_device_info = DeviceInfo(
entry_type="service", entry_type=DeviceEntryType.SERVICE,
identifiers={(DOMAIN, f"{coordinator.latitude}-{coordinator.longitude}")}, identifiers={(DOMAIN, f"{coordinator.latitude}-{coordinator.longitude}")},
manufacturer=MANUFACTURER, manufacturer=MANUFACTURER,
name=DEFAULT_NAME, name=DEFAULT_NAME,

View File

@ -1,6 +1,10 @@
{ {
"config": { "config": {
"abort": {
"already_configured": "\u041c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435\u0442\u043e \u0432\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u043e"
},
"error": { "error": {
"invalid_api_key": "\u041d\u0435\u0432\u0430\u043b\u0438\u0434\u0435\u043d API \u043a\u043b\u044e\u0447",
"wrong_location": "\u0412 \u0442\u0430\u0437\u0438 \u043e\u0431\u043b\u0430\u0441\u0442 \u043d\u044f\u043c\u0430 \u0438\u0437\u043c\u0435\u0440\u0432\u0430\u0442\u0435\u043b\u043d\u0438 \u0441\u0442\u0430\u043d\u0446\u0438\u0438 \u043d\u0430 Airly." "wrong_location": "\u0412 \u0442\u0430\u0437\u0438 \u043e\u0431\u043b\u0430\u0441\u0442 \u043d\u044f\u043c\u0430 \u0438\u0437\u043c\u0435\u0440\u0432\u0430\u0442\u0435\u043b\u043d\u0438 \u0441\u0442\u0430\u043d\u0446\u0438\u0438 \u043d\u0430 Airly."
}, },
"step": { "step": {

View File

@ -0,0 +1,30 @@
{
"config": {
"abort": {
"already_configured": "\u30ed\u30b1\u30fc\u30b7\u30e7\u30f3\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059"
},
"error": {
"invalid_api_key": "\u7121\u52b9\u306aAPI\u30ad\u30fc",
"wrong_location": "\u3053\u306e\u30a8\u30ea\u30a2\u306b\u3001Airly\u306e\u6e2c\u5b9a\u30b9\u30c6\u30fc\u30b7\u30e7\u30f3\u306f\u3042\u308a\u307e\u305b\u3093\u3002"
},
"step": {
"user": {
"data": {
"api_key": "API\u30ad\u30fc",
"latitude": "\u7def\u5ea6",
"longitude": "\u7d4c\u5ea6",
"name": "\u540d\u524d"
},
"description": "Airly\u306e\u7a7a\u6c17\u54c1\u8cea\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3057\u307e\u3059\u3002 API\u30ad\u30fc\u3092\u751f\u6210\u3059\u308b\u306b\u306f\u3001https://developer.airly.eu/register \u306b\u30a2\u30af\u30bb\u30b9\u3057\u3066\u304f\u3060\u3055\u3044",
"title": "Airly"
}
}
},
"system_health": {
"info": {
"can_reach_server": "Airly\u30b5\u30fc\u30d0\u30fc\u306b\u5230\u9054",
"requests_per_day": "1\u65e5\u3042\u305f\u308a\u306e\u8a31\u53ef\u3055\u308c\u305f\u30ea\u30af\u30a8\u30b9\u30c8",
"requests_remaining": "\u6b8b\u308a\u306e\u8a31\u53ef\u3055\u308c\u305f\u30ea\u30af\u30a8\u30b9\u30c8"
}
}
}

View File

@ -4,21 +4,27 @@
"already_configured": "Konum zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" "already_configured": "Konum zaten yap\u0131land\u0131r\u0131lm\u0131\u015f"
}, },
"error": { "error": {
"invalid_api_key": "Ge\u00e7ersiz API anahtar\u0131" "invalid_api_key": "Ge\u00e7ersiz API anahtar\u0131",
"wrong_location": "Bu b\u00f6lgede Airly \u00f6l\u00e7\u00fcm istasyonu yok."
}, },
"step": { "step": {
"user": { "user": {
"data": { "data": {
"api_key": "API Anahtar\u0131", "api_key": "API Anahtar\u0131",
"latitude": "Enlem", "latitude": "Enlem",
"longitude": "Boylam" "longitude": "Boylam",
} "name": "Ad"
},
"description": "Airly hava kalitesi entegrasyonunu ayarlay\u0131n. API anahtar\u0131 olu\u015fturmak i\u00e7in https://developer.airly.eu/register adresine gidin.",
"title": "Airly"
} }
} }
}, },
"system_health": { "system_health": {
"info": { "info": {
"can_reach_server": "Airly sunucusuna eri\u015fin" "can_reach_server": "Airly sunucusuna eri\u015fin",
"requests_per_day": "G\u00fcnl\u00fck izin verilen istek say\u0131s\u0131",
"requests_remaining": "Kalan izin verilen istekler"
} }
} }
} }

View File

@ -8,7 +8,13 @@ from pyairnow.conv import aqi_to_concentration
from pyairnow.errors import AirNowError from pyairnow.errors import AirNowError
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_RADIUS from homeassistant.const import (
CONF_API_KEY,
CONF_LATITUDE,
CONF_LONGITUDE,
CONF_RADIUS,
Platform,
)
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
@ -33,7 +39,7 @@ from .const import (
) )
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
PLATFORMS = ["sensor"] PLATFORMS = [Platform.SENSOR]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:

View File

@ -86,7 +86,8 @@ class AirNowSensor(CoordinatorEntity, SensorEntity):
@property @property
def native_value(self): def native_value(self):
"""Return the state.""" """Return the state."""
self._state = self.coordinator.data[self.entity_description.key] self._state = self.coordinator.data.get(self.entity_description.key)
return self._state return self._state
@property @property

View File

@ -0,0 +1,26 @@
{
"config": {
"abort": {
"already_configured": "\u30c7\u30d0\u30a4\u30b9\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059"
},
"error": {
"cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f",
"invalid_auth": "\u7121\u52b9\u306a\u8a8d\u8a3c",
"invalid_location": "\u305d\u306e\u5834\u6240\u306b\u5bfe\u3059\u308b\u7d50\u679c\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093",
"unknown": "\u4e88\u671f\u3057\u306a\u3044\u30a8\u30e9\u30fc"
},
"step": {
"user": {
"data": {
"api_key": "API\u30ad\u30fc",
"latitude": "\u7def\u5ea6",
"longitude": "\u7d4c\u5ea6",
"radius": "\u30b9\u30c6\u30fc\u30b7\u30e7\u30f3\u534a\u5f84(\u30de\u30a4\u30eb; \u30aa\u30d7\u30b7\u30e7\u30f3)"
},
"description": "AirNow\u306e\u7a7a\u6c17\u54c1\u8cea\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3057\u307e\u3059\u3002 API\u30ad\u30fc\u3092\u751f\u6210\u3059\u308b\u306b\u306f\u3001https://docs.airnowapi.org/account/request/ \u306b\u30a2\u30af\u30bb\u30b9\u3057\u3066\u304f\u3060\u3055\u3044",
"title": "AirNow"
}
}
},
"title": "AirNow"
}

View File

@ -17,6 +17,7 @@
"longitude": "Boylam", "longitude": "Boylam",
"radius": "\u0130stasyon Yar\u0131\u00e7ap\u0131 (mil; iste\u011fe ba\u011fl\u0131)" "radius": "\u0130stasyon Yar\u0131\u00e7ap\u0131 (mil; iste\u011fe ba\u011fl\u0131)"
}, },
"description": "AirNow hava kalitesi entegrasyonunu ayarlay\u0131n. API anahtar\u0131 olu\u015fturmak i\u00e7in https://docs.airnowapi.org/account/request/ adresine gidin.",
"title": "AirNow" "title": "AirNow"
} }
} }

View File

@ -7,6 +7,7 @@ import logging
from airthings import Airthings, AirthingsError from airthings import Airthings, AirthingsError
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
@ -15,7 +16,7 @@ from .const import CONF_ID, CONF_SECRET, DOMAIN
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
PLATFORMS: list[str] = ["sensor"] PLATFORMS: list[Platform] = [Platform.SENSOR]
SCAN_INTERVAL = timedelta(minutes=6) SCAN_INTERVAL = timedelta(minutes=6)

View File

@ -3,7 +3,7 @@
"name": "Airthings", "name": "Airthings",
"config_flow": true, "config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/airthings", "documentation": "https://www.home-assistant.io/integrations/airthings",
"requirements": ["airthings_cloud==0.0.1"], "requirements": ["airthings_cloud==0.1.0"],
"codeowners": [ "codeowners": [
"@danielhiversen" "@danielhiversen"
], ],

View File

@ -4,9 +4,10 @@ from __future__ import annotations
from airthings import AirthingsDevice from airthings import AirthingsDevice
from homeassistant.components.sensor import ( from homeassistant.components.sensor import (
STATE_CLASS_MEASUREMENT, SensorDeviceClass,
SensorEntity, SensorEntity,
SensorEntityDescription, SensorEntityDescription,
SensorStateClass,
StateType, StateType,
) )
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
@ -14,14 +15,6 @@ from homeassistant.const import (
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
CONCENTRATION_PARTS_PER_BILLION, CONCENTRATION_PARTS_PER_BILLION,
CONCENTRATION_PARTS_PER_MILLION, CONCENTRATION_PARTS_PER_MILLION,
DEVICE_CLASS_BATTERY,
DEVICE_CLASS_CO2,
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_PM1,
DEVICE_CLASS_PM25,
DEVICE_CLASS_PRESSURE,
DEVICE_CLASS_SIGNAL_STRENGTH,
DEVICE_CLASS_TEMPERATURE,
ENTITY_CATEGORY_DIAGNOSTIC, ENTITY_CATEGORY_DIAGNOSTIC,
PERCENTAGE, PERCENTAGE,
PRESSURE_MBAR, PRESSURE_MBAR,
@ -46,32 +39,32 @@ SENSORS: dict[str, SensorEntityDescription] = {
), ),
"temp": SensorEntityDescription( "temp": SensorEntityDescription(
key="temp", key="temp",
device_class=DEVICE_CLASS_TEMPERATURE, device_class=SensorDeviceClass.TEMPERATURE,
native_unit_of_measurement=TEMP_CELSIUS, native_unit_of_measurement=TEMP_CELSIUS,
name="Temperature", name="Temperature",
), ),
"humidity": SensorEntityDescription( "humidity": SensorEntityDescription(
key="humidity", key="humidity",
device_class=DEVICE_CLASS_HUMIDITY, device_class=SensorDeviceClass.HUMIDITY,
native_unit_of_measurement=PERCENTAGE, native_unit_of_measurement=PERCENTAGE,
name="Humidity", name="Humidity",
), ),
"pressure": SensorEntityDescription( "pressure": SensorEntityDescription(
key="pressure", key="pressure",
device_class=DEVICE_CLASS_PRESSURE, device_class=SensorDeviceClass.PRESSURE,
native_unit_of_measurement=PRESSURE_MBAR, native_unit_of_measurement=PRESSURE_MBAR,
name="Pressure", name="Pressure",
), ),
"battery": SensorEntityDescription( "battery": SensorEntityDescription(
key="battery", key="battery",
device_class=DEVICE_CLASS_BATTERY, device_class=SensorDeviceClass.BATTERY,
native_unit_of_measurement=PERCENTAGE, native_unit_of_measurement=PERCENTAGE,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC, entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
name="Battery", name="Battery",
), ),
"co2": SensorEntityDescription( "co2": SensorEntityDescription(
key="co2", key="co2",
device_class=DEVICE_CLASS_CO2, device_class=SensorDeviceClass.CO2,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION, native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
name="CO2", name="CO2",
), ),
@ -96,7 +89,7 @@ SENSORS: dict[str, SensorEntityDescription] = {
"rssi": SensorEntityDescription( "rssi": SensorEntityDescription(
key="rssi", key="rssi",
native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS, native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS,
device_class=DEVICE_CLASS_SIGNAL_STRENGTH, device_class=SensorDeviceClass.SIGNAL_STRENGTH,
name="RSSI", name="RSSI",
entity_registry_enabled_default=False, entity_registry_enabled_default=False,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC, entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
@ -104,13 +97,13 @@ SENSORS: dict[str, SensorEntityDescription] = {
"pm1": SensorEntityDescription( "pm1": SensorEntityDescription(
key="pm1", key="pm1",
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
device_class=DEVICE_CLASS_PM1, device_class=SensorDeviceClass.PM1,
name="PM1", name="PM1",
), ),
"pm25": SensorEntityDescription( "pm25": SensorEntityDescription(
key="pm25", key="pm25",
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
device_class=DEVICE_CLASS_PM25, device_class=SensorDeviceClass.PM25,
name="PM25", name="PM25",
), ),
} }
@ -140,7 +133,7 @@ async def async_setup_entry(
class AirthingsHeaterEnergySensor(CoordinatorEntity, SensorEntity): class AirthingsHeaterEnergySensor(CoordinatorEntity, SensorEntity):
"""Representation of a Airthings Sensor device.""" """Representation of a Airthings Sensor device."""
_attr_state_class = STATE_CLASS_MEASUREMENT _attr_state_class = SensorStateClass.MEASUREMENT
def __init__( def __init__(
self, self,

View File

@ -0,0 +1,21 @@
{
"config": {
"abort": {
"already_configured": "Akun sudah dikonfigurasi"
},
"error": {
"cannot_connect": "Gagal terhubung",
"invalid_auth": "Autentikasi tidak valid",
"unknown": "Kesalahan yang tidak diharapkan"
},
"step": {
"user": {
"data": {
"description": "Masuk di {url} untuk menemukan kredensial Anda",
"id": "ID",
"secret": "Kode Rahasia"
}
}
}
}
}

View File

@ -0,0 +1,21 @@
{
"config": {
"abort": {
"already_configured": "\u30a2\u30ab\u30a6\u30f3\u30c8\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059"
},
"error": {
"cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f",
"invalid_auth": "\u7121\u52b9\u306a\u8a8d\u8a3c",
"unknown": "\u4e88\u671f\u3057\u306a\u3044\u30a8\u30e9\u30fc"
},
"step": {
"user": {
"data": {
"description": "{url} \u306b\u30ed\u30b0\u30a4\u30f3\u3057\u3066\u3001\u8cc7\u683c\u60c5\u5831\u3092\u78ba\u8a8d\u3057\u3066\u304f\u3060\u3055\u3044",
"id": "ID",
"secret": "\u30b7\u30fc\u30af\u30ec\u30c3\u30c8"
}
}
}
}
}

View File

@ -11,6 +11,7 @@
"step": { "step": {
"user": { "user": {
"data": { "data": {
"description": "Zaloguj si\u0119 pod {url}, aby znale\u017a\u0107 swoje dane uwierzytelniaj\u0105ce",
"id": "ID", "id": "ID",
"secret": "Sekret" "secret": "Sekret"
} }

View File

@ -0,0 +1,21 @@
{
"config": {
"abort": {
"already_configured": "Hesap zaten yap\u0131land\u0131r\u0131lm\u0131\u015f"
},
"error": {
"cannot_connect": "Ba\u011flanma hatas\u0131",
"invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama",
"unknown": "Beklenmeyen hata"
},
"step": {
"user": {
"data": {
"description": "Kimlik bilgilerinizi bulmak i\u00e7in {url} adresinden giri\u015f yap\u0131n",
"id": "ID",
"secret": "Gizli"
}
}
}
}
}

View File

@ -6,7 +6,7 @@ from airtouch4pyapi.airtouch import AirTouchStatus
from homeassistant.components.climate import SCAN_INTERVAL from homeassistant.components.climate import SCAN_INTERVAL
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST from homeassistant.const import CONF_HOST, Platform
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
@ -15,7 +15,7 @@ from .const import DOMAIN
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
PLATFORMS = ["climate"] PLATFORMS = [Platform.CLIMATE]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:

View File

@ -4,13 +4,15 @@
"already_configured": "Perangkat sudah dikonfigurasi" "already_configured": "Perangkat sudah dikonfigurasi"
}, },
"error": { "error": {
"cannot_connect": "Gagal terhubung" "cannot_connect": "Gagal terhubung",
"no_units": "Tidak dapat menemukan Grup AirTouch 4 apa pun."
}, },
"step": { "step": {
"user": { "user": {
"data": { "data": {
"host": "Host" "host": "Host"
} },
"title": "Siapkan detail koneksi AirTouch 4 Anda."
} }
} }
} }

View File

@ -0,0 +1,19 @@
{
"config": {
"abort": {
"already_configured": "\u30c7\u30d0\u30a4\u30b9\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059"
},
"error": {
"cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f",
"no_units": "AirTouch 4 Groups\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3067\u3057\u305f\u3002"
},
"step": {
"user": {
"data": {
"host": "\u30db\u30b9\u30c8"
},
"title": "AirTouch 4\u63a5\u7d9a\u306e\u8a73\u7d30\u8a2d\u5b9a\u3092\u3057\u307e\u3059\u3002"
}
}
}
}

View File

@ -0,0 +1,19 @@
{
"config": {
"abort": {
"already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f"
},
"error": {
"cannot_connect": "Ba\u011flanma hatas\u0131",
"no_units": "Herhangi bir AirTouch 4 Grubu bulunamad\u0131."
},
"step": {
"user": {
"data": {
"host": "Ana bilgisayar"
},
"title": "AirTouch 4 ba\u011flant\u0131 ayr\u0131nt\u0131lar\u0131n\u0131z\u0131 ayarlay\u0131n."
}
}
}
}

View File

@ -16,7 +16,6 @@ from pyairvisual.errors import (
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ( from homeassistant.const import (
ATTR_ATTRIBUTION,
CONF_API_KEY, CONF_API_KEY,
CONF_IP_ADDRESS, CONF_IP_ADDRESS,
CONF_LATITUDE, CONF_LATITUDE,
@ -24,6 +23,7 @@ from homeassistant.const import (
CONF_PASSWORD, CONF_PASSWORD,
CONF_SHOW_ON_MAP, CONF_SHOW_ON_MAP,
CONF_STATE, CONF_STATE,
Platform,
) )
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import ConfigEntryAuthFailed from homeassistant.exceptions import ConfigEntryAuthFailed
@ -52,7 +52,7 @@ from .const import (
LOGGER, LOGGER,
) )
PLATFORMS = ["sensor"] PLATFORMS = [Platform.SENSOR]
DEFAULT_ATTRIBUTION = "Data provided by AirVisual" DEFAULT_ATTRIBUTION = "Data provided by AirVisual"
DEFAULT_NODE_PRO_UPDATE_INTERVAL = timedelta(minutes=1) DEFAULT_NODE_PRO_UPDATE_INTERVAL = timedelta(minutes=1)
@ -191,9 +191,6 @@ def _standardize_node_pro_config_entry(hass: HomeAssistant, entry: ConfigEntry)
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up AirVisual as config entry.""" """Set up AirVisual as config entry."""
hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][entry.entry_id] = {}
if CONF_API_KEY in entry.data: if CONF_API_KEY in entry.data:
_standardize_geography_config_entry(hass, entry) _standardize_geography_config_entry(hass, entry)
@ -272,7 +269,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
) )
await coordinator.async_config_entry_first_refresh() await coordinator.async_config_entry_first_refresh()
hass.data[DOMAIN][entry.entry_id][DATA_COORDINATOR] = coordinator hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][entry.entry_id] = {DATA_COORDINATOR: coordinator}
# Reassess the interval between 2 server requests # Reassess the interval between 2 server requests
if CONF_API_KEY in entry.data: if CONF_API_KEY in entry.data:
@ -356,7 +354,7 @@ class AirVisualEntity(CoordinatorEntity):
"""Initialize.""" """Initialize."""
super().__init__(coordinator) super().__init__(coordinator)
self._attr_extra_state_attributes = {ATTR_ATTRIBUTION: DEFAULT_ATTRIBUTION} self._attr_extra_state_attributes = {}
self._entry = entry self._entry = entry
self.entity_description = description self.entity_description = description

View File

@ -91,6 +91,7 @@ class AirVisualFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
self, user_input: dict[str, str], integration_type: str self, user_input: dict[str, str], integration_type: str
) -> FlowResult: ) -> FlowResult:
"""Validate a Cloud API key.""" """Validate a Cloud API key."""
errors = {}
websession = aiohttp_client.async_get_clientsession(self.hass) websession = aiohttp_client.async_get_clientsession(self.hass)
cloud_api = CloudAPI(user_input[CONF_API_KEY], session=websession) cloud_api = CloudAPI(user_input[CONF_API_KEY], session=websession)
@ -117,27 +118,20 @@ class AirVisualFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
try: try:
await coro await coro
except InvalidKeyError: except InvalidKeyError:
return self.async_show_form( errors[CONF_API_KEY] = "invalid_api_key"
step_id=error_step,
data_schema=error_schema,
errors={CONF_API_KEY: "invalid_api_key"},
)
except NotFoundError: except NotFoundError:
return self.async_show_form( errors[CONF_CITY] = "location_not_found"
step_id=error_step,
data_schema=error_schema,
errors={CONF_CITY: "location_not_found"},
)
except AirVisualError as err: except AirVisualError as err:
LOGGER.error(err) LOGGER.error(err)
return self.async_show_form( errors["base"] = "unknown"
step_id=error_step,
data_schema=error_schema,
errors={"base": "unknown"},
)
valid_keys.add(user_input[CONF_API_KEY]) valid_keys.add(user_input[CONF_API_KEY])
if errors:
return self.async_show_form(
step_id=error_step, data_schema=error_schema, errors=errors
)
existing_entry = await self.async_set_unique_id(self._geo_id) existing_entry = await self.async_set_unique_id(self._geo_id)
if existing_entry: if existing_entry:
self.hass.config_entries.async_update_entry(existing_entry, data=user_input) self.hass.config_entries.async_update_entry(existing_entry, data=user_input)

View File

@ -2,9 +2,9 @@
from __future__ import annotations from __future__ import annotations
from homeassistant.components.sensor import ( from homeassistant.components.sensor import (
STATE_CLASS_MEASUREMENT,
SensorEntity, SensorEntity,
SensorEntityDescription, SensorEntityDescription,
SensorStateClass,
) )
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ( from homeassistant.const import (
@ -81,7 +81,7 @@ GEOGRAPHY_SENSOR_DESCRIPTIONS = (
name="Air Quality Index", name="Air Quality Index",
device_class=DEVICE_CLASS_AQI, device_class=DEVICE_CLASS_AQI,
native_unit_of_measurement="AQI", native_unit_of_measurement="AQI",
state_class=STATE_CLASS_MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
), ),
SensorEntityDescription( SensorEntityDescription(
key=SENSOR_KIND_POLLUTANT, key=SENSOR_KIND_POLLUTANT,
@ -98,7 +98,7 @@ NODE_PRO_SENSOR_DESCRIPTIONS = (
name="Air Quality Index", name="Air Quality Index",
device_class=DEVICE_CLASS_AQI, device_class=DEVICE_CLASS_AQI,
native_unit_of_measurement="AQI", native_unit_of_measurement="AQI",
state_class=STATE_CLASS_MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
), ),
SensorEntityDescription( SensorEntityDescription(
key=SENSOR_KIND_BATTERY_LEVEL, key=SENSOR_KIND_BATTERY_LEVEL,
@ -112,7 +112,7 @@ NODE_PRO_SENSOR_DESCRIPTIONS = (
name="C02", name="C02",
device_class=DEVICE_CLASS_CO2, device_class=DEVICE_CLASS_CO2,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION, native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
state_class=STATE_CLASS_MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
), ),
SensorEntityDescription( SensorEntityDescription(
key=SENSOR_KIND_HUMIDITY, key=SENSOR_KIND_HUMIDITY,
@ -125,35 +125,35 @@ NODE_PRO_SENSOR_DESCRIPTIONS = (
name="PM 0.1", name="PM 0.1",
device_class=DEVICE_CLASS_PM1, device_class=DEVICE_CLASS_PM1,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=STATE_CLASS_MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
), ),
SensorEntityDescription( SensorEntityDescription(
key=SENSOR_KIND_PM_1_0, key=SENSOR_KIND_PM_1_0,
name="PM 1.0", name="PM 1.0",
device_class=DEVICE_CLASS_PM10, device_class=DEVICE_CLASS_PM10,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=STATE_CLASS_MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
), ),
SensorEntityDescription( SensorEntityDescription(
key=SENSOR_KIND_PM_2_5, key=SENSOR_KIND_PM_2_5,
name="PM 2.5", name="PM 2.5",
device_class=DEVICE_CLASS_PM25, device_class=DEVICE_CLASS_PM25,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=STATE_CLASS_MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
), ),
SensorEntityDescription( SensorEntityDescription(
key=SENSOR_KIND_TEMPERATURE, key=SENSOR_KIND_TEMPERATURE,
name="Temperature", name="Temperature",
device_class=DEVICE_CLASS_TEMPERATURE, device_class=DEVICE_CLASS_TEMPERATURE,
native_unit_of_measurement=TEMP_CELSIUS, native_unit_of_measurement=TEMP_CELSIUS,
state_class=STATE_CLASS_MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
), ),
SensorEntityDescription( SensorEntityDescription(
key=SENSOR_KIND_VOC, key=SENSOR_KIND_VOC,
name="VOC", name="VOC",
device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS, device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION, native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
state_class=STATE_CLASS_MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
), ),
) )

View File

@ -9,8 +9,14 @@
"invalid_api_key": "\u041d\u0435\u0432\u0430\u043b\u0438\u0434\u0435\u043d API \u043a\u043b\u044e\u0447" "invalid_api_key": "\u041d\u0435\u0432\u0430\u043b\u0438\u0434\u0435\u043d API \u043a\u043b\u044e\u0447"
}, },
"step": { "step": {
"geography_by_coords": {
"data": {
"api_key": "API \u043a\u043b\u044e\u0447"
}
},
"geography_by_name": { "geography_by_name": {
"data": { "data": {
"api_key": "API \u043a\u043b\u044e\u0447",
"city": "\u0413\u0440\u0430\u0434", "city": "\u0413\u0440\u0430\u0434",
"country": "\u0421\u0442\u0440\u0430\u043d\u0430" "country": "\u0421\u0442\u0440\u0430\u043d\u0430"
} }

View File

@ -0,0 +1,63 @@
{
"config": {
"abort": {
"already_configured": "\u30ed\u30b1\u30fc\u30b7\u30e7\u30f3\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059\u3002\u307e\u305f\u306f\u3001Node/Pro ID\u306f\u65e2\u306b\u767b\u9332\u3055\u308c\u3066\u3044\u307e\u3059\u3002",
"reauth_successful": "\u518d\u8a8d\u8a3c\u306b\u6210\u529f\u3057\u307e\u3057\u305f"
},
"error": {
"cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f",
"general_error": "\u4e88\u671f\u3057\u306a\u3044\u30a8\u30e9\u30fc",
"invalid_api_key": "\u7121\u52b9\u306aAPI\u30ad\u30fc",
"location_not_found": "\u30ed\u30b1\u30fc\u30b7\u30e7\u30f3\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093"
},
"step": {
"geography_by_coords": {
"data": {
"api_key": "API\u30ad\u30fc",
"latitude": "\u7def\u5ea6",
"longitude": "\u7d4c\u5ea6"
},
"description": "AirVisual cloud API\u3092\u4f7f\u7528\u3057\u3066\u3001\u7def\u5ea6/\u7d4c\u5ea6\u3092\u76e3\u8996\u3057\u307e\u3059\u3002",
"title": "Geography\u306e\u8a2d\u5b9a"
},
"geography_by_name": {
"data": {
"api_key": "API\u30ad\u30fc",
"city": "\u90fd\u5e02",
"country": "\u56fd",
"state": "\u5dde"
},
"description": "AirVisual cloud API\u3092\u4f7f\u7528\u3057\u3066\u3001\u90fd\u5e02/\u5dde/\u56fd\u3092\u76e3\u8996\u3057\u307e\u3059\u3002",
"title": "Geography\u306e\u8a2d\u5b9a"
},
"node_pro": {
"data": {
"ip_address": "\u30db\u30b9\u30c8",
"password": "\u30d1\u30b9\u30ef\u30fc\u30c9"
},
"description": "\u500b\u4eba\u306eAirVisual\u30e6\u30cb\u30c3\u30c8\u3092\u76e3\u8996\u3057\u307e\u3059\u3002\u30d1\u30b9\u30ef\u30fc\u30c9\u306f\u3001\u672c\u4f53\u306eUI\u304b\u3089\u53d6\u5f97\u3067\u304d\u307e\u3059\u3002",
"title": "AirVisual Node/Pro\u306e\u8a2d\u5b9a"
},
"reauth_confirm": {
"data": {
"api_key": "API\u30ad\u30fc"
},
"title": "AirVisual\u3092\u518d\u8a8d\u8a3c"
},
"user": {
"description": "\u76e3\u8996\u3057\u305f\u3044\u3001AirVisual\u306e\u30c7\u30fc\u30bf\u306e\u7a2e\u985e\u3092\u9078\u629e\u3057\u307e\u3059\u3002",
"title": "AirVisual\u306e\u8a2d\u5b9a"
}
}
},
"options": {
"step": {
"init": {
"data": {
"show_on_map": "\u76e3\u8996\u5bfe\u8c61\u306e\u5730\u7406\u3092\u5730\u56f3\u306b\u8868\u793a"
},
"title": "AirVisual\u306e\u8a2d\u5b9a"
}
}
}
}

View File

@ -1,9 +1,20 @@
{ {
"state": { "state": {
"airvisual__pollutant_label": { "airvisual__pollutant_label": {
"co": "\u0412\u044a\u0433\u043b\u0435\u0440\u043e\u0434\u0435\u043d \u043e\u043a\u0438\u0441",
"n2": "\u0410\u0437\u043e\u0442\u0435\u043d \u0434\u0438\u043e\u043a\u0441\u0438\u0434",
"o3": "\u041e\u0437\u043e\u043d", "o3": "\u041e\u0437\u043e\u043d",
"p1": "PM10", "p1": "PM10",
"p2": "PM2.5" "p2": "PM2.5",
"s2": "\u0421\u0435\u0440\u0435\u043d \u0434\u0438\u043e\u043a\u0441\u0438\u0434"
},
"airvisual__pollutant_level": {
"good": "\u0414\u043e\u0431\u0440\u043e",
"hazardous": "\u041e\u043f\u0430\u0441\u043d\u043e",
"moderate": "\u0423\u043c\u0435\u0440\u0435\u043d\u043e",
"unhealthy": "\u041d\u0435\u0437\u0434\u0440\u0430\u0432\u043e\u0441\u043b\u043e\u0432\u043d\u043e",
"unhealthy_sensitive": "\u041d\u0435\u0437\u0434\u0440\u0430\u0432\u043e\u0441\u043b\u043e\u0432\u043d\u043e \u0437\u0430 \u0447\u0443\u0432\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u043d\u0438 \u0433\u0440\u0443\u043f\u0438",
"very_unhealthy": "\u041c\u043d\u043e\u0433\u043e \u043d\u0435\u0437\u0434\u0440\u0430\u0432\u043e\u0441\u043b\u043e\u0432\u043d\u043e"
} }
} }
} }

View File

@ -0,0 +1,20 @@
{
"state": {
"airvisual__pollutant_label": {
"co": "Karbon monoksida",
"n2": "Nitrogen dioksida",
"o3": "Ozon",
"p1": "PM10",
"p2": "PM2.5",
"s2": "Sulfur Dioksida"
},
"airvisual__pollutant_level": {
"good": "Bagus",
"hazardous": "Berbahaya",
"moderate": "Sedang",
"unhealthy": "Tidak sehat",
"unhealthy_sensitive": "Tidak sehat untuk kelompok sensitif",
"very_unhealthy": "Sangat tidak sehat"
}
}
}

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