Compare commits

..

95 Commits

Author SHA1 Message Date
Paulus Schoutsen
245eec7041 Merge pull request #55532 from home-assistant/rc 2021-09-01 11:28:21 -07:00
Bram Kragten
493309daa7 Bumped version to 2021.9.0 2021-09-01 19:40:48 +02:00
Paulus Schoutsen
af68802c17 Tweaks for the iotawatt integration (#55510) 2021-09-01 19:37:43 +02:00
Brian Egge
576cece7a9 Fix None support_color_modes TypeError (#55497)
* Fix None support_color_modes TypeError 

https://github.com/home-assistant/core/issues/55451

* Update __init__.py
2021-09-01 19:37:43 +02:00
Otto Winter
3b9859940f ESPHome light color mode use capabilities (#55206)
Co-authored-by: Oxan van Leeuwen <oxan@oxanvanleeuwen.nl>
2021-09-01 19:37:41 +02:00
Paulus Schoutsen
a315fd059a Bumped version to 2021.9.0b7 2021-08-31 22:57:33 -07:00
Brett Adams
ba9ef004c8 Add missing device class for temperature sensor in Advantage Air (#55508) 2021-08-31 22:57:13 -07:00
Felipe Martins Diel
22f745b17c Fix BroadlinkSwitch._attr_assumed_state (#55505) 2021-08-31 22:57:12 -07:00
muppet3000
05cf223146 Added trailing slash to US growatt URL (#55504) 2021-08-31 22:57:12 -07:00
Erik Montnemery
d4aadd8af0 Improve log for sum statistics (#55502) 2021-08-31 22:56:28 -07:00
Erik Montnemery
4045eee2e5 Correct sum statistics when only last_reset has changed (#55498)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2021-08-31 22:53:58 -07:00
Joakim Sørensen
83a51f7f30 Add cache-control headers to supervisor entrypoint (#55493) 2021-08-31 22:52:05 -07:00
gjong
29110fe157 Remove Youless native unit of measurement (#55492) 2021-08-31 22:52:05 -07:00
gjong
e87b7e24b4 Increase YouLess polling interval (#55490) 2021-08-31 22:52:04 -07:00
uvjustin
d9056c01a6 Fix ArestSwitchBase missing is on attribute (#55483) 2021-08-31 22:52:03 -07:00
Matthew Garrett
a724bc21b6 Assistant sensors (#55480) 2021-08-31 22:52:03 -07:00
Paulus Schoutsen
ef00178339 Add Eagle 200 name back (#55477)
* Add Eagle 200 name back

* add comment

* update tests
2021-08-31 22:52:02 -07:00
Erik Montnemery
b8770c3958 Make new cycles for sensor sum statistics start with 0 as zero-point (#55473) 2021-08-31 22:52:01 -07:00
Eric Severance
f0c0cfcac0 Wemo Insight devices need polling when off (#55348) 2021-08-31 22:52:00 -07:00
Bram Kragten
4c48ad9108 Bumped version to Bumped version to 2021.9.0b6 2021-08-30 23:35:50 +02:00
Bram Kragten
92b0453749 Update frontend to 20210830.0 (#55472) 2021-08-30 23:33:47 +02:00
Raman Gupta
8ab801a7b4 Fix area_id and area_name template functions (#55470) 2021-08-30 23:33:46 +02:00
Aaron Bach
f92c7b1aea Bump aioambient to 1.3.0 (#55468) 2021-08-30 23:33:45 +02:00
Aaron Bach
0d9fbf864f Bump pyiqvia to 1.1.0 (#55466) 2021-08-30 23:33:44 +02:00
Aaron Bach
275f9c8a28 Bump pyopenuv to 2.2.0 (#55464) 2021-08-30 23:33:43 +02:00
Erik Montnemery
84f3b1514f Fix race in MQTT sensor when last_reset_topic is configured (#55463) 2021-08-30 23:33:43 +02:00
Greg
802f5613c4 Add IoTaWatt integration (#55364)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2021-08-30 23:33:42 +02:00
Paulus Schoutsen
8be40cbb00 Bumped version to 2021.9.0b5 2021-08-30 09:41:51 -07:00
Raman Gupta
46ce4e92f6 Bump zwave-js-server-python to 0.29.1 (#55460) 2021-08-30 09:41:42 -07:00
J. Nick Koston
39f11bb46d Bump zeroconf to 0.36.2 (#55459)
- Now sends NSEC records when requesting non-existent address types
  Implements RFC6762 sec 6.2 (http://datatracker.ietf.org/doc/html/rfc6762#section-6.2)

- This solves a case where a HomeKit bridge can take a while to update
  because it is waiting to see if an AAAA (IPv6) address is available
2021-08-30 09:41:42 -07:00
Erik Montnemery
3b0fe9adde Revert "Deprecate last_reset options in MQTT sensor" (#55457)
This reverts commit f9fa5fa804.
2021-08-30 09:41:41 -07:00
Simone Chemelli
707778229b Fix noise/attenuation units to UI display for Fritz (#55447) 2021-08-30 09:41:40 -07:00
Erik Montnemery
a474534c08 Fix exception when shutting down DSMR (#55441)
* Fix exception when shutting down DSMR

* Update homeassistant/components/dsmr/sensor.py

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2021-08-30 09:41:39 -07:00
Erik Montnemery
65ad99d51c Fix crash in buienradar sensor due to self.hass not set (#55438) 2021-08-30 09:41:39 -07:00
Erik Montnemery
4052a0db89 Improve statistics error messages when sensor's unit is changing (#55436)
* Improve error messages when sensor's unit is changing

* Improve test coverage
2021-08-30 09:41:38 -07:00
Raman Gupta
b546fc5067 Don't set zwave_js sensor device class to energy when unit is wrong (#55434) 2021-08-30 09:41:37 -07:00
Christopher Kochan
5dcc760755 Add Sense energy sensors (#54833)
Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
2021-08-30 09:41:36 -07:00
Paulus Schoutsen
fb06acf39d Bumped version to 2021.9.0b4 2021-08-29 20:45:45 -07:00
Raman Gupta
948f191f16 Make zwave_js discovery log message more descriptive (#55432) 2021-08-29 20:45:33 -07:00
Klaas Schoute
2c0d9105ac Update entity names for P1 Monitor integration (#55430) 2021-08-29 20:45:32 -07:00
J. Nick Koston
10df9f3542 Bump zeroconf to 0.36.1 (#55425)
- Fixes duplicate records in the cache

- Changelog: https://github.com/jstasiak/python-zeroconf/compare/0.36.0...0.36.1
2021-08-29 20:45:32 -07:00
Aaron Bach
6cf799459b Ensure ReCollect Waste shows pickups for midnight on the actual day (#55424) 2021-08-29 20:44:57 -07:00
Marc Mueller
47e2d1caa5 Fix device_class - qnap drive_temp sensor (#55409) 2021-08-29 20:41:25 -07:00
J. Nick Koston
69d8f94e3b Show device_id in HomeKit when the device registry entry is missing a name (#55391)
- Reported at: https://community.home-assistant.io/t/homekit-unknown-error-occurred/333385
2021-08-29 20:41:24 -07:00
Aaron Bach
4b7803ed03 Bump simplisafe-python to 11.0.6 (#55385) 2021-08-29 20:41:24 -07:00
J. Nick Koston
ff6015ff89 Implement import of consider_home in nmap_tracker to avoid breaking change (#55379) 2021-08-29 20:41:23 -07:00
Matt Krasowski
fbd144de46 Handle incorrect values reported by some Shelly devices (#55042) 2021-08-29 20:41:22 -07:00
Paulus Schoutsen
adaebdeea8 Bumped version to 2021.9.0b3 2021-08-28 08:59:25 -07:00
Maciej Bieniek
910cb5865a Address late review for Tractive integration (#55371) 2021-08-28 08:58:38 -07:00
Joakim Sørensen
baf0d9b2d9 Pin regex to 2021.8.28 (#55368) 2021-08-28 08:58:37 -07:00
Jason Hunter
c1bce68549 close connection on connection retry, bump onvif lib (#55363) 2021-08-28 08:58:36 -07:00
Nathan Spencer
bde4c0e46f Bump pylitterbot to 2021.8.1 (#55360) 2021-08-28 08:58:35 -07:00
Paulus Schoutsen
a275e7aa67 Fix wolflink super call (#55359) 2021-08-28 08:58:35 -07:00
Aaron Bach
d96e416d26 Ensure ReCollect Waste starts up even if no future pickup is found (#55349) 2021-08-28 08:58:34 -07:00
Paulus Schoutsen
efc3894303 Convert solarlog to coordinator (#55345) 2021-08-28 08:58:33 -07:00
Daniel Hjelseth Høyer
06b47ee2f5 Tractive name (#55342) 2021-08-28 08:58:33 -07:00
Raman Gupta
08ca43221f Listen to node events in the zwave_js node status sensor (#55341) 2021-08-28 08:58:32 -07:00
J. Nick Koston
8641740ed8 Ensure yeelights resync state if they are busy on first connect (#55333) 2021-08-28 08:58:31 -07:00
Paulus Schoutsen
d0ada6c6e2 Bumped version to 2021.9.0b2 2021-08-27 10:00:20 -07:00
Anders Melchiorsen
76bb036968 Upgrade aiolifx to 0.6.10 (#55344) 2021-08-27 10:00:00 -07:00
J. Nick Koston
d8b64be41c Retrigger config flow when the ssdp location changes for a UDN (#55343)
Fixes #55229
2021-08-27 09:59:59 -07:00
jan iversen
b3e0b7b86e Add modbus name to log_error (#55336) 2021-08-27 09:59:59 -07:00
Chris Talkington
e097e4c1c2 Fix reauth for sonarr (#55329)
* fix reauth for sonarr

* Update config_flow.py

* Update config_flow.py

* Update config_flow.py

* Update test_config_flow.py

* Update config_flow.py

* Update test_config_flow.py

* Update config_flow.py
2021-08-27 09:59:58 -07:00
Robert Hillis
34f0fecef8 Fix sonos alarm schema (#55318) 2021-08-27 09:59:57 -07:00
Erik Montnemery
f53a10d39a Handle statistics for sensor with changing state class (#55316) 2021-08-27 09:59:56 -07:00
J. Nick Koston
5b993129d6 Fix lifx model to be a string (#55309)
Fixes #55307
2021-08-27 09:59:56 -07:00
J. Nick Koston
865656d436 Always send powerview move command in case shade is out of sync (#55308) 2021-08-27 09:59:55 -07:00
Aaron Bach
fb25c6c115 Bump simplisafe-python to 11.0.5 (#55306) 2021-08-27 09:59:54 -07:00
Aaron Bach
c963cf8743 Bump aiorecollect to 1.0.8 (#55300) 2021-08-27 09:59:54 -07:00
rikroe
ddb28db21a Bump bimmer_connected to 0.7.20 (#55299) 2021-08-27 09:59:53 -07:00
J. Nick Koston
bfc98b444f Fix creation of new nmap tracker entities (#55297) 2021-08-27 09:59:52 -07:00
realPy
f9a0f44137 Correct flash light livarno when use hue (#55294)
Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
2021-08-27 09:59:51 -07:00
J. Nick Koston
93750d71ce Gracefully handle pyudev failing to filter on WSL (#55286)
* Gracefully handle pyudev failing to filter on WSL

* add debug message

* add mocks so we reach the new check
2021-08-27 09:59:51 -07:00
Paulus Schoutsen
06e4003640 Bump ring to 0.7.1 (#55282) 2021-08-27 09:59:50 -07:00
J. Nick Koston
97ff5e2085 Set yeelight capabilities from external discovery (#55280) 2021-08-27 09:59:49 -07:00
J. Nick Koston
8a2c07ce19 Ensure yeelight model is set in the config entry (#55281)
* Ensure yeelight model is set in the config entry

- If the model was not set in the config entry the light could
  be sent commands it could not handle

* update tests

* fix test
2021-08-27 09:59:21 -07:00
J. Nick Koston
9f7398e0df Fix yeelight brightness when nightlight switch is disabled (#55278) 2021-08-27 09:57:07 -07:00
J. Nick Koston
7df84dadad Fix some yeelights showing wrong state after on/off (#55279) 2021-08-27 09:56:22 -07:00
Chris
2a1e943b18 Fix unique_id conflict in smarttthings (#55235) 2021-08-27 09:54:26 -07:00
prwood80
e6e72bfa82 Improve performance of ring camera still images (#53803)
Co-authored-by: Pat Wood <prwood80@users.noreply.github.com>
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
2021-08-27 09:54:25 -07:00
Paulus Schoutsen
219868b308 Bumped version to 2021.9.0b1 2021-08-26 09:37:25 -07:00
Maciej Bieniek
67dd861d8c Fix AttributeError for non-MIOT Xiaomi Miio purifiers (#55271) 2021-08-26 09:37:20 -07:00
Florian Gareis
f2765ba320 Don't create DSL sensor for devices that don't support DSL (#55269) 2021-08-26 09:37:19 -07:00
Erik Montnemery
aefd3df914 Warn if a sensor with state_class_total has a decreasing value twice (#55251) 2021-08-26 09:37:18 -07:00
Franck Nijhof
3658eeb8d1 Fix MQTT add-on discovery to be ignorable (#55250) 2021-08-26 09:37:07 -07:00
Erik Montnemery
080cb6b6e9 Fix double precision float for postgresql (#55249) 2021-08-26 09:37:06 -07:00
Joakim Sørensen
20796303da Only postfix image name for container (#55248) 2021-08-26 09:37:06 -07:00
J. Nick Koston
dff6151ff4 Abort zha usb discovery if deconz is setup (#55245)
* Abort zha usb discovery if deconz is setup

* Update tests/components/zha/test_config_flow.py

* add deconz domain const

* Update homeassistant/components/zha/config_flow.py

Co-authored-by: Robert Svensson <Kane610@users.noreply.github.com>

Co-authored-by: Robert Svensson <Kane610@users.noreply.github.com>
2021-08-26 09:37:05 -07:00
Alexei Chetroi
6f24f4e302 Bump up ZHA dependencies (#55242)
* Bump up ZHA dependencies

* Bump up zha-device-handlers
2021-08-26 09:37:04 -07:00
J. Nick Koston
175febe635 Defer zha auto configure probe until after clicking configure (#55239) 2021-08-26 09:37:03 -07:00
J. Nick Koston
aa907f4d10 Only warn once per entity when the async_camera_image signature needs to be updated (#55238) 2021-08-26 09:37:02 -07:00
J. Nick Koston
3d09478aea Limit USB discovery to specific manufacturer/description/serial_number matches (#55236)
* Limit USB discovery to specific manufacturer/description/serial_number matches

* test for None case
2021-08-26 09:37:01 -07:00
Marc Mueller
05df9b4b8b Remove temperature conversion - tado (#55231) 2021-08-26 09:37:01 -07:00
jjlawren
1865a28083 Set up polling task with subscriptions in Sonos (#54355) 2021-08-26 09:37:00 -07:00
Franck Nijhof
f78d57515a Bumped version to 2021.9.0b0 2021-08-25 22:11:21 +02:00
914 changed files with 12203 additions and 17504 deletions

View File

@@ -1032,8 +1032,6 @@ omit =
homeassistant/components/tank_utility/sensor.py
homeassistant/components/tankerkoenig/*
homeassistant/components/tapsaff/binary_sensor.py
homeassistant/components/tautulli/const.py
homeassistant/components/tautulli/coordinator.py
homeassistant/components/tautulli/sensor.py
homeassistant/components/ted5000/sensor.py
homeassistant/components/telegram/notify.py

View File

@@ -65,6 +65,7 @@ jobs:
matrix:
arch: ${{ fromJson(needs.init.outputs.architectures) }}
tag:
- "3.9-alpine3.13"
- "3.9-alpine3.14"
steps:
- name: Checkout the repository
@@ -105,6 +106,7 @@ jobs:
matrix:
arch: ${{ fromJson(needs.init.outputs.architectures) }}
tag:
- "3.9-alpine3.13"
- "3.9-alpine3.14"
steps:
- name: Checkout the repository

View File

@@ -202,7 +202,7 @@ homeassistant/components/group/* @home-assistant/core
homeassistant/components/growatt_server/* @indykoning @muppet3000 @JasperPlant
homeassistant/components/guardian/* @bachya
homeassistant/components/habitica/* @ASMfreaK @leikoilja
homeassistant/components/harmony/* @ehendrix23 @bramkragten @bdraco @mkeesey @Aohzan
homeassistant/components/harmony/* @ehendrix23 @bramkragten @bdraco @mkeesey
homeassistant/components/hassio/* @home-assistant/supervisor
homeassistant/components/heatmiser/* @andylockran
homeassistant/components/heos/* @andrewsayre
@@ -553,7 +553,6 @@ homeassistant/components/uptimerobot/* @ludeeus
homeassistant/components/usb/* @bdraco
homeassistant/components/usgs_earthquakes_feed/* @exxamalte
homeassistant/components/utility_meter/* @dgomes
homeassistant/components/vallox/* @andre-richter
homeassistant/components/velbus/* @Cereal2nd @brefra
homeassistant/components/velux/* @Julius2342
homeassistant/components/vera/* @pavoni

View File

@@ -16,21 +16,6 @@ RUN \
-e ./homeassistant \
&& python3 -m compileall homeassistant/homeassistant
# Fix Bug with Alpine 3.14 and sqlite 3.35
# https://gitlab.alpinelinux.org/alpine/aports/-/issues/12524
ARG BUILD_ARCH
RUN \
if [ "${BUILD_ARCH}" = "amd64" ]; then \
export APK_ARCH=x86_64; \
elif [ "${BUILD_ARCH}" = "i386" ]; then \
export APK_ARCH=x86; \
else \
export APK_ARCH=${BUILD_ARCH}; \
fi \
&& curl -O http://dl-cdn.alpinelinux.org/alpine/v3.13/main/${APK_ARCH}/sqlite-libs-3.34.1-r0.apk \
&& apk add --no-cache sqlite-libs-3.34.1-r0.apk \
&& rm -f sqlite-libs-3.34.1-r0.apk
# Home Assistant S6-Overlay
COPY rootfs /

View File

@@ -6,8 +6,6 @@ RUN \
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
&& apt-get update \
&& DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
# Additional library needed by some tests and accordingly by VScode Tests Discovery
bluez \
libudev-dev \
libavformat-dev \
libavcodec-dev \

View File

@@ -118,6 +118,14 @@ homeassistant.util.pressure
:undoc-members:
:show-inheritance:
homeassistant.util.ruamel\_yaml
-------------------------------
.. automodule:: homeassistant.util.ruamel_yaml
:members:
:undoc-members:
:show-inheritance:
homeassistant.util.ssl
----------------------

View File

@@ -342,11 +342,7 @@ def async_enable_logging(
err_log_path, backupCount=1
)
try:
err_handler.doRollover()
except OSError as err:
_LOGGER.error("Error rolling over log file: %s", err)
err_handler.doRollover()
err_handler.setLevel(logging.INFO if verbose else logging.WARNING)
err_handler.setFormatter(logging.Formatter(fmt, datefmt=datefmt))

View File

@@ -2,7 +2,7 @@
"config": {
"abort": {
"reauth_successful": "La r\u00e9-authentification a r\u00e9ussi",
"single_instance_allowed": "D\u00e9j\u00e0 configur\u00e9. Une seule configuration possible."
"single_instance_allowed": "D\u00e9ja configur\u00e9. Une seule configuration possible."
},
"error": {
"cannot_connect": "\u00c9chec de connexion",

View File

@@ -14,7 +14,7 @@
"api_key": "Cl\u00e9 d'API",
"latitude": "Latitude",
"longitude": "Longitude",
"name": "Nom"
"name": "Nom de l'int\u00e9gration"
},
"description": "Si vous avez besoin d'aide pour la configuration, consultez le site suivant : https://www.home-assistant.io/integrations/accuweather/\n\nCertains capteurs ne sont pas activ\u00e9s par d\u00e9faut. Vous pouvez les activer dans le registre des entit\u00e9s apr\u00e8s la configuration de l'int\u00e9gration.\nLes pr\u00e9visions m\u00e9t\u00e9orologiques ne sont pas activ\u00e9es par d\u00e9faut. Vous pouvez l'activer dans les options d'int\u00e9gration.",
"title": "AccuWeather"

View File

@@ -3,9 +3,7 @@
"step": {
"user": {
"data": {
"account_id": "ID de la cuenta",
"host": "Anfitri\u00f3n",
"password": "Contrase\u00f1a"
"account_id": "ID de la cuenta"
}
}
}

View File

@@ -17,9 +17,9 @@
"host": "H\u00f4te",
"password": "Mot de passe",
"port": "Port",
"ssl": "Utilise un certificat SSL",
"ssl": "AdGuard Home utilise un certificat SSL",
"username": "Nom d'utilisateur",
"verify_ssl": "V\u00e9rifier le certificat SSL"
"verify_ssl": "AdGuard Home utilise un certificat appropri\u00e9"
},
"description": "Configurez votre instance AdGuard Home pour permettre la surveillance et le contr\u00f4le."
}

View File

@@ -1,7 +1,5 @@
"""Constant values for the AEMET OpenData component."""
from __future__ import annotations
from homeassistant.components.sensor import SensorEntityDescription
from homeassistant.components.weather import (
ATTR_CONDITION_CLEAR_NIGHT,
ATTR_CONDITION_CLOUDY,
@@ -42,6 +40,9 @@ DEFAULT_NAME = "AEMET"
DOMAIN = "aemet"
ENTRY_NAME = "name"
ENTRY_WEATHER_COORDINATOR = "weather_coordinator"
SENSOR_NAME = "sensor_name"
SENSOR_UNIT = "sensor_unit"
SENSOR_DEVICE_CLASS = "sensor_device_class"
ATTR_API_CONDITION = "condition"
ATTR_API_FORECAST_DAILY = "forecast-daily"
@@ -199,145 +200,118 @@ FORECAST_MODE_ATTR_API = {
FORECAST_MODE_HOURLY: ATTR_API_FORECAST_HOURLY,
}
FORECAST_SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
SensorEntityDescription(
key=ATTR_FORECAST_CONDITION,
name="Condition",
),
SensorEntityDescription(
key=ATTR_FORECAST_PRECIPITATION,
name="Precipitation",
native_unit_of_measurement=PRECIPITATION_MILLIMETERS_PER_HOUR,
),
SensorEntityDescription(
key=ATTR_FORECAST_PRECIPITATION_PROBABILITY,
name="Precipitation probability",
native_unit_of_measurement=PERCENTAGE,
),
SensorEntityDescription(
key=ATTR_FORECAST_TEMP,
name="Temperature",
native_unit_of_measurement=TEMP_CELSIUS,
device_class=DEVICE_CLASS_TEMPERATURE,
),
SensorEntityDescription(
key=ATTR_FORECAST_TEMP_LOW,
name="Temperature Low",
native_unit_of_measurement=TEMP_CELSIUS,
device_class=DEVICE_CLASS_TEMPERATURE,
),
SensorEntityDescription(
key=ATTR_FORECAST_TIME,
name="Time",
device_class=DEVICE_CLASS_TIMESTAMP,
),
SensorEntityDescription(
key=ATTR_FORECAST_WIND_BEARING,
name="Wind bearing",
native_unit_of_measurement=DEGREE,
),
SensorEntityDescription(
key=ATTR_FORECAST_WIND_SPEED,
name="Wind speed",
native_unit_of_measurement=SPEED_KILOMETERS_PER_HOUR,
),
)
WEATHER_SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
SensorEntityDescription(
key=ATTR_API_CONDITION,
name="Condition",
),
SensorEntityDescription(
key=ATTR_API_HUMIDITY,
name="Humidity",
native_unit_of_measurement=PERCENTAGE,
device_class=DEVICE_CLASS_HUMIDITY,
),
SensorEntityDescription(
key=ATTR_API_PRESSURE,
name="Pressure",
native_unit_of_measurement=PRESSURE_HPA,
device_class=DEVICE_CLASS_PRESSURE,
),
SensorEntityDescription(
key=ATTR_API_RAIN,
name="Rain",
native_unit_of_measurement=PRECIPITATION_MILLIMETERS_PER_HOUR,
),
SensorEntityDescription(
key=ATTR_API_RAIN_PROB,
name="Rain probability",
native_unit_of_measurement=PERCENTAGE,
),
SensorEntityDescription(
key=ATTR_API_SNOW,
name="Snow",
native_unit_of_measurement=PRECIPITATION_MILLIMETERS_PER_HOUR,
),
SensorEntityDescription(
key=ATTR_API_SNOW_PROB,
name="Snow probability",
native_unit_of_measurement=PERCENTAGE,
),
SensorEntityDescription(
key=ATTR_API_STATION_ID,
name="Station ID",
),
SensorEntityDescription(
key=ATTR_API_STATION_NAME,
name="Station name",
),
SensorEntityDescription(
key=ATTR_API_STATION_TIMESTAMP,
name="Station timestamp",
device_class=DEVICE_CLASS_TIMESTAMP,
),
SensorEntityDescription(
key=ATTR_API_STORM_PROB,
name="Storm probability",
native_unit_of_measurement=PERCENTAGE,
),
SensorEntityDescription(
key=ATTR_API_TEMPERATURE,
name="Temperature",
native_unit_of_measurement=TEMP_CELSIUS,
device_class=DEVICE_CLASS_TEMPERATURE,
),
SensorEntityDescription(
key=ATTR_API_TEMPERATURE_FEELING,
name="Temperature feeling",
native_unit_of_measurement=TEMP_CELSIUS,
device_class=DEVICE_CLASS_TEMPERATURE,
),
SensorEntityDescription(
key=ATTR_API_TOWN_ID,
name="Town ID",
),
SensorEntityDescription(
key=ATTR_API_TOWN_NAME,
name="Town name",
),
SensorEntityDescription(
key=ATTR_API_TOWN_TIMESTAMP,
name="Town timestamp",
device_class=DEVICE_CLASS_TIMESTAMP,
),
SensorEntityDescription(
key=ATTR_API_WIND_BEARING,
name="Wind bearing",
native_unit_of_measurement=DEGREE,
),
SensorEntityDescription(
key=ATTR_API_WIND_MAX_SPEED,
name="Wind max speed",
native_unit_of_measurement=SPEED_KILOMETERS_PER_HOUR,
),
SensorEntityDescription(
key=ATTR_API_WIND_SPEED,
name="Wind speed",
native_unit_of_measurement=SPEED_KILOMETERS_PER_HOUR,
),
)
FORECAST_SENSOR_TYPES = {
ATTR_FORECAST_CONDITION: {
SENSOR_NAME: "Condition",
},
ATTR_FORECAST_PRECIPITATION: {
SENSOR_NAME: "Precipitation",
SENSOR_UNIT: PRECIPITATION_MILLIMETERS_PER_HOUR,
},
ATTR_FORECAST_PRECIPITATION_PROBABILITY: {
SENSOR_NAME: "Precipitation probability",
SENSOR_UNIT: PERCENTAGE,
},
ATTR_FORECAST_TEMP: {
SENSOR_NAME: "Temperature",
SENSOR_UNIT: TEMP_CELSIUS,
SENSOR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE,
},
ATTR_FORECAST_TEMP_LOW: {
SENSOR_NAME: "Temperature Low",
SENSOR_UNIT: TEMP_CELSIUS,
SENSOR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE,
},
ATTR_FORECAST_TIME: {
SENSOR_NAME: "Time",
SENSOR_DEVICE_CLASS: DEVICE_CLASS_TIMESTAMP,
},
ATTR_FORECAST_WIND_BEARING: {
SENSOR_NAME: "Wind bearing",
SENSOR_UNIT: DEGREE,
},
ATTR_FORECAST_WIND_SPEED: {
SENSOR_NAME: "Wind speed",
SENSOR_UNIT: SPEED_KILOMETERS_PER_HOUR,
},
}
WEATHER_SENSOR_TYPES = {
ATTR_API_CONDITION: {
SENSOR_NAME: "Condition",
},
ATTR_API_HUMIDITY: {
SENSOR_NAME: "Humidity",
SENSOR_UNIT: PERCENTAGE,
SENSOR_DEVICE_CLASS: DEVICE_CLASS_HUMIDITY,
},
ATTR_API_PRESSURE: {
SENSOR_NAME: "Pressure",
SENSOR_UNIT: PRESSURE_HPA,
SENSOR_DEVICE_CLASS: DEVICE_CLASS_PRESSURE,
},
ATTR_API_RAIN: {
SENSOR_NAME: "Rain",
SENSOR_UNIT: PRECIPITATION_MILLIMETERS_PER_HOUR,
},
ATTR_API_RAIN_PROB: {
SENSOR_NAME: "Rain probability",
SENSOR_UNIT: PERCENTAGE,
},
ATTR_API_SNOW: {
SENSOR_NAME: "Snow",
SENSOR_UNIT: PRECIPITATION_MILLIMETERS_PER_HOUR,
},
ATTR_API_SNOW_PROB: {
SENSOR_NAME: "Snow probability",
SENSOR_UNIT: PERCENTAGE,
},
ATTR_API_STATION_ID: {
SENSOR_NAME: "Station ID",
},
ATTR_API_STATION_NAME: {
SENSOR_NAME: "Station name",
},
ATTR_API_STATION_TIMESTAMP: {
SENSOR_NAME: "Station timestamp",
SENSOR_DEVICE_CLASS: DEVICE_CLASS_TIMESTAMP,
},
ATTR_API_STORM_PROB: {
SENSOR_NAME: "Storm probability",
SENSOR_UNIT: PERCENTAGE,
},
ATTR_API_TEMPERATURE: {
SENSOR_NAME: "Temperature",
SENSOR_UNIT: TEMP_CELSIUS,
SENSOR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE,
},
ATTR_API_TEMPERATURE_FEELING: {
SENSOR_NAME: "Temperature feeling",
SENSOR_UNIT: TEMP_CELSIUS,
SENSOR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE,
},
ATTR_API_TOWN_ID: {
SENSOR_NAME: "Town ID",
},
ATTR_API_TOWN_NAME: {
SENSOR_NAME: "Town name",
},
ATTR_API_TOWN_TIMESTAMP: {
SENSOR_NAME: "Town timestamp",
SENSOR_DEVICE_CLASS: DEVICE_CLASS_TIMESTAMP,
},
ATTR_API_WIND_BEARING: {
SENSOR_NAME: "Wind bearing",
SENSOR_UNIT: DEGREE,
},
ATTR_API_WIND_MAX_SPEED: {
SENSOR_NAME: "Wind max speed",
SENSOR_UNIT: SPEED_KILOMETERS_PER_HOUR,
},
ATTR_API_WIND_SPEED: {
SENSOR_NAME: "Wind speed",
SENSOR_UNIT: SPEED_KILOMETERS_PER_HOUR,
},
}
WIND_BEARING_MAP = {
"C": None,

View File

@@ -1,7 +1,5 @@
"""Support for the AEMET OpenData service."""
from __future__ import annotations
from homeassistant.components.sensor import SensorEntity, SensorEntityDescription
from homeassistant.components.sensor import SensorEntity
from homeassistant.const import ATTR_ATTRIBUTION
from homeassistant.helpers.update_coordinator import CoordinatorEntity
@@ -16,6 +14,9 @@ from .const import (
FORECAST_MONITORED_CONDITIONS,
FORECAST_SENSOR_TYPES,
MONITORED_CONDITIONS,
SENSOR_DEVICE_CLASS,
SENSOR_NAME,
SENSOR_UNIT,
WEATHER_SENSOR_TYPES,
)
from .weather_update_coordinator import WeatherUpdateCoordinator
@@ -27,30 +28,37 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
name = domain_data[ENTRY_NAME]
weather_coordinator = domain_data[ENTRY_WEATHER_COORDINATOR]
unique_id = config_entry.unique_id
entities: list[AbstractAemetSensor] = [
AemetSensor(name, unique_id, weather_coordinator, description)
for description in WEATHER_SENSOR_TYPES
if description.key in MONITORED_CONDITIONS
]
entities.extend(
[
AemetForecastSensor(
name_prefix,
unique_id_prefix,
weather_sensor_types = WEATHER_SENSOR_TYPES
forecast_sensor_types = FORECAST_SENSOR_TYPES
entities = []
for sensor_type in MONITORED_CONDITIONS:
unique_id = f"{config_entry.unique_id}-{sensor_type}"
entities.append(
AemetSensor(
name,
unique_id,
sensor_type,
weather_sensor_types[sensor_type],
weather_coordinator,
mode,
description,
)
for mode in FORECAST_MODES
if (
(name_prefix := f"{domain_data[ENTRY_NAME]} {mode} Forecast")
and (unique_id_prefix := f"{unique_id}-forecast-{mode}")
)
for mode in FORECAST_MODES:
name = f"{domain_data[ENTRY_NAME]} {mode}"
for sensor_type in FORECAST_MONITORED_CONDITIONS:
unique_id = f"{config_entry.unique_id}-forecast-{mode}-{sensor_type}"
entities.append(
AemetForecastSensor(
f"{name} Forecast",
unique_id,
sensor_type,
forecast_sensor_types[sensor_type],
weather_coordinator,
mode,
)
)
for description in FORECAST_SENSOR_TYPES
if description.key in FORECAST_MONITORED_CONDITIONS
]
)
async_add_entities(entities)
@@ -64,14 +72,20 @@ class AbstractAemetSensor(CoordinatorEntity, SensorEntity):
self,
name,
unique_id,
sensor_type,
sensor_configuration,
coordinator: WeatherUpdateCoordinator,
description: SensorEntityDescription,
):
"""Initialize the sensor."""
super().__init__(coordinator)
self.entity_description = description
self._attr_name = f"{name} {description.name}"
self._attr_unique_id = unique_id
self._name = name
self._unique_id = unique_id
self._sensor_type = sensor_type
self._sensor_name = sensor_configuration[SENSOR_NAME]
self._attr_name = f"{self._name} {self._sensor_name}"
self._attr_unique_id = self._unique_id
self._attr_device_class = sensor_configuration.get(SENSOR_DEVICE_CLASS)
self._attr_native_unit_of_measurement = sensor_configuration.get(SENSOR_UNIT)
class AemetSensor(AbstractAemetSensor):
@@ -81,21 +95,20 @@ class AemetSensor(AbstractAemetSensor):
self,
name,
unique_id,
sensor_type,
sensor_configuration,
weather_coordinator: WeatherUpdateCoordinator,
description: SensorEntityDescription,
):
"""Initialize the sensor."""
super().__init__(
name=name,
unique_id=f"{unique_id}-{description.key}",
coordinator=weather_coordinator,
description=description,
name, unique_id, sensor_type, sensor_configuration, weather_coordinator
)
self._weather_coordinator = weather_coordinator
@property
def native_value(self):
"""Return the state of the device."""
return self.coordinator.data.get(self.entity_description.key)
return self._weather_coordinator.data.get(self._sensor_type)
class AemetForecastSensor(AbstractAemetSensor):
@@ -105,17 +118,16 @@ class AemetForecastSensor(AbstractAemetSensor):
self,
name,
unique_id,
sensor_type,
sensor_configuration,
weather_coordinator: WeatherUpdateCoordinator,
forecast_mode,
description: SensorEntityDescription,
):
"""Initialize the sensor."""
super().__init__(
name=name,
unique_id=f"{unique_id}-{description.key}",
coordinator=weather_coordinator,
description=description,
name, unique_id, sensor_type, sensor_configuration, weather_coordinator
)
self._weather_coordinator = weather_coordinator
self._forecast_mode = forecast_mode
self._attr_entity_registry_enabled_default = (
self._forecast_mode == FORECAST_MODE_DAILY
@@ -125,9 +137,9 @@ class AemetForecastSensor(AbstractAemetSensor):
def native_value(self):
"""Return the state of the device."""
forecast = None
forecasts = self.coordinator.data.get(
forecasts = self._weather_coordinator.data.get(
FORECAST_MODE_ATTR_API[self._forecast_mode]
)
if forecasts:
forecast = forecasts[0].get(self.entity_description.key)
forecast = forecasts[0].get(self._sensor_type)
return forecast

View File

@@ -4,13 +4,13 @@
"already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9"
},
"error": {
"already_in_progress": "La configuration est d\u00e9j\u00e0 en cours",
"already_in_progress": "La configuration de l'appareil est d\u00e9j\u00e0 en cours.",
"cannot_connect": "\u00c9chec de connexion"
},
"step": {
"user": {
"data": {
"host": "H\u00f4te",
"host": "Nom d'h\u00f4te ou adresse IP",
"port": "Port"
},
"title": "Configurer l'agent DVR"

View File

@@ -3,6 +3,23 @@ from __future__ import annotations
from typing import Final
from homeassistant.components.sensor import STATE_CLASS_MEASUREMENT
from homeassistant.const import (
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
DEVICE_CLASS_AQI,
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_PM1,
DEVICE_CLASS_PM10,
DEVICE_CLASS_PM25,
DEVICE_CLASS_PRESSURE,
DEVICE_CLASS_TEMPERATURE,
PERCENTAGE,
PRESSURE_HPA,
TEMP_CELSIUS,
)
from .model import AirlySensorEntityDescription
ATTR_API_ADVICE: Final = "ADVICE"
ATTR_API_CAQI: Final = "CAQI"
ATTR_API_CAQI_DESCRIPTION: Final = "DESCRIPTION"
@@ -32,3 +49,56 @@ MANUFACTURER: Final = "Airly sp. z o.o."
MAX_UPDATE_INTERVAL: Final = 90
MIN_UPDATE_INTERVAL: Final = 5
NO_AIRLY_SENSORS: Final = "There are no Airly sensors in this area yet."
SENSOR_TYPES: tuple[AirlySensorEntityDescription, ...] = (
AirlySensorEntityDescription(
key=ATTR_API_CAQI,
device_class=DEVICE_CLASS_AQI,
name=ATTR_API_CAQI,
native_unit_of_measurement="CAQI",
),
AirlySensorEntityDescription(
key=ATTR_API_PM1,
device_class=DEVICE_CLASS_PM1,
name=ATTR_API_PM1,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=STATE_CLASS_MEASUREMENT,
),
AirlySensorEntityDescription(
key=ATTR_API_PM25,
device_class=DEVICE_CLASS_PM25,
name="PM2.5",
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=STATE_CLASS_MEASUREMENT,
),
AirlySensorEntityDescription(
key=ATTR_API_PM10,
device_class=DEVICE_CLASS_PM10,
name=ATTR_API_PM10,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=STATE_CLASS_MEASUREMENT,
),
AirlySensorEntityDescription(
key=ATTR_API_HUMIDITY,
device_class=DEVICE_CLASS_HUMIDITY,
name=ATTR_API_HUMIDITY.capitalize(),
native_unit_of_measurement=PERCENTAGE,
state_class=STATE_CLASS_MEASUREMENT,
value=lambda value: round(value, 1),
),
AirlySensorEntityDescription(
key=ATTR_API_PRESSURE,
device_class=DEVICE_CLASS_PRESSURE,
name=ATTR_API_PRESSURE.capitalize(),
native_unit_of_measurement=PRESSURE_HPA,
state_class=STATE_CLASS_MEASUREMENT,
),
AirlySensorEntityDescription(
key=ATTR_API_TEMPERATURE,
device_class=DEVICE_CLASS_TEMPERATURE,
name=ATTR_API_TEMPERATURE.capitalize(),
native_unit_of_measurement=TEMP_CELSIUS,
state_class=STATE_CLASS_MEASUREMENT,
value=lambda value: round(value, 1),
),
)

View File

@@ -0,0 +1,14 @@
"""Type definitions for Airly integration."""
from __future__ import annotations
from dataclasses import dataclass
from typing import Callable
from homeassistant.components.sensor import SensorEntityDescription
@dataclass
class AirlySensorEntityDescription(SensorEntityDescription):
"""Class describing Airly sensor entities."""
value: Callable = round

View File

@@ -1,30 +1,11 @@
"""Support for the Airly sensor service."""
from __future__ import annotations
from dataclasses import dataclass
from typing import Any, Callable, cast
from typing import Any, cast
from homeassistant.components.sensor import (
STATE_CLASS_MEASUREMENT,
SensorEntity,
SensorEntityDescription,
)
from homeassistant.components.sensor import SensorEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
ATTR_ATTRIBUTION,
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
CONF_NAME,
DEVICE_CLASS_AQI,
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_PM1,
DEVICE_CLASS_PM10,
DEVICE_CLASS_PM25,
DEVICE_CLASS_PRESSURE,
DEVICE_CLASS_TEMPERATURE,
PERCENTAGE,
PRESSURE_HPA,
TEMP_CELSIUS,
)
from homeassistant.const import ATTR_ATTRIBUTION, CONF_NAME
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType
@@ -37,12 +18,8 @@ from .const import (
ATTR_API_CAQI,
ATTR_API_CAQI_DESCRIPTION,
ATTR_API_CAQI_LEVEL,
ATTR_API_HUMIDITY,
ATTR_API_PM1,
ATTR_API_PM10,
ATTR_API_PM25,
ATTR_API_PRESSURE,
ATTR_API_TEMPERATURE,
ATTR_DESCRIPTION,
ATTR_LEVEL,
ATTR_LIMIT,
@@ -51,74 +28,15 @@ from .const import (
DEFAULT_NAME,
DOMAIN,
MANUFACTURER,
SENSOR_TYPES,
SUFFIX_LIMIT,
SUFFIX_PERCENT,
)
from .model import AirlySensorEntityDescription
PARALLEL_UPDATES = 1
@dataclass
class AirlySensorEntityDescription(SensorEntityDescription):
"""Class describing Airly sensor entities."""
value: Callable = round
SENSOR_TYPES: tuple[AirlySensorEntityDescription, ...] = (
AirlySensorEntityDescription(
key=ATTR_API_CAQI,
device_class=DEVICE_CLASS_AQI,
name=ATTR_API_CAQI,
native_unit_of_measurement="CAQI",
),
AirlySensorEntityDescription(
key=ATTR_API_PM1,
device_class=DEVICE_CLASS_PM1,
name=ATTR_API_PM1,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=STATE_CLASS_MEASUREMENT,
),
AirlySensorEntityDescription(
key=ATTR_API_PM25,
device_class=DEVICE_CLASS_PM25,
name="PM2.5",
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=STATE_CLASS_MEASUREMENT,
),
AirlySensorEntityDescription(
key=ATTR_API_PM10,
device_class=DEVICE_CLASS_PM10,
name=ATTR_API_PM10,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=STATE_CLASS_MEASUREMENT,
),
AirlySensorEntityDescription(
key=ATTR_API_HUMIDITY,
device_class=DEVICE_CLASS_HUMIDITY,
name=ATTR_API_HUMIDITY.capitalize(),
native_unit_of_measurement=PERCENTAGE,
state_class=STATE_CLASS_MEASUREMENT,
value=lambda value: round(value, 1),
),
AirlySensorEntityDescription(
key=ATTR_API_PRESSURE,
device_class=DEVICE_CLASS_PRESSURE,
name=ATTR_API_PRESSURE.capitalize(),
native_unit_of_measurement=PRESSURE_HPA,
state_class=STATE_CLASS_MEASUREMENT,
),
AirlySensorEntityDescription(
key=ATTR_API_TEMPERATURE,
device_class=DEVICE_CLASS_TEMPERATURE,
name=ATTR_API_TEMPERATURE.capitalize(),
native_unit_of_measurement=TEMP_CELSIUS,
state_class=STATE_CLASS_MEASUREMENT,
value=lambda value: round(value, 1),
),
)
async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:

View File

@@ -1,7 +1,7 @@
{
"config": {
"abort": {
"already_configured": "L'emplacement est d\u00e9j\u00e0 configur\u00e9"
"already_configured": "L'int\u00e9gration des coordonn\u00e9es d'Airly est d\u00e9j\u00e0 configur\u00e9."
},
"error": {
"invalid_api_key": "Cl\u00e9 API invalide",
@@ -13,7 +13,7 @@
"api_key": "Cl\u00e9 d'API",
"latitude": "Latitude",
"longitude": "Longitude",
"name": "Nom"
"name": "Nom de l'int\u00e9gration"
},
"description": "Configurez l'int\u00e9gration de la qualit\u00e9 de l'air Airly. Pour g\u00e9n\u00e9rer une cl\u00e9 API, rendez-vous sur https://developer.airly.eu/register.",
"title": "Airly"

View File

@@ -1,15 +1,14 @@
"""Support for the AirNow sensor service."""
from __future__ import annotations
from homeassistant.components.sensor import SensorEntity, SensorEntityDescription
from homeassistant.components.sensor import SensorEntity
from homeassistant.const import (
ATTR_ATTRIBUTION,
ATTR_DEVICE_CLASS,
ATTR_ICON,
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
CONCENTRATION_PARTS_PER_MILLION,
)
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from . import AirNowDataUpdateCoordinator
from .const import (
ATTR_API_AQI,
ATTR_API_AQI_DESCRIPTION,
@@ -23,69 +22,69 @@ from .const import (
ATTRIBUTION = "Data provided by AirNow"
ATTR_LABEL = "label"
ATTR_UNIT = "unit"
PARALLEL_UPDATES = 1
SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
SensorEntityDescription(
key=ATTR_API_AQI,
icon="mdi:blur",
name=ATTR_API_AQI,
native_unit_of_measurement="aqi",
),
SensorEntityDescription(
key=ATTR_API_PM25,
icon="mdi:blur",
name=ATTR_API_PM25,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
),
SensorEntityDescription(
key=ATTR_API_O3,
icon="mdi:blur",
name=ATTR_API_O3,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
),
)
SENSOR_TYPES = {
ATTR_API_AQI: {
ATTR_DEVICE_CLASS: None,
ATTR_ICON: "mdi:blur",
ATTR_LABEL: ATTR_API_AQI,
ATTR_UNIT: "aqi",
},
ATTR_API_PM25: {
ATTR_DEVICE_CLASS: None,
ATTR_ICON: "mdi:blur",
ATTR_LABEL: ATTR_API_PM25,
ATTR_UNIT: CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
},
ATTR_API_O3: {
ATTR_DEVICE_CLASS: None,
ATTR_ICON: "mdi:blur",
ATTR_LABEL: ATTR_API_O3,
ATTR_UNIT: CONCENTRATION_PARTS_PER_MILLION,
},
}
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up AirNow sensor entities based on a config entry."""
coordinator = hass.data[DOMAIN][config_entry.entry_id]
entities = [AirNowSensor(coordinator, description) for description in SENSOR_TYPES]
sensors = []
for sensor in SENSOR_TYPES:
sensors.append(AirNowSensor(coordinator, sensor))
async_add_entities(entities, False)
async_add_entities(sensors, False)
class AirNowSensor(CoordinatorEntity, SensorEntity):
"""Define an AirNow sensor."""
coordinator: AirNowDataUpdateCoordinator
def __init__(
self,
coordinator: AirNowDataUpdateCoordinator,
description: SensorEntityDescription,
) -> None:
def __init__(self, coordinator, kind):
"""Initialize."""
super().__init__(coordinator)
self.entity_description = description
self.kind = kind
self._state = None
self._attrs = {ATTR_ATTRIBUTION: ATTRIBUTION}
self._attr_name = f"AirNow {description.name}"
self._attr_unique_id = (
f"{coordinator.latitude}-{coordinator.longitude}-{description.key.lower()}"
)
self._attr_name = f"AirNow {SENSOR_TYPES[self.kind][ATTR_LABEL]}"
self._attr_icon = SENSOR_TYPES[self.kind][ATTR_ICON]
self._attr_device_class = SENSOR_TYPES[self.kind][ATTR_DEVICE_CLASS]
self._attr_native_unit_of_measurement = SENSOR_TYPES[self.kind][ATTR_UNIT]
self._attr_unique_id = f"{self.coordinator.latitude}-{self.coordinator.longitude}-{self.kind.lower()}"
@property
def native_value(self):
"""Return the state."""
self._state = self.coordinator.data[self.entity_description.key]
self._state = self.coordinator.data[self.kind]
return self._state
@property
def extra_state_attributes(self):
"""Return the state attributes."""
if self.entity_description.key == ATTR_API_AQI:
if self.kind == ATTR_API_AQI:
self._attrs[SENSOR_AQI_ATTR_DESCR] = self.coordinator.data[
ATTR_API_AQI_DESCRIPTION
]

View File

@@ -4,7 +4,7 @@
"already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9"
},
"error": {
"cannot_connect": "\u00c9chec de connexion",
"cannot_connect": "\u00c9chec \u00e0 la connexion",
"invalid_auth": "Authentification invalide",
"invalid_location": "Aucun r\u00e9sultat trouv\u00e9 pour cet emplacement",
"unknown": "Erreur inattendue"
@@ -12,7 +12,7 @@
"step": {
"user": {
"data": {
"api_key": "Cl\u00e9 d'API",
"api_key": "Cl\u00e9 API",
"latitude": "Latitude",
"longitude": "Longitude",
"radius": "Rayon d'action de la station (en miles, facultatif)"

View File

@@ -1,9 +0,0 @@
{
"config": {
"step": {
"user": {
"title": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03b1 \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 {intergration}."
}
}
}
}

View File

@@ -1,15 +0,0 @@
{
"config": {
"error": {
"no_units": "No se pudo encontrar ning\u00fan grupo AirTouch 4."
},
"step": {
"user": {
"data": {
"host": "Anfitri\u00f3n"
},
"title": "Configura los detalles de conexi\u00f3n de tu AirTouch 4."
}
}
}
}

View File

@@ -1,17 +0,0 @@
{
"config": {
"abort": {
"already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9"
},
"error": {
"cannot_connect": "\u00c9chec de connexion"
},
"step": {
"user": {
"data": {
"host": "H\u00f4te"
}
}
}
}
}

View File

@@ -13,7 +13,7 @@
"step": {
"geography_by_coords": {
"data": {
"api_key": "Cl\u00e9 d'API",
"api_key": "Clef d'API",
"latitude": "Latitude",
"longitude": "Longitude"
},
@@ -22,7 +22,7 @@
},
"geography_by_name": {
"data": {
"api_key": "Cl\u00e9 d'API",
"api_key": "Clef d'API",
"city": "Ville",
"country": "Pays",
"state": "Etat"

View File

@@ -9,11 +9,11 @@
"s2": "Di\u00f3xido de azufre"
},
"airvisual__pollutant_level": {
"good": "Bueno",
"hazardous": "Da\u00f1ino",
"good": "Bien",
"hazardous": "Peligroso",
"moderate": "Moderado",
"unhealthy": "Insalubre",
"unhealthy_sensitive": "Insalubre para grupos sensibles",
"unhealthy_sensitive": "Incorrecto para grupos sensibles",
"very_unhealthy": "Muy poco saludable"
}
}

View File

@@ -1,12 +1,12 @@
{
"state": {
"airvisual__pollutant_label": {
"co": "tlenek w\u0119gla",
"n2": "dwutlenek azotu",
"o3": "ozon",
"co": "Tlenek w\u0119gla",
"n2": "Dwutlenek azotu",
"o3": "Ozon",
"p1": "PM10",
"p2": "PM2.5",
"s2": "dwutlenek siarki"
"s2": "Dwutlenek siarki"
},
"airvisual__pollutant_level": {
"good": "dobry",

View File

@@ -11,10 +11,7 @@ from homeassistant.components.alarm_control_panel.const import (
SUPPORT_ALARM_ARM_NIGHT,
SUPPORT_ALARM_ARM_VACATION,
)
from homeassistant.components.automation import (
AutomationActionType,
AutomationTriggerInfo,
)
from homeassistant.components.automation import AutomationActionType
from homeassistant.components.device_automation import DEVICE_TRIGGER_BASE_SCHEMA
from homeassistant.components.homeassistant.triggers import state as state_trigger
from homeassistant.const import (
@@ -132,7 +129,7 @@ async def async_attach_trigger(
hass: HomeAssistant,
config: ConfigType,
action: AutomationActionType,
automation_info: AutomationTriggerInfo,
automation_info: dict,
) -> CALLBACK_TYPE:
"""Attach a trigger."""
if config[CONF_TYPE] == "triggered":

View File

@@ -4,7 +4,7 @@
"arm_away": "Schakel {entity_name} in voor vertrek",
"arm_home": "Schakel {entity_name} in voor thuis",
"arm_night": "Schakel {entity_name} in voor 's nachts",
"arm_vacation": "Schakel {entity_name} in voor vakantie",
"arm_vacation": "Schakel {entity_name} in op vakantie",
"disarm": "Schakel {entity_name} uit",
"trigger": "Laat {entity_name} afgaan"
},
@@ -12,7 +12,7 @@
"is_armed_away": "{entity_name} ingeschakeld voor vertrek",
"is_armed_home": "{entity_name} ingeschakeld voor thuis",
"is_armed_night": "{entity_name} is ingeschakeld voor 's nachts",
"is_armed_vacation": "{entity_name} is ingeschakeld voor vakantie",
"is_armed_vacation": "{entity_name} is in vakantie geschakeld",
"is_disarmed": "{entity_name} is uitgeschakeld",
"is_triggered": "{entity_name} gaat af"
},
@@ -20,7 +20,7 @@
"armed_away": "{entity_name} ingeschakeld voor vertrek",
"armed_home": "{entity_name} ingeschakeld voor thuis",
"armed_night": "{entity_name} ingeschakeld voor 's nachts",
"armed_vacation": "{entity_name} schakelde in voor vakantie",
"armed_vacation": "{entity_name} schakelde vakantie in",
"disarmed": "{entity_name} uitgeschakeld",
"triggered": "{entity_name} afgegaan"
}
@@ -40,5 +40,5 @@
"triggered": "Gaat af"
}
},
"title": "Alarmbedieningspaneel"
"title": "Alarm bedieningspaneel"
}

View File

@@ -1483,6 +1483,16 @@ class AlexaRangeController(AlexaCapability):
if self.entity.state in (STATE_UNAVAILABLE, STATE_UNKNOWN, None):
return None
# Fan Speed
if self.instance == f"{fan.DOMAIN}.{fan.ATTR_SPEED}":
speed_list = self.entity.attributes.get(fan.ATTR_SPEED_LIST)
speed = self.entity.attributes.get(fan.ATTR_SPEED)
if speed_list is not None and speed is not None:
speed_index = next(
(i for i, v in enumerate(speed_list) if v == speed), None
)
return speed_index
# Cover Position
if self.instance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}":
return self.entity.attributes.get(cover.ATTR_CURRENT_POSITION)

View File

@@ -535,6 +535,10 @@ class FanCapabilities(AlexaEntity):
if supported & fan.SUPPORT_SET_SPEED:
yield AlexaPercentageController(self.entity)
yield AlexaPowerLevelController(self.entity)
# The use of legacy speeds is deprecated in the schema, support will be removed after a quarter (2021.7)
yield AlexaRangeController(
self.entity, instance=f"{fan.DOMAIN}.{fan.ATTR_SPEED}"
)
if supported & fan.SUPPORT_OSCILLATE:
yield AlexaToggleController(
self.entity, instance=f"{fan.DOMAIN}.{fan.ATTR_OSCILLATING}"

View File

@@ -1091,8 +1091,24 @@ async def async_api_set_range(hass, config, directive, context):
data = {ATTR_ENTITY_ID: entity.entity_id}
range_value = directive.payload["rangeValue"]
# Fan Speed
if instance == f"{fan.DOMAIN}.{fan.ATTR_SPEED}":
range_value = int(range_value)
service = fan.SERVICE_SET_SPEED
speed_list = entity.attributes[fan.ATTR_SPEED_LIST]
speed = next((v for i, v in enumerate(speed_list) if i == range_value), None)
if not speed:
msg = "Entity does not support value"
raise AlexaInvalidValueError(msg)
if speed == fan.SPEED_OFF:
service = fan.SERVICE_TURN_OFF
data[fan.ATTR_SPEED] = speed
# Cover Position
if instance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}":
elif instance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}":
range_value = int(range_value)
if range_value == 0:
service = cover.SERVICE_CLOSE_COVER
@@ -1168,8 +1184,29 @@ async def async_api_adjust_range(hass, config, directive, context):
range_delta_default = bool(directive.payload["rangeValueDeltaDefault"])
response_value = 0
# Fan Speed
if instance == f"{fan.DOMAIN}.{fan.ATTR_SPEED}":
range_delta = int(range_delta)
service = fan.SERVICE_SET_SPEED
speed_list = entity.attributes[fan.ATTR_SPEED_LIST]
current_speed = entity.attributes[fan.ATTR_SPEED]
current_speed_index = next(
(i for i, v in enumerate(speed_list) if v == current_speed), 0
)
new_speed_index = min(
len(speed_list) - 1, max(0, current_speed_index + range_delta)
)
speed = next(
(v for i, v in enumerate(speed_list) if i == new_speed_index), None
)
if speed == fan.SPEED_OFF:
service = fan.SERVICE_TURN_OFF
data[fan.ATTR_SPEED] = response_value = speed
# Cover Position
if instance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}":
elif instance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}":
range_delta = int(range_delta * 20) if range_delta_default else int(range_delta)
service = SERVICE_SET_COVER_POSITION
current = entity.attributes.get(cover.ATTR_POSITION)

View File

@@ -1,8 +1,8 @@
{
"config": {
"abort": {
"cannot_connect": "\u00c9chec de connexion",
"missing_configuration": "Le composant n'est pas configur\u00e9. Veuillez suivre la documentation.",
"cannot_connect": "Impossible de se connecter au serveur Almond",
"missing_configuration": "Veuillez consulter la documentation pour savoir comment configurer Almond.",
"no_url_available": "Aucune URL disponible. Pour plus d'informations sur cette erreur, [consultez la section d'aide] ( {docs_url} )",
"single_instance_allowed": "D\u00e9j\u00e0 configur\u00e9. Une seule configuration possible."
},

View File

@@ -1,22 +1,22 @@
{
"config": {
"abort": {
"reauth_successful": "La r\u00e9-authentification a r\u00e9ussi"
"reauth_successful": "R\u00e9-authentification r\u00e9ussie"
},
"error": {
"cannot_connect": "\u00c9chec de connexion",
"invalid_api_key": "Cl\u00e9 API invalide"
"invalid_api_key": "Cl\u00e9 API non valide"
},
"step": {
"reauth_confirm": {
"data": {
"api_key": "Cl\u00e9 d'API",
"api_key": "cl\u00e9 API",
"description": "R\u00e9-authentifiez-vous avec votre compte Ambee."
}
},
"user": {
"data": {
"api_key": "Cl\u00e9 d'API",
"api_key": "cl\u00e9 API",
"latitude": "Latitude",
"longitude": "Longitude",
"name": "Nom"

View File

@@ -1,10 +1,10 @@
{
"state": {
"ambee__risk": {
"high": "wysoki",
"low": "niski",
"moderate": "umiarkowany",
"very high": "bardzo wysoki"
"high": "Wysoki",
"low": "Niski",
"moderate": "Umiarkowany",
"very high": "Bardzo wysoki"
}
}
}

View File

@@ -6,7 +6,7 @@
"missing_configuration": "Le composant n'est pas configur\u00e9. Veuillez suivre la documentation."
},
"create_entry": {
"default": "Authentification r\u00e9ussie"
"default": "Authentifi\u00e9 avec succ\u00e8s avec Ambiclimate"
},
"error": {
"follow_link": "Veuillez suivre le lien et vous authentifier avant d'appuyer sur Soumettre.",

View File

@@ -1,17 +1,38 @@
"""Support for Ambient Weather Station Service."""
from __future__ import annotations
from typing import Any
from aioambient import Client
from aioambient.errors import WebsocketError
from homeassistant.components.binary_sensor import (
DEVICE_CLASS_CONNECTIVITY,
DOMAIN as BINARY_SENSOR,
)
from homeassistant.components.sensor import DOMAIN as SENSOR
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
ATTR_LOCATION,
ATTR_NAME,
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
CONCENTRATION_PARTS_PER_MILLION,
CONF_API_KEY,
DEGREE,
DEVICE_CLASS_BATTERY,
DEVICE_CLASS_CO2,
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_ILLUMINANCE,
DEVICE_CLASS_PRESSURE,
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_TIMESTAMP,
EVENT_HOMEASSISTANT_STOP,
IRRADIATION_WATTS_PER_SQUARE_METER,
LIGHT_LUX,
PERCENTAGE,
PRECIPITATION_INCHES,
PRECIPITATION_INCHES_PER_HOUR,
PRESSURE_INHG,
SPEED_MILES_PER_HOUR,
TEMP_FAHRENHEIT,
)
from homeassistant.core import Event, HomeAssistant, callback
from homeassistant.exceptions import ConfigEntryNotReady
@@ -20,43 +41,266 @@ from homeassistant.helpers.dispatcher import (
async_dispatcher_connect,
async_dispatcher_send,
)
from homeassistant.helpers.entity import Entity, EntityDescription
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.event import async_call_later
from .const import (
ATTR_LAST_DATA,
ATTR_MONITORED_CONDITIONS,
CONF_APP_KEY,
DATA_CLIENT,
DOMAIN,
LOGGER,
TYPE_SOLARRADIATION,
TYPE_SOLARRADIATION_LX,
)
PLATFORMS = ["binary_sensor", "sensor"]
PLATFORMS = [BINARY_SENSOR, SENSOR]
DATA_CONFIG = "config"
DEFAULT_SOCKET_MIN_RETRY = 15
TYPE_24HOURRAININ = "24hourrainin"
TYPE_BAROMABSIN = "baromabsin"
TYPE_BAROMRELIN = "baromrelin"
TYPE_BATT1 = "batt1"
TYPE_BATT10 = "batt10"
TYPE_BATT2 = "batt2"
TYPE_BATT3 = "batt3"
TYPE_BATT4 = "batt4"
TYPE_BATT5 = "batt5"
TYPE_BATT6 = "batt6"
TYPE_BATT7 = "batt7"
TYPE_BATT8 = "batt8"
TYPE_BATT9 = "batt9"
TYPE_BATT_CO2 = "batt_co2"
TYPE_BATTOUT = "battout"
TYPE_CO2 = "co2"
TYPE_DAILYRAININ = "dailyrainin"
TYPE_DEWPOINT = "dewPoint"
TYPE_EVENTRAININ = "eventrainin"
TYPE_FEELSLIKE = "feelsLike"
TYPE_HOURLYRAININ = "hourlyrainin"
TYPE_HUMIDITY = "humidity"
TYPE_HUMIDITY1 = "humidity1"
TYPE_HUMIDITY10 = "humidity10"
TYPE_HUMIDITY2 = "humidity2"
TYPE_HUMIDITY3 = "humidity3"
TYPE_HUMIDITY4 = "humidity4"
TYPE_HUMIDITY5 = "humidity5"
TYPE_HUMIDITY6 = "humidity6"
TYPE_HUMIDITY7 = "humidity7"
TYPE_HUMIDITY8 = "humidity8"
TYPE_HUMIDITY9 = "humidity9"
TYPE_HUMIDITYIN = "humidityin"
TYPE_LASTRAIN = "lastRain"
TYPE_MAXDAILYGUST = "maxdailygust"
TYPE_MONTHLYRAININ = "monthlyrainin"
TYPE_PM25 = "pm25"
TYPE_PM25_24H = "pm25_24h"
TYPE_PM25_BATT = "batt_25"
TYPE_PM25_IN = "pm25_in"
TYPE_PM25_IN_24H = "pm25_in_24h"
TYPE_PM25IN_BATT = "batt_25in"
TYPE_RELAY1 = "relay1"
TYPE_RELAY10 = "relay10"
TYPE_RELAY2 = "relay2"
TYPE_RELAY3 = "relay3"
TYPE_RELAY4 = "relay4"
TYPE_RELAY5 = "relay5"
TYPE_RELAY6 = "relay6"
TYPE_RELAY7 = "relay7"
TYPE_RELAY8 = "relay8"
TYPE_RELAY9 = "relay9"
TYPE_SOILHUM1 = "soilhum1"
TYPE_SOILHUM10 = "soilhum10"
TYPE_SOILHUM2 = "soilhum2"
TYPE_SOILHUM3 = "soilhum3"
TYPE_SOILHUM4 = "soilhum4"
TYPE_SOILHUM5 = "soilhum5"
TYPE_SOILHUM6 = "soilhum6"
TYPE_SOILHUM7 = "soilhum7"
TYPE_SOILHUM8 = "soilhum8"
TYPE_SOILHUM9 = "soilhum9"
TYPE_SOILTEMP1F = "soiltemp1f"
TYPE_SOILTEMP10F = "soiltemp10f"
TYPE_SOILTEMP2F = "soiltemp2f"
TYPE_SOILTEMP3F = "soiltemp3f"
TYPE_SOILTEMP4F = "soiltemp4f"
TYPE_SOILTEMP5F = "soiltemp5f"
TYPE_SOILTEMP6F = "soiltemp6f"
TYPE_SOILTEMP7F = "soiltemp7f"
TYPE_SOILTEMP8F = "soiltemp8f"
TYPE_SOILTEMP9F = "soiltemp9f"
TYPE_SOLARRADIATION = "solarradiation"
TYPE_SOLARRADIATION_LX = "solarradiation_lx"
TYPE_TEMP10F = "temp10f"
TYPE_TEMP1F = "temp1f"
TYPE_TEMP2F = "temp2f"
TYPE_TEMP3F = "temp3f"
TYPE_TEMP4F = "temp4f"
TYPE_TEMP5F = "temp5f"
TYPE_TEMP6F = "temp6f"
TYPE_TEMP7F = "temp7f"
TYPE_TEMP8F = "temp8f"
TYPE_TEMP9F = "temp9f"
TYPE_TEMPF = "tempf"
TYPE_TEMPINF = "tempinf"
TYPE_TOTALRAININ = "totalrainin"
TYPE_UV = "uv"
TYPE_WEEKLYRAININ = "weeklyrainin"
TYPE_WINDDIR = "winddir"
TYPE_WINDDIR_AVG10M = "winddir_avg10m"
TYPE_WINDDIR_AVG2M = "winddir_avg2m"
TYPE_WINDGUSTDIR = "windgustdir"
TYPE_WINDGUSTMPH = "windgustmph"
TYPE_WINDSPDMPH_AVG10M = "windspdmph_avg10m"
TYPE_WINDSPDMPH_AVG2M = "windspdmph_avg2m"
TYPE_WINDSPEEDMPH = "windspeedmph"
TYPE_YEARLYRAININ = "yearlyrainin"
SENSOR_TYPES = {
TYPE_24HOURRAININ: ("24 Hr Rain", PRECIPITATION_INCHES, SENSOR, None),
TYPE_BAROMABSIN: ("Abs Pressure", PRESSURE_INHG, SENSOR, DEVICE_CLASS_PRESSURE),
TYPE_BAROMRELIN: ("Rel Pressure", PRESSURE_INHG, SENSOR, DEVICE_CLASS_PRESSURE),
TYPE_BATT10: ("Battery 10", None, BINARY_SENSOR, DEVICE_CLASS_BATTERY),
TYPE_BATT1: ("Battery 1", None, BINARY_SENSOR, DEVICE_CLASS_BATTERY),
TYPE_BATT2: ("Battery 2", None, BINARY_SENSOR, DEVICE_CLASS_BATTERY),
TYPE_BATT3: ("Battery 3", None, BINARY_SENSOR, DEVICE_CLASS_BATTERY),
TYPE_BATT4: ("Battery 4", None, BINARY_SENSOR, DEVICE_CLASS_BATTERY),
TYPE_BATT5: ("Battery 5", None, BINARY_SENSOR, DEVICE_CLASS_BATTERY),
TYPE_BATT6: ("Battery 6", None, BINARY_SENSOR, DEVICE_CLASS_BATTERY),
TYPE_BATT7: ("Battery 7", None, BINARY_SENSOR, DEVICE_CLASS_BATTERY),
TYPE_BATT8: ("Battery 8", None, BINARY_SENSOR, DEVICE_CLASS_BATTERY),
TYPE_BATT9: ("Battery 9", None, BINARY_SENSOR, DEVICE_CLASS_BATTERY),
TYPE_BATTOUT: ("Battery", None, BINARY_SENSOR, DEVICE_CLASS_BATTERY),
TYPE_BATT_CO2: ("CO2 Battery", None, BINARY_SENSOR, DEVICE_CLASS_BATTERY),
TYPE_CO2: ("co2", CONCENTRATION_PARTS_PER_MILLION, SENSOR, DEVICE_CLASS_CO2),
TYPE_DAILYRAININ: ("Daily Rain", PRECIPITATION_INCHES, SENSOR, None),
TYPE_DEWPOINT: ("Dew Point", TEMP_FAHRENHEIT, SENSOR, DEVICE_CLASS_TEMPERATURE),
TYPE_EVENTRAININ: ("Event Rain", PRECIPITATION_INCHES, SENSOR, None),
TYPE_FEELSLIKE: ("Feels Like", TEMP_FAHRENHEIT, SENSOR, DEVICE_CLASS_TEMPERATURE),
TYPE_HOURLYRAININ: (
"Hourly Rain Rate",
PRECIPITATION_INCHES_PER_HOUR,
SENSOR,
None,
),
TYPE_HUMIDITY10: ("Humidity 10", PERCENTAGE, SENSOR, DEVICE_CLASS_HUMIDITY),
TYPE_HUMIDITY1: ("Humidity 1", PERCENTAGE, SENSOR, DEVICE_CLASS_HUMIDITY),
TYPE_HUMIDITY2: ("Humidity 2", PERCENTAGE, SENSOR, DEVICE_CLASS_HUMIDITY),
TYPE_HUMIDITY3: ("Humidity 3", PERCENTAGE, SENSOR, DEVICE_CLASS_HUMIDITY),
TYPE_HUMIDITY4: ("Humidity 4", PERCENTAGE, SENSOR, DEVICE_CLASS_HUMIDITY),
TYPE_HUMIDITY5: ("Humidity 5", PERCENTAGE, SENSOR, DEVICE_CLASS_HUMIDITY),
TYPE_HUMIDITY6: ("Humidity 6", PERCENTAGE, SENSOR, DEVICE_CLASS_HUMIDITY),
TYPE_HUMIDITY7: ("Humidity 7", PERCENTAGE, SENSOR, DEVICE_CLASS_HUMIDITY),
TYPE_HUMIDITY8: ("Humidity 8", PERCENTAGE, SENSOR, DEVICE_CLASS_HUMIDITY),
TYPE_HUMIDITY9: ("Humidity 9", PERCENTAGE, SENSOR, DEVICE_CLASS_HUMIDITY),
TYPE_HUMIDITY: ("Humidity", PERCENTAGE, SENSOR, DEVICE_CLASS_HUMIDITY),
TYPE_HUMIDITYIN: ("Humidity In", PERCENTAGE, SENSOR, DEVICE_CLASS_HUMIDITY),
TYPE_LASTRAIN: ("Last Rain", None, SENSOR, DEVICE_CLASS_TIMESTAMP),
TYPE_MAXDAILYGUST: ("Max Gust", SPEED_MILES_PER_HOUR, SENSOR, None),
TYPE_MONTHLYRAININ: ("Monthly Rain", PRECIPITATION_INCHES, SENSOR, None),
TYPE_PM25_24H: (
"PM25 24h Avg",
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
SENSOR,
None,
),
TYPE_PM25_BATT: ("PM25 Battery", None, BINARY_SENSOR, DEVICE_CLASS_BATTERY),
TYPE_PM25_IN: (
"PM25 Indoor",
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
SENSOR,
None,
),
TYPE_PM25_IN_24H: (
"PM25 Indoor 24h Avg",
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
SENSOR,
None,
),
TYPE_PM25: ("PM25", CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, SENSOR, None),
TYPE_PM25IN_BATT: (
"PM25 Indoor Battery",
None,
BINARY_SENSOR,
DEVICE_CLASS_BATTERY,
),
TYPE_RELAY10: ("Relay 10", None, BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY),
TYPE_RELAY1: ("Relay 1", None, BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY),
TYPE_RELAY2: ("Relay 2", None, BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY),
TYPE_RELAY3: ("Relay 3", None, BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY),
TYPE_RELAY4: ("Relay 4", None, BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY),
TYPE_RELAY5: ("Relay 5", None, BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY),
TYPE_RELAY6: ("Relay 6", None, BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY),
TYPE_RELAY7: ("Relay 7", None, BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY),
TYPE_RELAY8: ("Relay 8", None, BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY),
TYPE_RELAY9: ("Relay 9", None, BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY),
TYPE_SOILHUM10: ("Soil Humidity 10", PERCENTAGE, SENSOR, DEVICE_CLASS_HUMIDITY),
TYPE_SOILHUM1: ("Soil Humidity 1", PERCENTAGE, SENSOR, DEVICE_CLASS_HUMIDITY),
TYPE_SOILHUM2: ("Soil Humidity 2", PERCENTAGE, SENSOR, DEVICE_CLASS_HUMIDITY),
TYPE_SOILHUM3: ("Soil Humidity 3", PERCENTAGE, SENSOR, DEVICE_CLASS_HUMIDITY),
TYPE_SOILHUM4: ("Soil Humidity 4", PERCENTAGE, SENSOR, DEVICE_CLASS_HUMIDITY),
TYPE_SOILHUM5: ("Soil Humidity 5", PERCENTAGE, SENSOR, DEVICE_CLASS_HUMIDITY),
TYPE_SOILHUM6: ("Soil Humidity 6", PERCENTAGE, SENSOR, DEVICE_CLASS_HUMIDITY),
TYPE_SOILHUM7: ("Soil Humidity 7", PERCENTAGE, SENSOR, DEVICE_CLASS_HUMIDITY),
TYPE_SOILHUM8: ("Soil Humidity 8", PERCENTAGE, SENSOR, DEVICE_CLASS_HUMIDITY),
TYPE_SOILHUM9: ("Soil Humidity 9", PERCENTAGE, SENSOR, DEVICE_CLASS_HUMIDITY),
TYPE_SOILTEMP10F: (
"Soil Temp 10",
TEMP_FAHRENHEIT,
SENSOR,
DEVICE_CLASS_TEMPERATURE,
),
TYPE_SOILTEMP1F: ("Soil Temp 1", TEMP_FAHRENHEIT, SENSOR, DEVICE_CLASS_TEMPERATURE),
TYPE_SOILTEMP2F: ("Soil Temp 2", TEMP_FAHRENHEIT, SENSOR, DEVICE_CLASS_TEMPERATURE),
TYPE_SOILTEMP3F: ("Soil Temp 3", TEMP_FAHRENHEIT, SENSOR, DEVICE_CLASS_TEMPERATURE),
TYPE_SOILTEMP4F: ("Soil Temp 4", TEMP_FAHRENHEIT, SENSOR, DEVICE_CLASS_TEMPERATURE),
TYPE_SOILTEMP5F: ("Soil Temp 5", TEMP_FAHRENHEIT, SENSOR, DEVICE_CLASS_TEMPERATURE),
TYPE_SOILTEMP6F: ("Soil Temp 6", TEMP_FAHRENHEIT, SENSOR, DEVICE_CLASS_TEMPERATURE),
TYPE_SOILTEMP7F: ("Soil Temp 7", TEMP_FAHRENHEIT, SENSOR, DEVICE_CLASS_TEMPERATURE),
TYPE_SOILTEMP8F: ("Soil Temp 8", TEMP_FAHRENHEIT, SENSOR, DEVICE_CLASS_TEMPERATURE),
TYPE_SOILTEMP9F: ("Soil Temp 9", TEMP_FAHRENHEIT, SENSOR, DEVICE_CLASS_TEMPERATURE),
TYPE_SOLARRADIATION: (
"Solar Rad",
IRRADIATION_WATTS_PER_SQUARE_METER,
SENSOR,
None,
),
TYPE_SOLARRADIATION_LX: (
"Solar Rad (lx)",
LIGHT_LUX,
SENSOR,
DEVICE_CLASS_ILLUMINANCE,
),
TYPE_TEMP10F: ("Temp 10", TEMP_FAHRENHEIT, SENSOR, DEVICE_CLASS_TEMPERATURE),
TYPE_TEMP1F: ("Temp 1", TEMP_FAHRENHEIT, SENSOR, DEVICE_CLASS_TEMPERATURE),
TYPE_TEMP2F: ("Temp 2", TEMP_FAHRENHEIT, SENSOR, DEVICE_CLASS_TEMPERATURE),
TYPE_TEMP3F: ("Temp 3", TEMP_FAHRENHEIT, SENSOR, DEVICE_CLASS_TEMPERATURE),
TYPE_TEMP4F: ("Temp 4", TEMP_FAHRENHEIT, SENSOR, DEVICE_CLASS_TEMPERATURE),
TYPE_TEMP5F: ("Temp 5", TEMP_FAHRENHEIT, SENSOR, DEVICE_CLASS_TEMPERATURE),
TYPE_TEMP6F: ("Temp 6", TEMP_FAHRENHEIT, SENSOR, DEVICE_CLASS_TEMPERATURE),
TYPE_TEMP7F: ("Temp 7", TEMP_FAHRENHEIT, SENSOR, DEVICE_CLASS_TEMPERATURE),
TYPE_TEMP8F: ("Temp 8", TEMP_FAHRENHEIT, SENSOR, DEVICE_CLASS_TEMPERATURE),
TYPE_TEMP9F: ("Temp 9", TEMP_FAHRENHEIT, SENSOR, DEVICE_CLASS_TEMPERATURE),
TYPE_TEMPF: ("Temp", TEMP_FAHRENHEIT, SENSOR, DEVICE_CLASS_TEMPERATURE),
TYPE_TEMPINF: ("Inside Temp", TEMP_FAHRENHEIT, SENSOR, DEVICE_CLASS_TEMPERATURE),
TYPE_TOTALRAININ: ("Lifetime Rain", PRECIPITATION_INCHES, SENSOR, None),
TYPE_UV: ("uv", "Index", SENSOR, None),
TYPE_WEEKLYRAININ: ("Weekly Rain", PRECIPITATION_INCHES, SENSOR, None),
TYPE_WINDDIR: ("Wind Dir", DEGREE, SENSOR, None),
TYPE_WINDDIR_AVG10M: ("Wind Dir Avg 10m", DEGREE, SENSOR, None),
TYPE_WINDDIR_AVG2M: ("Wind Dir Avg 2m", SPEED_MILES_PER_HOUR, SENSOR, None),
TYPE_WINDGUSTDIR: ("Gust Dir", DEGREE, SENSOR, None),
TYPE_WINDGUSTMPH: ("Wind Gust", SPEED_MILES_PER_HOUR, SENSOR, None),
TYPE_WINDSPDMPH_AVG10M: ("Wind Avg 10m", SPEED_MILES_PER_HOUR, SENSOR, None),
TYPE_WINDSPDMPH_AVG2M: ("Wind Avg 2m", SPEED_MILES_PER_HOUR, SENSOR, None),
TYPE_WINDSPEEDMPH: ("Wind Speed", SPEED_MILES_PER_HOUR, SENSOR, None),
TYPE_YEARLYRAININ: ("Yearly Rain", PRECIPITATION_INCHES, SENSOR, None),
}
CONFIG_SCHEMA = cv.deprecated(DOMAIN)
@callback
def async_wm2_to_lx(value: float) -> int:
"""Calculate illuminance (in lux)."""
return round(value / 0.0079)
@callback
def async_hydrate_station_data(data: dict[str, Any]) -> dict[str, Any]:
"""Hydrate station data with addition or normalized data."""
if (irradiation := data.get(TYPE_SOLARRADIATION)) is not None:
data[TYPE_SOLARRADIATION_LX] = async_wm2_to_lx(irradiation)
return data
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
"""Set up the Ambient PWS as config entry."""
hass.data.setdefault(DOMAIN, {DATA_CLIENT: {}})
@@ -162,14 +406,13 @@ class AmbientStation:
def on_data(data: dict) -> None:
"""Define a handler to fire when the data is received."""
mac = data["macAddress"]
if data == self.stations[mac][ATTR_LAST_DATA]:
return
LOGGER.debug("New data received: %s", data)
self.stations[mac][ATTR_LAST_DATA] = async_hydrate_station_data(data)
async_dispatcher_send(self._hass, f"ambient_station_data_update_{mac}")
mac_address = data["macAddress"]
if data != self.stations[mac_address][ATTR_LAST_DATA]:
LOGGER.debug("New data received: %s", data)
self.stations[mac_address][ATTR_LAST_DATA] = data
async_dispatcher_send(
self._hass, f"ambient_station_data_update_{mac_address}"
)
def on_disconnect() -> None:
"""Define a handler to fire when the websocket is disconnected."""
@@ -178,19 +421,26 @@ class AmbientStation:
def on_subscribed(data: dict) -> None:
"""Define a handler to fire when the subscription is set."""
for station in data["devices"]:
mac = station["macAddress"]
if mac in self.stations:
if station["macAddress"] in self.stations:
continue
LOGGER.debug("New station subscription: %s", data)
self.stations[mac] = {
ATTR_LAST_DATA: async_hydrate_station_data(station["lastData"]),
# Only create entities based on the data coming through the socket.
# If the user is monitoring brightness (in W/m^2), make sure we also
# add a calculated sensor for the same data measured in lx:
monitored_conditions = [
k for k in station["lastData"] if k in SENSOR_TYPES
]
if TYPE_SOLARRADIATION in monitored_conditions:
monitored_conditions.append(TYPE_SOLARRADIATION_LX)
self.stations[station["macAddress"]] = {
ATTR_LAST_DATA: station["lastData"],
ATTR_LOCATION: station.get("info", {}).get("location"),
ATTR_NAME: station.get("info", {}).get("name", mac),
ATTR_MONITORED_CONDITIONS: monitored_conditions,
ATTR_NAME: station.get("info", {}).get(
"name", station["macAddress"]
),
}
# If the websocket disconnects and reconnects, the on_subscribed
# handler will get called again; in that case, we don't want to
# attempt forward setup of the config entry (because it will have
@@ -217,26 +467,28 @@ class AmbientStation:
class AmbientWeatherEntity(Entity):
"""Define a base Ambient PWS entity."""
_attr_should_poll = False
def __init__(
self,
ambient: AmbientStation,
mac_address: str,
station_name: str,
description: EntityDescription,
sensor_type: str,
sensor_name: str,
device_class: str | None,
) -> None:
"""Initialize the sensor."""
self._ambient = ambient
self._attr_device_class = device_class
self._attr_device_info = {
"identifiers": {(DOMAIN, mac_address)},
"name": station_name,
"manufacturer": "Ambient Weather",
}
self._attr_name = f"{station_name}_{description.name}"
self._attr_unique_id = f"{mac_address}_{description.key}"
self._attr_name = f"{station_name}_{sensor_name}"
self._attr_should_poll = False
self._attr_unique_id = f"{mac_address}_{sensor_type}"
self._mac_address = mac_address
self.entity_description = description
self._sensor_type = sensor_type
async def async_added_to_hass(self) -> None:
"""Register callbacks."""
@@ -244,18 +496,18 @@ class AmbientWeatherEntity(Entity):
@callback
def update() -> None:
"""Update the state."""
if self.entity_description.key == TYPE_SOLARRADIATION_LX:
if self._sensor_type == TYPE_SOLARRADIATION_LX:
self._attr_available = (
self._ambient.stations[self._mac_address][ATTR_LAST_DATA][
self._ambient.stations[self._mac_address][ATTR_LAST_DATA].get(
TYPE_SOLARRADIATION
]
)
is not None
)
else:
self._attr_available = (
self._ambient.stations[self._mac_address][ATTR_LAST_DATA][
self.entity_description.key
]
self._ambient.stations[self._mac_address][ATTR_LAST_DATA].get(
self._sensor_type
)
is not None
)

View File

@@ -1,209 +1,34 @@
"""Support for Ambient Weather Station binary sensors."""
from __future__ import annotations
from dataclasses import dataclass
from typing import Literal
from homeassistant.components.binary_sensor import (
DEVICE_CLASS_BATTERY,
DEVICE_CLASS_CONNECTIVITY,
DOMAIN as BINARY_SENSOR,
BinarySensorEntity,
BinarySensorEntityDescription,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_NAME
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import AmbientWeatherEntity
from .const import ATTR_LAST_DATA, DATA_CLIENT, DOMAIN
TYPE_BATT1 = "batt1"
TYPE_BATT10 = "batt10"
TYPE_BATT2 = "batt2"
TYPE_BATT3 = "batt3"
TYPE_BATT4 = "batt4"
TYPE_BATT5 = "batt5"
TYPE_BATT6 = "batt6"
TYPE_BATT7 = "batt7"
TYPE_BATT8 = "batt8"
TYPE_BATT9 = "batt9"
TYPE_BATT_CO2 = "batt_co2"
TYPE_BATTOUT = "battout"
TYPE_PM25_BATT = "batt_25"
TYPE_PM25IN_BATT = "batt_25in"
TYPE_RELAY1 = "relay1"
TYPE_RELAY10 = "relay10"
TYPE_RELAY2 = "relay2"
TYPE_RELAY3 = "relay3"
TYPE_RELAY4 = "relay4"
TYPE_RELAY5 = "relay5"
TYPE_RELAY6 = "relay6"
TYPE_RELAY7 = "relay7"
TYPE_RELAY8 = "relay8"
TYPE_RELAY9 = "relay9"
@dataclass
class AmbientBinarySensorDescriptionMixin:
"""Define an entity description mixin for binary sensors."""
on_state: Literal[0, 1]
@dataclass
class AmbientBinarySensorDescription(
BinarySensorEntityDescription, AmbientBinarySensorDescriptionMixin
):
"""Describe an Ambient PWS binary sensor."""
BINARY_SENSOR_DESCRIPTIONS = (
AmbientBinarySensorDescription(
key=TYPE_BATTOUT,
name="Battery",
device_class=DEVICE_CLASS_BATTERY,
on_state=0,
),
AmbientBinarySensorDescription(
key=TYPE_BATT1,
name="Battery 1",
device_class=DEVICE_CLASS_BATTERY,
on_state=0,
),
AmbientBinarySensorDescription(
key=TYPE_BATT2,
name="Battery 2",
device_class=DEVICE_CLASS_BATTERY,
on_state=0,
),
AmbientBinarySensorDescription(
key=TYPE_BATT3,
name="Battery 3",
device_class=DEVICE_CLASS_BATTERY,
on_state=0,
),
AmbientBinarySensorDescription(
key=TYPE_BATT4,
name="Battery 4",
device_class=DEVICE_CLASS_BATTERY,
on_state=0,
),
AmbientBinarySensorDescription(
key=TYPE_BATT5,
name="Battery 5",
device_class=DEVICE_CLASS_BATTERY,
on_state=0,
),
AmbientBinarySensorDescription(
key=TYPE_BATT6,
name="Battery 6",
device_class=DEVICE_CLASS_BATTERY,
on_state=0,
),
AmbientBinarySensorDescription(
key=TYPE_BATT7,
name="Battery 7",
device_class=DEVICE_CLASS_BATTERY,
on_state=0,
),
AmbientBinarySensorDescription(
key=TYPE_BATT8,
name="Battery 8",
device_class=DEVICE_CLASS_BATTERY,
on_state=0,
),
AmbientBinarySensorDescription(
key=TYPE_BATT9,
name="Battery 9",
device_class=DEVICE_CLASS_BATTERY,
on_state=0,
),
AmbientBinarySensorDescription(
key=TYPE_BATT10,
name="Battery 10",
device_class=DEVICE_CLASS_BATTERY,
on_state=0,
),
AmbientBinarySensorDescription(
key=TYPE_BATT_CO2,
name="CO2 Battery",
device_class=DEVICE_CLASS_BATTERY,
on_state=0,
),
AmbientBinarySensorDescription(
key=TYPE_PM25IN_BATT,
name="PM25 Indoor Battery",
device_class=DEVICE_CLASS_BATTERY,
on_state=0,
),
AmbientBinarySensorDescription(
key=TYPE_PM25_BATT,
name="PM25 Battery",
device_class=DEVICE_CLASS_BATTERY,
on_state=0,
),
AmbientBinarySensorDescription(
key=TYPE_RELAY1,
name="Relay 1",
device_class=DEVICE_CLASS_CONNECTIVITY,
on_state=1,
),
AmbientBinarySensorDescription(
key=TYPE_RELAY2,
name="Relay 2",
device_class=DEVICE_CLASS_CONNECTIVITY,
on_state=1,
),
AmbientBinarySensorDescription(
key=TYPE_RELAY3,
name="Relay 3",
device_class=DEVICE_CLASS_CONNECTIVITY,
on_state=1,
),
AmbientBinarySensorDescription(
key=TYPE_RELAY4,
name="Relay 4",
device_class=DEVICE_CLASS_CONNECTIVITY,
on_state=1,
),
AmbientBinarySensorDescription(
key=TYPE_RELAY5,
name="Relay 5",
device_class=DEVICE_CLASS_CONNECTIVITY,
on_state=1,
),
AmbientBinarySensorDescription(
key=TYPE_RELAY6,
name="Relay 6",
device_class=DEVICE_CLASS_CONNECTIVITY,
on_state=1,
),
AmbientBinarySensorDescription(
key=TYPE_RELAY7,
name="Relay 7",
device_class=DEVICE_CLASS_CONNECTIVITY,
on_state=1,
),
AmbientBinarySensorDescription(
key=TYPE_RELAY8,
name="Relay 8",
device_class=DEVICE_CLASS_CONNECTIVITY,
on_state=1,
),
AmbientBinarySensorDescription(
key=TYPE_RELAY9,
name="Relay 9",
device_class=DEVICE_CLASS_CONNECTIVITY,
on_state=1,
),
AmbientBinarySensorDescription(
key=TYPE_RELAY10,
name="Relay 10",
device_class=DEVICE_CLASS_CONNECTIVITY,
on_state=1,
),
from . import (
SENSOR_TYPES,
TYPE_BATT1,
TYPE_BATT2,
TYPE_BATT3,
TYPE_BATT4,
TYPE_BATT5,
TYPE_BATT6,
TYPE_BATT7,
TYPE_BATT8,
TYPE_BATT9,
TYPE_BATT10,
TYPE_BATT_CO2,
TYPE_BATTOUT,
TYPE_PM25_BATT,
TYPE_PM25IN_BATT,
AmbientWeatherEntity,
)
from .const import ATTR_LAST_DATA, ATTR_MONITORED_CONDITIONS, DATA_CLIENT, DOMAIN
async def async_setup_entry(
@@ -212,29 +37,51 @@ async def async_setup_entry(
"""Set up Ambient PWS binary sensors based on a config entry."""
ambient = hass.data[DOMAIN][DATA_CLIENT][entry.entry_id]
async_add_entities(
[
AmbientWeatherBinarySensor(
ambient, mac_address, station[ATTR_NAME], description
)
for mac_address, station in ambient.stations.items()
for description in BINARY_SENSOR_DESCRIPTIONS
if description.key in station[ATTR_LAST_DATA]
]
)
binary_sensor_list = []
for mac_address, station in ambient.stations.items():
for condition in station[ATTR_MONITORED_CONDITIONS]:
name, _, kind, device_class = SENSOR_TYPES[condition]
if kind == BINARY_SENSOR:
binary_sensor_list.append(
AmbientWeatherBinarySensor(
ambient,
mac_address,
station[ATTR_NAME],
condition,
name,
device_class,
)
)
async_add_entities(binary_sensor_list)
class AmbientWeatherBinarySensor(AmbientWeatherEntity, BinarySensorEntity):
"""Define an Ambient binary sensor."""
entity_description: AmbientBinarySensorDescription
@callback
def update_from_latest_data(self) -> None:
"""Fetch new state data for the entity."""
self._attr_is_on = (
self._ambient.stations[self._mac_address][ATTR_LAST_DATA][
self.entity_description.key
]
== self.entity_description.on_state
state = self._ambient.stations[self._mac_address][ATTR_LAST_DATA].get(
self._sensor_type
)
if self._sensor_type in (
TYPE_BATT1,
TYPE_BATT10,
TYPE_BATT2,
TYPE_BATT3,
TYPE_BATT4,
TYPE_BATT5,
TYPE_BATT6,
TYPE_BATT7,
TYPE_BATT8,
TYPE_BATT9,
TYPE_BATT_CO2,
TYPE_BATTOUT,
TYPE_PM25_BATT,
TYPE_PM25IN_BATT,
):
self._attr_is_on = state == 0
else:
self._attr_is_on = state == 1

View File

@@ -5,10 +5,8 @@ DOMAIN = "ambient_station"
LOGGER = logging.getLogger(__package__)
ATTR_LAST_DATA = "last_data"
ATTR_MONITORED_CONDITIONS = "monitored_conditions"
CONF_APP_KEY = "app_key"
DATA_CLIENT = "data_client"
TYPE_SOLARRADIATION = "solarradiation"
TYPE_SOLARRADIATION_LX = "solarradiation_lx"

View File

@@ -1,554 +1,20 @@
"""Support for Ambient Weather Station sensors."""
from __future__ import annotations
from homeassistant.components.sensor import SensorEntity, SensorEntityDescription
from homeassistant.components.sensor import DOMAIN as SENSOR, SensorEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
ATTR_NAME,
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
CONCENTRATION_PARTS_PER_MILLION,
DEGREE,
DEVICE_CLASS_CO2,
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_ILLUMINANCE,
DEVICE_CLASS_PM25,
DEVICE_CLASS_PRESSURE,
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_TIMESTAMP,
IRRADIATION_WATTS_PER_SQUARE_METER,
LIGHT_LUX,
PERCENTAGE,
PRECIPITATION_INCHES,
PRECIPITATION_INCHES_PER_HOUR,
PRESSURE_INHG,
SPEED_MILES_PER_HOUR,
TEMP_FAHRENHEIT,
)
from homeassistant.const import ATTR_NAME
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import TYPE_SOLARRADIATION, TYPE_SOLARRADIATION_LX, AmbientWeatherEntity
from .const import ATTR_LAST_DATA, DATA_CLIENT, DOMAIN
TYPE_24HOURRAININ = "24hourrainin"
TYPE_BAROMABSIN = "baromabsin"
TYPE_BAROMRELIN = "baromrelin"
TYPE_CO2 = "co2"
TYPE_DAILYRAININ = "dailyrainin"
TYPE_DEWPOINT = "dewPoint"
TYPE_EVENTRAININ = "eventrainin"
TYPE_FEELSLIKE = "feelsLike"
TYPE_HOURLYRAININ = "hourlyrainin"
TYPE_HUMIDITY = "humidity"
TYPE_HUMIDITY1 = "humidity1"
TYPE_HUMIDITY10 = "humidity10"
TYPE_HUMIDITY2 = "humidity2"
TYPE_HUMIDITY3 = "humidity3"
TYPE_HUMIDITY4 = "humidity4"
TYPE_HUMIDITY5 = "humidity5"
TYPE_HUMIDITY6 = "humidity6"
TYPE_HUMIDITY7 = "humidity7"
TYPE_HUMIDITY8 = "humidity8"
TYPE_HUMIDITY9 = "humidity9"
TYPE_HUMIDITYIN = "humidityin"
TYPE_LASTRAIN = "lastRain"
TYPE_MAXDAILYGUST = "maxdailygust"
TYPE_MONTHLYRAININ = "monthlyrainin"
TYPE_PM25 = "pm25"
TYPE_PM25_24H = "pm25_24h"
TYPE_PM25_IN = "pm25_in"
TYPE_PM25_IN_24H = "pm25_in_24h"
TYPE_SOILHUM1 = "soilhum1"
TYPE_SOILHUM10 = "soilhum10"
TYPE_SOILHUM2 = "soilhum2"
TYPE_SOILHUM3 = "soilhum3"
TYPE_SOILHUM4 = "soilhum4"
TYPE_SOILHUM5 = "soilhum5"
TYPE_SOILHUM6 = "soilhum6"
TYPE_SOILHUM7 = "soilhum7"
TYPE_SOILHUM8 = "soilhum8"
TYPE_SOILHUM9 = "soilhum9"
TYPE_SOILTEMP1F = "soiltemp1f"
TYPE_SOILTEMP10F = "soiltemp10f"
TYPE_SOILTEMP2F = "soiltemp2f"
TYPE_SOILTEMP3F = "soiltemp3f"
TYPE_SOILTEMP4F = "soiltemp4f"
TYPE_SOILTEMP5F = "soiltemp5f"
TYPE_SOILTEMP6F = "soiltemp6f"
TYPE_SOILTEMP7F = "soiltemp7f"
TYPE_SOILTEMP8F = "soiltemp8f"
TYPE_SOILTEMP9F = "soiltemp9f"
TYPE_TEMP10F = "temp10f"
TYPE_TEMP1F = "temp1f"
TYPE_TEMP2F = "temp2f"
TYPE_TEMP3F = "temp3f"
TYPE_TEMP4F = "temp4f"
TYPE_TEMP5F = "temp5f"
TYPE_TEMP6F = "temp6f"
TYPE_TEMP7F = "temp7f"
TYPE_TEMP8F = "temp8f"
TYPE_TEMP9F = "temp9f"
TYPE_TEMPF = "tempf"
TYPE_TEMPINF = "tempinf"
TYPE_TOTALRAININ = "totalrainin"
TYPE_UV = "uv"
TYPE_WEEKLYRAININ = "weeklyrainin"
TYPE_WINDDIR = "winddir"
TYPE_WINDDIR_AVG10M = "winddir_avg10m"
TYPE_WINDDIR_AVG2M = "winddir_avg2m"
TYPE_WINDGUSTDIR = "windgustdir"
TYPE_WINDGUSTMPH = "windgustmph"
TYPE_WINDSPDMPH_AVG10M = "windspdmph_avg10m"
TYPE_WINDSPDMPH_AVG2M = "windspdmph_avg2m"
TYPE_WINDSPEEDMPH = "windspeedmph"
TYPE_YEARLYRAININ = "yearlyrainin"
SENSOR_DESCRIPTIONS = (
SensorEntityDescription(
key=TYPE_24HOURRAININ,
name="24 Hr Rain",
icon="mdi:water",
native_unit_of_measurement=PRECIPITATION_INCHES,
),
SensorEntityDescription(
key=TYPE_BAROMABSIN,
name="Abs Pressure",
native_unit_of_measurement=PRESSURE_INHG,
device_class=DEVICE_CLASS_PRESSURE,
),
SensorEntityDescription(
key=TYPE_BAROMRELIN,
name="Rel Pressure",
native_unit_of_measurement=PRESSURE_INHG,
device_class=DEVICE_CLASS_PRESSURE,
),
SensorEntityDescription(
key=TYPE_CO2,
name="co2",
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
device_class=DEVICE_CLASS_CO2,
),
SensorEntityDescription(
key=TYPE_DAILYRAININ,
name="Daily Rain",
icon="mdi:water",
native_unit_of_measurement=PRECIPITATION_INCHES,
),
SensorEntityDescription(
key=TYPE_DEWPOINT,
name="Dew Point",
native_unit_of_measurement=TEMP_FAHRENHEIT,
device_class=DEVICE_CLASS_TEMPERATURE,
),
SensorEntityDescription(
key=TYPE_EVENTRAININ,
name="Event Rain",
icon="mdi:water",
native_unit_of_measurement=PRECIPITATION_INCHES,
),
SensorEntityDescription(
key=TYPE_FEELSLIKE,
name="Feels Like",
native_unit_of_measurement=TEMP_FAHRENHEIT,
device_class=DEVICE_CLASS_TEMPERATURE,
),
SensorEntityDescription(
key=TYPE_HOURLYRAININ,
name="Hourly Rain Rate",
icon="mdi:water",
native_unit_of_measurement=PRECIPITATION_INCHES_PER_HOUR,
),
SensorEntityDescription(
key=TYPE_HUMIDITY10,
name="Humidity 10",
native_unit_of_measurement=PERCENTAGE,
device_class=DEVICE_CLASS_HUMIDITY,
),
SensorEntityDescription(
key=TYPE_HUMIDITY1,
name="Humidity 1",
native_unit_of_measurement=PERCENTAGE,
device_class=DEVICE_CLASS_HUMIDITY,
),
SensorEntityDescription(
key=TYPE_HUMIDITY2,
name="Humidity 2",
native_unit_of_measurement=PERCENTAGE,
device_class=DEVICE_CLASS_HUMIDITY,
),
SensorEntityDescription(
key=TYPE_HUMIDITY3,
name="Humidity 3",
native_unit_of_measurement=PERCENTAGE,
device_class=DEVICE_CLASS_HUMIDITY,
),
SensorEntityDescription(
key=TYPE_HUMIDITY4,
name="Humidity 4",
native_unit_of_measurement=PERCENTAGE,
device_class=DEVICE_CLASS_HUMIDITY,
),
SensorEntityDescription(
key=TYPE_HUMIDITY5,
name="Humidity 5",
native_unit_of_measurement=PERCENTAGE,
device_class=DEVICE_CLASS_HUMIDITY,
),
SensorEntityDescription(
key=TYPE_HUMIDITY6,
name="Humidity 6",
native_unit_of_measurement=PERCENTAGE,
device_class=DEVICE_CLASS_HUMIDITY,
),
SensorEntityDescription(
key=TYPE_HUMIDITY7,
name="Humidity 7",
native_unit_of_measurement=PERCENTAGE,
device_class=DEVICE_CLASS_HUMIDITY,
),
SensorEntityDescription(
key=TYPE_HUMIDITY8,
name="Humidity 8",
native_unit_of_measurement=PERCENTAGE,
device_class=DEVICE_CLASS_HUMIDITY,
),
SensorEntityDescription(
key=TYPE_HUMIDITY9,
name="Humidity 9",
native_unit_of_measurement=PERCENTAGE,
device_class=DEVICE_CLASS_HUMIDITY,
),
SensorEntityDescription(
key=TYPE_HUMIDITY,
name="Humidity",
native_unit_of_measurement=PERCENTAGE,
device_class=DEVICE_CLASS_HUMIDITY,
),
SensorEntityDescription(
key=TYPE_HUMIDITYIN,
name="Humidity In",
native_unit_of_measurement=PERCENTAGE,
device_class=DEVICE_CLASS_HUMIDITY,
),
SensorEntityDescription(
key=TYPE_LASTRAIN,
name="Last Rain",
icon="mdi:water",
device_class=DEVICE_CLASS_TIMESTAMP,
),
SensorEntityDescription(
key=TYPE_MAXDAILYGUST,
name="Max Gust",
icon="mdi:weather-windy",
native_unit_of_measurement=SPEED_MILES_PER_HOUR,
),
SensorEntityDescription(
key=TYPE_MONTHLYRAININ,
name="Monthly Rain",
icon="mdi:water",
native_unit_of_measurement=PRECIPITATION_INCHES,
),
SensorEntityDescription(
key=TYPE_PM25_24H,
name="PM25 24h Avg",
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
device_class=DEVICE_CLASS_PM25,
),
SensorEntityDescription(
key=TYPE_PM25_IN,
name="PM25 Indoor",
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
device_class=DEVICE_CLASS_PM25,
),
SensorEntityDescription(
key=TYPE_PM25_IN_24H,
name="PM25 Indoor 24h Avg",
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
device_class=DEVICE_CLASS_PM25,
),
SensorEntityDescription(
key=TYPE_PM25,
name="PM25",
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
device_class=DEVICE_CLASS_PM25,
),
SensorEntityDescription(
key=TYPE_SOILHUM10,
name="Soil Humidity 10",
native_unit_of_measurement=PERCENTAGE,
device_class=DEVICE_CLASS_HUMIDITY,
),
SensorEntityDescription(
key=TYPE_SOILHUM1,
name="Soil Humidity 1",
native_unit_of_measurement=PERCENTAGE,
device_class=DEVICE_CLASS_HUMIDITY,
),
SensorEntityDescription(
key=TYPE_SOILHUM2,
name="Soil Humidity 2",
native_unit_of_measurement=PERCENTAGE,
device_class=DEVICE_CLASS_HUMIDITY,
),
SensorEntityDescription(
key=TYPE_SOILHUM3,
name="Soil Humidity 3",
native_unit_of_measurement=PERCENTAGE,
device_class=DEVICE_CLASS_HUMIDITY,
),
SensorEntityDescription(
key=TYPE_SOILHUM4,
name="Soil Humidity 4",
native_unit_of_measurement=PERCENTAGE,
device_class=DEVICE_CLASS_HUMIDITY,
),
SensorEntityDescription(
key=TYPE_SOILHUM5,
name="Soil Humidity 5",
native_unit_of_measurement=PERCENTAGE,
device_class=DEVICE_CLASS_HUMIDITY,
),
SensorEntityDescription(
key=TYPE_SOILHUM6,
name="Soil Humidity 6",
native_unit_of_measurement=PERCENTAGE,
device_class=DEVICE_CLASS_HUMIDITY,
),
SensorEntityDescription(
key=TYPE_SOILHUM7,
name="Soil Humidity 7",
native_unit_of_measurement=PERCENTAGE,
device_class=DEVICE_CLASS_HUMIDITY,
),
SensorEntityDescription(
key=TYPE_SOILHUM8,
name="Soil Humidity 8",
native_unit_of_measurement=PERCENTAGE,
device_class=DEVICE_CLASS_HUMIDITY,
),
SensorEntityDescription(
key=TYPE_SOILHUM9,
name="Soil Humidity 9",
native_unit_of_measurement=PERCENTAGE,
device_class=DEVICE_CLASS_HUMIDITY,
),
SensorEntityDescription(
key=TYPE_SOILTEMP10F,
name="Soil Temp 10",
native_unit_of_measurement=TEMP_FAHRENHEIT,
device_class=DEVICE_CLASS_TEMPERATURE,
),
SensorEntityDescription(
key=TYPE_SOILTEMP1F,
name="Soil Temp 1",
native_unit_of_measurement=TEMP_FAHRENHEIT,
device_class=DEVICE_CLASS_TEMPERATURE,
),
SensorEntityDescription(
key=TYPE_SOILTEMP2F,
name="Soil Temp 2",
native_unit_of_measurement=TEMP_FAHRENHEIT,
device_class=DEVICE_CLASS_TEMPERATURE,
),
SensorEntityDescription(
key=TYPE_SOILTEMP3F,
name="Soil Temp 3",
native_unit_of_measurement=TEMP_FAHRENHEIT,
device_class=DEVICE_CLASS_TEMPERATURE,
),
SensorEntityDescription(
key=TYPE_SOILTEMP4F,
name="Soil Temp 4",
native_unit_of_measurement=TEMP_FAHRENHEIT,
device_class=DEVICE_CLASS_TEMPERATURE,
),
SensorEntityDescription(
key=TYPE_SOILTEMP5F,
name="Soil Temp 5",
native_unit_of_measurement=TEMP_FAHRENHEIT,
device_class=DEVICE_CLASS_TEMPERATURE,
),
SensorEntityDescription(
key=TYPE_SOILTEMP6F,
name="Soil Temp 6",
native_unit_of_measurement=TEMP_FAHRENHEIT,
device_class=DEVICE_CLASS_TEMPERATURE,
),
SensorEntityDescription(
key=TYPE_SOILTEMP7F,
name="Soil Temp 7",
native_unit_of_measurement=TEMP_FAHRENHEIT,
device_class=DEVICE_CLASS_TEMPERATURE,
),
SensorEntityDescription(
key=TYPE_SOILTEMP8F,
name="Soil Temp 8",
native_unit_of_measurement=TEMP_FAHRENHEIT,
device_class=DEVICE_CLASS_TEMPERATURE,
),
SensorEntityDescription(
key=TYPE_SOILTEMP9F,
name="Soil Temp 9",
native_unit_of_measurement=TEMP_FAHRENHEIT,
device_class=DEVICE_CLASS_TEMPERATURE,
),
SensorEntityDescription(
key=TYPE_SOLARRADIATION,
name="Solar Rad",
native_unit_of_measurement=IRRADIATION_WATTS_PER_SQUARE_METER,
device_class=DEVICE_CLASS_ILLUMINANCE,
),
SensorEntityDescription(
key=TYPE_SOLARRADIATION_LX,
name="Solar Rad (lx)",
native_unit_of_measurement=LIGHT_LUX,
device_class=DEVICE_CLASS_ILLUMINANCE,
),
SensorEntityDescription(
key=TYPE_TEMP10F,
name="Temp 10",
native_unit_of_measurement=TEMP_FAHRENHEIT,
device_class=DEVICE_CLASS_TEMPERATURE,
),
SensorEntityDescription(
key=TYPE_TEMP1F,
name="Temp 1",
native_unit_of_measurement=TEMP_FAHRENHEIT,
device_class=DEVICE_CLASS_TEMPERATURE,
),
SensorEntityDescription(
key=TYPE_TEMP2F,
name="Temp 2",
native_unit_of_measurement=TEMP_FAHRENHEIT,
device_class=DEVICE_CLASS_TEMPERATURE,
),
SensorEntityDescription(
key=TYPE_TEMP3F,
name="Temp 3",
native_unit_of_measurement=TEMP_FAHRENHEIT,
device_class=DEVICE_CLASS_TEMPERATURE,
),
SensorEntityDescription(
key=TYPE_TEMP4F,
name="Temp 4",
native_unit_of_measurement=TEMP_FAHRENHEIT,
device_class=DEVICE_CLASS_TEMPERATURE,
),
SensorEntityDescription(
key=TYPE_TEMP5F,
name="Temp 5",
native_unit_of_measurement=TEMP_FAHRENHEIT,
device_class=DEVICE_CLASS_TEMPERATURE,
),
SensorEntityDescription(
key=TYPE_TEMP6F,
name="Temp 6",
native_unit_of_measurement=TEMP_FAHRENHEIT,
device_class=DEVICE_CLASS_TEMPERATURE,
),
SensorEntityDescription(
key=TYPE_TEMP7F,
name="Temp 7",
native_unit_of_measurement=TEMP_FAHRENHEIT,
device_class=DEVICE_CLASS_TEMPERATURE,
),
SensorEntityDescription(
key=TYPE_TEMP8F,
name="Temp 8",
native_unit_of_measurement=TEMP_FAHRENHEIT,
device_class=DEVICE_CLASS_TEMPERATURE,
),
SensorEntityDescription(
key=TYPE_TEMP9F,
name="Temp 9",
native_unit_of_measurement=TEMP_FAHRENHEIT,
device_class=DEVICE_CLASS_TEMPERATURE,
),
SensorEntityDescription(
key=TYPE_TEMPF,
name="Temp",
native_unit_of_measurement=TEMP_FAHRENHEIT,
device_class=DEVICE_CLASS_TEMPERATURE,
),
SensorEntityDescription(
key=TYPE_TEMPINF,
name="Inside Temp",
native_unit_of_measurement=TEMP_FAHRENHEIT,
device_class=DEVICE_CLASS_TEMPERATURE,
),
SensorEntityDescription(
key=TYPE_TOTALRAININ,
name="Lifetime Rain",
icon="mdi:water",
native_unit_of_measurement=PRECIPITATION_INCHES,
),
SensorEntityDescription(
key=TYPE_UV,
name="UV Index",
native_unit_of_measurement="Index",
device_class=DEVICE_CLASS_ILLUMINANCE,
),
SensorEntityDescription(
key=TYPE_WEEKLYRAININ,
name="Weekly Rain",
icon="mdi:water",
native_unit_of_measurement=PRECIPITATION_INCHES,
),
SensorEntityDescription(
key=TYPE_WINDDIR,
name="Wind Dir",
icon="mdi:weather-windy",
native_unit_of_measurement=DEGREE,
),
SensorEntityDescription(
key=TYPE_WINDDIR_AVG10M,
name="Wind Dir Avg 10m",
icon="mdi:weather-windy",
native_unit_of_measurement=DEGREE,
),
SensorEntityDescription(
key=TYPE_WINDDIR_AVG2M,
name="Wind Dir Avg 2m",
icon="mdi:weather-windy",
native_unit_of_measurement=SPEED_MILES_PER_HOUR,
),
SensorEntityDescription(
key=TYPE_WINDGUSTDIR,
name="Gust Dir",
icon="mdi:weather-windy",
native_unit_of_measurement=DEGREE,
),
SensorEntityDescription(
key=TYPE_WINDGUSTMPH,
name="Wind Gust",
icon="mdi:weather-windy",
native_unit_of_measurement=SPEED_MILES_PER_HOUR,
),
SensorEntityDescription(
key=TYPE_WINDSPDMPH_AVG10M,
name="Wind Avg 10m",
icon="mdi:weather-windy",
native_unit_of_measurement=SPEED_MILES_PER_HOUR,
),
SensorEntityDescription(
key=TYPE_WINDSPDMPH_AVG2M,
name="Wind Avg 2m",
icon="mdi:weather-windy",
native_unit_of_measurement=SPEED_MILES_PER_HOUR,
),
SensorEntityDescription(
key=TYPE_WINDSPEEDMPH,
name="Wind Speed",
icon="mdi:weather-windy",
native_unit_of_measurement=SPEED_MILES_PER_HOUR,
),
SensorEntityDescription(
key=TYPE_YEARLYRAININ,
name="Yearly Rain",
icon="mdi:water",
native_unit_of_measurement=PRECIPITATION_INCHES,
),
from . import (
SENSOR_TYPES,
TYPE_SOLARRADIATION,
TYPE_SOLARRADIATION_LX,
AmbientStation,
AmbientWeatherEntity,
)
from .const import ATTR_LAST_DATA, ATTR_MONITORED_CONDITIONS, DATA_CLIENT, DOMAIN
async def async_setup_entry(
@@ -557,22 +23,62 @@ async def async_setup_entry(
"""Set up Ambient PWS sensors based on a config entry."""
ambient = hass.data[DOMAIN][DATA_CLIENT][entry.entry_id]
async_add_entities(
[
AmbientWeatherSensor(ambient, mac_address, station[ATTR_NAME], description)
for mac_address, station in ambient.stations.items()
for description in SENSOR_DESCRIPTIONS
if description.key in station[ATTR_LAST_DATA]
]
)
sensor_list = []
for mac_address, station in ambient.stations.items():
for condition in station[ATTR_MONITORED_CONDITIONS]:
name, unit, kind, device_class = SENSOR_TYPES[condition]
if kind == SENSOR:
sensor_list.append(
AmbientWeatherSensor(
ambient,
mac_address,
station[ATTR_NAME],
condition,
name,
device_class,
unit,
)
)
async_add_entities(sensor_list)
class AmbientWeatherSensor(AmbientWeatherEntity, SensorEntity):
"""Define an Ambient sensor."""
def __init__(
self,
ambient: AmbientStation,
mac_address: str,
station_name: str,
sensor_type: str,
sensor_name: str,
device_class: str | None,
unit: str | None,
) -> None:
"""Initialize the sensor."""
super().__init__(
ambient, mac_address, station_name, sensor_type, sensor_name, device_class
)
self._attr_native_unit_of_measurement = unit
@callback
def update_from_latest_data(self) -> None:
"""Fetch new state data for the sensor."""
self._attr_native_value = self._ambient.stations[self._mac_address][
ATTR_LAST_DATA
][self.entity_description.key]
if self._sensor_type == TYPE_SOLARRADIATION_LX:
# If the user requests the solarradiation_lx sensor, use the
# value of the solarradiation sensor and apply a very accurate
# approximation of converting sunlight W/m^2 to lx:
w_m2_brightness_val = self._ambient.stations[self._mac_address][
ATTR_LAST_DATA
].get(TYPE_SOLARRADIATION)
if w_m2_brightness_val is None:
self._attr_native_value = None
else:
self._attr_native_value = round(float(w_m2_brightness_val) / 0.0079)
else:
self._attr_native_value = self._ambient.stations[self._mac_address][
ATTR_LAST_DATA
].get(self._sensor_type)

View File

@@ -1,10 +1,10 @@
{
"config": {
"abort": {
"already_configured": "Le service est d\u00e9j\u00e0 configur\u00e9"
"already_configured": "Cette cl\u00e9 d'application est d\u00e9j\u00e0 utilis\u00e9e."
},
"error": {
"invalid_key": "Cl\u00e9 API invalide",
"invalid_key": "Cl\u00e9 d'API et / ou cl\u00e9 d'application non valide",
"no_devices": "Aucun appareil trouv\u00e9 dans le compte"
},
"step": {

View File

@@ -232,6 +232,8 @@ class AmcrestBinarySensor(BinarySensorEntity):
async def async_added_to_hass(self) -> None:
"""Subscribe to signals."""
assert self.hass is not None
self._unsub_dispatcher.append(
async_dispatcher_connect(
self.hass,

View File

@@ -180,6 +180,7 @@ class AmcrestCam(Camera):
raise CannotSnapshot
async def _async_get_image(self) -> None:
assert self.hass is not None
try:
# Send the request to snap a picture and return raw jpg data
# Snapshot command needs a much longer read timeout than other commands.
@@ -200,6 +201,7 @@ class AmcrestCam(Camera):
self, width: int | None = None, height: int | None = None
) -> bytes | None:
"""Return a still image response from the camera."""
assert self.hass is not None
_LOGGER.debug("Take snapshot from %s", self._name)
try:
# Amcrest cameras only support one snapshot command at a time.
@@ -224,6 +226,7 @@ class AmcrestCam(Camera):
self, request: web.Request
) -> web.StreamResponse | None:
"""Return an MJPEG stream."""
assert self.hass is not None
# The snapshot implementation is handled by the parent class
if self._stream_source == "snapshot":
return await super().handle_async_mjpeg_stream(request)
@@ -341,6 +344,7 @@ class AmcrestCam(Camera):
async def async_added_to_hass(self) -> None:
"""Subscribe to signals and add camera to list."""
assert self.hass is not None
self._unsub_dispatcher.extend(
async_dispatcher_connect(
self.hass,
@@ -360,6 +364,7 @@ class AmcrestCam(Camera):
async def async_will_remove_from_hass(self) -> None:
"""Remove camera from list and disconnect from signals."""
assert self.hass is not None
self.hass.data[DATA_AMCREST][CAMERAS].remove(self.entity_id)
for unsub_dispatcher in self._unsub_dispatcher:
unsub_dispatcher()
@@ -374,16 +379,15 @@ class AmcrestCam(Camera):
try:
if self._brand is None:
resp = self._api.vendor_information.strip()
_LOGGER.debug("Assigned brand=%s", resp)
if resp.startswith("vendor="):
self._brand = resp.split("=")[-1]
else:
self._brand = "unknown"
if self._model is None:
resp = self._api.device_type.strip()
_LOGGER.debug("Assigned model=%s", resp)
if resp:
self._model = resp
_LOGGER.debug("Device_type=%s", resp)
if resp.startswith("type="):
self._model = resp.split("=")[-1]
else:
self._model = "unknown"
if self._attr_unique_id is None:
@@ -424,46 +428,57 @@ class AmcrestCam(Camera):
async def async_enable_recording(self) -> None:
"""Call the job and enable recording."""
assert self.hass is not None
await self.hass.async_add_executor_job(self._enable_recording, True)
async def async_disable_recording(self) -> None:
"""Call the job and disable recording."""
assert self.hass is not None
await self.hass.async_add_executor_job(self._enable_recording, False)
async def async_enable_audio(self) -> None:
"""Call the job and enable audio."""
assert self.hass is not None
await self.hass.async_add_executor_job(self._enable_audio, True)
async def async_disable_audio(self) -> None:
"""Call the job and disable audio."""
assert self.hass is not None
await self.hass.async_add_executor_job(self._enable_audio, False)
async def async_enable_motion_recording(self) -> None:
"""Call the job and enable motion recording."""
assert self.hass is not None
await self.hass.async_add_executor_job(self._enable_motion_recording, True)
async def async_disable_motion_recording(self) -> None:
"""Call the job and disable motion recording."""
assert self.hass is not None
await self.hass.async_add_executor_job(self._enable_motion_recording, False)
async def async_goto_preset(self, preset: int) -> None:
"""Call the job and move camera to preset position."""
assert self.hass is not None
await self.hass.async_add_executor_job(self._goto_preset, preset)
async def async_set_color_bw(self, color_bw: str) -> None:
"""Call the job and set camera color mode."""
assert self.hass is not None
await self.hass.async_add_executor_job(self._set_color_bw, color_bw)
async def async_start_tour(self) -> None:
"""Call the job and start camera tour."""
assert self.hass is not None
await self.hass.async_add_executor_job(self._start_tour, True)
async def async_stop_tour(self) -> None:
"""Call the job and stop camera tour."""
assert self.hass is not None
await self.hass.async_add_executor_job(self._start_tour, False)
async def async_ptz_control(self, movement: str, travel_time: float) -> None:
"""Move or zoom camera in specified direction."""
assert self.hass is not None
code = _ACTION[_MOV.index(movement)]
kwargs = {"code": code, "arg1": 0, "arg2": 0, "arg3": 0}

View File

@@ -129,6 +129,7 @@ class AmcrestSensor(SensorEntity):
async def async_added_to_hass(self) -> None:
"""Subscribe to update signal."""
assert self.hass is not None
self._unsub_dispatcher = async_dispatcher_connect(
self.hass,
service_signal(SERVICE_UPDATE, self._signal_name),

View File

@@ -1,16 +1,10 @@
"""Support for APCUPSd sensors."""
from __future__ import annotations
import logging
from apcaccess.status import ALL_UNITS
import voluptuous as vol
from homeassistant.components.sensor import (
PLATFORM_SCHEMA,
SensorEntity,
SensorEntityDescription,
)
from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity
from homeassistant.const import (
CONF_RESOURCES,
DEVICE_CLASS_TEMPERATURE,
@@ -31,360 +25,74 @@ from . import DOMAIN
_LOGGER = logging.getLogger(__name__)
SENSOR_PREFIX = "UPS "
SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
SensorEntityDescription(
key="alarmdel",
name="Alarm Delay",
icon="mdi:alarm",
),
SensorEntityDescription(
key="ambtemp",
name="Ambient Temperature",
icon="mdi:thermometer",
),
SensorEntityDescription(
key="apc",
name="Status Data",
icon="mdi:information-outline",
),
SensorEntityDescription(
key="apcmodel",
name="Model",
icon="mdi:information-outline",
),
SensorEntityDescription(
key="badbatts",
name="Bad Batteries",
icon="mdi:information-outline",
),
SensorEntityDescription(
key="battdate",
name="Battery Replaced",
icon="mdi:calendar-clock",
),
SensorEntityDescription(
key="battstat",
name="Battery Status",
icon="mdi:information-outline",
),
SensorEntityDescription(
key="battv",
name="Battery Voltage",
native_unit_of_measurement=ELECTRIC_POTENTIAL_VOLT,
icon="mdi:flash",
),
SensorEntityDescription(
key="bcharge",
name="Battery",
native_unit_of_measurement=PERCENTAGE,
icon="mdi:battery",
),
SensorEntityDescription(
key="cable",
name="Cable Type",
icon="mdi:ethernet-cable",
),
SensorEntityDescription(
key="cumonbatt",
name="Total Time on Battery",
icon="mdi:timer-outline",
),
SensorEntityDescription(
key="date",
name="Status Date",
icon="mdi:calendar-clock",
),
SensorEntityDescription(
key="dipsw",
name="Dip Switch Settings",
icon="mdi:information-outline",
),
SensorEntityDescription(
key="dlowbatt",
name="Low Battery Signal",
icon="mdi:clock-alert",
),
SensorEntityDescription(
key="driver",
name="Driver",
icon="mdi:information-outline",
),
SensorEntityDescription(
key="dshutd",
name="Shutdown Delay",
icon="mdi:timer-outline",
),
SensorEntityDescription(
key="dwake",
name="Wake Delay",
icon="mdi:timer-outline",
),
SensorEntityDescription(
key="endapc",
name="Date and Time",
icon="mdi:calendar-clock",
),
SensorEntityDescription(
key="extbatts",
name="External Batteries",
icon="mdi:information-outline",
),
SensorEntityDescription(
key="firmware",
name="Firmware Version",
icon="mdi:information-outline",
),
SensorEntityDescription(
key="hitrans",
name="Transfer High",
native_unit_of_measurement=ELECTRIC_POTENTIAL_VOLT,
icon="mdi:flash",
),
SensorEntityDescription(
key="hostname",
name="Hostname",
icon="mdi:information-outline",
),
SensorEntityDescription(
key="humidity",
name="Ambient Humidity",
native_unit_of_measurement=PERCENTAGE,
icon="mdi:water-percent",
),
SensorEntityDescription(
key="itemp",
name="Internal Temperature",
native_unit_of_measurement=TEMP_CELSIUS,
device_class=DEVICE_CLASS_TEMPERATURE,
),
SensorEntityDescription(
key="lastxfer",
name="Last Transfer",
icon="mdi:transfer",
),
SensorEntityDescription(
key="linefail",
name="Input Voltage Status",
icon="mdi:information-outline",
),
SensorEntityDescription(
key="linefreq",
name="Line Frequency",
native_unit_of_measurement=FREQUENCY_HERTZ,
icon="mdi:information-outline",
),
SensorEntityDescription(
key="linev",
name="Input Voltage",
native_unit_of_measurement=ELECTRIC_POTENTIAL_VOLT,
icon="mdi:flash",
),
SensorEntityDescription(
key="loadpct",
name="Load",
native_unit_of_measurement=PERCENTAGE,
icon="mdi:gauge",
),
SensorEntityDescription(
key="loadapnt",
name="Load Apparent Power",
native_unit_of_measurement=PERCENTAGE,
icon="mdi:gauge",
),
SensorEntityDescription(
key="lotrans",
name="Transfer Low",
native_unit_of_measurement=ELECTRIC_POTENTIAL_VOLT,
icon="mdi:flash",
),
SensorEntityDescription(
key="mandate",
name="Manufacture Date",
icon="mdi:calendar",
),
SensorEntityDescription(
key="masterupd",
name="Master Update",
icon="mdi:information-outline",
),
SensorEntityDescription(
key="maxlinev",
name="Input Voltage High",
native_unit_of_measurement=ELECTRIC_POTENTIAL_VOLT,
icon="mdi:flash",
),
SensorEntityDescription(
key="maxtime",
name="Battery Timeout",
icon="mdi:timer-off-outline",
),
SensorEntityDescription(
key="mbattchg",
name="Battery Shutdown",
native_unit_of_measurement=PERCENTAGE,
icon="mdi:battery-alert",
),
SensorEntityDescription(
key="minlinev",
name="Input Voltage Low",
native_unit_of_measurement=ELECTRIC_POTENTIAL_VOLT,
icon="mdi:flash",
),
SensorEntityDescription(
key="mintimel",
name="Shutdown Time",
icon="mdi:timer-outline",
),
SensorEntityDescription(
key="model",
name="Model",
icon="mdi:information-outline",
),
SensorEntityDescription(
key="nombattv",
name="Battery Nominal Voltage",
native_unit_of_measurement=ELECTRIC_POTENTIAL_VOLT,
icon="mdi:flash",
),
SensorEntityDescription(
key="nominv",
name="Nominal Input Voltage",
native_unit_of_measurement=ELECTRIC_POTENTIAL_VOLT,
icon="mdi:flash",
),
SensorEntityDescription(
key="nomoutv",
name="Nominal Output Voltage",
native_unit_of_measurement=ELECTRIC_POTENTIAL_VOLT,
icon="mdi:flash",
),
SensorEntityDescription(
key="nompower",
name="Nominal Output Power",
native_unit_of_measurement=POWER_WATT,
icon="mdi:flash",
),
SensorEntityDescription(
key="nomapnt",
name="Nominal Apparent Power",
native_unit_of_measurement=POWER_VOLT_AMPERE,
icon="mdi:flash",
),
SensorEntityDescription(
key="numxfers",
name="Transfer Count",
icon="mdi:counter",
),
SensorEntityDescription(
key="outcurnt",
name="Output Current",
native_unit_of_measurement=ELECTRIC_CURRENT_AMPERE,
icon="mdi:flash",
),
SensorEntityDescription(
key="outputv",
name="Output Voltage",
native_unit_of_measurement=ELECTRIC_POTENTIAL_VOLT,
icon="mdi:flash",
),
SensorEntityDescription(
key="reg1",
name="Register 1 Fault",
icon="mdi:information-outline",
),
SensorEntityDescription(
key="reg2",
name="Register 2 Fault",
icon="mdi:information-outline",
),
SensorEntityDescription(
key="reg3",
name="Register 3 Fault",
icon="mdi:information-outline",
),
SensorEntityDescription(
key="retpct",
name="Restore Requirement",
native_unit_of_measurement=PERCENTAGE,
icon="mdi:battery-alert",
),
SensorEntityDescription(
key="selftest",
name="Last Self Test",
icon="mdi:calendar-clock",
),
SensorEntityDescription(
key="sense",
name="Sensitivity",
icon="mdi:information-outline",
),
SensorEntityDescription(
key="serialno",
name="Serial Number",
icon="mdi:information-outline",
),
SensorEntityDescription(
key="starttime",
name="Startup Time",
icon="mdi:calendar-clock",
),
SensorEntityDescription(
key="statflag",
name="Status Flag",
icon="mdi:information-outline",
),
SensorEntityDescription(
key="status",
name="Status",
icon="mdi:information-outline",
),
SensorEntityDescription(
key="stesti",
name="Self Test Interval",
icon="mdi:information-outline",
),
SensorEntityDescription(
key="timeleft",
name="Time Left",
icon="mdi:clock-alert",
),
SensorEntityDescription(
key="tonbatt",
name="Time on Battery",
icon="mdi:timer-outline",
),
SensorEntityDescription(
key="upsmode",
name="Mode",
icon="mdi:information-outline",
),
SensorEntityDescription(
key="upsname",
name="Name",
icon="mdi:information-outline",
),
SensorEntityDescription(
key="version",
name="Daemon Info",
icon="mdi:information-outline",
),
SensorEntityDescription(
key="xoffbat",
name="Transfer from Battery",
icon="mdi:transfer",
),
SensorEntityDescription(
key="xoffbatt",
name="Transfer from Battery",
icon="mdi:transfer",
),
SensorEntityDescription(
key="xonbatt",
name="Transfer to Battery",
icon="mdi:transfer",
),
)
SENSOR_KEYS: list[str] = [desc.key for desc in SENSOR_TYPES]
SENSOR_TYPES = {
"alarmdel": ["Alarm Delay", None, "mdi:alarm", None],
"ambtemp": ["Ambient Temperature", None, "mdi:thermometer", None],
"apc": ["Status Data", None, "mdi:information-outline", None],
"apcmodel": ["Model", None, "mdi:information-outline", None],
"badbatts": ["Bad Batteries", None, "mdi:information-outline", None],
"battdate": ["Battery Replaced", None, "mdi:calendar-clock", None],
"battstat": ["Battery Status", None, "mdi:information-outline", None],
"battv": ["Battery Voltage", ELECTRIC_POTENTIAL_VOLT, "mdi:flash", None],
"bcharge": ["Battery", PERCENTAGE, "mdi:battery", None],
"cable": ["Cable Type", None, "mdi:ethernet-cable", None],
"cumonbatt": ["Total Time on Battery", None, "mdi:timer-outline", None],
"date": ["Status Date", None, "mdi:calendar-clock", None],
"dipsw": ["Dip Switch Settings", None, "mdi:information-outline", None],
"dlowbatt": ["Low Battery Signal", None, "mdi:clock-alert", None],
"driver": ["Driver", None, "mdi:information-outline", None],
"dshutd": ["Shutdown Delay", None, "mdi:timer-outline", None],
"dwake": ["Wake Delay", None, "mdi:timer-outline", None],
"endapc": ["Date and Time", None, "mdi:calendar-clock", None],
"extbatts": ["External Batteries", None, "mdi:information-outline", None],
"firmware": ["Firmware Version", None, "mdi:information-outline", None],
"hitrans": ["Transfer High", ELECTRIC_POTENTIAL_VOLT, "mdi:flash", None],
"hostname": ["Hostname", None, "mdi:information-outline", None],
"humidity": ["Ambient Humidity", PERCENTAGE, "mdi:water-percent", None],
"itemp": ["Internal Temperature", TEMP_CELSIUS, None, DEVICE_CLASS_TEMPERATURE],
"lastxfer": ["Last Transfer", None, "mdi:transfer", None],
"linefail": ["Input Voltage Status", None, "mdi:information-outline", None],
"linefreq": ["Line Frequency", FREQUENCY_HERTZ, "mdi:information-outline", None],
"linev": ["Input Voltage", ELECTRIC_POTENTIAL_VOLT, "mdi:flash", None],
"loadpct": ["Load", PERCENTAGE, "mdi:gauge", None],
"loadapnt": ["Load Apparent Power", PERCENTAGE, "mdi:gauge", None],
"lotrans": ["Transfer Low", ELECTRIC_POTENTIAL_VOLT, "mdi:flash", None],
"mandate": ["Manufacture Date", None, "mdi:calendar", None],
"masterupd": ["Master Update", None, "mdi:information-outline", None],
"maxlinev": ["Input Voltage High", ELECTRIC_POTENTIAL_VOLT, "mdi:flash", None],
"maxtime": ["Battery Timeout", None, "mdi:timer-off-outline", None],
"mbattchg": ["Battery Shutdown", PERCENTAGE, "mdi:battery-alert", None],
"minlinev": ["Input Voltage Low", ELECTRIC_POTENTIAL_VOLT, "mdi:flash", None],
"mintimel": ["Shutdown Time", None, "mdi:timer-outline", None],
"model": ["Model", None, "mdi:information-outline", None],
"nombattv": ["Battery Nominal Voltage", ELECTRIC_POTENTIAL_VOLT, "mdi:flash", None],
"nominv": ["Nominal Input Voltage", ELECTRIC_POTENTIAL_VOLT, "mdi:flash", None],
"nomoutv": ["Nominal Output Voltage", ELECTRIC_POTENTIAL_VOLT, "mdi:flash", None],
"nompower": ["Nominal Output Power", POWER_WATT, "mdi:flash", None],
"nomapnt": ["Nominal Apparent Power", POWER_VOLT_AMPERE, "mdi:flash", None],
"numxfers": ["Transfer Count", None, "mdi:counter", None],
"outcurnt": ["Output Current", ELECTRIC_CURRENT_AMPERE, "mdi:flash", None],
"outputv": ["Output Voltage", ELECTRIC_POTENTIAL_VOLT, "mdi:flash", None],
"reg1": ["Register 1 Fault", None, "mdi:information-outline", None],
"reg2": ["Register 2 Fault", None, "mdi:information-outline", None],
"reg3": ["Register 3 Fault", None, "mdi:information-outline", None],
"retpct": ["Restore Requirement", PERCENTAGE, "mdi:battery-alert", None],
"selftest": ["Last Self Test", None, "mdi:calendar-clock", None],
"sense": ["Sensitivity", None, "mdi:information-outline", None],
"serialno": ["Serial Number", None, "mdi:information-outline", None],
"starttime": ["Startup Time", None, "mdi:calendar-clock", None],
"statflag": ["Status Flag", None, "mdi:information-outline", None],
"status": ["Status", None, "mdi:information-outline", None],
"stesti": ["Self Test Interval", None, "mdi:information-outline", None],
"timeleft": ["Time Left", None, "mdi:clock-alert", None],
"tonbatt": ["Time on Battery", None, "mdi:timer-outline", None],
"upsmode": ["Mode", None, "mdi:information-outline", None],
"upsname": ["Name", None, "mdi:information-outline", None],
"version": ["Daemon Info", None, "mdi:information-outline", None],
"xoffbat": ["Transfer from Battery", None, "mdi:transfer", None],
"xoffbatt": ["Transfer from Battery", None, "mdi:transfer", None],
"xonbatt": ["Transfer to Battery", None, "mdi:transfer", None],
}
SPECIFIC_UNITS = {"ITEMP": TEMP_CELSIUS}
INFERRED_UNITS = {
@@ -403,7 +111,7 @@ INFERRED_UNITS = {
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{
vol.Required(CONF_RESOURCES, default=[]): vol.All(
cv.ensure_list, [vol.In(SENSOR_KEYS)]
cv.ensure_list, [vol.In(SENSOR_TYPES)]
)
}
)
@@ -412,20 +120,25 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the APCUPSd sensors."""
apcups_data = hass.data[DOMAIN]
resources = config[CONF_RESOURCES]
entities = []
for resource in resources:
if resource.upper() not in apcups_data.status:
for resource in config[CONF_RESOURCES]:
sensor_type = resource.lower()
if sensor_type not in SENSOR_TYPES:
SENSOR_TYPES[sensor_type] = [
sensor_type.title(),
"",
"mdi:information-outline",
]
if sensor_type.upper() not in apcups_data.status:
_LOGGER.warning(
"Sensor type: %s does not appear in the APCUPSd status output",
resource,
sensor_type,
)
entities = [
APCUPSdSensor(apcups_data, description)
for description in SENSOR_TYPES
if description.key in resources
]
entities.append(APCUPSdSensor(apcups_data, sensor_type))
add_entities(entities, True)
@@ -446,18 +159,22 @@ def infer_unit(value):
class APCUPSdSensor(SensorEntity):
"""Representation of a sensor entity for APCUPSd status values."""
def __init__(self, data, description: SensorEntityDescription):
def __init__(self, data, sensor_type):
"""Initialize the sensor."""
self.entity_description = description
self._data = data
self._attr_name = f"{SENSOR_PREFIX}{description.name}"
self.type = sensor_type
self._attr_name = SENSOR_PREFIX + SENSOR_TYPES[sensor_type][0]
self._attr_icon = SENSOR_TYPES[self.type][2]
self._attr_native_unit_of_measurement = SENSOR_TYPES[sensor_type][1]
self._attr_device_class = SENSOR_TYPES[sensor_type][3]
def update(self):
"""Get the latest status and use it to update our sensor state."""
key = self.entity_description.key.upper()
if key not in self._data.status:
if self.type.upper() not in self._data.status:
self._attr_native_value = None
else:
self._attr_native_value, inferred_unit = infer_unit(self._data.status[key])
if not self.native_unit_of_measurement:
self._attr_native_value, inferred_unit = infer_unit(
self._data.status[self.type.upper()]
)
if not self._attr_native_unit_of_measurement:
self._attr_native_unit_of_measurement = inferred_unit

View File

@@ -5,6 +5,7 @@
"documentation": "https://www.home-assistant.io/integrations/apple_tv",
"requirements": ["pyatv==0.8.2"],
"zeroconf": ["_mediaremotetv._tcp.local.", "_touch-able._tcp.local."],
"after_dependencies": ["discovery"],
"codeowners": ["@postlund"],
"iot_class": "local_push"
}

View File

@@ -1,7 +1,7 @@
{
"config": {
"abort": {
"already_configured_device": "L'appareil est d\u00e9j\u00e0 configur\u00e9",
"already_configured_device": "Le p\u00e9riph\u00e9rique est d\u00e9j\u00e0 configur\u00e9",
"already_in_progress": "La configuration est d\u00e9j\u00e0 en cours",
"backoff": "L'appareil n'accepte pas les demandes d'appariement pour le moment (vous avez peut-\u00eatre saisi un code PIN non valide trop de fois), r\u00e9essayez plus tard.",
"device_did_not_pair": "Aucune tentative pour terminer l'appairage n'a \u00e9t\u00e9 effectu\u00e9e \u00e0 partir de l'appareil.",
@@ -11,10 +11,10 @@
},
"error": {
"already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9",
"invalid_auth": "Authentification invalide",
"no_devices_found": "Aucun appareil trouv\u00e9 sur le r\u00e9seau",
"invalid_auth": "Autentification invalide",
"no_devices_found": "Aucun appareil d\u00e9tect\u00e9 sur le r\u00e9seau",
"no_usable_service": "Un dispositif a \u00e9t\u00e9 trouv\u00e9, mais aucun moyen d\u2019\u00e9tablir un lien avec lui. Si vous continuez \u00e0 voir ce message, essayez de sp\u00e9cifier son adresse IP ou de red\u00e9marrer votre Apple TV.",
"unknown": "Erreur inattendue"
"unknown": "Erreur innatendue"
},
"flow_title": "Apple TV: {name}",
"step": {

View File

@@ -1,15 +1,8 @@
"""Support for AquaLogic sensors."""
from __future__ import annotations
from dataclasses import dataclass
import voluptuous as vol
from homeassistant.components.sensor import (
PLATFORM_SCHEMA,
SensorEntity,
SensorEntityDescription,
)
from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity
from homeassistant.const import (
CONF_MONITORED_CONDITIONS,
DEVICE_CLASS_TEMPERATURE,
@@ -23,88 +16,40 @@ import homeassistant.helpers.config_validation as cv
from . import DOMAIN, UPDATE_TOPIC
TEMP_UNITS = [TEMP_CELSIUS, TEMP_FAHRENHEIT]
PERCENT_UNITS = [PERCENTAGE, PERCENTAGE]
SALT_UNITS = ["g/L", "PPM"]
WATT_UNITS = [POWER_WATT, POWER_WATT]
NO_UNITS = [None, None]
@dataclass
class AquaLogicSensorEntityDescription(SensorEntityDescription):
"""Describes AquaLogic sensor entity."""
unit_metric: str | None = None
unit_imperial: str | None = None
# keys correspond to property names in aqualogic.core.AquaLogic
SENSOR_TYPES: tuple[AquaLogicSensorEntityDescription, ...] = (
AquaLogicSensorEntityDescription(
key="air_temp",
name="Air Temperature",
unit_metric=TEMP_CELSIUS,
unit_imperial=TEMP_FAHRENHEIT,
device_class=DEVICE_CLASS_TEMPERATURE,
),
AquaLogicSensorEntityDescription(
key="pool_temp",
name="Pool Temperature",
unit_metric=TEMP_CELSIUS,
unit_imperial=TEMP_FAHRENHEIT,
icon="mdi:oil-temperature",
device_class=DEVICE_CLASS_TEMPERATURE,
),
AquaLogicSensorEntityDescription(
key="spa_temp",
name="Spa Temperature",
unit_metric=TEMP_CELSIUS,
unit_imperial=TEMP_FAHRENHEIT,
icon="mdi:oil-temperature",
device_class=DEVICE_CLASS_TEMPERATURE,
),
AquaLogicSensorEntityDescription(
key="pool_chlorinator",
name="Pool Chlorinator",
unit_metric=PERCENTAGE,
unit_imperial=PERCENTAGE,
icon="mdi:gauge",
),
AquaLogicSensorEntityDescription(
key="spa_chlorinator",
name="Spa Chlorinator",
unit_metric=PERCENTAGE,
unit_imperial=PERCENTAGE,
icon="mdi:gauge",
),
AquaLogicSensorEntityDescription(
key="salt_level",
name="Salt Level",
unit_metric="g/L",
unit_imperial="PPM",
icon="mdi:gauge",
),
AquaLogicSensorEntityDescription(
key="pump_speed",
name="Pump Speed",
unit_metric=PERCENTAGE,
unit_imperial=PERCENTAGE,
icon="mdi:speedometer",
),
AquaLogicSensorEntityDescription(
key="pump_power",
name="Pump Power",
unit_metric=POWER_WATT,
unit_imperial=POWER_WATT,
icon="mdi:gauge",
),
AquaLogicSensorEntityDescription(
key="status",
name="Status",
icon="mdi:alert",
),
)
SENSOR_KEYS: list[str] = [desc.key for desc in SENSOR_TYPES]
# sensor_type [ description, unit, icon, device_class ]
# sensor_type corresponds to property names in aqualogic.core.AquaLogic
SENSOR_TYPES = {
"air_temp": ["Air Temperature", TEMP_UNITS, None, DEVICE_CLASS_TEMPERATURE],
"pool_temp": [
"Pool Temperature",
TEMP_UNITS,
"mdi:oil-temperature",
DEVICE_CLASS_TEMPERATURE,
],
"spa_temp": [
"Spa Temperature",
TEMP_UNITS,
"mdi:oil-temperature",
DEVICE_CLASS_TEMPERATURE,
],
"pool_chlorinator": ["Pool Chlorinator", PERCENT_UNITS, "mdi:gauge", None],
"spa_chlorinator": ["Spa Chlorinator", PERCENT_UNITS, "mdi:gauge", None],
"salt_level": ["Salt Level", SALT_UNITS, "mdi:gauge", None],
"pump_speed": ["Pump Speed", PERCENT_UNITS, "mdi:speedometer", None],
"pump_power": ["Pump Power", WATT_UNITS, "mdi:gauge", None],
"status": ["Status", NO_UNITS, "mdi:alert", None],
}
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{
vol.Required(CONF_MONITORED_CONDITIONS, default=SENSOR_KEYS): vol.All(
cv.ensure_list, [vol.In(SENSOR_KEYS)]
vol.Required(CONF_MONITORED_CONDITIONS, default=list(SENSOR_TYPES)): vol.All(
cv.ensure_list, [vol.In(SENSOR_TYPES)]
)
}
)
@@ -112,29 +57,26 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Set up the sensor platform."""
sensors = []
processor = hass.data[DOMAIN]
monitored_conditions = config[CONF_MONITORED_CONDITIONS]
for sensor_type in config[CONF_MONITORED_CONDITIONS]:
sensors.append(AquaLogicSensor(processor, sensor_type))
entities = [
AquaLogicSensor(processor, description)
for description in SENSOR_TYPES
if description.key in monitored_conditions
]
async_add_entities(entities)
async_add_entities(sensors)
class AquaLogicSensor(SensorEntity):
"""Sensor implementation for the AquaLogic component."""
entity_description: AquaLogicSensorEntityDescription
_attr_should_poll = False
def __init__(self, processor, description: AquaLogicSensorEntityDescription):
def __init__(self, processor, sensor_type):
"""Initialize sensor."""
self.entity_description = description
self._processor = processor
self._attr_name = f"AquaLogic {description.name}"
self._type = sensor_type
self._attr_name = f"AquaLogic {SENSOR_TYPES[sensor_type][0]}"
self._attr_icon = SENSOR_TYPES[sensor_type][2]
async def async_added_to_hass(self):
"""Register callbacks."""
@@ -150,15 +92,11 @@ class AquaLogicSensor(SensorEntity):
panel = self._processor.panel
if panel is not None:
if panel.is_metric:
self._attr_native_unit_of_measurement = (
self.entity_description.unit_metric
)
self._attr_native_unit_of_measurement = SENSOR_TYPES[self._type][1][0]
else:
self._attr_native_unit_of_measurement = (
self.entity_description.unit_imperial
)
self._attr_native_unit_of_measurement = SENSOR_TYPES[self._type][1][1]
self._attr_native_value = getattr(panel, self.entity_description.key)
self._attr_native_value = getattr(panel, self._type)
self.async_write_ha_state()
else:
self._attr_native_unit_of_measurement = None

View File

@@ -5,10 +5,7 @@ from typing import Any
import voluptuous as vol
from homeassistant.components.automation import (
AutomationActionType,
AutomationTriggerInfo,
)
from homeassistant.components.automation import AutomationActionType
from homeassistant.components.device_automation import DEVICE_TRIGGER_BASE_SCHEMA
from homeassistant.const import (
ATTR_ENTITY_ID,
@@ -60,10 +57,10 @@ async def async_attach_trigger(
hass: HomeAssistant,
config: ConfigType,
action: AutomationActionType,
automation_info: AutomationTriggerInfo,
automation_info: dict,
) -> CALLBACK_TYPE:
"""Attach a trigger."""
trigger_data = automation_info["trigger_data"]
trigger_data = automation_info.get("trigger_data", {}) if automation_info else {}
job = HassJob(action)
if config[CONF_TYPE] == "turn_on":

View File

@@ -1,8 +1,8 @@
{
"config": {
"abort": {
"already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9",
"already_in_progress": "La configuration est d\u00e9j\u00e0 en cours",
"already_configured": "L'appareil \u00e9tait d\u00e9j\u00e0 configur\u00e9.",
"already_in_progress": "Le flux de configuration de l'appareil est d\u00e9j\u00e0 en cours.",
"cannot_connect": "\u00c9chec de connexion"
},
"error": {

View File

@@ -8,7 +8,6 @@
"invalid_host": "\u05e9\u05dd \u05de\u05d0\u05e8\u05d7 \u05d0\u05d5 \u05db\u05ea\u05d5\u05d1\u05ea IP \u05dc\u05d0 \u05d7\u05d5\u05e7\u05d9\u05d9\u05dd",
"pwd_and_ssh": "\u05e1\u05e4\u05e7 \u05e8\u05e7 \u05e1\u05d9\u05e1\u05de\u05d4 \u05d0\u05d5 \u05e7\u05d5\u05d1\u05e5 \u05de\u05e4\u05ea\u05d7 SSH",
"pwd_or_ssh": "\u05d0\u05e0\u05d0 \u05e1\u05e4\u05e7 \u05e1\u05d9\u05e1\u05de\u05d4 \u05d0\u05d5 \u05e7\u05d5\u05d1\u05e5 \u05de\u05e4\u05ea\u05d7 SSH",
"ssh_not_file": "\u05e7\u05d5\u05d1\u05e5 \u05de\u05e4\u05ea\u05d7 SSH \u05dc\u05d0 \u05e0\u05de\u05e6\u05d0",
"unknown": "\u05e9\u05d2\u05d9\u05d0\u05d4 \u05d1\u05dc\u05ea\u05d9 \u05e6\u05e4\u05d5\u05d9\u05d4"
},
"step": {

View File

@@ -1,7 +1,7 @@
{
"config": {
"abort": {
"already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9"
"already_configured": "Un seul appareil Atag peut \u00eatre ajout\u00e9 \u00e0 Home Assistant"
},
"error": {
"cannot_connect": "\u00c9chec de connexion",
@@ -10,7 +10,7 @@
"step": {
"user": {
"data": {
"host": "H\u00f4te",
"host": "Nom d'h\u00f4te ou adresse IP",
"port": "Port"
},
"title": "Se connecter \u00e0 l'appareil"

View File

@@ -5,8 +5,8 @@
"reauth_successful": "La r\u00e9-authentification a r\u00e9ussi"
},
"error": {
"cannot_connect": "\u00c9chec de connexion",
"invalid_auth": "Authentification invalide",
"cannot_connect": "Impossible de se connecter, veuillez r\u00e9essayer",
"invalid_auth": "Authentification non valide",
"unknown": "Erreur inattendue"
},
"step": {

View File

@@ -1,7 +1,7 @@
{
"config": {
"error": {
"cannot_connect": "\u00c9chec de connexion"
"cannot_connect": "\u00c9chec \u00e0 la connexion"
},
"step": {
"user": {

View File

@@ -2,7 +2,7 @@
from __future__ import annotations
import logging
from typing import Any, Awaitable, Callable, Dict, TypedDict, cast
from typing import Any, Awaitable, Callable, Dict, cast
import voluptuous as vol
from voluptuous.humanize import humanize_error
@@ -106,23 +106,6 @@ _LOGGER = logging.getLogger(__name__)
AutomationActionType = Callable[[HomeAssistant, TemplateVarsType], Awaitable[None]]
class AutomationTriggerData(TypedDict):
"""Automation trigger data."""
id: str
idx: str
class AutomationTriggerInfo(TypedDict):
"""Information about automation trigger."""
domain: str
name: str
home_assistant_start: bool
variables: TemplateVarsType
trigger_data: AutomationTriggerData
@bind_hass
def is_on(hass, entity_id):
"""

View File

@@ -1,5 +1,4 @@
"""Constants for the Awair component."""
from __future__ import annotations
from dataclasses import dataclass
from datetime import timedelta
@@ -7,8 +6,9 @@ import logging
from python_awair.devices import AwairDevice
from homeassistant.components.sensor import SensorEntityDescription
from homeassistant.const import (
ATTR_DEVICE_CLASS,
ATTR_ICON,
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
CONCENTRATION_PARTS_PER_BILLION,
CONCENTRATION_PARTS_PER_MILLION,
@@ -36,6 +36,10 @@ API_VOC = "volatile_organic_compounds"
ATTRIBUTION = "Awair air quality sensor"
ATTR_LABEL = "label"
ATTR_UNIT = "unit"
ATTR_UNIQUE_ID = "unique_id"
DOMAIN = "awair"
DUST_ALIASES = [API_PM25, API_PM10]
@@ -44,89 +48,71 @@ LOGGER = logging.getLogger(__package__)
UPDATE_INTERVAL = timedelta(minutes=5)
@dataclass
class AwairRequiredKeysMixin:
"""Mixinf for required keys."""
unique_id_tag: str
@dataclass
class AwairSensorEntityDescription(SensorEntityDescription, AwairRequiredKeysMixin):
"""Describes Awair sensor entity."""
SENSOR_TYPE_SCORE = AwairSensorEntityDescription(
key=API_SCORE,
icon="mdi:blur",
native_unit_of_measurement=PERCENTAGE,
name="Awair score",
unique_id_tag="score", # matches legacy format
)
SENSOR_TYPES: tuple[AwairSensorEntityDescription, ...] = (
AwairSensorEntityDescription(
key=API_HUMID,
device_class=DEVICE_CLASS_HUMIDITY,
native_unit_of_measurement=PERCENTAGE,
name="Humidity",
unique_id_tag="HUMID", # matches legacy format
),
AwairSensorEntityDescription(
key=API_LUX,
device_class=DEVICE_CLASS_ILLUMINANCE,
native_unit_of_measurement=LIGHT_LUX,
name="Illuminance",
unique_id_tag="illuminance",
),
AwairSensorEntityDescription(
key=API_SPL_A,
icon="mdi:ear-hearing",
native_unit_of_measurement=SOUND_PRESSURE_WEIGHTED_DBA,
name="Sound level",
unique_id_tag="sound_level",
),
AwairSensorEntityDescription(
key=API_VOC,
icon="mdi:cloud",
native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
name="Volatile organic compounds",
unique_id_tag="VOC", # matches legacy format
),
AwairSensorEntityDescription(
key=API_TEMP,
device_class=DEVICE_CLASS_TEMPERATURE,
native_unit_of_measurement=TEMP_CELSIUS,
name="Temperature",
unique_id_tag="TEMP", # matches legacy format
),
AwairSensorEntityDescription(
key=API_CO2,
device_class=DEVICE_CLASS_CO2,
icon="mdi:cloud",
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
name="Carbon dioxide",
unique_id_tag="CO2", # matches legacy format
),
)
SENSOR_TYPES_DUST: tuple[AwairSensorEntityDescription, ...] = (
AwairSensorEntityDescription(
key=API_PM25,
icon="mdi:blur",
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
name="PM2.5",
unique_id_tag="PM25", # matches legacy format
),
AwairSensorEntityDescription(
key=API_PM10,
icon="mdi:blur",
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
name="PM10",
unique_id_tag="PM10", # matches legacy format
),
)
SENSOR_TYPES = {
API_SCORE: {
ATTR_DEVICE_CLASS: None,
ATTR_ICON: "mdi:blur",
ATTR_UNIT: PERCENTAGE,
ATTR_LABEL: "Awair score",
ATTR_UNIQUE_ID: "score", # matches legacy format
},
API_HUMID: {
ATTR_DEVICE_CLASS: DEVICE_CLASS_HUMIDITY,
ATTR_ICON: None,
ATTR_UNIT: PERCENTAGE,
ATTR_LABEL: "Humidity",
ATTR_UNIQUE_ID: "HUMID", # matches legacy format
},
API_LUX: {
ATTR_DEVICE_CLASS: DEVICE_CLASS_ILLUMINANCE,
ATTR_ICON: None,
ATTR_UNIT: LIGHT_LUX,
ATTR_LABEL: "Illuminance",
ATTR_UNIQUE_ID: "illuminance",
},
API_SPL_A: {
ATTR_DEVICE_CLASS: None,
ATTR_ICON: "mdi:ear-hearing",
ATTR_UNIT: SOUND_PRESSURE_WEIGHTED_DBA,
ATTR_LABEL: "Sound level",
ATTR_UNIQUE_ID: "sound_level",
},
API_VOC: {
ATTR_DEVICE_CLASS: None,
ATTR_ICON: "mdi:cloud",
ATTR_UNIT: CONCENTRATION_PARTS_PER_BILLION,
ATTR_LABEL: "Volatile organic compounds",
ATTR_UNIQUE_ID: "VOC", # matches legacy format
},
API_TEMP: {
ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE,
ATTR_ICON: None,
ATTR_UNIT: TEMP_CELSIUS,
ATTR_LABEL: "Temperature",
ATTR_UNIQUE_ID: "TEMP", # matches legacy format
},
API_PM25: {
ATTR_DEVICE_CLASS: None,
ATTR_ICON: "mdi:blur",
ATTR_UNIT: CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
ATTR_LABEL: "PM2.5",
ATTR_UNIQUE_ID: "PM25", # matches legacy format
},
API_PM10: {
ATTR_DEVICE_CLASS: None,
ATTR_ICON: "mdi:blur",
ATTR_UNIT: CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
ATTR_LABEL: "PM10",
ATTR_UNIQUE_ID: "PM10", # matches legacy format
},
API_CO2: {
ATTR_DEVICE_CLASS: DEVICE_CLASS_CO2,
ATTR_ICON: "mdi:cloud",
ATTR_UNIT: CONCENTRATION_PARTS_PER_MILLION,
ATTR_LABEL: "Carbon dioxide",
ATTR_UNIQUE_ID: "CO2", # matches legacy format
},
}
@dataclass

View File

@@ -7,7 +7,7 @@ import voluptuous as vol
from homeassistant.components.awair import AwairDataUpdateCoordinator, AwairResult
from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity
from homeassistant.config_entries import SOURCE_IMPORT
from homeassistant.const import ATTR_ATTRIBUTION, CONF_ACCESS_TOKEN
from homeassistant.const import ATTR_ATTRIBUTION, ATTR_DEVICE_CLASS, CONF_ACCESS_TOKEN
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr
import homeassistant.helpers.config_validation as cv
@@ -22,14 +22,15 @@ from .const import (
API_SCORE,
API_TEMP,
API_VOC,
ATTR_ICON,
ATTR_LABEL,
ATTR_UNIQUE_ID,
ATTR_UNIT,
ATTRIBUTION,
DOMAIN,
DUST_ALIASES,
LOGGER,
SENSOR_TYPE_SCORE,
SENSOR_TYPES,
SENSOR_TYPES_DUST,
AwairSensorEntityDescription,
)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
@@ -59,20 +60,16 @@ async def async_setup_entry(
):
"""Set up Awair sensor entity based on a config entry."""
coordinator = hass.data[DOMAIN][config_entry.entry_id]
entities = []
sensors = []
data: list[AwairResult] = coordinator.data.values()
for result in data:
if result.air_data:
entities.append(AwairSensor(result.device, coordinator, SENSOR_TYPE_SCORE))
sensors.append(AwairSensor(API_SCORE, result.device, coordinator))
device_sensors = result.air_data.sensors.keys()
entities.extend(
[
AwairSensor(result.device, coordinator, description)
for description in (*SENSOR_TYPES, *SENSOR_TYPES_DUST)
if description.key in device_sensors
]
)
for sensor in device_sensors:
if sensor in SENSOR_TYPES:
sensors.append(AwairSensor(sensor, result.device, coordinator))
# The "DUST" sensor for Awair is a combo pm2.5/pm10 sensor only
# present on first-gen devices in lieu of separate pm2.5/pm10 sensors.
@@ -81,53 +78,45 @@ async def async_setup_entry(
# that data - because we can't really tell what kind of particles the
# "DUST" sensor actually detected. However, it's still useful data.
if API_DUST in device_sensors:
entities.extend(
[
AwairSensor(result.device, coordinator, description)
for description in SENSOR_TYPES_DUST
]
)
for alias_kind in DUST_ALIASES:
sensors.append(AwairSensor(alias_kind, result.device, coordinator))
async_add_entities(entities)
async_add_entities(sensors)
class AwairSensor(CoordinatorEntity, SensorEntity):
"""Defines an Awair sensor entity."""
entity_description: AwairSensorEntityDescription
def __init__(
self,
kind: str,
device: AwairDevice,
coordinator: AwairDataUpdateCoordinator,
description: AwairSensorEntityDescription,
) -> None:
"""Set up an individual AwairSensor."""
super().__init__(coordinator)
self.entity_description = description
self._kind = kind
self._device = device
@property
def name(self) -> str | None:
def name(self) -> str:
"""Return the name of the sensor."""
name = SENSOR_TYPES[self._kind][ATTR_LABEL]
if self._device.name:
return f"{self._device.name} {self.entity_description.name}"
name = f"{self._device.name} {name}"
return self.entity_description.name
return name
@property
def unique_id(self) -> str:
"""Return the uuid as the unique_id."""
unique_id_tag = self.entity_description.unique_id_tag
unique_id_tag = SENSOR_TYPES[self._kind][ATTR_UNIQUE_ID]
# This integration used to create a sensor that was labelled as a "PM2.5"
# sensor for first-gen Awair devices, but its unique_id reflected the truth:
# under the hood, it was a "DUST" sensor. So we preserve that specific unique_id
# for users with first-gen devices that are upgrading.
if (
self.entity_description.key == API_PM25
and API_DUST in self._air_data.sensors
):
if self._kind == API_PM25 and API_DUST in self._air_data.sensors:
unique_id_tag = "DUST"
return f"{self._device.uuid}_{unique_id_tag}"
@@ -138,17 +127,16 @@ class AwairSensor(CoordinatorEntity, SensorEntity):
# If the last update was successful...
if self.coordinator.last_update_success and self._air_data:
# and the results included our sensor type...
sensor_type = self.entity_description.key
if sensor_type in self._air_data.sensors:
if self._kind in self._air_data.sensors:
# then we are available.
return True
# or, we're a dust alias
if sensor_type in DUST_ALIASES and API_DUST in self._air_data.sensors:
if self._kind in DUST_ALIASES and API_DUST in self._air_data.sensors:
return True
# or we are API_SCORE
if sensor_type == API_SCORE:
if self._kind == API_SCORE:
# then we are available.
return True
@@ -159,24 +147,38 @@ class AwairSensor(CoordinatorEntity, SensorEntity):
def native_value(self) -> float:
"""Return the state, rounding off to reasonable values."""
state: float
sensor_type = self.entity_description.key
# Special-case for "SCORE", which we treat as the AQI
if sensor_type == API_SCORE:
if self._kind == API_SCORE:
state = self._air_data.score
elif sensor_type in DUST_ALIASES and API_DUST in self._air_data.sensors:
elif self._kind in DUST_ALIASES and API_DUST in self._air_data.sensors:
state = self._air_data.sensors.dust
else:
state = self._air_data.sensors[sensor_type]
state = self._air_data.sensors[self._kind]
if sensor_type in {API_VOC, API_SCORE}:
if self._kind == API_VOC or self._kind == API_SCORE:
return round(state)
if sensor_type == API_TEMP:
if self._kind == API_TEMP:
return round(state, 1)
return round(state, 2)
@property
def icon(self) -> str:
"""Return the icon."""
return SENSOR_TYPES[self._kind][ATTR_ICON]
@property
def device_class(self) -> str:
"""Return the device_class."""
return SENSOR_TYPES[self._kind][ATTR_DEVICE_CLASS]
@property
def native_unit_of_measurement(self) -> str:
"""Return the unit the value is expressed in."""
return SENSOR_TYPES[self._kind][ATTR_UNIT]
@property
def extra_state_attributes(self) -> dict:
"""Return the Awair Index alongside state attributes.
@@ -199,11 +201,10 @@ class AwairSensor(CoordinatorEntity, SensorEntity):
https://docs.developer.getawair.com/?version=latest#awair-score-and-index
"""
sensor_type = self.entity_description.key
attrs = {ATTR_ATTRIBUTION: ATTRIBUTION}
if sensor_type in self._air_data.indices:
attrs["awair_index"] = abs(self._air_data.indices[sensor_type])
elif sensor_type in DUST_ALIASES and API_DUST in self._air_data.indices:
if self._kind in self._air_data.indices:
attrs["awair_index"] = abs(self._air_data.indices[self._kind])
elif self._kind in DUST_ALIASES and API_DUST in self._air_data.indices:
attrs["awair_index"] = abs(self._air_data.indices.dust)
return attrs

View File

@@ -3,11 +3,11 @@
"abort": {
"already_configured": "Le compte est d\u00e9j\u00e0 configur\u00e9",
"no_devices_found": "Aucun appareil trouv\u00e9 sur le r\u00e9seau",
"reauth_successful": "La r\u00e9-authentification a r\u00e9ussi"
"reauth_successful": "Jeton d'acc\u00e8s mis \u00e0 jour avec succ\u00e8s"
},
"error": {
"invalid_access_token": "Jeton d'acc\u00e8s non valide",
"unknown": "Erreur inattendue"
"unknown": "Erreur d'API Awair inconnue."
},
"step": {
"reauth": {

View File

@@ -7,7 +7,7 @@
},
"error": {
"already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9",
"already_in_progress": "La configuration est d\u00e9j\u00e0 en cours",
"already_in_progress": "Le flux de configuration de l'appareil est d\u00e9j\u00e0 en cours.",
"cannot_connect": "\u00c9chec de connexion",
"invalid_auth": "Authentification invalide"
},
@@ -15,7 +15,7 @@
"step": {
"user": {
"data": {
"host": "H\u00f4te",
"host": "Nom d'h\u00f4te ou adresse IP",
"password": "Mot de passe",
"port": "Port",
"username": "Nom d'utilisateur"

View File

@@ -2,7 +2,7 @@
"config": {
"abort": {
"already_configured": "Le compte est d\u00e9j\u00e0 configur\u00e9",
"reauth_successful": "La r\u00e9-authentification a r\u00e9ussi"
"reauth_successful": "Jeton d'acc\u00e8s mis \u00e0 jour avec succ\u00e8s"
},
"error": {
"cannot_connect": "\u00c9chec de connexion",

View File

@@ -1,14 +1,4 @@
{
"device_automation": {
"condition_type": {
"is_no_update": "{entity_name} \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03bd\u03b7\u03bc\u03b5\u03c1\u03c9\u03bc\u03ad\u03bd\u03bf",
"is_update": "{entity_name} \u03ad\u03c7\u03b5\u03b9 \u03bc\u03b9\u03b1 \u03b4\u03b9\u03b1\u03b8\u03ad\u03c3\u03b9\u03bc\u03b7 \u03b5\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b7"
},
"trigger_type": {
"no_update": "{entity_name} \u03b5\u03bd\u03b7\u03bc\u03b5\u03c1\u03ce\u03b8\u03b7\u03ba\u03b5",
"update": "{entity_name} \u03ad\u03bb\u03b1\u03b2\u03b5 \u03bc\u03b9\u03b1 \u03b4\u03b9\u03b1\u03b8\u03ad\u03c3\u03b9\u03bc\u03b7 \u03b5\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b7"
}
},
"state": {
"_": {
"off": "\u0391\u03bd\u03b5\u03bd\u03b5\u03c1\u03b3\u03cc\u03c2",
@@ -82,10 +72,6 @@
"off": "\u0394\u03b5\u03bd \u0395\u03bd\u03c4\u03bf\u03c0\u03af\u03c3\u03c4\u03b7\u03ba\u03b5",
"on": "\u0395\u03bd\u03c4\u03bf\u03c0\u03af\u03c3\u03c4\u03b7\u03ba\u03b5"
},
"update": {
"off": "\u03a0\u03bb\u03ae\u03c1\u03c9\u03c2 \u03b5\u03bd\u03b7\u03bc\u03b5\u03c1\u03c9\u03bc\u03ad\u03bd\u03bf",
"on": "\u0394\u03b9\u03b1\u03b8\u03ad\u03c3\u03b9\u03bc\u03b7 \u03b5\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b7"
},
"vibration": {
"off": "\u0394\u03b5\u03bd \u0395\u03bd\u03c4\u03bf\u03c0\u03af\u03c3\u03c4\u03b7\u03ba\u03b5",
"on": "\u0395\u03bd\u03c4\u03bf\u03c0\u03af\u03c3\u03c4\u03b7\u03ba\u03b5"

View File

@@ -178,10 +178,6 @@
"off": "No detectado",
"on": "Detectado"
},
"update": {
"off": "Actualizado",
"on": "Actualizaci\u00f3n disponible"
},
"vibration": {
"off": "No detectado",
"on": "Detectado"

View File

@@ -115,12 +115,12 @@
"on": "Connect\u00e9"
},
"door": {
"off": "Ferm\u00e9",
"on": "Ouvert"
"off": "Ferm\u00e9e",
"on": "Ouverte"
},
"garage_door": {
"off": "Ferm\u00e9",
"on": "Ouvert"
"off": "Ferm\u00e9e",
"on": "Ouverte"
},
"gas": {
"off": "Non d\u00e9tect\u00e9",
@@ -191,8 +191,8 @@
"on": "D\u00e9tect\u00e9e"
},
"window": {
"off": "Ferm\u00e9",
"on": "Ouvert"
"off": "Ferm\u00e9e",
"on": "Ouverte"
}
},
"title": "Capteur binaire"

View File

@@ -17,7 +17,7 @@
"is_no_problem": "sensor {entity_name} nie wykrywa problemu",
"is_no_smoke": "sensor {entity_name} nie wykrywa dymu",
"is_no_sound": "sensor {entity_name} nie wykrywa d\u017awi\u0119ku",
"is_no_update": "dla {entity_name} nie ma dost\u0119pnej aktualizacji",
"is_no_update": "{entity_name} jest aktualny(-a)",
"is_no_vibration": "sensor {entity_name} nie wykrywa wibracji",
"is_not_bat_low": "bateria {entity_name} nie jest roz\u0142adowana",
"is_not_cold": "sensor {entity_name} nie wykrywa zimna",
@@ -43,7 +43,7 @@
"is_smoke": "sensor {entity_name} wykrywa dym",
"is_sound": "sensor {entity_name} wykrywa d\u017awi\u0119k",
"is_unsafe": "sensor {entity_name} wykrywa zagro\u017cenie",
"is_update": "dla {entity_name} jest dost\u0119pna aktualizacja",
"is_update": "{entity_name} ma dost\u0119pn\u0105 aktualizacj\u0119",
"is_vibration": "sensor {entity_name} wykrywa wibracje"
},
"trigger_type": {
@@ -63,7 +63,7 @@
"no_problem": "sensor {entity_name} przestanie wykrywa\u0107 problem",
"no_smoke": "sensor {entity_name} przestanie wykrywa\u0107 dym",
"no_sound": "sensor {entity_name} przestanie wykrywa\u0107 d\u017awi\u0119k",
"no_update": "wykonano aktualizacj\u0119 dla {entity_name}",
"no_update": "{entity_name} zosta\u0142 zaktualizowany(-a)",
"no_vibration": "sensor {entity_name} przestanie wykrywa\u0107 wibracje",
"not_bat_low": "nast\u0105pi na\u0142adowanie baterii {entity_name}",
"not_cold": "sensor {entity_name} przestanie wykrywa\u0107 zimno",
@@ -183,8 +183,8 @@
"on": "wykryto"
},
"update": {
"off": "brak aktualizacji",
"on": "dost\u0119pna aktualizacja"
"off": "Aktualny(-a)",
"on": "Dost\u0119pna aktualizacja"
},
"vibration": {
"off": "brak",

View File

@@ -2,11 +2,11 @@
"config": {
"abort": {
"address_already_configured": "Un p\u00e9riph\u00e9rique BleBox est d\u00e9j\u00e0 configur\u00e9 \u00e0 {address}.",
"already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9"
"already_configured": "Ce p\u00e9riph\u00e9rique BleBox est d\u00e9j\u00e0 configur\u00e9."
},
"error": {
"cannot_connect": "\u00c9chec de connexion",
"unknown": "Erreur inattendue",
"cannot_connect": "Impossible de connecter le p\u00e9riph\u00e9rique BleBox. (V\u00e9rifiez les journaux pour les erreurs.)",
"unknown": "Erreur inconnue lors de la connexion au p\u00e9riph\u00e9rique BleBox. (V\u00e9rifiez les journaux pour les erreurs.)",
"unsupported_version": "L'appareil BleBox a un micrologiciel obsol\u00e8te. Veuillez d'abord le mettre \u00e0 jour."
},
"flow_title": "P\u00e9riph\u00e9rique Blebox: {name} ({host)}",

View File

@@ -1,7 +1,7 @@
{
"config": {
"abort": {
"already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9"
"already_configured": "P\u00e9riph\u00e9rique d\u00e9j\u00e0 configur\u00e9"
},
"error": {
"cannot_connect": "\u00c9chec de connexion",
@@ -20,7 +20,7 @@
"user": {
"data": {
"password": "Mot de passe",
"username": "Nom d'utilisateur"
"username": "Identifiant"
},
"title": "Connectez-vous avec un compte Blink"
}

View File

@@ -4,7 +4,7 @@
"already_configured": "Le compte est d\u00e9j\u00e0 configur\u00e9"
},
"error": {
"cannot_connect": "\u00c9chec de connexion",
"cannot_connect": "\u00c9chec \u00e0 la connexion",
"invalid_auth": "Authentification invalide"
},
"step": {

View File

@@ -4,7 +4,7 @@
"already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9"
},
"error": {
"cannot_connect": "\u00c9chec de connexion",
"cannot_connect": "Echec de connexion",
"invalid_auth": "Authentification invalide",
"old_firmware": "Ancien micrologiciel non pris en charge sur l'appareil Bond - veuillez mettre \u00e0 niveau avant de continuer",
"unknown": "Erreur inattendue"
@@ -19,7 +19,7 @@
},
"user": {
"data": {
"access_token": "Jeton d'acc\u00e8s",
"access_token": "Token d'acc\u00e8s",
"host": "H\u00f4te"
}
}

View File

@@ -2,8 +2,7 @@
"config": {
"error": {
"pairing_failed": "El emparejamiento ha fallado; compruebe que el Bosch Smart Home Controller est\u00e1 en modo de emparejamiento (el LED parpadea) y que su contrase\u00f1a es correcta.",
"session_error": "Error de sesi\u00f3n: La API devuelve un resultado no correcto.",
"unknown": "Error inesperado"
"session_error": "Error de sesi\u00f3n: La API devuelve un resultado no correcto."
},
"flow_title": "Bosch SHC: {name}",
"step": {
@@ -19,9 +18,6 @@
"description": "La integraci\u00f3n bosch_shc necesita volver a autentificar su cuenta"
},
"user": {
"data": {
"host": "Anfitri\u00f3n"
},
"description": "Configura tu Bosch Smart Home Controller para permitir la supervisi\u00f3n y el control con Home Assistant.",
"title": "Par\u00e1metros de autenticaci\u00f3n SHC"
}

View File

@@ -2,11 +2,11 @@
"config": {
"abort": {
"already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9",
"reauth_successful": "La r\u00e9-authentification a r\u00e9ussi"
"reauth_successful": "R\u00e9-authentification r\u00e9ussie"
},
"error": {
"cannot_connect": "\u00c9chec de connexion",
"invalid_auth": "Authentification invalide",
"invalid_auth": "Authentification incorrecte",
"pairing_failed": "L'appairage a \u00e9chou\u00e9\u00a0; veuillez v\u00e9rifier que le Bosch Smart Home Controller est en mode d'appairage (voyant clignotant) et que votre mot de passe est correct.",
"session_error": "Erreur de session\u00a0: l'API renvoie un r\u00e9sultat non-OK.",
"unknown": "Erreur inattendue"
@@ -23,7 +23,7 @@
},
"reauth_confirm": {
"description": "L'int\u00e9gration bosch_shc doit r\u00e9-authentifier votre compte",
"title": "R\u00e9-authentifier l'int\u00e9gration"
"title": "R\u00e9authentification de l'int\u00e9gration"
},
"user": {
"data": {

View File

@@ -1,12 +1,12 @@
{
"config": {
"abort": {
"already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9",
"already_configured": "Ce t\u00e9l\u00e9viseur est d\u00e9j\u00e0 configur\u00e9.",
"no_ip_control": "Le contr\u00f4le IP est d\u00e9sactiv\u00e9 sur votre t\u00e9l\u00e9viseur ou le t\u00e9l\u00e9viseur n'est pas pris en charge."
},
"error": {
"cannot_connect": "\u00c9chec de connexion",
"invalid_host": "Nom d'h\u00f4te ou adresse IP non valide",
"cannot_connect": "\u00c9chec de connexion, h\u00f4te ou code PIN non valide.",
"invalid_host": "Nom d'h\u00f4te ou adresse IP invalide.",
"unsupported_model": "Votre mod\u00e8le de t\u00e9l\u00e9viseur n'est pas pris en charge."
},
"step": {
@@ -19,7 +19,7 @@
},
"user": {
"data": {
"host": "H\u00f4te"
"host": "Nom d'h\u00f4te ou adresse IP"
},
"description": "Configurez l'int\u00e9gration du t\u00e9l\u00e9viseur Sony Bravia. Si vous rencontrez des probl\u00e8mes de configuration, rendez-vous sur: https://www.home-assistant.io/integrations/braviatv \n\n Assurez-vous que votre t\u00e9l\u00e9viseur est allum\u00e9.",
"title": "Sony Bravia TV"

View File

@@ -2,7 +2,7 @@
"config": {
"abort": {
"already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9",
"already_in_progress": "La configuration est d\u00e9j\u00e0 en cours",
"already_in_progress": "Il y a d\u00e9j\u00e0 un processus de configuration en cours pour cet appareil",
"cannot_connect": "\u00c9chec de connexion",
"invalid_host": "Nom d'h\u00f4te ou adresse IP non valide",
"not_supported": "Dispositif non pris en charge",

View File

@@ -1,7 +1,7 @@
{
"config": {
"abort": {
"already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9",
"already_configured": "Cette imprimante est d\u00e9j\u00e0 configur\u00e9e.",
"unsupported_model": "Ce mod\u00e8le d'imprimante n'est pas pris en charge."
},
"error": {
@@ -13,7 +13,7 @@
"step": {
"user": {
"data": {
"host": "H\u00f4te",
"host": "Nom d'h\u00f4te ou adresse IP",
"type": "Type d'imprimante"
},
"description": "Configurez l'int\u00e9gration de l'imprimante Brother. Si vous avez des probl\u00e8mes avec la configuration, allez \u00e0 : https://www.home-assistant.io/integrations/brother"

View File

@@ -10,7 +10,7 @@
"step": {
"user": {
"data": {
"host": "H\u00f4te",
"host": "Nom d'h\u00f4te ou adresse IP",
"passkey": "Cha\u00eene de cl\u00e9 d'acc\u00e8s",
"password": "Mot de passe",
"port": "Port",

View File

@@ -1,7 +1,7 @@
{
"config": {
"abort": {
"single_instance_allowed": "D\u00e9j\u00e0 configur\u00e9. Une seule configuration possible."
"single_instance_allowed": "Une seule configuration de Google Cast est n\u00e9cessaire."
},
"error": {
"invalid_known_hosts": "Les h\u00f4tes connus doivent \u00eatre une liste d'h\u00f4tes s\u00e9par\u00e9s par des virgules."
@@ -15,7 +15,7 @@
"title": "Google Cast"
},
"confirm": {
"description": "Voulez-vous commencer la configuration ?"
"description": "Voulez-vous configurer Google Cast?"
}
}
},

View File

@@ -3,7 +3,6 @@ from __future__ import annotations
from datetime import datetime, timedelta
import logging
from typing import Optional
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, CONF_PORT
@@ -45,7 +44,7 @@ async def async_unload_entry(hass, entry):
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
class CertExpiryDataUpdateCoordinator(DataUpdateCoordinator[Optional[datetime]]):
class CertExpiryDataUpdateCoordinator(DataUpdateCoordinator[datetime]):
"""Class to manage fetching Cert Expiry data from single endpoint."""
def __init__(self, hass, host, port):

View File

@@ -1,6 +1,4 @@
"""Config flow for the Cert Expiry platform."""
from __future__ import annotations
import logging
import voluptuous as vol
@@ -27,7 +25,7 @@ class CertexpiryConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
def __init__(self) -> None:
"""Initialize the config flow."""
self._errors: dict[str, str] = {}
self._errors = {}
async def _test_connection(self, user_input=None):
"""Test connection to the server and try to get the certificate."""

View File

@@ -1,7 +1,7 @@
{
"config": {
"abort": {
"already_configured": "Le service est d\u00e9j\u00e0 configur\u00e9",
"already_configured": "Cette combinaison h\u00f4te et port est d\u00e9j\u00e0 configur\u00e9e",
"import_failed": "\u00c9chec de l'importation \u00e0 partir de la configuration"
},
"error": {

View File

@@ -5,10 +5,7 @@ from typing import Any
import voluptuous as vol
from homeassistant.components.automation import (
AutomationActionType,
AutomationTriggerInfo,
)
from homeassistant.components.automation import AutomationActionType
from homeassistant.components.device_automation import DEVICE_TRIGGER_BASE_SCHEMA
from homeassistant.components.homeassistant.triggers import (
numeric_state as numeric_state_trigger,
@@ -115,7 +112,7 @@ async def async_attach_trigger(
hass: HomeAssistant,
config: ConfigType,
action: AutomationActionType,
automation_info: AutomationTriggerInfo,
automation_info: dict,
) -> CALLBACK_TYPE:
"""Attach a trigger."""
trigger_type = config[CONF_TYPE]

View File

@@ -228,7 +228,7 @@ async def async_setup(hass, config):
cloud.iot.register_on_connect(_on_connect)
await cloud.initialize()
await cloud.start()
await http_api.async_setup(hass)
account_link.async_setup(hass)

View File

@@ -4,10 +4,9 @@ import logging
from typing import Any
import aiohttp
from awesomeversion import AwesomeVersion
from hass_nabucasa import account_link
from homeassistant.const import __version__ as HA_VERSION
from homeassistant.const import MAJOR_VERSION, MINOR_VERSION, PATCH_VERSION
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import config_entry_oauth2_flow, event
@@ -17,8 +16,6 @@ DATA_SERVICES = "cloud_account_link_services"
CACHE_TIMEOUT = 3600
_LOGGER = logging.getLogger(__name__)
CURRENT_VERSION = AwesomeVersion(HA_VERSION)
@callback
def async_setup(hass: HomeAssistant):
@@ -33,12 +30,43 @@ async def async_provide_implementation(hass: HomeAssistant, domain: str):
services = await _get_services(hass)
for service in services:
if service["service"] == domain and CURRENT_VERSION >= service["min_version"]:
if service["service"] == domain and _is_older(service["min_version"]):
return CloudOAuth2Implementation(hass, domain)
return
@callback
def _is_older(version: str) -> bool:
"""Test if a version is older than the current HA version."""
version_parts = version.split(".")
if len(version_parts) != 3:
return False
try:
version_parts = [int(val) for val in version_parts]
except ValueError:
return False
patch_number_str = ""
for char in PATCH_VERSION:
if char.isnumeric():
patch_number_str += char
else:
break
try:
patch_number = int(patch_number_str)
except ValueError:
patch_number = 0
cur_version_parts = [MAJOR_VERSION, MINOR_VERSION, patch_number]
return version_parts <= cur_version_parts
async def _get_services(hass):
"""Get the available services."""
services = hass.data.get(DATA_SERVICES)

View File

@@ -108,8 +108,8 @@ class CloudClient(Interface):
return self._google_config
async def cloud_started(self) -> None:
"""When cloud is started."""
async def logged_in(self) -> None:
"""When user logs in."""
is_new_user = await self.prefs.async_set_username(self.cloud.username)
async def enable_alexa(_):
@@ -150,10 +150,7 @@ class CloudClient(Interface):
if tasks:
await asyncio.gather(*(task(None) for task in tasks))
async def cloud_stopped(self) -> None:
"""When the cloud is stopped."""
async def logout_cleanups(self) -> None:
async def cleanups(self) -> None:
"""Cleanup some stuff after logout."""
await self.prefs.async_set_username(None)

View File

@@ -62,7 +62,7 @@ class CloudGoogleConfig(AbstractConfig):
@property
def should_report_state(self):
"""Return if states should be proactively reported."""
return self.enabled and self._prefs.google_report_state
return self._cloud.is_logged_in and self._prefs.google_report_state
@property
def local_sdk_webhook_id(self):

View File

@@ -6,7 +6,7 @@ import logging
import aiohttp
import async_timeout
import attr
from hass_nabucasa import Cloud, auth, cloud_api, thingtalk
from hass_nabucasa import Cloud, auth, thingtalk
from hass_nabucasa.const import STATE_DISCONNECTED
from hass_nabucasa.voice import MAP_VOICE
import voluptuous as vol
@@ -24,6 +24,7 @@ from homeassistant.const import (
HTTP_BAD_GATEWAY,
HTTP_BAD_REQUEST,
HTTP_INTERNAL_SERVER_ERROR,
HTTP_OK,
HTTP_UNAUTHORIZED,
)
@@ -46,6 +47,30 @@ from .const import (
_LOGGER = logging.getLogger(__name__)
WS_TYPE_STATUS = "cloud/status"
SCHEMA_WS_STATUS = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend(
{vol.Required("type"): WS_TYPE_STATUS}
)
WS_TYPE_SUBSCRIPTION = "cloud/subscription"
SCHEMA_WS_SUBSCRIPTION = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend(
{vol.Required("type"): WS_TYPE_SUBSCRIPTION}
)
WS_TYPE_HOOK_CREATE = "cloud/cloudhook/create"
SCHEMA_WS_HOOK_CREATE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend(
{vol.Required("type"): WS_TYPE_HOOK_CREATE, vol.Required("webhook_id"): str}
)
WS_TYPE_HOOK_DELETE = "cloud/cloudhook/delete"
SCHEMA_WS_HOOK_DELETE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend(
{vol.Required("type"): WS_TYPE_HOOK_DELETE, vol.Required("webhook_id"): str}
)
_CLOUD_ERRORS = {
InvalidTrustedNetworks: (
HTTP_INTERNAL_SERVER_ERROR,
@@ -69,11 +94,17 @@ _CLOUD_ERRORS = {
async def async_setup(hass):
"""Initialize the HTTP API."""
async_register_command = hass.components.websocket_api.async_register_command
async_register_command(websocket_cloud_status)
async_register_command(websocket_subscription)
async_register_command(WS_TYPE_STATUS, websocket_cloud_status, SCHEMA_WS_STATUS)
async_register_command(
WS_TYPE_SUBSCRIPTION, websocket_subscription, SCHEMA_WS_SUBSCRIPTION
)
async_register_command(websocket_update_prefs)
async_register_command(websocket_hook_create)
async_register_command(websocket_hook_delete)
async_register_command(
WS_TYPE_HOOK_CREATE, websocket_hook_create, SCHEMA_WS_HOOK_CREATE
)
async_register_command(
WS_TYPE_HOOK_DELETE, websocket_hook_delete, SCHEMA_WS_HOOK_DELETE
)
async_register_command(websocket_remote_connect)
async_register_command(websocket_remote_disconnect)
@@ -280,7 +311,6 @@ class CloudForgotPasswordView(HomeAssistantView):
@websocket_api.async_response
@websocket_api.websocket_command({vol.Required("type"): "cloud/status"})
async def websocket_cloud_status(hass, connection, msg):
"""Handle request for account info.
@@ -314,19 +344,36 @@ def _require_cloud_login(handler):
@_require_cloud_login
@websocket_api.async_response
@websocket_api.websocket_command({vol.Required("type"): "cloud/subscription"})
async def websocket_subscription(hass, connection, msg):
"""Handle request for account info."""
cloud = hass.data[DOMAIN]
try:
with async_timeout.timeout(REQUEST_TIMEOUT):
data = await cloud_api.async_subscription_info(cloud)
except aiohttp.ClientError:
connection.send_error(
msg["id"], "request_failed", "Failed to request subscription"
with async_timeout.timeout(REQUEST_TIMEOUT):
response = await cloud.fetch_subscription_info()
if response.status != HTTP_OK:
connection.send_message(
websocket_api.error_message(
msg["id"], "request_failed", "Failed to request subscription"
)
)
else:
connection.send_result(msg["id"], data)
data = await response.json()
# Check if a user is subscribed but local info is outdated
# In that case, let's refresh and reconnect
if data.get("provider") and not cloud.is_connected:
_LOGGER.debug("Found disconnected account with valid subscriotion, connecting")
await cloud.auth.async_renew_access_token()
# Cancel reconnect in progress
if cloud.iot.state != STATE_DISCONNECTED:
await cloud.iot.disconnect()
hass.async_create_task(cloud.iot.connect())
connection.send_message(websocket_api.result_message(msg["id"], data))
@_require_cloud_login
@@ -382,12 +429,6 @@ async def websocket_update_prefs(hass, connection, msg):
@_require_cloud_login
@websocket_api.async_response
@_ws_handle_cloud_errors
@websocket_api.websocket_command(
{
vol.Required("type"): "cloud/cloudhook/create",
vol.Required("webhook_id"): str,
}
)
async def websocket_hook_create(hass, connection, msg):
"""Handle request for account info."""
cloud = hass.data[DOMAIN]
@@ -398,12 +439,6 @@ async def websocket_hook_create(hass, connection, msg):
@_require_cloud_login
@websocket_api.async_response
@_ws_handle_cloud_errors
@websocket_api.websocket_command(
{
vol.Required("type"): "cloud/cloudhook/delete",
vol.Required("webhook_id"): str,
}
)
async def websocket_hook_delete(hass, connection, msg):
"""Handle request for account info."""
cloud = hass.data[DOMAIN]

View File

@@ -2,7 +2,7 @@
"domain": "cloud",
"name": "Home Assistant Cloud",
"documentation": "https://www.home-assistant.io/integrations/cloud",
"requirements": ["hass-nabucasa==0.49.0"],
"requirements": ["hass-nabucasa==0.46.0"],
"dependencies": ["http", "webhook"],
"after_dependencies": ["google_assistant", "alexa"],
"codeowners": ["@home-assistant/cloud"],

View File

@@ -7,11 +7,10 @@
"relayer_connected": "Relayer Connected",
"remote_connected": "Remote Connected",
"remote_enabled": "Remote Enabled",
"remote_server": "Remote Server",
"alexa_enabled": "Alexa Enabled",
"google_enabled": "Google Enabled",
"logged_in": "Logged In",
"subscription_expiration": "Subscription Expiration"
}
}
}
}

View File

@@ -33,7 +33,6 @@ async def system_health_info(hass):
data["remote_connected"] = cloud.remote.is_connected
data["alexa_enabled"] = client.prefs.alexa_enabled
data["google_enabled"] = client.prefs.google_enabled
data["remote_server"] = cloud.remote.snitun_server
data["can_reach_cert_server"] = system_health.async_check_can_reach_url(
hass, cloud.acme_directory_server

View File

@@ -10,7 +10,6 @@
"relayer_connected": "Encaminador connectat",
"remote_connected": "Connexi\u00f3 remota establerta",
"remote_enabled": "Connexi\u00f3 remota activada",
"remote_server": "Servidor remot",
"subscription_expiration": "Caducitat de la subscripci\u00f3"
}
}

View File

@@ -10,7 +10,6 @@
"relayer_connected": "Relay Verbunden",
"remote_connected": "Remote verbunden",
"remote_enabled": "Remote aktiviert",
"remote_server": "Remote-Server",
"subscription_expiration": "Ablauf des Abonnements"
}
}

View File

@@ -1,7 +0,0 @@
{
"system_health": {
"info": {
"remote_server": "\u0391\u03c0\u03bf\u03bc\u03b1\u03ba\u03c1\u03c5\u03c3\u03bc\u03ad\u03bd\u03bf\u03c2 \u0394\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae\u03c2"
}
}
}

View File

@@ -10,7 +10,6 @@
"relayer_connected": "Relayer Connected",
"remote_connected": "Remote Connected",
"remote_enabled": "Remote Enabled",
"remote_server": "Remote Server",
"subscription_expiration": "Subscription Expiration"
}
}

View File

@@ -10,7 +10,6 @@
"relayer_connected": "Relayer conectado",
"remote_connected": "Remoto conectado",
"remote_enabled": "Remoto habilitado",
"remote_server": "Servidor remoto",
"subscription_expiration": "Caducidad de la suscripci\u00f3n"
}
}

View File

@@ -10,7 +10,6 @@
"relayer_connected": "Edastaja on \u00fchendatud",
"remote_connected": "Kaug\u00fchendus on loodud",
"remote_enabled": "Kaug\u00fchendus on lubatud",
"remote_server": "Kaugserver",
"subscription_expiration": "Tellimuse aegumine"
}
}

View File

@@ -3,8 +3,7 @@
"info": {
"alexa_enabled": "Alexa \u05de\u05d5\u05e4\u05e2\u05dc\u05ea",
"google_enabled": "Google \u05de\u05d5\u05e4\u05e2\u05dc",
"logged_in": "\u05de\u05d7\u05d5\u05d1\u05e8",
"remote_server": "\u05e9\u05e8\u05ea \u05de\u05e8\u05d5\u05d7\u05e7"
"logged_in": "\u05de\u05d7\u05d5\u05d1\u05e8"
}
}
}

View File

@@ -10,7 +10,6 @@
"relayer_connected": "K\u00f6zvet\u00edt\u0151 csatlakoztatva",
"remote_connected": "T\u00e1voli csatlakoz\u00e1s",
"remote_enabled": "T\u00e1voli hozz\u00e1f\u00e9r\u00e9s enged\u00e9lyezve",
"remote_server": "T\u00e1voli szerver",
"subscription_expiration": "El\u0151fizet\u00e9s lej\u00e1rata"
}
}

View File

@@ -10,7 +10,6 @@
"relayer_connected": "Relayer connesso",
"remote_connected": "Connesso in remoto",
"remote_enabled": "Remoto abilitato",
"remote_server": "Server remoto",
"subscription_expiration": "Scadenza abbonamento"
}
}

View File

@@ -10,7 +10,6 @@
"relayer_connected": "Relayer verbonden",
"remote_connected": "Op afstand verbonden",
"remote_enabled": "Op afstand ingeschakeld",
"remote_server": "Externe server",
"subscription_expiration": "Afloop abonnement"
}
}

View File

@@ -10,7 +10,6 @@
"relayer_connected": "Relayer tilkoblet",
"remote_connected": "Ekstern tilkobling",
"remote_enabled": "Ekstern aktivert",
"remote_server": "Ekstern server",
"subscription_expiration": "Abonnementets utl\u00f8p"
}
}

View File

@@ -10,7 +10,6 @@
"relayer_connected": "Relayer pod\u0142\u0105czony",
"remote_connected": "Zdalny dost\u0119p pod\u0142\u0105czony",
"remote_enabled": "Zdalny dost\u0119p w\u0142\u0105czony",
"remote_server": "Zdalny serwer",
"subscription_expiration": "Wyga\u015bni\u0119cie subskrypcji"
}
}

View File

@@ -10,7 +10,6 @@
"relayer_connected": "Relayer \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d",
"remote_connected": "\u0423\u0434\u0430\u043b\u0451\u043d\u043d\u044b\u0439 \u0434\u043e\u0441\u0442\u0443\u043f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d",
"remote_enabled": "\u0423\u0434\u0430\u043b\u0451\u043d\u043d\u044b\u0439 \u0434\u043e\u0441\u0442\u0443\u043f \u0430\u043a\u0442\u0438\u0432\u0438\u0440\u043e\u0432\u0430\u043d",
"remote_server": "\u0423\u0434\u0430\u043b\u0451\u043d\u043d\u044b\u0439 \u0441\u0435\u0440\u0432\u0435\u0440",
"subscription_expiration": "\u0421\u0440\u043e\u043a \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f \u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0438"
}
}

View File

@@ -1,7 +0,0 @@
{
"system_health": {
"info": {
"remote_server": "\u0e40\u0e0b\u0e34\u0e23\u0e4c\u0e1f\u0e40\u0e27\u0e2d\u0e23\u0e4c\u0e23\u0e30\u0e22\u0e30\u0e44\u0e01\u0e25"
}
}
}

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