mirror of
https://github.com/home-assistant/core.git
synced 2025-07-29 16:17:20 +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/helpers.py
|
||||||
homeassistant/components/acmeda/hub.py
|
homeassistant/components/acmeda/hub.py
|
||||||
homeassistant/components/acmeda/sensor.py
|
homeassistant/components/acmeda/sensor.py
|
||||||
|
homeassistant/components/adax/__init__.py
|
||||||
|
homeassistant/components/adax/climate.py
|
||||||
homeassistant/components/adguard/__init__.py
|
homeassistant/components/adguard/__init__.py
|
||||||
homeassistant/components/adguard/const.py
|
homeassistant/components/adguard/const.py
|
||||||
homeassistant/components/adguard/sensor.py
|
homeassistant/components/adguard/sensor.py
|
||||||
@ -35,7 +37,6 @@ omit =
|
|||||||
homeassistant/components/airnow/__init__.py
|
homeassistant/components/airnow/__init__.py
|
||||||
homeassistant/components/airnow/sensor.py
|
homeassistant/components/airnow/sensor.py
|
||||||
homeassistant/components/airvisual/__init__.py
|
homeassistant/components/airvisual/__init__.py
|
||||||
homeassistant/components/airvisual/air_quality.py
|
|
||||||
homeassistant/components/airvisual/sensor.py
|
homeassistant/components/airvisual/sensor.py
|
||||||
homeassistant/components/aladdin_connect/*
|
homeassistant/components/aladdin_connect/*
|
||||||
homeassistant/components/alarmdecoder/__init__.py
|
homeassistant/components/alarmdecoder/__init__.py
|
||||||
@ -105,6 +106,8 @@ omit =
|
|||||||
homeassistant/components/bloomsky/*
|
homeassistant/components/bloomsky/*
|
||||||
homeassistant/components/bluesound/*
|
homeassistant/components/bluesound/*
|
||||||
homeassistant/components/bluetooth_tracker/*
|
homeassistant/components/bluetooth_tracker/*
|
||||||
|
homeassistant/components/bme280/__init__.py
|
||||||
|
homeassistant/components/bme280/const.py
|
||||||
homeassistant/components/bme280/sensor.py
|
homeassistant/components/bme280/sensor.py
|
||||||
homeassistant/components/bme680/sensor.py
|
homeassistant/components/bme680/sensor.py
|
||||||
homeassistant/components/bmp280/sensor.py
|
homeassistant/components/bmp280/sensor.py
|
||||||
@ -131,9 +134,7 @@ omit =
|
|||||||
homeassistant/components/brottsplatskartan/sensor.py
|
homeassistant/components/brottsplatskartan/sensor.py
|
||||||
homeassistant/components/browser/*
|
homeassistant/components/browser/*
|
||||||
homeassistant/components/brunt/cover.py
|
homeassistant/components/brunt/cover.py
|
||||||
homeassistant/components/bsblan/__init__.py
|
|
||||||
homeassistant/components/bsblan/climate.py
|
homeassistant/components/bsblan/climate.py
|
||||||
homeassistant/components/bsblan/const.py
|
|
||||||
homeassistant/components/bt_home_hub_5/device_tracker.py
|
homeassistant/components/bt_home_hub_5/device_tracker.py
|
||||||
homeassistant/components/bt_smarthub/device_tracker.py
|
homeassistant/components/bt_smarthub/device_tracker.py
|
||||||
homeassistant/components/buienradar/sensor.py
|
homeassistant/components/buienradar/sensor.py
|
||||||
@ -153,7 +154,6 @@ omit =
|
|||||||
homeassistant/components/clicksend/notify.py
|
homeassistant/components/clicksend/notify.py
|
||||||
homeassistant/components/clicksend_tts/notify.py
|
homeassistant/components/clicksend_tts/notify.py
|
||||||
homeassistant/components/cmus/media_player.py
|
homeassistant/components/cmus/media_player.py
|
||||||
homeassistant/components/co2signal/*
|
|
||||||
homeassistant/components/coinbase/sensor.py
|
homeassistant/components/coinbase/sensor.py
|
||||||
homeassistant/components/comed_hourly_pricing/sensor.py
|
homeassistant/components/comed_hourly_pricing/sensor.py
|
||||||
homeassistant/components/comfoconnect/fan.py
|
homeassistant/components/comfoconnect/fan.py
|
||||||
@ -275,6 +275,7 @@ omit =
|
|||||||
homeassistant/components/esphome/fan.py
|
homeassistant/components/esphome/fan.py
|
||||||
homeassistant/components/esphome/light.py
|
homeassistant/components/esphome/light.py
|
||||||
homeassistant/components/esphome/number.py
|
homeassistant/components/esphome/number.py
|
||||||
|
homeassistant/components/esphome/select.py
|
||||||
homeassistant/components/esphome/sensor.py
|
homeassistant/components/esphome/sensor.py
|
||||||
homeassistant/components/esphome/switch.py
|
homeassistant/components/esphome/switch.py
|
||||||
homeassistant/components/essent/sensor.py
|
homeassistant/components/essent/sensor.py
|
||||||
@ -320,7 +321,8 @@ omit =
|
|||||||
homeassistant/components/flick_electric/const.py
|
homeassistant/components/flick_electric/const.py
|
||||||
homeassistant/components/flick_electric/sensor.py
|
homeassistant/components/flick_electric/sensor.py
|
||||||
homeassistant/components/flock/notify.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/__init__.py
|
||||||
homeassistant/components/flunearyou/sensor.py
|
homeassistant/components/flunearyou/sensor.py
|
||||||
homeassistant/components/flux_led/light.py
|
homeassistant/components/flux_led/light.py
|
||||||
@ -348,7 +350,6 @@ omit =
|
|||||||
homeassistant/components/fritzbox_callmonitor/const.py
|
homeassistant/components/fritzbox_callmonitor/const.py
|
||||||
homeassistant/components/fritzbox_callmonitor/base.py
|
homeassistant/components/fritzbox_callmonitor/base.py
|
||||||
homeassistant/components/fritzbox_callmonitor/sensor.py
|
homeassistant/components/fritzbox_callmonitor/sensor.py
|
||||||
homeassistant/components/fritzbox_netmonitor/sensor.py
|
|
||||||
homeassistant/components/fronius/sensor.py
|
homeassistant/components/fronius/sensor.py
|
||||||
homeassistant/components/frontier_silicon/media_player.py
|
homeassistant/components/frontier_silicon/media_player.py
|
||||||
homeassistant/components/futurenow/light.py
|
homeassistant/components/futurenow/light.py
|
||||||
@ -356,12 +357,9 @@ omit =
|
|||||||
homeassistant/components/garages_amsterdam/__init__.py
|
homeassistant/components/garages_amsterdam/__init__.py
|
||||||
homeassistant/components/garages_amsterdam/binary_sensor.py
|
homeassistant/components/garages_amsterdam/binary_sensor.py
|
||||||
homeassistant/components/garages_amsterdam/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/gc100/*
|
||||||
homeassistant/components/geniushub/*
|
homeassistant/components/geniushub/*
|
||||||
|
homeassistant/components/generic_hygrostat/*
|
||||||
homeassistant/components/github/sensor.py
|
homeassistant/components/github/sensor.py
|
||||||
homeassistant/components/gitlab_ci/sensor.py
|
homeassistant/components/gitlab_ci/sensor.py
|
||||||
homeassistant/components/gitter/sensor.py
|
homeassistant/components/gitter/sensor.py
|
||||||
@ -372,6 +370,7 @@ omit =
|
|||||||
homeassistant/components/goalfeed/*
|
homeassistant/components/goalfeed/*
|
||||||
homeassistant/components/goalzero/__init__.py
|
homeassistant/components/goalzero/__init__.py
|
||||||
homeassistant/components/goalzero/binary_sensor.py
|
homeassistant/components/goalzero/binary_sensor.py
|
||||||
|
homeassistant/components/goalzero/sensor.py
|
||||||
homeassistant/components/goalzero/switch.py
|
homeassistant/components/goalzero/switch.py
|
||||||
homeassistant/components/google/*
|
homeassistant/components/google/*
|
||||||
homeassistant/components/google_cloud/tts.py
|
homeassistant/components/google_cloud/tts.py
|
||||||
@ -398,10 +397,6 @@ omit =
|
|||||||
homeassistant/components/habitica/const.py
|
homeassistant/components/habitica/const.py
|
||||||
homeassistant/components/habitica/sensor.py
|
homeassistant/components/habitica/sensor.py
|
||||||
homeassistant/components/hangouts/*
|
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/harman_kardon_avr/media_player.py
|
||||||
homeassistant/components/harmony/const.py
|
homeassistant/components/harmony/const.py
|
||||||
homeassistant/components/harmony/data.py
|
homeassistant/components/harmony/data.py
|
||||||
@ -415,7 +410,8 @@ omit =
|
|||||||
homeassistant/components/heatmiser/climate.py
|
homeassistant/components/heatmiser/climate.py
|
||||||
homeassistant/components/hikvision/binary_sensor.py
|
homeassistant/components/hikvision/binary_sensor.py
|
||||||
homeassistant/components/hikvisioncam/switch.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/hitron_coda/device_tracker.py
|
||||||
homeassistant/components/hive/__init__.py
|
homeassistant/components/hive/__init__.py
|
||||||
homeassistant/components/hive/climate.py
|
homeassistant/components/hive/climate.py
|
||||||
@ -426,15 +422,18 @@ omit =
|
|||||||
homeassistant/components/hive/water_heater.py
|
homeassistant/components/hive/water_heater.py
|
||||||
homeassistant/components/hlk_sw16/__init__.py
|
homeassistant/components/hlk_sw16/__init__.py
|
||||||
homeassistant/components/hlk_sw16/switch.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/*
|
||||||
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/api.py
|
||||||
homeassistant/components/home_plus_control/helpers.py
|
|
||||||
homeassistant/components/home_plus_control/switch.py
|
homeassistant/components/home_plus_control/switch.py
|
||||||
homeassistant/components/homeworks/*
|
homeassistant/components/homeworks/*
|
||||||
|
homeassistant/components/honeywell/__init__.py
|
||||||
homeassistant/components/honeywell/climate.py
|
homeassistant/components/honeywell/climate.py
|
||||||
homeassistant/components/horizon/media_player.py
|
homeassistant/components/horizon/media_player.py
|
||||||
homeassistant/components/hp_ilo/sensor.py
|
homeassistant/components/hp_ilo/sensor.py
|
||||||
@ -525,8 +524,6 @@ omit =
|
|||||||
homeassistant/components/kira/*
|
homeassistant/components/kira/*
|
||||||
homeassistant/components/kiwi/lock.py
|
homeassistant/components/kiwi/lock.py
|
||||||
homeassistant/components/knx/*
|
homeassistant/components/knx/*
|
||||||
homeassistant/components/knx/climate.py
|
|
||||||
homeassistant/components/knx/cover.py
|
|
||||||
homeassistant/components/kodi/__init__.py
|
homeassistant/components/kodi/__init__.py
|
||||||
homeassistant/components/kodi/browse_media.py
|
homeassistant/components/kodi/browse_media.py
|
||||||
homeassistant/components/kodi/const.py
|
homeassistant/components/kodi/const.py
|
||||||
@ -624,6 +621,7 @@ omit =
|
|||||||
homeassistant/components/mill/__init__.py
|
homeassistant/components/mill/__init__.py
|
||||||
homeassistant/components/mill/climate.py
|
homeassistant/components/mill/climate.py
|
||||||
homeassistant/components/mill/const.py
|
homeassistant/components/mill/const.py
|
||||||
|
homeassistant/components/mill/sensor.py
|
||||||
homeassistant/components/minecraft_server/__init__.py
|
homeassistant/components/minecraft_server/__init__.py
|
||||||
homeassistant/components/minecraft_server/binary_sensor.py
|
homeassistant/components/minecraft_server/binary_sensor.py
|
||||||
homeassistant/components/minecraft_server/const.py
|
homeassistant/components/minecraft_server/const.py
|
||||||
@ -638,6 +636,7 @@ omit =
|
|||||||
homeassistant/components/modbus/cover.py
|
homeassistant/components/modbus/cover.py
|
||||||
homeassistant/components/modbus/climate.py
|
homeassistant/components/modbus/climate.py
|
||||||
homeassistant/components/modbus/modbus.py
|
homeassistant/components/modbus/modbus.py
|
||||||
|
homeassistant/components/modbus/validators.py
|
||||||
homeassistant/components/modem_callerid/sensor.py
|
homeassistant/components/modem_callerid/sensor.py
|
||||||
homeassistant/components/motion_blinds/__init__.py
|
homeassistant/components/motion_blinds/__init__.py
|
||||||
homeassistant/components/motion_blinds/const.py
|
homeassistant/components/motion_blinds/const.py
|
||||||
@ -655,7 +654,6 @@ omit =
|
|||||||
homeassistant/components/mvglive/sensor.py
|
homeassistant/components/mvglive/sensor.py
|
||||||
homeassistant/components/mychevy/*
|
homeassistant/components/mychevy/*
|
||||||
homeassistant/components/mycroft/*
|
homeassistant/components/mycroft/*
|
||||||
homeassistant/components/mycroft/notify.py
|
|
||||||
homeassistant/components/mysensors/__init__.py
|
homeassistant/components/mysensors/__init__.py
|
||||||
homeassistant/components/mysensors/binary_sensor.py
|
homeassistant/components/mysensors/binary_sensor.py
|
||||||
homeassistant/components/mysensors/climate.py
|
homeassistant/components/mysensors/climate.py
|
||||||
@ -692,6 +690,7 @@ omit =
|
|||||||
homeassistant/components/neurio_energy/sensor.py
|
homeassistant/components/neurio_energy/sensor.py
|
||||||
homeassistant/components/nexia/climate.py
|
homeassistant/components/nexia/climate.py
|
||||||
homeassistant/components/nextcloud/*
|
homeassistant/components/nextcloud/*
|
||||||
|
homeassistant/components/nfandroidtv/__init__.py
|
||||||
homeassistant/components/nfandroidtv/notify.py
|
homeassistant/components/nfandroidtv/notify.py
|
||||||
homeassistant/components/niko_home_control/light.py
|
homeassistant/components/niko_home_control/light.py
|
||||||
homeassistant/components/nilu/air_quality.py
|
homeassistant/components/nilu/air_quality.py
|
||||||
@ -827,8 +826,6 @@ omit =
|
|||||||
homeassistant/components/radarr/sensor.py
|
homeassistant/components/radarr/sensor.py
|
||||||
homeassistant/components/radiotherm/climate.py
|
homeassistant/components/radiotherm/climate.py
|
||||||
homeassistant/components/rainbird/*
|
homeassistant/components/rainbird/*
|
||||||
homeassistant/components/rainbird/sensor.py
|
|
||||||
homeassistant/components/rainbird/switch.py
|
|
||||||
homeassistant/components/raincloud/*
|
homeassistant/components/raincloud/*
|
||||||
homeassistant/components/rainmachine/__init__.py
|
homeassistant/components/rainmachine/__init__.py
|
||||||
homeassistant/components/rainmachine/binary_sensor.py
|
homeassistant/components/rainmachine/binary_sensor.py
|
||||||
@ -875,7 +872,6 @@ omit =
|
|||||||
homeassistant/components/rova/sensor.py
|
homeassistant/components/rova/sensor.py
|
||||||
homeassistant/components/rpi_camera/*
|
homeassistant/components/rpi_camera/*
|
||||||
homeassistant/components/rpi_gpio/*
|
homeassistant/components/rpi_gpio/*
|
||||||
homeassistant/components/rpi_gpio/cover.py
|
|
||||||
homeassistant/components/rpi_gpio_pwm/light.py
|
homeassistant/components/rpi_gpio_pwm/light.py
|
||||||
homeassistant/components/rpi_pfio/*
|
homeassistant/components/rpi_pfio/*
|
||||||
homeassistant/components/rpi_rf/switch.py
|
homeassistant/components/rpi_rf/switch.py
|
||||||
@ -895,7 +891,6 @@ omit =
|
|||||||
homeassistant/components/screenlogic/services.py
|
homeassistant/components/screenlogic/services.py
|
||||||
homeassistant/components/screenlogic/switch.py
|
homeassistant/components/screenlogic/switch.py
|
||||||
homeassistant/components/scsgate/*
|
homeassistant/components/scsgate/*
|
||||||
homeassistant/components/scsgate/cover.py
|
|
||||||
homeassistant/components/sendgrid/notify.py
|
homeassistant/components/sendgrid/notify.py
|
||||||
homeassistant/components/sense/*
|
homeassistant/components/sense/*
|
||||||
homeassistant/components/sensehat/light.py
|
homeassistant/components/sensehat/light.py
|
||||||
@ -973,7 +968,6 @@ omit =
|
|||||||
homeassistant/components/sonos/*
|
homeassistant/components/sonos/*
|
||||||
homeassistant/components/sony_projector/switch.py
|
homeassistant/components/sony_projector/switch.py
|
||||||
homeassistant/components/spc/*
|
homeassistant/components/spc/*
|
||||||
homeassistant/components/speedtestdotnet/*
|
|
||||||
homeassistant/components/spider/*
|
homeassistant/components/spider/*
|
||||||
homeassistant/components/splunk/*
|
homeassistant/components/splunk/*
|
||||||
homeassistant/components/spotify/__init__.py
|
homeassistant/components/spotify/__init__.py
|
||||||
@ -998,8 +992,6 @@ omit =
|
|||||||
homeassistant/components/swiss_public_transport/sensor.py
|
homeassistant/components/swiss_public_transport/sensor.py
|
||||||
homeassistant/components/swisscom/device_tracker.py
|
homeassistant/components/swisscom/device_tracker.py
|
||||||
homeassistant/components/switchbot/switch.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/switchmate/switch.py
|
||||||
homeassistant/components/syncthing/__init__.py
|
homeassistant/components/syncthing/__init__.py
|
||||||
homeassistant/components/syncthing/sensor.py
|
homeassistant/components/syncthing/sensor.py
|
||||||
@ -1020,7 +1012,6 @@ omit =
|
|||||||
homeassistant/components/system_bridge/sensor.py
|
homeassistant/components/system_bridge/sensor.py
|
||||||
homeassistant/components/systemmonitor/sensor.py
|
homeassistant/components/systemmonitor/sensor.py
|
||||||
homeassistant/components/tado/*
|
homeassistant/components/tado/*
|
||||||
homeassistant/components/tado/device_tracker.py
|
|
||||||
homeassistant/components/tahoma/*
|
homeassistant/components/tahoma/*
|
||||||
homeassistant/components/tank_utility/sensor.py
|
homeassistant/components/tank_utility/sensor.py
|
||||||
homeassistant/components/tankerkoenig/*
|
homeassistant/components/tankerkoenig/*
|
||||||
@ -1088,9 +1079,6 @@ omit =
|
|||||||
homeassistant/components/traccar/const.py
|
homeassistant/components/traccar/const.py
|
||||||
homeassistant/components/trackr/device_tracker.py
|
homeassistant/components/trackr/device_tracker.py
|
||||||
homeassistant/components/tradfri/*
|
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_train/sensor.py
|
||||||
homeassistant/components/trafikverket_weatherstation/sensor.py
|
homeassistant/components/trafikverket_weatherstation/sensor.py
|
||||||
homeassistant/components/transmission/sensor.py
|
homeassistant/components/transmission/sensor.py
|
||||||
@ -1211,20 +1199,29 @@ omit =
|
|||||||
homeassistant/components/xiaomi_miio/device_tracker.py
|
homeassistant/components/xiaomi_miio/device_tracker.py
|
||||||
homeassistant/components/xiaomi_miio/fan.py
|
homeassistant/components/xiaomi_miio/fan.py
|
||||||
homeassistant/components/xiaomi_miio/gateway.py
|
homeassistant/components/xiaomi_miio/gateway.py
|
||||||
|
homeassistant/components/xiaomi_miio/humidifier.py
|
||||||
homeassistant/components/xiaomi_miio/light.py
|
homeassistant/components/xiaomi_miio/light.py
|
||||||
|
homeassistant/components/xiaomi_miio/number.py
|
||||||
homeassistant/components/xiaomi_miio/remote.py
|
homeassistant/components/xiaomi_miio/remote.py
|
||||||
|
homeassistant/components/xiaomi_miio/select.py
|
||||||
homeassistant/components/xiaomi_miio/sensor.py
|
homeassistant/components/xiaomi_miio/sensor.py
|
||||||
homeassistant/components/xiaomi_miio/switch.py
|
homeassistant/components/xiaomi_miio/switch.py
|
||||||
homeassistant/components/xiaomi_miio/vacuum.py
|
homeassistant/components/xiaomi_miio/vacuum.py
|
||||||
homeassistant/components/xiaomi_tv/media_player.py
|
homeassistant/components/xiaomi_tv/media_player.py
|
||||||
homeassistant/components/xmpp/notify.py
|
homeassistant/components/xmpp/notify.py
|
||||||
homeassistant/components/xs1/*
|
homeassistant/components/xs1/*
|
||||||
|
homeassistant/components/yale_smart_alarm/__init__.py
|
||||||
homeassistant/components/yale_smart_alarm/alarm_control_panel.py
|
homeassistant/components/yale_smart_alarm/alarm_control_panel.py
|
||||||
|
homeassistant/components/yale_smart_alarm/const.py
|
||||||
|
homeassistant/components/yale_smart_alarm/coordinator.py
|
||||||
homeassistant/components/yamaha_musiccast/__init__.py
|
homeassistant/components/yamaha_musiccast/__init__.py
|
||||||
homeassistant/components/yamaha_musiccast/media_player.py
|
homeassistant/components/yamaha_musiccast/media_player.py
|
||||||
homeassistant/components/yandex_transport/*
|
homeassistant/components/yandex_transport/*
|
||||||
homeassistant/components/yeelightsunflower/light.py
|
homeassistant/components/yeelightsunflower/light.py
|
||||||
homeassistant/components/yi/camera.py
|
homeassistant/components/yi/camera.py
|
||||||
|
homeassistant/components/youless/__init__.py
|
||||||
|
homeassistant/components/youless/const.py
|
||||||
|
homeassistant/components/youless/sensor.py
|
||||||
homeassistant/components/zabbix/*
|
homeassistant/components/zabbix/*
|
||||||
homeassistant/components/zamg/sensor.py
|
homeassistant/components/zamg/sensor.py
|
||||||
homeassistant/components/zamg/weather.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 }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Build base image
|
- name: Build base image
|
||||||
uses: home-assistant/builder@2021.06.2
|
uses: home-assistant/builder@2021.07.0
|
||||||
with:
|
with:
|
||||||
args: |
|
args: |
|
||||||
$BUILD_ARGS \
|
$BUILD_ARGS \
|
||||||
@ -134,6 +134,7 @@ jobs:
|
|||||||
machine:
|
machine:
|
||||||
- generic-x86-64
|
- generic-x86-64
|
||||||
- intel-nuc
|
- intel-nuc
|
||||||
|
- khadas-vim3
|
||||||
- odroid-c2
|
- odroid-c2
|
||||||
- odroid-c4
|
- odroid-c4
|
||||||
- odroid-n2
|
- odroid-n2
|
||||||
@ -167,7 +168,7 @@ jobs:
|
|||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Build base image
|
- name: Build base image
|
||||||
uses: home-assistant/builder@2021.06.2
|
uses: home-assistant/builder@2021.07.0
|
||||||
with:
|
with:
|
||||||
args: |
|
args: |
|
||||||
$BUILD_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 report --fail-under=94
|
||||||
coverage xml
|
coverage xml
|
||||||
- name: Upload coverage to Codecov
|
- 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:
|
lock:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: dessant/lock-threads@v2.0.3
|
- uses: dessant/lock-threads@v2.1.1
|
||||||
with:
|
with:
|
||||||
github-token: ${{ github.token }}
|
github-token: ${{ github.token }}
|
||||||
issue-lock-inactive-days: "30"
|
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 PRs marked as no-stale
|
||||||
# - No issues marked as no-stale or help-wanted
|
# - No issues marked as no-stale or help-wanted
|
||||||
- name: 90 days stale issues & PRs policy
|
- name: 90 days stale issues & PRs policy
|
||||||
uses: actions/stale@v3.0.19
|
uses: actions/stale@v4
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
days-before-stale: 90
|
days-before-stale: 90
|
||||||
@ -53,7 +53,7 @@ jobs:
|
|||||||
# - No PRs marked as no-stale or new-integrations
|
# - No PRs marked as no-stale or new-integrations
|
||||||
# - No issues (-1)
|
# - No issues (-1)
|
||||||
- name: 30 days stale PRs policy
|
- name: 30 days stale PRs policy
|
||||||
uses: actions/stale@v3.0.19
|
uses: actions/stale@v4
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
days-before-stale: 30
|
days-before-stale: 30
|
||||||
@ -78,7 +78,7 @@ jobs:
|
|||||||
# - No Issues marked as no-stale or help-wanted
|
# - No Issues marked as no-stale or help-wanted
|
||||||
# - No PRs (-1)
|
# - No PRs (-1)
|
||||||
- name: Needs more information stale issues policy
|
- name: Needs more information stale issues policy
|
||||||
uses: actions/stale@v3.0.19
|
uses: actions/stale@v4
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
only-labels: "needs-more-information"
|
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: requirements_diff
|
||||||
|
|
||||||
- name: Build wheels
|
- name: Build wheels
|
||||||
uses: home-assistant/wheels@2021.06.0
|
uses: home-assistant/wheels@2021.07.0
|
||||||
with:
|
with:
|
||||||
tag: ${{ matrix.tag }}
|
tag: ${{ matrix.tag }}
|
||||||
arch: ${{ matrix.arch }}
|
arch: ${{ matrix.arch }}
|
||||||
@ -150,7 +150,7 @@ jobs:
|
|||||||
done
|
done
|
||||||
|
|
||||||
- name: Build wheels
|
- name: Build wheels
|
||||||
uses: home-assistant/wheels@2021.06.0
|
uses: home-assistant/wheels@2021.07.0
|
||||||
with:
|
with:
|
||||||
tag: ${{ matrix.tag }}
|
tag: ${{ matrix.tag }}
|
||||||
arch: ${{ matrix.arch }}
|
arch: ${{ matrix.arch }}
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/asottile/pyupgrade
|
- repo: https://github.com/asottile/pyupgrade
|
||||||
rev: v2.16.0
|
rev: v2.23.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: pyupgrade
|
- id: pyupgrade
|
||||||
args: [--py38-plus]
|
args: [--py38-plus]
|
||||||
- repo: https://github.com/psf/black
|
- repo: https://github.com/psf/black
|
||||||
rev: 21.6b0
|
rev: 21.7b0
|
||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- id: black
|
||||||
args:
|
args:
|
||||||
@ -70,7 +70,7 @@ repos:
|
|||||||
- id: prettier
|
- id: prettier
|
||||||
stages: [manual]
|
stages: [manual]
|
||||||
- repo: https://github.com/cdce8p/python-typing-update
|
- repo: https://github.com/cdce8p/python-typing-update
|
||||||
rev: v0.3.3
|
rev: v0.3.5
|
||||||
hooks:
|
hooks:
|
||||||
# Run `python-typing-update` hook manually from time to time
|
# Run `python-typing-update` hook manually from time to time
|
||||||
# to update python typing syntax.
|
# to update python typing syntax.
|
||||||
|
@ -9,15 +9,18 @@ homeassistant.components.actiontec.*
|
|||||||
homeassistant.components.aftership.*
|
homeassistant.components.aftership.*
|
||||||
homeassistant.components.air_quality.*
|
homeassistant.components.air_quality.*
|
||||||
homeassistant.components.airly.*
|
homeassistant.components.airly.*
|
||||||
|
homeassistant.components.airvisual.*
|
||||||
homeassistant.components.aladdin_connect.*
|
homeassistant.components.aladdin_connect.*
|
||||||
homeassistant.components.alarm_control_panel.*
|
homeassistant.components.alarm_control_panel.*
|
||||||
homeassistant.components.amazon_polly.*
|
homeassistant.components.amazon_polly.*
|
||||||
homeassistant.components.ambee.*
|
homeassistant.components.ambee.*
|
||||||
|
homeassistant.components.ambient_station.*
|
||||||
homeassistant.components.ampio.*
|
homeassistant.components.ampio.*
|
||||||
homeassistant.components.automation.*
|
homeassistant.components.automation.*
|
||||||
homeassistant.components.binary_sensor.*
|
homeassistant.components.binary_sensor.*
|
||||||
homeassistant.components.bluetooth_tracker.*
|
homeassistant.components.bluetooth_tracker.*
|
||||||
homeassistant.components.bond.*
|
homeassistant.components.bond.*
|
||||||
|
homeassistant.components.braviatv.*
|
||||||
homeassistant.components.brother.*
|
homeassistant.components.brother.*
|
||||||
homeassistant.components.calendar.*
|
homeassistant.components.calendar.*
|
||||||
homeassistant.components.camera.*
|
homeassistant.components.camera.*
|
||||||
@ -25,17 +28,24 @@ homeassistant.components.canary.*
|
|||||||
homeassistant.components.cover.*
|
homeassistant.components.cover.*
|
||||||
homeassistant.components.device_automation.*
|
homeassistant.components.device_automation.*
|
||||||
homeassistant.components.device_tracker.*
|
homeassistant.components.device_tracker.*
|
||||||
|
homeassistant.components.devolo_home_control.*
|
||||||
homeassistant.components.dnsip.*
|
homeassistant.components.dnsip.*
|
||||||
homeassistant.components.dsmr.*
|
homeassistant.components.dsmr.*
|
||||||
homeassistant.components.dunehd.*
|
homeassistant.components.dunehd.*
|
||||||
homeassistant.components.elgato.*
|
homeassistant.components.elgato.*
|
||||||
|
homeassistant.components.esphome.*
|
||||||
|
homeassistant.components.energy.*
|
||||||
|
homeassistant.components.fastdotcom.*
|
||||||
homeassistant.components.fitbit.*
|
homeassistant.components.fitbit.*
|
||||||
|
homeassistant.components.flunearyou.*
|
||||||
homeassistant.components.forecast_solar.*
|
homeassistant.components.forecast_solar.*
|
||||||
homeassistant.components.fritzbox.*
|
homeassistant.components.fritzbox.*
|
||||||
homeassistant.components.frontend.*
|
homeassistant.components.frontend.*
|
||||||
|
homeassistant.components.fritz.*
|
||||||
homeassistant.components.geo_location.*
|
homeassistant.components.geo_location.*
|
||||||
homeassistant.components.gios.*
|
homeassistant.components.gios.*
|
||||||
homeassistant.components.group.*
|
homeassistant.components.group.*
|
||||||
|
homeassistant.components.guardian.*
|
||||||
homeassistant.components.history.*
|
homeassistant.components.history.*
|
||||||
homeassistant.components.homeassistant.triggers.event
|
homeassistant.components.homeassistant.triggers.event
|
||||||
homeassistant.components.http.*
|
homeassistant.components.http.*
|
||||||
@ -45,6 +55,7 @@ homeassistant.components.image_processing.*
|
|||||||
homeassistant.components.integration.*
|
homeassistant.components.integration.*
|
||||||
homeassistant.components.knx.*
|
homeassistant.components.knx.*
|
||||||
homeassistant.components.kraken.*
|
homeassistant.components.kraken.*
|
||||||
|
homeassistant.components.lcn.*
|
||||||
homeassistant.components.light.*
|
homeassistant.components.light.*
|
||||||
homeassistant.components.local_ip.*
|
homeassistant.components.local_ip.*
|
||||||
homeassistant.components.lock.*
|
homeassistant.components.lock.*
|
||||||
@ -52,30 +63,43 @@ homeassistant.components.mailbox.*
|
|||||||
homeassistant.components.media_player.*
|
homeassistant.components.media_player.*
|
||||||
homeassistant.components.mysensors.*
|
homeassistant.components.mysensors.*
|
||||||
homeassistant.components.nam.*
|
homeassistant.components.nam.*
|
||||||
|
homeassistant.components.nest.*
|
||||||
|
homeassistant.components.netatmo.*
|
||||||
homeassistant.components.network.*
|
homeassistant.components.network.*
|
||||||
homeassistant.components.no_ip.*
|
homeassistant.components.no_ip.*
|
||||||
homeassistant.components.notify.*
|
homeassistant.components.notify.*
|
||||||
|
homeassistant.components.notion.*
|
||||||
homeassistant.components.number.*
|
homeassistant.components.number.*
|
||||||
homeassistant.components.onewire.*
|
homeassistant.components.onewire.*
|
||||||
|
homeassistant.components.openuv.*
|
||||||
homeassistant.components.persistent_notification.*
|
homeassistant.components.persistent_notification.*
|
||||||
homeassistant.components.pi_hole.*
|
homeassistant.components.pi_hole.*
|
||||||
homeassistant.components.proximity.*
|
homeassistant.components.proximity.*
|
||||||
|
homeassistant.components.rainmachine.*
|
||||||
|
homeassistant.components.recollect_waste.*
|
||||||
homeassistant.components.recorder.purge
|
homeassistant.components.recorder.purge
|
||||||
homeassistant.components.recorder.repack
|
homeassistant.components.recorder.repack
|
||||||
homeassistant.components.recorder.statistics
|
homeassistant.components.recorder.statistics
|
||||||
homeassistant.components.remote.*
|
homeassistant.components.remote.*
|
||||||
|
homeassistant.components.renault.*
|
||||||
|
homeassistant.components.rituals_perfume_genie.*
|
||||||
homeassistant.components.scene.*
|
homeassistant.components.scene.*
|
||||||
homeassistant.components.select.*
|
homeassistant.components.select.*
|
||||||
homeassistant.components.sensor.*
|
homeassistant.components.sensor.*
|
||||||
|
homeassistant.components.shelly.*
|
||||||
|
homeassistant.components.simplisafe.*
|
||||||
homeassistant.components.slack.*
|
homeassistant.components.slack.*
|
||||||
homeassistant.components.sonos.media_player
|
homeassistant.components.sonos.media_player
|
||||||
homeassistant.components.ssdp.*
|
homeassistant.components.ssdp.*
|
||||||
homeassistant.components.stream.*
|
homeassistant.components.stream.*
|
||||||
homeassistant.components.sun.*
|
homeassistant.components.sun.*
|
||||||
homeassistant.components.switch.*
|
homeassistant.components.switch.*
|
||||||
|
homeassistant.components.switcher_kis.*
|
||||||
homeassistant.components.synology_dsm.*
|
homeassistant.components.synology_dsm.*
|
||||||
homeassistant.components.systemmonitor.*
|
homeassistant.components.systemmonitor.*
|
||||||
|
homeassistant.components.tag.*
|
||||||
homeassistant.components.tcp.*
|
homeassistant.components.tcp.*
|
||||||
|
homeassistant.components.tile.*
|
||||||
homeassistant.components.tts.*
|
homeassistant.components.tts.*
|
||||||
homeassistant.components.upcloud.*
|
homeassistant.components.upcloud.*
|
||||||
homeassistant.components.uptime.*
|
homeassistant.components.uptime.*
|
||||||
|
15
.vscode/tasks.json
vendored
15
.vscode/tasks.json
vendored
@ -2,13 +2,10 @@
|
|||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"tasks": [
|
"tasks": [
|
||||||
{
|
{
|
||||||
"label": "Preview",
|
"label": "Run Home Assistant Core",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "hass -c ./config",
|
"command": "hass -c ./config",
|
||||||
"group": {
|
"group": "test",
|
||||||
"kind": "test",
|
|
||||||
"isDefault": true
|
|
||||||
},
|
|
||||||
"presentation": {
|
"presentation": {
|
||||||
"reveal": "always",
|
"reveal": "always",
|
||||||
"panel": "new"
|
"panel": "new"
|
||||||
@ -19,7 +16,9 @@
|
|||||||
"label": "Pytest",
|
"label": "Pytest",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "pytest --timeout=10 tests",
|
"command": "pytest --timeout=10 tests",
|
||||||
"dependsOn": ["Install all Test Requirements"],
|
"dependsOn": [
|
||||||
|
"Install all Test Requirements"
|
||||||
|
],
|
||||||
"group": {
|
"group": {
|
||||||
"kind": "test",
|
"kind": "test",
|
||||||
"isDefault": true
|
"isDefault": true
|
||||||
@ -48,7 +47,9 @@
|
|||||||
"label": "Pylint",
|
"label": "Pylint",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "pylint homeassistant",
|
"command": "pylint homeassistant",
|
||||||
"dependsOn": ["Install all Requirements"],
|
"dependsOn": [
|
||||||
|
"Install all Requirements"
|
||||||
|
],
|
||||||
"group": {
|
"group": {
|
||||||
"kind": "test",
|
"kind": "test",
|
||||||
"isDefault": true
|
"isDefault": true
|
||||||
|
18
CODEOWNERS
18
CODEOWNERS
@ -22,6 +22,7 @@ homeassistant/scripts/check_config.py @kellerza
|
|||||||
homeassistant/components/abode/* @shred86
|
homeassistant/components/abode/* @shred86
|
||||||
homeassistant/components/accuweather/* @bieniu
|
homeassistant/components/accuweather/* @bieniu
|
||||||
homeassistant/components/acmeda/* @atmurray
|
homeassistant/components/acmeda/* @atmurray
|
||||||
|
homeassistant/components/adax/* @danielhiversen
|
||||||
homeassistant/components/adguard/* @frenck
|
homeassistant/components/adguard/* @frenck
|
||||||
homeassistant/components/advantage_air/* @Bre77
|
homeassistant/components/advantage_air/* @Bre77
|
||||||
homeassistant/components/aemet/* @noltari
|
homeassistant/components/aemet/* @noltari
|
||||||
@ -97,7 +98,7 @@ homeassistant/components/configurator/* @home-assistant/core
|
|||||||
homeassistant/components/control4/* @lawtancool
|
homeassistant/components/control4/* @lawtancool
|
||||||
homeassistant/components/conversation/* @home-assistant/core
|
homeassistant/components/conversation/* @home-assistant/core
|
||||||
homeassistant/components/coolmaster/* @OnFreund
|
homeassistant/components/coolmaster/* @OnFreund
|
||||||
homeassistant/components/coronavirus/* @home_assistant/core
|
homeassistant/components/coronavirus/* @home-assistant/core
|
||||||
homeassistant/components/counter/* @fabaff
|
homeassistant/components/counter/* @fabaff
|
||||||
homeassistant/components/cover/* @home-assistant/core
|
homeassistant/components/cover/* @home-assistant/core
|
||||||
homeassistant/components/cpuspeed/* @fabaff
|
homeassistant/components/cpuspeed/* @fabaff
|
||||||
@ -139,6 +140,7 @@ homeassistant/components/emby/* @mezz64
|
|||||||
homeassistant/components/emoncms/* @borpin
|
homeassistant/components/emoncms/* @borpin
|
||||||
homeassistant/components/emonitor/* @bdraco
|
homeassistant/components/emonitor/* @bdraco
|
||||||
homeassistant/components/emulated_kasa/* @kbickar
|
homeassistant/components/emulated_kasa/* @kbickar
|
||||||
|
homeassistant/components/energy/* @home-assistant/core
|
||||||
homeassistant/components/enigma2/* @fbradyirl
|
homeassistant/components/enigma2/* @fbradyirl
|
||||||
homeassistant/components/enocean/* @bdurrer
|
homeassistant/components/enocean/* @bdurrer
|
||||||
homeassistant/components/enphase_envoy/* @gtdiehl
|
homeassistant/components/enphase_envoy/* @gtdiehl
|
||||||
@ -160,6 +162,7 @@ homeassistant/components/fireservicerota/* @cyberjunky
|
|||||||
homeassistant/components/firmata/* @DaAwesomeP
|
homeassistant/components/firmata/* @DaAwesomeP
|
||||||
homeassistant/components/fixer/* @fabaff
|
homeassistant/components/fixer/* @fabaff
|
||||||
homeassistant/components/flick_electric/* @ZephireNZ
|
homeassistant/components/flick_electric/* @ZephireNZ
|
||||||
|
homeassistant/components/flipr/* @cnico
|
||||||
homeassistant/components/flo/* @dmulcahey
|
homeassistant/components/flo/* @dmulcahey
|
||||||
homeassistant/components/flock/* @fabaff
|
homeassistant/components/flock/* @fabaff
|
||||||
homeassistant/components/flume/* @ChrisMandich @bdraco
|
homeassistant/components/flume/* @ChrisMandich @bdraco
|
||||||
@ -175,8 +178,8 @@ homeassistant/components/fritzbox/* @mib1185
|
|||||||
homeassistant/components/fronius/* @nielstron
|
homeassistant/components/fronius/* @nielstron
|
||||||
homeassistant/components/frontend/* @home-assistant/frontend
|
homeassistant/components/frontend/* @home-assistant/frontend
|
||||||
homeassistant/components/garages_amsterdam/* @klaasnicolaas
|
homeassistant/components/garages_amsterdam/* @klaasnicolaas
|
||||||
homeassistant/components/garmin_connect/* @cyberjunky
|
|
||||||
homeassistant/components/gdacs/* @exxamalte
|
homeassistant/components/gdacs/* @exxamalte
|
||||||
|
homeassistant/components/generic_hygrostat/* @Shulyaka
|
||||||
homeassistant/components/geniushub/* @zxdavb
|
homeassistant/components/geniushub/* @zxdavb
|
||||||
homeassistant/components/geo_json_events/* @exxamalte
|
homeassistant/components/geo_json_events/* @exxamalte
|
||||||
homeassistant/components/geo_rss_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/* @bdraco
|
||||||
homeassistant/components/homekit_controller/* @Jc2k @bdraco
|
homeassistant/components/homekit_controller/* @Jc2k @bdraco
|
||||||
homeassistant/components/homematic/* @pvizeli @danielperna84
|
homeassistant/components/homematic/* @pvizeli @danielperna84
|
||||||
|
homeassistant/components/honeywell/* @rdfurman
|
||||||
homeassistant/components/http/* @home-assistant/core
|
homeassistant/components/http/* @home-assistant/core
|
||||||
homeassistant/components/huawei_lte/* @scop @fphammerle
|
homeassistant/components/huawei_lte/* @scop @fphammerle
|
||||||
homeassistant/components/huawei_router/* @abmantis
|
homeassistant/components/huawei_router/* @abmantis
|
||||||
@ -329,6 +333,7 @@ homeassistant/components/netdata/* @fabaff
|
|||||||
homeassistant/components/nexia/* @bdraco
|
homeassistant/components/nexia/* @bdraco
|
||||||
homeassistant/components/nextbus/* @vividboarder
|
homeassistant/components/nextbus/* @vividboarder
|
||||||
homeassistant/components/nextcloud/* @meichthys
|
homeassistant/components/nextcloud/* @meichthys
|
||||||
|
homeassistant/components/nfandroidtv/* @tkdrob
|
||||||
homeassistant/components/nightscout/* @marciogranzotto
|
homeassistant/components/nightscout/* @marciogranzotto
|
||||||
homeassistant/components/nilu/* @hfurubotten
|
homeassistant/components/nilu/* @hfurubotten
|
||||||
homeassistant/components/nissan_leaf/* @filcole
|
homeassistant/components/nissan_leaf/* @filcole
|
||||||
@ -384,6 +389,7 @@ homeassistant/components/powerwall/* @bdraco @jrester
|
|||||||
homeassistant/components/profiler/* @bdraco
|
homeassistant/components/profiler/* @bdraco
|
||||||
homeassistant/components/progettihwsw/* @ardaseremet
|
homeassistant/components/progettihwsw/* @ardaseremet
|
||||||
homeassistant/components/prometheus/* @knyar
|
homeassistant/components/prometheus/* @knyar
|
||||||
|
homeassistant/components/prosegur/* @dgomes
|
||||||
homeassistant/components/proxmoxve/* @k4ds3 @jhollowe @Corbeno
|
homeassistant/components/proxmoxve/* @k4ds3 @jhollowe @Corbeno
|
||||||
homeassistant/components/ps4/* @ktnrg45
|
homeassistant/components/ps4/* @ktnrg45
|
||||||
homeassistant/components/push/* @dgomes
|
homeassistant/components/push/* @dgomes
|
||||||
@ -404,6 +410,7 @@ homeassistant/components/rainmachine/* @bachya
|
|||||||
homeassistant/components/random/* @fabaff
|
homeassistant/components/random/* @fabaff
|
||||||
homeassistant/components/recollect_waste/* @bachya
|
homeassistant/components/recollect_waste/* @bachya
|
||||||
homeassistant/components/rejseplanen/* @DarkFox
|
homeassistant/components/rejseplanen/* @DarkFox
|
||||||
|
homeassistant/components/renault/* @epenet
|
||||||
homeassistant/components/repetier/* @MTrab
|
homeassistant/components/repetier/* @MTrab
|
||||||
homeassistant/components/rflink/* @javicalle
|
homeassistant/components/rflink/* @javicalle
|
||||||
homeassistant/components/rfxtrx/* @danielhiversen @elupus @RobBie1221
|
homeassistant/components/rfxtrx/* @danielhiversen @elupus @RobBie1221
|
||||||
@ -442,6 +449,7 @@ homeassistant/components/sighthound/* @robmarkcole
|
|||||||
homeassistant/components/signal_messenger/* @bbernhard
|
homeassistant/components/signal_messenger/* @bbernhard
|
||||||
homeassistant/components/simplisafe/* @bachya
|
homeassistant/components/simplisafe/* @bachya
|
||||||
homeassistant/components/sinch/* @bendikrb
|
homeassistant/components/sinch/* @bendikrb
|
||||||
|
homeassistant/components/siren/* @home-assistant/core @raman325
|
||||||
homeassistant/components/sisyphus/* @jkeljo
|
homeassistant/components/sisyphus/* @jkeljo
|
||||||
homeassistant/components/sky_hub/* @rogerselwyn
|
homeassistant/components/sky_hub/* @rogerselwyn
|
||||||
homeassistant/components/slack/* @bachya
|
homeassistant/components/slack/* @bachya
|
||||||
@ -502,7 +510,7 @@ homeassistant/components/tapsaff/* @bazwilliams
|
|||||||
homeassistant/components/tasmota/* @emontnemery
|
homeassistant/components/tasmota/* @emontnemery
|
||||||
homeassistant/components/tautulli/* @ludeeus
|
homeassistant/components/tautulli/* @ludeeus
|
||||||
homeassistant/components/tellduslive/* @fredrike
|
homeassistant/components/tellduslive/* @fredrike
|
||||||
homeassistant/components/template/* @PhracturedBlue @tetienne
|
homeassistant/components/template/* @PhracturedBlue @tetienne @home-assistant/core
|
||||||
homeassistant/components/tesla/* @zabuldon @alandtse
|
homeassistant/components/tesla/* @zabuldon @alandtse
|
||||||
homeassistant/components/tfiac/* @fredrike @mellado
|
homeassistant/components/tfiac/* @fredrike @mellado
|
||||||
homeassistant/components/thethingsnetwork/* @fabaff
|
homeassistant/components/thethingsnetwork/* @fabaff
|
||||||
@ -554,11 +562,12 @@ homeassistant/components/wallbox/* @hesselonline
|
|||||||
homeassistant/components/waqi/* @andrey-git
|
homeassistant/components/waqi/* @andrey-git
|
||||||
homeassistant/components/watson_tts/* @rutkai
|
homeassistant/components/watson_tts/* @rutkai
|
||||||
homeassistant/components/weather/* @fabaff
|
homeassistant/components/weather/* @fabaff
|
||||||
homeassistant/components/webostv/* @bendavid
|
homeassistant/components/webostv/* @bendavid @thecode
|
||||||
homeassistant/components/websocket_api/* @home-assistant/core
|
homeassistant/components/websocket_api/* @home-assistant/core
|
||||||
homeassistant/components/wemo/* @esev
|
homeassistant/components/wemo/* @esev
|
||||||
homeassistant/components/wiffi/* @mampfes
|
homeassistant/components/wiffi/* @mampfes
|
||||||
homeassistant/components/wilight/* @leofig-rj
|
homeassistant/components/wilight/* @leofig-rj
|
||||||
|
homeassistant/components/wirelesstag/* @sergeymaysak
|
||||||
homeassistant/components/withings/* @vangorra
|
homeassistant/components/withings/* @vangorra
|
||||||
homeassistant/components/wled/* @frenck
|
homeassistant/components/wled/* @frenck
|
||||||
homeassistant/components/wolflink/* @adamkrol93
|
homeassistant/components/wolflink/* @adamkrol93
|
||||||
@ -576,6 +585,7 @@ homeassistant/components/yandex_transport/* @rishatik92 @devbis
|
|||||||
homeassistant/components/yeelight/* @rytilahti @zewelor @shenxn
|
homeassistant/components/yeelight/* @rytilahti @zewelor @shenxn
|
||||||
homeassistant/components/yeelightsunflower/* @lindsaymarkward
|
homeassistant/components/yeelightsunflower/* @lindsaymarkward
|
||||||
homeassistant/components/yi/* @bachya
|
homeassistant/components/yi/* @bachya
|
||||||
|
homeassistant/components/youless/* @gjong
|
||||||
homeassistant/components/zeroconf/* @bdraco
|
homeassistant/components/zeroconf/* @bdraco
|
||||||
homeassistant/components/zerproc/* @emlove
|
homeassistant/components/zerproc/* @emlove
|
||||||
homeassistant/components/zha/* @dmulcahey @adminiuga
|
homeassistant/components/zha/* @dmulcahey @adminiuga
|
||||||
|
10
build.json
10
build.json
@ -2,11 +2,11 @@
|
|||||||
"image": "homeassistant/{arch}-homeassistant",
|
"image": "homeassistant/{arch}-homeassistant",
|
||||||
"shadow_repository": "ghcr.io/home-assistant",
|
"shadow_repository": "ghcr.io/home-assistant",
|
||||||
"build_from": {
|
"build_from": {
|
||||||
"aarch64": "ghcr.io/home-assistant/aarch64-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.06.2",
|
"armhf": "ghcr.io/home-assistant/armhf-homeassistant-base:2021.07.0",
|
||||||
"armv7": "ghcr.io/home-assistant/armv7-homeassistant-base:2021.06.2",
|
"armv7": "ghcr.io/home-assistant/armv7-homeassistant-base:2021.07.0",
|
||||||
"amd64": "ghcr.io/home-assistant/amd64-homeassistant-base:2021.06.2",
|
"amd64": "ghcr.io/home-assistant/amd64-homeassistant-base:2021.07.0",
|
||||||
"i386": "ghcr.io/home-assistant/i386-homeassistant-base:2021.06.2"
|
"i386": "ghcr.io/home-assistant/i386-homeassistant-base:2021.07.0"
|
||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
"io.hass.type": "core",
|
"io.hass.type": "core",
|
||||||
|
@ -146,8 +146,8 @@ def daemonize() -> None:
|
|||||||
|
|
||||||
# redirect standard file descriptors to devnull
|
# redirect standard file descriptors to devnull
|
||||||
# pylint: disable=consider-using-with
|
# pylint: disable=consider-using-with
|
||||||
infd = open(os.devnull)
|
infd = open(os.devnull, encoding="utf8")
|
||||||
outfd = open(os.devnull, "a+")
|
outfd = open(os.devnull, "a+", encoding="utf8")
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
sys.stderr.flush()
|
sys.stderr.flush()
|
||||||
os.dup2(infd.fileno(), sys.stdin.fileno())
|
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 that Home Assistant is not already running."""
|
||||||
# Check pid file
|
# Check pid file
|
||||||
try:
|
try:
|
||||||
with open(pid_file) as file:
|
with open(pid_file, encoding="utf8") as file:
|
||||||
pid = int(file.readline())
|
pid = int(file.readline())
|
||||||
except OSError:
|
except OSError:
|
||||||
# PID File does not exist
|
# PID File does not exist
|
||||||
@ -182,7 +182,7 @@ def write_pid(pid_file: str) -> None:
|
|||||||
"""Create a PID File."""
|
"""Create a PID File."""
|
||||||
pid = os.getpid()
|
pid = os.getpid()
|
||||||
try:
|
try:
|
||||||
with open(pid_file, "w") as file:
|
with open(pid_file, "w", encoding="utf8") as file:
|
||||||
file.write(str(pid))
|
file.write(str(pid))
|
||||||
except OSError:
|
except OSError:
|
||||||
print(f"Fatal Error: Unable to write pid file {pid_file}")
|
print(f"Fatal Error: Unable to write pid file {pid_file}")
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
"""Auth provider that validates credentials via an external command."""
|
"""Auth provider that validates credentials via an external command."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio.subprocess
|
import asyncio
|
||||||
import collections
|
import collections
|
||||||
from collections.abc import Mapping
|
from collections.abc import Mapping
|
||||||
import logging
|
import logging
|
||||||
@ -64,7 +64,7 @@ class CommandLineAuthProvider(AuthProvider):
|
|||||||
"""Validate a username and password."""
|
"""Validate a username and password."""
|
||||||
env = {"username": username, "password": password}
|
env = {"username": username, "password": password}
|
||||||
try:
|
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_COMMAND],
|
||||||
*self.config[CONF_ARGS],
|
*self.config[CONF_ARGS],
|
||||||
env=env,
|
env=env,
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
"""Support for the Abode Security System."""
|
"""Support for the Abode Security System."""
|
||||||
from copy import deepcopy
|
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
|
||||||
from abodepy import Abode
|
from abodepy import Abode
|
||||||
@ -8,7 +7,6 @@ import abodepy.helpers.timeline as TIMELINE
|
|||||||
from requests.exceptions import ConnectTimeout, HTTPError
|
from requests.exceptions import ConnectTimeout, HTTPError
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.config_entries import SOURCE_IMPORT
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_ATTRIBUTION,
|
ATTR_ATTRIBUTION,
|
||||||
ATTR_DATE,
|
ATTR_DATE,
|
||||||
@ -44,22 +42,7 @@ ATTR_APP_TYPE = "app_type"
|
|||||||
ATTR_EVENT_BY = "event_by"
|
ATTR_EVENT_BY = "event_by"
|
||||||
ATTR_VALUE = "value"
|
ATTR_VALUE = "value"
|
||||||
|
|
||||||
CONFIG_SCHEMA = vol.Schema(
|
CONFIG_SCHEMA = cv.deprecated(DOMAIN)
|
||||||
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,
|
|
||||||
)
|
|
||||||
|
|
||||||
CHANGE_SETTING_SCHEMA = vol.Schema(
|
CHANGE_SETTING_SCHEMA = vol.Schema(
|
||||||
{vol.Required(ATTR_SETTING): cv.string, vol.Required(ATTR_VALUE): cv.string}
|
{vol.Required(ATTR_SETTING): cv.string, vol.Required(ATTR_VALUE): cv.string}
|
||||||
@ -92,22 +75,6 @@ class AbodeSystem:
|
|||||||
self.logout_listener = None
|
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):
|
async def async_setup_entry(hass, config_entry):
|
||||||
"""Set up Abode integration from a config entry."""
|
"""Set up Abode integration from a config entry."""
|
||||||
username = config_entry.data.get(CONF_USERNAME)
|
username = config_entry.data.get(CONF_USERNAME)
|
||||||
@ -284,17 +251,13 @@ class AbodeEntity(Entity):
|
|||||||
"""Initialize Abode entity."""
|
"""Initialize Abode entity."""
|
||||||
self._data = data
|
self._data = data
|
||||||
self._available = True
|
self._available = True
|
||||||
|
self._attr_should_poll = data.polling
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def available(self):
|
def available(self):
|
||||||
"""Return the available state."""
|
"""Return the available state."""
|
||||||
return self._available
|
return self._available
|
||||||
|
|
||||||
@property
|
|
||||||
def should_poll(self):
|
|
||||||
"""Return the polling state."""
|
|
||||||
return self._data.polling
|
|
||||||
|
|
||||||
async def async_added_to_hass(self):
|
async def async_added_to_hass(self):
|
||||||
"""Subscribe to Abode connection status updates."""
|
"""Subscribe to Abode connection status updates."""
|
||||||
await self.hass.async_add_executor_job(
|
await self.hass.async_add_executor_job(
|
||||||
@ -324,6 +287,8 @@ class AbodeDevice(AbodeEntity):
|
|||||||
"""Initialize Abode device."""
|
"""Initialize Abode device."""
|
||||||
super().__init__(data)
|
super().__init__(data)
|
||||||
self._device = device
|
self._device = device
|
||||||
|
self._attr_name = device.name
|
||||||
|
self._attr_unique_id = device.device_uuid
|
||||||
|
|
||||||
async def async_added_to_hass(self):
|
async def async_added_to_hass(self):
|
||||||
"""Subscribe to device events."""
|
"""Subscribe to device events."""
|
||||||
@ -345,11 +310,6 @@ class AbodeDevice(AbodeEntity):
|
|||||||
"""Update device state."""
|
"""Update device state."""
|
||||||
self._device.refresh()
|
self._device.refresh()
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self):
|
|
||||||
"""Return the name of the device."""
|
|
||||||
return self._device.name
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def extra_state_attributes(self):
|
def extra_state_attributes(self):
|
||||||
"""Return the state attributes."""
|
"""Return the state attributes."""
|
||||||
@ -361,11 +321,6 @@ class AbodeDevice(AbodeEntity):
|
|||||||
"device_type": self._device.type,
|
"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
|
@property
|
||||||
def device_info(self):
|
def device_info(self):
|
||||||
"""Return device registry information for this entity."""
|
"""Return device registry information for this entity."""
|
||||||
@ -388,22 +343,13 @@ class AbodeAutomation(AbodeEntity):
|
|||||||
"""Initialize for Abode automation."""
|
"""Initialize for Abode automation."""
|
||||||
super().__init__(data)
|
super().__init__(data)
|
||||||
self._automation = automation
|
self._automation = automation
|
||||||
|
self._attr_name = automation.name
|
||||||
|
self._attr_unique_id = automation.automation_id
|
||||||
|
self._attr_extra_state_attributes = {
|
||||||
|
ATTR_ATTRIBUTION: ATTRIBUTION,
|
||||||
|
"type": "CUE automation",
|
||||||
|
}
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
"""Update automation state."""
|
"""Update automation state."""
|
||||||
self._automation.refresh()
|
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):
|
class AbodeAlarm(AbodeDevice, alarm.AlarmControlPanelEntity):
|
||||||
"""An alarm_control_panel implementation for Abode."""
|
"""An alarm_control_panel implementation for Abode."""
|
||||||
|
|
||||||
@property
|
_attr_icon = ICON
|
||||||
def icon(self):
|
_attr_code_arm_required = False
|
||||||
"""Return the icon."""
|
_attr_supported_features = SUPPORT_ALARM_ARM_HOME | SUPPORT_ALARM_ARM_AWAY
|
||||||
return ICON
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state(self):
|
def state(self):
|
||||||
@ -46,16 +45,6 @@ class AbodeAlarm(AbodeDevice, alarm.AlarmControlPanelEntity):
|
|||||||
state = None
|
state = None
|
||||||
return state
|
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):
|
def alarm_disarm(self, code=None):
|
||||||
"""Send disarm command."""
|
"""Send disarm command."""
|
||||||
self._device.set_standby()
|
self._device.set_standby()
|
||||||
|
@ -158,13 +158,3 @@ class AbodeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
self._password = user_input[CONF_PASSWORD]
|
self._password = user_input[CONF_PASSWORD]
|
||||||
|
|
||||||
return await self._async_abode_login(step_id="reauth_confirm")
|
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."""
|
"""Initialize a sensor for an Abode device."""
|
||||||
super().__init__(data, device)
|
super().__init__(data, device)
|
||||||
self._sensor_type = sensor_type
|
self._sensor_type = sensor_type
|
||||||
self._name = f"{self._device.name} {SENSOR_TYPES[self._sensor_type][0]}"
|
self._attr_name = f"{device.name} {SENSOR_TYPES[sensor_type][0]}"
|
||||||
self._device_class = SENSOR_TYPES[self._sensor_type][1]
|
self._attr_device_class = SENSOR_TYPES[self._sensor_type][1]
|
||||||
|
self._attr_unique_id = f"{device.device_uuid}-{sensor_type}"
|
||||||
@property
|
if self._sensor_type == CONST.TEMP_STATUS_KEY:
|
||||||
def name(self):
|
self._attr_unit_of_measurement = device.temp_unit
|
||||||
"""Return the name of the sensor."""
|
elif self._sensor_type == CONST.HUMI_STATUS_KEY:
|
||||||
return self._name
|
self._attr_unit_of_measurement = device.humidity_unit
|
||||||
|
elif self._sensor_type == CONST.LUX_STATUS_KEY:
|
||||||
@property
|
self._attr_unit_of_measurement = device.lux_unit
|
||||||
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}"
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state(self):
|
def state(self):
|
||||||
@ -68,13 +60,3 @@ class AbodeSensor(AbodeDevice, SensorEntity):
|
|||||||
return self._device.humidity
|
return self._device.humidity
|
||||||
if self._sensor_type == CONST.LUX_STATUS_KEY:
|
if self._sensor_type == CONST.LUX_STATUS_KEY:
|
||||||
return self._device.lux
|
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):
|
class AbodeAutomationSwitch(AbodeAutomation, SwitchEntity):
|
||||||
"""A switch implementation for Abode automations."""
|
"""A switch implementation for Abode automations."""
|
||||||
|
|
||||||
|
_attr_icon = ICON
|
||||||
|
|
||||||
async def async_added_to_hass(self):
|
async def async_added_to_hass(self):
|
||||||
"""Set up trigger automation service."""
|
"""Set up trigger automation service."""
|
||||||
await super().async_added_to_hass()
|
await super().async_added_to_hass()
|
||||||
@ -73,8 +75,3 @@ class AbodeAutomationSwitch(AbodeAutomation, SwitchEntity):
|
|||||||
def is_on(self):
|
def is_on(self):
|
||||||
"""Return True if the automation is enabled."""
|
"""Return True if the automation is enabled."""
|
||||||
return self._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": {
|
"user": {
|
||||||
"data": {
|
"data": {
|
||||||
"password": "Passwort",
|
"password": "Passwort",
|
||||||
"username": "E-Mail-Adresse"
|
"username": "E-Mail"
|
||||||
},
|
},
|
||||||
"title": "Gib deine Abode-Anmeldeinformationen ein"
|
"title": "Gib deine Abode-Anmeldeinformationen ein"
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
"abort": {
|
"abort": {
|
||||||
|
"reauth_successful": "La reautenticaci\u00f3n fue exitosa",
|
||||||
"single_instance_allowed": "Solo se permite una \u00fanica configuraci\u00f3n de Abode."
|
"single_instance_allowed": "Solo se permite una \u00fanica configuraci\u00f3n de Abode."
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
|
"cannot_connect": "No se pudo conectar",
|
||||||
|
"invalid_auth": "Autenticaci\u00f3n inv\u00e1lida",
|
||||||
"invalid_mfa_code": "C\u00f3digo MFA no v\u00e1lido"
|
"invalid_mfa_code": "C\u00f3digo MFA no v\u00e1lido"
|
||||||
},
|
},
|
||||||
"step": {
|
"step": {
|
||||||
@ -15,7 +18,8 @@
|
|||||||
},
|
},
|
||||||
"reauth_confirm": {
|
"reauth_confirm": {
|
||||||
"data": {
|
"data": {
|
||||||
"password": "Contrase\u00f1a"
|
"password": "Contrase\u00f1a",
|
||||||
|
"username": "Correo electr\u00f3nico"
|
||||||
},
|
},
|
||||||
"title": "Complete su informaci\u00f3n de inicio de sesi\u00f3n de Abode"
|
"title": "Complete su informaci\u00f3n de inicio de sesi\u00f3n de Abode"
|
||||||
},
|
},
|
||||||
|
@ -20,7 +20,8 @@
|
|||||||
"data": {
|
"data": {
|
||||||
"password": "Jelsz\u00f3",
|
"password": "Jelsz\u00f3",
|
||||||
"username": "E-mail"
|
"username": "E-mail"
|
||||||
}
|
},
|
||||||
|
"title": "T\u00f6ltse ki az Abode bejelentkez\u00e9si adatait"
|
||||||
},
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"data": {
|
"data": {
|
||||||
|
@ -3,7 +3,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from typing import Final
|
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 (
|
from homeassistant.components.weather import (
|
||||||
ATTR_CONDITION_CLEAR_NIGHT,
|
ATTR_CONDITION_CLEAR_NIGHT,
|
||||||
ATTR_CONDITION_CLOUDY,
|
ATTR_CONDITION_CLOUDY,
|
||||||
@ -21,8 +21,6 @@ from homeassistant.components.weather import (
|
|||||||
ATTR_CONDITION_WINDY,
|
ATTR_CONDITION_WINDY,
|
||||||
)
|
)
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_DEVICE_CLASS,
|
|
||||||
ATTR_ICON,
|
|
||||||
CONCENTRATION_PARTS_PER_CUBIC_METER,
|
CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||||
DEVICE_CLASS_TEMPERATURE,
|
DEVICE_CLASS_TEMPERATURE,
|
||||||
LENGTH_FEET,
|
LENGTH_FEET,
|
||||||
@ -38,16 +36,12 @@ from homeassistant.const import (
|
|||||||
UV_INDEX,
|
UV_INDEX,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .model import SensorDescription
|
from .model import AccuWeatherSensorDescription
|
||||||
|
|
||||||
API_IMPERIAL: Final = "Imperial"
|
API_IMPERIAL: Final = "Imperial"
|
||||||
API_METRIC: Final = "Metric"
|
API_METRIC: Final = "Metric"
|
||||||
ATTRIBUTION: Final = "Data provided by AccuWeather"
|
ATTRIBUTION: Final = "Data provided by AccuWeather"
|
||||||
ATTR_ENABLED: Final = "enabled"
|
|
||||||
ATTR_FORECAST: Final = "forecast"
|
ATTR_FORECAST: Final = "forecast"
|
||||||
ATTR_LABEL: Final = "label"
|
|
||||||
ATTR_UNIT_IMPERIAL: Final = "unit_imperial"
|
|
||||||
ATTR_UNIT_METRIC: Final = "unit_metric"
|
|
||||||
CONF_FORECAST: Final = "forecast"
|
CONF_FORECAST: Final = "forecast"
|
||||||
DOMAIN: Final = "accuweather"
|
DOMAIN: Final = "accuweather"
|
||||||
MANUFACTURER: Final = "AccuWeather, Inc."
|
MANUFACTURER: Final = "AccuWeather, Inc."
|
||||||
@ -71,276 +65,263 @@ CONDITION_CLASSES: Final[dict[str, list[int]]] = {
|
|||||||
ATTR_CONDITION_WINDY: [32],
|
ATTR_CONDITION_WINDY: [32],
|
||||||
}
|
}
|
||||||
|
|
||||||
FORECAST_SENSOR_TYPES: Final[dict[str, SensorDescription]] = {
|
FORECAST_SENSOR_TYPES: Final[tuple[AccuWeatherSensorDescription, ...]] = (
|
||||||
"CloudCoverDay": {
|
AccuWeatherSensorDescription(
|
||||||
ATTR_DEVICE_CLASS: None,
|
key="CloudCoverDay",
|
||||||
ATTR_ICON: "mdi:weather-cloudy",
|
icon="mdi:weather-cloudy",
|
||||||
ATTR_LABEL: "Cloud Cover Day",
|
name="Cloud Cover Day",
|
||||||
ATTR_UNIT_METRIC: PERCENTAGE,
|
unit_metric=PERCENTAGE,
|
||||||
ATTR_UNIT_IMPERIAL: PERCENTAGE,
|
unit_imperial=PERCENTAGE,
|
||||||
ATTR_ENABLED: False,
|
entity_registry_enabled_default=False,
|
||||||
},
|
),
|
||||||
"CloudCoverNight": {
|
AccuWeatherSensorDescription(
|
||||||
ATTR_DEVICE_CLASS: None,
|
key="CloudCoverNight",
|
||||||
ATTR_ICON: "mdi:weather-cloudy",
|
icon="mdi:weather-cloudy",
|
||||||
ATTR_LABEL: "Cloud Cover Night",
|
name="Cloud Cover Night",
|
||||||
ATTR_UNIT_METRIC: PERCENTAGE,
|
unit_metric=PERCENTAGE,
|
||||||
ATTR_UNIT_IMPERIAL: PERCENTAGE,
|
unit_imperial=PERCENTAGE,
|
||||||
ATTR_ENABLED: False,
|
entity_registry_enabled_default=False,
|
||||||
},
|
),
|
||||||
"Grass": {
|
AccuWeatherSensorDescription(
|
||||||
ATTR_DEVICE_CLASS: None,
|
key="Grass",
|
||||||
ATTR_ICON: "mdi:grass",
|
icon="mdi:grass",
|
||||||
ATTR_LABEL: "Grass Pollen",
|
name="Grass Pollen",
|
||||||
ATTR_UNIT_METRIC: CONCENTRATION_PARTS_PER_CUBIC_METER,
|
unit_metric=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||||
ATTR_UNIT_IMPERIAL: CONCENTRATION_PARTS_PER_CUBIC_METER,
|
unit_imperial=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||||
ATTR_ENABLED: False,
|
entity_registry_enabled_default=False,
|
||||||
},
|
),
|
||||||
"HoursOfSun": {
|
AccuWeatherSensorDescription(
|
||||||
ATTR_DEVICE_CLASS: None,
|
key="HoursOfSun",
|
||||||
ATTR_ICON: "mdi:weather-partly-cloudy",
|
icon="mdi:weather-partly-cloudy",
|
||||||
ATTR_LABEL: "Hours Of Sun",
|
name="Hours Of Sun",
|
||||||
ATTR_UNIT_METRIC: TIME_HOURS,
|
unit_metric=TIME_HOURS,
|
||||||
ATTR_UNIT_IMPERIAL: TIME_HOURS,
|
unit_imperial=TIME_HOURS,
|
||||||
ATTR_ENABLED: True,
|
),
|
||||||
},
|
AccuWeatherSensorDescription(
|
||||||
"Mold": {
|
key="Mold",
|
||||||
ATTR_DEVICE_CLASS: None,
|
icon="mdi:blur",
|
||||||
ATTR_ICON: "mdi:blur",
|
name="Mold Pollen",
|
||||||
ATTR_LABEL: "Mold Pollen",
|
unit_metric=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||||
ATTR_UNIT_METRIC: CONCENTRATION_PARTS_PER_CUBIC_METER,
|
unit_imperial=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||||
ATTR_UNIT_IMPERIAL: CONCENTRATION_PARTS_PER_CUBIC_METER,
|
entity_registry_enabled_default=False,
|
||||||
ATTR_ENABLED: False,
|
),
|
||||||
},
|
AccuWeatherSensorDescription(
|
||||||
"Ozone": {
|
key="Ozone",
|
||||||
ATTR_DEVICE_CLASS: None,
|
icon="mdi:vector-triangle",
|
||||||
ATTR_ICON: "mdi:vector-triangle",
|
name="Ozone",
|
||||||
ATTR_LABEL: "Ozone",
|
unit_metric=None,
|
||||||
ATTR_UNIT_METRIC: None,
|
unit_imperial=None,
|
||||||
ATTR_UNIT_IMPERIAL: None,
|
entity_registry_enabled_default=False,
|
||||||
ATTR_ENABLED: False,
|
),
|
||||||
},
|
AccuWeatherSensorDescription(
|
||||||
"Ragweed": {
|
key="Ragweed",
|
||||||
ATTR_DEVICE_CLASS: None,
|
icon="mdi:sprout",
|
||||||
ATTR_ICON: "mdi:sprout",
|
name="Ragweed Pollen",
|
||||||
ATTR_LABEL: "Ragweed Pollen",
|
unit_metric=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||||
ATTR_UNIT_METRIC: CONCENTRATION_PARTS_PER_CUBIC_METER,
|
unit_imperial=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||||
ATTR_UNIT_IMPERIAL: CONCENTRATION_PARTS_PER_CUBIC_METER,
|
entity_registry_enabled_default=False,
|
||||||
ATTR_ENABLED: False,
|
),
|
||||||
},
|
AccuWeatherSensorDescription(
|
||||||
"RealFeelTemperatureMax": {
|
key="RealFeelTemperatureMax",
|
||||||
ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE,
|
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||||
ATTR_ICON: None,
|
name="RealFeel Temperature Max",
|
||||||
ATTR_LABEL: "RealFeel Temperature Max",
|
unit_metric=TEMP_CELSIUS,
|
||||||
ATTR_UNIT_METRIC: TEMP_CELSIUS,
|
unit_imperial=TEMP_FAHRENHEIT,
|
||||||
ATTR_UNIT_IMPERIAL: TEMP_FAHRENHEIT,
|
),
|
||||||
ATTR_ENABLED: True,
|
AccuWeatherSensorDescription(
|
||||||
},
|
key="RealFeelTemperatureMin",
|
||||||
"RealFeelTemperatureMin": {
|
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||||
ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE,
|
name="RealFeel Temperature Min",
|
||||||
ATTR_ICON: None,
|
unit_metric=TEMP_CELSIUS,
|
||||||
ATTR_LABEL: "RealFeel Temperature Min",
|
unit_imperial=TEMP_FAHRENHEIT,
|
||||||
ATTR_UNIT_METRIC: TEMP_CELSIUS,
|
),
|
||||||
ATTR_UNIT_IMPERIAL: TEMP_FAHRENHEIT,
|
AccuWeatherSensorDescription(
|
||||||
ATTR_ENABLED: True,
|
key="RealFeelTemperatureShadeMax",
|
||||||
},
|
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||||
"RealFeelTemperatureShadeMax": {
|
name="RealFeel Temperature Shade Max",
|
||||||
ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE,
|
unit_metric=TEMP_CELSIUS,
|
||||||
ATTR_ICON: None,
|
unit_imperial=TEMP_FAHRENHEIT,
|
||||||
ATTR_LABEL: "RealFeel Temperature Shade Max",
|
entity_registry_enabled_default=False,
|
||||||
ATTR_UNIT_METRIC: TEMP_CELSIUS,
|
),
|
||||||
ATTR_UNIT_IMPERIAL: TEMP_FAHRENHEIT,
|
AccuWeatherSensorDescription(
|
||||||
ATTR_ENABLED: False,
|
key="RealFeelTemperatureShadeMin",
|
||||||
},
|
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||||
"RealFeelTemperatureShadeMin": {
|
name="RealFeel Temperature Shade Min",
|
||||||
ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE,
|
unit_metric=TEMP_CELSIUS,
|
||||||
ATTR_ICON: None,
|
unit_imperial=TEMP_FAHRENHEIT,
|
||||||
ATTR_LABEL: "RealFeel Temperature Shade Min",
|
entity_registry_enabled_default=False,
|
||||||
ATTR_UNIT_METRIC: TEMP_CELSIUS,
|
),
|
||||||
ATTR_UNIT_IMPERIAL: TEMP_FAHRENHEIT,
|
AccuWeatherSensorDescription(
|
||||||
ATTR_ENABLED: False,
|
key="ThunderstormProbabilityDay",
|
||||||
},
|
icon="mdi:weather-lightning",
|
||||||
"ThunderstormProbabilityDay": {
|
name="Thunderstorm Probability Day",
|
||||||
ATTR_DEVICE_CLASS: None,
|
unit_metric=PERCENTAGE,
|
||||||
ATTR_ICON: "mdi:weather-lightning",
|
unit_imperial=PERCENTAGE,
|
||||||
ATTR_LABEL: "Thunderstorm Probability Day",
|
),
|
||||||
ATTR_UNIT_METRIC: PERCENTAGE,
|
AccuWeatherSensorDescription(
|
||||||
ATTR_UNIT_IMPERIAL: PERCENTAGE,
|
key="ThunderstormProbabilityNight",
|
||||||
ATTR_ENABLED: True,
|
icon="mdi:weather-lightning",
|
||||||
},
|
name="Thunderstorm Probability Night",
|
||||||
"ThunderstormProbabilityNight": {
|
unit_metric=PERCENTAGE,
|
||||||
ATTR_DEVICE_CLASS: None,
|
unit_imperial=PERCENTAGE,
|
||||||
ATTR_ICON: "mdi:weather-lightning",
|
),
|
||||||
ATTR_LABEL: "Thunderstorm Probability Night",
|
AccuWeatherSensorDescription(
|
||||||
ATTR_UNIT_METRIC: PERCENTAGE,
|
key="Tree",
|
||||||
ATTR_UNIT_IMPERIAL: PERCENTAGE,
|
icon="mdi:tree-outline",
|
||||||
ATTR_ENABLED: True,
|
name="Tree Pollen",
|
||||||
},
|
unit_metric=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||||
"Tree": {
|
unit_imperial=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||||
ATTR_DEVICE_CLASS: None,
|
entity_registry_enabled_default=False,
|
||||||
ATTR_ICON: "mdi:tree-outline",
|
),
|
||||||
ATTR_LABEL: "Tree Pollen",
|
AccuWeatherSensorDescription(
|
||||||
ATTR_UNIT_METRIC: CONCENTRATION_PARTS_PER_CUBIC_METER,
|
key="UVIndex",
|
||||||
ATTR_UNIT_IMPERIAL: CONCENTRATION_PARTS_PER_CUBIC_METER,
|
icon="mdi:weather-sunny",
|
||||||
ATTR_ENABLED: False,
|
name="UV Index",
|
||||||
},
|
unit_metric=UV_INDEX,
|
||||||
"UVIndex": {
|
unit_imperial=UV_INDEX,
|
||||||
ATTR_DEVICE_CLASS: None,
|
),
|
||||||
ATTR_ICON: "mdi:weather-sunny",
|
AccuWeatherSensorDescription(
|
||||||
ATTR_LABEL: "UV Index",
|
key="WindGustDay",
|
||||||
ATTR_UNIT_METRIC: UV_INDEX,
|
icon="mdi:weather-windy",
|
||||||
ATTR_UNIT_IMPERIAL: UV_INDEX,
|
name="Wind Gust Day",
|
||||||
ATTR_ENABLED: True,
|
unit_metric=SPEED_KILOMETERS_PER_HOUR,
|
||||||
},
|
unit_imperial=SPEED_MILES_PER_HOUR,
|
||||||
"WindGustDay": {
|
entity_registry_enabled_default=False,
|
||||||
ATTR_DEVICE_CLASS: None,
|
),
|
||||||
ATTR_ICON: "mdi:weather-windy",
|
AccuWeatherSensorDescription(
|
||||||
ATTR_LABEL: "Wind Gust Day",
|
key="WindGustNight",
|
||||||
ATTR_UNIT_METRIC: SPEED_KILOMETERS_PER_HOUR,
|
icon="mdi:weather-windy",
|
||||||
ATTR_UNIT_IMPERIAL: SPEED_MILES_PER_HOUR,
|
name="Wind Gust Night",
|
||||||
ATTR_ENABLED: False,
|
unit_metric=SPEED_KILOMETERS_PER_HOUR,
|
||||||
},
|
unit_imperial=SPEED_MILES_PER_HOUR,
|
||||||
"WindGustNight": {
|
entity_registry_enabled_default=False,
|
||||||
ATTR_DEVICE_CLASS: None,
|
),
|
||||||
ATTR_ICON: "mdi:weather-windy",
|
AccuWeatherSensorDescription(
|
||||||
ATTR_LABEL: "Wind Gust Night",
|
key="WindDay",
|
||||||
ATTR_UNIT_METRIC: SPEED_KILOMETERS_PER_HOUR,
|
icon="mdi:weather-windy",
|
||||||
ATTR_UNIT_IMPERIAL: SPEED_MILES_PER_HOUR,
|
name="Wind Day",
|
||||||
ATTR_ENABLED: False,
|
unit_metric=SPEED_KILOMETERS_PER_HOUR,
|
||||||
},
|
unit_imperial=SPEED_MILES_PER_HOUR,
|
||||||
"WindDay": {
|
),
|
||||||
ATTR_DEVICE_CLASS: None,
|
AccuWeatherSensorDescription(
|
||||||
ATTR_ICON: "mdi:weather-windy",
|
key="WindNight",
|
||||||
ATTR_LABEL: "Wind Day",
|
icon="mdi:weather-windy",
|
||||||
ATTR_UNIT_METRIC: SPEED_KILOMETERS_PER_HOUR,
|
name="Wind Night",
|
||||||
ATTR_UNIT_IMPERIAL: SPEED_MILES_PER_HOUR,
|
unit_metric=SPEED_KILOMETERS_PER_HOUR,
|
||||||
ATTR_ENABLED: True,
|
unit_imperial=SPEED_MILES_PER_HOUR,
|
||||||
},
|
),
|
||||||
"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,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
SENSOR_TYPES: Final[dict[str, SensorDescription]] = {
|
SENSOR_TYPES: Final[tuple[AccuWeatherSensorDescription, ...]] = (
|
||||||
"ApparentTemperature": {
|
AccuWeatherSensorDescription(
|
||||||
ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE,
|
key="ApparentTemperature",
|
||||||
ATTR_ICON: None,
|
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||||
ATTR_LABEL: "Apparent Temperature",
|
name="Apparent Temperature",
|
||||||
ATTR_UNIT_METRIC: TEMP_CELSIUS,
|
unit_metric=TEMP_CELSIUS,
|
||||||
ATTR_UNIT_IMPERIAL: TEMP_FAHRENHEIT,
|
unit_imperial=TEMP_FAHRENHEIT,
|
||||||
ATTR_ENABLED: False,
|
entity_registry_enabled_default=False,
|
||||||
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
},
|
),
|
||||||
"Ceiling": {
|
AccuWeatherSensorDescription(
|
||||||
ATTR_DEVICE_CLASS: None,
|
key="Ceiling",
|
||||||
ATTR_ICON: "mdi:weather-fog",
|
icon="mdi:weather-fog",
|
||||||
ATTR_LABEL: "Cloud Ceiling",
|
name="Cloud Ceiling",
|
||||||
ATTR_UNIT_METRIC: LENGTH_METERS,
|
unit_metric=LENGTH_METERS,
|
||||||
ATTR_UNIT_IMPERIAL: LENGTH_FEET,
|
unit_imperial=LENGTH_FEET,
|
||||||
ATTR_ENABLED: True,
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
),
|
||||||
},
|
AccuWeatherSensorDescription(
|
||||||
"CloudCover": {
|
key="CloudCover",
|
||||||
ATTR_DEVICE_CLASS: None,
|
icon="mdi:weather-cloudy",
|
||||||
ATTR_ICON: "mdi:weather-cloudy",
|
name="Cloud Cover",
|
||||||
ATTR_LABEL: "Cloud Cover",
|
unit_metric=PERCENTAGE,
|
||||||
ATTR_UNIT_METRIC: PERCENTAGE,
|
unit_imperial=PERCENTAGE,
|
||||||
ATTR_UNIT_IMPERIAL: PERCENTAGE,
|
entity_registry_enabled_default=False,
|
||||||
ATTR_ENABLED: False,
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
),
|
||||||
},
|
AccuWeatherSensorDescription(
|
||||||
"DewPoint": {
|
key="DewPoint",
|
||||||
ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE,
|
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||||
ATTR_ICON: None,
|
name="Dew Point",
|
||||||
ATTR_LABEL: "Dew Point",
|
unit_metric=TEMP_CELSIUS,
|
||||||
ATTR_UNIT_METRIC: TEMP_CELSIUS,
|
unit_imperial=TEMP_FAHRENHEIT,
|
||||||
ATTR_UNIT_IMPERIAL: TEMP_FAHRENHEIT,
|
entity_registry_enabled_default=False,
|
||||||
ATTR_ENABLED: False,
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
),
|
||||||
},
|
AccuWeatherSensorDescription(
|
||||||
"RealFeelTemperature": {
|
key="RealFeelTemperature",
|
||||||
ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE,
|
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||||
ATTR_ICON: None,
|
name="RealFeel Temperature",
|
||||||
ATTR_LABEL: "RealFeel Temperature",
|
unit_metric=TEMP_CELSIUS,
|
||||||
ATTR_UNIT_METRIC: TEMP_CELSIUS,
|
unit_imperial=TEMP_FAHRENHEIT,
|
||||||
ATTR_UNIT_IMPERIAL: TEMP_FAHRENHEIT,
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
ATTR_ENABLED: True,
|
),
|
||||||
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
AccuWeatherSensorDescription(
|
||||||
},
|
key="RealFeelTemperatureShade",
|
||||||
"RealFeelTemperatureShade": {
|
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||||
ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE,
|
name="RealFeel Temperature Shade",
|
||||||
ATTR_ICON: None,
|
unit_metric=TEMP_CELSIUS,
|
||||||
ATTR_LABEL: "RealFeel Temperature Shade",
|
unit_imperial=TEMP_FAHRENHEIT,
|
||||||
ATTR_UNIT_METRIC: TEMP_CELSIUS,
|
entity_registry_enabled_default=False,
|
||||||
ATTR_UNIT_IMPERIAL: TEMP_FAHRENHEIT,
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
ATTR_ENABLED: False,
|
),
|
||||||
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
AccuWeatherSensorDescription(
|
||||||
},
|
key="Precipitation",
|
||||||
"Precipitation": {
|
icon="mdi:weather-rainy",
|
||||||
ATTR_DEVICE_CLASS: None,
|
name="Precipitation",
|
||||||
ATTR_ICON: "mdi:weather-rainy",
|
unit_metric=LENGTH_MILLIMETERS,
|
||||||
ATTR_LABEL: "Precipitation",
|
unit_imperial=LENGTH_INCHES,
|
||||||
ATTR_UNIT_METRIC: LENGTH_MILLIMETERS,
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
ATTR_UNIT_IMPERIAL: LENGTH_INCHES,
|
),
|
||||||
ATTR_ENABLED: True,
|
AccuWeatherSensorDescription(
|
||||||
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
key="PressureTendency",
|
||||||
},
|
device_class="accuweather__pressure_tendency",
|
||||||
"PressureTendency": {
|
icon="mdi:gauge",
|
||||||
ATTR_DEVICE_CLASS: "accuweather__pressure_tendency",
|
name="Pressure Tendency",
|
||||||
ATTR_ICON: "mdi:gauge",
|
unit_metric=None,
|
||||||
ATTR_LABEL: "Pressure Tendency",
|
unit_imperial=None,
|
||||||
ATTR_UNIT_METRIC: None,
|
),
|
||||||
ATTR_UNIT_IMPERIAL: None,
|
AccuWeatherSensorDescription(
|
||||||
ATTR_ENABLED: True,
|
key="UVIndex",
|
||||||
},
|
icon="mdi:weather-sunny",
|
||||||
"UVIndex": {
|
name="UV Index",
|
||||||
ATTR_DEVICE_CLASS: None,
|
unit_metric=UV_INDEX,
|
||||||
ATTR_ICON: "mdi:weather-sunny",
|
unit_imperial=UV_INDEX,
|
||||||
ATTR_LABEL: "UV Index",
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
ATTR_UNIT_METRIC: UV_INDEX,
|
),
|
||||||
ATTR_UNIT_IMPERIAL: UV_INDEX,
|
AccuWeatherSensorDescription(
|
||||||
ATTR_ENABLED: True,
|
key="WetBulbTemperature",
|
||||||
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||||
},
|
name="Wet Bulb Temperature",
|
||||||
"WetBulbTemperature": {
|
unit_metric=TEMP_CELSIUS,
|
||||||
ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE,
|
unit_imperial=TEMP_FAHRENHEIT,
|
||||||
ATTR_ICON: None,
|
entity_registry_enabled_default=False,
|
||||||
ATTR_LABEL: "Wet Bulb Temperature",
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
ATTR_UNIT_METRIC: TEMP_CELSIUS,
|
),
|
||||||
ATTR_UNIT_IMPERIAL: TEMP_FAHRENHEIT,
|
AccuWeatherSensorDescription(
|
||||||
ATTR_ENABLED: False,
|
key="WindChillTemperature",
|
||||||
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||||
},
|
name="Wind Chill Temperature",
|
||||||
"WindChillTemperature": {
|
unit_metric=TEMP_CELSIUS,
|
||||||
ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE,
|
unit_imperial=TEMP_FAHRENHEIT,
|
||||||
ATTR_ICON: None,
|
entity_registry_enabled_default=False,
|
||||||
ATTR_LABEL: "Wind Chill Temperature",
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
ATTR_UNIT_METRIC: TEMP_CELSIUS,
|
),
|
||||||
ATTR_UNIT_IMPERIAL: TEMP_FAHRENHEIT,
|
AccuWeatherSensorDescription(
|
||||||
ATTR_ENABLED: False,
|
key="Wind",
|
||||||
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
icon="mdi:weather-windy",
|
||||||
},
|
name="Wind",
|
||||||
"Wind": {
|
unit_metric=SPEED_KILOMETERS_PER_HOUR,
|
||||||
ATTR_DEVICE_CLASS: None,
|
unit_imperial=SPEED_MILES_PER_HOUR,
|
||||||
ATTR_ICON: "mdi:weather-windy",
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
ATTR_LABEL: "Wind",
|
),
|
||||||
ATTR_UNIT_METRIC: SPEED_KILOMETERS_PER_HOUR,
|
AccuWeatherSensorDescription(
|
||||||
ATTR_UNIT_IMPERIAL: SPEED_MILES_PER_HOUR,
|
key="WindGust",
|
||||||
ATTR_ENABLED: True,
|
icon="mdi:weather-windy",
|
||||||
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
name="Wind Gust",
|
||||||
},
|
unit_metric=SPEED_KILOMETERS_PER_HOUR,
|
||||||
"WindGust": {
|
unit_imperial=SPEED_MILES_PER_HOUR,
|
||||||
ATTR_DEVICE_CLASS: None,
|
entity_registry_enabled_default=False,
|
||||||
ATTR_ICON: "mdi:weather-windy",
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
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,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
@ -1,16 +1,14 @@
|
|||||||
"""Type definitions for AccuWeather integration."""
|
"""Type definitions for AccuWeather integration."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import TypedDict
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from homeassistant.components.sensor import SensorEntityDescription
|
||||||
|
|
||||||
|
|
||||||
class SensorDescription(TypedDict, total=False):
|
@dataclass
|
||||||
"""Sensor description class."""
|
class AccuWeatherSensorDescription(SensorEntityDescription):
|
||||||
|
"""Class describing AccuWeather sensor entities."""
|
||||||
|
|
||||||
device_class: str | None
|
unit_metric: str | None = None
|
||||||
icon: str | None
|
unit_imperial: str | None = None
|
||||||
label: str
|
|
||||||
unit_metric: str | None
|
|
||||||
unit_imperial: str | None
|
|
||||||
enabled: bool
|
|
||||||
state_class: str | None
|
|
||||||
|
@ -3,17 +3,10 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from typing import Any, cast
|
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.config_entries import ConfigEntry
|
||||||
from homeassistant.const import (
|
from homeassistant.const import ATTR_ATTRIBUTION, CONF_NAME, DEVICE_CLASS_TEMPERATURE
|
||||||
ATTR_ATTRIBUTION,
|
|
||||||
ATTR_DEVICE_CLASS,
|
|
||||||
ATTR_ICON,
|
|
||||||
CONF_NAME,
|
|
||||||
DEVICE_CLASS_TEMPERATURE,
|
|
||||||
)
|
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers.entity import DeviceInfo
|
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.helpers.typing import StateType
|
from homeassistant.helpers.typing import StateType
|
||||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||||
@ -22,11 +15,7 @@ from . import AccuWeatherDataUpdateCoordinator
|
|||||||
from .const import (
|
from .const import (
|
||||||
API_IMPERIAL,
|
API_IMPERIAL,
|
||||||
API_METRIC,
|
API_METRIC,
|
||||||
ATTR_ENABLED,
|
|
||||||
ATTR_FORECAST,
|
ATTR_FORECAST,
|
||||||
ATTR_LABEL,
|
|
||||||
ATTR_UNIT_IMPERIAL,
|
|
||||||
ATTR_UNIT_METRIC,
|
|
||||||
ATTRIBUTION,
|
ATTRIBUTION,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
FORECAST_SENSOR_TYPES,
|
FORECAST_SENSOR_TYPES,
|
||||||
@ -35,6 +24,7 @@ from .const import (
|
|||||||
NAME,
|
NAME,
|
||||||
SENSOR_TYPES,
|
SENSOR_TYPES,
|
||||||
)
|
)
|
||||||
|
from .model import AccuWeatherSensorDescription
|
||||||
|
|
||||||
PARALLEL_UPDATES = 1
|
PARALLEL_UPDATES = 1
|
||||||
|
|
||||||
@ -48,17 +38,19 @@ async def async_setup_entry(
|
|||||||
coordinator: AccuWeatherDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
|
coordinator: AccuWeatherDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
|
||||||
|
|
||||||
sensors: list[AccuWeatherSensor] = []
|
sensors: list[AccuWeatherSensor] = []
|
||||||
for sensor in SENSOR_TYPES:
|
for description in SENSOR_TYPES:
|
||||||
sensors.append(AccuWeatherSensor(name, sensor, coordinator))
|
sensors.append(AccuWeatherSensor(name, coordinator, description))
|
||||||
|
|
||||||
if coordinator.forecast:
|
if coordinator.forecast:
|
||||||
for sensor in FORECAST_SENSOR_TYPES:
|
for description in FORECAST_SENSOR_TYPES:
|
||||||
for day in range(MAX_FORECAST_DAYS + 1):
|
for day in range(MAX_FORECAST_DAYS + 1):
|
||||||
# Some air quality/allergy sensors are only available for certain
|
# Some air quality/allergy sensors are only available for certain
|
||||||
# locations.
|
# locations.
|
||||||
if sensor in coordinator.data[ATTR_FORECAST][0]:
|
if description.key in coordinator.data[ATTR_FORECAST][0]:
|
||||||
sensors.append(
|
sensors.append(
|
||||||
AccuWeatherSensor(name, sensor, coordinator, forecast_day=day)
|
AccuWeatherSensor(
|
||||||
|
name, coordinator, description, forecast_day=day
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
async_add_entities(sensors)
|
async_add_entities(sensors)
|
||||||
@ -68,119 +60,107 @@ class AccuWeatherSensor(CoordinatorEntity, SensorEntity):
|
|||||||
"""Define an AccuWeather entity."""
|
"""Define an AccuWeather entity."""
|
||||||
|
|
||||||
coordinator: AccuWeatherDataUpdateCoordinator
|
coordinator: AccuWeatherDataUpdateCoordinator
|
||||||
|
entity_description: AccuWeatherSensorDescription
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
name: str,
|
name: str,
|
||||||
kind: str,
|
|
||||||
coordinator: AccuWeatherDataUpdateCoordinator,
|
coordinator: AccuWeatherDataUpdateCoordinator,
|
||||||
|
description: AccuWeatherSensorDescription,
|
||||||
forecast_day: int | None = None,
|
forecast_day: int | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize."""
|
"""Initialize."""
|
||||||
super().__init__(coordinator)
|
super().__init__(coordinator)
|
||||||
self._sensor_data = _get_sensor_data(coordinator.data, forecast_day, kind)
|
self.entity_description = description
|
||||||
if forecast_day is None:
|
self._sensor_data = _get_sensor_data(
|
||||||
self._description = SENSOR_TYPES[kind]
|
coordinator.data, forecast_day, description.key
|
||||||
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._attrs = {ATTR_ATTRIBUTION: ATTRIBUTION}
|
self._attrs = {ATTR_ATTRIBUTION: ATTRIBUTION}
|
||||||
self.forecast_day = forecast_day
|
if forecast_day is not None:
|
||||||
self._attr_state_class = self._description.get(ATTR_STATE_CLASS)
|
self._attr_name = f"{name} {description.name} {forecast_day}d"
|
||||||
|
self._attr_unique_id = (
|
||||||
@property
|
f"{coordinator.location_key}-{description.key}-{forecast_day}".lower()
|
||||||
def name(self) -> str:
|
)
|
||||||
"""Return the name."""
|
else:
|
||||||
if self.forecast_day is not None:
|
self._attr_name = f"{name} {description.name}"
|
||||||
return f"{self._name} {self._description[ATTR_LABEL]} {self.forecast_day}d"
|
self._attr_unique_id = (
|
||||||
return f"{self._name} {self._description[ATTR_LABEL]}"
|
f"{coordinator.location_key}-{description.key}".lower()
|
||||||
|
)
|
||||||
@property
|
if coordinator.is_metric:
|
||||||
def unique_id(self) -> str:
|
self._unit_system = API_METRIC
|
||||||
"""Return a unique_id for this entity."""
|
self._attr_unit_of_measurement = description.unit_metric
|
||||||
if self.forecast_day is not None:
|
else:
|
||||||
return f"{self.coordinator.location_key}-{self.kind}-{self.forecast_day}".lower()
|
self._unit_system = API_IMPERIAL
|
||||||
return f"{self.coordinator.location_key}-{self.kind}".lower()
|
self._attr_unit_of_measurement = description.unit_imperial
|
||||||
|
self._attr_device_info = {
|
||||||
@property
|
"identifiers": {(DOMAIN, coordinator.location_key)},
|
||||||
def device_info(self) -> DeviceInfo:
|
|
||||||
"""Return the device info."""
|
|
||||||
return {
|
|
||||||
"identifiers": {(DOMAIN, self.coordinator.location_key)},
|
|
||||||
"name": NAME,
|
"name": NAME,
|
||||||
"manufacturer": MANUFACTURER,
|
"manufacturer": MANUFACTURER,
|
||||||
"entry_type": "service",
|
"entry_type": "service",
|
||||||
}
|
}
|
||||||
|
self.forecast_day = forecast_day
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state(self) -> StateType:
|
def state(self) -> StateType:
|
||||||
"""Return the state."""
|
"""Return the state."""
|
||||||
if self.forecast_day is not None:
|
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"])
|
return cast(float, self._sensor_data["Value"])
|
||||||
if self.kind == "UVIndex":
|
if self.entity_description.key == "UVIndex":
|
||||||
return cast(int, self._sensor_data["Value"])
|
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"])
|
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"])
|
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())
|
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"])
|
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"])
|
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"])
|
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["Speed"]["Value"])
|
||||||
return cast(StateType, self._sensor_data)
|
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
|
@property
|
||||||
def extra_state_attributes(self) -> dict[str, Any]:
|
def extra_state_attributes(self) -> dict[str, Any]:
|
||||||
"""Return the state attributes."""
|
"""Return the state attributes."""
|
||||||
if self.forecast_day is not None:
|
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"]
|
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"]
|
self._attrs["level"] = self._sensor_data["Category"]
|
||||||
return self._attrs
|
return self._attrs
|
||||||
if self.kind == "UVIndex":
|
if self.entity_description.key == "UVIndex":
|
||||||
self._attrs["level"] = self.coordinator.data["UVIndexText"]
|
self._attrs["level"] = self.coordinator.data["UVIndexText"]
|
||||||
elif self.kind == "Precipitation":
|
elif self.entity_description.key == "Precipitation":
|
||||||
self._attrs["type"] = self.coordinator.data["PrecipitationType"]
|
self._attrs["type"] = self.coordinator.data["PrecipitationType"]
|
||||||
return self._attrs
|
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
|
@callback
|
||||||
def _handle_coordinator_update(self) -> None:
|
def _handle_coordinator_update(self) -> None:
|
||||||
"""Handle data update."""
|
"""Handle data update."""
|
||||||
self._sensor_data = _get_sensor_data(
|
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()
|
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": {
|
"error": {
|
||||||
"cannot_connect": "Verbindung fehlgeschlagen",
|
"cannot_connect": "Verbindung fehlgeschlagen",
|
||||||
"invalid_api_key": "Ung\u00fcltiger API-Schl\u00fcssel",
|
"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": {
|
"step": {
|
||||||
"user": {
|
"user": {
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"single_instance_allowed": "Ya configurado. Solo es posible una \u00fanica configuraci\u00f3n."
|
||||||
|
},
|
||||||
"error": {
|
"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."
|
"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": {
|
"step": {
|
||||||
|
@ -14,8 +14,22 @@
|
|||||||
"latitude": "\u05e7\u05d5 \u05e8\u05d5\u05d7\u05d1",
|
"latitude": "\u05e7\u05d5 \u05e8\u05d5\u05d7\u05d1",
|
||||||
"longitude": "\u05e7\u05d5 \u05d0\u05d5\u05e8\u05da",
|
"longitude": "\u05e7\u05d5 \u05d0\u05d5\u05e8\u05da",
|
||||||
"name": "\u05e9\u05dd"
|
"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"
|
"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.config_entries import ConfigEntry
|
||||||
from homeassistant.const import CONF_NAME, TEMP_CELSIUS, TEMP_FAHRENHEIT
|
from homeassistant.const import CONF_NAME, TEMP_CELSIUS, TEMP_FAHRENHEIT
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity import DeviceInfo
|
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||||
from homeassistant.util.dt import utc_from_timestamp
|
from homeassistant.util.dt import utc_from_timestamp
|
||||||
@ -60,29 +59,15 @@ class AccuWeatherEntity(CoordinatorEntity, WeatherEntity):
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize."""
|
"""Initialize."""
|
||||||
super().__init__(coordinator)
|
super().__init__(coordinator)
|
||||||
self._name = name
|
self._unit_system = API_METRIC if coordinator.is_metric else API_IMPERIAL
|
||||||
self._unit_system = API_METRIC if self.coordinator.is_metric else API_IMPERIAL
|
self._attr_name = name
|
||||||
|
self._attr_unique_id = coordinator.location_key
|
||||||
@property
|
self._attr_temperature_unit = (
|
||||||
def name(self) -> str:
|
TEMP_CELSIUS if coordinator.is_metric else TEMP_FAHRENHEIT
|
||||||
"""Return the name."""
|
)
|
||||||
return self._name
|
self._attr_attribution = ATTRIBUTION
|
||||||
|
self._attr_device_info = {
|
||||||
@property
|
"identifiers": {(DOMAIN, coordinator.location_key)},
|
||||||
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)},
|
|
||||||
"name": NAME,
|
"name": NAME,
|
||||||
"manufacturer": MANUFACTURER,
|
"manufacturer": MANUFACTURER,
|
||||||
"entry_type": "service",
|
"entry_type": "service",
|
||||||
@ -107,11 +92,6 @@ class AccuWeatherEntity(CoordinatorEntity, WeatherEntity):
|
|||||||
float, self.coordinator.data["Temperature"][self._unit_system]["Value"]
|
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
|
@property
|
||||||
def pressure(self) -> float:
|
def pressure(self) -> float:
|
||||||
"""Return the pressure."""
|
"""Return the pressure."""
|
||||||
|
@ -67,6 +67,8 @@ def setup_platform(
|
|||||||
class AcerSwitch(SwitchEntity):
|
class AcerSwitch(SwitchEntity):
|
||||||
"""Represents an Acer Projector as a switch."""
|
"""Represents an Acer Projector as a switch."""
|
||||||
|
|
||||||
|
_attr_icon = ICON
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
serial_port: str,
|
serial_port: str,
|
||||||
@ -79,9 +81,7 @@ class AcerSwitch(SwitchEntity):
|
|||||||
port=serial_port, timeout=timeout, write_timeout=write_timeout
|
port=serial_port, timeout=timeout, write_timeout=write_timeout
|
||||||
)
|
)
|
||||||
self._serial_port = serial_port
|
self._serial_port = serial_port
|
||||||
self._name = name
|
self._attr_name = name
|
||||||
self._state = False
|
|
||||||
self._available = False
|
|
||||||
self._attributes = {
|
self._attributes = {
|
||||||
LAMP_HOURS: STATE_UNKNOWN,
|
LAMP_HOURS: STATE_UNKNOWN,
|
||||||
INPUT_SOURCE: STATE_UNKNOWN,
|
INPUT_SOURCE: STATE_UNKNOWN,
|
||||||
@ -116,57 +116,33 @@ class AcerSwitch(SwitchEntity):
|
|||||||
return match.group(1)
|
return match.group(1)
|
||||||
return STATE_UNKNOWN
|
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:
|
def update(self) -> None:
|
||||||
"""Get the latest state from the projector."""
|
"""Get the latest state from the projector."""
|
||||||
awns = self._write_read_format(CMD_DICT[LAMP])
|
awns = self._write_read_format(CMD_DICT[LAMP])
|
||||||
if awns == "Lamp 1":
|
if awns == "Lamp 1":
|
||||||
self._state = True
|
self._attr_is_on = True
|
||||||
self._available = True
|
self._attr_available = True
|
||||||
elif awns == "Lamp 0":
|
elif awns == "Lamp 0":
|
||||||
self._state = False
|
self._attr_is_on = False
|
||||||
self._available = True
|
self._attr_available = True
|
||||||
else:
|
else:
|
||||||
self._available = False
|
self._attr_available = False
|
||||||
|
|
||||||
for key in self._attributes:
|
for key in self._attributes:
|
||||||
msg = CMD_DICT.get(key)
|
msg = CMD_DICT.get(key)
|
||||||
if msg:
|
if msg:
|
||||||
awns = self._write_read_format(msg)
|
awns = self._write_read_format(msg)
|
||||||
self._attributes[key] = awns
|
self._attributes[key] = awns
|
||||||
|
self._attr_extra_state_attributes = self._attributes
|
||||||
|
|
||||||
def turn_on(self, **kwargs: Any) -> None:
|
def turn_on(self, **kwargs: Any) -> None:
|
||||||
"""Turn the projector on."""
|
"""Turn the projector on."""
|
||||||
msg = CMD_DICT[STATE_ON]
|
msg = CMD_DICT[STATE_ON]
|
||||||
self._write_read(msg)
|
self._write_read(msg)
|
||||||
self._state = True
|
self._attr_is_on = True
|
||||||
|
|
||||||
def turn_off(self, **kwargs: Any) -> None:
|
def turn_off(self, **kwargs: Any) -> None:
|
||||||
"""Turn the projector off."""
|
"""Turn the projector off."""
|
||||||
msg = CMD_DICT[STATE_OFF]
|
msg = CMD_DICT[STATE_OFF]
|
||||||
self._write_read(msg)
|
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",
|
"host": "Host",
|
||||||
"password": "Passwort",
|
"password": "Passwort",
|
||||||
"port": "Port",
|
"port": "Port",
|
||||||
"ssl": "AdGuard Home verwendet ein SSL-Zertifikat",
|
"ssl": "Verwendet ein SSL-Zertifikat",
|
||||||
"username": "Benutzername",
|
"username": "Benutzername",
|
||||||
"verify_ssl": "SSL-Zertifikat \u00fcberpr\u00fcfen"
|
"verify_ssl": "SSL-Zertifikat \u00fcberpr\u00fcfen"
|
||||||
},
|
},
|
||||||
|
@ -1,9 +1,16 @@
|
|||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "A szolg\u00e1ltat\u00e1s m\u00e1r konfigur\u00e1lva van"
|
||||||
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"cannot_connect": "Sikertelen csatlakoz\u00e1s"
|
"cannot_connect": "Sikertelen csatlakoz\u00e1s"
|
||||||
},
|
},
|
||||||
"step": {
|
"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": {
|
"user": {
|
||||||
"data": {
|
"data": {
|
||||||
"host": "Hoszt",
|
"host": "Hoszt",
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
"abort": {
|
"abort": {
|
||||||
|
"already_configured": "Layanan sudah dikonfigurasi",
|
||||||
"existing_instance_updated": "Memperbarui konfigurasi yang ada."
|
"existing_instance_updated": "Memperbarui konfigurasi yang ada."
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
|
@ -268,15 +268,17 @@ class AdsHub:
|
|||||||
class AdsEntity(Entity):
|
class AdsEntity(Entity):
|
||||||
"""Representation of ADS entity."""
|
"""Representation of ADS entity."""
|
||||||
|
|
||||||
|
_attr_should_poll = False
|
||||||
|
|
||||||
def __init__(self, ads_hub, name, ads_var):
|
def __init__(self, ads_hub, name, ads_var):
|
||||||
"""Initialize ADS binary sensor."""
|
"""Initialize ADS binary sensor."""
|
||||||
self._name = name
|
|
||||||
self._unique_id = ads_var
|
|
||||||
self._state_dict = {}
|
self._state_dict = {}
|
||||||
self._state_dict[STATE_KEY_STATE] = None
|
self._state_dict[STATE_KEY_STATE] = None
|
||||||
self._ads_hub = ads_hub
|
self._ads_hub = ads_hub
|
||||||
self._ads_var = ads_var
|
self._ads_var = ads_var
|
||||||
self._event = None
|
self._event = None
|
||||||
|
self._attr_unique_id = ads_var
|
||||||
|
self._attr_name = name
|
||||||
|
|
||||||
async def async_initialize_device(
|
async def async_initialize_device(
|
||||||
self, ads_var, plctype, state_key=STATE_KEY_STATE, factor=None
|
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)
|
_LOGGER.debug("Variable %s: Timeout during first update", ads_var)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def available(self) -> bool:
|
||||||
"""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):
|
|
||||||
"""Return False if state has not been updated yet."""
|
"""Return False if state has not been updated yet."""
|
||||||
return self._state_dict[STATE_KEY_STATE] is not None
|
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):
|
def __init__(self, ads_hub, name, ads_var, device_class):
|
||||||
"""Initialize ADS binary sensor."""
|
"""Initialize ADS binary sensor."""
|
||||||
super().__init__(ads_hub, name, ads_var)
|
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):
|
async def async_added_to_hass(self):
|
||||||
"""Register device notification."""
|
"""Register device notification."""
|
||||||
await self.async_initialize_device(self._ads_var, self._ads_hub.PLCTYPE_BOOL)
|
await self.async_initialize_device(self._ads_var, self._ads_hub.PLCTYPE_BOOL)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self):
|
def is_on(self) -> bool:
|
||||||
"""Return True if the entity is on."""
|
"""Return True if the entity is on."""
|
||||||
return self._state_dict[STATE_KEY_STATE]
|
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_open = ads_var_open
|
||||||
self._ads_var_close = ads_var_close
|
self._ads_var_close = ads_var_close
|
||||||
self._ads_var_stop = ads_var_stop
|
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):
|
async def async_added_to_hass(self):
|
||||||
"""Register device notification."""
|
"""Register device notification."""
|
||||||
@ -119,11 +124,6 @@ class AdsCover(AdsEntity, CoverEntity):
|
|||||||
self._ads_var_position, self._ads_hub.PLCTYPE_BYTE, STATE_KEY_POSITION
|
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
|
@property
|
||||||
def is_closed(self):
|
def is_closed(self):
|
||||||
"""Return if the cover is closed."""
|
"""Return if the cover is closed."""
|
||||||
@ -138,19 +138,6 @@ class AdsCover(AdsEntity, CoverEntity):
|
|||||||
"""Return current position of cover."""
|
"""Return current position of cover."""
|
||||||
return self._state_dict[STATE_KEY_POSITION]
|
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):
|
def stop_cover(self, **kwargs):
|
||||||
"""Fire the stop action."""
|
"""Fire the stop action."""
|
||||||
if self._ads_var_stop:
|
if self._ads_var_stop:
|
||||||
@ -185,7 +172,7 @@ class AdsCover(AdsEntity, CoverEntity):
|
|||||||
self.set_cover_position(position=0)
|
self.set_cover_position(position=0)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def available(self):
|
def available(self) -> bool:
|
||||||
"""Return False if state has not been updated yet."""
|
"""Return False if state has not been updated yet."""
|
||||||
if self._ads_var is not None or self._ads_var_position is not None:
|
if self._ads_var is not None or self._ads_var_position is not None:
|
||||||
return (
|
return (
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
"""Support for ADS light sources."""
|
"""Support for ADS light sources."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components.light import (
|
from homeassistant.components.light import (
|
||||||
@ -48,6 +50,8 @@ class AdsLight(AdsEntity, LightEntity):
|
|||||||
super().__init__(ads_hub, name, ads_var_enable)
|
super().__init__(ads_hub, name, ads_var_enable)
|
||||||
self._state_dict[STATE_KEY_BRIGHTNESS] = None
|
self._state_dict[STATE_KEY_BRIGHTNESS] = None
|
||||||
self._ads_var_brightness = ads_var_brightness
|
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):
|
async def async_added_to_hass(self):
|
||||||
"""Register device notification."""
|
"""Register device notification."""
|
||||||
@ -61,19 +65,12 @@ class AdsLight(AdsEntity, LightEntity):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def brightness(self):
|
def brightness(self) -> int | None:
|
||||||
"""Return the brightness of the light (0..255)."""
|
"""Return the brightness of the light (0..255)."""
|
||||||
return self._state_dict[STATE_KEY_BRIGHTNESS]
|
return self._state_dict[STATE_KEY_BRIGHTNESS]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def supported_features(self):
|
def is_on(self) -> bool:
|
||||||
"""Flag supported features."""
|
|
||||||
if self._ads_var_brightness is not None:
|
|
||||||
return SUPPORT_BRIGHTNESS
|
|
||||||
return 0
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_on(self):
|
|
||||||
"""Return True if the entity is on."""
|
"""Return True if the entity is on."""
|
||||||
return self._state_dict[STATE_KEY_STATE]
|
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.components.sensor import PLATFORM_SCHEMA, SensorEntity
|
||||||
from homeassistant.const import CONF_NAME, CONF_UNIT_OF_MEASUREMENT
|
from homeassistant.const import CONF_NAME, CONF_UNIT_OF_MEASUREMENT
|
||||||
import homeassistant.helpers.config_validation as cv
|
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
|
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):
|
def __init__(self, ads_hub, ads_var, ads_type, name, unit_of_measurement, factor):
|
||||||
"""Initialize AdsSensor entity."""
|
"""Initialize AdsSensor entity."""
|
||||||
super().__init__(ads_hub, name, ads_var)
|
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._ads_type = ads_type
|
||||||
self._factor = factor
|
self._factor = factor
|
||||||
|
|
||||||
@ -63,11 +64,6 @@ class AdsSensor(AdsEntity, SensorEntity):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state(self):
|
def state(self) -> StateType:
|
||||||
"""Return the state of the device."""
|
"""Return the state of the device."""
|
||||||
return self._state_dict[STATE_KEY_STATE]
|
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)
|
await self.async_initialize_device(self._ads_var, self._ads_hub.PLCTYPE_BOOL)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self):
|
def is_on(self) -> bool:
|
||||||
"""Return True if the entity is on."""
|
"""Return True if the entity is on."""
|
||||||
return self._state_dict[STATE_KEY_STATE]
|
return self._state_dict[STATE_KEY_STATE]
|
||||||
|
|
||||||
|
@ -35,15 +35,13 @@ class AdvantageAirZoneFilter(AdvantageAirEntity, BinarySensorEntity):
|
|||||||
|
|
||||||
_attr_device_class = DEVICE_CLASS_PROBLEM
|
_attr_device_class = DEVICE_CLASS_PROBLEM
|
||||||
|
|
||||||
@property
|
def __init__(self, instance, ac_key):
|
||||||
def name(self):
|
"""Initialize an Advantage Air Filter."""
|
||||||
"""Return the name."""
|
super().__init__(instance, ac_key)
|
||||||
return f'{self._ac["name"]} Filter'
|
self._attr_name = f'{self._ac["name"]} Filter'
|
||||||
|
self._attr_unique_id = (
|
||||||
@property
|
f'{self.coordinator.data["system"]["rid"]}-{ac_key}-filter'
|
||||||
def unique_id(self):
|
)
|
||||||
"""Return a unique id."""
|
|
||||||
return f'{self.coordinator.data["system"]["rid"]}-{self.ac_key}-filter'
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self):
|
def is_on(self):
|
||||||
@ -56,15 +54,13 @@ class AdvantageAirZoneMotion(AdvantageAirEntity, BinarySensorEntity):
|
|||||||
|
|
||||||
_attr_device_class = DEVICE_CLASS_MOTION
|
_attr_device_class = DEVICE_CLASS_MOTION
|
||||||
|
|
||||||
@property
|
def __init__(self, instance, ac_key, zone_key):
|
||||||
def name(self):
|
"""Initialize an Advantage Air Zone Motion."""
|
||||||
"""Return the name."""
|
super().__init__(instance, ac_key, zone_key)
|
||||||
return f'{self._zone["name"]} Motion'
|
self._attr_name = f'{self._zone["name"]} Motion'
|
||||||
|
self._attr_unique_id = (
|
||||||
@property
|
f'{self.coordinator.data["system"]["rid"]}-{ac_key}-{zone_key}-motion'
|
||||||
def unique_id(self):
|
)
|
||||||
"""Return a unique id."""
|
|
||||||
return f'{self.coordinator.data["system"]["rid"]}-{self.ac_key}-{self.zone_key}-motion'
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self):
|
def is_on(self):
|
||||||
@ -77,15 +73,13 @@ class AdvantageAirZoneMyZone(AdvantageAirEntity, BinarySensorEntity):
|
|||||||
|
|
||||||
_attr_entity_registry_enabled_default = False
|
_attr_entity_registry_enabled_default = False
|
||||||
|
|
||||||
@property
|
def __init__(self, instance, ac_key, zone_key):
|
||||||
def name(self):
|
"""Initialize an Advantage Air Zone MyZone."""
|
||||||
"""Return the name."""
|
super().__init__(instance, ac_key, zone_key)
|
||||||
return f'{self._zone["name"]} MyZone'
|
self._attr_name = f'{self._zone["name"]} MyZone'
|
||||||
|
self._attr_unique_id = (
|
||||||
@property
|
f'{self.coordinator.data["system"]["rid"]}-{ac_key}-{zone_key}-myzone'
|
||||||
def unique_id(self):
|
)
|
||||||
"""Return a unique id."""
|
|
||||||
return f'{self.coordinator.data["system"]["rid"]}-{self.ac_key}-{self.zone_key}-myzone'
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self):
|
def is_on(self):
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
"""Climate platform for Advantage Air integration."""
|
"""Climate platform for Advantage Air integration."""
|
||||||
|
|
||||||
from homeassistant.components.climate import ClimateEntity
|
from homeassistant.components.climate import ClimateEntity
|
||||||
from homeassistant.components.climate.const import (
|
from homeassistant.components.climate.const import (
|
||||||
FAN_AUTO,
|
FAN_AUTO,
|
||||||
@ -16,6 +15,7 @@ from homeassistant.components.climate.const import (
|
|||||||
SUPPORT_TARGET_TEMPERATURE,
|
SUPPORT_TARGET_TEMPERATURE,
|
||||||
)
|
)
|
||||||
from homeassistant.const import ATTR_TEMPERATURE, PRECISION_WHOLE, TEMP_CELSIUS
|
from homeassistant.const import ATTR_TEMPERATURE, PRECISION_WHOLE, TEMP_CELSIUS
|
||||||
|
from homeassistant.core import callback
|
||||||
from homeassistant.helpers import entity_platform
|
from homeassistant.helpers import entity_platform
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
@ -84,39 +84,26 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
|||||||
class AdvantageAirClimateEntity(AdvantageAirEntity, ClimateEntity):
|
class AdvantageAirClimateEntity(AdvantageAirEntity, ClimateEntity):
|
||||||
"""AdvantageAir Climate class."""
|
"""AdvantageAir Climate class."""
|
||||||
|
|
||||||
@property
|
_attr_temperature_unit = TEMP_CELSIUS
|
||||||
def temperature_unit(self):
|
_attr_target_temperature_step = PRECISION_WHOLE
|
||||||
"""Return the temperature unit."""
|
_attr_max_temp = 32
|
||||||
return TEMP_CELSIUS
|
_attr_min_temp = 16
|
||||||
|
|
||||||
@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
|
|
||||||
|
|
||||||
|
|
||||||
class AdvantageAirAC(AdvantageAirClimateEntity):
|
class AdvantageAirAC(AdvantageAirClimateEntity):
|
||||||
"""AdvantageAir AC unit."""
|
"""AdvantageAir AC unit."""
|
||||||
|
|
||||||
@property
|
_attr_fan_modes = [FAN_AUTO, FAN_LOW, FAN_MEDIUM, FAN_HIGH]
|
||||||
def name(self):
|
_attr_hvac_modes = AC_HVAC_MODES
|
||||||
"""Return the name."""
|
_attr_supported_features = SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE
|
||||||
return self._ac["name"]
|
|
||||||
|
|
||||||
@property
|
def __init__(self, instance, ac_key):
|
||||||
def unique_id(self):
|
"""Initialize an AdvantageAir AC unit."""
|
||||||
"""Return a unique id."""
|
super().__init__(instance, ac_key)
|
||||||
return f'{self.coordinator.data["system"]["rid"]}-{self.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
|
@property
|
||||||
def target_temperature(self):
|
def target_temperature(self):
|
||||||
@ -130,28 +117,11 @@ class AdvantageAirAC(AdvantageAirClimateEntity):
|
|||||||
return ADVANTAGE_AIR_HVAC_MODES.get(self._ac["mode"])
|
return ADVANTAGE_AIR_HVAC_MODES.get(self._ac["mode"])
|
||||||
return HVAC_MODE_OFF
|
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
|
@property
|
||||||
def fan_mode(self):
|
def fan_mode(self):
|
||||||
"""Return the current fan modes."""
|
"""Return the current fan modes."""
|
||||||
return ADVANTAGE_AIR_FAN_MODES.get(self._ac["fan"])
|
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):
|
async def async_set_hvac_mode(self, hvac_mode):
|
||||||
"""Set the HVAC Mode and State."""
|
"""Set the HVAC Mode and State."""
|
||||||
if hvac_mode == HVAC_MODE_OFF:
|
if hvac_mode == HVAC_MODE_OFF:
|
||||||
@ -185,42 +155,30 @@ class AdvantageAirAC(AdvantageAirClimateEntity):
|
|||||||
class AdvantageAirZone(AdvantageAirClimateEntity):
|
class AdvantageAirZone(AdvantageAirClimateEntity):
|
||||||
"""AdvantageAir Zone control."""
|
"""AdvantageAir Zone control."""
|
||||||
|
|
||||||
@property
|
_attr_hvac_modes = ZONE_HVAC_MODES
|
||||||
def name(self):
|
_attr_supported_features = SUPPORT_TARGET_TEMPERATURE
|
||||||
"""Return the name."""
|
|
||||||
return self._zone["name"]
|
|
||||||
|
|
||||||
@property
|
def __init__(self, instance, ac_key, zone_key):
|
||||||
def unique_id(self):
|
"""Initialize an AdvantageAir Zone control."""
|
||||||
"""Return a unique id."""
|
super().__init__(instance, ac_key, zone_key)
|
||||||
return f'{self.coordinator.data["system"]["rid"]}-{self.ac_key}-{self.zone_key}'
|
self._attr_name = self._zone["name"]
|
||||||
|
self._attr_unique_id = (
|
||||||
|
f'{self.coordinator.data["system"]["rid"]}-{ac_key}-{zone_key}'
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
async def async_added_to_hass(self):
|
||||||
def current_temperature(self):
|
"""When entity is added to hass."""
|
||||||
"""Return the current temperature."""
|
self.async_on_remove(self.coordinator.async_add_listener(self._update_callback))
|
||||||
return self._zone["measuredTemp"]
|
|
||||||
|
|
||||||
@property
|
@callback
|
||||||
def target_temperature(self):
|
def _update_callback(self) -> None:
|
||||||
"""Return the target temperature."""
|
"""Load data from integration."""
|
||||||
return self._zone["setTemp"]
|
self._attr_current_temperature = self._zone["measuredTemp"]
|
||||||
|
self._attr_target_temperature = self._zone["setTemp"]
|
||||||
@property
|
self._attr_hvac_mode = HVAC_MODE_OFF
|
||||||
def hvac_mode(self):
|
|
||||||
"""Return the current HVAC modes."""
|
|
||||||
if self._zone["state"] == ADVANTAGE_AIR_STATE_OPEN:
|
if self._zone["state"] == ADVANTAGE_AIR_STATE_OPEN:
|
||||||
return HVAC_MODE_FAN_ONLY
|
self._attr_hvac_mode = HVAC_MODE_FAN_ONLY
|
||||||
return HVAC_MODE_OFF
|
self.async_write_ha_state()
|
||||||
|
|
||||||
@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
|
|
||||||
|
|
||||||
async def async_set_hvac_mode(self, hvac_mode):
|
async def async_set_hvac_mode(self, hvac_mode):
|
||||||
"""Set the HVAC Mode and State."""
|
"""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):
|
class AdvantageAirZoneVent(AdvantageAirEntity, CoverEntity):
|
||||||
"""Advantage Air Cover Class."""
|
"""Advantage Air Cover Class."""
|
||||||
|
|
||||||
@property
|
_attr_device_class = DEVICE_CLASS_DAMPER
|
||||||
def name(self):
|
_attr_supported_features = SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION
|
||||||
"""Return the name."""
|
|
||||||
return f'{self._zone["name"]}'
|
|
||||||
|
|
||||||
@property
|
def __init__(self, instance, ac_key, zone_key):
|
||||||
def unique_id(self):
|
"""Initialize an Advantage Air Cover Class."""
|
||||||
"""Return a unique id."""
|
super().__init__(instance, ac_key, zone_key)
|
||||||
return f'{self.coordinator.data["system"]["rid"]}-{self.ac_key}-{self.zone_key}'
|
self._attr_name = f'{self._zone["name"]}'
|
||||||
|
self._attr_unique_id = (
|
||||||
@property
|
f'{self.coordinator.data["system"]["rid"]}-{ac_key}-{zone_key}'
|
||||||
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
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_closed(self):
|
def is_closed(self):
|
||||||
|
@ -14,6 +14,13 @@ class AdvantageAirEntity(CoordinatorEntity):
|
|||||||
self.async_change = instance["async_change"]
|
self.async_change = instance["async_change"]
|
||||||
self.ac_key = ac_key
|
self.ac_key = ac_key
|
||||||
self.zone_key = zone_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
|
@property
|
||||||
def _ac(self):
|
def _ac(self):
|
||||||
@ -22,14 +29,3 @@ class AdvantageAirEntity(CoordinatorEntity):
|
|||||||
@property
|
@property
|
||||||
def _zone(self):
|
def _zone(self):
|
||||||
return self.coordinator.data["aircons"][self.ac_key]["zones"][self.zone_key]
|
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",
|
"name": "Advantage Air",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/advantage_air",
|
"documentation": "https://www.home-assistant.io/integrations/advantage_air",
|
||||||
"codeowners": ["@Bre77"],
|
"codeowners": [
|
||||||
"requirements": ["advantage_air==0.2.1"],
|
"@Bre77"
|
||||||
|
],
|
||||||
|
"requirements": [
|
||||||
|
"advantage_air==0.2.5"
|
||||||
|
],
|
||||||
"quality_scale": "platinum",
|
"quality_scale": "platinum",
|
||||||
"iot_class": "local_polling"
|
"iot_class": "local_polling"
|
||||||
}
|
}
|
@ -1,8 +1,8 @@
|
|||||||
"""Sensor platform for Advantage Air integration."""
|
"""Sensor platform for Advantage Air integration."""
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components.sensor import SensorEntity
|
from homeassistant.components.sensor import STATE_CLASS_MEASUREMENT, SensorEntity
|
||||||
from homeassistant.const import PERCENTAGE
|
from homeassistant.const import PERCENTAGE, TEMP_CELSIUS
|
||||||
from homeassistant.helpers import config_validation as cv, entity_platform
|
from homeassistant.helpers import config_validation as cv, entity_platform
|
||||||
|
|
||||||
from .const import ADVANTAGE_AIR_STATE_OPEN, DOMAIN as ADVANTAGE_AIR_DOMAIN
|
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, "On"))
|
||||||
entities.append(AdvantageAirTimeTo(instance, ac_key, "Off"))
|
entities.append(AdvantageAirTimeTo(instance, ac_key, "Off"))
|
||||||
for zone_key, zone in ac_device["zones"].items():
|
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:
|
if zone["type"] != 0:
|
||||||
entities.append(AdvantageAirZoneVent(instance, ac_key, zone_key))
|
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
|
# Only show wireless signal strength sensors when using wireless sensors
|
||||||
if zone["rssi"] > 0:
|
if zone["rssi"] > 0:
|
||||||
entities.append(AdvantageAirZoneSignal(instance, ac_key, zone_key))
|
entities.append(AdvantageAirZoneSignal(instance, ac_key, zone_key))
|
||||||
@ -50,17 +51,11 @@ class AdvantageAirTimeTo(AdvantageAirEntity, SensorEntity):
|
|||||||
"""Initialize the Advantage Air timer control."""
|
"""Initialize the Advantage Air timer control."""
|
||||||
super().__init__(instance, ac_key)
|
super().__init__(instance, ac_key)
|
||||||
self.action = action
|
self.action = action
|
||||||
self._time_key = f"countDownTo{self.action}"
|
self._time_key = f"countDownTo{action}"
|
||||||
|
self._attr_name = f'{self._ac["name"]} Time To {action}'
|
||||||
@property
|
self._attr_unique_id = (
|
||||||
def name(self):
|
f'{self.coordinator.data["system"]["rid"]}-{self.ac_key}-timeto{action}'
|
||||||
"""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}'
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state(self):
|
def state(self):
|
||||||
@ -84,16 +79,15 @@ class AdvantageAirZoneVent(AdvantageAirEntity, SensorEntity):
|
|||||||
"""Representation of Advantage Air Zone Vent Sensor."""
|
"""Representation of Advantage Air Zone Vent Sensor."""
|
||||||
|
|
||||||
_attr_unit_of_measurement = PERCENTAGE
|
_attr_unit_of_measurement = PERCENTAGE
|
||||||
|
_attr_state_class = STATE_CLASS_MEASUREMENT
|
||||||
|
|
||||||
@property
|
def __init__(self, instance, ac_key, zone_key):
|
||||||
def name(self):
|
"""Initialize an Advantage Air Zone Vent Sensor."""
|
||||||
"""Return the name."""
|
super().__init__(instance, ac_key, zone_key=zone_key)
|
||||||
return f'{self._zone["name"]} Vent'
|
self._attr_name = f'{self._zone["name"]} Vent'
|
||||||
|
self._attr_unique_id = (
|
||||||
@property
|
f'{self.coordinator.data["system"]["rid"]}-{ac_key}-{zone_key}-vent'
|
||||||
def unique_id(self):
|
)
|
||||||
"""Return a unique id."""
|
|
||||||
return f'{self.coordinator.data["system"]["rid"]}-{self.ac_key}-{self.zone_key}-vent'
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state(self):
|
def state(self):
|
||||||
@ -114,16 +108,15 @@ class AdvantageAirZoneSignal(AdvantageAirEntity, SensorEntity):
|
|||||||
"""Representation of Advantage Air Zone wireless signal sensor."""
|
"""Representation of Advantage Air Zone wireless signal sensor."""
|
||||||
|
|
||||||
_attr_unit_of_measurement = PERCENTAGE
|
_attr_unit_of_measurement = PERCENTAGE
|
||||||
|
_attr_state_class = STATE_CLASS_MEASUREMENT
|
||||||
|
|
||||||
@property
|
def __init__(self, instance, ac_key, zone_key):
|
||||||
def name(self):
|
"""Initialize an Advantage Air Zone wireless signal sensor."""
|
||||||
"""Return the name."""
|
super().__init__(instance, ac_key, zone_key=zone_key)
|
||||||
return f'{self._zone["name"]} Signal'
|
self._attr_name = f'{self._zone["name"]} Signal'
|
||||||
|
self._attr_unique_id = (
|
||||||
@property
|
f'{self.coordinator.data["system"]["rid"]}-{ac_key}-{zone_key}-signal'
|
||||||
def unique_id(self):
|
)
|
||||||
"""Return a unique id."""
|
|
||||||
return f'{self.coordinator.data["system"]["rid"]}-{self.ac_key}-{self.zone_key}-signal'
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state(self):
|
def state(self):
|
||||||
@ -142,3 +135,23 @@ class AdvantageAirZoneSignal(AdvantageAirEntity, SensorEntity):
|
|||||||
if self._zone["rssi"] >= 20:
|
if self._zone["rssi"] >= 20:
|
||||||
return "mdi:wifi-strength-1"
|
return "mdi:wifi-strength-1"
|
||||||
return "mdi:wifi-strength-outline"
|
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):
|
class AdvantageAirFreshAir(AdvantageAirEntity, ToggleEntity):
|
||||||
"""Representation of Advantage Air fresh air control."""
|
"""Representation of Advantage Air fresh air control."""
|
||||||
|
|
||||||
@property
|
_attr_icon = "mdi:air-filter"
|
||||||
def name(self):
|
|
||||||
"""Return the name."""
|
|
||||||
return f'{self._ac["name"]} Fresh Air'
|
|
||||||
|
|
||||||
@property
|
def __init__(self, instance, ac_key):
|
||||||
def unique_id(self):
|
"""Initialize an Advantage Air fresh air control."""
|
||||||
"""Return a unique id."""
|
super().__init__(instance, ac_key)
|
||||||
return f'{self.coordinator.data["system"]["rid"]}-{self.ac_key}-freshair'
|
self._attr_name = f'{self._ac["name"]} Fresh Air'
|
||||||
|
self._attr_unique_id = (
|
||||||
|
f'{self.coordinator.data["system"]["rid"]}-{ac_key}-freshair'
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self):
|
def is_on(self):
|
||||||
"""Return the fresh air status."""
|
"""Return the fresh air status."""
|
||||||
return self._ac["freshAirStatus"] == ADVANTAGE_AIR_STATE_ON
|
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):
|
async def async_turn_on(self, **kwargs):
|
||||||
"""Turn fresh air on."""
|
"""Turn fresh air on."""
|
||||||
await self.async_change(
|
await self.async_change(
|
||||||
|
@ -9,10 +9,10 @@
|
|||||||
"step": {
|
"step": {
|
||||||
"user": {
|
"user": {
|
||||||
"data": {
|
"data": {
|
||||||
"ip_address": "IP Adresse",
|
"ip_address": "IP-Adresse",
|
||||||
"port": "Port"
|
"port": "Port"
|
||||||
},
|
},
|
||||||
"description": "Anschluss an die API Ihres Advantage Air Wandtabletts.",
|
"description": "Anschluss an die API deines Advantage Air Wandtabletts.",
|
||||||
"title": "Verbinden"
|
"title": "Verbinden"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -66,6 +66,8 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
|||||||
class AbstractAemetSensor(CoordinatorEntity, SensorEntity):
|
class AbstractAemetSensor(CoordinatorEntity, SensorEntity):
|
||||||
"""Abstract class for an AEMET OpenData sensor."""
|
"""Abstract class for an AEMET OpenData sensor."""
|
||||||
|
|
||||||
|
_attr_extra_state_attributes = {ATTR_ATTRIBUTION: ATTRIBUTION}
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
name,
|
name,
|
||||||
@ -80,33 +82,10 @@ class AbstractAemetSensor(CoordinatorEntity, SensorEntity):
|
|||||||
self._unique_id = unique_id
|
self._unique_id = unique_id
|
||||||
self._sensor_type = sensor_type
|
self._sensor_type = sensor_type
|
||||||
self._sensor_name = sensor_configuration[SENSOR_NAME]
|
self._sensor_name = sensor_configuration[SENSOR_NAME]
|
||||||
self._unit_of_measurement = sensor_configuration.get(SENSOR_UNIT)
|
self._attr_name = f"{self._name} {self._sensor_name}"
|
||||||
self._device_class = sensor_configuration.get(SENSOR_DEVICE_CLASS)
|
self._attr_unique_id = self._unique_id
|
||||||
|
self._attr_device_class = sensor_configuration.get(SENSOR_DEVICE_CLASS)
|
||||||
@property
|
self._attr_unit_of_measurement = sensor_configuration.get(SENSOR_UNIT)
|
||||||
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}
|
|
||||||
|
|
||||||
|
|
||||||
class AemetSensor(AbstractAemetSensor):
|
class AemetSensor(AbstractAemetSensor):
|
||||||
@ -150,11 +129,9 @@ class AemetForecastSensor(AbstractAemetSensor):
|
|||||||
)
|
)
|
||||||
self._weather_coordinator = weather_coordinator
|
self._weather_coordinator = weather_coordinator
|
||||||
self._forecast_mode = forecast_mode
|
self._forecast_mode = forecast_mode
|
||||||
|
self._attr_entity_registry_enabled_default = (
|
||||||
@property
|
self._forecast_mode == FORECAST_MODE_DAILY
|
||||||
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
|
@property
|
||||||
def state(self):
|
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"
|
"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",
|
"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"
|
"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",
|
"longitude": "Hossz\u00fas\u00e1g",
|
||||||
"name": "Az integr\u00e1ci\u00f3 neve"
|
"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"
|
"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"
|
"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):
|
class AemetWeather(CoordinatorEntity, WeatherEntity):
|
||||||
"""Implementation of an AEMET OpenData sensor."""
|
"""Implementation of an AEMET OpenData sensor."""
|
||||||
|
|
||||||
|
_attr_attribution = ATTRIBUTION
|
||||||
|
_attr_temperature_unit = TEMP_CELSIUS
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
name,
|
name,
|
||||||
@ -48,25 +51,18 @@ class AemetWeather(CoordinatorEntity, WeatherEntity):
|
|||||||
):
|
):
|
||||||
"""Initialize the sensor."""
|
"""Initialize the sensor."""
|
||||||
super().__init__(coordinator)
|
super().__init__(coordinator)
|
||||||
self._name = name
|
|
||||||
self._unique_id = unique_id
|
|
||||||
self._forecast_mode = forecast_mode
|
self._forecast_mode = forecast_mode
|
||||||
|
self._attr_entity_registry_enabled_default = (
|
||||||
@property
|
self._forecast_mode == FORECAST_MODE_DAILY
|
||||||
def attribution(self):
|
)
|
||||||
"""Return the attribution."""
|
self._attr_name = name
|
||||||
return ATTRIBUTION
|
self._attr_unique_id = unique_id
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def condition(self):
|
def condition(self):
|
||||||
"""Return the current condition."""
|
"""Return the current condition."""
|
||||||
return self.coordinator.data[ATTR_API_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
|
@property
|
||||||
def forecast(self):
|
def forecast(self):
|
||||||
"""Return the forecast array."""
|
"""Return the forecast array."""
|
||||||
@ -77,11 +73,6 @@ class AemetWeather(CoordinatorEntity, WeatherEntity):
|
|||||||
"""Return the humidity."""
|
"""Return the humidity."""
|
||||||
return self.coordinator.data[ATTR_API_HUMIDITY]
|
return self.coordinator.data[ATTR_API_HUMIDITY]
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self):
|
|
||||||
"""Return the name of the sensor."""
|
|
||||||
return self._name
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def pressure(self):
|
def pressure(self):
|
||||||
"""Return the pressure."""
|
"""Return the pressure."""
|
||||||
@ -92,16 +83,6 @@ class AemetWeather(CoordinatorEntity, WeatherEntity):
|
|||||||
"""Return the temperature."""
|
"""Return the temperature."""
|
||||||
return self.coordinator.data[ATTR_API_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
|
@property
|
||||||
def wind_bearing(self):
|
def wind_bearing(self):
|
||||||
"""Return the temperature."""
|
"""Return the temperature."""
|
||||||
|
@ -109,38 +109,26 @@ async def async_setup_platform(
|
|||||||
class AfterShipSensor(SensorEntity):
|
class AfterShipSensor(SensorEntity):
|
||||||
"""Representation of a AfterShip sensor."""
|
"""Representation of a AfterShip sensor."""
|
||||||
|
|
||||||
|
_attr_unit_of_measurement: str = "packages"
|
||||||
|
_attr_icon: str = ICON
|
||||||
|
|
||||||
def __init__(self, aftership: Tracking, name: str) -> None:
|
def __init__(self, aftership: Tracking, name: str) -> None:
|
||||||
"""Initialize the sensor."""
|
"""Initialize the sensor."""
|
||||||
self._attributes: dict[str, Any] = {}
|
self._attributes: dict[str, Any] = {}
|
||||||
self._name: str = name
|
|
||||||
self._state: int | None = None
|
self._state: int | None = None
|
||||||
self.aftership = aftership
|
self.aftership = aftership
|
||||||
|
self._attr_name = name
|
||||||
@property
|
|
||||||
def name(self) -> str:
|
|
||||||
"""Return the name of the sensor."""
|
|
||||||
return self._name
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state(self) -> int | None:
|
def state(self) -> int | None:
|
||||||
"""Return the state of the sensor."""
|
"""Return the state of the sensor."""
|
||||||
return self._state
|
return self._state
|
||||||
|
|
||||||
@property
|
|
||||||
def unit_of_measurement(self) -> str:
|
|
||||||
"""Return the unit of measurement of this entity, if any."""
|
|
||||||
return "packages"
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def extra_state_attributes(self) -> dict[str, str]:
|
def extra_state_attributes(self) -> dict[str, str]:
|
||||||
"""Return attributes for the sensor."""
|
"""Return attributes for the sensor."""
|
||||||
return self._attributes
|
return self._attributes
|
||||||
|
|
||||||
@property
|
|
||||||
def icon(self) -> str:
|
|
||||||
"""Icon to use in the frontend."""
|
|
||||||
return ICON
|
|
||||||
|
|
||||||
async def async_added_to_hass(self) -> None:
|
async def async_added_to_hass(self) -> None:
|
||||||
"""Register callbacks."""
|
"""Register callbacks."""
|
||||||
self.async_on_remove(
|
self.async_on_remove(
|
||||||
|
@ -35,90 +35,60 @@ async def async_setup_entry(
|
|||||||
class AgentBaseStation(AlarmControlPanelEntity):
|
class AgentBaseStation(AlarmControlPanelEntity):
|
||||||
"""Representation of an Agent DVR Alarm Control Panel."""
|
"""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):
|
def __init__(self, client):
|
||||||
"""Initialize the alarm control panel."""
|
"""Initialize the alarm control panel."""
|
||||||
self._state = None
|
|
||||||
self._client = client
|
self._client = client
|
||||||
self._unique_id = f"{client.unique}_CP"
|
self._attr_name = f"{client.name} {CONST_ALARM_CONTROL_PANEL_NAME}"
|
||||||
name = CONST_ALARM_CONTROL_PANEL_NAME
|
self._attr_unique_id = f"{client.unique}_CP"
|
||||||
self._name = name = f"{client.name} {name}"
|
self._attr_device_info = {
|
||||||
|
"identifiers": {(AGENT_DOMAIN, client.unique)},
|
||||||
@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)},
|
|
||||||
"manufacturer": "Agent",
|
"manufacturer": "Agent",
|
||||||
"model": CONST_ALARM_CONTROL_PANEL_NAME,
|
"model": CONST_ALARM_CONTROL_PANEL_NAME,
|
||||||
"sw_version": self._client.version,
|
"sw_version": client.version,
|
||||||
}
|
}
|
||||||
|
|
||||||
async def async_update(self):
|
async def async_update(self):
|
||||||
"""Update the state of the device."""
|
"""Update the state of the device."""
|
||||||
await self._client.update()
|
await self._client.update()
|
||||||
|
self._attr_available = self._client.is_available
|
||||||
armed = self._client.is_armed
|
armed = self._client.is_armed
|
||||||
if armed is None:
|
if armed is None:
|
||||||
self._state = None
|
self._attr_state = None
|
||||||
return
|
return
|
||||||
if armed:
|
if armed:
|
||||||
prof = (await self._client.get_active_profile()).lower()
|
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:
|
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:
|
elif prof == CONF_NIGHT_MODE_NAME:
|
||||||
self._state = STATE_ALARM_ARMED_NIGHT
|
self._attr_state = STATE_ALARM_ARMED_NIGHT
|
||||||
else:
|
else:
|
||||||
self._state = STATE_ALARM_DISARMED
|
self._attr_state = STATE_ALARM_DISARMED
|
||||||
|
|
||||||
async def async_alarm_disarm(self, code=None):
|
async def async_alarm_disarm(self, code=None):
|
||||||
"""Send disarm command."""
|
"""Send disarm command."""
|
||||||
await self._client.disarm()
|
await self._client.disarm()
|
||||||
self._state = STATE_ALARM_DISARMED
|
self._attr_state = STATE_ALARM_DISARMED
|
||||||
|
|
||||||
async def async_alarm_arm_away(self, code=None):
|
async def async_alarm_arm_away(self, code=None):
|
||||||
"""Send arm away command. Uses custom mode."""
|
"""Send arm away command. Uses custom mode."""
|
||||||
await self._client.arm()
|
await self._client.arm()
|
||||||
await self._client.set_active_profile(CONF_AWAY_MODE_NAME)
|
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):
|
async def async_alarm_arm_home(self, code=None):
|
||||||
"""Send arm home command. Uses custom mode."""
|
"""Send arm home command. Uses custom mode."""
|
||||||
await self._client.arm()
|
await self._client.arm()
|
||||||
await self._client.set_active_profile(CONF_HOME_MODE_NAME)
|
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):
|
async def async_alarm_arm_night(self, code=None):
|
||||||
"""Send arm night command. Uses custom mode."""
|
"""Send arm night command. Uses custom mode."""
|
||||||
await self._client.arm()
|
await self._client.arm()
|
||||||
await self._client.set_active_profile(CONF_NIGHT_MODE_NAME)
|
await self._client.set_active_profile(CONF_NIGHT_MODE_NAME)
|
||||||
self._state = STATE_ALARM_ARMED_NIGHT
|
self._attr_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
|
|
||||||
|
@ -67,31 +67,27 @@ async def async_setup_entry(
|
|||||||
class AgentCamera(MjpegCamera):
|
class AgentCamera(MjpegCamera):
|
||||||
"""Representation of an Agent Device Stream."""
|
"""Representation of an Agent Device Stream."""
|
||||||
|
|
||||||
|
_attr_supported_features = SUPPORT_ON_OFF
|
||||||
|
|
||||||
def __init__(self, device):
|
def __init__(self, device):
|
||||||
"""Initialize as a subclass of MjpegCamera."""
|
"""Initialize as a subclass of MjpegCamera."""
|
||||||
self._servername = device.client.name
|
|
||||||
self.server_url = device.client._server_url
|
|
||||||
|
|
||||||
device_info = {
|
device_info = {
|
||||||
CONF_NAME: device.name,
|
CONF_NAME: device.name,
|
||||||
CONF_MJPEG_URL: f"{self.server_url}{device.mjpeg_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"{self.server_url}{device.still_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.device = device
|
||||||
self._removed = False
|
self._removed = False
|
||||||
self._name = f"{self._servername} {device.name}"
|
self._attr_name = f"{device.client.name} {device.name}"
|
||||||
self._unique_id = f"{device._client.unique}_{device.typeID}_{device.id}"
|
self._attr_unique_id = f"{device._client.unique}_{device.typeID}_{device.id}"
|
||||||
|
self._attr_should_poll = True
|
||||||
super().__init__(device_info)
|
super().__init__(device_info)
|
||||||
|
self._attr_device_info = {
|
||||||
@property
|
"identifiers": {(AGENT_DOMAIN, self.unique_id)},
|
||||||
def device_info(self):
|
"name": self.name,
|
||||||
"""Return the device info for adding the entity to the agent object."""
|
|
||||||
return {
|
|
||||||
"identifiers": {(AGENT_DOMAIN, self._unique_id)},
|
|
||||||
"name": self._name,
|
|
||||||
"manufacturer": "Agent",
|
"manufacturer": "Agent",
|
||||||
"model": "Camera",
|
"model": "Camera",
|
||||||
"sw_version": self.device.client.version,
|
"sw_version": device.client.version,
|
||||||
}
|
}
|
||||||
|
|
||||||
async def async_update(self):
|
async def async_update(self):
|
||||||
@ -99,18 +95,18 @@ class AgentCamera(MjpegCamera):
|
|||||||
try:
|
try:
|
||||||
await self.device.update()
|
await self.device.update()
|
||||||
if self._removed:
|
if self._removed:
|
||||||
_LOGGER.debug("%s reacquired", self._name)
|
_LOGGER.debug("%s reacquired", self.name)
|
||||||
self._removed = False
|
self._removed = False
|
||||||
except AgentError:
|
except AgentError:
|
||||||
# server still available - camera error
|
# server still available - camera error
|
||||||
if self.device.client.is_available and not self._removed:
|
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
|
self._removed = True
|
||||||
|
self._attr_available = self.device.client.is_available
|
||||||
@property
|
self._attr_icon = "mdi:camcorder-off"
|
||||||
def extra_state_attributes(self):
|
if self.is_on:
|
||||||
"""Return the Agent DVR camera state attributes."""
|
self._attr_icon = "mdi:camcorder"
|
||||||
return {
|
self._attr_extra_state_attributes = {
|
||||||
ATTR_ATTRIBUTION: ATTRIBUTION,
|
ATTR_ATTRIBUTION: ATTRIBUTION,
|
||||||
"editable": False,
|
"editable": False,
|
||||||
"enabled": self.is_on,
|
"enabled": self.is_on,
|
||||||
@ -121,11 +117,6 @@ class AgentCamera(MjpegCamera):
|
|||||||
"alerts_enabled": self.device.alerts_active,
|
"alerts_enabled": self.device.alerts_active,
|
||||||
}
|
}
|
||||||
|
|
||||||
@property
|
|
||||||
def should_poll(self) -> bool:
|
|
||||||
"""Update the state periodically."""
|
|
||||||
return True
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_recording(self) -> bool:
|
def is_recording(self) -> bool:
|
||||||
"""Return whether the monitor is recording."""
|
"""Return whether the monitor is recording."""
|
||||||
@ -141,43 +132,21 @@ class AgentCamera(MjpegCamera):
|
|||||||
"""Return whether the monitor has alerted."""
|
"""Return whether the monitor has alerted."""
|
||||||
return self.device.detected
|
return self.device.detected
|
||||||
|
|
||||||
@property
|
|
||||||
def available(self) -> bool:
|
|
||||||
"""Return True if entity is available."""
|
|
||||||
return self.device.client.is_available
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def connected(self) -> bool:
|
def connected(self) -> bool:
|
||||||
"""Return True if entity is connected."""
|
"""Return True if entity is connected."""
|
||||||
return self.device.connected
|
return self.device.connected
|
||||||
|
|
||||||
@property
|
|
||||||
def supported_features(self) -> int:
|
|
||||||
"""Return supported features."""
|
|
||||||
return SUPPORT_ON_OFF
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self) -> bool:
|
def is_on(self) -> bool:
|
||||||
"""Return true if on."""
|
"""Return true if on."""
|
||||||
return self.device.online
|
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
|
@property
|
||||||
def motion_detection_enabled(self):
|
def motion_detection_enabled(self):
|
||||||
"""Return the camera motion detection status."""
|
"""Return the camera motion detection status."""
|
||||||
return self.device.detector_active
|
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):
|
async def async_enable_alerts(self):
|
||||||
"""Enable alerts."""
|
"""Enable alerts."""
|
||||||
await self.device.alerts_on()
|
await self.device.alerts_on()
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
"host": "Host",
|
"host": "Host",
|
||||||
"port": "Port"
|
"port": "Port"
|
||||||
},
|
},
|
||||||
"title": "Richten Sie den Agent DVR ein"
|
"title": "Richte den Agent DVR ein"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
"abort": {
|
"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": {
|
"error": {
|
||||||
"already_in_progress": "\u05d6\u05e8\u05d9\u05de\u05ea \u05d4\u05ea\u05e6\u05d5\u05e8\u05d4 \u05db\u05d1\u05e8 \u05de\u05ea\u05d1\u05e6\u05e2\u05ea",
|
"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 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 (
|
from homeassistant.const import (
|
||||||
ATTR_DEVICE_CLASS,
|
|
||||||
ATTR_ICON,
|
|
||||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
DEVICE_CLASS_HUMIDITY,
|
DEVICE_CLASS_HUMIDITY,
|
||||||
DEVICE_CLASS_PRESSURE,
|
DEVICE_CLASS_PRESSURE,
|
||||||
@ -16,7 +14,7 @@ from homeassistant.const import (
|
|||||||
TEMP_CELSIUS,
|
TEMP_CELSIUS,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .model import SensorDescription
|
from .model import AirlySensorEntityDescription
|
||||||
|
|
||||||
ATTR_API_ADVICE: Final = "ADVICE"
|
ATTR_API_ADVICE: Final = "ADVICE"
|
||||||
ATTR_API_CAQI: Final = "CAQI"
|
ATTR_API_CAQI: Final = "CAQI"
|
||||||
@ -31,12 +29,9 @@ ATTR_API_TEMPERATURE: Final = "TEMPERATURE"
|
|||||||
|
|
||||||
ATTR_ADVICE: Final = "advice"
|
ATTR_ADVICE: Final = "advice"
|
||||||
ATTR_DESCRIPTION: Final = "description"
|
ATTR_DESCRIPTION: Final = "description"
|
||||||
ATTR_LABEL: Final = "label"
|
|
||||||
ATTR_LEVEL: Final = "level"
|
ATTR_LEVEL: Final = "level"
|
||||||
ATTR_LIMIT: Final = "limit"
|
ATTR_LIMIT: Final = "limit"
|
||||||
ATTR_PERCENT: Final = "percent"
|
ATTR_PERCENT: Final = "percent"
|
||||||
ATTR_UNIT: Final = "unit"
|
|
||||||
ATTR_VALUE: Final = "value"
|
|
||||||
|
|
||||||
SUFFIX_PERCENT: Final = "PERCENT"
|
SUFFIX_PERCENT: Final = "PERCENT"
|
||||||
SUFFIX_LIMIT: Final = "LIMIT"
|
SUFFIX_LIMIT: Final = "LIMIT"
|
||||||
@ -51,52 +46,54 @@ MAX_UPDATE_INTERVAL: Final = 90
|
|||||||
MIN_UPDATE_INTERVAL: Final = 5
|
MIN_UPDATE_INTERVAL: Final = 5
|
||||||
NO_AIRLY_SENSORS: Final = "There are no Airly sensors in this area yet."
|
NO_AIRLY_SENSORS: Final = "There are no Airly sensors in this area yet."
|
||||||
|
|
||||||
SENSOR_TYPES: dict[str, SensorDescription] = {
|
SENSOR_TYPES: tuple[AirlySensorEntityDescription, ...] = (
|
||||||
ATTR_API_CAQI: {
|
AirlySensorEntityDescription(
|
||||||
ATTR_LABEL: ATTR_API_CAQI,
|
key=ATTR_API_CAQI,
|
||||||
ATTR_UNIT: "CAQI",
|
name=ATTR_API_CAQI,
|
||||||
ATTR_VALUE: round,
|
unit_of_measurement="CAQI",
|
||||||
},
|
),
|
||||||
ATTR_API_PM1: {
|
AirlySensorEntityDescription(
|
||||||
ATTR_ICON: "mdi:blur",
|
key=ATTR_API_PM1,
|
||||||
ATTR_LABEL: ATTR_API_PM1,
|
icon="mdi:blur",
|
||||||
ATTR_UNIT: CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
name=ATTR_API_PM1,
|
||||||
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
ATTR_VALUE: round,
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
},
|
),
|
||||||
ATTR_API_PM25: {
|
AirlySensorEntityDescription(
|
||||||
ATTR_ICON: "mdi:blur",
|
key=ATTR_API_PM25,
|
||||||
ATTR_LABEL: "PM2.5",
|
icon="mdi:blur",
|
||||||
ATTR_UNIT: CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
name="PM2.5",
|
||||||
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
ATTR_VALUE: round,
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
},
|
),
|
||||||
ATTR_API_PM10: {
|
AirlySensorEntityDescription(
|
||||||
ATTR_ICON: "mdi:blur",
|
key=ATTR_API_PM10,
|
||||||
ATTR_LABEL: ATTR_API_PM10,
|
icon="mdi:blur",
|
||||||
ATTR_UNIT: CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
name=ATTR_API_PM10,
|
||||||
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
ATTR_VALUE: round,
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
},
|
),
|
||||||
ATTR_API_HUMIDITY: {
|
AirlySensorEntityDescription(
|
||||||
ATTR_DEVICE_CLASS: DEVICE_CLASS_HUMIDITY,
|
key=ATTR_API_HUMIDITY,
|
||||||
ATTR_LABEL: ATTR_API_HUMIDITY.capitalize(),
|
device_class=DEVICE_CLASS_HUMIDITY,
|
||||||
ATTR_UNIT: PERCENTAGE,
|
name=ATTR_API_HUMIDITY.capitalize(),
|
||||||
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
unit_of_measurement=PERCENTAGE,
|
||||||
ATTR_VALUE: lambda value: round(value, 1),
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
},
|
value=lambda value: round(value, 1),
|
||||||
ATTR_API_PRESSURE: {
|
),
|
||||||
ATTR_DEVICE_CLASS: DEVICE_CLASS_PRESSURE,
|
AirlySensorEntityDescription(
|
||||||
ATTR_LABEL: ATTR_API_PRESSURE.capitalize(),
|
key=ATTR_API_PRESSURE,
|
||||||
ATTR_UNIT: PRESSURE_HPA,
|
device_class=DEVICE_CLASS_PRESSURE,
|
||||||
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
name=ATTR_API_PRESSURE.capitalize(),
|
||||||
ATTR_VALUE: round,
|
unit_of_measurement=PRESSURE_HPA,
|
||||||
},
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
ATTR_API_TEMPERATURE: {
|
),
|
||||||
ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE,
|
AirlySensorEntityDescription(
|
||||||
ATTR_LABEL: ATTR_API_TEMPERATURE.capitalize(),
|
key=ATTR_API_TEMPERATURE,
|
||||||
ATTR_UNIT: TEMP_CELSIUS,
|
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||||
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
name=ATTR_API_TEMPERATURE.capitalize(),
|
||||||
ATTR_VALUE: lambda value: round(value, 1),
|
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."""
|
"""Type definitions for Airly integration."""
|
||||||
from __future__ import annotations
|
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):
|
@dataclass
|
||||||
"""Sensor description class."""
|
class AirlySensorEntityDescription(SensorEntityDescription):
|
||||||
|
"""Class describing Airly sensor entities."""
|
||||||
|
|
||||||
device_class: str | None
|
value: Callable = round
|
||||||
icon: str | None
|
|
||||||
label: str
|
|
||||||
unit: str
|
|
||||||
state_class: str | None
|
|
||||||
value: Callable
|
|
||||||
|
@ -3,16 +3,10 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from typing import Any, cast
|
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.config_entries import ConfigEntry
|
||||||
from homeassistant.const import (
|
from homeassistant.const import ATTR_ATTRIBUTION, CONF_NAME
|
||||||
ATTR_ATTRIBUTION,
|
|
||||||
ATTR_DEVICE_CLASS,
|
|
||||||
ATTR_ICON,
|
|
||||||
CONF_NAME,
|
|
||||||
)
|
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity import DeviceInfo
|
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.helpers.typing import StateType
|
from homeassistant.helpers.typing import StateType
|
||||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||||
@ -27,12 +21,9 @@ from .const import (
|
|||||||
ATTR_API_PM10,
|
ATTR_API_PM10,
|
||||||
ATTR_API_PM25,
|
ATTR_API_PM25,
|
||||||
ATTR_DESCRIPTION,
|
ATTR_DESCRIPTION,
|
||||||
ATTR_LABEL,
|
|
||||||
ATTR_LEVEL,
|
ATTR_LEVEL,
|
||||||
ATTR_LIMIT,
|
ATTR_LIMIT,
|
||||||
ATTR_PERCENT,
|
ATTR_PERCENT,
|
||||||
ATTR_UNIT,
|
|
||||||
ATTR_VALUE,
|
|
||||||
ATTRIBUTION,
|
ATTRIBUTION,
|
||||||
DEFAULT_NAME,
|
DEFAULT_NAME,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
@ -41,6 +32,7 @@ from .const import (
|
|||||||
SUFFIX_LIMIT,
|
SUFFIX_LIMIT,
|
||||||
SUFFIX_PERCENT,
|
SUFFIX_PERCENT,
|
||||||
)
|
)
|
||||||
|
from .model import AirlySensorEntityDescription
|
||||||
|
|
||||||
PARALLEL_UPDATES = 1
|
PARALLEL_UPDATES = 1
|
||||||
|
|
||||||
@ -54,10 +46,10 @@ async def async_setup_entry(
|
|||||||
coordinator = hass.data[DOMAIN][entry.entry_id]
|
coordinator = hass.data[DOMAIN][entry.entry_id]
|
||||||
|
|
||||||
sensors = []
|
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
|
# When we use the nearest method, we are not sure which sensors are available
|
||||||
if coordinator.data.get(sensor):
|
if coordinator.data.get(description.key):
|
||||||
sensors.append(AirlySensor(coordinator, name, sensor))
|
sensors.append(AirlySensor(coordinator, name, description))
|
||||||
|
|
||||||
async_add_entities(sensors, False)
|
async_add_entities(sensors, False)
|
||||||
|
|
||||||
@ -66,47 +58,54 @@ class AirlySensor(CoordinatorEntity, SensorEntity):
|
|||||||
"""Define an Airly sensor."""
|
"""Define an Airly sensor."""
|
||||||
|
|
||||||
coordinator: AirlyDataUpdateCoordinator
|
coordinator: AirlyDataUpdateCoordinator
|
||||||
|
entity_description: AirlySensorEntityDescription
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, coordinator: AirlyDataUpdateCoordinator, name: str, kind: str
|
self,
|
||||||
|
coordinator: AirlyDataUpdateCoordinator,
|
||||||
|
name: str,
|
||||||
|
description: AirlySensorEntityDescription,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize."""
|
"""Initialize."""
|
||||||
super().__init__(coordinator)
|
super().__init__(coordinator)
|
||||||
self._description = description = SENSOR_TYPES[kind]
|
self._attr_device_info = {
|
||||||
self._attr_device_class = description.get(ATTR_DEVICE_CLASS)
|
"identifiers": {
|
||||||
self._attr_icon = description.get(ATTR_ICON)
|
(DOMAIN, f"{coordinator.latitude}-{coordinator.longitude}")
|
||||||
self._attr_name = f"{name} {description[ATTR_LABEL]}"
|
},
|
||||||
self._attr_state_class = description.get(ATTR_STATE_CLASS)
|
"name": DEFAULT_NAME,
|
||||||
|
"manufacturer": MANUFACTURER,
|
||||||
|
"entry_type": "service",
|
||||||
|
}
|
||||||
|
self._attr_name = f"{name} {description.name}"
|
||||||
self._attr_unique_id = (
|
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._attrs: dict[str, Any] = {ATTR_ATTRIBUTION: ATTRIBUTION}
|
||||||
self.kind = kind
|
self.entity_description = description
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state(self) -> StateType:
|
def state(self) -> StateType:
|
||||||
"""Return the state."""
|
"""Return the state."""
|
||||||
state = self.coordinator.data[self.kind]
|
state = self.coordinator.data[self.entity_description.key]
|
||||||
return cast(StateType, self._description[ATTR_VALUE](state))
|
return cast(StateType, self.entity_description.value(state))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def extra_state_attributes(self) -> dict[str, Any]:
|
def extra_state_attributes(self) -> dict[str, Any]:
|
||||||
"""Return the state attributes."""
|
"""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_LEVEL] = self.coordinator.data[ATTR_API_CAQI_LEVEL]
|
||||||
self._attrs[ATTR_ADVICE] = self.coordinator.data[ATTR_API_ADVICE]
|
self._attrs[ATTR_ADVICE] = self.coordinator.data[ATTR_API_ADVICE]
|
||||||
self._attrs[ATTR_DESCRIPTION] = self.coordinator.data[
|
self._attrs[ATTR_DESCRIPTION] = self.coordinator.data[
|
||||||
ATTR_API_CAQI_DESCRIPTION
|
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[
|
self._attrs[ATTR_LIMIT] = self.coordinator.data[
|
||||||
f"{ATTR_API_PM25}_{SUFFIX_LIMIT}"
|
f"{ATTR_API_PM25}_{SUFFIX_LIMIT}"
|
||||||
]
|
]
|
||||||
self._attrs[ATTR_PERCENT] = round(
|
self._attrs[ATTR_PERCENT] = round(
|
||||||
self.coordinator.data[f"{ATTR_API_PM25}_{SUFFIX_PERCENT}"]
|
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[
|
self._attrs[ATTR_LIMIT] = self.coordinator.data[
|
||||||
f"{ATTR_API_PM10}_{SUFFIX_LIMIT}"
|
f"{ATTR_API_PM10}_{SUFFIX_LIMIT}"
|
||||||
]
|
]
|
||||||
@ -114,18 +113,3 @@ class AirlySensor(CoordinatorEntity, SensorEntity):
|
|||||||
self.coordinator.data[f"{ATTR_API_PM10}_{SUFFIX_PERCENT}"]
|
self.coordinator.data[f"{ATTR_API_PM10}_{SUFFIX_PERCENT}"]
|
||||||
)
|
)
|
||||||
return self._attrs
|
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": {
|
"system_health": {
|
||||||
"info": {
|
"info": {
|
||||||
|
"can_reach_server": "\u00c9rje el az Airly szervert",
|
||||||
"requests_per_day": "Enged\u00e9lyezett k\u00e9r\u00e9sek naponta",
|
"requests_per_day": "Enged\u00e9lyezett k\u00e9r\u00e9sek naponta",
|
||||||
"requests_remaining": "Fennmarad\u00f3 enged\u00e9lyezett k\u00e9r\u00e9sek"
|
"requests_remaining": "Fennmarad\u00f3 enged\u00e9lyezett k\u00e9r\u00e9sek"
|
||||||
}
|
}
|
||||||
|
@ -67,16 +67,13 @@ class AirNowSensor(CoordinatorEntity, SensorEntity):
|
|||||||
"""Initialize."""
|
"""Initialize."""
|
||||||
super().__init__(coordinator)
|
super().__init__(coordinator)
|
||||||
self.kind = kind
|
self.kind = kind
|
||||||
self._device_class = None
|
|
||||||
self._state = None
|
self._state = None
|
||||||
self._icon = None
|
|
||||||
self._unit_of_measurement = None
|
|
||||||
self._attrs = {ATTR_ATTRIBUTION: ATTRIBUTION}
|
self._attrs = {ATTR_ATTRIBUTION: ATTRIBUTION}
|
||||||
|
self._attr_name = f"AirNow {SENSOR_TYPES[self.kind][ATTR_LABEL]}"
|
||||||
@property
|
self._attr_icon = SENSOR_TYPES[self.kind][ATTR_ICON]
|
||||||
def name(self):
|
self._attr_device_class = SENSOR_TYPES[self.kind][ATTR_DEVICE_CLASS]
|
||||||
"""Return the name."""
|
self._attr_unit_of_measurement = SENSOR_TYPES[self.kind][ATTR_UNIT]
|
||||||
return f"AirNow {SENSOR_TYPES[self.kind][ATTR_LABEL]}"
|
self._attr_unique_id = f"{self.coordinator.latitude}-{self.coordinator.longitude}-{self.kind.lower()}"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state(self):
|
def state(self):
|
||||||
@ -96,24 +93,3 @@ class AirNowSensor(CoordinatorEntity, SensorEntity):
|
|||||||
]
|
]
|
||||||
|
|
||||||
return self._attrs
|
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": {
|
"config": {
|
||||||
"step": {
|
"step": {
|
||||||
"user": {
|
"user": {
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
"longitude": "L\u00e4ngengrad",
|
"longitude": "L\u00e4ngengrad",
|
||||||
"radius": "Stationsradius (Meilen; optional)"
|
"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"
|
"title": "AirNow"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,8 +14,10 @@
|
|||||||
"data": {
|
"data": {
|
||||||
"api_key": "API kulcs",
|
"api_key": "API kulcs",
|
||||||
"latitude": "Sz\u00e9less\u00e9g",
|
"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"
|
"title": "AirNow"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
"""The airvisual component."""
|
"""The airvisual component."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Mapping
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from math import ceil
|
from math import ceil
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from pyairvisual import CloudAPI, NodeSamba
|
from pyairvisual import CloudAPI, NodeSamba
|
||||||
from pyairvisual.errors import (
|
from pyairvisual.errors import (
|
||||||
@ -10,6 +14,7 @@ from pyairvisual.errors import (
|
|||||||
NodeProError,
|
NodeProError,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_ATTRIBUTION,
|
ATTR_ATTRIBUTION,
|
||||||
CONF_API_KEY,
|
CONF_API_KEY,
|
||||||
@ -20,9 +25,13 @@ from homeassistant.const import (
|
|||||||
CONF_SHOW_ON_MAP,
|
CONF_SHOW_ON_MAP,
|
||||||
CONF_STATE,
|
CONF_STATE,
|
||||||
)
|
)
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.exceptions import ConfigEntryAuthFailed
|
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 (
|
from homeassistant.helpers.update_coordinator import (
|
||||||
CoordinatorEntity,
|
CoordinatorEntity,
|
||||||
DataUpdateCoordinator,
|
DataUpdateCoordinator,
|
||||||
@ -42,7 +51,7 @@ from .const import (
|
|||||||
LOGGER,
|
LOGGER,
|
||||||
)
|
)
|
||||||
|
|
||||||
PLATFORMS = ["air_quality", "sensor"]
|
PLATFORMS = ["sensor"]
|
||||||
|
|
||||||
DATA_LISTENER = "listener"
|
DATA_LISTENER = "listener"
|
||||||
|
|
||||||
@ -53,11 +62,8 @@ CONFIG_SCHEMA = cv.deprecated(DOMAIN)
|
|||||||
|
|
||||||
|
|
||||||
@callback
|
@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."""
|
"""Generate a unique ID from a geography dict."""
|
||||||
if not geography_dict:
|
|
||||||
return
|
|
||||||
|
|
||||||
if CONF_CITY in geography_dict:
|
if CONF_CITY in geography_dict:
|
||||||
return ", ".join(
|
return ", ".join(
|
||||||
(
|
(
|
||||||
@ -72,7 +78,9 @@ def async_get_geography_id(geography_dict):
|
|||||||
|
|
||||||
|
|
||||||
@callback
|
@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.
|
"""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
|
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
|
@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."""
|
"""Get all DataUpdateCoordinator objects related to a particular API key."""
|
||||||
coordinators = []
|
coordinators = []
|
||||||
for entry_id, coordinator in hass.data[DOMAIN][DATA_COORDINATOR].items():
|
for entry_id, coordinator in hass.data[DOMAIN][DATA_COORDINATOR].items():
|
||||||
config_entry = hass.config_entries.async_get_entry(entry_id)
|
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)
|
coordinators.append(coordinator)
|
||||||
return coordinators
|
return coordinators
|
||||||
|
|
||||||
|
|
||||||
@callback
|
@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)."""
|
"""Sync the update interval for geography-based data coordinators (by API key)."""
|
||||||
coordinators = async_get_cloud_coordinators_by_api_key(hass, 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
|
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
|
@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."""
|
"""Ensure that geography config entries have appropriate properties."""
|
||||||
entry_updates = {}
|
entry_updates = {}
|
||||||
|
|
||||||
@ -164,9 +172,11 @@ def _standardize_geography_config_entry(hass, config_entry):
|
|||||||
|
|
||||||
|
|
||||||
@callback
|
@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."""
|
"""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 CONF_INTEGRATION_TYPE not in config_entry.data:
|
||||||
# If the config entry data doesn't contain the integration type, add it:
|
# 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)
|
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."""
|
"""Set up AirVisual as config entry."""
|
||||||
|
hass.data.setdefault(DOMAIN, {DATA_COORDINATOR: {}, DATA_LISTENER: {}})
|
||||||
|
|
||||||
if CONF_API_KEY in config_entry.data:
|
if CONF_API_KEY in config_entry.data:
|
||||||
_standardize_geography_config_entry(hass, config_entry)
|
_standardize_geography_config_entry(hass, config_entry)
|
||||||
|
|
||||||
websession = aiohttp_client.async_get_clientsession(hass)
|
websession = aiohttp_client.async_get_clientsession(hass)
|
||||||
cloud_api = CloudAPI(config_entry.data[CONF_API_KEY], session=websession)
|
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."""
|
"""Get new data from the API."""
|
||||||
if CONF_CITY in config_entry.data:
|
if CONF_CITY in config_entry.data:
|
||||||
api_coro = cloud_api.air_quality.city(
|
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.entry_id
|
||||||
] = config_entry.add_update_listener(async_reload_entry)
|
] = config_entry.add_update_listener(async_reload_entry)
|
||||||
else:
|
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)
|
_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."""
|
"""Get new data from the API."""
|
||||||
try:
|
try:
|
||||||
async with NodeSamba(
|
async with NodeSamba(
|
||||||
@ -262,7 +287,7 @@ async def async_setup_entry(hass, config_entry):
|
|||||||
return True
|
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."""
|
"""Migrate an old config entry."""
|
||||||
version = config_entry.version
|
version = config_entry.version
|
||||||
|
|
||||||
@ -304,7 +329,7 @@ async def async_migrate_entry(hass, config_entry):
|
|||||||
return True
|
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 an AirVisual config entry."""
|
||||||
unload_ok = await hass.config_entries.async_unload_platforms(
|
unload_ok = await hass.config_entries.async_unload_platforms(
|
||||||
config_entry, PLATFORMS
|
config_entry, PLATFORMS
|
||||||
@ -325,7 +350,7 @@ async def async_unload_entry(hass, config_entry):
|
|||||||
return unload_ok
|
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."""
|
"""Handle an options update."""
|
||||||
await hass.config_entries.async_reload(config_entry.entry_id)
|
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):
|
class AirVisualEntity(CoordinatorEntity):
|
||||||
"""Define a generic AirVisual entity."""
|
"""Define a generic AirVisual entity."""
|
||||||
|
|
||||||
def __init__(self, coordinator):
|
def __init__(self, coordinator: DataUpdateCoordinator) -> None:
|
||||||
"""Initialize."""
|
"""Initialize."""
|
||||||
super().__init__(coordinator)
|
super().__init__(coordinator)
|
||||||
self._attrs = {ATTR_ATTRIBUTION: DEFAULT_ATTRIBUTION}
|
|
||||||
|
|
||||||
@property
|
self._attr_extra_state_attributes = {ATTR_ATTRIBUTION: DEFAULT_ATTRIBUTION}
|
||||||
def extra_state_attributes(self):
|
|
||||||
"""Return the device state attributes."""
|
|
||||||
return self._attrs
|
|
||||||
|
|
||||||
async def async_added_to_hass(self):
|
async def async_added_to_hass(self) -> None:
|
||||||
"""Register callbacks."""
|
"""Register callbacks."""
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def update():
|
def update() -> None:
|
||||||
"""Update the state."""
|
"""Update the state."""
|
||||||
self.update_from_latest_data()
|
self.update_from_latest_data()
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
@ -357,6 +378,6 @@ class AirVisualEntity(CoordinatorEntity):
|
|||||||
self.update_from_latest_data()
|
self.update_from_latest_data()
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def update_from_latest_data(self):
|
def update_from_latest_data(self) -> None:
|
||||||
"""Update the entity from the latest data."""
|
"""Update the entity from the latest data."""
|
||||||
raise NotImplementedError
|
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."""
|
"""Define a config flow manager for AirVisual."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
from pyairvisual import CloudAPI, NodeSamba
|
from pyairvisual import CloudAPI, NodeSamba
|
||||||
@ -11,6 +13,7 @@ from pyairvisual.errors import (
|
|||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant import config_entries
|
from homeassistant import config_entries
|
||||||
|
from homeassistant.config_entries import ConfigEntry, OptionsFlow
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_API_KEY,
|
CONF_API_KEY,
|
||||||
CONF_IP_ADDRESS,
|
CONF_IP_ADDRESS,
|
||||||
@ -21,6 +24,7 @@ from homeassistant.const import (
|
|||||||
CONF_STATE,
|
CONF_STATE,
|
||||||
)
|
)
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
|
from homeassistant.data_entry_flow import FlowResult
|
||||||
from homeassistant.helpers import aiohttp_client, config_validation as cv
|
from homeassistant.helpers import aiohttp_client, config_validation as cv
|
||||||
|
|
||||||
from . import async_get_geography_id
|
from . import async_get_geography_id
|
||||||
@ -64,13 +68,13 @@ class AirVisualFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
|
|
||||||
VERSION = 2
|
VERSION = 2
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self) -> None:
|
||||||
"""Initialize the config flow."""
|
"""Initialize the config flow."""
|
||||||
self._entry_data_for_reauth = None
|
self._entry_data_for_reauth: dict[str, str] = {}
|
||||||
self._geo_id = None
|
self._geo_id: str | None = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def geography_coords_schema(self):
|
def geography_coords_schema(self) -> vol.Schema:
|
||||||
"""Return the data schema for the cloud API."""
|
"""Return the data schema for the cloud API."""
|
||||||
return API_KEY_DATA_SCHEMA.extend(
|
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."""
|
"""Validate a Cloud API key."""
|
||||||
websession = aiohttp_client.async_get_clientsession(self.hass)
|
websession = aiohttp_client.async_get_clientsession(self.hass)
|
||||||
cloud_api = CloudAPI(user_input[CONF_API_KEY], session=websession)
|
cloud_api = CloudAPI(user_input[CONF_API_KEY], session=websession)
|
||||||
@ -142,25 +148,29 @@ class AirVisualFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
data={**user_input, CONF_INTEGRATION_TYPE: integration_type},
|
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."""
|
"""Handle the initialization of the integration via the cloud API."""
|
||||||
self._geo_id = async_get_geography_id(user_input)
|
self._geo_id = async_get_geography_id(user_input)
|
||||||
await self._async_set_unique_id(self._geo_id)
|
await self._async_set_unique_id(self._geo_id)
|
||||||
self._abort_if_unique_id_configured()
|
self._abort_if_unique_id_configured()
|
||||||
return await self._async_finish_geography(user_input, integration_type)
|
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."""
|
"""Set the unique ID of the config flow and abort if it already exists."""
|
||||||
await self.async_set_unique_id(unique_id)
|
await self.async_set_unique_id(unique_id)
|
||||||
self._abort_if_unique_id_configured()
|
self._abort_if_unique_id_configured()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@callback
|
@callback
|
||||||
def async_get_options_flow(config_entry):
|
def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
|
||||||
"""Define the config flow to handle options."""
|
"""Define the config flow to handle options."""
|
||||||
return AirVisualOptionsFlowHandler(config_entry)
|
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."""
|
"""Handle the initialization of the cloud API based on latitude/longitude."""
|
||||||
if not user_input:
|
if not user_input:
|
||||||
return self.async_show_form(
|
return self.async_show_form(
|
||||||
@ -171,7 +181,9 @@ class AirVisualFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
user_input, INTEGRATION_TYPE_GEOGRAPHY_COORDS
|
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."""
|
"""Handle the initialization of the cloud API based on city/state/country."""
|
||||||
if not user_input:
|
if not user_input:
|
||||||
return self.async_show_form(
|
return self.async_show_form(
|
||||||
@ -182,7 +194,9 @@ class AirVisualFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
user_input, INTEGRATION_TYPE_GEOGRAPHY_NAME
|
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."""
|
"""Handle the initialization of the integration with a Node/Pro."""
|
||||||
if not user_input:
|
if not user_input:
|
||||||
return self.async_show_form(step_id="node_pro", data_schema=NODE_PRO_SCHEMA)
|
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},
|
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."""
|
"""Handle configuration by re-auth."""
|
||||||
self._entry_data_for_reauth = data
|
self._entry_data_for_reauth = data
|
||||||
self._geo_id = async_get_geography_id(data)
|
self._geo_id = async_get_geography_id(data)
|
||||||
return await self.async_step_reauth_confirm()
|
return await self.async_step_reauth_confirm()
|
||||||
|
|
||||||
async def async_step_reauth_confirm(self, user_input=None):
|
async def async_step_reauth_confirm(
|
||||||
|
self, user_input: dict[str, str] | None = None
|
||||||
|
) -> FlowResult:
|
||||||
"""Handle re-auth completion."""
|
"""Handle re-auth completion."""
|
||||||
if not user_input:
|
if not user_input:
|
||||||
return self.async_show_form(
|
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]
|
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."""
|
"""Handle the start of the config flow."""
|
||||||
if not user_input:
|
if not user_input:
|
||||||
return self.async_show_form(
|
return self.async_show_form(
|
||||||
@ -244,11 +262,13 @@ class AirVisualFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
class AirVisualOptionsFlowHandler(config_entries.OptionsFlow):
|
class AirVisualOptionsFlowHandler(config_entries.OptionsFlow):
|
||||||
"""Handle an AirVisual options flow."""
|
"""Handle an AirVisual options flow."""
|
||||||
|
|
||||||
def __init__(self, config_entry):
|
def __init__(self, config_entry: ConfigEntry) -> None:
|
||||||
"""Initialize."""
|
"""Initialize."""
|
||||||
self.config_entry = config_entry
|
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."""
|
"""Manage the options."""
|
||||||
if user_input is not None:
|
if user_input is not None:
|
||||||
return self.async_create_entry(title="", data=user_input)
|
return self.async_create_entry(title="", data=user_input)
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"name": "AirVisual",
|
"name": "AirVisual",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/airvisual",
|
"documentation": "https://www.home-assistant.io/integrations/airvisual",
|
||||||
"requirements": ["pyairvisual==5.0.8"],
|
"requirements": ["pyairvisual==5.0.9"],
|
||||||
"codeowners": ["@bachya"],
|
"codeowners": ["@bachya"],
|
||||||
"iot_class": "cloud_polling"
|
"iot_class": "cloud_polling"
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
"""Support for AirVisual air quality sensors."""
|
"""Support for AirVisual air quality sensors."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
from homeassistant.components.sensor import SensorEntity
|
from homeassistant.components.sensor import SensorEntity
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_LATITUDE,
|
ATTR_LATITUDE,
|
||||||
ATTR_LONGITUDE,
|
ATTR_LONGITUDE,
|
||||||
@ -12,12 +15,16 @@ from homeassistant.const import (
|
|||||||
CONF_SHOW_ON_MAP,
|
CONF_SHOW_ON_MAP,
|
||||||
CONF_STATE,
|
CONF_STATE,
|
||||||
DEVICE_CLASS_BATTERY,
|
DEVICE_CLASS_BATTERY,
|
||||||
|
DEVICE_CLASS_CO2,
|
||||||
DEVICE_CLASS_HUMIDITY,
|
DEVICE_CLASS_HUMIDITY,
|
||||||
DEVICE_CLASS_TEMPERATURE,
|
DEVICE_CLASS_TEMPERATURE,
|
||||||
PERCENTAGE,
|
PERCENTAGE,
|
||||||
TEMP_CELSIUS,
|
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 . import AirVisualEntity
|
||||||
from .const import (
|
from .const import (
|
||||||
@ -36,12 +43,21 @@ ATTR_POLLUTANT_SYMBOL = "pollutant_symbol"
|
|||||||
ATTR_POLLUTANT_UNIT = "pollutant_unit"
|
ATTR_POLLUTANT_UNIT = "pollutant_unit"
|
||||||
ATTR_REGION = "region"
|
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_AQI = "air_quality_index"
|
||||||
SENSOR_KIND_POLLUTANT = "main_pollutant"
|
|
||||||
SENSOR_KIND_BATTERY_LEVEL = "battery_level"
|
SENSOR_KIND_BATTERY_LEVEL = "battery_level"
|
||||||
|
SENSOR_KIND_CO2 = "carbon_dioxide"
|
||||||
SENSOR_KIND_HUMIDITY = "humidity"
|
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_TEMPERATURE = "temperature"
|
||||||
|
SENSOR_KIND_VOC = "voc"
|
||||||
|
|
||||||
GEOGRAPHY_SENSORS = [
|
GEOGRAPHY_SENSORS = [
|
||||||
(SENSOR_KIND_LEVEL, "Air Pollution Level", "mdi:gauge", None),
|
(SENSOR_KIND_LEVEL, "Air Pollution Level", "mdi:gauge", None),
|
||||||
@ -51,27 +67,74 @@ GEOGRAPHY_SENSORS = [
|
|||||||
GEOGRAPHY_SENSOR_LOCALES = {"cn": "Chinese", "us": "U.S."}
|
GEOGRAPHY_SENSOR_LOCALES = {"cn": "Chinese", "us": "U.S."}
|
||||||
|
|
||||||
NODE_PRO_SENSORS = [
|
NODE_PRO_SENSORS = [
|
||||||
(SENSOR_KIND_BATTERY_LEVEL, "Battery", DEVICE_CLASS_BATTERY, PERCENTAGE),
|
(SENSOR_KIND_AQI, "Air Quality Index", None, "mdi:chart-line", "AQI"),
|
||||||
(SENSOR_KIND_HUMIDITY, "Humidity", DEVICE_CLASS_HUMIDITY, PERCENTAGE),
|
(SENSOR_KIND_BATTERY_LEVEL, "Battery", DEVICE_CLASS_BATTERY, None, PERCENTAGE),
|
||||||
(SENSOR_KIND_TEMPERATURE, "Temperature", DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS),
|
(
|
||||||
|
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 = {
|
STATE_POLLUTANT_LABEL_CO = "co"
|
||||||
"co": "Carbon Monoxide",
|
STATE_POLLUTANT_LABEL_N2 = "n2"
|
||||||
"n2": "Nitrogen Dioxide",
|
STATE_POLLUTANT_LABEL_O3 = "o3"
|
||||||
"o3": "Ozone",
|
STATE_POLLUTANT_LABEL_P1 = "p1"
|
||||||
"p1": "PM10",
|
STATE_POLLUTANT_LABEL_P2 = "p2"
|
||||||
"p2": "PM2.5",
|
STATE_POLLUTANT_LABEL_S2 = "s2"
|
||||||
"s2": "Sulfur Dioxide",
|
|
||||||
}
|
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 = {
|
POLLUTANT_LEVELS = {
|
||||||
(0, 50): ("Good", "mdi:emoticon-excited"),
|
(0, 50): (STATE_POLLUTANT_LEVEL_GOOD, "mdi:emoticon-excited"),
|
||||||
(51, 100): ("Moderate", "mdi:emoticon-happy"),
|
(51, 100): (STATE_POLLUTANT_LEVEL_MODERATE, "mdi:emoticon-happy"),
|
||||||
(101, 150): ("Unhealthy for sensitive groups", "mdi:emoticon-neutral"),
|
(101, 150): (STATE_POLLUTANT_LEVEL_UNHEALTHY_SENSITIVE, "mdi:emoticon-neutral"),
|
||||||
(151, 200): ("Unhealthy", "mdi:emoticon-sad"),
|
(151, 200): (STATE_POLLUTANT_LEVEL_UNHEALTHY, "mdi:emoticon-sad"),
|
||||||
(201, 300): ("Very unhealthy", "mdi:emoticon-dead"),
|
(201, 300): (STATE_POLLUTANT_LEVEL_VERY_UNHEALTHY, "mdi:emoticon-dead"),
|
||||||
(301, 1000): ("Hazardous", "mdi:biohazard"),
|
(301, 1000): (STATE_POLLUTANT_LEVEL_HAZARDOUS, "mdi:biohazard"),
|
||||||
}
|
}
|
||||||
|
|
||||||
POLLUTANT_UNITS = {
|
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."""
|
"""Set up AirVisual sensors based on a config entry."""
|
||||||
coordinator = hass.data[DOMAIN][DATA_COORDINATOR][config_entry.entry_id]
|
coordinator = hass.data[DOMAIN][DATA_COORDINATOR][config_entry.entry_id]
|
||||||
|
|
||||||
|
sensors: list[AirVisualGeographySensor | AirVisualNodeProSensor]
|
||||||
if config_entry.data[CONF_INTEGRATION_TYPE] in [
|
if config_entry.data[CONF_INTEGRATION_TYPE] in [
|
||||||
INTEGRATION_TYPE_GEOGRAPHY_COORDS,
|
INTEGRATION_TYPE_GEOGRAPHY_COORDS,
|
||||||
INTEGRATION_TYPE_GEOGRAPHY_NAME,
|
INTEGRATION_TYPE_GEOGRAPHY_NAME,
|
||||||
@ -107,8 +175,8 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
|||||||
]
|
]
|
||||||
else:
|
else:
|
||||||
sensors = [
|
sensors = [
|
||||||
AirVisualNodeProSensor(coordinator, kind, name, device_class, unit)
|
AirVisualNodeProSensor(coordinator, kind, name, device_class, icon, unit)
|
||||||
for kind, name, device_class, unit in NODE_PRO_SENSORS
|
for kind, name, device_class, icon, unit in NODE_PRO_SENSORS
|
||||||
]
|
]
|
||||||
|
|
||||||
async_add_entities(sensors, True)
|
async_add_entities(sensors, True)
|
||||||
@ -117,53 +185,45 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
|||||||
class AirVisualGeographySensor(AirVisualEntity, SensorEntity):
|
class AirVisualGeographySensor(AirVisualEntity, SensorEntity):
|
||||||
"""Define an AirVisual sensor related to geography data via the Cloud API."""
|
"""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."""
|
"""Initialize."""
|
||||||
super().__init__(coordinator)
|
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_CITY: config_entry.data.get(CONF_CITY),
|
||||||
ATTR_STATE: config_entry.data.get(CONF_STATE),
|
ATTR_STATE: config_entry.data.get(CONF_STATE),
|
||||||
ATTR_COUNTRY: config_entry.data.get(CONF_COUNTRY),
|
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._config_entry = config_entry
|
||||||
self._kind = kind
|
self._kind = kind
|
||||||
self._locale = locale
|
self._locale = locale
|
||||||
self._name = name
|
|
||||||
self._state = None
|
|
||||||
|
|
||||||
self._attr_icon = icon
|
|
||||||
self._attr_unit_of_measurement = unit
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def available(self):
|
def available(self) -> bool:
|
||||||
"""Return True if entity is available."""
|
"""Return if entity is available."""
|
||||||
try:
|
return super().available and self.coordinator.data["current"]["pollution"]
|
||||||
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}"
|
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def update_from_latest_data(self):
|
def update_from_latest_data(self) -> None:
|
||||||
"""Update the entity from the latest data."""
|
"""Update the entity from the latest data."""
|
||||||
try:
|
try:
|
||||||
data = self.coordinator.data["current"]["pollution"]
|
data = self.coordinator.data["current"]["pollution"]
|
||||||
@ -172,17 +232,17 @@ class AirVisualGeographySensor(AirVisualEntity, SensorEntity):
|
|||||||
|
|
||||||
if self._kind == SENSOR_KIND_LEVEL:
|
if self._kind == SENSOR_KIND_LEVEL:
|
||||||
aqi = data[f"aqi{self._locale}"]
|
aqi = data[f"aqi{self._locale}"]
|
||||||
[(self._state, self._attr_icon)] = [
|
[(self._attr_state, self._attr_icon)] = [
|
||||||
(name, icon)
|
(name, icon)
|
||||||
for (floor, ceiling), (name, icon) in POLLUTANT_LEVELS.items()
|
for (floor, ceiling), (name, icon) in POLLUTANT_LEVELS.items()
|
||||||
if floor <= aqi <= ceiling
|
if floor <= aqi <= ceiling
|
||||||
]
|
]
|
||||||
elif self._kind == SENSOR_KIND_AQI:
|
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:
|
elif self._kind == SENSOR_KIND_POLLUTANT:
|
||||||
symbol = data[f"main{self._locale}"]
|
symbol = data[f"main{self._locale}"]
|
||||||
self._state = POLLUTANT_LABELS[symbol]
|
self._attr_state = symbol
|
||||||
self._attrs.update(
|
self._attr_extra_state_attributes.update(
|
||||||
{
|
{
|
||||||
ATTR_POLLUTANT_SYMBOL: symbol,
|
ATTR_POLLUTANT_SYMBOL: symbol,
|
||||||
ATTR_POLLUTANT_UNIT: POLLUTANT_UNITS[symbol],
|
ATTR_POLLUTANT_UNIT: POLLUTANT_UNITS[symbol],
|
||||||
@ -206,33 +266,43 @@ class AirVisualGeographySensor(AirVisualEntity, SensorEntity):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if self._config_entry.options[CONF_SHOW_ON_MAP]:
|
if self._config_entry.options[CONF_SHOW_ON_MAP]:
|
||||||
self._attrs[ATTR_LATITUDE] = latitude
|
self._attr_extra_state_attributes[ATTR_LATITUDE] = latitude
|
||||||
self._attrs[ATTR_LONGITUDE] = longitude
|
self._attr_extra_state_attributes[ATTR_LONGITUDE] = longitude
|
||||||
self._attrs.pop("lati", None)
|
self._attr_extra_state_attributes.pop("lati", None)
|
||||||
self._attrs.pop("long", None)
|
self._attr_extra_state_attributes.pop("long", None)
|
||||||
else:
|
else:
|
||||||
self._attrs["lati"] = latitude
|
self._attr_extra_state_attributes["lati"] = latitude
|
||||||
self._attrs["long"] = longitude
|
self._attr_extra_state_attributes["long"] = longitude
|
||||||
self._attrs.pop(ATTR_LATITUDE, None)
|
self._attr_extra_state_attributes.pop(ATTR_LATITUDE, None)
|
||||||
self._attrs.pop(ATTR_LONGITUDE, None)
|
self._attr_extra_state_attributes.pop(ATTR_LONGITUDE, None)
|
||||||
|
|
||||||
|
|
||||||
class AirVisualNodeProSensor(AirVisualEntity, SensorEntity):
|
class AirVisualNodeProSensor(AirVisualEntity, SensorEntity):
|
||||||
"""Define an AirVisual sensor related to a Node/Pro unit."""
|
"""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."""
|
"""Initialize."""
|
||||||
super().__init__(coordinator)
|
super().__init__(coordinator)
|
||||||
|
|
||||||
self._kind = kind
|
|
||||||
self._name = name
|
|
||||||
self._state = None
|
|
||||||
|
|
||||||
self._attr_device_class = device_class
|
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._attr_unit_of_measurement = unit
|
||||||
|
self._kind = kind
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_info(self):
|
def device_info(self) -> DeviceInfo:
|
||||||
"""Return device registry information for this entity."""
|
"""Return device registry information for this entity."""
|
||||||
return {
|
return {
|
||||||
"identifiers": {(DOMAIN, self.coordinator.data["serial_number"])},
|
"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
|
@callback
|
||||||
def update_from_latest_data(self):
|
def update_from_latest_data(self) -> None:
|
||||||
"""Update the entity from the latest data."""
|
"""Update the entity from the latest data."""
|
||||||
if self._kind == SENSOR_KIND_BATTERY_LEVEL:
|
if self._kind == SENSOR_KIND_AQI:
|
||||||
self._state = self.coordinator.data["status"]["battery"]
|
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:
|
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:
|
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": {
|
"config": {
|
||||||
"abort": {
|
"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"
|
"reauth_successful": "Die erneute Authentifizierung war erfolgreich"
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
@ -35,18 +35,18 @@
|
|||||||
"ip_address": "Host",
|
"ip_address": "Host",
|
||||||
"password": "Passwort"
|
"password": "Passwort"
|
||||||
},
|
},
|
||||||
"description": "\u00dcberwachen Sie eine pers\u00f6nliche AirVisual-Einheit. Das Passwort kann von der Benutzeroberfl\u00e4che des Ger\u00e4ts abgerufen werden.",
|
"description": "\u00dcberwache eine pers\u00f6nliche AirVisual-Einheit. Das Passwort kann von der Benutzeroberfl\u00e4che des Ger\u00e4ts abgerufen werden.",
|
||||||
"title": "Konfigurieren Sie einen AirVisual Node/Pro"
|
"title": "Konfiguriere einen AirVisual Node/Pro"
|
||||||
},
|
},
|
||||||
"reauth_confirm": {
|
"reauth_confirm": {
|
||||||
"data": {
|
"data": {
|
||||||
"api_key": "API-Key"
|
"api_key": "API-Schl\u00fcssel"
|
||||||
},
|
},
|
||||||
"title": "AirVisual erneut authentifizieren"
|
"title": "AirVisual erneut authentifizieren"
|
||||||
},
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"description": "W\u00e4hlen Sie aus, welche Art von AirVisual-Daten Sie \u00fcberwachen m\u00f6chten.",
|
"description": "W\u00e4hle aus, welche Art von AirVisual-Daten du \u00fcberwachen m\u00f6chtest.",
|
||||||
"title": "Konfigurieren Sie AirVisual"
|
"title": "Konfiguriere AirVisual"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -54,9 +54,9 @@
|
|||||||
"step": {
|
"step": {
|
||||||
"init": {
|
"init": {
|
||||||
"data": {
|
"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": {
|
"error": {
|
||||||
"cannot_connect": "\u05d4\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4",
|
"cannot_connect": "\u05d4\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4",
|
||||||
"general_error": "\u05e9\u05d2\u05d9\u05d0\u05d4 \u05d1\u05dc\u05ea\u05d9 \u05e6\u05e4\u05d5\u05d9\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"
|
"location_not_found": "\u05d4\u05de\u05d9\u05e7\u05d5\u05dd \u05dc\u05d0 \u05e0\u05de\u05e6\u05d0"
|
||||||
},
|
},
|
||||||
"step": {
|
"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": {
|
"error": {
|
||||||
"cannot_connect": "Sikertelen csatlakoz\u00e1s",
|
"cannot_connect": "Sikertelen csatlakoz\u00e1s",
|
||||||
"general_error": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt",
|
"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": {
|
"step": {
|
||||||
"geography_by_coords": {
|
"geography_by_coords": {
|
||||||
@ -15,14 +16,19 @@
|
|||||||
"api_key": "API kulcs",
|
"api_key": "API kulcs",
|
||||||
"latitude": "Sz\u00e9less\u00e9g",
|
"latitude": "Sz\u00e9less\u00e9g",
|
||||||
"longitude": "Hossz\u00fas\u00e1g"
|
"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": {
|
"geography_by_name": {
|
||||||
"data": {
|
"data": {
|
||||||
"api_key": "API kulcs",
|
"api_key": "API kulcs",
|
||||||
"city": "V\u00e1ros",
|
"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": {
|
"node_pro": {
|
||||||
"data": {
|
"data": {
|
||||||
@ -33,7 +39,8 @@
|
|||||||
"reauth_confirm": {
|
"reauth_confirm": {
|
||||||
"data": {
|
"data": {
|
||||||
"api_key": "API kulcs"
|
"api_key": "API kulcs"
|
||||||
}
|
},
|
||||||
|
"title": "Az AirVisual \u00fajb\u00f3li hiteles\u00edt\u00e9se"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
"latitude": "\u0428\u0438\u0440\u043e\u0442\u0430",
|
"latitude": "\u0428\u0438\u0440\u043e\u0442\u0430",
|
||||||
"longitude": "\u0414\u043e\u043b\u0433\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"
|
"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": {
|
"geography_by_name": {
|
||||||
@ -25,9 +25,9 @@
|
|||||||
"api_key": "\u041a\u043b\u044e\u0447 API",
|
"api_key": "\u041a\u043b\u044e\u0447 API",
|
||||||
"city": "\u0413\u043e\u0440\u043e\u0434",
|
"city": "\u0413\u043e\u0440\u043e\u0434",
|
||||||
"country": "\u0421\u0442\u0440\u0430\u043d\u0430",
|
"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"
|
"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": {
|
"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