Compare commits

..

111 Commits

Author SHA1 Message Date
Franck Nijhof 78222bd51c 2023.6.3 (#95119) 2023-06-23 19:08:57 +02:00
Franck Nijhof 9f6dab0643 Remove incompatible config schema for Fully Kiosk 2023-06-23 17:56:25 +02:00
Franck Nijhof 4cf9beccd8 Bumped version to 2023.6.3 2023-06-23 16:46:52 +02:00
Matthias Alphart 8f9425f09f Fix KNX device trigger passing info data (#95076) 2023-06-23 16:46:33 +02:00
Marcel van der Veldt 0fa954040e Fix removal of orphaned Matter devices (#95044) 2023-06-23 16:46:30 +02:00
Hmmbob e26b8e11d3 Fix goodwe midnight error (#95041) 2023-06-23 16:45:56 +02:00
Richard Kroegel ced6968e85 Bump bimmer_connected to 0.13.7 (#95017) 2023-06-23 16:45:16 +02:00
Raman Gupta 44e7243e25 Fix zwave_js device diagnostics dump (#94999)
* Fix zwave_js device diagnostics dump

* Update tests/components/zwave_js/test_diagnostics.py

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

* Update tests/components/zwave_js/test_diagnostics.py

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

* Update tests/components/zwave_js/test_diagnostics.py

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

* Update tests/components/zwave_js/test_diagnostics.py

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

* Update tests/components/zwave_js/test_diagnostics.py

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

* Update tests/components/zwave_js/test_diagnostics.py

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

* improve test

---------

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2023-06-23 16:43:34 +02:00
Marcel van der Veldt bbbc9f646f Bump Matter Server to 3.5.1: some small fixes and stability improvements (#94985) 2023-06-23 16:43:31 +02:00
epenet cda784c969 Add error handling to hassio issues (#94951) 2023-06-23 16:43:28 +02:00
Quentame 34ef89b16b Fix Meteo France blocked config entry when weather alert API fails (#94911)
* Fix: do not block config entry when weather alert API fails

* PR review
2023-06-23 16:43:24 +02:00
Allen Porter f8cfaa6147 Bump ical to 4.5.4 (#94894) 2023-06-23 16:43:21 +02:00
Álvaro Fernández Rojas 5da5522481 Update aioairzone to v0.6.4 (#94873) 2023-06-23 16:43:18 +02:00
Ernst Klamer cee8004641 Bump bthome to 2.12.0 (#94822) 2023-06-23 16:43:15 +02:00
J. Nick Koston e1751647f4 Bump HAP-python to 4.7.0 (#94812) 2023-06-23 16:43:11 +02:00
Austin Mroczek f33d671a5d Fix Totalconnect BinarySensorDeviceClass logic (#94772)
* handle temperature

* test for temperature

* test for unknown
2023-06-23 16:43:07 +02:00
Jan Čermák 254b1fd314 Fix warning from rapt_ble caused by payload version 2 (#94718) 2023-06-23 16:43:04 +02:00
Raman Gupta 89c6494056 Fix zwave_js trigger event reattach logic (#94702) 2023-06-23 16:43:01 +02:00
Dirk Sarodnick b52cfd3324 Fix bluetooth tracker asyncio usage (#94695)
* fix for asyncio usage

fixes the error "Passing coroutines is forbidden, use tasks explicitly", caused by passing an async function into asyncio.wait directly instead of creating a task for it.

* removes unnecessary default param

* corrects formatting for black
2023-06-23 16:42:55 +02:00
Joost Lekkerkerker 6329f6bc07 Add strings for YouTube reauthentication (#94655) 2023-06-23 16:38:29 +02:00
Joost Lekkerkerker 57dd62e7d6 Make YouTube select lower quality thumbnails (#94652)
* Make YouTube select lower quality thumbnails

* Make YouTube select lower quality thumbnails

* Make YouTube select lower quality thumbnails

* Make YouTube select lower quality thumbnails

* Add tests

* Add tests

* Add tests

* Add tests

* Add tests
2023-06-23 16:38:21 +02:00
Dominik 203820d836 Fix glances raid plugin data (#94597)
Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
2023-06-23 16:38:14 +02:00
Dominik e1c486fc4a Bump minimum typing_extensions to 4.6.3 (#94587) 2023-06-23 16:37:27 +02:00
Alistair Tudor 78bbec0a6e Fix unit for Habitica text sensors (#94550) 2023-06-23 16:35:33 +02:00
Tom Harris ffe35c73b6 Handle Insteon events correctly (#94549)
Make events generalized
2023-06-23 16:35:30 +02:00
Joost Lekkerkerker d2385f97a7 Handle LastFM unavailable (#94456) 2023-06-23 16:35:27 +02:00
Kim Frellsen bd0b8dc0bc Fortios device tracker updates (#92331)
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
Co-authored-by: Erik Montnemery <erik@montnemery.com>
2023-06-23 16:35:24 +02:00
Mike Heath 3f936993a9 Register Fully Kiosk services regardless of setup result (#88647)
* Register services at integration level

If HA is unable to connect to Fully Kiosk, the services don't get
registered. This can cause repair to create notifications saying
that the 'fully_kiosk.load_url' service is unknown.

Fixes #85444

* Validate config entry is loaded

* Refactor service invocation

Raises `HomeAssistantError` when the user provides an device id that is
not in the device registry or a device that is not a Fully Kiosk
device. If the device's config entry is not loaded, a warning is
logged.

* Update homeassistant/components/fully_kiosk/services.py

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

* Assert HomeAssistantError when integration unloaded

* Remove unused import

* Set CONFIG_SCHEMA

* Update homeassistant/components/fully_kiosk/__init__.py

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

* Add test for non fkb devices targets in service calls

* Apply suggestions from code review

---------

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2023-06-23 16:35:19 +02:00
Paulus Schoutsen e5c5790768 2023.6.2 (#94621) 2023-06-15 00:19:30 -04:00
Paulus Schoutsen 905bdd0dd5 Bumped version to 2023.6.2 2023-06-14 21:18:40 -04:00
Erik Montnemery 9cbcfca2cd Improve multipan debug logging (#94580) 2023-06-14 21:18:35 -04:00
Chris Talkington e6b8e4fd48 Fix failed recovery in ipp (#94573) 2023-06-14 21:18:34 -04:00
Chris Talkington 8f437c5833 Fix failed recovery in roku (#94572) 2023-06-14 21:18:33 -04:00
Ian Foster d28d909114 Fix keyboard_remote for python 3.11 (#94570)
* started work to update keyboard_remote to work with python 3.11

* updated function names

* all checks pass

* fixed asyncio for python 3.11

* cleanup

* Update homeassistant/components/keyboard_remote/__init__.py

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

* Update __init__.py

added:
from __future__ import annotations

* Fix typing

---------

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2023-06-14 21:18:32 -04:00
Maciej Bieniek f67577ebe0 Catch InvalidAuthError in shutdown() method for Shelly gen2 devices (#94563)
* Catch InvalidAuthError in shutdown() method

* Add test

* Revert unwanted change in tests
2023-06-14 21:18:31 -04:00
Franck Nijhof 70d33129d4 Update Home Assistant base image to 2023.06.0 (#94556) 2023-06-14 21:18:29 -04:00
Aaron Bach a63ce8100e Bump regenmaschine to 2023.06.0 (#94554) 2023-06-14 21:18:28 -04:00
J. Nick Koston d557c6e43e Bump yalexs-ble to 2.1.18 (#94547) 2023-06-14 21:18:27 -04:00
Raman Gupta fd0404bb4a Fix entity and device selector TypedDict's (#94510) 2023-06-14 21:18:26 -04:00
Chris Phillips 576cf52573 Bump russound_rio to 1.0.0 (#94500) 2023-06-14 21:17:51 -04:00
mover85 e83f0bb7a5 Revert "Bump pydaikin 2.9.1 (#93635)" (#94469)
Revert to pydaikin 2.9.0
2023-06-14 21:17:08 -04:00
Raman Gupta fa8e952324 Set default value for endpoint in zwave device automations (#94445)
* Set default value for endpoint in zwave device automations

* add test case
2023-06-14 21:17:07 -04:00
G Johansson 25a4679266 Fix reload service in Command Line (#94436)
* Fix reload in Command Line

* Add read new yaml
2023-06-14 21:17:06 -04:00
G Johansson f5aa4f5866 Fix manual update for Command Line (#94433)
Manual update command line
2023-06-14 21:17:04 -04:00
Yuxin Wang 0083649e43 Add unit inference for Amps and VA in APCUPSD integration (#94431)
* Add unit inference for Amps and VA

* Rename `init_integration` to `async_init_integration` for better consistency with HA naming style
2023-06-14 21:17:03 -04:00
Sander 2505de35c9 Fix: Xiaomi Miio Fan, delay off countdown unit conversion (#94428) 2023-06-14 21:17:02 -04:00
Christopher Bailey 238eebb0b6 Bump unifiprotect to 4.10.3 (#94416)
* Bump unifiprotect to 4.10.3

* Reqs
2023-06-14 21:17:01 -04:00
Matthias Alphart 4cb30e69ac Update knx-frontend to 2023.6.9.195839 (#94404) 2023-06-14 21:16:19 -04:00
Joost Lekkerkerker ac00977e57 Abort youtube configuration if user has no channel (#94402)
* Abort configuration if user has no channel

* Clean up

---------

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2023-06-14 21:15:21 -04:00
jasonkuster b2db849798 Fix ZHA binding api to actually return responses (#94388) 2023-06-14 21:15:20 -04:00
Raman Gupta 2c7a176580 Re-add event listeners after Z-Wave server disconnection (#94383)
* Re-add event listeners after Z-Wave server disconnection

* switch order

* Add tests
2023-06-14 21:15:19 -04:00
Matthias Alphart 4dbc408696 Update xknxproject to 3.1.1 (#94375) 2023-06-14 21:14:42 -04:00
Sebastian Muszynski 582fd11a70 Fix deprecated asyncio.wait use with coroutines (#94371) 2023-06-14 21:13:25 -04:00
Jan Bouwhuis 96cb5ff8b0 Fix dep noaa-coops for noaa_tides (#94370)
Bump noaa-coops to 0.1.9
2023-06-14 21:13:23 -04:00
Jafar Atili 6029e23ab7 fix: electrasmart - cast temperature to int in set_temperature (#94368)
fix: cast temperature to int
2023-06-14 21:13:22 -04:00
Jonathan Keljo 3434d74993 Upgrade sisyphus-control to 3.1.3 (#94310) 2023-06-14 21:13:21 -04:00
Nathan Spencer e091793b6c Bump pylitterbot to 2023.4.2 (#94301) 2023-06-14 21:13:20 -04:00
Glenn Waters 9c8444da0e Bump elkm1-lib to 2.2.5 (#94296)
Co-authored-by: J. Nick Koston <nick@koston.org>
2023-06-14 21:13:19 -04:00
Tom Harris 427f0f4bee Fix issue with Insteon linked devices maintaining current state (#94286)
* Bump pyinsteon

* Update tests
2023-06-14 21:13:18 -04:00
FFT 95528f875e Change pyoppleio to pyoppleio-legacy (#88050)
* Change pyoppleio to pyoppleio-310 (#75268)

* [m] change opple component's dependency to a new working one
2023-06-14 21:13:17 -04:00
Paulus Schoutsen a5f86bff45 2023.6.1 (#94288) 2023-06-08 14:57:37 -04:00
Paulus Schoutsen d991970754 Bumped version to 2023.6.1 2023-06-08 13:39:33 -04:00
Tom Harris d745b44180 Fix Insteon startup for users with X10 devices (#94277) 2023-06-08 13:39:10 -04:00
Tom Harris 602fcd6b1b Restructure Insteon start-up (#92818)
* Restructure startup

* Code review

* Further typing

* Fix circular import
2023-06-08 13:39:09 -04:00
Franck Nijhof b39b0a960e Fix repair issue about no yaml for config entries (#94271) 2023-06-08 13:35:08 -04:00
Paulus Schoutsen 40bb796f03 Fix default value when logger used (#94269) 2023-06-08 13:28:54 -04:00
Christopher Bailey 2801ba6cad Bump unifiprotect to 4.10.2 (#94263) 2023-06-08 13:28:52 -04:00
Paul Bottein 5da0ef36ea Update frontend to 20230608.0 (#94256) 2023-06-08 13:28:51 -04:00
Joost Lekkerkerker d861292900 Retrieve friends in an async manner in Lastfm (#94255) 2023-06-08 13:28:50 -04:00
Jc2k a3fda43c64 Bump aiohomekit to 2.6.5 (fixes python 3.11 regression) (#94245) 2023-06-08 13:28:49 -04:00
Joost Lekkerkerker 8705a26a1a Catch exception when user has no lastfm friends (#94235) 2023-06-08 13:28:48 -04:00
jan iversen 2b1c45c28c Solve wrong return code from modbus. (#94234) 2023-06-08 13:28:47 -04:00
Jan Bouwhuis 0cf3825183 Fix imap crash on email without subject (#94230) 2023-06-08 13:28:46 -04:00
Kostas Chatzikokolakis 413e1c97d7 Bump pulsectl to 23.5.2 (#94227) 2023-06-08 13:28:45 -04:00
Joost Lekkerkerker 3b27a3aabf Bump python-opensky to 0.0.9 (#94224) 2023-06-08 13:28:44 -04:00
Joost Lekkerkerker 4509e13ceb Bump python-opensky (#93916) 2023-06-08 13:28:43 -04:00
Álvaro Fernández Rojas 33bf8c600b Update aioairzone-cloud to v0.1.8 (#94223) 2023-06-08 13:27:46 -04:00
Jan-Philipp Benecke b508875f17 Set httpx log level to warning (#94217)
Set log level of httpx to warning
2023-06-08 13:27:45 -04:00
Allen Porter ac963a2b6e Require pydantic 1.10.8 or higher (#94208)
* Requied pydantic 1.10.9 or higher

* Simplify constraint to 2.0

* Drop constraint by one patch release to 1.10.8 or higher

* Add package constraints to gen requirements script
2023-06-08 13:27:43 -04:00
James Connor 13029cf26f Fix ambiclimate for Python 3.11 (#94203)
Fix ambiclimate python 3.11 break
2023-06-08 13:27:42 -04:00
Paulus Schoutsen f39a6b96ff Rename Local Media to My Media (#94201)
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
Co-authored-by: Franck Nijhof <git@frenck.dev>
2023-06-08 13:27:41 -04:00
Mick Vleeshouwer c6a17d6832 Bump pyoverkiz to 1.8.0 (#94176) 2023-06-08 13:27:40 -04:00
Joost Lekkerkerker 74c0552a12 Fix Abode unit of measurement (#94168)
Change unit of measurement to HA const
2023-06-08 13:27:38 -04:00
Justin Vanderhooft f24b514c9a Bump melnor-bluetooth to fix a timezone issue (#94159) 2023-06-08 13:27:37 -04:00
Erik Montnemery e1c47fdb61 Fix OTBR reset (#94157) 2023-06-08 13:27:36 -04:00
j4n-e4t 93baf24394 Add error handling to input_select integration (#93940) 2023-06-08 12:27:23 -04:00
Franck Nijhof a4e236d0b9 2023.6.0 (#94158) 2023-06-07 15:39:47 +02:00
Franck Nijhof 421fa5b035 Bumped version to 2023.6.0 2023-06-07 13:49:03 +02:00
Erik Montnemery 3d3fecbd23 Disable google assistant local control of climate entities (#94153) 2023-06-07 13:48:20 +02:00
Erik Montnemery 468be632fd Add debug logs to cloud migration (#94151) 2023-06-07 13:48:17 +02:00
Bram Kragten 74ccdcda68 Update frontend to 20230607.0 (#94150) 2023-06-07 13:48:14 +02:00
Erik Montnemery 5cc61acfb2 Fix migration of Google Assistant cloud settings (#94148) 2023-06-07 13:48:11 +02:00
Christopher Bailey 02d55a8e49 Bump unifiprotect to 4.10.1 (#94141) 2023-06-07 13:48:06 +02:00
Paulus Schoutsen f4e3ef6b51 Bumped version to 2023.6.0b6 2023-06-06 22:00:28 -04:00
Paulus Schoutsen 7740539df0 Bump waqiasync to 1.1.0 (#94136) 2023-06-06 22:00:20 -04:00
Christopher Bailey b077bf9b86 Fix multiple smart detects firing at once for UniFi Protect (#94133)
* Fix multiple smart detects firing at once

* Tweak

* Clean up logging. Linting

* Linting
2023-06-06 22:00:19 -04:00
Joakim Plate 23f2898836 Correct zha device classes for voc and pm25 (#94130)
Correct zha device classes
2023-06-06 22:00:18 -04:00
Shay Levy e6638ca356 Remove goalfeed integration (#94129) 2023-06-06 21:59:32 -04:00
Jean-François Roy 93d52d8835 Bump aiobafi6 to 0.8.2 (#94125) 2023-06-06 21:58:09 -04:00
puddly 26e08abb9a Revert "Increase Zigbee command retries (#93877)" (#94123) 2023-06-06 21:58:08 -04:00
J. Nick Koston 6a573b507e Remove mark_read service from persistent_notification (#94122)
* Remove mark_read from persistent_notification

Nothing on the frontend uses this, and the service is not documented

There is not much point in keeping this as the notifications
are no longer stored in the state machine

* adjust

* adjust
2023-06-06 21:58:07 -04:00
Bram Kragten 2b39550e55 Update frontend to 20230606.0 (#94119) 2023-06-06 21:58:06 -04:00
J. Nick Koston 0e50baf007 Verify persistant notifications can be dismissed by the id they are created with (#94112) 2023-06-06 21:58:05 -04:00
Luke 286de1f051 Bump python-roborock to 23.4 (#94111)
* bump to 23.0

* bump to 23.4
2023-06-06 21:58:04 -04:00
Luke 3e23996247 Bump Roborock to 0.21.0 (#94035)
bump to 21.0
2023-06-06 21:58:03 -04:00
Álvaro Fernández Rojas 7a658117bb Update aioairzone to v0.6.3 and fix issue with latest firmware update (#94100) 2023-06-06 21:56:36 -04:00
Luke 49388eab3a Add diagnostics to Roborock (#94099)
* Add diagnostics

* Update homeassistant/components/roborock/models.py

Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>

* adds snapshot

---------

Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
2023-06-06 21:56:35 -04:00
Justin Vanderhooft e6fcc6b73c fix: Bump melnor-bluetooth to fix deadlock (#94098) 2023-06-06 21:56:33 -04:00
Robert Svensson e00012289d Bump aiounifi to v48 - Fix fail to initialise due to board_rev not exist (#94093) 2023-06-06 21:56:32 -04:00
Luke f373f1abd5 Add missing translation keys for Roborock mop intensity (#94088) 2023-06-06 21:56:31 -04:00
puddly 2c43672a8a Include port info in the ZHA websocket settings response (#93934) 2023-06-06 21:56:30 -04:00
186 changed files with 2676 additions and 872 deletions
-1
View File
@@ -420,7 +420,6 @@ omit =
homeassistant/components/gitlab_ci/sensor.py
homeassistant/components/gitter/sensor.py
homeassistant/components/glances/sensor.py
homeassistant/components/goalfeed/*
homeassistant/components/goodwe/__init__.py
homeassistant/components/goodwe/button.py
homeassistant/components/goodwe/coordinator.py
+5 -5
View File
@@ -1,11 +1,11 @@
image: homeassistant/{arch}-homeassistant
shadow_repository: ghcr.io/home-assistant
build_from:
aarch64: ghcr.io/home-assistant/aarch64-homeassistant-base:2023.05.0
armhf: ghcr.io/home-assistant/armhf-homeassistant-base:2023.05.0
armv7: ghcr.io/home-assistant/armv7-homeassistant-base:2023.05.0
amd64: ghcr.io/home-assistant/amd64-homeassistant-base:2023.05.0
i386: ghcr.io/home-assistant/i386-homeassistant-base:2023.05.0
aarch64: ghcr.io/home-assistant/aarch64-homeassistant-base:2023.06.0
armhf: ghcr.io/home-assistant/armhf-homeassistant-base:2023.06.0
armv7: ghcr.io/home-assistant/armv7-homeassistant-base:2023.06.0
amd64: ghcr.io/home-assistant/amd64-homeassistant-base:2023.06.0
i386: ghcr.io/home-assistant/i386-homeassistant-base:2023.06.0
codenotary:
signer: notary@home-assistant.io
base_image: notary@home-assistant.io
+1
View File
@@ -391,6 +391,7 @@ def async_enable_logging(
logging.getLogger("requests").setLevel(logging.WARNING)
logging.getLogger("urllib3").setLevel(logging.WARNING)
logging.getLogger("aiohttp.access").setLevel(logging.WARNING)
logging.getLogger("httpx").setLevel(logging.WARNING)
sys.excepthook = lambda *args: logging.getLogger(None).exception(
"Uncaught exception", exc_info=args # type: ignore[arg-type]
+2 -1
View File
@@ -12,6 +12,7 @@ from homeassistant.components.sensor import (
SensorEntityDescription,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import LIGHT_LUX
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
@@ -71,7 +72,7 @@ class AbodeSensor(AbodeDevice, SensorEntity):
elif description.key == CONST.HUMI_STATUS_KEY:
self._attr_native_unit_of_measurement = device.humidity_unit
elif description.key == CONST.LUX_STATUS_KEY:
self._attr_native_unit_of_measurement = device.lux_unit
self._attr_native_unit_of_measurement = LIGHT_LUX
@property
def native_value(self) -> float | None:
@@ -11,5 +11,5 @@
"documentation": "https://www.home-assistant.io/integrations/airzone",
"iot_class": "local_polling",
"loggers": ["aioairzone"],
"requirements": ["aioairzone==0.6.1"]
"requirements": ["aioairzone==0.6.4"]
}
@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/airzone_cloud",
"iot_class": "cloud_polling",
"loggers": ["aioairzone_cloud"],
"requirements": ["aioairzone-cloud==0.1.7"]
"requirements": ["aioairzone-cloud==0.1.8"]
}
@@ -98,7 +98,7 @@ async def async_setup_entry(
tasks = []
for heater in data_connection.get_devices():
tasks.append(heater.update_device_info())
tasks.append(asyncio.create_task(heater.update_device_info()))
await asyncio.wait(tasks)
devs = []
@@ -430,7 +430,9 @@ INFERRED_UNITS = {
" Percent": PERCENTAGE,
" Volts": UnitOfElectricPotential.VOLT,
" Ampere": UnitOfElectricCurrent.AMPERE,
" Amps": UnitOfElectricCurrent.AMPERE,
" Volt-Ampere": UnitOfApparentPower.VOLT_AMPERE,
" VA": UnitOfApparentPower.VOLT_AMPERE,
" Watts": UnitOfPower.WATT,
" Hz": UnitOfFrequency.HERTZ,
" C": UnitOfTemperature.CELSIUS,
@@ -28,5 +28,5 @@
"documentation": "https://www.home-assistant.io/integrations/august",
"iot_class": "cloud_push",
"loggers": ["pubnub", "yalexs"],
"requirements": ["yalexs==1.5.1", "yalexs-ble==2.1.17"]
"requirements": ["yalexs==1.5.1", "yalexs-ble==2.1.18"]
}
+1 -1
View File
@@ -5,7 +5,7 @@
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/baf",
"iot_class": "local_push",
"requirements": ["aiobafi6==0.8.0"],
"requirements": ["aiobafi6==0.8.2"],
"zeroconf": [
{
"type": "_api._tcp.local.",
@@ -173,7 +173,11 @@ async def async_setup_scanner(
rssi = await hass.async_add_executor_job(client.request_rssi)
client.close()
tasks.append(see_device(hass, async_see, mac, friendly_name, rssi))
tasks.append(
asyncio.create_task(
see_device(hass, async_see, mac, friendly_name, rssi)
)
)
if tasks:
await asyncio.wait(tasks)
@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/bmw_connected_drive",
"iot_class": "cloud_polling",
"loggers": ["bimmer_connected"],
"requirements": ["bimmer_connected==0.13.6"]
"requirements": ["bimmer-connected==0.13.7"]
}
@@ -20,5 +20,5 @@
"dependencies": ["bluetooth_adapters"],
"documentation": "https://www.home-assistant.io/integrations/bthome",
"iot_class": "local_push",
"requirements": ["bthome-ble==2.11.3"]
"requirements": ["bthome-ble==2.12.0"]
}
+27
View File
@@ -47,6 +47,15 @@ from .coordinator import (
from .device import device_key_to_bluetooth_entity_key
SENSOR_DESCRIPTIONS = {
# Acceleration (m/s²)
(
BTHomeSensorDeviceClass.ACCELERATION,
Units.ACCELERATION_METERS_PER_SQUARE_SECOND,
): SensorEntityDescription(
key=f"{BTHomeSensorDeviceClass.ACCELERATION}_{Units.ACCELERATION_METERS_PER_SQUARE_SECOND}",
native_unit_of_measurement=Units.ACCELERATION_METERS_PER_SQUARE_SECOND,
state_class=SensorStateClass.MEASUREMENT,
),
# Battery (percent)
(BTHomeSensorDeviceClass.BATTERY, Units.PERCENTAGE): SensorEntityDescription(
key=f"{BTHomeSensorDeviceClass.BATTERY}_{Units.PERCENTAGE}",
@@ -131,6 +140,15 @@ SENSOR_DESCRIPTIONS = {
native_unit_of_measurement=UnitOfVolume.CUBIC_METERS,
state_class=SensorStateClass.TOTAL,
),
# Gyroscope (°/s)
(
BTHomeSensorDeviceClass.GYROSCOPE,
Units.GYROSCOPE_DEGREES_PER_SECOND,
): SensorEntityDescription(
key=f"{BTHomeSensorDeviceClass.GYROSCOPE}_{Units.GYROSCOPE_DEGREES_PER_SECOND}",
native_unit_of_measurement=Units.GYROSCOPE_DEGREES_PER_SECOND,
state_class=SensorStateClass.MEASUREMENT,
),
# Humidity in (percent)
(BTHomeSensorDeviceClass.HUMIDITY, Units.PERCENTAGE): SensorEntityDescription(
key=f"{BTHomeSensorDeviceClass.HUMIDITY}_{Units.PERCENTAGE}",
@@ -242,6 +260,15 @@ SENSOR_DESCRIPTIONS = {
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
state_class=SensorStateClass.MEASUREMENT,
),
# Timestamp (datetime object)
(
BTHomeSensorDeviceClass.TIMESTAMP,
None,
): SensorEntityDescription(
key=f"{BTHomeSensorDeviceClass.TIMESTAMP}",
device_class=SensorDeviceClass.TIMESTAMP,
state_class=SensorStateClass.MEASUREMENT,
),
# UV index (-)
(
BTHomeSensorDeviceClass.UV_INDEX,
@@ -221,6 +221,11 @@ class CloudAlexaConfig(alexa_config.AbstractConfig):
async def on_hass_started(hass: HomeAssistant) -> None:
if self._prefs.alexa_settings_version != ALEXA_SETTINGS_VERSION:
_LOGGER.info(
"Start migration of Alexa settings from v%s to v%s",
self._prefs.alexa_settings_version,
ALEXA_SETTINGS_VERSION,
)
if self._prefs.alexa_settings_version < 2 or (
# Recover from a bug we had in 2023.5.0 where entities didn't get exposed
self._prefs.alexa_settings_version < 3
@@ -233,6 +238,11 @@ class CloudAlexaConfig(alexa_config.AbstractConfig):
):
self._migrate_alexa_entity_settings_v1()
_LOGGER.info(
"Finished migration of Alexa settings from v%s to v%s",
self._prefs.alexa_settings_version,
ALEXA_SETTINGS_VERSION,
)
await self._prefs.async_update(
alexa_settings_version=ALEXA_SETTINGS_VERSION
)
@@ -108,7 +108,12 @@ def _supported_legacy(hass: HomeAssistant, entity_id: str) -> bool:
if domain in SUPPORTED_DOMAINS:
return True
device_class = get_device_class(hass, entity_id)
try:
device_class = get_device_class(hass, entity_id)
except HomeAssistantError:
# The entity no longer exists
return False
if (
domain == "binary_sensor"
and device_class in SUPPORTED_BINARY_SENSOR_DEVICE_CLASSES
@@ -208,6 +213,11 @@ class CloudGoogleConfig(AbstractConfig):
async def on_hass_started(hass: HomeAssistant) -> None:
if self._prefs.google_settings_version != GOOGLE_SETTINGS_VERSION:
_LOGGER.info(
"Start migration of Google Assistant settings from v%s to v%s",
self._prefs.google_settings_version,
GOOGLE_SETTINGS_VERSION,
)
if self._prefs.google_settings_version < 2 or (
# Recover from a bug we had in 2023.5.0 where entities didn't get exposed
self._prefs.google_settings_version < 3
@@ -220,6 +230,11 @@ class CloudGoogleConfig(AbstractConfig):
):
self._migrate_google_entity_settings_v1()
_LOGGER.info(
"Finished migration of Google Assistant settings from v%s to v%s",
self._prefs.google_settings_version,
GOOGLE_SETTINGS_VERSION,
)
await self._prefs.async_update(
google_settings_version=GOOGLE_SETTINGS_VERSION
)
@@ -46,12 +46,15 @@ from homeassistant.const import (
CONF_UNIQUE_ID,
CONF_UNIT_OF_MEASUREMENT,
CONF_VALUE_TEMPLATE,
SERVICE_RELOAD,
Platform,
)
from homeassistant.core import HomeAssistant
from homeassistant.core import Event, HomeAssistant, ServiceCall
from homeassistant.helpers import discovery
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.reload import async_setup_reload_service
from homeassistant.helpers.entity_platform import async_get_platforms
from homeassistant.helpers.reload import async_integration_yaml_config
from homeassistant.helpers.service import async_register_admin_service
from homeassistant.helpers.typing import ConfigType
from .const import CONF_COMMAND_TIMEOUT, DEFAULT_TIMEOUT, DOMAIN
@@ -163,14 +166,39 @@ CONFIG_SCHEMA = vol.Schema(
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up Command Line from yaml config."""
command_line_config: list[dict[str, dict[str, Any]]] = config.get(DOMAIN, [])
async def _reload_config(call: Event | ServiceCall) -> None:
"""Reload Command Line."""
reload_config = await async_integration_yaml_config(hass, "command_line")
reset_platforms = async_get_platforms(hass, "command_line")
for reset_platform in reset_platforms:
_LOGGER.debug("Reload resetting platform: %s", reset_platform.domain)
await reset_platform.async_reset()
if not reload_config:
return
await async_load_platforms(hass, reload_config.get(DOMAIN, []), reload_config)
async_register_admin_service(hass, DOMAIN, SERVICE_RELOAD, _reload_config)
await async_load_platforms(hass, config.get(DOMAIN, []), config)
return True
async def async_load_platforms(
hass: HomeAssistant,
command_line_config: list[dict[str, dict[str, Any]]],
config: ConfigType,
) -> None:
"""Load platforms from yaml."""
if not command_line_config:
return True
return
_LOGGER.debug("Full config loaded: %s", command_line_config)
load_coroutines: list[Coroutine[Any, Any, None]] = []
platforms: list[Platform] = []
reload_configs: list[tuple] = []
for platform_config in command_line_config:
for platform, _config in platform_config.items():
if (mapped_platform := PLATFORM_MAPPING[platform]) not in platforms:
@@ -180,6 +208,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
platform_config,
PLATFORM_MAPPING[platform],
)
reload_configs.append((PLATFORM_MAPPING[platform], _config))
load_coroutines.append(
discovery.async_load_platform(
hass,
@@ -190,10 +219,6 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
)
)
await async_setup_reload_service(hass, DOMAIN, platforms)
if load_coroutines:
_LOGGER.debug("Loading platforms: %s", platforms)
await asyncio.gather(*load_coroutines)
return True
@@ -30,6 +30,7 @@ from homeassistant.helpers.event import async_track_time_interval
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
from homeassistant.helpers.template import Template
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from homeassistant.util import dt as dt_util
from .const import CONF_COMMAND_TIMEOUT, DEFAULT_TIMEOUT, DOMAIN, LOGGER
from .sensor import CommandSensorData
@@ -183,3 +184,10 @@ class CommandBinarySensor(BinarySensorEntity):
self._attr_is_on = False
self.async_write_ha_state()
async def async_update(self) -> None:
"""Update the entity.
Only used by the generic entity update service.
"""
await self._update_entity_state(dt_util.now())
@@ -31,7 +31,7 @@ from homeassistant.helpers.event import async_track_time_interval
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
from homeassistant.helpers.template import Template
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from homeassistant.util import slugify
from homeassistant.util import dt as dt_util, slugify
from .const import CONF_COMMAND_TIMEOUT, DEFAULT_TIMEOUT, DOMAIN, LOGGER
from .utils import call_shell_with_timeout, check_output_or_log
@@ -220,6 +220,13 @@ class CommandCover(CoverEntity):
self._state = int(payload)
await self.async_update_ha_state(True)
async def async_update(self) -> None:
"""Update the entity.
Only used by the generic entity update service.
"""
await self._update_entity_state(dt_util.now())
async def async_open_cover(self, **kwargs: Any) -> None:
"""Open the cover."""
await self.hass.async_add_executor_job(self._move_cover, self._command_open)
@@ -33,6 +33,7 @@ from homeassistant.helpers.event import async_track_time_interval
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
from homeassistant.helpers.template import Template
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from homeassistant.util import dt as dt_util
from .const import CONF_COMMAND_TIMEOUT, DEFAULT_TIMEOUT, DOMAIN, LOGGER
from .utils import check_output_or_log
@@ -200,6 +201,13 @@ class CommandSensor(SensorEntity):
self.async_write_ha_state()
async def async_update(self) -> None:
"""Update the entity.
Only used by the generic entity update service.
"""
await self._update_entity_state(dt_util.now())
class CommandSensorData:
"""The class for handling the data retrieval."""
@@ -34,7 +34,7 @@ from homeassistant.helpers.issue_registry import IssueSeverity, async_create_iss
from homeassistant.helpers.template import Template
from homeassistant.helpers.template_entity import ManualTriggerEntity
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from homeassistant.util import slugify
from homeassistant.util import dt as dt_util, slugify
from .const import CONF_COMMAND_TIMEOUT, DEFAULT_TIMEOUT, DOMAIN, LOGGER
from .utils import call_shell_with_timeout, check_output_or_log
@@ -240,6 +240,13 @@ class CommandSwitch(ManualTriggerEntity, SwitchEntity):
self._process_manual_data(payload)
await self.async_update_ha_state(True)
async def async_update(self) -> None:
"""Update the entity.
Only used by the generic entity update service.
"""
await self._update_entity_state(dt_util.now())
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the device on."""
if await self._switch(self._command_on) and not self._command_state:
@@ -7,6 +7,6 @@
"iot_class": "local_polling",
"loggers": ["pydaikin"],
"quality_scale": "platinum",
"requirements": ["pydaikin==2.9.1"],
"requirements": ["pydaikin==2.9.0"],
"zeroconf": ["_dkapi._tcp.local."]
}
+1 -1
View File
@@ -42,7 +42,7 @@ async def async_setup_entry(
[
DaikinZoneSwitch(daikin_api, zone_id)
for zone_id, zone in enumerate(zones)
if zone[0] != ("-", "0")
if zone != ("-", "0")
]
)
if daikin_api.device.support_advanced_modes:
@@ -250,7 +250,7 @@ class ElectraClimateEntity(ClimateEntity):
if (temperature := kwargs.get(ATTR_TEMPERATURE)) is None:
raise ValueError("No target temperature provided")
self._electra_ac_device.set_temperature(temperature)
self._electra_ac_device.set_temperature(int(temperature))
await self._async_operate_electra_ac()
def _update_device_attrs(self) -> None:
+1 -1
View File
@@ -15,5 +15,5 @@
"documentation": "https://www.home-assistant.io/integrations/elkm1",
"iot_class": "local_push",
"loggers": ["elkm1_lib"],
"requirements": ["elkm1-lib==2.2.2"]
"requirements": ["elkm1-lib==2.2.5"]
}
+2 -2
View File
@@ -192,7 +192,7 @@ class Flexit(ClimateEntity):
result = float(
await self._async_read_int16_from_register(register_type, register)
)
if result == -1:
if not result:
return -1
return result / 10.0
@@ -200,6 +200,6 @@ class Flexit(ClimateEntity):
result = await self._hub.async_pymodbus_call(
self._slave, register, value, CALL_TYPE_WRITE_REGISTER
)
if result == -1:
if not result:
return False
return True
@@ -43,7 +43,7 @@ def get_scanner(hass: HomeAssistant, config: ConfigType) -> FortiOSDeviceScanner
fgt = FortiOSAPI()
try:
fgt.tokenlogin(host, token, verify_ssl)
fgt.tokenlogin(host, token, verify_ssl, None, 12, "root")
except ConnectionError as ex:
_LOGGER.error("ConnectionError to FortiOS API: %s", ex)
return None
@@ -77,7 +77,12 @@ class FortiOSDeviceScanner(DeviceScanner):
def update(self):
"""Update clients from the device."""
clients_json = self._fgt.monitor("user/device/query", "")
clients_json = self._fgt.monitor(
"user/device/query",
"",
parameters={"filter": "format=master_mac|hostname|is_online"},
)
self._clients_json = clients_json
self._clients = []
@@ -85,8 +90,12 @@ class FortiOSDeviceScanner(DeviceScanner):
if clients_json:
try:
for client in clients_json["results"]:
if client["is_online"]:
self._clients.append(client["mac"].upper())
if (
"is_online" in client
and "master_mac" in client
and client["is_online"]
):
self._clients.append(client["master_mac"].upper())
except KeyError as kex:
_LOGGER.error("Key not found in clients: %s", kex)
@@ -106,17 +115,10 @@ class FortiOSDeviceScanner(DeviceScanner):
return None
for client in data["results"]:
if client["mac"] == device:
try:
if "master_mac" in client and client["master_mac"] == device:
if "hostname" in client:
name = client["hostname"]
_LOGGER.debug("Getting device name=%s", name)
return name
except KeyError as kex:
_LOGGER.debug(
"No hostname found for %s in client data: %s",
device,
kex,
)
return device.replace(":", "_")
else:
name = client["master_mac"].replace(":", "_")
return name
return None
@@ -20,5 +20,5 @@
"documentation": "https://www.home-assistant.io/integrations/frontend",
"integration_type": "system",
"quality_scale": "internal",
"requirements": ["home-assistant-frontend==20230605.0"]
"requirements": ["home-assistant-frontend==20230608.0"]
}
@@ -2,6 +2,7 @@
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers.typing import ConfigType
from .const import DOMAIN
from .coordinator import FullyKioskDataUpdateCoordinator
@@ -17,6 +18,14 @@ PLATFORMS = [
]
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up Fully Kiosk Browser."""
await async_setup_services(hass)
return True
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Fully Kiosk Browser from a config entry."""
@@ -28,8 +37,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
coordinator.async_update_listeners()
await async_setup_services(hass)
return True
@@ -1,14 +1,12 @@
"""Services for the Fully Kiosk Browser integration."""
from __future__ import annotations
from collections.abc import Callable
from typing import Any
from fullykiosk import FullyKiosk
import voluptuous as vol
from homeassistant.config_entries import ConfigEntry, ConfigEntryState
from homeassistant.const import ATTR_DEVICE_ID
from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.exceptions import HomeAssistantError
import homeassistant.helpers.config_validation as cv
import homeassistant.helpers.device_registry as dr
@@ -16,59 +14,53 @@ from .const import (
ATTR_APPLICATION,
ATTR_URL,
DOMAIN,
LOGGER,
SERVICE_LOAD_URL,
SERVICE_START_APPLICATION,
)
from .coordinator import FullyKioskDataUpdateCoordinator
async def async_setup_services(hass: HomeAssistant) -> None:
"""Set up the services for the Fully Kiosk Browser integration."""
async def execute_service(
call: ServiceCall,
fully_method: Callable,
*args: list[str],
**kwargs: dict[str, Any],
) -> None:
"""Execute a Fully service call.
:param call: {ServiceCall} HA service call.
:param fully_method: {Callable} A method of the FullyKiosk class.
:param args: Arguments for fully_method.
:param kwargs: Key-word arguments for fully_method.
:return: None
"""
LOGGER.debug(
"Calling Fully service %s with args: %s, %s", ServiceCall, args, kwargs
)
async def collect_coordinators(
device_ids: list[str],
) -> list[FullyKioskDataUpdateCoordinator]:
config_entries = list[ConfigEntry]()
registry = dr.async_get(hass)
for target in call.data[ATTR_DEVICE_ID]:
for target in device_ids:
device = registry.async_get(target)
if device:
for key in device.config_entries:
entry = hass.config_entries.async_get_entry(key)
if not entry:
continue
if entry.domain != DOMAIN:
continue
coordinator = hass.data[DOMAIN][key]
# fully_method(coordinator.fully, *args, **kwargs) would make
# test_services.py fail.
await getattr(coordinator.fully, fully_method.__name__)(
*args, **kwargs
device_entries = list[ConfigEntry]()
for entry_id in device.config_entries:
entry = hass.config_entries.async_get_entry(entry_id)
if entry and entry.domain == DOMAIN:
device_entries.append(entry)
if not device_entries:
raise HomeAssistantError(
f"Device '{target}' is not a {DOMAIN} device"
)
break
config_entries.extend(device_entries)
else:
raise HomeAssistantError(
f"Device '{target}' not found in device registry"
)
coordinators = list[FullyKioskDataUpdateCoordinator]()
for config_entry in config_entries:
if config_entry.state != ConfigEntryState.LOADED:
raise HomeAssistantError(f"{config_entry.title} is not loaded")
coordinators.append(hass.data[DOMAIN][config_entry.entry_id])
return coordinators
async def async_load_url(call: ServiceCall) -> None:
"""Load a URL on the Fully Kiosk Browser."""
await execute_service(call, FullyKiosk.loadUrl, call.data[ATTR_URL])
for coordinator in await collect_coordinators(call.data[ATTR_DEVICE_ID]):
await coordinator.fully.loadUrl(call.data[ATTR_URL])
async def async_start_app(call: ServiceCall) -> None:
"""Start an app on the device."""
await execute_service(
call, FullyKiosk.startApplication, call.data[ATTR_APPLICATION]
)
for coordinator in await collect_coordinators(call.data[ATTR_DEVICE_ID]):
await coordinator.fully.startApplication(call.data[ATTR_APPLICATION])
# Register all the above services
service_mapping = [
+27 -27
View File
@@ -223,13 +223,6 @@ SENSOR_TYPES = {
icon="mdi:docker",
state_class=SensorStateClass.MEASUREMENT,
),
("raid", "used"): GlancesSensorEntityDescription(
key="used",
type="raid",
name_suffix="Raid used",
icon="mdi:harddisk",
state_class=SensorStateClass.MEASUREMENT,
),
("raid", "available"): GlancesSensorEntityDescription(
key="available",
type="raid",
@@ -237,6 +230,13 @@ SENSOR_TYPES = {
icon="mdi:harddisk",
state_class=SensorStateClass.MEASUREMENT,
),
("raid", "used"): GlancesSensorEntityDescription(
key="used",
type="raid",
name_suffix="Raid used",
icon="mdi:harddisk",
state_class=SensorStateClass.MEASUREMENT,
),
}
@@ -269,36 +269,36 @@ async def async_setup_entry(
if sensor_type in ["fs", "sensors", "raid"]:
for sensor_label, params in sensors.items():
for param in params:
sensor_description = SENSOR_TYPES[(sensor_type, param)]
if sensor_description := SENSOR_TYPES.get((sensor_type, param)):
_migrate_old_unique_ids(
hass,
f"{coordinator.host}-{name} {sensor_label} {sensor_description.name_suffix}",
f"{sensor_label}-{sensor_description.key}",
)
entities.append(
GlancesSensor(
coordinator,
name,
sensor_label,
sensor_description,
)
)
else:
for sensor in sensors:
if sensor_description := SENSOR_TYPES.get((sensor_type, sensor)):
_migrate_old_unique_ids(
hass,
f"{coordinator.host}-{name} {sensor_label} {sensor_description.name_suffix}",
f"{sensor_label}-{sensor_description.key}",
f"{coordinator.host}-{name} {sensor_description.name_suffix}",
f"-{sensor_description.key}",
)
entities.append(
GlancesSensor(
coordinator,
name,
sensor_label,
"",
sensor_description,
)
)
else:
for sensor in sensors:
sensor_description = SENSOR_TYPES[(sensor_type, sensor)]
_migrate_old_unique_ids(
hass,
f"{coordinator.host}-{name} {sensor_description.name_suffix}",
f"-{sensor_description.key}",
)
entities.append(
GlancesSensor(
coordinator,
name,
"",
sensor_description,
)
)
async_add_entities(entities)
@@ -1,65 +0,0 @@
"""Component for the Goalfeed service."""
import json
import pysher
import requests
import voluptuous as vol
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.typing import ConfigType
# Version downgraded due to regression in library
# For details: https://github.com/nlsdfnbch/Pysher/issues/38
DOMAIN = "goalfeed"
CONFIG_SCHEMA = vol.Schema(
{
DOMAIN: vol.Schema(
{
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
}
)
},
extra=vol.ALLOW_EXTRA,
)
GOALFEED_HOST = "feed.goalfeed.ca"
GOALFEED_AUTH_ENDPOINT = "https://goalfeed.ca/feed/auth"
GOALFEED_APP_ID = "bfd4ed98c1ff22c04074"
def setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the Goalfeed component."""
conf = config[DOMAIN]
username = conf.get(CONF_USERNAME)
password = conf.get(CONF_PASSWORD)
def goal_handler(data):
"""Handle goal events."""
goal = json.loads(json.loads(data))
hass.bus.fire("goal", event_data=goal)
def connect_handler(data):
"""Handle connection."""
post_data = {
"username": username,
"password": password,
"connection_info": data,
}
resp = requests.post(GOALFEED_AUTH_ENDPOINT, post_data, timeout=30).json()
channel = pusher.subscribe("private-goals", resp["auth"])
channel.bind("goal", goal_handler)
pusher = pysher.Pusher(
GOALFEED_APP_ID, secure=False, port=8080, custom_host=GOALFEED_HOST
)
pusher.connection.bind("pusher:connection_established", connect_handler)
pusher.connect()
return True
@@ -1,9 +0,0 @@
{
"domain": "goalfeed",
"name": "Goalfeed",
"codeowners": [],
"documentation": "https://www.home-assistant.io/integrations/goalfeed",
"iot_class": "cloud_push",
"loggers": ["pysher"],
"requirements": ["pysher==1.0.7"]
}
+1 -1
View File
@@ -243,7 +243,7 @@ class InverterSensor(CoordinatorEntity[GoodweUpdateCoordinator], SensorEntity):
In contrast to "total" sensors, these "daily" sensors need to be reset to 0 on midnight.
"""
if not self.coordinator.last_update_success:
self.coordinator.reset_sensor(self._sensor.id)
self.coordinator.reset_sensor(self._sensor.id_)
self.async_write_ha_state()
_LOGGER.debug("Goodwe reset %s to 0", self.name)
next_midnight = dt_util.start_of_local_day(
@@ -186,7 +186,7 @@ STORE_GOOGLE_LOCAL_WEBHOOK_ID = "local_webhook_id"
SOURCE_CLOUD = "cloud"
SOURCE_LOCAL = "local"
NOT_EXPOSE_LOCAL = {TYPE_ALARM, TYPE_LOCK}
NOT_EXPOSE_LOCAL = {TYPE_ALARM, TYPE_LOCK, TYPE_THERMOSTAT}
FAN_SPEEDS = {
"5/5": ["High", "Max", "Fast", "5"],
+2 -2
View File
@@ -24,7 +24,7 @@ MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=15)
SensorType = namedtuple("SensorType", ["name", "icon", "unit", "path"])
SENSORS_TYPES = {
"name": SensorType("Name", None, "", ["profile", "name"]),
"name": SensorType("Name", None, None, ["profile", "name"]),
"hp": SensorType("HP", "mdi:heart", "HP", ["stats", "hp"]),
"maxHealth": SensorType("max HP", "mdi:heart", "HP", ["stats", "maxHealth"]),
"mp": SensorType("Mana", "mdi:auto-fix", "MP", ["stats", "mp"]),
@@ -35,7 +35,7 @@ SENSORS_TYPES = {
"Lvl", "mdi:arrow-up-bold-circle-outline", "Lvl", ["stats", "lvl"]
),
"gp": SensorType("Gold", "mdi:circle-multiple", "Gold", ["stats", "gp"]),
"class": SensorType("Class", "mdi:sword", "", ["stats", "class"]),
"class": SensorType("Class", "mdi:sword", None, ["stats", "class"]),
}
TASKS_TYPES = {
+5 -1
View File
@@ -305,7 +305,11 @@ class SupervisorIssues:
async def update(self) -> None:
"""Update issues from Supervisor resolution center."""
data = await self._client.get_resolution_info()
try:
data = await self._client.get_resolution_info()
except HassioAPIError as err:
_LOGGER.error("Failed to update supervisor issues: %r", err)
return
self.unhealthy_reasons = set(data[ATTR_UNHEALTHY])
self.unsupported_reasons = set(data[ATTR_UNSUPPORTED])
@@ -96,19 +96,29 @@ class MultiprotocolAddonManager(AddonManager):
) -> None:
"""Register a multipan platform."""
self._platforms[integration_domain] = platform
if self._channel is not None or not await platform.async_using_multipan(hass):
channel = await platform.async_get_channel(hass)
using_multipan = await platform.async_using_multipan(hass)
_LOGGER.info(
"Registering new multipan platform '%s', using multipan: %s, channel: %s",
integration_domain,
using_multipan,
channel,
)
if self._channel is not None or not using_multipan:
return
new_channel = await platform.async_get_channel(hass)
if new_channel is None:
if channel is None:
return
_LOGGER.info(
"Setting multipan channel to %s (source: '%s')",
new_channel,
channel,
integration_domain,
)
self.async_set_channel(new_channel)
self.async_set_channel(channel)
async def async_change_channel(
self, channel: int, delay: float
@@ -626,10 +626,10 @@ class HomeDriver(AccessoryDriver): # type: ignore[misc]
@pyhap_callback # type: ignore[misc]
def pair(
self, client_uuid: UUID, client_public: str, client_permissions: int
self, client_username_bytes: bytes, client_public: str, client_permissions: int
) -> bool:
"""Override super function to dismiss setup message if paired."""
success = super().pair(client_uuid, client_public, client_permissions)
success = super().pair(client_username_bytes, client_public, client_permissions)
if success:
async_dismiss_setup_message(self.hass, self._entry_id)
return cast(bool, success)
@@ -9,7 +9,7 @@
"iot_class": "local_push",
"loggers": ["pyhap"],
"requirements": [
"HAP-python==4.6.0",
"HAP-python==4.7.0",
"fnv-hash-fast==0.3.1",
"PyQRCode==1.2.1",
"base36==0.1.1"
@@ -14,6 +14,6 @@
"documentation": "https://www.home-assistant.io/integrations/homekit_controller",
"iot_class": "local_push",
"loggers": ["aiohomekit", "commentjson"],
"requirements": ["aiohomekit==2.6.4"],
"requirements": ["aiohomekit==2.6.5"],
"zeroconf": ["_hap._tcp.local.", "_hap._udp.local."]
}
+1 -1
View File
@@ -120,7 +120,7 @@ class ImapMessage:
@property
def subject(self) -> str:
"""Decode the message subject."""
decoded_header = decode_header(self.email_message["Subject"])
decoded_header = decode_header(self.email_message["Subject"] or "")
subject_header = make_header(decoded_header)
return str(subject_header)
@@ -302,12 +302,9 @@ class InputSelect(collection.CollectionEntity, SelectEntity, RestoreEntity):
async def async_select_option(self, option: str) -> None:
"""Select new option."""
if option not in self.options:
_LOGGER.warning(
"Invalid option: %s (possible options: %s)",
option,
", ".join(self.options),
raise HomeAssistantError(
f"Invalid option: {option} (possible options: {', '.join(self.options)})"
)
return
self._attr_current_option = option
self.async_write_ha_state()
@@ -25,7 +25,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import SIGNAL_ADD_ENTITIES
from .insteon_entity import InsteonEntity
from .utils import async_add_insteon_entities
from .utils import async_add_insteon_devices, async_add_insteon_entities
SENSOR_TYPES = {
OPEN_CLOSE_SENSOR: BinarySensorDeviceClass.OPENING,
@@ -62,7 +62,12 @@ async def async_setup_entry(
signal = f"{SIGNAL_ADD_ENTITIES}_{Platform.BINARY_SENSOR}"
async_dispatcher_connect(hass, signal, async_add_insteon_binary_sensor_entities)
async_add_insteon_binary_sensor_entities()
async_add_insteon_devices(
hass,
Platform.BINARY_SENSOR,
InsteonBinarySensorEntity,
async_add_entities,
)
class InsteonBinarySensorEntity(InsteonEntity, BinarySensorEntity):
+7 -2
View File
@@ -23,7 +23,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import SIGNAL_ADD_ENTITIES
from .insteon_entity import InsteonEntity
from .utils import async_add_insteon_entities
from .utils import async_add_insteon_devices, async_add_insteon_entities
FAN_ONLY = "fan_only"
@@ -71,7 +71,12 @@ async def async_setup_entry(
signal = f"{SIGNAL_ADD_ENTITIES}_{Platform.CLIMATE}"
async_dispatcher_connect(hass, signal, async_add_insteon_climate_entities)
async_add_insteon_climate_entities()
async_add_insteon_devices(
hass,
Platform.CLIMATE,
InsteonClimateEntity,
async_add_entities,
)
class InsteonClimateEntity(InsteonEntity, ClimateEntity):
+7 -2
View File
@@ -15,7 +15,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import SIGNAL_ADD_ENTITIES
from .insteon_entity import InsteonEntity
from .utils import async_add_insteon_entities
from .utils import async_add_insteon_devices, async_add_insteon_entities
async def async_setup_entry(
@@ -34,7 +34,12 @@ async def async_setup_entry(
signal = f"{SIGNAL_ADD_ENTITIES}_{Platform.COVER}"
async_dispatcher_connect(hass, signal, async_add_insteon_cover_entities)
async_add_insteon_cover_entities()
async_add_insteon_devices(
hass,
Platform.COVER,
InsteonCoverEntity,
async_add_entities,
)
class InsteonCoverEntity(InsteonEntity, CoverEntity):
+7 -2
View File
@@ -17,7 +17,7 @@ from homeassistant.util.percentage import (
from .const import SIGNAL_ADD_ENTITIES
from .insteon_entity import InsteonEntity
from .utils import async_add_insteon_entities
from .utils import async_add_insteon_devices, async_add_insteon_entities
SPEED_RANGE = (1, 255) # off is not included
@@ -38,7 +38,12 @@ async def async_setup_entry(
signal = f"{SIGNAL_ADD_ENTITIES}_{Platform.FAN}"
async_dispatcher_connect(hass, signal, async_add_insteon_fan_entities)
async_add_insteon_fan_entities()
async_add_insteon_devices(
hass,
Platform.FAN,
InsteonFanEntity,
async_add_entities,
)
class InsteonFanEntity(InsteonEntity, FanEntity):
+9 -6
View File
@@ -1,4 +1,7 @@
"""Utility methods for the Insteon platform."""
from collections.abc import Iterable
from pyinsteon.device_types.device_base import Device
from pyinsteon.device_types.ipdb import (
AccessControl_Morningstar,
ClimateControl_Thermostat,
@@ -44,7 +47,7 @@ from pyinsteon.device_types.ipdb import (
from homeassistant.const import Platform
DEVICE_PLATFORM = {
DEVICE_PLATFORM: dict[Device, dict[Platform, Iterable[int]]] = {
AccessControl_Morningstar: {Platform.LOCK: [1]},
DimmableLightingControl: {Platform.LIGHT: [1]},
DimmableLightingControl_Dial: {Platform.LIGHT: [1]},
@@ -101,11 +104,11 @@ DEVICE_PLATFORM = {
}
def get_device_platforms(device):
def get_device_platforms(device) -> dict[Platform, Iterable[int]]:
"""Return the HA platforms for a device type."""
return DEVICE_PLATFORM.get(type(device), {}).keys()
return DEVICE_PLATFORM.get(type(device), {})
def get_platform_groups(device, domain) -> dict:
"""Return the platforms that a device belongs in."""
return DEVICE_PLATFORM.get(type(device), {}).get(domain, {}) # type: ignore[attr-defined]
def get_device_platform_groups(device: Device, platform: Platform) -> Iterable[int]:
"""Return the list of device groups for a platform."""
return get_device_platforms(device).get(platform, [])
+7 -2
View File
@@ -12,7 +12,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import SIGNAL_ADD_ENTITIES
from .insteon_entity import InsteonEntity
from .utils import async_add_insteon_entities
from .utils import async_add_insteon_devices, async_add_insteon_entities
MAX_BRIGHTNESS = 255
@@ -37,7 +37,12 @@ async def async_setup_entry(
signal = f"{SIGNAL_ADD_ENTITIES}_{Platform.LIGHT}"
async_dispatcher_connect(hass, signal, async_add_insteon_light_entities)
async_add_insteon_light_entities()
async_add_insteon_devices(
hass,
Platform.LIGHT,
InsteonDimmerEntity,
async_add_entities,
)
class InsteonDimmerEntity(InsteonEntity, LightEntity):
+7 -2
View File
@@ -11,7 +11,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import SIGNAL_ADD_ENTITIES
from .insteon_entity import InsteonEntity
from .utils import async_add_insteon_entities
from .utils import async_add_insteon_devices, async_add_insteon_entities
async def async_setup_entry(
@@ -30,7 +30,12 @@ async def async_setup_entry(
signal = f"{SIGNAL_ADD_ENTITIES}_{Platform.LOCK}"
async_dispatcher_connect(hass, signal, async_add_insteon_lock_entities)
async_add_insteon_lock_entities()
async_add_insteon_devices(
hass,
Platform.LOCK,
InsteonLockEntity,
async_add_entities,
)
class InsteonLockEntity(InsteonEntity, LockEntity):
@@ -17,7 +17,7 @@
"iot_class": "local_push",
"loggers": ["pyinsteon", "pypubsub"],
"requirements": [
"pyinsteon==1.4.2",
"pyinsteon==1.4.3",
"insteon-frontend-home-assistant==0.3.5"
],
"usb": [
+7 -2
View File
@@ -10,7 +10,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import SIGNAL_ADD_ENTITIES
from .insteon_entity import InsteonEntity
from .utils import async_add_insteon_entities
from .utils import async_add_insteon_devices, async_add_insteon_entities
async def async_setup_entry(
@@ -33,7 +33,12 @@ async def async_setup_entry(
signal = f"{SIGNAL_ADD_ENTITIES}_{Platform.SWITCH}"
async_dispatcher_connect(hass, signal, async_add_insteon_switch_entities)
async_add_insteon_switch_entities()
async_add_insteon_devices(
hass,
Platform.SWITCH,
InsteonSwitchEntity,
async_add_entities,
)
class InsteonSwitchEntity(InsteonEntity, SwitchEntity):
+48 -17
View File
@@ -1,7 +1,10 @@
"""Utilities used by insteon component."""
from __future__ import annotations
import asyncio
from collections.abc import Callable
import logging
from typing import TYPE_CHECKING, Any
from pyinsteon import devices
from pyinsteon.address import Address
@@ -30,6 +33,7 @@ from homeassistant.const import (
CONF_ENTITY_ID,
CONF_PLATFORM,
ENTITY_MATCH_ALL,
Platform,
)
from homeassistant.core import HomeAssistant, ServiceCall, callback
from homeassistant.helpers import device_registry as dr
@@ -38,6 +42,7 @@ from homeassistant.helpers.dispatcher import (
async_dispatcher_send,
dispatcher_send,
)
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import (
CONF_CAT,
@@ -78,7 +83,7 @@ from .const import (
SRV_X10_ALL_LIGHTS_ON,
SRV_X10_ALL_UNITS_OFF,
)
from .ipdb import get_device_platforms, get_platform_groups
from .ipdb import get_device_platform_groups, get_device_platforms
from .schemas import (
ADD_ALL_LINK_SCHEMA,
ADD_DEFAULT_LINKS_SCHEMA,
@@ -89,6 +94,9 @@ from .schemas import (
X10_HOUSECODE_SCHEMA,
)
if TYPE_CHECKING:
from .insteon_entity import InsteonEntity
_LOGGER = logging.getLogger(__name__)
@@ -107,8 +115,8 @@ def add_insteon_events(hass: HomeAssistant, device: Device) -> None:
"""Register Insteon device events."""
@callback
def async_fire_group_on_off_event(
name: str, address: Address, group: int, button: str
def async_fire_insteon_event(
name: str, address: Address, group: int, button: str | None = None
):
# Firing an event when a button is pressed.
if button and button[-2] == "_":
@@ -132,12 +140,15 @@ def add_insteon_events(hass: HomeAssistant, device: Device) -> None:
_LOGGER.debug("Firing event %s with %s", event, schema)
hass.bus.async_fire(event, schema)
if str(device.address).startswith("X10"):
return
for name_or_group, event in device.events.items():
if isinstance(name_or_group, int):
for _, event in device.events[name_or_group].items():
_register_event(event, async_fire_group_on_off_event)
_register_event(event, async_fire_insteon_event)
else:
_register_event(event, async_fire_group_on_off_event)
_register_event(event, async_fire_insteon_event)
def register_new_device_callback(hass):
@@ -158,8 +169,10 @@ def register_new_device_callback(hass):
await device.async_status()
platforms = get_device_platforms(device)
for platform in platforms:
groups = get_device_platform_groups(device, platform)
signal = f"{SIGNAL_ADD_ENTITIES}_{platform}"
dispatcher_send(hass, signal, {"address": device.address})
dispatcher_send(hass, signal, {"address": device.address, "groups": groups})
add_insteon_events(hass, device)
devices.subscribe(async_new_insteon_device, force_strong_ref=True)
@@ -383,20 +396,38 @@ def print_aldb_to_log(aldb):
@callback
def async_add_insteon_entities(
hass, platform, entity_type, async_add_entities, discovery_info
):
"""Add Insteon devices to a platform."""
new_entities = []
device_list = [discovery_info.get("address")] if discovery_info else devices
for address in device_list:
device = devices[address]
groups = get_platform_groups(device, platform)
for group in groups:
new_entities.append(entity_type(device, group))
hass: HomeAssistant,
platform: Platform,
entity_type: type[InsteonEntity],
async_add_entities: AddEntitiesCallback,
discovery_info: dict[str, Any],
) -> None:
"""Add an Insteon group to a platform."""
address = discovery_info["address"]
device = devices[address]
new_entities = [
entity_type(device=device, group=group) for group in discovery_info["groups"]
]
async_add_entities(new_entities)
@callback
def async_add_insteon_devices(
hass: HomeAssistant,
platform: Platform,
entity_type: type[InsteonEntity],
async_add_entities: AddEntitiesCallback,
) -> None:
"""Add all entities to a platform."""
for address in devices:
device = devices[address]
groups = get_device_platform_groups(device, platform)
discovery_info = {"address": address, "groups": groups}
async_add_insteon_entities(
hass, platform, entity_type, async_add_entities, discovery_info
)
def get_usb_ports() -> dict[str, str]:
"""Return a dict of USB ports and their friendly names."""
ports = list_ports.comports()
+11 -15
View File
@@ -19,21 +19,18 @@ PLATFORMS = [Platform.SENSOR]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up IPP from a config entry."""
hass.data.setdefault(DOMAIN, {})
if not (coordinator := hass.data[DOMAIN].get(entry.entry_id)):
# Create IPP instance for this entry
coordinator = IPPDataUpdateCoordinator(
hass,
host=entry.data[CONF_HOST],
port=entry.data[CONF_PORT],
base_path=entry.data[CONF_BASE_PATH],
tls=entry.data[CONF_SSL],
verify_ssl=entry.data[CONF_VERIFY_SSL],
)
hass.data[DOMAIN][entry.entry_id] = coordinator
coordinator = IPPDataUpdateCoordinator(
hass,
host=entry.data[CONF_HOST],
port=entry.data[CONF_PORT],
base_path=entry.data[CONF_BASE_PATH],
tls=entry.data[CONF_SSL],
verify_ssl=entry.data[CONF_VERIFY_SSL],
)
await coordinator.async_config_entry_first_refresh()
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
@@ -41,7 +38,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok:
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok
@@ -1,11 +1,14 @@
"""Receive signals from a keyboard and use it as a remote control."""
# pylint: disable=import-error
from __future__ import annotations
import asyncio
from contextlib import suppress
import logging
import os
from typing import Any
import aionotify
from asyncinotify import Inotify, Mask
from evdev import InputDevice, categorize, ecodes, list_devices
import voluptuous as vol
@@ -64,9 +67,9 @@ CONFIG_SCHEMA = vol.Schema(
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the keyboard_remote."""
config = config[DOMAIN]
domain_config: list[dict[str, Any]] = config[DOMAIN]
remote = KeyboardRemote(hass, config)
remote = KeyboardRemote(hass, domain_config)
remote.setup()
return True
@@ -75,12 +78,13 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
class KeyboardRemote:
"""Manage device connection/disconnection using inotify to asynchronously monitor."""
def __init__(self, hass, config):
def __init__(self, hass: HomeAssistant, config: list[dict[str, Any]]) -> None:
"""Create handlers and setup dictionaries to keep track of them."""
self.hass = hass
self.handlers_by_name = {}
self.handlers_by_descriptor = {}
self.active_handlers_by_descriptor = {}
self.active_handlers_by_descriptor: dict[str, asyncio.Future] = {}
self.inotify = None
self.watcher = None
self.monitor_task = None
@@ -110,16 +114,12 @@ class KeyboardRemote:
connected, and start monitoring for device connection/disconnection.
"""
# start watching
self.watcher = aionotify.Watcher()
self.watcher.watch(
alias="devinput",
path=DEVINPUT,
flags=aionotify.Flags.CREATE
| aionotify.Flags.ATTRIB
| aionotify.Flags.DELETE,
_LOGGER.debug("Start monitoring")
self.inotify = Inotify()
self.watcher = self.inotify.add_watch(
DEVINPUT, Mask.CREATE | Mask.ATTRIB | Mask.DELETE
)
await self.watcher.setup(self.hass.loop)
# add initial devices (do this AFTER starting watcher in order to
# avoid race conditions leading to missing device connections)
@@ -134,7 +134,9 @@ class KeyboardRemote:
continue
self.active_handlers_by_descriptor[descriptor] = handler
initial_start_monitoring.add(handler.async_start_monitoring(dev))
initial_start_monitoring.add(
asyncio.create_task(handler.async_device_start_monitoring(dev))
)
if initial_start_monitoring:
await asyncio.wait(initial_start_monitoring)
@@ -146,6 +148,10 @@ class KeyboardRemote:
_LOGGER.debug("Cleanup on shutdown")
if self.inotify and self.watcher:
self.inotify.rm_watch(self.watcher)
self.watcher = None
if self.monitor_task is not None:
if not self.monitor_task.done():
self.monitor_task.cancel()
@@ -153,11 +159,16 @@ class KeyboardRemote:
handler_stop_monitoring = set()
for handler in self.active_handlers_by_descriptor.values():
handler_stop_monitoring.add(handler.async_stop_monitoring())
handler_stop_monitoring.add(
asyncio.create_task(handler.async_device_stop_monitoring())
)
if handler_stop_monitoring:
await asyncio.wait(handler_stop_monitoring)
if self.inotify:
self.inotify.close()
self.inotify = None
def get_device_handler(self, descriptor):
"""Find the correct device handler given a descriptor (path)."""
@@ -187,20 +198,21 @@ class KeyboardRemote:
async def async_monitor_devices(self):
"""Monitor asynchronously for device connection/disconnection or permissions changes."""
_LOGGER.debug("Start monitoring loop")
try:
while True:
event = await self.watcher.get_event()
async for event in self.inotify:
descriptor = f"{DEVINPUT}/{event.name}"
_LOGGER.debug("got events for %s: %s", descriptor, event.mask)
descriptor_active = descriptor in self.active_handlers_by_descriptor
if (event.flags & aionotify.Flags.DELETE) and descriptor_active:
if (event.mask & Mask.DELETE) and descriptor_active:
handler = self.active_handlers_by_descriptor[descriptor]
del self.active_handlers_by_descriptor[descriptor]
await handler.async_stop_monitoring()
await handler.async_device_stop_monitoring()
elif (
(event.flags & aionotify.Flags.CREATE)
or (event.flags & aionotify.Flags.ATTRIB)
(event.mask & Mask.CREATE) or (event.mask & Mask.ATTRIB)
) and not descriptor_active:
dev, handler = await self.hass.async_add_executor_job(
self.get_device_handler, descriptor
@@ -208,31 +220,32 @@ class KeyboardRemote:
if handler is None:
continue
self.active_handlers_by_descriptor[descriptor] = handler
await handler.async_start_monitoring(dev)
await handler.async_device_start_monitoring(dev)
except asyncio.CancelledError:
_LOGGER.debug("Monitoring canceled")
return
class DeviceHandler:
"""Manage input events using evdev with asyncio."""
def __init__(self, hass, dev_block):
def __init__(self, hass: HomeAssistant, dev_block: dict[str, Any]) -> None:
"""Fill configuration data."""
self.hass = hass
key_types = dev_block.get(TYPE)
key_types = dev_block[TYPE]
self.key_values = set()
for key_type in key_types:
self.key_values.add(KEY_VALUE[key_type])
self.emulate_key_hold = dev_block.get(EMULATE_KEY_HOLD)
self.emulate_key_hold_delay = dev_block.get(EMULATE_KEY_HOLD_DELAY)
self.emulate_key_hold_repeat = dev_block.get(EMULATE_KEY_HOLD_REPEAT)
self.emulate_key_hold = dev_block[EMULATE_KEY_HOLD]
self.emulate_key_hold_delay = dev_block[EMULATE_KEY_HOLD_DELAY]
self.emulate_key_hold_repeat = dev_block[EMULATE_KEY_HOLD_REPEAT]
self.monitor_task = None
self.dev = None
async def async_keyrepeat(self, path, name, code, delay, repeat):
async def async_device_keyrepeat(self, path, name, code, delay, repeat):
"""Emulate keyboard delay/repeat behaviour by sending key events on a timer."""
await asyncio.sleep(delay)
@@ -248,8 +261,9 @@ class KeyboardRemote:
)
await asyncio.sleep(repeat)
async def async_start_monitoring(self, dev):
async def async_device_start_monitoring(self, dev):
"""Start event monitoring task and issue event."""
_LOGGER.debug("Keyboard async_device_start_monitoring, %s", dev.name)
if self.monitor_task is None:
self.dev = dev
self.monitor_task = self.hass.async_create_task(
@@ -261,7 +275,7 @@ class KeyboardRemote:
)
_LOGGER.debug("Keyboard (re-)connected, %s", dev.name)
async def async_stop_monitoring(self):
async def async_device_stop_monitoring(self):
"""Stop event monitoring task and issue event."""
if self.monitor_task is not None:
with suppress(OSError):
@@ -295,6 +309,7 @@ class KeyboardRemote:
_LOGGER.debug("Start device monitoring")
await self.hass.async_add_executor_job(dev.grab)
async for event in dev.async_read_loop():
# pylint: disable=no-member
if event.type is ecodes.EV_KEY:
if event.value in self.key_values:
_LOGGER.debug(categorize(event))
@@ -313,7 +328,7 @@ class KeyboardRemote:
and self.emulate_key_hold
):
repeat_tasks[event.code] = self.hass.async_create_task(
self.async_keyrepeat(
self.async_device_keyrepeat(
dev.path,
dev.name,
event.code,
@@ -5,5 +5,5 @@
"documentation": "https://www.home-assistant.io/integrations/keyboard_remote",
"iot_class": "local_push",
"loggers": ["aionotify", "evdev"],
"requirements": ["evdev==1.4.0", "aionotify==0.2.0"]
"requirements": ["evdev==1.6.1", "asyncinotify==4.0.2"]
}
@@ -84,6 +84,7 @@ async def async_attach_trigger(
trigger_info: TriggerInfo,
) -> CALLBACK_TYPE:
"""Attach a trigger."""
trigger_data = trigger_info["trigger_data"]
dst_addresses: list[str] = config.get(EXTRA_FIELD_DESTINATION, [])
job = HassJob(action, f"KNX device trigger {trigger_info}")
knx: KNXModule = hass.data[DOMAIN]
@@ -95,7 +96,7 @@ async def async_attach_trigger(
return
hass.async_run_hass_job(
job,
{"trigger": telegram},
{"trigger": {**trigger_data, **telegram}},
)
return knx.telegrams.async_listen_telegram(
+2 -2
View File
@@ -12,7 +12,7 @@
"quality_scale": "platinum",
"requirements": [
"xknx==2.10.0",
"xknxproject==3.1.0",
"knx_frontend==2023.5.31.141540"
"xknxproject==3.1.1",
"knx-frontend==2023.6.9.195839"
]
}
+8 -4
View File
@@ -3,7 +3,7 @@ from __future__ import annotations
from typing import TYPE_CHECKING, Final
from knx_frontend import get_build_id, locate_dir
from knx_frontend import entrypoint_js, is_dev_build, locate_dir
import voluptuous as vol
from xknx.telegram import TelegramDirection
from xknxproject.exceptions import XknxProjectException
@@ -31,9 +31,10 @@ async def register_panel(hass: HomeAssistant) -> None:
if DOMAIN not in hass.data.get("frontend_panels", {}):
path = locate_dir()
build_id = get_build_id()
hass.http.register_static_path(
URL_BASE, path, cache_headers=(build_id != "dev")
URL_BASE,
path,
cache_headers=not is_dev_build,
)
await panel_custom.async_register_panel(
hass=hass,
@@ -41,12 +42,13 @@ async def register_panel(hass: HomeAssistant) -> None:
webcomponent_name="knx-frontend",
sidebar_title=DOMAIN.upper(),
sidebar_icon="mdi:bus-electric",
module_url=f"{URL_BASE}/entrypoint-{build_id}.js",
module_url=f"{URL_BASE}/{entrypoint_js()}",
embed_iframe=True,
require_admin=True,
)
@websocket_api.require_admin
@websocket_api.websocket_command(
{
vol.Required("type"): "knx/info",
@@ -129,6 +131,7 @@ async def ws_project_file_remove(
connection.send_result(msg["id"])
@websocket_api.require_admin
@websocket_api.websocket_command(
{
vol.Required("type"): "knx/group_monitor_info",
@@ -155,6 +158,7 @@ def ws_group_monitor_info(
)
@websocket_api.require_admin
@websocket_api.websocket_command(
{
vol.Required("type"): "knx/subscribe_telegrams",
+11 -5
View File
@@ -3,7 +3,7 @@ from __future__ import annotations
from typing import Any
from pylast import LastFMNetwork, User, WSError
from pylast import LastFMNetwork, PyLastError, User, WSError
import voluptuous as vol
from homeassistant.config_entries import (
@@ -128,11 +128,14 @@ class LastFmConfigFlowHandler(ConfigFlow, domain=DOMAIN):
main_user, _ = get_lastfm_user(
self.data[CONF_API_KEY], self.data[CONF_MAIN_USER]
)
friends_response = await self.hass.async_add_executor_job(
main_user.get_friends
)
friends = [
SelectOptionDict(value=friend.name, label=friend.get_name(True))
for friend in main_user.get_friends()
for friend in friends_response
]
except WSError:
except PyLastError:
friends = []
return self.async_show_form(
step_id="friends",
@@ -197,11 +200,14 @@ class LastFmOptionsFlowHandler(OptionsFlowWithConfigEntry):
self.options[CONF_API_KEY],
self.options[CONF_MAIN_USER],
)
friends_response = await self.hass.async_add_executor_job(
main_user.get_friends
)
friends = [
SelectOptionDict(value=friend.name, label=friend.get_name(True))
for friend in main_user.get_friends()
for friend in friends_response
]
except WSError:
except PyLastError:
friends = []
else:
friends = []
+20 -16
View File
@@ -3,7 +3,7 @@ from __future__ import annotations
import hashlib
from pylast import LastFMNetwork, Track, User, WSError
from pylast import LastFMNetwork, PyLastError, Track, User
import voluptuous as vol
from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity
@@ -104,26 +104,30 @@ class LastFmSensor(SensorEntity):
def update(self) -> None:
"""Update device state."""
self._attr_native_value = STATE_NOT_SCROBBLING
try:
self._user.get_playcount()
except WSError as exc:
play_count = self._user.get_playcount()
self._attr_entity_picture = self._user.get_image()
now_playing = self._user.get_now_playing()
top_tracks = self._user.get_top_tracks(limit=1)
last_tracks = self._user.get_recent_tracks(limit=1)
except PyLastError as exc:
self._attr_available = False
LOGGER.error("Failed to load LastFM user `%s`: %r", self._user.name, exc)
return
self._attr_entity_picture = self._user.get_image()
if now_playing := self._user.get_now_playing():
self._attr_available = True
if now_playing:
self._attr_native_value = format_track(now_playing)
else:
self._attr_native_value = STATE_NOT_SCROBBLING
top_played = None
if top_tracks := self._user.get_top_tracks(limit=1):
top_played = format_track(top_tracks[0].item)
last_played = None
if last_tracks := self._user.get_recent_tracks(limit=1):
last_played = format_track(last_tracks[0].track)
play_count = self._user.get_playcount()
self._attr_extra_state_attributes = {
ATTR_LAST_PLAYED: last_played,
ATTR_PLAY_COUNT: play_count,
ATTR_TOP_PLAYED: top_played,
ATTR_LAST_PLAYED: None,
ATTR_TOP_PLAYED: None,
}
if len(last_tracks) > 0:
self._attr_extra_state_attributes[ATTR_LAST_PLAYED] = format_track(
last_tracks[0].track
)
if len(top_tracks) > 0:
self._attr_extra_state_attributes[ATTR_TOP_PLAYED] = format_track(
top_tracks[0].item
)
@@ -12,5 +12,5 @@
"integration_type": "hub",
"iot_class": "cloud_push",
"loggers": ["pylitterbot"],
"requirements": ["pylitterbot==2023.4.0"]
"requirements": ["pylitterbot==2023.4.2"]
}
@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/local_calendar",
"iot_class": "local_polling",
"loggers": ["ical"],
"requirements": ["ical==4.5.1"]
"requirements": ["ical==4.5.4"]
}
+1 -4
View File
@@ -13,7 +13,6 @@ from homeassistant.helpers.typing import ConfigType
from . import websocket_api
from .const import (
ATTR_LEVEL,
DEFAULT_LOGSEVERITY,
DOMAIN,
LOGGER_DEFAULT,
LOGGER_FILTERS,
@@ -39,9 +38,7 @@ CONFIG_SCHEMA = vol.Schema(
{
DOMAIN: vol.Schema(
{
vol.Optional(
LOGGER_DEFAULT, default=DEFAULT_LOGSEVERITY
): _VALID_LOG_LEVEL,
vol.Optional(LOGGER_DEFAULT): _VALID_LOG_LEVEL,
vol.Optional(LOGGER_LOGS): vol.Schema({cv.string: _VALID_LOG_LEVEL}),
vol.Optional(LOGGER_FILTERS): vol.Schema({cv.string: [cv.is_regex]}),
}
+1 -1
View File
@@ -119,7 +119,7 @@ class LoggerSettings:
self._yaml_config = yaml_config
self._default_level = logging.INFO
if DOMAIN in yaml_config:
if DOMAIN in yaml_config and LOGGER_DEFAULT in yaml_config[DOMAIN]:
self._default_level = yaml_config[DOMAIN][LOGGER_DEFAULT]
self._store: Store[dict[str, dict[str, dict[str, Any]]]] = Store(
hass, STORAGE_VERSION, STORAGE_KEY
+5 -2
View File
@@ -2,11 +2,12 @@
from __future__ import annotations
import asyncio
from contextlib import suppress
import async_timeout
from matter_server.client import MatterClient
from matter_server.client.exceptions import CannotConnect, InvalidServerVersion
from matter_server.common.errors import MatterError, NodeCommissionFailed
from matter_server.common.errors import MatterError, NodeCommissionFailed, NodeNotExists
import voluptuous as vol
from homeassistant.components.hassio import AddonError, AddonManager, AddonState
@@ -207,7 +208,9 @@ async def async_remove_config_entry_device(
)
matter = get_matter(hass)
await matter.matter_client.remove_node(node.node_id)
with suppress(NodeNotExists):
# ignore if the server has already removed the node.
await matter.matter_client.remove_node(node.node_id)
return True
+2 -2
View File
@@ -92,7 +92,7 @@ class MatterAdapter:
get_clean_name(basic_info.nodeLabel)
or get_clean_name(basic_info.productLabel)
or get_clean_name(basic_info.productName)
or device_type.__class__.__name__
or device_type.__name__
if device_type
else None
)
@@ -117,7 +117,7 @@ class MatterAdapter:
identifiers.add((DOMAIN, f"{ID_TYPE_SERIAL}_{basic_info.serialNumber}"))
model = (
get_clean_name(basic_info.productName) or device_type.__class__.__name__
get_clean_name(basic_info.productName) or device_type.__name__
if device_type
else None
)
@@ -6,5 +6,5 @@
"dependencies": ["websocket_api"],
"documentation": "https://www.home-assistant.io/integrations/matter",
"iot_class": "local_push",
"requirements": ["python-matter-server==3.4.1"]
"requirements": ["python-matter-server==3.5.1"]
}
@@ -38,7 +38,7 @@ def async_setup(hass: HomeAssistant) -> None:
class LocalSource(MediaSource):
"""Provide local directories as media sources."""
name: str = "Local Media"
name: str = "My media"
def __init__(self, hass: HomeAssistant) -> None:
"""Initialize local source."""
@@ -12,5 +12,5 @@
"dependencies": ["bluetooth_adapters"],
"documentation": "https://www.home-assistant.io/integrations/melnor",
"iot_class": "local_polling",
"requirements": ["melnor-bluetooth==0.0.22"]
"requirements": ["melnor-bluetooth==0.0.25"]
}
@@ -133,10 +133,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
await coordinator_alert.async_refresh()
if not coordinator_alert.last_update_success:
raise ConfigEntryNotReady
hass.data[DOMAIN][department] = True
if coordinator_alert.last_update_success:
hass.data[DOMAIN][department] = True
else:
_LOGGER.warning(
(
@@ -158,11 +156,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
undo_listener = entry.add_update_listener(_async_update_listener)
hass.data[DOMAIN][entry.entry_id] = {
UNDO_UPDATE_LISTENER: undo_listener,
COORDINATOR_FORECAST: coordinator_forecast,
COORDINATOR_RAIN: coordinator_rain,
COORDINATOR_ALERT: coordinator_alert,
UNDO_UPDATE_LISTENER: undo_listener,
}
if coordinator_alert and coordinator_alert.last_update_success:
hass.data[DOMAIN][entry.entry_id][COORDINATOR_ALERT] = coordinator_alert
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
@@ -5,5 +5,5 @@
"documentation": "https://www.home-assistant.io/integrations/noaa_tides",
"iot_class": "cloud_polling",
"loggers": ["noaa_coops"],
"requirements": ["noaa-coops==0.1.8"]
"requirements": ["noaa-coops==0.1.9"]
}
@@ -4,5 +4,5 @@
"codeowners": ["@joostlek"],
"documentation": "https://www.home-assistant.io/integrations/opensky",
"iot_class": "cloud_polling",
"requirements": ["python-opensky==0.0.7"]
"requirements": ["python-opensky==0.0.9"]
}
+1 -1
View File
@@ -78,7 +78,7 @@ def setup_platform(
latitude = config.get(CONF_LATITUDE, hass.config.latitude)
longitude = config.get(CONF_LONGITUDE, hass.config.longitude)
radius = config.get(CONF_RADIUS, 0)
bounding_box = OpenSky.get_bounding_box(latitude, longitude, radius)
bounding_box = OpenSky.get_bounding_box(latitude, longitude, radius * 1000)
session = async_get_clientsession(hass)
opensky = OpenSky(session=session)
add_entities(
+1 -1
View File
@@ -5,5 +5,5 @@
"documentation": "https://www.home-assistant.io/integrations/opple",
"iot_class": "local_polling",
"loggers": ["pyoppleio"],
"requirements": ["pyoppleio==1.0.5"]
"requirements": ["pyoppleio-legacy==1.0.8"]
}
+1 -1
View File
@@ -8,5 +8,5 @@
"documentation": "https://www.home-assistant.io/integrations/otbr",
"integration_type": "service",
"iot_class": "local_polling",
"requirements": ["python-otbr-api==2.1.0"]
"requirements": ["python-otbr-api==2.2.0"]
}
+5
View File
@@ -95,6 +95,11 @@ class OTBRData:
"""Create an active operational dataset."""
return await self.api.create_active_dataset(dataset)
@_handle_otbr_error
async def delete_active_dataset(self) -> None:
"""Delete the active operational dataset."""
return await self.api.delete_active_dataset()
@_handle_otbr_error
async def set_active_dataset_tlvs(self, dataset: bytes) -> None:
"""Set current active operational dataset in TLVS format."""
@@ -81,6 +81,12 @@ async def websocket_create_network(
connection.send_error(msg["id"], "set_enabled_failed", str(exc))
return
try:
await data.delete_active_dataset()
except HomeAssistantError as exc:
connection.send_error(msg["id"], "delete_active_dataset_failed", str(exc))
return
try:
await data.create_active_dataset(
python_otbr_api.ActiveDataSet(
@@ -13,7 +13,7 @@
"integration_type": "hub",
"iot_class": "cloud_polling",
"loggers": ["boto3", "botocore", "pyhumps", "pyoverkiz", "s3transfer"],
"requirements": ["pyoverkiz==1.7.9"],
"requirements": ["pyoverkiz==1.8.0"],
"zeroconf": [
{
"type": "_kizbox._tcp.local.",
@@ -29,8 +29,6 @@ ATTR_NOTIFICATION_ID: Final = "notification_id"
ATTR_TITLE: Final = "title"
ATTR_STATUS: Final = "status"
STATUS_UNREAD = "unread"
STATUS_READ = "read"
# Remove EVENT_PERSISTENT_NOTIFICATIONS_UPDATED in Home Assistant 2023.9
EVENT_PERSISTENT_NOTIFICATIONS_UPDATED = "persistent_notifications_updated"
@@ -43,7 +41,6 @@ class Notification(TypedDict):
message: str
notification_id: str
title: str | None
status: str
class UpdateType(StrEnum):
@@ -98,7 +95,6 @@ def async_create(
notifications[notification_id] = {
ATTR_MESSAGE: message,
ATTR_NOTIFICATION_ID: notification_id,
ATTR_STATUS: STATUS_UNREAD,
ATTR_TITLE: title,
ATTR_CREATED_AT: dt_util.utcnow(),
}
@@ -135,7 +131,6 @@ def async_dismiss(hass: HomeAssistant, notification_id: str) -> None:
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the persistent notification component."""
notifications = _async_get_or_create_notifications(hass)
@callback
def create_service(call: ServiceCall) -> None:
@@ -152,29 +147,6 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Handle the dismiss notification service call."""
async_dismiss(hass, call.data[ATTR_NOTIFICATION_ID])
@callback
def mark_read_service(call: ServiceCall) -> None:
"""Handle the mark_read notification service call."""
notification_id = call.data.get(ATTR_NOTIFICATION_ID)
if notification_id not in notifications:
_LOGGER.error(
(
"Marking persistent_notification read failed: "
"Notification ID %s not found"
),
notification_id,
)
return
notification = notifications[notification_id]
notification[ATTR_STATUS] = STATUS_READ
async_dispatcher_send(
hass,
SIGNAL_PERSISTENT_NOTIFICATIONS_UPDATED,
UpdateType.UPDATED,
{notification_id: notification},
)
hass.services.async_register(
DOMAIN,
"create",
@@ -192,10 +164,6 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
DOMAIN, "dismiss", dismiss_service, SCHEMA_SERVICE_NOTIFICATION
)
hass.services.async_register(
DOMAIN, "mark_read", mark_read_service, SCHEMA_SERVICE_NOTIFICATION
)
websocket_api.async_register_command(hass, websocket_get_notifications)
websocket_api.async_register_command(hass, websocket_subscribe_notifications)
@@ -33,15 +33,3 @@ dismiss:
example: 1234
selector:
text:
mark_read:
name: Mark read
description: Mark a notification read.
fields:
notification_id:
name: Notification ID
description: Target ID of the notification, which should be mark read.
required: true
example: 1234
selector:
text:
@@ -4,5 +4,5 @@
"codeowners": [],
"documentation": "https://www.home-assistant.io/integrations/pulseaudio_loopback",
"iot_class": "local_polling",
"requirements": ["pulsectl==20.2.4"]
"requirements": ["pulsectl==23.5.2"]
}
@@ -10,7 +10,7 @@
"integration_type": "device",
"iot_class": "local_polling",
"loggers": ["regenmaschine"],
"requirements": ["regenmaschine==2023.05.1"],
"requirements": ["regenmaschine==2023.06.0"],
"zeroconf": [
{
"type": "_http._tcp.local.",
@@ -16,5 +16,5 @@
"dependencies": ["bluetooth_adapters"],
"documentation": "https://www.home-assistant.io/integrations/rapt_ble",
"iot_class": "local_push",
"requirements": ["rapt-ble==0.1.1"]
"requirements": ["rapt-ble==0.1.2"]
}
@@ -0,0 +1,38 @@
"""Support for the Airzone diagnostics."""
from __future__ import annotations
from typing import Any
from homeassistant.components.diagnostics.util import async_redact_data
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_UNIQUE_ID
from homeassistant.core import HomeAssistant
from .const import DOMAIN
from .coordinator import RoborockDataUpdateCoordinator
TO_REDACT_CONFIG = ["token", "sn", "rruid", CONF_UNIQUE_ID, "username", "uid"]
TO_REDACT_COORD = ["duid", "localKey", "mac", "bssid"]
async def async_get_config_entry_diagnostics(
hass: HomeAssistant, config_entry: ConfigEntry
) -> dict[str, Any]:
"""Return diagnostics for a config entry."""
coordinators: dict[str, RoborockDataUpdateCoordinator] = hass.data[DOMAIN][
config_entry.entry_id
]
return {
"config_entry": async_redact_data(config_entry.data, TO_REDACT_CONFIG),
"coordinators": {
f"**REDACTED-{i}**": {
"roborock_device_info": async_redact_data(
coordinator.roborock_device_info.as_dict(), TO_REDACT_COORD
),
"api": coordinator.api.diagnostic_data,
}
for i, coordinator in enumerate(coordinators.values())
},
}
@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/roborock",
"iot_class": "local_polling",
"loggers": ["roborock"],
"requirements": ["python-roborock==0.17.0"]
"requirements": ["python-roborock==0.23.4"]
}
@@ -1,5 +1,6 @@
"""Roborock Models."""
from dataclasses import dataclass
from typing import Any
from roborock.containers import HomeDataDevice, HomeDataProduct, NetworkInfo
from roborock.roborock_typing import DeviceProp
@@ -13,3 +14,12 @@ class RoborockHassDeviceInfo:
network_info: NetworkInfo
product: HomeDataProduct
props: DeviceProp
def as_dict(self) -> dict[str, dict[str, Any]]:
"""Turn RoborockHassDeviceInfo into a dictionary."""
return {
"device": self.device.as_dict(),
"network_info": self.network_info.as_dict(),
"product": self.product.as_dict(),
"props": self.props.as_dict(),
}
@@ -90,8 +90,11 @@
"name": "Mop intensity",
"state": {
"off": "Off",
"low": "Low",
"mild": "Mild",
"medium": "Medium",
"moderate": "Moderate",
"high": "High",
"intense": "Intense",
"custom": "Custom"
}
+4 -7
View File
@@ -22,13 +22,11 @@ PLATFORMS = [
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Roku from a config entry."""
hass.data.setdefault(DOMAIN, {})
if not (coordinator := hass.data[DOMAIN].get(entry.entry_id)):
coordinator = RokuDataUpdateCoordinator(hass, host=entry.data[CONF_HOST])
hass.data[DOMAIN][entry.entry_id] = coordinator
coordinator = RokuDataUpdateCoordinator(hass, host=entry.data[CONF_HOST])
await coordinator.async_config_entry_first_refresh()
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
@@ -36,7 +34,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok:
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok
@@ -5,5 +5,5 @@
"documentation": "https://www.home-assistant.io/integrations/russound_rio",
"iot_class": "local_push",
"loggers": ["russound_rio"],
"requirements": ["russound_rio==0.1.8"]
"requirements": ["russound-rio==1.0.0"]
}
@@ -535,7 +535,10 @@ class ShellyRpcCoordinator(ShellyCoordinatorBase[RpcDevice]):
async def shutdown(self) -> None:
"""Shutdown the coordinator."""
if self.device.connected:
await async_stop_scanner(self.device)
try:
await async_stop_scanner(self.device)
except InvalidAuthError:
self.entry.async_start_reauth(self.hass)
await self.device.shutdown()
await self._async_disconnected()
@@ -5,5 +5,5 @@
"documentation": "https://www.home-assistant.io/integrations/sisyphus",
"iot_class": "local_push",
"loggers": ["sisyphus_control"],
"requirements": ["sisyphus-control==3.1.2"]
"requirements": ["sisyphus-control==3.1.3"]
}
@@ -7,6 +7,6 @@
"documentation": "https://www.home-assistant.io/integrations/thread",
"integration_type": "service",
"iot_class": "local_polling",
"requirements": ["python-otbr-api==2.1.0", "pyroute2==0.7.5"],
"requirements": ["python-otbr-api==2.2.0", "pyroute2==0.7.5"],
"zeroconf": ["_meshcop._udp.local."]
}
@@ -81,15 +81,9 @@ class TotalConnectZoneSecurityBinarySensor(TotalConnectZoneBinarySensor):
return BinarySensorDeviceClass.MOTION
if self._zone.is_type_medical():
return BinarySensorDeviceClass.SAFETY
# "security" type is a generic category so test for it last
if self._zone.is_type_security():
return BinarySensorDeviceClass.DOOR
_LOGGER.error(
"TotalConnect zone %s reported an unexpected device class",
self._zone.zoneid,
)
return None
if self._zone.is_type_temperature():
return BinarySensorDeviceClass.PROBLEM
return BinarySensorDeviceClass.DOOR
def update(self):
"""Return the state of the device."""
+1 -1
View File
@@ -8,7 +8,7 @@
"iot_class": "local_push",
"loggers": ["aiounifi"],
"quality_scale": "platinum",
"requirements": ["aiounifi==47"],
"requirements": ["aiounifi==48"],
"ssdp": [
{
"manufacturer": "Ubiquiti Networks",
@@ -14,8 +14,6 @@ from pyunifiprotect.data import (
ProtectAdoptableDeviceModel,
ProtectModelWithId,
Sensor,
SmartDetectAudioType,
SmartDetectObjectType,
)
from pyunifiprotect.data.nvr import UOSDisk
@@ -364,8 +362,7 @@ EVENT_SENSORS: tuple[ProtectBinaryEventEntityDescription, ...] = (
ufp_value="is_smart_detected",
ufp_required_field="can_detect_person",
ufp_enabled="is_person_detection_on",
ufp_event_obj="last_smart_detect_event",
ufp_smart_type=SmartDetectObjectType.PERSON,
ufp_event_obj="last_person_detect_event",
),
ProtectBinaryEventEntityDescription(
key="smart_obj_vehicle",
@@ -374,8 +371,7 @@ EVENT_SENSORS: tuple[ProtectBinaryEventEntityDescription, ...] = (
ufp_value="is_smart_detected",
ufp_required_field="can_detect_vehicle",
ufp_enabled="is_vehicle_detection_on",
ufp_event_obj="last_smart_detect_event",
ufp_smart_type=SmartDetectObjectType.VEHICLE,
ufp_event_obj="last_vehicle_detect_event",
),
ProtectBinaryEventEntityDescription(
key="smart_obj_face",
@@ -384,8 +380,7 @@ EVENT_SENSORS: tuple[ProtectBinaryEventEntityDescription, ...] = (
ufp_value="is_smart_detected",
ufp_required_field="can_detect_face",
ufp_enabled="is_face_detection_on",
ufp_event_obj="last_smart_detect_event",
ufp_smart_type=SmartDetectObjectType.FACE,
ufp_event_obj="last_face_detect_event",
),
ProtectBinaryEventEntityDescription(
key="smart_obj_package",
@@ -394,8 +389,7 @@ EVENT_SENSORS: tuple[ProtectBinaryEventEntityDescription, ...] = (
ufp_value="is_smart_detected",
ufp_required_field="can_detect_package",
ufp_enabled="is_package_detection_on",
ufp_event_obj="last_smart_detect_event",
ufp_smart_type=SmartDetectObjectType.PACKAGE,
ufp_event_obj="last_package_detect_event",
),
ProtectBinaryEventEntityDescription(
key="smart_audio_any",
@@ -412,8 +406,7 @@ EVENT_SENSORS: tuple[ProtectBinaryEventEntityDescription, ...] = (
ufp_value="is_smart_detected",
ufp_required_field="can_detect_smoke",
ufp_enabled="is_smoke_detection_on",
ufp_event_obj="last_smart_audio_detect_event",
ufp_smart_type=SmartDetectAudioType.SMOKE,
ufp_event_obj="last_smoke_detect_event",
),
ProtectBinaryEventEntityDescription(
key="smart_audio_cmonx",
@@ -422,8 +415,7 @@ EVENT_SENSORS: tuple[ProtectBinaryEventEntityDescription, ...] = (
ufp_value="is_smart_detected",
ufp_required_field="can_detect_smoke",
ufp_enabled="is_smoke_detection_on",
ufp_event_obj="last_smart_audio_detect_event",
ufp_smart_type=SmartDetectAudioType.CMONX,
ufp_event_obj="last_cmonx_detect_event",
),
)
@@ -40,6 +40,11 @@ from .utils import async_dispatch_id as _ufpd, async_get_devices_by_type
_LOGGER = logging.getLogger(__name__)
ProtectDeviceType = ProtectAdoptableDeviceModel | NVR
SMART_EVENTS = {
EventType.SMART_DETECT,
EventType.SMART_AUDIO_DETECT,
EventType.SMART_DETECT_LINE,
}
@callback
@@ -223,6 +228,25 @@ class ProtectData:
# trigger updates for camera that the event references
elif isinstance(obj, Event):
if obj.type in SMART_EVENTS:
if obj.camera is not None:
if obj.end is None:
_LOGGER.debug(
"%s (%s): New smart detection started for %s (%s)",
obj.camera.name,
obj.camera.mac,
obj.smart_detect_types,
obj.id,
)
else:
_LOGGER.debug(
"%s (%s): Smart detection ended for %s (%s)",
obj.camera.name,
obj.camera.mac,
obj.smart_detect_types,
obj.id,
)
if obj.type == EventType.DEVICE_ADOPTED:
if obj.metadata is not None and obj.metadata.device_id is not None:
device = self.api.bootstrap.get_device_from_id(
@@ -41,7 +41,7 @@
"iot_class": "local_push",
"loggers": ["pyunifiprotect", "unifi_discovery"],
"quality_scale": "platinum",
"requirements": ["pyunifiprotect==4.9.1", "unifi-discovery==1.1.7"],
"requirements": ["pyunifiprotect==4.10.3", "unifi-discovery==1.1.7"],
"ssdp": [
{
"manufacturer": "Ubiquiti Networks",
+17 -17
View File
@@ -3,6 +3,7 @@ from __future__ import annotations
from collections.abc import Callable, Coroutine
from dataclasses import dataclass
from datetime import timedelta
from enum import Enum
import logging
from typing import Any, Generic, TypeVar, cast
@@ -10,6 +11,7 @@ from typing import Any, Generic, TypeVar, cast
from pyunifiprotect.data import NVR, Event, ProtectAdoptableDeviceModel
from homeassistant.helpers.entity import EntityDescription
from homeassistant.util import dt as dt_util
from .utils import get_nested_attr
@@ -67,7 +69,6 @@ class ProtectEventMixin(ProtectRequiredKeysMixin[T]):
"""Mixin for events."""
ufp_event_obj: str | None = None
ufp_smart_type: str | None = None
def get_event_obj(self, obj: T) -> Event | None:
"""Return value from UniFi Protect device."""
@@ -79,23 +80,22 @@ class ProtectEventMixin(ProtectRequiredKeysMixin[T]):
def get_is_on(self, obj: T) -> bool:
"""Return value if event is active."""
value = bool(self.get_ufp_value(obj))
if value:
event = self.get_event_obj(obj)
value = event is not None
if not value:
_LOGGER.debug("%s (%s): missing event", self.name, obj.mac)
event = self.get_event_obj(obj)
if event is None:
return False
if event is not None and self.ufp_smart_type is not None:
value = self.ufp_smart_type in event.smart_detect_types
if not value:
_LOGGER.debug(
"%s (%s): %s not in %s",
self.name,
obj.mac,
self.ufp_smart_type,
event.smart_detect_types,
)
now = dt_util.utcnow()
value = now > event.start
if value and event.end is not None and now > event.end:
value = False
# only log if the recent ended recently
if event.end + timedelta(seconds=10) < now:
_LOGGER.debug(
"%s (%s): end ended at %s",
self.name,
obj.mac,
event.end.isoformat(),
)
if value:
_LOGGER.debug("%s (%s): value is on", self.name, obj.mac)
@@ -15,7 +15,6 @@ from pyunifiprotect.data import (
ProtectDeviceModel,
ProtectModelWithId,
Sensor,
SmartDetectObjectType,
)
from homeassistant.components.sensor import (
@@ -528,10 +527,9 @@ EVENT_SENSORS: tuple[ProtectSensorEventEntityDescription, ...] = (
name="License Plate Detected",
icon="mdi:car",
translation_key="license_plate",
ufp_smart_type=SmartDetectObjectType.LICENSE_PLATE,
ufp_value="is_smart_detected",
ufp_required_field="can_detect_license_plate",
ufp_event_obj="last_smart_detect_event",
ufp_event_obj="last_license_plate_detect_event",
),
)
@@ -767,8 +765,7 @@ class ProtectEventSensor(EventEntityMixin, SensorEntity):
EventEntityMixin._async_update_device_from_protect(self, device)
is_on = self.entity_description.get_is_on(device)
is_license_plate = (
self.entity_description.ufp_smart_type
== SmartDetectObjectType.LICENSE_PLATE
self.entity_description.ufp_event_obj == "last_license_plate_detect_event"
)
if (
not is_on

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