mirror of
https://github.com/home-assistant/core.git
synced 2025-07-28 15:47:12 +00:00
Merge pull request #35828 from home-assistant/rc
This commit is contained in:
commit
70b14518d0
80
.coveragerc
80
.coveragerc
@ -16,11 +16,16 @@ omit =
|
|||||||
homeassistant/components/adguard/switch.py
|
homeassistant/components/adguard/switch.py
|
||||||
homeassistant/components/ads/*
|
homeassistant/components/ads/*
|
||||||
homeassistant/components/aftership/sensor.py
|
homeassistant/components/aftership/sensor.py
|
||||||
|
homeassistant/components/agent_dvr/__init__.py
|
||||||
|
homeassistant/components/agent_dvr/camera.py
|
||||||
|
homeassistant/components/agent_dvr/const.py
|
||||||
|
homeassistant/components/agent_dvr/helpers.py
|
||||||
homeassistant/components/airly/__init__.py
|
homeassistant/components/airly/__init__.py
|
||||||
homeassistant/components/airly/air_quality.py
|
homeassistant/components/airly/air_quality.py
|
||||||
homeassistant/components/airly/sensor.py
|
homeassistant/components/airly/sensor.py
|
||||||
homeassistant/components/airly/const.py
|
homeassistant/components/airly/const.py
|
||||||
homeassistant/components/airvisual/__init__.py
|
homeassistant/components/airvisual/__init__.py
|
||||||
|
homeassistant/components/airvisual/air_quality.py
|
||||||
homeassistant/components/airvisual/sensor.py
|
homeassistant/components/airvisual/sensor.py
|
||||||
homeassistant/components/aladdin_connect/cover.py
|
homeassistant/components/aladdin_connect/cover.py
|
||||||
homeassistant/components/alarmdecoder/*
|
homeassistant/components/alarmdecoder/*
|
||||||
@ -57,7 +62,7 @@ omit =
|
|||||||
homeassistant/components/aten_pe/*
|
homeassistant/components/aten_pe/*
|
||||||
homeassistant/components/atome/*
|
homeassistant/components/atome/*
|
||||||
homeassistant/components/aurora_abb_powerone/sensor.py
|
homeassistant/components/aurora_abb_powerone/sensor.py
|
||||||
homeassistant/components/automatic/device_tracker.py
|
homeassistant/components/automatic/*
|
||||||
homeassistant/components/avea/light.py
|
homeassistant/components/avea/light.py
|
||||||
homeassistant/components/avion/light.py
|
homeassistant/components/avion/light.py
|
||||||
homeassistant/components/avri/sensor.py
|
homeassistant/components/avri/sensor.py
|
||||||
@ -89,12 +94,16 @@ omit =
|
|||||||
homeassistant/components/braviatv/const.py
|
homeassistant/components/braviatv/const.py
|
||||||
homeassistant/components/braviatv/media_player.py
|
homeassistant/components/braviatv/media_player.py
|
||||||
homeassistant/components/broadlink/const.py
|
homeassistant/components/broadlink/const.py
|
||||||
|
homeassistant/components/broadlink/device.py
|
||||||
homeassistant/components/broadlink/remote.py
|
homeassistant/components/broadlink/remote.py
|
||||||
homeassistant/components/broadlink/sensor.py
|
homeassistant/components/broadlink/sensor.py
|
||||||
homeassistant/components/broadlink/switch.py
|
homeassistant/components/broadlink/switch.py
|
||||||
homeassistant/components/brottsplatskartan/sensor.py
|
homeassistant/components/brottsplatskartan/sensor.py
|
||||||
homeassistant/components/browser/*
|
homeassistant/components/browser/*
|
||||||
homeassistant/components/brunt/cover.py
|
homeassistant/components/brunt/cover.py
|
||||||
|
homeassistant/components/bsblan/__init__.py
|
||||||
|
homeassistant/components/bsblan/climate.py
|
||||||
|
homeassistant/components/bsblan/const.py
|
||||||
homeassistant/components/bt_home_hub_5/device_tracker.py
|
homeassistant/components/bt_home_hub_5/device_tracker.py
|
||||||
homeassistant/components/bt_smarthub/device_tracker.py
|
homeassistant/components/bt_smarthub/device_tracker.py
|
||||||
homeassistant/components/buienradar/sensor.py
|
homeassistant/components/buienradar/sensor.py
|
||||||
@ -142,6 +151,9 @@ omit =
|
|||||||
homeassistant/components/denon/media_player.py
|
homeassistant/components/denon/media_player.py
|
||||||
homeassistant/components/denonavr/media_player.py
|
homeassistant/components/denonavr/media_player.py
|
||||||
homeassistant/components/deutsche_bahn/sensor.py
|
homeassistant/components/deutsche_bahn/sensor.py
|
||||||
|
homeassistant/components/devolo_home_control/__init__.py
|
||||||
|
homeassistant/components/devolo_home_control/const.py
|
||||||
|
homeassistant/components/devolo_home_control/switch.py
|
||||||
homeassistant/components/dht/sensor.py
|
homeassistant/components/dht/sensor.py
|
||||||
homeassistant/components/digital_ocean/*
|
homeassistant/components/digital_ocean/*
|
||||||
homeassistant/components/digitalloggers/switch.py
|
homeassistant/components/digitalloggers/switch.py
|
||||||
@ -225,6 +237,9 @@ omit =
|
|||||||
homeassistant/components/fleetgo/device_tracker.py
|
homeassistant/components/fleetgo/device_tracker.py
|
||||||
homeassistant/components/flexit/climate.py
|
homeassistant/components/flexit/climate.py
|
||||||
homeassistant/components/flic/binary_sensor.py
|
homeassistant/components/flic/binary_sensor.py
|
||||||
|
homeassistant/components/flick_electric/__init__.py
|
||||||
|
homeassistant/components/flick_electric/const.py
|
||||||
|
homeassistant/components/flick_electric/sensor.py
|
||||||
homeassistant/components/flock/notify.py
|
homeassistant/components/flock/notify.py
|
||||||
homeassistant/components/flume/*
|
homeassistant/components/flume/*
|
||||||
homeassistant/components/flunearyou/__init__.py
|
homeassistant/components/flunearyou/__init__.py
|
||||||
@ -298,6 +313,7 @@ omit =
|
|||||||
homeassistant/components/hitron_coda/device_tracker.py
|
homeassistant/components/hitron_coda/device_tracker.py
|
||||||
homeassistant/components/hive/*
|
homeassistant/components/hive/*
|
||||||
homeassistant/components/hlk_sw16/*
|
homeassistant/components/hlk_sw16/*
|
||||||
|
homeassistant/components/home_connect/*
|
||||||
homeassistant/components/homematic/*
|
homeassistant/components/homematic/*
|
||||||
homeassistant/components/homematic/climate.py
|
homeassistant/components/homematic/climate.py
|
||||||
homeassistant/components/homematic/cover.py
|
homeassistant/components/homematic/cover.py
|
||||||
@ -310,7 +326,11 @@ omit =
|
|||||||
homeassistant/components/huawei_lte/*
|
homeassistant/components/huawei_lte/*
|
||||||
homeassistant/components/huawei_router/device_tracker.py
|
homeassistant/components/huawei_router/device_tracker.py
|
||||||
homeassistant/components/hue/light.py
|
homeassistant/components/hue/light.py
|
||||||
|
homeassistant/components/hunterdouglas_powerview/__init__.py
|
||||||
homeassistant/components/hunterdouglas_powerview/scene.py
|
homeassistant/components/hunterdouglas_powerview/scene.py
|
||||||
|
homeassistant/components/hunterdouglas_powerview/sensor.py
|
||||||
|
homeassistant/components/hunterdouglas_powerview/cover.py
|
||||||
|
homeassistant/components/hunterdouglas_powerview/entity.py
|
||||||
homeassistant/components/hydrawise/*
|
homeassistant/components/hydrawise/*
|
||||||
homeassistant/components/hyperion/light.py
|
homeassistant/components/hyperion/light.py
|
||||||
homeassistant/components/ialarm/alarm_control_panel.py
|
homeassistant/components/ialarm/alarm_control_panel.py
|
||||||
@ -343,11 +363,27 @@ omit =
|
|||||||
homeassistant/components/iqvia/*
|
homeassistant/components/iqvia/*
|
||||||
homeassistant/components/irish_rail_transport/sensor.py
|
homeassistant/components/irish_rail_transport/sensor.py
|
||||||
homeassistant/components/iss/binary_sensor.py
|
homeassistant/components/iss/binary_sensor.py
|
||||||
homeassistant/components/isy994/*
|
homeassistant/components/isy994/__init__.py
|
||||||
|
homeassistant/components/isy994/binary_sensor.py
|
||||||
|
homeassistant/components/isy994/climate.py
|
||||||
|
homeassistant/components/isy994/cover.py
|
||||||
|
homeassistant/components/isy994/entity.py
|
||||||
|
homeassistant/components/isy994/fan.py
|
||||||
|
homeassistant/components/isy994/helpers.py
|
||||||
|
homeassistant/components/isy994/light.py
|
||||||
|
homeassistant/components/isy994/lock.py
|
||||||
|
homeassistant/components/isy994/sensor.py
|
||||||
|
homeassistant/components/isy994/services.py
|
||||||
|
homeassistant/components/isy994/switch.py
|
||||||
homeassistant/components/itach/remote.py
|
homeassistant/components/itach/remote.py
|
||||||
homeassistant/components/itunes/media_player.py
|
homeassistant/components/itunes/media_player.py
|
||||||
homeassistant/components/joaoapps_join/*
|
homeassistant/components/joaoapps_join/*
|
||||||
homeassistant/components/juicenet/*
|
homeassistant/components/juicenet/__init__.py
|
||||||
|
homeassistant/components/juicenet/const.py
|
||||||
|
homeassistant/components/juicenet/device.py
|
||||||
|
homeassistant/components/juicenet/entity.py
|
||||||
|
homeassistant/components/juicenet/sensor.py
|
||||||
|
homeassistant/components/juicenet/switch.py
|
||||||
homeassistant/components/kaiterra/*
|
homeassistant/components/kaiterra/*
|
||||||
homeassistant/components/kankun/switch.py
|
homeassistant/components/kankun/switch.py
|
||||||
homeassistant/components/keba/*
|
homeassistant/components/keba/*
|
||||||
@ -428,6 +464,7 @@ omit =
|
|||||||
homeassistant/components/miflora/sensor.py
|
homeassistant/components/miflora/sensor.py
|
||||||
homeassistant/components/mikrotik/hub.py
|
homeassistant/components/mikrotik/hub.py
|
||||||
homeassistant/components/mikrotik/device_tracker.py
|
homeassistant/components/mikrotik/device_tracker.py
|
||||||
|
homeassistant/components/mill/__init__.py
|
||||||
homeassistant/components/mill/climate.py
|
homeassistant/components/mill/climate.py
|
||||||
homeassistant/components/mill/const.py
|
homeassistant/components/mill/const.py
|
||||||
homeassistant/components/minecraft_server/__init__.py
|
homeassistant/components/minecraft_server/__init__.py
|
||||||
@ -502,7 +539,14 @@ omit =
|
|||||||
homeassistant/components/ombi/*
|
homeassistant/components/ombi/*
|
||||||
homeassistant/components/onewire/sensor.py
|
homeassistant/components/onewire/sensor.py
|
||||||
homeassistant/components/onkyo/media_player.py
|
homeassistant/components/onkyo/media_player.py
|
||||||
|
homeassistant/components/onvif/__init__.py
|
||||||
|
homeassistant/components/onvif/base.py
|
||||||
|
homeassistant/components/onvif/binary_sensor.py
|
||||||
homeassistant/components/onvif/camera.py
|
homeassistant/components/onvif/camera.py
|
||||||
|
homeassistant/components/onvif/device.py
|
||||||
|
homeassistant/components/onvif/event.py
|
||||||
|
homeassistant/components/onvif/parsers.py
|
||||||
|
homeassistant/components/onvif/sensor.py
|
||||||
homeassistant/components/opencv/*
|
homeassistant/components/opencv/*
|
||||||
homeassistant/components/openevse/sensor.py
|
homeassistant/components/openevse/sensor.py
|
||||||
homeassistant/components/openexchangerates/sensor.py
|
homeassistant/components/openexchangerates/sensor.py
|
||||||
@ -527,7 +571,6 @@ omit =
|
|||||||
homeassistant/components/osramlightify/light.py
|
homeassistant/components/osramlightify/light.py
|
||||||
homeassistant/components/otp/sensor.py
|
homeassistant/components/otp/sensor.py
|
||||||
homeassistant/components/panasonic_bluray/media_player.py
|
homeassistant/components/panasonic_bluray/media_player.py
|
||||||
homeassistant/components/panasonic_viera/__init__.py
|
|
||||||
homeassistant/components/panasonic_viera/media_player.py
|
homeassistant/components/panasonic_viera/media_player.py
|
||||||
homeassistant/components/pandora/media_player.py
|
homeassistant/components/pandora/media_player.py
|
||||||
homeassistant/components/pcal9535a/*
|
homeassistant/components/pcal9535a/*
|
||||||
@ -569,7 +612,6 @@ omit =
|
|||||||
homeassistant/components/qrcode/image_processing.py
|
homeassistant/components/qrcode/image_processing.py
|
||||||
homeassistant/components/quantum_gateway/device_tracker.py
|
homeassistant/components/quantum_gateway/device_tracker.py
|
||||||
homeassistant/components/qvr_pro/*
|
homeassistant/components/qvr_pro/*
|
||||||
homeassistant/components/qwikswitch/*
|
|
||||||
homeassistant/components/rachio/*
|
homeassistant/components/rachio/*
|
||||||
homeassistant/components/radarr/sensor.py
|
homeassistant/components/radarr/sensor.py
|
||||||
homeassistant/components/radiotherm/climate.py
|
homeassistant/components/radiotherm/climate.py
|
||||||
@ -599,7 +641,6 @@ omit =
|
|||||||
homeassistant/components/ring/camera.py
|
homeassistant/components/ring/camera.py
|
||||||
homeassistant/components/ripple/sensor.py
|
homeassistant/components/ripple/sensor.py
|
||||||
homeassistant/components/rocketchat/notify.py
|
homeassistant/components/rocketchat/notify.py
|
||||||
homeassistant/components/roku/remote.py
|
|
||||||
homeassistant/components/roomba/binary_sensor.py
|
homeassistant/components/roomba/binary_sensor.py
|
||||||
homeassistant/components/roomba/braava.py
|
homeassistant/components/roomba/braava.py
|
||||||
homeassistant/components/roomba/irobot_base.py
|
homeassistant/components/roomba/irobot_base.py
|
||||||
@ -608,7 +649,7 @@ omit =
|
|||||||
homeassistant/components/roomba/vacuum.py
|
homeassistant/components/roomba/vacuum.py
|
||||||
homeassistant/components/route53/*
|
homeassistant/components/route53/*
|
||||||
homeassistant/components/rova/sensor.py
|
homeassistant/components/rova/sensor.py
|
||||||
homeassistant/components/rpi_camera/camera.py
|
homeassistant/components/rpi_camera/*
|
||||||
homeassistant/components/rpi_gpio/*
|
homeassistant/components/rpi_gpio/*
|
||||||
homeassistant/components/rpi_gpio/cover.py
|
homeassistant/components/rpi_gpio/cover.py
|
||||||
homeassistant/components/rpi_gpio_pwm/light.py
|
homeassistant/components/rpi_gpio_pwm/light.py
|
||||||
@ -672,7 +713,6 @@ omit =
|
|||||||
homeassistant/components/somfy/*
|
homeassistant/components/somfy/*
|
||||||
homeassistant/components/somfy_mylink/*
|
homeassistant/components/somfy_mylink/*
|
||||||
homeassistant/components/sonarr/sensor.py
|
homeassistant/components/sonarr/sensor.py
|
||||||
homeassistant/components/songpal/*
|
|
||||||
homeassistant/components/sonos/*
|
homeassistant/components/sonos/*
|
||||||
homeassistant/components/sony_projector/switch.py
|
homeassistant/components/sony_projector/switch.py
|
||||||
homeassistant/components/spc/*
|
homeassistant/components/spc/*
|
||||||
@ -773,6 +813,10 @@ omit =
|
|||||||
homeassistant/components/ubus/device_tracker.py
|
homeassistant/components/ubus/device_tracker.py
|
||||||
homeassistant/components/ue_smart_radio/media_player.py
|
homeassistant/components/ue_smart_radio/media_player.py
|
||||||
homeassistant/components/unifiled/*
|
homeassistant/components/unifiled/*
|
||||||
|
homeassistant/components/upb/__init__.py
|
||||||
|
homeassistant/components/upb/const.py
|
||||||
|
homeassistant/components/upb/light.py
|
||||||
|
homeassistant/components/upb/scene.py
|
||||||
homeassistant/components/upcloud/*
|
homeassistant/components/upcloud/*
|
||||||
homeassistant/components/upnp/*
|
homeassistant/components/upnp/*
|
||||||
homeassistant/components/upc_connect/*
|
homeassistant/components/upc_connect/*
|
||||||
@ -816,6 +860,7 @@ omit =
|
|||||||
homeassistant/components/webostv/*
|
homeassistant/components/webostv/*
|
||||||
homeassistant/components/wemo/*
|
homeassistant/components/wemo/*
|
||||||
homeassistant/components/whois/sensor.py
|
homeassistant/components/whois/sensor.py
|
||||||
|
homeassistant/components/wiffi/*
|
||||||
homeassistant/components/wink/*
|
homeassistant/components/wink/*
|
||||||
homeassistant/components/wirelesstag/*
|
homeassistant/components/wirelesstag/*
|
||||||
homeassistant/components/worldtidesinfo/sensor.py
|
homeassistant/components/worldtidesinfo/sensor.py
|
||||||
@ -829,7 +874,17 @@ omit =
|
|||||||
homeassistant/components/xfinity/device_tracker.py
|
homeassistant/components/xfinity/device_tracker.py
|
||||||
homeassistant/components/xiaomi/camera.py
|
homeassistant/components/xiaomi/camera.py
|
||||||
homeassistant/components/xiaomi_aqara/*
|
homeassistant/components/xiaomi_aqara/*
|
||||||
homeassistant/components/xiaomi_miio/*
|
homeassistant/components/xiaomi_miio/__init__.py
|
||||||
|
homeassistant/components/xiaomi_miio/air_quality.py
|
||||||
|
homeassistant/components/xiaomi_miio/alarm_control_panel.py
|
||||||
|
homeassistant/components/xiaomi_miio/device_tracker.py
|
||||||
|
homeassistant/components/xiaomi_miio/fan.py
|
||||||
|
homeassistant/components/xiaomi_miio/gateway.py
|
||||||
|
homeassistant/components/xiaomi_miio/light.py
|
||||||
|
homeassistant/components/xiaomi_miio/remote.py
|
||||||
|
homeassistant/components/xiaomi_miio/sensor.py
|
||||||
|
homeassistant/components/xiaomi_miio/switch.py
|
||||||
|
homeassistant/components/xiaomi_miio/vacuum.py
|
||||||
homeassistant/components/xiaomi_tv/media_player.py
|
homeassistant/components/xiaomi_tv/media_player.py
|
||||||
homeassistant/components/xmpp/notify.py
|
homeassistant/components/xmpp/notify.py
|
||||||
homeassistant/components/xs1/*
|
homeassistant/components/xs1/*
|
||||||
@ -844,8 +899,9 @@ omit =
|
|||||||
homeassistant/components/zamg/weather.py
|
homeassistant/components/zamg/weather.py
|
||||||
homeassistant/components/zengge/light.py
|
homeassistant/components/zengge/light.py
|
||||||
homeassistant/components/zeroconf/*
|
homeassistant/components/zeroconf/*
|
||||||
|
homeassistant/components/zerproc/__init__.py
|
||||||
|
homeassistant/components/zerproc/const.py
|
||||||
homeassistant/components/zestimate/sensor.py
|
homeassistant/components/zestimate/sensor.py
|
||||||
homeassistant/components/zha/__init__.py
|
|
||||||
homeassistant/components/zha/api.py
|
homeassistant/components/zha/api.py
|
||||||
homeassistant/components/zha/core/channels/*
|
homeassistant/components/zha/core/channels/*
|
||||||
homeassistant/components/zha/core/const.py
|
homeassistant/components/zha/core/const.py
|
||||||
@ -864,6 +920,10 @@ omit =
|
|||||||
homeassistant/components/zoneminder/*
|
homeassistant/components/zoneminder/*
|
||||||
homeassistant/components/supla/*
|
homeassistant/components/supla/*
|
||||||
homeassistant/components/zwave/util.py
|
homeassistant/components/zwave/util.py
|
||||||
|
homeassistant/components/ozw/__init__.py
|
||||||
|
homeassistant/components/ozw/discovery.py
|
||||||
|
homeassistant/components/ozw/entity.py
|
||||||
|
homeassistant/components/ozw/services.py
|
||||||
|
|
||||||
[report]
|
[report]
|
||||||
# Regexes for lines to exclude from consideration
|
# Regexes for lines to exclude from consideration
|
||||||
|
5
.hadolint.yaml
Normal file
5
.hadolint.yaml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
ignored:
|
||||||
|
- DL3006
|
||||||
|
- DL3008
|
||||||
|
- DL3013
|
||||||
|
- DL3018
|
@ -1,6 +1,6 @@
|
|||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/asottile/pyupgrade
|
- repo: https://github.com/asottile/pyupgrade
|
||||||
rev: v2.1.0
|
rev: v2.3.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: pyupgrade
|
- id: pyupgrade
|
||||||
args: [--py37-plus]
|
args: [--py37-plus]
|
||||||
@ -18,9 +18,9 @@ repos:
|
|||||||
- id: codespell
|
- id: codespell
|
||||||
args:
|
args:
|
||||||
- --ignore-words-list=hass,alot,datas,dof,dur,farenheit,hist,iff,ines,ist,lightsensor,mut,nd,pres,referer,ser,serie,te,technik,ue,uint,visability,wan,wanna,withing
|
- --ignore-words-list=hass,alot,datas,dof,dur,farenheit,hist,iff,ines,ist,lightsensor,mut,nd,pres,referer,ser,serie,te,technik,ue,uint,visability,wan,wanna,withing
|
||||||
- --skip="./.*,*.json"
|
- --skip="./.*,*.csv,*.json"
|
||||||
- --quiet-level=2
|
- --quiet-level=2
|
||||||
exclude_types: [json]
|
exclude_types: [csv, json]
|
||||||
- repo: https://gitlab.com/pycqa/flake8
|
- repo: https://gitlab.com/pycqa/flake8
|
||||||
rev: 3.7.9
|
rev: 3.7.9
|
||||||
hooks:
|
hooks:
|
||||||
|
@ -11,6 +11,9 @@ addons:
|
|||||||
- libswscale-dev
|
- libswscale-dev
|
||||||
- libswresample-dev
|
- libswresample-dev
|
||||||
- libavfilter-dev
|
- libavfilter-dev
|
||||||
|
sources:
|
||||||
|
- sourceline: ppa:savoury1/ffmpeg4
|
||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
fast_finish: true
|
fast_finish: true
|
||||||
include:
|
include:
|
||||||
|
31
CODEOWNERS
31
CODEOWNERS
@ -15,6 +15,7 @@ homeassistant/scripts/check_config.py @kellerza
|
|||||||
# Integrations
|
# Integrations
|
||||||
homeassistant/components/abode/* @shred86
|
homeassistant/components/abode/* @shred86
|
||||||
homeassistant/components/adguard/* @frenck
|
homeassistant/components/adguard/* @frenck
|
||||||
|
homeassistant/components/agent_dvr/* @ispysoftware
|
||||||
homeassistant/components/airly/* @bieniu
|
homeassistant/components/airly/* @bieniu
|
||||||
homeassistant/components/airvisual/* @bachya
|
homeassistant/components/airvisual/* @bachya
|
||||||
homeassistant/components/alarmdecoder/* @ajschmidt8
|
homeassistant/components/alarmdecoder/* @ajschmidt8
|
||||||
@ -47,12 +48,13 @@ homeassistant/components/avea/* @pattyland
|
|||||||
homeassistant/components/avri/* @timvancann
|
homeassistant/components/avri/* @timvancann
|
||||||
homeassistant/components/awair/* @danielsjf
|
homeassistant/components/awair/* @danielsjf
|
||||||
homeassistant/components/aws/* @awarecan @robbiet480
|
homeassistant/components/aws/* @awarecan @robbiet480
|
||||||
homeassistant/components/axis/* @kane610
|
homeassistant/components/axis/* @Kane610
|
||||||
homeassistant/components/azure_event_hub/* @eavanvalkenburg
|
homeassistant/components/azure_event_hub/* @eavanvalkenburg
|
||||||
homeassistant/components/azure_service_bus/* @hfurubotten
|
homeassistant/components/azure_service_bus/* @hfurubotten
|
||||||
homeassistant/components/beewi_smartclim/* @alemuro
|
homeassistant/components/beewi_smartclim/* @alemuro
|
||||||
homeassistant/components/bitcoin/* @fabaff
|
homeassistant/components/bitcoin/* @fabaff
|
||||||
homeassistant/components/bizkaibus/* @UgaitzEtxebarria
|
homeassistant/components/bizkaibus/* @UgaitzEtxebarria
|
||||||
|
homeassistant/components/blebox/* @gadgetmobile
|
||||||
homeassistant/components/blink/* @fronzbot
|
homeassistant/components/blink/* @fronzbot
|
||||||
homeassistant/components/bmp280/* @belidzs
|
homeassistant/components/bmp280/* @belidzs
|
||||||
homeassistant/components/bmw_connected_drive/* @gerard33
|
homeassistant/components/bmw_connected_drive/* @gerard33
|
||||||
@ -61,6 +63,7 @@ homeassistant/components/braviatv/* @robbiet480 @bieniu
|
|||||||
homeassistant/components/broadlink/* @danielhiversen @felipediel
|
homeassistant/components/broadlink/* @danielhiversen @felipediel
|
||||||
homeassistant/components/brother/* @bieniu
|
homeassistant/components/brother/* @bieniu
|
||||||
homeassistant/components/brunt/* @eavanvalkenburg
|
homeassistant/components/brunt/* @eavanvalkenburg
|
||||||
|
homeassistant/components/bsblan/* @liudger
|
||||||
homeassistant/components/bt_smarthub/* @jxwolstenholme
|
homeassistant/components/bt_smarthub/* @jxwolstenholme
|
||||||
homeassistant/components/buienradar/* @mjj4791 @ties
|
homeassistant/components/buienradar/* @mjj4791 @ties
|
||||||
homeassistant/components/cast/* @emontnemery
|
homeassistant/components/cast/* @emontnemery
|
||||||
@ -82,12 +85,13 @@ homeassistant/components/cpuspeed/* @fabaff
|
|||||||
homeassistant/components/cups/* @fabaff
|
homeassistant/components/cups/* @fabaff
|
||||||
homeassistant/components/daikin/* @fredrike
|
homeassistant/components/daikin/* @fredrike
|
||||||
homeassistant/components/darksky/* @fabaff
|
homeassistant/components/darksky/* @fabaff
|
||||||
homeassistant/components/deconz/* @kane610
|
homeassistant/components/deconz/* @Kane610
|
||||||
homeassistant/components/delijn/* @bollewolle
|
homeassistant/components/delijn/* @bollewolle
|
||||||
homeassistant/components/demo/* @home-assistant/core
|
homeassistant/components/demo/* @home-assistant/core
|
||||||
homeassistant/components/denonavr/* @scarface-4711 @starkillerOG
|
homeassistant/components/denonavr/* @scarface-4711 @starkillerOG
|
||||||
homeassistant/components/derivative/* @afaucogney
|
homeassistant/components/derivative/* @afaucogney
|
||||||
homeassistant/components/device_automation/* @home-assistant/core
|
homeassistant/components/device_automation/* @home-assistant/core
|
||||||
|
homeassistant/components/devolo_home_control/* @2Fake @Shutgun
|
||||||
homeassistant/components/digital_ocean/* @fabaff
|
homeassistant/components/digital_ocean/* @fabaff
|
||||||
homeassistant/components/directv/* @ctalkington
|
homeassistant/components/directv/* @ctalkington
|
||||||
homeassistant/components/discogs/* @thibmaek
|
homeassistant/components/discogs/* @thibmaek
|
||||||
@ -122,9 +126,11 @@ homeassistant/components/file/* @fabaff
|
|||||||
homeassistant/components/filter/* @dgomes
|
homeassistant/components/filter/* @dgomes
|
||||||
homeassistant/components/fitbit/* @robbiet480
|
homeassistant/components/fitbit/* @robbiet480
|
||||||
homeassistant/components/fixer/* @fabaff
|
homeassistant/components/fixer/* @fabaff
|
||||||
|
homeassistant/components/flick_electric/* @ZephireNZ
|
||||||
homeassistant/components/flock/* @fabaff
|
homeassistant/components/flock/* @fabaff
|
||||||
homeassistant/components/flume/* @ChrisMandich @bdraco
|
homeassistant/components/flume/* @ChrisMandich @bdraco
|
||||||
homeassistant/components/flunearyou/* @bachya
|
homeassistant/components/flunearyou/* @bachya
|
||||||
|
homeassistant/components/forked_daapd/* @uvjustin
|
||||||
homeassistant/components/fortigate/* @kifeo
|
homeassistant/components/fortigate/* @kifeo
|
||||||
homeassistant/components/fortios/* @kimfrellsen
|
homeassistant/components/fortios/* @kimfrellsen
|
||||||
homeassistant/components/foscam/* @skgsergio
|
homeassistant/components/foscam/* @skgsergio
|
||||||
@ -163,6 +169,7 @@ homeassistant/components/hikvisioncam/* @fbradyirl
|
|||||||
homeassistant/components/hisense_aehw4a1/* @bannhead
|
homeassistant/components/hisense_aehw4a1/* @bannhead
|
||||||
homeassistant/components/history/* @home-assistant/core
|
homeassistant/components/history/* @home-assistant/core
|
||||||
homeassistant/components/hive/* @Rendili @KJonline
|
homeassistant/components/hive/* @Rendili @KJonline
|
||||||
|
homeassistant/components/home_connect/* @DavidMStraub
|
||||||
homeassistant/components/homeassistant/* @home-assistant/core
|
homeassistant/components/homeassistant/* @home-assistant/core
|
||||||
homeassistant/components/homekit/* @bdraco
|
homeassistant/components/homekit/* @bdraco
|
||||||
homeassistant/components/homekit_controller/* @Jc2k
|
homeassistant/components/homekit_controller/* @Jc2k
|
||||||
@ -174,6 +181,7 @@ homeassistant/components/http/* @home-assistant/core
|
|||||||
homeassistant/components/huawei_lte/* @scop
|
homeassistant/components/huawei_lte/* @scop
|
||||||
homeassistant/components/huawei_router/* @abmantis
|
homeassistant/components/huawei_router/* @abmantis
|
||||||
homeassistant/components/hue/* @balloob
|
homeassistant/components/hue/* @balloob
|
||||||
|
homeassistant/components/hunterdouglas_powerview/* @bdraco
|
||||||
homeassistant/components/iammeter/* @lewei50
|
homeassistant/components/iammeter/* @lewei50
|
||||||
homeassistant/components/iaqualink/* @flz
|
homeassistant/components/iaqualink/* @flz
|
||||||
homeassistant/components/icloud/* @Quentame
|
homeassistant/components/icloud/* @Quentame
|
||||||
@ -195,7 +203,7 @@ homeassistant/components/ipp/* @ctalkington
|
|||||||
homeassistant/components/iqvia/* @bachya
|
homeassistant/components/iqvia/* @bachya
|
||||||
homeassistant/components/irish_rail_transport/* @ttroy50
|
homeassistant/components/irish_rail_transport/* @ttroy50
|
||||||
homeassistant/components/islamic_prayer_times/* @engrbm87
|
homeassistant/components/islamic_prayer_times/* @engrbm87
|
||||||
homeassistant/components/isy994/* @bdraco
|
homeassistant/components/isy994/* @bdraco @shbatm
|
||||||
homeassistant/components/izone/* @Swamp-Ig
|
homeassistant/components/izone/* @Swamp-Ig
|
||||||
homeassistant/components/jewish_calendar/* @tsvi
|
homeassistant/components/jewish_calendar/* @tsvi
|
||||||
homeassistant/components/juicenet/* @jesserockz
|
homeassistant/components/juicenet/* @jesserockz
|
||||||
@ -239,7 +247,7 @@ homeassistant/components/minecraft_server/* @elmurato
|
|||||||
homeassistant/components/minio/* @tkislan
|
homeassistant/components/minio/* @tkislan
|
||||||
homeassistant/components/mobile_app/* @robbiet480
|
homeassistant/components/mobile_app/* @robbiet480
|
||||||
homeassistant/components/modbus/* @adamchengtkc @janiversen
|
homeassistant/components/modbus/* @adamchengtkc @janiversen
|
||||||
homeassistant/components/monoprice/* @etsinko
|
homeassistant/components/monoprice/* @etsinko @OnFreund
|
||||||
homeassistant/components/moon/* @fabaff
|
homeassistant/components/moon/* @fabaff
|
||||||
homeassistant/components/mpd/* @fabaff
|
homeassistant/components/mpd/* @fabaff
|
||||||
homeassistant/components/mqtt/* @home-assistant/core @emontnemery
|
homeassistant/components/mqtt/* @home-assistant/core @emontnemery
|
||||||
@ -267,6 +275,7 @@ homeassistant/components/nsw_fuel_station/* @nickw444
|
|||||||
homeassistant/components/nsw_rural_fire_service_feed/* @exxamalte
|
homeassistant/components/nsw_rural_fire_service_feed/* @exxamalte
|
||||||
homeassistant/components/nuheat/* @bdraco
|
homeassistant/components/nuheat/* @bdraco
|
||||||
homeassistant/components/nuki/* @pvizeli
|
homeassistant/components/nuki/* @pvizeli
|
||||||
|
homeassistant/components/numato/* @clssn
|
||||||
homeassistant/components/nut/* @bdraco
|
homeassistant/components/nut/* @bdraco
|
||||||
homeassistant/components/nws/* @MatthewFlamm
|
homeassistant/components/nws/* @MatthewFlamm
|
||||||
homeassistant/components/nzbget/* @chriscla
|
homeassistant/components/nzbget/* @chriscla
|
||||||
@ -275,13 +284,16 @@ homeassistant/components/ohmconnect/* @robbiet480
|
|||||||
homeassistant/components/ombi/* @larssont
|
homeassistant/components/ombi/* @larssont
|
||||||
homeassistant/components/onboarding/* @home-assistant/core
|
homeassistant/components/onboarding/* @home-assistant/core
|
||||||
homeassistant/components/onewire/* @garbled1
|
homeassistant/components/onewire/* @garbled1
|
||||||
|
homeassistant/components/onvif/* @hunterjm
|
||||||
homeassistant/components/openerz/* @misialq
|
homeassistant/components/openerz/* @misialq
|
||||||
|
homeassistant/components/opengarage/* @danielhiversen
|
||||||
homeassistant/components/opentherm_gw/* @mvn23
|
homeassistant/components/opentherm_gw/* @mvn23
|
||||||
homeassistant/components/openuv/* @bachya
|
homeassistant/components/openuv/* @bachya
|
||||||
homeassistant/components/openweathermap/* @fabaff
|
homeassistant/components/openweathermap/* @fabaff
|
||||||
homeassistant/components/opnsense/* @mtreinish
|
homeassistant/components/opnsense/* @mtreinish
|
||||||
homeassistant/components/orangepi_gpio/* @pascallj
|
homeassistant/components/orangepi_gpio/* @pascallj
|
||||||
homeassistant/components/oru/* @bvlaicu
|
homeassistant/components/oru/* @bvlaicu
|
||||||
|
homeassistant/components/ozw/* @cgarwood @marcelveldt @MartinHjelmare
|
||||||
homeassistant/components/panasonic_viera/* @joogps
|
homeassistant/components/panasonic_viera/* @joogps
|
||||||
homeassistant/components/panel_custom/* @home-assistant/frontend
|
homeassistant/components/panel_custom/* @home-assistant/frontend
|
||||||
homeassistant/components/panel_iframe/* @home-assistant/frontend
|
homeassistant/components/panel_iframe/* @home-assistant/frontend
|
||||||
@ -289,7 +301,7 @@ homeassistant/components/pcal9535a/* @Shulyaka
|
|||||||
homeassistant/components/persistent_notification/* @home-assistant/core
|
homeassistant/components/persistent_notification/* @home-assistant/core
|
||||||
homeassistant/components/philips_js/* @elupus
|
homeassistant/components/philips_js/* @elupus
|
||||||
homeassistant/components/pi4ioe5v9xxxx/* @antonverburg
|
homeassistant/components/pi4ioe5v9xxxx/* @antonverburg
|
||||||
homeassistant/components/pi_hole/* @fabaff @johnluetke
|
homeassistant/components/pi_hole/* @fabaff @johnluetke @shenxn
|
||||||
homeassistant/components/pilight/* @trekky12
|
homeassistant/components/pilight/* @trekky12
|
||||||
homeassistant/components/plaato/* @JohNan
|
homeassistant/components/plaato/* @JohNan
|
||||||
homeassistant/components/plant/* @ChristianKuehnel
|
homeassistant/components/plant/* @ChristianKuehnel
|
||||||
@ -357,13 +369,14 @@ homeassistant/components/solax/* @squishykid
|
|||||||
homeassistant/components/soma/* @ratsept
|
homeassistant/components/soma/* @ratsept
|
||||||
homeassistant/components/somfy/* @tetienne
|
homeassistant/components/somfy/* @tetienne
|
||||||
homeassistant/components/sonarr/* @ctalkington
|
homeassistant/components/sonarr/* @ctalkington
|
||||||
homeassistant/components/songpal/* @rytilahti
|
homeassistant/components/songpal/* @rytilahti @shenxn
|
||||||
homeassistant/components/sonos/* @amelchio
|
homeassistant/components/sonos/* @amelchio
|
||||||
homeassistant/components/spaceapi/* @fabaff
|
homeassistant/components/spaceapi/* @fabaff
|
||||||
homeassistant/components/speedtestdotnet/* @rohankapoorcom
|
homeassistant/components/speedtestdotnet/* @rohankapoorcom
|
||||||
homeassistant/components/spider/* @peternijssen
|
homeassistant/components/spider/* @peternijssen
|
||||||
homeassistant/components/spotify/* @frenck
|
homeassistant/components/spotify/* @frenck
|
||||||
homeassistant/components/sql/* @dgomes
|
homeassistant/components/sql/* @dgomes
|
||||||
|
homeassistant/components/squeezebox/* @rajlaud
|
||||||
homeassistant/components/starline/* @anonym-tsk
|
homeassistant/components/starline/* @anonym-tsk
|
||||||
homeassistant/components/statistics/* @fabaff
|
homeassistant/components/statistics/* @fabaff
|
||||||
homeassistant/components/stiebel_eltron/* @fucm
|
homeassistant/components/stiebel_eltron/* @fucm
|
||||||
@ -406,12 +419,14 @@ homeassistant/components/tradfri/* @ggravlingen
|
|||||||
homeassistant/components/trafikverket_train/* @endor-force
|
homeassistant/components/trafikverket_train/* @endor-force
|
||||||
homeassistant/components/transmission/* @engrbm87 @JPHutchins
|
homeassistant/components/transmission/* @engrbm87 @JPHutchins
|
||||||
homeassistant/components/tts/* @pvizeli
|
homeassistant/components/tts/* @pvizeli
|
||||||
|
homeassistant/components/tuya/* @ollo69
|
||||||
homeassistant/components/twentemilieu/* @frenck
|
homeassistant/components/twentemilieu/* @frenck
|
||||||
homeassistant/components/twilio_call/* @robbiet480
|
homeassistant/components/twilio_call/* @robbiet480
|
||||||
homeassistant/components/twilio_sms/* @robbiet480
|
homeassistant/components/twilio_sms/* @robbiet480
|
||||||
homeassistant/components/ubee/* @mzdrale
|
homeassistant/components/ubee/* @mzdrale
|
||||||
homeassistant/components/unifi/* @kane610
|
homeassistant/components/unifi/* @Kane610
|
||||||
homeassistant/components/unifiled/* @florisvdk
|
homeassistant/components/unifiled/* @florisvdk
|
||||||
|
homeassistant/components/upb/* @gwww
|
||||||
homeassistant/components/upc_connect/* @pvizeli
|
homeassistant/components/upc_connect/* @pvizeli
|
||||||
homeassistant/components/upcloud/* @scop
|
homeassistant/components/upcloud/* @scop
|
||||||
homeassistant/components/updater/* @home-assistant/core
|
homeassistant/components/updater/* @home-assistant/core
|
||||||
@ -436,6 +451,7 @@ homeassistant/components/weather/* @fabaff
|
|||||||
homeassistant/components/webostv/* @bendavid
|
homeassistant/components/webostv/* @bendavid
|
||||||
homeassistant/components/websocket_api/* @home-assistant/core
|
homeassistant/components/websocket_api/* @home-assistant/core
|
||||||
homeassistant/components/wemo/* @sqldiablo
|
homeassistant/components/wemo/* @sqldiablo
|
||||||
|
homeassistant/components/wiffi/* @mampfes
|
||||||
homeassistant/components/withings/* @vangorra
|
homeassistant/components/withings/* @vangorra
|
||||||
homeassistant/components/wled/* @frenck
|
homeassistant/components/wled/* @frenck
|
||||||
homeassistant/components/workday/* @fabaff
|
homeassistant/components/workday/* @fabaff
|
||||||
@ -455,6 +471,7 @@ homeassistant/components/yessssms/* @flowolf
|
|||||||
homeassistant/components/yi/* @bachya
|
homeassistant/components/yi/* @bachya
|
||||||
homeassistant/components/yr/* @danielhiversen
|
homeassistant/components/yr/* @danielhiversen
|
||||||
homeassistant/components/zeroconf/* @robbiet480 @Kane610
|
homeassistant/components/zeroconf/* @robbiet480 @Kane610
|
||||||
|
homeassistant/components/zerproc/* @emlove
|
||||||
homeassistant/components/zha/* @dmulcahey @adminiuga
|
homeassistant/components/zha/* @dmulcahey @adminiuga
|
||||||
homeassistant/components/zone/* @home-assistant/core
|
homeassistant/components/zone/* @home-assistant/core
|
||||||
homeassistant/components/zoneminder/* @rohankapoorcom
|
homeassistant/components/zoneminder/* @rohankapoorcom
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
FROM python:3.7
|
FROM python:3.8
|
||||||
|
|
||||||
RUN apt-get update \
|
RUN \
|
||||||
&& apt-get install -y --no-install-recommends \
|
apt-get update && apt-get install -y --no-install-recommends \
|
||||||
libudev-dev \
|
libudev-dev \
|
||||||
libavformat-dev \
|
libavformat-dev \
|
||||||
libavcodec-dev \
|
libavcodec-dev \
|
||||||
@ -18,8 +18,7 @@ WORKDIR /usr/src
|
|||||||
|
|
||||||
# Setup hass-release
|
# Setup hass-release
|
||||||
RUN git clone --depth 1 https://github.com/home-assistant/hass-release \
|
RUN git clone --depth 1 https://github.com/home-assistant/hass-release \
|
||||||
&& cd hass-release \
|
&& pip3 install -e hass-release/
|
||||||
&& pip3 install -e .
|
|
||||||
|
|
||||||
WORKDIR /workspaces
|
WORKDIR /workspaces
|
||||||
|
|
||||||
|
@ -4,9 +4,9 @@ trigger:
|
|||||||
batch: true
|
batch: true
|
||||||
branches:
|
branches:
|
||||||
include:
|
include:
|
||||||
- rc
|
- rc
|
||||||
- dev
|
- dev
|
||||||
- master
|
- master
|
||||||
pr:
|
pr:
|
||||||
- rc
|
- rc
|
||||||
- dev
|
- dev
|
||||||
@ -14,208 +14,229 @@ pr:
|
|||||||
|
|
||||||
resources:
|
resources:
|
||||||
containers:
|
containers:
|
||||||
- container: 37
|
- container: 37
|
||||||
image: homeassistant/ci-azure:3.7
|
image: homeassistant/ci-azure:3.7
|
||||||
|
- container: 38
|
||||||
|
image: homeassistant/ci-azure:3.8
|
||||||
repositories:
|
repositories:
|
||||||
- repository: azure
|
- repository: azure
|
||||||
type: github
|
type: github
|
||||||
name: 'home-assistant/ci-azure'
|
name: "home-assistant/ci-azure"
|
||||||
endpoint: 'home-assistant'
|
endpoint: "home-assistant"
|
||||||
variables:
|
variables:
|
||||||
- name: PythonMain
|
- name: PythonMain
|
||||||
value: '37'
|
value: "37"
|
||||||
|
- name: versionHadolint
|
||||||
|
value: "v1.17.6"
|
||||||
|
|
||||||
stages:
|
stages:
|
||||||
|
- stage: "Overview"
|
||||||
|
jobs:
|
||||||
|
- job: "Lint"
|
||||||
|
pool:
|
||||||
|
vmImage: "ubuntu-latest"
|
||||||
|
container: $[ variables['PythonMain'] ]
|
||||||
|
steps:
|
||||||
|
- template: templates/azp-step-cache.yaml@azure
|
||||||
|
parameters:
|
||||||
|
keyfile: "requirements_test.txt | homeassistant/package_constraints.txt"
|
||||||
|
build: |
|
||||||
|
python -m venv venv
|
||||||
|
|
||||||
- stage: 'Overview'
|
. venv/bin/activate
|
||||||
jobs:
|
pip install -r requirements_test.txt -c homeassistant/package_constraints.txt
|
||||||
- job: 'Lint'
|
pre-commit install-hooks
|
||||||
pool:
|
- script: |
|
||||||
vmImage: 'ubuntu-latest'
|
. venv/bin/activate
|
||||||
container: $[ variables['PythonMain'] ]
|
pre-commit run --hook-stage manual check-executables-have-shebangs --all-files
|
||||||
steps:
|
displayName: "Run executables check"
|
||||||
- template: templates/azp-step-cache.yaml@azure
|
- script: |
|
||||||
parameters:
|
. venv/bin/activate
|
||||||
keyfile: 'requirements_test.txt | homeassistant/package_constraints.txt'
|
pre-commit run codespell --all-files
|
||||||
build: |
|
displayName: "Run codespell"
|
||||||
python -m venv venv
|
- script: |
|
||||||
|
. venv/bin/activate
|
||||||
|
pre-commit run flake8 --all-files
|
||||||
|
displayName: "Run flake8"
|
||||||
|
- script: |
|
||||||
|
. venv/bin/activate
|
||||||
|
pre-commit run bandit --all-files
|
||||||
|
displayName: "Run bandit"
|
||||||
|
- script: |
|
||||||
|
. venv/bin/activate
|
||||||
|
pre-commit run isort --all-files --show-diff-on-failure
|
||||||
|
displayName: "Run isort"
|
||||||
|
- script: |
|
||||||
|
. venv/bin/activate
|
||||||
|
pre-commit run check-json --all-files
|
||||||
|
displayName: "Run check-json"
|
||||||
|
- script: |
|
||||||
|
. venv/bin/activate
|
||||||
|
pre-commit run yamllint --all-files
|
||||||
|
displayName: "Run yamllint"
|
||||||
|
- script: |
|
||||||
|
. venv/bin/activate
|
||||||
|
pre-commit run pyupgrade --all-files --show-diff-on-failure
|
||||||
|
displayName: "Run pyupgrade"
|
||||||
|
# Prettier seems to hang on Azure, unknown why yet.
|
||||||
|
# Temporarily disable the check to no block PRs
|
||||||
|
# - script: |
|
||||||
|
# . venv/bin/activate
|
||||||
|
# pre-commit run prettier --all-files --show-diff-on-failure
|
||||||
|
# displayName: 'Run prettier'
|
||||||
|
- job: "Validate"
|
||||||
|
pool:
|
||||||
|
vmImage: "ubuntu-latest"
|
||||||
|
container: $[ variables['PythonMain'] ]
|
||||||
|
steps:
|
||||||
|
- template: templates/azp-step-cache.yaml@azure
|
||||||
|
parameters:
|
||||||
|
keyfile: "homeassistant/package_constraints.txt"
|
||||||
|
build: |
|
||||||
|
python -m venv venv
|
||||||
|
|
||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
pip install -r requirements_test.txt -c homeassistant/package_constraints.txt
|
pip install -e .
|
||||||
pre-commit install-hooks
|
- script: |
|
||||||
- script: |
|
. venv/bin/activate
|
||||||
. venv/bin/activate
|
python -m script.hassfest --action validate
|
||||||
pre-commit run --hook-stage manual check-executables-have-shebangs --all-files
|
displayName: "Validate manifests"
|
||||||
displayName: 'Run executables check'
|
- script: |
|
||||||
- script: |
|
. venv/bin/activate
|
||||||
. venv/bin/activate
|
./script/gen_requirements_all.py validate
|
||||||
pre-commit run codespell --all-files
|
displayName: "requirements_all validate"
|
||||||
displayName: 'Run codespell'
|
- job: "CheckFormat"
|
||||||
- script: |
|
pool:
|
||||||
. venv/bin/activate
|
vmImage: "ubuntu-latest"
|
||||||
pre-commit run flake8 --all-files
|
container: $[ variables['PythonMain'] ]
|
||||||
displayName: 'Run flake8'
|
steps:
|
||||||
- script: |
|
- template: templates/azp-step-cache.yaml@azure
|
||||||
. venv/bin/activate
|
parameters:
|
||||||
pre-commit run bandit --all-files
|
keyfile: "requirements_test.txt | homeassistant/package_constraints.txt"
|
||||||
displayName: 'Run bandit'
|
build: |
|
||||||
- script: |
|
python -m venv venv
|
||||||
. venv/bin/activate
|
|
||||||
pre-commit run isort --all-files --show-diff-on-failure
|
|
||||||
displayName: 'Run isort'
|
|
||||||
- script: |
|
|
||||||
. venv/bin/activate
|
|
||||||
pre-commit run check-json --all-files
|
|
||||||
displayName: 'Run check-json'
|
|
||||||
- script: |
|
|
||||||
. venv/bin/activate
|
|
||||||
pre-commit run yamllint --all-files
|
|
||||||
displayName: 'Run yamllint'
|
|
||||||
- script: |
|
|
||||||
. venv/bin/activate
|
|
||||||
pre-commit run pyupgrade --all-files --show-diff-on-failure
|
|
||||||
displayName: 'Run pyupgrade'
|
|
||||||
# Prettier seems to hang on Azure, unknown why yet.
|
|
||||||
# Temporarily disable the check to no block PRs
|
|
||||||
# - script: |
|
|
||||||
# . venv/bin/activate
|
|
||||||
# pre-commit run prettier --all-files --show-diff-on-failure
|
|
||||||
# displayName: 'Run prettier'
|
|
||||||
- job: 'Validate'
|
|
||||||
pool:
|
|
||||||
vmImage: 'ubuntu-latest'
|
|
||||||
container: $[ variables['PythonMain'] ]
|
|
||||||
steps:
|
|
||||||
- template: templates/azp-step-cache.yaml@azure
|
|
||||||
parameters:
|
|
||||||
keyfile: 'homeassistant/package_constraints.txt'
|
|
||||||
build: |
|
|
||||||
python -m venv venv
|
|
||||||
|
|
||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
pip install -e .
|
pip install -r requirements_test.txt -c homeassistant/package_constraints.txt
|
||||||
- script: |
|
pre-commit install-hooks
|
||||||
. venv/bin/activate
|
- script: |
|
||||||
python -m script.hassfest --action validate
|
. venv/bin/activate
|
||||||
displayName: 'Validate manifests'
|
pre-commit run black --all-files --show-diff-on-failure
|
||||||
- script: |
|
displayName: "Check Black formatting"
|
||||||
. venv/bin/activate
|
- job: "Docker"
|
||||||
./script/gen_requirements_all.py validate
|
pool:
|
||||||
displayName: 'requirements_all validate'
|
vmImage: "ubuntu-latest"
|
||||||
- job: 'CheckFormat'
|
steps:
|
||||||
pool:
|
- script: sudo docker pull hadolint/hadolint:$(versionHadolint)
|
||||||
vmImage: 'ubuntu-latest'
|
displayName: "Install Hadolint"
|
||||||
container: $[ variables['PythonMain'] ]
|
- script: |
|
||||||
steps:
|
set -e
|
||||||
- template: templates/azp-step-cache.yaml@azure
|
for dockerfile in Dockerfile Dockerfile.dev
|
||||||
parameters:
|
do
|
||||||
keyfile: 'requirements_test.txt | homeassistant/package_constraints.txt'
|
echo "Linting: $dockerfile"
|
||||||
build: |
|
docker run --rm -i \
|
||||||
python -m venv venv
|
-v "$(pwd)/.hadolint.yaml:/.hadolint.yaml:ro" \
|
||||||
|
hadolint/hadolint:$(versionHadolint) < "$dockerfile"
|
||||||
|
done
|
||||||
|
displayName: "Run Hadolint"
|
||||||
|
|
||||||
. venv/bin/activate
|
- stage: "Tests"
|
||||||
pip install -r requirements_test.txt -c homeassistant/package_constraints.txt
|
dependsOn:
|
||||||
pre-commit install-hooks
|
- "Overview"
|
||||||
- script: |
|
jobs:
|
||||||
. venv/bin/activate
|
- job: "PyTest"
|
||||||
pre-commit run black --all-files --show-diff-on-failure
|
pool:
|
||||||
displayName: 'Check Black formatting'
|
vmImage: "ubuntu-latest"
|
||||||
|
strategy:
|
||||||
|
maxParallel: 3
|
||||||
|
matrix:
|
||||||
|
Python37:
|
||||||
|
python.container: "37"
|
||||||
|
Python38:
|
||||||
|
python.container: "38"
|
||||||
|
container: $[ variables['python.container'] ]
|
||||||
|
steps:
|
||||||
|
- template: templates/azp-step-cache.yaml@azure
|
||||||
|
parameters:
|
||||||
|
keyfile: "requirements_test_all.txt | homeassistant/package_constraints.txt"
|
||||||
|
build: |
|
||||||
|
set -e
|
||||||
|
python -m venv venv
|
||||||
|
|
||||||
- stage: 'Tests'
|
. venv/bin/activate
|
||||||
dependsOn:
|
pip install -U pip setuptools pytest-azurepipelines pytest-xdist -c homeassistant/package_constraints.txt
|
||||||
- 'Overview'
|
pip install -r requirements_test_all.txt -c homeassistant/package_constraints.txt
|
||||||
jobs:
|
# This is a TEMP. Eventually we should make sure our 4 dependencies drop typing.
|
||||||
- job: 'PyTest'
|
# Find offending deps with `pipdeptree -r -p typing`
|
||||||
pool:
|
pip uninstall -y typing
|
||||||
vmImage: 'ubuntu-latest'
|
- script: |
|
||||||
strategy:
|
. venv/bin/activate
|
||||||
maxParallel: 3
|
pip install -e .
|
||||||
matrix:
|
displayName: "Install Home Assistant"
|
||||||
Python37:
|
- script: |
|
||||||
python.container: '37'
|
set -e
|
||||||
container: $[ variables['python.container'] ]
|
|
||||||
steps:
|
|
||||||
- template: templates/azp-step-cache.yaml@azure
|
|
||||||
parameters:
|
|
||||||
keyfile: 'requirements_test_all.txt | homeassistant/package_constraints.txt'
|
|
||||||
build: |
|
|
||||||
set -e
|
|
||||||
python -m venv venv
|
|
||||||
|
|
||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
pip install -U pip setuptools pytest-azurepipelines pytest-xdist -c homeassistant/package_constraints.txt
|
pytest --timeout=9 --durations=10 -n auto --dist=loadfile -qq -o console_output_style=count -p no:sugar tests
|
||||||
pip install -r requirements_test_all.txt -c homeassistant/package_constraints.txt
|
script/check_dirty
|
||||||
# This is a TEMP. Eventually we should make sure our 4 dependencies drop typing.
|
displayName: "Run pytest for python $(python.container)"
|
||||||
# Find offending deps with `pipdeptree -r -p typing`
|
condition: and(succeeded(), ne(variables['python.container'], variables['PythonMain']))
|
||||||
pip uninstall -y typing
|
- script: |
|
||||||
- script: |
|
set -e
|
||||||
. venv/bin/activate
|
|
||||||
pip install -e .
|
|
||||||
displayName: 'Install Home Assistant'
|
|
||||||
- script: |
|
|
||||||
set -e
|
|
||||||
|
|
||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
pytest --timeout=9 --durations=10 -n auto --dist=loadfile -qq -o console_output_style=count -p no:sugar tests
|
pytest --timeout=9 --durations=10 -n auto --dist=loadfile --cov homeassistant --cov-report html -qq -o console_output_style=count -p no:sugar tests
|
||||||
script/check_dirty
|
codecov --token $(codecovToken)
|
||||||
displayName: 'Run pytest for python $(python.container)'
|
script/check_dirty
|
||||||
condition: and(succeeded(), ne(variables['python.container'], variables['PythonMain']))
|
displayName: "Run pytest for python $(python.container) / coverage"
|
||||||
- script: |
|
condition: and(succeeded(), eq(variables['python.container'], variables['PythonMain']))
|
||||||
set -e
|
|
||||||
|
|
||||||
. venv/bin/activate
|
- stage: "FullCheck"
|
||||||
pytest --timeout=9 --durations=10 -n auto --dist=loadfile --cov homeassistant --cov-report html -qq -o console_output_style=count -p no:sugar tests
|
dependsOn:
|
||||||
codecov --token $(codecovToken)
|
- "Overview"
|
||||||
script/check_dirty
|
jobs:
|
||||||
displayName: 'Run pytest for python $(python.container) / coverage'
|
- job: "Pylint"
|
||||||
condition: and(succeeded(), eq(variables['python.container'], variables['PythonMain']))
|
pool:
|
||||||
|
vmImage: "ubuntu-latest"
|
||||||
|
container: $[ variables['PythonMain'] ]
|
||||||
|
steps:
|
||||||
|
- template: templates/azp-step-cache.yaml@azure
|
||||||
|
parameters:
|
||||||
|
keyfile: "requirements_all.txt | requirements_test.txt | homeassistant/package_constraints.txt"
|
||||||
|
build: |
|
||||||
|
set -e
|
||||||
|
python -m venv venv
|
||||||
|
|
||||||
- stage: 'FullCheck'
|
. venv/bin/activate
|
||||||
dependsOn:
|
pip install -U pip setuptools wheel
|
||||||
- 'Overview'
|
pip install -r requirements_all.txt -c homeassistant/package_constraints.txt
|
||||||
jobs:
|
pip install -r requirements_test.txt -c homeassistant/package_constraints.txt
|
||||||
- job: 'Pylint'
|
# This is a TEMP. Eventually we should make sure our 4 dependencies drop typing.
|
||||||
pool:
|
# Find offending deps with `pipdeptree -r -p typing`
|
||||||
vmImage: 'ubuntu-latest'
|
pip uninstall -y typing
|
||||||
container: $[ variables['PythonMain'] ]
|
- script: |
|
||||||
steps:
|
. venv/bin/activate
|
||||||
- template: templates/azp-step-cache.yaml@azure
|
pip install -e .
|
||||||
parameters:
|
displayName: "Install Home Assistant"
|
||||||
keyfile: 'requirements_all.txt | requirements_test.txt | homeassistant/package_constraints.txt'
|
- script: |
|
||||||
build: |
|
. venv/bin/activate
|
||||||
set -e
|
pylint homeassistant
|
||||||
python -m venv venv
|
displayName: "Run pylint"
|
||||||
|
- job: "Mypy"
|
||||||
|
pool:
|
||||||
|
vmImage: "ubuntu-latest"
|
||||||
|
container: $[ variables['PythonMain'] ]
|
||||||
|
steps:
|
||||||
|
- template: templates/azp-step-cache.yaml@azure
|
||||||
|
parameters:
|
||||||
|
keyfile: "requirements_test.txt | setup.py | homeassistant/package_constraints.txt"
|
||||||
|
build: |
|
||||||
|
python -m venv venv
|
||||||
|
|
||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
pip install -U pip setuptools wheel
|
pip install -e . -r requirements_test.txt -c homeassistant/package_constraints.txt
|
||||||
pip install -r requirements_all.txt -c homeassistant/package_constraints.txt
|
pre-commit install-hooks
|
||||||
pip install -r requirements_test.txt -c homeassistant/package_constraints.txt
|
- script: |
|
||||||
# This is a TEMP. Eventually we should make sure our 4 dependencies drop typing.
|
. venv/bin/activate
|
||||||
# Find offending deps with `pipdeptree -r -p typing`
|
pre-commit run mypy --all-files
|
||||||
pip uninstall -y typing
|
displayName: "Run mypy"
|
||||||
- script: |
|
|
||||||
. venv/bin/activate
|
|
||||||
pip install -e .
|
|
||||||
displayName: 'Install Home Assistant'
|
|
||||||
- script: |
|
|
||||||
. venv/bin/activate
|
|
||||||
pylint homeassistant
|
|
||||||
displayName: 'Run pylint'
|
|
||||||
- job: 'Mypy'
|
|
||||||
pool:
|
|
||||||
vmImage: 'ubuntu-latest'
|
|
||||||
container: $[ variables['PythonMain'] ]
|
|
||||||
steps:
|
|
||||||
- template: templates/azp-step-cache.yaml@azure
|
|
||||||
parameters:
|
|
||||||
keyfile: 'requirements_test.txt | setup.py | homeassistant/package_constraints.txt'
|
|
||||||
build: |
|
|
||||||
python -m venv venv
|
|
||||||
|
|
||||||
. venv/bin/activate
|
|
||||||
pip install -e . -r requirements_test.txt -c homeassistant/package_constraints.txt
|
|
||||||
pre-commit install-hooks
|
|
||||||
- script: |
|
|
||||||
. venv/bin/activate
|
|
||||||
pre-commit run mypy --all-files
|
|
||||||
displayName: 'Run mypy'
|
|
||||||
|
@ -8,6 +8,8 @@ import sys
|
|||||||
import threading
|
import threading
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
|
import yarl
|
||||||
|
|
||||||
from homeassistant.const import REQUIRED_PYTHON_VER, RESTART_EXIT_CODE, __version__
|
from homeassistant.const import REQUIRED_PYTHON_VER, RESTART_EXIT_CODE, __version__
|
||||||
|
|
||||||
|
|
||||||
@ -256,10 +258,17 @@ async def setup_and_run_hass(config_dir: str, args: argparse.Namespace) -> int:
|
|||||||
if hass is None:
|
if hass is None:
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
if args.open_ui and hass.config.api is not None:
|
if args.open_ui:
|
||||||
import webbrowser # pylint: disable=import-outside-toplevel
|
import webbrowser # pylint: disable=import-outside-toplevel
|
||||||
|
|
||||||
hass.add_job(webbrowser.open, hass.config.api.base_url)
|
if hass.config.api is not None:
|
||||||
|
scheme = "https" if hass.config.api.use_ssl else "http"
|
||||||
|
url = str(
|
||||||
|
yarl.URL.build(
|
||||||
|
scheme=scheme, host="127.0.0.1", port=hass.config.api.port
|
||||||
|
)
|
||||||
|
)
|
||||||
|
hass.add_job(webbrowser.open, url)
|
||||||
|
|
||||||
return await hass.async_run()
|
return await hass.async_run()
|
||||||
|
|
||||||
|
@ -61,8 +61,7 @@ class CommandLineAuthProvider(AuthProvider):
|
|||||||
"""Validate a username and password."""
|
"""Validate a username and password."""
|
||||||
env = {"username": username, "password": password}
|
env = {"username": username, "password": password}
|
||||||
try:
|
try:
|
||||||
# pylint: disable=no-member
|
process = await asyncio.subprocess.create_subprocess_exec( # pylint: disable=no-member
|
||||||
process = await asyncio.subprocess.create_subprocess_exec(
|
|
||||||
self.config[CONF_COMMAND],
|
self.config[CONF_COMMAND],
|
||||||
*self.config[CONF_ARGS],
|
*self.config[CONF_ARGS],
|
||||||
env=env,
|
env=env,
|
||||||
|
@ -138,8 +138,9 @@ class Data:
|
|||||||
if not bcrypt.checkpw(password.encode(), user_hash):
|
if not bcrypt.checkpw(password.encode(), user_hash):
|
||||||
raise InvalidAuth
|
raise InvalidAuth
|
||||||
|
|
||||||
# pylint: disable=no-self-use
|
def hash_password( # pylint: disable=no-self-use
|
||||||
def hash_password(self, password: str, for_storage: bool = False) -> bytes:
|
self, password: str, for_storage: bool = False
|
||||||
|
) -> bytes:
|
||||||
"""Encode a password."""
|
"""Encode a password."""
|
||||||
hashed: bytes = bcrypt.hashpw(password.encode(), bcrypt.gensalt(rounds=12))
|
hashed: bytes = bcrypt.hashpw(password.encode(), bcrypt.gensalt(rounds=12))
|
||||||
|
|
||||||
|
@ -249,6 +249,10 @@ def async_enable_logging(
|
|||||||
logging.getLogger("urllib3").setLevel(logging.WARNING)
|
logging.getLogger("urllib3").setLevel(logging.WARNING)
|
||||||
logging.getLogger("aiohttp.access").setLevel(logging.WARNING)
|
logging.getLogger("aiohttp.access").setLevel(logging.WARNING)
|
||||||
|
|
||||||
|
sys.excepthook = lambda *args: logging.getLogger(None).exception(
|
||||||
|
"Uncaught exception", exc_info=args # type: ignore
|
||||||
|
)
|
||||||
|
|
||||||
# Log errors to a file if we have write access to file or config dir
|
# Log errors to a file if we have write access to file or config dir
|
||||||
if log_file is None:
|
if log_file is None:
|
||||||
err_log_path = hass.config.path(ERROR_LOG_FILENAME)
|
err_log_path = hass.config.path(ERROR_LOG_FILENAME)
|
||||||
|
@ -25,7 +25,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class AbodeAlarm(AbodeDevice, alarm.AlarmControlPanel):
|
class AbodeAlarm(AbodeDevice, alarm.AlarmControlPanelEntity):
|
||||||
"""An alarm_control_panel implementation for Abode."""
|
"""An alarm_control_panel implementation for Abode."""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -3,7 +3,7 @@ import abodepy.helpers.constants as CONST
|
|||||||
|
|
||||||
from homeassistant.components.binary_sensor import (
|
from homeassistant.components.binary_sensor import (
|
||||||
DEVICE_CLASS_WINDOW,
|
DEVICE_CLASS_WINDOW,
|
||||||
BinarySensorDevice,
|
BinarySensorEntity,
|
||||||
)
|
)
|
||||||
|
|
||||||
from . import AbodeDevice
|
from . import AbodeDevice
|
||||||
@ -30,7 +30,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
|||||||
async_add_entities(entities)
|
async_add_entities(entities)
|
||||||
|
|
||||||
|
|
||||||
class AbodeBinarySensor(AbodeDevice, BinarySensorDevice):
|
class AbodeBinarySensor(AbodeDevice, BinarySensorEntity):
|
||||||
"""A binary sensor implementation for Abode device."""
|
"""A binary sensor implementation for Abode device."""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
"""Support for Abode Security System covers."""
|
"""Support for Abode Security System covers."""
|
||||||
import abodepy.helpers.constants as CONST
|
import abodepy.helpers.constants as CONST
|
||||||
|
|
||||||
from homeassistant.components.cover import CoverDevice
|
from homeassistant.components.cover import CoverEntity
|
||||||
|
|
||||||
from . import AbodeDevice
|
from . import AbodeDevice
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
@ -19,7 +19,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
|||||||
async_add_entities(entities)
|
async_add_entities(entities)
|
||||||
|
|
||||||
|
|
||||||
class AbodeCover(AbodeDevice, CoverDevice):
|
class AbodeCover(AbodeDevice, CoverEntity):
|
||||||
"""Representation of an Abode cover."""
|
"""Representation of an Abode cover."""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -10,7 +10,7 @@ from homeassistant.components.light import (
|
|||||||
SUPPORT_BRIGHTNESS,
|
SUPPORT_BRIGHTNESS,
|
||||||
SUPPORT_COLOR,
|
SUPPORT_COLOR,
|
||||||
SUPPORT_COLOR_TEMP,
|
SUPPORT_COLOR_TEMP,
|
||||||
Light,
|
LightEntity,
|
||||||
)
|
)
|
||||||
from homeassistant.util.color import (
|
from homeassistant.util.color import (
|
||||||
color_temperature_kelvin_to_mired,
|
color_temperature_kelvin_to_mired,
|
||||||
@ -33,7 +33,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
|||||||
async_add_entities(entities)
|
async_add_entities(entities)
|
||||||
|
|
||||||
|
|
||||||
class AbodeLight(AbodeDevice, Light):
|
class AbodeLight(AbodeDevice, LightEntity):
|
||||||
"""Representation of an Abode light."""
|
"""Representation of an Abode light."""
|
||||||
|
|
||||||
def turn_on(self, **kwargs):
|
def turn_on(self, **kwargs):
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
"""Support for the Abode Security System locks."""
|
"""Support for the Abode Security System locks."""
|
||||||
import abodepy.helpers.constants as CONST
|
import abodepy.helpers.constants as CONST
|
||||||
|
|
||||||
from homeassistant.components.lock import LockDevice
|
from homeassistant.components.lock import LockEntity
|
||||||
|
|
||||||
from . import AbodeDevice
|
from . import AbodeDevice
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
@ -19,7 +19,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
|||||||
async_add_entities(entities)
|
async_add_entities(entities)
|
||||||
|
|
||||||
|
|
||||||
class AbodeLock(AbodeDevice, LockDevice):
|
class AbodeLock(AbodeDevice, LockEntity):
|
||||||
"""Representation of an Abode lock."""
|
"""Representation of an Abode lock."""
|
||||||
|
|
||||||
def lock(self, **kwargs):
|
def lock(self, **kwargs):
|
||||||
|
@ -3,7 +3,10 @@
|
|||||||
"step": {
|
"step": {
|
||||||
"user": {
|
"user": {
|
||||||
"title": "Fill in your Abode login information",
|
"title": "Fill in your Abode login information",
|
||||||
"data": { "username": "Email Address", "password": "Password" }
|
"data": {
|
||||||
|
"username": "[%key:common::config_flow::data::email%]",
|
||||||
|
"password": "[%key:common::config_flow::data::password%]"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
"""Support for Abode Security System switches."""
|
"""Support for Abode Security System switches."""
|
||||||
import abodepy.helpers.constants as CONST
|
import abodepy.helpers.constants as CONST
|
||||||
|
|
||||||
from homeassistant.components.switch import SwitchDevice
|
from homeassistant.components.switch import SwitchEntity
|
||||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||||
|
|
||||||
from . import AbodeAutomation, AbodeDevice
|
from . import AbodeAutomation, AbodeDevice
|
||||||
@ -28,7 +28,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
|||||||
async_add_entities(entities)
|
async_add_entities(entities)
|
||||||
|
|
||||||
|
|
||||||
class AbodeSwitch(AbodeDevice, SwitchDevice):
|
class AbodeSwitch(AbodeDevice, SwitchEntity):
|
||||||
"""Representation of an Abode switch."""
|
"""Representation of an Abode switch."""
|
||||||
|
|
||||||
def turn_on(self, **kwargs):
|
def turn_on(self, **kwargs):
|
||||||
@ -45,7 +45,7 @@ class AbodeSwitch(AbodeDevice, SwitchDevice):
|
|||||||
return self._device.is_on
|
return self._device.is_on
|
||||||
|
|
||||||
|
|
||||||
class AbodeAutomationSwitch(AbodeAutomation, SwitchDevice):
|
class AbodeAutomationSwitch(AbodeAutomation, SwitchEntity):
|
||||||
"""A switch implementation for Abode automations."""
|
"""A switch implementation for Abode automations."""
|
||||||
|
|
||||||
async def async_added_to_hass(self):
|
async def async_added_to_hass(self):
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
"user": {
|
"user": {
|
||||||
"data": {
|
"data": {
|
||||||
"password": "Password",
|
"password": "Password",
|
||||||
"username": "Email Address"
|
"username": "Email"
|
||||||
},
|
},
|
||||||
"title": "Fill in your Abode login information"
|
"title": "Fill in your Abode login information"
|
||||||
}
|
}
|
||||||
|
7
homeassistant/components/abode/translations/fi.json
Normal file
7
homeassistant/components/abode/translations/fi.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"error": {
|
||||||
|
"connection_error": "Yhteytt\u00e4 Abodeen ei voi muodostaa."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -12,9 +12,9 @@
|
|||||||
"user": {
|
"user": {
|
||||||
"data": {
|
"data": {
|
||||||
"password": "\ube44\ubc00\ubc88\ud638",
|
"password": "\ube44\ubc00\ubc88\ud638",
|
||||||
"username": "\uc774\uba54\uc77c \uc8fc\uc18c"
|
"username": "\uc774\uba54\uc77c"
|
||||||
},
|
},
|
||||||
"title": "Abode \uc0ac\uc6a9\uc790 \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694"
|
"title": "Abode \uc0ac\uc6a9\uc790 \uc815\ubcf4 \uc785\ub825\ud558\uae30"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
"error": {
|
"error": {
|
||||||
"connection_error": "Kan ikke koble til Abode.",
|
"connection_error": "Kan ikke koble til Abode.",
|
||||||
"identifier_exists": "Kontoen er allerede registrert.",
|
"identifier_exists": "Kontoen er allerede registrert.",
|
||||||
"invalid_credentials": "Ugyldig brukerinformasjon"
|
"invalid_credentials": "Ugyldig legitimasjon"
|
||||||
},
|
},
|
||||||
"step": {
|
"step": {
|
||||||
"user": {
|
"user": {
|
||||||
|
@ -11,8 +11,8 @@
|
|||||||
"step": {
|
"step": {
|
||||||
"user": {
|
"user": {
|
||||||
"data": {
|
"data": {
|
||||||
"password": "Has\u0142o",
|
"password": "[%key_id:common::config_flow::data::password%]",
|
||||||
"username": "Adres e-mail"
|
"username": "[%key_id:common::config_flow::data::email%]"
|
||||||
},
|
},
|
||||||
"title": "Wprowad\u017a informacje logowania Abode"
|
"title": "Wprowad\u017a informacje logowania Abode"
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
"user": {
|
"user": {
|
||||||
"data": {
|
"data": {
|
||||||
"password": "\u5bc6\u78bc",
|
"password": "\u5bc6\u78bc",
|
||||||
"username": "\u96fb\u5b50\u90f5\u4ef6\u5730\u5740"
|
"username": "\u96fb\u5b50\u90f5\u4ef6"
|
||||||
},
|
},
|
||||||
"title": "\u586b\u5beb Abode \u767b\u5165\u8cc7\u8a0a"
|
"title": "\u586b\u5beb Abode \u767b\u5165\u8cc7\u8a0a"
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,6 @@
|
|||||||
"domain": "acer_projector",
|
"domain": "acer_projector",
|
||||||
"name": "Acer Projector",
|
"name": "Acer Projector",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/acer_projector",
|
"documentation": "https://www.home-assistant.io/integrations/acer_projector",
|
||||||
"requirements": ["pyserial==3.1.1"],
|
"requirements": ["pyserial==3.4"],
|
||||||
"codeowners": []
|
"codeowners": []
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ import re
|
|||||||
import serial
|
import serial
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice
|
from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchEntity
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_FILENAME,
|
CONF_FILENAME,
|
||||||
CONF_NAME,
|
CONF_NAME,
|
||||||
@ -69,7 +69,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
|||||||
add_entities([AcerSwitch(serial_port, name, timeout, write_timeout)], True)
|
add_entities([AcerSwitch(serial_port, name, timeout, write_timeout)], True)
|
||||||
|
|
||||||
|
|
||||||
class AcerSwitch(SwitchDevice):
|
class AcerSwitch(SwitchEntity):
|
||||||
"""Represents an Acer Projector as a switch."""
|
"""Represents an Acer Projector as a switch."""
|
||||||
|
|
||||||
def __init__(self, serial_port, name, timeout, write_timeout, **kwargs):
|
def __init__(self, serial_port, name, timeout, write_timeout, **kwargs):
|
||||||
|
@ -206,4 +206,5 @@ class AdGuardHomeDeviceEntity(AdGuardHomeEntity):
|
|||||||
"name": "AdGuard Home",
|
"name": "AdGuard Home",
|
||||||
"manufacturer": "AdGuard Team",
|
"manufacturer": "AdGuard Team",
|
||||||
"sw_version": self.hass.data[DOMAIN].get(DATA_ADGUARD_VERION),
|
"sw_version": self.hass.data[DOMAIN].get(DATA_ADGUARD_VERION),
|
||||||
|
"entry_type": "service",
|
||||||
}
|
}
|
||||||
|
@ -4,10 +4,10 @@
|
|||||||
"user": {
|
"user": {
|
||||||
"description": "Set up your AdGuard Home instance to allow monitoring and control.",
|
"description": "Set up your AdGuard Home instance to allow monitoring and control.",
|
||||||
"data": {
|
"data": {
|
||||||
"host": "Host",
|
"host": "[%key:common::config_flow::data::host%]",
|
||||||
"password": "Password",
|
"password": "[%key:common::config_flow::data::password%]",
|
||||||
"port": "Port",
|
"port": "[%key:common::config_flow::data::port%]",
|
||||||
"username": "Username",
|
"username": "[%key:common::config_flow::data::username%]",
|
||||||
"ssl": "AdGuard Home uses a SSL certificate",
|
"ssl": "AdGuard Home uses a SSL certificate",
|
||||||
"verify_ssl": "AdGuard Home uses a proper certificate"
|
"verify_ssl": "AdGuard Home uses a proper certificate"
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ from homeassistant.components.adguard.const import (
|
|||||||
DATA_ADGUARD_VERION,
|
DATA_ADGUARD_VERION,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
)
|
)
|
||||||
from homeassistant.components.switch import SwitchDevice
|
from homeassistant.components.switch import SwitchEntity
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.exceptions import PlatformNotReady
|
from homeassistant.exceptions import PlatformNotReady
|
||||||
from homeassistant.helpers.typing import HomeAssistantType
|
from homeassistant.helpers.typing import HomeAssistantType
|
||||||
@ -45,7 +45,7 @@ async def async_setup_entry(
|
|||||||
async_add_entities(switches, True)
|
async_add_entities(switches, True)
|
||||||
|
|
||||||
|
|
||||||
class AdGuardHomeSwitch(AdGuardHomeDeviceEntity, SwitchDevice):
|
class AdGuardHomeSwitch(AdGuardHomeDeviceEntity, SwitchEntity):
|
||||||
"""Defines a AdGuard Home switch."""
|
"""Defines a AdGuard Home switch."""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
|
@ -16,9 +16,7 @@
|
|||||||
},
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"data": {
|
"data": {
|
||||||
"host": "\u0410\u0434\u0440\u0435\u0441",
|
|
||||||
"password": "\u041f\u0430\u0440\u043e\u043b\u0430",
|
"password": "\u041f\u0430\u0440\u043e\u043b\u0430",
|
||||||
"port": "\u041f\u043e\u0440\u0442",
|
|
||||||
"ssl": "AdGuard Home \u0438\u0437\u043f\u043e\u043b\u0437\u0432\u0430 SSL \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442",
|
"ssl": "AdGuard Home \u0438\u0437\u043f\u043e\u043b\u0437\u0432\u0430 SSL \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442",
|
||||||
"username": "\u041f\u043e\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043b\u0441\u043a\u043e \u0438\u043c\u0435",
|
"username": "\u041f\u043e\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043b\u0441\u043a\u043e \u0438\u043c\u0435",
|
||||||
"verify_ssl": "AdGuard Home \u0438\u0437\u043f\u043e\u043b\u0437\u0432\u0430 \u043d\u0430\u0434\u0435\u0436\u0434\u0435\u043d \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442"
|
"verify_ssl": "AdGuard Home \u0438\u0437\u043f\u043e\u043b\u0437\u0432\u0430 \u043d\u0430\u0434\u0435\u0436\u0434\u0435\u043d \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442"
|
||||||
|
@ -16,11 +16,11 @@
|
|||||||
},
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"data": {
|
"data": {
|
||||||
"host": "Amfitri\u00f3",
|
"host": "[%key::common::config_flow::data::host%]",
|
||||||
"password": "Contrasenya",
|
"password": "[%key::common::config_flow::data::password%]",
|
||||||
"port": "Port",
|
"port": "[%key::common::config_flow::data::port%]",
|
||||||
"ssl": "AdGuard Home utilitza un certificat SSL",
|
"ssl": "AdGuard Home utilitza un certificat SSL",
|
||||||
"username": "Nom d'usuari",
|
"username": "[%key::common::config_flow::data::username%]",
|
||||||
"verify_ssl": "AdGuard Home utilitza un certificat adequat"
|
"verify_ssl": "AdGuard Home utilitza un certificat adequat"
|
||||||
},
|
},
|
||||||
"description": "Configuraci\u00f3 de la inst\u00e0ncia d'AdGuard Home, permet el control i la monitoritzaci\u00f3.",
|
"description": "Configuraci\u00f3 de la inst\u00e0ncia d'AdGuard Home, permet el control i la monitoritzaci\u00f3.",
|
||||||
|
@ -16,9 +16,7 @@
|
|||||||
},
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"data": {
|
"data": {
|
||||||
"host": "V\u00e6rt",
|
|
||||||
"password": "Adgangskode",
|
"password": "Adgangskode",
|
||||||
"port": "Port",
|
|
||||||
"ssl": "AdGuard Home bruger et SSL-certifikat",
|
"ssl": "AdGuard Home bruger et SSL-certifikat",
|
||||||
"username": "Brugernavn",
|
"username": "Brugernavn",
|
||||||
"verify_ssl": "AdGuard Home bruger et korrekt certifikat"
|
"verify_ssl": "AdGuard Home bruger et korrekt certifikat"
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
"user": {
|
"user": {
|
||||||
"data": {
|
"data": {
|
||||||
"password": "Contrase\u00f1a",
|
"password": "Contrase\u00f1a",
|
||||||
"port": "Puerto",
|
|
||||||
"ssl": "AdGuard Home utiliza un certificado SSL",
|
"ssl": "AdGuard Home utiliza un certificado SSL",
|
||||||
"username": "Nombre de usuario",
|
"username": "Nombre de usuario",
|
||||||
"verify_ssl": "AdGuard Home utiliza un certificado adecuado"
|
"verify_ssl": "AdGuard Home utiliza un certificado adecuado"
|
||||||
|
15
homeassistant/components/adguard/translations/fi.json
Normal file
15
homeassistant/components/adguard/translations/fi.json
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"error": {
|
||||||
|
"connection_error": "Yhdist\u00e4minen ep\u00e4onnistui."
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"host": "Palvelin",
|
||||||
|
"port": "Portti"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
12
homeassistant/components/adguard/translations/he.json
Normal file
12
homeassistant/components/adguard/translations/he.json
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"host": "Host",
|
||||||
|
"port": "\u05e4\u05d5\u05e8\u05d8"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -6,8 +6,7 @@
|
|||||||
"step": {
|
"step": {
|
||||||
"user": {
|
"user": {
|
||||||
"data": {
|
"data": {
|
||||||
"password": "Kata sandi",
|
"password": "Kata sandi"
|
||||||
"port": "Port"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
"verify_ssl": "AdGuard Home \uc740 \uc62c\ubc14\ub978 \uc778\uc99d\uc11c\ub97c \uc0ac\uc6a9\ud558\uace0 \uc788\uc2b5\ub2c8\ub2e4"
|
"verify_ssl": "AdGuard Home \uc740 \uc62c\ubc14\ub978 \uc778\uc99d\uc11c\ub97c \uc0ac\uc6a9\ud558\uace0 \uc788\uc2b5\ub2c8\ub2e4"
|
||||||
},
|
},
|
||||||
"description": "\ubaa8\ub2c8\ud130\ub9c1 \ubc0f \uc81c\uc5b4\uac00 \uac00\ub2a5\ud558\ub3c4\ub85d AdGuard Home \uc778\uc2a4\ud134\uc2a4\ub97c \uc124\uc815\ud574\uc8fc\uc138\uc694.",
|
"description": "\ubaa8\ub2c8\ud130\ub9c1 \ubc0f \uc81c\uc5b4\uac00 \uac00\ub2a5\ud558\ub3c4\ub85d AdGuard Home \uc778\uc2a4\ud134\uc2a4\ub97c \uc124\uc815\ud574\uc8fc\uc138\uc694.",
|
||||||
"title": "AdGuard Home \uc5f0\uacb0"
|
"title": "AdGuard Home \uc5f0\uacb0\ud558\uae30"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
},
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"data": {
|
"data": {
|
||||||
"host": "Apparat",
|
"host": "Host",
|
||||||
"password": "Passwuert",
|
"password": "Passwuert",
|
||||||
"port": "Port",
|
"port": "Port",
|
||||||
"ssl": "AdGuard Home benotzt een SSL Zertifikat",
|
"ssl": "AdGuard Home benotzt een SSL Zertifikat",
|
||||||
|
@ -16,9 +16,7 @@
|
|||||||
},
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"data": {
|
"data": {
|
||||||
"host": "Host",
|
|
||||||
"password": "Wachtwoord",
|
"password": "Wachtwoord",
|
||||||
"port": "Poort",
|
|
||||||
"ssl": "AdGuard Home maakt gebruik van een SSL certificaat",
|
"ssl": "AdGuard Home maakt gebruik van een SSL certificaat",
|
||||||
"username": "Gebruikersnaam",
|
"username": "Gebruikersnaam",
|
||||||
"verify_ssl": "AdGuard Home maakt gebruik van een goed certificaat"
|
"verify_ssl": "AdGuard Home maakt gebruik van een goed certificaat"
|
||||||
|
@ -17,10 +17,8 @@
|
|||||||
"user": {
|
"user": {
|
||||||
"data": {
|
"data": {
|
||||||
"host": "Vert",
|
"host": "Vert",
|
||||||
"password": "Passord",
|
|
||||||
"port": "",
|
"port": "",
|
||||||
"ssl": "AdGuard Hjem bruker et SSL-sertifikat",
|
"ssl": "AdGuard Hjem bruker et SSL-sertifikat",
|
||||||
"username": "Brukernavn",
|
|
||||||
"verify_ssl": "AdGuard Home bruker et riktig sertifikat"
|
"verify_ssl": "AdGuard Home bruker et riktig sertifikat"
|
||||||
},
|
},
|
||||||
"description": "Sett opp din AdGuard Hjem instans for \u00e5 tillate overv\u00e5king og kontroll.",
|
"description": "Sett opp din AdGuard Hjem instans for \u00e5 tillate overv\u00e5king og kontroll.",
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
"single_instance_allowed": "Dozwolona jest tylko jedna konfiguracja AdGuard Home."
|
"single_instance_allowed": "Dozwolona jest tylko jedna konfiguracja AdGuard Home."
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"connection_error": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia."
|
"connection_error": "[%key_id:common::config_flow::error::cannot_connect%]"
|
||||||
},
|
},
|
||||||
"step": {
|
"step": {
|
||||||
"hassio_confirm": {
|
"hassio_confirm": {
|
||||||
@ -16,11 +16,11 @@
|
|||||||
},
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"data": {
|
"data": {
|
||||||
"host": "Nazwa hosta lub adres IP",
|
"host": "[%key_id:common::config_flow::data::host%]",
|
||||||
"password": "Has\u0142o",
|
"password": "[%key_id:common::config_flow::data::password%]",
|
||||||
"port": "Port",
|
"port": "[%key_id:common::config_flow::data::port%]",
|
||||||
"ssl": "AdGuard Home u\u017cywa certyfikatu SSL",
|
"ssl": "AdGuard Home u\u017cywa certyfikatu SSL",
|
||||||
"username": "Nazwa u\u017cytkownika",
|
"username": "[%key_id:common::config_flow::data::username%]",
|
||||||
"verify_ssl": "AdGuard Home u\u017cywa odpowiedniego certyfikatu."
|
"verify_ssl": "AdGuard Home u\u017cywa odpowiedniego certyfikatu."
|
||||||
},
|
},
|
||||||
"description": "Skonfiguruj instancj\u0119 AdGuard Home, aby umo\u017cliwi\u0107 monitorowanie i kontrol\u0119.",
|
"description": "Skonfiguruj instancj\u0119 AdGuard Home, aby umo\u017cliwi\u0107 monitorowanie i kontrol\u0119.",
|
||||||
|
@ -14,9 +14,7 @@
|
|||||||
},
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"data": {
|
"data": {
|
||||||
"host": "Host",
|
|
||||||
"password": "Senha",
|
"password": "Senha",
|
||||||
"port": "Porta",
|
|
||||||
"ssl": "O AdGuard Home usa um certificado SSL",
|
"ssl": "O AdGuard Home usa um certificado SSL",
|
||||||
"username": "Nome de usu\u00e1rio",
|
"username": "Nome de usu\u00e1rio",
|
||||||
"verify_ssl": "O AdGuard Home usa um certificado apropriado"
|
"verify_ssl": "O AdGuard Home usa um certificado apropriado"
|
||||||
|
@ -3,9 +3,7 @@
|
|||||||
"step": {
|
"step": {
|
||||||
"user": {
|
"user": {
|
||||||
"data": {
|
"data": {
|
||||||
"host": "Servidor",
|
|
||||||
"password": "Palavra-passe",
|
"password": "Palavra-passe",
|
||||||
"port": "Porta",
|
|
||||||
"username": "Nome de Utilizador"
|
"username": "Nome de Utilizador"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
"username": "\u041b\u043e\u0433\u0438\u043d",
|
"username": "\u041b\u043e\u0433\u0438\u043d",
|
||||||
"verify_ssl": "AdGuard Home \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0439 \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442"
|
"verify_ssl": "AdGuard Home \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0439 \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442"
|
||||||
},
|
},
|
||||||
"description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u0442\u0435 \u044d\u0442\u043e\u0442 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u0434\u043b\u044f \u043c\u043e\u043d\u0438\u0442\u043e\u0440\u0438\u043d\u0433\u0430 \u0438 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u044f AdGuard Home.",
|
"description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u043c\u043e\u043d\u0438\u0442\u043e\u0440\u0438\u043d\u0433\u0430 \u0438 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u044f AdGuard Home.",
|
||||||
"title": "AdGuard Home"
|
"title": "AdGuard Home"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,9 +16,7 @@
|
|||||||
},
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"data": {
|
"data": {
|
||||||
"host": "V\u00e4rd",
|
|
||||||
"password": "L\u00f6senord",
|
"password": "L\u00f6senord",
|
||||||
"port": "Port",
|
|
||||||
"ssl": "AdGuard Home anv\u00e4nder ett SSL-certifikat",
|
"ssl": "AdGuard Home anv\u00e4nder ett SSL-certifikat",
|
||||||
"username": "Anv\u00e4ndarnamn",
|
"username": "Anv\u00e4ndarnamn",
|
||||||
"verify_ssl": "AdGuard Home anv\u00e4nder ett korrekt certifikat"
|
"verify_ssl": "AdGuard Home anv\u00e4nder ett korrekt certifikat"
|
||||||
|
@ -3,9 +3,7 @@
|
|||||||
"step": {
|
"step": {
|
||||||
"user": {
|
"user": {
|
||||||
"data": {
|
"data": {
|
||||||
"host": "\u0110\u1ecba ch\u1ec9",
|
|
||||||
"password": "M\u1eadt kh\u1ea9u",
|
"password": "M\u1eadt kh\u1ea9u",
|
||||||
"port": "C\u1ed5ng",
|
|
||||||
"username": "T\u00ean \u0111\u0103ng nh\u1eadp"
|
"username": "T\u00ean \u0111\u0103ng nh\u1eadp"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,6 @@
|
|||||||
"user": {
|
"user": {
|
||||||
"data": {
|
"data": {
|
||||||
"password": "\u5bc6\u7801",
|
"password": "\u5bc6\u7801",
|
||||||
"port": "\u7aef\u53e3",
|
|
||||||
"username": "\u7528\u6237\u540d"
|
"username": "\u7528\u6237\u540d"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ import voluptuous as vol
|
|||||||
from homeassistant.components.binary_sensor import (
|
from homeassistant.components.binary_sensor import (
|
||||||
DEVICE_CLASSES_SCHEMA,
|
DEVICE_CLASSES_SCHEMA,
|
||||||
PLATFORM_SCHEMA,
|
PLATFORM_SCHEMA,
|
||||||
BinarySensorDevice,
|
BinarySensorEntity,
|
||||||
)
|
)
|
||||||
from homeassistant.const import CONF_DEVICE_CLASS, CONF_NAME
|
from homeassistant.const import CONF_DEVICE_CLASS, CONF_NAME
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
@ -37,7 +37,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
|||||||
add_entities([ads_sensor])
|
add_entities([ads_sensor])
|
||||||
|
|
||||||
|
|
||||||
class AdsBinarySensor(AdsEntity, BinarySensorDevice):
|
class AdsBinarySensor(AdsEntity, BinarySensorEntity):
|
||||||
"""Representation of ADS binary sensors."""
|
"""Representation of ADS binary sensors."""
|
||||||
|
|
||||||
def __init__(self, ads_hub, name, ads_var, device_class):
|
def __init__(self, ads_hub, name, ads_var, device_class):
|
||||||
|
@ -11,7 +11,7 @@ from homeassistant.components.cover import (
|
|||||||
SUPPORT_OPEN,
|
SUPPORT_OPEN,
|
||||||
SUPPORT_SET_POSITION,
|
SUPPORT_SET_POSITION,
|
||||||
SUPPORT_STOP,
|
SUPPORT_STOP,
|
||||||
CoverDevice,
|
CoverEntity,
|
||||||
)
|
)
|
||||||
from homeassistant.const import CONF_DEVICE_CLASS, CONF_NAME
|
from homeassistant.const import CONF_DEVICE_CLASS, CONF_NAME
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
@ -78,7 +78,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class AdsCover(AdsEntity, CoverDevice):
|
class AdsCover(AdsEntity, CoverEntity):
|
||||||
"""Representation of ADS cover."""
|
"""Representation of ADS cover."""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
|
@ -7,7 +7,7 @@ from homeassistant.components.light import (
|
|||||||
ATTR_BRIGHTNESS,
|
ATTR_BRIGHTNESS,
|
||||||
PLATFORM_SCHEMA,
|
PLATFORM_SCHEMA,
|
||||||
SUPPORT_BRIGHTNESS,
|
SUPPORT_BRIGHTNESS,
|
||||||
Light,
|
LightEntity,
|
||||||
)
|
)
|
||||||
from homeassistant.const import CONF_NAME
|
from homeassistant.const import CONF_NAME
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
@ -43,7 +43,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
|||||||
add_entities([AdsLight(ads_hub, ads_var_enable, ads_var_brightness, name)])
|
add_entities([AdsLight(ads_hub, ads_var_enable, ads_var_brightness, name)])
|
||||||
|
|
||||||
|
|
||||||
class AdsLight(AdsEntity, Light):
|
class AdsLight(AdsEntity, LightEntity):
|
||||||
"""Representation of ADS light."""
|
"""Representation of ADS light."""
|
||||||
|
|
||||||
def __init__(self, ads_hub, ads_var_enable, ads_var_brightness, name):
|
def __init__(self, ads_hub, ads_var_enable, ads_var_brightness, name):
|
||||||
|
@ -3,7 +3,7 @@ import logging
|
|||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice
|
from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchEntity
|
||||||
from homeassistant.const import CONF_NAME
|
from homeassistant.const import CONF_NAME
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
@ -31,7 +31,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
|||||||
add_entities([AdsSwitch(ads_hub, name, ads_var)])
|
add_entities([AdsSwitch(ads_hub, name, ads_var)])
|
||||||
|
|
||||||
|
|
||||||
class AdsSwitch(AdsEntity, SwitchDevice):
|
class AdsSwitch(AdsEntity, SwitchEntity):
|
||||||
"""Representation of an ADS switch device."""
|
"""Representation of an ADS switch device."""
|
||||||
|
|
||||||
async def async_added_to_hass(self):
|
async def async_added_to_hass(self):
|
||||||
|
82
homeassistant/components/agent_dvr/__init__.py
Normal file
82
homeassistant/components/agent_dvr/__init__.py
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
"""Support for Agent."""
|
||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from agent import AgentError
|
||||||
|
from agent.a import Agent
|
||||||
|
|
||||||
|
from homeassistant.exceptions import ConfigEntryNotReady
|
||||||
|
from homeassistant.helpers import device_registry as dr
|
||||||
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
|
|
||||||
|
from .const import CONNECTION, DOMAIN as AGENT_DOMAIN, SERVER_URL
|
||||||
|
|
||||||
|
ATTRIBUTION = "ispyconnect.com"
|
||||||
|
DEFAULT_BRAND = "Agent DVR by ispyconnect.com"
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
FORWARDS = ["camera"]
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup(hass, config):
|
||||||
|
"""Old way to set up integrations."""
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass, config_entry):
|
||||||
|
"""Set up the Agent component."""
|
||||||
|
hass.data.setdefault(AGENT_DOMAIN, {})
|
||||||
|
|
||||||
|
server_origin = config_entry.data[SERVER_URL]
|
||||||
|
|
||||||
|
agent_client = Agent(server_origin, async_get_clientsession(hass))
|
||||||
|
try:
|
||||||
|
await agent_client.update()
|
||||||
|
except AgentError:
|
||||||
|
await agent_client.close()
|
||||||
|
raise ConfigEntryNotReady
|
||||||
|
|
||||||
|
if not agent_client.is_available:
|
||||||
|
raise ConfigEntryNotReady
|
||||||
|
|
||||||
|
await agent_client.get_devices()
|
||||||
|
|
||||||
|
hass.data[AGENT_DOMAIN][config_entry.entry_id] = {CONNECTION: agent_client}
|
||||||
|
|
||||||
|
device_registry = await dr.async_get_registry(hass)
|
||||||
|
|
||||||
|
device_registry.async_get_or_create(
|
||||||
|
config_entry_id=config_entry.entry_id,
|
||||||
|
identifiers={(AGENT_DOMAIN, agent_client.unique)},
|
||||||
|
manufacturer="iSpyConnect",
|
||||||
|
name=f"Agent {agent_client.name}",
|
||||||
|
model="Agent DVR",
|
||||||
|
sw_version=agent_client.version,
|
||||||
|
)
|
||||||
|
|
||||||
|
for forward in FORWARDS:
|
||||||
|
hass.async_create_task(
|
||||||
|
hass.config_entries.async_forward_entry_setup(config_entry, forward)
|
||||||
|
)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def async_unload_entry(hass, config_entry):
|
||||||
|
"""Unload a config entry."""
|
||||||
|
unload_ok = all(
|
||||||
|
await asyncio.gather(
|
||||||
|
*[
|
||||||
|
hass.config_entries.async_forward_entry_unload(config_entry, forward)
|
||||||
|
for forward in FORWARDS
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
await hass.data[AGENT_DOMAIN][config_entry.entry_id][CONNECTION].close()
|
||||||
|
|
||||||
|
if unload_ok:
|
||||||
|
hass.data[AGENT_DOMAIN].pop(config_entry.entry_id)
|
||||||
|
|
||||||
|
return unload_ok
|
215
homeassistant/components/agent_dvr/camera.py
Normal file
215
homeassistant/components/agent_dvr/camera.py
Normal file
@ -0,0 +1,215 @@
|
|||||||
|
"""Support for Agent camera streaming."""
|
||||||
|
from datetime import timedelta
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from agent import AgentError
|
||||||
|
|
||||||
|
from homeassistant.components.camera import SUPPORT_ON_OFF
|
||||||
|
from homeassistant.components.mjpeg.camera import (
|
||||||
|
CONF_MJPEG_URL,
|
||||||
|
CONF_STILL_IMAGE_URL,
|
||||||
|
MjpegCamera,
|
||||||
|
filter_urllib3_logging,
|
||||||
|
)
|
||||||
|
from homeassistant.const import ATTR_ATTRIBUTION, CONF_NAME
|
||||||
|
from homeassistant.helpers import entity_platform
|
||||||
|
|
||||||
|
from .const import (
|
||||||
|
ATTRIBUTION,
|
||||||
|
CAMERA_SCAN_INTERVAL_SECS,
|
||||||
|
CONNECTION,
|
||||||
|
DOMAIN as AGENT_DOMAIN,
|
||||||
|
)
|
||||||
|
|
||||||
|
SCAN_INTERVAL = timedelta(seconds=CAMERA_SCAN_INTERVAL_SECS)
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
_DEV_EN_ALT = "enable_alerts"
|
||||||
|
_DEV_DS_ALT = "disable_alerts"
|
||||||
|
_DEV_EN_REC = "start_recording"
|
||||||
|
_DEV_DS_REC = "stop_recording"
|
||||||
|
_DEV_SNAP = "snapshot"
|
||||||
|
|
||||||
|
CAMERA_SERVICES = {
|
||||||
|
_DEV_EN_ALT: "async_enable_alerts",
|
||||||
|
_DEV_DS_ALT: "async_disable_alerts",
|
||||||
|
_DEV_EN_REC: "async_start_recording",
|
||||||
|
_DEV_DS_REC: "async_stop_recording",
|
||||||
|
_DEV_SNAP: "async_snapshot",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(
|
||||||
|
hass, config_entry, async_add_entities, discovery_info=None
|
||||||
|
):
|
||||||
|
"""Set up the Agent cameras."""
|
||||||
|
filter_urllib3_logging()
|
||||||
|
cameras = []
|
||||||
|
|
||||||
|
server = hass.data[AGENT_DOMAIN][config_entry.entry_id][CONNECTION]
|
||||||
|
if not server.devices:
|
||||||
|
_LOGGER.warning("Could not fetch cameras from Agent server")
|
||||||
|
return
|
||||||
|
|
||||||
|
for device in server.devices:
|
||||||
|
if device.typeID == 2:
|
||||||
|
camera = AgentCamera(device)
|
||||||
|
cameras.append(camera)
|
||||||
|
|
||||||
|
async_add_entities(cameras)
|
||||||
|
|
||||||
|
platform = entity_platform.current_platform.get()
|
||||||
|
for service, method in CAMERA_SERVICES.items():
|
||||||
|
platform.async_register_entity_service(service, {}, method)
|
||||||
|
|
||||||
|
|
||||||
|
class AgentCamera(MjpegCamera):
|
||||||
|
"""Representation of an Agent Device Stream."""
|
||||||
|
|
||||||
|
def __init__(self, device):
|
||||||
|
"""Initialize as a subclass of MjpegCamera."""
|
||||||
|
self._servername = device.client.name
|
||||||
|
self.server_url = device.client._server_url
|
||||||
|
|
||||||
|
device_info = {
|
||||||
|
CONF_NAME: device.name,
|
||||||
|
CONF_MJPEG_URL: f"{self.server_url}{device.mjpeg_image_url}&size=640x480",
|
||||||
|
CONF_STILL_IMAGE_URL: f"{self.server_url}{device.still_image_url}&size=640x480",
|
||||||
|
}
|
||||||
|
self.device = device
|
||||||
|
self._removed = False
|
||||||
|
self._name = f"{self._servername} {device.name}"
|
||||||
|
self._unique_id = f"{device._client.unique}_{device.typeID}_{device.id}"
|
||||||
|
super().__init__(device_info)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device_info(self):
|
||||||
|
"""Return the device info for adding the entity to the agent object."""
|
||||||
|
return {
|
||||||
|
"identifiers": {(AGENT_DOMAIN, self._unique_id)},
|
||||||
|
"name": self._name,
|
||||||
|
"manufacturer": "Agent",
|
||||||
|
"model": "Camera",
|
||||||
|
"sw_version": self.device.client.version,
|
||||||
|
}
|
||||||
|
|
||||||
|
async def async_update(self):
|
||||||
|
"""Update our state from the Agent API."""
|
||||||
|
try:
|
||||||
|
await self.device.update()
|
||||||
|
if self._removed:
|
||||||
|
_LOGGER.debug("%s reacquired", self._name)
|
||||||
|
self._removed = False
|
||||||
|
except AgentError:
|
||||||
|
if self.device.client.is_available: # server still available - camera error
|
||||||
|
if not self._removed:
|
||||||
|
_LOGGER.error("%s lost", self._name)
|
||||||
|
self._removed = True
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device_state_attributes(self):
|
||||||
|
"""Return the Agent DVR camera state attributes."""
|
||||||
|
return {
|
||||||
|
ATTR_ATTRIBUTION: ATTRIBUTION,
|
||||||
|
"editable": False,
|
||||||
|
"enabled": self.is_on,
|
||||||
|
"connected": self.connected,
|
||||||
|
"detected": self.is_detected,
|
||||||
|
"alerted": self.is_alerted,
|
||||||
|
"has_ptz": self.device.has_ptz,
|
||||||
|
"alerts_enabled": self.device.alerts_active,
|
||||||
|
}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def should_poll(self) -> bool:
|
||||||
|
"""Update the state periodically."""
|
||||||
|
return True
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_recording(self) -> bool:
|
||||||
|
"""Return whether the monitor is recording."""
|
||||||
|
return self.device.recording
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_alerted(self) -> bool:
|
||||||
|
"""Return whether the monitor has alerted."""
|
||||||
|
return self.device.alerted
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_detected(self) -> bool:
|
||||||
|
"""Return whether the monitor has alerted."""
|
||||||
|
return self.device.detected
|
||||||
|
|
||||||
|
@property
|
||||||
|
def available(self) -> bool:
|
||||||
|
"""Return True if entity is available."""
|
||||||
|
return self.device.client.is_available
|
||||||
|
|
||||||
|
@property
|
||||||
|
def connected(self) -> bool:
|
||||||
|
"""Return True if entity is connected."""
|
||||||
|
return self.device.connected
|
||||||
|
|
||||||
|
@property
|
||||||
|
def supported_features(self) -> int:
|
||||||
|
"""Return supported features."""
|
||||||
|
return SUPPORT_ON_OFF
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_on(self) -> bool:
|
||||||
|
"""Return true if on."""
|
||||||
|
return self.device.online
|
||||||
|
|
||||||
|
@property
|
||||||
|
def icon(self):
|
||||||
|
"""Return the icon to use in the frontend, if any."""
|
||||||
|
if self.is_on:
|
||||||
|
return "mdi:camcorder"
|
||||||
|
return "mdi:camcorder-off"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def motion_detection_enabled(self):
|
||||||
|
"""Return the camera motion detection status."""
|
||||||
|
return self.device.detector_active
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unique_id(self) -> str:
|
||||||
|
"""Return a unique identifier for this agent object."""
|
||||||
|
return self._unique_id
|
||||||
|
|
||||||
|
async def async_enable_alerts(self):
|
||||||
|
"""Enable alerts."""
|
||||||
|
await self.device.alerts_on()
|
||||||
|
|
||||||
|
async def async_disable_alerts(self):
|
||||||
|
"""Disable alerts."""
|
||||||
|
await self.device.alerts_off()
|
||||||
|
|
||||||
|
async def async_enable_motion_detection(self):
|
||||||
|
"""Enable motion detection."""
|
||||||
|
await self.device.detector_on()
|
||||||
|
|
||||||
|
async def async_disable_motion_detection(self):
|
||||||
|
"""Disable motion detection."""
|
||||||
|
await self.device.detector_off()
|
||||||
|
|
||||||
|
async def async_start_recording(self):
|
||||||
|
"""Start recording."""
|
||||||
|
await self.device.record()
|
||||||
|
|
||||||
|
async def async_stop_recording(self):
|
||||||
|
"""Stop recording."""
|
||||||
|
await self.device.record_stop()
|
||||||
|
|
||||||
|
async def async_turn_on(self):
|
||||||
|
"""Enable the camera."""
|
||||||
|
await self.device.enable()
|
||||||
|
|
||||||
|
async def async_snapshot(self):
|
||||||
|
"""Take a snapshot."""
|
||||||
|
await self.device.snapshot()
|
||||||
|
|
||||||
|
async def async_turn_off(self):
|
||||||
|
"""Disable the camera."""
|
||||||
|
await self.device.disable()
|
81
homeassistant/components/agent_dvr/config_flow.py
Normal file
81
homeassistant/components/agent_dvr/config_flow.py
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
"""Config flow to configure Agent devices."""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from agent import AgentConnectionError, AgentError
|
||||||
|
from agent.a import Agent
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant import config_entries
|
||||||
|
from homeassistant.const import CONF_HOST, CONF_PORT
|
||||||
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
|
|
||||||
|
from .const import DOMAIN, SERVER_URL # pylint:disable=unused-import
|
||||||
|
from .helpers import generate_url
|
||||||
|
|
||||||
|
DEFAULT_PORT = 8090
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class AgentFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
|
"""Handle an Agent config flow."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Initialize the Agent config flow."""
|
||||||
|
self.device_config = {}
|
||||||
|
|
||||||
|
async def async_step_user(self, info=None):
|
||||||
|
"""Handle an Agent config flow."""
|
||||||
|
errors = {}
|
||||||
|
|
||||||
|
if info is not None:
|
||||||
|
host = info[CONF_HOST]
|
||||||
|
port = info[CONF_PORT]
|
||||||
|
|
||||||
|
server_origin = generate_url(host, port)
|
||||||
|
agent_client = Agent(server_origin, async_get_clientsession(self.hass))
|
||||||
|
|
||||||
|
try:
|
||||||
|
await agent_client.update()
|
||||||
|
except AgentConnectionError:
|
||||||
|
pass
|
||||||
|
except AgentError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
await agent_client.close()
|
||||||
|
|
||||||
|
if agent_client.is_available:
|
||||||
|
await self.async_set_unique_id(agent_client.unique)
|
||||||
|
|
||||||
|
self._abort_if_unique_id_configured(
|
||||||
|
updates={
|
||||||
|
CONF_HOST: info[CONF_HOST],
|
||||||
|
CONF_PORT: info[CONF_PORT],
|
||||||
|
SERVER_URL: server_origin,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
self.device_config = {
|
||||||
|
CONF_HOST: host,
|
||||||
|
CONF_PORT: port,
|
||||||
|
SERVER_URL: server_origin,
|
||||||
|
}
|
||||||
|
|
||||||
|
return await self._create_entry(agent_client.name)
|
||||||
|
|
||||||
|
errors["base"] = "device_unavailable"
|
||||||
|
|
||||||
|
data = {
|
||||||
|
vol.Required(CONF_HOST): str,
|
||||||
|
vol.Required(CONF_PORT, default=DEFAULT_PORT): int,
|
||||||
|
}
|
||||||
|
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="user",
|
||||||
|
description_placeholders=self.device_config,
|
||||||
|
data_schema=vol.Schema(data),
|
||||||
|
errors=errors,
|
||||||
|
)
|
||||||
|
|
||||||
|
async def _create_entry(self, server_name):
|
||||||
|
"""Create entry for device."""
|
||||||
|
return self.async_create_entry(title=server_name, data=self.device_config)
|
11
homeassistant/components/agent_dvr/const.py
Normal file
11
homeassistant/components/agent_dvr/const.py
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
"""Constants for agent_dvr component."""
|
||||||
|
DOMAIN = "agent_dvr"
|
||||||
|
SERVERS = "servers"
|
||||||
|
DEVICES = "devices"
|
||||||
|
ENTITIES = "entities"
|
||||||
|
CAMERA_SCAN_INTERVAL_SECS = 5
|
||||||
|
SERVICE_UPDATE = "update"
|
||||||
|
SIGNAL_UPDATE_AGENT = "agent_update"
|
||||||
|
ATTRIBUTION = "Data provided by ispyconnect.com"
|
||||||
|
SERVER_URL = "server_url"
|
||||||
|
CONNECTION = "connection"
|
13
homeassistant/components/agent_dvr/helpers.py
Normal file
13
homeassistant/components/agent_dvr/helpers.py
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
"""Helpers for Agent DVR component."""
|
||||||
|
|
||||||
|
|
||||||
|
def generate_url(host, port) -> str:
|
||||||
|
"""Create a URL from the host and port."""
|
||||||
|
server_origin = host
|
||||||
|
if "://" not in host:
|
||||||
|
server_origin = f"http://{host}"
|
||||||
|
|
||||||
|
if server_origin[-1] == "/":
|
||||||
|
server_origin = server_origin[:-1]
|
||||||
|
|
||||||
|
return f"{server_origin}:{port}/"
|
8
homeassistant/components/agent_dvr/manifest.json
Normal file
8
homeassistant/components/agent_dvr/manifest.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"domain": "agent_dvr",
|
||||||
|
"name": "Agent DVR",
|
||||||
|
"documentation": "https://www.home-assistant.io/integrations/agent_dvr/",
|
||||||
|
"requirements": ["agent-py==0.0.20"],
|
||||||
|
"config_flow": true,
|
||||||
|
"codeowners": ["@ispysoftware"]
|
||||||
|
}
|
34
homeassistant/components/agent_dvr/services.yaml
Normal file
34
homeassistant/components/agent_dvr/services.yaml
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
start_recording:
|
||||||
|
description: Enable continuous recording.
|
||||||
|
fields:
|
||||||
|
entity_id:
|
||||||
|
description: "Name(s) of the entity to start recording."
|
||||||
|
example: "camera.camera_1"
|
||||||
|
|
||||||
|
stop_recording:
|
||||||
|
description: Disable continuous recording.
|
||||||
|
fields:
|
||||||
|
entity_id:
|
||||||
|
description: "Name(s) of the entity to stop recording."
|
||||||
|
example: "camera.camera_1"
|
||||||
|
|
||||||
|
enable_alerts:
|
||||||
|
description: Enable alerts
|
||||||
|
fields:
|
||||||
|
entity_id:
|
||||||
|
description: "Name(s) of the entity to enable alerts."
|
||||||
|
example: "camera.camera_1"
|
||||||
|
|
||||||
|
disable_alerts:
|
||||||
|
description: Disable alerts
|
||||||
|
fields:
|
||||||
|
entity_id:
|
||||||
|
description: "Name(s) of the entity to disable alerts."
|
||||||
|
example: "camera.camera_1"
|
||||||
|
|
||||||
|
snapshot:
|
||||||
|
description: Take a photo
|
||||||
|
fields:
|
||||||
|
entity_id:
|
||||||
|
description: "Name(s) of the entity to take a snapshot."
|
||||||
|
example: "camera.camera_1"
|
21
homeassistant/components/agent_dvr/strings.json
Normal file
21
homeassistant/components/agent_dvr/strings.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"title": "Agent DVR",
|
||||||
|
"config": {
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"title": "Set up Agent DVR",
|
||||||
|
"data": {
|
||||||
|
"host": "[%key:common::config_flow::data::host%]",
|
||||||
|
"port": "[%key:common::config_flow::data::port%]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "Device is already configured"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"already_in_progress": "Config flow for device is already in progress.",
|
||||||
|
"device_unavailable": "Device is not available"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
21
homeassistant/components/agent_dvr/translations/ca.json
Normal file
21
homeassistant/components/agent_dvr/translations/ca.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "Dispositiu ja est\u00e0 configurat"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"already_in_progress": "El flux de dades de configuraci\u00f3 pel dispositiu ja est\u00e0 en curs.",
|
||||||
|
"device_unavailable": "Dispositiu no est\u00e0 disponible"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"host": "Amfitri\u00f3",
|
||||||
|
"port": "Port"
|
||||||
|
},
|
||||||
|
"title": "Configuraci\u00f3 de Agent DVR"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "Agent DVR"
|
||||||
|
}
|
21
homeassistant/components/agent_dvr/translations/de.json
Normal file
21
homeassistant/components/agent_dvr/translations/de.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "Ger\u00e4t ist bereits konfiguriert"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"already_in_progress": "Der Konfigurationsfluss f\u00fcr das Ger\u00e4t wird bereits ausgef\u00fchrt.",
|
||||||
|
"device_unavailable": "Ger\u00e4t ist nicht verf\u00fcgbar"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"host": "Host",
|
||||||
|
"port": "Port"
|
||||||
|
},
|
||||||
|
"title": "Richten Sie den Agent DVR ein"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "Agent DVR"
|
||||||
|
}
|
21
homeassistant/components/agent_dvr/translations/en.json
Normal file
21
homeassistant/components/agent_dvr/translations/en.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "Device is already configured"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"already_in_progress": "Config flow for device is already in progress.",
|
||||||
|
"device_unavailable": "Device is not available"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"host": "Host",
|
||||||
|
"port": "Port"
|
||||||
|
},
|
||||||
|
"title": "Set up Agent DVR"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "Agent DVR"
|
||||||
|
}
|
21
homeassistant/components/agent_dvr/translations/es.json
Normal file
21
homeassistant/components/agent_dvr/translations/es.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "El dispositivo ya est\u00e1 configurado"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"already_in_progress": "La configuraci\u00f3n del flujo para el dispositivo ya est\u00e1 en marcha.",
|
||||||
|
"device_unavailable": "El dispositivo no est\u00e1 disponible"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"host": "Host",
|
||||||
|
"port": "Puerto"
|
||||||
|
},
|
||||||
|
"title": "Configurar el Agente de DVR"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "Agente DVR"
|
||||||
|
}
|
21
homeassistant/components/agent_dvr/translations/fi.json
Normal file
21
homeassistant/components/agent_dvr/translations/fi.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "Laite on jo m\u00e4\u00e4ritetty"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"already_in_progress": "Laitteen m\u00e4\u00e4ritysvirta on jo k\u00e4ynniss\u00e4.",
|
||||||
|
"device_unavailable": "Laite ei ole k\u00e4ytett\u00e4viss\u00e4"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"host": "Palvelin",
|
||||||
|
"port": "Portti"
|
||||||
|
},
|
||||||
|
"title": "Asenna Agent DVR"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "Agent DVR"
|
||||||
|
}
|
20
homeassistant/components/agent_dvr/translations/fr.json
Normal file
20
homeassistant/components/agent_dvr/translations/fr.json
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"already_in_progress": "La configuration de l'appareil est d\u00e9j\u00e0 en cours.",
|
||||||
|
"device_unavailable": "L'appareil n'est pas disponible"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"host": "H\u00f4te",
|
||||||
|
"port": "Port"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "Agent DVR"
|
||||||
|
}
|
18
homeassistant/components/agent_dvr/translations/he.json
Normal file
18
homeassistant/components/agent_dvr/translations/he.json
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "\u05d4\u05de\u05db\u05e9\u05d9\u05e8 \u05db\u05d1\u05e8 \u05de\u05d5\u05d2\u05d3\u05e8"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"device_unavailable": "\u05d4\u05de\u05db\u05e9\u05d9\u05e8 \u05d0\u05d9\u05e0\u05d5 \u05d6\u05de\u05d9\u05df"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"host": "Host",
|
||||||
|
"port": "\u05e4\u05d5\u05e8\u05d8"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
21
homeassistant/components/agent_dvr/translations/it.json
Normal file
21
homeassistant/components/agent_dvr/translations/it.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"already_in_progress": "Il flusso di configurazione per il dispositivo \u00e8 gi\u00e0 in corso.",
|
||||||
|
"device_unavailable": "Il dispositivo non \u00e8 disponibile"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"host": "Host",
|
||||||
|
"port": "Porta"
|
||||||
|
},
|
||||||
|
"title": "Configurare Agent DVR"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "Agente DVR"
|
||||||
|
}
|
21
homeassistant/components/agent_dvr/translations/ko.json
Normal file
21
homeassistant/components/agent_dvr/translations/ko.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4.",
|
||||||
|
"device_unavailable": "\uae30\uae30\ub97c \uc0ac\uc6a9\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"host": "\ud638\uc2a4\ud2b8",
|
||||||
|
"port": "\ud3ec\ud2b8"
|
||||||
|
},
|
||||||
|
"title": "Agent DVR \uc124\uc815\ud558\uae30"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "Agent DVR"
|
||||||
|
}
|
21
homeassistant/components/agent_dvr/translations/lb.json
Normal file
21
homeassistant/components/agent_dvr/translations/lb.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "Apparat ass scho konfigur\u00e9iert"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"already_in_progress": "Konfiguratioun's Oflaf fir den Apparat ass schonn am gaangen.",
|
||||||
|
"device_unavailable": "Apparat ass net erreechbar"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"host": "Apparat",
|
||||||
|
"port": "Port"
|
||||||
|
},
|
||||||
|
"title": "Agent DVR ariichten"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "Agent DVR"
|
||||||
|
}
|
21
homeassistant/components/agent_dvr/translations/no.json
Normal file
21
homeassistant/components/agent_dvr/translations/no.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "Enheten er allerede konfigurert"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"already_in_progress": "Konfigurasjonsflyt for enhet p\u00e5g\u00e5r allerede.",
|
||||||
|
"device_unavailable": "Enheten er ikke tilgjengelig"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"host": "Vert",
|
||||||
|
"port": "Port"
|
||||||
|
},
|
||||||
|
"title": "Konfigurere Agent DVR"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "Agent DVR"
|
||||||
|
}
|
21
homeassistant/components/agent_dvr/translations/pl.json
Normal file
21
homeassistant/components/agent_dvr/translations/pl.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "[%key_id:common::config_flow::abort::already_configured_device%]"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"already_in_progress": "Konfiguracja urz\u0105dzenia jest ju\u017c w toku.",
|
||||||
|
"device_unavailable": "Urz\u0105dzenie nie jest dost\u0119pne."
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"host": "[%key_id:common::config_flow::data::host%]",
|
||||||
|
"port": "[%key_id:common::config_flow::data::port%]"
|
||||||
|
},
|
||||||
|
"title": "Konfiguracja Agent DVR"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "Agent DVR"
|
||||||
|
}
|
21
homeassistant/components/agent_dvr/translations/ru.json
Normal file
21
homeassistant/components/agent_dvr/translations/ru.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u044d\u0442\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430."
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"already_in_progress": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u044d\u0442\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f.",
|
||||||
|
"device_unavailable": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u043d\u0435\u0434\u043e\u0441\u0442\u0443\u043f\u043d\u043e."
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"host": "\u0425\u043e\u0441\u0442",
|
||||||
|
"port": "\u041f\u043e\u0440\u0442"
|
||||||
|
},
|
||||||
|
"title": "Agent DVR"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "Agent DVR"
|
||||||
|
}
|
21
homeassistant/components/agent_dvr/translations/sv.json
Normal file
21
homeassistant/components/agent_dvr/translations/sv.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "Enheten \u00e4r redan konfigurerad"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"already_in_progress": "Konfigurationsfl\u00f6de f\u00f6r enhet p\u00e5g\u00e5r redan.",
|
||||||
|
"device_unavailable": "Enheten \u00e4r inte tillg\u00e4nglig"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"host": "V\u00e4rd",
|
||||||
|
"port": "Port"
|
||||||
|
},
|
||||||
|
"title": "Konfigurera DVR Agent"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "DVR Agent"
|
||||||
|
}
|
21
homeassistant/components/agent_dvr/translations/zh-Hant.json
Normal file
21
homeassistant/components/agent_dvr/translations/zh-Hant.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "\u8a2d\u5099\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"already_in_progress": "\u8a2d\u5099\u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d\u3002",
|
||||||
|
"device_unavailable": "\u8a2d\u5099\u7121\u6cd5\u4f7f\u7528"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"host": "\u4e3b\u6a5f\u7aef",
|
||||||
|
"port": "\u901a\u8a0a\u57e0"
|
||||||
|
},
|
||||||
|
"title": "\u8a2d\u5b9a Agent DVR"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "Agent DVR"
|
||||||
|
}
|
@ -69,18 +69,6 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
|||||||
async_add_entities(sensors, False)
|
async_add_entities(sensors, False)
|
||||||
|
|
||||||
|
|
||||||
def round_state(func):
|
|
||||||
"""Round state."""
|
|
||||||
|
|
||||||
def _decorator(self):
|
|
||||||
res = func(self)
|
|
||||||
if isinstance(res, float):
|
|
||||||
return round(res)
|
|
||||||
return res
|
|
||||||
|
|
||||||
return _decorator
|
|
||||||
|
|
||||||
|
|
||||||
class AirlySensor(Entity):
|
class AirlySensor(Entity):
|
||||||
"""Define an Airly sensor."""
|
"""Define an Airly sensor."""
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
"description": "Set up Airly air quality integration. To generate API key go to https://developer.airly.eu/register",
|
"description": "Set up Airly air quality integration. To generate API key go to https://developer.airly.eu/register",
|
||||||
"data": {
|
"data": {
|
||||||
"name": "Name of the integration",
|
"name": "Name of the integration",
|
||||||
"api_key": "Airly API key",
|
"api_key": "[%key:common::config_flow::data::api_key%]",
|
||||||
"latitude": "Latitude",
|
"latitude": "Latitude",
|
||||||
"longitude": "Longitude"
|
"longitude": "Longitude"
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
"longitude": "Longitud",
|
"longitude": "Longitud",
|
||||||
"name": "Nom de la integraci\u00f3"
|
"name": "Nom de la integraci\u00f3"
|
||||||
},
|
},
|
||||||
"description": "Configura una integraci\u00f3 de qualitat d\u2019aire Airly. Per generar la clau API, v\u00e9s a https://developer.airly.eu/register",
|
"description": "Configura una integraci\u00f3 de qualitat d'aire Airly. Per generar la clau API, v\u00e9s a https://developer.airly.eu/register",
|
||||||
"title": "Airly"
|
"title": "Airly"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
"step": {
|
"step": {
|
||||||
"user": {
|
"user": {
|
||||||
"data": {
|
"data": {
|
||||||
"api_key": "Airly API key",
|
"api_key": "API Key",
|
||||||
"latitude": "Latitude",
|
"latitude": "Latitude",
|
||||||
"longitude": "Longitude",
|
"longitude": "Longitude",
|
||||||
"name": "Name of the integration"
|
"name": "Name of the integration"
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
"step": {
|
"step": {
|
||||||
"user": {
|
"user": {
|
||||||
"data": {
|
"data": {
|
||||||
"api_key": "Airly API \ud0a4",
|
"api_key": "API \ud0a4",
|
||||||
"latitude": "\uc704\ub3c4",
|
"latitude": "\uc704\ub3c4",
|
||||||
"longitude": "\uacbd\ub3c4",
|
"longitude": "\uacbd\ub3c4",
|
||||||
"name": "\ud1b5\ud569 \uad6c\uc131\uc694\uc18c\uc758 \uc774\ub984"
|
"name": "\ud1b5\ud569 \uad6c\uc131\uc694\uc18c\uc758 \uc774\ub984"
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
"abort": {
|
"abort": {
|
||||||
"already_configured": "Airly integrering for disse koordinatene er allerede konfigurert."
|
"already_configured": "Airly integrasjonen for disse koordinatene er allerede konfigurert."
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"auth": "API-n\u00f8kkelen er ikke korrekt.",
|
"auth": "API-n\u00f8kkelen er ikke korrekt.",
|
||||||
@ -15,7 +15,7 @@
|
|||||||
"longitude": "Lengdegrad",
|
"longitude": "Lengdegrad",
|
||||||
"name": "Navn p\u00e5 integrasjonen"
|
"name": "Navn p\u00e5 integrasjonen"
|
||||||
},
|
},
|
||||||
"description": "Sett opp Airly luftkvalitet integrering. For \u00e5 generere API-n\u00f8kkel g\u00e5 til https://developer.airly.eu/register",
|
"description": "Sett opp Airly luftkvalitet integrasjon. For \u00e5 opprette API-n\u00f8kkel, g\u00e5 til [https://developer.airly.eu/register](https://developer.airly.eu/register)",
|
||||||
"title": ""
|
"title": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
"step": {
|
"step": {
|
||||||
"user": {
|
"user": {
|
||||||
"data": {
|
"data": {
|
||||||
"api_key": "Klucz API Airly",
|
"api_key": "[%key_id:common::config_flow::data::api_key%] Airly",
|
||||||
"latitude": "Szeroko\u015b\u0107 geograficzna",
|
"latitude": "Szeroko\u015b\u0107 geograficzna",
|
||||||
"longitude": "D\u0142ugo\u015b\u0107 geograficzna",
|
"longitude": "D\u0142ugo\u015b\u0107 geograficzna",
|
||||||
"name": "Nazwa integracji"
|
"name": "Nazwa integracji"
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
"step": {
|
"step": {
|
||||||
"user": {
|
"user": {
|
||||||
"data": {
|
"data": {
|
||||||
"api_key": "Airly API \u5bc6\u9470",
|
"api_key": "API \u5bc6\u9470",
|
||||||
"latitude": "\u7def\u5ea6",
|
"latitude": "\u7def\u5ea6",
|
||||||
"longitude": "\u7d93\u5ea6",
|
"longitude": "\u7d93\u5ea6",
|
||||||
"name": "\u6574\u5408\u540d\u7a31"
|
"name": "\u6574\u5408\u540d\u7a31"
|
||||||
|
@ -1,38 +1,44 @@
|
|||||||
"""The airvisual component."""
|
"""The airvisual component."""
|
||||||
import logging
|
import asyncio
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
from pyairvisual import Client
|
from pyairvisual import Client
|
||||||
from pyairvisual.errors import AirVisualError, InvalidKeyError
|
from pyairvisual.errors import AirVisualError, NodeProError
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.config_entries import SOURCE_IMPORT
|
from homeassistant.config_entries import SOURCE_IMPORT
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
|
ATTR_ATTRIBUTION,
|
||||||
CONF_API_KEY,
|
CONF_API_KEY,
|
||||||
|
CONF_IP_ADDRESS,
|
||||||
CONF_LATITUDE,
|
CONF_LATITUDE,
|
||||||
CONF_LONGITUDE,
|
CONF_LONGITUDE,
|
||||||
|
CONF_PASSWORD,
|
||||||
CONF_SHOW_ON_MAP,
|
CONF_SHOW_ON_MAP,
|
||||||
CONF_STATE,
|
CONF_STATE,
|
||||||
)
|
)
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
from homeassistant.exceptions import ConfigEntryNotReady
|
|
||||||
from homeassistant.helpers import aiohttp_client, config_validation as cv
|
from homeassistant.helpers import aiohttp_client, config_validation as cv
|
||||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
from homeassistant.helpers.entity import Entity
|
||||||
from homeassistant.helpers.event import async_track_time_interval
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
CONF_CITY,
|
CONF_CITY,
|
||||||
CONF_COUNTRY,
|
CONF_COUNTRY,
|
||||||
CONF_GEOGRAPHIES,
|
CONF_GEOGRAPHIES,
|
||||||
DATA_CLIENT,
|
CONF_INTEGRATION_TYPE,
|
||||||
DEFAULT_SCAN_INTERVAL,
|
DATA_COORDINATOR,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
TOPIC_UPDATE,
|
INTEGRATION_TYPE_GEOGRAPHY,
|
||||||
|
INTEGRATION_TYPE_NODE_PRO,
|
||||||
|
LOGGER,
|
||||||
)
|
)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
PLATFORMS = ["air_quality", "sensor"]
|
||||||
|
|
||||||
DATA_LISTENER = "listener"
|
|
||||||
|
|
||||||
|
DEFAULT_ATTRIBUTION = "Data provided by AirVisual"
|
||||||
|
DEFAULT_GEOGRAPHY_SCAN_INTERVAL = timedelta(minutes=10)
|
||||||
|
DEFAULT_NODE_PRO_SCAN_INTERVAL = timedelta(minutes=1)
|
||||||
DEFAULT_OPTIONS = {CONF_SHOW_ON_MAP: True}
|
DEFAULT_OPTIONS = {CONF_SHOW_ON_MAP: True}
|
||||||
|
|
||||||
GEOGRAPHY_COORDINATES_SCHEMA = vol.Schema(
|
GEOGRAPHY_COORDINATES_SCHEMA = vol.Schema(
|
||||||
@ -66,6 +72,9 @@ CONFIG_SCHEMA = vol.Schema({DOMAIN: CLOUD_API_SCHEMA}, extra=vol.ALLOW_EXTRA)
|
|||||||
@callback
|
@callback
|
||||||
def async_get_geography_id(geography_dict):
|
def async_get_geography_id(geography_dict):
|
||||||
"""Generate a unique ID from a geography dict."""
|
"""Generate a unique ID from a geography dict."""
|
||||||
|
if not geography_dict:
|
||||||
|
return
|
||||||
|
|
||||||
if CONF_CITY in geography_dict:
|
if CONF_CITY in geography_dict:
|
||||||
return ", ".join(
|
return ", ".join(
|
||||||
(
|
(
|
||||||
@ -81,7 +90,7 @@ def async_get_geography_id(geography_dict):
|
|||||||
|
|
||||||
async def async_setup(hass, config):
|
async def async_setup(hass, config):
|
||||||
"""Set up the AirVisual component."""
|
"""Set up the AirVisual component."""
|
||||||
hass.data[DOMAIN] = {DATA_CLIENT: {}, DATA_LISTENER: {}}
|
hass.data[DOMAIN] = {DATA_COORDINATOR: {}}
|
||||||
|
|
||||||
if DOMAIN not in config:
|
if DOMAIN not in config:
|
||||||
return True
|
return True
|
||||||
@ -103,44 +112,118 @@ async def async_setup(hass, config):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass, config_entry):
|
@callback
|
||||||
"""Set up AirVisual as config entry."""
|
def _standardize_geography_config_entry(hass, config_entry):
|
||||||
|
"""Ensure that geography config entries have appropriate properties."""
|
||||||
entry_updates = {}
|
entry_updates = {}
|
||||||
|
|
||||||
if not config_entry.unique_id:
|
if not config_entry.unique_id:
|
||||||
# If the config entry doesn't already have a unique ID, set one:
|
# If the config entry doesn't already have a unique ID, set one:
|
||||||
entry_updates["unique_id"] = config_entry.data[CONF_API_KEY]
|
entry_updates["unique_id"] = config_entry.data[CONF_API_KEY]
|
||||||
if not config_entry.options:
|
if not config_entry.options:
|
||||||
# If the config entry doesn't already have any options set, set defaults:
|
# If the config entry doesn't already have any options set, set defaults:
|
||||||
entry_updates["options"] = DEFAULT_OPTIONS
|
entry_updates["options"] = {CONF_SHOW_ON_MAP: True}
|
||||||
|
if CONF_INTEGRATION_TYPE not in config_entry.data:
|
||||||
|
# If the config entry data doesn't contain the integration type, add it:
|
||||||
|
entry_updates["data"] = {
|
||||||
|
**config_entry.data,
|
||||||
|
CONF_INTEGRATION_TYPE: INTEGRATION_TYPE_GEOGRAPHY,
|
||||||
|
}
|
||||||
|
|
||||||
if entry_updates:
|
if not entry_updates:
|
||||||
hass.config_entries.async_update_entry(config_entry, **entry_updates)
|
return
|
||||||
|
|
||||||
|
hass.config_entries.async_update_entry(config_entry, **entry_updates)
|
||||||
|
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def _standardize_node_pro_config_entry(hass, config_entry):
|
||||||
|
"""Ensure that Node/Pro config entries have appropriate properties."""
|
||||||
|
entry_updates = {}
|
||||||
|
|
||||||
|
if CONF_INTEGRATION_TYPE not in config_entry.data:
|
||||||
|
# If the config entry data doesn't contain the integration type, add it:
|
||||||
|
entry_updates["data"] = {
|
||||||
|
**config_entry.data,
|
||||||
|
CONF_INTEGRATION_TYPE: INTEGRATION_TYPE_NODE_PRO,
|
||||||
|
}
|
||||||
|
|
||||||
|
if not entry_updates:
|
||||||
|
return
|
||||||
|
|
||||||
|
hass.config_entries.async_update_entry(config_entry, **entry_updates)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass, config_entry):
|
||||||
|
"""Set up AirVisual as config entry."""
|
||||||
websession = aiohttp_client.async_get_clientsession(hass)
|
websession = aiohttp_client.async_get_clientsession(hass)
|
||||||
|
|
||||||
hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id] = AirVisualData(
|
if CONF_API_KEY in config_entry.data:
|
||||||
hass, Client(websession, api_key=config_entry.data[CONF_API_KEY]), config_entry
|
_standardize_geography_config_entry(hass, config_entry)
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
client = Client(api_key=config_entry.data[CONF_API_KEY], session=websession)
|
||||||
await hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id].async_update()
|
|
||||||
except InvalidKeyError:
|
|
||||||
_LOGGER.error("Invalid API key provided")
|
|
||||||
raise ConfigEntryNotReady
|
|
||||||
|
|
||||||
hass.async_create_task(
|
async def async_update_data():
|
||||||
hass.config_entries.async_forward_entry_setup(config_entry, "sensor")
|
"""Get new data from the API."""
|
||||||
)
|
if CONF_CITY in config_entry.data:
|
||||||
|
api_coro = client.api.city(
|
||||||
|
config_entry.data[CONF_CITY],
|
||||||
|
config_entry.data[CONF_STATE],
|
||||||
|
config_entry.data[CONF_COUNTRY],
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
api_coro = client.api.nearest_city(
|
||||||
|
config_entry.data[CONF_LATITUDE], config_entry.data[CONF_LONGITUDE],
|
||||||
|
)
|
||||||
|
|
||||||
async def refresh(event_time):
|
try:
|
||||||
"""Refresh data from AirVisual."""
|
return await api_coro
|
||||||
await hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id].async_update()
|
except AirVisualError as err:
|
||||||
|
raise UpdateFailed(f"Error while retrieving data: {err}")
|
||||||
|
|
||||||
hass.data[DOMAIN][DATA_LISTENER][config_entry.entry_id] = async_track_time_interval(
|
coordinator = DataUpdateCoordinator(
|
||||||
hass, refresh, DEFAULT_SCAN_INTERVAL
|
hass,
|
||||||
)
|
LOGGER,
|
||||||
|
name="geography data",
|
||||||
|
update_interval=DEFAULT_GEOGRAPHY_SCAN_INTERVAL,
|
||||||
|
update_method=async_update_data,
|
||||||
|
)
|
||||||
|
|
||||||
config_entry.add_update_listener(async_update_options)
|
# Only geography-based entries have options:
|
||||||
|
config_entry.add_update_listener(async_update_options)
|
||||||
|
else:
|
||||||
|
_standardize_node_pro_config_entry(hass, config_entry)
|
||||||
|
|
||||||
|
client = Client(session=websession)
|
||||||
|
|
||||||
|
async def async_update_data():
|
||||||
|
"""Get new data from the API."""
|
||||||
|
try:
|
||||||
|
return await client.node.from_samba(
|
||||||
|
config_entry.data[CONF_IP_ADDRESS],
|
||||||
|
config_entry.data[CONF_PASSWORD],
|
||||||
|
include_history=False,
|
||||||
|
include_trends=False,
|
||||||
|
)
|
||||||
|
except NodeProError as err:
|
||||||
|
raise UpdateFailed(f"Error while retrieving data: {err}")
|
||||||
|
|
||||||
|
coordinator = DataUpdateCoordinator(
|
||||||
|
hass,
|
||||||
|
LOGGER,
|
||||||
|
name="Node/Pro data",
|
||||||
|
update_interval=DEFAULT_NODE_PRO_SCAN_INTERVAL,
|
||||||
|
update_method=async_update_data,
|
||||||
|
)
|
||||||
|
|
||||||
|
await coordinator.async_refresh()
|
||||||
|
|
||||||
|
hass.data[DOMAIN][DATA_COORDINATOR][config_entry.entry_id] = coordinator
|
||||||
|
|
||||||
|
for component in PLATFORMS:
|
||||||
|
hass.async_create_task(
|
||||||
|
hass.config_entries.async_forward_entry_setup(config_entry, component)
|
||||||
|
)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -149,7 +232,7 @@ async def async_migrate_entry(hass, config_entry):
|
|||||||
"""Migrate an old config entry."""
|
"""Migrate an old config entry."""
|
||||||
version = config_entry.version
|
version = config_entry.version
|
||||||
|
|
||||||
_LOGGER.debug("Migrating from version %s", version)
|
LOGGER.debug("Migrating from version %s", version)
|
||||||
|
|
||||||
# 1 -> 2: One geography per config entry
|
# 1 -> 2: One geography per config entry
|
||||||
if version == 1:
|
if version == 1:
|
||||||
@ -178,65 +261,84 @@ async def async_migrate_entry(hass, config_entry):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
_LOGGER.info("Migration to version %s successful", version)
|
LOGGER.info("Migration to version %s successful", version)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
async def async_unload_entry(hass, config_entry):
|
async def async_unload_entry(hass, config_entry):
|
||||||
"""Unload an AirVisual config entry."""
|
"""Unload an AirVisual config entry."""
|
||||||
hass.data[DOMAIN][DATA_CLIENT].pop(config_entry.entry_id)
|
unload_ok = all(
|
||||||
|
await asyncio.gather(
|
||||||
|
*[
|
||||||
|
hass.config_entries.async_forward_entry_unload(config_entry, component)
|
||||||
|
for component in PLATFORMS
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if unload_ok:
|
||||||
|
hass.data[DOMAIN][DATA_COORDINATOR].pop(config_entry.entry_id)
|
||||||
|
|
||||||
remove_listener = hass.data[DOMAIN][DATA_LISTENER].pop(config_entry.entry_id)
|
return unload_ok
|
||||||
remove_listener()
|
|
||||||
|
|
||||||
await hass.config_entries.async_forward_entry_unload(config_entry, "sensor")
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
async def async_update_options(hass, config_entry):
|
async def async_update_options(hass, config_entry):
|
||||||
"""Handle an options update."""
|
"""Handle an options update."""
|
||||||
airvisual = hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id]
|
coordinator = hass.data[DOMAIN][DATA_COORDINATOR][config_entry.entry_id]
|
||||||
airvisual.async_update_options(config_entry.options)
|
await coordinator.async_request_refresh()
|
||||||
|
|
||||||
|
|
||||||
class AirVisualData:
|
class AirVisualEntity(Entity):
|
||||||
"""Define a class to manage data from the AirVisual cloud API."""
|
"""Define a generic AirVisual entity."""
|
||||||
|
|
||||||
def __init__(self, hass, client, config_entry):
|
def __init__(self, coordinator):
|
||||||
"""Initialize."""
|
"""Initialize."""
|
||||||
self._client = client
|
self._attrs = {ATTR_ATTRIBUTION: DEFAULT_ATTRIBUTION}
|
||||||
self._hass = hass
|
self._icon = None
|
||||||
self.data = {}
|
self._unit = None
|
||||||
self.geography_data = config_entry.data
|
self.coordinator = coordinator
|
||||||
self.geography_id = config_entry.unique_id
|
|
||||||
self.options = config_entry.options
|
@property
|
||||||
|
def available(self):
|
||||||
|
"""Return if entity is available."""
|
||||||
|
return self.coordinator.last_update_success
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device_state_attributes(self):
|
||||||
|
"""Return the device state attributes."""
|
||||||
|
return self._attrs
|
||||||
|
|
||||||
|
@property
|
||||||
|
def icon(self):
|
||||||
|
"""Return the icon."""
|
||||||
|
return self._icon
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unit_of_measurement(self):
|
||||||
|
"""Return the unit the value is expressed in."""
|
||||||
|
return self._unit
|
||||||
|
|
||||||
|
async def async_added_to_hass(self):
|
||||||
|
"""Register callbacks."""
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def update():
|
||||||
|
"""Update the state."""
|
||||||
|
self.update_from_latest_data()
|
||||||
|
self.async_write_ha_state()
|
||||||
|
|
||||||
|
self.async_on_remove(self.coordinator.async_add_listener(update))
|
||||||
|
|
||||||
|
self.update_from_latest_data()
|
||||||
|
|
||||||
async def async_update(self):
|
async def async_update(self):
|
||||||
"""Get new data for all locations from the AirVisual cloud API."""
|
"""Update the entity.
|
||||||
if CONF_CITY in self.geography_data:
|
|
||||||
api_coro = self._client.api.city(
|
|
||||||
self.geography_data[CONF_CITY],
|
|
||||||
self.geography_data[CONF_STATE],
|
|
||||||
self.geography_data[CONF_COUNTRY],
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
api_coro = self._client.api.nearest_city(
|
|
||||||
self.geography_data[CONF_LATITUDE], self.geography_data[CONF_LONGITUDE],
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
Only used by the generic entity update service.
|
||||||
self.data[self.geography_id] = await api_coro
|
"""
|
||||||
except AirVisualError as err:
|
await self.coordinator.async_request_refresh()
|
||||||
_LOGGER.error("Error while retrieving data: %s", err)
|
|
||||||
self.data[self.geography_id] = {}
|
|
||||||
|
|
||||||
_LOGGER.debug("Received new data")
|
|
||||||
async_dispatcher_send(self._hass, TOPIC_UPDATE)
|
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_update_options(self, options):
|
def update_from_latest_data(self):
|
||||||
"""Update the data manager's options."""
|
"""Update the entity from the latest data."""
|
||||||
self.options = options
|
raise NotImplementedError
|
||||||
async_dispatcher_send(self._hass, TOPIC_UPDATE)
|
|
||||||
|
112
homeassistant/components/airvisual/air_quality.py
Normal file
112
homeassistant/components/airvisual/air_quality.py
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
"""Support for AirVisual Node/Pro units."""
|
||||||
|
from homeassistant.components.air_quality import AirQualityEntity
|
||||||
|
from homeassistant.const import CONCENTRATION_MICROGRAMS_PER_CUBIC_METER
|
||||||
|
from homeassistant.core import callback
|
||||||
|
|
||||||
|
from . import AirVisualEntity
|
||||||
|
from .const import (
|
||||||
|
CONF_INTEGRATION_TYPE,
|
||||||
|
DATA_COORDINATOR,
|
||||||
|
DOMAIN,
|
||||||
|
INTEGRATION_TYPE_GEOGRAPHY,
|
||||||
|
)
|
||||||
|
|
||||||
|
ATTR_HUMIDITY = "humidity"
|
||||||
|
ATTR_SENSOR_LIFE = "{0}_sensor_life"
|
||||||
|
ATTR_VOC = "voc"
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
|
"""Set up AirVisual air quality entities based on a config entry."""
|
||||||
|
coordinator = hass.data[DOMAIN][DATA_COORDINATOR][config_entry.entry_id]
|
||||||
|
|
||||||
|
# Geography-based AirVisual integrations don't utilize this platform:
|
||||||
|
if config_entry.data[CONF_INTEGRATION_TYPE] == INTEGRATION_TYPE_GEOGRAPHY:
|
||||||
|
return
|
||||||
|
|
||||||
|
async_add_entities([AirVisualNodeProSensor(coordinator)], True)
|
||||||
|
|
||||||
|
|
||||||
|
class AirVisualNodeProSensor(AirVisualEntity, AirQualityEntity):
|
||||||
|
"""Define a sensor for a AirVisual Node/Pro."""
|
||||||
|
|
||||||
|
def __init__(self, airvisual):
|
||||||
|
"""Initialize."""
|
||||||
|
super().__init__(airvisual)
|
||||||
|
|
||||||
|
self._icon = "mdi:chemical-weapon"
|
||||||
|
self._unit = CONCENTRATION_MICROGRAMS_PER_CUBIC_METER
|
||||||
|
|
||||||
|
@property
|
||||||
|
def air_quality_index(self):
|
||||||
|
"""Return the Air Quality Index (AQI)."""
|
||||||
|
if self.coordinator.data["current"]["settings"]["is_aqi_usa"]:
|
||||||
|
return self.coordinator.data["current"]["measurements"]["aqi_us"]
|
||||||
|
return self.coordinator.data["current"]["measurements"]["aqi_cn"]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def available(self):
|
||||||
|
"""Return True if entity is available."""
|
||||||
|
return bool(self.coordinator.data)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def carbon_dioxide(self):
|
||||||
|
"""Return the CO2 (carbon dioxide) level."""
|
||||||
|
return self.coordinator.data["current"]["measurements"].get("co2")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device_info(self):
|
||||||
|
"""Return device registry information for this entity."""
|
||||||
|
return {
|
||||||
|
"identifiers": {
|
||||||
|
(DOMAIN, self.coordinator.data["current"]["serial_number"])
|
||||||
|
},
|
||||||
|
"name": self.coordinator.data["current"]["settings"]["node_name"],
|
||||||
|
"manufacturer": "AirVisual",
|
||||||
|
"model": f'{self.coordinator.data["current"]["status"]["model"]}',
|
||||||
|
"sw_version": (
|
||||||
|
f'Version {self.coordinator.data["current"]["status"]["system_version"]}'
|
||||||
|
f'{self.coordinator.data["current"]["status"]["app_version"]}'
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
"""Return the name."""
|
||||||
|
node_name = self.coordinator.data["current"]["settings"]["node_name"]
|
||||||
|
return f"{node_name} Node/Pro: Air Quality"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def particulate_matter_2_5(self):
|
||||||
|
"""Return the particulate matter 2.5 level."""
|
||||||
|
return self.coordinator.data["current"]["measurements"].get("pm2_5")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def particulate_matter_10(self):
|
||||||
|
"""Return the particulate matter 10 level."""
|
||||||
|
return self.coordinator.data["current"]["measurements"].get("pm1_0")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def particulate_matter_0_1(self):
|
||||||
|
"""Return the particulate matter 0.1 level."""
|
||||||
|
return self.coordinator.data["current"]["measurements"].get("pm0_1")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unique_id(self):
|
||||||
|
"""Return a unique, Home Assistant friendly identifier for this entity."""
|
||||||
|
return self.coordinator.data["current"]["serial_number"]
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def update_from_latest_data(self):
|
||||||
|
"""Update the entity from the latest data."""
|
||||||
|
self._attrs.update(
|
||||||
|
{
|
||||||
|
ATTR_VOC: self.coordinator.data["current"]["measurements"].get("voc"),
|
||||||
|
**{
|
||||||
|
ATTR_SENSOR_LIFE.format(pollutant): lifespan
|
||||||
|
for pollutant, lifespan in self.coordinator.data["current"][
|
||||||
|
"status"
|
||||||
|
]["sensor_life"].items()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
@ -2,21 +2,30 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
from pyairvisual import Client
|
from pyairvisual import Client
|
||||||
from pyairvisual.errors import InvalidKeyError
|
from pyairvisual.errors import InvalidKeyError, NodeProError
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant import config_entries
|
from homeassistant import config_entries
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_API_KEY,
|
CONF_API_KEY,
|
||||||
|
CONF_IP_ADDRESS,
|
||||||
CONF_LATITUDE,
|
CONF_LATITUDE,
|
||||||
CONF_LONGITUDE,
|
CONF_LONGITUDE,
|
||||||
|
CONF_PASSWORD,
|
||||||
CONF_SHOW_ON_MAP,
|
CONF_SHOW_ON_MAP,
|
||||||
)
|
)
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
from homeassistant.helpers import aiohttp_client, config_validation as cv
|
from homeassistant.helpers import aiohttp_client, config_validation as cv
|
||||||
|
|
||||||
from . import async_get_geography_id
|
from . import async_get_geography_id
|
||||||
from .const import CONF_GEOGRAPHIES, DOMAIN # pylint: disable=unused-import
|
from .const import ( # pylint: disable=unused-import
|
||||||
|
CONF_GEOGRAPHIES,
|
||||||
|
CONF_INTEGRATION_TYPE,
|
||||||
|
DOMAIN,
|
||||||
|
INTEGRATION_TYPE_GEOGRAPHY,
|
||||||
|
INTEGRATION_TYPE_NODE_PRO,
|
||||||
|
LOGGER,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class AirVisualFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
class AirVisualFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
@ -26,7 +35,7 @@ class AirVisualFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL
|
CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def cloud_api_schema(self):
|
def geography_schema(self):
|
||||||
"""Return the data schema for the cloud API."""
|
"""Return the data schema for the cloud API."""
|
||||||
return vol.Schema(
|
return vol.Schema(
|
||||||
{
|
{
|
||||||
@ -40,38 +49,47 @@ class AirVisualFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def pick_integration_type_schema(self):
|
||||||
|
"""Return the data schema for picking the integration type."""
|
||||||
|
return vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required("type"): vol.In(
|
||||||
|
[INTEGRATION_TYPE_GEOGRAPHY, INTEGRATION_TYPE_NODE_PRO]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def node_pro_schema(self):
|
||||||
|
"""Return the data schema for a Node/Pro."""
|
||||||
|
return vol.Schema(
|
||||||
|
{vol.Required(CONF_IP_ADDRESS): str, vol.Required(CONF_PASSWORD): str}
|
||||||
|
)
|
||||||
|
|
||||||
async def _async_set_unique_id(self, unique_id):
|
async def _async_set_unique_id(self, unique_id):
|
||||||
"""Set the unique ID of the config flow and abort if it already exists."""
|
"""Set the unique ID of the config flow and abort if it already exists."""
|
||||||
await self.async_set_unique_id(unique_id)
|
await self.async_set_unique_id(unique_id)
|
||||||
self._abort_if_unique_id_configured()
|
self._abort_if_unique_id_configured()
|
||||||
|
|
||||||
@callback
|
|
||||||
async def _show_form(self, errors=None):
|
|
||||||
"""Show the form to the user."""
|
|
||||||
return self.async_show_form(
|
|
||||||
step_id="user", data_schema=self.cloud_api_schema, errors=errors or {},
|
|
||||||
)
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@callback
|
@callback
|
||||||
def async_get_options_flow(config_entry):
|
def async_get_options_flow(config_entry):
|
||||||
"""Define the config flow to handle options."""
|
"""Define the config flow to handle options."""
|
||||||
return AirVisualOptionsFlowHandler(config_entry)
|
return AirVisualOptionsFlowHandler(config_entry)
|
||||||
|
|
||||||
async def async_step_import(self, import_config):
|
async def async_step_geography(self, user_input=None):
|
||||||
"""Import a config entry from configuration.yaml."""
|
"""Handle the initialization of the integration via the cloud API."""
|
||||||
return await self.async_step_user(import_config)
|
|
||||||
|
|
||||||
async def async_step_user(self, user_input=None):
|
|
||||||
"""Handle the start of the config flow."""
|
|
||||||
if not user_input:
|
if not user_input:
|
||||||
return await self._show_form()
|
return self.async_show_form(
|
||||||
|
step_id="geography", data_schema=self.geography_schema
|
||||||
|
)
|
||||||
|
|
||||||
geo_id = async_get_geography_id(user_input)
|
geo_id = async_get_geography_id(user_input)
|
||||||
await self._async_set_unique_id(geo_id)
|
await self._async_set_unique_id(geo_id)
|
||||||
self._abort_if_unique_id_configured()
|
self._abort_if_unique_id_configured()
|
||||||
|
|
||||||
# Find older config entries without unique ID
|
# Find older config entries without unique ID:
|
||||||
for entry in self._async_current_entries():
|
for entry in self._async_current_entries():
|
||||||
if entry.version != 1:
|
if entry.version != 1:
|
||||||
continue
|
continue
|
||||||
@ -83,7 +101,7 @@ class AirVisualFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
return self.async_abort(reason="already_configured")
|
return self.async_abort(reason="already_configured")
|
||||||
|
|
||||||
websession = aiohttp_client.async_get_clientsession(self.hass)
|
websession = aiohttp_client.async_get_clientsession(self.hass)
|
||||||
client = Client(websession, api_key=user_input[CONF_API_KEY])
|
client = Client(session=websession, api_key=user_input[CONF_API_KEY])
|
||||||
|
|
||||||
# If this is the first (and only the first) time we've seen this API key, check
|
# If this is the first (and only the first) time we've seen this API key, check
|
||||||
# that it's valid:
|
# that it's valid:
|
||||||
@ -97,16 +115,66 @@ class AirVisualFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
try:
|
try:
|
||||||
await client.api.nearest_city()
|
await client.api.nearest_city()
|
||||||
except InvalidKeyError:
|
except InvalidKeyError:
|
||||||
return await self._show_form(
|
return self.async_show_form(
|
||||||
errors={CONF_API_KEY: "invalid_api_key"}
|
step_id="geography",
|
||||||
|
data_schema=self.geography_schema,
|
||||||
|
errors={CONF_API_KEY: "invalid_api_key"},
|
||||||
)
|
)
|
||||||
|
|
||||||
checked_keys.add(user_input[CONF_API_KEY])
|
checked_keys.add(user_input[CONF_API_KEY])
|
||||||
|
|
||||||
return self.async_create_entry(
|
return self.async_create_entry(
|
||||||
title=f"Cloud API ({geo_id})", data=user_input
|
title=f"Cloud API ({geo_id})",
|
||||||
|
data={**user_input, CONF_INTEGRATION_TYPE: INTEGRATION_TYPE_GEOGRAPHY},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def async_step_import(self, import_config):
|
||||||
|
"""Import a config entry from configuration.yaml."""
|
||||||
|
return await self.async_step_geography(import_config)
|
||||||
|
|
||||||
|
async def async_step_node_pro(self, user_input=None):
|
||||||
|
"""Handle the initialization of the integration with a Node/Pro."""
|
||||||
|
if not user_input:
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="node_pro", data_schema=self.node_pro_schema
|
||||||
|
)
|
||||||
|
|
||||||
|
await self._async_set_unique_id(user_input[CONF_IP_ADDRESS])
|
||||||
|
|
||||||
|
websession = aiohttp_client.async_get_clientsession(self.hass)
|
||||||
|
client = Client(session=websession)
|
||||||
|
|
||||||
|
try:
|
||||||
|
await client.node.from_samba(
|
||||||
|
user_input[CONF_IP_ADDRESS],
|
||||||
|
user_input[CONF_PASSWORD],
|
||||||
|
include_history=False,
|
||||||
|
include_trends=False,
|
||||||
|
)
|
||||||
|
except NodeProError as err:
|
||||||
|
LOGGER.error("Error connecting to Node/Pro unit: %s", err)
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="node_pro",
|
||||||
|
data_schema=self.node_pro_schema,
|
||||||
|
errors={CONF_IP_ADDRESS: "unable_to_connect"},
|
||||||
|
)
|
||||||
|
|
||||||
|
return self.async_create_entry(
|
||||||
|
title=f"Node/Pro ({user_input[CONF_IP_ADDRESS]})",
|
||||||
|
data={**user_input, CONF_INTEGRATION_TYPE: INTEGRATION_TYPE_NODE_PRO},
|
||||||
|
)
|
||||||
|
|
||||||
|
async def async_step_user(self, user_input=None):
|
||||||
|
"""Handle the start of the config flow."""
|
||||||
|
if not user_input:
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="user", data_schema=self.pick_integration_type_schema
|
||||||
|
)
|
||||||
|
|
||||||
|
if user_input["type"] == INTEGRATION_TYPE_GEOGRAPHY:
|
||||||
|
return await self.async_step_geography()
|
||||||
|
return await self.async_step_node_pro()
|
||||||
|
|
||||||
|
|
||||||
class AirVisualOptionsFlowHandler(config_entries.OptionsFlow):
|
class AirVisualOptionsFlowHandler(config_entries.OptionsFlow):
|
||||||
"""Handle an AirVisual options flow."""
|
"""Handle an AirVisual options flow."""
|
||||||
|
@ -1,14 +1,15 @@
|
|||||||
"""Define AirVisual constants."""
|
"""Define AirVisual constants."""
|
||||||
from datetime import timedelta
|
import logging
|
||||||
|
|
||||||
DOMAIN = "airvisual"
|
DOMAIN = "airvisual"
|
||||||
|
LOGGER = logging.getLogger(__package__)
|
||||||
|
|
||||||
|
INTEGRATION_TYPE_GEOGRAPHY = "Geographical Location"
|
||||||
|
INTEGRATION_TYPE_NODE_PRO = "AirVisual Node/Pro"
|
||||||
|
|
||||||
CONF_CITY = "city"
|
CONF_CITY = "city"
|
||||||
CONF_COUNTRY = "country"
|
CONF_COUNTRY = "country"
|
||||||
CONF_GEOGRAPHIES = "geographies"
|
CONF_GEOGRAPHIES = "geographies"
|
||||||
|
CONF_INTEGRATION_TYPE = "integration_type"
|
||||||
|
|
||||||
DATA_CLIENT = "client"
|
DATA_COORDINATOR = "coordinator"
|
||||||
|
|
||||||
DEFAULT_SCAN_INTERVAL = timedelta(minutes=10)
|
|
||||||
|
|
||||||
TOPIC_UPDATE = f"{DOMAIN}_update"
|
|
||||||
|
@ -3,6 +3,6 @@
|
|||||||
"name": "AirVisual",
|
"name": "AirVisual",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/airvisual",
|
"documentation": "https://www.home-assistant.io/integrations/airvisual",
|
||||||
"requirements": ["pyairvisual==3.0.1"],
|
"requirements": ["pyairvisual==4.4.0"],
|
||||||
"codeowners": ["@bachya"]
|
"codeowners": ["@bachya"]
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_ATTRIBUTION,
|
|
||||||
ATTR_LATITUDE,
|
ATTR_LATITUDE,
|
||||||
ATTR_LONGITUDE,
|
ATTR_LONGITUDE,
|
||||||
ATTR_STATE,
|
ATTR_STATE,
|
||||||
@ -13,12 +12,23 @@ from homeassistant.const import (
|
|||||||
CONF_LONGITUDE,
|
CONF_LONGITUDE,
|
||||||
CONF_SHOW_ON_MAP,
|
CONF_SHOW_ON_MAP,
|
||||||
CONF_STATE,
|
CONF_STATE,
|
||||||
|
DEVICE_CLASS_BATTERY,
|
||||||
|
DEVICE_CLASS_HUMIDITY,
|
||||||
|
DEVICE_CLASS_TEMPERATURE,
|
||||||
|
TEMP_CELSIUS,
|
||||||
|
UNIT_PERCENTAGE,
|
||||||
)
|
)
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
|
||||||
from homeassistant.helpers.entity import Entity
|
|
||||||
|
|
||||||
from .const import CONF_CITY, CONF_COUNTRY, DATA_CLIENT, DOMAIN, TOPIC_UPDATE
|
from . import AirVisualEntity
|
||||||
|
from .const import (
|
||||||
|
CONF_CITY,
|
||||||
|
CONF_COUNTRY,
|
||||||
|
CONF_INTEGRATION_TYPE,
|
||||||
|
DATA_COORDINATOR,
|
||||||
|
DOMAIN,
|
||||||
|
INTEGRATION_TYPE_GEOGRAPHY,
|
||||||
|
)
|
||||||
|
|
||||||
_LOGGER = getLogger(__name__)
|
_LOGGER = getLogger(__name__)
|
||||||
|
|
||||||
@ -28,8 +38,6 @@ ATTR_POLLUTANT_SYMBOL = "pollutant_symbol"
|
|||||||
ATTR_POLLUTANT_UNIT = "pollutant_unit"
|
ATTR_POLLUTANT_UNIT = "pollutant_unit"
|
||||||
ATTR_REGION = "region"
|
ATTR_REGION = "region"
|
||||||
|
|
||||||
DEFAULT_ATTRIBUTION = "Data provided by AirVisual"
|
|
||||||
|
|
||||||
MASS_PARTS_PER_MILLION = "ppm"
|
MASS_PARTS_PER_MILLION = "ppm"
|
||||||
MASS_PARTS_PER_BILLION = "ppb"
|
MASS_PARTS_PER_BILLION = "ppb"
|
||||||
VOLUME_MICROGRAMS_PER_CUBIC_METER = "µg/m3"
|
VOLUME_MICROGRAMS_PER_CUBIC_METER = "µg/m3"
|
||||||
@ -37,11 +45,22 @@ VOLUME_MICROGRAMS_PER_CUBIC_METER = "µg/m3"
|
|||||||
SENSOR_KIND_LEVEL = "air_pollution_level"
|
SENSOR_KIND_LEVEL = "air_pollution_level"
|
||||||
SENSOR_KIND_AQI = "air_quality_index"
|
SENSOR_KIND_AQI = "air_quality_index"
|
||||||
SENSOR_KIND_POLLUTANT = "main_pollutant"
|
SENSOR_KIND_POLLUTANT = "main_pollutant"
|
||||||
SENSORS = [
|
SENSOR_KIND_BATTERY_LEVEL = "battery_level"
|
||||||
|
SENSOR_KIND_HUMIDITY = "humidity"
|
||||||
|
SENSOR_KIND_TEMPERATURE = "temperature"
|
||||||
|
|
||||||
|
GEOGRAPHY_SENSORS = [
|
||||||
(SENSOR_KIND_LEVEL, "Air Pollution Level", "mdi:gauge", None),
|
(SENSOR_KIND_LEVEL, "Air Pollution Level", "mdi:gauge", None),
|
||||||
(SENSOR_KIND_AQI, "Air Quality Index", "mdi:chart-line", "AQI"),
|
(SENSOR_KIND_AQI, "Air Quality Index", "mdi:chart-line", "AQI"),
|
||||||
(SENSOR_KIND_POLLUTANT, "Main Pollutant", "mdi:chemical-weapon", None),
|
(SENSOR_KIND_POLLUTANT, "Main Pollutant", "mdi:chemical-weapon", None),
|
||||||
]
|
]
|
||||||
|
GEOGRAPHY_SENSOR_LOCALES = {"cn": "Chinese", "us": "U.S."}
|
||||||
|
|
||||||
|
NODE_PRO_SENSORS = [
|
||||||
|
(SENSOR_KIND_BATTERY_LEVEL, "Battery", DEVICE_CLASS_BATTERY, UNIT_PERCENTAGE),
|
||||||
|
(SENSOR_KIND_HUMIDITY, "Humidity", DEVICE_CLASS_HUMIDITY, UNIT_PERCENTAGE),
|
||||||
|
(SENSOR_KIND_TEMPERATURE, "Temperature", DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS),
|
||||||
|
]
|
||||||
|
|
||||||
POLLUTANT_LEVEL_MAPPING = [
|
POLLUTANT_LEVEL_MAPPING = [
|
||||||
{"label": "Good", "icon": "mdi:emoticon-excited", "minimum": 0, "maximum": 50},
|
{"label": "Good", "icon": "mdi:emoticon-excited", "minimum": 0, "maximum": 50},
|
||||||
@ -71,31 +90,43 @@ POLLUTANT_MAPPING = {
|
|||||||
"s2": {"label": "Sulfur Dioxide", "unit": CONCENTRATION_PARTS_PER_BILLION},
|
"s2": {"label": "Sulfur Dioxide", "unit": CONCENTRATION_PARTS_PER_BILLION},
|
||||||
}
|
}
|
||||||
|
|
||||||
SENSOR_LOCALES = {"cn": "Chinese", "us": "U.S."}
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
async def async_setup_entry(hass, entry, async_add_entities):
|
|
||||||
"""Set up AirVisual sensors based on a config entry."""
|
"""Set up AirVisual sensors based on a config entry."""
|
||||||
airvisual = hass.data[DOMAIN][DATA_CLIENT][entry.entry_id]
|
coordinator = hass.data[DOMAIN][DATA_COORDINATOR][config_entry.entry_id]
|
||||||
|
|
||||||
async_add_entities(
|
if config_entry.data[CONF_INTEGRATION_TYPE] == INTEGRATION_TYPE_GEOGRAPHY:
|
||||||
[
|
sensors = [
|
||||||
AirVisualSensor(airvisual, kind, name, icon, unit, locale, geography_id)
|
AirVisualGeographySensor(
|
||||||
for geography_id in airvisual.data
|
coordinator, config_entry, kind, name, icon, unit, locale,
|
||||||
for locale in SENSOR_LOCALES
|
)
|
||||||
for kind, name, icon, unit in SENSORS
|
for locale in GEOGRAPHY_SENSOR_LOCALES
|
||||||
],
|
for kind, name, icon, unit in GEOGRAPHY_SENSORS
|
||||||
True,
|
]
|
||||||
)
|
else:
|
||||||
|
sensors = [
|
||||||
|
AirVisualNodeProSensor(coordinator, kind, name, device_class, unit)
|
||||||
|
for kind, name, device_class, unit in NODE_PRO_SENSORS
|
||||||
|
]
|
||||||
|
|
||||||
|
async_add_entities(sensors, True)
|
||||||
|
|
||||||
|
|
||||||
class AirVisualSensor(Entity):
|
class AirVisualGeographySensor(AirVisualEntity):
|
||||||
"""Define an AirVisual sensor."""
|
"""Define an AirVisual sensor related to geography data via the Cloud API."""
|
||||||
|
|
||||||
def __init__(self, airvisual, kind, name, icon, unit, locale, geography_id):
|
def __init__(self, coordinator, config_entry, kind, name, icon, unit, locale):
|
||||||
"""Initialize."""
|
"""Initialize."""
|
||||||
self._airvisual = airvisual
|
super().__init__(coordinator)
|
||||||
self._geography_id = geography_id
|
|
||||||
|
self._attrs.update(
|
||||||
|
{
|
||||||
|
ATTR_CITY: config_entry.data.get(CONF_CITY),
|
||||||
|
ATTR_STATE: config_entry.data.get(CONF_STATE),
|
||||||
|
ATTR_COUNTRY: config_entry.data.get(CONF_COUNTRY),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self._config_entry = config_entry
|
||||||
self._icon = icon
|
self._icon = icon
|
||||||
self._kind = kind
|
self._kind = kind
|
||||||
self._locale = locale
|
self._locale = locale
|
||||||
@ -103,37 +134,20 @@ class AirVisualSensor(Entity):
|
|||||||
self._state = None
|
self._state = None
|
||||||
self._unit = unit
|
self._unit = unit
|
||||||
|
|
||||||
self._attrs = {
|
|
||||||
ATTR_ATTRIBUTION: DEFAULT_ATTRIBUTION,
|
|
||||||
ATTR_CITY: airvisual.data[geography_id].get(CONF_CITY),
|
|
||||||
ATTR_STATE: airvisual.data[geography_id].get(CONF_STATE),
|
|
||||||
ATTR_COUNTRY: airvisual.data[geography_id].get(CONF_COUNTRY),
|
|
||||||
}
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def available(self):
|
def available(self):
|
||||||
"""Return True if entity is available."""
|
"""Return True if entity is available."""
|
||||||
try:
|
try:
|
||||||
return bool(
|
return self.coordinator.last_update_success and bool(
|
||||||
self._airvisual.data[self._geography_id]["current"]["pollution"]
|
self.coordinator.data["current"]["pollution"]
|
||||||
)
|
)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@property
|
|
||||||
def device_state_attributes(self):
|
|
||||||
"""Return the device state attributes."""
|
|
||||||
return self._attrs
|
|
||||||
|
|
||||||
@property
|
|
||||||
def icon(self):
|
|
||||||
"""Return the icon."""
|
|
||||||
return self._icon
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
"""Return the name."""
|
"""Return the name."""
|
||||||
return f"{SENSOR_LOCALES[self._locale]} {self._name}"
|
return f"{GEOGRAPHY_SENSOR_LOCALES[self._locale]} {self._name}"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state(self):
|
def state(self):
|
||||||
@ -143,27 +157,13 @@ class AirVisualSensor(Entity):
|
|||||||
@property
|
@property
|
||||||
def unique_id(self):
|
def unique_id(self):
|
||||||
"""Return a unique, Home Assistant friendly identifier for this entity."""
|
"""Return a unique, Home Assistant friendly identifier for this entity."""
|
||||||
return f"{self._geography_id}_{self._locale}_{self._kind}"
|
return f"{self._config_entry.unique_id}_{self._locale}_{self._kind}"
|
||||||
|
|
||||||
@property
|
@callback
|
||||||
def unit_of_measurement(self):
|
def update_from_latest_data(self):
|
||||||
"""Return the unit the value is expressed in."""
|
"""Update the entity from the latest data."""
|
||||||
return self._unit
|
|
||||||
|
|
||||||
async def async_added_to_hass(self):
|
|
||||||
"""Register callbacks."""
|
|
||||||
|
|
||||||
@callback
|
|
||||||
def update():
|
|
||||||
"""Update the state."""
|
|
||||||
self.async_schedule_update_ha_state(True)
|
|
||||||
|
|
||||||
self.async_on_remove(async_dispatcher_connect(self.hass, TOPIC_UPDATE, update))
|
|
||||||
|
|
||||||
async def async_update(self):
|
|
||||||
"""Update the sensor."""
|
|
||||||
try:
|
try:
|
||||||
data = self._airvisual.data[self._geography_id]["current"]["pollution"]
|
data = self.coordinator.data["current"]["pollution"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -188,18 +188,79 @@ class AirVisualSensor(Entity):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
if CONF_LATITUDE in self._airvisual.geography_data:
|
if CONF_LATITUDE in self._config_entry.data:
|
||||||
if self._airvisual.options[CONF_SHOW_ON_MAP]:
|
if self._config_entry.options[CONF_SHOW_ON_MAP]:
|
||||||
self._attrs[ATTR_LATITUDE] = self._airvisual.geography_data[
|
self._attrs[ATTR_LATITUDE] = self._config_entry.data[CONF_LATITUDE]
|
||||||
CONF_LATITUDE
|
self._attrs[ATTR_LONGITUDE] = self._config_entry.data[CONF_LONGITUDE]
|
||||||
]
|
|
||||||
self._attrs[ATTR_LONGITUDE] = self._airvisual.geography_data[
|
|
||||||
CONF_LONGITUDE
|
|
||||||
]
|
|
||||||
self._attrs.pop("lati", None)
|
self._attrs.pop("lati", None)
|
||||||
self._attrs.pop("long", None)
|
self._attrs.pop("long", None)
|
||||||
else:
|
else:
|
||||||
self._attrs["lati"] = self._airvisual.geography_data[CONF_LATITUDE]
|
self._attrs["lati"] = self._config_entry.data[CONF_LATITUDE]
|
||||||
self._attrs["long"] = self._airvisual.geography_data[CONF_LONGITUDE]
|
self._attrs["long"] = self._config_entry.data[CONF_LONGITUDE]
|
||||||
self._attrs.pop(ATTR_LATITUDE, None)
|
self._attrs.pop(ATTR_LATITUDE, None)
|
||||||
self._attrs.pop(ATTR_LONGITUDE, None)
|
self._attrs.pop(ATTR_LONGITUDE, None)
|
||||||
|
|
||||||
|
|
||||||
|
class AirVisualNodeProSensor(AirVisualEntity):
|
||||||
|
"""Define an AirVisual sensor related to a Node/Pro unit."""
|
||||||
|
|
||||||
|
def __init__(self, coordinator, kind, name, device_class, unit):
|
||||||
|
"""Initialize."""
|
||||||
|
super().__init__(coordinator)
|
||||||
|
|
||||||
|
self._device_class = device_class
|
||||||
|
self._kind = kind
|
||||||
|
self._name = name
|
||||||
|
self._state = None
|
||||||
|
self._unit = unit
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device_class(self):
|
||||||
|
"""Return the device class."""
|
||||||
|
return self._device_class
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device_info(self):
|
||||||
|
"""Return device registry information for this entity."""
|
||||||
|
return {
|
||||||
|
"identifiers": {
|
||||||
|
(DOMAIN, self.coordinator.data["current"]["serial_number"])
|
||||||
|
},
|
||||||
|
"name": self.coordinator.data["current"]["settings"]["node_name"],
|
||||||
|
"manufacturer": "AirVisual",
|
||||||
|
"model": f'{self.coordinator.data["current"]["status"]["model"]}',
|
||||||
|
"sw_version": (
|
||||||
|
f'Version {self.coordinator.data["current"]["status"]["system_version"]}'
|
||||||
|
f'{self.coordinator.data["current"]["status"]["app_version"]}'
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
"""Return the name."""
|
||||||
|
node_name = self.coordinator.data["current"]["settings"]["node_name"]
|
||||||
|
return f"{node_name} Node/Pro: {self._name}"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state(self):
|
||||||
|
"""Return the state."""
|
||||||
|
return self._state
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unique_id(self):
|
||||||
|
"""Return a unique, Home Assistant friendly identifier for this entity."""
|
||||||
|
return f"{self.coordinator.data['current']['serial_number']}_{self._kind}"
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def update_from_latest_data(self):
|
||||||
|
"""Update the entity from the latest data."""
|
||||||
|
if self._kind == SENSOR_KIND_BATTERY_LEVEL:
|
||||||
|
self._state = self.coordinator.data["current"]["status"]["battery"]
|
||||||
|
elif self._kind == SENSOR_KIND_HUMIDITY:
|
||||||
|
self._state = self.coordinator.data["current"]["measurements"].get(
|
||||||
|
"humidity"
|
||||||
|
)
|
||||||
|
elif self._kind == SENSOR_KIND_TEMPERATURE:
|
||||||
|
self._state = self.coordinator.data["current"]["measurements"].get(
|
||||||
|
"temperature_C"
|
||||||
|
)
|
||||||
|
@ -1,27 +1,49 @@
|
|||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
"step": {
|
"step": {
|
||||||
"user": {
|
"geography": {
|
||||||
"title": "Configure AirVisual",
|
"title": "Configure a Geography",
|
||||||
"description": "Monitor air quality in a geographical location.",
|
"description": "Use the AirVisual cloud API to monitor a geographical location.",
|
||||||
"data": {
|
"data": {
|
||||||
"api_key": "API Key",
|
"api_key": "[%key:common::config_flow::data::api_key%]",
|
||||||
"latitude": "Latitude",
|
"latitude": "Latitude",
|
||||||
"longitude": "Longitude"
|
"longitude": "Longitude"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"node_pro": {
|
||||||
|
"title": "Configure an AirVisual Node/Pro",
|
||||||
|
"description": "Monitor a personal AirVisual unit. The password can be retrieved from the unit's UI.",
|
||||||
|
"data": {
|
||||||
|
"ip_address": "Unit IP Address/Hostname",
|
||||||
|
"password": "[%key:common::config_flow::data::password%]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"title": "Configure AirVisual",
|
||||||
|
"description": "Pick what type of AirVisual data you want to monitor.",
|
||||||
|
"data": {
|
||||||
|
"cloud_api": "Geographical Location",
|
||||||
|
"node_pro": "AirVisual Node Pro",
|
||||||
|
"type": "Integration Type"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"error": { "invalid_api_key": "Invalid API key" },
|
"error": {
|
||||||
|
"general_error": "There was an unknown error.",
|
||||||
|
"invalid_api_key": "Invalid API key provided.",
|
||||||
|
"unable_to_connect": "Unable to connect to Node/Pro unit."
|
||||||
|
},
|
||||||
"abort": {
|
"abort": {
|
||||||
"already_configured": "These coordinates have already been registered."
|
"already_configured": "These coordinates or Node/Pro ID are already registered."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"options": {
|
"options": {
|
||||||
"step": {
|
"step": {
|
||||||
"init": {
|
"init": {
|
||||||
"title": "Configure AirVisual",
|
"title": "Configure AirVisual",
|
||||||
"description": "Set various options for the AirVisual integration.",
|
"data": {
|
||||||
"data": { "show_on_map": "Show monitored geography on the map" }
|
"show_on_map": "Show monitored geography on the map"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,13 +5,13 @@
|
|||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"general_error": "S'ha produ\u00eft un error desconegut.",
|
"general_error": "S'ha produ\u00eft un error desconegut.",
|
||||||
"invalid_api_key": "Clau API inv\u00e0lida",
|
"invalid_api_key": "Clau API proporiconada no v\u00e0lida.",
|
||||||
"unable_to_connect": "No s'ha pogut connectar a la unitat Node/Pro."
|
"unable_to_connect": "No s'ha pogut connectar a la unitat Node/Pro."
|
||||||
},
|
},
|
||||||
"step": {
|
"step": {
|
||||||
"geography": {
|
"geography": {
|
||||||
"data": {
|
"data": {
|
||||||
"api_key": "Clau API",
|
"api_key": "[%key::common::config_flow::data::api_key%]",
|
||||||
"latitude": "Latitud",
|
"latitude": "Latitud",
|
||||||
"longitude": "Longitud"
|
"longitude": "Longitud"
|
||||||
},
|
},
|
||||||
|
@ -14,7 +14,8 @@
|
|||||||
"api_key": "API-Schl\u00fcssel",
|
"api_key": "API-Schl\u00fcssel",
|
||||||
"latitude": "Breitengrad",
|
"latitude": "Breitengrad",
|
||||||
"longitude": "L\u00e4ngengrad"
|
"longitude": "L\u00e4ngengrad"
|
||||||
}
|
},
|
||||||
|
"title": "Konfigurieren Sie eine Geografie"
|
||||||
},
|
},
|
||||||
"node_pro": {
|
"node_pro": {
|
||||||
"data": {
|
"data": {
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
"abort": {
|
"abort": {
|
||||||
"already_configured": "These coordinates have already been registered."
|
"already_configured": "These coordinates or Node/Pro ID are already registered."
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"invalid_api_key": "Invalid API key"
|
"general_error": "There was an unknown error.",
|
||||||
|
"invalid_api_key": "Invalid API key provided.",
|
||||||
|
"unable_to_connect": "Unable to connect to Node/Pro unit."
|
||||||
},
|
},
|
||||||
"step": {
|
"step": {
|
||||||
"user": {
|
"geography": {
|
||||||
"data": {
|
"data": {
|
||||||
"api_key": "API Key",
|
"api_key": "API Key",
|
||||||
"latitude": "Latitude",
|
"latitude": "Latitude",
|
||||||
@ -19,7 +21,7 @@
|
|||||||
"node_pro": {
|
"node_pro": {
|
||||||
"data": {
|
"data": {
|
||||||
"ip_address": "Unit IP Address/Hostname",
|
"ip_address": "Unit IP Address/Hostname",
|
||||||
"password": "Unit Password"
|
"password": "Password"
|
||||||
},
|
},
|
||||||
"description": "Monitor a personal AirVisual unit. The password can be retrieved from the unit's UI.",
|
"description": "Monitor a personal AirVisual unit. The password can be retrieved from the unit's UI.",
|
||||||
"title": "Configure an AirVisual Node/Pro"
|
"title": "Configure an AirVisual Node/Pro"
|
||||||
|
@ -4,14 +4,34 @@
|
|||||||
"already_configured": "Estas coordenadas ya han sido registradas."
|
"already_configured": "Estas coordenadas ya han sido registradas."
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"invalid_api_key": "Clave de API inv\u00e1lida"
|
"general_error": "Se ha producido un error desconocido.",
|
||||||
|
"unable_to_connect": "No se puede conectar a la unidad Node/Pro."
|
||||||
},
|
},
|
||||||
"step": {
|
"step": {
|
||||||
|
"geography": {
|
||||||
|
"data": {
|
||||||
|
"latitude": "Latitud",
|
||||||
|
"longitude": "Longitud"
|
||||||
|
},
|
||||||
|
"description": "Use la API de AirVisual para monitorear una ubicaci\u00f3n geogr\u00e1fica.",
|
||||||
|
"title": "Configurar una geograf\u00eda"
|
||||||
|
},
|
||||||
|
"node_pro": {
|
||||||
|
"data": {
|
||||||
|
"ip_address": "Direcci\u00f3n IP/nombre de host de la unidad",
|
||||||
|
"password": "Contrase\u00f1a de la unidad"
|
||||||
|
},
|
||||||
|
"description": "Monitoree una unidad AirVisual personal. La contrase\u00f1a se puede recuperar de la interfaz de usuario de la unidad.",
|
||||||
|
"title": "Configurar un AirVisual Node/Pro"
|
||||||
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"data": {
|
"data": {
|
||||||
"api_key": "Clave API",
|
"api_key": "Clave API",
|
||||||
|
"cloud_api": "Localizaci\u00f3n geogr\u00e1fica",
|
||||||
"latitude": "Latitud",
|
"latitude": "Latitud",
|
||||||
"longitude": "Longitud"
|
"longitude": "Longitud",
|
||||||
|
"node_pro": "AirVisual Node Pro",
|
||||||
|
"type": "Tipo de integraci\u00f3n"
|
||||||
},
|
},
|
||||||
"description": "Monitoree la calidad del aire en una ubicaci\u00f3n geogr\u00e1fica.",
|
"description": "Monitoree la calidad del aire en una ubicaci\u00f3n geogr\u00e1fica.",
|
||||||
"title": "Configurar AirVisual"
|
"title": "Configurar AirVisual"
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
"abort": {
|
"abort": {
|
||||||
"already_configured": "Esta clave API ya est\u00e1 en uso."
|
"already_configured": "Estas coordenadas o Nodo/Pro ID ya est\u00e1n registradas."
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"general_error": "Se ha producido un error desconocido.",
|
"general_error": "Se ha producido un error desconocido.",
|
||||||
"invalid_api_key": "Clave API inv\u00e1lida",
|
"invalid_api_key": "Se proporciona una clave API no v\u00e1lida.",
|
||||||
"unable_to_connect": "No se puede conectar a la unidad Node/Pro."
|
"unable_to_connect": "No se puede conectar a la unidad Node/Pro."
|
||||||
},
|
},
|
||||||
"step": {
|
"step": {
|
||||||
@ -35,7 +35,7 @@
|
|||||||
"node_pro": "AirVisual Node Pro",
|
"node_pro": "AirVisual Node Pro",
|
||||||
"type": "Tipo de Integraci\u00f3n"
|
"type": "Tipo de Integraci\u00f3n"
|
||||||
},
|
},
|
||||||
"description": "Monitorizar la calidad del aire en una ubicaci\u00f3n geogr\u00e1fica.",
|
"description": "Elige qu\u00e9 tipo de datos de AirVisual quieres monitorear.",
|
||||||
"title": "Configurar AirVisual"
|
"title": "Configurar AirVisual"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
28
homeassistant/components/airvisual/translations/fi.json
Normal file
28
homeassistant/components/airvisual/translations/fi.json
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"error": {
|
||||||
|
"general_error": "Tapahtui tuntematon virhe."
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"geography": {
|
||||||
|
"data": {
|
||||||
|
"api_key": "API-avain",
|
||||||
|
"latitude": "Leveysaste",
|
||||||
|
"longitude": "Pituusaste"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_pro": {
|
||||||
|
"data": {
|
||||||
|
"password": "Salasana"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"cloud_api": "Maantieteellinen sijainti",
|
||||||
|
"node_pro": "AirVisual Node Pro",
|
||||||
|
"type": "Integrointityyppi"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -4,21 +4,36 @@
|
|||||||
"already_configured": "Cette cl\u00e9 API est d\u00e9j\u00e0 utilis\u00e9e."
|
"already_configured": "Cette cl\u00e9 API est d\u00e9j\u00e0 utilis\u00e9e."
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"invalid_api_key": "Cl\u00e9 API invalide"
|
"general_error": "Une erreur inconnue est survenue.",
|
||||||
|
"invalid_api_key": "La cl\u00e9 API fournie n'est pas valide.",
|
||||||
|
"unable_to_connect": "Impossible de se connecter \u00e0 l'unit\u00e9 Node / Pro."
|
||||||
},
|
},
|
||||||
"step": {
|
"step": {
|
||||||
"geography": {
|
"geography": {
|
||||||
"data": {
|
"data": {
|
||||||
"api_key": "Cl\u00e9 d'API",
|
"api_key": "Cl\u00e9 API",
|
||||||
"latitude": "Latitude",
|
"latitude": "Latitude",
|
||||||
"longitude": "Longitude"
|
"longitude": "Longitude"
|
||||||
}
|
},
|
||||||
|
"description": "Utilisez l'API cloud AirVisual pour surveiller une position g\u00e9ographique.",
|
||||||
|
"title": "Configurer une g\u00e9ographie"
|
||||||
|
},
|
||||||
|
"node_pro": {
|
||||||
|
"data": {
|
||||||
|
"ip_address": "Adresse IP / nom d'h\u00f4te de l'unit\u00e9",
|
||||||
|
"password": "Mot de passe de l'unit\u00e9"
|
||||||
|
},
|
||||||
|
"description": "Surveillez une unit\u00e9 AirVisual personnelle. Le mot de passe peut \u00eatre r\u00e9cup\u00e9r\u00e9 dans l'interface utilisateur de l'unit\u00e9.",
|
||||||
|
"title": "Configurer un AirVisual Node/Pro"
|
||||||
},
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"data": {
|
"data": {
|
||||||
"api_key": "Cl\u00e9 API",
|
"api_key": "Cl\u00e9 API",
|
||||||
|
"cloud_api": "Localisation g\u00e9ographique",
|
||||||
"latitude": "Latitude",
|
"latitude": "Latitude",
|
||||||
"longitude": "Longitude"
|
"longitude": "Longitude",
|
||||||
|
"node_pro": "AirVisual Node Pro",
|
||||||
|
"type": "Type d'int\u00e9gration"
|
||||||
},
|
},
|
||||||
"description": "Surveiller la qualit\u00e9 de l\u2019air dans un emplacement g\u00e9ographique.",
|
"description": "Surveiller la qualit\u00e9 de l\u2019air dans un emplacement g\u00e9ographique.",
|
||||||
"title": "Configurer AirVisual"
|
"title": "Configurer AirVisual"
|
||||||
@ -28,6 +43,9 @@
|
|||||||
"options": {
|
"options": {
|
||||||
"step": {
|
"step": {
|
||||||
"init": {
|
"init": {
|
||||||
|
"data": {
|
||||||
|
"show_on_map": "Afficher la g\u00e9ographie surveill\u00e9e sur la carte"
|
||||||
|
},
|
||||||
"description": "D\u00e9finissez diverses options pour l'int\u00e9gration d'AirVisual.",
|
"description": "D\u00e9finissez diverses options pour l'int\u00e9gration d'AirVisual.",
|
||||||
"title": "Configurer AirVisual"
|
"title": "Configurer AirVisual"
|
||||||
}
|
}
|
||||||
|
14
homeassistant/components/airvisual/translations/he.json
Normal file
14
homeassistant/components/airvisual/translations/he.json
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"error": {
|
||||||
|
"invalid_api_key": "\u05de\u05e4\u05ea\u05d7 API \u05dc\u05d0 \u05d7\u05d5\u05e7\u05d9 \u05e1\u05d5\u05e4\u05e7"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"geography": {
|
||||||
|
"data": {
|
||||||
|
"api_key": "\u05de\u05e4\u05ea\u05d7 API"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -7,7 +7,6 @@
|
|||||||
"step": {
|
"step": {
|
||||||
"geography": {
|
"geography": {
|
||||||
"data": {
|
"data": {
|
||||||
"api_key": "\u090f\u092a\u0940\u0906\u0908 \u0915\u0941\u0902\u091c\u0940",
|
|
||||||
"latitude": "\u0905\u0915\u094d\u0937\u093e\u0902\u0936",
|
"latitude": "\u0905\u0915\u094d\u0937\u093e\u0902\u0936",
|
||||||
"longitude": "\u0926\u0947\u0936\u093e\u0928\u094d\u0924\u0930"
|
"longitude": "\u0926\u0947\u0936\u093e\u0928\u094d\u0924\u0930"
|
||||||
},
|
},
|
||||||
|
16
homeassistant/components/airvisual/translations/hu.json
Normal file
16
homeassistant/components/airvisual/translations/hu.json
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"error": {
|
||||||
|
"general_error": "Ismeretlen hiba t\u00f6rt\u00e9nt."
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"geography": {
|
||||||
|
"data": {
|
||||||
|
"api_key": "API Kulcs",
|
||||||
|
"latitude": "Sz\u00e9less\u00e9g",
|
||||||
|
"longitude": "Hossz\u00fas\u00e1g"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,19 +1,41 @@
|
|||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
"abort": {
|
"abort": {
|
||||||
"already_configured": "Queste coordinate sono gi\u00e0 state registrate."
|
"already_configured": "Queste coordinate o Node/Pro ID sono gi\u00e0 registrate."
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"invalid_api_key": "Chiave API non valida"
|
"general_error": "Si \u00e8 verificato un errore sconosciuto.",
|
||||||
|
"invalid_api_key": "Chiave API non valida fornita.",
|
||||||
|
"unable_to_connect": "Impossibile connettersi all'unit\u00e0 Node/Pro."
|
||||||
},
|
},
|
||||||
"step": {
|
"step": {
|
||||||
"user": {
|
"geography": {
|
||||||
"data": {
|
"data": {
|
||||||
"api_key": "Chiave API",
|
"api_key": "Chiave API",
|
||||||
"latitude": "Latitudine",
|
"latitude": "Latitudine",
|
||||||
"longitude": "Logitudine"
|
"longitude": "Logitudine"
|
||||||
},
|
},
|
||||||
"description": "Monitorare la qualit\u00e0 dell'aria in una posizione geografica.",
|
"description": "Utilizzare l'API di AirVisual cloud per monitorare una posizione geografica.",
|
||||||
|
"title": "Configurare una Geografia"
|
||||||
|
},
|
||||||
|
"node_pro": {
|
||||||
|
"data": {
|
||||||
|
"ip_address": "Indirizzo IP/Nome host dell'unit\u00e0",
|
||||||
|
"password": "Password dell'unit\u00e0"
|
||||||
|
},
|
||||||
|
"description": "Monitorare un'unit\u00e0 AirVisual personale. La password pu\u00f2 essere recuperata dall'interfaccia utente dell'unit\u00e0.",
|
||||||
|
"title": "Configurare un AirVisual Node/Pro"
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"api_key": "Chiave API",
|
||||||
|
"cloud_api": "Posizione geografica",
|
||||||
|
"latitude": "Latitudine",
|
||||||
|
"longitude": "Logitudine",
|
||||||
|
"node_pro": "AirVisual Node Pro",
|
||||||
|
"type": "Tipo di integrazione"
|
||||||
|
},
|
||||||
|
"description": "Scegliere il tipo di dati AirVisual che si desidera monitorare.",
|
||||||
"title": "Configura AirVisual"
|
"title": "Configura AirVisual"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user