mirror of
https://github.com/home-assistant/core.git
synced 2026-03-03 14:26:59 +01:00
Compare commits
1 Commits
2023.10.5
...
test-new-t
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dba2295371 |
@@ -24,13 +24,11 @@ base_platforms: &base_platforms
|
||||
- homeassistant/components/datetime/**
|
||||
- homeassistant/components/device_tracker/**
|
||||
- homeassistant/components/diagnostics/**
|
||||
- homeassistant/components/event/**
|
||||
- homeassistant/components/fan/**
|
||||
- homeassistant/components/geo_location/**
|
||||
- homeassistant/components/humidifier/**
|
||||
- homeassistant/components/image/**
|
||||
- homeassistant/components/image_processing/**
|
||||
- homeassistant/components/lawn_mower/**
|
||||
- homeassistant/components/light/**
|
||||
- homeassistant/components/lock/**
|
||||
- homeassistant/components/media_player/**
|
||||
@@ -55,7 +53,6 @@ base_platforms: &base_platforms
|
||||
components: &components
|
||||
- homeassistant/components/alexa/**
|
||||
- homeassistant/components/application_credentials/**
|
||||
- homeassistant/components/assist_pipeline/**
|
||||
- homeassistant/components/auth/**
|
||||
- homeassistant/components/automation/**
|
||||
- homeassistant/components/backup/**
|
||||
@@ -88,7 +85,6 @@ components: &components
|
||||
- homeassistant/components/lovelace/**
|
||||
- homeassistant/components/media_source/**
|
||||
- homeassistant/components/mjpeg/**
|
||||
- homeassistant/components/modbus/**
|
||||
- homeassistant/components/mqtt/**
|
||||
- homeassistant/components/network/**
|
||||
- homeassistant/components/onboarding/**
|
||||
|
||||
135
.coveragerc
135
.coveragerc
@@ -29,13 +29,11 @@ omit =
|
||||
homeassistant/components/adguard/switch.py
|
||||
homeassistant/components/ads/*
|
||||
homeassistant/components/aemet/weather_update_coordinator.py
|
||||
homeassistant/components/aftership/__init__.py
|
||||
homeassistant/components/aftership/sensor.py
|
||||
homeassistant/components/aftership/*
|
||||
homeassistant/components/agent_dvr/alarm_control_panel.py
|
||||
homeassistant/components/agent_dvr/camera.py
|
||||
homeassistant/components/agent_dvr/helpers.py
|
||||
homeassistant/components/airnow/__init__.py
|
||||
homeassistant/components/airnow/coordinator.py
|
||||
homeassistant/components/airnow/sensor.py
|
||||
homeassistant/components/airq/__init__.py
|
||||
homeassistant/components/airq/coordinator.py
|
||||
@@ -46,7 +44,6 @@ omit =
|
||||
homeassistant/components/airthings_ble/sensor.py
|
||||
homeassistant/components/airtouch4/__init__.py
|
||||
homeassistant/components/airtouch4/climate.py
|
||||
homeassistant/components/airtouch4/coordinator.py
|
||||
homeassistant/components/airvisual/__init__.py
|
||||
homeassistant/components/airvisual/sensor.py
|
||||
homeassistant/components/airvisual_pro/__init__.py
|
||||
@@ -60,7 +57,6 @@ omit =
|
||||
homeassistant/components/ambiclimate/climate.py
|
||||
homeassistant/components/ambient_station/__init__.py
|
||||
homeassistant/components/ambient_station/binary_sensor.py
|
||||
homeassistant/components/ambient_station/entity.py
|
||||
homeassistant/components/ambient_station/sensor.py
|
||||
homeassistant/components/amcrest/*
|
||||
homeassistant/components/ampio/*
|
||||
@@ -86,7 +82,6 @@ omit =
|
||||
homeassistant/components/arwn/sensor.py
|
||||
homeassistant/components/aseko_pool_live/__init__.py
|
||||
homeassistant/components/aseko_pool_live/binary_sensor.py
|
||||
homeassistant/components/aseko_pool_live/coordinator.py
|
||||
homeassistant/components/aseko_pool_live/entity.py
|
||||
homeassistant/components/aseko_pool_live/sensor.py
|
||||
homeassistant/components/asterisk_cdr/mailbox.py
|
||||
@@ -103,7 +98,6 @@ omit =
|
||||
homeassistant/components/azure_devops/__init__.py
|
||||
homeassistant/components/azure_devops/sensor.py
|
||||
homeassistant/components/azure_service_bus/*
|
||||
homeassistant/components/awair/coordinator.py
|
||||
homeassistant/components/baf/__init__.py
|
||||
homeassistant/components/baf/climate.py
|
||||
homeassistant/components/baf/entity.py
|
||||
@@ -173,18 +167,12 @@ omit =
|
||||
homeassistant/components/cmus/media_player.py
|
||||
homeassistant/components/coinbase/sensor.py
|
||||
homeassistant/components/comed_hourly_pricing/sensor.py
|
||||
homeassistant/components/comelit/__init__.py
|
||||
homeassistant/components/comelit/const.py
|
||||
homeassistant/components/comelit/cover.py
|
||||
homeassistant/components/comelit/coordinator.py
|
||||
homeassistant/components/comelit/light.py
|
||||
homeassistant/components/comfoconnect/fan.py
|
||||
homeassistant/components/concord232/alarm_control_panel.py
|
||||
homeassistant/components/concord232/binary_sensor.py
|
||||
homeassistant/components/control4/__init__.py
|
||||
homeassistant/components/control4/director_utils.py
|
||||
homeassistant/components/control4/light.py
|
||||
homeassistant/components/coolmaster/coordinator.py
|
||||
homeassistant/components/cppm_tracker/device_tracker.py
|
||||
homeassistant/components/crownstone/__init__.py
|
||||
homeassistant/components/crownstone/devices.py
|
||||
@@ -223,12 +211,10 @@ omit =
|
||||
homeassistant/components/dominos/*
|
||||
homeassistant/components/doods/*
|
||||
homeassistant/components/doorbird/__init__.py
|
||||
homeassistant/components/doorbird/camera.py
|
||||
homeassistant/components/doorbird/button.py
|
||||
homeassistant/components/doorbird/device.py
|
||||
homeassistant/components/doorbird/camera.py
|
||||
homeassistant/components/doorbird/entity.py
|
||||
homeassistant/components/doorbird/util.py
|
||||
homeassistant/components/doorbird/view.py
|
||||
homeassistant/components/dormakaba_dkey/__init__.py
|
||||
homeassistant/components/dormakaba_dkey/binary_sensor.py
|
||||
homeassistant/components/dormakaba_dkey/entity.py
|
||||
@@ -243,13 +229,6 @@ omit =
|
||||
homeassistant/components/dublin_bus_transport/sensor.py
|
||||
homeassistant/components/dunehd/__init__.py
|
||||
homeassistant/components/dunehd/media_player.py
|
||||
homeassistant/components/duotecno/__init__.py
|
||||
homeassistant/components/duotecno/entity.py
|
||||
homeassistant/components/duotecno/switch.py
|
||||
homeassistant/components/duotecno/cover.py
|
||||
homeassistant/components/duotecno/light.py
|
||||
homeassistant/components/duotecno/climate.py
|
||||
homeassistant/components/duotecno/binary_sensor.py
|
||||
homeassistant/components/dwd_weather_warnings/const.py
|
||||
homeassistant/components/dwd_weather_warnings/coordinator.py
|
||||
homeassistant/components/dwd_weather_warnings/sensor.py
|
||||
@@ -263,12 +242,6 @@ omit =
|
||||
homeassistant/components/ecobee/notify.py
|
||||
homeassistant/components/ecobee/sensor.py
|
||||
homeassistant/components/ecobee/weather.py
|
||||
homeassistant/components/ecoforest/__init__.py
|
||||
homeassistant/components/ecoforest/coordinator.py
|
||||
homeassistant/components/ecoforest/entity.py
|
||||
homeassistant/components/ecoforest/number.py
|
||||
homeassistant/components/ecoforest/sensor.py
|
||||
homeassistant/components/ecoforest/switch.py
|
||||
homeassistant/components/econet/__init__.py
|
||||
homeassistant/components/econet/binary_sensor.py
|
||||
homeassistant/components/econet/climate.py
|
||||
@@ -287,11 +260,6 @@ omit =
|
||||
homeassistant/components/eight_sleep/__init__.py
|
||||
homeassistant/components/eight_sleep/binary_sensor.py
|
||||
homeassistant/components/eight_sleep/sensor.py
|
||||
homeassistant/components/electric_kiwi/__init__.py
|
||||
homeassistant/components/electric_kiwi/api.py
|
||||
homeassistant/components/electric_kiwi/oauth2.py
|
||||
homeassistant/components/electric_kiwi/coordinator.py
|
||||
homeassistant/components/electric_kiwi/select.py
|
||||
homeassistant/components/eliqonline/sensor.py
|
||||
homeassistant/components/elkm1/__init__.py
|
||||
homeassistant/components/elkm1/alarm_control_panel.py
|
||||
@@ -304,8 +272,7 @@ omit =
|
||||
homeassistant/components/elmax/alarm_control_panel.py
|
||||
homeassistant/components/elmax/binary_sensor.py
|
||||
homeassistant/components/elmax/common.py
|
||||
homeassistant/components/elmax/const.py
|
||||
homeassistant/components/elmax/cover.py
|
||||
homeassistant/components/elmax/binary_sensor.py
|
||||
homeassistant/components/elmax/switch.py
|
||||
homeassistant/components/elv/*
|
||||
homeassistant/components/emby/media_player.py
|
||||
@@ -322,13 +289,7 @@ omit =
|
||||
homeassistant/components/enocean/sensor.py
|
||||
homeassistant/components/enocean/switch.py
|
||||
homeassistant/components/enphase_envoy/__init__.py
|
||||
homeassistant/components/enphase_envoy/binary_sensor.py
|
||||
homeassistant/components/enphase_envoy/coordinator.py
|
||||
homeassistant/components/enphase_envoy/entity.py
|
||||
homeassistant/components/enphase_envoy/number.py
|
||||
homeassistant/components/enphase_envoy/select.py
|
||||
homeassistant/components/enphase_envoy/sensor.py
|
||||
homeassistant/components/enphase_envoy/switch.py
|
||||
homeassistant/components/entur_public_transport/*
|
||||
homeassistant/components/environment_canada/__init__.py
|
||||
homeassistant/components/environment_canada/camera.py
|
||||
@@ -343,8 +304,10 @@ omit =
|
||||
homeassistant/components/escea/__init__.py
|
||||
homeassistant/components/escea/climate.py
|
||||
homeassistant/components/escea/discovery.py
|
||||
homeassistant/components/esphome/__init__.py
|
||||
homeassistant/components/esphome/bluetooth/*
|
||||
homeassistant/components/esphome/manager.py
|
||||
homeassistant/components/esphome/domain_data.py
|
||||
homeassistant/components/esphome/entry_data.py
|
||||
homeassistant/components/etherscan/sensor.py
|
||||
homeassistant/components/eufy/*
|
||||
homeassistant/components/eufylife_ble/__init__.py
|
||||
@@ -352,23 +315,17 @@ omit =
|
||||
homeassistant/components/everlights/light.py
|
||||
homeassistant/components/evohome/*
|
||||
homeassistant/components/ezviz/__init__.py
|
||||
homeassistant/components/ezviz/alarm_control_panel.py
|
||||
homeassistant/components/ezviz/binary_sensor.py
|
||||
homeassistant/components/ezviz/button.py
|
||||
homeassistant/components/ezviz/camera.py
|
||||
homeassistant/components/ezviz/image.py
|
||||
homeassistant/components/ezviz/light.py
|
||||
homeassistant/components/ezviz/coordinator.py
|
||||
homeassistant/components/ezviz/number.py
|
||||
homeassistant/components/ezviz/entity.py
|
||||
homeassistant/components/ezviz/select.py
|
||||
homeassistant/components/ezviz/sensor.py
|
||||
homeassistant/components/ezviz/siren.py
|
||||
homeassistant/components/ezviz/switch.py
|
||||
homeassistant/components/ezviz/update.py
|
||||
homeassistant/components/faa_delays/__init__.py
|
||||
homeassistant/components/faa_delays/binary_sensor.py
|
||||
homeassistant/components/faa_delays/coordinator.py
|
||||
homeassistant/components/familyhub/camera.py
|
||||
homeassistant/components/fastdotcom/*
|
||||
homeassistant/components/ffmpeg/camera.py
|
||||
@@ -393,6 +350,7 @@ omit =
|
||||
homeassistant/components/firmata/pin.py
|
||||
homeassistant/components/firmata/sensor.py
|
||||
homeassistant/components/firmata/switch.py
|
||||
homeassistant/components/fitbit/*
|
||||
homeassistant/components/fivem/__init__.py
|
||||
homeassistant/components/fivem/binary_sensor.py
|
||||
homeassistant/components/fivem/coordinator.py
|
||||
@@ -429,6 +387,7 @@ omit =
|
||||
homeassistant/components/freebox/device_tracker.py
|
||||
homeassistant/components/freebox/home_base.py
|
||||
homeassistant/components/freebox/router.py
|
||||
homeassistant/components/freebox/sensor.py
|
||||
homeassistant/components/freebox/switch.py
|
||||
homeassistant/components/fritz/common.py
|
||||
homeassistant/components/fritz/device_tracker.py
|
||||
@@ -444,7 +403,6 @@ omit =
|
||||
homeassistant/components/garadget/cover.py
|
||||
homeassistant/components/garages_amsterdam/__init__.py
|
||||
homeassistant/components/garages_amsterdam/binary_sensor.py
|
||||
homeassistant/components/garages_amsterdam/entity.py
|
||||
homeassistant/components/garages_amsterdam/sensor.py
|
||||
homeassistant/components/gc100/*
|
||||
homeassistant/components/geniushub/*
|
||||
@@ -541,12 +499,7 @@ omit =
|
||||
homeassistant/components/hvv_departures/__init__.py
|
||||
homeassistant/components/hvv_departures/binary_sensor.py
|
||||
homeassistant/components/hvv_departures/sensor.py
|
||||
homeassistant/components/hydrawise/__init__.py
|
||||
homeassistant/components/hydrawise/binary_sensor.py
|
||||
homeassistant/components/hydrawise/const.py
|
||||
homeassistant/components/hydrawise/coordinator.py
|
||||
homeassistant/components/hydrawise/sensor.py
|
||||
homeassistant/components/hydrawise/switch.py
|
||||
homeassistant/components/hydrawise/*
|
||||
homeassistant/components/ialarm/alarm_control_panel.py
|
||||
homeassistant/components/iammeter/sensor.py
|
||||
homeassistant/components/iaqualink/binary_sensor.py
|
||||
@@ -640,7 +593,6 @@ omit =
|
||||
homeassistant/components/keymitt_ble/entity.py
|
||||
homeassistant/components/keymitt_ble/switch.py
|
||||
homeassistant/components/keymitt_ble/coordinator.py
|
||||
homeassistant/components/kitchen_sink/weather.py
|
||||
homeassistant/components/kiwi/lock.py
|
||||
homeassistant/components/kodi/__init__.py
|
||||
homeassistant/components/kodi/browse_media.py
|
||||
@@ -672,7 +624,6 @@ omit =
|
||||
homeassistant/components/lg_soundbar/__init__.py
|
||||
homeassistant/components/lg_soundbar/media_player.py
|
||||
homeassistant/components/life360/__init__.py
|
||||
homeassistant/components/life360/button.py
|
||||
homeassistant/components/life360/coordinator.py
|
||||
homeassistant/components/life360/device_tracker.py
|
||||
homeassistant/components/lightwave/*
|
||||
@@ -722,13 +673,11 @@ omit =
|
||||
homeassistant/components/mailgun/notify.py
|
||||
homeassistant/components/map/*
|
||||
homeassistant/components/mastodon/notify.py
|
||||
homeassistant/components/matrix/__init__.py
|
||||
homeassistant/components/matrix/notify.py
|
||||
homeassistant/components/matrix/*
|
||||
homeassistant/components/matter/__init__.py
|
||||
homeassistant/components/meater/__init__.py
|
||||
homeassistant/components/meater/sensor.py
|
||||
homeassistant/components/medcom_ble/__init__.py
|
||||
homeassistant/components/medcom_ble/sensor.py
|
||||
homeassistant/components/media_extractor/*
|
||||
homeassistant/components/mediaroom/media_player.py
|
||||
homeassistant/components/melcloud/__init__.py
|
||||
homeassistant/components/melcloud/climate.py
|
||||
@@ -746,16 +695,16 @@ omit =
|
||||
homeassistant/components/meteoclimatic/__init__.py
|
||||
homeassistant/components/meteoclimatic/sensor.py
|
||||
homeassistant/components/meteoclimatic/weather.py
|
||||
homeassistant/components/metoffice/sensor.py
|
||||
homeassistant/components/metoffice/weather.py
|
||||
homeassistant/components/microsoft/tts.py
|
||||
homeassistant/components/miflora/sensor.py
|
||||
homeassistant/components/mikrotik/hub.py
|
||||
homeassistant/components/mill/climate.py
|
||||
homeassistant/components/mill/sensor.py
|
||||
homeassistant/components/minecraft_server/__init__.py
|
||||
homeassistant/components/minecraft_server/binary_sensor.py
|
||||
homeassistant/components/minecraft_server/coordinator.py
|
||||
homeassistant/components/minecraft_server/entity.py
|
||||
homeassistant/components/minecraft_server/sensor.py
|
||||
homeassistant/components/minio/minio_helper.py
|
||||
homeassistant/components/mitemp_bt/sensor.py
|
||||
homeassistant/components/mjpeg/camera.py
|
||||
homeassistant/components/mjpeg/util.py
|
||||
homeassistant/components/mochad/__init__.py
|
||||
@@ -768,9 +717,7 @@ omit =
|
||||
homeassistant/components/moehlenhoff_alpha2/climate.py
|
||||
homeassistant/components/moehlenhoff_alpha2/sensor.py
|
||||
homeassistant/components/motion_blinds/__init__.py
|
||||
homeassistant/components/motion_blinds/coordinator.py
|
||||
homeassistant/components/motion_blinds/cover.py
|
||||
homeassistant/components/motion_blinds/entity.py
|
||||
homeassistant/components/motion_blinds/sensor.py
|
||||
homeassistant/components/mpd/media_player.py
|
||||
homeassistant/components/mqtt_room/sensor.py
|
||||
@@ -803,18 +750,16 @@ omit =
|
||||
homeassistant/components/neato/__init__.py
|
||||
homeassistant/components/neato/api.py
|
||||
homeassistant/components/neato/camera.py
|
||||
homeassistant/components/neato/entity.py
|
||||
homeassistant/components/neato/hub.py
|
||||
homeassistant/components/neato/sensor.py
|
||||
homeassistant/components/neato/switch.py
|
||||
homeassistant/components/neato/vacuum.py
|
||||
homeassistant/components/neato/button.py
|
||||
homeassistant/components/nederlandse_spoorwegen/sensor.py
|
||||
homeassistant/components/nest/legacy/*
|
||||
homeassistant/components/netdata/sensor.py
|
||||
homeassistant/components/netgear/__init__.py
|
||||
homeassistant/components/netgear/button.py
|
||||
homeassistant/components/netgear/device_tracker.py
|
||||
homeassistant/components/netgear/entity.py
|
||||
homeassistant/components/netgear/router.py
|
||||
homeassistant/components/netgear/sensor.py
|
||||
homeassistant/components/netgear/switch.py
|
||||
@@ -867,7 +812,6 @@ omit =
|
||||
homeassistant/components/obihai/connectivity.py
|
||||
homeassistant/components/obihai/sensor.py
|
||||
homeassistant/components/octoprint/__init__.py
|
||||
homeassistant/components/octoprint/coordinator.py
|
||||
homeassistant/components/oem/climate.py
|
||||
homeassistant/components/ohmconnect/sensor.py
|
||||
homeassistant/components/ombi/*
|
||||
@@ -898,11 +842,11 @@ omit =
|
||||
homeassistant/components/opengarage/cover.py
|
||||
homeassistant/components/opengarage/entity.py
|
||||
homeassistant/components/opengarage/sensor.py
|
||||
homeassistant/components/openhardwaremonitor/sensor.py
|
||||
homeassistant/components/openhome/__init__.py
|
||||
homeassistant/components/openhome/const.py
|
||||
homeassistant/components/openhome/media_player.py
|
||||
homeassistant/components/opensensemap/air_quality.py
|
||||
homeassistant/components/opensky/sensor.py
|
||||
homeassistant/components/opentherm_gw/__init__.py
|
||||
homeassistant/components/opentherm_gw/binary_sensor.py
|
||||
homeassistant/components/opentherm_gw/climate.py
|
||||
@@ -980,8 +924,6 @@ omit =
|
||||
homeassistant/components/point/sensor.py
|
||||
homeassistant/components/poolsense/__init__.py
|
||||
homeassistant/components/poolsense/binary_sensor.py
|
||||
homeassistant/components/poolsense/coordinator.py
|
||||
homeassistant/components/poolsense/entity.py
|
||||
homeassistant/components/poolsense/sensor.py
|
||||
homeassistant/components/powerwall/__init__.py
|
||||
homeassistant/components/progettihwsw/__init__.py
|
||||
@@ -1032,13 +974,8 @@ omit =
|
||||
homeassistant/components/rainmachine/util.py
|
||||
homeassistant/components/renson/__init__.py
|
||||
homeassistant/components/renson/const.py
|
||||
homeassistant/components/renson/coordinator.py
|
||||
homeassistant/components/renson/entity.py
|
||||
homeassistant/components/renson/sensor.py
|
||||
homeassistant/components/renson/button.py
|
||||
homeassistant/components/renson/fan.py
|
||||
homeassistant/components/renson/binary_sensor.py
|
||||
homeassistant/components/renson/number.py
|
||||
homeassistant/components/raspyrfm/*
|
||||
homeassistant/components/recollect_waste/sensor.py
|
||||
homeassistant/components/recorder/repack.py
|
||||
@@ -1055,7 +992,6 @@ omit =
|
||||
homeassistant/components/reolink/light.py
|
||||
homeassistant/components/reolink/number.py
|
||||
homeassistant/components/reolink/select.py
|
||||
homeassistant/components/reolink/sensor.py
|
||||
homeassistant/components/reolink/siren.py
|
||||
homeassistant/components/reolink/switch.py
|
||||
homeassistant/components/reolink/update.py
|
||||
@@ -1099,10 +1035,9 @@ omit =
|
||||
homeassistant/components/saj/sensor.py
|
||||
homeassistant/components/satel_integra/*
|
||||
homeassistant/components/schluter/*
|
||||
homeassistant/components/screenlogic/__init__.py
|
||||
homeassistant/components/screenlogic/binary_sensor.py
|
||||
homeassistant/components/screenlogic/climate.py
|
||||
homeassistant/components/screenlogic/coordinator.py
|
||||
homeassistant/components/screenlogic/const.py
|
||||
homeassistant/components/screenlogic/entity.py
|
||||
homeassistant/components/screenlogic/light.py
|
||||
homeassistant/components/screenlogic/number.py
|
||||
@@ -1166,7 +1101,6 @@ omit =
|
||||
homeassistant/components/smarty/*
|
||||
homeassistant/components/sms/__init__.py
|
||||
homeassistant/components/sms/const.py
|
||||
homeassistant/components/sms/coordinator.py
|
||||
homeassistant/components/sms/gateway.py
|
||||
homeassistant/components/sms/notify.py
|
||||
homeassistant/components/sms/sensor.py
|
||||
@@ -1183,7 +1117,6 @@ omit =
|
||||
homeassistant/components/solaredge_local/sensor.py
|
||||
homeassistant/components/solarlog/__init__.py
|
||||
homeassistant/components/solarlog/sensor.py
|
||||
homeassistant/components/solarlog/coordinator.py
|
||||
homeassistant/components/solax/__init__.py
|
||||
homeassistant/components/solax/sensor.py
|
||||
homeassistant/components/soma/__init__.py
|
||||
@@ -1220,13 +1153,7 @@ omit =
|
||||
homeassistant/components/squeezebox/__init__.py
|
||||
homeassistant/components/squeezebox/browse_media.py
|
||||
homeassistant/components/squeezebox/media_player.py
|
||||
homeassistant/components/starlink/__init__.py
|
||||
homeassistant/components/starlink/binary_sensor.py
|
||||
homeassistant/components/starlink/button.py
|
||||
homeassistant/components/starlink/coordinator.py
|
||||
homeassistant/components/starlink/device_tracker.py
|
||||
homeassistant/components/starlink/sensor.py
|
||||
homeassistant/components/starlink/switch.py
|
||||
homeassistant/components/starline/__init__.py
|
||||
homeassistant/components/starline/account.py
|
||||
homeassistant/components/starline/binary_sensor.py
|
||||
@@ -1276,9 +1203,6 @@ omit =
|
||||
homeassistant/components/switchbot/sensor.py
|
||||
homeassistant/components/switchbot/switch.py
|
||||
homeassistant/components/switchbot/lock.py
|
||||
homeassistant/components/switchbot_cloud/coordinator.py
|
||||
homeassistant/components/switchbot_cloud/entity.py
|
||||
homeassistant/components/switchbot_cloud/switch.py
|
||||
homeassistant/components/switchmate/switch.py
|
||||
homeassistant/components/syncthing/__init__.py
|
||||
homeassistant/components/syncthing/sensor.py
|
||||
@@ -1301,7 +1225,6 @@ omit =
|
||||
homeassistant/components/system_bridge/__init__.py
|
||||
homeassistant/components/system_bridge/binary_sensor.py
|
||||
homeassistant/components/system_bridge/coordinator.py
|
||||
homeassistant/components/system_bridge/notify.py
|
||||
homeassistant/components/system_bridge/sensor.py
|
||||
homeassistant/components/systemmonitor/sensor.py
|
||||
homeassistant/components/tado/__init__.py
|
||||
@@ -1313,8 +1236,6 @@ omit =
|
||||
homeassistant/components/tank_utility/sensor.py
|
||||
homeassistant/components/tankerkoenig/__init__.py
|
||||
homeassistant/components/tankerkoenig/binary_sensor.py
|
||||
homeassistant/components/tankerkoenig/coordinator.py
|
||||
homeassistant/components/tankerkoenig/entity.py
|
||||
homeassistant/components/tankerkoenig/sensor.py
|
||||
homeassistant/components/tapsaff/binary_sensor.py
|
||||
homeassistant/components/tautulli/__init__.py
|
||||
@@ -1376,6 +1297,9 @@ omit =
|
||||
homeassistant/components/tplink_omada/__init__.py
|
||||
homeassistant/components/tplink_omada/binary_sensor.py
|
||||
homeassistant/components/tplink_omada/controller.py
|
||||
homeassistant/components/tplink_omada/coordinator.py
|
||||
homeassistant/components/tplink_omada/entity.py
|
||||
homeassistant/components/tplink_omada/switch.py
|
||||
homeassistant/components/tplink_omada/update.py
|
||||
homeassistant/components/traccar/device_tracker.py
|
||||
homeassistant/components/tractive/__init__.py
|
||||
@@ -1393,13 +1317,10 @@ omit =
|
||||
homeassistant/components/tradfri/sensor.py
|
||||
homeassistant/components/tradfri/switch.py
|
||||
homeassistant/components/trafikverket_train/__init__.py
|
||||
homeassistant/components/trafikverket_train/coordinator.py
|
||||
homeassistant/components/trafikverket_train/sensor.py
|
||||
homeassistant/components/trafikverket_train/util.py
|
||||
homeassistant/components/trafikverket_weatherstation/__init__.py
|
||||
homeassistant/components/trafikverket_weatherstation/coordinator.py
|
||||
homeassistant/components/trafikverket_weatherstation/sensor.py
|
||||
homeassistant/components/transmission/__init__.py
|
||||
homeassistant/components/transmission/sensor.py
|
||||
homeassistant/components/transmission/switch.py
|
||||
homeassistant/components/travisci/sensor.py
|
||||
@@ -1482,12 +1403,6 @@ omit =
|
||||
homeassistant/components/vlc/media_player.py
|
||||
homeassistant/components/vlc_telnet/__init__.py
|
||||
homeassistant/components/vlc_telnet/media_player.py
|
||||
homeassistant/components/vodafone_station/__init__.py
|
||||
homeassistant/components/vodafone_station/button.py
|
||||
homeassistant/components/vodafone_station/const.py
|
||||
homeassistant/components/vodafone_station/coordinator.py
|
||||
homeassistant/components/vodafone_station/device_tracker.py
|
||||
homeassistant/components/vodafone_station/sensor.py
|
||||
homeassistant/components/volkszaehler/sensor.py
|
||||
homeassistant/components/volumio/__init__.py
|
||||
homeassistant/components/volumio/browse_media.py
|
||||
@@ -1508,15 +1423,11 @@ omit =
|
||||
homeassistant/components/watson_tts/tts.py
|
||||
homeassistant/components/watttime/__init__.py
|
||||
homeassistant/components/watttime/sensor.py
|
||||
homeassistant/components/weatherflow/__init__.py
|
||||
homeassistant/components/weatherflow/const.py
|
||||
homeassistant/components/weatherflow/sensor.py
|
||||
homeassistant/components/wiffi/__init__.py
|
||||
homeassistant/components/wiffi/binary_sensor.py
|
||||
homeassistant/components/wiffi/sensor.py
|
||||
homeassistant/components/wiffi/wiffi_strings.py
|
||||
homeassistant/components/wirelesstag/*
|
||||
homeassistant/components/withings/api.py
|
||||
homeassistant/components/wolflink/__init__.py
|
||||
homeassistant/components/wolflink/sensor.py
|
||||
homeassistant/components/worldtidesinfo/sensor.py
|
||||
@@ -1561,6 +1472,7 @@ omit =
|
||||
homeassistant/components/yale_smart_alarm/alarm_control_panel.py
|
||||
homeassistant/components/yale_smart_alarm/binary_sensor.py
|
||||
homeassistant/components/yale_smart_alarm/button.py
|
||||
homeassistant/components/yale_smart_alarm/coordinator.py
|
||||
homeassistant/components/yale_smart_alarm/entity.py
|
||||
homeassistant/components/yale_smart_alarm/lock.py
|
||||
homeassistant/components/yalexs_ble/__init__.py
|
||||
@@ -1575,9 +1487,6 @@ omit =
|
||||
homeassistant/components/yamaha_musiccast/select.py
|
||||
homeassistant/components/yamaha_musiccast/switch.py
|
||||
homeassistant/components/yandex_transport/sensor.py
|
||||
homeassistant/components/yardian/__init__.py
|
||||
homeassistant/components/yardian/coordinator.py
|
||||
homeassistant/components/yardian/switch.py
|
||||
homeassistant/components/yeelightsunflower/light.py
|
||||
homeassistant/components/yi/camera.py
|
||||
homeassistant/components/yolink/__init__.py
|
||||
|
||||
@@ -7,46 +7,42 @@
|
||||
"containerEnv": { "DEVCONTAINER": "1" },
|
||||
"appPort": ["8123:8123"],
|
||||
"runArgs": ["-e", "GIT_EDITOR=code --wait"],
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": [
|
||||
"ms-python.vscode-pylance",
|
||||
"visualstudioexptteam.vscodeintellicode",
|
||||
"redhat.vscode-yaml",
|
||||
"esbenp.prettier-vscode",
|
||||
"GitHub.vscode-pull-request-github"
|
||||
],
|
||||
// Please keep this file in sync with settings in home-assistant/.vscode/settings.default.json
|
||||
"settings": {
|
||||
"python.pythonPath": "/usr/local/bin/python",
|
||||
"python.linting.enabled": true,
|
||||
"python.linting.pylintEnabled": true,
|
||||
"python.formatting.blackPath": "/usr/local/bin/black",
|
||||
"python.linting.pycodestylePath": "/usr/local/bin/pycodestyle",
|
||||
"python.linting.pydocstylePath": "/usr/local/bin/pydocstyle",
|
||||
"python.linting.mypyPath": "/usr/local/bin/mypy",
|
||||
"python.linting.pylintPath": "/usr/local/bin/pylint",
|
||||
"python.formatting.provider": "black",
|
||||
"python.testing.pytestArgs": ["--no-cov"],
|
||||
"editor.formatOnPaste": false,
|
||||
"editor.formatOnSave": true,
|
||||
"editor.formatOnType": true,
|
||||
"files.trimTrailingWhitespace": true,
|
||||
"terminal.integrated.profiles.linux": {
|
||||
"zsh": {
|
||||
"path": "/usr/bin/zsh"
|
||||
}
|
||||
},
|
||||
"terminal.integrated.defaultProfile.linux": "zsh",
|
||||
"yaml.customTags": [
|
||||
"!input scalar",
|
||||
"!secret scalar",
|
||||
"!include_dir_named scalar",
|
||||
"!include_dir_list scalar",
|
||||
"!include_dir_merge_list scalar",
|
||||
"!include_dir_merge_named scalar"
|
||||
]
|
||||
"extensions": [
|
||||
"ms-python.vscode-pylance",
|
||||
"visualstudioexptteam.vscodeintellicode",
|
||||
"redhat.vscode-yaml",
|
||||
"esbenp.prettier-vscode",
|
||||
"GitHub.vscode-pull-request-github"
|
||||
],
|
||||
// Please keep this file in sync with settings in home-assistant/.vscode/settings.default.json
|
||||
"settings": {
|
||||
"python.pythonPath": "/usr/local/bin/python",
|
||||
"python.linting.enabled": true,
|
||||
"python.linting.pylintEnabled": true,
|
||||
"python.formatting.blackPath": "/usr/local/bin/black",
|
||||
"python.linting.pycodestylePath": "/usr/local/bin/pycodestyle",
|
||||
"python.linting.pydocstylePath": "/usr/local/bin/pydocstyle",
|
||||
"python.linting.mypyPath": "/usr/local/bin/mypy",
|
||||
"python.linting.pylintPath": "/usr/local/bin/pylint",
|
||||
"python.formatting.provider": "black",
|
||||
"python.testing.pytestArgs": ["--no-cov"],
|
||||
"editor.formatOnPaste": false,
|
||||
"editor.formatOnSave": true,
|
||||
"editor.formatOnType": true,
|
||||
"files.trimTrailingWhitespace": true,
|
||||
"terminal.integrated.profiles.linux": {
|
||||
"zsh": {
|
||||
"path": "/usr/bin/zsh"
|
||||
}
|
||||
}
|
||||
},
|
||||
"terminal.integrated.defaultProfile.linux": "zsh",
|
||||
"yaml.customTags": [
|
||||
"!input scalar",
|
||||
"!secret scalar",
|
||||
"!include_dir_named scalar",
|
||||
"!include_dir_list scalar",
|
||||
"!include_dir_merge_list scalar",
|
||||
"!include_dir_merge_named scalar"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
37
.github/workflows/builder.yml
vendored
37
.github/workflows/builder.yml
vendored
@@ -24,12 +24,12 @@ jobs:
|
||||
publish: ${{ steps.version.outputs.publish }}
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v4.1.0
|
||||
uses: actions/checkout@v3.5.3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
uses: actions/setup-python@v4.7.0
|
||||
uses: actions/setup-python@v4.6.1
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
|
||||
@@ -56,10 +56,10 @@ jobs:
|
||||
if: github.repository_owner == 'home-assistant' && needs.init.outputs.publish == 'true'
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v4.1.0
|
||||
uses: actions/checkout@v3.5.3
|
||||
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
uses: actions/setup-python@v4.7.0
|
||||
uses: actions/setup-python@v4.6.1
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
|
||||
@@ -98,7 +98,7 @@ jobs:
|
||||
arch: ${{ fromJson(needs.init.outputs.architectures) }}
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v4.1.0
|
||||
uses: actions/checkout@v3.5.3
|
||||
|
||||
- name: Download nightly wheels of frontend
|
||||
if: needs.init.outputs.channel == 'dev'
|
||||
@@ -124,7 +124,7 @@ jobs:
|
||||
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
if: needs.init.outputs.channel == 'dev'
|
||||
uses: actions/setup-python@v4.7.0
|
||||
uses: actions/setup-python@v4.6.1
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
|
||||
@@ -190,14 +190,14 @@ jobs:
|
||||
echo "${{ github.sha }};${{ github.ref }};${{ github.event_name }};${{ github.actor }}" > rootfs/OFFICIAL_IMAGE
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3.0.0
|
||||
uses: docker/login-action@v2.2.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build base image
|
||||
uses: home-assistant/builder@2023.09.0
|
||||
uses: home-assistant/builder@2023.06.1
|
||||
with:
|
||||
args: |
|
||||
$BUILD_ARGS \
|
||||
@@ -205,6 +205,8 @@ jobs:
|
||||
--cosign \
|
||||
--target /data \
|
||||
--generic ${{ needs.init.outputs.version }}
|
||||
env:
|
||||
CAS_API_KEY: ${{ secrets.CAS_TOKEN }}
|
||||
|
||||
- name: Archive translations
|
||||
shell: bash
|
||||
@@ -249,10 +251,9 @@ jobs:
|
||||
- raspberrypi4-64
|
||||
- tinker
|
||||
- yellow
|
||||
- green
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v4.1.0
|
||||
uses: actions/checkout@v3.5.3
|
||||
|
||||
- name: Set build additional args
|
||||
run: |
|
||||
@@ -266,20 +267,22 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3.0.0
|
||||
uses: docker/login-action@v2.2.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build base image
|
||||
uses: home-assistant/builder@2023.09.0
|
||||
uses: home-assistant/builder@2023.06.1
|
||||
with:
|
||||
args: |
|
||||
$BUILD_ARGS \
|
||||
--target /data/machine \
|
||||
--cosign \
|
||||
--machine "${{ needs.init.outputs.version }}=${{ matrix.machine }}"
|
||||
env:
|
||||
CAS_API_KEY: ${{ secrets.CAS_TOKEN }}
|
||||
|
||||
publish_ha:
|
||||
name: Publish version files
|
||||
@@ -289,7 +292,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v4.1.0
|
||||
uses: actions/checkout@v3.5.3
|
||||
|
||||
- name: Initialize git
|
||||
uses: home-assistant/actions/helpers/git-init@master
|
||||
@@ -327,21 +330,21 @@ jobs:
|
||||
id-token: write
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v4.1.0
|
||||
uses: actions/checkout@v3.5.3
|
||||
|
||||
- name: Install Cosign
|
||||
uses: sigstore/cosign-installer@v3.1.2
|
||||
uses: sigstore/cosign-installer@v3.1.1
|
||||
with:
|
||||
cosign-release: "v2.0.2"
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v3.0.0
|
||||
uses: docker/login-action@v2.2.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3.0.0
|
||||
uses: docker/login-action@v2.2.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
|
||||
234
.github/workflows/ci.yaml
vendored
234
.github/workflows/ci.yaml
vendored
@@ -19,10 +19,6 @@ on:
|
||||
description: "Skip pytest"
|
||||
default: false
|
||||
type: boolean
|
||||
skip-coverage:
|
||||
description: "Skip coverage"
|
||||
default: false
|
||||
type: boolean
|
||||
pylint-only:
|
||||
description: "Only run pylint"
|
||||
default: false
|
||||
@@ -35,11 +31,10 @@ on:
|
||||
env:
|
||||
CACHE_VERSION: 5
|
||||
PIP_CACHE_VERSION: 4
|
||||
MYPY_CACHE_VERSION: 5
|
||||
BLACK_CACHE_VERSION: 1
|
||||
HA_SHORT_VERSION: "2023.10"
|
||||
DEFAULT_PYTHON: "3.11"
|
||||
ALL_PYTHON_VERSIONS: "['3.11']"
|
||||
MYPY_CACHE_VERSION: 4
|
||||
HA_SHORT_VERSION: 2023.8
|
||||
DEFAULT_PYTHON: "3.10"
|
||||
ALL_PYTHON_VERSIONS: "['3.10', '3.11']"
|
||||
# 10.3 is the oldest supported version
|
||||
# - 10.3.32 is the version currently shipped with Synology (as of 17 Feb 2022)
|
||||
# 10.6 is the current long-term-support
|
||||
@@ -56,7 +51,6 @@ env:
|
||||
POSTGRESQL_VERSIONS: "['postgres:12.14','postgres:15.2']"
|
||||
PRE_COMMIT_CACHE: ~/.cache/pre-commit
|
||||
PIP_CACHE: /tmp/pip-cache
|
||||
BLACK_CACHE: /tmp/black-cache
|
||||
SQLALCHEMY_WARN_20: 1
|
||||
PYTHONASYNCIODEBUG: 1
|
||||
HASS_CI: 1
|
||||
@@ -85,11 +79,10 @@ jobs:
|
||||
test_groups: ${{ steps.info.outputs.test_groups }}
|
||||
tests_glob: ${{ steps.info.outputs.tests_glob }}
|
||||
tests: ${{ steps.info.outputs.tests }}
|
||||
skip_coverage: ${{ steps.info.outputs.skip_coverage }}
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.1.0
|
||||
uses: actions/checkout@v3.5.3
|
||||
- name: Generate partial Python venv restore key
|
||||
id: generate_python_cache_key
|
||||
run: >-
|
||||
@@ -134,7 +127,6 @@ jobs:
|
||||
test_group_count=10
|
||||
tests="[]"
|
||||
tests_glob=""
|
||||
skip_coverage=""
|
||||
|
||||
if [[ "${{ steps.integrations.outputs.changes }}" != "[]" ]];
|
||||
then
|
||||
@@ -184,12 +176,6 @@ jobs:
|
||||
test_full_suite="true"
|
||||
fi
|
||||
|
||||
if [[ "${{ github.event.inputs.skip-coverage }}" == "true" ]] \
|
||||
|| [[ "${{ contains(github.event.pull_request.labels.*.name, 'ci-skip-coverage') }}" == "true" ]];
|
||||
then
|
||||
skip_coverage="true"
|
||||
fi
|
||||
|
||||
# Output & sent to GitHub Actions
|
||||
echo "mariadb_groups: ${mariadb_groups}"
|
||||
echo "mariadb_groups=${mariadb_groups}" >> $GITHUB_OUTPUT
|
||||
@@ -209,8 +195,6 @@ jobs:
|
||||
echo "tests=${tests}" >> $GITHUB_OUTPUT
|
||||
echo "tests_glob: ${tests_glob}"
|
||||
echo "tests_glob=${tests_glob}" >> $GITHUB_OUTPUT
|
||||
echo "skip_coverage: ${skip_coverage}"
|
||||
echo "skip_coverage=${skip_coverage}" >> $GITHUB_OUTPUT
|
||||
|
||||
pre-commit:
|
||||
name: Prepare pre-commit base
|
||||
@@ -222,16 +206,16 @@ jobs:
|
||||
- info
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.1.0
|
||||
uses: actions/checkout@v3.5.3
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
id: python
|
||||
uses: actions/setup-python@v4.7.0
|
||||
uses: actions/setup-python@v4.6.1
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
check-latest: true
|
||||
- name: Restore base Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@v3.3.2
|
||||
uses: actions/cache@v3.3.1
|
||||
with:
|
||||
path: venv
|
||||
key: >-
|
||||
@@ -246,7 +230,7 @@ jobs:
|
||||
pip install "$(cat requirements_test.txt | grep pre-commit)"
|
||||
- name: Restore pre-commit environment from cache
|
||||
id: cache-precommit
|
||||
uses: actions/cache@v3.3.2
|
||||
uses: actions/cache@v3.3.1
|
||||
with:
|
||||
path: ${{ env.PRE_COMMIT_CACHE }}
|
||||
lookup-only: true
|
||||
@@ -267,23 +251,16 @@ jobs:
|
||||
- pre-commit
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.1.0
|
||||
uses: actions/checkout@v3.5.3
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
uses: actions/setup-python@v4.7.0
|
||||
uses: actions/setup-python@v4.6.1
|
||||
id: python
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
check-latest: true
|
||||
- name: Generate partial black restore key
|
||||
id: generate-black-key
|
||||
run: |
|
||||
black_version=$(cat requirements_test_pre_commit.txt | grep black | cut -d '=' -f 3)
|
||||
echo "version=$black_version" >> $GITHUB_OUTPUT
|
||||
echo "key=black-${{ env.BLACK_CACHE_VERSION }}-$black_version-${{
|
||||
env.HA_SHORT_VERSION }}-$(date -u '+%Y-%m-%dT%H:%M:%s')" >> $GITHUB_OUTPUT
|
||||
- name: Restore base Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@v3.3.2
|
||||
uses: actions/cache/restore@v3.3.1
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
@@ -292,36 +269,21 @@ jobs:
|
||||
needs.info.outputs.pre-commit_cache_key }}
|
||||
- name: Restore pre-commit environment from cache
|
||||
id: cache-precommit
|
||||
uses: actions/cache/restore@v3.3.2
|
||||
uses: actions/cache/restore@v3.3.1
|
||||
with:
|
||||
path: ${{ env.PRE_COMMIT_CACHE }}
|
||||
fail-on-cache-miss: true
|
||||
key: >-
|
||||
${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
|
||||
needs.info.outputs.pre-commit_cache_key }}
|
||||
- name: Restore black cache
|
||||
uses: actions/cache@v3.3.2
|
||||
with:
|
||||
path: ${{ env.BLACK_CACHE }}
|
||||
key: >-
|
||||
${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
|
||||
steps.generate-black-key.outputs.key }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ steps.python.outputs.python-version }}-black-${{
|
||||
env.BLACK_CACHE_VERSION }}-${{ steps.generate-black-key.outputs.version }}-${{
|
||||
env.HA_SHORT_VERSION }}-
|
||||
- name: Run black (fully)
|
||||
if: needs.info.outputs.test_full_suite == 'true'
|
||||
env:
|
||||
BLACK_CACHE_DIR: ${{ env.BLACK_CACHE }}
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
pre-commit run --hook-stage manual black --all-files --show-diff-on-failure
|
||||
- name: Run black (partially)
|
||||
if: needs.info.outputs.test_full_suite == 'false'
|
||||
shell: bash
|
||||
env:
|
||||
BLACK_CACHE_DIR: ${{ env.BLACK_CACHE }}
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
shopt -s globstar
|
||||
@@ -335,16 +297,16 @@ jobs:
|
||||
- pre-commit
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.1.0
|
||||
uses: actions/checkout@v3.5.3
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
uses: actions/setup-python@v4.7.0
|
||||
uses: actions/setup-python@v4.6.1
|
||||
id: python
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
check-latest: true
|
||||
- name: Restore base Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@v3.3.2
|
||||
uses: actions/cache/restore@v3.3.1
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
@@ -353,7 +315,7 @@ jobs:
|
||||
needs.info.outputs.pre-commit_cache_key }}
|
||||
- name: Restore pre-commit environment from cache
|
||||
id: cache-precommit
|
||||
uses: actions/cache/restore@v3.3.2
|
||||
uses: actions/cache/restore@v3.3.1
|
||||
with:
|
||||
path: ${{ env.PRE_COMMIT_CACHE }}
|
||||
fail-on-cache-miss: true
|
||||
@@ -384,16 +346,16 @@ jobs:
|
||||
- pre-commit
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.1.0
|
||||
uses: actions/checkout@v3.5.3
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
uses: actions/setup-python@v4.7.0
|
||||
uses: actions/setup-python@v4.6.1
|
||||
id: python
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
check-latest: true
|
||||
- name: Restore base Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@v3.3.2
|
||||
uses: actions/cache/restore@v3.3.1
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
@@ -402,7 +364,7 @@ jobs:
|
||||
needs.info.outputs.pre-commit_cache_key }}
|
||||
- name: Restore pre-commit environment from cache
|
||||
id: cache-precommit
|
||||
uses: actions/cache/restore@v3.3.2
|
||||
uses: actions/cache/restore@v3.3.1
|
||||
with:
|
||||
path: ${{ env.PRE_COMMIT_CACHE }}
|
||||
fail-on-cache-miss: true
|
||||
@@ -478,10 +440,10 @@ jobs:
|
||||
python-version: ${{ fromJSON(needs.info.outputs.python_versions) }}
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.1.0
|
||||
uses: actions/checkout@v3.5.3
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
id: python
|
||||
uses: actions/setup-python@v4.7.0
|
||||
uses: actions/setup-python@v4.6.1
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
check-latest: true
|
||||
@@ -492,7 +454,7 @@ jobs:
|
||||
env.HA_SHORT_VERSION }}-$(date -u '+%Y-%m-%dT%H:%M:%s')" >> $GITHUB_OUTPUT
|
||||
- name: Restore base Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@v3.3.2
|
||||
uses: actions/cache@v3.3.1
|
||||
with:
|
||||
path: venv
|
||||
lookup-only: true
|
||||
@@ -501,7 +463,7 @@ jobs:
|
||||
needs.info.outputs.python_cache_key }}
|
||||
- name: Restore pip wheel cache
|
||||
if: steps.cache-venv.outputs.cache-hit != 'true'
|
||||
uses: actions/cache@v3.3.2
|
||||
uses: actions/cache@v3.3.1
|
||||
with:
|
||||
path: ${{ env.PIP_CACHE }}
|
||||
key: >-
|
||||
@@ -530,9 +492,9 @@ jobs:
|
||||
python -m venv venv
|
||||
. venv/bin/activate
|
||||
python --version
|
||||
PIP_CACHE_DIR=$PIP_CACHE pip install -U "pip>=21.3.1" setuptools wheel
|
||||
PIP_CACHE_DIR=$PIP_CACHE pip install -r requirements_all.txt
|
||||
PIP_CACHE_DIR=$PIP_CACHE pip install -r requirements_test.txt
|
||||
pip install --cache-dir=$PIP_CACHE -U "pip>=21.3.1,<23.2" setuptools wheel
|
||||
pip install --cache-dir=$PIP_CACHE -r requirements_all.txt
|
||||
pip install --cache-dir=$PIP_CACHE -r requirements_test.txt
|
||||
pip install -e . --config-settings editable_mode=compat
|
||||
|
||||
hassfest:
|
||||
@@ -546,16 +508,16 @@ jobs:
|
||||
- base
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.1.0
|
||||
uses: actions/checkout@v3.5.3
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
id: python
|
||||
uses: actions/setup-python@v4.7.0
|
||||
uses: actions/setup-python@v4.6.1
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
check-latest: true
|
||||
- name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@v3.3.2
|
||||
uses: actions/cache/restore@v3.3.1
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
@@ -578,16 +540,16 @@ jobs:
|
||||
- base
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.1.0
|
||||
uses: actions/checkout@v3.5.3
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
id: python
|
||||
uses: actions/setup-python@v4.7.0
|
||||
uses: actions/setup-python@v4.6.1
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
check-latest: true
|
||||
- name: Restore base Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@v3.3.2
|
||||
uses: actions/cache/restore@v3.3.1
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
@@ -611,16 +573,16 @@ jobs:
|
||||
- base
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.1.0
|
||||
uses: actions/checkout@v3.5.3
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
id: python
|
||||
uses: actions/setup-python@v4.7.0
|
||||
uses: actions/setup-python@v4.6.1
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
check-latest: true
|
||||
- name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@v3.3.2
|
||||
uses: actions/cache/restore@v3.3.1
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
@@ -655,10 +617,10 @@ jobs:
|
||||
- base
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.1.0
|
||||
uses: actions/checkout@v3.5.3
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
id: python
|
||||
uses: actions/setup-python@v4.7.0
|
||||
uses: actions/setup-python@v4.6.1
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
check-latest: true
|
||||
@@ -671,7 +633,7 @@ jobs:
|
||||
env.HA_SHORT_VERSION }}-$(date -u '+%Y-%m-%dT%H:%M:%s')" >> $GITHUB_OUTPUT
|
||||
- name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@v3.3.2
|
||||
uses: actions/cache/restore@v3.3.1
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
@@ -679,7 +641,7 @@ jobs:
|
||||
${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
|
||||
needs.info.outputs.python_cache_key }}
|
||||
- name: Restore mypy cache
|
||||
uses: actions/cache@v3.3.2
|
||||
uses: actions/cache@v3.3.1
|
||||
with:
|
||||
path: .mypy_cache
|
||||
key: >-
|
||||
@@ -737,16 +699,16 @@ jobs:
|
||||
bluez \
|
||||
ffmpeg
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.1.0
|
||||
uses: actions/checkout@v3.5.3
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
id: python
|
||||
uses: actions/setup-python@v4.7.0
|
||||
uses: actions/setup-python@v4.6.1
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
check-latest: true
|
||||
- name: Restore full Python ${{ matrix.python-version }} virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@v3.3.2
|
||||
uses: actions/cache/restore@v3.3.1
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
@@ -772,19 +734,9 @@ jobs:
|
||||
- name: Run pytest (fully)
|
||||
if: needs.info.outputs.test_full_suite == 'true'
|
||||
timeout-minutes: 60
|
||||
id: pytest-full
|
||||
env:
|
||||
PYTHONDONTWRITEBYTECODE: 1
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
python --version
|
||||
set -o pipefail
|
||||
cov_params=()
|
||||
if [[ "${{ needs.info.outputs.skip_coverage }}" != "true" ]]; then
|
||||
cov_params+=(--cov="homeassistant")
|
||||
cov_params+=(--cov-report=xml)
|
||||
fi
|
||||
|
||||
python3 -X dev -m pytest \
|
||||
-qq \
|
||||
--timeout=9 \
|
||||
@@ -793,54 +745,37 @@ jobs:
|
||||
--dist=loadfile \
|
||||
--test-group-count ${{ needs.info.outputs.test_group_count }} \
|
||||
--test-group=${{ matrix.group }} \
|
||||
${cov_params[@]} \
|
||||
--cov="homeassistant" \
|
||||
--cov-report=xml \
|
||||
-o console_output_style=count \
|
||||
-p no:sugar \
|
||||
tests \
|
||||
2>&1 | tee pytest-${{ matrix.python-version }}-${{ matrix.group }}.txt
|
||||
tests
|
||||
- name: Run pytest (partially)
|
||||
if: needs.info.outputs.test_full_suite == 'false'
|
||||
timeout-minutes: 10
|
||||
id: pytest-partial
|
||||
shell: bash
|
||||
env:
|
||||
PYTHONDONTWRITEBYTECODE: 1
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
python --version
|
||||
set -o pipefail
|
||||
|
||||
if [[ ! -f "tests/components/${{ matrix.group }}/__init__.py" ]]; then
|
||||
echo "::error:: missing file tests/components/${{ matrix.group }}/__init__.py"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cov_params=()
|
||||
if [[ "${{ needs.info.outputs.skip_coverage }}" != "true" ]]; then
|
||||
cov_params+=(--cov="homeassistant.components.${{ matrix.group }}")
|
||||
cov_params+=(--cov-report=xml)
|
||||
cov_params+=(--cov-report=term-missing)
|
||||
fi
|
||||
|
||||
python3 -X dev -m pytest \
|
||||
-qq \
|
||||
--timeout=9 \
|
||||
-n auto \
|
||||
${cov_params[@]} \
|
||||
--cov="homeassistant.components.${{ matrix.group }}" \
|
||||
--cov-report=xml \
|
||||
--cov-report=term-missing \
|
||||
-o console_output_style=count \
|
||||
--durations=0 \
|
||||
--durations-min=1 \
|
||||
-p no:sugar \
|
||||
tests/components/${{ matrix.group }} \
|
||||
2>&1 | tee pytest-${{ matrix.python-version }}-${{ matrix.group }}.txt
|
||||
- name: Upload pytest output
|
||||
if: success() || failure() && (steps.pytest-full.conclusion == 'failure' || steps.pytest-partial.conclusion == 'failure')
|
||||
uses: actions/upload-artifact@v3.1.2
|
||||
with:
|
||||
name: pytest-${{ github.run_number }}
|
||||
path: pytest-*.txt
|
||||
tests/components/${{ matrix.group }}
|
||||
- name: Upload coverage artifact
|
||||
if: needs.info.outputs.skip_coverage != 'true'
|
||||
uses: actions/upload-artifact@v3.1.2
|
||||
with:
|
||||
name: coverage-${{ matrix.python-version }}-${{ matrix.group }}
|
||||
@@ -889,16 +824,16 @@ jobs:
|
||||
ffmpeg \
|
||||
libmariadb-dev-compat
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.1.0
|
||||
uses: actions/checkout@v3.5.3
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
id: python
|
||||
uses: actions/setup-python@v4.7.0
|
||||
uses: actions/setup-python@v4.6.1
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
check-latest: true
|
||||
- name: Restore full Python ${{ matrix.python-version }} virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@v3.3.2
|
||||
uses: actions/cache/restore@v3.3.1
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
@@ -927,27 +862,18 @@ jobs:
|
||||
python3 -m script.translations develop --all
|
||||
- name: Run pytest (partially)
|
||||
timeout-minutes: 20
|
||||
id: pytest-partial
|
||||
shell: bash
|
||||
env:
|
||||
PYTHONDONTWRITEBYTECODE: 1
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
python --version
|
||||
set -o pipefail
|
||||
mariadb=$(echo "${{ matrix.mariadb-group }}" | sed "s/:/-/g")
|
||||
cov_params=()
|
||||
if [[ "${{ needs.info.outputs.skip_coverage }}" != "true" ]]; then
|
||||
cov_params+=(--cov="homeassistant.components.recorder")
|
||||
cov_params+=(--cov-report=xml)
|
||||
cov_params+=(--cov-report=term-missing)
|
||||
fi
|
||||
|
||||
python3 -X dev -m pytest \
|
||||
-qq \
|
||||
--timeout=20 \
|
||||
-n 1 \
|
||||
${cov_params[@]} \
|
||||
--cov="homeassistant.components.recorder" \
|
||||
--cov-report=xml \
|
||||
--cov-report=term-missing \
|
||||
-o console_output_style=count \
|
||||
--durations=10 \
|
||||
-p no:sugar \
|
||||
@@ -955,16 +881,8 @@ jobs:
|
||||
tests/components/history \
|
||||
tests/components/logbook \
|
||||
tests/components/recorder \
|
||||
tests/components/sensor \
|
||||
2>&1 | tee pytest-${{ matrix.python-version }}-${mariadb}.txt
|
||||
- name: Upload pytest output
|
||||
if: success() || failure() && steps.pytest-partial.conclusion == 'failure'
|
||||
uses: actions/upload-artifact@v3.1.2
|
||||
with:
|
||||
name: pytest-${{ github.run_number }}
|
||||
path: pytest-*.txt
|
||||
tests/components/sensor
|
||||
- name: Upload coverage artifact
|
||||
if: needs.info.outputs.skip_coverage != 'true'
|
||||
uses: actions/upload-artifact@v3.1.2
|
||||
with:
|
||||
name: coverage-${{ matrix.python-version }}-mariadb
|
||||
@@ -1013,16 +931,16 @@ jobs:
|
||||
ffmpeg \
|
||||
postgresql-server-dev-14
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.1.0
|
||||
uses: actions/checkout@v3.5.3
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
id: python
|
||||
uses: actions/setup-python@v4.7.0
|
||||
uses: actions/setup-python@v4.6.1
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
check-latest: true
|
||||
- name: Restore full Python ${{ matrix.python-version }} virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@v3.3.2
|
||||
uses: actions/cache/restore@v3.3.1
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
@@ -1051,27 +969,18 @@ jobs:
|
||||
python3 -m script.translations develop --all
|
||||
- name: Run pytest (partially)
|
||||
timeout-minutes: 20
|
||||
id: pytest-partial
|
||||
shell: bash
|
||||
env:
|
||||
PYTHONDONTWRITEBYTECODE: 1
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
python --version
|
||||
set -o pipefail
|
||||
postgresql=$(echo "${{ matrix.postgresql-group }}" | sed "s/:/-/g")
|
||||
cov_params=()
|
||||
if [[ "${{ needs.info.outputs.skip_coverage }}" != "true" ]]; then
|
||||
cov_params+=(--cov="homeassistant.components.recorder")
|
||||
cov_params+=(--cov-report=xml)
|
||||
cov_params+=(--cov-report=term-missing)
|
||||
fi
|
||||
|
||||
python3 -X dev -m pytest \
|
||||
-qq \
|
||||
--timeout=9 \
|
||||
-n 1 \
|
||||
${cov_params[@]} \
|
||||
--cov="homeassistant.components.recorder" \
|
||||
--cov-report=xml \
|
||||
--cov-report=term-missing \
|
||||
-o console_output_style=count \
|
||||
--durations=0 \
|
||||
--durations-min=10 \
|
||||
@@ -1080,16 +989,8 @@ jobs:
|
||||
tests/components/history \
|
||||
tests/components/logbook \
|
||||
tests/components/recorder \
|
||||
tests/components/sensor \
|
||||
2>&1 | tee pytest-${{ matrix.python-version }}-${postgresql}.txt
|
||||
- name: Upload pytest output
|
||||
if: success() || failure() && steps.pytest-partial.conclusion == 'failure'
|
||||
uses: actions/upload-artifact@v3.1.2
|
||||
with:
|
||||
name: pytest-${{ github.run_number }}
|
||||
path: pytest-*.txt
|
||||
tests/components/sensor
|
||||
- name: Upload coverage artifact
|
||||
if: needs.info.outputs.skip_coverage != 'true'
|
||||
uses: actions/upload-artifact@v3.1.0
|
||||
with:
|
||||
name: coverage-${{ matrix.python-version }}-postgresql
|
||||
@@ -1100,7 +1001,6 @@ jobs:
|
||||
|
||||
coverage:
|
||||
name: Upload test coverage to Codecov
|
||||
if: needs.info.outputs.skip_coverage != 'true'
|
||||
runs-on: ubuntu-22.04
|
||||
needs:
|
||||
- info
|
||||
@@ -1108,7 +1008,7 @@ jobs:
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.1.0
|
||||
uses: actions/checkout@v3.5.3
|
||||
- name: Download all coverage artifacts
|
||||
uses: actions/download-artifact@v3
|
||||
- name: Upload coverage to Codecov (full coverage)
|
||||
@@ -1119,7 +1019,6 @@ jobs:
|
||||
with: |
|
||||
fail_ci_if_error: true
|
||||
flags: full-suite
|
||||
token: ${{ env.CODECOV_TOKEN }}
|
||||
attempt_limit: 5
|
||||
attempt_delay: 30000
|
||||
- name: Upload coverage to Codecov (partial coverage)
|
||||
@@ -1129,6 +1028,5 @@ jobs:
|
||||
action: codecov/codecov-action@v3.1.3
|
||||
with: |
|
||||
fail_ci_if_error: true
|
||||
token: ${{ env.CODECOV_TOKEN }}
|
||||
attempt_limit: 5
|
||||
attempt_delay: 30000
|
||||
|
||||
2
.github/workflows/stale.yml
vendored
2
.github/workflows/stale.yml
vendored
@@ -42,7 +42,7 @@ jobs:
|
||||
id: token
|
||||
# Pinned to a specific version of the action for security reasons
|
||||
# v1.7.0
|
||||
uses: tibdex/github-app-token@3beb63f4bd073e61482598c45c71c1019b59b73a
|
||||
uses: tibdex/github-app-token@b62528385c34dbc9f38e5f4225ac829252d1ea92
|
||||
with:
|
||||
app_id: ${{ secrets.ISSUE_TRIAGE_APP_ID }}
|
||||
private_key: ${{ secrets.ISSUE_TRIAGE_APP_PEM }}
|
||||
|
||||
4
.github/workflows/translations.yml
vendored
4
.github/workflows/translations.yml
vendored
@@ -19,10 +19,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v4.1.0
|
||||
uses: actions/checkout@v3.5.3
|
||||
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
uses: actions/setup-python@v4.7.0
|
||||
uses: actions/setup-python@v4.6.1
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
|
||||
|
||||
22
.github/workflows/wheels.yml
vendored
22
.github/workflows/wheels.yml
vendored
@@ -26,7 +26,7 @@ jobs:
|
||||
architectures: ${{ steps.info.outputs.architectures }}
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v4.1.0
|
||||
uses: actions/checkout@v3.5.3
|
||||
|
||||
- name: Get information
|
||||
id: info
|
||||
@@ -56,7 +56,7 @@ jobs:
|
||||
echo "CI_BUILD=1"
|
||||
echo "ENABLE_HEADLESS=1"
|
||||
|
||||
# Use C-Extension for SQLAlchemy
|
||||
# Use C-Extension for sqlalchemy
|
||||
echo "REQUIRE_SQLALCHEMY_CEXT=1"
|
||||
) > .env_file
|
||||
|
||||
@@ -84,7 +84,7 @@ jobs:
|
||||
arch: ${{ fromJson(needs.init.outputs.architectures) }}
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v4.1.0
|
||||
uses: actions/checkout@v3.5.3
|
||||
|
||||
- name: Download env_file
|
||||
uses: actions/download-artifact@v3
|
||||
@@ -97,7 +97,7 @@ jobs:
|
||||
name: requirements_diff
|
||||
|
||||
- name: Build wheels
|
||||
uses: home-assistant/wheels@2023.09.1
|
||||
uses: home-assistant/wheels@2023.04.0
|
||||
with:
|
||||
abi: ${{ matrix.abi }}
|
||||
tag: musllinux_1_2
|
||||
@@ -122,7 +122,7 @@ jobs:
|
||||
arch: ${{ fromJson(needs.init.outputs.architectures) }}
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v4.1.0
|
||||
uses: actions/checkout@v3.5.3
|
||||
|
||||
- name: Download env_file
|
||||
uses: actions/download-artifact@v3
|
||||
@@ -178,7 +178,7 @@ jobs:
|
||||
sed -i "/numpy/d" homeassistant/package_constraints.txt
|
||||
|
||||
- name: Build wheels (part 1)
|
||||
uses: home-assistant/wheels@2023.09.1
|
||||
uses: home-assistant/wheels@2023.04.0
|
||||
with:
|
||||
abi: ${{ matrix.abi }}
|
||||
tag: musllinux_1_2
|
||||
@@ -186,13 +186,13 @@ jobs:
|
||||
wheels-key: ${{ secrets.WHEELS_KEY }}
|
||||
env-file: true
|
||||
apk: "bluez-dev;libffi-dev;openssl-dev;glib-dev;eudev-dev;libxml2-dev;libxslt-dev;libpng-dev;libjpeg-turbo-dev;tiff-dev;cups-dev;gmp-dev;mpfr-dev;mpc1-dev;ffmpeg-dev;gammu-dev;yaml-dev;openblas-dev;fftw-dev;lapack-dev;gfortran;blas-dev;eigen-dev;freetype-dev;glew-dev;harfbuzz-dev;hdf5-dev;libdc1394-dev;libtbb-dev;mesa-dev;openexr-dev;openjpeg-dev;uchardet-dev"
|
||||
skip-binary: aiohttp;charset-normalizer;grpcio;SQLAlchemy;protobuf
|
||||
skip-binary: aiohttp;grpcio;sqlalchemy;protobuf
|
||||
constraints: "homeassistant/package_constraints.txt"
|
||||
requirements-diff: "requirements_diff.txt"
|
||||
requirements: "requirements_all.txtaa"
|
||||
|
||||
- name: Build wheels (part 2)
|
||||
uses: home-assistant/wheels@2023.09.1
|
||||
uses: home-assistant/wheels@2023.04.0
|
||||
with:
|
||||
abi: ${{ matrix.abi }}
|
||||
tag: musllinux_1_2
|
||||
@@ -200,13 +200,13 @@ jobs:
|
||||
wheels-key: ${{ secrets.WHEELS_KEY }}
|
||||
env-file: true
|
||||
apk: "bluez-dev;libffi-dev;openssl-dev;glib-dev;eudev-dev;libxml2-dev;libxslt-dev;libpng-dev;libjpeg-turbo-dev;tiff-dev;cups-dev;gmp-dev;mpfr-dev;mpc1-dev;ffmpeg-dev;gammu-dev;yaml-dev;openblas-dev;fftw-dev;lapack-dev;gfortran;blas-dev;eigen-dev;freetype-dev;glew-dev;harfbuzz-dev;hdf5-dev;libdc1394-dev;libtbb-dev;mesa-dev;openexr-dev;openjpeg-dev;uchardet-dev"
|
||||
skip-binary: aiohttp;charset-normalizer;grpcio;SQLAlchemy;protobuf
|
||||
skip-binary: aiohttp;grpcio;sqlalchemy;protobuf
|
||||
constraints: "homeassistant/package_constraints.txt"
|
||||
requirements-diff: "requirements_diff.txt"
|
||||
requirements: "requirements_all.txtab"
|
||||
|
||||
- name: Build wheels (part 3)
|
||||
uses: home-assistant/wheels@2023.09.1
|
||||
uses: home-assistant/wheels@2023.04.0
|
||||
with:
|
||||
abi: ${{ matrix.abi }}
|
||||
tag: musllinux_1_2
|
||||
@@ -214,7 +214,7 @@ jobs:
|
||||
wheels-key: ${{ secrets.WHEELS_KEY }}
|
||||
env-file: true
|
||||
apk: "bluez-dev;libffi-dev;openssl-dev;glib-dev;eudev-dev;libxml2-dev;libxslt-dev;libpng-dev;libjpeg-turbo-dev;tiff-dev;cups-dev;gmp-dev;mpfr-dev;mpc1-dev;ffmpeg-dev;gammu-dev;yaml-dev;openblas-dev;fftw-dev;lapack-dev;gfortran;blas-dev;eigen-dev;freetype-dev;glew-dev;harfbuzz-dev;hdf5-dev;libdc1394-dev;libtbb-dev;mesa-dev;openexr-dev;openjpeg-dev;uchardet-dev"
|
||||
skip-binary: aiohttp;charset-normalizer;grpcio;SQLAlchemy;protobuf
|
||||
skip-binary: aiohttp;grpcio;sqlalchemy;protobuf
|
||||
constraints: "homeassistant/package_constraints.txt"
|
||||
requirements-diff: "requirements_diff.txt"
|
||||
requirements: "requirements_all.txtac"
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -67,7 +67,6 @@ htmlcov/
|
||||
test-reports/
|
||||
test-results.xml
|
||||
test-output.xml
|
||||
pytest-*.txt
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.0.289
|
||||
rev: v0.0.272
|
||||
hooks:
|
||||
- id: ruff
|
||||
args:
|
||||
- --fix
|
||||
- repo: https://github.com/psf/black-pre-commit-mirror
|
||||
rev: 23.9.1
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 23.3.0
|
||||
hooks:
|
||||
- id: black
|
||||
args:
|
||||
@@ -17,11 +17,11 @@ repos:
|
||||
hooks:
|
||||
- id: codespell
|
||||
args:
|
||||
- --ignore-words-list=additionals,alle,alot,bund,currenty,datas,farenheit,falsy,fo,haa,hass,iif,incomfort,ines,ist,nam,nd,pres,pullrequests,resset,rime,ser,serie,te,technik,ue,unsecure,withing,zar
|
||||
- --skip="./.*,*.csv,*.json,*.ambr"
|
||||
- --ignore-words-list=additionals,alle,alot,ba,bre,bund,currenty,datas,dof,dur,ether,farenheit,falsy,fo,haa,hass,hist,iam,iff,iif,incomfort,ines,ist,lightsensor,mut,nam,nd,pres,pullrequests,referer,resset,rime,ser,serie,sur,te,technik,ue,uint,unsecure,visability,wan,wanna,withing,zar
|
||||
- --skip="./.*,*.csv,*.json"
|
||||
- --quiet-level=2
|
||||
exclude_types: [csv, json]
|
||||
exclude: ^tests/fixtures/|homeassistant/generated/|tests/components/.*/snapshots/
|
||||
exclude: ^tests/fixtures/|homeassistant/generated/
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.4.0
|
||||
hooks:
|
||||
@@ -35,7 +35,7 @@ repos:
|
||||
- --branch=master
|
||||
- --branch=rc
|
||||
- repo: https://github.com/adrienverge/yamllint.git
|
||||
rev: v1.32.0
|
||||
rev: v1.28.0
|
||||
hooks:
|
||||
- id: yamllint
|
||||
- repo: https://github.com/pre-commit/mirrors-prettier
|
||||
@@ -43,7 +43,7 @@ repos:
|
||||
hooks:
|
||||
- id: prettier
|
||||
- repo: https://github.com/cdce8p/python-typing-update
|
||||
rev: v0.6.0
|
||||
rev: v0.5.0
|
||||
hooks:
|
||||
# Run `python-typing-update` hook manually from time to time
|
||||
# to update python typing syntax.
|
||||
@@ -52,7 +52,7 @@ repos:
|
||||
- id: python-typing-update
|
||||
stages: [manual]
|
||||
args:
|
||||
- --py311-plus
|
||||
- --py310-plus
|
||||
- --force
|
||||
- --keep-updates
|
||||
files: ^(homeassistant|tests|script)/.+\.py$
|
||||
|
||||
@@ -53,7 +53,6 @@ homeassistant.components.airzone_cloud.*
|
||||
homeassistant.components.aladdin_connect.*
|
||||
homeassistant.components.alarm_control_panel.*
|
||||
homeassistant.components.alert.*
|
||||
homeassistant.components.alexa.*
|
||||
homeassistant.components.amazon_polly.*
|
||||
homeassistant.components.ambient_station.*
|
||||
homeassistant.components.amcrest.*
|
||||
@@ -88,7 +87,6 @@ homeassistant.components.camera.*
|
||||
homeassistant.components.canary.*
|
||||
homeassistant.components.clickatell.*
|
||||
homeassistant.components.clicksend.*
|
||||
homeassistant.components.climate.*
|
||||
homeassistant.components.cloud.*
|
||||
homeassistant.components.configurator.*
|
||||
homeassistant.components.cover.*
|
||||
@@ -105,19 +103,16 @@ homeassistant.components.dhcp.*
|
||||
homeassistant.components.diagnostics.*
|
||||
homeassistant.components.dlna_dmr.*
|
||||
homeassistant.components.dnsip.*
|
||||
homeassistant.components.doorbird.*
|
||||
homeassistant.components.dormakaba_dkey.*
|
||||
homeassistant.components.dsmr.*
|
||||
homeassistant.components.dunehd.*
|
||||
homeassistant.components.efergy.*
|
||||
homeassistant.components.electrasmart.*
|
||||
homeassistant.components.electric_kiwi.*
|
||||
homeassistant.components.elgato.*
|
||||
homeassistant.components.elkm1.*
|
||||
homeassistant.components.emulated_hue.*
|
||||
homeassistant.components.energy.*
|
||||
homeassistant.components.esphome.*
|
||||
homeassistant.components.event.*
|
||||
homeassistant.components.evil_genius_labs.*
|
||||
homeassistant.components.fan.*
|
||||
homeassistant.components.fastdotcom.*
|
||||
@@ -137,11 +132,9 @@ homeassistant.components.fully_kiosk.*
|
||||
homeassistant.components.geo_location.*
|
||||
homeassistant.components.geocaching.*
|
||||
homeassistant.components.gios.*
|
||||
homeassistant.components.glances.*
|
||||
homeassistant.components.goalzero.*
|
||||
homeassistant.components.google.*
|
||||
homeassistant.components.google_sheets.*
|
||||
homeassistant.components.gpsd.*
|
||||
homeassistant.components.greeneye_monitor.*
|
||||
homeassistant.components.group.*
|
||||
homeassistant.components.guardian.*
|
||||
@@ -152,7 +145,6 @@ homeassistant.components.history.*
|
||||
homeassistant.components.homeassistant.exposed_entities
|
||||
homeassistant.components.homeassistant.triggers.event
|
||||
homeassistant.components.homeassistant_alerts.*
|
||||
homeassistant.components.homeassistant_green.*
|
||||
homeassistant.components.homeassistant_hardware.*
|
||||
homeassistant.components.homeassistant_sky_connect.*
|
||||
homeassistant.components.homeassistant_yellow.*
|
||||
@@ -180,7 +172,6 @@ homeassistant.components.huawei_lte.*
|
||||
homeassistant.components.hydrawise.*
|
||||
homeassistant.components.hyperion.*
|
||||
homeassistant.components.ibeacon.*
|
||||
homeassistant.components.idasen_desk.*
|
||||
homeassistant.components.image.*
|
||||
homeassistant.components.image_processing.*
|
||||
homeassistant.components.image_upload.*
|
||||
@@ -188,9 +179,7 @@ homeassistant.components.imap.*
|
||||
homeassistant.components.input_button.*
|
||||
homeassistant.components.input_select.*
|
||||
homeassistant.components.integration.*
|
||||
homeassistant.components.ipp.*
|
||||
homeassistant.components.iqvia.*
|
||||
homeassistant.components.islamic_prayer_times.*
|
||||
homeassistant.components.isy994.*
|
||||
homeassistant.components.jellyfin.*
|
||||
homeassistant.components.jewish_calendar.*
|
||||
@@ -202,7 +191,6 @@ homeassistant.components.lacrosse.*
|
||||
homeassistant.components.lacrosse_view.*
|
||||
homeassistant.components.lametric.*
|
||||
homeassistant.components.laundrify.*
|
||||
homeassistant.components.lawn_mower.*
|
||||
homeassistant.components.lcn.*
|
||||
homeassistant.components.ld2410_ble.*
|
||||
homeassistant.components.lidarr.*
|
||||
@@ -214,14 +202,11 @@ homeassistant.components.local_ip.*
|
||||
homeassistant.components.lock.*
|
||||
homeassistant.components.logbook.*
|
||||
homeassistant.components.logger.*
|
||||
homeassistant.components.london_underground.*
|
||||
homeassistant.components.lookin.*
|
||||
homeassistant.components.luftdaten.*
|
||||
homeassistant.components.mailbox.*
|
||||
homeassistant.components.mastodon.*
|
||||
homeassistant.components.matrix.*
|
||||
homeassistant.components.matter.*
|
||||
homeassistant.components.media_extractor.*
|
||||
homeassistant.components.media_player.*
|
||||
homeassistant.components.media_source.*
|
||||
homeassistant.components.metoffice.*
|
||||
@@ -260,10 +245,7 @@ homeassistant.components.peco.*
|
||||
homeassistant.components.persistent_notification.*
|
||||
homeassistant.components.pi_hole.*
|
||||
homeassistant.components.ping.*
|
||||
homeassistant.components.plugwise.*
|
||||
homeassistant.components.poolsense.*
|
||||
homeassistant.components.powerwall.*
|
||||
homeassistant.components.private_ble_device.*
|
||||
homeassistant.components.proximity.*
|
||||
homeassistant.components.prusalink.*
|
||||
homeassistant.components.pure_energie.*
|
||||
@@ -312,7 +294,6 @@ homeassistant.components.sonarr.*
|
||||
homeassistant.components.speedtestdotnet.*
|
||||
homeassistant.components.sql.*
|
||||
homeassistant.components.ssdp.*
|
||||
homeassistant.components.starlink.*
|
||||
homeassistant.components.statistics.*
|
||||
homeassistant.components.steamist.*
|
||||
homeassistant.components.stookalert.*
|
||||
@@ -321,7 +302,6 @@ homeassistant.components.sun.*
|
||||
homeassistant.components.surepetcare.*
|
||||
homeassistant.components.switch.*
|
||||
homeassistant.components.switchbee.*
|
||||
homeassistant.components.switchbot_cloud.*
|
||||
homeassistant.components.switcher_kis.*
|
||||
homeassistant.components.synology_dsm.*
|
||||
homeassistant.components.systemmonitor.*
|
||||
@@ -339,11 +319,9 @@ homeassistant.components.tplink.*
|
||||
homeassistant.components.tplink_omada.*
|
||||
homeassistant.components.tractive.*
|
||||
homeassistant.components.tradfri.*
|
||||
homeassistant.components.trafikverket_camera.*
|
||||
homeassistant.components.trafikverket_ferry.*
|
||||
homeassistant.components.trafikverket_train.*
|
||||
homeassistant.components.trafikverket_weatherstation.*
|
||||
homeassistant.components.trend.*
|
||||
homeassistant.components.tts.*
|
||||
homeassistant.components.twentemilieu.*
|
||||
homeassistant.components.unifi.*
|
||||
|
||||
148
CODEOWNERS
148
CODEOWNERS
@@ -19,6 +19,8 @@ build.json @home-assistant/supervisor
|
||||
|
||||
# Other code
|
||||
/homeassistant/scripts/check_config.py @kellerza
|
||||
/homeassistant/const.py @epenet
|
||||
/homeassistant/util/ @epenet
|
||||
|
||||
# Integrations
|
||||
/homeassistant/components/abode/ @shred86
|
||||
@@ -47,10 +49,10 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/airq/ @Sibgatulin @dl2080
|
||||
/homeassistant/components/airthings/ @danielhiversen
|
||||
/tests/components/airthings/ @danielhiversen
|
||||
/homeassistant/components/airthings_ble/ @vincegio @LaStrada
|
||||
/tests/components/airthings_ble/ @vincegio @LaStrada
|
||||
/homeassistant/components/airtouch4/ @samsinnamon
|
||||
/tests/components/airtouch4/ @samsinnamon
|
||||
/homeassistant/components/airthings_ble/ @vincegio
|
||||
/tests/components/airthings_ble/ @vincegio
|
||||
/homeassistant/components/airtouch4/ @LonePurpleWolf
|
||||
/tests/components/airtouch4/ @LonePurpleWolf
|
||||
/homeassistant/components/airvisual/ @bachya
|
||||
/tests/components/airvisual/ @bachya
|
||||
/homeassistant/components/airvisual_pro/ @bachya
|
||||
@@ -193,8 +195,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/camera/ @home-assistant/core
|
||||
/homeassistant/components/cast/ @emontnemery
|
||||
/tests/components/cast/ @emontnemery
|
||||
/homeassistant/components/cert_expiry/ @jjlawren
|
||||
/tests/components/cert_expiry/ @jjlawren
|
||||
/homeassistant/components/cert_expiry/ @Cereal2nd @jjlawren
|
||||
/tests/components/cert_expiry/ @Cereal2nd @jjlawren
|
||||
/homeassistant/components/circuit/ @braam
|
||||
/homeassistant/components/cisco_ios/ @fbradyirl
|
||||
/homeassistant/components/cisco_mobility_express/ @fbradyirl
|
||||
@@ -205,14 +207,10 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/cloud/ @home-assistant/cloud
|
||||
/homeassistant/components/cloudflare/ @ludeeus @ctalkington
|
||||
/tests/components/cloudflare/ @ludeeus @ctalkington
|
||||
/homeassistant/components/co2signal/ @jpbede
|
||||
/tests/components/co2signal/ @jpbede
|
||||
/homeassistant/components/coinbase/ @tombrien
|
||||
/tests/components/coinbase/ @tombrien
|
||||
/homeassistant/components/color_extractor/ @GenericStudent
|
||||
/tests/components/color_extractor/ @GenericStudent
|
||||
/homeassistant/components/comelit/ @chemelli74
|
||||
/tests/components/comelit/ @chemelli74
|
||||
/homeassistant/components/comfoconnect/ @michaelarnauts
|
||||
/tests/components/comfoconnect/ @michaelarnauts
|
||||
/homeassistant/components/command_line/ @gjohansson-ST
|
||||
@@ -279,6 +277,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/discord/ @tkdrob
|
||||
/homeassistant/components/discovergy/ @jpbede
|
||||
/tests/components/discovergy/ @jpbede
|
||||
/homeassistant/components/discovery/ @home-assistant/core
|
||||
/tests/components/discovery/ @home-assistant/core
|
||||
/homeassistant/components/dlink/ @tkdrob
|
||||
/tests/components/dlink/ @tkdrob
|
||||
/homeassistant/components/dlna_dmr/ @StevenLooman @chishm
|
||||
@@ -297,10 +297,10 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/dsmr/ @Robbie1221 @frenck
|
||||
/homeassistant/components/dsmr_reader/ @depl0y @glodenox
|
||||
/tests/components/dsmr_reader/ @depl0y @glodenox
|
||||
/homeassistant/components/duotecno/ @cereal2nd
|
||||
/tests/components/duotecno/ @cereal2nd
|
||||
/homeassistant/components/dwd_weather_warnings/ @runningman84 @stephan192 @andarotajo
|
||||
/tests/components/dwd_weather_warnings/ @runningman84 @stephan192 @andarotajo
|
||||
/homeassistant/components/dunehd/ @bieniu
|
||||
/tests/components/dunehd/ @bieniu
|
||||
/homeassistant/components/dwd_weather_warnings/ @runningman84 @stephan192 @Hummel95 @andarotajo
|
||||
/tests/components/dwd_weather_warnings/ @runningman84 @stephan192 @Hummel95 @andarotajo
|
||||
/homeassistant/components/dynalite/ @ziv1234
|
||||
/tests/components/dynalite/ @ziv1234
|
||||
/homeassistant/components/eafm/ @Jc2k
|
||||
@@ -309,8 +309,6 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/easyenergy/ @klaasnicolaas
|
||||
/homeassistant/components/ecobee/ @marthoc @marcolivierarsenault
|
||||
/tests/components/ecobee/ @marthoc @marcolivierarsenault
|
||||
/homeassistant/components/ecoforest/ @pjanuario
|
||||
/tests/components/ecoforest/ @pjanuario
|
||||
/homeassistant/components/econet/ @vangorra @w1ll1am23
|
||||
/tests/components/econet/ @vangorra @w1ll1am23
|
||||
/homeassistant/components/ecovacs/ @OverloadUT @mib1185
|
||||
@@ -323,8 +321,6 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/eight_sleep/ @mezz64 @raman325
|
||||
/homeassistant/components/electrasmart/ @jafar-atili
|
||||
/tests/components/electrasmart/ @jafar-atili
|
||||
/homeassistant/components/electric_kiwi/ @mikey0000
|
||||
/tests/components/electric_kiwi/ @mikey0000
|
||||
/homeassistant/components/elgato/ @frenck
|
||||
/tests/components/elgato/ @frenck
|
||||
/homeassistant/components/elkm1/ @gwww @bdraco
|
||||
@@ -347,8 +343,8 @@ build.json @home-assistant/supervisor
|
||||
/homeassistant/components/enigma2/ @fbradyirl
|
||||
/homeassistant/components/enocean/ @bdurrer
|
||||
/tests/components/enocean/ @bdurrer
|
||||
/homeassistant/components/enphase_envoy/ @bdraco @cgarwood @dgomes @joostlek
|
||||
/tests/components/enphase_envoy/ @bdraco @cgarwood @dgomes @joostlek
|
||||
/homeassistant/components/enphase_envoy/ @gtdiehl
|
||||
/tests/components/enphase_envoy/ @gtdiehl
|
||||
/homeassistant/components/entur_public_transport/ @hfurubotten
|
||||
/homeassistant/components/environment_canada/ @gwww @michaeldavie
|
||||
/tests/components/environment_canada/ @gwww @michaeldavie
|
||||
@@ -360,12 +356,10 @@ build.json @home-assistant/supervisor
|
||||
/homeassistant/components/eq3btsmart/ @rytilahti
|
||||
/homeassistant/components/escea/ @lazdavila
|
||||
/tests/components/escea/ @lazdavila
|
||||
/homeassistant/components/esphome/ @OttoWinter @jesserockz @kbx81 @bdraco
|
||||
/tests/components/esphome/ @OttoWinter @jesserockz @kbx81 @bdraco
|
||||
/homeassistant/components/esphome/ @OttoWinter @jesserockz @bdraco
|
||||
/tests/components/esphome/ @OttoWinter @jesserockz @bdraco
|
||||
/homeassistant/components/eufylife_ble/ @bdr99
|
||||
/tests/components/eufylife_ble/ @bdr99
|
||||
/homeassistant/components/event/ @home-assistant/core
|
||||
/tests/components/event/ @home-assistant/core
|
||||
/homeassistant/components/evil_genius_labs/ @balloob
|
||||
/tests/components/evil_genius_labs/ @balloob
|
||||
/homeassistant/components/evohome/ @zxdavb
|
||||
@@ -390,8 +384,6 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/fireservicerota/ @cyberjunky
|
||||
/homeassistant/components/firmata/ @DaAwesomeP
|
||||
/tests/components/firmata/ @DaAwesomeP
|
||||
/homeassistant/components/fitbit/ @allenporter
|
||||
/tests/components/fitbit/ @allenporter
|
||||
/homeassistant/components/fivem/ @Sander0542
|
||||
/tests/components/fivem/ @Sander0542
|
||||
/homeassistant/components/fjaraskupan/ @elupus
|
||||
@@ -404,8 +396,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/flo/ @dmulcahey
|
||||
/homeassistant/components/flume/ @ChrisMandich @bdraco @jeeftor
|
||||
/tests/components/flume/ @ChrisMandich @bdraco @jeeftor
|
||||
/homeassistant/components/flux_led/ @icemanch
|
||||
/tests/components/flux_led/ @icemanch
|
||||
/homeassistant/components/flux_led/ @icemanch @bdraco
|
||||
/tests/components/flux_led/ @icemanch @bdraco
|
||||
/homeassistant/components/forecast_solar/ @klaasnicolaas @frenck
|
||||
/tests/components/forecast_solar/ @klaasnicolaas @frenck
|
||||
/homeassistant/components/forked_daapd/ @uvjustin
|
||||
@@ -433,8 +425,6 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/fully_kiosk/ @cgarwood
|
||||
/homeassistant/components/garages_amsterdam/ @klaasnicolaas
|
||||
/tests/components/garages_amsterdam/ @klaasnicolaas
|
||||
/homeassistant/components/gardena_bluetooth/ @elupus
|
||||
/tests/components/gardena_bluetooth/ @elupus
|
||||
/homeassistant/components/gdacs/ @exxamalte
|
||||
/tests/components/gdacs/ @exxamalte
|
||||
/homeassistant/components/generic/ @davet2001
|
||||
@@ -529,8 +519,6 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/homeassistant/ @home-assistant/core
|
||||
/homeassistant/components/homeassistant_alerts/ @home-assistant/core
|
||||
/tests/components/homeassistant_alerts/ @home-assistant/core
|
||||
/homeassistant/components/homeassistant_green/ @home-assistant/core
|
||||
/tests/components/homeassistant_green/ @home-assistant/core
|
||||
/homeassistant/components/homeassistant_hardware/ @home-assistant/core
|
||||
/tests/components/homeassistant_hardware/ @home-assistant/core
|
||||
/homeassistant/components/homeassistant_sky_connect/ @home-assistant/core
|
||||
@@ -562,7 +550,6 @@ build.json @home-assistant/supervisor
|
||||
/homeassistant/components/hvv_departures/ @vigonotion
|
||||
/tests/components/hvv_departures/ @vigonotion
|
||||
/homeassistant/components/hydrawise/ @dknowles2 @ptcryan
|
||||
/tests/components/hydrawise/ @dknowles2 @ptcryan
|
||||
/homeassistant/components/hyperion/ @dermotduffy
|
||||
/tests/components/hyperion/ @dermotduffy
|
||||
/homeassistant/components/ialarm/ @RyuzakiKK
|
||||
@@ -574,8 +561,6 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/ibeacon/ @bdraco
|
||||
/homeassistant/components/icloud/ @Quentame @nzapponi
|
||||
/tests/components/icloud/ @Quentame @nzapponi
|
||||
/homeassistant/components/idasen_desk/ @abmantis
|
||||
/tests/components/idasen_desk/ @abmantis
|
||||
/homeassistant/components/ign_sismologia/ @exxamalte
|
||||
/tests/components/ign_sismologia/ @exxamalte
|
||||
/homeassistant/components/image/ @home-assistant/core
|
||||
@@ -617,8 +602,8 @@ build.json @home-assistant/supervisor
|
||||
/homeassistant/components/iotawatt/ @gtdiehl @jyavenard
|
||||
/tests/components/iotawatt/ @gtdiehl @jyavenard
|
||||
/homeassistant/components/iperf3/ @rohankapoorcom
|
||||
/homeassistant/components/ipma/ @dgomes
|
||||
/tests/components/ipma/ @dgomes
|
||||
/homeassistant/components/ipma/ @dgomes @abmantis
|
||||
/tests/components/ipma/ @dgomes @abmantis
|
||||
/homeassistant/components/ipp/ @ctalkington
|
||||
/tests/components/ipp/ @ctalkington
|
||||
/homeassistant/components/iqvia/ @bachya
|
||||
@@ -682,8 +667,6 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/launch_library/ @ludeeus @DurgNomis-drol
|
||||
/homeassistant/components/laundrify/ @xLarry
|
||||
/tests/components/laundrify/ @xLarry
|
||||
/homeassistant/components/lawn_mower/ @home-assistant/core
|
||||
/tests/components/lawn_mower/ @home-assistant/core
|
||||
/homeassistant/components/lcn/ @alengwenus
|
||||
/tests/components/lcn/ @alengwenus
|
||||
/homeassistant/components/ld2410_ble/ @930913
|
||||
@@ -695,6 +678,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/lidarr/ @tkdrob
|
||||
/homeassistant/components/life360/ @pnbruckner
|
||||
/tests/components/life360/ @pnbruckner
|
||||
/homeassistant/components/lifx/ @bdraco
|
||||
/tests/components/lifx/ @bdraco
|
||||
/homeassistant/components/light/ @home-assistant/core
|
||||
/tests/components/light/ @home-assistant/core
|
||||
/homeassistant/components/linux_battery/ @fabaff
|
||||
@@ -716,8 +701,6 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/logger/ @home-assistant/core
|
||||
/homeassistant/components/logi_circle/ @evanjd
|
||||
/tests/components/logi_circle/ @evanjd
|
||||
/homeassistant/components/london_underground/ @jpbede
|
||||
/tests/components/london_underground/ @jpbede
|
||||
/homeassistant/components/lookin/ @ANMalko @bdraco
|
||||
/tests/components/lookin/ @ANMalko @bdraco
|
||||
/homeassistant/components/loqed/ @mikewoudenberg
|
||||
@@ -734,16 +717,12 @@ build.json @home-assistant/supervisor
|
||||
/homeassistant/components/lyric/ @timmo001
|
||||
/tests/components/lyric/ @timmo001
|
||||
/homeassistant/components/mastodon/ @fabaff
|
||||
/homeassistant/components/matrix/ @PaarthShah
|
||||
/tests/components/matrix/ @PaarthShah
|
||||
/homeassistant/components/matter/ @home-assistant/matter
|
||||
/tests/components/matter/ @home-assistant/matter
|
||||
/homeassistant/components/mazda/ @bdr99
|
||||
/tests/components/mazda/ @bdr99
|
||||
/homeassistant/components/meater/ @Sotolotl @emontnemery
|
||||
/tests/components/meater/ @Sotolotl @emontnemery
|
||||
/homeassistant/components/medcom_ble/ @elafargue
|
||||
/tests/components/medcom_ble/ @elafargue
|
||||
/homeassistant/components/media_extractor/ @joostlek
|
||||
/tests/components/media_extractor/ @joostlek
|
||||
/homeassistant/components/media_player/ @home-assistant/core
|
||||
/tests/components/media_player/ @home-assistant/core
|
||||
/homeassistant/components/media_source/ @hunterjm
|
||||
@@ -766,6 +745,7 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/meteoclimatic/ @adrianmo
|
||||
/homeassistant/components/metoffice/ @MrHarcombe @avee87
|
||||
/tests/components/metoffice/ @MrHarcombe @avee87
|
||||
/homeassistant/components/miflora/ @danielhiversen @basnijholt
|
||||
/homeassistant/components/mikrotik/ @engrbm87
|
||||
/tests/components/mikrotik/ @engrbm87
|
||||
/homeassistant/components/mill/ @danielhiversen
|
||||
@@ -780,8 +760,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/moat/ @bdraco
|
||||
/homeassistant/components/mobile_app/ @home-assistant/core
|
||||
/tests/components/mobile_app/ @home-assistant/core
|
||||
/homeassistant/components/modbus/ @janiversen
|
||||
/tests/components/modbus/ @janiversen
|
||||
/homeassistant/components/modbus/ @adamchengtkc @janiversen @vzahradnik
|
||||
/tests/components/modbus/ @adamchengtkc @janiversen @vzahradnik
|
||||
/homeassistant/components/modem_callerid/ @tkdrob
|
||||
/tests/components/modem_callerid/ @tkdrob
|
||||
/homeassistant/components/modern_forms/ @wonderslug
|
||||
@@ -807,8 +787,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/mutesync/ @currentoor
|
||||
/homeassistant/components/my/ @home-assistant/core
|
||||
/tests/components/my/ @home-assistant/core
|
||||
/homeassistant/components/myq/ @ehendrix23 @Lash-L
|
||||
/tests/components/myq/ @ehendrix23 @Lash-L
|
||||
/homeassistant/components/myq/ @ehendrix23
|
||||
/tests/components/myq/ @ehendrix23
|
||||
/homeassistant/components/mysensors/ @MartinHjelmare @functionpointer
|
||||
/tests/components/mysensors/ @MartinHjelmare @functionpointer
|
||||
/homeassistant/components/mystrom/ @fabaff
|
||||
@@ -909,7 +889,6 @@ build.json @home-assistant/supervisor
|
||||
/homeassistant/components/openhome/ @bazwilliams
|
||||
/tests/components/openhome/ @bazwilliams
|
||||
/homeassistant/components/opensky/ @joostlek
|
||||
/tests/components/opensky/ @joostlek
|
||||
/homeassistant/components/opentherm_gw/ @mvn23
|
||||
/tests/components/opentherm_gw/ @mvn23
|
||||
/homeassistant/components/openuv/ @bachya
|
||||
@@ -937,8 +916,6 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/panel_iframe/ @home-assistant/frontend
|
||||
/homeassistant/components/peco/ @IceBotYT
|
||||
/tests/components/peco/ @IceBotYT
|
||||
/homeassistant/components/pegel_online/ @mib1185
|
||||
/tests/components/pegel_online/ @mib1185
|
||||
/homeassistant/components/persistent_notification/ @home-assistant/core
|
||||
/tests/components/persistent_notification/ @home-assistant/core
|
||||
/homeassistant/components/philips_js/ @elupus
|
||||
@@ -963,8 +940,6 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/poolsense/ @haemishkyd
|
||||
/homeassistant/components/powerwall/ @bdraco @jrester @daniel-simpson
|
||||
/tests/components/powerwall/ @bdraco @jrester @daniel-simpson
|
||||
/homeassistant/components/private_ble_device/ @Jc2k
|
||||
/tests/components/private_ble_device/ @Jc2k
|
||||
/homeassistant/components/profiler/ @bdraco
|
||||
/tests/components/profiler/ @bdraco
|
||||
/homeassistant/components/progettihwsw/ @ardaseremet
|
||||
@@ -1045,6 +1020,8 @@ build.json @home-assistant/supervisor
|
||||
/homeassistant/components/repairs/ @home-assistant/core
|
||||
/tests/components/repairs/ @home-assistant/core
|
||||
/homeassistant/components/repetier/ @MTrab @ShadowBr0ther
|
||||
/homeassistant/components/rest/ @epenet
|
||||
/tests/components/rest/ @epenet
|
||||
/homeassistant/components/rflink/ @javicalle
|
||||
/tests/components/rflink/ @javicalle
|
||||
/homeassistant/components/rfxtrx/ @danielhiversen @elupus @RobBie1221
|
||||
@@ -1073,8 +1050,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/rss_feed_template/ @home-assistant/core
|
||||
/homeassistant/components/rtsp_to_webrtc/ @allenporter
|
||||
/tests/components/rtsp_to_webrtc/ @allenporter
|
||||
/homeassistant/components/ruckus_unleashed/ @lanrat @ms264556 @gabe565
|
||||
/tests/components/ruckus_unleashed/ @lanrat @ms264556 @gabe565
|
||||
/homeassistant/components/ruckus_unleashed/ @gabe565
|
||||
/tests/components/ruckus_unleashed/ @gabe565
|
||||
/homeassistant/components/ruuvi_gateway/ @akx
|
||||
/tests/components/ruuvi_gateway/ @akx
|
||||
/homeassistant/components/ruuvitag_ble/ @akx
|
||||
@@ -1092,11 +1069,9 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/scene/ @home-assistant/core
|
||||
/homeassistant/components/schedule/ @home-assistant/core
|
||||
/tests/components/schedule/ @home-assistant/core
|
||||
/homeassistant/components/schlage/ @dknowles2
|
||||
/tests/components/schlage/ @dknowles2
|
||||
/homeassistant/components/schluter/ @prairieapps
|
||||
/homeassistant/components/scrape/ @fabaff @gjohansson-ST
|
||||
/tests/components/scrape/ @fabaff @gjohansson-ST
|
||||
/homeassistant/components/scrape/ @fabaff @gjohansson-ST @epenet
|
||||
/tests/components/scrape/ @fabaff @gjohansson-ST @epenet
|
||||
/homeassistant/components/screenlogic/ @dieselrabbit @bdraco
|
||||
/tests/components/screenlogic/ @dieselrabbit @bdraco
|
||||
/homeassistant/components/script/ @home-assistant/core
|
||||
@@ -1151,8 +1126,8 @@ build.json @home-assistant/supervisor
|
||||
/homeassistant/components/sky_hub/ @rogerselwyn
|
||||
/homeassistant/components/skybell/ @tkdrob
|
||||
/tests/components/skybell/ @tkdrob
|
||||
/homeassistant/components/slack/ @tkdrob @fletcherau
|
||||
/tests/components/slack/ @tkdrob @fletcherau
|
||||
/homeassistant/components/slack/ @tkdrob
|
||||
/tests/components/slack/ @tkdrob
|
||||
/homeassistant/components/sleepiq/ @mfugate1 @kbickar
|
||||
/tests/components/sleepiq/ @mfugate1 @kbickar
|
||||
/homeassistant/components/slide/ @ualex73
|
||||
@@ -1200,8 +1175,8 @@ build.json @home-assistant/supervisor
|
||||
/homeassistant/components/spider/ @peternijssen
|
||||
/tests/components/spider/ @peternijssen
|
||||
/homeassistant/components/splunk/ @Bre77
|
||||
/homeassistant/components/spotify/ @frenck @joostlek
|
||||
/tests/components/spotify/ @frenck @joostlek
|
||||
/homeassistant/components/spotify/ @frenck
|
||||
/tests/components/spotify/ @frenck
|
||||
/homeassistant/components/sql/ @gjohansson-ST @dougiteixeira
|
||||
/tests/components/sql/ @gjohansson-ST @dougiteixeira
|
||||
/homeassistant/components/squeezebox/ @rajlaud
|
||||
@@ -1243,10 +1218,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/switch_as_x/ @home-assistant/core
|
||||
/homeassistant/components/switchbee/ @jafar-atili
|
||||
/tests/components/switchbee/ @jafar-atili
|
||||
/homeassistant/components/switchbot/ @danielhiversen @RenierM26 @murtas @Eloston @dsypniewski
|
||||
/tests/components/switchbot/ @danielhiversen @RenierM26 @murtas @Eloston @dsypniewski
|
||||
/homeassistant/components/switchbot_cloud/ @SeraphicRav
|
||||
/tests/components/switchbot_cloud/ @SeraphicRav
|
||||
/homeassistant/components/switchbot/ @bdraco @danielhiversen @RenierM26 @murtas @Eloston @dsypniewski
|
||||
/tests/components/switchbot/ @bdraco @danielhiversen @RenierM26 @murtas @Eloston @dsypniewski
|
||||
/homeassistant/components/switcher_kis/ @thecode
|
||||
/tests/components/switcher_kis/ @thecode
|
||||
/homeassistant/components/switchmate/ @danielhiversen @qiz-li
|
||||
@@ -1317,8 +1290,6 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/trace/ @home-assistant/core
|
||||
/homeassistant/components/tractive/ @Danielhiversen @zhulik @bieniu
|
||||
/tests/components/tractive/ @Danielhiversen @zhulik @bieniu
|
||||
/homeassistant/components/trafikverket_camera/ @gjohansson-ST
|
||||
/tests/components/trafikverket_camera/ @gjohansson-ST
|
||||
/homeassistant/components/trafikverket_ferry/ @gjohansson-ST
|
||||
/tests/components/trafikverket_ferry/ @gjohansson-ST
|
||||
/homeassistant/components/trafikverket_train/ @endor-force @gjohansson-ST
|
||||
@@ -1327,16 +1298,14 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/trafikverket_weatherstation/ @endor-force @gjohansson-ST
|
||||
/homeassistant/components/transmission/ @engrbm87 @JPHutchins
|
||||
/tests/components/transmission/ @engrbm87 @JPHutchins
|
||||
/homeassistant/components/trend/ @jpbede
|
||||
/tests/components/trend/ @jpbede
|
||||
/homeassistant/components/tts/ @home-assistant/core @pvizeli
|
||||
/tests/components/tts/ @home-assistant/core @pvizeli
|
||||
/homeassistant/components/tuya/ @Tuya @zlinoliver @frenck
|
||||
/tests/components/tuya/ @Tuya @zlinoliver @frenck
|
||||
/homeassistant/components/twentemilieu/ @frenck
|
||||
/tests/components/twentemilieu/ @frenck
|
||||
/homeassistant/components/twinkly/ @dr1rrb @Robbie1221 @Olen
|
||||
/tests/components/twinkly/ @dr1rrb @Robbie1221 @Olen
|
||||
/homeassistant/components/twinkly/ @dr1rrb @Robbie1221
|
||||
/tests/components/twinkly/ @dr1rrb @Robbie1221
|
||||
/homeassistant/components/twitch/ @joostlek
|
||||
/tests/components/twitch/ @joostlek
|
||||
/homeassistant/components/ukraine_alarm/ @PaulAnnekov
|
||||
@@ -1372,11 +1341,11 @@ build.json @home-assistant/supervisor
|
||||
/homeassistant/components/velbus/ @Cereal2nd @brefra
|
||||
/tests/components/velbus/ @Cereal2nd @brefra
|
||||
/homeassistant/components/velux/ @Julius2342
|
||||
/homeassistant/components/venstar/ @garbled1 @jhollowe
|
||||
/tests/components/venstar/ @garbled1 @jhollowe
|
||||
/homeassistant/components/verisure/ @frenck
|
||||
/tests/components/verisure/ @frenck
|
||||
/homeassistant/components/versasense/ @imstevenxyz
|
||||
/homeassistant/components/venstar/ @garbled1
|
||||
/tests/components/venstar/ @garbled1
|
||||
/homeassistant/components/verisure/ @frenck @niro1987
|
||||
/tests/components/verisure/ @frenck @niro1987
|
||||
/homeassistant/components/versasense/ @flamm3blemuff1n
|
||||
/homeassistant/components/version/ @ludeeus
|
||||
/tests/components/version/ @ludeeus
|
||||
/homeassistant/components/vesync/ @markperdue @webdjoe @thegardenmonkey
|
||||
@@ -1388,8 +1357,6 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/vizio/ @raman325
|
||||
/homeassistant/components/vlc_telnet/ @rodripf @MartinHjelmare
|
||||
/tests/components/vlc_telnet/ @rodripf @MartinHjelmare
|
||||
/homeassistant/components/vodafone_station/ @paoloantinori @chemelli74
|
||||
/tests/components/vodafone_station/ @paoloantinori @chemelli74
|
||||
/homeassistant/components/voip/ @balloob @synesthesiam
|
||||
/tests/components/voip/ @balloob @synesthesiam
|
||||
/homeassistant/components/volumio/ @OnFreund
|
||||
@@ -1400,12 +1367,9 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/vulcan/ @Antoni-Czaplicki
|
||||
/homeassistant/components/wake_on_lan/ @ntilley905
|
||||
/tests/components/wake_on_lan/ @ntilley905
|
||||
/homeassistant/components/wake_word/ @home-assistant/core @synesthesiam
|
||||
/tests/components/wake_word/ @home-assistant/core @synesthesiam
|
||||
/homeassistant/components/wallbox/ @hesselonline
|
||||
/tests/components/wallbox/ @hesselonline
|
||||
/homeassistant/components/waqi/ @joostlek
|
||||
/tests/components/waqi/ @joostlek
|
||||
/homeassistant/components/waqi/ @andrey-git
|
||||
/homeassistant/components/water_heater/ @home-assistant/core
|
||||
/tests/components/water_heater/ @home-assistant/core
|
||||
/homeassistant/components/watson_tts/ @rutkai
|
||||
@@ -1415,10 +1379,6 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/waze_travel_time/ @eifinger
|
||||
/homeassistant/components/weather/ @home-assistant/core
|
||||
/tests/components/weather/ @home-assistant/core
|
||||
/homeassistant/components/weatherflow/ @natekspencer @jeeftor
|
||||
/tests/components/weatherflow/ @natekspencer @jeeftor
|
||||
/homeassistant/components/weatherkit/ @tjhorner
|
||||
/tests/components/weatherkit/ @tjhorner
|
||||
/homeassistant/components/webhook/ @home-assistant/core
|
||||
/tests/components/webhook/ @home-assistant/core
|
||||
/homeassistant/components/webostv/ @thecode
|
||||
@@ -1436,8 +1396,8 @@ build.json @home-assistant/supervisor
|
||||
/homeassistant/components/wilight/ @leofig-rj
|
||||
/tests/components/wilight/ @leofig-rj
|
||||
/homeassistant/components/wirelesstag/ @sergeymaysak
|
||||
/homeassistant/components/withings/ @vangorra @joostlek
|
||||
/tests/components/withings/ @vangorra @joostlek
|
||||
/homeassistant/components/withings/ @vangorra
|
||||
/tests/components/withings/ @vangorra
|
||||
/homeassistant/components/wiz/ @sbidy
|
||||
/tests/components/wiz/ @sbidy
|
||||
/homeassistant/components/wled/ @frenck
|
||||
@@ -1470,8 +1430,6 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/yamaha_musiccast/ @vigonotion @micha91
|
||||
/homeassistant/components/yandex_transport/ @rishatik92 @devbis
|
||||
/tests/components/yandex_transport/ @rishatik92 @devbis
|
||||
/homeassistant/components/yardian/ @h3l1o5
|
||||
/tests/components/yardian/ @h3l1o5
|
||||
/homeassistant/components/yeelight/ @zewelor @shenxn @starkillerOG @alexyao2015
|
||||
/tests/components/yeelight/ @zewelor @shenxn @starkillerOG @alexyao2015
|
||||
/homeassistant/components/yeelightsunflower/ @lindsaymarkward
|
||||
|
||||
@@ -15,8 +15,9 @@ COPY homeassistant/package_constraints.txt homeassistant/homeassistant/
|
||||
RUN \
|
||||
pip3 install \
|
||||
--no-cache-dir \
|
||||
--no-index \
|
||||
--only-binary=:all: \
|
||||
--index-url "https://wheels.home-assistant.io/musllinux-index/" \
|
||||
--find-links "${WHEELS_LINKS}" \
|
||||
-r homeassistant/requirements.txt
|
||||
|
||||
COPY requirements_all.txt home_assistant_frontend-* home_assistant_intents-* homeassistant/
|
||||
@@ -38,8 +39,9 @@ RUN \
|
||||
MALLOC_CONF="background_thread:true,metadata_thp:auto,dirty_decay_ms:20000,muzzy_decay_ms:20000" \
|
||||
pip3 install \
|
||||
--no-cache-dir \
|
||||
--no-index \
|
||||
--only-binary=:all: \
|
||||
--index-url "https://wheels.home-assistant.io/musllinux-index/" \
|
||||
--find-links "${WHEELS_LINKS}" \
|
||||
-r homeassistant/requirements_all.txt
|
||||
|
||||
## Setup Home Assistant Core
|
||||
@@ -47,8 +49,9 @@ COPY . homeassistant/
|
||||
RUN \
|
||||
pip3 install \
|
||||
--no-cache-dir \
|
||||
--no-index \
|
||||
--only-binary=:all: \
|
||||
--index-url "https://wheels.home-assistant.io/musllinux-index/" \
|
||||
--find-links "${WHEELS_LINKS}" \
|
||||
-e ./homeassistant \
|
||||
&& python3 -m compileall \
|
||||
homeassistant/homeassistant
|
||||
|
||||
10
build.yaml
10
build.yaml
@@ -1,10 +1,10 @@
|
||||
image: ghcr.io/home-assistant/{arch}-homeassistant
|
||||
build_from:
|
||||
aarch64: ghcr.io/home-assistant/aarch64-homeassistant-base:2023.09.0
|
||||
armhf: ghcr.io/home-assistant/armhf-homeassistant-base:2023.09.0
|
||||
armv7: ghcr.io/home-assistant/armv7-homeassistant-base:2023.09.0
|
||||
amd64: ghcr.io/home-assistant/amd64-homeassistant-base:2023.09.0
|
||||
i386: ghcr.io/home-assistant/i386-homeassistant-base:2023.09.0
|
||||
aarch64: ghcr.io/home-assistant/aarch64-homeassistant-base:2023.06.1
|
||||
armhf: ghcr.io/home-assistant/armhf-homeassistant-base:2023.06.1
|
||||
armv7: ghcr.io/home-assistant/armv7-homeassistant-base:2023.06.1
|
||||
amd64: ghcr.io/home-assistant/amd64-homeassistant-base:2023.06.1
|
||||
i386: ghcr.io/home-assistant/i386-homeassistant-base:2023.06.1
|
||||
codenotary:
|
||||
signer: notary@home-assistant.io
|
||||
base_image: notary@home-assistant.io
|
||||
|
||||
@@ -148,6 +148,16 @@ def get_arguments() -> argparse.Namespace:
|
||||
return arguments
|
||||
|
||||
|
||||
def cmdline() -> list[str]:
|
||||
"""Collect path and arguments to re-execute the current hass instance."""
|
||||
if os.path.basename(sys.argv[0]) == "__main__.py":
|
||||
modulepath = os.path.dirname(sys.argv[0])
|
||||
os.environ["PYTHONPATH"] = os.path.dirname(modulepath)
|
||||
return [sys.executable, "-m", "homeassistant"] + list(sys.argv[1:])
|
||||
|
||||
return sys.argv
|
||||
|
||||
|
||||
def check_threads() -> None:
|
||||
"""Check if there are any lingering threads."""
|
||||
try:
|
||||
|
||||
@@ -1,15 +1,32 @@
|
||||
"""Enum backports from standard lib.
|
||||
|
||||
This file contained the backport of the StrEnum of Python 3.11.
|
||||
|
||||
Since we have dropped support for Python 3.10, we can remove this backport.
|
||||
This file is kept for now to avoid breaking custom components that might
|
||||
import it.
|
||||
"""
|
||||
"""Enum backports from standard lib."""
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import StrEnum
|
||||
from enum import Enum
|
||||
from typing import Any
|
||||
|
||||
__all__ = [
|
||||
"StrEnum",
|
||||
]
|
||||
from typing_extensions import Self
|
||||
|
||||
|
||||
class StrEnum(str, Enum):
|
||||
"""Partial backport of Python 3.11's StrEnum for our basic use cases."""
|
||||
|
||||
def __new__(cls, value: str, *args: Any, **kwargs: Any) -> Self:
|
||||
"""Create a new StrEnum instance."""
|
||||
if not isinstance(value, str):
|
||||
raise TypeError(f"{value!r} is not a string")
|
||||
return super().__new__(cls, value, *args, **kwargs)
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""Return self.value."""
|
||||
return str(self.value)
|
||||
|
||||
@staticmethod
|
||||
def _generate_next_value_(
|
||||
name: str, start: int, count: int, last_values: list[Any]
|
||||
) -> Any:
|
||||
"""Make `auto()` explicitly unsupported.
|
||||
|
||||
We may revisit this when it's very clear that Python 3.11's
|
||||
`StrEnum.auto()` behavior will no longer change.
|
||||
"""
|
||||
raise TypeError("auto() is not supported by this implementation")
|
||||
|
||||
@@ -3,24 +3,27 @@ from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from types import GenericAlias
|
||||
from typing import Any, Generic, Self, TypeVar, overload
|
||||
from typing import Any, Generic, TypeVar, overload
|
||||
|
||||
from typing_extensions import Self
|
||||
|
||||
_T = TypeVar("_T")
|
||||
_R = TypeVar("_R")
|
||||
|
||||
|
||||
class cached_property(Generic[_T]):
|
||||
class cached_property(Generic[_T, _R]): # pylint: disable=invalid-name
|
||||
"""Backport of Python 3.12's cached_property.
|
||||
|
||||
Includes https://github.com/python/cpython/pull/101890/files
|
||||
"""
|
||||
|
||||
def __init__(self, func: Callable[[Any], _T]) -> None:
|
||||
def __init__(self, func: Callable[[_T], _R]) -> None:
|
||||
"""Initialize."""
|
||||
self.func: Callable[[Any], _T] = func
|
||||
self.attrname: str | None = None
|
||||
self.func = func
|
||||
self.attrname: Any = None
|
||||
self.__doc__ = func.__doc__
|
||||
|
||||
def __set_name__(self, owner: type[Any], name: str) -> None:
|
||||
def __set_name__(self, owner: type[_T], name: str) -> None:
|
||||
"""Set name."""
|
||||
if self.attrname is None:
|
||||
self.attrname = name
|
||||
@@ -31,16 +34,14 @@ class cached_property(Generic[_T]):
|
||||
)
|
||||
|
||||
@overload
|
||||
def __get__(self, instance: None, owner: type[Any] | None = None) -> Self:
|
||||
def __get__(self, instance: None, owner: type[_T]) -> Self:
|
||||
...
|
||||
|
||||
@overload
|
||||
def __get__(self, instance: Any, owner: type[Any] | None = None) -> _T:
|
||||
def __get__(self, instance: _T, owner: type[_T]) -> _R:
|
||||
...
|
||||
|
||||
def __get__(
|
||||
self, instance: Any | None, owner: type[Any] | None = None
|
||||
) -> _T | Self:
|
||||
def __get__(self, instance: _T | None, owner: type[_T] | None = None) -> _R | Self:
|
||||
"""Get."""
|
||||
if instance is None:
|
||||
return self
|
||||
|
||||
@@ -110,7 +110,8 @@ async def async_setup_hass(
|
||||
runtime_config: RuntimeConfig,
|
||||
) -> core.HomeAssistant | None:
|
||||
"""Set up Home Assistant."""
|
||||
hass = core.HomeAssistant(runtime_config.config_dir)
|
||||
hass = core.HomeAssistant()
|
||||
hass.config.config_dir = runtime_config.config_dir
|
||||
|
||||
async_enable_logging(
|
||||
hass,
|
||||
@@ -133,7 +134,6 @@ async def async_setup_hass(
|
||||
|
||||
_LOGGER.info("Config directory: %s", runtime_config.config_dir)
|
||||
|
||||
loader.async_setup(hass)
|
||||
config_dict = None
|
||||
basic_setup_success = False
|
||||
|
||||
@@ -177,15 +177,14 @@ async def async_setup_hass(
|
||||
old_config = hass.config
|
||||
old_logging = hass.data.get(DATA_LOGGING)
|
||||
|
||||
hass = core.HomeAssistant(old_config.config_dir)
|
||||
hass = core.HomeAssistant()
|
||||
if old_logging:
|
||||
hass.data[DATA_LOGGING] = old_logging
|
||||
hass.config.skip_pip = old_config.skip_pip
|
||||
hass.config.skip_pip_packages = old_config.skip_pip_packages
|
||||
hass.config.internal_url = old_config.internal_url
|
||||
hass.config.external_url = old_config.external_url
|
||||
# Setup loader cache after the config dir has been set
|
||||
loader.async_setup(hass)
|
||||
hass.config.config_dir = old_config.config_dir
|
||||
|
||||
if safe_mode:
|
||||
_LOGGER.info("Starting in safe mode")
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
"homekit",
|
||||
"ibeacon",
|
||||
"icloud",
|
||||
"itunes",
|
||||
"weatherkit"
|
||||
"itunes"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"domain": "ikea",
|
||||
"name": "IKEA",
|
||||
"integrations": ["symfonisk", "tradfri", "idasen_desk"]
|
||||
"integrations": ["symfonisk", "tradfri"]
|
||||
}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"domain": "switchbot",
|
||||
"name": "SwitchBot",
|
||||
"integrations": ["switchbot", "switchbot_cloud"]
|
||||
}
|
||||
@@ -2,7 +2,6 @@
|
||||
"domain": "trafikverket",
|
||||
"name": "Trafikverket",
|
||||
"integrations": [
|
||||
"trafikverket_camera",
|
||||
"trafikverket_ferry",
|
||||
"trafikverket_train",
|
||||
"trafikverket_weatherstation"
|
||||
|
||||
@@ -28,7 +28,6 @@ from homeassistant.const import (
|
||||
from homeassistant.core import Event, HomeAssistant, ServiceCall
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
||||
from homeassistant.helpers import config_validation as cv, entity
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.dispatcher import dispatcher_send
|
||||
|
||||
from .const import ATTRIBUTION, CONF_POLLING, DOMAIN, LOGGER
|
||||
@@ -288,14 +287,14 @@ class AbodeDevice(AbodeEntity):
|
||||
"""Initialize Abode device."""
|
||||
super().__init__(data)
|
||||
self._device = device
|
||||
self._attr_unique_id = device.uuid
|
||||
self._attr_unique_id = device.device_uuid
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Subscribe to device events."""
|
||||
await super().async_added_to_hass()
|
||||
await self.hass.async_add_executor_job(
|
||||
self._data.abode.events.add_device_callback,
|
||||
self._device.id,
|
||||
self._device.device_id,
|
||||
self._update_callback,
|
||||
)
|
||||
|
||||
@@ -303,7 +302,7 @@ class AbodeDevice(AbodeEntity):
|
||||
"""Unsubscribe from device events."""
|
||||
await super().async_will_remove_from_hass()
|
||||
await self.hass.async_add_executor_job(
|
||||
self._data.abode.events.remove_all_device_callbacks, self._device.id
|
||||
self._data.abode.events.remove_all_device_callbacks, self._device.device_id
|
||||
)
|
||||
|
||||
def update(self) -> None:
|
||||
@@ -314,17 +313,17 @@ class AbodeDevice(AbodeEntity):
|
||||
def extra_state_attributes(self) -> dict[str, str]:
|
||||
"""Return the state attributes."""
|
||||
return {
|
||||
"device_id": self._device.id,
|
||||
"device_id": self._device.device_id,
|
||||
"battery_low": self._device.battery_low,
|
||||
"no_response": self._device.no_response,
|
||||
"device_type": self._device.type,
|
||||
}
|
||||
|
||||
@property
|
||||
def device_info(self) -> DeviceInfo:
|
||||
def device_info(self) -> entity.DeviceInfo:
|
||||
"""Return device registry information for this entity."""
|
||||
return DeviceInfo(
|
||||
identifiers={(DOMAIN, self._device.id)},
|
||||
return entity.DeviceInfo(
|
||||
identifiers={(DOMAIN, self._device.device_id)},
|
||||
manufacturer="Abode",
|
||||
model=self._device.type,
|
||||
name=self._device.name,
|
||||
|
||||
@@ -69,7 +69,7 @@ class AbodeAlarm(AbodeDevice, alarm.AlarmControlPanelEntity):
|
||||
def extra_state_attributes(self) -> dict[str, str]:
|
||||
"""Return the state attributes."""
|
||||
return {
|
||||
"device_id": self._device.id,
|
||||
"device_id": self._device.device_id,
|
||||
"battery_backup": self._device.battery,
|
||||
"cellular_backup": self._device.is_cellular,
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ async def async_setup_entry(
|
||||
data: AbodeSystem = hass.data[DOMAIN]
|
||||
|
||||
async_add_entities(
|
||||
AbodeCamera(data, device, TIMELINE.CAPTURE_IMAGE)
|
||||
AbodeCamera(data, device, TIMELINE.CAPTURE_IMAGE) # pylint: disable=no-member
|
||||
for device in data.abode.get_devices(generic_type=CONST.TYPE_CAMERA)
|
||||
)
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
"""Support for Abode Security System sensors."""
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
from typing import cast
|
||||
|
||||
from jaraco.abode.devices.sensor import Sensor as AbodeSense
|
||||
@@ -14,52 +12,25 @@ from homeassistant.components.sensor import (
|
||||
SensorEntityDescription,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import LIGHT_LUX, PERCENTAGE, UnitOfTemperature
|
||||
from homeassistant.const import LIGHT_LUX
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import AbodeDevice, AbodeSystem
|
||||
from .const import DOMAIN
|
||||
|
||||
ABODE_TEMPERATURE_UNIT_HA_UNIT = {
|
||||
CONST.UNIT_FAHRENHEIT: UnitOfTemperature.FAHRENHEIT,
|
||||
CONST.UNIT_CELSIUS: UnitOfTemperature.CELSIUS,
|
||||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
class AbodeSensorDescriptionMixin:
|
||||
"""Mixin for Abode sensor."""
|
||||
|
||||
value_fn: Callable[[AbodeSense], float]
|
||||
native_unit_of_measurement_fn: Callable[[AbodeSense], str]
|
||||
|
||||
|
||||
@dataclass
|
||||
class AbodeSensorDescription(SensorEntityDescription, AbodeSensorDescriptionMixin):
|
||||
"""Class describing Abode sensor entities."""
|
||||
|
||||
|
||||
SENSOR_TYPES: tuple[AbodeSensorDescription, ...] = (
|
||||
AbodeSensorDescription(
|
||||
SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
|
||||
SensorEntityDescription(
|
||||
key=CONST.TEMP_STATUS_KEY,
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
native_unit_of_measurement_fn=lambda device: ABODE_TEMPERATURE_UNIT_HA_UNIT[
|
||||
device.temp_unit
|
||||
],
|
||||
value_fn=lambda device: cast(float, device.temp),
|
||||
),
|
||||
AbodeSensorDescription(
|
||||
SensorEntityDescription(
|
||||
key=CONST.HUMI_STATUS_KEY,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
native_unit_of_measurement_fn=lambda _: PERCENTAGE,
|
||||
value_fn=lambda device: cast(float, device.humidity),
|
||||
),
|
||||
AbodeSensorDescription(
|
||||
SensorEntityDescription(
|
||||
key=CONST.LUX_STATUS_KEY,
|
||||
device_class=SensorDeviceClass.ILLUMINANCE,
|
||||
native_unit_of_measurement_fn=lambda _: LIGHT_LUX,
|
||||
value_fn=lambda device: cast(float, device.lux),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -81,26 +52,32 @@ async def async_setup_entry(
|
||||
class AbodeSensor(AbodeDevice, SensorEntity):
|
||||
"""A sensor implementation for Abode devices."""
|
||||
|
||||
entity_description: AbodeSensorDescription
|
||||
_device: AbodeSense
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
data: AbodeSystem,
|
||||
device: AbodeSense,
|
||||
description: AbodeSensorDescription,
|
||||
description: SensorEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize a sensor for an Abode device."""
|
||||
super().__init__(data, device)
|
||||
self.entity_description = description
|
||||
self._attr_unique_id = f"{device.uuid}-{description.key}"
|
||||
self._attr_unique_id = f"{device.device_uuid}-{description.key}"
|
||||
if description.key == CONST.TEMP_STATUS_KEY:
|
||||
self._attr_native_unit_of_measurement = device.temp_unit
|
||||
elif description.key == CONST.HUMI_STATUS_KEY:
|
||||
self._attr_native_unit_of_measurement = device.humidity_unit
|
||||
elif description.key == CONST.LUX_STATUS_KEY:
|
||||
self._attr_native_unit_of_measurement = LIGHT_LUX
|
||||
|
||||
@property
|
||||
def native_value(self) -> float:
|
||||
def native_value(self) -> float | None:
|
||||
"""Return the state of the sensor."""
|
||||
return self.entity_description.value_fn(self._device)
|
||||
|
||||
@property
|
||||
def native_unit_of_measurement(self) -> str:
|
||||
"""Return the native unit of measurement."""
|
||||
return self.entity_description.native_unit_of_measurement_fn(self._device)
|
||||
if self.entity_description.key == CONST.TEMP_STATUS_KEY:
|
||||
return cast(float, self._device.temp)
|
||||
if self.entity_description.key == CONST.HUMI_STATUS_KEY:
|
||||
return cast(float, self._device.humidity)
|
||||
if self.entity_description.key == CONST.LUX_STATUS_KEY:
|
||||
return cast(float, self._device.lux)
|
||||
return None
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
capture_image:
|
||||
name: Capture image
|
||||
description: Request a new image capture from a camera device.
|
||||
fields:
|
||||
entity_id:
|
||||
name: Entity
|
||||
description: Entity id of the camera to request an image.
|
||||
required: true
|
||||
selector:
|
||||
entity:
|
||||
@@ -8,21 +12,31 @@ capture_image:
|
||||
domain: camera
|
||||
|
||||
change_setting:
|
||||
name: Change setting
|
||||
description: Change an Abode system setting.
|
||||
fields:
|
||||
setting:
|
||||
name: Setting
|
||||
description: Setting to change.
|
||||
required: true
|
||||
example: beeper_mute
|
||||
selector:
|
||||
text:
|
||||
value:
|
||||
name: Value
|
||||
description: Value of the setting.
|
||||
required: true
|
||||
example: "1"
|
||||
selector:
|
||||
text:
|
||||
|
||||
trigger_automation:
|
||||
name: Trigger automation
|
||||
description: Trigger an Abode automation.
|
||||
fields:
|
||||
entity_id:
|
||||
name: Entity
|
||||
description: Entity id of the automation to trigger.
|
||||
required: true
|
||||
selector:
|
||||
entity:
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
}
|
||||
},
|
||||
"reauth_confirm": {
|
||||
"title": "[%key:component::abode::config::step::user::title%]",
|
||||
"title": "Fill in your Abode login information",
|
||||
"data": {
|
||||
"username": "[%key:common::config_flow::data::email%]",
|
||||
"password": "[%key:common::config_flow::data::password%]"
|
||||
@@ -31,41 +31,5 @@
|
||||
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]",
|
||||
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
|
||||
}
|
||||
},
|
||||
"services": {
|
||||
"capture_image": {
|
||||
"name": "Capture image",
|
||||
"description": "Request a new image capture from a camera device.",
|
||||
"fields": {
|
||||
"entity_id": {
|
||||
"name": "Entity",
|
||||
"description": "Entity id of the camera to request an image."
|
||||
}
|
||||
}
|
||||
},
|
||||
"change_setting": {
|
||||
"name": "Change setting",
|
||||
"description": "Change an Abode system setting.",
|
||||
"fields": {
|
||||
"setting": {
|
||||
"name": "Setting",
|
||||
"description": "Setting to change."
|
||||
},
|
||||
"value": {
|
||||
"name": "Value",
|
||||
"description": "Value of the setting."
|
||||
}
|
||||
}
|
||||
},
|
||||
"trigger_automation": {
|
||||
"name": "Trigger automation",
|
||||
"description": "Trigger an Abode automation.",
|
||||
"fields": {
|
||||
"entity_id": {
|
||||
"name": "Entity",
|
||||
"description": "Entity id of the automation to trigger."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"""The AccuWeather component."""
|
||||
from __future__ import annotations
|
||||
|
||||
from asyncio import timeout
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
from typing import Any
|
||||
@@ -9,6 +8,7 @@ from typing import Any
|
||||
from accuweather import AccuWeather, ApiError, InvalidApiKeyError, RequestsExceededError
|
||||
from aiohttp import ClientSession
|
||||
from aiohttp.client_exceptions import ClientConnectorError
|
||||
from async_timeout import timeout
|
||||
|
||||
from homeassistant.components.sensor import DOMAIN as SENSOR_PLATFORM
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
@@ -16,7 +16,8 @@ from homeassistant.const import CONF_API_KEY, CONF_NAME, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
|
||||
from homeassistant.helpers.device_registry import DeviceEntryType
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
from .const import ATTR_FORECAST, CONF_FORECAST, DOMAIN, MANUFACTURER
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from asyncio import timeout
|
||||
from typing import Any
|
||||
|
||||
from accuweather import AccuWeather, ApiError, InvalidApiKeyError, RequestsExceededError
|
||||
from aiohttp import ClientError
|
||||
from aiohttp.client_exceptions import ClientConnectorError
|
||||
from async_timeout import timeout
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries
|
||||
|
||||
@@ -50,8 +50,3 @@ CONDITION_CLASSES: Final[dict[str, list[int]]] = {
|
||||
ATTR_CONDITION_SUNNY: [1, 2, 5],
|
||||
ATTR_CONDITION_WINDY: [32],
|
||||
}
|
||||
CONDITION_MAP = {
|
||||
cond_code: cond_ha
|
||||
for cond_ha, cond_codes in CONDITION_CLASSES.items()
|
||||
for cond_code in cond_codes
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"name": "AccuWeather",
|
||||
"codeowners": ["@bieniu"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/accuweather",
|
||||
"documentation": "https://www.home-assistant.io/integrations/accuweather/",
|
||||
"integration_type": "service",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["accuweather"],
|
||||
|
||||
@@ -25,6 +25,7 @@ from homeassistant.const import (
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.typing import StateType
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from . import AccuWeatherDataUpdateCoordinator
|
||||
@@ -49,7 +50,7 @@ PARALLEL_UPDATES = 1
|
||||
class AccuWeatherSensorDescriptionMixin:
|
||||
"""Mixin for AccuWeather sensor."""
|
||||
|
||||
value_fn: Callable[[dict[str, Any]], str | int | float | None]
|
||||
value_fn: Callable[[dict[str, Any]], StateType]
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -58,281 +59,194 @@ class AccuWeatherSensorDescription(
|
||||
):
|
||||
"""Class describing AccuWeather sensor entities."""
|
||||
|
||||
attr_fn: Callable[[dict[str, Any]], dict[str, Any]] = lambda _: {}
|
||||
day: int | None = None
|
||||
attr_fn: Callable[[dict[str, Any]], dict[str, StateType]] = lambda _: {}
|
||||
|
||||
|
||||
FORECAST_SENSOR_TYPES: tuple[AccuWeatherSensorDescription, ...] = (
|
||||
*(
|
||||
AccuWeatherSensorDescription(
|
||||
key="AirQuality",
|
||||
icon="mdi:air-filter",
|
||||
value_fn=lambda data: cast(str, data[ATTR_CATEGORY]),
|
||||
device_class=SensorDeviceClass.ENUM,
|
||||
options=["good", "hazardous", "high", "low", "moderate", "unhealthy"],
|
||||
translation_key=f"air_quality_{day}d",
|
||||
day=day,
|
||||
)
|
||||
for day in range(MAX_FORECAST_DAYS + 1)
|
||||
AccuWeatherSensorDescription(
|
||||
key="AirQuality",
|
||||
icon="mdi:air-filter",
|
||||
name="Air quality",
|
||||
value_fn=lambda data: cast(str, data[ATTR_CATEGORY]),
|
||||
device_class=SensorDeviceClass.ENUM,
|
||||
options=["good", "hazardous", "high", "low", "moderate", "unhealthy"],
|
||||
translation_key="air_quality",
|
||||
),
|
||||
*(
|
||||
AccuWeatherSensorDescription(
|
||||
key="CloudCoverDay",
|
||||
icon="mdi:weather-cloudy",
|
||||
entity_registry_enabled_default=False,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
value_fn=lambda data: cast(int, data),
|
||||
translation_key=f"cloud_cover_day_{day}d",
|
||||
day=day,
|
||||
)
|
||||
for day in range(MAX_FORECAST_DAYS + 1)
|
||||
AccuWeatherSensorDescription(
|
||||
key="CloudCoverDay",
|
||||
icon="mdi:weather-cloudy",
|
||||
name="Cloud cover day",
|
||||
entity_registry_enabled_default=False,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
value_fn=lambda data: cast(int, data),
|
||||
),
|
||||
*(
|
||||
AccuWeatherSensorDescription(
|
||||
key="CloudCoverNight",
|
||||
icon="mdi:weather-cloudy",
|
||||
entity_registry_enabled_default=False,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
value_fn=lambda data: cast(int, data),
|
||||
translation_key=f"cloud_cover_night_{day}d",
|
||||
day=day,
|
||||
)
|
||||
for day in range(MAX_FORECAST_DAYS + 1)
|
||||
AccuWeatherSensorDescription(
|
||||
key="CloudCoverNight",
|
||||
icon="mdi:weather-cloudy",
|
||||
name="Cloud cover night",
|
||||
entity_registry_enabled_default=False,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
value_fn=lambda data: cast(int, data),
|
||||
),
|
||||
*(
|
||||
AccuWeatherSensorDescription(
|
||||
key="Grass",
|
||||
icon="mdi:grass",
|
||||
entity_registry_enabled_default=False,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||
value_fn=lambda data: cast(int, data[ATTR_VALUE]),
|
||||
attr_fn=lambda data: {ATTR_LEVEL: data[ATTR_CATEGORY]},
|
||||
translation_key=f"grass_pollen_{day}d",
|
||||
day=day,
|
||||
)
|
||||
for day in range(MAX_FORECAST_DAYS + 1)
|
||||
AccuWeatherSensorDescription(
|
||||
key="Grass",
|
||||
icon="mdi:grass",
|
||||
name="Grass pollen",
|
||||
entity_registry_enabled_default=False,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||
value_fn=lambda data: cast(int, data[ATTR_VALUE]),
|
||||
attr_fn=lambda data: {ATTR_LEVEL: data[ATTR_CATEGORY]},
|
||||
translation_key="grass_pollen",
|
||||
),
|
||||
*(
|
||||
AccuWeatherSensorDescription(
|
||||
key="HoursOfSun",
|
||||
icon="mdi:weather-partly-cloudy",
|
||||
native_unit_of_measurement=UnitOfTime.HOURS,
|
||||
value_fn=lambda data: cast(float, data),
|
||||
translation_key=f"hours_of_sun_{day}d",
|
||||
day=day,
|
||||
)
|
||||
for day in range(MAX_FORECAST_DAYS + 1)
|
||||
AccuWeatherSensorDescription(
|
||||
key="HoursOfSun",
|
||||
icon="mdi:weather-partly-cloudy",
|
||||
name="Hours of sun",
|
||||
native_unit_of_measurement=UnitOfTime.HOURS,
|
||||
value_fn=lambda data: cast(float, data),
|
||||
),
|
||||
*(
|
||||
AccuWeatherSensorDescription(
|
||||
key="LongPhraseDay",
|
||||
value_fn=lambda data: cast(str, data),
|
||||
translation_key=f"condition_day_{day}d",
|
||||
day=day,
|
||||
)
|
||||
for day in range(MAX_FORECAST_DAYS + 1)
|
||||
AccuWeatherSensorDescription(
|
||||
key="LongPhraseDay",
|
||||
name="Condition day",
|
||||
value_fn=lambda data: cast(str, data),
|
||||
),
|
||||
*(
|
||||
AccuWeatherSensorDescription(
|
||||
key="LongPhraseNight",
|
||||
value_fn=lambda data: cast(str, data),
|
||||
translation_key=f"condition_night_{day}d",
|
||||
day=day,
|
||||
)
|
||||
for day in range(MAX_FORECAST_DAYS + 1)
|
||||
AccuWeatherSensorDescription(
|
||||
key="LongPhraseNight",
|
||||
name="Condition night",
|
||||
value_fn=lambda data: cast(str, data),
|
||||
),
|
||||
*(
|
||||
AccuWeatherSensorDescription(
|
||||
key="Mold",
|
||||
icon="mdi:blur",
|
||||
entity_registry_enabled_default=False,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||
value_fn=lambda data: cast(int, data[ATTR_VALUE]),
|
||||
attr_fn=lambda data: {ATTR_LEVEL: data[ATTR_CATEGORY]},
|
||||
translation_key=f"mold_pollen_{day}d",
|
||||
day=day,
|
||||
)
|
||||
for day in range(MAX_FORECAST_DAYS + 1)
|
||||
AccuWeatherSensorDescription(
|
||||
key="Mold",
|
||||
icon="mdi:blur",
|
||||
name="Mold pollen",
|
||||
entity_registry_enabled_default=False,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||
value_fn=lambda data: cast(int, data[ATTR_VALUE]),
|
||||
attr_fn=lambda data: {ATTR_LEVEL: data[ATTR_CATEGORY]},
|
||||
translation_key="mold_pollen",
|
||||
),
|
||||
*(
|
||||
AccuWeatherSensorDescription(
|
||||
key="Ragweed",
|
||||
icon="mdi:sprout",
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||
entity_registry_enabled_default=False,
|
||||
value_fn=lambda data: cast(int, data[ATTR_VALUE]),
|
||||
attr_fn=lambda data: {ATTR_LEVEL: data[ATTR_CATEGORY]},
|
||||
translation_key=f"ragweed_pollen_{day}d",
|
||||
day=day,
|
||||
)
|
||||
for day in range(MAX_FORECAST_DAYS + 1)
|
||||
AccuWeatherSensorDescription(
|
||||
key="Ragweed",
|
||||
icon="mdi:sprout",
|
||||
name="Ragweed pollen",
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||
entity_registry_enabled_default=False,
|
||||
value_fn=lambda data: cast(int, data[ATTR_VALUE]),
|
||||
attr_fn=lambda data: {ATTR_LEVEL: data[ATTR_CATEGORY]},
|
||||
translation_key="ragweed_pollen",
|
||||
),
|
||||
*(
|
||||
AccuWeatherSensorDescription(
|
||||
key="RealFeelTemperatureMax",
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||
value_fn=lambda data: cast(float, data[ATTR_VALUE]),
|
||||
translation_key=f"realfeel_temperature_max_{day}d",
|
||||
day=day,
|
||||
)
|
||||
for day in range(MAX_FORECAST_DAYS + 1)
|
||||
AccuWeatherSensorDescription(
|
||||
key="RealFeelTemperatureMax",
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
name="RealFeel temperature max",
|
||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||
value_fn=lambda data: cast(float, data[ATTR_VALUE]),
|
||||
),
|
||||
*(
|
||||
AccuWeatherSensorDescription(
|
||||
key="RealFeelTemperatureMin",
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||
value_fn=lambda data: cast(float, data[ATTR_VALUE]),
|
||||
translation_key=f"realfeel_temperature_min_{day}d",
|
||||
day=day,
|
||||
)
|
||||
for day in range(MAX_FORECAST_DAYS + 1)
|
||||
AccuWeatherSensorDescription(
|
||||
key="RealFeelTemperatureMin",
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
name="RealFeel temperature min",
|
||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||
value_fn=lambda data: cast(float, data[ATTR_VALUE]),
|
||||
),
|
||||
*(
|
||||
AccuWeatherSensorDescription(
|
||||
key="RealFeelTemperatureShadeMax",
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
entity_registry_enabled_default=False,
|
||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||
value_fn=lambda data: cast(float, data[ATTR_VALUE]),
|
||||
translation_key=f"realfeel_temperature_shade_max_{day}d",
|
||||
day=day,
|
||||
)
|
||||
for day in range(MAX_FORECAST_DAYS + 1)
|
||||
AccuWeatherSensorDescription(
|
||||
key="RealFeelTemperatureShadeMax",
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
name="RealFeel temperature shade max",
|
||||
entity_registry_enabled_default=False,
|
||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||
value_fn=lambda data: cast(float, data[ATTR_VALUE]),
|
||||
),
|
||||
*(
|
||||
AccuWeatherSensorDescription(
|
||||
key="RealFeelTemperatureShadeMin",
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
entity_registry_enabled_default=False,
|
||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||
value_fn=lambda data: cast(float, data[ATTR_VALUE]),
|
||||
translation_key=f"realfeel_temperature_shade_min_{day}d",
|
||||
day=day,
|
||||
)
|
||||
for day in range(MAX_FORECAST_DAYS + 1)
|
||||
AccuWeatherSensorDescription(
|
||||
key="RealFeelTemperatureShadeMin",
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
name="RealFeel temperature shade min",
|
||||
entity_registry_enabled_default=False,
|
||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||
value_fn=lambda data: cast(float, data[ATTR_VALUE]),
|
||||
),
|
||||
*(
|
||||
AccuWeatherSensorDescription(
|
||||
key="SolarIrradianceDay",
|
||||
icon="mdi:weather-sunny",
|
||||
entity_registry_enabled_default=False,
|
||||
native_unit_of_measurement=UnitOfIrradiance.WATTS_PER_SQUARE_METER,
|
||||
value_fn=lambda data: cast(float, data[ATTR_VALUE]),
|
||||
translation_key=f"solar_irradiance_day_{day}d",
|
||||
day=day,
|
||||
)
|
||||
for day in range(MAX_FORECAST_DAYS + 1)
|
||||
AccuWeatherSensorDescription(
|
||||
key="SolarIrradianceDay",
|
||||
icon="mdi:weather-sunny",
|
||||
name="Solar irradiance day",
|
||||
entity_registry_enabled_default=False,
|
||||
native_unit_of_measurement=UnitOfIrradiance.WATTS_PER_SQUARE_METER,
|
||||
value_fn=lambda data: cast(float, data[ATTR_VALUE]),
|
||||
),
|
||||
*(
|
||||
AccuWeatherSensorDescription(
|
||||
key="SolarIrradianceNight",
|
||||
icon="mdi:weather-sunny",
|
||||
entity_registry_enabled_default=False,
|
||||
native_unit_of_measurement=UnitOfIrradiance.WATTS_PER_SQUARE_METER,
|
||||
value_fn=lambda data: cast(float, data[ATTR_VALUE]),
|
||||
translation_key=f"solar_irradiance_night_{day}d",
|
||||
day=day,
|
||||
)
|
||||
for day in range(MAX_FORECAST_DAYS + 1)
|
||||
AccuWeatherSensorDescription(
|
||||
key="SolarIrradianceNight",
|
||||
icon="mdi:weather-sunny",
|
||||
name="Solar irradiance night",
|
||||
entity_registry_enabled_default=False,
|
||||
native_unit_of_measurement=UnitOfIrradiance.WATTS_PER_SQUARE_METER,
|
||||
value_fn=lambda data: cast(float, data[ATTR_VALUE]),
|
||||
),
|
||||
*(
|
||||
AccuWeatherSensorDescription(
|
||||
key="ThunderstormProbabilityDay",
|
||||
icon="mdi:weather-lightning",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
value_fn=lambda data: cast(int, data),
|
||||
translation_key=f"thunderstorm_probability_day_{day}d",
|
||||
day=day,
|
||||
)
|
||||
for day in range(MAX_FORECAST_DAYS + 1)
|
||||
AccuWeatherSensorDescription(
|
||||
key="ThunderstormProbabilityDay",
|
||||
icon="mdi:weather-lightning",
|
||||
name="Thunderstorm probability day",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
value_fn=lambda data: cast(int, data),
|
||||
),
|
||||
*(
|
||||
AccuWeatherSensorDescription(
|
||||
key="ThunderstormProbabilityNight",
|
||||
icon="mdi:weather-lightning",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
value_fn=lambda data: cast(int, data),
|
||||
translation_key=f"thunderstorm_probability_night_{day}d",
|
||||
day=day,
|
||||
)
|
||||
for day in range(MAX_FORECAST_DAYS + 1)
|
||||
AccuWeatherSensorDescription(
|
||||
key="ThunderstormProbabilityNight",
|
||||
icon="mdi:weather-lightning",
|
||||
name="Thunderstorm probability night",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
value_fn=lambda data: cast(int, data),
|
||||
),
|
||||
*(
|
||||
AccuWeatherSensorDescription(
|
||||
key="Tree",
|
||||
icon="mdi:tree-outline",
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||
entity_registry_enabled_default=False,
|
||||
value_fn=lambda data: cast(int, data[ATTR_VALUE]),
|
||||
attr_fn=lambda data: {ATTR_LEVEL: data[ATTR_CATEGORY]},
|
||||
translation_key=f"tree_pollen_{day}d",
|
||||
day=day,
|
||||
)
|
||||
for day in range(MAX_FORECAST_DAYS + 1)
|
||||
AccuWeatherSensorDescription(
|
||||
key="Tree",
|
||||
icon="mdi:tree-outline",
|
||||
name="Tree pollen",
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||
entity_registry_enabled_default=False,
|
||||
value_fn=lambda data: cast(int, data[ATTR_VALUE]),
|
||||
attr_fn=lambda data: {ATTR_LEVEL: data[ATTR_CATEGORY]},
|
||||
translation_key="tree_pollen",
|
||||
),
|
||||
*(
|
||||
AccuWeatherSensorDescription(
|
||||
key="UVIndex",
|
||||
icon="mdi:weather-sunny",
|
||||
native_unit_of_measurement=UV_INDEX,
|
||||
value_fn=lambda data: cast(int, data[ATTR_VALUE]),
|
||||
attr_fn=lambda data: {ATTR_LEVEL: data[ATTR_CATEGORY]},
|
||||
translation_key=f"uv_index_{day}d",
|
||||
day=day,
|
||||
)
|
||||
for day in range(MAX_FORECAST_DAYS + 1)
|
||||
AccuWeatherSensorDescription(
|
||||
key="UVIndex",
|
||||
icon="mdi:weather-sunny",
|
||||
name="UV index",
|
||||
native_unit_of_measurement=UV_INDEX,
|
||||
value_fn=lambda data: cast(int, data[ATTR_VALUE]),
|
||||
attr_fn=lambda data: {ATTR_LEVEL: data[ATTR_CATEGORY]},
|
||||
translation_key="uv_index",
|
||||
),
|
||||
*(
|
||||
AccuWeatherSensorDescription(
|
||||
key="WindGustDay",
|
||||
device_class=SensorDeviceClass.WIND_SPEED,
|
||||
entity_registry_enabled_default=False,
|
||||
native_unit_of_measurement=UnitOfSpeed.KILOMETERS_PER_HOUR,
|
||||
value_fn=lambda data: cast(float, data[ATTR_SPEED][ATTR_VALUE]),
|
||||
attr_fn=lambda data: {"direction": data[ATTR_DIRECTION][ATTR_ENGLISH]},
|
||||
translation_key=f"wind_gust_speed_day_{day}d",
|
||||
day=day,
|
||||
)
|
||||
for day in range(MAX_FORECAST_DAYS + 1)
|
||||
AccuWeatherSensorDescription(
|
||||
key="WindGustDay",
|
||||
device_class=SensorDeviceClass.WIND_SPEED,
|
||||
name="Wind gust day",
|
||||
entity_registry_enabled_default=False,
|
||||
native_unit_of_measurement=UnitOfSpeed.KILOMETERS_PER_HOUR,
|
||||
value_fn=lambda data: cast(float, data[ATTR_SPEED][ATTR_VALUE]),
|
||||
attr_fn=lambda data: {"direction": data[ATTR_DIRECTION][ATTR_ENGLISH]},
|
||||
),
|
||||
*(
|
||||
AccuWeatherSensorDescription(
|
||||
key="WindGustNight",
|
||||
device_class=SensorDeviceClass.WIND_SPEED,
|
||||
entity_registry_enabled_default=False,
|
||||
native_unit_of_measurement=UnitOfSpeed.KILOMETERS_PER_HOUR,
|
||||
value_fn=lambda data: cast(float, data[ATTR_SPEED][ATTR_VALUE]),
|
||||
attr_fn=lambda data: {"direction": data[ATTR_DIRECTION][ATTR_ENGLISH]},
|
||||
translation_key=f"wind_gust_speed_night_{day}d",
|
||||
day=day,
|
||||
)
|
||||
for day in range(MAX_FORECAST_DAYS + 1)
|
||||
AccuWeatherSensorDescription(
|
||||
key="WindGustNight",
|
||||
device_class=SensorDeviceClass.WIND_SPEED,
|
||||
name="Wind gust night",
|
||||
entity_registry_enabled_default=False,
|
||||
native_unit_of_measurement=UnitOfSpeed.KILOMETERS_PER_HOUR,
|
||||
value_fn=lambda data: cast(float, data[ATTR_SPEED][ATTR_VALUE]),
|
||||
attr_fn=lambda data: {"direction": data[ATTR_DIRECTION][ATTR_ENGLISH]},
|
||||
),
|
||||
*(
|
||||
AccuWeatherSensorDescription(
|
||||
key="WindDay",
|
||||
device_class=SensorDeviceClass.WIND_SPEED,
|
||||
native_unit_of_measurement=UnitOfSpeed.KILOMETERS_PER_HOUR,
|
||||
value_fn=lambda data: cast(float, data[ATTR_SPEED][ATTR_VALUE]),
|
||||
attr_fn=lambda data: {"direction": data[ATTR_DIRECTION][ATTR_ENGLISH]},
|
||||
translation_key=f"wind_speed_day_{day}d",
|
||||
day=day,
|
||||
)
|
||||
for day in range(MAX_FORECAST_DAYS + 1)
|
||||
AccuWeatherSensorDescription(
|
||||
key="WindDay",
|
||||
device_class=SensorDeviceClass.WIND_SPEED,
|
||||
name="Wind day",
|
||||
native_unit_of_measurement=UnitOfSpeed.KILOMETERS_PER_HOUR,
|
||||
value_fn=lambda data: cast(float, data[ATTR_SPEED][ATTR_VALUE]),
|
||||
attr_fn=lambda data: {"direction": data[ATTR_DIRECTION][ATTR_ENGLISH]},
|
||||
),
|
||||
*(
|
||||
AccuWeatherSensorDescription(
|
||||
key="WindNight",
|
||||
device_class=SensorDeviceClass.WIND_SPEED,
|
||||
native_unit_of_measurement=UnitOfSpeed.KILOMETERS_PER_HOUR,
|
||||
value_fn=lambda data: cast(float, data[ATTR_SPEED][ATTR_VALUE]),
|
||||
attr_fn=lambda data: {"direction": data[ATTR_DIRECTION][ATTR_ENGLISH]},
|
||||
translation_key=f"wind_speed_night_{day}d",
|
||||
day=day,
|
||||
)
|
||||
for day in range(MAX_FORECAST_DAYS + 1)
|
||||
AccuWeatherSensorDescription(
|
||||
key="WindNight",
|
||||
device_class=SensorDeviceClass.WIND_SPEED,
|
||||
name="Wind night",
|
||||
native_unit_of_measurement=UnitOfSpeed.KILOMETERS_PER_HOUR,
|
||||
value_fn=lambda data: cast(float, data[ATTR_SPEED][ATTR_VALUE]),
|
||||
attr_fn=lambda data: {"direction": data[ATTR_DIRECTION][ATTR_ENGLISH]},
|
||||
),
|
||||
)
|
||||
|
||||
@@ -340,117 +254,118 @@ SENSOR_TYPES: tuple[AccuWeatherSensorDescription, ...] = (
|
||||
AccuWeatherSensorDescription(
|
||||
key="ApparentTemperature",
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
name="Apparent temperature",
|
||||
entity_registry_enabled_default=False,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||
value_fn=lambda data: cast(float, data[API_METRIC][ATTR_VALUE]),
|
||||
translation_key="apparent_temperature",
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="Ceiling",
|
||||
device_class=SensorDeviceClass.DISTANCE,
|
||||
icon="mdi:weather-fog",
|
||||
name="Cloud ceiling",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UnitOfLength.METERS,
|
||||
value_fn=lambda data: cast(float, data[API_METRIC][ATTR_VALUE]),
|
||||
suggested_display_precision=0,
|
||||
translation_key="cloud_ceiling",
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="CloudCover",
|
||||
icon="mdi:weather-cloudy",
|
||||
name="Cloud cover",
|
||||
entity_registry_enabled_default=False,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
value_fn=lambda data: cast(int, data),
|
||||
translation_key="cloud_cover",
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="DewPoint",
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
name="Dew point",
|
||||
entity_registry_enabled_default=False,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||
value_fn=lambda data: cast(float, data[API_METRIC][ATTR_VALUE]),
|
||||
translation_key="dew_point",
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="RealFeelTemperature",
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
name="RealFeel temperature",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||
value_fn=lambda data: cast(float, data[API_METRIC][ATTR_VALUE]),
|
||||
translation_key="realfeel_temperature",
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="RealFeelTemperatureShade",
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
name="RealFeel temperature shade",
|
||||
entity_registry_enabled_default=False,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||
value_fn=lambda data: cast(float, data[API_METRIC][ATTR_VALUE]),
|
||||
translation_key="realfeel_temperature_shade",
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="Precipitation",
|
||||
device_class=SensorDeviceClass.PRECIPITATION_INTENSITY,
|
||||
name="Precipitation",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UnitOfVolumetricFlux.MILLIMETERS_PER_HOUR,
|
||||
value_fn=lambda data: cast(float, data[API_METRIC][ATTR_VALUE]),
|
||||
attr_fn=lambda data: {"type": data["PrecipitationType"]},
|
||||
translation_key="precipitation",
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="PressureTendency",
|
||||
device_class=SensorDeviceClass.ENUM,
|
||||
icon="mdi:gauge",
|
||||
name="Pressure tendency",
|
||||
options=["falling", "rising", "steady"],
|
||||
value_fn=lambda data: cast(str, data["LocalizedText"]).lower(),
|
||||
translation_key="pressure_tendency",
|
||||
value_fn=lambda data: cast(str, data["LocalizedText"]).lower(),
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="UVIndex",
|
||||
icon="mdi:weather-sunny",
|
||||
name="UV index",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UV_INDEX,
|
||||
value_fn=lambda data: cast(int, data),
|
||||
attr_fn=lambda data: {ATTR_LEVEL: data["UVIndexText"]},
|
||||
translation_key="uv_index",
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="WetBulbTemperature",
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
name="Wet bulb temperature",
|
||||
entity_registry_enabled_default=False,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||
value_fn=lambda data: cast(float, data[API_METRIC][ATTR_VALUE]),
|
||||
translation_key="wet_bulb_temperature",
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="WindChillTemperature",
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
name="Wind chill temperature",
|
||||
entity_registry_enabled_default=False,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||
value_fn=lambda data: cast(float, data[API_METRIC][ATTR_VALUE]),
|
||||
translation_key="wind_chill_temperature",
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="Wind",
|
||||
device_class=SensorDeviceClass.WIND_SPEED,
|
||||
name="Wind",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UnitOfSpeed.KILOMETERS_PER_HOUR,
|
||||
value_fn=lambda data: cast(float, data[ATTR_SPEED][API_METRIC][ATTR_VALUE]),
|
||||
translation_key="wind_speed",
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="WindGust",
|
||||
device_class=SensorDeviceClass.WIND_SPEED,
|
||||
name="Wind gust",
|
||||
entity_registry_enabled_default=False,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UnitOfSpeed.KILOMETERS_PER_HOUR,
|
||||
value_fn=lambda data: cast(float, data[ATTR_SPEED][API_METRIC][ATTR_VALUE]),
|
||||
translation_key="wind_gust_speed",
|
||||
),
|
||||
)
|
||||
|
||||
@@ -467,12 +382,14 @@ async def async_setup_entry(
|
||||
]
|
||||
|
||||
if coordinator.forecast:
|
||||
for description in FORECAST_SENSOR_TYPES:
|
||||
# Some air quality/allergy sensors are only available for certain
|
||||
# locations.
|
||||
if description.key not in coordinator.data[ATTR_FORECAST][description.day]:
|
||||
continue
|
||||
sensors.append(AccuWeatherSensor(coordinator, description))
|
||||
# Some air quality/allergy sensors are only available for certain
|
||||
# locations.
|
||||
sensors.extend(
|
||||
AccuWeatherSensor(coordinator, description, forecast_day=day)
|
||||
for day in range(MAX_FORECAST_DAYS + 1)
|
||||
for description in FORECAST_SENSOR_TYPES
|
||||
if description.key in coordinator.data[ATTR_FORECAST][0]
|
||||
)
|
||||
|
||||
async_add_entities(sensors)
|
||||
|
||||
@@ -490,24 +407,28 @@ class AccuWeatherSensor(
|
||||
self,
|
||||
coordinator: AccuWeatherDataUpdateCoordinator,
|
||||
description: AccuWeatherSensorDescription,
|
||||
forecast_day: int | None = None,
|
||||
) -> None:
|
||||
"""Initialize."""
|
||||
super().__init__(coordinator)
|
||||
self.forecast_day = description.day
|
||||
self.entity_description = description
|
||||
self._sensor_data = _get_sensor_data(
|
||||
coordinator.data, description.key, self.forecast_day
|
||||
coordinator.data, description.key, forecast_day
|
||||
)
|
||||
if self.forecast_day is not None:
|
||||
self._attr_unique_id = f"{coordinator.location_key}-{description.key}-{self.forecast_day}".lower()
|
||||
if forecast_day is not None:
|
||||
self._attr_name = f"{description.name} {forecast_day}d"
|
||||
self._attr_unique_id = (
|
||||
f"{coordinator.location_key}-{description.key}-{forecast_day}".lower()
|
||||
)
|
||||
else:
|
||||
self._attr_unique_id = (
|
||||
f"{coordinator.location_key}-{description.key}".lower()
|
||||
)
|
||||
self._attr_device_info = coordinator.device_info
|
||||
self.forecast_day = forecast_day
|
||||
|
||||
@property
|
||||
def native_value(self) -> str | int | float | None:
|
||||
def native_value(self) -> StateType:
|
||||
"""Return the state."""
|
||||
return self.entity_description.value_fn(self._sensor_data)
|
||||
|
||||
|
||||
@@ -24,8 +24,14 @@
|
||||
},
|
||||
"entity": {
|
||||
"sensor": {
|
||||
"air_quality_0d": {
|
||||
"name": "Air quality today",
|
||||
"pressure_tendency": {
|
||||
"state": {
|
||||
"steady": "Steady",
|
||||
"rising": "Rising",
|
||||
"falling": "Falling"
|
||||
}
|
||||
},
|
||||
"air_quality": {
|
||||
"state": {
|
||||
"good": "Good",
|
||||
"hazardous": "Hazardous",
|
||||
@@ -35,761 +41,80 @@
|
||||
"unhealthy": "Unhealthy"
|
||||
}
|
||||
},
|
||||
"air_quality_1d": {
|
||||
"name": "Air quality day 1",
|
||||
"state": {
|
||||
"good": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::good%]",
|
||||
"hazardous": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::hazardous%]",
|
||||
"high": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::high%]",
|
||||
"low": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::low%]",
|
||||
"moderate": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::moderate%]",
|
||||
"unhealthy": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::unhealthy%]"
|
||||
}
|
||||
},
|
||||
"air_quality_2d": {
|
||||
"name": "Air quality day 2",
|
||||
"state": {
|
||||
"good": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::good%]",
|
||||
"hazardous": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::hazardous%]",
|
||||
"high": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::high%]",
|
||||
"low": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::low%]",
|
||||
"moderate": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::moderate%]",
|
||||
"unhealthy": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::unhealthy%]"
|
||||
}
|
||||
},
|
||||
"air_quality_3d": {
|
||||
"name": "Air quality day 3",
|
||||
"state": {
|
||||
"good": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::good%]",
|
||||
"hazardous": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::hazardous%]",
|
||||
"high": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::high%]",
|
||||
"low": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::low%]",
|
||||
"moderate": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::moderate%]",
|
||||
"unhealthy": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::unhealthy%]"
|
||||
}
|
||||
},
|
||||
"air_quality_4d": {
|
||||
"name": "Air quality day 4",
|
||||
"state": {
|
||||
"good": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::good%]",
|
||||
"hazardous": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::hazardous%]",
|
||||
"high": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::high%]",
|
||||
"low": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::low%]",
|
||||
"moderate": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::moderate%]",
|
||||
"unhealthy": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::unhealthy%]"
|
||||
}
|
||||
},
|
||||
"apparent_temperature": {
|
||||
"name": "Apparent temperature"
|
||||
},
|
||||
"cloud_ceiling": {
|
||||
"name": "Cloud ceiling"
|
||||
},
|
||||
"cloud_cover": {
|
||||
"name": "Cloud cover"
|
||||
},
|
||||
"cloud_cover_day_0d": {
|
||||
"name": "Cloud cover today"
|
||||
},
|
||||
"cloud_cover_day_1d": {
|
||||
"name": "Cloud cover day 1"
|
||||
},
|
||||
"cloud_cover_day_2d": {
|
||||
"name": "Cloud cover day 2"
|
||||
},
|
||||
"cloud_cover_day_3d": {
|
||||
"name": "Cloud cover day 3"
|
||||
},
|
||||
"cloud_cover_day_4d": {
|
||||
"name": "Cloud cover day 4"
|
||||
},
|
||||
"cloud_cover_night_0d": {
|
||||
"name": "Cloud cover tonight"
|
||||
},
|
||||
"cloud_cover_night_1d": {
|
||||
"name": "Cloud cover night 1"
|
||||
},
|
||||
"cloud_cover_night_2d": {
|
||||
"name": "Cloud cover night 2"
|
||||
},
|
||||
"cloud_cover_night_3d": {
|
||||
"name": "Cloud cover night 3"
|
||||
},
|
||||
"cloud_cover_night_4d": {
|
||||
"name": "Cloud cover night 4"
|
||||
},
|
||||
"condition_day_0d": {
|
||||
"name": "Condition today"
|
||||
},
|
||||
"condition_day_1d": {
|
||||
"name": "Condition day 1"
|
||||
},
|
||||
"condition_day_2d": {
|
||||
"name": "Condition day 2"
|
||||
},
|
||||
"condition_day_3d": {
|
||||
"name": "Condition day 3"
|
||||
},
|
||||
"condition_day_4d": {
|
||||
"name": "Condition day 4"
|
||||
},
|
||||
"condition_night_0d": {
|
||||
"name": "Condition tonight"
|
||||
},
|
||||
"condition_night_1d": {
|
||||
"name": "Condition night 1"
|
||||
},
|
||||
"condition_night_2d": {
|
||||
"name": "Condition night 2"
|
||||
},
|
||||
"condition_night_3d": {
|
||||
"name": "Condition night 3"
|
||||
},
|
||||
"condition_night_4d": {
|
||||
"name": "Condition night 4"
|
||||
},
|
||||
"dew_point": {
|
||||
"name": "Dew point"
|
||||
},
|
||||
"grass_pollen_0d": {
|
||||
"name": "Grass pollen today",
|
||||
"grass_pollen": {
|
||||
"state_attributes": {
|
||||
"level": {
|
||||
"name": "Level",
|
||||
"state": {
|
||||
"good": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::good%]",
|
||||
"hazardous": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::hazardous%]",
|
||||
"high": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::high%]",
|
||||
"low": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::low%]",
|
||||
"moderate": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::moderate%]",
|
||||
"unhealthy": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::unhealthy%]"
|
||||
"good": "[%key:component::accuweather::entity::sensor::air_quality::state::good%]",
|
||||
"hazardous": "[%key:component::accuweather::entity::sensor::air_quality::state::hazardous%]",
|
||||
"high": "[%key:component::accuweather::entity::sensor::air_quality::state::high%]",
|
||||
"low": "[%key:component::accuweather::entity::sensor::air_quality::state::low%]",
|
||||
"moderate": "[%key:component::accuweather::entity::sensor::air_quality::state::moderate%]",
|
||||
"unhealthy": "[%key:component::accuweather::entity::sensor::air_quality::state::unhealthy%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"grass_pollen_1d": {
|
||||
"name": "Grass pollen day 1",
|
||||
"mold_pollen": {
|
||||
"state_attributes": {
|
||||
"level": {
|
||||
"name": "[%key:component::accuweather::entity::sensor::grass_pollen_0d::state_attributes::level::name%]",
|
||||
"name": "Level",
|
||||
"state": {
|
||||
"good": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::good%]",
|
||||
"hazardous": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::hazardous%]",
|
||||
"high": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::high%]",
|
||||
"low": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::low%]",
|
||||
"moderate": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::moderate%]",
|
||||
"unhealthy": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::unhealthy%]"
|
||||
"good": "[%key:component::accuweather::entity::sensor::air_quality::state::good%]",
|
||||
"hazardous": "[%key:component::accuweather::entity::sensor::air_quality::state::hazardous%]",
|
||||
"high": "[%key:component::accuweather::entity::sensor::air_quality::state::high%]",
|
||||
"low": "[%key:component::accuweather::entity::sensor::air_quality::state::low%]",
|
||||
"moderate": "[%key:component::accuweather::entity::sensor::air_quality::state::moderate%]",
|
||||
"unhealthy": "[%key:component::accuweather::entity::sensor::air_quality::state::unhealthy%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"grass_pollen_2d": {
|
||||
"name": "Grass pollen day 2",
|
||||
"ragweed_pollen": {
|
||||
"state_attributes": {
|
||||
"level": {
|
||||
"name": "[%key:component::accuweather::entity::sensor::grass_pollen_0d::state_attributes::level::name%]",
|
||||
"name": "Level",
|
||||
"state": {
|
||||
"good": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::good%]",
|
||||
"hazardous": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::hazardous%]",
|
||||
"high": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::high%]",
|
||||
"low": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::low%]",
|
||||
"moderate": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::moderate%]",
|
||||
"unhealthy": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::unhealthy%]"
|
||||
"good": "[%key:component::accuweather::entity::sensor::air_quality::state::good%]",
|
||||
"hazardous": "[%key:component::accuweather::entity::sensor::air_quality::state::hazardous%]",
|
||||
"high": "[%key:component::accuweather::entity::sensor::air_quality::state::high%]",
|
||||
"low": "[%key:component::accuweather::entity::sensor::air_quality::state::low%]",
|
||||
"moderate": "[%key:component::accuweather::entity::sensor::air_quality::state::moderate%]",
|
||||
"unhealthy": "[%key:component::accuweather::entity::sensor::air_quality::state::unhealthy%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"grass_pollen_3d": {
|
||||
"name": "Grass pollen day 3",
|
||||
"tree_pollen": {
|
||||
"state_attributes": {
|
||||
"level": {
|
||||
"name": "[%key:component::accuweather::entity::sensor::grass_pollen_0d::state_attributes::level::name%]",
|
||||
"name": "Level",
|
||||
"state": {
|
||||
"good": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::good%]",
|
||||
"hazardous": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::hazardous%]",
|
||||
"high": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::high%]",
|
||||
"low": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::low%]",
|
||||
"moderate": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::moderate%]",
|
||||
"unhealthy": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::unhealthy%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"grass_pollen_4d": {
|
||||
"name": "Grass pollen day 4",
|
||||
"state_attributes": {
|
||||
"level": {
|
||||
"name": "[%key:component::accuweather::entity::sensor::grass_pollen_0d::state_attributes::level::name%]",
|
||||
"state": {
|
||||
"good": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::good%]",
|
||||
"hazardous": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::hazardous%]",
|
||||
"high": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::high%]",
|
||||
"low": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::low%]",
|
||||
"moderate": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::moderate%]",
|
||||
"unhealthy": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::unhealthy%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"hours_of_sun_0d": {
|
||||
"name": "Hours of sun today"
|
||||
},
|
||||
"hours_of_sun_1d": {
|
||||
"name": "Hours of sun day 1"
|
||||
},
|
||||
"hours_of_sun_2d": {
|
||||
"name": "Hours of sun day 2"
|
||||
},
|
||||
"hours_of_sun_3d": {
|
||||
"name": "Hours of sun day 3"
|
||||
},
|
||||
"hours_of_sun_4d": {
|
||||
"name": "Hours of sun day 4"
|
||||
},
|
||||
"mold_pollen_0d": {
|
||||
"name": "Mold pollen today",
|
||||
"state_attributes": {
|
||||
"level": {
|
||||
"name": "[%key:component::accuweather::entity::sensor::grass_pollen_0d::state_attributes::level::name%]",
|
||||
"state": {
|
||||
"good": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::good%]",
|
||||
"hazardous": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::hazardous%]",
|
||||
"high": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::high%]",
|
||||
"low": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::low%]",
|
||||
"moderate": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::moderate%]",
|
||||
"unhealthy": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::unhealthy%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"mold_pollen_1d": {
|
||||
"name": "Mold pollen day 1",
|
||||
"state_attributes": {
|
||||
"level": {
|
||||
"name": "[%key:component::accuweather::entity::sensor::grass_pollen_0d::state_attributes::level::name%]",
|
||||
"state": {
|
||||
"good": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::good%]",
|
||||
"hazardous": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::hazardous%]",
|
||||
"high": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::high%]",
|
||||
"low": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::low%]",
|
||||
"moderate": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::moderate%]",
|
||||
"unhealthy": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::unhealthy%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"mold_pollen_2d": {
|
||||
"name": "Mold pollen day 2",
|
||||
"state_attributes": {
|
||||
"level": {
|
||||
"name": "[%key:component::accuweather::entity::sensor::grass_pollen_0d::state_attributes::level::name%]",
|
||||
"state": {
|
||||
"good": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::good%]",
|
||||
"hazardous": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::hazardous%]",
|
||||
"high": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::high%]",
|
||||
"low": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::low%]",
|
||||
"moderate": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::moderate%]",
|
||||
"unhealthy": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::unhealthy%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"mold_pollen_3d": {
|
||||
"name": "Mold pollen day 3",
|
||||
"state_attributes": {
|
||||
"level": {
|
||||
"name": "[%key:component::accuweather::entity::sensor::grass_pollen_0d::state_attributes::level::name%]",
|
||||
"state": {
|
||||
"good": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::good%]",
|
||||
"hazardous": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::hazardous%]",
|
||||
"high": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::high%]",
|
||||
"low": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::low%]",
|
||||
"moderate": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::moderate%]",
|
||||
"unhealthy": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::unhealthy%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"mold_pollen_4d": {
|
||||
"name": "Mold pollen day 4",
|
||||
"state_attributes": {
|
||||
"level": {
|
||||
"name": "[%key:component::accuweather::entity::sensor::grass_pollen_0d::state_attributes::level::name%]",
|
||||
"state": {
|
||||
"good": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::good%]",
|
||||
"hazardous": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::hazardous%]",
|
||||
"high": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::high%]",
|
||||
"low": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::low%]",
|
||||
"moderate": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::moderate%]",
|
||||
"unhealthy": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::unhealthy%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"precipitation": {
|
||||
"name": "[%key:component::sensor::entity_component::precipitation::name%]"
|
||||
},
|
||||
"pressure_tendency": {
|
||||
"name": "Pressure tendency",
|
||||
"state": {
|
||||
"steady": "Steady",
|
||||
"rising": "Rising",
|
||||
"falling": "Falling"
|
||||
}
|
||||
},
|
||||
"ragweed_pollen_0d": {
|
||||
"name": "Ragweed pollen today",
|
||||
"state_attributes": {
|
||||
"level": {
|
||||
"name": "[%key:component::accuweather::entity::sensor::grass_pollen_0d::state_attributes::level::name%]",
|
||||
"state": {
|
||||
"good": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::good%]",
|
||||
"hazardous": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::hazardous%]",
|
||||
"high": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::high%]",
|
||||
"low": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::low%]",
|
||||
"moderate": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::moderate%]",
|
||||
"unhealthy": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::unhealthy%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"ragweed_pollen_1d": {
|
||||
"name": "Ragweed pollen day 1",
|
||||
"state_attributes": {
|
||||
"level": {
|
||||
"name": "[%key:component::accuweather::entity::sensor::grass_pollen_0d::state_attributes::level::name%]",
|
||||
"state": {
|
||||
"good": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::good%]",
|
||||
"hazardous": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::hazardous%]",
|
||||
"high": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::high%]",
|
||||
"low": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::low%]",
|
||||
"moderate": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::moderate%]",
|
||||
"unhealthy": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::unhealthy%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"ragweed_pollen_2d": {
|
||||
"name": "Ragweed pollen day 2",
|
||||
"state_attributes": {
|
||||
"level": {
|
||||
"name": "[%key:component::accuweather::entity::sensor::grass_pollen_0d::state_attributes::level::name%]",
|
||||
"state": {
|
||||
"good": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::good%]",
|
||||
"hazardous": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::hazardous%]",
|
||||
"high": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::high%]",
|
||||
"low": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::low%]",
|
||||
"moderate": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::moderate%]",
|
||||
"unhealthy": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::unhealthy%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"ragweed_pollen_3d": {
|
||||
"name": "Ragweed pollen day 3",
|
||||
"state_attributes": {
|
||||
"level": {
|
||||
"name": "[%key:component::accuweather::entity::sensor::grass_pollen_0d::state_attributes::level::name%]",
|
||||
"state": {
|
||||
"good": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::good%]",
|
||||
"hazardous": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::hazardous%]",
|
||||
"high": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::high%]",
|
||||
"low": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::low%]",
|
||||
"moderate": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::moderate%]",
|
||||
"unhealthy": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::unhealthy%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"ragweed_pollen_4d": {
|
||||
"name": "Ragweed pollen day 4",
|
||||
"state_attributes": {
|
||||
"level": {
|
||||
"name": "[%key:component::accuweather::entity::sensor::grass_pollen_0d::state_attributes::level::name%]",
|
||||
"state": {
|
||||
"good": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::good%]",
|
||||
"hazardous": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::hazardous%]",
|
||||
"high": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::high%]",
|
||||
"low": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::low%]",
|
||||
"moderate": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::moderate%]",
|
||||
"unhealthy": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::unhealthy%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"realfeel_temperature": {
|
||||
"name": "RealFeel temperature"
|
||||
},
|
||||
"realfeel_temperature_max_0d": {
|
||||
"name": "RealFeel temperature max today"
|
||||
},
|
||||
"realfeel_temperature_max_1d": {
|
||||
"name": "RealFeel temperature max day 1"
|
||||
},
|
||||
"realfeel_temperature_max_2d": {
|
||||
"name": "RealFeel temperature max day 2"
|
||||
},
|
||||
"realfeel_temperature_max_3d": {
|
||||
"name": "RealFeel temperature max day 3"
|
||||
},
|
||||
"realfeel_temperature_max_4d": {
|
||||
"name": "RealFeel temperature max day 4"
|
||||
},
|
||||
"realfeel_temperature_min_0d": {
|
||||
"name": "RealFeel temperature min today"
|
||||
},
|
||||
"realfeel_temperature_min_1d": {
|
||||
"name": "RealFeel temperature min day 1"
|
||||
},
|
||||
"realfeel_temperature_min_2d": {
|
||||
"name": "RealFeel temperature min day 2"
|
||||
},
|
||||
"realfeel_temperature_min_3d": {
|
||||
"name": "RealFeel temperature min day 3"
|
||||
},
|
||||
"realfeel_temperature_min_4d": {
|
||||
"name": "RealFeel temperature min day 4"
|
||||
},
|
||||
"realfeel_temperature_shade": {
|
||||
"name": "RealFeel temperature shade"
|
||||
},
|
||||
"realfeel_temperature_shade_max_0d": {
|
||||
"name": "RealFeel temperature shade max today"
|
||||
},
|
||||
"realfeel_temperature_shade_max_1d": {
|
||||
"name": "RealFeel temperature shade max day 1"
|
||||
},
|
||||
"realfeel_temperature_shade_max_2d": {
|
||||
"name": "RealFeel temperature shade max day 2"
|
||||
},
|
||||
"realfeel_temperature_shade_max_3d": {
|
||||
"name": "RealFeel temperature shade max day 3"
|
||||
},
|
||||
"realfeel_temperature_shade_max_4d": {
|
||||
"name": "RealFeel temperature shade max day 4"
|
||||
},
|
||||
"realfeel_temperature_shade_min_0d": {
|
||||
"name": "RealFeel temperature shade min today"
|
||||
},
|
||||
"realfeel_temperature_shade_min_1d": {
|
||||
"name": "RealFeel temperature shade min day 1"
|
||||
},
|
||||
"realfeel_temperature_shade_min_2d": {
|
||||
"name": "RealFeel temperature shade min day 2"
|
||||
},
|
||||
"realfeel_temperature_shade_min_3d": {
|
||||
"name": "RealFeel temperature shade min day 3"
|
||||
},
|
||||
"realfeel_temperature_shade_min_4d": {
|
||||
"name": "RealFeel temperature shade min day 4"
|
||||
},
|
||||
"solar_irradiance_day_0d": {
|
||||
"name": "Solar irradiance today"
|
||||
},
|
||||
"solar_irradiance_day_1d": {
|
||||
"name": "Solar irradiance day 1"
|
||||
},
|
||||
"solar_irradiance_day_2d": {
|
||||
"name": "Solar irradiance day 2"
|
||||
},
|
||||
"solar_irradiance_day_3d": {
|
||||
"name": "Solar irradiance day 3"
|
||||
},
|
||||
"solar_irradiance_day_4d": {
|
||||
"name": "Solar irradiance day 4"
|
||||
},
|
||||
"solar_irradiance_night_0d": {
|
||||
"name": "Solar irradiance tonight"
|
||||
},
|
||||
"solar_irradiance_night_1d": {
|
||||
"name": "Solar irradiance night 1"
|
||||
},
|
||||
"solar_irradiance_night_2d": {
|
||||
"name": "Solar irradiance night 2"
|
||||
},
|
||||
"solar_irradiance_night_3d": {
|
||||
"name": "Solar irradiance night 3"
|
||||
},
|
||||
"solar_irradiance_night_4d": {
|
||||
"name": "Solar irradiance night 4"
|
||||
},
|
||||
"thunderstorm_probability_day_0d": {
|
||||
"name": "Thunderstorm probability today"
|
||||
},
|
||||
"thunderstorm_probability_day_1d": {
|
||||
"name": "Thunderstorm probability day 1"
|
||||
},
|
||||
"thunderstorm_probability_day_2d": {
|
||||
"name": "Thunderstorm probability day 2"
|
||||
},
|
||||
"thunderstorm_probability_day_3d": {
|
||||
"name": "Thunderstorm probability day 3"
|
||||
},
|
||||
"thunderstorm_probability_day_4d": {
|
||||
"name": "Thunderstorm probability day 4"
|
||||
},
|
||||
"thunderstorm_probability_night_0d": {
|
||||
"name": "Thunderstorm probability tonight"
|
||||
},
|
||||
"thunderstorm_probability_night_1d": {
|
||||
"name": "Thunderstorm probability night 1"
|
||||
},
|
||||
"thunderstorm_probability_night_2d": {
|
||||
"name": "Thunderstorm probability night 2"
|
||||
},
|
||||
"thunderstorm_probability_night_3d": {
|
||||
"name": "Thunderstorm probability night 3"
|
||||
},
|
||||
"thunderstorm_probability_night_4d": {
|
||||
"name": "Thunderstorm probability night 4"
|
||||
},
|
||||
"tree_pollen_0d": {
|
||||
"name": "Tree pollen today",
|
||||
"state_attributes": {
|
||||
"level": {
|
||||
"name": "[%key:component::accuweather::entity::sensor::grass_pollen_0d::state_attributes::level::name%]",
|
||||
"state": {
|
||||
"good": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::good%]",
|
||||
"hazardous": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::hazardous%]",
|
||||
"high": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::high%]",
|
||||
"low": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::low%]",
|
||||
"moderate": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::moderate%]",
|
||||
"unhealthy": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::unhealthy%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tree_pollen_1d": {
|
||||
"name": "Tree pollen day 1",
|
||||
"state_attributes": {
|
||||
"level": {
|
||||
"name": "[%key:component::accuweather::entity::sensor::grass_pollen_0d::state_attributes::level::name%]",
|
||||
"state": {
|
||||
"good": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::good%]",
|
||||
"hazardous": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::hazardous%]",
|
||||
"high": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::high%]",
|
||||
"low": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::low%]",
|
||||
"moderate": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::moderate%]",
|
||||
"unhealthy": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::unhealthy%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tree_pollen_2d": {
|
||||
"name": "Tree pollen day 2",
|
||||
"state_attributes": {
|
||||
"level": {
|
||||
"name": "[%key:component::accuweather::entity::sensor::grass_pollen_0d::state_attributes::level::name%]",
|
||||
"state": {
|
||||
"good": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::good%]",
|
||||
"hazardous": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::hazardous%]",
|
||||
"high": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::high%]",
|
||||
"low": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::low%]",
|
||||
"moderate": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::moderate%]",
|
||||
"unhealthy": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::unhealthy%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tree_pollen_3d": {
|
||||
"name": "Tree pollen day 3",
|
||||
"state_attributes": {
|
||||
"level": {
|
||||
"name": "[%key:component::accuweather::entity::sensor::grass_pollen_0d::state_attributes::level::name%]",
|
||||
"state": {
|
||||
"good": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::good%]",
|
||||
"hazardous": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::hazardous%]",
|
||||
"high": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::high%]",
|
||||
"low": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::low%]",
|
||||
"moderate": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::moderate%]",
|
||||
"unhealthy": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::unhealthy%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tree_pollen_4d": {
|
||||
"name": "Tree pollen day 4",
|
||||
"state_attributes": {
|
||||
"level": {
|
||||
"name": "[%key:component::accuweather::entity::sensor::grass_pollen_0d::state_attributes::level::name%]",
|
||||
"state": {
|
||||
"good": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::good%]",
|
||||
"hazardous": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::hazardous%]",
|
||||
"high": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::high%]",
|
||||
"low": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::low%]",
|
||||
"moderate": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::moderate%]",
|
||||
"unhealthy": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::unhealthy%]"
|
||||
"good": "[%key:component::accuweather::entity::sensor::air_quality::state::good%]",
|
||||
"hazardous": "[%key:component::accuweather::entity::sensor::air_quality::state::hazardous%]",
|
||||
"high": "[%key:component::accuweather::entity::sensor::air_quality::state::high%]",
|
||||
"low": "[%key:component::accuweather::entity::sensor::air_quality::state::low%]",
|
||||
"moderate": "[%key:component::accuweather::entity::sensor::air_quality::state::moderate%]",
|
||||
"unhealthy": "[%key:component::accuweather::entity::sensor::air_quality::state::unhealthy%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"uv_index": {
|
||||
"name": "UV index",
|
||||
"state_attributes": {
|
||||
"level": {
|
||||
"name": "[%key:component::accuweather::entity::sensor::grass_pollen_0d::state_attributes::level::name%]",
|
||||
"name": "Level",
|
||||
"state": {
|
||||
"good": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::good%]",
|
||||
"hazardous": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::hazardous%]",
|
||||
"high": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::high%]",
|
||||
"low": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::low%]",
|
||||
"moderate": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::moderate%]",
|
||||
"unhealthy": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::unhealthy%]"
|
||||
"good": "[%key:component::accuweather::entity::sensor::air_quality::state::good%]",
|
||||
"hazardous": "[%key:component::accuweather::entity::sensor::air_quality::state::hazardous%]",
|
||||
"high": "[%key:component::accuweather::entity::sensor::air_quality::state::high%]",
|
||||
"low": "[%key:component::accuweather::entity::sensor::air_quality::state::low%]",
|
||||
"moderate": "[%key:component::accuweather::entity::sensor::air_quality::state::moderate%]",
|
||||
"unhealthy": "[%key:component::accuweather::entity::sensor::air_quality::state::unhealthy%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"uv_index_0d": {
|
||||
"name": "UV index today",
|
||||
"state_attributes": {
|
||||
"level": {
|
||||
"name": "[%key:component::accuweather::entity::sensor::grass_pollen_0d::state_attributes::level::name%]",
|
||||
"state": {
|
||||
"good": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::good%]",
|
||||
"hazardous": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::hazardous%]",
|
||||
"high": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::high%]",
|
||||
"low": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::low%]",
|
||||
"moderate": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::moderate%]",
|
||||
"unhealthy": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::unhealthy%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"uv_index_1d": {
|
||||
"name": "UV index day 1",
|
||||
"state_attributes": {
|
||||
"level": {
|
||||
"name": "[%key:component::accuweather::entity::sensor::grass_pollen_0d::state_attributes::level::name%]",
|
||||
"state": {
|
||||
"good": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::good%]",
|
||||
"hazardous": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::hazardous%]",
|
||||
"high": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::high%]",
|
||||
"low": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::low%]",
|
||||
"moderate": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::moderate%]",
|
||||
"unhealthy": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::unhealthy%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"uv_index_2d": {
|
||||
"name": "UV index day 2",
|
||||
"state_attributes": {
|
||||
"level": {
|
||||
"name": "[%key:component::accuweather::entity::sensor::grass_pollen_0d::state_attributes::level::name%]",
|
||||
"state": {
|
||||
"good": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::good%]",
|
||||
"hazardous": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::hazardous%]",
|
||||
"high": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::high%]",
|
||||
"low": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::low%]",
|
||||
"moderate": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::moderate%]",
|
||||
"unhealthy": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::unhealthy%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"uv_index_3d": {
|
||||
"name": "UV index day 3",
|
||||
"state_attributes": {
|
||||
"level": {
|
||||
"name": "[%key:component::accuweather::entity::sensor::grass_pollen_0d::state_attributes::level::name%]",
|
||||
"state": {
|
||||
"good": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::good%]",
|
||||
"hazardous": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::hazardous%]",
|
||||
"high": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::high%]",
|
||||
"low": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::low%]",
|
||||
"moderate": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::moderate%]",
|
||||
"unhealthy": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::unhealthy%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"uv_index_4d": {
|
||||
"name": "UV index day 4",
|
||||
"state_attributes": {
|
||||
"level": {
|
||||
"name": "[%key:component::accuweather::entity::sensor::grass_pollen_0d::state_attributes::level::name%]",
|
||||
"state": {
|
||||
"good": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::good%]",
|
||||
"hazardous": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::hazardous%]",
|
||||
"high": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::high%]",
|
||||
"low": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::low%]",
|
||||
"moderate": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::moderate%]",
|
||||
"unhealthy": "[%key:component::accuweather::entity::sensor::air_quality_0d::state::unhealthy%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"wet_bulb_temperature": {
|
||||
"name": "Wet bulb temperature"
|
||||
},
|
||||
"wind_speed": {
|
||||
"name": "[%key:component::weather::entity_component::_::state_attributes::wind_speed::name%]"
|
||||
},
|
||||
"wind_chill_temperature": {
|
||||
"name": "Wind chill temperature"
|
||||
},
|
||||
"wind_gust_speed": {
|
||||
"name": "[%key:component::weather::entity_component::_::state_attributes::wind_gust_speed::name%]"
|
||||
},
|
||||
"wind_gust_speed_day_0d": {
|
||||
"name": "Wind gust speed today"
|
||||
},
|
||||
"wind_gust_speed_day_1d": {
|
||||
"name": "Wind gust speed day 1"
|
||||
},
|
||||
"wind_gust_speed_day_2d": {
|
||||
"name": "Wind gust speed day 2"
|
||||
},
|
||||
"wind_gust_speed_day_3d": {
|
||||
"name": "Wind gust speed day 3"
|
||||
},
|
||||
"wind_gust_speed_day_4d": {
|
||||
"name": "Wind gust speed day 4"
|
||||
},
|
||||
"wind_gust_speed_night_0d": {
|
||||
"name": "Wind gust speed tonight"
|
||||
},
|
||||
"wind_gust_speed_night_1d": {
|
||||
"name": "Wind gust speed night 1"
|
||||
},
|
||||
"wind_gust_speed_night_2d": {
|
||||
"name": "Wind gust speed night 2"
|
||||
},
|
||||
"wind_gust_speed_night_3d": {
|
||||
"name": "Wind gust speed night 3"
|
||||
},
|
||||
"wind_gust_speed_night_4d": {
|
||||
"name": "Wind gust speed night 4"
|
||||
},
|
||||
"wind_speed_day_0d": {
|
||||
"name": "Wind speed today"
|
||||
},
|
||||
"wind_speed_day_1d": {
|
||||
"name": "Wind speed day 1"
|
||||
},
|
||||
"wind_speed_day_2d": {
|
||||
"name": "Wind speed day 2"
|
||||
},
|
||||
"wind_speed_day_3d": {
|
||||
"name": "Wind speed day 3"
|
||||
},
|
||||
"wind_speed_day_4d": {
|
||||
"name": "Wind speed day 4"
|
||||
},
|
||||
"wind_speed_night_0d": {
|
||||
"name": "Wind speed tonight"
|
||||
},
|
||||
"wind_speed_night_1d": {
|
||||
"name": "Wind speed night 1"
|
||||
},
|
||||
"wind_speed_night_2d": {
|
||||
"name": "Wind speed night 2"
|
||||
},
|
||||
"wind_speed_night_3d": {
|
||||
"name": "Wind speed night 3"
|
||||
},
|
||||
"wind_speed_night_4d": {
|
||||
"name": "Wind speed night 4"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -14,11 +14,9 @@ from homeassistant.components.weather import (
|
||||
ATTR_FORECAST_NATIVE_WIND_SPEED,
|
||||
ATTR_FORECAST_PRECIPITATION_PROBABILITY,
|
||||
ATTR_FORECAST_TIME,
|
||||
ATTR_FORECAST_UV_INDEX,
|
||||
ATTR_FORECAST_WIND_BEARING,
|
||||
Forecast,
|
||||
SingleCoordinatorWeatherEntity,
|
||||
WeatherEntityFeature,
|
||||
WeatherEntity,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
@@ -28,8 +26,9 @@ from homeassistant.const import (
|
||||
UnitOfSpeed,
|
||||
UnitOfTemperature,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
from homeassistant.util.dt import utc_from_timestamp
|
||||
|
||||
from . import AccuWeatherDataUpdateCoordinator
|
||||
@@ -40,7 +39,7 @@ from .const import (
|
||||
ATTR_SPEED,
|
||||
ATTR_VALUE,
|
||||
ATTRIBUTION,
|
||||
CONDITION_MAP,
|
||||
CONDITION_CLASSES,
|
||||
DOMAIN,
|
||||
)
|
||||
|
||||
@@ -58,7 +57,7 @@ async def async_setup_entry(
|
||||
|
||||
|
||||
class AccuWeatherEntity(
|
||||
SingleCoordinatorWeatherEntity[AccuWeatherDataUpdateCoordinator]
|
||||
CoordinatorEntity[AccuWeatherDataUpdateCoordinator], WeatherEntity
|
||||
):
|
||||
"""Define an AccuWeather entity."""
|
||||
|
||||
@@ -68,6 +67,9 @@ class AccuWeatherEntity(
|
||||
def __init__(self, coordinator: AccuWeatherDataUpdateCoordinator) -> None:
|
||||
"""Initialize."""
|
||||
super().__init__(coordinator)
|
||||
# Coordinator data is used also for sensors which don't have units automatically
|
||||
# converted, hence the weather entity's native units follow the configured unit
|
||||
# system
|
||||
self._attr_native_precipitation_unit = UnitOfPrecipitationDepth.MILLIMETERS
|
||||
self._attr_native_pressure_unit = UnitOfPressure.HPA
|
||||
self._attr_native_temperature_unit = UnitOfTemperature.CELSIUS
|
||||
@@ -76,13 +78,18 @@ class AccuWeatherEntity(
|
||||
self._attr_unique_id = coordinator.location_key
|
||||
self._attr_attribution = ATTRIBUTION
|
||||
self._attr_device_info = coordinator.device_info
|
||||
if self.coordinator.forecast:
|
||||
self._attr_supported_features = WeatherEntityFeature.FORECAST_DAILY
|
||||
|
||||
@property
|
||||
def condition(self) -> str | None:
|
||||
"""Return the current condition."""
|
||||
return CONDITION_MAP.get(self.coordinator.data["WeatherIcon"])
|
||||
try:
|
||||
return [
|
||||
k
|
||||
for k, v in CONDITION_CLASSES.items()
|
||||
if self.coordinator.data["WeatherIcon"] in v
|
||||
][0]
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
@property
|
||||
def cloud_coverage(self) -> float:
|
||||
@@ -140,11 +147,6 @@ class AccuWeatherEntity(
|
||||
"""Return the visibility."""
|
||||
return cast(float, self.coordinator.data["Visibility"][API_METRIC][ATTR_VALUE])
|
||||
|
||||
@property
|
||||
def uv_index(self) -> float:
|
||||
"""Return the UV index."""
|
||||
return cast(float, self.coordinator.data["UVIndex"])
|
||||
|
||||
@property
|
||||
def forecast(self) -> list[Forecast] | None:
|
||||
"""Return the forecast array."""
|
||||
@@ -170,14 +172,10 @@ class AccuWeatherEntity(
|
||||
ATTR_FORECAST_NATIVE_WIND_GUST_SPEED: item["WindGustDay"][ATTR_SPEED][
|
||||
ATTR_VALUE
|
||||
],
|
||||
ATTR_FORECAST_UV_INDEX: item["UVIndex"][ATTR_VALUE],
|
||||
ATTR_FORECAST_WIND_BEARING: item["WindDay"][ATTR_DIRECTION]["Degrees"],
|
||||
ATTR_FORECAST_CONDITION: CONDITION_MAP.get(item["IconDay"]),
|
||||
ATTR_FORECAST_CONDITION: [
|
||||
k for k, v in CONDITION_CLASSES.items() if item["IconDay"] in v
|
||||
][0],
|
||||
}
|
||||
for item in self.coordinator.data[ATTR_FORECAST]
|
||||
]
|
||||
|
||||
@callback
|
||||
def _async_forecast_daily(self) -> list[Forecast] | None:
|
||||
"""Return the daily forecast in native units."""
|
||||
return self.forecast
|
||||
|
||||
@@ -74,9 +74,9 @@ class AcmedaBase(entity.Entity):
|
||||
return self.roller.id
|
||||
|
||||
@property
|
||||
def device_info(self) -> dr.DeviceInfo:
|
||||
def device_info(self) -> entity.DeviceInfo:
|
||||
"""Return the device info."""
|
||||
return dr.DeviceInfo(
|
||||
return entity.DeviceInfo(
|
||||
identifiers={(DOMAIN, self.unique_id)},
|
||||
manufacturer="Rollease Acmeda",
|
||||
name=self.roller.name,
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from asyncio import timeout
|
||||
from contextlib import suppress
|
||||
from typing import Any
|
||||
|
||||
import aiopulse
|
||||
import async_timeout
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries
|
||||
@@ -43,7 +43,7 @@ class AcmedaFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
|
||||
hubs: list[aiopulse.Hub] = []
|
||||
with suppress(asyncio.TimeoutError):
|
||||
async with timeout(5):
|
||||
async with async_timeout.timeout(5):
|
||||
async for hub in aiopulse.Hub.discover():
|
||||
if hub.id not in already_configured:
|
||||
hubs.append(hub)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import telnetlib # pylint: disable=deprecated-module
|
||||
import telnetlib
|
||||
from typing import Final
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
@@ -23,7 +23,7 @@ from homeassistant.const import (
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import ACCOUNT_ID, CONNECTION_TYPE, DOMAIN, LOCAL
|
||||
|
||||
@@ -4,8 +4,8 @@ from __future__ import annotations
|
||||
from adguardhome import AdGuardHome, AdGuardHomeError
|
||||
|
||||
from homeassistant.config_entries import SOURCE_HASSIO, ConfigEntry
|
||||
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers.device_registry import DeviceEntryType
|
||||
from homeassistant.helpers.entity import DeviceInfo, Entity
|
||||
|
||||
from .const import DATA_ADGUARD_VERSION, DOMAIN, LOGGER
|
||||
|
||||
|
||||
@@ -1,43 +1,65 @@
|
||||
add_url:
|
||||
name: Add url
|
||||
description: Add a new filter subscription to AdGuard Home.
|
||||
fields:
|
||||
name:
|
||||
name: Name
|
||||
description: The name of the filter subscription.
|
||||
required: true
|
||||
example: Example
|
||||
selector:
|
||||
text:
|
||||
url:
|
||||
name: Url
|
||||
description: The filter URL to subscribe to, containing the filter rules.
|
||||
required: true
|
||||
example: https://www.example.com/filter/1.txt
|
||||
selector:
|
||||
text:
|
||||
|
||||
remove_url:
|
||||
name: Remove url
|
||||
description: Removes a filter subscription from AdGuard Home.
|
||||
fields:
|
||||
url:
|
||||
name: Url
|
||||
description: The filter subscription URL to remove.
|
||||
required: true
|
||||
example: https://www.example.com/filter/1.txt
|
||||
selector:
|
||||
text:
|
||||
|
||||
enable_url:
|
||||
name: Enable url
|
||||
description: Enables a filter subscription in AdGuard Home.
|
||||
fields:
|
||||
url:
|
||||
name: Url
|
||||
description: The filter subscription URL to enable.
|
||||
required: true
|
||||
example: https://www.example.com/filter/1.txt
|
||||
selector:
|
||||
text:
|
||||
|
||||
disable_url:
|
||||
name: Disable url
|
||||
description: Disables a filter subscription in AdGuard Home.
|
||||
fields:
|
||||
url:
|
||||
name: Url
|
||||
description: The filter subscription URL to disable.
|
||||
required: true
|
||||
example: https://www.example.com/filter/1.txt
|
||||
selector:
|
||||
text:
|
||||
|
||||
refresh:
|
||||
name: Refresh
|
||||
description: Refresh all filter subscriptions in AdGuard Home.
|
||||
fields:
|
||||
force:
|
||||
name: Force
|
||||
description: Force update (bypasses AdGuard Home throttling). "true" to force, or "false" to omit for a regular refresh.
|
||||
default: false
|
||||
selector:
|
||||
boolean:
|
||||
|
||||
@@ -72,61 +72,5 @@
|
||||
"name": "Query log"
|
||||
}
|
||||
}
|
||||
},
|
||||
"services": {
|
||||
"add_url": {
|
||||
"name": "Add URL",
|
||||
"description": "Add a new filter subscription to AdGuard Home.",
|
||||
"fields": {
|
||||
"name": {
|
||||
"name": "[%key:common::config_flow::data::name%]",
|
||||
"description": "The name of the filter subscription."
|
||||
},
|
||||
"url": {
|
||||
"name": "[%key:common::config_flow::data::url%]",
|
||||
"description": "The filter URL to subscribe to, containing the filter rules."
|
||||
}
|
||||
}
|
||||
},
|
||||
"remove_url": {
|
||||
"name": "Remove URL",
|
||||
"description": "Removes a filter subscription from AdGuard Home.",
|
||||
"fields": {
|
||||
"url": {
|
||||
"name": "[%key:common::config_flow::data::url%]",
|
||||
"description": "The filter subscription URL to remove."
|
||||
}
|
||||
}
|
||||
},
|
||||
"enable_url": {
|
||||
"name": "Enable URL",
|
||||
"description": "Enables a filter subscription in AdGuard Home.",
|
||||
"fields": {
|
||||
"url": {
|
||||
"name": "[%key:common::config_flow::data::url%]",
|
||||
"description": "The filter subscription URL to enable."
|
||||
}
|
||||
}
|
||||
},
|
||||
"disable_url": {
|
||||
"name": "Disable URL",
|
||||
"description": "Disables a filter subscription in AdGuard Home.",
|
||||
"fields": {
|
||||
"url": {
|
||||
"name": "[%key:common::config_flow::data::url%]",
|
||||
"description": "The filter subscription URL to disable."
|
||||
}
|
||||
}
|
||||
},
|
||||
"refresh": {
|
||||
"name": "Refresh",
|
||||
"description": "Refresh all filter subscriptions in AdGuard Home.",
|
||||
"fields": {
|
||||
"force": {
|
||||
"name": "Force",
|
||||
"description": "Force update (bypasses AdGuard Home throttling). \"true\" to force, or \"false\" to omit for a regular refresh."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
"""Support for Automation Device Specification (ADS)."""
|
||||
import asyncio
|
||||
from asyncio import timeout
|
||||
from collections import namedtuple
|
||||
import ctypes
|
||||
import logging
|
||||
import struct
|
||||
import threading
|
||||
|
||||
import async_timeout
|
||||
import pyads
|
||||
import voluptuous as vol
|
||||
|
||||
@@ -301,7 +301,7 @@ class AdsEntity(Entity):
|
||||
self._ads_hub.add_device_notification, ads_var, plctype, update
|
||||
)
|
||||
try:
|
||||
async with timeout(10):
|
||||
async with async_timeout.timeout(10):
|
||||
await self._event.wait()
|
||||
except asyncio.TimeoutError:
|
||||
_LOGGER.debug("Variable %s: Timeout during first update", ads_var)
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
# Describes the format for available ADS services
|
||||
|
||||
write_data_by_name:
|
||||
name: Write data by name
|
||||
description: Write a value to the connected ADS device.
|
||||
fields:
|
||||
adsvar:
|
||||
name: ADS variable
|
||||
description: The name of the variable to write to.
|
||||
required: true
|
||||
example: ".global_var"
|
||||
selector:
|
||||
text:
|
||||
adstype:
|
||||
name: ADS type
|
||||
description: The data type of the variable to write to.
|
||||
required: true
|
||||
selector:
|
||||
select:
|
||||
@@ -19,6 +25,8 @@ write_data_by_name:
|
||||
- "udint"
|
||||
- "uint"
|
||||
value:
|
||||
name: Value
|
||||
description: The value to write to the variable.
|
||||
required: true
|
||||
selector:
|
||||
number:
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"services": {
|
||||
"write_data_by_name": {
|
||||
"name": "Write data by name",
|
||||
"description": "Write a value to the connected ADS device.",
|
||||
"fields": {
|
||||
"adsvar": {
|
||||
"name": "ADS variable",
|
||||
"description": "The name of the variable to write to."
|
||||
},
|
||||
"adstype": {
|
||||
"name": "ADS type",
|
||||
"description": "The data type of the variable to write to."
|
||||
},
|
||||
"value": {
|
||||
"name": "Value",
|
||||
"description": "The value to write to the variable."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -125,13 +125,6 @@ class AdvantageAirAC(AdvantageAirAcEntity, ClimateEntity):
|
||||
@property
|
||||
def target_temperature(self) -> float | None:
|
||||
"""Return the current target temperature."""
|
||||
# If the system is in MyZone mode, and a zone is set, return that temperature instead.
|
||||
if (
|
||||
self._ac["myZone"] > 0
|
||||
and not self._ac.get(ADVANTAGE_AIR_MYAUTO_ENABLED)
|
||||
and not self._ac.get(ADVANTAGE_AIR_MYTEMP_ENABLED)
|
||||
):
|
||||
return self._myzone["setTemp"]
|
||||
return self._ac["setTemp"]
|
||||
|
||||
@property
|
||||
|
||||
@@ -4,7 +4,7 @@ from typing import Any
|
||||
from advantage_air import ApiError
|
||||
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from .const import DOMAIN
|
||||
@@ -62,12 +62,6 @@ class AdvantageAirAcEntity(AdvantageAirEntity):
|
||||
def _ac(self) -> dict[str, Any]:
|
||||
return self.coordinator.data["aircons"][self.ac_key]["info"]
|
||||
|
||||
@property
|
||||
def _myzone(self) -> dict[str, Any]:
|
||||
return self.coordinator.data["aircons"][self.ac_key]["zones"].get(
|
||||
f"z{self._ac['myZone']:02}"
|
||||
)
|
||||
|
||||
|
||||
class AdvantageAirZoneEntity(AdvantageAirAcEntity):
|
||||
"""Parent class for Advantage Air Zone Entities."""
|
||||
|
||||
@@ -4,7 +4,7 @@ from typing import Any
|
||||
from homeassistant.components.light import ATTR_BRIGHTNESS, ColorMode, LightEntity
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import ADVANTAGE_AIR_STATE_ON, DOMAIN as ADVANTAGE_AIR_DOMAIN
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
set_time_to:
|
||||
name: Set Time To
|
||||
description: Control timers to turn the system on or off after a set number of minutes
|
||||
target:
|
||||
entity:
|
||||
integration: advantage_air
|
||||
domain: sensor
|
||||
fields:
|
||||
minutes:
|
||||
name: Minutes
|
||||
description: Minutes until action
|
||||
required: true
|
||||
selector:
|
||||
number:
|
||||
|
||||
@@ -13,19 +13,7 @@
|
||||
"port": "[%key:common::config_flow::data::port%]"
|
||||
},
|
||||
"description": "Connect to the API of your Advantage Air wall mounted tablet.",
|
||||
"title": "[%key:common::action::connect%]"
|
||||
}
|
||||
}
|
||||
},
|
||||
"services": {
|
||||
"set_time_to": {
|
||||
"name": "Set time to",
|
||||
"description": "Controls timers to turn the system on or off after a set number of minutes.",
|
||||
"fields": {
|
||||
"minutes": {
|
||||
"name": "Minutes",
|
||||
"description": "Minutes until action."
|
||||
}
|
||||
"title": "Connect"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
from homeassistant.components.update import UpdateEntity
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import DOMAIN as ADVANTAGE_AIR_DOMAIN
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
"""The AEMET OpenData component."""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from aemet_opendata.exceptions import TownNotFound
|
||||
from aemet_opendata.interface import AEMET, ConnectionOptions
|
||||
from aemet_opendata.interface import AEMET
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers import aiohttp_client
|
||||
|
||||
from .const import (
|
||||
CONF_STATION_UPDATES,
|
||||
@@ -32,17 +27,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
longitude = entry.data[CONF_LONGITUDE]
|
||||
station_updates = entry.options.get(CONF_STATION_UPDATES, True)
|
||||
|
||||
options = ConnectionOptions(api_key, station_updates, True)
|
||||
aemet = AEMET(aiohttp_client.async_get_clientsession(hass), options)
|
||||
try:
|
||||
await aemet.select_coordinates(latitude, longitude)
|
||||
except TownNotFound as err:
|
||||
_LOGGER.error(err)
|
||||
return False
|
||||
except asyncio.TimeoutError as err:
|
||||
raise ConfigEntryNotReady("AEMET OpenData API timed out") from err
|
||||
aemet = AEMET(api_key)
|
||||
weather_coordinator = WeatherUpdateCoordinator(
|
||||
hass, aemet, latitude, longitude, station_updates
|
||||
)
|
||||
|
||||
weather_coordinator = WeatherUpdateCoordinator(hass, aemet)
|
||||
await weather_coordinator.async_config_entry_first_refresh()
|
||||
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
"""Config flow for AEMET OpenData."""
|
||||
from __future__ import annotations
|
||||
|
||||
from aemet_opendata.exceptions import AuthError
|
||||
from aemet_opendata.interface import AEMET, ConnectionOptions
|
||||
from aemet_opendata import AEMET
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers import aiohttp_client, config_validation as cv
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.schema_config_entry_flow import (
|
||||
SchemaFlowFormStep,
|
||||
SchemaOptionsFlowHandler,
|
||||
@@ -40,11 +39,8 @@ class AemetConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
await self.async_set_unique_id(f"{latitude}-{longitude}")
|
||||
self._abort_if_unique_id_configured()
|
||||
|
||||
options = ConnectionOptions(user_input[CONF_API_KEY], False, True)
|
||||
aemet = AEMET(aiohttp_client.async_get_clientsession(self.hass), options)
|
||||
try:
|
||||
await aemet.select_coordinates(latitude, longitude)
|
||||
except AuthError:
|
||||
api_online = await _is_aemet_api_online(self.hass, user_input[CONF_API_KEY])
|
||||
if not api_online:
|
||||
errors["base"] = "invalid_api_key"
|
||||
|
||||
if not errors:
|
||||
@@ -74,3 +70,10 @@ class AemetConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
) -> SchemaOptionsFlowHandler:
|
||||
"""Get the options flow for this handler."""
|
||||
return SchemaOptionsFlowHandler(config_entry, OPTIONS_FLOW)
|
||||
|
||||
|
||||
async def _is_aemet_api_online(hass, api_key):
|
||||
aemet = AEMET(api_key)
|
||||
return await hass.async_add_executor_job(
|
||||
aemet.get_conventional_observation_stations, False
|
||||
)
|
||||
|
||||
@@ -1,19 +1,6 @@
|
||||
"""Constant values for the AEMET OpenData component."""
|
||||
from __future__ import annotations
|
||||
|
||||
from aemet_opendata.const import (
|
||||
AOD_COND_CLEAR_NIGHT,
|
||||
AOD_COND_CLOUDY,
|
||||
AOD_COND_FOG,
|
||||
AOD_COND_LIGHTNING,
|
||||
AOD_COND_LIGHTNING_RAINY,
|
||||
AOD_COND_PARTLY_CLODUY,
|
||||
AOD_COND_POURING,
|
||||
AOD_COND_RAINY,
|
||||
AOD_COND_SNOWY,
|
||||
AOD_COND_SUNNY,
|
||||
)
|
||||
|
||||
from homeassistant.components.weather import (
|
||||
ATTR_CONDITION_CLEAR_NIGHT,
|
||||
ATTR_CONDITION_CLOUDY,
|
||||
@@ -46,7 +33,6 @@ ATTR_API_FORECAST_TEMP = "temperature"
|
||||
ATTR_API_FORECAST_TEMP_LOW = "templow"
|
||||
ATTR_API_FORECAST_TIME = "datetime"
|
||||
ATTR_API_FORECAST_WIND_BEARING = "wind_bearing"
|
||||
ATTR_API_FORECAST_WIND_MAX_SPEED = "wind_max_speed"
|
||||
ATTR_API_FORECAST_WIND_SPEED = "wind_speed"
|
||||
ATTR_API_HUMIDITY = "humidity"
|
||||
ATTR_API_PRESSURE = "pressure"
|
||||
@@ -68,16 +54,94 @@ ATTR_API_WIND_MAX_SPEED = "wind-max-speed"
|
||||
ATTR_API_WIND_SPEED = "wind-speed"
|
||||
|
||||
CONDITIONS_MAP = {
|
||||
AOD_COND_CLEAR_NIGHT: ATTR_CONDITION_CLEAR_NIGHT,
|
||||
AOD_COND_CLOUDY: ATTR_CONDITION_CLOUDY,
|
||||
AOD_COND_FOG: ATTR_CONDITION_FOG,
|
||||
AOD_COND_LIGHTNING: ATTR_CONDITION_LIGHTNING,
|
||||
AOD_COND_LIGHTNING_RAINY: ATTR_CONDITION_LIGHTNING_RAINY,
|
||||
AOD_COND_PARTLY_CLODUY: ATTR_CONDITION_PARTLYCLOUDY,
|
||||
AOD_COND_POURING: ATTR_CONDITION_POURING,
|
||||
AOD_COND_RAINY: ATTR_CONDITION_RAINY,
|
||||
AOD_COND_SNOWY: ATTR_CONDITION_SNOWY,
|
||||
AOD_COND_SUNNY: ATTR_CONDITION_SUNNY,
|
||||
ATTR_CONDITION_CLEAR_NIGHT: {
|
||||
"11n", # Despejado (de noche)
|
||||
},
|
||||
ATTR_CONDITION_CLOUDY: {
|
||||
"14", # Nuboso
|
||||
"14n", # Nuboso (de noche)
|
||||
"15", # Muy nuboso
|
||||
"15n", # Muy nuboso (de noche)
|
||||
"16", # Cubierto
|
||||
"16n", # Cubierto (de noche)
|
||||
"17", # Nubes altas
|
||||
"17n", # Nubes altas (de noche)
|
||||
},
|
||||
ATTR_CONDITION_FOG: {
|
||||
"81", # Niebla
|
||||
"81n", # Niebla (de noche)
|
||||
"82", # Bruma - Neblina
|
||||
"82n", # Bruma - Neblina (de noche)
|
||||
},
|
||||
ATTR_CONDITION_LIGHTNING: {
|
||||
"51", # Intervalos nubosos con tormenta
|
||||
"51n", # Intervalos nubosos con tormenta (de noche)
|
||||
"52", # Nuboso con tormenta
|
||||
"52n", # Nuboso con tormenta (de noche)
|
||||
"53", # Muy nuboso con tormenta
|
||||
"53n", # Muy nuboso con tormenta (de noche)
|
||||
"54", # Cubierto con tormenta
|
||||
"54n", # Cubierto con tormenta (de noche)
|
||||
},
|
||||
ATTR_CONDITION_LIGHTNING_RAINY: {
|
||||
"61", # Intervalos nubosos con tormenta y lluvia escasa
|
||||
"61n", # Intervalos nubosos con tormenta y lluvia escasa (de noche)
|
||||
"62", # Nuboso con tormenta y lluvia escasa
|
||||
"62n", # Nuboso con tormenta y lluvia escasa (de noche)
|
||||
"63", # Muy nuboso con tormenta y lluvia escasa
|
||||
"63n", # Muy nuboso con tormenta y lluvia escasa (de noche)
|
||||
"64", # Cubierto con tormenta y lluvia escasa
|
||||
"64n", # Cubierto con tormenta y lluvia escasa (de noche)
|
||||
},
|
||||
ATTR_CONDITION_PARTLYCLOUDY: {
|
||||
"12", # Poco nuboso
|
||||
"12n", # Poco nuboso (de noche)
|
||||
"13", # Intervalos nubosos
|
||||
"13n", # Intervalos nubosos (de noche)
|
||||
},
|
||||
ATTR_CONDITION_POURING: {
|
||||
"27", # Chubascos
|
||||
"27n", # Chubascos (de noche)
|
||||
},
|
||||
ATTR_CONDITION_RAINY: {
|
||||
"23", # Intervalos nubosos con lluvia
|
||||
"23n", # Intervalos nubosos con lluvia (de noche)
|
||||
"24", # Nuboso con lluvia
|
||||
"24n", # Nuboso con lluvia (de noche)
|
||||
"25", # Muy nuboso con lluvia
|
||||
"25n", # Muy nuboso con lluvia (de noche)
|
||||
"26", # Cubierto con lluvia
|
||||
"26n", # Cubierto con lluvia (de noche)
|
||||
"43", # Intervalos nubosos con lluvia escasa
|
||||
"43n", # Intervalos nubosos con lluvia escasa (de noche)
|
||||
"44", # Nuboso con lluvia escasa
|
||||
"44n", # Nuboso con lluvia escasa (de noche)
|
||||
"45", # Muy nuboso con lluvia escasa
|
||||
"45n", # Muy nuboso con lluvia escasa (de noche)
|
||||
"46", # Cubierto con lluvia escasa
|
||||
"46n", # Cubierto con lluvia escasa (de noche)
|
||||
},
|
||||
ATTR_CONDITION_SNOWY: {
|
||||
"33", # Intervalos nubosos con nieve
|
||||
"33n", # Intervalos nubosos con nieve (de noche)
|
||||
"34", # Nuboso con nieve
|
||||
"34n", # Nuboso con nieve (de noche)
|
||||
"35", # Muy nuboso con nieve
|
||||
"35n", # Muy nuboso con nieve (de noche)
|
||||
"36", # Cubierto con nieve
|
||||
"36n", # Cubierto con nieve (de noche)
|
||||
"71", # Intervalos nubosos con nieve escasa
|
||||
"71n", # Intervalos nubosos con nieve escasa (de noche)
|
||||
"72", # Nuboso con nieve escasa
|
||||
"72n", # Nuboso con nieve escasa (de noche)
|
||||
"73", # Muy nuboso con nieve escasa
|
||||
"73n", # Muy nuboso con nieve escasa (de noche)
|
||||
"74", # Cubierto con nieve escasa
|
||||
"74n", # Cubierto con nieve escasa (de noche)
|
||||
},
|
||||
ATTR_CONDITION_SUNNY: {
|
||||
"11", # Despejado
|
||||
},
|
||||
}
|
||||
|
||||
FORECAST_MONITORED_CONDITIONS = [
|
||||
@@ -122,3 +186,16 @@ FORECAST_MODE_ATTR_API = {
|
||||
FORECAST_MODE_DAILY: ATTR_API_FORECAST_DAILY,
|
||||
FORECAST_MODE_HOURLY: ATTR_API_FORECAST_HOURLY,
|
||||
}
|
||||
|
||||
|
||||
WIND_BEARING_MAP = {
|
||||
"C": None,
|
||||
"N": 0.0,
|
||||
"NE": 45.0,
|
||||
"E": 90.0,
|
||||
"SE": 135.0,
|
||||
"S": 180.0,
|
||||
"SO": 225.0,
|
||||
"O": 270.0,
|
||||
"NO": 315.0,
|
||||
}
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/aemet",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["aemet_opendata"],
|
||||
"requirements": ["AEMET-OpenData==0.4.5"]
|
||||
"requirements": ["AEMET-OpenData==0.2.2"]
|
||||
}
|
||||
|
||||
@@ -30,7 +30,6 @@ from .const import (
|
||||
ATTR_API_FORECAST_TEMP_LOW,
|
||||
ATTR_API_FORECAST_TIME,
|
||||
ATTR_API_FORECAST_WIND_BEARING,
|
||||
ATTR_API_FORECAST_WIND_MAX_SPEED,
|
||||
ATTR_API_FORECAST_WIND_SPEED,
|
||||
ATTR_API_HUMIDITY,
|
||||
ATTR_API_PRESSURE,
|
||||
@@ -100,12 +99,6 @@ FORECAST_SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
|
||||
name="Wind bearing",
|
||||
native_unit_of_measurement=DEGREE,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=ATTR_API_FORECAST_WIND_MAX_SPEED,
|
||||
name="Wind max speed",
|
||||
native_unit_of_measurement=UnitOfSpeed.KILOMETERS_PER_HOUR,
|
||||
device_class=SensorDeviceClass.WIND_SPEED,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=ATTR_API_FORECAST_WIND_SPEED,
|
||||
name="Wind speed",
|
||||
@@ -213,14 +206,13 @@ WEATHER_SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
|
||||
name="Wind max speed",
|
||||
native_unit_of_measurement=UnitOfSpeed.KILOMETERS_PER_HOUR,
|
||||
device_class=SensorDeviceClass.WIND_SPEED,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=ATTR_API_WIND_SPEED,
|
||||
name="Wind speed",
|
||||
native_unit_of_measurement=UnitOfSpeed.KILOMETERS_PER_HOUR,
|
||||
device_class=SensorDeviceClass.WIND_SPEED,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
device_class=SensorDeviceClass.WIND_SPEED,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -1,20 +1,14 @@
|
||||
"""Support for the AEMET OpenData service."""
|
||||
from typing import cast
|
||||
|
||||
from homeassistant.components.weather import (
|
||||
ATTR_FORECAST_CONDITION,
|
||||
ATTR_FORECAST_NATIVE_PRECIPITATION,
|
||||
ATTR_FORECAST_NATIVE_TEMP,
|
||||
ATTR_FORECAST_NATIVE_TEMP_LOW,
|
||||
ATTR_FORECAST_NATIVE_WIND_GUST_SPEED,
|
||||
ATTR_FORECAST_NATIVE_WIND_SPEED,
|
||||
ATTR_FORECAST_PRECIPITATION_PROBABILITY,
|
||||
ATTR_FORECAST_TIME,
|
||||
ATTR_FORECAST_WIND_BEARING,
|
||||
DOMAIN as WEATHER_DOMAIN,
|
||||
Forecast,
|
||||
SingleCoordinatorWeatherEntity,
|
||||
WeatherEntityFeature,
|
||||
WeatherEntity,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
@@ -23,9 +17,9 @@ from homeassistant.const import (
|
||||
UnitOfSpeed,
|
||||
UnitOfTemperature,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from .const import (
|
||||
ATTR_API_CONDITION,
|
||||
@@ -36,13 +30,11 @@ from .const import (
|
||||
ATTR_API_FORECAST_TEMP_LOW,
|
||||
ATTR_API_FORECAST_TIME,
|
||||
ATTR_API_FORECAST_WIND_BEARING,
|
||||
ATTR_API_FORECAST_WIND_MAX_SPEED,
|
||||
ATTR_API_FORECAST_WIND_SPEED,
|
||||
ATTR_API_HUMIDITY,
|
||||
ATTR_API_PRESSURE,
|
||||
ATTR_API_TEMPERATURE,
|
||||
ATTR_API_WIND_BEARING,
|
||||
ATTR_API_WIND_MAX_SPEED,
|
||||
ATTR_API_WIND_SPEED,
|
||||
ATTRIBUTION,
|
||||
DOMAIN,
|
||||
@@ -72,7 +64,6 @@ FORECAST_MAP = {
|
||||
ATTR_API_FORECAST_TEMP: ATTR_FORECAST_NATIVE_TEMP,
|
||||
ATTR_API_FORECAST_TIME: ATTR_FORECAST_TIME,
|
||||
ATTR_API_FORECAST_WIND_BEARING: ATTR_FORECAST_WIND_BEARING,
|
||||
ATTR_API_FORECAST_WIND_MAX_SPEED: ATTR_FORECAST_NATIVE_WIND_GUST_SPEED,
|
||||
ATTR_API_FORECAST_WIND_SPEED: ATTR_FORECAST_NATIVE_WIND_SPEED,
|
||||
},
|
||||
}
|
||||
@@ -88,33 +79,15 @@ async def async_setup_entry(
|
||||
weather_coordinator = domain_data[ENTRY_WEATHER_COORDINATOR]
|
||||
|
||||
entities = []
|
||||
entity_registry = er.async_get(hass)
|
||||
|
||||
# Add daily + hourly entity for legacy config entries, only add daily for new
|
||||
# config entries. This can be removed in HA Core 2024.3
|
||||
if entity_registry.async_get_entity_id(
|
||||
WEATHER_DOMAIN,
|
||||
DOMAIN,
|
||||
f"{config_entry.unique_id} {FORECAST_MODE_HOURLY}",
|
||||
):
|
||||
for mode in FORECAST_MODES:
|
||||
name = f"{domain_data[ENTRY_NAME]} {mode}"
|
||||
unique_id = f"{config_entry.unique_id} {mode}"
|
||||
entities.append(AemetWeather(name, unique_id, weather_coordinator, mode))
|
||||
else:
|
||||
entities.append(
|
||||
AemetWeather(
|
||||
domain_data[ENTRY_NAME],
|
||||
config_entry.unique_id,
|
||||
weather_coordinator,
|
||||
FORECAST_MODE_DAILY,
|
||||
)
|
||||
)
|
||||
for mode in FORECAST_MODES:
|
||||
name = f"{domain_data[ENTRY_NAME]} {mode}"
|
||||
unique_id = f"{config_entry.unique_id} {mode}"
|
||||
entities.append(AemetWeather(name, unique_id, weather_coordinator, mode))
|
||||
|
||||
async_add_entities(entities, False)
|
||||
|
||||
|
||||
class AemetWeather(SingleCoordinatorWeatherEntity[WeatherUpdateCoordinator]):
|
||||
class AemetWeather(CoordinatorEntity[WeatherUpdateCoordinator], WeatherEntity):
|
||||
"""Implementation of an AEMET OpenData sensor."""
|
||||
|
||||
_attr_attribution = ATTRIBUTION
|
||||
@@ -122,9 +95,6 @@ class AemetWeather(SingleCoordinatorWeatherEntity[WeatherUpdateCoordinator]):
|
||||
_attr_native_pressure_unit = UnitOfPressure.HPA
|
||||
_attr_native_temperature_unit = UnitOfTemperature.CELSIUS
|
||||
_attr_native_wind_speed_unit = UnitOfSpeed.KILOMETERS_PER_HOUR
|
||||
_attr_supported_features = (
|
||||
WeatherEntityFeature.FORECAST_DAILY | WeatherEntityFeature.FORECAST_HOURLY
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -147,32 +117,15 @@ class AemetWeather(SingleCoordinatorWeatherEntity[WeatherUpdateCoordinator]):
|
||||
"""Return the current condition."""
|
||||
return self.coordinator.data[ATTR_API_CONDITION]
|
||||
|
||||
def _forecast(self, forecast_mode: str) -> list[Forecast]:
|
||||
"""Return the forecast array."""
|
||||
forecasts = self.coordinator.data[FORECAST_MODE_ATTR_API[forecast_mode]]
|
||||
forecast_map = FORECAST_MAP[forecast_mode]
|
||||
return cast(
|
||||
list[Forecast],
|
||||
[
|
||||
{ha_key: forecast[api_key] for api_key, ha_key in forecast_map.items()}
|
||||
for forecast in forecasts
|
||||
],
|
||||
)
|
||||
|
||||
@property
|
||||
def forecast(self) -> list[Forecast]:
|
||||
def forecast(self):
|
||||
"""Return the forecast array."""
|
||||
return self._forecast(self._forecast_mode)
|
||||
|
||||
@callback
|
||||
def _async_forecast_daily(self) -> list[Forecast]:
|
||||
"""Return the daily forecast in native units."""
|
||||
return self._forecast(FORECAST_MODE_DAILY)
|
||||
|
||||
@callback
|
||||
def _async_forecast_hourly(self) -> list[Forecast]:
|
||||
"""Return the hourly forecast in native units."""
|
||||
return self._forecast(FORECAST_MODE_HOURLY)
|
||||
forecasts = self.coordinator.data[FORECAST_MODE_ATTR_API[self._forecast_mode]]
|
||||
forecast_map = FORECAST_MAP[self._forecast_mode]
|
||||
return [
|
||||
{ha_key: forecast[api_key] for api_key, ha_key in forecast_map.items()}
|
||||
for forecast in forecasts
|
||||
]
|
||||
|
||||
@property
|
||||
def humidity(self):
|
||||
@@ -194,11 +147,6 @@ class AemetWeather(SingleCoordinatorWeatherEntity[WeatherUpdateCoordinator]):
|
||||
"""Return the wind bearing."""
|
||||
return self.coordinator.data[ATTR_API_WIND_BEARING]
|
||||
|
||||
@property
|
||||
def native_wind_gust_speed(self):
|
||||
"""Return the wind gust speed in native units."""
|
||||
return self.coordinator.data[ATTR_API_WIND_MAX_SPEED]
|
||||
|
||||
@property
|
||||
def native_wind_speed(self):
|
||||
"""Return the wind speed."""
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
"""Weather data coordinator for the AEMET OpenData service."""
|
||||
from __future__ import annotations
|
||||
|
||||
from asyncio import timeout
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
from typing import Any, Final
|
||||
|
||||
from aemet_opendata.const import (
|
||||
AEMET_ATTR_DATE,
|
||||
AEMET_ATTR_DAY,
|
||||
AEMET_ATTR_DIRECTION,
|
||||
AEMET_ATTR_ELABORATED,
|
||||
AEMET_ATTR_FEEL_TEMPERATURE,
|
||||
AEMET_ATTR_FORECAST,
|
||||
AEMET_ATTR_HUMIDITY,
|
||||
AEMET_ATTR_ID,
|
||||
AEMET_ATTR_IDEMA,
|
||||
AEMET_ATTR_MAX,
|
||||
AEMET_ATTR_MIN,
|
||||
AEMET_ATTR_NAME,
|
||||
AEMET_ATTR_PRECIPITATION,
|
||||
AEMET_ATTR_PRECIPITATION_PROBABILITY,
|
||||
AEMET_ATTR_SKY_STATE,
|
||||
@@ -24,25 +25,24 @@ from aemet_opendata.const import (
|
||||
AEMET_ATTR_SPEED,
|
||||
AEMET_ATTR_STATION_DATE,
|
||||
AEMET_ATTR_STATION_HUMIDITY,
|
||||
AEMET_ATTR_STATION_LOCATION,
|
||||
AEMET_ATTR_STATION_PRESSURE,
|
||||
AEMET_ATTR_STATION_PRESSURE_SEA,
|
||||
AEMET_ATTR_STATION_TEMPERATURE,
|
||||
AEMET_ATTR_STORM_PROBABILITY,
|
||||
AEMET_ATTR_TEMPERATURE,
|
||||
AEMET_ATTR_TEMPERATURE_FEELING,
|
||||
AEMET_ATTR_WIND,
|
||||
AEMET_ATTR_WIND_GUST,
|
||||
ATTR_DATA,
|
||||
)
|
||||
from aemet_opendata.exceptions import AemetError
|
||||
from aemet_opendata.forecast import ForecastValue
|
||||
from aemet_opendata.helpers import (
|
||||
get_forecast_day_value,
|
||||
get_forecast_hour_value,
|
||||
get_forecast_interval_value,
|
||||
)
|
||||
from aemet_opendata.interface import AEMET
|
||||
import async_timeout
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
@@ -57,7 +57,6 @@ from .const import (
|
||||
ATTR_API_FORECAST_TEMP_LOW,
|
||||
ATTR_API_FORECAST_TIME,
|
||||
ATTR_API_FORECAST_WIND_BEARING,
|
||||
ATTR_API_FORECAST_WIND_MAX_SPEED,
|
||||
ATTR_API_FORECAST_WIND_SPEED,
|
||||
ATTR_API_HUMIDITY,
|
||||
ATTR_API_PRESSURE,
|
||||
@@ -79,19 +78,22 @@ from .const import (
|
||||
ATTR_API_WIND_SPEED,
|
||||
CONDITIONS_MAP,
|
||||
DOMAIN,
|
||||
WIND_BEARING_MAP,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
API_TIMEOUT: Final[int] = 120
|
||||
STATION_MAX_DELTA = timedelta(hours=2)
|
||||
WEATHER_UPDATE_INTERVAL = timedelta(minutes=10)
|
||||
|
||||
|
||||
def format_condition(condition: str) -> str:
|
||||
"""Return condition from dict CONDITIONS_MAP."""
|
||||
val = ForecastValue.parse_condition(condition)
|
||||
return CONDITIONS_MAP.get(val, val)
|
||||
for key, value in CONDITIONS_MAP.items():
|
||||
if condition in value:
|
||||
return key
|
||||
_LOGGER.error('Condition "%s" not found in CONDITIONS_MAP', condition)
|
||||
return condition
|
||||
|
||||
|
||||
def format_float(value) -> float | None:
|
||||
@@ -110,33 +112,128 @@ def format_int(value) -> int | None:
|
||||
return None
|
||||
|
||||
|
||||
class TownNotFound(UpdateFailed):
|
||||
"""Raised when town is not found."""
|
||||
|
||||
|
||||
class WeatherUpdateCoordinator(DataUpdateCoordinator):
|
||||
"""Weather data update coordinator."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
aemet: AEMET,
|
||||
) -> None:
|
||||
def __init__(self, hass, aemet, latitude, longitude, station_updates):
|
||||
"""Initialize coordinator."""
|
||||
self.aemet = aemet
|
||||
|
||||
super().__init__(
|
||||
hass,
|
||||
_LOGGER,
|
||||
name=DOMAIN,
|
||||
update_interval=WEATHER_UPDATE_INTERVAL,
|
||||
hass, _LOGGER, name=DOMAIN, update_interval=WEATHER_UPDATE_INTERVAL
|
||||
)
|
||||
|
||||
async def _async_update_data(self) -> dict[str, Any]:
|
||||
"""Update coordinator data."""
|
||||
async with timeout(API_TIMEOUT):
|
||||
try:
|
||||
await self.aemet.update()
|
||||
except AemetError as error:
|
||||
raise UpdateFailed(error) from error
|
||||
weather_response = self.aemet.legacy_weather()
|
||||
return self._convert_weather_response(weather_response)
|
||||
self._aemet = aemet
|
||||
self._station = None
|
||||
self._town = None
|
||||
self._latitude = latitude
|
||||
self._longitude = longitude
|
||||
self._station_updates = station_updates
|
||||
self._data = {
|
||||
"daily": None,
|
||||
"hourly": None,
|
||||
"station": None,
|
||||
}
|
||||
|
||||
async def _async_update_data(self):
|
||||
data = {}
|
||||
async with async_timeout.timeout(120):
|
||||
weather_response = await self._get_aemet_weather()
|
||||
data = self._convert_weather_response(weather_response)
|
||||
return data
|
||||
|
||||
async def _get_aemet_weather(self):
|
||||
"""Poll weather data from AEMET OpenData."""
|
||||
weather = await self.hass.async_add_executor_job(self._get_weather_and_forecast)
|
||||
return weather
|
||||
|
||||
def _get_weather_station(self):
|
||||
if not self._station:
|
||||
self._station = (
|
||||
self._aemet.get_conventional_observation_station_by_coordinates(
|
||||
self._latitude, self._longitude
|
||||
)
|
||||
)
|
||||
if self._station:
|
||||
_LOGGER.debug(
|
||||
"station found for coordinates [%s, %s]: %s",
|
||||
self._latitude,
|
||||
self._longitude,
|
||||
self._station,
|
||||
)
|
||||
if not self._station:
|
||||
_LOGGER.debug(
|
||||
"station not found for coordinates [%s, %s]",
|
||||
self._latitude,
|
||||
self._longitude,
|
||||
)
|
||||
return self._station
|
||||
|
||||
def _get_weather_town(self):
|
||||
if not self._town:
|
||||
self._town = self._aemet.get_town_by_coordinates(
|
||||
self._latitude, self._longitude
|
||||
)
|
||||
if self._town:
|
||||
_LOGGER.debug(
|
||||
"Town found for coordinates [%s, %s]: %s",
|
||||
self._latitude,
|
||||
self._longitude,
|
||||
self._town,
|
||||
)
|
||||
if not self._town:
|
||||
_LOGGER.error(
|
||||
"Town not found for coordinates [%s, %s]",
|
||||
self._latitude,
|
||||
self._longitude,
|
||||
)
|
||||
raise TownNotFound
|
||||
return self._town
|
||||
|
||||
def _get_weather_and_forecast(self):
|
||||
"""Get weather and forecast data from AEMET OpenData."""
|
||||
|
||||
self._get_weather_town()
|
||||
|
||||
daily = self._aemet.get_specific_forecast_town_daily(self._town[AEMET_ATTR_ID])
|
||||
if not daily:
|
||||
_LOGGER.error(
|
||||
'Error fetching daily data for town "%s"', self._town[AEMET_ATTR_ID]
|
||||
)
|
||||
|
||||
hourly = self._aemet.get_specific_forecast_town_hourly(
|
||||
self._town[AEMET_ATTR_ID]
|
||||
)
|
||||
if not hourly:
|
||||
_LOGGER.error(
|
||||
'Error fetching hourly data for town "%s"', self._town[AEMET_ATTR_ID]
|
||||
)
|
||||
|
||||
station = None
|
||||
if self._station_updates and self._get_weather_station():
|
||||
station = self._aemet.get_conventional_observation_station_data(
|
||||
self._station[AEMET_ATTR_IDEMA]
|
||||
)
|
||||
if not station:
|
||||
_LOGGER.error(
|
||||
'Error fetching data for station "%s"',
|
||||
self._station[AEMET_ATTR_IDEMA],
|
||||
)
|
||||
|
||||
if daily:
|
||||
self._data["daily"] = daily
|
||||
if hourly:
|
||||
self._data["hourly"] = hourly
|
||||
if station:
|
||||
self._data["station"] = station
|
||||
|
||||
return AemetWeather(
|
||||
self._data["daily"],
|
||||
self._data["hourly"],
|
||||
self._data["station"],
|
||||
)
|
||||
|
||||
def _convert_weather_response(self, weather_response):
|
||||
"""Format the weather response correctly."""
|
||||
@@ -331,7 +428,6 @@ class WeatherUpdateCoordinator(DataUpdateCoordinator):
|
||||
),
|
||||
ATTR_API_FORECAST_TEMP: self._get_temperature(day, hour),
|
||||
ATTR_API_FORECAST_TIME: dt_util.as_utc(forecast_dt).isoformat(),
|
||||
ATTR_API_FORECAST_WIND_MAX_SPEED: self._get_wind_max_speed(day, hour),
|
||||
ATTR_API_FORECAST_WIND_SPEED: self._get_wind_speed(day, hour),
|
||||
ATTR_API_FORECAST_WIND_BEARING: self._get_wind_bearing(day, hour),
|
||||
}
|
||||
@@ -422,14 +518,14 @@ class WeatherUpdateCoordinator(DataUpdateCoordinator):
|
||||
|
||||
def _get_station_id(self):
|
||||
"""Get station ID from weather data."""
|
||||
if self.aemet.station:
|
||||
return self.aemet.station.get_id()
|
||||
if self._station:
|
||||
return self._station[AEMET_ATTR_IDEMA]
|
||||
return None
|
||||
|
||||
def _get_station_name(self):
|
||||
"""Get station name from weather data."""
|
||||
if self.aemet.station:
|
||||
return self.aemet.station.get_name()
|
||||
if self._station:
|
||||
return self._station[AEMET_ATTR_STATION_LOCATION]
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
@@ -465,19 +561,19 @@ class WeatherUpdateCoordinator(DataUpdateCoordinator):
|
||||
@staticmethod
|
||||
def _get_temperature_feeling(day_data, hour):
|
||||
"""Get temperature from weather data."""
|
||||
val = get_forecast_hour_value(day_data[AEMET_ATTR_FEEL_TEMPERATURE], hour)
|
||||
val = get_forecast_hour_value(day_data[AEMET_ATTR_TEMPERATURE_FEELING], hour)
|
||||
return format_int(val)
|
||||
|
||||
def _get_town_id(self):
|
||||
"""Get town ID from weather data."""
|
||||
if self.aemet.town:
|
||||
return self.aemet.town.get_id()
|
||||
if self._town:
|
||||
return self._town[AEMET_ATTR_ID]
|
||||
return None
|
||||
|
||||
def _get_town_name(self):
|
||||
"""Get town name from weather data."""
|
||||
if self.aemet.town:
|
||||
return self.aemet.town.get_name()
|
||||
if self._town:
|
||||
return self._town[AEMET_ATTR_NAME]
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
@@ -486,7 +582,10 @@ class WeatherUpdateCoordinator(DataUpdateCoordinator):
|
||||
val = get_forecast_hour_value(
|
||||
day_data[AEMET_ATTR_WIND_GUST], hour, key=AEMET_ATTR_DIRECTION
|
||||
)[0]
|
||||
return ForecastValue.parse_wind_direction(val)
|
||||
if val in WIND_BEARING_MAP:
|
||||
return WIND_BEARING_MAP[val]
|
||||
_LOGGER.error("%s not found in Wind Bearing map", val)
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _get_wind_bearing_day(day_data):
|
||||
@@ -494,7 +593,10 @@ class WeatherUpdateCoordinator(DataUpdateCoordinator):
|
||||
val = get_forecast_day_value(
|
||||
day_data[AEMET_ATTR_WIND], key=AEMET_ATTR_DIRECTION
|
||||
)
|
||||
return ForecastValue.parse_wind_direction(val)
|
||||
if val in WIND_BEARING_MAP:
|
||||
return WIND_BEARING_MAP[val]
|
||||
_LOGGER.error("%s not found in Wind Bearing map", val)
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _get_wind_max_speed(day_data, hour):
|
||||
@@ -521,3 +623,12 @@ class WeatherUpdateCoordinator(DataUpdateCoordinator):
|
||||
if val:
|
||||
return format_int(val)
|
||||
return None
|
||||
|
||||
|
||||
@dataclass
|
||||
class AemetWeather:
|
||||
"""Class to harmonize weather data model."""
|
||||
|
||||
daily: dict = field(default_factory=dict)
|
||||
hourly: dict = field(default_factory=dict)
|
||||
station: dict = field(default_factory=dict)
|
||||
|
||||
@@ -1,42 +1 @@
|
||||
"""The AfterShip integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
from pyaftership import AfterShip, AfterShipException
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_API_KEY, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
PLATFORMS: list[Platform] = [Platform.SENSOR]
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up AfterShip from a config entry."""
|
||||
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
|
||||
session = async_get_clientsession(hass)
|
||||
aftership = AfterShip(api_key=entry.data[CONF_API_KEY], session=session)
|
||||
|
||||
try:
|
||||
await aftership.trackings.list()
|
||||
except AfterShipException as err:
|
||||
raise ConfigEntryNotReady from err
|
||||
|
||||
hass.data[DOMAIN][entry.entry_id] = aftership
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Unload a config entry."""
|
||||
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
|
||||
hass.data[DOMAIN].pop(entry.entry_id)
|
||||
|
||||
return unload_ok
|
||||
"""The aftership component."""
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
"""Config flow for AfterShip integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from pyaftership import AfterShip, AfterShipException
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import ConfigFlow
|
||||
from homeassistant.const import CONF_API_KEY, CONF_NAME
|
||||
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN
|
||||
from homeassistant.data_entry_flow import AbortFlow, FlowResult
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AfterShipConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a config flow for AfterShip."""
|
||||
|
||||
VERSION = 1
|
||||
|
||||
async def async_step_user(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> FlowResult:
|
||||
"""Handle the initial step."""
|
||||
errors: dict[str, str] = {}
|
||||
if user_input is not None:
|
||||
self._async_abort_entries_match({CONF_API_KEY: user_input[CONF_API_KEY]})
|
||||
try:
|
||||
aftership = AfterShip(
|
||||
api_key=user_input[CONF_API_KEY],
|
||||
session=async_get_clientsession(self.hass),
|
||||
)
|
||||
await aftership.trackings.list()
|
||||
except AfterShipException:
|
||||
_LOGGER.exception("Aftership raised exception")
|
||||
errors["base"] = "cannot_connect"
|
||||
else:
|
||||
return self.async_create_entry(title="AfterShip", data=user_input)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="user",
|
||||
data_schema=vol.Schema({vol.Required(CONF_API_KEY): str}),
|
||||
errors=errors,
|
||||
)
|
||||
|
||||
async def async_step_import(self, config: dict[str, Any]) -> FlowResult:
|
||||
"""Import configuration from yaml."""
|
||||
try:
|
||||
self._async_abort_entries_match({CONF_API_KEY: config[CONF_API_KEY]})
|
||||
except AbortFlow as err:
|
||||
async_create_issue(
|
||||
self.hass,
|
||||
DOMAIN,
|
||||
"deprecated_yaml_import_issue_already_configured",
|
||||
breaks_in_ha_version="2024.4.0",
|
||||
is_fixable=False,
|
||||
issue_domain=DOMAIN,
|
||||
severity=IssueSeverity.WARNING,
|
||||
translation_key="deprecated_yaml_import_issue_already_configured",
|
||||
translation_placeholders={
|
||||
"domain": DOMAIN,
|
||||
"integration_title": "AfterShip",
|
||||
},
|
||||
)
|
||||
raise err
|
||||
|
||||
async_create_issue(
|
||||
self.hass,
|
||||
HOMEASSISTANT_DOMAIN,
|
||||
f"deprecated_yaml_{DOMAIN}",
|
||||
breaks_in_ha_version="2024.4.0",
|
||||
is_fixable=False,
|
||||
issue_domain=DOMAIN,
|
||||
severity=IssueSeverity.WARNING,
|
||||
translation_key="deprecated_yaml",
|
||||
translation_placeholders={
|
||||
"domain": DOMAIN,
|
||||
"integration_title": "AfterShip",
|
||||
},
|
||||
)
|
||||
return self.async_create_entry(
|
||||
title=config.get(CONF_NAME, "AfterShip"),
|
||||
data={CONF_API_KEY: config[CONF_API_KEY]},
|
||||
)
|
||||
@@ -2,7 +2,6 @@
|
||||
"domain": "aftership",
|
||||
"name": "AfterShip",
|
||||
"codeowners": [],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/aftership",
|
||||
"iot_class": "cloud_polling",
|
||||
"requirements": ["pyaftership==21.11.0"]
|
||||
|
||||
@@ -11,7 +11,6 @@ from homeassistant.components.sensor import (
|
||||
PLATFORM_SCHEMA as BASE_PLATFORM_SCHEMA,
|
||||
SensorEntity,
|
||||
)
|
||||
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
|
||||
from homeassistant.const import CONF_API_KEY, CONF_NAME
|
||||
from homeassistant.core import HomeAssistant, ServiceCall
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
@@ -21,7 +20,6 @@ from homeassistant.helpers.dispatcher import (
|
||||
async_dispatcher_send,
|
||||
)
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
from homeassistant.util import Throttle
|
||||
|
||||
@@ -60,43 +58,19 @@ async def async_setup_platform(
|
||||
discovery_info: DiscoveryInfoType | None = None,
|
||||
) -> None:
|
||||
"""Set up the AfterShip sensor platform."""
|
||||
aftership = AfterShip(
|
||||
api_key=config[CONF_API_KEY], session=async_get_clientsession(hass)
|
||||
)
|
||||
apikey = config[CONF_API_KEY]
|
||||
name = config[CONF_NAME]
|
||||
|
||||
session = async_get_clientsession(hass)
|
||||
aftership = AfterShip(api_key=apikey, session=session)
|
||||
|
||||
try:
|
||||
await aftership.trackings.list()
|
||||
except AfterShipException:
|
||||
async_create_issue(
|
||||
hass,
|
||||
DOMAIN,
|
||||
"deprecated_yaml_import_issue_cannot_connect",
|
||||
breaks_in_ha_version="2024.4.0",
|
||||
is_fixable=False,
|
||||
issue_domain=DOMAIN,
|
||||
severity=IssueSeverity.WARNING,
|
||||
translation_key="deprecated_yaml_import_issue_cannot_connect",
|
||||
translation_placeholders={
|
||||
"integration_title": "AfterShip",
|
||||
"url": "/config/integrations/dashboard/add?domain=aftership",
|
||||
},
|
||||
)
|
||||
except AfterShipException as err:
|
||||
_LOGGER.error("No tracking data found. Check API key is correct: %s", err)
|
||||
return
|
||||
|
||||
hass.async_create_task(
|
||||
hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_IMPORT}, data=config
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up AfterShip sensor entities based on a config entry."""
|
||||
aftership: AfterShip = hass.data[DOMAIN][config_entry.entry_id]
|
||||
|
||||
async_add_entities([AfterShipSensor(aftership, config_entry.title)], True)
|
||||
async_add_entities([AfterShipSensor(aftership, name)], True)
|
||||
|
||||
async def handle_add_tracking(call: ServiceCall) -> None:
|
||||
"""Call when a user adds a new Aftership tracking from Home Assistant."""
|
||||
|
||||
@@ -1,29 +1,43 @@
|
||||
# Describes the format for available aftership services
|
||||
|
||||
add_tracking:
|
||||
name: Add tracking
|
||||
description: Add new tracking number to Aftership.
|
||||
fields:
|
||||
tracking_number:
|
||||
name: Tracking number
|
||||
description: Tracking number for the new tracking
|
||||
required: true
|
||||
example: "123456789"
|
||||
selector:
|
||||
text:
|
||||
title:
|
||||
name: Title
|
||||
description: A custom title for the new tracking
|
||||
example: "Laptop"
|
||||
selector:
|
||||
text:
|
||||
slug:
|
||||
name: Slug
|
||||
description: Slug (carrier) of the new tracking
|
||||
example: "USPS"
|
||||
selector:
|
||||
text:
|
||||
|
||||
remove_tracking:
|
||||
name: Remove tracking
|
||||
description: Remove a tracking number from Aftership.
|
||||
fields:
|
||||
tracking_number:
|
||||
name: Tracking number
|
||||
description: Tracking number of the tracking to remove
|
||||
required: true
|
||||
example: "123456789"
|
||||
selector:
|
||||
text:
|
||||
slug:
|
||||
name: Slug
|
||||
description: Slug (carrier) of the tracking to remove
|
||||
example: "USPS"
|
||||
selector:
|
||||
text:
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
{
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"api_key": "[%key:common::config_flow::data::api_key%]"
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
|
||||
},
|
||||
"abort": {
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
||||
}
|
||||
},
|
||||
"services": {
|
||||
"add_tracking": {
|
||||
"name": "Add tracking",
|
||||
"description": "Adds a new tracking number to Aftership.",
|
||||
"fields": {
|
||||
"tracking_number": {
|
||||
"name": "Tracking number",
|
||||
"description": "Tracking number for the new tracking."
|
||||
},
|
||||
"title": {
|
||||
"name": "Title",
|
||||
"description": "A custom title for the new tracking."
|
||||
},
|
||||
"slug": {
|
||||
"name": "Slug",
|
||||
"description": "Slug (carrier) of the new tracking."
|
||||
}
|
||||
}
|
||||
},
|
||||
"remove_tracking": {
|
||||
"name": "Remove tracking",
|
||||
"description": "Removes a tracking number from Aftership.",
|
||||
"fields": {
|
||||
"tracking_number": {
|
||||
"name": "[%key:component::aftership::services::add_tracking::fields::tracking_number::name%]",
|
||||
"description": "Tracking number of the tracking to remove."
|
||||
},
|
||||
"slug": {
|
||||
"name": "[%key:component::aftership::services::add_tracking::fields::slug::name%]",
|
||||
"description": "Slug (carrier) of the tracking to remove."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"issues": {
|
||||
"deprecated_yaml_import_issue_already_configured": {
|
||||
"title": "The {integration_title} YAML configuration import failed",
|
||||
"description": "Configuring {integration_title} using YAML is being removed but the YAML configuration was already imported.\n\nRemove the YAML configuration and restart Home Assistant."
|
||||
},
|
||||
"deprecated_yaml_import_issue_cannot_connect": {
|
||||
"title": "The {integration_title} YAML configuration import failed",
|
||||
"description": "Configuring {integration_title} using YAML is being removed but there was an connection error importing your YAML configuration.\n\nEnsure connection to {integration_title} works and restart Home Assistant to try again or remove the {integration_title} YAML configuration from your configuration.yaml file and continue to [set up the integration]({url}) manually."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ from homeassistant.const import (
|
||||
STATE_ALARM_DISARMED,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import CONNECTION, DOMAIN as AGENT_DOMAIN
|
||||
@@ -47,16 +47,14 @@ class AgentBaseStation(AlarmControlPanelEntity):
|
||||
| AlarmControlPanelEntityFeature.ARM_AWAY
|
||||
| AlarmControlPanelEntityFeature.ARM_NIGHT
|
||||
)
|
||||
_attr_has_entity_name = True
|
||||
_attr_name = None
|
||||
|
||||
def __init__(self, client):
|
||||
"""Initialize the alarm control panel."""
|
||||
self._client = client
|
||||
self._attr_name = f"{client.name} {CONST_ALARM_CONTROL_PANEL_NAME}"
|
||||
self._attr_unique_id = f"{client.unique}_CP"
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(AGENT_DOMAIN, client.unique)},
|
||||
name=f"{client.name} {CONST_ALARM_CONTROL_PANEL_NAME}",
|
||||
manufacturer="Agent",
|
||||
model=CONST_ALARM_CONTROL_PANEL_NAME,
|
||||
sw_version=client.version,
|
||||
|
||||
@@ -8,7 +8,7 @@ from homeassistant.components.camera import CameraEntityFeature
|
||||
from homeassistant.components.mjpeg import MjpegCamera, filter_urllib3_logging
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import (
|
||||
AddEntitiesCallback,
|
||||
async_get_current_platform,
|
||||
@@ -72,13 +72,12 @@ class AgentCamera(MjpegCamera):
|
||||
_attr_attribution = ATTRIBUTION
|
||||
_attr_should_poll = True # Cameras default to False
|
||||
_attr_supported_features = CameraEntityFeature.ON_OFF
|
||||
_attr_has_entity_name = True
|
||||
_attr_name = None
|
||||
|
||||
def __init__(self, device):
|
||||
"""Initialize as a subclass of MjpegCamera."""
|
||||
self.device = device
|
||||
self._removed = False
|
||||
self._attr_name = f"{device.client.name} {device.name}"
|
||||
self._attr_unique_id = f"{device._client.unique}_{device.typeID}_{device.id}"
|
||||
super().__init__(
|
||||
name=device.name,
|
||||
@@ -89,7 +88,7 @@ class AgentCamera(MjpegCamera):
|
||||
identifiers={(AGENT_DOMAIN, self.unique_id)},
|
||||
manufacturer="Agent",
|
||||
model="Camera",
|
||||
name=f"{device.client.name} {device.name}",
|
||||
name=self.name,
|
||||
sw_version=device.client.version,
|
||||
)
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"name": "Agent DVR",
|
||||
"codeowners": ["@ispysoftware"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/agent_dvr",
|
||||
"documentation": "https://www.home-assistant.io/integrations/agent_dvr/",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["agent"],
|
||||
"requirements": ["agent-py==0.0.23"]
|
||||
|
||||
@@ -1,28 +1,38 @@
|
||||
start_recording:
|
||||
name: Start recording
|
||||
description: Enable continuous recording.
|
||||
target:
|
||||
entity:
|
||||
integration: agent_dvr
|
||||
domain: camera
|
||||
|
||||
stop_recording:
|
||||
name: Stop recording
|
||||
description: Disable continuous recording.
|
||||
target:
|
||||
entity:
|
||||
integration: agent_dvr
|
||||
domain: camera
|
||||
|
||||
enable_alerts:
|
||||
name: Enable alerts
|
||||
description: Enable alerts
|
||||
target:
|
||||
entity:
|
||||
integration: agent_dvr
|
||||
domain: camera
|
||||
|
||||
disable_alerts:
|
||||
name: Disable alerts
|
||||
description: Disable alerts
|
||||
target:
|
||||
entity:
|
||||
integration: agent_dvr
|
||||
domain: camera
|
||||
|
||||
snapshot:
|
||||
name: Snapshot
|
||||
description: Take a photo
|
||||
target:
|
||||
entity:
|
||||
integration: agent_dvr
|
||||
|
||||
@@ -16,27 +16,5 @@
|
||||
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]",
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
|
||||
}
|
||||
},
|
||||
"services": {
|
||||
"start_recording": {
|
||||
"name": "Start recording",
|
||||
"description": "Enables continuous recording."
|
||||
},
|
||||
"stop_recording": {
|
||||
"name": "Stop recording",
|
||||
"description": "Disables continuous recording."
|
||||
},
|
||||
"enable_alerts": {
|
||||
"name": "Enable alerts",
|
||||
"description": "Enables alerts."
|
||||
},
|
||||
"disable_alerts": {
|
||||
"name": "Disable alerts",
|
||||
"description": "Disables alerts."
|
||||
},
|
||||
"snapshot": {
|
||||
"name": "Snapshot",
|
||||
"description": "Takes a photo."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,13 @@ from __future__ import annotations
|
||||
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
from math import ceil
|
||||
|
||||
from aiohttp import ClientSession
|
||||
from aiohttp.client_exceptions import ClientConnectorError
|
||||
from airly import Airly
|
||||
from airly.exceptions import AirlyError
|
||||
import async_timeout
|
||||
|
||||
from homeassistant.components.air_quality import DOMAIN as AIR_QUALITY_PLATFORM
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
@@ -10,15 +17,53 @@ from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, Pla
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from .const import CONF_USE_NEAREST, DOMAIN, MIN_UPDATE_INTERVAL
|
||||
from .coordinator import AirlyDataUpdateCoordinator
|
||||
from .const import (
|
||||
ATTR_API_ADVICE,
|
||||
ATTR_API_CAQI,
|
||||
ATTR_API_CAQI_DESCRIPTION,
|
||||
ATTR_API_CAQI_LEVEL,
|
||||
CONF_USE_NEAREST,
|
||||
DOMAIN,
|
||||
MAX_UPDATE_INTERVAL,
|
||||
MIN_UPDATE_INTERVAL,
|
||||
NO_AIRLY_SENSORS,
|
||||
)
|
||||
|
||||
PLATFORMS = [Platform.SENSOR]
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def set_update_interval(instances_count: int, requests_remaining: int) -> timedelta:
|
||||
"""Return data update interval.
|
||||
|
||||
The number of requests is reset at midnight UTC so we calculate the update
|
||||
interval based on number of minutes until midnight, the number of Airly instances
|
||||
and the number of remaining requests.
|
||||
"""
|
||||
now = dt_util.utcnow()
|
||||
midnight = dt_util.find_next_time_expression_time(
|
||||
now, seconds=[0], minutes=[0], hours=[0]
|
||||
)
|
||||
minutes_to_midnight = (midnight - now).total_seconds() / 60
|
||||
interval = timedelta(
|
||||
minutes=min(
|
||||
max(
|
||||
ceil(minutes_to_midnight / requests_remaining * instances_count),
|
||||
MIN_UPDATE_INTERVAL,
|
||||
),
|
||||
MAX_UPDATE_INTERVAL,
|
||||
)
|
||||
)
|
||||
|
||||
_LOGGER.debug("Data will be update every %s", interval)
|
||||
|
||||
return interval
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up Airly as config entry."""
|
||||
api_key = entry.data[CONF_API_KEY]
|
||||
@@ -45,7 +90,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
str(longitude),
|
||||
),
|
||||
):
|
||||
device_entry = device_registry.async_get_device(identifiers={old_ids}) # type: ignore[arg-type]
|
||||
device_entry = device_registry.async_get_device({old_ids}) # type: ignore[arg-type]
|
||||
if device_entry and entry.entry_id in device_entry.config_entries:
|
||||
new_ids = (DOMAIN, f"{latitude}-{longitude}")
|
||||
device_registry.async_update_device(
|
||||
@@ -86,3 +131,75 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
hass.data[DOMAIN].pop(entry.entry_id)
|
||||
|
||||
return unload_ok
|
||||
|
||||
|
||||
class AirlyDataUpdateCoordinator(DataUpdateCoordinator):
|
||||
"""Define an object to hold Airly data."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
session: ClientSession,
|
||||
api_key: str,
|
||||
latitude: float,
|
||||
longitude: float,
|
||||
update_interval: timedelta,
|
||||
use_nearest: bool,
|
||||
) -> None:
|
||||
"""Initialize."""
|
||||
self.latitude = latitude
|
||||
self.longitude = longitude
|
||||
# Currently, Airly only supports Polish and English
|
||||
language = "pl" if hass.config.language == "pl" else "en"
|
||||
self.airly = Airly(api_key, session, language=language)
|
||||
self.use_nearest = use_nearest
|
||||
|
||||
super().__init__(hass, _LOGGER, name=DOMAIN, update_interval=update_interval)
|
||||
|
||||
async def _async_update_data(self) -> dict[str, str | float | int]:
|
||||
"""Update data via library."""
|
||||
data: dict[str, str | float | int] = {}
|
||||
if self.use_nearest:
|
||||
measurements = self.airly.create_measurements_session_nearest(
|
||||
self.latitude, self.longitude, max_distance_km=5
|
||||
)
|
||||
else:
|
||||
measurements = self.airly.create_measurements_session_point(
|
||||
self.latitude, self.longitude
|
||||
)
|
||||
async with async_timeout.timeout(20):
|
||||
try:
|
||||
await measurements.update()
|
||||
except (AirlyError, ClientConnectorError) as error:
|
||||
raise UpdateFailed(error) from error
|
||||
|
||||
_LOGGER.debug(
|
||||
"Requests remaining: %s/%s",
|
||||
self.airly.requests_remaining,
|
||||
self.airly.requests_per_day,
|
||||
)
|
||||
|
||||
# Airly API sometimes returns None for requests remaining so we update
|
||||
# update_interval only if we have valid value.
|
||||
if self.airly.requests_remaining:
|
||||
self.update_interval = set_update_interval(
|
||||
len(self.hass.config_entries.async_entries(DOMAIN)),
|
||||
self.airly.requests_remaining,
|
||||
)
|
||||
|
||||
values = measurements.current["values"]
|
||||
index = measurements.current["indexes"][0]
|
||||
standards = measurements.current["standards"]
|
||||
|
||||
if index["description"] == NO_AIRLY_SENSORS:
|
||||
raise UpdateFailed("Can't retrieve data: no Airly sensors in this area")
|
||||
for value in values:
|
||||
data[value["name"]] = value["value"]
|
||||
for standard in standards:
|
||||
data[f"{standard['pollutant']}_LIMIT"] = standard["limit"]
|
||||
data[f"{standard['pollutant']}_PERCENT"] = standard["percent"]
|
||||
data[ATTR_API_CAQI] = index["value"]
|
||||
data[ATTR_API_CAQI_LEVEL] = index["level"].lower().replace("_", " ")
|
||||
data[ATTR_API_CAQI_DESCRIPTION] = index["description"]
|
||||
data[ATTR_API_ADVICE] = index["advice"]
|
||||
return data
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
"""Adds config flow for Airly."""
|
||||
from __future__ import annotations
|
||||
|
||||
from asyncio import timeout
|
||||
from http import HTTPStatus
|
||||
from typing import Any
|
||||
|
||||
from aiohttp import ClientSession
|
||||
from airly import Airly
|
||||
from airly.exceptions import AirlyError
|
||||
import async_timeout
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries
|
||||
@@ -105,7 +105,7 @@ async def test_location(
|
||||
measurements = airly.create_measurements_session_point(
|
||||
latitude=latitude, longitude=longitude
|
||||
)
|
||||
async with timeout(10):
|
||||
async with async_timeout.timeout(10):
|
||||
await measurements.update()
|
||||
|
||||
current = measurements.current
|
||||
|
||||
@@ -1,126 +0,0 @@
|
||||
"""DataUpdateCoordinator for the Airly integration."""
|
||||
from asyncio import timeout
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
from math import ceil
|
||||
|
||||
from aiohttp import ClientSession
|
||||
from aiohttp.client_exceptions import ClientConnectorError
|
||||
from airly import Airly
|
||||
from airly.exceptions import AirlyError
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from .const import (
|
||||
ATTR_API_ADVICE,
|
||||
ATTR_API_CAQI,
|
||||
ATTR_API_CAQI_DESCRIPTION,
|
||||
ATTR_API_CAQI_LEVEL,
|
||||
DOMAIN,
|
||||
MAX_UPDATE_INTERVAL,
|
||||
MIN_UPDATE_INTERVAL,
|
||||
NO_AIRLY_SENSORS,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def set_update_interval(instances_count: int, requests_remaining: int) -> timedelta:
|
||||
"""Return data update interval.
|
||||
|
||||
The number of requests is reset at midnight UTC so we calculate the update
|
||||
interval based on number of minutes until midnight, the number of Airly instances
|
||||
and the number of remaining requests.
|
||||
"""
|
||||
now = dt_util.utcnow()
|
||||
midnight = dt_util.find_next_time_expression_time(
|
||||
now, seconds=[0], minutes=[0], hours=[0]
|
||||
)
|
||||
minutes_to_midnight = (midnight - now).total_seconds() / 60
|
||||
interval = timedelta(
|
||||
minutes=min(
|
||||
max(
|
||||
ceil(minutes_to_midnight / requests_remaining * instances_count),
|
||||
MIN_UPDATE_INTERVAL,
|
||||
),
|
||||
MAX_UPDATE_INTERVAL,
|
||||
)
|
||||
)
|
||||
|
||||
_LOGGER.debug("Data will be update every %s", interval)
|
||||
|
||||
return interval
|
||||
|
||||
|
||||
class AirlyDataUpdateCoordinator(DataUpdateCoordinator):
|
||||
"""Define an object to hold Airly data."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
session: ClientSession,
|
||||
api_key: str,
|
||||
latitude: float,
|
||||
longitude: float,
|
||||
update_interval: timedelta,
|
||||
use_nearest: bool,
|
||||
) -> None:
|
||||
"""Initialize."""
|
||||
self.latitude = latitude
|
||||
self.longitude = longitude
|
||||
# Currently, Airly only supports Polish and English
|
||||
language = "pl" if hass.config.language == "pl" else "en"
|
||||
self.airly = Airly(api_key, session, language=language)
|
||||
self.use_nearest = use_nearest
|
||||
|
||||
super().__init__(hass, _LOGGER, name=DOMAIN, update_interval=update_interval)
|
||||
|
||||
async def _async_update_data(self) -> dict[str, str | float | int]:
|
||||
"""Update data via library."""
|
||||
data: dict[str, str | float | int] = {}
|
||||
if self.use_nearest:
|
||||
measurements = self.airly.create_measurements_session_nearest(
|
||||
self.latitude, self.longitude, max_distance_km=5
|
||||
)
|
||||
else:
|
||||
measurements = self.airly.create_measurements_session_point(
|
||||
self.latitude, self.longitude
|
||||
)
|
||||
async with timeout(20):
|
||||
try:
|
||||
await measurements.update()
|
||||
except (AirlyError, ClientConnectorError) as error:
|
||||
raise UpdateFailed(error) from error
|
||||
|
||||
_LOGGER.debug(
|
||||
"Requests remaining: %s/%s",
|
||||
self.airly.requests_remaining,
|
||||
self.airly.requests_per_day,
|
||||
)
|
||||
|
||||
# Airly API sometimes returns None for requests remaining so we update
|
||||
# update_interval only if we have valid value.
|
||||
if self.airly.requests_remaining:
|
||||
self.update_interval = set_update_interval(
|
||||
len(self.hass.config_entries.async_entries(DOMAIN)),
|
||||
self.airly.requests_remaining,
|
||||
)
|
||||
|
||||
values = measurements.current["values"]
|
||||
index = measurements.current["indexes"][0]
|
||||
standards = measurements.current["standards"]
|
||||
|
||||
if index["description"] == NO_AIRLY_SENSORS:
|
||||
raise UpdateFailed("Can't retrieve data: no Airly sensors in this area")
|
||||
for value in values:
|
||||
data[value["name"]] = value["value"]
|
||||
for standard in standards:
|
||||
data[f"{standard['pollutant']}_LIMIT"] = standard["limit"]
|
||||
data[f"{standard['pollutant']}_PERCENT"] = standard["percent"]
|
||||
data[ATTR_API_CAQI] = index["value"]
|
||||
data[ATTR_API_CAQI_LEVEL] = index["level"].lower().replace("_", " ")
|
||||
data[ATTR_API_CAQI_DESCRIPTION] = index["description"]
|
||||
data[ATTR_API_ADVICE] = index["advice"]
|
||||
return data
|
||||
@@ -20,7 +20,8 @@ from homeassistant.const import (
|
||||
UnitOfTemperature,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
|
||||
from homeassistant.helpers.device_registry import DeviceEntryType
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
},
|
||||
"abort": {
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_location%]",
|
||||
"wrong_location": "[%key:component::airly::config::error::wrong_location%]"
|
||||
"wrong_location": "No Airly measuring stations in this area."
|
||||
}
|
||||
},
|
||||
"system_health": {
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
import datetime
|
||||
import logging
|
||||
|
||||
from aiohttp.client_exceptions import ClientConnectorError
|
||||
from pyairnow import WebServiceAPI
|
||||
from pyairnow.conv import aqi_to_concentration
|
||||
from pyairnow.errors import AirNowError
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
CONF_API_KEY,
|
||||
@@ -12,9 +17,26 @@ from homeassistant.const import (
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import AirNowDataUpdateCoordinator
|
||||
from .const import (
|
||||
ATTR_API_AQI,
|
||||
ATTR_API_AQI_DESCRIPTION,
|
||||
ATTR_API_AQI_LEVEL,
|
||||
ATTR_API_AQI_PARAM,
|
||||
ATTR_API_CAT_DESCRIPTION,
|
||||
ATTR_API_CAT_LEVEL,
|
||||
ATTR_API_CATEGORY,
|
||||
ATTR_API_PM25,
|
||||
ATTR_API_POLLUTANT,
|
||||
ATTR_API_REPORT_DATE,
|
||||
ATTR_API_REPORT_HOUR,
|
||||
ATTR_API_STATE,
|
||||
ATTR_API_STATION,
|
||||
ATTR_API_STATION_LATITUDE,
|
||||
ATTR_API_STATION_LONGITUDE,
|
||||
DOMAIN,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
PLATFORMS = [Platform.SENSOR]
|
||||
@@ -25,9 +47,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
api_key = entry.data[CONF_API_KEY]
|
||||
latitude = entry.data[CONF_LATITUDE]
|
||||
longitude = entry.data[CONF_LONGITUDE]
|
||||
|
||||
# Station Radius is a user-configurable option
|
||||
distance = entry.options[CONF_RADIUS]
|
||||
distance = entry.data[CONF_RADIUS]
|
||||
|
||||
# Reports are published hourly but update twice per hour
|
||||
update_interval = datetime.timedelta(minutes=30)
|
||||
@@ -45,33 +65,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
hass.data[DOMAIN][entry.entry_id] = coordinator
|
||||
|
||||
# Listen for option changes
|
||||
entry.async_on_unload(entry.add_update_listener(update_listener))
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Migrate old entry."""
|
||||
_LOGGER.debug("Migrating from version %s", entry.version)
|
||||
|
||||
if entry.version == 1:
|
||||
new_options = {CONF_RADIUS: entry.data[CONF_RADIUS]}
|
||||
new_data = entry.data.copy()
|
||||
del new_data[CONF_RADIUS]
|
||||
|
||||
entry.version = 2
|
||||
hass.config_entries.async_update_entry(
|
||||
entry, data=new_data, options=new_options
|
||||
)
|
||||
|
||||
_LOGGER.info("Migration to version %s successful", entry.version)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Unload a config entry."""
|
||||
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
@@ -82,6 +80,70 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
return unload_ok
|
||||
|
||||
|
||||
async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
||||
"""Handle options update."""
|
||||
await hass.config_entries.async_reload(entry.entry_id)
|
||||
class AirNowDataUpdateCoordinator(DataUpdateCoordinator):
|
||||
"""Define an object to hold Airly data."""
|
||||
|
||||
def __init__(
|
||||
self, hass, session, api_key, latitude, longitude, distance, update_interval
|
||||
):
|
||||
"""Initialize."""
|
||||
self.latitude = latitude
|
||||
self.longitude = longitude
|
||||
self.distance = distance
|
||||
|
||||
self.airnow = WebServiceAPI(api_key, session=session)
|
||||
|
||||
super().__init__(hass, _LOGGER, name=DOMAIN, update_interval=update_interval)
|
||||
|
||||
async def _async_update_data(self):
|
||||
"""Update data via library."""
|
||||
data = {}
|
||||
try:
|
||||
obs = await self.airnow.observations.latLong(
|
||||
self.latitude,
|
||||
self.longitude,
|
||||
distance=self.distance,
|
||||
)
|
||||
|
||||
except (AirNowError, ClientConnectorError) as error:
|
||||
raise UpdateFailed(error) from error
|
||||
|
||||
if not obs:
|
||||
raise UpdateFailed("No data was returned from AirNow")
|
||||
|
||||
max_aqi = 0
|
||||
max_aqi_level = 0
|
||||
max_aqi_desc = ""
|
||||
max_aqi_poll = ""
|
||||
for obv in obs:
|
||||
# Convert AQIs to Concentration
|
||||
pollutant = obv[ATTR_API_AQI_PARAM]
|
||||
concentration = aqi_to_concentration(obv[ATTR_API_AQI], pollutant)
|
||||
data[obv[ATTR_API_AQI_PARAM]] = concentration
|
||||
|
||||
# Overall AQI is the max of all pollutant AQIs
|
||||
if obv[ATTR_API_AQI] > max_aqi:
|
||||
max_aqi = obv[ATTR_API_AQI]
|
||||
max_aqi_level = obv[ATTR_API_CATEGORY][ATTR_API_CAT_LEVEL]
|
||||
max_aqi_desc = obv[ATTR_API_CATEGORY][ATTR_API_CAT_DESCRIPTION]
|
||||
max_aqi_poll = pollutant
|
||||
|
||||
# Copy other data from PM2.5 Value
|
||||
if obv[ATTR_API_AQI_PARAM] == ATTR_API_PM25:
|
||||
# Copy Report Details
|
||||
data[ATTR_API_REPORT_DATE] = obv[ATTR_API_REPORT_DATE]
|
||||
data[ATTR_API_REPORT_HOUR] = obv[ATTR_API_REPORT_HOUR]
|
||||
|
||||
# Copy Station Details
|
||||
data[ATTR_API_STATE] = obv[ATTR_API_STATE]
|
||||
data[ATTR_API_STATION] = obv[ATTR_API_STATION]
|
||||
data[ATTR_API_STATION_LATITUDE] = obv[ATTR_API_STATION_LATITUDE]
|
||||
data[ATTR_API_STATION_LONGITUDE] = obv[ATTR_API_STATION_LONGITUDE]
|
||||
|
||||
# Store Overall AQI
|
||||
data[ATTR_API_AQI] = max_aqi
|
||||
data[ATTR_API_AQI_LEVEL] = max_aqi_level
|
||||
data[ATTR_API_AQI_DESCRIPTION] = max_aqi_desc
|
||||
data[ATTR_API_POLLUTANT] = max_aqi_poll
|
||||
|
||||
return data
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
"""Config flow for AirNow integration."""
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from pyairnow import WebServiceAPI
|
||||
from pyairnow.errors import AirNowError, EmptyResponseError, InvalidKeyError
|
||||
from pyairnow.errors import AirNowError, InvalidKeyError
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries, core, data_entry_flow, exceptions
|
||||
from homeassistant import config_entries, core, exceptions
|
||||
from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_RADIUS
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
@@ -36,8 +35,6 @@ async def validate_input(hass: core.HomeAssistant, data):
|
||||
raise InvalidAuth from exc
|
||||
except AirNowError as exc:
|
||||
raise CannotConnect from exc
|
||||
except EmptyResponseError as exc:
|
||||
raise InvalidLocation from exc
|
||||
|
||||
if not test_data:
|
||||
raise InvalidLocation
|
||||
@@ -49,7 +46,7 @@ async def validate_input(hass: core.HomeAssistant, data):
|
||||
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a config flow for AirNow."""
|
||||
|
||||
VERSION = 2
|
||||
VERSION = 1
|
||||
|
||||
async def async_step_user(self, user_input=None):
|
||||
"""Handle the initial step."""
|
||||
@@ -76,14 +73,12 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
errors["base"] = "unknown"
|
||||
else:
|
||||
# Create Entry
|
||||
radius = user_input.pop(CONF_RADIUS)
|
||||
return self.async_create_entry(
|
||||
title=(
|
||||
f"AirNow Sensor at {user_input[CONF_LATITUDE]},"
|
||||
f" {user_input[CONF_LONGITUDE]}"
|
||||
),
|
||||
data=user_input,
|
||||
options={CONF_RADIUS: radius},
|
||||
)
|
||||
|
||||
return self.async_show_form(
|
||||
@@ -97,49 +92,12 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
vol.Optional(
|
||||
CONF_LONGITUDE, default=self.hass.config.longitude
|
||||
): cv.longitude,
|
||||
vol.Optional(CONF_RADIUS, default=150): vol.All(
|
||||
int, vol.Range(min=5)
|
||||
),
|
||||
vol.Optional(CONF_RADIUS, default=150): int,
|
||||
}
|
||||
),
|
||||
errors=errors,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@core.callback
|
||||
def async_get_options_flow(
|
||||
config_entry: config_entries.ConfigEntry,
|
||||
) -> config_entries.OptionsFlow:
|
||||
"""Return the options flow."""
|
||||
return OptionsFlowHandler(config_entry)
|
||||
|
||||
|
||||
class OptionsFlowHandler(config_entries.OptionsFlowWithConfigEntry):
|
||||
"""Handle an options flow for AirNow."""
|
||||
|
||||
async def async_step_init(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> data_entry_flow.FlowResult:
|
||||
"""Manage the options."""
|
||||
if user_input is not None:
|
||||
return self.async_create_entry(data=user_input)
|
||||
|
||||
options_schema = vol.Schema(
|
||||
{
|
||||
vol.Optional(CONF_RADIUS): vol.All(
|
||||
int,
|
||||
vol.Range(min=5),
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="init",
|
||||
data_schema=self.add_suggested_values_to_schema(
|
||||
options_schema, self.config_entry.options
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class CannotConnect(exceptions.HomeAssistantError):
|
||||
"""Error to indicate we cannot connect."""
|
||||
|
||||
@@ -1,99 +0,0 @@
|
||||
"""DataUpdateCoordinator for the AirNow integration."""
|
||||
import logging
|
||||
|
||||
from aiohttp.client_exceptions import ClientConnectorError
|
||||
from pyairnow import WebServiceAPI
|
||||
from pyairnow.conv import aqi_to_concentration
|
||||
from pyairnow.errors import AirNowError
|
||||
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
from .const import (
|
||||
ATTR_API_AQI,
|
||||
ATTR_API_AQI_DESCRIPTION,
|
||||
ATTR_API_AQI_LEVEL,
|
||||
ATTR_API_AQI_PARAM,
|
||||
ATTR_API_CAT_DESCRIPTION,
|
||||
ATTR_API_CAT_LEVEL,
|
||||
ATTR_API_CATEGORY,
|
||||
ATTR_API_PM25,
|
||||
ATTR_API_POLLUTANT,
|
||||
ATTR_API_REPORT_DATE,
|
||||
ATTR_API_REPORT_HOUR,
|
||||
ATTR_API_STATE,
|
||||
ATTR_API_STATION,
|
||||
ATTR_API_STATION_LATITUDE,
|
||||
ATTR_API_STATION_LONGITUDE,
|
||||
DOMAIN,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AirNowDataUpdateCoordinator(DataUpdateCoordinator):
|
||||
"""The AirNow update coordinator."""
|
||||
|
||||
def __init__(
|
||||
self, hass, session, api_key, latitude, longitude, distance, update_interval
|
||||
):
|
||||
"""Initialize."""
|
||||
self.latitude = latitude
|
||||
self.longitude = longitude
|
||||
self.distance = distance
|
||||
|
||||
self.airnow = WebServiceAPI(api_key, session=session)
|
||||
|
||||
super().__init__(hass, _LOGGER, name=DOMAIN, update_interval=update_interval)
|
||||
|
||||
async def _async_update_data(self):
|
||||
"""Update data via library."""
|
||||
data = {}
|
||||
try:
|
||||
obs = await self.airnow.observations.latLong(
|
||||
self.latitude,
|
||||
self.longitude,
|
||||
distance=self.distance,
|
||||
)
|
||||
|
||||
except (AirNowError, ClientConnectorError) as error:
|
||||
raise UpdateFailed(error) from error
|
||||
|
||||
if not obs:
|
||||
raise UpdateFailed("No data was returned from AirNow")
|
||||
|
||||
max_aqi = 0
|
||||
max_aqi_level = 0
|
||||
max_aqi_desc = ""
|
||||
max_aqi_poll = ""
|
||||
for obv in obs:
|
||||
# Convert AQIs to Concentration
|
||||
pollutant = obv[ATTR_API_AQI_PARAM]
|
||||
concentration = aqi_to_concentration(obv[ATTR_API_AQI], pollutant)
|
||||
data[obv[ATTR_API_AQI_PARAM]] = concentration
|
||||
|
||||
# Overall AQI is the max of all pollutant AQIs
|
||||
if obv[ATTR_API_AQI] > max_aqi:
|
||||
max_aqi = obv[ATTR_API_AQI]
|
||||
max_aqi_level = obv[ATTR_API_CATEGORY][ATTR_API_CAT_LEVEL]
|
||||
max_aqi_desc = obv[ATTR_API_CATEGORY][ATTR_API_CAT_DESCRIPTION]
|
||||
max_aqi_poll = pollutant
|
||||
|
||||
# Copy other data from PM2.5 Value
|
||||
if obv[ATTR_API_AQI_PARAM] == ATTR_API_PM25:
|
||||
# Copy Report Details
|
||||
data[ATTR_API_REPORT_DATE] = obv[ATTR_API_REPORT_DATE]
|
||||
data[ATTR_API_REPORT_HOUR] = obv[ATTR_API_REPORT_HOUR]
|
||||
|
||||
# Copy Station Details
|
||||
data[ATTR_API_STATE] = obv[ATTR_API_STATE]
|
||||
data[ATTR_API_STATION] = obv[ATTR_API_STATION]
|
||||
data[ATTR_API_STATION_LATITUDE] = obv[ATTR_API_STATION_LATITUDE]
|
||||
data[ATTR_API_STATION_LONGITUDE] = obv[ATTR_API_STATION_LONGITUDE]
|
||||
|
||||
# Store Overall AQI
|
||||
data[ATTR_API_AQI] = max_aqi
|
||||
data[ATTR_API_AQI_LEVEL] = max_aqi_level
|
||||
data[ATTR_API_AQI_DESCRIPTION] = max_aqi_desc
|
||||
data[ATTR_API_POLLUTANT] = max_aqi_poll
|
||||
|
||||
return data
|
||||
@@ -17,7 +17,8 @@ from homeassistant.const import (
|
||||
CONCENTRATION_PARTS_PER_MILLION,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
|
||||
from homeassistant.helpers.device_registry import DeviceEntryType
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.typing import StateType
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
@@ -29,9 +30,6 @@ from .const import (
|
||||
ATTR_API_AQI_LEVEL,
|
||||
ATTR_API_O3,
|
||||
ATTR_API_PM25,
|
||||
ATTR_API_STATION,
|
||||
ATTR_API_STATION_LATITUDE,
|
||||
ATTR_API_STATION_LONGITUDE,
|
||||
DEFAULT_NAME,
|
||||
DOMAIN,
|
||||
)
|
||||
@@ -42,7 +40,6 @@ PARALLEL_UPDATES = 1
|
||||
|
||||
ATTR_DESCR = "description"
|
||||
ATTR_LEVEL = "level"
|
||||
ATTR_STATION = "reporting_station"
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -58,16 +55,6 @@ class AirNowEntityDescription(SensorEntityDescription, AirNowEntityDescriptionMi
|
||||
"""Describes Airnow sensor entity."""
|
||||
|
||||
|
||||
def station_extra_attrs(data: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Process extra attributes for station location (if available)."""
|
||||
if ATTR_API_STATION in data:
|
||||
return {
|
||||
"lat": data.get(ATTR_API_STATION_LATITUDE),
|
||||
"long": data.get(ATTR_API_STATION_LONGITUDE),
|
||||
}
|
||||
return {}
|
||||
|
||||
|
||||
SENSOR_TYPES: tuple[AirNowEntityDescription, ...] = (
|
||||
AirNowEntityDescription(
|
||||
key=ATTR_API_AQI,
|
||||
@@ -98,13 +85,6 @@ SENSOR_TYPES: tuple[AirNowEntityDescription, ...] = (
|
||||
value_fn=lambda data: data.get(ATTR_API_O3),
|
||||
extra_state_attributes_fn=None,
|
||||
),
|
||||
AirNowEntityDescription(
|
||||
key=ATTR_API_STATION,
|
||||
translation_key="station",
|
||||
icon="mdi:blur",
|
||||
value_fn=lambda data: data.get(ATTR_API_STATION),
|
||||
extra_state_attributes_fn=station_extra_attrs,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -14,33 +14,17 @@
|
||||
"error": {
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
|
||||
"invalid_location": "No results found for that location, try changing the location or station radius.",
|
||||
"invalid_location": "No results found for that location",
|
||||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||
},
|
||||
"abort": {
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"step": {
|
||||
"init": {
|
||||
"data": {
|
||||
"radius": "Station Radius (miles)"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"entity": {
|
||||
"sensor": {
|
||||
"o3": {
|
||||
"name": "[%key:component::sensor::entity_component::ozone::name%]"
|
||||
},
|
||||
"station": {
|
||||
"name": "PM2.5 reporting station",
|
||||
"state_attributes": {
|
||||
"lat": { "name": "[%key:common::config_flow::data::latitude%]" },
|
||||
"long": { "name": "[%key:common::config_flow::data::longitude%]" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_IP_ADDRESS, CONF_PASSWORD
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||
|
||||
from .const import DOMAIN, MANUFACTURER, TARGET_ROUTE, UPDATE_INTERVAL
|
||||
|
||||
@@ -21,7 +21,7 @@ from homeassistant.const import (
|
||||
UnitOfTemperature,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.typing import StateType
|
||||
from homeassistant.helpers.update_coordinator import (
|
||||
|
||||
@@ -34,12 +34,8 @@ class Discovery:
|
||||
|
||||
|
||||
def get_name(device: AirthingsDevice) -> str:
|
||||
"""Generate name with model and identifier for device."""
|
||||
|
||||
name = device.friendly_name()
|
||||
if identifier := device.identifier:
|
||||
name += f" ({identifier})"
|
||||
return name
|
||||
"""Generate name with identifier for device."""
|
||||
return f"{device.name} ({device.identifier})"
|
||||
|
||||
|
||||
class AirthingsDeviceUpdateError(Exception):
|
||||
@@ -160,7 +156,7 @@ class AirthingsConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
return self.async_abort(reason="no_devices_found")
|
||||
|
||||
titles = {
|
||||
address: discovery.device.name
|
||||
address: get_name(discovery.device)
|
||||
for (address, discovery) in self._discovered_devices.items()
|
||||
}
|
||||
return self.async_show_form(
|
||||
|
||||
@@ -3,26 +3,13 @@
|
||||
"name": "Airthings BLE",
|
||||
"bluetooth": [
|
||||
{
|
||||
"manufacturer_id": 820,
|
||||
"service_uuid": "b42e1f6e-ade7-11e4-89d3-123b93f75cba"
|
||||
},
|
||||
{
|
||||
"manufacturer_id": 820,
|
||||
"service_uuid": "b42e4a8e-ade7-11e4-89d3-123b93f75cba"
|
||||
},
|
||||
{
|
||||
"manufacturer_id": 820,
|
||||
"service_uuid": "b42e1c08-ade7-11e4-89d3-123b93f75cba"
|
||||
},
|
||||
{
|
||||
"manufacturer_id": 820,
|
||||
"service_uuid": "b42e3882-ade7-11e4-89d3-123b93f75cba"
|
||||
"manufacturer_id": 820
|
||||
}
|
||||
],
|
||||
"codeowners": ["@vincegio", "@LaStrada"],
|
||||
"codeowners": ["@vincegio"],
|
||||
"config_flow": true,
|
||||
"dependencies": ["bluetooth_adapters"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/airthings_ble",
|
||||
"iot_class": "local_polling",
|
||||
"requirements": ["airthings-ble==0.5.6-2"]
|
||||
"requirements": ["airthings-ble==0.5.3"]
|
||||
}
|
||||
|
||||
@@ -5,35 +5,26 @@ import logging
|
||||
|
||||
from airthings_ble import AirthingsDevice
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components.sensor import (
|
||||
SensorDeviceClass,
|
||||
SensorEntity,
|
||||
SensorEntityDescription,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
CONCENTRATION_PARTS_PER_BILLION,
|
||||
CONCENTRATION_PARTS_PER_MILLION,
|
||||
LIGHT_LUX,
|
||||
PERCENTAGE,
|
||||
EntityCategory,
|
||||
Platform,
|
||||
UnitOfPressure,
|
||||
UnitOfTemperature,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.device_registry import (
|
||||
CONNECTION_BLUETOOTH,
|
||||
DeviceInfo,
|
||||
async_get as device_async_get,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.device_registry import CONNECTION_BLUETOOTH
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.entity_registry import (
|
||||
RegistryEntry,
|
||||
async_entries_for_device,
|
||||
async_get as entity_async_get,
|
||||
)
|
||||
from homeassistant.helpers.typing import StateType
|
||||
from homeassistant.helpers.update_coordinator import (
|
||||
CoordinatorEntity,
|
||||
@@ -117,44 +108,9 @@ SENSORS_MAPPING_TEMPLATE: dict[str, SensorEntityDescription] = {
|
||||
}
|
||||
|
||||
|
||||
@callback
|
||||
def async_migrate(hass: HomeAssistant, address: str, sensor_name: str) -> None:
|
||||
"""Migrate entities to new unique ids (with BLE Address)."""
|
||||
ent_reg = entity_async_get(hass)
|
||||
unique_id_trailer = f"_{sensor_name}"
|
||||
new_unique_id = f"{address}{unique_id_trailer}"
|
||||
if ent_reg.async_get_entity_id(DOMAIN, Platform.SENSOR, new_unique_id):
|
||||
# New unique id already exists
|
||||
return
|
||||
dev_reg = device_async_get(hass)
|
||||
if not (
|
||||
device := dev_reg.async_get_device(
|
||||
connections={(CONNECTION_BLUETOOTH, address)}
|
||||
)
|
||||
):
|
||||
return
|
||||
entities = async_entries_for_device(
|
||||
ent_reg,
|
||||
device_id=device.id,
|
||||
include_disabled_entities=True,
|
||||
)
|
||||
matching_reg_entry: RegistryEntry | None = None
|
||||
for entry in entities:
|
||||
if entry.unique_id.endswith(unique_id_trailer) and (
|
||||
not matching_reg_entry or "(" not in entry.unique_id
|
||||
):
|
||||
matching_reg_entry = entry
|
||||
if not matching_reg_entry or matching_reg_entry.unique_id == new_unique_id:
|
||||
# Already has the newest unique id format
|
||||
return
|
||||
entity_id = matching_reg_entry.entity_id
|
||||
ent_reg.async_update_entity(entity_id=entity_id, new_unique_id=new_unique_id)
|
||||
_LOGGER.debug("Migrated entity '%s' to unique id '%s'", entity_id, new_unique_id)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: ConfigEntry,
|
||||
entry: config_entries.ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the Airthings BLE sensors."""
|
||||
@@ -182,7 +138,6 @@ async def async_setup_entry(
|
||||
sensor_value,
|
||||
)
|
||||
continue
|
||||
async_migrate(hass, coordinator.data.address, sensor_type)
|
||||
entities.append(
|
||||
AirthingsSensor(coordinator, coordinator.data, sensors_mapping[sensor_type])
|
||||
)
|
||||
@@ -207,11 +162,11 @@ class AirthingsSensor(
|
||||
super().__init__(coordinator)
|
||||
self.entity_description = entity_description
|
||||
|
||||
name = airthings_device.name
|
||||
if identifier := airthings_device.identifier:
|
||||
name += f" ({identifier})"
|
||||
name = f"{airthings_device.name} {airthings_device.identifier}"
|
||||
|
||||
self._attr_unique_id = f"{airthings_device.address}_{entity_description.key}"
|
||||
self._attr_unique_id = f"{name}_{entity_description.key}"
|
||||
|
||||
self._id = airthings_device.address
|
||||
self._attr_device_info = DeviceInfo(
|
||||
connections={
|
||||
(
|
||||
@@ -220,10 +175,9 @@ class AirthingsSensor(
|
||||
)
|
||||
},
|
||||
name=name,
|
||||
manufacturer=airthings_device.manufacturer,
|
||||
manufacturer="Airthings",
|
||||
hw_version=airthings_device.hw_version,
|
||||
sw_version=airthings_device.sw_version,
|
||||
model=airthings_device.model,
|
||||
)
|
||||
|
||||
@property
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
"""The AirTouch4 integration."""
|
||||
from airtouch4pyapi import AirTouch
|
||||
import logging
|
||||
|
||||
from airtouch4pyapi import AirTouch
|
||||
from airtouch4pyapi.airtouch import AirTouchStatus
|
||||
|
||||
from homeassistant.components.climate import SCAN_INTERVAL
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_HOST, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import AirtouchDataUpdateCoordinator
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
PLATFORMS = [Platform.CLIMATE]
|
||||
|
||||
@@ -38,3 +44,38 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
hass.data[DOMAIN].pop(entry.entry_id)
|
||||
|
||||
return unload_ok
|
||||
|
||||
|
||||
class AirtouchDataUpdateCoordinator(DataUpdateCoordinator):
|
||||
"""Class to manage fetching Airtouch data."""
|
||||
|
||||
def __init__(self, hass, airtouch):
|
||||
"""Initialize global Airtouch data updater."""
|
||||
self.airtouch = airtouch
|
||||
|
||||
super().__init__(
|
||||
hass,
|
||||
_LOGGER,
|
||||
name=DOMAIN,
|
||||
update_interval=SCAN_INTERVAL,
|
||||
)
|
||||
|
||||
async def _async_update_data(self):
|
||||
"""Fetch data from Airtouch."""
|
||||
await self.airtouch.UpdateInfo()
|
||||
if self.airtouch.Status != AirTouchStatus.OK:
|
||||
raise UpdateFailed("Airtouch connection issue")
|
||||
return {
|
||||
"acs": [
|
||||
{"ac_number": ac.AcNumber, "is_on": ac.IsOn}
|
||||
for ac in self.airtouch.GetAcs()
|
||||
],
|
||||
"groups": [
|
||||
{
|
||||
"group_number": group.GroupNumber,
|
||||
"group_name": group.GroupName,
|
||||
"is_on": group.IsOn,
|
||||
}
|
||||
for group in self.airtouch.GetGroups()
|
||||
],
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ from homeassistant.components.climate import (
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import ATTR_TEMPERATURE, UnitOfTemperature
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
@@ -84,9 +84,6 @@ async def async_setup_entry(
|
||||
class AirtouchAC(CoordinatorEntity, ClimateEntity):
|
||||
"""Representation of an AirTouch 4 ac."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
_attr_name = None
|
||||
|
||||
_attr_supported_features = (
|
||||
ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.FAN_MODE
|
||||
)
|
||||
@@ -98,25 +95,38 @@ class AirtouchAC(CoordinatorEntity, ClimateEntity):
|
||||
self._ac_number = ac_number
|
||||
self._airtouch = coordinator.airtouch
|
||||
self._info = info
|
||||
self._unit = self._airtouch.GetAcs()[ac_number]
|
||||
self._attr_unique_id = f"ac_{ac_number}"
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, f"ac_{ac_number}")},
|
||||
name=f"AC {ac_number}",
|
||||
manufacturer="Airtouch",
|
||||
model="Airtouch 4",
|
||||
)
|
||||
self._unit = self._airtouch.GetAcs()[self._ac_number]
|
||||
|
||||
@callback
|
||||
def _handle_coordinator_update(self):
|
||||
self._unit = self._airtouch.GetAcs()[self._ac_number]
|
||||
return super()._handle_coordinator_update()
|
||||
|
||||
@property
|
||||
def device_info(self) -> DeviceInfo:
|
||||
"""Return device info for this device."""
|
||||
return DeviceInfo(
|
||||
identifiers={(DOMAIN, self.unique_id)},
|
||||
name=self.name,
|
||||
manufacturer="Airtouch",
|
||||
model="Airtouch 4",
|
||||
)
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return unique ID for this device."""
|
||||
return f"ac_{self._ac_number}"
|
||||
|
||||
@property
|
||||
def current_temperature(self):
|
||||
"""Return the current temperature."""
|
||||
return self._unit.Temperature
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the climate device."""
|
||||
return f"AC {self._ac_number}"
|
||||
|
||||
@property
|
||||
def fan_mode(self):
|
||||
"""Return fan mode of the AC this group belongs to."""
|
||||
@@ -190,8 +200,6 @@ class AirtouchAC(CoordinatorEntity, ClimateEntity):
|
||||
class AirtouchGroup(CoordinatorEntity, ClimateEntity):
|
||||
"""Representation of an AirTouch 4 group."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
_attr_name = None
|
||||
_attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE
|
||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||
_attr_hvac_modes = AT_GROUP_MODES
|
||||
@@ -200,22 +208,30 @@ class AirtouchGroup(CoordinatorEntity, ClimateEntity):
|
||||
"""Initialize the climate device."""
|
||||
super().__init__(coordinator)
|
||||
self._group_number = group_number
|
||||
self._attr_unique_id = group_number
|
||||
self._airtouch = coordinator.airtouch
|
||||
self._info = info
|
||||
self._unit = self._airtouch.GetGroupByGroupNumber(group_number)
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, group_number)},
|
||||
manufacturer="Airtouch",
|
||||
model="Airtouch 4",
|
||||
name=self._unit.GroupName,
|
||||
)
|
||||
self._unit = self._airtouch.GetGroupByGroupNumber(self._group_number)
|
||||
|
||||
@callback
|
||||
def _handle_coordinator_update(self):
|
||||
self._unit = self._airtouch.GetGroupByGroupNumber(self._group_number)
|
||||
return super()._handle_coordinator_update()
|
||||
|
||||
@property
|
||||
def device_info(self) -> DeviceInfo:
|
||||
"""Return device info for this device."""
|
||||
return DeviceInfo(
|
||||
identifiers={(DOMAIN, self.unique_id)},
|
||||
manufacturer="Airtouch",
|
||||
model="Airtouch 4",
|
||||
name=self.name,
|
||||
)
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return unique ID for this device."""
|
||||
return self._group_number
|
||||
|
||||
@property
|
||||
def min_temp(self):
|
||||
"""Return Minimum Temperature for AC of this group."""
|
||||
@@ -226,6 +242,11 @@ class AirtouchGroup(CoordinatorEntity, ClimateEntity):
|
||||
"""Return Max Temperature for AC of this group."""
|
||||
return self._airtouch.acs[self._unit.BelongsToAc].MaxSetpoint
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the climate device."""
|
||||
return self._unit.GroupName
|
||||
|
||||
@property
|
||||
def current_temperature(self):
|
||||
"""Return the current temperature."""
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
"""DataUpdateCoordinator for the airtouch integration."""
|
||||
import logging
|
||||
|
||||
from airtouch4pyapi.airtouch import AirTouchStatus
|
||||
|
||||
from homeassistant.components.climate import SCAN_INTERVAL
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AirtouchDataUpdateCoordinator(DataUpdateCoordinator):
|
||||
"""Class to manage fetching Airtouch data."""
|
||||
|
||||
def __init__(self, hass, airtouch):
|
||||
"""Initialize global Airtouch data updater."""
|
||||
self.airtouch = airtouch
|
||||
|
||||
super().__init__(
|
||||
hass,
|
||||
_LOGGER,
|
||||
name=DOMAIN,
|
||||
update_interval=SCAN_INTERVAL,
|
||||
)
|
||||
|
||||
async def _async_update_data(self):
|
||||
"""Fetch data from Airtouch."""
|
||||
await self.airtouch.UpdateInfo()
|
||||
if self.airtouch.Status != AirTouchStatus.OK:
|
||||
raise UpdateFailed("Airtouch connection issue")
|
||||
return {
|
||||
"acs": [
|
||||
{"ac_number": ac.AcNumber, "is_on": ac.IsOn}
|
||||
for ac in self.airtouch.GetAcs()
|
||||
],
|
||||
"groups": [
|
||||
{
|
||||
"group_number": group.GroupNumber,
|
||||
"group_name": group.GroupName,
|
||||
"is_on": group.IsOn,
|
||||
}
|
||||
for group in self.airtouch.GetGroups()
|
||||
],
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"domain": "airtouch4",
|
||||
"name": "AirTouch 4",
|
||||
"codeowners": ["@samsinnamon"],
|
||||
"codeowners": ["@LonePurpleWolf"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/airtouch4",
|
||||
"iot_class": "local_polling",
|
||||
|
||||
@@ -421,10 +421,8 @@ class AirVisualEntity(CoordinatorEntity):
|
||||
self._entry = entry
|
||||
self.entity_description = description
|
||||
|
||||
# pylint: disable-next=hass-missing-super-call
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Register callbacks."""
|
||||
await super().async_added_to_hass()
|
||||
|
||||
@callback
|
||||
def update() -> None:
|
||||
|
||||
@@ -109,21 +109,19 @@ class AirVisualFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
"airvisual_checked_api_keys_lock", asyncio.Lock()
|
||||
)
|
||||
|
||||
if integration_type == INTEGRATION_TYPE_GEOGRAPHY_COORDS:
|
||||
coro = cloud_api.air_quality.nearest_city()
|
||||
error_schema = self.geography_coords_schema
|
||||
error_step = "geography_by_coords"
|
||||
else:
|
||||
coro = cloud_api.air_quality.city(
|
||||
user_input[CONF_CITY], user_input[CONF_STATE], user_input[CONF_COUNTRY]
|
||||
)
|
||||
error_schema = GEOGRAPHY_NAME_SCHEMA
|
||||
error_step = "geography_by_name"
|
||||
|
||||
async with valid_keys_lock:
|
||||
if user_input[CONF_API_KEY] not in valid_keys:
|
||||
if integration_type == INTEGRATION_TYPE_GEOGRAPHY_COORDS:
|
||||
coro = cloud_api.air_quality.nearest_city()
|
||||
error_schema = self.geography_coords_schema
|
||||
error_step = "geography_by_coords"
|
||||
else:
|
||||
coro = cloud_api.air_quality.city(
|
||||
user_input[CONF_CITY],
|
||||
user_input[CONF_STATE],
|
||||
user_input[CONF_COUNTRY],
|
||||
)
|
||||
error_schema = GEOGRAPHY_NAME_SCHEMA
|
||||
error_step = "geography_by_name"
|
||||
|
||||
try:
|
||||
await coro
|
||||
except (InvalidKeyError, KeyExpiredError, UnauthorizedError):
|
||||
|
||||
@@ -8,5 +8,5 @@
|
||||
"integration_type": "service",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["pyairvisual", "pysmb"],
|
||||
"requirements": ["pyairvisual==2023.08.1"]
|
||||
"requirements": ["pyairvisual==2022.12.1"]
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
}
|
||||
},
|
||||
"geography_by_name": {
|
||||
"title": "[%key:component::airvisual::config::step::geography_by_coords::title%]",
|
||||
"title": "Configure a Geography",
|
||||
"description": "Use the AirVisual cloud API to monitor a city/state/country.",
|
||||
"data": {
|
||||
"api_key": "[%key:common::config_flow::data::api_key%]",
|
||||
@@ -45,7 +45,7 @@
|
||||
"options": {
|
||||
"step": {
|
||||
"init": {
|
||||
"title": "[%key:component::airvisual::config::step::user::title%]",
|
||||
"title": "Configure AirVisual",
|
||||
"data": {
|
||||
"show_on_map": "Show monitored geography on the map"
|
||||
}
|
||||
|
||||
@@ -23,8 +23,7 @@ from homeassistant.const import (
|
||||
)
|
||||
from homeassistant.core import Event, HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.entity import EntityDescription
|
||||
from homeassistant.helpers.entity import DeviceInfo, EntityDescription
|
||||
from homeassistant.helpers.update_coordinator import (
|
||||
CoordinatorEntity,
|
||||
DataUpdateCoordinator,
|
||||
@@ -61,10 +60,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Get data from the device."""
|
||||
try:
|
||||
data = await node.async_get_latest_measurements()
|
||||
data["history"] = {}
|
||||
if data["settings"].get("follow_mode") == "device":
|
||||
history = await node.async_get_history(include_trends=False)
|
||||
data["history"] = history.get("measurements", [])[-1]
|
||||
except InvalidAuthenticationError as err:
|
||||
raise ConfigEntryAuthFailed("Invalid Samba password") from err
|
||||
except NodeConnectionError as err:
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["pyairvisual", "pysmb"],
|
||||
"requirements": ["pyairvisual==2023.08.1"]
|
||||
"requirements": ["pyairvisual==2022.12.1"]
|
||||
}
|
||||
|
||||
@@ -30,9 +30,7 @@ from .const import DOMAIN
|
||||
class AirVisualProMeasurementKeyMixin:
|
||||
"""Define an entity description mixin to include a measurement key."""
|
||||
|
||||
value_fn: Callable[
|
||||
[dict[str, Any], dict[str, Any], dict[str, Any], dict[str, Any]], float | int
|
||||
]
|
||||
value_fn: Callable[[dict[str, Any], dict[str, Any], dict[str, Any]], float | int]
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -45,81 +43,75 @@ class AirVisualProMeasurementDescription(
|
||||
SENSOR_DESCRIPTIONS = (
|
||||
AirVisualProMeasurementDescription(
|
||||
key="air_quality_index",
|
||||
name="Air quality index",
|
||||
device_class=SensorDeviceClass.AQI,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value_fn=lambda settings, status, measurements, history: measurements[
|
||||
value_fn=lambda settings, status, measurements: measurements[
|
||||
async_get_aqi_locale(settings)
|
||||
],
|
||||
),
|
||||
AirVisualProMeasurementDescription(
|
||||
key="outdoor_air_quality_index",
|
||||
device_class=SensorDeviceClass.AQI,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value_fn=lambda settings, status, measurements, history: int(
|
||||
history.get(
|
||||
f'Outdoor {"AQI(US)" if settings["is_aqi_usa"] else "AQI(CN)"}', -1
|
||||
)
|
||||
),
|
||||
translation_key="outdoor_air_quality_index",
|
||||
),
|
||||
AirVisualProMeasurementDescription(
|
||||
key="battery_level",
|
||||
name="Battery",
|
||||
device_class=SensorDeviceClass.BATTERY,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
value_fn=lambda settings, status, measurements, history: status["battery"],
|
||||
value_fn=lambda settings, status, measurements: status["battery"],
|
||||
),
|
||||
AirVisualProMeasurementDescription(
|
||||
key="carbon_dioxide",
|
||||
name="C02",
|
||||
device_class=SensorDeviceClass.CO2,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value_fn=lambda settings, status, measurements, history: measurements["co2"],
|
||||
value_fn=lambda settings, status, measurements: measurements["co2"],
|
||||
),
|
||||
AirVisualProMeasurementDescription(
|
||||
key="humidity",
|
||||
name="Humidity",
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
value_fn=lambda settings, status, measurements, history: measurements[
|
||||
"humidity"
|
||||
],
|
||||
value_fn=lambda settings, status, measurements: measurements["humidity"],
|
||||
),
|
||||
AirVisualProMeasurementDescription(
|
||||
key="particulate_matter_0_1",
|
||||
translation_key="pm01",
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value_fn=lambda settings, status, measurements, history: measurements["pm0_1"],
|
||||
),
|
||||
AirVisualProMeasurementDescription(
|
||||
key="particulate_matter_1_0",
|
||||
name="PM 0.1",
|
||||
device_class=SensorDeviceClass.PM1,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value_fn=lambda settings, status, measurements, history: measurements["pm1_0"],
|
||||
value_fn=lambda settings, status, measurements: measurements["pm0_1"],
|
||||
),
|
||||
AirVisualProMeasurementDescription(
|
||||
key="particulate_matter_1_0",
|
||||
name="PM 1.0",
|
||||
device_class=SensorDeviceClass.PM10,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value_fn=lambda settings, status, measurements: measurements["pm1_0"],
|
||||
),
|
||||
AirVisualProMeasurementDescription(
|
||||
key="particulate_matter_2_5",
|
||||
name="PM 2.5",
|
||||
device_class=SensorDeviceClass.PM25,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value_fn=lambda settings, status, measurements, history: measurements["pm2_5"],
|
||||
value_fn=lambda settings, status, measurements: measurements["pm2_5"],
|
||||
),
|
||||
AirVisualProMeasurementDescription(
|
||||
key="temperature",
|
||||
name="Temperature",
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value_fn=lambda settings, status, measurements, history: measurements[
|
||||
"temperature_C"
|
||||
],
|
||||
value_fn=lambda settings, status, measurements: measurements["temperature_C"],
|
||||
),
|
||||
AirVisualProMeasurementDescription(
|
||||
key="voc",
|
||||
name="VOC",
|
||||
device_class=SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value_fn=lambda settings, status, measurements, history: measurements["voc"],
|
||||
value_fn=lambda settings, status, measurements: measurements["voc"],
|
||||
),
|
||||
)
|
||||
|
||||
@@ -158,5 +150,4 @@ class AirVisualProSensor(AirVisualProEntity, SensorEntity):
|
||||
self.coordinator.data["settings"],
|
||||
self.coordinator.data["status"],
|
||||
self.coordinator.data["measurements"],
|
||||
self.coordinator.data["history"],
|
||||
)
|
||||
|
||||
@@ -24,15 +24,5 @@
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
||||
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
|
||||
}
|
||||
},
|
||||
"entity": {
|
||||
"sensor": {
|
||||
"pm01": {
|
||||
"name": "PM0.1"
|
||||
},
|
||||
"outdoor_air_quality_index": {
|
||||
"name": "Outdoor air quality index"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ PLATFORMS: list[Platform] = [
|
||||
Platform.CLIMATE,
|
||||
Platform.SELECT,
|
||||
Platform.SENSOR,
|
||||
Platform.WATER_HEATER,
|
||||
]
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -217,8 +217,8 @@ class AirzoneClimate(AirzoneZoneEntity, ClimateEntity):
|
||||
if ATTR_TEMPERATURE in kwargs:
|
||||
params[API_SET_POINT] = kwargs[ATTR_TEMPERATURE]
|
||||
if ATTR_TARGET_TEMP_LOW in kwargs and ATTR_TARGET_TEMP_HIGH in kwargs:
|
||||
params[API_COOL_SET_POINT] = kwargs[ATTR_TARGET_TEMP_HIGH]
|
||||
params[API_HEAT_SET_POINT] = kwargs[ATTR_TARGET_TEMP_LOW]
|
||||
params[API_COOL_SET_POINT] = kwargs[ATTR_TARGET_TEMP_LOW]
|
||||
params[API_HEAT_SET_POINT] = kwargs[ATTR_TARGET_TEMP_HIGH]
|
||||
await self._async_update_hvac_params(params)
|
||||
|
||||
@callback
|
||||
@@ -248,8 +248,8 @@ class AirzoneClimate(AirzoneZoneEntity, ClimateEntity):
|
||||
self._attr_fan_mode = self._speeds.get(self.get_airzone_value(AZD_SPEED))
|
||||
if self.supported_features & ClimateEntityFeature.TARGET_TEMPERATURE_RANGE:
|
||||
self._attr_target_temperature_high = self.get_airzone_value(
|
||||
AZD_COOL_TEMP_SET
|
||||
)
|
||||
self._attr_target_temperature_low = self.get_airzone_value(
|
||||
AZD_HEAT_TEMP_SET
|
||||
)
|
||||
self._attr_target_temperature_low = self.get_airzone_value(
|
||||
AZD_COOL_TEMP_SET
|
||||
)
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
"""The Airzone integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
from asyncio import timeout
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from aioairzone.exceptions import AirzoneError
|
||||
from aioairzone.localapi import AirzoneLocalApi
|
||||
import async_timeout
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
@@ -35,7 +35,7 @@ class AirzoneUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
|
||||
async def _async_update_data(self) -> dict[str, Any]:
|
||||
"""Update data via library."""
|
||||
async with timeout(AIOAIRZONE_DEVICE_TIMEOUT_SEC):
|
||||
async with async_timeout.timeout(AIOAIRZONE_DEVICE_TIMEOUT_SEC):
|
||||
try:
|
||||
await self.airzone.update()
|
||||
except AirzoneError as error:
|
||||
|
||||
@@ -10,7 +10,6 @@ from aioairzone.const import (
|
||||
AZD_AVAILABLE,
|
||||
AZD_FIRMWARE,
|
||||
AZD_FULL_NAME,
|
||||
AZD_HOT_WATER,
|
||||
AZD_ID,
|
||||
AZD_MAC,
|
||||
AZD_MODEL,
|
||||
@@ -27,7 +26,7 @@ from aioairzone.exceptions import AirzoneError
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from .const import DOMAIN, MANUFACTURER
|
||||
@@ -82,47 +81,6 @@ class AirzoneSystemEntity(AirzoneEntity):
|
||||
return value
|
||||
|
||||
|
||||
class AirzoneHotWaterEntity(AirzoneEntity):
|
||||
"""Define an Airzone Hot Water entity."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: AirzoneUpdateCoordinator,
|
||||
entry: ConfigEntry,
|
||||
) -> None:
|
||||
"""Initialize."""
|
||||
super().__init__(coordinator)
|
||||
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, f"{entry.entry_id}_dhw")},
|
||||
manufacturer=MANUFACTURER,
|
||||
model="DHW",
|
||||
name=self.get_airzone_value(AZD_NAME),
|
||||
via_device=(DOMAIN, f"{entry.entry_id}_ws"),
|
||||
)
|
||||
self._attr_unique_id = entry.unique_id or entry.entry_id
|
||||
|
||||
def get_airzone_value(self, key: str) -> Any:
|
||||
"""Return DHW value by key."""
|
||||
return self.coordinator.data[AZD_HOT_WATER].get(key)
|
||||
|
||||
async def _async_update_dhw_params(self, params: dict[str, Any]) -> None:
|
||||
"""Send DHW parameters to API."""
|
||||
_params = {
|
||||
API_SYSTEM_ID: 0,
|
||||
**params,
|
||||
}
|
||||
_LOGGER.debug("update_dhw_params=%s", _params)
|
||||
try:
|
||||
await self.coordinator.airzone.set_dhw_parameters(_params)
|
||||
except AirzoneError as error:
|
||||
raise HomeAssistantError(
|
||||
f"Failed to set dhw {self.name}: {error}"
|
||||
) from error
|
||||
|
||||
self.coordinator.async_set_updated_data(self.coordinator.airzone.data())
|
||||
|
||||
|
||||
class AirzoneWebServerEntity(AirzoneEntity):
|
||||
"""Define an Airzone WebServer entity."""
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user