mirror of
https://github.com/home-assistant/core.git
synced 2025-07-28 23:57:06 +00:00
Merge pull request #53930 from home-assistant/rc
This commit is contained in:
commit
f0f4c13cbe
65
.coveragerc
65
.coveragerc
@ -20,6 +20,8 @@ omit =
|
||||
homeassistant/components/acmeda/helpers.py
|
||||
homeassistant/components/acmeda/hub.py
|
||||
homeassistant/components/acmeda/sensor.py
|
||||
homeassistant/components/adax/__init__.py
|
||||
homeassistant/components/adax/climate.py
|
||||
homeassistant/components/adguard/__init__.py
|
||||
homeassistant/components/adguard/const.py
|
||||
homeassistant/components/adguard/sensor.py
|
||||
@ -35,7 +37,6 @@ omit =
|
||||
homeassistant/components/airnow/__init__.py
|
||||
homeassistant/components/airnow/sensor.py
|
||||
homeassistant/components/airvisual/__init__.py
|
||||
homeassistant/components/airvisual/air_quality.py
|
||||
homeassistant/components/airvisual/sensor.py
|
||||
homeassistant/components/aladdin_connect/*
|
||||
homeassistant/components/alarmdecoder/__init__.py
|
||||
@ -105,6 +106,8 @@ omit =
|
||||
homeassistant/components/bloomsky/*
|
||||
homeassistant/components/bluesound/*
|
||||
homeassistant/components/bluetooth_tracker/*
|
||||
homeassistant/components/bme280/__init__.py
|
||||
homeassistant/components/bme280/const.py
|
||||
homeassistant/components/bme280/sensor.py
|
||||
homeassistant/components/bme680/sensor.py
|
||||
homeassistant/components/bmp280/sensor.py
|
||||
@ -131,9 +134,7 @@ omit =
|
||||
homeassistant/components/brottsplatskartan/sensor.py
|
||||
homeassistant/components/browser/*
|
||||
homeassistant/components/brunt/cover.py
|
||||
homeassistant/components/bsblan/__init__.py
|
||||
homeassistant/components/bsblan/climate.py
|
||||
homeassistant/components/bsblan/const.py
|
||||
homeassistant/components/bt_home_hub_5/device_tracker.py
|
||||
homeassistant/components/bt_smarthub/device_tracker.py
|
||||
homeassistant/components/buienradar/sensor.py
|
||||
@ -153,7 +154,6 @@ omit =
|
||||
homeassistant/components/clicksend/notify.py
|
||||
homeassistant/components/clicksend_tts/notify.py
|
||||
homeassistant/components/cmus/media_player.py
|
||||
homeassistant/components/co2signal/*
|
||||
homeassistant/components/coinbase/sensor.py
|
||||
homeassistant/components/comed_hourly_pricing/sensor.py
|
||||
homeassistant/components/comfoconnect/fan.py
|
||||
@ -275,6 +275,7 @@ omit =
|
||||
homeassistant/components/esphome/fan.py
|
||||
homeassistant/components/esphome/light.py
|
||||
homeassistant/components/esphome/number.py
|
||||
homeassistant/components/esphome/select.py
|
||||
homeassistant/components/esphome/sensor.py
|
||||
homeassistant/components/esphome/switch.py
|
||||
homeassistant/components/essent/sensor.py
|
||||
@ -320,7 +321,8 @@ omit =
|
||||
homeassistant/components/flick_electric/const.py
|
||||
homeassistant/components/flick_electric/sensor.py
|
||||
homeassistant/components/flock/notify.py
|
||||
homeassistant/components/flume/*
|
||||
homeassistant/components/flume/__init__.py
|
||||
homeassistant/components/flume/sensor.py
|
||||
homeassistant/components/flunearyou/__init__.py
|
||||
homeassistant/components/flunearyou/sensor.py
|
||||
homeassistant/components/flux_led/light.py
|
||||
@ -348,7 +350,6 @@ omit =
|
||||
homeassistant/components/fritzbox_callmonitor/const.py
|
||||
homeassistant/components/fritzbox_callmonitor/base.py
|
||||
homeassistant/components/fritzbox_callmonitor/sensor.py
|
||||
homeassistant/components/fritzbox_netmonitor/sensor.py
|
||||
homeassistant/components/fronius/sensor.py
|
||||
homeassistant/components/frontier_silicon/media_player.py
|
||||
homeassistant/components/futurenow/light.py
|
||||
@ -356,12 +357,9 @@ omit =
|
||||
homeassistant/components/garages_amsterdam/__init__.py
|
||||
homeassistant/components/garages_amsterdam/binary_sensor.py
|
||||
homeassistant/components/garages_amsterdam/sensor.py
|
||||
homeassistant/components/garmin_connect/__init__.py
|
||||
homeassistant/components/garmin_connect/const.py
|
||||
homeassistant/components/garmin_connect/sensor.py
|
||||
homeassistant/components/garmin_connect/alarm_util.py
|
||||
homeassistant/components/gc100/*
|
||||
homeassistant/components/geniushub/*
|
||||
homeassistant/components/generic_hygrostat/*
|
||||
homeassistant/components/github/sensor.py
|
||||
homeassistant/components/gitlab_ci/sensor.py
|
||||
homeassistant/components/gitter/sensor.py
|
||||
@ -372,6 +370,7 @@ omit =
|
||||
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_cloud/tts.py
|
||||
@ -398,10 +397,6 @@ omit =
|
||||
homeassistant/components/habitica/const.py
|
||||
homeassistant/components/habitica/sensor.py
|
||||
homeassistant/components/hangouts/*
|
||||
homeassistant/components/hangouts/__init__.py
|
||||
homeassistant/components/hangouts/const.py
|
||||
homeassistant/components/hangouts/hangouts_bot.py
|
||||
homeassistant/components/hangouts/hangups_utils.py
|
||||
homeassistant/components/harman_kardon_avr/media_player.py
|
||||
homeassistant/components/harmony/const.py
|
||||
homeassistant/components/harmony/data.py
|
||||
@ -415,7 +410,8 @@ omit =
|
||||
homeassistant/components/heatmiser/climate.py
|
||||
homeassistant/components/hikvision/binary_sensor.py
|
||||
homeassistant/components/hikvisioncam/switch.py
|
||||
homeassistant/components/hisense_aehw4a1/*
|
||||
homeassistant/components/hisense_aehw4a1/__init__.py
|
||||
homeassistant/components/hisense_aehw4a1/climate.py
|
||||
homeassistant/components/hitron_coda/device_tracker.py
|
||||
homeassistant/components/hive/__init__.py
|
||||
homeassistant/components/hive/climate.py
|
||||
@ -426,15 +422,18 @@ omit =
|
||||
homeassistant/components/hive/water_heater.py
|
||||
homeassistant/components/hlk_sw16/__init__.py
|
||||
homeassistant/components/hlk_sw16/switch.py
|
||||
homeassistant/components/home_connect/*
|
||||
homeassistant/components/home_connect/__init__.py
|
||||
homeassistant/components/home_connect/api.py
|
||||
homeassistant/components/home_connect/binary_sensor.py
|
||||
homeassistant/components/home_connect/entity.py
|
||||
homeassistant/components/home_connect/light.py
|
||||
homeassistant/components/home_connect/sensor.py
|
||||
homeassistant/components/home_connect/switch.py
|
||||
homeassistant/components/homematic/*
|
||||
homeassistant/components/homematic/climate.py
|
||||
homeassistant/components/homematic/cover.py
|
||||
homeassistant/components/homematic/notify.py
|
||||
homeassistant/components/home_plus_control/api.py
|
||||
homeassistant/components/home_plus_control/helpers.py
|
||||
homeassistant/components/home_plus_control/switch.py
|
||||
homeassistant/components/homeworks/*
|
||||
homeassistant/components/honeywell/__init__.py
|
||||
homeassistant/components/honeywell/climate.py
|
||||
homeassistant/components/horizon/media_player.py
|
||||
homeassistant/components/hp_ilo/sensor.py
|
||||
@ -525,8 +524,6 @@ omit =
|
||||
homeassistant/components/kira/*
|
||||
homeassistant/components/kiwi/lock.py
|
||||
homeassistant/components/knx/*
|
||||
homeassistant/components/knx/climate.py
|
||||
homeassistant/components/knx/cover.py
|
||||
homeassistant/components/kodi/__init__.py
|
||||
homeassistant/components/kodi/browse_media.py
|
||||
homeassistant/components/kodi/const.py
|
||||
@ -624,6 +621,7 @@ omit =
|
||||
homeassistant/components/mill/__init__.py
|
||||
homeassistant/components/mill/climate.py
|
||||
homeassistant/components/mill/const.py
|
||||
homeassistant/components/mill/sensor.py
|
||||
homeassistant/components/minecraft_server/__init__.py
|
||||
homeassistant/components/minecraft_server/binary_sensor.py
|
||||
homeassistant/components/minecraft_server/const.py
|
||||
@ -638,6 +636,7 @@ omit =
|
||||
homeassistant/components/modbus/cover.py
|
||||
homeassistant/components/modbus/climate.py
|
||||
homeassistant/components/modbus/modbus.py
|
||||
homeassistant/components/modbus/validators.py
|
||||
homeassistant/components/modem_callerid/sensor.py
|
||||
homeassistant/components/motion_blinds/__init__.py
|
||||
homeassistant/components/motion_blinds/const.py
|
||||
@ -655,7 +654,6 @@ omit =
|
||||
homeassistant/components/mvglive/sensor.py
|
||||
homeassistant/components/mychevy/*
|
||||
homeassistant/components/mycroft/*
|
||||
homeassistant/components/mycroft/notify.py
|
||||
homeassistant/components/mysensors/__init__.py
|
||||
homeassistant/components/mysensors/binary_sensor.py
|
||||
homeassistant/components/mysensors/climate.py
|
||||
@ -692,6 +690,7 @@ omit =
|
||||
homeassistant/components/neurio_energy/sensor.py
|
||||
homeassistant/components/nexia/climate.py
|
||||
homeassistant/components/nextcloud/*
|
||||
homeassistant/components/nfandroidtv/__init__.py
|
||||
homeassistant/components/nfandroidtv/notify.py
|
||||
homeassistant/components/niko_home_control/light.py
|
||||
homeassistant/components/nilu/air_quality.py
|
||||
@ -827,8 +826,6 @@ omit =
|
||||
homeassistant/components/radarr/sensor.py
|
||||
homeassistant/components/radiotherm/climate.py
|
||||
homeassistant/components/rainbird/*
|
||||
homeassistant/components/rainbird/sensor.py
|
||||
homeassistant/components/rainbird/switch.py
|
||||
homeassistant/components/raincloud/*
|
||||
homeassistant/components/rainmachine/__init__.py
|
||||
homeassistant/components/rainmachine/binary_sensor.py
|
||||
@ -875,7 +872,6 @@ omit =
|
||||
homeassistant/components/rova/sensor.py
|
||||
homeassistant/components/rpi_camera/*
|
||||
homeassistant/components/rpi_gpio/*
|
||||
homeassistant/components/rpi_gpio/cover.py
|
||||
homeassistant/components/rpi_gpio_pwm/light.py
|
||||
homeassistant/components/rpi_pfio/*
|
||||
homeassistant/components/rpi_rf/switch.py
|
||||
@ -895,7 +891,6 @@ omit =
|
||||
homeassistant/components/screenlogic/services.py
|
||||
homeassistant/components/screenlogic/switch.py
|
||||
homeassistant/components/scsgate/*
|
||||
homeassistant/components/scsgate/cover.py
|
||||
homeassistant/components/sendgrid/notify.py
|
||||
homeassistant/components/sense/*
|
||||
homeassistant/components/sensehat/light.py
|
||||
@ -973,7 +968,6 @@ omit =
|
||||
homeassistant/components/sonos/*
|
||||
homeassistant/components/sony_projector/switch.py
|
||||
homeassistant/components/spc/*
|
||||
homeassistant/components/speedtestdotnet/*
|
||||
homeassistant/components/spider/*
|
||||
homeassistant/components/splunk/*
|
||||
homeassistant/components/spotify/__init__.py
|
||||
@ -998,8 +992,6 @@ omit =
|
||||
homeassistant/components/swiss_public_transport/sensor.py
|
||||
homeassistant/components/swisscom/device_tracker.py
|
||||
homeassistant/components/switchbot/switch.py
|
||||
homeassistant/components/switcher_kis/sensor.py
|
||||
homeassistant/components/switcher_kis/switch.py
|
||||
homeassistant/components/switchmate/switch.py
|
||||
homeassistant/components/syncthing/__init__.py
|
||||
homeassistant/components/syncthing/sensor.py
|
||||
@ -1020,7 +1012,6 @@ omit =
|
||||
homeassistant/components/system_bridge/sensor.py
|
||||
homeassistant/components/systemmonitor/sensor.py
|
||||
homeassistant/components/tado/*
|
||||
homeassistant/components/tado/device_tracker.py
|
||||
homeassistant/components/tahoma/*
|
||||
homeassistant/components/tank_utility/sensor.py
|
||||
homeassistant/components/tankerkoenig/*
|
||||
@ -1088,9 +1079,6 @@ omit =
|
||||
homeassistant/components/traccar/const.py
|
||||
homeassistant/components/trackr/device_tracker.py
|
||||
homeassistant/components/tradfri/*
|
||||
homeassistant/components/tradfri/light.py
|
||||
homeassistant/components/tradfri/cover.py
|
||||
homeassistant/components/tradfri/base_class.py
|
||||
homeassistant/components/trafikverket_train/sensor.py
|
||||
homeassistant/components/trafikverket_weatherstation/sensor.py
|
||||
homeassistant/components/transmission/sensor.py
|
||||
@ -1211,20 +1199,29 @@ omit =
|
||||
homeassistant/components/xiaomi_miio/device_tracker.py
|
||||
homeassistant/components/xiaomi_miio/fan.py
|
||||
homeassistant/components/xiaomi_miio/gateway.py
|
||||
homeassistant/components/xiaomi_miio/humidifier.py
|
||||
homeassistant/components/xiaomi_miio/light.py
|
||||
homeassistant/components/xiaomi_miio/number.py
|
||||
homeassistant/components/xiaomi_miio/remote.py
|
||||
homeassistant/components/xiaomi_miio/select.py
|
||||
homeassistant/components/xiaomi_miio/sensor.py
|
||||
homeassistant/components/xiaomi_miio/switch.py
|
||||
homeassistant/components/xiaomi_miio/vacuum.py
|
||||
homeassistant/components/xiaomi_tv/media_player.py
|
||||
homeassistant/components/xmpp/notify.py
|
||||
homeassistant/components/xs1/*
|
||||
homeassistant/components/yale_smart_alarm/__init__.py
|
||||
homeassistant/components/yale_smart_alarm/alarm_control_panel.py
|
||||
homeassistant/components/yale_smart_alarm/const.py
|
||||
homeassistant/components/yale_smart_alarm/coordinator.py
|
||||
homeassistant/components/yamaha_musiccast/__init__.py
|
||||
homeassistant/components/yamaha_musiccast/media_player.py
|
||||
homeassistant/components/yandex_transport/*
|
||||
homeassistant/components/yeelightsunflower/light.py
|
||||
homeassistant/components/yi/camera.py
|
||||
homeassistant/components/youless/__init__.py
|
||||
homeassistant/components/youless/const.py
|
||||
homeassistant/components/youless/sensor.py
|
||||
homeassistant/components/zabbix/*
|
||||
homeassistant/components/zamg/sensor.py
|
||||
homeassistant/components/zamg/weather.py
|
||||
|
5
.github/workflows/builder.yml
vendored
5
.github/workflows/builder.yml
vendored
@ -115,7 +115,7 @@ jobs:
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build base image
|
||||
uses: home-assistant/builder@2021.06.2
|
||||
uses: home-assistant/builder@2021.07.0
|
||||
with:
|
||||
args: |
|
||||
$BUILD_ARGS \
|
||||
@ -134,6 +134,7 @@ jobs:
|
||||
machine:
|
||||
- generic-x86-64
|
||||
- intel-nuc
|
||||
- khadas-vim3
|
||||
- odroid-c2
|
||||
- odroid-c4
|
||||
- odroid-n2
|
||||
@ -167,7 +168,7 @@ jobs:
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build base image
|
||||
uses: home-assistant/builder@2021.06.2
|
||||
uses: home-assistant/builder@2021.07.0
|
||||
with:
|
||||
args: |
|
||||
$BUILD_ARGS \
|
||||
|
2
.github/workflows/ci.yaml
vendored
2
.github/workflows/ci.yaml
vendored
@ -740,4 +740,4 @@ jobs:
|
||||
coverage report --fail-under=94
|
||||
coverage xml
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v1.5.2
|
||||
uses: codecov/codecov-action@v2.0.2
|
||||
|
2
.github/workflows/lock.yml
vendored
2
.github/workflows/lock.yml
vendored
@ -9,7 +9,7 @@ jobs:
|
||||
lock:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/lock-threads@v2.0.3
|
||||
- uses: dessant/lock-threads@v2.1.1
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
issue-lock-inactive-days: "30"
|
||||
|
6
.github/workflows/stale.yml
vendored
6
.github/workflows/stale.yml
vendored
@ -16,7 +16,7 @@ jobs:
|
||||
# - No PRs marked as no-stale
|
||||
# - No issues marked as no-stale or help-wanted
|
||||
- name: 90 days stale issues & PRs policy
|
||||
uses: actions/stale@v3.0.19
|
||||
uses: actions/stale@v4
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
days-before-stale: 90
|
||||
@ -53,7 +53,7 @@ jobs:
|
||||
# - No PRs marked as no-stale or new-integrations
|
||||
# - No issues (-1)
|
||||
- name: 30 days stale PRs policy
|
||||
uses: actions/stale@v3.0.19
|
||||
uses: actions/stale@v4
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
days-before-stale: 30
|
||||
@ -78,7 +78,7 @@ jobs:
|
||||
# - No Issues marked as no-stale or help-wanted
|
||||
# - No PRs (-1)
|
||||
- name: Needs more information stale issues policy
|
||||
uses: actions/stale@v3.0.19
|
||||
uses: actions/stale@v4
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
only-labels: "needs-more-information"
|
||||
|
4
.github/workflows/wheels.yml
vendored
4
.github/workflows/wheels.yml
vendored
@ -81,7 +81,7 @@ jobs:
|
||||
name: requirements_diff
|
||||
|
||||
- name: Build wheels
|
||||
uses: home-assistant/wheels@2021.06.0
|
||||
uses: home-assistant/wheels@2021.07.0
|
||||
with:
|
||||
tag: ${{ matrix.tag }}
|
||||
arch: ${{ matrix.arch }}
|
||||
@ -150,7 +150,7 @@ jobs:
|
||||
done
|
||||
|
||||
- name: Build wheels
|
||||
uses: home-assistant/wheels@2021.06.0
|
||||
uses: home-assistant/wheels@2021.07.0
|
||||
with:
|
||||
tag: ${{ matrix.tag }}
|
||||
arch: ${{ matrix.arch }}
|
||||
|
@ -1,11 +1,11 @@
|
||||
repos:
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v2.16.0
|
||||
rev: v2.23.0
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args: [--py38-plus]
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 21.6b0
|
||||
rev: 21.7b0
|
||||
hooks:
|
||||
- id: black
|
||||
args:
|
||||
@ -70,7 +70,7 @@ repos:
|
||||
- id: prettier
|
||||
stages: [manual]
|
||||
- repo: https://github.com/cdce8p/python-typing-update
|
||||
rev: v0.3.3
|
||||
rev: v0.3.5
|
||||
hooks:
|
||||
# Run `python-typing-update` hook manually from time to time
|
||||
# to update python typing syntax.
|
||||
|
@ -9,15 +9,18 @@ homeassistant.components.actiontec.*
|
||||
homeassistant.components.aftership.*
|
||||
homeassistant.components.air_quality.*
|
||||
homeassistant.components.airly.*
|
||||
homeassistant.components.airvisual.*
|
||||
homeassistant.components.aladdin_connect.*
|
||||
homeassistant.components.alarm_control_panel.*
|
||||
homeassistant.components.amazon_polly.*
|
||||
homeassistant.components.ambee.*
|
||||
homeassistant.components.ambient_station.*
|
||||
homeassistant.components.ampio.*
|
||||
homeassistant.components.automation.*
|
||||
homeassistant.components.binary_sensor.*
|
||||
homeassistant.components.bluetooth_tracker.*
|
||||
homeassistant.components.bond.*
|
||||
homeassistant.components.braviatv.*
|
||||
homeassistant.components.brother.*
|
||||
homeassistant.components.calendar.*
|
||||
homeassistant.components.camera.*
|
||||
@ -25,17 +28,24 @@ homeassistant.components.canary.*
|
||||
homeassistant.components.cover.*
|
||||
homeassistant.components.device_automation.*
|
||||
homeassistant.components.device_tracker.*
|
||||
homeassistant.components.devolo_home_control.*
|
||||
homeassistant.components.dnsip.*
|
||||
homeassistant.components.dsmr.*
|
||||
homeassistant.components.dunehd.*
|
||||
homeassistant.components.elgato.*
|
||||
homeassistant.components.esphome.*
|
||||
homeassistant.components.energy.*
|
||||
homeassistant.components.fastdotcom.*
|
||||
homeassistant.components.fitbit.*
|
||||
homeassistant.components.flunearyou.*
|
||||
homeassistant.components.forecast_solar.*
|
||||
homeassistant.components.fritzbox.*
|
||||
homeassistant.components.frontend.*
|
||||
homeassistant.components.fritz.*
|
||||
homeassistant.components.geo_location.*
|
||||
homeassistant.components.gios.*
|
||||
homeassistant.components.group.*
|
||||
homeassistant.components.guardian.*
|
||||
homeassistant.components.history.*
|
||||
homeassistant.components.homeassistant.triggers.event
|
||||
homeassistant.components.http.*
|
||||
@ -45,6 +55,7 @@ homeassistant.components.image_processing.*
|
||||
homeassistant.components.integration.*
|
||||
homeassistant.components.knx.*
|
||||
homeassistant.components.kraken.*
|
||||
homeassistant.components.lcn.*
|
||||
homeassistant.components.light.*
|
||||
homeassistant.components.local_ip.*
|
||||
homeassistant.components.lock.*
|
||||
@ -52,30 +63,43 @@ homeassistant.components.mailbox.*
|
||||
homeassistant.components.media_player.*
|
||||
homeassistant.components.mysensors.*
|
||||
homeassistant.components.nam.*
|
||||
homeassistant.components.nest.*
|
||||
homeassistant.components.netatmo.*
|
||||
homeassistant.components.network.*
|
||||
homeassistant.components.no_ip.*
|
||||
homeassistant.components.notify.*
|
||||
homeassistant.components.notion.*
|
||||
homeassistant.components.number.*
|
||||
homeassistant.components.onewire.*
|
||||
homeassistant.components.openuv.*
|
||||
homeassistant.components.persistent_notification.*
|
||||
homeassistant.components.pi_hole.*
|
||||
homeassistant.components.proximity.*
|
||||
homeassistant.components.rainmachine.*
|
||||
homeassistant.components.recollect_waste.*
|
||||
homeassistant.components.recorder.purge
|
||||
homeassistant.components.recorder.repack
|
||||
homeassistant.components.recorder.statistics
|
||||
homeassistant.components.remote.*
|
||||
homeassistant.components.renault.*
|
||||
homeassistant.components.rituals_perfume_genie.*
|
||||
homeassistant.components.scene.*
|
||||
homeassistant.components.select.*
|
||||
homeassistant.components.sensor.*
|
||||
homeassistant.components.shelly.*
|
||||
homeassistant.components.simplisafe.*
|
||||
homeassistant.components.slack.*
|
||||
homeassistant.components.sonos.media_player
|
||||
homeassistant.components.ssdp.*
|
||||
homeassistant.components.stream.*
|
||||
homeassistant.components.sun.*
|
||||
homeassistant.components.switch.*
|
||||
homeassistant.components.switcher_kis.*
|
||||
homeassistant.components.synology_dsm.*
|
||||
homeassistant.components.systemmonitor.*
|
||||
homeassistant.components.tag.*
|
||||
homeassistant.components.tcp.*
|
||||
homeassistant.components.tile.*
|
||||
homeassistant.components.tts.*
|
||||
homeassistant.components.upcloud.*
|
||||
homeassistant.components.uptime.*
|
||||
|
15
.vscode/tasks.json
vendored
15
.vscode/tasks.json
vendored
@ -2,13 +2,10 @@
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "Preview",
|
||||
"label": "Run Home Assistant Core",
|
||||
"type": "shell",
|
||||
"command": "hass -c ./config",
|
||||
"group": {
|
||||
"kind": "test",
|
||||
"isDefault": true
|
||||
},
|
||||
"group": "test",
|
||||
"presentation": {
|
||||
"reveal": "always",
|
||||
"panel": "new"
|
||||
@ -19,7 +16,9 @@
|
||||
"label": "Pytest",
|
||||
"type": "shell",
|
||||
"command": "pytest --timeout=10 tests",
|
||||
"dependsOn": ["Install all Test Requirements"],
|
||||
"dependsOn": [
|
||||
"Install all Test Requirements"
|
||||
],
|
||||
"group": {
|
||||
"kind": "test",
|
||||
"isDefault": true
|
||||
@ -48,7 +47,9 @@
|
||||
"label": "Pylint",
|
||||
"type": "shell",
|
||||
"command": "pylint homeassistant",
|
||||
"dependsOn": ["Install all Requirements"],
|
||||
"dependsOn": [
|
||||
"Install all Requirements"
|
||||
],
|
||||
"group": {
|
||||
"kind": "test",
|
||||
"isDefault": true
|
||||
|
18
CODEOWNERS
18
CODEOWNERS
@ -22,6 +22,7 @@ homeassistant/scripts/check_config.py @kellerza
|
||||
homeassistant/components/abode/* @shred86
|
||||
homeassistant/components/accuweather/* @bieniu
|
||||
homeassistant/components/acmeda/* @atmurray
|
||||
homeassistant/components/adax/* @danielhiversen
|
||||
homeassistant/components/adguard/* @frenck
|
||||
homeassistant/components/advantage_air/* @Bre77
|
||||
homeassistant/components/aemet/* @noltari
|
||||
@ -97,7 +98,7 @@ homeassistant/components/configurator/* @home-assistant/core
|
||||
homeassistant/components/control4/* @lawtancool
|
||||
homeassistant/components/conversation/* @home-assistant/core
|
||||
homeassistant/components/coolmaster/* @OnFreund
|
||||
homeassistant/components/coronavirus/* @home_assistant/core
|
||||
homeassistant/components/coronavirus/* @home-assistant/core
|
||||
homeassistant/components/counter/* @fabaff
|
||||
homeassistant/components/cover/* @home-assistant/core
|
||||
homeassistant/components/cpuspeed/* @fabaff
|
||||
@ -139,6 +140,7 @@ homeassistant/components/emby/* @mezz64
|
||||
homeassistant/components/emoncms/* @borpin
|
||||
homeassistant/components/emonitor/* @bdraco
|
||||
homeassistant/components/emulated_kasa/* @kbickar
|
||||
homeassistant/components/energy/* @home-assistant/core
|
||||
homeassistant/components/enigma2/* @fbradyirl
|
||||
homeassistant/components/enocean/* @bdurrer
|
||||
homeassistant/components/enphase_envoy/* @gtdiehl
|
||||
@ -160,6 +162,7 @@ homeassistant/components/fireservicerota/* @cyberjunky
|
||||
homeassistant/components/firmata/* @DaAwesomeP
|
||||
homeassistant/components/fixer/* @fabaff
|
||||
homeassistant/components/flick_electric/* @ZephireNZ
|
||||
homeassistant/components/flipr/* @cnico
|
||||
homeassistant/components/flo/* @dmulcahey
|
||||
homeassistant/components/flock/* @fabaff
|
||||
homeassistant/components/flume/* @ChrisMandich @bdraco
|
||||
@ -175,8 +178,8 @@ homeassistant/components/fritzbox/* @mib1185
|
||||
homeassistant/components/fronius/* @nielstron
|
||||
homeassistant/components/frontend/* @home-assistant/frontend
|
||||
homeassistant/components/garages_amsterdam/* @klaasnicolaas
|
||||
homeassistant/components/garmin_connect/* @cyberjunky
|
||||
homeassistant/components/gdacs/* @exxamalte
|
||||
homeassistant/components/generic_hygrostat/* @Shulyaka
|
||||
homeassistant/components/geniushub/* @zxdavb
|
||||
homeassistant/components/geo_json_events/* @exxamalte
|
||||
homeassistant/components/geo_rss_events/* @exxamalte
|
||||
@ -213,6 +216,7 @@ homeassistant/components/homeassistant/* @home-assistant/core
|
||||
homeassistant/components/homekit/* @bdraco
|
||||
homeassistant/components/homekit_controller/* @Jc2k @bdraco
|
||||
homeassistant/components/homematic/* @pvizeli @danielperna84
|
||||
homeassistant/components/honeywell/* @rdfurman
|
||||
homeassistant/components/http/* @home-assistant/core
|
||||
homeassistant/components/huawei_lte/* @scop @fphammerle
|
||||
homeassistant/components/huawei_router/* @abmantis
|
||||
@ -329,6 +333,7 @@ homeassistant/components/netdata/* @fabaff
|
||||
homeassistant/components/nexia/* @bdraco
|
||||
homeassistant/components/nextbus/* @vividboarder
|
||||
homeassistant/components/nextcloud/* @meichthys
|
||||
homeassistant/components/nfandroidtv/* @tkdrob
|
||||
homeassistant/components/nightscout/* @marciogranzotto
|
||||
homeassistant/components/nilu/* @hfurubotten
|
||||
homeassistant/components/nissan_leaf/* @filcole
|
||||
@ -384,6 +389,7 @@ homeassistant/components/powerwall/* @bdraco @jrester
|
||||
homeassistant/components/profiler/* @bdraco
|
||||
homeassistant/components/progettihwsw/* @ardaseremet
|
||||
homeassistant/components/prometheus/* @knyar
|
||||
homeassistant/components/prosegur/* @dgomes
|
||||
homeassistant/components/proxmoxve/* @k4ds3 @jhollowe @Corbeno
|
||||
homeassistant/components/ps4/* @ktnrg45
|
||||
homeassistant/components/push/* @dgomes
|
||||
@ -404,6 +410,7 @@ homeassistant/components/rainmachine/* @bachya
|
||||
homeassistant/components/random/* @fabaff
|
||||
homeassistant/components/recollect_waste/* @bachya
|
||||
homeassistant/components/rejseplanen/* @DarkFox
|
||||
homeassistant/components/renault/* @epenet
|
||||
homeassistant/components/repetier/* @MTrab
|
||||
homeassistant/components/rflink/* @javicalle
|
||||
homeassistant/components/rfxtrx/* @danielhiversen @elupus @RobBie1221
|
||||
@ -442,6 +449,7 @@ homeassistant/components/sighthound/* @robmarkcole
|
||||
homeassistant/components/signal_messenger/* @bbernhard
|
||||
homeassistant/components/simplisafe/* @bachya
|
||||
homeassistant/components/sinch/* @bendikrb
|
||||
homeassistant/components/siren/* @home-assistant/core @raman325
|
||||
homeassistant/components/sisyphus/* @jkeljo
|
||||
homeassistant/components/sky_hub/* @rogerselwyn
|
||||
homeassistant/components/slack/* @bachya
|
||||
@ -502,7 +510,7 @@ homeassistant/components/tapsaff/* @bazwilliams
|
||||
homeassistant/components/tasmota/* @emontnemery
|
||||
homeassistant/components/tautulli/* @ludeeus
|
||||
homeassistant/components/tellduslive/* @fredrike
|
||||
homeassistant/components/template/* @PhracturedBlue @tetienne
|
||||
homeassistant/components/template/* @PhracturedBlue @tetienne @home-assistant/core
|
||||
homeassistant/components/tesla/* @zabuldon @alandtse
|
||||
homeassistant/components/tfiac/* @fredrike @mellado
|
||||
homeassistant/components/thethingsnetwork/* @fabaff
|
||||
@ -554,11 +562,12 @@ homeassistant/components/wallbox/* @hesselonline
|
||||
homeassistant/components/waqi/* @andrey-git
|
||||
homeassistant/components/watson_tts/* @rutkai
|
||||
homeassistant/components/weather/* @fabaff
|
||||
homeassistant/components/webostv/* @bendavid
|
||||
homeassistant/components/webostv/* @bendavid @thecode
|
||||
homeassistant/components/websocket_api/* @home-assistant/core
|
||||
homeassistant/components/wemo/* @esev
|
||||
homeassistant/components/wiffi/* @mampfes
|
||||
homeassistant/components/wilight/* @leofig-rj
|
||||
homeassistant/components/wirelesstag/* @sergeymaysak
|
||||
homeassistant/components/withings/* @vangorra
|
||||
homeassistant/components/wled/* @frenck
|
||||
homeassistant/components/wolflink/* @adamkrol93
|
||||
@ -576,6 +585,7 @@ homeassistant/components/yandex_transport/* @rishatik92 @devbis
|
||||
homeassistant/components/yeelight/* @rytilahti @zewelor @shenxn
|
||||
homeassistant/components/yeelightsunflower/* @lindsaymarkward
|
||||
homeassistant/components/yi/* @bachya
|
||||
homeassistant/components/youless/* @gjong
|
||||
homeassistant/components/zeroconf/* @bdraco
|
||||
homeassistant/components/zerproc/* @emlove
|
||||
homeassistant/components/zha/* @dmulcahey @adminiuga
|
||||
|
10
build.json
10
build.json
@ -2,11 +2,11 @@
|
||||
"image": "homeassistant/{arch}-homeassistant",
|
||||
"shadow_repository": "ghcr.io/home-assistant",
|
||||
"build_from": {
|
||||
"aarch64": "ghcr.io/home-assistant/aarch64-homeassistant-base:2021.06.2",
|
||||
"armhf": "ghcr.io/home-assistant/armhf-homeassistant-base:2021.06.2",
|
||||
"armv7": "ghcr.io/home-assistant/armv7-homeassistant-base:2021.06.2",
|
||||
"amd64": "ghcr.io/home-assistant/amd64-homeassistant-base:2021.06.2",
|
||||
"i386": "ghcr.io/home-assistant/i386-homeassistant-base:2021.06.2"
|
||||
"aarch64": "ghcr.io/home-assistant/aarch64-homeassistant-base:2021.07.0",
|
||||
"armhf": "ghcr.io/home-assistant/armhf-homeassistant-base:2021.07.0",
|
||||
"armv7": "ghcr.io/home-assistant/armv7-homeassistant-base:2021.07.0",
|
||||
"amd64": "ghcr.io/home-assistant/amd64-homeassistant-base:2021.07.0",
|
||||
"i386": "ghcr.io/home-assistant/i386-homeassistant-base:2021.07.0"
|
||||
},
|
||||
"labels": {
|
||||
"io.hass.type": "core",
|
||||
|
@ -146,8 +146,8 @@ def daemonize() -> None:
|
||||
|
||||
# redirect standard file descriptors to devnull
|
||||
# pylint: disable=consider-using-with
|
||||
infd = open(os.devnull)
|
||||
outfd = open(os.devnull, "a+")
|
||||
infd = open(os.devnull, encoding="utf8")
|
||||
outfd = open(os.devnull, "a+", encoding="utf8")
|
||||
sys.stdout.flush()
|
||||
sys.stderr.flush()
|
||||
os.dup2(infd.fileno(), sys.stdin.fileno())
|
||||
@ -159,7 +159,7 @@ def check_pid(pid_file: str) -> None:
|
||||
"""Check that Home Assistant is not already running."""
|
||||
# Check pid file
|
||||
try:
|
||||
with open(pid_file) as file:
|
||||
with open(pid_file, encoding="utf8") as file:
|
||||
pid = int(file.readline())
|
||||
except OSError:
|
||||
# PID File does not exist
|
||||
@ -182,7 +182,7 @@ def write_pid(pid_file: str) -> None:
|
||||
"""Create a PID File."""
|
||||
pid = os.getpid()
|
||||
try:
|
||||
with open(pid_file, "w") as file:
|
||||
with open(pid_file, "w", encoding="utf8") as file:
|
||||
file.write(str(pid))
|
||||
except OSError:
|
||||
print(f"Fatal Error: Unable to write pid file {pid_file}")
|
||||
|
@ -1,7 +1,7 @@
|
||||
"""Auth provider that validates credentials via an external command."""
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio.subprocess
|
||||
import asyncio
|
||||
import collections
|
||||
from collections.abc import Mapping
|
||||
import logging
|
||||
@ -64,7 +64,7 @@ class CommandLineAuthProvider(AuthProvider):
|
||||
"""Validate a username and password."""
|
||||
env = {"username": username, "password": password}
|
||||
try:
|
||||
process = await asyncio.subprocess.create_subprocess_exec( # pylint: disable=no-member
|
||||
process = await asyncio.create_subprocess_exec(
|
||||
self.config[CONF_COMMAND],
|
||||
*self.config[CONF_ARGS],
|
||||
env=env,
|
||||
|
@ -1,5 +1,4 @@
|
||||
"""Support for the Abode Security System."""
|
||||
from copy import deepcopy
|
||||
from functools import partial
|
||||
|
||||
from abodepy import Abode
|
||||
@ -8,7 +7,6 @@ import abodepy.helpers.timeline as TIMELINE
|
||||
from requests.exceptions import ConnectTimeout, HTTPError
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import SOURCE_IMPORT
|
||||
from homeassistant.const import (
|
||||
ATTR_ATTRIBUTION,
|
||||
ATTR_DATE,
|
||||
@ -44,22 +42,7 @@ ATTR_APP_TYPE = "app_type"
|
||||
ATTR_EVENT_BY = "event_by"
|
||||
ATTR_VALUE = "value"
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema(
|
||||
vol.All(
|
||||
# Deprecated in Home Assistant 2021.6
|
||||
cv.deprecated(DOMAIN),
|
||||
{
|
||||
DOMAIN: vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_USERNAME): cv.string,
|
||||
vol.Required(CONF_PASSWORD): cv.string,
|
||||
vol.Optional(CONF_POLLING, default=False): cv.boolean,
|
||||
}
|
||||
)
|
||||
},
|
||||
),
|
||||
extra=vol.ALLOW_EXTRA,
|
||||
)
|
||||
CONFIG_SCHEMA = cv.deprecated(DOMAIN)
|
||||
|
||||
CHANGE_SETTING_SCHEMA = vol.Schema(
|
||||
{vol.Required(ATTR_SETTING): cv.string, vol.Required(ATTR_VALUE): cv.string}
|
||||
@ -92,22 +75,6 @@ class AbodeSystem:
|
||||
self.logout_listener = None
|
||||
|
||||
|
||||
async def async_setup(hass, config):
|
||||
"""Set up Abode integration."""
|
||||
if DOMAIN not in config:
|
||||
return True
|
||||
|
||||
conf = config[DOMAIN]
|
||||
|
||||
hass.async_create_task(
|
||||
hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_IMPORT}, data=deepcopy(conf)
|
||||
)
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry):
|
||||
"""Set up Abode integration from a config entry."""
|
||||
username = config_entry.data.get(CONF_USERNAME)
|
||||
@ -284,17 +251,13 @@ class AbodeEntity(Entity):
|
||||
"""Initialize Abode entity."""
|
||||
self._data = data
|
||||
self._available = True
|
||||
self._attr_should_poll = data.polling
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
"""Return the available state."""
|
||||
return self._available
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""Return the polling state."""
|
||||
return self._data.polling
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""Subscribe to Abode connection status updates."""
|
||||
await self.hass.async_add_executor_job(
|
||||
@ -324,6 +287,8 @@ class AbodeDevice(AbodeEntity):
|
||||
"""Initialize Abode device."""
|
||||
super().__init__(data)
|
||||
self._device = device
|
||||
self._attr_name = device.name
|
||||
self._attr_unique_id = device.device_uuid
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""Subscribe to device events."""
|
||||
@ -345,11 +310,6 @@ class AbodeDevice(AbodeEntity):
|
||||
"""Update device state."""
|
||||
self._device.refresh()
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the device."""
|
||||
return self._device.name
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self):
|
||||
"""Return the state attributes."""
|
||||
@ -361,11 +321,6 @@ class AbodeDevice(AbodeEntity):
|
||||
"device_type": self._device.type,
|
||||
}
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return a unique ID to use for this device."""
|
||||
return self._device.device_uuid
|
||||
|
||||
@property
|
||||
def device_info(self):
|
||||
"""Return device registry information for this entity."""
|
||||
@ -388,22 +343,13 @@ class AbodeAutomation(AbodeEntity):
|
||||
"""Initialize for Abode automation."""
|
||||
super().__init__(data)
|
||||
self._automation = automation
|
||||
self._attr_name = automation.name
|
||||
self._attr_unique_id = automation.automation_id
|
||||
self._attr_extra_state_attributes = {
|
||||
ATTR_ATTRIBUTION: ATTRIBUTION,
|
||||
"type": "CUE automation",
|
||||
}
|
||||
|
||||
def update(self):
|
||||
"""Update automation state."""
|
||||
self._automation.refresh()
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the automation."""
|
||||
return self._automation.name
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self):
|
||||
"""Return the state attributes."""
|
||||
return {ATTR_ATTRIBUTION: ATTRIBUTION, "type": "CUE automation"}
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return a unique ID to use for this automation."""
|
||||
return self._automation.automation_id
|
||||
|
@ -28,10 +28,9 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
class AbodeAlarm(AbodeDevice, alarm.AlarmControlPanelEntity):
|
||||
"""An alarm_control_panel implementation for Abode."""
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
"""Return the icon."""
|
||||
return ICON
|
||||
_attr_icon = ICON
|
||||
_attr_code_arm_required = False
|
||||
_attr_supported_features = SUPPORT_ALARM_ARM_HOME | SUPPORT_ALARM_ARM_AWAY
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
@ -46,16 +45,6 @@ class AbodeAlarm(AbodeDevice, alarm.AlarmControlPanelEntity):
|
||||
state = None
|
||||
return state
|
||||
|
||||
@property
|
||||
def code_arm_required(self):
|
||||
"""Whether the code is required for arm actions."""
|
||||
return False
|
||||
|
||||
@property
|
||||
def supported_features(self) -> int:
|
||||
"""Return the list of supported features."""
|
||||
return SUPPORT_ALARM_ARM_HOME | SUPPORT_ALARM_ARM_AWAY
|
||||
|
||||
def alarm_disarm(self, code=None):
|
||||
"""Send disarm command."""
|
||||
self._device.set_standby()
|
||||
|
@ -158,13 +158,3 @@ class AbodeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
self._password = user_input[CONF_PASSWORD]
|
||||
|
||||
return await self._async_abode_login(step_id="reauth_confirm")
|
||||
|
||||
async def async_step_import(self, import_config):
|
||||
"""Import a config entry from configuration.yaml."""
|
||||
if self._async_current_entries():
|
||||
LOGGER.warning("Already configured; Only a single configuration possible")
|
||||
return self.async_abort(reason="single_instance_allowed")
|
||||
|
||||
self._polling = import_config.get(CONF_POLLING, False)
|
||||
|
||||
return await self.async_step_user(import_config)
|
||||
|
@ -41,23 +41,15 @@ class AbodeSensor(AbodeDevice, SensorEntity):
|
||||
"""Initialize a sensor for an Abode device."""
|
||||
super().__init__(data, device)
|
||||
self._sensor_type = sensor_type
|
||||
self._name = f"{self._device.name} {SENSOR_TYPES[self._sensor_type][0]}"
|
||||
self._device_class = SENSOR_TYPES[self._sensor_type][1]
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the sensor."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
"""Return the device class."""
|
||||
return self._device_class
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return a unique ID to use for this device."""
|
||||
return f"{self._device.device_uuid}-{self._sensor_type}"
|
||||
self._attr_name = f"{device.name} {SENSOR_TYPES[sensor_type][0]}"
|
||||
self._attr_device_class = SENSOR_TYPES[self._sensor_type][1]
|
||||
self._attr_unique_id = f"{device.device_uuid}-{sensor_type}"
|
||||
if self._sensor_type == CONST.TEMP_STATUS_KEY:
|
||||
self._attr_unit_of_measurement = device.temp_unit
|
||||
elif self._sensor_type == CONST.HUMI_STATUS_KEY:
|
||||
self._attr_unit_of_measurement = device.humidity_unit
|
||||
elif self._sensor_type == CONST.LUX_STATUS_KEY:
|
||||
self._attr_unit_of_measurement = device.lux_unit
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
@ -68,13 +60,3 @@ class AbodeSensor(AbodeDevice, SensorEntity):
|
||||
return self._device.humidity
|
||||
if self._sensor_type == CONST.LUX_STATUS_KEY:
|
||||
return self._device.lux
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
"""Return the units of measurement."""
|
||||
if self._sensor_type == CONST.TEMP_STATUS_KEY:
|
||||
return self._device.temp_unit
|
||||
if self._sensor_type == CONST.HUMI_STATUS_KEY:
|
||||
return self._device.humidity_unit
|
||||
if self._sensor_type == CONST.LUX_STATUS_KEY:
|
||||
return self._device.lux_unit
|
||||
|
@ -48,6 +48,8 @@ class AbodeSwitch(AbodeDevice, SwitchEntity):
|
||||
class AbodeAutomationSwitch(AbodeAutomation, SwitchEntity):
|
||||
"""A switch implementation for Abode automations."""
|
||||
|
||||
_attr_icon = ICON
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""Set up trigger automation service."""
|
||||
await super().async_added_to_hass()
|
||||
@ -73,8 +75,3 @@ class AbodeAutomationSwitch(AbodeAutomation, SwitchEntity):
|
||||
def is_on(self):
|
||||
"""Return True if the automation is enabled."""
|
||||
return self._automation.is_enabled
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
"""Return the robot icon to match Home Assistant automations."""
|
||||
return ICON
|
||||
|
@ -26,7 +26,7 @@
|
||||
"user": {
|
||||
"data": {
|
||||
"password": "Passwort",
|
||||
"username": "E-Mail-Adresse"
|
||||
"username": "E-Mail"
|
||||
},
|
||||
"title": "Gib deine Abode-Anmeldeinformationen ein"
|
||||
}
|
||||
|
@ -1,9 +1,12 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"reauth_successful": "La reautenticaci\u00f3n fue exitosa",
|
||||
"single_instance_allowed": "Solo se permite una \u00fanica configuraci\u00f3n de Abode."
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "No se pudo conectar",
|
||||
"invalid_auth": "Autenticaci\u00f3n inv\u00e1lida",
|
||||
"invalid_mfa_code": "C\u00f3digo MFA no v\u00e1lido"
|
||||
},
|
||||
"step": {
|
||||
@ -15,7 +18,8 @@
|
||||
},
|
||||
"reauth_confirm": {
|
||||
"data": {
|
||||
"password": "Contrase\u00f1a"
|
||||
"password": "Contrase\u00f1a",
|
||||
"username": "Correo electr\u00f3nico"
|
||||
},
|
||||
"title": "Complete su informaci\u00f3n de inicio de sesi\u00f3n de Abode"
|
||||
},
|
||||
|
@ -20,7 +20,8 @@
|
||||
"data": {
|
||||
"password": "Jelsz\u00f3",
|
||||
"username": "E-mail"
|
||||
}
|
||||
},
|
||||
"title": "T\u00f6ltse ki az Abode bejelentkez\u00e9si adatait"
|
||||
},
|
||||
"user": {
|
||||
"data": {
|
||||
|
@ -3,7 +3,7 @@ from __future__ import annotations
|
||||
|
||||
from typing import Final
|
||||
|
||||
from homeassistant.components.sensor import ATTR_STATE_CLASS, STATE_CLASS_MEASUREMENT
|
||||
from homeassistant.components.sensor import STATE_CLASS_MEASUREMENT
|
||||
from homeassistant.components.weather import (
|
||||
ATTR_CONDITION_CLEAR_NIGHT,
|
||||
ATTR_CONDITION_CLOUDY,
|
||||
@ -21,8 +21,6 @@ from homeassistant.components.weather import (
|
||||
ATTR_CONDITION_WINDY,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
ATTR_DEVICE_CLASS,
|
||||
ATTR_ICON,
|
||||
CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
LENGTH_FEET,
|
||||
@ -38,16 +36,12 @@ from homeassistant.const import (
|
||||
UV_INDEX,
|
||||
)
|
||||
|
||||
from .model import SensorDescription
|
||||
from .model import AccuWeatherSensorDescription
|
||||
|
||||
API_IMPERIAL: Final = "Imperial"
|
||||
API_METRIC: Final = "Metric"
|
||||
ATTRIBUTION: Final = "Data provided by AccuWeather"
|
||||
ATTR_ENABLED: Final = "enabled"
|
||||
ATTR_FORECAST: Final = "forecast"
|
||||
ATTR_LABEL: Final = "label"
|
||||
ATTR_UNIT_IMPERIAL: Final = "unit_imperial"
|
||||
ATTR_UNIT_METRIC: Final = "unit_metric"
|
||||
CONF_FORECAST: Final = "forecast"
|
||||
DOMAIN: Final = "accuweather"
|
||||
MANUFACTURER: Final = "AccuWeather, Inc."
|
||||
@ -71,276 +65,263 @@ CONDITION_CLASSES: Final[dict[str, list[int]]] = {
|
||||
ATTR_CONDITION_WINDY: [32],
|
||||
}
|
||||
|
||||
FORECAST_SENSOR_TYPES: Final[dict[str, SensorDescription]] = {
|
||||
"CloudCoverDay": {
|
||||
ATTR_DEVICE_CLASS: None,
|
||||
ATTR_ICON: "mdi:weather-cloudy",
|
||||
ATTR_LABEL: "Cloud Cover Day",
|
||||
ATTR_UNIT_METRIC: PERCENTAGE,
|
||||
ATTR_UNIT_IMPERIAL: PERCENTAGE,
|
||||
ATTR_ENABLED: False,
|
||||
},
|
||||
"CloudCoverNight": {
|
||||
ATTR_DEVICE_CLASS: None,
|
||||
ATTR_ICON: "mdi:weather-cloudy",
|
||||
ATTR_LABEL: "Cloud Cover Night",
|
||||
ATTR_UNIT_METRIC: PERCENTAGE,
|
||||
ATTR_UNIT_IMPERIAL: PERCENTAGE,
|
||||
ATTR_ENABLED: False,
|
||||
},
|
||||
"Grass": {
|
||||
ATTR_DEVICE_CLASS: None,
|
||||
ATTR_ICON: "mdi:grass",
|
||||
ATTR_LABEL: "Grass Pollen",
|
||||
ATTR_UNIT_METRIC: CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||
ATTR_UNIT_IMPERIAL: CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||
ATTR_ENABLED: False,
|
||||
},
|
||||
"HoursOfSun": {
|
||||
ATTR_DEVICE_CLASS: None,
|
||||
ATTR_ICON: "mdi:weather-partly-cloudy",
|
||||
ATTR_LABEL: "Hours Of Sun",
|
||||
ATTR_UNIT_METRIC: TIME_HOURS,
|
||||
ATTR_UNIT_IMPERIAL: TIME_HOURS,
|
||||
ATTR_ENABLED: True,
|
||||
},
|
||||
"Mold": {
|
||||
ATTR_DEVICE_CLASS: None,
|
||||
ATTR_ICON: "mdi:blur",
|
||||
ATTR_LABEL: "Mold Pollen",
|
||||
ATTR_UNIT_METRIC: CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||
ATTR_UNIT_IMPERIAL: CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||
ATTR_ENABLED: False,
|
||||
},
|
||||
"Ozone": {
|
||||
ATTR_DEVICE_CLASS: None,
|
||||
ATTR_ICON: "mdi:vector-triangle",
|
||||
ATTR_LABEL: "Ozone",
|
||||
ATTR_UNIT_METRIC: None,
|
||||
ATTR_UNIT_IMPERIAL: None,
|
||||
ATTR_ENABLED: False,
|
||||
},
|
||||
"Ragweed": {
|
||||
ATTR_DEVICE_CLASS: None,
|
||||
ATTR_ICON: "mdi:sprout",
|
||||
ATTR_LABEL: "Ragweed Pollen",
|
||||
ATTR_UNIT_METRIC: CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||
ATTR_UNIT_IMPERIAL: CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||
ATTR_ENABLED: False,
|
||||
},
|
||||
"RealFeelTemperatureMax": {
|
||||
ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE,
|
||||
ATTR_ICON: None,
|
||||
ATTR_LABEL: "RealFeel Temperature Max",
|
||||
ATTR_UNIT_METRIC: TEMP_CELSIUS,
|
||||
ATTR_UNIT_IMPERIAL: TEMP_FAHRENHEIT,
|
||||
ATTR_ENABLED: True,
|
||||
},
|
||||
"RealFeelTemperatureMin": {
|
||||
ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE,
|
||||
ATTR_ICON: None,
|
||||
ATTR_LABEL: "RealFeel Temperature Min",
|
||||
ATTR_UNIT_METRIC: TEMP_CELSIUS,
|
||||
ATTR_UNIT_IMPERIAL: TEMP_FAHRENHEIT,
|
||||
ATTR_ENABLED: True,
|
||||
},
|
||||
"RealFeelTemperatureShadeMax": {
|
||||
ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE,
|
||||
ATTR_ICON: None,
|
||||
ATTR_LABEL: "RealFeel Temperature Shade Max",
|
||||
ATTR_UNIT_METRIC: TEMP_CELSIUS,
|
||||
ATTR_UNIT_IMPERIAL: TEMP_FAHRENHEIT,
|
||||
ATTR_ENABLED: False,
|
||||
},
|
||||
"RealFeelTemperatureShadeMin": {
|
||||
ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE,
|
||||
ATTR_ICON: None,
|
||||
ATTR_LABEL: "RealFeel Temperature Shade Min",
|
||||
ATTR_UNIT_METRIC: TEMP_CELSIUS,
|
||||
ATTR_UNIT_IMPERIAL: TEMP_FAHRENHEIT,
|
||||
ATTR_ENABLED: False,
|
||||
},
|
||||
"ThunderstormProbabilityDay": {
|
||||
ATTR_DEVICE_CLASS: None,
|
||||
ATTR_ICON: "mdi:weather-lightning",
|
||||
ATTR_LABEL: "Thunderstorm Probability Day",
|
||||
ATTR_UNIT_METRIC: PERCENTAGE,
|
||||
ATTR_UNIT_IMPERIAL: PERCENTAGE,
|
||||
ATTR_ENABLED: True,
|
||||
},
|
||||
"ThunderstormProbabilityNight": {
|
||||
ATTR_DEVICE_CLASS: None,
|
||||
ATTR_ICON: "mdi:weather-lightning",
|
||||
ATTR_LABEL: "Thunderstorm Probability Night",
|
||||
ATTR_UNIT_METRIC: PERCENTAGE,
|
||||
ATTR_UNIT_IMPERIAL: PERCENTAGE,
|
||||
ATTR_ENABLED: True,
|
||||
},
|
||||
"Tree": {
|
||||
ATTR_DEVICE_CLASS: None,
|
||||
ATTR_ICON: "mdi:tree-outline",
|
||||
ATTR_LABEL: "Tree Pollen",
|
||||
ATTR_UNIT_METRIC: CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||
ATTR_UNIT_IMPERIAL: CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||
ATTR_ENABLED: False,
|
||||
},
|
||||
"UVIndex": {
|
||||
ATTR_DEVICE_CLASS: None,
|
||||
ATTR_ICON: "mdi:weather-sunny",
|
||||
ATTR_LABEL: "UV Index",
|
||||
ATTR_UNIT_METRIC: UV_INDEX,
|
||||
ATTR_UNIT_IMPERIAL: UV_INDEX,
|
||||
ATTR_ENABLED: True,
|
||||
},
|
||||
"WindGustDay": {
|
||||
ATTR_DEVICE_CLASS: None,
|
||||
ATTR_ICON: "mdi:weather-windy",
|
||||
ATTR_LABEL: "Wind Gust Day",
|
||||
ATTR_UNIT_METRIC: SPEED_KILOMETERS_PER_HOUR,
|
||||
ATTR_UNIT_IMPERIAL: SPEED_MILES_PER_HOUR,
|
||||
ATTR_ENABLED: False,
|
||||
},
|
||||
"WindGustNight": {
|
||||
ATTR_DEVICE_CLASS: None,
|
||||
ATTR_ICON: "mdi:weather-windy",
|
||||
ATTR_LABEL: "Wind Gust Night",
|
||||
ATTR_UNIT_METRIC: SPEED_KILOMETERS_PER_HOUR,
|
||||
ATTR_UNIT_IMPERIAL: SPEED_MILES_PER_HOUR,
|
||||
ATTR_ENABLED: False,
|
||||
},
|
||||
"WindDay": {
|
||||
ATTR_DEVICE_CLASS: None,
|
||||
ATTR_ICON: "mdi:weather-windy",
|
||||
ATTR_LABEL: "Wind Day",
|
||||
ATTR_UNIT_METRIC: SPEED_KILOMETERS_PER_HOUR,
|
||||
ATTR_UNIT_IMPERIAL: SPEED_MILES_PER_HOUR,
|
||||
ATTR_ENABLED: True,
|
||||
},
|
||||
"WindNight": {
|
||||
ATTR_DEVICE_CLASS: None,
|
||||
ATTR_ICON: "mdi:weather-windy",
|
||||
ATTR_LABEL: "Wind Night",
|
||||
ATTR_UNIT_METRIC: SPEED_KILOMETERS_PER_HOUR,
|
||||
ATTR_UNIT_IMPERIAL: SPEED_MILES_PER_HOUR,
|
||||
ATTR_ENABLED: True,
|
||||
},
|
||||
}
|
||||
FORECAST_SENSOR_TYPES: Final[tuple[AccuWeatherSensorDescription, ...]] = (
|
||||
AccuWeatherSensorDescription(
|
||||
key="CloudCoverDay",
|
||||
icon="mdi:weather-cloudy",
|
||||
name="Cloud Cover Day",
|
||||
unit_metric=PERCENTAGE,
|
||||
unit_imperial=PERCENTAGE,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="CloudCoverNight",
|
||||
icon="mdi:weather-cloudy",
|
||||
name="Cloud Cover Night",
|
||||
unit_metric=PERCENTAGE,
|
||||
unit_imperial=PERCENTAGE,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="Grass",
|
||||
icon="mdi:grass",
|
||||
name="Grass Pollen",
|
||||
unit_metric=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||
unit_imperial=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="HoursOfSun",
|
||||
icon="mdi:weather-partly-cloudy",
|
||||
name="Hours Of Sun",
|
||||
unit_metric=TIME_HOURS,
|
||||
unit_imperial=TIME_HOURS,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="Mold",
|
||||
icon="mdi:blur",
|
||||
name="Mold Pollen",
|
||||
unit_metric=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||
unit_imperial=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="Ozone",
|
||||
icon="mdi:vector-triangle",
|
||||
name="Ozone",
|
||||
unit_metric=None,
|
||||
unit_imperial=None,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="Ragweed",
|
||||
icon="mdi:sprout",
|
||||
name="Ragweed Pollen",
|
||||
unit_metric=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||
unit_imperial=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="RealFeelTemperatureMax",
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
name="RealFeel Temperature Max",
|
||||
unit_metric=TEMP_CELSIUS,
|
||||
unit_imperial=TEMP_FAHRENHEIT,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="RealFeelTemperatureMin",
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
name="RealFeel Temperature Min",
|
||||
unit_metric=TEMP_CELSIUS,
|
||||
unit_imperial=TEMP_FAHRENHEIT,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="RealFeelTemperatureShadeMax",
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
name="RealFeel Temperature Shade Max",
|
||||
unit_metric=TEMP_CELSIUS,
|
||||
unit_imperial=TEMP_FAHRENHEIT,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="RealFeelTemperatureShadeMin",
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
name="RealFeel Temperature Shade Min",
|
||||
unit_metric=TEMP_CELSIUS,
|
||||
unit_imperial=TEMP_FAHRENHEIT,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="ThunderstormProbabilityDay",
|
||||
icon="mdi:weather-lightning",
|
||||
name="Thunderstorm Probability Day",
|
||||
unit_metric=PERCENTAGE,
|
||||
unit_imperial=PERCENTAGE,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="ThunderstormProbabilityNight",
|
||||
icon="mdi:weather-lightning",
|
||||
name="Thunderstorm Probability Night",
|
||||
unit_metric=PERCENTAGE,
|
||||
unit_imperial=PERCENTAGE,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="Tree",
|
||||
icon="mdi:tree-outline",
|
||||
name="Tree Pollen",
|
||||
unit_metric=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||
unit_imperial=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="UVIndex",
|
||||
icon="mdi:weather-sunny",
|
||||
name="UV Index",
|
||||
unit_metric=UV_INDEX,
|
||||
unit_imperial=UV_INDEX,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="WindGustDay",
|
||||
icon="mdi:weather-windy",
|
||||
name="Wind Gust Day",
|
||||
unit_metric=SPEED_KILOMETERS_PER_HOUR,
|
||||
unit_imperial=SPEED_MILES_PER_HOUR,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="WindGustNight",
|
||||
icon="mdi:weather-windy",
|
||||
name="Wind Gust Night",
|
||||
unit_metric=SPEED_KILOMETERS_PER_HOUR,
|
||||
unit_imperial=SPEED_MILES_PER_HOUR,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="WindDay",
|
||||
icon="mdi:weather-windy",
|
||||
name="Wind Day",
|
||||
unit_metric=SPEED_KILOMETERS_PER_HOUR,
|
||||
unit_imperial=SPEED_MILES_PER_HOUR,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="WindNight",
|
||||
icon="mdi:weather-windy",
|
||||
name="Wind Night",
|
||||
unit_metric=SPEED_KILOMETERS_PER_HOUR,
|
||||
unit_imperial=SPEED_MILES_PER_HOUR,
|
||||
),
|
||||
)
|
||||
|
||||
SENSOR_TYPES: Final[dict[str, SensorDescription]] = {
|
||||
"ApparentTemperature": {
|
||||
ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE,
|
||||
ATTR_ICON: None,
|
||||
ATTR_LABEL: "Apparent Temperature",
|
||||
ATTR_UNIT_METRIC: TEMP_CELSIUS,
|
||||
ATTR_UNIT_IMPERIAL: TEMP_FAHRENHEIT,
|
||||
ATTR_ENABLED: False,
|
||||
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
||||
},
|
||||
"Ceiling": {
|
||||
ATTR_DEVICE_CLASS: None,
|
||||
ATTR_ICON: "mdi:weather-fog",
|
||||
ATTR_LABEL: "Cloud Ceiling",
|
||||
ATTR_UNIT_METRIC: LENGTH_METERS,
|
||||
ATTR_UNIT_IMPERIAL: LENGTH_FEET,
|
||||
ATTR_ENABLED: True,
|
||||
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
||||
},
|
||||
"CloudCover": {
|
||||
ATTR_DEVICE_CLASS: None,
|
||||
ATTR_ICON: "mdi:weather-cloudy",
|
||||
ATTR_LABEL: "Cloud Cover",
|
||||
ATTR_UNIT_METRIC: PERCENTAGE,
|
||||
ATTR_UNIT_IMPERIAL: PERCENTAGE,
|
||||
ATTR_ENABLED: False,
|
||||
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
||||
},
|
||||
"DewPoint": {
|
||||
ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE,
|
||||
ATTR_ICON: None,
|
||||
ATTR_LABEL: "Dew Point",
|
||||
ATTR_UNIT_METRIC: TEMP_CELSIUS,
|
||||
ATTR_UNIT_IMPERIAL: TEMP_FAHRENHEIT,
|
||||
ATTR_ENABLED: False,
|
||||
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
||||
},
|
||||
"RealFeelTemperature": {
|
||||
ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE,
|
||||
ATTR_ICON: None,
|
||||
ATTR_LABEL: "RealFeel Temperature",
|
||||
ATTR_UNIT_METRIC: TEMP_CELSIUS,
|
||||
ATTR_UNIT_IMPERIAL: TEMP_FAHRENHEIT,
|
||||
ATTR_ENABLED: True,
|
||||
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
||||
},
|
||||
"RealFeelTemperatureShade": {
|
||||
ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE,
|
||||
ATTR_ICON: None,
|
||||
ATTR_LABEL: "RealFeel Temperature Shade",
|
||||
ATTR_UNIT_METRIC: TEMP_CELSIUS,
|
||||
ATTR_UNIT_IMPERIAL: TEMP_FAHRENHEIT,
|
||||
ATTR_ENABLED: False,
|
||||
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
||||
},
|
||||
"Precipitation": {
|
||||
ATTR_DEVICE_CLASS: None,
|
||||
ATTR_ICON: "mdi:weather-rainy",
|
||||
ATTR_LABEL: "Precipitation",
|
||||
ATTR_UNIT_METRIC: LENGTH_MILLIMETERS,
|
||||
ATTR_UNIT_IMPERIAL: LENGTH_INCHES,
|
||||
ATTR_ENABLED: True,
|
||||
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
||||
},
|
||||
"PressureTendency": {
|
||||
ATTR_DEVICE_CLASS: "accuweather__pressure_tendency",
|
||||
ATTR_ICON: "mdi:gauge",
|
||||
ATTR_LABEL: "Pressure Tendency",
|
||||
ATTR_UNIT_METRIC: None,
|
||||
ATTR_UNIT_IMPERIAL: None,
|
||||
ATTR_ENABLED: True,
|
||||
},
|
||||
"UVIndex": {
|
||||
ATTR_DEVICE_CLASS: None,
|
||||
ATTR_ICON: "mdi:weather-sunny",
|
||||
ATTR_LABEL: "UV Index",
|
||||
ATTR_UNIT_METRIC: UV_INDEX,
|
||||
ATTR_UNIT_IMPERIAL: UV_INDEX,
|
||||
ATTR_ENABLED: True,
|
||||
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
||||
},
|
||||
"WetBulbTemperature": {
|
||||
ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE,
|
||||
ATTR_ICON: None,
|
||||
ATTR_LABEL: "Wet Bulb Temperature",
|
||||
ATTR_UNIT_METRIC: TEMP_CELSIUS,
|
||||
ATTR_UNIT_IMPERIAL: TEMP_FAHRENHEIT,
|
||||
ATTR_ENABLED: False,
|
||||
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
||||
},
|
||||
"WindChillTemperature": {
|
||||
ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE,
|
||||
ATTR_ICON: None,
|
||||
ATTR_LABEL: "Wind Chill Temperature",
|
||||
ATTR_UNIT_METRIC: TEMP_CELSIUS,
|
||||
ATTR_UNIT_IMPERIAL: TEMP_FAHRENHEIT,
|
||||
ATTR_ENABLED: False,
|
||||
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
||||
},
|
||||
"Wind": {
|
||||
ATTR_DEVICE_CLASS: None,
|
||||
ATTR_ICON: "mdi:weather-windy",
|
||||
ATTR_LABEL: "Wind",
|
||||
ATTR_UNIT_METRIC: SPEED_KILOMETERS_PER_HOUR,
|
||||
ATTR_UNIT_IMPERIAL: SPEED_MILES_PER_HOUR,
|
||||
ATTR_ENABLED: True,
|
||||
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
||||
},
|
||||
"WindGust": {
|
||||
ATTR_DEVICE_CLASS: None,
|
||||
ATTR_ICON: "mdi:weather-windy",
|
||||
ATTR_LABEL: "Wind Gust",
|
||||
ATTR_UNIT_METRIC: SPEED_KILOMETERS_PER_HOUR,
|
||||
ATTR_UNIT_IMPERIAL: SPEED_MILES_PER_HOUR,
|
||||
ATTR_ENABLED: False,
|
||||
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
||||
},
|
||||
}
|
||||
SENSOR_TYPES: Final[tuple[AccuWeatherSensorDescription, ...]] = (
|
||||
AccuWeatherSensorDescription(
|
||||
key="ApparentTemperature",
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
name="Apparent Temperature",
|
||||
unit_metric=TEMP_CELSIUS,
|
||||
unit_imperial=TEMP_FAHRENHEIT,
|
||||
entity_registry_enabled_default=False,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="Ceiling",
|
||||
icon="mdi:weather-fog",
|
||||
name="Cloud Ceiling",
|
||||
unit_metric=LENGTH_METERS,
|
||||
unit_imperial=LENGTH_FEET,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="CloudCover",
|
||||
icon="mdi:weather-cloudy",
|
||||
name="Cloud Cover",
|
||||
unit_metric=PERCENTAGE,
|
||||
unit_imperial=PERCENTAGE,
|
||||
entity_registry_enabled_default=False,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="DewPoint",
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
name="Dew Point",
|
||||
unit_metric=TEMP_CELSIUS,
|
||||
unit_imperial=TEMP_FAHRENHEIT,
|
||||
entity_registry_enabled_default=False,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="RealFeelTemperature",
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
name="RealFeel Temperature",
|
||||
unit_metric=TEMP_CELSIUS,
|
||||
unit_imperial=TEMP_FAHRENHEIT,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="RealFeelTemperatureShade",
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
name="RealFeel Temperature Shade",
|
||||
unit_metric=TEMP_CELSIUS,
|
||||
unit_imperial=TEMP_FAHRENHEIT,
|
||||
entity_registry_enabled_default=False,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="Precipitation",
|
||||
icon="mdi:weather-rainy",
|
||||
name="Precipitation",
|
||||
unit_metric=LENGTH_MILLIMETERS,
|
||||
unit_imperial=LENGTH_INCHES,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="PressureTendency",
|
||||
device_class="accuweather__pressure_tendency",
|
||||
icon="mdi:gauge",
|
||||
name="Pressure Tendency",
|
||||
unit_metric=None,
|
||||
unit_imperial=None,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="UVIndex",
|
||||
icon="mdi:weather-sunny",
|
||||
name="UV Index",
|
||||
unit_metric=UV_INDEX,
|
||||
unit_imperial=UV_INDEX,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="WetBulbTemperature",
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
name="Wet Bulb Temperature",
|
||||
unit_metric=TEMP_CELSIUS,
|
||||
unit_imperial=TEMP_FAHRENHEIT,
|
||||
entity_registry_enabled_default=False,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="WindChillTemperature",
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
name="Wind Chill Temperature",
|
||||
unit_metric=TEMP_CELSIUS,
|
||||
unit_imperial=TEMP_FAHRENHEIT,
|
||||
entity_registry_enabled_default=False,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="Wind",
|
||||
icon="mdi:weather-windy",
|
||||
name="Wind",
|
||||
unit_metric=SPEED_KILOMETERS_PER_HOUR,
|
||||
unit_imperial=SPEED_MILES_PER_HOUR,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="WindGust",
|
||||
icon="mdi:weather-windy",
|
||||
name="Wind Gust",
|
||||
unit_metric=SPEED_KILOMETERS_PER_HOUR,
|
||||
unit_imperial=SPEED_MILES_PER_HOUR,
|
||||
entity_registry_enabled_default=False,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
)
|
||||
|
@ -1,16 +1,14 @@
|
||||
"""Type definitions for AccuWeather integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TypedDict
|
||||
from dataclasses import dataclass
|
||||
|
||||
from homeassistant.components.sensor import SensorEntityDescription
|
||||
|
||||
|
||||
class SensorDescription(TypedDict, total=False):
|
||||
"""Sensor description class."""
|
||||
@dataclass
|
||||
class AccuWeatherSensorDescription(SensorEntityDescription):
|
||||
"""Class describing AccuWeather sensor entities."""
|
||||
|
||||
device_class: str | None
|
||||
icon: str | None
|
||||
label: str
|
||||
unit_metric: str | None
|
||||
unit_imperial: str | None
|
||||
enabled: bool
|
||||
state_class: str | None
|
||||
unit_metric: str | None = None
|
||||
unit_imperial: str | None = None
|
||||
|
@ -3,17 +3,10 @@ from __future__ import annotations
|
||||
|
||||
from typing import Any, cast
|
||||
|
||||
from homeassistant.components.sensor import ATTR_STATE_CLASS, SensorEntity
|
||||
from homeassistant.components.sensor import SensorEntity
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
ATTR_ATTRIBUTION,
|
||||
ATTR_DEVICE_CLASS,
|
||||
ATTR_ICON,
|
||||
CONF_NAME,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
)
|
||||
from homeassistant.const import ATTR_ATTRIBUTION, CONF_NAME, DEVICE_CLASS_TEMPERATURE
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.typing import StateType
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
@ -22,11 +15,7 @@ from . import AccuWeatherDataUpdateCoordinator
|
||||
from .const import (
|
||||
API_IMPERIAL,
|
||||
API_METRIC,
|
||||
ATTR_ENABLED,
|
||||
ATTR_FORECAST,
|
||||
ATTR_LABEL,
|
||||
ATTR_UNIT_IMPERIAL,
|
||||
ATTR_UNIT_METRIC,
|
||||
ATTRIBUTION,
|
||||
DOMAIN,
|
||||
FORECAST_SENSOR_TYPES,
|
||||
@ -35,6 +24,7 @@ from .const import (
|
||||
NAME,
|
||||
SENSOR_TYPES,
|
||||
)
|
||||
from .model import AccuWeatherSensorDescription
|
||||
|
||||
PARALLEL_UPDATES = 1
|
||||
|
||||
@ -48,17 +38,19 @@ async def async_setup_entry(
|
||||
coordinator: AccuWeatherDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
|
||||
|
||||
sensors: list[AccuWeatherSensor] = []
|
||||
for sensor in SENSOR_TYPES:
|
||||
sensors.append(AccuWeatherSensor(name, sensor, coordinator))
|
||||
for description in SENSOR_TYPES:
|
||||
sensors.append(AccuWeatherSensor(name, coordinator, description))
|
||||
|
||||
if coordinator.forecast:
|
||||
for sensor in FORECAST_SENSOR_TYPES:
|
||||
for description in FORECAST_SENSOR_TYPES:
|
||||
for day in range(MAX_FORECAST_DAYS + 1):
|
||||
# Some air quality/allergy sensors are only available for certain
|
||||
# locations.
|
||||
if sensor in coordinator.data[ATTR_FORECAST][0]:
|
||||
if description.key in coordinator.data[ATTR_FORECAST][0]:
|
||||
sensors.append(
|
||||
AccuWeatherSensor(name, sensor, coordinator, forecast_day=day)
|
||||
AccuWeatherSensor(
|
||||
name, coordinator, description, forecast_day=day
|
||||
)
|
||||
)
|
||||
|
||||
async_add_entities(sensors)
|
||||
@ -68,119 +60,107 @@ class AccuWeatherSensor(CoordinatorEntity, SensorEntity):
|
||||
"""Define an AccuWeather entity."""
|
||||
|
||||
coordinator: AccuWeatherDataUpdateCoordinator
|
||||
entity_description: AccuWeatherSensorDescription
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
kind: str,
|
||||
coordinator: AccuWeatherDataUpdateCoordinator,
|
||||
description: AccuWeatherSensorDescription,
|
||||
forecast_day: int | None = None,
|
||||
) -> None:
|
||||
"""Initialize."""
|
||||
super().__init__(coordinator)
|
||||
self._sensor_data = _get_sensor_data(coordinator.data, forecast_day, kind)
|
||||
if forecast_day is None:
|
||||
self._description = SENSOR_TYPES[kind]
|
||||
else:
|
||||
self._description = FORECAST_SENSOR_TYPES[kind]
|
||||
self._unit_system = API_METRIC if coordinator.is_metric else API_IMPERIAL
|
||||
self._name = name
|
||||
self.kind = kind
|
||||
self._device_class = None
|
||||
self.entity_description = description
|
||||
self._sensor_data = _get_sensor_data(
|
||||
coordinator.data, forecast_day, description.key
|
||||
)
|
||||
self._attrs = {ATTR_ATTRIBUTION: ATTRIBUTION}
|
||||
self.forecast_day = forecast_day
|
||||
self._attr_state_class = self._description.get(ATTR_STATE_CLASS)
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
"""Return the name."""
|
||||
if self.forecast_day is not None:
|
||||
return f"{self._name} {self._description[ATTR_LABEL]} {self.forecast_day}d"
|
||||
return f"{self._name} {self._description[ATTR_LABEL]}"
|
||||
|
||||
@property
|
||||
def unique_id(self) -> str:
|
||||
"""Return a unique_id for this entity."""
|
||||
if self.forecast_day is not None:
|
||||
return f"{self.coordinator.location_key}-{self.kind}-{self.forecast_day}".lower()
|
||||
return f"{self.coordinator.location_key}-{self.kind}".lower()
|
||||
|
||||
@property
|
||||
def device_info(self) -> DeviceInfo:
|
||||
"""Return the device info."""
|
||||
return {
|
||||
"identifiers": {(DOMAIN, self.coordinator.location_key)},
|
||||
if forecast_day is not None:
|
||||
self._attr_name = f"{name} {description.name} {forecast_day}d"
|
||||
self._attr_unique_id = (
|
||||
f"{coordinator.location_key}-{description.key}-{forecast_day}".lower()
|
||||
)
|
||||
else:
|
||||
self._attr_name = f"{name} {description.name}"
|
||||
self._attr_unique_id = (
|
||||
f"{coordinator.location_key}-{description.key}".lower()
|
||||
)
|
||||
if coordinator.is_metric:
|
||||
self._unit_system = API_METRIC
|
||||
self._attr_unit_of_measurement = description.unit_metric
|
||||
else:
|
||||
self._unit_system = API_IMPERIAL
|
||||
self._attr_unit_of_measurement = description.unit_imperial
|
||||
self._attr_device_info = {
|
||||
"identifiers": {(DOMAIN, coordinator.location_key)},
|
||||
"name": NAME,
|
||||
"manufacturer": MANUFACTURER,
|
||||
"entry_type": "service",
|
||||
}
|
||||
self.forecast_day = forecast_day
|
||||
|
||||
@property
|
||||
def state(self) -> StateType:
|
||||
"""Return the state."""
|
||||
if self.forecast_day is not None:
|
||||
if self._description["device_class"] == DEVICE_CLASS_TEMPERATURE:
|
||||
if self.entity_description.device_class == DEVICE_CLASS_TEMPERATURE:
|
||||
return cast(float, self._sensor_data["Value"])
|
||||
if self.kind == "UVIndex":
|
||||
if self.entity_description.key == "UVIndex":
|
||||
return cast(int, self._sensor_data["Value"])
|
||||
if self.kind in ["Grass", "Mold", "Ragweed", "Tree", "Ozone"]:
|
||||
if self.entity_description.key in ("Grass", "Mold", "Ragweed", "Tree", "Ozone"):
|
||||
return cast(int, self._sensor_data["Value"])
|
||||
if self.kind == "Ceiling":
|
||||
if self.entity_description.key == "Ceiling":
|
||||
return round(self._sensor_data[self._unit_system]["Value"])
|
||||
if self.kind == "PressureTendency":
|
||||
if self.entity_description.key == "PressureTendency":
|
||||
return cast(str, self._sensor_data["LocalizedText"].lower())
|
||||
if self._description["device_class"] == DEVICE_CLASS_TEMPERATURE:
|
||||
if self.entity_description.device_class == DEVICE_CLASS_TEMPERATURE:
|
||||
return cast(float, self._sensor_data[self._unit_system]["Value"])
|
||||
if self.kind == "Precipitation":
|
||||
if self.entity_description.key == "Precipitation":
|
||||
return cast(float, self._sensor_data[self._unit_system]["Value"])
|
||||
if self.kind in ["Wind", "WindGust"]:
|
||||
if self.entity_description.key in ("Wind", "WindGust"):
|
||||
return cast(float, self._sensor_data["Speed"][self._unit_system]["Value"])
|
||||
if self.kind in ["WindDay", "WindNight", "WindGustDay", "WindGustNight"]:
|
||||
if self.entity_description.key in (
|
||||
"WindDay",
|
||||
"WindNight",
|
||||
"WindGustDay",
|
||||
"WindGustNight",
|
||||
):
|
||||
return cast(StateType, self._sensor_data["Speed"]["Value"])
|
||||
return cast(StateType, self._sensor_data)
|
||||
|
||||
@property
|
||||
def icon(self) -> str | None:
|
||||
"""Return the icon."""
|
||||
return self._description[ATTR_ICON]
|
||||
|
||||
@property
|
||||
def device_class(self) -> str | None:
|
||||
"""Return the device_class."""
|
||||
return self._description[ATTR_DEVICE_CLASS]
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self) -> str | None:
|
||||
"""Return the unit the value is expressed in."""
|
||||
if self.coordinator.is_metric:
|
||||
return self._description[ATTR_UNIT_METRIC]
|
||||
return self._description[ATTR_UNIT_IMPERIAL]
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self) -> dict[str, Any]:
|
||||
"""Return the state attributes."""
|
||||
if self.forecast_day is not None:
|
||||
if self.kind in ["WindDay", "WindNight", "WindGustDay", "WindGustNight"]:
|
||||
if self.entity_description.key in (
|
||||
"WindDay",
|
||||
"WindNight",
|
||||
"WindGustDay",
|
||||
"WindGustNight",
|
||||
):
|
||||
self._attrs["direction"] = self._sensor_data["Direction"]["English"]
|
||||
elif self.kind in ["Grass", "Mold", "Ragweed", "Tree", "UVIndex", "Ozone"]:
|
||||
elif self.entity_description.key in (
|
||||
"Grass",
|
||||
"Mold",
|
||||
"Ozone",
|
||||
"Ragweed",
|
||||
"Tree",
|
||||
"UVIndex",
|
||||
):
|
||||
self._attrs["level"] = self._sensor_data["Category"]
|
||||
return self._attrs
|
||||
if self.kind == "UVIndex":
|
||||
if self.entity_description.key == "UVIndex":
|
||||
self._attrs["level"] = self.coordinator.data["UVIndexText"]
|
||||
elif self.kind == "Precipitation":
|
||||
elif self.entity_description.key == "Precipitation":
|
||||
self._attrs["type"] = self.coordinator.data["PrecipitationType"]
|
||||
return self._attrs
|
||||
|
||||
@property
|
||||
def entity_registry_enabled_default(self) -> bool:
|
||||
"""Return if the entity should be enabled when first added to the entity registry."""
|
||||
return self._description[ATTR_ENABLED]
|
||||
|
||||
@callback
|
||||
def _handle_coordinator_update(self) -> None:
|
||||
"""Handle data update."""
|
||||
self._sensor_data = _get_sensor_data(
|
||||
self.coordinator.data, self.forecast_day, self.kind
|
||||
self.coordinator.data, self.forecast_day, self.entity_description.key
|
||||
)
|
||||
self.async_write_ha_state()
|
||||
|
||||
|
30
homeassistant/components/accuweather/translations/ar.json
Normal file
30
homeassistant/components/accuweather/translations/ar.json
Normal file
@ -0,0 +1,30 @@
|
||||
{
|
||||
"config": {
|
||||
"error": {
|
||||
"requests_exceeded": "\u062a\u0645 \u062a\u062c\u0627\u0648\u0632 \u0627\u0644\u0639\u062f\u062f \u0627\u0644\u0645\u0633\u0645\u0648\u062d \u0628\u0647 \u0645\u0646 \u0627\u0644\u0637\u0644\u0628\u0627\u062a \u0625\u0644\u0649 Accuweather API. \u0639\u0644\u064a\u0643 \u0627\u0644\u0627\u0646\u062a\u0638\u0627\u0631 \u0623\u0648 \u062a\u063a\u064a\u064a\u0631 \u0645\u0641\u062a\u0627\u062d API."
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"description": "\u0625\u0630\u0627 \u0643\u0646\u062a \u0628\u062d\u0627\u062c\u0629 \u0625\u0644\u0649 \u0645\u0633\u0627\u0639\u062f\u0629 \u0641\u064a \u0627\u0644\u062a\u0643\u0648\u064a\u0646 \u060c \u0641\u0642\u0645 \u0628\u0625\u0644\u0642\u0627\u0621 \u0646\u0638\u0631\u0629 \u0647\u0646\u0627: https://www.home-assistant.io/integrations/accuweather/ \n\n \u0644\u0627 \u064a\u062a\u0645 \u062a\u0645\u0643\u064a\u0646 \u0628\u0639\u0636 \u0623\u062c\u0647\u0632\u0629 \u0627\u0644\u0627\u0633\u062a\u0634\u0639\u0627\u0631 \u0628\u0634\u0643\u0644 \u0627\u0641\u062a\u0631\u0627\u0636\u064a. \u064a\u0645\u0643\u0646\u0643 \u062a\u0645\u0643\u064a\u0646\u0647\u0645 \u0641\u064a \u0633\u062c\u0644 \u0627\u0644\u0643\u064a\u0627\u0646 \u0628\u0639\u062f \u062a\u0643\u0648\u064a\u0646 \u0627\u0644\u062a\u0643\u0627\u0645\u0644.\n \u0644\u0627 \u064a\u062a\u0645 \u062a\u0645\u0643\u064a\u0646 \u062a\u0648\u0642\u0639\u0627\u062a \u0627\u0644\u0637\u0642\u0633 \u0627\u0641\u062a\u0631\u0627\u0636\u064a\u064b\u0627. \u064a\u0645\u0643\u0646\u0643 \u062a\u0645\u0643\u064a\u0646\u0647 \u0641\u064a \u062e\u064a\u0627\u0631\u0627\u062a \u0627\u0644\u062a\u0643\u0627\u0645\u0644.",
|
||||
"title": "AccuWeather"
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"forecast": "\u0627\u0644\u0646\u0634\u0631\u0629 \u0627\u0644\u062c\u0648\u064a\u0629"
|
||||
},
|
||||
"description": "\u0646\u0638\u0631\u064b\u0627 \u0644\u0642\u064a\u0648\u062f \u0627\u0644\u0625\u0635\u062f\u0627\u0631 \u0627\u0644\u0645\u062c\u0627\u0646\u064a \u0645\u0646 \u0645\u0641\u062a\u0627\u062d AccuWeather API \u060c \u0639\u0646\u062f \u062a\u0645\u0643\u064a\u0646 \u0627\u0644\u062a\u0646\u0628\u0624 \u0628\u0627\u0644\u0637\u0642\u0633 \u060c \u0633\u064a\u062a\u0645 \u0625\u062c\u0631\u0627\u0621 \u062a\u062d\u062f\u064a\u062b\u0627\u062a \u0627\u0644\u0628\u064a\u0627\u0646\u0627\u062a \u0643\u0644 80 \u062f\u0642\u064a\u0642\u0629 \u0628\u062f\u0644\u0627\u064b \u0645\u0646 \u0643\u0644 40 \u062f\u0642\u064a\u0642\u0629.",
|
||||
"title": "\u062e\u064a\u0627\u0631\u0627\u062a AccuWeather"
|
||||
}
|
||||
}
|
||||
},
|
||||
"system_health": {
|
||||
"info": {
|
||||
"can_reach_server": "\u0627\u0644\u0648\u0635\u0648\u0644 \u0625\u0644\u0649 \u062e\u0627\u062f\u0645 AccuWeather",
|
||||
"remaining_requests": "\u0627\u0644\u0637\u0644\u0628\u0627\u062a \u0627\u0644\u0645\u062a\u0628\u0642\u064a\u0629 \u0627\u0644\u0645\u0633\u0645\u0648\u062d \u0628\u0647\u0627"
|
||||
}
|
||||
}
|
||||
}
|
@ -6,7 +6,7 @@
|
||||
"error": {
|
||||
"cannot_connect": "Verbindung fehlgeschlagen",
|
||||
"invalid_api_key": "Ung\u00fcltiger API-Schl\u00fcssel",
|
||||
"requests_exceeded": "Die zul\u00e4ssige Anzahl von Anforderungen an die Accuweather-API wurde \u00fcberschritten. Sie m\u00fcssen warten oder den API-Schl\u00fcssel \u00e4ndern."
|
||||
"requests_exceeded": "Die zul\u00e4ssige Anzahl von Anforderungen an die Accuweather-API wurde \u00fcberschritten. Du musst warten oder den API-Schl\u00fcssel \u00e4ndern."
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
|
@ -1,6 +1,11 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"single_instance_allowed": "Ya configurado. Solo es posible una \u00fanica configuraci\u00f3n."
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "No se pudo conectar",
|
||||
"invalid_api_key": "Clave de API no v\u00e1lida",
|
||||
"requests_exceeded": "Se super\u00f3 el n\u00famero permitido de solicitudes a la API de Accuweather. Tiene que esperar o cambiar la clave de API."
|
||||
},
|
||||
"step": {
|
||||
|
@ -14,8 +14,22 @@
|
||||
"latitude": "\u05e7\u05d5 \u05e8\u05d5\u05d7\u05d1",
|
||||
"longitude": "\u05e7\u05d5 \u05d0\u05d5\u05e8\u05da",
|
||||
"name": "\u05e9\u05dd"
|
||||
}
|
||||
},
|
||||
"title": "AccuWeather"
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"step": {
|
||||
"user": {
|
||||
"description": "\u05d1\u05e9\u05dc \u05de\u05d2\u05d1\u05dc\u05d5\u05ea \u05d4\u05d2\u05d9\u05e8\u05e1\u05d4 \u05d4\u05d7\u05d9\u05e0\u05de\u05d9\u05ea \u05e9\u05dc \u05de\u05e4\u05ea\u05d7 \u05d4-API \u05e9\u05dc AccuWeather, \u05db\u05d0\u05e9\u05e8 \u05ea\u05e4\u05e2\u05d9\u05dc \u05ea\u05d7\u05d6\u05d9\u05ea \u05de\u05d6\u05d2 \u05d0\u05d5\u05d5\u05d9\u05e8, \u05e2\u05d3\u05db\u05d5\u05e0\u05d9 \u05e0\u05ea\u05d5\u05e0\u05d9\u05dd \u05d9\u05d1\u05d5\u05e6\u05e2\u05d5 \u05db\u05dc 80 \u05d3\u05e7\u05d5\u05ea \u05d1\u05de\u05e7\u05d5\u05dd \u05db\u05dc 40 \u05d3\u05e7\u05d5\u05ea.",
|
||||
"title": "\u05d0\u05e4\u05e9\u05e8\u05d5\u05d9\u05d5\u05ea AccuWeather"
|
||||
}
|
||||
}
|
||||
},
|
||||
"system_health": {
|
||||
"info": {
|
||||
"can_reach_server": "\u05d4\u05e9\u05d2\u05ea \u05e9\u05e8\u05ea AccuWeather"
|
||||
}
|
||||
}
|
||||
}
|
@ -25,5 +25,11 @@
|
||||
"title": "AccuWeather be\u00e1ll\u00edt\u00e1sok"
|
||||
}
|
||||
}
|
||||
},
|
||||
"system_health": {
|
||||
"info": {
|
||||
"can_reach_server": "\u00c9rje el az AccuWeather szervert",
|
||||
"remaining_requests": "Fennmarad\u00f3 enged\u00e9lyezett k\u00e9r\u00e9sek"
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
{
|
||||
"state": {
|
||||
"accuweather__pressure_tendency": {
|
||||
"falling": "\u0647\u0628\u0648\u0637",
|
||||
"rising": "\u0627\u0631\u062a\u0641\u0627\u0639",
|
||||
"steady": "\u062b\u0627\u0628\u062a"
|
||||
}
|
||||
}
|
||||
}
|
@ -19,7 +19,6 @@ from homeassistant.components.weather import (
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_NAME, TEMP_CELSIUS, TEMP_FAHRENHEIT
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
from homeassistant.util.dt import utc_from_timestamp
|
||||
@ -60,29 +59,15 @@ class AccuWeatherEntity(CoordinatorEntity, WeatherEntity):
|
||||
) -> None:
|
||||
"""Initialize."""
|
||||
super().__init__(coordinator)
|
||||
self._name = name
|
||||
self._unit_system = API_METRIC if self.coordinator.is_metric else API_IMPERIAL
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
"""Return the name."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def attribution(self) -> str:
|
||||
"""Return the attribution."""
|
||||
return ATTRIBUTION
|
||||
|
||||
@property
|
||||
def unique_id(self) -> str:
|
||||
"""Return a unique_id for this entity."""
|
||||
return self.coordinator.location_key
|
||||
|
||||
@property
|
||||
def device_info(self) -> DeviceInfo:
|
||||
"""Return the device info."""
|
||||
return {
|
||||
"identifiers": {(DOMAIN, self.coordinator.location_key)},
|
||||
self._unit_system = API_METRIC if coordinator.is_metric else API_IMPERIAL
|
||||
self._attr_name = name
|
||||
self._attr_unique_id = coordinator.location_key
|
||||
self._attr_temperature_unit = (
|
||||
TEMP_CELSIUS if coordinator.is_metric else TEMP_FAHRENHEIT
|
||||
)
|
||||
self._attr_attribution = ATTRIBUTION
|
||||
self._attr_device_info = {
|
||||
"identifiers": {(DOMAIN, coordinator.location_key)},
|
||||
"name": NAME,
|
||||
"manufacturer": MANUFACTURER,
|
||||
"entry_type": "service",
|
||||
@ -107,11 +92,6 @@ class AccuWeatherEntity(CoordinatorEntity, WeatherEntity):
|
||||
float, self.coordinator.data["Temperature"][self._unit_system]["Value"]
|
||||
)
|
||||
|
||||
@property
|
||||
def temperature_unit(self) -> str:
|
||||
"""Return the unit of measurement."""
|
||||
return TEMP_CELSIUS if self.coordinator.is_metric else TEMP_FAHRENHEIT
|
||||
|
||||
@property
|
||||
def pressure(self) -> float:
|
||||
"""Return the pressure."""
|
||||
|
@ -67,6 +67,8 @@ def setup_platform(
|
||||
class AcerSwitch(SwitchEntity):
|
||||
"""Represents an Acer Projector as a switch."""
|
||||
|
||||
_attr_icon = ICON
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
serial_port: str,
|
||||
@ -79,9 +81,7 @@ class AcerSwitch(SwitchEntity):
|
||||
port=serial_port, timeout=timeout, write_timeout=write_timeout
|
||||
)
|
||||
self._serial_port = serial_port
|
||||
self._name = name
|
||||
self._state = False
|
||||
self._available = False
|
||||
self._attr_name = name
|
||||
self._attributes = {
|
||||
LAMP_HOURS: STATE_UNKNOWN,
|
||||
INPUT_SOURCE: STATE_UNKNOWN,
|
||||
@ -116,57 +116,33 @@ class AcerSwitch(SwitchEntity):
|
||||
return match.group(1)
|
||||
return STATE_UNKNOWN
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
"""Return if projector is available."""
|
||||
return self._available
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
"""Return name of the projector."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def icon(self) -> str:
|
||||
"""Return the icon."""
|
||||
return ICON
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
"""Return if the projector is turned on."""
|
||||
return self._state
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self) -> dict[str, str]:
|
||||
"""Return state attributes."""
|
||||
return self._attributes
|
||||
|
||||
def update(self) -> None:
|
||||
"""Get the latest state from the projector."""
|
||||
awns = self._write_read_format(CMD_DICT[LAMP])
|
||||
if awns == "Lamp 1":
|
||||
self._state = True
|
||||
self._available = True
|
||||
self._attr_is_on = True
|
||||
self._attr_available = True
|
||||
elif awns == "Lamp 0":
|
||||
self._state = False
|
||||
self._available = True
|
||||
self._attr_is_on = False
|
||||
self._attr_available = True
|
||||
else:
|
||||
self._available = False
|
||||
self._attr_available = False
|
||||
|
||||
for key in self._attributes:
|
||||
msg = CMD_DICT.get(key)
|
||||
if msg:
|
||||
awns = self._write_read_format(msg)
|
||||
self._attributes[key] = awns
|
||||
self._attr_extra_state_attributes = self._attributes
|
||||
|
||||
def turn_on(self, **kwargs: Any) -> None:
|
||||
"""Turn the projector on."""
|
||||
msg = CMD_DICT[STATE_ON]
|
||||
self._write_read(msg)
|
||||
self._state = True
|
||||
self._attr_is_on = True
|
||||
|
||||
def turn_off(self, **kwargs: Any) -> None:
|
||||
"""Turn the projector off."""
|
||||
msg = CMD_DICT[STATE_OFF]
|
||||
self._write_read(msg)
|
||||
self._state = False
|
||||
self._attr_is_on = False
|
||||
|
18
homeassistant/components/adax/__init__.py
Normal file
18
homeassistant/components/adax/__init__.py
Normal file
@ -0,0 +1,18 @@
|
||||
"""The Adax integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
PLATFORMS = ["climate"]
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up Adax from a config entry."""
|
||||
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Unload a config entry."""
|
||||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
152
homeassistant/components/adax/climate.py
Normal file
152
homeassistant/components/adax/climate.py
Normal file
@ -0,0 +1,152 @@
|
||||
"""Support for Adax wifi-enabled home heaters."""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from adax import Adax
|
||||
|
||||
from homeassistant.components.climate import ClimateEntity
|
||||
from homeassistant.components.climate.const import (
|
||||
HVAC_MODE_HEAT,
|
||||
HVAC_MODE_OFF,
|
||||
SUPPORT_TARGET_TEMPERATURE,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
ATTR_TEMPERATURE,
|
||||
CONF_PASSWORD,
|
||||
PRECISION_WHOLE,
|
||||
TEMP_CELSIUS,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import ACCOUNT_ID
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the Adax thermostat with config flow."""
|
||||
adax_data_handler = Adax(
|
||||
entry.data[ACCOUNT_ID],
|
||||
entry.data[CONF_PASSWORD],
|
||||
websession=async_get_clientsession(hass),
|
||||
)
|
||||
|
||||
async_add_entities(
|
||||
AdaxDevice(room, adax_data_handler)
|
||||
for room in await adax_data_handler.get_rooms()
|
||||
)
|
||||
|
||||
|
||||
class AdaxDevice(ClimateEntity):
|
||||
"""Representation of a heater."""
|
||||
|
||||
def __init__(self, heater_data: dict[str, Any], adax_data_handler: Adax) -> None:
|
||||
"""Initialize the heater."""
|
||||
self._heater_data = heater_data
|
||||
self._adax_data_handler = adax_data_handler
|
||||
|
||||
@property
|
||||
def supported_features(self) -> int:
|
||||
"""Return the list of supported features."""
|
||||
return SUPPORT_TARGET_TEMPERATURE
|
||||
|
||||
@property
|
||||
def unique_id(self) -> str:
|
||||
"""Return a unique ID."""
|
||||
return f"{self._heater_data['homeId']}_{self._heater_data['id']}"
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
"""Return the name of the device, if any."""
|
||||
return self._heater_data["name"]
|
||||
|
||||
@property
|
||||
def hvac_mode(self) -> str:
|
||||
"""Return hvac operation ie. heat, cool mode."""
|
||||
if self._heater_data["heatingEnabled"]:
|
||||
return HVAC_MODE_HEAT
|
||||
return HVAC_MODE_OFF
|
||||
|
||||
@property
|
||||
def icon(self) -> str:
|
||||
"""Return nice icon for heater."""
|
||||
if self.hvac_mode == HVAC_MODE_HEAT:
|
||||
return "mdi:radiator"
|
||||
return "mdi:radiator-off"
|
||||
|
||||
@property
|
||||
def hvac_modes(self) -> list[str]:
|
||||
"""Return the list of available hvac operation modes."""
|
||||
return [HVAC_MODE_HEAT, HVAC_MODE_OFF]
|
||||
|
||||
async def async_set_hvac_mode(self, hvac_mode: str) -> None:
|
||||
"""Set hvac mode."""
|
||||
if hvac_mode == HVAC_MODE_HEAT:
|
||||
temperature = max(
|
||||
self.min_temp, self._heater_data.get("targetTemperature", self.min_temp)
|
||||
)
|
||||
await self._adax_data_handler.set_room_target_temperature(
|
||||
self._heater_data["id"], temperature, True
|
||||
)
|
||||
elif hvac_mode == HVAC_MODE_OFF:
|
||||
await self._adax_data_handler.set_room_target_temperature(
|
||||
self._heater_data["id"], self.min_temp, False
|
||||
)
|
||||
else:
|
||||
return
|
||||
await self._adax_data_handler.update()
|
||||
|
||||
@property
|
||||
def temperature_unit(self) -> str:
|
||||
"""Return the unit of measurement which this device uses."""
|
||||
return TEMP_CELSIUS
|
||||
|
||||
@property
|
||||
def min_temp(self) -> int:
|
||||
"""Return the minimum temperature."""
|
||||
return 5
|
||||
|
||||
@property
|
||||
def max_temp(self) -> int:
|
||||
"""Return the maximum temperature."""
|
||||
return 35
|
||||
|
||||
@property
|
||||
def current_temperature(self) -> float | None:
|
||||
"""Return the current temperature."""
|
||||
return self._heater_data.get("temperature")
|
||||
|
||||
@property
|
||||
def target_temperature(self) -> int | None:
|
||||
"""Return the temperature we try to reach."""
|
||||
return self._heater_data.get("targetTemperature")
|
||||
|
||||
@property
|
||||
def target_temperature_step(self) -> int:
|
||||
"""Return the supported step of target temperature."""
|
||||
return PRECISION_WHOLE
|
||||
|
||||
async def async_set_temperature(self, **kwargs: Any) -> None:
|
||||
"""Set new target temperature."""
|
||||
temperature = kwargs.get(ATTR_TEMPERATURE)
|
||||
if temperature is None:
|
||||
return
|
||||
await self._adax_data_handler.set_room_target_temperature(
|
||||
self._heater_data["id"], temperature, True
|
||||
)
|
||||
|
||||
async def async_update(self) -> None:
|
||||
"""Get the latest data."""
|
||||
for room in await self._adax_data_handler.get_rooms():
|
||||
if room["id"] == self._heater_data["id"]:
|
||||
self._heater_data = room
|
||||
return
|
73
homeassistant/components/adax/config_flow.py
Normal file
73
homeassistant/components/adax/config_flow.py
Normal file
@ -0,0 +1,73 @@
|
||||
"""Config flow for Adax integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
import adax
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.const import CONF_PASSWORD
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.data_entry_flow import FlowResult
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
|
||||
from .const import ACCOUNT_ID, DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
STEP_USER_DATA_SCHEMA = vol.Schema(
|
||||
{vol.Required(ACCOUNT_ID): int, vol.Required(CONF_PASSWORD): str}
|
||||
)
|
||||
|
||||
|
||||
async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> None:
|
||||
"""Validate the user input allows us to connect."""
|
||||
account_id = data[ACCOUNT_ID]
|
||||
password = data[CONF_PASSWORD].replace(" ", "")
|
||||
|
||||
token = await adax.get_adax_token(
|
||||
async_get_clientsession(hass), account_id, password
|
||||
)
|
||||
if token is None:
|
||||
_LOGGER.info("Adax: Failed to login to retrieve token")
|
||||
raise CannotConnect
|
||||
|
||||
|
||||
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a config flow for Adax."""
|
||||
|
||||
VERSION = 1
|
||||
|
||||
async def async_step_user(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> FlowResult:
|
||||
"""Handle the initial step."""
|
||||
if user_input is None:
|
||||
return self.async_show_form(
|
||||
step_id="user", data_schema=STEP_USER_DATA_SCHEMA
|
||||
)
|
||||
|
||||
errors = {}
|
||||
|
||||
await self.async_set_unique_id(user_input[ACCOUNT_ID])
|
||||
self._abort_if_unique_id_configured()
|
||||
|
||||
try:
|
||||
await validate_input(self.hass, user_input)
|
||||
except CannotConnect:
|
||||
errors["base"] = "cannot_connect"
|
||||
else:
|
||||
return self.async_create_entry(
|
||||
title=user_input[ACCOUNT_ID], data=user_input
|
||||
)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors
|
||||
)
|
||||
|
||||
|
||||
class CannotConnect(HomeAssistantError):
|
||||
"""Error to indicate we cannot connect."""
|
5
homeassistant/components/adax/const.py
Normal file
5
homeassistant/components/adax/const.py
Normal file
@ -0,0 +1,5 @@
|
||||
"""Constants for the Adax integration."""
|
||||
from typing import Final
|
||||
|
||||
ACCOUNT_ID: Final = "account_id"
|
||||
DOMAIN: Final = "adax"
|
13
homeassistant/components/adax/manifest.json
Normal file
13
homeassistant/components/adax/manifest.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"domain": "adax",
|
||||
"name": "Adax",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/adax",
|
||||
"requirements": [
|
||||
"adax==0.0.1"
|
||||
],
|
||||
"codeowners": [
|
||||
"@danielhiversen"
|
||||
],
|
||||
"iot_class": "cloud_polling"
|
||||
}
|
18
homeassistant/components/adax/strings.json
Normal file
18
homeassistant/components/adax/strings.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"account_id": "Account ID",
|
||||
"password": "[%key:common::config_flow::data::password%]"
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
|
||||
},
|
||||
"abort": {
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
||||
}
|
||||
}
|
||||
}
|
20
homeassistant/components/adax/translations/ca.json
Normal file
20
homeassistant/components/adax/translations/ca.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "El dispositiu ja est\u00e0 configurat"
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "Ha fallat la connexi\u00f3",
|
||||
"invalid_auth": "Autenticaci\u00f3 inv\u00e0lida"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"account_id": "ID del compte",
|
||||
"host": "Amfitri\u00f3",
|
||||
"password": "Contrasenya"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
19
homeassistant/components/adax/translations/cs.json
Normal file
19
homeassistant/components/adax/translations/cs.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Za\u0159\u00edzen\u00ed je ji\u017e nastaveno"
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit",
|
||||
"invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"host": "Hostitel",
|
||||
"password": "Heslo"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
20
homeassistant/components/adax/translations/de.json
Normal file
20
homeassistant/components/adax/translations/de.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Ger\u00e4t ist bereits konfiguriert"
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "Verbindung fehlgeschlagen",
|
||||
"invalid_auth": "Ung\u00fcltige Authentifizierung"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"account_id": "Konto-ID",
|
||||
"host": "Host",
|
||||
"password": "Passwort"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
20
homeassistant/components/adax/translations/en.json
Normal file
20
homeassistant/components/adax/translations/en.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Device is already configured"
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "Failed to connect",
|
||||
"invalid_auth": "Invalid authentication"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"account_id": "Account ID",
|
||||
"host": "Host",
|
||||
"password": "Password"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
20
homeassistant/components/adax/translations/et.json
Normal file
20
homeassistant/components/adax/translations/et.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Seade on juba h\u00e4\u00e4lestatud"
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "\u00dchendamine nurjus",
|
||||
"invalid_auth": "Tuvastamise viga"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"account_id": "Konto ID",
|
||||
"host": "Host",
|
||||
"password": "Salas\u00f5na"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
20
homeassistant/components/adax/translations/fr.json
Normal file
20
homeassistant/components/adax/translations/fr.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9"
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "\u00c9chec de connexion",
|
||||
"invalid_auth": "Authentification invalide"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"account_id": "identifiant de compte",
|
||||
"host": "H\u00f4te",
|
||||
"password": "Mot de passe"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
20
homeassistant/components/adax/translations/he.json
Normal file
20
homeassistant/components/adax/translations/he.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "\u05ea\u05e6\u05d5\u05e8\u05ea \u05d4\u05d4\u05ea\u05e7\u05df \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4"
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "\u05d4\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4",
|
||||
"invalid_auth": "\u05d0\u05d9\u05de\u05d5\u05ea \u05dc\u05d0 \u05d7\u05d5\u05e7\u05d9"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"account_id": "\u05de\u05d6\u05d4\u05d4 \u05d7\u05e9\u05d1\u05d5\u05df",
|
||||
"host": "\u05de\u05d0\u05e8\u05d7",
|
||||
"password": "\u05e1\u05d9\u05e1\u05de\u05d4"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
20
homeassistant/components/adax/translations/it.json
Normal file
20
homeassistant/components/adax/translations/it.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato"
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "Impossibile connettersi",
|
||||
"invalid_auth": "Autenticazione non valida"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"account_id": "ID account",
|
||||
"host": "Host",
|
||||
"password": "Password"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
20
homeassistant/components/adax/translations/nl.json
Normal file
20
homeassistant/components/adax/translations/nl.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Apparaat is al geconfigureerd"
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "Kan geen verbinding maken",
|
||||
"invalid_auth": "Ongeldige authenticatie"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"account_id": "Account ID",
|
||||
"host": "Host",
|
||||
"password": "Wachtwoord"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
20
homeassistant/components/adax/translations/pl.json
Normal file
20
homeassistant/components/adax/translations/pl.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane"
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia",
|
||||
"invalid_auth": "Niepoprawne uwierzytelnienie"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"account_id": "Identyfikator konta",
|
||||
"host": "Nazwa hosta lub adres IP",
|
||||
"password": "Has\u0142o"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
20
homeassistant/components/adax/translations/ru.json
Normal file
20
homeassistant/components/adax/translations/ru.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "\u042d\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043e \u0432 Home Assistant."
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.",
|
||||
"invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438."
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"account_id": "ID \u0443\u0447\u0451\u0442\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438",
|
||||
"host": "\u0425\u043e\u0441\u0442",
|
||||
"password": "\u041f\u0430\u0440\u043e\u043b\u044c"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
20
homeassistant/components/adax/translations/zh-Hant.json
Normal file
20
homeassistant/components/adax/translations/zh-Hant.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "\u88dd\u7f6e\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210"
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "\u9023\u7dda\u5931\u6557",
|
||||
"invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"account_id": "\u5e33\u865f ID",
|
||||
"host": "\u4e3b\u6a5f\u7aef",
|
||||
"password": "\u5bc6\u78bc"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -17,7 +17,7 @@
|
||||
"host": "Host",
|
||||
"password": "Passwort",
|
||||
"port": "Port",
|
||||
"ssl": "AdGuard Home verwendet ein SSL-Zertifikat",
|
||||
"ssl": "Verwendet ein SSL-Zertifikat",
|
||||
"username": "Benutzername",
|
||||
"verify_ssl": "SSL-Zertifikat \u00fcberpr\u00fcfen"
|
||||
},
|
||||
|
@ -1,9 +1,16 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "A szolg\u00e1ltat\u00e1s m\u00e1r konfigur\u00e1lva van"
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "Sikertelen csatlakoz\u00e1s"
|
||||
},
|
||||
"step": {
|
||||
"hassio_confirm": {
|
||||
"description": "Be szeretn\u00e9 \u00e1ll\u00edtani a Home Assistant-ot, hogy csatlakozzon az AdGuard Home-hoz, amelyet a kieg\u00e9sz\u00edt\u0151 biztos\u00edt: {addon} ?",
|
||||
"title": "Az AdGuard Home a Home Assistant kieg\u00e9sz\u00edt\u0151 seg\u00edts\u00e9g\u00e9vel"
|
||||
},
|
||||
"user": {
|
||||
"data": {
|
||||
"host": "Hoszt",
|
||||
|
@ -1,6 +1,7 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Layanan sudah dikonfigurasi",
|
||||
"existing_instance_updated": "Memperbarui konfigurasi yang ada."
|
||||
},
|
||||
"error": {
|
||||
|
@ -268,15 +268,17 @@ class AdsHub:
|
||||
class AdsEntity(Entity):
|
||||
"""Representation of ADS entity."""
|
||||
|
||||
_attr_should_poll = False
|
||||
|
||||
def __init__(self, ads_hub, name, ads_var):
|
||||
"""Initialize ADS binary sensor."""
|
||||
self._name = name
|
||||
self._unique_id = ads_var
|
||||
self._state_dict = {}
|
||||
self._state_dict[STATE_KEY_STATE] = None
|
||||
self._ads_hub = ads_hub
|
||||
self._ads_var = ads_var
|
||||
self._event = None
|
||||
self._attr_unique_id = ads_var
|
||||
self._attr_name = name
|
||||
|
||||
async def async_initialize_device(
|
||||
self, ads_var, plctype, state_key=STATE_KEY_STATE, factor=None
|
||||
@ -311,21 +313,6 @@ class AdsEntity(Entity):
|
||||
_LOGGER.debug("Variable %s: Timeout during first update", ads_var)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the default name of the binary sensor."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return an unique identifier for this entity."""
|
||||
return self._unique_id
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""Return False because entity pushes its state to HA."""
|
||||
return False
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
def available(self) -> bool:
|
||||
"""Return False if state has not been updated yet."""
|
||||
return self._state_dict[STATE_KEY_STATE] is not None
|
||||
|
@ -40,18 +40,13 @@ class AdsBinarySensor(AdsEntity, BinarySensorEntity):
|
||||
def __init__(self, ads_hub, name, ads_var, device_class):
|
||||
"""Initialize ADS binary sensor."""
|
||||
super().__init__(ads_hub, name, ads_var)
|
||||
self._device_class = device_class or DEVICE_CLASS_MOVING
|
||||
self._attr_device_class = device_class or DEVICE_CLASS_MOVING
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""Register device notification."""
|
||||
await self.async_initialize_device(self._ads_var, self._ads_hub.PLCTYPE_BOOL)
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
def is_on(self) -> bool:
|
||||
"""Return True if the entity is on."""
|
||||
return self._state_dict[STATE_KEY_STATE]
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
"""Return the device class."""
|
||||
return self._device_class
|
||||
|
@ -105,7 +105,12 @@ class AdsCover(AdsEntity, CoverEntity):
|
||||
self._ads_var_open = ads_var_open
|
||||
self._ads_var_close = ads_var_close
|
||||
self._ads_var_stop = ads_var_stop
|
||||
self._device_class = device_class
|
||||
self._attr_device_class = device_class
|
||||
self._attr_supported_features = SUPPORT_OPEN | SUPPORT_CLOSE
|
||||
if ads_var_stop is not None:
|
||||
self._attr_supported_features |= SUPPORT_STOP
|
||||
if ads_var_pos_set is not None:
|
||||
self._attr_supported_features |= SUPPORT_SET_POSITION
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""Register device notification."""
|
||||
@ -119,11 +124,6 @@ class AdsCover(AdsEntity, CoverEntity):
|
||||
self._ads_var_position, self._ads_hub.PLCTYPE_BYTE, STATE_KEY_POSITION
|
||||
)
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
"""Return the class of this cover."""
|
||||
return self._device_class
|
||||
|
||||
@property
|
||||
def is_closed(self):
|
||||
"""Return if the cover is closed."""
|
||||
@ -138,19 +138,6 @@ class AdsCover(AdsEntity, CoverEntity):
|
||||
"""Return current position of cover."""
|
||||
return self._state_dict[STATE_KEY_POSITION]
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
"""Flag supported features."""
|
||||
supported_features = SUPPORT_OPEN | SUPPORT_CLOSE
|
||||
|
||||
if self._ads_var_stop is not None:
|
||||
supported_features |= SUPPORT_STOP
|
||||
|
||||
if self._ads_var_pos_set is not None:
|
||||
supported_features |= SUPPORT_SET_POSITION
|
||||
|
||||
return supported_features
|
||||
|
||||
def stop_cover(self, **kwargs):
|
||||
"""Fire the stop action."""
|
||||
if self._ads_var_stop:
|
||||
@ -185,7 +172,7 @@ class AdsCover(AdsEntity, CoverEntity):
|
||||
self.set_cover_position(position=0)
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
def available(self) -> bool:
|
||||
"""Return False if state has not been updated yet."""
|
||||
if self._ads_var is not None or self._ads_var_position is not None:
|
||||
return (
|
||||
|
@ -1,4 +1,6 @@
|
||||
"""Support for ADS light sources."""
|
||||
from __future__ import annotations
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.light import (
|
||||
@ -48,6 +50,8 @@ class AdsLight(AdsEntity, LightEntity):
|
||||
super().__init__(ads_hub, name, ads_var_enable)
|
||||
self._state_dict[STATE_KEY_BRIGHTNESS] = None
|
||||
self._ads_var_brightness = ads_var_brightness
|
||||
if ads_var_brightness is not None:
|
||||
self._attr_supported_features = SUPPORT_BRIGHTNESS
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""Register device notification."""
|
||||
@ -61,19 +65,12 @@ class AdsLight(AdsEntity, LightEntity):
|
||||
)
|
||||
|
||||
@property
|
||||
def brightness(self):
|
||||
def brightness(self) -> int | None:
|
||||
"""Return the brightness of the light (0..255)."""
|
||||
return self._state_dict[STATE_KEY_BRIGHTNESS]
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
"""Flag supported features."""
|
||||
if self._ads_var_brightness is not None:
|
||||
return SUPPORT_BRIGHTNESS
|
||||
return 0
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
def is_on(self) -> bool:
|
||||
"""Return True if the entity is on."""
|
||||
return self._state_dict[STATE_KEY_STATE]
|
||||
|
||||
|
@ -5,6 +5,7 @@ from homeassistant.components import ads
|
||||
from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity
|
||||
from homeassistant.const import CONF_NAME, CONF_UNIT_OF_MEASUREMENT
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.typing import StateType
|
||||
|
||||
from . import CONF_ADS_FACTOR, CONF_ADS_TYPE, CONF_ADS_VAR, STATE_KEY_STATE, AdsEntity
|
||||
|
||||
@ -49,7 +50,7 @@ class AdsSensor(AdsEntity, SensorEntity):
|
||||
def __init__(self, ads_hub, ads_var, ads_type, name, unit_of_measurement, factor):
|
||||
"""Initialize AdsSensor entity."""
|
||||
super().__init__(ads_hub, name, ads_var)
|
||||
self._unit_of_measurement = unit_of_measurement
|
||||
self._attr_unit_of_measurement = unit_of_measurement
|
||||
self._ads_type = ads_type
|
||||
self._factor = factor
|
||||
|
||||
@ -63,11 +64,6 @@ class AdsSensor(AdsEntity, SensorEntity):
|
||||
)
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
def state(self) -> StateType:
|
||||
"""Return the state of the device."""
|
||||
return self._state_dict[STATE_KEY_STATE]
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
"""Return the unit of measurement."""
|
||||
return self._unit_of_measurement
|
||||
|
@ -35,7 +35,7 @@ class AdsSwitch(AdsEntity, SwitchEntity):
|
||||
await self.async_initialize_device(self._ads_var, self._ads_hub.PLCTYPE_BOOL)
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
def is_on(self) -> bool:
|
||||
"""Return True if the entity is on."""
|
||||
return self._state_dict[STATE_KEY_STATE]
|
||||
|
||||
|
@ -35,15 +35,13 @@ class AdvantageAirZoneFilter(AdvantageAirEntity, BinarySensorEntity):
|
||||
|
||||
_attr_device_class = DEVICE_CLASS_PROBLEM
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name."""
|
||||
return f'{self._ac["name"]} Filter'
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return a unique id."""
|
||||
return f'{self.coordinator.data["system"]["rid"]}-{self.ac_key}-filter'
|
||||
def __init__(self, instance, ac_key):
|
||||
"""Initialize an Advantage Air Filter."""
|
||||
super().__init__(instance, ac_key)
|
||||
self._attr_name = f'{self._ac["name"]} Filter'
|
||||
self._attr_unique_id = (
|
||||
f'{self.coordinator.data["system"]["rid"]}-{ac_key}-filter'
|
||||
)
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
@ -56,15 +54,13 @@ class AdvantageAirZoneMotion(AdvantageAirEntity, BinarySensorEntity):
|
||||
|
||||
_attr_device_class = DEVICE_CLASS_MOTION
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name."""
|
||||
return f'{self._zone["name"]} Motion'
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return a unique id."""
|
||||
return f'{self.coordinator.data["system"]["rid"]}-{self.ac_key}-{self.zone_key}-motion'
|
||||
def __init__(self, instance, ac_key, zone_key):
|
||||
"""Initialize an Advantage Air Zone Motion."""
|
||||
super().__init__(instance, ac_key, zone_key)
|
||||
self._attr_name = f'{self._zone["name"]} Motion'
|
||||
self._attr_unique_id = (
|
||||
f'{self.coordinator.data["system"]["rid"]}-{ac_key}-{zone_key}-motion'
|
||||
)
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
@ -77,15 +73,13 @@ class AdvantageAirZoneMyZone(AdvantageAirEntity, BinarySensorEntity):
|
||||
|
||||
_attr_entity_registry_enabled_default = False
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name."""
|
||||
return f'{self._zone["name"]} MyZone'
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return a unique id."""
|
||||
return f'{self.coordinator.data["system"]["rid"]}-{self.ac_key}-{self.zone_key}-myzone'
|
||||
def __init__(self, instance, ac_key, zone_key):
|
||||
"""Initialize an Advantage Air Zone MyZone."""
|
||||
super().__init__(instance, ac_key, zone_key)
|
||||
self._attr_name = f'{self._zone["name"]} MyZone'
|
||||
self._attr_unique_id = (
|
||||
f'{self.coordinator.data["system"]["rid"]}-{ac_key}-{zone_key}-myzone'
|
||||
)
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
|
@ -1,5 +1,4 @@
|
||||
"""Climate platform for Advantage Air integration."""
|
||||
|
||||
from homeassistant.components.climate import ClimateEntity
|
||||
from homeassistant.components.climate.const import (
|
||||
FAN_AUTO,
|
||||
@ -16,6 +15,7 @@ from homeassistant.components.climate.const import (
|
||||
SUPPORT_TARGET_TEMPERATURE,
|
||||
)
|
||||
from homeassistant.const import ATTR_TEMPERATURE, PRECISION_WHOLE, TEMP_CELSIUS
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers import entity_platform
|
||||
|
||||
from .const import (
|
||||
@ -84,39 +84,26 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
class AdvantageAirClimateEntity(AdvantageAirEntity, ClimateEntity):
|
||||
"""AdvantageAir Climate class."""
|
||||
|
||||
@property
|
||||
def temperature_unit(self):
|
||||
"""Return the temperature unit."""
|
||||
return TEMP_CELSIUS
|
||||
|
||||
@property
|
||||
def target_temperature_step(self):
|
||||
"""Return the supported temperature step."""
|
||||
return PRECISION_WHOLE
|
||||
|
||||
@property
|
||||
def max_temp(self):
|
||||
"""Return the maximum supported temperature."""
|
||||
return 32
|
||||
|
||||
@property
|
||||
def min_temp(self):
|
||||
"""Return the minimum supported temperature."""
|
||||
return 16
|
||||
_attr_temperature_unit = TEMP_CELSIUS
|
||||
_attr_target_temperature_step = PRECISION_WHOLE
|
||||
_attr_max_temp = 32
|
||||
_attr_min_temp = 16
|
||||
|
||||
|
||||
class AdvantageAirAC(AdvantageAirClimateEntity):
|
||||
"""AdvantageAir AC unit."""
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name."""
|
||||
return self._ac["name"]
|
||||
_attr_fan_modes = [FAN_AUTO, FAN_LOW, FAN_MEDIUM, FAN_HIGH]
|
||||
_attr_hvac_modes = AC_HVAC_MODES
|
||||
_attr_supported_features = SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return a unique id."""
|
||||
return f'{self.coordinator.data["system"]["rid"]}-{self.ac_key}'
|
||||
def __init__(self, instance, ac_key):
|
||||
"""Initialize an AdvantageAir AC unit."""
|
||||
super().__init__(instance, ac_key)
|
||||
self._attr_name = self._ac["name"]
|
||||
self._attr_unique_id = f'{self.coordinator.data["system"]["rid"]}-{ac_key}'
|
||||
if self._ac.get("myAutoModeEnabled"):
|
||||
self._attr_hvac_modes = AC_HVAC_MODES + [HVAC_MODE_AUTO]
|
||||
|
||||
@property
|
||||
def target_temperature(self):
|
||||
@ -130,28 +117,11 @@ class AdvantageAirAC(AdvantageAirClimateEntity):
|
||||
return ADVANTAGE_AIR_HVAC_MODES.get(self._ac["mode"])
|
||||
return HVAC_MODE_OFF
|
||||
|
||||
@property
|
||||
def hvac_modes(self):
|
||||
"""Return the supported HVAC modes."""
|
||||
if self._ac.get("myAutoModeEnabled"):
|
||||
return AC_HVAC_MODES + [HVAC_MODE_AUTO]
|
||||
return AC_HVAC_MODES
|
||||
|
||||
@property
|
||||
def fan_mode(self):
|
||||
"""Return the current fan modes."""
|
||||
return ADVANTAGE_AIR_FAN_MODES.get(self._ac["fan"])
|
||||
|
||||
@property
|
||||
def fan_modes(self):
|
||||
"""Return the supported fan modes."""
|
||||
return [FAN_AUTO, FAN_LOW, FAN_MEDIUM, FAN_HIGH]
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
"""Return the supported features."""
|
||||
return SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE
|
||||
|
||||
async def async_set_hvac_mode(self, hvac_mode):
|
||||
"""Set the HVAC Mode and State."""
|
||||
if hvac_mode == HVAC_MODE_OFF:
|
||||
@ -185,42 +155,30 @@ class AdvantageAirAC(AdvantageAirClimateEntity):
|
||||
class AdvantageAirZone(AdvantageAirClimateEntity):
|
||||
"""AdvantageAir Zone control."""
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name."""
|
||||
return self._zone["name"]
|
||||
_attr_hvac_modes = ZONE_HVAC_MODES
|
||||
_attr_supported_features = SUPPORT_TARGET_TEMPERATURE
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return a unique id."""
|
||||
return f'{self.coordinator.data["system"]["rid"]}-{self.ac_key}-{self.zone_key}'
|
||||
def __init__(self, instance, ac_key, zone_key):
|
||||
"""Initialize an AdvantageAir Zone control."""
|
||||
super().__init__(instance, ac_key, zone_key)
|
||||
self._attr_name = self._zone["name"]
|
||||
self._attr_unique_id = (
|
||||
f'{self.coordinator.data["system"]["rid"]}-{ac_key}-{zone_key}'
|
||||
)
|
||||
|
||||
@property
|
||||
def current_temperature(self):
|
||||
"""Return the current temperature."""
|
||||
return self._zone["measuredTemp"]
|
||||
async def async_added_to_hass(self):
|
||||
"""When entity is added to hass."""
|
||||
self.async_on_remove(self.coordinator.async_add_listener(self._update_callback))
|
||||
|
||||
@property
|
||||
def target_temperature(self):
|
||||
"""Return the target temperature."""
|
||||
return self._zone["setTemp"]
|
||||
|
||||
@property
|
||||
def hvac_mode(self):
|
||||
"""Return the current HVAC modes."""
|
||||
@callback
|
||||
def _update_callback(self) -> None:
|
||||
"""Load data from integration."""
|
||||
self._attr_current_temperature = self._zone["measuredTemp"]
|
||||
self._attr_target_temperature = self._zone["setTemp"]
|
||||
self._attr_hvac_mode = HVAC_MODE_OFF
|
||||
if self._zone["state"] == ADVANTAGE_AIR_STATE_OPEN:
|
||||
return HVAC_MODE_FAN_ONLY
|
||||
return HVAC_MODE_OFF
|
||||
|
||||
@property
|
||||
def hvac_modes(self):
|
||||
"""Return supported HVAC modes."""
|
||||
return ZONE_HVAC_MODES
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
"""Return the supported features."""
|
||||
return SUPPORT_TARGET_TEMPERATURE
|
||||
self._attr_hvac_mode = HVAC_MODE_FAN_ONLY
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_set_hvac_mode(self, hvac_mode):
|
||||
"""Set the HVAC Mode and State."""
|
||||
|
@ -36,25 +36,16 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
class AdvantageAirZoneVent(AdvantageAirEntity, CoverEntity):
|
||||
"""Advantage Air Cover Class."""
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name."""
|
||||
return f'{self._zone["name"]}'
|
||||
_attr_device_class = DEVICE_CLASS_DAMPER
|
||||
_attr_supported_features = SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return a unique id."""
|
||||
return f'{self.coordinator.data["system"]["rid"]}-{self.ac_key}-{self.zone_key}'
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
"""Return the device class of the vent."""
|
||||
return DEVICE_CLASS_DAMPER
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
"""Return the supported features."""
|
||||
return SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION
|
||||
def __init__(self, instance, ac_key, zone_key):
|
||||
"""Initialize an Advantage Air Cover Class."""
|
||||
super().__init__(instance, ac_key, zone_key)
|
||||
self._attr_name = f'{self._zone["name"]}'
|
||||
self._attr_unique_id = (
|
||||
f'{self.coordinator.data["system"]["rid"]}-{ac_key}-{zone_key}'
|
||||
)
|
||||
|
||||
@property
|
||||
def is_closed(self):
|
||||
|
@ -14,6 +14,13 @@ class AdvantageAirEntity(CoordinatorEntity):
|
||||
self.async_change = instance["async_change"]
|
||||
self.ac_key = ac_key
|
||||
self.zone_key = zone_key
|
||||
self._attr_device_info = {
|
||||
"identifiers": {(DOMAIN, self.coordinator.data["system"]["rid"])},
|
||||
"name": self.coordinator.data["system"]["name"],
|
||||
"manufacturer": "Advantage Air",
|
||||
"model": self.coordinator.data["system"]["sysType"],
|
||||
"sw_version": self.coordinator.data["system"]["myAppRev"],
|
||||
}
|
||||
|
||||
@property
|
||||
def _ac(self):
|
||||
@ -22,14 +29,3 @@ class AdvantageAirEntity(CoordinatorEntity):
|
||||
@property
|
||||
def _zone(self):
|
||||
return self.coordinator.data["aircons"][self.ac_key]["zones"][self.zone_key]
|
||||
|
||||
@property
|
||||
def device_info(self):
|
||||
"""Return parent device information."""
|
||||
return {
|
||||
"identifiers": {(DOMAIN, self.coordinator.data["system"]["rid"])},
|
||||
"name": self.coordinator.data["system"]["name"],
|
||||
"manufacturer": "Advantage Air",
|
||||
"model": self.coordinator.data["system"]["sysType"],
|
||||
"sw_version": self.coordinator.data["system"]["myAppRev"],
|
||||
}
|
||||
|
@ -3,8 +3,12 @@
|
||||
"name": "Advantage Air",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/advantage_air",
|
||||
"codeowners": ["@Bre77"],
|
||||
"requirements": ["advantage_air==0.2.1"],
|
||||
"codeowners": [
|
||||
"@Bre77"
|
||||
],
|
||||
"requirements": [
|
||||
"advantage_air==0.2.5"
|
||||
],
|
||||
"quality_scale": "platinum",
|
||||
"iot_class": "local_polling"
|
||||
}
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
"""Sensor platform for Advantage Air integration."""
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.sensor import SensorEntity
|
||||
from homeassistant.const import PERCENTAGE
|
||||
from homeassistant.components.sensor import STATE_CLASS_MEASUREMENT, SensorEntity
|
||||
from homeassistant.const import PERCENTAGE, TEMP_CELSIUS
|
||||
from homeassistant.helpers import config_validation as cv, entity_platform
|
||||
|
||||
from .const import ADVANTAGE_AIR_STATE_OPEN, DOMAIN as ADVANTAGE_AIR_DOMAIN
|
||||
@ -25,9 +25,10 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
entities.append(AdvantageAirTimeTo(instance, ac_key, "On"))
|
||||
entities.append(AdvantageAirTimeTo(instance, ac_key, "Off"))
|
||||
for zone_key, zone in ac_device["zones"].items():
|
||||
# Only show damper sensors when zone is in temperature control
|
||||
# Only show damper and temp sensors when zone is in temperature control
|
||||
if zone["type"] != 0:
|
||||
entities.append(AdvantageAirZoneVent(instance, ac_key, zone_key))
|
||||
entities.append(AdvantageAirZoneTemp(instance, ac_key, zone_key))
|
||||
# Only show wireless signal strength sensors when using wireless sensors
|
||||
if zone["rssi"] > 0:
|
||||
entities.append(AdvantageAirZoneSignal(instance, ac_key, zone_key))
|
||||
@ -50,17 +51,11 @@ class AdvantageAirTimeTo(AdvantageAirEntity, SensorEntity):
|
||||
"""Initialize the Advantage Air timer control."""
|
||||
super().__init__(instance, ac_key)
|
||||
self.action = action
|
||||
self._time_key = f"countDownTo{self.action}"
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name."""
|
||||
return f'{self._ac["name"]} Time To {self.action}'
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return a unique id."""
|
||||
return f'{self.coordinator.data["system"]["rid"]}-{self.ac_key}-timeto{self.action}'
|
||||
self._time_key = f"countDownTo{action}"
|
||||
self._attr_name = f'{self._ac["name"]} Time To {action}'
|
||||
self._attr_unique_id = (
|
||||
f'{self.coordinator.data["system"]["rid"]}-{self.ac_key}-timeto{action}'
|
||||
)
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
@ -84,16 +79,15 @@ class AdvantageAirZoneVent(AdvantageAirEntity, SensorEntity):
|
||||
"""Representation of Advantage Air Zone Vent Sensor."""
|
||||
|
||||
_attr_unit_of_measurement = PERCENTAGE
|
||||
_attr_state_class = STATE_CLASS_MEASUREMENT
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name."""
|
||||
return f'{self._zone["name"]} Vent'
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return a unique id."""
|
||||
return f'{self.coordinator.data["system"]["rid"]}-{self.ac_key}-{self.zone_key}-vent'
|
||||
def __init__(self, instance, ac_key, zone_key):
|
||||
"""Initialize an Advantage Air Zone Vent Sensor."""
|
||||
super().__init__(instance, ac_key, zone_key=zone_key)
|
||||
self._attr_name = f'{self._zone["name"]} Vent'
|
||||
self._attr_unique_id = (
|
||||
f'{self.coordinator.data["system"]["rid"]}-{ac_key}-{zone_key}-vent'
|
||||
)
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
@ -114,16 +108,15 @@ class AdvantageAirZoneSignal(AdvantageAirEntity, SensorEntity):
|
||||
"""Representation of Advantage Air Zone wireless signal sensor."""
|
||||
|
||||
_attr_unit_of_measurement = PERCENTAGE
|
||||
_attr_state_class = STATE_CLASS_MEASUREMENT
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name."""
|
||||
return f'{self._zone["name"]} Signal'
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return a unique id."""
|
||||
return f'{self.coordinator.data["system"]["rid"]}-{self.ac_key}-{self.zone_key}-signal'
|
||||
def __init__(self, instance, ac_key, zone_key):
|
||||
"""Initialize an Advantage Air Zone wireless signal sensor."""
|
||||
super().__init__(instance, ac_key, zone_key=zone_key)
|
||||
self._attr_name = f'{self._zone["name"]} Signal'
|
||||
self._attr_unique_id = (
|
||||
f'{self.coordinator.data["system"]["rid"]}-{ac_key}-{zone_key}-signal'
|
||||
)
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
@ -142,3 +135,23 @@ class AdvantageAirZoneSignal(AdvantageAirEntity, SensorEntity):
|
||||
if self._zone["rssi"] >= 20:
|
||||
return "mdi:wifi-strength-1"
|
||||
return "mdi:wifi-strength-outline"
|
||||
|
||||
|
||||
class AdvantageAirZoneTemp(AdvantageAirEntity, SensorEntity):
|
||||
"""Representation of Advantage Air Zone wireless signal sensor."""
|
||||
|
||||
_attr_unit_of_measurement = TEMP_CELSIUS
|
||||
_attr_state_class = STATE_CLASS_MEASUREMENT
|
||||
_attr_icon = "mdi:thermometer"
|
||||
_attr_entity_registry_enabled_default = False
|
||||
|
||||
def __init__(self, instance, ac_key, zone_key):
|
||||
"""Initialize an Advantage Air Zone Temp Sensor."""
|
||||
super().__init__(instance, ac_key, zone_key)
|
||||
self._attr_name = f'{self._zone["name"]} Temperature'
|
||||
self._attr_unique_id = f'{self.coordinator.data["system"]["rid"]}-{self.ac_key}-{self.zone_key}-temp'
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
"""Return the current value of the measured temperature."""
|
||||
return self._zone["measuredTemp"]
|
||||
|
@ -25,26 +25,21 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
class AdvantageAirFreshAir(AdvantageAirEntity, ToggleEntity):
|
||||
"""Representation of Advantage Air fresh air control."""
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name."""
|
||||
return f'{self._ac["name"]} Fresh Air'
|
||||
_attr_icon = "mdi:air-filter"
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return a unique id."""
|
||||
return f'{self.coordinator.data["system"]["rid"]}-{self.ac_key}-freshair'
|
||||
def __init__(self, instance, ac_key):
|
||||
"""Initialize an Advantage Air fresh air control."""
|
||||
super().__init__(instance, ac_key)
|
||||
self._attr_name = f'{self._ac["name"]} Fresh Air'
|
||||
self._attr_unique_id = (
|
||||
f'{self.coordinator.data["system"]["rid"]}-{ac_key}-freshair'
|
||||
)
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return the fresh air status."""
|
||||
return self._ac["freshAirStatus"] == ADVANTAGE_AIR_STATE_ON
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
"""Return a representative icon of the fresh air switch."""
|
||||
return "mdi:air-filter"
|
||||
|
||||
async def async_turn_on(self, **kwargs):
|
||||
"""Turn fresh air on."""
|
||||
await self.async_change(
|
||||
|
@ -9,10 +9,10 @@
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"ip_address": "IP Adresse",
|
||||
"ip_address": "IP-Adresse",
|
||||
"port": "Port"
|
||||
},
|
||||
"description": "Anschluss an die API Ihres Advantage Air Wandtabletts.",
|
||||
"description": "Anschluss an die API deines Advantage Air Wandtabletts.",
|
||||
"title": "Verbinden"
|
||||
}
|
||||
}
|
||||
|
@ -66,6 +66,8 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
class AbstractAemetSensor(CoordinatorEntity, SensorEntity):
|
||||
"""Abstract class for an AEMET OpenData sensor."""
|
||||
|
||||
_attr_extra_state_attributes = {ATTR_ATTRIBUTION: ATTRIBUTION}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name,
|
||||
@ -80,33 +82,10 @@ class AbstractAemetSensor(CoordinatorEntity, SensorEntity):
|
||||
self._unique_id = unique_id
|
||||
self._sensor_type = sensor_type
|
||||
self._sensor_name = sensor_configuration[SENSOR_NAME]
|
||||
self._unit_of_measurement = sensor_configuration.get(SENSOR_UNIT)
|
||||
self._device_class = sensor_configuration.get(SENSOR_DEVICE_CLASS)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the sensor."""
|
||||
return f"{self._name} {self._sensor_name}"
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return a unique_id for this entity."""
|
||||
return self._unique_id
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
"""Return the device_class."""
|
||||
return self._device_class
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
"""Return the unit of measurement of this entity, if any."""
|
||||
return self._unit_of_measurement
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self):
|
||||
"""Return the state attributes."""
|
||||
return {ATTR_ATTRIBUTION: ATTRIBUTION}
|
||||
self._attr_name = f"{self._name} {self._sensor_name}"
|
||||
self._attr_unique_id = self._unique_id
|
||||
self._attr_device_class = sensor_configuration.get(SENSOR_DEVICE_CLASS)
|
||||
self._attr_unit_of_measurement = sensor_configuration.get(SENSOR_UNIT)
|
||||
|
||||
|
||||
class AemetSensor(AbstractAemetSensor):
|
||||
@ -150,11 +129,9 @@ class AemetForecastSensor(AbstractAemetSensor):
|
||||
)
|
||||
self._weather_coordinator = weather_coordinator
|
||||
self._forecast_mode = forecast_mode
|
||||
|
||||
@property
|
||||
def entity_registry_enabled_default(self) -> bool:
|
||||
"""Return if the entity should be enabled when first added to the entity registry."""
|
||||
return self._forecast_mode == FORECAST_MODE_DAILY
|
||||
self._attr_entity_registry_enabled_default = (
|
||||
self._forecast_mode == FORECAST_MODE_DAILY
|
||||
)
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
|
11
homeassistant/components/aemet/translations/ar.json
Normal file
11
homeassistant/components/aemet/translations/ar.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"options": {
|
||||
"step": {
|
||||
"init": {
|
||||
"data": {
|
||||
"station_updates": "\u062c\u0645\u0639 \u0627\u0644\u0628\u064a\u0627\u0646\u0627\u062a \u0645\u0646 \u0645\u062d\u0637\u0627\u062a \u0627\u0644\u0637\u0642\u0633 AEMET"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -15,7 +15,7 @@
|
||||
"name": "Name der Integration"
|
||||
},
|
||||
"description": "Richte die AEMET OpenData Integration ein. Um den API-Schl\u00fcssel zu generieren, besuche https://opendata.aemet.es/centrodedescargas/altaUsuario",
|
||||
"title": "[void]"
|
||||
"title": "AEMET OpenData"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -18,5 +18,14 @@
|
||||
"title": "AEMET OpenData"
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"step": {
|
||||
"init": {
|
||||
"data": {
|
||||
"station_updates": "Recueillir les donn\u00e9es des stations m\u00e9t\u00e9orologiques AEMET"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -14,8 +14,18 @@
|
||||
"longitude": "Hossz\u00fas\u00e1g",
|
||||
"name": "Az integr\u00e1ci\u00f3 neve"
|
||||
},
|
||||
"description": "\u00c1ll\u00edtsa be az AEMET OpenData integr\u00e1ci\u00f3t. Az API-kulcs el\u0151\u00e1ll\u00edt\u00e1s\u00e1hoz keresse fel a https://opendata.aemet.es/centrodedescargas/altaUsuario webhelyet.",
|
||||
"title": "AEMET OpenData"
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"step": {
|
||||
"init": {
|
||||
"data": {
|
||||
"station_updates": "Gy\u0171jts\u00f6n adatokat az AEMET meteorol\u00f3giai \u00e1llom\u00e1sokr\u00f3l"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -18,5 +18,14 @@
|
||||
"title": "AEMET OpenData"
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"step": {
|
||||
"init": {
|
||||
"data": {
|
||||
"station_updates": "Kumpulkan data dari stasiun cuaca AEMET"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -39,6 +39,9 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
class AemetWeather(CoordinatorEntity, WeatherEntity):
|
||||
"""Implementation of an AEMET OpenData sensor."""
|
||||
|
||||
_attr_attribution = ATTRIBUTION
|
||||
_attr_temperature_unit = TEMP_CELSIUS
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name,
|
||||
@ -48,25 +51,18 @@ class AemetWeather(CoordinatorEntity, WeatherEntity):
|
||||
):
|
||||
"""Initialize the sensor."""
|
||||
super().__init__(coordinator)
|
||||
self._name = name
|
||||
self._unique_id = unique_id
|
||||
self._forecast_mode = forecast_mode
|
||||
|
||||
@property
|
||||
def attribution(self):
|
||||
"""Return the attribution."""
|
||||
return ATTRIBUTION
|
||||
self._attr_entity_registry_enabled_default = (
|
||||
self._forecast_mode == FORECAST_MODE_DAILY
|
||||
)
|
||||
self._attr_name = name
|
||||
self._attr_unique_id = unique_id
|
||||
|
||||
@property
|
||||
def condition(self):
|
||||
"""Return the current condition."""
|
||||
return self.coordinator.data[ATTR_API_CONDITION]
|
||||
|
||||
@property
|
||||
def entity_registry_enabled_default(self) -> bool:
|
||||
"""Return if the entity should be enabled when first added to the entity registry."""
|
||||
return self._forecast_mode == FORECAST_MODE_DAILY
|
||||
|
||||
@property
|
||||
def forecast(self):
|
||||
"""Return the forecast array."""
|
||||
@ -77,11 +73,6 @@ class AemetWeather(CoordinatorEntity, WeatherEntity):
|
||||
"""Return the humidity."""
|
||||
return self.coordinator.data[ATTR_API_HUMIDITY]
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the sensor."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def pressure(self):
|
||||
"""Return the pressure."""
|
||||
@ -92,16 +83,6 @@ class AemetWeather(CoordinatorEntity, WeatherEntity):
|
||||
"""Return the temperature."""
|
||||
return self.coordinator.data[ATTR_API_TEMPERATURE]
|
||||
|
||||
@property
|
||||
def temperature_unit(self):
|
||||
"""Return the unit of measurement."""
|
||||
return TEMP_CELSIUS
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return a unique_id for this entity."""
|
||||
return self._unique_id
|
||||
|
||||
@property
|
||||
def wind_bearing(self):
|
||||
"""Return the temperature."""
|
||||
|
@ -109,38 +109,26 @@ async def async_setup_platform(
|
||||
class AfterShipSensor(SensorEntity):
|
||||
"""Representation of a AfterShip sensor."""
|
||||
|
||||
_attr_unit_of_measurement: str = "packages"
|
||||
_attr_icon: str = ICON
|
||||
|
||||
def __init__(self, aftership: Tracking, name: str) -> None:
|
||||
"""Initialize the sensor."""
|
||||
self._attributes: dict[str, Any] = {}
|
||||
self._name: str = name
|
||||
self._state: int | None = None
|
||||
self.aftership = aftership
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
"""Return the name of the sensor."""
|
||||
return self._name
|
||||
self._attr_name = name
|
||||
|
||||
@property
|
||||
def state(self) -> int | None:
|
||||
"""Return the state of the sensor."""
|
||||
return self._state
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self) -> str:
|
||||
"""Return the unit of measurement of this entity, if any."""
|
||||
return "packages"
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self) -> dict[str, str]:
|
||||
"""Return attributes for the sensor."""
|
||||
return self._attributes
|
||||
|
||||
@property
|
||||
def icon(self) -> str:
|
||||
"""Icon to use in the frontend."""
|
||||
return ICON
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Register callbacks."""
|
||||
self.async_on_remove(
|
||||
|
@ -35,90 +35,60 @@ async def async_setup_entry(
|
||||
class AgentBaseStation(AlarmControlPanelEntity):
|
||||
"""Representation of an Agent DVR Alarm Control Panel."""
|
||||
|
||||
_attr_icon = ICON
|
||||
_attr_supported_features = (
|
||||
SUPPORT_ALARM_ARM_HOME | SUPPORT_ALARM_ARM_AWAY | SUPPORT_ALARM_ARM_NIGHT
|
||||
)
|
||||
|
||||
def __init__(self, client):
|
||||
"""Initialize the alarm control panel."""
|
||||
self._state = None
|
||||
self._client = client
|
||||
self._unique_id = f"{client.unique}_CP"
|
||||
name = CONST_ALARM_CONTROL_PANEL_NAME
|
||||
self._name = name = f"{client.name} {name}"
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
"""Return icon."""
|
||||
return ICON
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
"""Return the state of the device."""
|
||||
return self._state
|
||||
|
||||
@property
|
||||
def supported_features(self) -> int:
|
||||
"""Return the list of supported features."""
|
||||
return SUPPORT_ALARM_ARM_HOME | SUPPORT_ALARM_ARM_AWAY | SUPPORT_ALARM_ARM_NIGHT
|
||||
|
||||
@property
|
||||
def device_info(self):
|
||||
"""Return the device info for adding the entity to the agent object."""
|
||||
return {
|
||||
"identifiers": {(AGENT_DOMAIN, self._client.unique)},
|
||||
self._attr_name = f"{client.name} {CONST_ALARM_CONTROL_PANEL_NAME}"
|
||||
self._attr_unique_id = f"{client.unique}_CP"
|
||||
self._attr_device_info = {
|
||||
"identifiers": {(AGENT_DOMAIN, client.unique)},
|
||||
"manufacturer": "Agent",
|
||||
"model": CONST_ALARM_CONTROL_PANEL_NAME,
|
||||
"sw_version": self._client.version,
|
||||
"sw_version": client.version,
|
||||
}
|
||||
|
||||
async def async_update(self):
|
||||
"""Update the state of the device."""
|
||||
await self._client.update()
|
||||
self._attr_available = self._client.is_available
|
||||
armed = self._client.is_armed
|
||||
if armed is None:
|
||||
self._state = None
|
||||
self._attr_state = None
|
||||
return
|
||||
if armed:
|
||||
prof = (await self._client.get_active_profile()).lower()
|
||||
self._state = STATE_ALARM_ARMED_AWAY
|
||||
self._attr_state = STATE_ALARM_ARMED_AWAY
|
||||
if prof == CONF_HOME_MODE_NAME:
|
||||
self._state = STATE_ALARM_ARMED_HOME
|
||||
self._attr_state = STATE_ALARM_ARMED_HOME
|
||||
elif prof == CONF_NIGHT_MODE_NAME:
|
||||
self._state = STATE_ALARM_ARMED_NIGHT
|
||||
self._attr_state = STATE_ALARM_ARMED_NIGHT
|
||||
else:
|
||||
self._state = STATE_ALARM_DISARMED
|
||||
self._attr_state = STATE_ALARM_DISARMED
|
||||
|
||||
async def async_alarm_disarm(self, code=None):
|
||||
"""Send disarm command."""
|
||||
await self._client.disarm()
|
||||
self._state = STATE_ALARM_DISARMED
|
||||
self._attr_state = STATE_ALARM_DISARMED
|
||||
|
||||
async def async_alarm_arm_away(self, code=None):
|
||||
"""Send arm away command. Uses custom mode."""
|
||||
await self._client.arm()
|
||||
await self._client.set_active_profile(CONF_AWAY_MODE_NAME)
|
||||
self._state = STATE_ALARM_ARMED_AWAY
|
||||
self._attr_state = STATE_ALARM_ARMED_AWAY
|
||||
|
||||
async def async_alarm_arm_home(self, code=None):
|
||||
"""Send arm home command. Uses custom mode."""
|
||||
await self._client.arm()
|
||||
await self._client.set_active_profile(CONF_HOME_MODE_NAME)
|
||||
self._state = STATE_ALARM_ARMED_HOME
|
||||
self._attr_state = STATE_ALARM_ARMED_HOME
|
||||
|
||||
async def async_alarm_arm_night(self, code=None):
|
||||
"""Send arm night command. Uses custom mode."""
|
||||
await self._client.arm()
|
||||
await self._client.set_active_profile(CONF_NIGHT_MODE_NAME)
|
||||
self._state = STATE_ALARM_ARMED_NIGHT
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the base station."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
"""Device available."""
|
||||
return self._client.is_available
|
||||
|
||||
@property
|
||||
def unique_id(self) -> str:
|
||||
"""Return a unique ID."""
|
||||
return self._unique_id
|
||||
self._attr_state = STATE_ALARM_ARMED_NIGHT
|
||||
|
@ -67,31 +67,27 @@ async def async_setup_entry(
|
||||
class AgentCamera(MjpegCamera):
|
||||
"""Representation of an Agent Device Stream."""
|
||||
|
||||
_attr_supported_features = SUPPORT_ON_OFF
|
||||
|
||||
def __init__(self, device):
|
||||
"""Initialize as a subclass of MjpegCamera."""
|
||||
self._servername = device.client.name
|
||||
self.server_url = device.client._server_url
|
||||
|
||||
device_info = {
|
||||
CONF_NAME: device.name,
|
||||
CONF_MJPEG_URL: f"{self.server_url}{device.mjpeg_image_url}&size={device.mjpegStreamWidth}x{device.mjpegStreamHeight}",
|
||||
CONF_STILL_IMAGE_URL: f"{self.server_url}{device.still_image_url}&size={device.mjpegStreamWidth}x{device.mjpegStreamHeight}",
|
||||
CONF_MJPEG_URL: f"{device.client._server_url}{device.mjpeg_image_url}&size={device.mjpegStreamWidth}x{device.mjpegStreamHeight}",
|
||||
CONF_STILL_IMAGE_URL: f"{device.client._server_url}{device.still_image_url}&size={device.mjpegStreamWidth}x{device.mjpegStreamHeight}",
|
||||
}
|
||||
self.device = device
|
||||
self._removed = False
|
||||
self._name = f"{self._servername} {device.name}"
|
||||
self._unique_id = f"{device._client.unique}_{device.typeID}_{device.id}"
|
||||
self._attr_name = f"{device.client.name} {device.name}"
|
||||
self._attr_unique_id = f"{device._client.unique}_{device.typeID}_{device.id}"
|
||||
self._attr_should_poll = True
|
||||
super().__init__(device_info)
|
||||
|
||||
@property
|
||||
def device_info(self):
|
||||
"""Return the device info for adding the entity to the agent object."""
|
||||
return {
|
||||
"identifiers": {(AGENT_DOMAIN, self._unique_id)},
|
||||
"name": self._name,
|
||||
self._attr_device_info = {
|
||||
"identifiers": {(AGENT_DOMAIN, self.unique_id)},
|
||||
"name": self.name,
|
||||
"manufacturer": "Agent",
|
||||
"model": "Camera",
|
||||
"sw_version": self.device.client.version,
|
||||
"sw_version": device.client.version,
|
||||
}
|
||||
|
||||
async def async_update(self):
|
||||
@ -99,18 +95,18 @@ class AgentCamera(MjpegCamera):
|
||||
try:
|
||||
await self.device.update()
|
||||
if self._removed:
|
||||
_LOGGER.debug("%s reacquired", self._name)
|
||||
_LOGGER.debug("%s reacquired", self.name)
|
||||
self._removed = False
|
||||
except AgentError:
|
||||
# server still available - camera error
|
||||
if self.device.client.is_available and not self._removed:
|
||||
_LOGGER.error("%s lost", self._name)
|
||||
_LOGGER.error("%s lost", self.name)
|
||||
self._removed = True
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self):
|
||||
"""Return the Agent DVR camera state attributes."""
|
||||
return {
|
||||
self._attr_available = self.device.client.is_available
|
||||
self._attr_icon = "mdi:camcorder-off"
|
||||
if self.is_on:
|
||||
self._attr_icon = "mdi:camcorder"
|
||||
self._attr_extra_state_attributes = {
|
||||
ATTR_ATTRIBUTION: ATTRIBUTION,
|
||||
"editable": False,
|
||||
"enabled": self.is_on,
|
||||
@ -121,11 +117,6 @@ class AgentCamera(MjpegCamera):
|
||||
"alerts_enabled": self.device.alerts_active,
|
||||
}
|
||||
|
||||
@property
|
||||
def should_poll(self) -> bool:
|
||||
"""Update the state periodically."""
|
||||
return True
|
||||
|
||||
@property
|
||||
def is_recording(self) -> bool:
|
||||
"""Return whether the monitor is recording."""
|
||||
@ -141,43 +132,21 @@ class AgentCamera(MjpegCamera):
|
||||
"""Return whether the monitor has alerted."""
|
||||
return self.device.detected
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
"""Return True if entity is available."""
|
||||
return self.device.client.is_available
|
||||
|
||||
@property
|
||||
def connected(self) -> bool:
|
||||
"""Return True if entity is connected."""
|
||||
return self.device.connected
|
||||
|
||||
@property
|
||||
def supported_features(self) -> int:
|
||||
"""Return supported features."""
|
||||
return SUPPORT_ON_OFF
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
"""Return true if on."""
|
||||
return self.device.online
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
"""Return the icon to use in the frontend, if any."""
|
||||
if self.is_on:
|
||||
return "mdi:camcorder"
|
||||
return "mdi:camcorder-off"
|
||||
|
||||
@property
|
||||
def motion_detection_enabled(self):
|
||||
"""Return the camera motion detection status."""
|
||||
return self.device.detector_active
|
||||
|
||||
@property
|
||||
def unique_id(self) -> str:
|
||||
"""Return a unique identifier for this agent object."""
|
||||
return self._unique_id
|
||||
|
||||
async def async_enable_alerts(self):
|
||||
"""Enable alerts."""
|
||||
await self.device.alerts_on()
|
||||
|
@ -13,7 +13,7 @@
|
||||
"host": "Host",
|
||||
"port": "Port"
|
||||
},
|
||||
"title": "Richten Sie den Agent DVR ein"
|
||||
"title": "Richte den Agent DVR ein"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "\u05d4\u05de\u05db\u05e9\u05d9\u05e8 \u05db\u05d1\u05e8 \u05de\u05d5\u05d2\u05d3\u05e8"
|
||||
"already_configured": "\u05ea\u05e6\u05d5\u05e8\u05ea \u05d4\u05d4\u05ea\u05e7\u05df \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4"
|
||||
},
|
||||
"error": {
|
||||
"already_in_progress": "\u05d6\u05e8\u05d9\u05de\u05ea \u05d4\u05ea\u05e6\u05d5\u05e8\u05d4 \u05db\u05d1\u05e8 \u05de\u05ea\u05d1\u05e6\u05e2\u05ea",
|
||||
|
@ -3,10 +3,8 @@ from __future__ import annotations
|
||||
|
||||
from typing import Final
|
||||
|
||||
from homeassistant.components.sensor import ATTR_STATE_CLASS, STATE_CLASS_MEASUREMENT
|
||||
from homeassistant.components.sensor import STATE_CLASS_MEASUREMENT
|
||||
from homeassistant.const import (
|
||||
ATTR_DEVICE_CLASS,
|
||||
ATTR_ICON,
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
DEVICE_CLASS_PRESSURE,
|
||||
@ -16,7 +14,7 @@ from homeassistant.const import (
|
||||
TEMP_CELSIUS,
|
||||
)
|
||||
|
||||
from .model import SensorDescription
|
||||
from .model import AirlySensorEntityDescription
|
||||
|
||||
ATTR_API_ADVICE: Final = "ADVICE"
|
||||
ATTR_API_CAQI: Final = "CAQI"
|
||||
@ -31,12 +29,9 @@ ATTR_API_TEMPERATURE: Final = "TEMPERATURE"
|
||||
|
||||
ATTR_ADVICE: Final = "advice"
|
||||
ATTR_DESCRIPTION: Final = "description"
|
||||
ATTR_LABEL: Final = "label"
|
||||
ATTR_LEVEL: Final = "level"
|
||||
ATTR_LIMIT: Final = "limit"
|
||||
ATTR_PERCENT: Final = "percent"
|
||||
ATTR_UNIT: Final = "unit"
|
||||
ATTR_VALUE: Final = "value"
|
||||
|
||||
SUFFIX_PERCENT: Final = "PERCENT"
|
||||
SUFFIX_LIMIT: Final = "LIMIT"
|
||||
@ -51,52 +46,54 @@ MAX_UPDATE_INTERVAL: Final = 90
|
||||
MIN_UPDATE_INTERVAL: Final = 5
|
||||
NO_AIRLY_SENSORS: Final = "There are no Airly sensors in this area yet."
|
||||
|
||||
SENSOR_TYPES: dict[str, SensorDescription] = {
|
||||
ATTR_API_CAQI: {
|
||||
ATTR_LABEL: ATTR_API_CAQI,
|
||||
ATTR_UNIT: "CAQI",
|
||||
ATTR_VALUE: round,
|
||||
},
|
||||
ATTR_API_PM1: {
|
||||
ATTR_ICON: "mdi:blur",
|
||||
ATTR_LABEL: ATTR_API_PM1,
|
||||
ATTR_UNIT: CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
||||
ATTR_VALUE: round,
|
||||
},
|
||||
ATTR_API_PM25: {
|
||||
ATTR_ICON: "mdi:blur",
|
||||
ATTR_LABEL: "PM2.5",
|
||||
ATTR_UNIT: CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
||||
ATTR_VALUE: round,
|
||||
},
|
||||
ATTR_API_PM10: {
|
||||
ATTR_ICON: "mdi:blur",
|
||||
ATTR_LABEL: ATTR_API_PM10,
|
||||
ATTR_UNIT: CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
||||
ATTR_VALUE: round,
|
||||
},
|
||||
ATTR_API_HUMIDITY: {
|
||||
ATTR_DEVICE_CLASS: DEVICE_CLASS_HUMIDITY,
|
||||
ATTR_LABEL: ATTR_API_HUMIDITY.capitalize(),
|
||||
ATTR_UNIT: PERCENTAGE,
|
||||
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
||||
ATTR_VALUE: lambda value: round(value, 1),
|
||||
},
|
||||
ATTR_API_PRESSURE: {
|
||||
ATTR_DEVICE_CLASS: DEVICE_CLASS_PRESSURE,
|
||||
ATTR_LABEL: ATTR_API_PRESSURE.capitalize(),
|
||||
ATTR_UNIT: PRESSURE_HPA,
|
||||
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
||||
ATTR_VALUE: round,
|
||||
},
|
||||
ATTR_API_TEMPERATURE: {
|
||||
ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE,
|
||||
ATTR_LABEL: ATTR_API_TEMPERATURE.capitalize(),
|
||||
ATTR_UNIT: TEMP_CELSIUS,
|
||||
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
||||
ATTR_VALUE: lambda value: round(value, 1),
|
||||
},
|
||||
}
|
||||
SENSOR_TYPES: tuple[AirlySensorEntityDescription, ...] = (
|
||||
AirlySensorEntityDescription(
|
||||
key=ATTR_API_CAQI,
|
||||
name=ATTR_API_CAQI,
|
||||
unit_of_measurement="CAQI",
|
||||
),
|
||||
AirlySensorEntityDescription(
|
||||
key=ATTR_API_PM1,
|
||||
icon="mdi:blur",
|
||||
name=ATTR_API_PM1,
|
||||
unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
AirlySensorEntityDescription(
|
||||
key=ATTR_API_PM25,
|
||||
icon="mdi:blur",
|
||||
name="PM2.5",
|
||||
unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
AirlySensorEntityDescription(
|
||||
key=ATTR_API_PM10,
|
||||
icon="mdi:blur",
|
||||
name=ATTR_API_PM10,
|
||||
unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
AirlySensorEntityDescription(
|
||||
key=ATTR_API_HUMIDITY,
|
||||
device_class=DEVICE_CLASS_HUMIDITY,
|
||||
name=ATTR_API_HUMIDITY.capitalize(),
|
||||
unit_of_measurement=PERCENTAGE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
value=lambda value: round(value, 1),
|
||||
),
|
||||
AirlySensorEntityDescription(
|
||||
key=ATTR_API_PRESSURE,
|
||||
device_class=DEVICE_CLASS_PRESSURE,
|
||||
name=ATTR_API_PRESSURE.capitalize(),
|
||||
unit_of_measurement=PRESSURE_HPA,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
AirlySensorEntityDescription(
|
||||
key=ATTR_API_TEMPERATURE,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
name=ATTR_API_TEMPERATURE.capitalize(),
|
||||
unit_of_measurement=TEMP_CELSIUS,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
value=lambda value: round(value, 1),
|
||||
),
|
||||
)
|
||||
|
@ -1,15 +1,14 @@
|
||||
"""Type definitions for Airly integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Callable, TypedDict
|
||||
from dataclasses import dataclass
|
||||
from typing import Callable
|
||||
|
||||
from homeassistant.components.sensor import SensorEntityDescription
|
||||
|
||||
|
||||
class SensorDescription(TypedDict, total=False):
|
||||
"""Sensor description class."""
|
||||
@dataclass
|
||||
class AirlySensorEntityDescription(SensorEntityDescription):
|
||||
"""Class describing Airly sensor entities."""
|
||||
|
||||
device_class: str | None
|
||||
icon: str | None
|
||||
label: str
|
||||
unit: str
|
||||
state_class: str | None
|
||||
value: Callable
|
||||
value: Callable = round
|
||||
|
@ -3,16 +3,10 @@ from __future__ import annotations
|
||||
|
||||
from typing import Any, cast
|
||||
|
||||
from homeassistant.components.sensor import ATTR_STATE_CLASS, SensorEntity
|
||||
from homeassistant.components.sensor import SensorEntity
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
ATTR_ATTRIBUTION,
|
||||
ATTR_DEVICE_CLASS,
|
||||
ATTR_ICON,
|
||||
CONF_NAME,
|
||||
)
|
||||
from homeassistant.const import ATTR_ATTRIBUTION, CONF_NAME
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.typing import StateType
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
@ -27,12 +21,9 @@ from .const import (
|
||||
ATTR_API_PM10,
|
||||
ATTR_API_PM25,
|
||||
ATTR_DESCRIPTION,
|
||||
ATTR_LABEL,
|
||||
ATTR_LEVEL,
|
||||
ATTR_LIMIT,
|
||||
ATTR_PERCENT,
|
||||
ATTR_UNIT,
|
||||
ATTR_VALUE,
|
||||
ATTRIBUTION,
|
||||
DEFAULT_NAME,
|
||||
DOMAIN,
|
||||
@ -41,6 +32,7 @@ from .const import (
|
||||
SUFFIX_LIMIT,
|
||||
SUFFIX_PERCENT,
|
||||
)
|
||||
from .model import AirlySensorEntityDescription
|
||||
|
||||
PARALLEL_UPDATES = 1
|
||||
|
||||
@ -54,10 +46,10 @@ async def async_setup_entry(
|
||||
coordinator = hass.data[DOMAIN][entry.entry_id]
|
||||
|
||||
sensors = []
|
||||
for sensor in SENSOR_TYPES:
|
||||
for description in SENSOR_TYPES:
|
||||
# When we use the nearest method, we are not sure which sensors are available
|
||||
if coordinator.data.get(sensor):
|
||||
sensors.append(AirlySensor(coordinator, name, sensor))
|
||||
if coordinator.data.get(description.key):
|
||||
sensors.append(AirlySensor(coordinator, name, description))
|
||||
|
||||
async_add_entities(sensors, False)
|
||||
|
||||
@ -66,47 +58,54 @@ class AirlySensor(CoordinatorEntity, SensorEntity):
|
||||
"""Define an Airly sensor."""
|
||||
|
||||
coordinator: AirlyDataUpdateCoordinator
|
||||
entity_description: AirlySensorEntityDescription
|
||||
|
||||
def __init__(
|
||||
self, coordinator: AirlyDataUpdateCoordinator, name: str, kind: str
|
||||
self,
|
||||
coordinator: AirlyDataUpdateCoordinator,
|
||||
name: str,
|
||||
description: AirlySensorEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize."""
|
||||
super().__init__(coordinator)
|
||||
self._description = description = SENSOR_TYPES[kind]
|
||||
self._attr_device_class = description.get(ATTR_DEVICE_CLASS)
|
||||
self._attr_icon = description.get(ATTR_ICON)
|
||||
self._attr_name = f"{name} {description[ATTR_LABEL]}"
|
||||
self._attr_state_class = description.get(ATTR_STATE_CLASS)
|
||||
self._attr_device_info = {
|
||||
"identifiers": {
|
||||
(DOMAIN, f"{coordinator.latitude}-{coordinator.longitude}")
|
||||
},
|
||||
"name": DEFAULT_NAME,
|
||||
"manufacturer": MANUFACTURER,
|
||||
"entry_type": "service",
|
||||
}
|
||||
self._attr_name = f"{name} {description.name}"
|
||||
self._attr_unique_id = (
|
||||
f"{coordinator.latitude}-{coordinator.longitude}-{kind.lower()}"
|
||||
f"{coordinator.latitude}-{coordinator.longitude}-{description.key}".lower()
|
||||
)
|
||||
self._attr_unit_of_measurement = description.get(ATTR_UNIT)
|
||||
self._attrs: dict[str, Any] = {ATTR_ATTRIBUTION: ATTRIBUTION}
|
||||
self.kind = kind
|
||||
self.entity_description = description
|
||||
|
||||
@property
|
||||
def state(self) -> StateType:
|
||||
"""Return the state."""
|
||||
state = self.coordinator.data[self.kind]
|
||||
return cast(StateType, self._description[ATTR_VALUE](state))
|
||||
state = self.coordinator.data[self.entity_description.key]
|
||||
return cast(StateType, self.entity_description.value(state))
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self) -> dict[str, Any]:
|
||||
"""Return the state attributes."""
|
||||
if self.kind == ATTR_API_CAQI:
|
||||
if self.entity_description.key == ATTR_API_CAQI:
|
||||
self._attrs[ATTR_LEVEL] = self.coordinator.data[ATTR_API_CAQI_LEVEL]
|
||||
self._attrs[ATTR_ADVICE] = self.coordinator.data[ATTR_API_ADVICE]
|
||||
self._attrs[ATTR_DESCRIPTION] = self.coordinator.data[
|
||||
ATTR_API_CAQI_DESCRIPTION
|
||||
]
|
||||
if self.kind == ATTR_API_PM25:
|
||||
if self.entity_description.key == ATTR_API_PM25:
|
||||
self._attrs[ATTR_LIMIT] = self.coordinator.data[
|
||||
f"{ATTR_API_PM25}_{SUFFIX_LIMIT}"
|
||||
]
|
||||
self._attrs[ATTR_PERCENT] = round(
|
||||
self.coordinator.data[f"{ATTR_API_PM25}_{SUFFIX_PERCENT}"]
|
||||
)
|
||||
if self.kind == ATTR_API_PM10:
|
||||
if self.entity_description.key == ATTR_API_PM10:
|
||||
self._attrs[ATTR_LIMIT] = self.coordinator.data[
|
||||
f"{ATTR_API_PM10}_{SUFFIX_LIMIT}"
|
||||
]
|
||||
@ -114,18 +113,3 @@ class AirlySensor(CoordinatorEntity, SensorEntity):
|
||||
self.coordinator.data[f"{ATTR_API_PM10}_{SUFFIX_PERCENT}"]
|
||||
)
|
||||
return self._attrs
|
||||
|
||||
@property
|
||||
def device_info(self) -> DeviceInfo:
|
||||
"""Return the device info."""
|
||||
return {
|
||||
"identifiers": {
|
||||
(
|
||||
DOMAIN,
|
||||
f"{self.coordinator.latitude}-{self.coordinator.longitude}",
|
||||
)
|
||||
},
|
||||
"name": DEFAULT_NAME,
|
||||
"manufacturer": MANUFACTURER,
|
||||
"entry_type": "service",
|
||||
}
|
||||
|
@ -22,6 +22,7 @@
|
||||
},
|
||||
"system_health": {
|
||||
"info": {
|
||||
"can_reach_server": "\u00c9rje el az Airly szervert",
|
||||
"requests_per_day": "Enged\u00e9lyezett k\u00e9r\u00e9sek naponta",
|
||||
"requests_remaining": "Fennmarad\u00f3 enged\u00e9lyezett k\u00e9r\u00e9sek"
|
||||
}
|
||||
|
@ -67,16 +67,13 @@ class AirNowSensor(CoordinatorEntity, SensorEntity):
|
||||
"""Initialize."""
|
||||
super().__init__(coordinator)
|
||||
self.kind = kind
|
||||
self._device_class = None
|
||||
self._state = None
|
||||
self._icon = None
|
||||
self._unit_of_measurement = None
|
||||
self._attrs = {ATTR_ATTRIBUTION: ATTRIBUTION}
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name."""
|
||||
return f"AirNow {SENSOR_TYPES[self.kind][ATTR_LABEL]}"
|
||||
self._attr_name = f"AirNow {SENSOR_TYPES[self.kind][ATTR_LABEL]}"
|
||||
self._attr_icon = SENSOR_TYPES[self.kind][ATTR_ICON]
|
||||
self._attr_device_class = SENSOR_TYPES[self.kind][ATTR_DEVICE_CLASS]
|
||||
self._attr_unit_of_measurement = SENSOR_TYPES[self.kind][ATTR_UNIT]
|
||||
self._attr_unique_id = f"{self.coordinator.latitude}-{self.coordinator.longitude}-{self.kind.lower()}"
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
@ -96,24 +93,3 @@ class AirNowSensor(CoordinatorEntity, SensorEntity):
|
||||
]
|
||||
|
||||
return self._attrs
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
"""Return the icon."""
|
||||
self._icon = SENSOR_TYPES[self.kind][ATTR_ICON]
|
||||
return self._icon
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
"""Return the device_class."""
|
||||
return SENSOR_TYPES[self.kind][ATTR_DEVICE_CLASS]
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return a unique_id for this entity."""
|
||||
return f"{self.coordinator.latitude}-{self.coordinator.longitude}-{self.kind.lower()}"
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
"""Return the unit the value is expressed in."""
|
||||
return SENSOR_TYPES[self.kind][ATTR_UNIT]
|
||||
|
@ -1,5 +1,4 @@
|
||||
{
|
||||
"title": "AirNow",
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
|
@ -17,7 +17,7 @@
|
||||
"longitude": "L\u00e4ngengrad",
|
||||
"radius": "Stationsradius (Meilen; optional)"
|
||||
},
|
||||
"description": "Richten Sie die AirNow-Luftqualit\u00e4tsintegration ein. Um den API-Schl\u00fcssel zu generieren, besuchen Sie https://docs.airnowapi.org/account/request/.",
|
||||
"description": "Richte die AirNow-Luftqualit\u00e4tsintegration ein. Um den API-Schl\u00fcssel zu generieren, besuche https://docs.airnowapi.org/account/request/.",
|
||||
"title": "AirNow"
|
||||
}
|
||||
}
|
||||
|
@ -14,8 +14,10 @@
|
||||
"data": {
|
||||
"api_key": "API kulcs",
|
||||
"latitude": "Sz\u00e9less\u00e9g",
|
||||
"longitude": "Hossz\u00fas\u00e1g"
|
||||
"longitude": "Hossz\u00fas\u00e1g",
|
||||
"radius": "\u00c1llom\u00e1s sugara (m\u00e9rf\u00f6ld; opcion\u00e1lis)"
|
||||
},
|
||||
"description": "\u00c1ll\u00edtsa be az AirNow leveg\u0151min\u0151s\u00e9gi integr\u00e1ci\u00f3t. Az API-kulcs el\u0151\u00e1ll\u00edt\u00e1s\u00e1hoz keresse fel a https://docs.airnowapi.org/account/request/ oldalt.",
|
||||
"title": "AirNow"
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,10 @@
|
||||
"""The airvisual component."""
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Mapping
|
||||
from datetime import timedelta
|
||||
from math import ceil
|
||||
from typing import Any
|
||||
|
||||
from pyairvisual import CloudAPI, NodeSamba
|
||||
from pyairvisual.errors import (
|
||||
@ -10,6 +14,7 @@ from pyairvisual.errors import (
|
||||
NodeProError,
|
||||
)
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
ATTR_ATTRIBUTION,
|
||||
CONF_API_KEY,
|
||||
@ -20,9 +25,13 @@ from homeassistant.const import (
|
||||
CONF_SHOW_ON_MAP,
|
||||
CONF_STATE,
|
||||
)
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed
|
||||
from homeassistant.helpers import aiohttp_client, config_validation as cv
|
||||
from homeassistant.helpers import (
|
||||
aiohttp_client,
|
||||
config_validation as cv,
|
||||
entity_registry,
|
||||
)
|
||||
from homeassistant.helpers.update_coordinator import (
|
||||
CoordinatorEntity,
|
||||
DataUpdateCoordinator,
|
||||
@ -42,7 +51,7 @@ from .const import (
|
||||
LOGGER,
|
||||
)
|
||||
|
||||
PLATFORMS = ["air_quality", "sensor"]
|
||||
PLATFORMS = ["sensor"]
|
||||
|
||||
DATA_LISTENER = "listener"
|
||||
|
||||
@ -53,11 +62,8 @@ CONFIG_SCHEMA = cv.deprecated(DOMAIN)
|
||||
|
||||
|
||||
@callback
|
||||
def async_get_geography_id(geography_dict):
|
||||
def async_get_geography_id(geography_dict: Mapping[str, Any]) -> str:
|
||||
"""Generate a unique ID from a geography dict."""
|
||||
if not geography_dict:
|
||||
return
|
||||
|
||||
if CONF_CITY in geography_dict:
|
||||
return ", ".join(
|
||||
(
|
||||
@ -72,7 +78,9 @@ def async_get_geography_id(geography_dict):
|
||||
|
||||
|
||||
@callback
|
||||
def async_get_cloud_api_update_interval(hass, api_key, num_consumers):
|
||||
def async_get_cloud_api_update_interval(
|
||||
hass: HomeAssistant, api_key: str, num_consumers: int
|
||||
) -> timedelta:
|
||||
"""Get a leveled scan interval for a particular cloud API key.
|
||||
|
||||
This will shift based on the number of active consumers, thus keeping the user
|
||||
@ -93,18 +101,22 @@ def async_get_cloud_api_update_interval(hass, api_key, num_consumers):
|
||||
|
||||
|
||||
@callback
|
||||
def async_get_cloud_coordinators_by_api_key(hass, api_key):
|
||||
def async_get_cloud_coordinators_by_api_key(
|
||||
hass: HomeAssistant, api_key: str
|
||||
) -> list[DataUpdateCoordinator]:
|
||||
"""Get all DataUpdateCoordinator objects related to a particular API key."""
|
||||
coordinators = []
|
||||
for entry_id, coordinator in hass.data[DOMAIN][DATA_COORDINATOR].items():
|
||||
config_entry = hass.config_entries.async_get_entry(entry_id)
|
||||
if config_entry.data.get(CONF_API_KEY) == api_key:
|
||||
if config_entry and config_entry.data.get(CONF_API_KEY) == api_key:
|
||||
coordinators.append(coordinator)
|
||||
return coordinators
|
||||
|
||||
|
||||
@callback
|
||||
def async_sync_geo_coordinator_update_intervals(hass, api_key):
|
||||
def async_sync_geo_coordinator_update_intervals(
|
||||
hass: HomeAssistant, api_key: str
|
||||
) -> None:
|
||||
"""Sync the update interval for geography-based data coordinators (by API key)."""
|
||||
coordinators = async_get_cloud_coordinators_by_api_key(hass, api_key)
|
||||
|
||||
@ -124,14 +136,10 @@ def async_sync_geo_coordinator_update_intervals(hass, api_key):
|
||||
coordinator.update_interval = update_interval
|
||||
|
||||
|
||||
async def async_setup(hass, config):
|
||||
"""Set up the AirVisual component."""
|
||||
hass.data[DOMAIN] = {DATA_COORDINATOR: {}, DATA_LISTENER: {}}
|
||||
return True
|
||||
|
||||
|
||||
@callback
|
||||
def _standardize_geography_config_entry(hass, config_entry):
|
||||
def _standardize_geography_config_entry(
|
||||
hass: HomeAssistant, config_entry: ConfigEntry
|
||||
) -> None:
|
||||
"""Ensure that geography config entries have appropriate properties."""
|
||||
entry_updates = {}
|
||||
|
||||
@ -164,9 +172,11 @@ def _standardize_geography_config_entry(hass, config_entry):
|
||||
|
||||
|
||||
@callback
|
||||
def _standardize_node_pro_config_entry(hass, config_entry):
|
||||
def _standardize_node_pro_config_entry(
|
||||
hass: HomeAssistant, config_entry: ConfigEntry
|
||||
) -> None:
|
||||
"""Ensure that Node/Pro config entries have appropriate properties."""
|
||||
entry_updates = {}
|
||||
entry_updates: dict[str, Any] = {}
|
||||
|
||||
if CONF_INTEGRATION_TYPE not in config_entry.data:
|
||||
# If the config entry data doesn't contain the integration type, add it:
|
||||
@ -181,15 +191,17 @@ def _standardize_node_pro_config_entry(hass, config_entry):
|
||||
hass.config_entries.async_update_entry(config_entry, **entry_updates)
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry):
|
||||
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
|
||||
"""Set up AirVisual as config entry."""
|
||||
hass.data.setdefault(DOMAIN, {DATA_COORDINATOR: {}, DATA_LISTENER: {}})
|
||||
|
||||
if CONF_API_KEY in config_entry.data:
|
||||
_standardize_geography_config_entry(hass, config_entry)
|
||||
|
||||
websession = aiohttp_client.async_get_clientsession(hass)
|
||||
cloud_api = CloudAPI(config_entry.data[CONF_API_KEY], session=websession)
|
||||
|
||||
async def async_update_data():
|
||||
async def async_update_data() -> dict[str, Any]:
|
||||
"""Get new data from the API."""
|
||||
if CONF_CITY in config_entry.data:
|
||||
api_coro = cloud_api.air_quality.city(
|
||||
@ -227,9 +239,22 @@ async def async_setup_entry(hass, config_entry):
|
||||
config_entry.entry_id
|
||||
] = config_entry.add_update_listener(async_reload_entry)
|
||||
else:
|
||||
# Remove outdated air_quality entities from the entity registry if they exist:
|
||||
ent_reg = entity_registry.async_get(hass)
|
||||
for entity_entry in [
|
||||
e
|
||||
for e in ent_reg.entities.values()
|
||||
if e.config_entry_id == config_entry.entry_id
|
||||
and e.entity_id.startswith("air_quality")
|
||||
]:
|
||||
LOGGER.debug(
|
||||
'Removing deprecated air_quality entity: "%s"', entity_entry.entity_id
|
||||
)
|
||||
ent_reg.async_remove(entity_entry.entity_id)
|
||||
|
||||
_standardize_node_pro_config_entry(hass, config_entry)
|
||||
|
||||
async def async_update_data():
|
||||
async def async_update_data() -> dict[str, Any]:
|
||||
"""Get new data from the API."""
|
||||
try:
|
||||
async with NodeSamba(
|
||||
@ -262,7 +287,7 @@ async def async_setup_entry(hass, config_entry):
|
||||
return True
|
||||
|
||||
|
||||
async def async_migrate_entry(hass, config_entry):
|
||||
async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
|
||||
"""Migrate an old config entry."""
|
||||
version = config_entry.version
|
||||
|
||||
@ -304,7 +329,7 @@ async def async_migrate_entry(hass, config_entry):
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass, config_entry):
|
||||
async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
|
||||
"""Unload an AirVisual config entry."""
|
||||
unload_ok = await hass.config_entries.async_unload_platforms(
|
||||
config_entry, PLATFORMS
|
||||
@ -325,7 +350,7 @@ async def async_unload_entry(hass, config_entry):
|
||||
return unload_ok
|
||||
|
||||
|
||||
async def async_reload_entry(hass, config_entry):
|
||||
async def async_reload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> None:
|
||||
"""Handle an options update."""
|
||||
await hass.config_entries.async_reload(config_entry.entry_id)
|
||||
|
||||
@ -333,21 +358,17 @@ async def async_reload_entry(hass, config_entry):
|
||||
class AirVisualEntity(CoordinatorEntity):
|
||||
"""Define a generic AirVisual entity."""
|
||||
|
||||
def __init__(self, coordinator):
|
||||
def __init__(self, coordinator: DataUpdateCoordinator) -> None:
|
||||
"""Initialize."""
|
||||
super().__init__(coordinator)
|
||||
self._attrs = {ATTR_ATTRIBUTION: DEFAULT_ATTRIBUTION}
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self):
|
||||
"""Return the device state attributes."""
|
||||
return self._attrs
|
||||
self._attr_extra_state_attributes = {ATTR_ATTRIBUTION: DEFAULT_ATTRIBUTION}
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Register callbacks."""
|
||||
|
||||
@callback
|
||||
def update():
|
||||
def update() -> None:
|
||||
"""Update the state."""
|
||||
self.update_from_latest_data()
|
||||
self.async_write_ha_state()
|
||||
@ -357,6 +378,6 @@ class AirVisualEntity(CoordinatorEntity):
|
||||
self.update_from_latest_data()
|
||||
|
||||
@callback
|
||||
def update_from_latest_data(self):
|
||||
def update_from_latest_data(self) -> None:
|
||||
"""Update the entity from the latest data."""
|
||||
raise NotImplementedError
|
||||
|
@ -1,108 +0,0 @@
|
||||
"""Support for AirVisual Node/Pro units."""
|
||||
from homeassistant.components.air_quality import AirQualityEntity
|
||||
from homeassistant.core import callback
|
||||
|
||||
from . import AirVisualEntity
|
||||
from .const import (
|
||||
CONF_INTEGRATION_TYPE,
|
||||
DATA_COORDINATOR,
|
||||
DOMAIN,
|
||||
INTEGRATION_TYPE_NODE_PRO,
|
||||
)
|
||||
|
||||
ATTR_HUMIDITY = "humidity"
|
||||
ATTR_SENSOR_LIFE = "{0}_sensor_life"
|
||||
ATTR_VOC = "voc"
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up AirVisual air quality entities based on a config entry."""
|
||||
# Geography-based AirVisual integrations don't utilize this platform:
|
||||
if config_entry.data[CONF_INTEGRATION_TYPE] != INTEGRATION_TYPE_NODE_PRO:
|
||||
return
|
||||
|
||||
coordinator = hass.data[DOMAIN][DATA_COORDINATOR][config_entry.entry_id]
|
||||
|
||||
async_add_entities([AirVisualNodeProSensor(coordinator)], True)
|
||||
|
||||
|
||||
class AirVisualNodeProSensor(AirVisualEntity, AirQualityEntity):
|
||||
"""Define a sensor for a AirVisual Node/Pro."""
|
||||
|
||||
def __init__(self, airvisual):
|
||||
"""Initialize."""
|
||||
super().__init__(airvisual)
|
||||
|
||||
self._attr_icon = "mdi:chemical-weapon"
|
||||
|
||||
@property
|
||||
def air_quality_index(self):
|
||||
"""Return the Air Quality Index (AQI)."""
|
||||
if self.coordinator.data["settings"]["is_aqi_usa"]:
|
||||
return self.coordinator.data["measurements"]["aqi_us"]
|
||||
return self.coordinator.data["measurements"]["aqi_cn"]
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
"""Return True if entity is available."""
|
||||
return bool(self.coordinator.data)
|
||||
|
||||
@property
|
||||
def carbon_dioxide(self):
|
||||
"""Return the CO2 (carbon dioxide) level."""
|
||||
return self.coordinator.data["measurements"].get("co2")
|
||||
|
||||
@property
|
||||
def device_info(self):
|
||||
"""Return device registry information for this entity."""
|
||||
return {
|
||||
"identifiers": {(DOMAIN, self.coordinator.data["serial_number"])},
|
||||
"name": self.coordinator.data["settings"]["node_name"],
|
||||
"manufacturer": "AirVisual",
|
||||
"model": f'{self.coordinator.data["status"]["model"]}',
|
||||
"sw_version": (
|
||||
f'Version {self.coordinator.data["status"]["system_version"]}'
|
||||
f'{self.coordinator.data["status"]["app_version"]}'
|
||||
),
|
||||
}
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name."""
|
||||
node_name = self.coordinator.data["settings"]["node_name"]
|
||||
return f"{node_name} Node/Pro: Air Quality"
|
||||
|
||||
@property
|
||||
def particulate_matter_2_5(self):
|
||||
"""Return the particulate matter 2.5 level."""
|
||||
return self.coordinator.data["measurements"].get("pm2_5")
|
||||
|
||||
@property
|
||||
def particulate_matter_10(self):
|
||||
"""Return the particulate matter 10 level."""
|
||||
return self.coordinator.data["measurements"].get("pm1_0")
|
||||
|
||||
@property
|
||||
def particulate_matter_0_1(self):
|
||||
"""Return the particulate matter 0.1 level."""
|
||||
return self.coordinator.data["measurements"].get("pm0_1")
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return a unique, Home Assistant friendly identifier for this entity."""
|
||||
return self.coordinator.data["serial_number"]
|
||||
|
||||
@callback
|
||||
def update_from_latest_data(self):
|
||||
"""Update the entity from the latest data."""
|
||||
self._attrs.update(
|
||||
{
|
||||
ATTR_VOC: self.coordinator.data["measurements"].get("voc"),
|
||||
**{
|
||||
ATTR_SENSOR_LIFE.format(pollutant): lifespan
|
||||
for pollutant, lifespan in self.coordinator.data["status"][
|
||||
"sensor_life"
|
||||
].items()
|
||||
},
|
||||
}
|
||||
)
|
@ -1,4 +1,6 @@
|
||||
"""Define a config flow manager for AirVisual."""
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
|
||||
from pyairvisual import CloudAPI, NodeSamba
|
||||
@ -11,6 +13,7 @@ from pyairvisual.errors import (
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.config_entries import ConfigEntry, OptionsFlow
|
||||
from homeassistant.const import (
|
||||
CONF_API_KEY,
|
||||
CONF_IP_ADDRESS,
|
||||
@ -21,6 +24,7 @@ from homeassistant.const import (
|
||||
CONF_STATE,
|
||||
)
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.data_entry_flow import FlowResult
|
||||
from homeassistant.helpers import aiohttp_client, config_validation as cv
|
||||
|
||||
from . import async_get_geography_id
|
||||
@ -64,13 +68,13 @@ class AirVisualFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
|
||||
VERSION = 2
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self) -> None:
|
||||
"""Initialize the config flow."""
|
||||
self._entry_data_for_reauth = None
|
||||
self._geo_id = None
|
||||
self._entry_data_for_reauth: dict[str, str] = {}
|
||||
self._geo_id: str | None = None
|
||||
|
||||
@property
|
||||
def geography_coords_schema(self):
|
||||
def geography_coords_schema(self) -> vol.Schema:
|
||||
"""Return the data schema for the cloud API."""
|
||||
return API_KEY_DATA_SCHEMA.extend(
|
||||
{
|
||||
@ -83,7 +87,9 @@ class AirVisualFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
}
|
||||
)
|
||||
|
||||
async def _async_finish_geography(self, user_input, integration_type):
|
||||
async def _async_finish_geography(
|
||||
self, user_input: dict[str, str], integration_type: str
|
||||
) -> FlowResult:
|
||||
"""Validate a Cloud API key."""
|
||||
websession = aiohttp_client.async_get_clientsession(self.hass)
|
||||
cloud_api = CloudAPI(user_input[CONF_API_KEY], session=websession)
|
||||
@ -142,25 +148,29 @@ class AirVisualFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
data={**user_input, CONF_INTEGRATION_TYPE: integration_type},
|
||||
)
|
||||
|
||||
async def _async_init_geography(self, user_input, integration_type):
|
||||
async def _async_init_geography(
|
||||
self, user_input: dict[str, str], integration_type: str
|
||||
) -> FlowResult:
|
||||
"""Handle the initialization of the integration via the cloud API."""
|
||||
self._geo_id = async_get_geography_id(user_input)
|
||||
await self._async_set_unique_id(self._geo_id)
|
||||
self._abort_if_unique_id_configured()
|
||||
return await self._async_finish_geography(user_input, integration_type)
|
||||
|
||||
async def _async_set_unique_id(self, unique_id):
|
||||
async def _async_set_unique_id(self, unique_id: str) -> None:
|
||||
"""Set the unique ID of the config flow and abort if it already exists."""
|
||||
await self.async_set_unique_id(unique_id)
|
||||
self._abort_if_unique_id_configured()
|
||||
|
||||
@staticmethod
|
||||
@callback
|
||||
def async_get_options_flow(config_entry):
|
||||
def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
|
||||
"""Define the config flow to handle options."""
|
||||
return AirVisualOptionsFlowHandler(config_entry)
|
||||
|
||||
async def async_step_geography_by_coords(self, user_input=None):
|
||||
async def async_step_geography_by_coords(
|
||||
self, user_input: dict[str, str] | None = None
|
||||
) -> FlowResult:
|
||||
"""Handle the initialization of the cloud API based on latitude/longitude."""
|
||||
if not user_input:
|
||||
return self.async_show_form(
|
||||
@ -171,7 +181,9 @@ class AirVisualFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
user_input, INTEGRATION_TYPE_GEOGRAPHY_COORDS
|
||||
)
|
||||
|
||||
async def async_step_geography_by_name(self, user_input=None):
|
||||
async def async_step_geography_by_name(
|
||||
self, user_input: dict[str, str] | None = None
|
||||
) -> FlowResult:
|
||||
"""Handle the initialization of the cloud API based on city/state/country."""
|
||||
if not user_input:
|
||||
return self.async_show_form(
|
||||
@ -182,7 +194,9 @@ class AirVisualFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
user_input, INTEGRATION_TYPE_GEOGRAPHY_NAME
|
||||
)
|
||||
|
||||
async def async_step_node_pro(self, user_input=None):
|
||||
async def async_step_node_pro(
|
||||
self, user_input: dict[str, str] | None = None
|
||||
) -> FlowResult:
|
||||
"""Handle the initialization of the integration with a Node/Pro."""
|
||||
if not user_input:
|
||||
return self.async_show_form(step_id="node_pro", data_schema=NODE_PRO_SCHEMA)
|
||||
@ -208,13 +222,15 @@ class AirVisualFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
data={**user_input, CONF_INTEGRATION_TYPE: INTEGRATION_TYPE_NODE_PRO},
|
||||
)
|
||||
|
||||
async def async_step_reauth(self, data):
|
||||
async def async_step_reauth(self, data: dict[str, str]) -> FlowResult:
|
||||
"""Handle configuration by re-auth."""
|
||||
self._entry_data_for_reauth = data
|
||||
self._geo_id = async_get_geography_id(data)
|
||||
return await self.async_step_reauth_confirm()
|
||||
|
||||
async def async_step_reauth_confirm(self, user_input=None):
|
||||
async def async_step_reauth_confirm(
|
||||
self, user_input: dict[str, str] | None = None
|
||||
) -> FlowResult:
|
||||
"""Handle re-auth completion."""
|
||||
if not user_input:
|
||||
return self.async_show_form(
|
||||
@ -227,7 +243,9 @@ class AirVisualFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
conf, self._entry_data_for_reauth[CONF_INTEGRATION_TYPE]
|
||||
)
|
||||
|
||||
async def async_step_user(self, user_input=None):
|
||||
async def async_step_user(
|
||||
self, user_input: dict[str, str] | None = None
|
||||
) -> FlowResult:
|
||||
"""Handle the start of the config flow."""
|
||||
if not user_input:
|
||||
return self.async_show_form(
|
||||
@ -244,11 +262,13 @@ class AirVisualFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
class AirVisualOptionsFlowHandler(config_entries.OptionsFlow):
|
||||
"""Handle an AirVisual options flow."""
|
||||
|
||||
def __init__(self, config_entry):
|
||||
def __init__(self, config_entry: ConfigEntry) -> None:
|
||||
"""Initialize."""
|
||||
self.config_entry = config_entry
|
||||
|
||||
async def async_step_init(self, user_input=None):
|
||||
async def async_step_init(
|
||||
self, user_input: dict[str, str] | None = None
|
||||
) -> FlowResult:
|
||||
"""Manage the options."""
|
||||
if user_input is not None:
|
||||
return self.async_create_entry(title="", data=user_input)
|
||||
|
@ -3,7 +3,7 @@
|
||||
"name": "AirVisual",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/airvisual",
|
||||
"requirements": ["pyairvisual==5.0.8"],
|
||||
"requirements": ["pyairvisual==5.0.9"],
|
||||
"codeowners": ["@bachya"],
|
||||
"iot_class": "cloud_polling"
|
||||
}
|
||||
|
@ -1,5 +1,8 @@
|
||||
"""Support for AirVisual air quality sensors."""
|
||||
from __future__ import annotations
|
||||
|
||||
from homeassistant.components.sensor import SensorEntity
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
ATTR_LATITUDE,
|
||||
ATTR_LONGITUDE,
|
||||
@ -12,12 +15,16 @@ from homeassistant.const import (
|
||||
CONF_SHOW_ON_MAP,
|
||||
CONF_STATE,
|
||||
DEVICE_CLASS_BATTERY,
|
||||
DEVICE_CLASS_CO2,
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
PERCENTAGE,
|
||||
TEMP_CELSIUS,
|
||||
)
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||
|
||||
from . import AirVisualEntity
|
||||
from .const import (
|
||||
@ -36,12 +43,21 @@ ATTR_POLLUTANT_SYMBOL = "pollutant_symbol"
|
||||
ATTR_POLLUTANT_UNIT = "pollutant_unit"
|
||||
ATTR_REGION = "region"
|
||||
|
||||
SENSOR_KIND_LEVEL = "air_pollution_level"
|
||||
DEVICE_CLASS_POLLUTANT_LABEL = "airvisual__pollutant_label"
|
||||
DEVICE_CLASS_POLLUTANT_LEVEL = "airvisual__pollutant_level"
|
||||
|
||||
SENSOR_KIND_AQI = "air_quality_index"
|
||||
SENSOR_KIND_POLLUTANT = "main_pollutant"
|
||||
SENSOR_KIND_BATTERY_LEVEL = "battery_level"
|
||||
SENSOR_KIND_CO2 = "carbon_dioxide"
|
||||
SENSOR_KIND_HUMIDITY = "humidity"
|
||||
SENSOR_KIND_LEVEL = "air_pollution_level"
|
||||
SENSOR_KIND_PM_0_1 = "particulate_matter_0_1"
|
||||
SENSOR_KIND_PM_1_0 = "particulate_matter_1_0"
|
||||
SENSOR_KIND_PM_2_5 = "particulate_matter_2_5"
|
||||
SENSOR_KIND_POLLUTANT = "main_pollutant"
|
||||
SENSOR_KIND_SENSOR_LIFE = "sensor_life"
|
||||
SENSOR_KIND_TEMPERATURE = "temperature"
|
||||
SENSOR_KIND_VOC = "voc"
|
||||
|
||||
GEOGRAPHY_SENSORS = [
|
||||
(SENSOR_KIND_LEVEL, "Air Pollution Level", "mdi:gauge", None),
|
||||
@ -51,27 +67,74 @@ GEOGRAPHY_SENSORS = [
|
||||
GEOGRAPHY_SENSOR_LOCALES = {"cn": "Chinese", "us": "U.S."}
|
||||
|
||||
NODE_PRO_SENSORS = [
|
||||
(SENSOR_KIND_BATTERY_LEVEL, "Battery", DEVICE_CLASS_BATTERY, PERCENTAGE),
|
||||
(SENSOR_KIND_HUMIDITY, "Humidity", DEVICE_CLASS_HUMIDITY, PERCENTAGE),
|
||||
(SENSOR_KIND_TEMPERATURE, "Temperature", DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS),
|
||||
(SENSOR_KIND_AQI, "Air Quality Index", None, "mdi:chart-line", "AQI"),
|
||||
(SENSOR_KIND_BATTERY_LEVEL, "Battery", DEVICE_CLASS_BATTERY, None, PERCENTAGE),
|
||||
(
|
||||
SENSOR_KIND_CO2,
|
||||
"C02",
|
||||
DEVICE_CLASS_CO2,
|
||||
None,
|
||||
CONCENTRATION_PARTS_PER_MILLION,
|
||||
),
|
||||
(SENSOR_KIND_HUMIDITY, "Humidity", DEVICE_CLASS_HUMIDITY, None, PERCENTAGE),
|
||||
(
|
||||
SENSOR_KIND_PM_0_1,
|
||||
"PM 0.1",
|
||||
None,
|
||||
"mdi:sprinkler",
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
),
|
||||
(
|
||||
SENSOR_KIND_PM_1_0,
|
||||
"PM 1.0",
|
||||
None,
|
||||
"mdi:sprinkler",
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
),
|
||||
(
|
||||
SENSOR_KIND_PM_2_5,
|
||||
"PM 2.5",
|
||||
None,
|
||||
"mdi:sprinkler",
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
),
|
||||
(
|
||||
SENSOR_KIND_TEMPERATURE,
|
||||
"Temperature",
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
None,
|
||||
TEMP_CELSIUS,
|
||||
),
|
||||
(
|
||||
SENSOR_KIND_VOC,
|
||||
"VOC",
|
||||
None,
|
||||
"mdi:sprinkler",
|
||||
CONCENTRATION_PARTS_PER_MILLION,
|
||||
),
|
||||
]
|
||||
|
||||
POLLUTANT_LABELS = {
|
||||
"co": "Carbon Monoxide",
|
||||
"n2": "Nitrogen Dioxide",
|
||||
"o3": "Ozone",
|
||||
"p1": "PM10",
|
||||
"p2": "PM2.5",
|
||||
"s2": "Sulfur Dioxide",
|
||||
}
|
||||
STATE_POLLUTANT_LABEL_CO = "co"
|
||||
STATE_POLLUTANT_LABEL_N2 = "n2"
|
||||
STATE_POLLUTANT_LABEL_O3 = "o3"
|
||||
STATE_POLLUTANT_LABEL_P1 = "p1"
|
||||
STATE_POLLUTANT_LABEL_P2 = "p2"
|
||||
STATE_POLLUTANT_LABEL_S2 = "s2"
|
||||
|
||||
STATE_POLLUTANT_LEVEL_GOOD = "good"
|
||||
STATE_POLLUTANT_LEVEL_MODERATE = "moderate"
|
||||
STATE_POLLUTANT_LEVEL_UNHEALTHY_SENSITIVE = "unhealthy_sensitive"
|
||||
STATE_POLLUTANT_LEVEL_UNHEALTHY = "unhealthy"
|
||||
STATE_POLLUTANT_LEVEL_VERY_UNHEALTHY = "very_unhealthy"
|
||||
STATE_POLLUTANT_LEVEL_HAZARDOUS = "hazardous"
|
||||
|
||||
POLLUTANT_LEVELS = {
|
||||
(0, 50): ("Good", "mdi:emoticon-excited"),
|
||||
(51, 100): ("Moderate", "mdi:emoticon-happy"),
|
||||
(101, 150): ("Unhealthy for sensitive groups", "mdi:emoticon-neutral"),
|
||||
(151, 200): ("Unhealthy", "mdi:emoticon-sad"),
|
||||
(201, 300): ("Very unhealthy", "mdi:emoticon-dead"),
|
||||
(301, 1000): ("Hazardous", "mdi:biohazard"),
|
||||
(0, 50): (STATE_POLLUTANT_LEVEL_GOOD, "mdi:emoticon-excited"),
|
||||
(51, 100): (STATE_POLLUTANT_LEVEL_MODERATE, "mdi:emoticon-happy"),
|
||||
(101, 150): (STATE_POLLUTANT_LEVEL_UNHEALTHY_SENSITIVE, "mdi:emoticon-neutral"),
|
||||
(151, 200): (STATE_POLLUTANT_LEVEL_UNHEALTHY, "mdi:emoticon-sad"),
|
||||
(201, 300): (STATE_POLLUTANT_LEVEL_VERY_UNHEALTHY, "mdi:emoticon-dead"),
|
||||
(301, 1000): (STATE_POLLUTANT_LEVEL_HAZARDOUS, "mdi:biohazard"),
|
||||
}
|
||||
|
||||
POLLUTANT_UNITS = {
|
||||
@ -84,10 +147,15 @@ POLLUTANT_UNITS = {
|
||||
}
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up AirVisual sensors based on a config entry."""
|
||||
coordinator = hass.data[DOMAIN][DATA_COORDINATOR][config_entry.entry_id]
|
||||
|
||||
sensors: list[AirVisualGeographySensor | AirVisualNodeProSensor]
|
||||
if config_entry.data[CONF_INTEGRATION_TYPE] in [
|
||||
INTEGRATION_TYPE_GEOGRAPHY_COORDS,
|
||||
INTEGRATION_TYPE_GEOGRAPHY_NAME,
|
||||
@ -107,8 +175,8 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
]
|
||||
else:
|
||||
sensors = [
|
||||
AirVisualNodeProSensor(coordinator, kind, name, device_class, unit)
|
||||
for kind, name, device_class, unit in NODE_PRO_SENSORS
|
||||
AirVisualNodeProSensor(coordinator, kind, name, device_class, icon, unit)
|
||||
for kind, name, device_class, icon, unit in NODE_PRO_SENSORS
|
||||
]
|
||||
|
||||
async_add_entities(sensors, True)
|
||||
@ -117,53 +185,45 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
class AirVisualGeographySensor(AirVisualEntity, SensorEntity):
|
||||
"""Define an AirVisual sensor related to geography data via the Cloud API."""
|
||||
|
||||
def __init__(self, coordinator, config_entry, kind, name, icon, unit, locale):
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: DataUpdateCoordinator,
|
||||
config_entry: ConfigEntry,
|
||||
kind: str,
|
||||
name: str,
|
||||
icon: str,
|
||||
unit: str | None,
|
||||
locale: str,
|
||||
) -> None:
|
||||
"""Initialize."""
|
||||
super().__init__(coordinator)
|
||||
|
||||
self._attrs.update(
|
||||
if kind == SENSOR_KIND_LEVEL:
|
||||
self._attr_device_class = DEVICE_CLASS_POLLUTANT_LEVEL
|
||||
elif kind == SENSOR_KIND_POLLUTANT:
|
||||
self._attr_device_class = DEVICE_CLASS_POLLUTANT_LABEL
|
||||
self._attr_extra_state_attributes.update(
|
||||
{
|
||||
ATTR_CITY: config_entry.data.get(CONF_CITY),
|
||||
ATTR_STATE: config_entry.data.get(CONF_STATE),
|
||||
ATTR_COUNTRY: config_entry.data.get(CONF_COUNTRY),
|
||||
}
|
||||
)
|
||||
self._attr_icon = icon
|
||||
self._attr_name = f"{GEOGRAPHY_SENSOR_LOCALES[locale]} {name}"
|
||||
self._attr_unique_id = f"{config_entry.unique_id}_{locale}_{kind}"
|
||||
self._attr_unit_of_measurement = unit
|
||||
self._config_entry = config_entry
|
||||
self._kind = kind
|
||||
self._locale = locale
|
||||
self._name = name
|
||||
self._state = None
|
||||
|
||||
self._attr_icon = icon
|
||||
self._attr_unit_of_measurement = unit
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
"""Return True if entity is available."""
|
||||
try:
|
||||
return self.coordinator.last_update_success and bool(
|
||||
self.coordinator.data["current"]["pollution"]
|
||||
)
|
||||
except KeyError:
|
||||
return False
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name."""
|
||||
return f"{GEOGRAPHY_SENSOR_LOCALES[self._locale]} {self._name}"
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
"""Return the state."""
|
||||
return self._state
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return a unique, Home Assistant friendly identifier for this entity."""
|
||||
return f"{self._config_entry.unique_id}_{self._locale}_{self._kind}"
|
||||
def available(self) -> bool:
|
||||
"""Return if entity is available."""
|
||||
return super().available and self.coordinator.data["current"]["pollution"]
|
||||
|
||||
@callback
|
||||
def update_from_latest_data(self):
|
||||
def update_from_latest_data(self) -> None:
|
||||
"""Update the entity from the latest data."""
|
||||
try:
|
||||
data = self.coordinator.data["current"]["pollution"]
|
||||
@ -172,17 +232,17 @@ class AirVisualGeographySensor(AirVisualEntity, SensorEntity):
|
||||
|
||||
if self._kind == SENSOR_KIND_LEVEL:
|
||||
aqi = data[f"aqi{self._locale}"]
|
||||
[(self._state, self._attr_icon)] = [
|
||||
[(self._attr_state, self._attr_icon)] = [
|
||||
(name, icon)
|
||||
for (floor, ceiling), (name, icon) in POLLUTANT_LEVELS.items()
|
||||
if floor <= aqi <= ceiling
|
||||
]
|
||||
elif self._kind == SENSOR_KIND_AQI:
|
||||
self._state = data[f"aqi{self._locale}"]
|
||||
self._attr_state = data[f"aqi{self._locale}"]
|
||||
elif self._kind == SENSOR_KIND_POLLUTANT:
|
||||
symbol = data[f"main{self._locale}"]
|
||||
self._state = POLLUTANT_LABELS[symbol]
|
||||
self._attrs.update(
|
||||
self._attr_state = symbol
|
||||
self._attr_extra_state_attributes.update(
|
||||
{
|
||||
ATTR_POLLUTANT_SYMBOL: symbol,
|
||||
ATTR_POLLUTANT_UNIT: POLLUTANT_UNITS[symbol],
|
||||
@ -206,33 +266,43 @@ class AirVisualGeographySensor(AirVisualEntity, SensorEntity):
|
||||
)
|
||||
|
||||
if self._config_entry.options[CONF_SHOW_ON_MAP]:
|
||||
self._attrs[ATTR_LATITUDE] = latitude
|
||||
self._attrs[ATTR_LONGITUDE] = longitude
|
||||
self._attrs.pop("lati", None)
|
||||
self._attrs.pop("long", None)
|
||||
self._attr_extra_state_attributes[ATTR_LATITUDE] = latitude
|
||||
self._attr_extra_state_attributes[ATTR_LONGITUDE] = longitude
|
||||
self._attr_extra_state_attributes.pop("lati", None)
|
||||
self._attr_extra_state_attributes.pop("long", None)
|
||||
else:
|
||||
self._attrs["lati"] = latitude
|
||||
self._attrs["long"] = longitude
|
||||
self._attrs.pop(ATTR_LATITUDE, None)
|
||||
self._attrs.pop(ATTR_LONGITUDE, None)
|
||||
self._attr_extra_state_attributes["lati"] = latitude
|
||||
self._attr_extra_state_attributes["long"] = longitude
|
||||
self._attr_extra_state_attributes.pop(ATTR_LATITUDE, None)
|
||||
self._attr_extra_state_attributes.pop(ATTR_LONGITUDE, None)
|
||||
|
||||
|
||||
class AirVisualNodeProSensor(AirVisualEntity, SensorEntity):
|
||||
"""Define an AirVisual sensor related to a Node/Pro unit."""
|
||||
|
||||
def __init__(self, coordinator, kind, name, device_class, unit):
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: DataUpdateCoordinator,
|
||||
kind: str,
|
||||
name: str,
|
||||
device_class: str | None,
|
||||
icon: str | None,
|
||||
unit: str,
|
||||
) -> None:
|
||||
"""Initialize."""
|
||||
super().__init__(coordinator)
|
||||
|
||||
self._kind = kind
|
||||
self._name = name
|
||||
self._state = None
|
||||
|
||||
self._attr_device_class = device_class
|
||||
self._attr_icon = icon
|
||||
self._attr_name = (
|
||||
f"{coordinator.data['settings']['node_name']} Node/Pro: {name}"
|
||||
)
|
||||
self._attr_unique_id = f"{coordinator.data['serial_number']}_{kind}"
|
||||
self._attr_unit_of_measurement = unit
|
||||
self._kind = kind
|
||||
|
||||
@property
|
||||
def device_info(self):
|
||||
def device_info(self) -> DeviceInfo:
|
||||
"""Return device registry information for this entity."""
|
||||
return {
|
||||
"identifiers": {(DOMAIN, self.coordinator.data["serial_number"])},
|
||||
@ -245,28 +315,29 @@ class AirVisualNodeProSensor(AirVisualEntity, SensorEntity):
|
||||
),
|
||||
}
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name."""
|
||||
node_name = self.coordinator.data["settings"]["node_name"]
|
||||
return f"{node_name} Node/Pro: {self._name}"
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
"""Return the state."""
|
||||
return self._state
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return a unique, Home Assistant friendly identifier for this entity."""
|
||||
return f"{self.coordinator.data['serial_number']}_{self._kind}"
|
||||
|
||||
@callback
|
||||
def update_from_latest_data(self):
|
||||
def update_from_latest_data(self) -> None:
|
||||
"""Update the entity from the latest data."""
|
||||
if self._kind == SENSOR_KIND_BATTERY_LEVEL:
|
||||
self._state = self.coordinator.data["status"]["battery"]
|
||||
if self._kind == SENSOR_KIND_AQI:
|
||||
if self.coordinator.data["settings"]["is_aqi_usa"]:
|
||||
self._attr_state = self.coordinator.data["measurements"]["aqi_us"]
|
||||
else:
|
||||
self._attr_state = self.coordinator.data["measurements"]["aqi_cn"]
|
||||
elif self._kind == SENSOR_KIND_BATTERY_LEVEL:
|
||||
self._attr_state = self.coordinator.data["status"]["battery"]
|
||||
elif self._kind == SENSOR_KIND_CO2:
|
||||
self._attr_state = self.coordinator.data["measurements"].get("co2")
|
||||
elif self._kind == SENSOR_KIND_HUMIDITY:
|
||||
self._state = self.coordinator.data["measurements"].get("humidity")
|
||||
self._attr_state = self.coordinator.data["measurements"].get("humidity")
|
||||
elif self._kind == SENSOR_KIND_PM_0_1:
|
||||
self._attr_state = self.coordinator.data["measurements"].get("pm0_1")
|
||||
elif self._kind == SENSOR_KIND_PM_1_0:
|
||||
self._attr_state = self.coordinator.data["measurements"].get("pm1_0")
|
||||
elif self._kind == SENSOR_KIND_PM_2_5:
|
||||
self._attr_state = self.coordinator.data["measurements"].get("pm2_5")
|
||||
elif self._kind == SENSOR_KIND_TEMPERATURE:
|
||||
self._state = self.coordinator.data["measurements"].get("temperature_C")
|
||||
self._attr_state = self.coordinator.data["measurements"].get(
|
||||
"temperature_C"
|
||||
)
|
||||
elif self._kind == SENSOR_KIND_VOC:
|
||||
self._attr_state = self.coordinator.data["measurements"].get("voc")
|
||||
|
20
homeassistant/components/airvisual/strings.sensor.json
Normal file
20
homeassistant/components/airvisual/strings.sensor.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"state": {
|
||||
"airvisual__pollutant_label": {
|
||||
"co": "Carbon Monoxide",
|
||||
"n2": "Nitrogen Dioxide",
|
||||
"o3": "Ozone",
|
||||
"p1": "PM10",
|
||||
"p2": "PM2.5",
|
||||
"s2": "Sulfur Dioxide"
|
||||
},
|
||||
"airvisual__pollutant_level": {
|
||||
"good": "Good",
|
||||
"moderate": "Moderate",
|
||||
"unhealthy": "Unhealthy",
|
||||
"unhealthy_sensitive": "Unhealthy for sensitive groups",
|
||||
"very_unhealthy": "Very unhealthy",
|
||||
"hazardous": "Hazardous"
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Diese Koordinaten oder Node/Pro ID sind bereits registriert.",
|
||||
"already_configured": "Diese Node/Pro ID oder Standort ist bereits konfiguriert.",
|
||||
"reauth_successful": "Die erneute Authentifizierung war erfolgreich"
|
||||
},
|
||||
"error": {
|
||||
@ -35,18 +35,18 @@
|
||||
"ip_address": "Host",
|
||||
"password": "Passwort"
|
||||
},
|
||||
"description": "\u00dcberwachen Sie eine pers\u00f6nliche AirVisual-Einheit. Das Passwort kann von der Benutzeroberfl\u00e4che des Ger\u00e4ts abgerufen werden.",
|
||||
"title": "Konfigurieren Sie einen AirVisual Node/Pro"
|
||||
"description": "\u00dcberwache eine pers\u00f6nliche AirVisual-Einheit. Das Passwort kann von der Benutzeroberfl\u00e4che des Ger\u00e4ts abgerufen werden.",
|
||||
"title": "Konfiguriere einen AirVisual Node/Pro"
|
||||
},
|
||||
"reauth_confirm": {
|
||||
"data": {
|
||||
"api_key": "API-Key"
|
||||
"api_key": "API-Schl\u00fcssel"
|
||||
},
|
||||
"title": "AirVisual erneut authentifizieren"
|
||||
},
|
||||
"user": {
|
||||
"description": "W\u00e4hlen Sie aus, welche Art von AirVisual-Daten Sie \u00fcberwachen m\u00f6chten.",
|
||||
"title": "Konfigurieren Sie AirVisual"
|
||||
"description": "W\u00e4hle aus, welche Art von AirVisual-Daten du \u00fcberwachen m\u00f6chtest.",
|
||||
"title": "Konfiguriere AirVisual"
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -54,9 +54,9 @@
|
||||
"step": {
|
||||
"init": {
|
||||
"data": {
|
||||
"show_on_map": "Zeigen Sie die \u00fcberwachte Geografie auf der Karte an"
|
||||
"show_on_map": "Zeige die \u00fcberwachte Geografie auf der Karte an"
|
||||
},
|
||||
"title": "Konfigurieren Sie AirVisual"
|
||||
"title": "Konfiguriere AirVisual"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@
|
||||
"error": {
|
||||
"cannot_connect": "\u05d4\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4",
|
||||
"general_error": "\u05e9\u05d2\u05d9\u05d0\u05d4 \u05d1\u05dc\u05ea\u05d9 \u05e6\u05e4\u05d5\u05d9\u05d4",
|
||||
"invalid_api_key": "\u05de\u05e4\u05ea\u05d7 API \u05dc\u05d0 \u05d7\u05d5\u05e7\u05d9 \u05e1\u05d5\u05e4\u05e7",
|
||||
"invalid_api_key": "\u05de\u05e4\u05ea\u05d7 API \u05dc\u05d0 \u05d7\u05d5\u05e7\u05d9",
|
||||
"location_not_found": "\u05d4\u05de\u05d9\u05e7\u05d5\u05dd \u05dc\u05d0 \u05e0\u05de\u05e6\u05d0"
|
||||
},
|
||||
"step": {
|
||||
@ -35,5 +35,14 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"step": {
|
||||
"init": {
|
||||
"data": {
|
||||
"show_on_map": "\u05d4\u05e6\u05d2 \u05d2\u05d9\u05d0\u05d5\u05d2\u05e8\u05e4\u05d9\u05d4 \u05de\u05e0\u05d5\u05d8\u05e8\u05ea \u05d1\u05de\u05e4\u05d4"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -7,7 +7,8 @@
|
||||
"error": {
|
||||
"cannot_connect": "Sikertelen csatlakoz\u00e1s",
|
||||
"general_error": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt",
|
||||
"invalid_api_key": "\u00c9rv\u00e9nytelen API kulcs"
|
||||
"invalid_api_key": "\u00c9rv\u00e9nytelen API kulcs",
|
||||
"location_not_found": "A hely nem tal\u00e1lhat\u00f3"
|
||||
},
|
||||
"step": {
|
||||
"geography_by_coords": {
|
||||
@ -15,14 +16,19 @@
|
||||
"api_key": "API kulcs",
|
||||
"latitude": "Sz\u00e9less\u00e9g",
|
||||
"longitude": "Hossz\u00fas\u00e1g"
|
||||
}
|
||||
},
|
||||
"description": "Haszn\u00e1lja az AirVisual felh\u0151 API-t a sz\u00e9less\u00e9g / hossz\u00fas\u00e1g figyel\u00e9s\u00e9hez.",
|
||||
"title": "Konfigur\u00e1lja a geogr\u00e1fi\u00e1t"
|
||||
},
|
||||
"geography_by_name": {
|
||||
"data": {
|
||||
"api_key": "API kulcs",
|
||||
"city": "V\u00e1ros",
|
||||
"country": "Orsz\u00e1g"
|
||||
}
|
||||
"country": "Orsz\u00e1g",
|
||||
"state": "\u00e1llapot"
|
||||
},
|
||||
"description": "Haszn\u00e1lja az AirVisual felh\u0151 API-t egy v\u00e1ros / \u00e1llam / orsz\u00e1g figyel\u00e9s\u00e9hez.",
|
||||
"title": "Konfigur\u00e1lja a geogr\u00e1fi\u00e1t"
|
||||
},
|
||||
"node_pro": {
|
||||
"data": {
|
||||
@ -33,7 +39,8 @@
|
||||
"reauth_confirm": {
|
||||
"data": {
|
||||
"api_key": "API kulcs"
|
||||
}
|
||||
},
|
||||
"title": "Az AirVisual \u00fajb\u00f3li hiteles\u00edt\u00e9se"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,7 @@
|
||||
"latitude": "\u0428\u0438\u0440\u043e\u0442\u0430",
|
||||
"longitude": "\u0414\u043e\u043b\u0433\u043e\u0442\u0430"
|
||||
},
|
||||
"description": "\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u043e\u0431\u043b\u0430\u0447\u043d\u044b\u0439 API AirVisual \u0434\u043b\u044f \u043c\u043e\u043d\u0438\u0442\u043e\u0440\u0438\u043d\u0433\u0430 \u0448\u0438\u0440\u043e\u0442\u044b/\u0434\u043e\u043b\u0433\u043e\u0442\u044b.",
|
||||
"description": "\u0414\u043b\u044f \u043c\u043e\u043d\u0438\u0442\u043e\u0440\u0438\u043d\u0433\u0430 \u043f\u043e \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u0430\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u043e\u0431\u043b\u0430\u0447\u043d\u044b\u0439 API AirVisual.",
|
||||
"title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u044f"
|
||||
},
|
||||
"geography_by_name": {
|
||||
@ -25,9 +25,9 @@
|
||||
"api_key": "\u041a\u043b\u044e\u0447 API",
|
||||
"city": "\u0413\u043e\u0440\u043e\u0434",
|
||||
"country": "\u0421\u0442\u0440\u0430\u043d\u0430",
|
||||
"state": "\u0448\u0442\u0430\u0442"
|
||||
"state": "\u0428\u0442\u0430\u0442"
|
||||
},
|
||||
"description": "\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u043e\u0431\u043b\u0430\u0447\u043d\u044b\u0439 API AirVisual \u0434\u043b\u044f \u043c\u043e\u043d\u0438\u0442\u043e\u0440\u0438\u043d\u0433\u0430 \u0433\u043e\u0440\u043e\u0434\u0430/\u0448\u0442\u0430\u0442\u0430/\u0441\u0442\u0440\u0430\u043d\u044b.",
|
||||
"description": "\u0414\u043b\u044f \u043c\u043e\u043d\u0438\u0442\u043e\u0440\u0438\u043d\u0433\u0430 \u0433\u043e\u0440\u043e\u0434\u0430/\u0448\u0442\u0430\u0442\u0430/\u0441\u0442\u0440\u0430\u043d\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u043e\u0431\u043b\u0430\u0447\u043d\u044b\u0439 API AirVisual.",
|
||||
"title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u044f"
|
||||
},
|
||||
"node_pro": {
|
||||
|
@ -0,0 +1,20 @@
|
||||
{
|
||||
"state": {
|
||||
"airvisual__pollutant_label": {
|
||||
"co": "Mon\u00f2xid de carboni",
|
||||
"n2": "Di\u00f2xid de nitrogen",
|
||||
"o3": "Oz\u00f3",
|
||||
"p1": "PM10",
|
||||
"p2": "PM2.5",
|
||||
"s2": "Di\u00f2xid de sofre"
|
||||
},
|
||||
"airvisual__pollutant_level": {
|
||||
"good": "Bo",
|
||||
"hazardous": "Perill\u00f3s",
|
||||
"moderate": "Moderat",
|
||||
"unhealthy": "Poc saludable",
|
||||
"unhealthy_sensitive": "Poc saludable per a grups sensibles",
|
||||
"very_unhealthy": "Molt poc saludable"
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
{
|
||||
"state": {
|
||||
"airvisual__pollutant_label": {
|
||||
"co": "Kohlenmonoxid",
|
||||
"n2": "Stickstoffdioxid",
|
||||
"o3": "Ozon",
|
||||
"p1": "PM10",
|
||||
"p2": "PM2,5",
|
||||
"s2": "Schwefeldioxid"
|
||||
},
|
||||
"airvisual__pollutant_level": {
|
||||
"good": "Gut",
|
||||
"hazardous": "Gef\u00e4hrlich",
|
||||
"moderate": "M\u00e4\u00dfig",
|
||||
"unhealthy": "Ungesund",
|
||||
"unhealthy_sensitive": "Ungesund f\u00fcr sensible Gruppen",
|
||||
"very_unhealthy": "Sehr ungesund"
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
{
|
||||
"state": {
|
||||
"airvisual__pollutant_label": {
|
||||
"co": "Carbon Monoxide",
|
||||
"n2": "Nitrogen Dioxide",
|
||||
"o3": "Ozone",
|
||||
"p1": "PM10",
|
||||
"p2": "PM2.5",
|
||||
"s2": "Sulfur Dioxide"
|
||||
},
|
||||
"airvisual__pollutant_level": {
|
||||
"good": "Good",
|
||||
"hazardous": "Hazardous",
|
||||
"moderate": "Moderate",
|
||||
"unhealthy": "Unhealthy",
|
||||
"unhealthy_sensitive": "Unhealthy for sensitive groups",
|
||||
"very_unhealthy": "Very unhealthy"
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user