Compare commits

..

122 Commits

Author SHA1 Message Date
Franck Nijhof
7aa14e20d1 2024.2.2 (#110720) 2024-02-16 15:47:38 +01:00
Franck Nijhof
b55b2c8da3 Bump version to 2024.2.2 2024-02-16 14:13:26 +01:00
Robert Resch
8c05ebd031 Bump deebot-client to 5.2.1 (#110683)
* Bump deebot-client to 5.2.0

* Bumb again

* Fix tests
2024-02-16 14:13:18 +01:00
Robert Svensson
34a3e88e0d Bump aiounifi to v71 (#110658) 2024-02-16 14:13:15 +01:00
J. Nick Koston
bf002ac0b0 Fix elkm1 service calls running in the executor (#110655)
fixes
```
  File "/usr/src/homeassistant/homeassistant/components/elkm1/__init__.py", line 416, in _set_time_service
    _getelk(service).panel.set_time(dt_util.now())
  File "/usr/local/lib/python3.11/site-packages/elkm1_lib/panel.py", line 55, in set_time
    self._connection.send(rw_encode(datetime))
  File "/usr/local/lib/python3.11/site-packages/elkm1_lib/connection.py", line 152, in send
    self._send(QueuedWrite(msg.message, msg.response_command), priority_send)
  File "/usr/local/lib/python3.11/site-packages/elkm1_lib/connection.py", line 148, in _send
    self._check_write_queue.set()
  File "/usr/local/lib/python3.11/asyncio/locks.py", line 192, in set
    fut.set_result(True)
  File "/usr/local/lib/python3.11/asyncio/base_events.py", line 763, in call_soon
    self._check_thread()
  File "/usr/local/lib/python3.11/asyncio/base_events.py", line 800, in _check_thread
    raise RuntimeError(
RuntimeError: Non-thread-safe operation invoked on an event loop other than the current one
```
2024-02-16 14:13:12 +01:00
jan iversen
6f529a2c77 Modbus, allow received int to be a float. (#110648) 2024-02-16 14:13:09 +01:00
J. Nick Koston
e5db7278e1 Fix tplink not updating IP from DHCP discovery and discovering twice (#110557)
We only called format_mac on the mac address if we connected
to the device during entry creation. Since the format of the
mac address from DHCP discovery did not match the format saved
in the unique id, the IP would not get updated and a second
discovery would appear

Thankfully the creation path does format the mac so we did not
create any entries with an inconsistantly formatted unique id

fixes #110460
2024-02-16 14:13:05 +01:00
J. Nick Koston
cdf67e9bb5 Bump orjson to 3.9.14 (#110552)
changelog: https://github.com/ijl/orjson/compare/3.9.13...3.9.14

fixes a crasher due to buffer overread (was only partially fixed in 3.9.13)
2024-02-16 14:13:03 +01:00
G Johansson
393359a546 Coerce to float in Sensibo climate react custom service (#110508) 2024-02-16 14:12:59 +01:00
Stefan Agner
9309e38302 Fix Raspberry Pi utilities installation on Alpine 3.19 (#110463) 2024-02-16 14:12:56 +01:00
wilburCforce
479ecc8b94 Update pylutron to 0.2.12 (#110414) 2024-02-16 14:12:53 +01:00
wilburCforce
ec7950aeda Update pylutron to 0.2.11 (#109853) 2024-02-16 14:12:48 +01:00
Robert Hillis
c763483049 Mitigate session closed error in Netgear LTE (#110412) 2024-02-16 14:10:34 +01:00
Steven Looman
fe84e7a576 Bump async-upnp-client to 0.38.2 (#110411) 2024-02-16 14:10:31 +01:00
Michael
5ba31290b8 Bump py-sucks to 0.9.9 (#110397)
bump py-sucks to 0.9.9
2024-02-16 14:10:28 +01:00
J. Nick Koston
de619e4ddc Fix zone radius calculation when radius is not 0 (#110354) 2024-02-16 14:10:25 +01:00
Nikolay Vasilchuk
56ceadaeeb Fix Starline GPS count sensor (#110348) 2024-02-16 14:10:22 +01:00
IceBotYT
da61564f82 Bump linear-garage-door to 0.2.9 (#110298) 2024-02-16 14:10:19 +01:00
starkillerOG
003673cd29 Fix TDBU naming in Motionblinds (#110283)
fix TDBU naming
2024-02-16 14:10:16 +01:00
Matthias Alphart
da6c571e65 Update xknxproject to 3.6.0 (#110282) 2024-02-16 14:10:13 +01:00
J. Nick Koston
159fab7025 Bump PySwitchbot to 0.45.0 (#110275) 2024-02-16 14:10:10 +01:00
Michael
96a10e76b8 Bump aiopegelonline to 0.0.8 (#110274) 2024-02-16 14:10:08 +01:00
G Johansson
e7068ae134 Fix cpu percentage in System Monitor (#110268)
* Fix cpu percentage in System Monitor

* Tests
2024-02-16 14:10:05 +01:00
Jan-Philipp Benecke
6a0c3f1b4f Handle no data error in Electricity Maps config flow (#110259)
Co-authored-by: Viktor Andersson <30777521+VIKTORVAV99@users.noreply.github.com>
2024-02-16 14:10:02 +01:00
Simon Lamon
a0ae18a1b6 Fix state classes issue translation in Group (#110238)
Fix state classes translation
2024-02-16 14:09:59 +01:00
David Bonnes
ad761bb2de Bump evohome-async to 0.4.19 (#110225)
bump client to 0.4.19
2024-02-16 14:09:56 +01:00
DustyArmstrong
edb69fb095 Bump datapoint to 0.9.9 + re-enable Met Office Integration (#110206) 2024-02-16 14:09:53 +01:00
Simon Lamon
58b28e6df1 Fix device class repairs issues placeholders in Group (#110181)
fix translation placeholders
2024-02-16 14:09:50 +01:00
Adam Goode
973a13abfa Properly report cover positions to prometheus (#110157) 2024-02-16 14:09:47 +01:00
J. Nick Koston
2a51377cef Bump yalexs to 1.11.2 (#110144)
changelog: https://github.com/bdraco/yalexs/compare/v1.11.1...v1.11.2
2024-02-16 14:09:44 +01:00
J. Nick Koston
87bd67656b Only schedule august activity update when a new activity is seen (#110141) 2024-02-16 14:09:41 +01:00
Maciej Bieniek
c79bc17d17 Fix typo in sensor icons configuration (#110133)
Co-authored-by: Maciej Bieniek <478555+bieniu@users.noreply.github.com>
2024-02-16 14:09:38 +01:00
A Björck
54270df217 Bump yalexs to 1.11.1, fixing camera snapshots from Yale Home (#110089) 2024-02-16 14:09:35 +01:00
Malte Franken
5a87cde71e Bump aio-geojson-usgs-earthquakes to 0.3 (#110084) 2024-02-16 14:09:32 +01:00
Christophe Gagnier
e825bcc282 Update pytechnove to 1.2.2 (#110074) 2024-02-16 14:09:29 +01:00
Aurélien Grenotton
b54a3170f0 Fix freebox pairing in bridge mode (#106131) 2024-02-16 14:09:25 +01:00
Luke Lashley
349d8f5c28 Better teardown and setup of Roborock connections (#106092)
Co-authored-by: Robert Resch <robert@resch.dev>
2024-02-16 14:08:20 +01:00
Franck Nijhof
cfd1f7809f 2024.2.1 (#110078) 2024-02-09 11:04:19 +01:00
Erik Montnemery
5f9cc2fec1 Prevent network access in emulated_hue tests (#109991) 2024-02-09 10:16:49 +01:00
Franck Nijhof
58d46f6dec Bump version to 2024.2.1 2024-02-09 09:02:01 +01:00
Brandon Rothweiler
74ea9e24df Bump py-aosmith to 1.0.8 (#110061) 2024-02-09 09:01:49 +01:00
David Bonnes
437a2a829f Bump evohome-async to 0.4.18 (#110056) 2024-02-09 09:01:46 +01:00
Michael Hansen
f5884c6279 Matching duplicate named entities is now an error in Assist (#110050)
* Matching duplicate named entities is now an error

* Update snapshot

* Only use area id
2024-02-09 09:01:43 +01:00
Michael
e4382a494c Log error and continue on parsing issues of translated strings (#110046) 2024-02-09 09:00:19 +01:00
Bram Kragten
56ff767969 Update frontend to 20240207.1 (#110039) 2024-02-09 09:00:17 +01:00
jan iversen
4a18f592c6 Avoid key_error in modbus climate with non-defined fan_mode. (#110017) 2024-02-09 09:00:14 +01:00
Robert Resch
7ff2f376d4 Bump aioecowitt to 2024.2.1 (#109999) 2024-02-09 09:00:10 +01:00
jan iversen
a18918bb73 Allow modbus negative min/max value. (#109995) 2024-02-09 09:00:06 +01:00
Robert Resch
49e5709826 Bump deebot-client to 5.1.1 (#109994) 2024-02-09 09:00:00 +01:00
jan iversen
c665903f9d Allow modbus min/max temperature to be negative. (#109977) 2024-02-09 08:59:58 +01:00
spycle
de44af2948 Bump pyMicrobot to 0.0.12 (#109970) 2024-02-09 08:59:55 +01:00
Erik Montnemery
95a800b6bc Don't blow up if config entries have unhashable unique IDs (#109966)
* Don't blow up if config entries have unhashable unique IDs

* Add test

* Add comment on when we remove the guard

* Don't stringify hashable non string unique_id
2024-02-09 08:59:52 +01:00
jan iversen
a9e9ec2c3d Allow modbus "scale" to be negative. (#109965) 2024-02-09 08:59:49 +01:00
Marcel van der Veldt
7309c3c290 Handle Matter nodes that become available after startup is done (#109956) 2024-02-09 08:59:46 +01:00
Malte Franken
f48d70654b Bump aio-geojson-geonetnz-volcano to 0.9 (#109940) 2024-02-09 08:59:43 +01:00
Marcel van der Veldt
a9b3c2e2b5 Skip polling of unavailable Matter nodes (#109917) 2024-02-09 08:59:41 +01:00
Jan-Philipp Benecke
19349e1779 Bump aioelectricitymaps to 0.4.0 (#109895) 2024-02-09 08:59:38 +01:00
Marcel van der Veldt
e320d715c7 Bump Python matter server to 5.5.0 (#109894) 2024-02-09 08:59:35 +01:00
Michael Hansen
44c9ea68eb Assist fixes (#109889)
* Don't pass entity ids in hassil slot lists

* Use first completed response

* Add more tests
2024-02-09 08:59:32 +01:00
Mike Degatano
dbfee24eb7 Allow disabling home assistant watchdog (#109818) 2024-02-09 08:59:27 +01:00
mkmer
3b7271d597 Catch APIRateLimit in Honeywell (#107806) 2024-02-09 08:58:44 +01:00
Franck Nijhof
9dbf84228e 2024.2.0 (#109883) 2024-02-07 18:31:28 +01:00
Joost Lekkerkerker
9e47d03086 Fix kitchen sink tests (#109243) 2024-02-07 17:40:10 +01:00
Franck Nijhof
f63aaf8b5a Bump version to 2024.2.0 2024-02-07 16:28:11 +01:00
Malte Franken
8375fc235d Bump aio-geojson-geonetnz-quakes to 0.16 (#109873) 2024-02-07 16:27:47 +01:00
Åke Strandberg
3030870de0 Remove soft hyphens from myuplink sensor names (#109845)
Remove soft hyphens from sensor names
2024-02-07 16:27:44 +01:00
Matrix
f61c70b686 Fix YoLink SpeakerHub support (#107925)
* improve

* Fix when hub offline/online message pushing

* fix as suggestion

* check config entry load state

* Add exception translation
2024-02-07 16:27:40 +01:00
Franck Nijhof
e720b398d2 Bump version to 2024.2.0b11 2024-02-07 12:44:50 +01:00
Bram Kragten
bd21490a57 Update frontend to 20240207.0 (#109871) 2024-02-07 12:34:36 +01:00
Malte Franken
75b308c1aa Bump aio-georss-gdacs to 0.9 (#109859) 2024-02-07 12:34:33 +01:00
Joakim Plate
881707e1fe Update nibe to 2.8.0 with LOG.SET fixes (#109825)
Update nibe to 2.8.0
2024-02-07 12:34:30 +01:00
Jiayi Chen
2ca3bbaea5 Update Growatt server URLs (#109122) 2024-02-07 12:34:24 +01:00
Franck Nijhof
ea4bdbb3a0 Bump version to 2024.2.0b10 2024-02-07 08:48:17 +01:00
puddly
40cfc31dcb Bump ZHA dependency zigpy to 0.62.3 (#109848) 2024-02-07 08:48:07 +01:00
starkillerOG
031aadff00 Bump motionblinds to 0.6.20 (#109837) 2024-02-07 08:48:04 +01:00
Vilppu Vuorinen
27691b7d48 Disable energy report based operations with API lib upgrade (#109832)
Co-authored-by: Franck Nijhof <git@frenck.dev>
2024-02-07 08:48:01 +01:00
Joost Lekkerkerker
fe94107af7 Make integration fields in Analytics Insights optional (#109789) 2024-02-07 08:47:58 +01:00
Teemu R
d784a76d32 Add tapo virtual integration (#109765) 2024-02-07 08:47:55 +01:00
Joost Lekkerkerker
ebb1912617 Show domain in oauth2 error log (#109708)
* Show token url in oauth2 error log

* Fix tests

* Use domain
2024-02-07 08:47:50 +01:00
Franck Nijhof
8c605c29c3 Bump version to 2024.2.0b9 2024-02-06 22:49:53 +01:00
Joakim Sørensen
74a75e709f Bump awesomeversion from 23.11.0 to 24.2.0 (#109830) 2024-02-06 22:49:41 +01:00
J. Nick Koston
2103875ff7 Bump aioesphomeapi to 21.0.2 (#109824) 2024-02-06 22:49:38 +01:00
Erik Montnemery
5c83b774bb Bump python-otbr-api to 2.6.0 (#109823) 2024-02-06 22:49:34 +01:00
Joost Lekkerkerker
2c870f9da9 Bump aioecowitt to 2024.2.0 (#109817) 2024-02-06 22:49:31 +01:00
Maciej Bieniek
40adb3809f Ignore trackable without details in Tractive integration (#109814)
Co-authored-by: Maciej Bieniek <478555+bieniu@users.noreply.github.com>
2024-02-06 22:49:28 +01:00
wittypluck
8aa1242221 Mark Unifi bandwidth sensors as unavailable when client disconnects (#109812)
* Set sensor as unavailable instead of resetting value to 0 on disconnect

* Update unit test on unavailable bandwidth sensor
2024-02-06 22:49:25 +01:00
J. Nick Koston
8569ddc5f9 Fix entity services targeting entities outside the platform when using areas/devices (#109810) 2024-02-06 22:49:22 +01:00
Franck Nijhof
7032415528 Don't block Supervisor entry setup with refreshing updates (#109809) 2024-02-06 22:49:19 +01:00
puddly
d099fb2a26 Pin chacha20poly1305-reuseable>=0.12.1 (#109807)
* Pin `chacha20poly1305-reuseable`
Prevents a runtime `assert isinstance(cipher, AESGCM)` error

* Update `gen_requirements_all.py` as well
2024-02-06 22:49:16 +01:00
Jan-Philipp Benecke
35fad52913 Bump aioelectricitymaps to 0.3.1 (#109797) 2024-02-06 22:49:13 +01:00
Vilppu Vuorinen
c170132827 Update MELCloud codeowners (#109793)
Co-authored-by: Franck Nijhof <git@frenck.dev>
2024-02-06 22:49:10 +01:00
Matthias Alphart
439f82a4ec Update xknx to 2.12.0 and xknxproject to 3.5.0 (#109787) 2024-02-06 22:49:07 +01:00
Steven B
2481d14632 Bump ring_doorbell to 0.8.7 (#109783) 2024-02-06 22:49:04 +01:00
Steven B
3cf826dc93 Bump ring_doorbell to 0.8.6 (#109199) 2024-02-06 22:48:59 +01:00
Jan-Philipp Benecke
e25ddf9650 Change state class of Tesla wall connector session energy entity (#109778) 2024-02-06 22:46:34 +01:00
puddly
8d79ac67f5 Bump ZHA dependencies (#109770)
* Bump ZHA dependencies

* Bump universal-silabs-flasher to 0.0.18

* Flip `Server_to_Client` enum in ZHA unit test

* Bump zigpy to 0.62.2
2024-02-06 22:46:31 +01:00
David F. Mulcahey
5025c15165 Buffer JsonDecodeError in Flo (#109767) 2024-02-06 22:46:28 +01:00
Joost Lekkerkerker
ffd5e04a29 Fix Radarr health check singularity (#109762)
* Fix Radarr health check singularity

* Fix comment
2024-02-06 22:46:25 +01:00
G Johansson
9fcdfd1b16 Bump holidays to 0.42 (#109760) 2024-02-06 22:46:21 +01:00
Vilppu Vuorinen
c1e5b2e6cc Fix compatibility issues with older pymelcloud version (#109757) 2024-02-06 22:42:58 +01:00
suaveolent
31c0d21204 Improve lupusec code quality (#109727)
* renamed async_add_devices

* fixed typo

* patch class instead of __init__

* ensure non blocking get_alarm

* exception handling

* added test case for json decode error

* avoid blockign calls

---------

Co-authored-by: suaveolent <suaveolent@users.noreply.github.com>
2024-02-06 22:42:54 +01:00
spycle
3ba63fc78f Fix keymitt_ble config-flow (#109644)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2024-02-06 22:42:51 +01:00
spycle
0395315267 Bump pyMicrobot to 0.0.10 (#109628) 2024-02-06 22:42:48 +01:00
TheJulianJES
6b354457c2 Fix ZHA creating unnecessary "summ received" entity after upgrade (#109268)
* Do not create `current_summ_received` entity until initialized once

* Update zha_devices_list.py to not expect summation received entities

The attribute isn't initialized for these devices in the test (which our check now expects it to be), hence we need to remove them from this list.

* Update sensor tests to have initial state for current_summ_received entity

The attribute needs to be initialized for it to be created which we do by plugging the attribute read.
The test expects the initial state to be "unknown", but hence we plugged the attribute (to create the entity), the state is whatever we plug the attribute read as.

* Update sensor tests to expect not updating current_summ_received entity if it doesn't exist
2024-02-06 22:42:43 +01:00
Franck Nijhof
df88335370 Bump version to 2024.2.0b8 2024-02-05 20:27:40 +01:00
Joost Lekkerkerker
4c6c5ee63d Handle startup error in Analytics insights (#109755) 2024-02-05 20:27:29 +01:00
Vilppu Vuorinen
65476914ed Reduce MELCloud poll frequency to avoid throttling (#109750) 2024-02-05 20:27:26 +01:00
Bouwe Westerdijk
d30a2e3611 Fix incorrectly assigning supported features for plugwise climates (#109749) 2024-02-05 20:27:23 +01:00
G Johansson
eb510e3630 Add missing new climate feature flags to Mill (#109748) 2024-02-05 20:27:20 +01:00
Michael
532df5b5f1 Use tracked entity friendly name for proximity sensors (#109744)
user tracked entity friendly name
2024-02-05 20:27:17 +01:00
Jan Bouwhuis
1534f99c80 Fix generic camera error when template renders to an invalid URL (#109737) 2024-02-05 20:27:14 +01:00
Cyrill Raccaud
a19aa9595a Bump python-bring-api to 3.0.0 (#109720) 2024-02-05 20:27:11 +01:00
Joost Lekkerkerker
e3191d098f Add strings to Ruuvitag BLE (#109717) 2024-02-05 20:27:08 +01:00
Bram Kragten
cc36071612 Update frontend to 20240205.0 (#109716) 2024-02-05 20:27:04 +01:00
Joakim Sørensen
2d90ee8237 Fix log string in Traccar Server Coordinator (#109709) 2024-02-05 20:27:01 +01:00
Simone Chemelli
16266703df Queue climate calls for Comelit SimpleHome (#109707) 2024-02-05 20:26:58 +01:00
Joost Lekkerkerker
dd2cc52119 Set Analytics Insights as diagnostic (#109702)
* Set Analytics Insights as diagnostic

* Set Analytics Insights as diagnostic
2024-02-05 20:26:54 +01:00
Joost Lekkerkerker
c48c8c25fa Remove obsolete check from Proximity (#109701) 2024-02-05 20:26:51 +01:00
Joost Lekkerkerker
83a5659d57 Set shorthand attribute in Epion (#109695) 2024-02-05 20:26:48 +01:00
Joost Lekkerkerker
3183cd346d Add data descriptions to analytics insights (#109694) 2024-02-05 20:26:45 +01:00
Marcel van der Veldt
5930c841d7 Bump python matter server to 5.4.1 (#109692) 2024-02-05 20:26:42 +01:00
Myles Eftos
f05ba22b5c Show site state in Amberelectric config flow (#104702) 2024-02-05 20:26:38 +01:00
196 changed files with 2439 additions and 770 deletions

View File

@@ -485,6 +485,7 @@ omit =
homeassistant/components/gpsd/sensor.py
homeassistant/components/greenwave/light.py
homeassistant/components/growatt_server/__init__.py
homeassistant/components/growatt_server/const.py
homeassistant/components/growatt_server/sensor.py
homeassistant/components/growatt_server/sensor_types/*
homeassistant/components/gstreamer/media_player.py

View File

@@ -786,8 +786,6 @@ build.json @home-assistant/supervisor
/homeassistant/components/media_source/ @hunterjm
/tests/components/media_source/ @hunterjm
/homeassistant/components/mediaroom/ @dgomes
/homeassistant/components/melcloud/ @vilppuvuorinen
/tests/components/melcloud/ @vilppuvuorinen
/homeassistant/components/melissa/ @kennedyshead
/tests/components/melissa/ @kennedyshead
/homeassistant/components/melnor/ @vanstinator

View File

@@ -1,6 +1,6 @@
{
"domain": "tplink",
"name": "TP-Link",
"integrations": ["tplink", "tplink_omada", "tplink_lte"],
"integrations": ["tplink", "tplink_omada", "tplink_lte", "tplink_tapo"],
"iot_standards": ["matter"]
}

View File

@@ -3,18 +3,46 @@ from __future__ import annotations
import amberelectric
from amberelectric.api import amber_api
from amberelectric.model.site import Site
from amberelectric.model.site import Site, SiteStatus
import voluptuous as vol
from homeassistant import config_entries
from homeassistant.const import CONF_API_TOKEN
from homeassistant.data_entry_flow import FlowResult
from homeassistant.helpers.selector import (
SelectOptionDict,
SelectSelector,
SelectSelectorConfig,
SelectSelectorMode,
)
from .const import CONF_SITE_ID, CONF_SITE_NAME, CONF_SITE_NMI, DOMAIN
from .const import CONF_SITE_ID, CONF_SITE_NAME, DOMAIN
API_URL = "https://app.amber.com.au/developers"
def generate_site_selector_name(site: Site) -> str:
"""Generate the name to show in the site drop down in the configuration flow."""
if site.status == SiteStatus.CLOSED:
return site.nmi + " (Closed: " + site.closed_on.isoformat() + ")" # type: ignore[no-any-return]
if site.status == SiteStatus.PENDING:
return site.nmi + " (Pending)" # type: ignore[no-any-return]
return site.nmi # type: ignore[no-any-return]
def filter_sites(sites: list[Site]) -> list[Site]:
"""Deduplicates the list of sites."""
filtered: list[Site] = []
filtered_nmi: set[str] = set()
for site in sorted(sites, key=lambda site: site.status.value):
if site.status == SiteStatus.ACTIVE or site.nmi not in filtered_nmi:
filtered.append(site)
filtered_nmi.add(site.nmi)
return filtered
class AmberElectricConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a config flow."""
@@ -31,7 +59,7 @@ class AmberElectricConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
api: amber_api.AmberApi = amber_api.AmberApi.create(configuration)
try:
sites: list[Site] = api.get_sites()
sites: list[Site] = filter_sites(api.get_sites())
if len(sites) == 0:
self._errors[CONF_API_TOKEN] = "no_site"
return None
@@ -86,38 +114,31 @@ class AmberElectricConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
assert self._sites is not None
assert self._api_token is not None
api_token = self._api_token
if user_input is not None:
site_nmi = user_input[CONF_SITE_NMI]
sites = [site for site in self._sites if site.nmi == site_nmi]
site = sites[0]
site_id = site.id
site_id = user_input[CONF_SITE_ID]
name = user_input.get(CONF_SITE_NAME, site_id)
return self.async_create_entry(
title=name,
data={
CONF_SITE_ID: site_id,
CONF_API_TOKEN: api_token,
CONF_SITE_NMI: site.nmi,
},
data={CONF_SITE_ID: site_id, CONF_API_TOKEN: self._api_token},
)
user_input = {
CONF_API_TOKEN: api_token,
CONF_SITE_NMI: "",
CONF_SITE_NAME: "",
}
return self.async_show_form(
step_id="site",
data_schema=vol.Schema(
{
vol.Required(
CONF_SITE_NMI, default=user_input[CONF_SITE_NMI]
): vol.In([site.nmi for site in self._sites]),
vol.Optional(
CONF_SITE_NAME, default=user_input[CONF_SITE_NAME]
): str,
vol.Required(CONF_SITE_ID): SelectSelector(
SelectSelectorConfig(
options=[
SelectOptionDict(
value=site.id,
label=generate_site_selector_name(site),
)
for site in self._sites
],
mode=SelectSelectorMode.DROPDOWN,
)
),
vol.Optional(CONF_SITE_NAME): str,
}
),
errors=self._errors,

View File

@@ -6,7 +6,6 @@ from homeassistant.const import Platform
DOMAIN = "amberelectric"
CONF_SITE_NAME = "site_name"
CONF_SITE_ID = "site_id"
CONF_SITE_NMI = "site_nmi"
ATTRIBUTION = "Data provided by Amber Electric"

View File

@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/amberelectric",
"iot_class": "cloud_polling",
"loggers": ["amberelectric"],
"requirements": ["amberelectric==1.0.4"]
"requirements": ["amberelectric==1.1.0"]
}

View File

@@ -3,11 +3,15 @@ from __future__ import annotations
from dataclasses import dataclass
from python_homeassistant_analytics import HomeassistantAnalyticsClient
from python_homeassistant_analytics import (
HomeassistantAnalyticsClient,
HomeassistantAnalyticsConnectionError,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import CONF_TRACKED_INTEGRATIONS, DOMAIN
@@ -28,7 +32,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Homeassistant Analytics from a config entry."""
client = HomeassistantAnalyticsClient(session=async_get_clientsession(hass))
integrations = await client.get_integrations()
try:
integrations = await client.get_integrations()
except HomeassistantAnalyticsConnectionError as ex:
raise ConfigEntryNotReady("Could not fetch integration list") from ex
names = {}
for integration in entry.options[CONF_TRACKED_INTEGRATIONS]:

View File

@@ -53,10 +53,25 @@ class HomeassistantAnalyticsConfigFlow(ConfigFlow, domain=DOMAIN):
) -> FlowResult:
"""Handle the initial step."""
self._async_abort_entries_match()
if user_input:
return self.async_create_entry(
title="Home Assistant Analytics Insights", data={}, options=user_input
)
errors: dict[str, str] = {}
if user_input is not None:
if not user_input.get(CONF_TRACKED_INTEGRATIONS) and not user_input.get(
CONF_TRACKED_CUSTOM_INTEGRATIONS
):
errors["base"] = "no_integrations_selected"
else:
return self.async_create_entry(
title="Home Assistant Analytics Insights",
data={},
options={
CONF_TRACKED_INTEGRATIONS: user_input.get(
CONF_TRACKED_INTEGRATIONS, []
),
CONF_TRACKED_CUSTOM_INTEGRATIONS: user_input.get(
CONF_TRACKED_CUSTOM_INTEGRATIONS, []
),
},
)
client = HomeassistantAnalyticsClient(
session=async_get_clientsession(self.hass)
@@ -78,16 +93,17 @@ class HomeassistantAnalyticsConfigFlow(ConfigFlow, domain=DOMAIN):
]
return self.async_show_form(
step_id="user",
errors=errors,
data_schema=vol.Schema(
{
vol.Required(CONF_TRACKED_INTEGRATIONS): SelectSelector(
vol.Optional(CONF_TRACKED_INTEGRATIONS): SelectSelector(
SelectSelectorConfig(
options=options,
multiple=True,
sort=True,
)
),
vol.Required(CONF_TRACKED_CUSTOM_INTEGRATIONS): SelectSelector(
vol.Optional(CONF_TRACKED_CUSTOM_INTEGRATIONS): SelectSelector(
SelectSelectorConfig(
options=list(custom_integrations),
multiple=True,
@@ -106,8 +122,24 @@ class HomeassistantAnalyticsOptionsFlowHandler(OptionsFlowWithConfigEntry):
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Manage the options."""
if user_input:
return self.async_create_entry(title="", data=user_input)
errors: dict[str, str] = {}
if user_input is not None:
if not user_input.get(CONF_TRACKED_INTEGRATIONS) and not user_input.get(
CONF_TRACKED_CUSTOM_INTEGRATIONS
):
errors["base"] = "no_integrations_selected"
else:
return self.async_create_entry(
title="",
data={
CONF_TRACKED_INTEGRATIONS: user_input.get(
CONF_TRACKED_INTEGRATIONS, []
),
CONF_TRACKED_CUSTOM_INTEGRATIONS: user_input.get(
CONF_TRACKED_CUSTOM_INTEGRATIONS, []
),
},
)
client = HomeassistantAnalyticsClient(
session=async_get_clientsession(self.hass)
@@ -129,17 +161,18 @@ class HomeassistantAnalyticsOptionsFlowHandler(OptionsFlowWithConfigEntry):
]
return self.async_show_form(
step_id="init",
errors=errors,
data_schema=self.add_suggested_values_to_schema(
vol.Schema(
{
vol.Required(CONF_TRACKED_INTEGRATIONS): SelectSelector(
vol.Optional(CONF_TRACKED_INTEGRATIONS): SelectSelector(
SelectSelectorConfig(
options=options,
multiple=True,
sort=True,
)
),
vol.Required(CONF_TRACKED_CUSTOM_INTEGRATIONS): SelectSelector(
vol.Optional(CONF_TRACKED_CUSTOM_INTEGRATIONS): SelectSelector(
SelectSelectorConfig(
options=list(custom_integrations),
multiple=True,

View File

@@ -10,6 +10,7 @@ from homeassistant.components.sensor import (
SensorStateClass,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
@@ -93,6 +94,7 @@ class HomeassistantAnalyticsSensor(
"""Home Assistant Analytics Sensor."""
_attr_has_entity_name = True
_attr_entity_category = EntityCategory.DIAGNOSTIC
entity_description: AnalyticsSensorEntityDescription

View File

@@ -5,24 +5,39 @@
"data": {
"tracked_integrations": "Integrations",
"tracked_custom_integrations": "Custom integrations"
},
"data_description": {
"tracked_integrations": "Select the integrations you want to track",
"tracked_custom_integrations": "Select the custom integrations you want to track"
}
}
},
"abort": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"already_configured": "[%key:common::config_flow::abort::already_configured_service%]"
},
"error": {
"no_integration_selected": "You must select at least one integration to track"
}
},
"options": {
"step": {
"init": {
"data": {
"tracked_integrations": "[%key:component::analytics_insights::config::step::user::data::tracked_integrations%]"
"tracked_integrations": "[%key:component::analytics_insights::config::step::user::data::tracked_integrations%]",
"tracked_custom_integrations": "[%key:component::analytics_insights::config::step::user::data::tracked_custom_integrations%]"
},
"data_description": {
"tracked_integrations": "[%key:component::analytics_insights::config::step::user::data_description::tracked_integrations%]",
"tracked_custom_integrations": "[%key:component::analytics_insights::config::step::user::data_description::tracked_custom_integrations%]"
}
}
},
"abort": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
},
"error": {
"no_integration_selected": "[%key:component::analytics_insights::config::error::no_integration_selected%]"
}
},
"entity": {

View File

@@ -5,5 +5,5 @@
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/aosmith",
"iot_class": "cloud_polling",
"requirements": ["py-aosmith==1.0.6"]
"requirements": ["py-aosmith==1.0.8"]
}

View File

@@ -249,10 +249,11 @@ class AugustData(AugustSubscriberMixin):
device = self.get_device_detail(device_id)
activities = activities_from_pubnub_message(device, date_time, message)
activity_stream = self.activity_stream
if activities:
activity_stream.async_process_newer_device_activities(activities)
if activities and activity_stream.async_process_newer_device_activities(
activities
):
self.async_signal_device_id_update(device.device_id)
activity_stream.async_schedule_house_id_refresh(device.house_id)
activity_stream.async_schedule_house_id_refresh(device.house_id)
@callback
def async_stop(self) -> None:

View File

@@ -28,5 +28,5 @@
"documentation": "https://www.home-assistant.io/integrations/august",
"iot_class": "cloud_push",
"loggers": ["pubnub", "yalexs"],
"requirements": ["yalexs==1.10.0", "yalexs-ble==2.4.1"]
"requirements": ["yalexs==1.11.2", "yalexs-ble==2.4.1"]
}

View File

@@ -14,6 +14,7 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_EMAIL, CONF_PASSWORD, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryError, ConfigEntryNotReady
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import DOMAIN
from .coordinator import BringDataUpdateCoordinator
@@ -29,14 +30,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
email = entry.data[CONF_EMAIL]
password = entry.data[CONF_PASSWORD]
bring = Bring(email, password)
def login_and_load_lists() -> None:
bring.login()
bring.loadLists()
session = async_get_clientsession(hass)
bring = Bring(email, password, sessionAsync=session)
try:
await hass.async_add_executor_job(login_and_load_lists)
await bring.loginAsync()
await bring.loadListsAsync()
except BringRequestException as e:
raise ConfigEntryNotReady(
f"Timeout while connecting for email '{email}'"

View File

@@ -11,6 +11,7 @@ import voluptuous as vol
from homeassistant import config_entries
from homeassistant.const import CONF_EMAIL, CONF_PASSWORD
from homeassistant.data_entry_flow import FlowResult
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.selector import (
TextSelector,
TextSelectorConfig,
@@ -48,14 +49,14 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle the initial step."""
errors: dict[str, str] = {}
if user_input is not None:
bring = Bring(user_input[CONF_EMAIL], user_input[CONF_PASSWORD])
def login_and_load_lists() -> None:
bring.login()
bring.loadLists()
session = async_get_clientsession(self.hass)
bring = Bring(
user_input[CONF_EMAIL], user_input[CONF_PASSWORD], sessionAsync=session
)
try:
await self.hass.async_add_executor_job(login_and_load_lists)
await bring.loginAsync()
await bring.loadListsAsync()
except BringRequestException:
errors["base"] = "cannot_connect"
except BringAuthException:

View File

@@ -40,9 +40,7 @@ class BringDataUpdateCoordinator(DataUpdateCoordinator[dict[str, BringData]]):
async def _async_update_data(self) -> dict[str, BringData]:
try:
lists_response = await self.hass.async_add_executor_job(
self.bring.loadLists
)
lists_response = await self.bring.loadListsAsync()
except BringRequestException as e:
raise UpdateFailed("Unable to connect and retrieve data from bring") from e
except BringParseException as e:
@@ -51,9 +49,7 @@ class BringDataUpdateCoordinator(DataUpdateCoordinator[dict[str, BringData]]):
list_dict = {}
for lst in lists_response["lists"]:
try:
items = await self.hass.async_add_executor_job(
self.bring.getItems, lst["listUuid"]
)
items = await self.bring.getItemsAsync(lst["listUuid"])
except BringRequestException as e:
raise UpdateFailed(
"Unable to connect and retrieve data from bring"

View File

@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/bring",
"integration_type": "service",
"iot_class": "cloud_polling",
"requirements": ["python-bring-api==2.0.0"]
"requirements": ["python-bring-api==3.0.0"]
}

View File

@@ -91,11 +91,8 @@ class BringTodoListEntity(
async def async_create_todo_item(self, item: TodoItem) -> None:
"""Add an item to the To-do list."""
try:
await self.hass.async_add_executor_job(
self.coordinator.bring.saveItem,
self.bring_list["listUuid"],
item.summary,
item.description or "",
await self.coordinator.bring.saveItemAsync(
self.bring_list["listUuid"], item.summary, item.description or ""
)
except BringRequestException as e:
raise HomeAssistantError("Unable to save todo item for bring") from e
@@ -126,16 +123,14 @@ class BringTodoListEntity(
assert item.uid
if item.status == TodoItemStatus.COMPLETED:
await self.hass.async_add_executor_job(
self.coordinator.bring.removeItem,
await self.coordinator.bring.removeItemAsync(
bring_list["listUuid"],
item.uid,
)
elif item.summary == item.uid:
try:
await self.hass.async_add_executor_job(
self.coordinator.bring.updateItem,
await self.coordinator.bring.updateItemAsync(
bring_list["listUuid"],
item.uid,
item.description or "",
@@ -144,13 +139,11 @@ class BringTodoListEntity(
raise HomeAssistantError("Unable to update todo item for bring") from e
else:
try:
await self.hass.async_add_executor_job(
self.coordinator.bring.removeItem,
await self.coordinator.bring.removeItemAsync(
bring_list["listUuid"],
item.uid,
)
await self.hass.async_add_executor_job(
self.coordinator.bring.saveItem,
await self.coordinator.bring.saveItemAsync(
bring_list["listUuid"],
item.summary,
item.description or "",
@@ -164,8 +157,8 @@ class BringTodoListEntity(
"""Delete an item from the To-do list."""
for uid in uids:
try:
await self.hass.async_add_executor_job(
self.coordinator.bring.removeItem, self.bring_list["listUuid"], uid
await self.coordinator.bring.removeItemAsync(
self.bring_list["listUuid"], uid
)
except BringRequestException as e:
raise HomeAssistantError("Unable to delete todo item for bring") from e

View File

@@ -1,4 +1,5 @@
"""Intents for the client integration."""
from __future__ import annotations
import voluptuous as vol
@@ -36,24 +37,34 @@ class GetTemperatureIntent(intent.IntentHandler):
if not entities:
raise intent.IntentHandleError("No climate entities")
if "area" in slots:
# Filter by area
area_name = slots["area"]["value"]
name_slot = slots.get("name", {})
entity_name: str | None = name_slot.get("value")
entity_text: str | None = name_slot.get("text")
area_slot = slots.get("area", {})
area_id = area_slot.get("value")
if area_id:
# Filter by area and optionally name
area_name = area_slot.get("text")
for maybe_climate in intent.async_match_states(
hass, area_name=area_name, domains=[DOMAIN]
hass, name=entity_name, area_name=area_id, domains=[DOMAIN]
):
climate_state = maybe_climate
break
if climate_state is None:
raise intent.IntentHandleError(f"No climate entity in area {area_name}")
raise intent.NoStatesMatchedError(
name=entity_text or entity_name,
area=area_name or area_id,
domains={DOMAIN},
device_classes=None,
)
climate_entity = component.get_entity(climate_state.entity_id)
elif "name" in slots:
elif entity_name:
# Filter by name
entity_name = slots["name"]["value"]
for maybe_climate in intent.async_match_states(
hass, name=entity_name, domains=[DOMAIN]
):
@@ -61,7 +72,12 @@ class GetTemperatureIntent(intent.IntentHandler):
break
if climate_state is None:
raise intent.IntentHandleError(f"No climate entity named {entity_name}")
raise intent.NoStatesMatchedError(
name=entity_name,
area=None,
domains={DOMAIN},
device_classes=None,
)
climate_entity = component.get_entity(climate_state.entity_id)
else:

View File

@@ -8,6 +8,7 @@ from aioelectricitymaps import (
ElectricityMaps,
ElectricityMapsError,
ElectricityMapsInvalidTokenError,
ElectricityMapsNoDataError,
)
import voluptuous as vol
@@ -151,6 +152,8 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
await fetch_latest_carbon_intensity(self.hass, em, data)
except ElectricityMapsInvalidTokenError:
errors["base"] = "invalid_auth"
except ElectricityMapsNoDataError:
errors["base"] = "no_data"
except ElectricityMapsError:
errors["base"] = "unknown"
else:

View File

@@ -7,5 +7,5 @@
"integration_type": "service",
"iot_class": "cloud_polling",
"loggers": ["aioelectricitymaps"],
"requirements": ["aioelectricitymaps==0.3.0"]
"requirements": ["aioelectricitymaps==0.4.0"]
}

View File

@@ -28,12 +28,9 @@
"error": {
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
"unknown": "[%key:common::config_flow::error::unknown%]",
"api_ratelimit": "API Ratelimit exceeded"
"no_data": "No data is available for the location you have selected."
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"unknown": "[%key:common::config_flow::error::unknown%]",
"api_ratelimit": "[%key:component::co2signal::config::error::api_ratelimit%]",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
}
},

View File

@@ -1,12 +1,11 @@
"""Support for climates."""
from __future__ import annotations
import asyncio
from enum import StrEnum
from typing import Any
from aiocomelit import ComelitSerialBridgeObject
from aiocomelit.const import CLIMATE, SLEEP_BETWEEN_CALLS
from aiocomelit.const import CLIMATE
from homeassistant.components.climate import (
ClimateEntity,
@@ -191,7 +190,6 @@ class ComelitClimateEntity(CoordinatorEntity[ComelitSerialBridge], ClimateEntity
await self.coordinator.api.set_clima_status(
self._device.index, ClimaAction.MANUAL
)
await asyncio.sleep(SLEEP_BETWEEN_CALLS)
await self.coordinator.api.set_clima_status(
self._device.index, ClimaAction.SET, target_temp
)
@@ -203,7 +201,6 @@ class ComelitClimateEntity(CoordinatorEntity[ComelitSerialBridge], ClimateEntity
await self.coordinator.api.set_clima_status(
self._device.index, ClimaAction.ON
)
await asyncio.sleep(SLEEP_BETWEEN_CALLS)
await self.coordinator.api.set_clima_status(
self._device.index, MODE_TO_ACTION[hvac_mode]
)

View File

@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/comelit",
"iot_class": "local_polling",
"loggers": ["aiocomelit"],
"requirements": ["aiocomelit==0.8.2"]
"requirements": ["aiocomelit==0.8.3"]
}

View File

@@ -223,22 +223,22 @@ class DefaultAgent(AbstractConversationAgent):
# Check if a trigger matched
if isinstance(result, SentenceTriggerResult):
# Gather callback responses in parallel
trigger_responses = await asyncio.gather(
*(
self._trigger_sentences[trigger_id].callback(
result.sentence, trigger_result
)
for trigger_id, trigger_result in result.matched_triggers.items()
trigger_callbacks = [
self._trigger_sentences[trigger_id].callback(
result.sentence, trigger_result
)
)
for trigger_id, trigger_result in result.matched_triggers.items()
]
# Use last non-empty result as response.
#
# There may be multiple copies of a trigger running when editing in
# the UI, so it's critical that we filter out empty responses here.
response_text: str | None = None
for trigger_response in trigger_responses:
response_text = response_text or trigger_response
for trigger_future in asyncio.as_completed(trigger_callbacks):
if trigger_response := await trigger_future:
response_text = trigger_response
break
# Convert to conversation result
response = intent.IntentResponse(language=language)
@@ -316,6 +316,20 @@ class DefaultAgent(AbstractConversationAgent):
),
conversation_id,
)
except intent.DuplicateNamesMatchedError as duplicate_names_error:
# Intent was valid, but two or more entities with the same name matched.
(
error_response_type,
error_response_args,
) = _get_duplicate_names_matched_response(duplicate_names_error)
return _make_error_result(
language,
intent.IntentResponseErrorCode.NO_VALID_TARGETS,
self._get_error_text(
error_response_type, lang_intents, **error_response_args
),
conversation_id,
)
except intent.IntentHandleError:
# Intent was valid and entities matched constraints, but an error
# occurred during handling.
@@ -724,7 +738,12 @@ class DefaultAgent(AbstractConversationAgent):
if async_should_expose(self.hass, DOMAIN, state.entity_id)
]
# Gather exposed entity names
# Gather exposed entity names.
#
# NOTE: We do not pass entity ids in here because multiple entities may
# have the same name. The intent matcher doesn't gather all matching
# values for a list, just the first. So we will need to match by name no
# matter what.
entity_names = []
for state in states:
# Checked against "requires_context" and "excludes_context" in hassil
@@ -740,7 +759,7 @@ class DefaultAgent(AbstractConversationAgent):
if not entity:
# Default name
entity_names.append((state.name, state.entity_id, context))
entity_names.append((state.name, state.name, context))
continue
if entity.aliases:
@@ -748,12 +767,15 @@ class DefaultAgent(AbstractConversationAgent):
if not alias.strip():
continue
entity_names.append((alias, state.entity_id, context))
entity_names.append((alias, alias, context))
# Default name
entity_names.append((state.name, state.entity_id, context))
entity_names.append((state.name, state.name, context))
# Expose all areas
# Expose all areas.
#
# We pass in area id here with the expectation that no two areas will
# share the same name or alias.
areas = ar.async_get(self.hass)
area_names = []
for area in areas.async_list_areas():
@@ -984,6 +1006,20 @@ def _get_no_states_matched_response(
return ErrorKey.NO_INTENT, {}
def _get_duplicate_names_matched_response(
duplicate_names_error: intent.DuplicateNamesMatchedError,
) -> tuple[ErrorKey, dict[str, Any]]:
"""Return key and template arguments for error when intent returns duplicate matches."""
if duplicate_names_error.area:
return ErrorKey.DUPLICATE_ENTITIES_IN_AREA, {
"entity": duplicate_names_error.name,
"area": duplicate_names_error.area,
}
return ErrorKey.DUPLICATE_ENTITIES, {"entity": duplicate_names_error.name}
def _collect_list_references(expression: Expression, list_names: set[str]) -> None:
"""Collect list reference names recursively."""
if isinstance(expression, Sequence):

View File

@@ -8,7 +8,7 @@
"documentation": "https://www.home-assistant.io/integrations/dlna_dmr",
"iot_class": "local_push",
"loggers": ["async_upnp_client"],
"requirements": ["async-upnp-client==0.38.1", "getmac==0.9.4"],
"requirements": ["async-upnp-client==0.38.2", "getmac==0.9.4"],
"ssdp": [
{
"deviceType": "urn:schemas-upnp-org:device:MediaRenderer:1",

View File

@@ -8,7 +8,7 @@
"documentation": "https://www.home-assistant.io/integrations/dlna_dms",
"iot_class": "local_polling",
"quality_scale": "platinum",
"requirements": ["async-upnp-client==0.38.1"],
"requirements": ["async-upnp-client==0.38.2"],
"ssdp": [
{
"deviceType": "urn:schemas-upnp-org:device:MediaServer:1",

View File

@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/ecovacs",
"iot_class": "cloud_push",
"loggers": ["sleekxmppfs", "sucks", "deebot_client"],
"requirements": ["py-sucks==0.9.8", "deebot-client==5.1.0"]
"requirements": ["py-sucks==0.9.9", "deebot-client==5.2.1"]
}

View File

@@ -38,8 +38,8 @@ class EcowittEntity(Entity):
"""Update the state on callback."""
self.async_write_ha_state()
self.ecowitt.update_cb.append(_update_state) # type: ignore[arg-type] # upstream bug
self.async_on_remove(lambda: self.ecowitt.update_cb.remove(_update_state)) # type: ignore[arg-type] # upstream bug
self.ecowitt.update_cb.append(_update_state)
self.async_on_remove(lambda: self.ecowitt.update_cb.remove(_update_state))
@property
def available(self) -> bool:

View File

@@ -6,5 +6,5 @@
"dependencies": ["webhook"],
"documentation": "https://www.home-assistant.io/integrations/ecowitt",
"iot_class": "local_push",
"requirements": ["aioecowitt==2023.5.0"]
"requirements": ["aioecowitt==2024.2.1"]
}

View File

@@ -10,7 +10,7 @@ from types import MappingProxyType
from typing import Any
from elkm1_lib.elements import Element
from elkm1_lib.elk import Elk
from elkm1_lib.elk import Elk, Panel
from elkm1_lib.util import parse_url
import voluptuous as vol
@@ -398,22 +398,30 @@ async def async_wait_for_elk_to_sync(
return success
@callback
def _async_get_elk_panel(hass: HomeAssistant, service: ServiceCall) -> Panel:
"""Get the ElkM1 panel from a service call."""
prefix = service.data["prefix"]
elk = _find_elk_by_prefix(hass, prefix)
if elk is None:
raise HomeAssistantError(f"No ElkM1 with prefix '{prefix}' found")
return elk.panel
def _create_elk_services(hass: HomeAssistant) -> None:
def _getelk(service: ServiceCall) -> Elk:
prefix = service.data["prefix"]
elk = _find_elk_by_prefix(hass, prefix)
if elk is None:
raise HomeAssistantError(f"No ElkM1 with prefix '{prefix}' found")
return elk
"""Create ElkM1 services."""
@callback
def _speak_word_service(service: ServiceCall) -> None:
_getelk(service).panel.speak_word(service.data["number"])
_async_get_elk_panel(hass, service).speak_word(service.data["number"])
@callback
def _speak_phrase_service(service: ServiceCall) -> None:
_getelk(service).panel.speak_phrase(service.data["number"])
_async_get_elk_panel(hass, service).speak_phrase(service.data["number"])
@callback
def _set_time_service(service: ServiceCall) -> None:
_getelk(service).panel.set_time(dt_util.now())
_async_get_elk_panel(hass, service).set_time(dt_util.now())
hass.services.async_register(
DOMAIN, "speak_word", _speak_word_service, SPEAK_SERVICE_SCHEMA

View File

@@ -88,9 +88,9 @@ class EpionSensor(CoordinatorEntity[EpionCoordinator], SensorEntity):
super().__init__(coordinator)
self._epion_device_id = epion_device_id
self.entity_description = description
self.unique_id = f"{epion_device_id}_{description.key}"
self._attr_unique_id = f"{epion_device_id}_{description.key}"
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, self._epion_device_id)},
identifiers={(DOMAIN, epion_device_id)},
manufacturer="Epion",
name=self.device.get("deviceName"),
sw_version=self.device.get("fwVersion"),

View File

@@ -15,7 +15,7 @@
"iot_class": "local_push",
"loggers": ["aioesphomeapi", "noiseprotocol", "bleak_esphome"],
"requirements": [
"aioesphomeapi==21.0.1",
"aioesphomeapi==21.0.2",
"esphome-dashboard-api==1.2.3",
"bleak-esphome==0.4.1"
],

View File

@@ -5,5 +5,5 @@
"documentation": "https://www.home-assistant.io/integrations/evohome",
"iot_class": "cloud_polling",
"loggers": ["evohomeasync", "evohomeasync2"],
"requirements": ["evohome-async==0.4.17"]
"requirements": ["evohome-async==0.4.19"]
}

View File

@@ -7,6 +7,7 @@ from typing import Any
from aioflo.api import API
from aioflo.errors import RequestError
from orjson import JSONDecodeError
from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
@@ -46,7 +47,7 @@ class FloDeviceDataUpdateCoordinator(DataUpdateCoordinator): # pylint: disable=
await self._update_device()
await self._update_consumption_data()
self._failure_count = 0
except (RequestError, TimeoutError) as error:
except (RequestError, TimeoutError, JSONDecodeError) as error:
self._failure_count += 1
if self._failure_count > 3:
raise UpdateFailed(error) from error

View File

@@ -11,7 +11,7 @@ from homeassistant.const import CONF_HOST, CONF_PORT
from homeassistant.data_entry_flow import FlowResult
from .const import DOMAIN
from .router import get_api
from .router import get_api, get_hosts_list_if_supported
_LOGGER = logging.getLogger(__name__)
@@ -69,7 +69,7 @@ class FreeboxFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
# Check permissions
await fbx.system.get_config()
await fbx.lan.get_hosts_list()
await get_hosts_list_if_supported(fbx)
# Close connection
await fbx.close()

View File

@@ -64,6 +64,33 @@ async def get_api(hass: HomeAssistant, host: str) -> Freepybox:
return Freepybox(APP_DESC, token_file, API_VERSION)
async def get_hosts_list_if_supported(
fbx_api: Freepybox,
) -> tuple[bool, list[dict[str, Any]]]:
"""Hosts list is not supported when freebox is configured in bridge mode."""
supports_hosts: bool = True
fbx_devices: list[dict[str, Any]] = []
try:
fbx_devices = await fbx_api.lan.get_hosts_list() or []
except HttpRequestError as err:
if (
(matcher := re.search(r"Request failed \(APIResponse: (.+)\)", str(err)))
and is_json(json_str := matcher.group(1))
and (json_resp := json.loads(json_str)).get("error_code") == "nodev"
):
# No need to retry, Host list not available
supports_hosts = False
_LOGGER.debug(
"Host list is not available using bridge mode (%s)",
json_resp.get("msg"),
)
else:
raise err
return supports_hosts, fbx_devices
class FreeboxRouter:
"""Representation of a Freebox router."""
@@ -111,27 +138,9 @@ class FreeboxRouter:
# Access to Host list not available in bridge mode, API return error_code 'nodev'
if self.supports_hosts:
try:
fbx_devices = await self._api.lan.get_hosts_list()
except HttpRequestError as err:
if (
(
matcher := re.search(
r"Request failed \(APIResponse: (.+)\)", str(err)
)
)
and is_json(json_str := matcher.group(1))
and (json_resp := json.loads(json_str)).get("error_code") == "nodev"
):
# No need to retry, Host list not available
self.supports_hosts = False
_LOGGER.debug(
"Host list is not available using bridge mode (%s)",
json_resp.get("msg"),
)
else:
raise err
self.supports_hosts, fbx_devices = await get_hosts_list_if_supported(
self._api
)
# Adds the Freebox itself
fbx_devices.append(

View File

@@ -20,5 +20,5 @@
"documentation": "https://www.home-assistant.io/integrations/frontend",
"integration_type": "system",
"quality_scale": "internal",
"requirements": ["home-assistant-frontend==20240202.0"]
"requirements": ["home-assistant-frontend==20240207.1"]
}

View File

@@ -8,5 +8,5 @@
"iot_class": "cloud_polling",
"loggers": ["aio_georss_gdacs", "aio_georss_client"],
"quality_scale": "platinum",
"requirements": ["aio-georss-gdacs==0.8"]
"requirements": ["aio-georss-gdacs==0.9"]
}

View File

@@ -8,6 +8,7 @@ import logging
from typing import Any
import httpx
import voluptuous as vol
import yarl
from homeassistant.components.camera import Camera, CameraEntityFeature
@@ -140,6 +141,12 @@ class GenericCamera(Camera):
_LOGGER.error("Error parsing template %s: %s", self._still_image_url, err)
return self._last_image
try:
vol.Schema(vol.Url())(url)
except vol.Invalid as err:
_LOGGER.warning("Invalid URL '%s': %s, returning last image", url, err)
return self._last_image
if url == self._last_url and self._limit_refetch:
return self._last_image

View File

@@ -8,5 +8,5 @@
"iot_class": "cloud_polling",
"loggers": ["aio_geojson_geonetnz_quakes"],
"quality_scale": "platinum",
"requirements": ["aio-geojson-geonetnz-quakes==0.15"]
"requirements": ["aio-geojson-geonetnz-quakes==0.16"]
}

View File

@@ -7,5 +7,5 @@
"integration_type": "service",
"iot_class": "cloud_polling",
"loggers": ["aio_geojson_geonetnz_volcano"],
"requirements": ["aio-geojson-geonetnz-volcano==0.8"]
"requirements": ["aio-geojson-geonetnz-volcano==0.9"]
}

View File

@@ -476,7 +476,7 @@ class SensorGroup(GroupEntity, SensorEntity):
translation_placeholders={
"entity_id": self.entity_id,
"source_entities": ", ".join(self._entity_ids),
"state_classes:": ", ".join(state_classes),
"state_classes": ", ".join(state_classes),
},
)
return None
@@ -519,7 +519,7 @@ class SensorGroup(GroupEntity, SensorEntity):
translation_placeholders={
"entity_id": self.entity_id,
"source_entities": ", ".join(self._entity_ids),
"device_classes:": ", ".join(device_classes),
"device_classes": ", ".join(device_classes),
},
)
return None

View File

@@ -265,7 +265,7 @@
},
"state_classes_not_matching": {
"title": "State classes is not correct",
"description": "Device classes `{state_classes}` on source entities `{source_entities}` needs to be same for sensor group `{entity_id}`.\n\nPlease correct the state classes on the source entities and reload the group sensor to fix this issue."
"description": "State classes `{state_classes}` on source entities `{source_entities}` needs to be same for sensor group `{entity_id}`.\n\nPlease correct the state classes on the source entities and reload the group sensor to fix this issue."
}
}
}

View File

@@ -8,13 +8,16 @@ DEFAULT_PLANT_ID = "0"
DEFAULT_NAME = "Growatt"
SERVER_URLS = [
"https://server-api.growatt.com/",
"https://server-us.growatt.com/",
"http://server.smten.com/",
"https://openapi.growatt.com/", # Other regional server
"https://openapi-cn.growatt.com/", # Chinese server
"https://openapi-us.growatt.com/", # North American server
"http://server.smten.com/", # smten server
]
DEPRECATED_URLS = [
"https://server.growatt.com/",
"https://server-api.growatt.com/",
"https://server-us.growatt.com/",
]
DEFAULT_URL = SERVER_URLS[0]

View File

@@ -1001,12 +1001,18 @@ class HassioDataUpdateCoordinator(DataUpdateCoordinator): # pylint: disable=has
raise_on_entry_error: bool = False,
) -> None:
"""Refresh data."""
if not scheduled:
if not scheduled and not raise_on_auth_failed:
# Force refreshing updates for non-scheduled updates
# If `raise_on_auth_failed` is set, it means this is
# the first refresh and we do not want to delay
# startup or cause a timeout so we only refresh the
# updates if this is not a scheduled refresh and
# we are not doing the first refresh.
try:
await self.hassio.refresh_updates()
except HassioAPIError as err:
_LOGGER.warning("Error on Supervisor API: %s", err)
await super()._async_refresh(
log_failures, raise_on_auth_failed, scheduled, raise_on_entry_error
)

View File

@@ -459,7 +459,7 @@ class HassIO:
This method returns a coroutine.
"""
return self.send_command("/refresh_updates", timeout=None)
return self.send_command("/refresh_updates", timeout=300)
@api_data
def retrieve_discovery_messages(self) -> Coroutine:
@@ -506,7 +506,6 @@ class HassIO:
options = {
"ssl": CONF_SSL_CERTIFICATE in http_config,
"port": port,
"watchdog": True,
"refresh_token": refresh_token.token,
}

View File

@@ -5,5 +5,5 @@
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/holiday",
"iot_class": "local_polling",
"requirements": ["holidays==0.41", "babel==2.13.1"]
"requirements": ["holidays==0.42", "babel==2.13.1"]
}

View File

@@ -7,6 +7,7 @@ from typing import Any
from aiohttp import ClientConnectionError
from aiosomecomfort import (
APIRateLimited,
AuthError,
ConnectionError as AscConnectionError,
SomeComfortError,
@@ -505,10 +506,11 @@ class HoneywellUSThermostat(ClimateEntity):
await self._device.refresh()
except (
asyncio.TimeoutError,
AscConnectionError,
APIRateLimited,
AuthError,
ClientConnectionError,
AscConnectionError,
asyncio.TimeoutError,
):
self._retry += 1
self._attr_available = self._retry <= RETRY
@@ -524,7 +526,12 @@ class HoneywellUSThermostat(ClimateEntity):
await _login()
return
except (AscConnectionError, ClientConnectionError, asyncio.TimeoutError):
except (
asyncio.TimeoutError,
AscConnectionError,
APIRateLimited,
ClientConnectionError,
):
self._retry += 1
self._attr_available = self._retry <= RETRY
return

View File

@@ -1,4 +1,5 @@
"""The Intent integration."""
from __future__ import annotations
import logging
@@ -155,16 +156,18 @@ class GetStateIntentHandler(intent.IntentHandler):
slots = self.async_validate_slots(intent_obj.slots)
# Entity name to match
name: str | None = slots.get("name", {}).get("value")
name_slot = slots.get("name", {})
entity_name: str | None = name_slot.get("value")
entity_text: str | None = name_slot.get("text")
# Look up area first to fail early
area_name = slots.get("area", {}).get("value")
area_slot = slots.get("area", {})
area_id = area_slot.get("value")
area_name = area_slot.get("text")
area: ar.AreaEntry | None = None
if area_name is not None:
if area_id is not None:
areas = ar.async_get(hass)
area = areas.async_get_area(area_name) or areas.async_get_area_by_name(
area_name
)
area = areas.async_get_area(area_id)
if area is None:
raise intent.IntentHandleError(f"No area named {area_name}")
@@ -186,7 +189,7 @@ class GetStateIntentHandler(intent.IntentHandler):
states = list(
intent.async_match_states(
hass,
name=name,
name=entity_name,
area=area,
domains=domains,
device_classes=device_classes,
@@ -197,13 +200,20 @@ class GetStateIntentHandler(intent.IntentHandler):
_LOGGER.debug(
"Found %s state(s) that matched: name=%s, area=%s, domains=%s, device_classes=%s, assistant=%s",
len(states),
name,
entity_name,
area,
domains,
device_classes,
intent_obj.assistant,
)
if entity_name and (len(states) > 1):
# Multiple entities matched for the same name
raise intent.DuplicateNamesMatchedError(
name=entity_text or entity_name,
area=area_name or area_id,
)
# Create response
response = intent_obj.create_response()
response.response_type = intent.IntentResponseType.QUERY_ANSWER

View File

@@ -138,6 +138,8 @@ class MicroBotConfigFlow(ConfigFlow, domain=DOMAIN):
await self._client.connect(init=True)
return self.async_show_form(step_id="link")
if not await self._client.is_connected():
await self._client.connect(init=False)
if not await self._client.is_connected():
errors["base"] = "linking"
else:

View File

@@ -13,7 +13,8 @@
"config_flow": true,
"dependencies": ["bluetooth_adapters"],
"documentation": "https://www.home-assistant.io/integrations/keymitt_ble",
"integration_type": "hub",
"iot_class": "assumed_state",
"loggers": ["keymitt_ble"],
"requirements": ["PyMicroBot==0.0.9"]
"requirements": ["PyMicroBot==0.0.12"]
}

View File

@@ -11,8 +11,8 @@
"loggers": ["xknx", "xknxproject"],
"quality_scale": "platinum",
"requirements": [
"xknx==2.11.2",
"xknxproject==3.4.0",
"xknx==2.12.0",
"xknxproject==3.6.0",
"knx-frontend==2024.1.20.105944"
]
}

View File

@@ -6,11 +6,12 @@ import logging
from typing import Any
from linear_garage_door import Linear
from linear_garage_door.errors import InvalidLoginError, ResponseError
from linear_garage_door.errors import InvalidLoginError
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
_LOGGER = logging.getLogger(__name__)
@@ -55,6 +56,7 @@ class LinearUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
email=self._email,
password=self._password,
device_id=self._device_id,
client_session=async_get_clientsession(self.hass),
)
except InvalidLoginError as err:
if (
@@ -63,8 +65,6 @@ class LinearUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
):
raise ConfigEntryAuthFailed from err
raise ConfigEntryNotReady from err
except ResponseError as err:
raise ConfigEntryNotReady from err
if not self._devices:
self._devices = await linear.get_devices(self._site_id)

View File

@@ -5,5 +5,5 @@
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/linear_garage_door",
"iot_class": "cloud_polling",
"requirements": ["linear-garage-door==0.2.7"]
"requirements": ["linear-garage-door==0.2.9"]
}

View File

@@ -1,4 +1,5 @@
"""Support for Lupusec Home Security system."""
from json import JSONDecodeError
import logging
import lupupy
@@ -111,16 +112,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
lupusec_system = await hass.async_add_executor_job(
lupupy.Lupusec, username, password, host
)
except LupusecException:
_LOGGER.error("Failed to connect to Lupusec device at %s", host)
return False
except Exception as ex: # pylint: disable=broad-except
_LOGGER.error(
"Unknown error while trying to connect to Lupusec device at %s: %s",
host,
ex,
)
except JSONDecodeError:
_LOGGER.error("Failed to connect to Lupusec device at %s", host)
return False
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = lupusec_system

View File

@@ -29,14 +29,14 @@ SCAN_INTERVAL = timedelta(seconds=2)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_devices: AddEntitiesCallback,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up an alarm control panel for a Lupusec device."""
data = hass.data[DOMAIN][config_entry.entry_id]
alarm_devices = [LupusecAlarm(data, data.get_alarm(), config_entry.entry_id)]
alarm = await hass.async_add_executor_job(data.get_alarm)
async_add_devices(alarm_devices)
async_add_entities([LupusecAlarm(data, alarm, config_entry.entry_id)])
class LupusecAlarm(LupusecDevice, AlarmControlPanelEntity):

View File

@@ -2,6 +2,7 @@
from __future__ import annotations
from datetime import timedelta
from functools import partial
import logging
import lupupy.constants as CONST
@@ -25,7 +26,7 @@ _LOGGER = logging.getLogger(__name__)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_devices: AddEntitiesCallback,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up a binary sensors for a Lupusec device."""
@@ -34,10 +35,12 @@ async def async_setup_entry(
device_types = CONST.TYPE_OPENING + CONST.TYPE_SENSOR
sensors = []
for device in data.get_devices(generic_type=device_types):
partial_func = partial(data.get_devices, generic_type=device_types)
devices = await hass.async_add_executor_job(partial_func)
for device in devices:
sensors.append(LupusecBinarySensor(device, config_entry.entry_id))
async_add_devices(sensors)
async_add_entities(sensors)
class LupusecBinarySensor(LupusecBaseSensor, BinarySensorEntity):

View File

@@ -1,5 +1,6 @@
""""Config flow for Lupusec integration."""
from json import JSONDecodeError
import logging
from typing import Any
@@ -50,6 +51,8 @@ class LupusecConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
await test_host_connection(self.hass, host, username, password)
except CannotConnect:
errors["base"] = "cannot_connect"
except JSONDecodeError:
errors["base"] = "cannot_connect"
except Exception: # pylint: disable=broad-except
_LOGGER.exception("Unexpected exception")
errors["base"] = "unknown"
@@ -80,6 +83,8 @@ class LupusecConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
await test_host_connection(self.hass, host, username, password)
except CannotConnect:
return self.async_abort(reason="cannot_connect")
except JSONDecodeError:
return self.async_abort(reason="cannot_connect")
except Exception: # pylint: disable=broad-except
_LOGGER.exception("Unexpected exception")
return self.async_abort(reason="unknown")

View File

@@ -21,7 +21,7 @@
"issues": {
"deprecated_yaml_import_issue_cannot_connect": {
"title": "The Lupus Electronics LUPUSEC YAML configuration import failed",
"description": "Configuring Lupus Electronics LUPUSEC using YAML is being removed but there was an connection error importing your YAML configuration.\n\nEnsure connection to Lupus Electronics LUPUSEC works and restart Home Assistant to try again or remove the Lupus Electronics LUPUSEC YAML configuration from your configuration.yaml file and continue to [set up the integration]({url}) manually."
"description": "Configuring Lupus Electronics LUPUSEC using YAML is being removed but there was a connection error importing your YAML configuration.\n\nEnsure connection to Lupus Electronics LUPUSEC works and restart Home Assistant to try again or remove the Lupus Electronics LUPUSEC YAML configuration from your configuration.yaml file and continue to [set up the integration]({url}) manually."
},
"deprecated_yaml_import_issue_unknown": {
"title": "The Lupus Electronics LUPUSEC YAML configuration import failed",

View File

@@ -2,6 +2,7 @@
from __future__ import annotations
from datetime import timedelta
from functools import partial
from typing import Any
import lupupy.constants as CONST
@@ -20,7 +21,7 @@ SCAN_INTERVAL = timedelta(seconds=2)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_devices: AddEntitiesCallback,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up Lupusec switch devices."""
@@ -29,10 +30,12 @@ async def async_setup_entry(
device_types = CONST.TYPE_SWITCH
switches = []
for device in data.get_devices(generic_type=device_types):
partial_func = partial(data.get_devices, generic_type=device_types)
devices = await hass.async_add_executor_job(partial_func)
for device in devices:
switches.append(LupusecSwitch(device, config_entry.entry_id))
async_add_devices(switches)
async_add_entities(switches)
class LupusecSwitch(LupusecBaseSensor, SwitchEntity):

View File

@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/lutron",
"iot_class": "local_polling",
"loggers": ["pylutron"],
"requirements": ["pylutron==0.2.8"]
"requirements": ["pylutron==0.2.12"]
}

View File

@@ -52,11 +52,27 @@ class MatterAdapter:
async def setup_nodes(self) -> None:
"""Set up all existing nodes and subscribe to new nodes."""
initialized_nodes: set[int] = set()
for node in self.matter_client.get_nodes():
if not node.available:
# ignore un-initialized nodes at startup
# catch them later when they become available.
continue
initialized_nodes.add(node.node_id)
self._setup_node(node)
def node_added_callback(event: EventType, node: MatterNode) -> None:
"""Handle node added event."""
initialized_nodes.add(node.node_id)
self._setup_node(node)
def node_updated_callback(event: EventType, node: MatterNode) -> None:
"""Handle node updated event."""
if node.node_id in initialized_nodes:
return
if not node.available:
return
initialized_nodes.add(node.node_id)
self._setup_node(node)
def endpoint_added_callback(event: EventType, data: dict[str, int]) -> None:
@@ -116,6 +132,11 @@ class MatterAdapter:
callback=node_added_callback, event_filter=EventType.NODE_ADDED
)
)
self.config_entry.async_on_unload(
self.matter_client.subscribe_events(
callback=node_updated_callback, event_filter=EventType.NODE_UPDATED
)
)
def _setup_node(self, node: MatterNode) -> None:
"""Set up an node."""

View File

@@ -129,6 +129,9 @@ class MatterEntity(Entity):
async def async_update(self) -> None:
"""Call when the entity needs to be updated."""
if not self._endpoint.node.available:
# skip poll when the node is not (yet) available
return
# manually poll/refresh the primary value
await self.matter_client.refresh_attribute(
self._endpoint.node.node_id,

View File

@@ -6,5 +6,5 @@
"dependencies": ["websocket_api"],
"documentation": "https://www.home-assistant.io/integrations/matter",
"iot_class": "local_push",
"requirements": ["python-matter-server==5.4.0"]
"requirements": ["python-matter-server==5.5.0"]
}

View File

@@ -22,7 +22,7 @@ from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60)
MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=15)
PLATFORMS = [Platform.CLIMATE, Platform.SENSOR, Platform.WATER_HEATER]
@@ -123,11 +123,6 @@ class MelCloudDevice:
via_device=(DOMAIN, f"{dev.mac}-{dev.serial}"),
)
@property
def daily_energy_consumed(self) -> float | None:
"""Return energy consumed during the current day in kWh."""
return self.device.daily_energy_consumed
async def mel_devices_setup(
hass: HomeAssistant, token: str
@@ -138,8 +133,8 @@ async def mel_devices_setup(
all_devices = await get_devices(
token,
session,
conf_update_interval=timedelta(minutes=5),
device_set_debounce=timedelta(seconds=1),
conf_update_interval=timedelta(minutes=30),
device_set_debounce=timedelta(seconds=2),
)
wrapped_devices: dict[str, list[MelCloudDevice]] = {}
for device_type, devices in all_devices.items():

View File

@@ -1,10 +1,10 @@
{
"domain": "melcloud",
"name": "MELCloud",
"codeowners": ["@vilppuvuorinen"],
"codeowners": [],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/melcloud",
"iot_class": "cloud_polling",
"loggers": ["pymelcloud"],
"requirements": ["pymelcloud==2.5.8"]
"requirements": ["pymelcloud==2.5.9"]
}

View File

@@ -58,16 +58,6 @@ ATA_SENSORS: tuple[MelcloudSensorEntityDescription, ...] = (
value_fn=lambda x: x.device.total_energy_consumed,
enabled=lambda x: x.device.has_energy_consumed_meter,
),
MelcloudSensorEntityDescription(
key="daily_energy",
translation_key="daily_energy",
icon="mdi:factory",
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL_INCREASING,
value_fn=lambda x: x.device.daily_energy_consumed,
enabled=lambda x: True,
),
)
ATW_SENSORS: tuple[MelcloudSensorEntityDescription, ...] = (
MelcloudSensorEntityDescription(
@@ -90,16 +80,6 @@ ATW_SENSORS: tuple[MelcloudSensorEntityDescription, ...] = (
value_fn=lambda x: x.device.tank_temperature,
enabled=lambda x: True,
),
MelcloudSensorEntityDescription(
key="daily_energy",
translation_key="daily_energy",
icon="mdi:factory",
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL_INCREASING,
value_fn=lambda x: x.device.daily_energy_consumed,
enabled=lambda x: True,
),
)
ATW_ZONE_SENSORS: tuple[MelcloudSensorEntityDescription, ...] = (
MelcloudSensorEntityDescription(

View File

@@ -65,9 +65,6 @@
"room_temperature": {
"name": "Room temperature"
},
"daily_energy": {
"name": "Daily energy consumed"
},
"outside_temperature": {
"name": "Outside temperature"
},

View File

@@ -4,9 +4,10 @@ from __future__ import annotations
import asyncio
import logging
import re
import sys
from typing import Any
import datapoint
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
CONF_API_KEY,
@@ -16,7 +17,7 @@ from homeassistant.const import (
Platform,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import ConfigEntryNotReady, HomeAssistantError
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.update_coordinator import TimestampDataUpdateCoordinator
@@ -34,9 +35,6 @@ from .const import (
from .data import MetOfficeData
from .helpers import fetch_data, fetch_site
if sys.version_info < (3, 12):
import datapoint
_LOGGER = logging.getLogger(__name__)
PLATFORMS = [Platform.SENSOR, Platform.WEATHER]
@@ -44,10 +42,6 @@ PLATFORMS = [Platform.SENSOR, Platform.WEATHER]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up a Met Office entry."""
if sys.version_info >= (3, 12):
raise HomeAssistantError(
"Met Office is not supported on Python 3.12. Please use Python 3.11."
)
latitude = entry.data[CONF_LATITUDE]
longitude = entry.data[CONF_LONGITUDE]

View File

@@ -2,12 +2,10 @@
from __future__ import annotations
from dataclasses import dataclass
import sys
if sys.version_info < (3, 12):
from datapoint.Forecast import Forecast
from datapoint.Site import Site
from datapoint.Timestep import Timestep
from datapoint.Forecast import Forecast
from datapoint.Site import Site
from datapoint.Timestep import Timestep
@dataclass

View File

@@ -2,7 +2,9 @@
from __future__ import annotations
import logging
import sys
import datapoint
from datapoint.Site import Site
from homeassistant.helpers.update_coordinator import UpdateFailed
from homeassistant.util.dt import utcnow
@@ -10,11 +12,6 @@ from homeassistant.util.dt import utcnow
from .const import MODE_3HOURLY
from .data import MetOfficeData
if sys.version_info < (3, 12):
import datapoint
from datapoint.Site import Site
_LOGGER = logging.getLogger(__name__)
@@ -34,7 +31,7 @@ def fetch_site(
def fetch_data(connection: datapoint.Manager, site: Site, mode: str) -> MetOfficeData:
"""Fetch weather and forecast from Datapoint API."""
try:
forecast = connection.get_forecast_for_site(site.id, mode)
forecast = connection.get_forecast_for_site(site.location_id, mode)
except (ValueError, datapoint.exceptions.APIException) as err:
_LOGGER.error("Check Met Office connection: %s", err.args)
raise UpdateFailed from err

View File

@@ -3,9 +3,8 @@
"name": "Met Office",
"codeowners": ["@MrHarcombe", "@avee87"],
"config_flow": true,
"disabled": "Integration library not compatible with Python 3.12",
"documentation": "https://www.home-assistant.io/integrations/metoffice",
"iot_class": "cloud_polling",
"loggers": ["datapoint"],
"requirements": ["datapoint==0.9.8;python_version<'3.12'"]
"requirements": ["datapoint==0.9.9"]
}

View File

@@ -251,6 +251,6 @@ class MetOfficeCurrentSensor(
return {
ATTR_LAST_UPDATE: self.coordinator.data.now.date,
ATTR_SENSOR_ID: self.entity_description.key,
ATTR_SITE_ID: self.coordinator.data.site.id,
ATTR_SITE_ID: self.coordinator.data.site.location_id,
ATTR_SITE_NAME: self.coordinator.data.site.name,
}

View File

@@ -1,4 +1,5 @@
"""Support for mill wifi-enabled home heaters."""
from typing import Any
import mill
@@ -186,9 +187,14 @@ class LocalMillHeater(CoordinatorEntity[MillDataUpdateCoordinator], ClimateEntit
_attr_max_temp = MAX_TEMP
_attr_min_temp = MIN_TEMP
_attr_name = None
_attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE
_attr_supported_features = (
ClimateEntityFeature.TARGET_TEMPERATURE
| ClimateEntityFeature.TURN_OFF
| ClimateEntityFeature.TURN_ON
)
_attr_target_temperature_step = PRECISION_TENTHS
_attr_temperature_unit = UnitOfTemperature.CELSIUS
_enable_turn_on_off_backwards_compatibility = False
def __init__(self, coordinator: MillDataUpdateCoordinator) -> None:
"""Initialize the thermostat."""

View File

@@ -186,7 +186,7 @@ BASE_STRUCT_SCHEMA = BASE_COMPONENT_SCHEMA.extend(
]
),
vol.Optional(CONF_STRUCTURE): cv.string,
vol.Optional(CONF_SCALE, default=1): cv.positive_float,
vol.Optional(CONF_SCALE, default=1): vol.Coerce(float),
vol.Optional(CONF_OFFSET, default=0): vol.Coerce(float),
vol.Optional(CONF_PRECISION): cv.positive_int,
vol.Optional(
@@ -241,8 +241,8 @@ CLIMATE_SCHEMA = vol.All(
{
vol.Required(CONF_TARGET_TEMP): cv.positive_int,
vol.Optional(CONF_TARGET_TEMP_WRITE_REGISTERS, default=False): cv.boolean,
vol.Optional(CONF_MAX_TEMP, default=35): cv.positive_float,
vol.Optional(CONF_MIN_TEMP, default=5): cv.positive_float,
vol.Optional(CONF_MAX_TEMP, default=35): vol.Coerce(float),
vol.Optional(CONF_MIN_TEMP, default=5): vol.Coerce(float),
vol.Optional(CONF_STEP, default=0.5): vol.Coerce(float),
vol.Optional(CONF_TEMPERATURE_UNIT, default=DEFAULT_TEMP_UNIT): cv.string,
vol.Optional(CONF_HVAC_ONOFF_REGISTER): cv.positive_int,
@@ -342,8 +342,8 @@ SENSOR_SCHEMA = vol.All(
vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string,
vol.Exclusive(CONF_VIRTUAL_COUNT, "vir_sen_count"): cv.positive_int,
vol.Exclusive(CONF_SLAVE_COUNT, "vir_sen_count"): cv.positive_int,
vol.Optional(CONF_MIN_VALUE): cv.positive_float,
vol.Optional(CONF_MAX_VALUE): cv.positive_float,
vol.Optional(CONF_MIN_VALUE): vol.Coerce(float),
vol.Optional(CONF_MAX_VALUE): vol.Coerce(float),
vol.Optional(CONF_NAN_VALUE): nan_validator,
vol.Optional(CONF_ZERO_SUPPRESS): cv.positive_float,
}

View File

@@ -199,6 +199,8 @@ class BaseStructPlatform(BasePlatform, RestoreEntity):
self._precision = config.get(CONF_PRECISION, 2)
else:
self._precision = config.get(CONF_PRECISION, 0)
if self._precision > 0 or self._scale != int(self._scale):
self._value_is_int = False
def _swap_registers(self, registers: list[int], slave_count: int) -> list[int]:
"""Do swap as needed."""

View File

@@ -364,7 +364,9 @@ class ModbusThermostat(BaseStructPlatform, RestoreEntity, ClimateEntity):
# Translate the value received
if fan_mode is not None:
self._attr_fan_mode = self._fan_mode_mapping_from_modbus[int(fan_mode)]
self._attr_fan_mode = self._fan_mode_mapping_from_modbus.get(
int(fan_mode), self._attr_fan_mode
)
# Read the on/off register if defined. If the value in this
# register is "OFF", it will take precedence over the value

View File

@@ -51,6 +51,7 @@ POSITION_DEVICE_MAP = {
BlindType.CurtainLeft: CoverDeviceClass.CURTAIN,
BlindType.CurtainRight: CoverDeviceClass.CURTAIN,
BlindType.SkylightBlind: CoverDeviceClass.SHADE,
BlindType.InsectScreen: CoverDeviceClass.SHADE,
}
TILT_DEVICE_MAP = {
@@ -69,6 +70,7 @@ TILT_ONLY_DEVICE_MAP = {
TDBU_DEVICE_MAP = {
BlindType.TopDownBottomUp: CoverDeviceClass.SHADE,
BlindType.TriangleBlind: CoverDeviceClass.BLIND,
}
@@ -398,6 +400,7 @@ class MotionTDBUDevice(MotionPositionDevice):
def __init__(self, coordinator, blind, device_class, motor):
"""Initialize the blind."""
super().__init__(coordinator, blind, device_class)
delattr(self, "_attr_name")
self._motor = motor
self._motor_key = motor[0]
self._attr_translation_key = motor.lower()

View File

@@ -21,5 +21,5 @@
"documentation": "https://www.home-assistant.io/integrations/motion_blinds",
"iot_class": "local_push",
"loggers": ["motionblinds"],
"requirements": ["motionblinds==0.6.19"]
"requirements": ["motionblinds==0.6.20"]
}

View File

@@ -1,5 +1,6 @@
"""Support for Motion Blinds sensors."""
from motionblinds import DEVICE_TYPES_WIFI, BlindType
from motionblinds import DEVICE_TYPES_WIFI
from motionblinds.motion_blinds import DEVICE_TYPE_TDBU
from homeassistant.components.sensor import SensorDeviceClass, SensorEntity
from homeassistant.config_entries import ConfigEntry
@@ -29,7 +30,7 @@ async def async_setup_entry(
for blind in motion_gateway.device_list.values():
entities.append(MotionSignalStrengthSensor(coordinator, blind))
if blind.type == BlindType.TopDownBottomUp:
if blind.device_type == DEVICE_TYPE_TDBU:
entities.append(MotionTDBUBatterySensor(coordinator, blind, "Bottom"))
entities.append(MotionTDBUBatterySensor(coordinator, blind, "Top"))
elif blind.battery_voltage is not None and blind.battery_voltage > 0:

View File

@@ -75,7 +75,7 @@ class MyUplinkDevicePointSensor(MyUplinkEntity, SensorEntity):
# Internal properties
self.point_id = device_point.parameter_id
self._attr_name = device_point.parameter_name
self._attr_name = device_point.parameter_name.replace("\u002d", "")
if entity_description is not None:
self.entity_description = entity_description

View File

@@ -212,7 +212,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
host = entry.data[CONF_HOST]
password = entry.data[CONF_PASSWORD]
if DOMAIN not in hass.data:
if not (data := hass.data.get(DOMAIN)) or data.websession.closed:
websession = async_create_clientsession(hass, cookie_jar=CookieJar(unsafe=True))
hass.data[DOMAIN] = LTEData(websession)
@@ -258,7 +258,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
if entry.state == ConfigEntryState.LOADED
]
if len(loaded_entries) == 1:
hass.data.pop(DOMAIN)
hass.data.pop(DOMAIN, None)
return unload_ok

View File

@@ -5,5 +5,5 @@
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/nibe_heatpump",
"iot_class": "local_polling",
"requirements": ["nibe==2.7.0"]
"requirements": ["nibe==2.8.0"]
}

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.5.0"]
"requirements": ["python-otbr-api==2.6.0"]
}

View File

@@ -7,5 +7,5 @@
"integration_type": "service",
"iot_class": "cloud_polling",
"loggers": ["aiopegelonline"],
"requirements": ["aiopegelonline==0.0.6"]
"requirements": ["aiopegelonline==0.0.8"]
}

View File

@@ -63,11 +63,6 @@ class PlugwiseClimateEntity(PlugwiseEntity, ClimateEntity):
self.gateway_data = coordinator.data.devices[gateway_id]
# Determine supported features
self._attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE
if HVACMode.OFF in self.hvac_modes:
self._attr_supported_features |= (
ClimateEntityFeature.TURN_OFF | ClimateEntityFeature.TURN_ON
)
if (
self.cdr_gateway["cooling_present"]
and self.cdr_gateway["smile_name"] != "Adam"
@@ -75,6 +70,10 @@ class PlugwiseClimateEntity(PlugwiseEntity, ClimateEntity):
self._attr_supported_features = (
ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
)
if HVACMode.OFF in self.hvac_modes:
self._attr_supported_features |= (
ClimateEntityFeature.TURN_OFF | ClimateEntityFeature.TURN_ON
)
if presets := self.device.get("preset_modes"):
self._attr_supported_features |= ClimateEntityFeature.PRESET_MODE
self._attr_preset_modes = presets

View File

@@ -21,7 +21,10 @@ from homeassistant.components.climate import (
ATTR_TARGET_TEMP_LOW,
HVACAction,
)
from homeassistant.components.cover import ATTR_POSITION, ATTR_TILT_POSITION
from homeassistant.components.cover import (
ATTR_CURRENT_POSITION,
ATTR_CURRENT_TILT_POSITION,
)
from homeassistant.components.http import HomeAssistantView
from homeassistant.components.humidifier import ATTR_AVAILABLE_MODES, ATTR_HUMIDITY
from homeassistant.components.light import ATTR_BRIGHTNESS
@@ -437,7 +440,7 @@ class PrometheusMetrics:
float(cover_state == state.state)
)
position = state.attributes.get(ATTR_POSITION)
position = state.attributes.get(ATTR_CURRENT_POSITION)
if position is not None:
position_metric = self._metric(
"cover_position",
@@ -446,7 +449,7 @@ class PrometheusMetrics:
)
position_metric.labels(**self._labels(state)).set(float(position))
tilt_position = state.attributes.get(ATTR_TILT_POSITION)
tilt_position = state.attributes.get(ATTR_CURRENT_TILT_POSITION)
if tilt_position is not None:
tilt_position_metric = self._metric(
"cover_tilt_position",

View File

@@ -69,6 +69,7 @@ class TrackedEntityDescriptor(NamedTuple):
entity_id: str
identifier: str
name: str
def _device_info(coordinator: ProximityDataUpdateCoordinator) -> DeviceInfo:
@@ -95,13 +96,24 @@ async def async_setup_entry(
entity_reg = er.async_get(hass)
for tracked_entity_id in coordinator.tracked_entities:
tracked_entity_object_id = tracked_entity_id.split(".")[-1]
if (entity_entry := entity_reg.async_get(tracked_entity_id)) is not None:
tracked_entity_descriptors.append(
TrackedEntityDescriptor(tracked_entity_id, entity_entry.id)
TrackedEntityDescriptor(
tracked_entity_id,
entity_entry.id,
entity_entry.name
or entity_entry.original_name
or tracked_entity_object_id,
)
)
else:
tracked_entity_descriptors.append(
TrackedEntityDescriptor(tracked_entity_id, tracked_entity_id)
TrackedEntityDescriptor(
tracked_entity_id,
tracked_entity_id,
tracked_entity_object_id,
)
)
entities += [
@@ -165,7 +177,7 @@ class ProximityTrackedEntitySensor(
self._attr_unique_id = f"{coordinator.config_entry.entry_id}_{tracked_entity_descriptor.identifier}_{description.key}"
self._attr_device_info = _device_info(coordinator)
self._attr_translation_placeholders = {
"tracked_entity": self.tracked_entity_id.split(".")[-1]
"tracked_entity": tracked_entity_descriptor.name
}
async def async_added_to_hass(self) -> None:
@@ -176,18 +188,19 @@ class ProximityTrackedEntitySensor(
)
@property
def data(self) -> dict[str, str | int | None] | None:
def data(self) -> dict[str, str | int | None]:
"""Get data from coordinator."""
return self.coordinator.data.entities.get(self.tracked_entity_id)
return self.coordinator.data.entities[self.tracked_entity_id]
@property
def available(self) -> bool:
"""Return if entity is available."""
return super().available and self.data is not None
return (
super().available
and self.tracked_entity_id in self.coordinator.data.entities
)
@property
def native_value(self) -> str | float | None:
"""Return native sensor value."""
if self.data is None:
return None
return self.data.get(self.entity_description.key)

View File

@@ -96,7 +96,7 @@ class DiskSpaceDataUpdateCoordinator(RadarrDataUpdateCoordinator[list[RootFolder
"""Fetch the data."""
root_folders = await self.api_client.async_get_root_folders()
if isinstance(root_folders, RootFolder):
root_folders = [root_folders]
return [root_folders]
return root_folders
@@ -105,7 +105,10 @@ class HealthDataUpdateCoordinator(RadarrDataUpdateCoordinator[list[Health]]):
async def _fetch_data(self) -> list[Health]:
"""Fetch the health data."""
return await self.api_client.async_get_failed_health_checks()
health = await self.api_client.async_get_failed_health_checks()
if isinstance(health, Health):
return [health]
return health
class MoviesDataUpdateCoordinator(RadarrDataUpdateCoordinator[int]):

View File

@@ -13,5 +13,5 @@
"documentation": "https://www.home-assistant.io/integrations/ring",
"iot_class": "cloud_polling",
"loggers": ["ring_doorbell"],
"requirements": ["ring-doorbell[listen]==0.8.5"]
"requirements": ["ring-doorbell[listen]==0.8.7"]
}

View File

@@ -115,6 +115,7 @@ async def setup_device(
device.name,
)
_LOGGER.debug(err)
await mqtt_client.async_release()
raise err
coordinator = RoborockDataUpdateCoordinator(
hass, device, networking, product_info, mqtt_client
@@ -125,6 +126,7 @@ async def setup_device(
try:
await coordinator.async_config_entry_first_refresh()
except ConfigEntryNotReady:
await coordinator.release()
if isinstance(coordinator.api, RoborockMqttClient):
_LOGGER.warning(
"Not setting up %s because the we failed to get data for the first time using the online client. "
@@ -153,14 +155,10 @@ async def setup_device(
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Handle removal of an entry."""
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok:
await asyncio.gather(
*(
coordinator.release()
for coordinator in hass.data[DOMAIN][entry.entry_id].values()
)
)
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
release_tasks = set()
for coordinator in hass.data[DOMAIN][entry.entry_id].values():
release_tasks.add(coordinator.release())
hass.data[DOMAIN].pop(entry.entry_id)
await asyncio.gather(*release_tasks)
return unload_ok

View File

@@ -77,7 +77,8 @@ class RoborockDataUpdateCoordinator(DataUpdateCoordinator[DeviceProp]):
async def release(self) -> None:
"""Disconnect from API."""
await self.api.async_disconnect()
await self.api.async_release()
await self.cloud_api.async_release()
async def _update_device_prop(self) -> None:
"""Update device properties."""

View File

@@ -1,5 +1,4 @@
"""Support for Roborock device base class."""
from typing import Any
from roborock.api import AttributeCache, RoborockClient
@@ -7,6 +6,7 @@ from roborock.cloud_api import RoborockMqttClient
from roborock.command_cache import CacheableAttribute
from roborock.containers import Consumable, Status
from roborock.exceptions import RoborockException
from roborock.roborock_message import RoborockDataProtocol
from roborock.roborock_typing import RoborockCommand
from homeassistant.exceptions import HomeAssistantError
@@ -24,7 +24,10 @@ class RoborockEntity(Entity):
_attr_has_entity_name = True
def __init__(
self, unique_id: str, device_info: DeviceInfo, api: RoborockClient
self,
unique_id: str,
device_info: DeviceInfo,
api: RoborockClient,
) -> None:
"""Initialize the coordinated Roborock Device."""
self._attr_unique_id = unique_id
@@ -75,6 +78,9 @@ class RoborockCoordinatedEntity(
self,
unique_id: str,
coordinator: RoborockDataUpdateCoordinator,
listener_request: list[RoborockDataProtocol]
| RoborockDataProtocol
| None = None,
) -> None:
"""Initialize the coordinated Roborock Device."""
RoborockEntity.__init__(
@@ -85,6 +91,23 @@ class RoborockCoordinatedEntity(
)
CoordinatorEntity.__init__(self, coordinator=coordinator)
self._attr_unique_id = unique_id
if isinstance(listener_request, RoborockDataProtocol):
listener_request = [listener_request]
self.listener_requests = listener_request or []
async def async_added_to_hass(self) -> None:
"""Add listeners when the device is added to hass."""
await super().async_added_to_hass()
for listener_request in self.listener_requests:
self.api.add_listener(
listener_request, self._update_from_listener, cache=self.api.cache
)
async def async_will_remove_from_hass(self) -> None:
"""Remove listeners when the device is removed from hass."""
for listener_request in self.listener_requests:
self.api.remove_listener(listener_request, self._update_from_listener)
await super().async_will_remove_from_hass()
@property
def _device_status(self) -> Status:
@@ -107,7 +130,7 @@ class RoborockCoordinatedEntity(
await self.coordinator.async_refresh()
return res
def _update_from_listener(self, value: Status | Consumable):
def _update_from_listener(self, value: Status | Consumable) -> None:
"""Update the status or consumable data from a listener and then write the new entity state."""
if isinstance(value, Status):
self.coordinator.roborock_device_info.props.status = value

View File

@@ -107,10 +107,8 @@ class RoborockSelectEntity(RoborockCoordinatedEntity, SelectEntity):
) -> None:
"""Create a select entity."""
self.entity_description = entity_description
super().__init__(unique_id, coordinator)
super().__init__(unique_id, coordinator, entity_description.protocol_listener)
self._attr_options = options
if (protocol := self.entity_description.protocol_listener) is not None:
self.api.add_listener(protocol, self._update_from_listener, self.api.cache)
async def async_select_option(self, option: str) -> None:
"""Set the option."""

View File

@@ -232,10 +232,8 @@ class RoborockSensorEntity(RoborockCoordinatedEntity, SensorEntity):
description: RoborockSensorDescription,
) -> None:
"""Initialize the entity."""
super().__init__(unique_id, coordinator)
self.entity_description = description
if (protocol := self.entity_description.protocol_listener) is not None:
self.api.add_listener(protocol, self._update_from_listener, self.api.cache)
super().__init__(unique_id, coordinator, description.protocol_listener)
@property
def native_value(self) -> StateType | datetime.datetime:

View File

@@ -92,14 +92,16 @@ class RoborockVacuum(RoborockCoordinatedEntity, StateVacuumEntity):
) -> None:
"""Initialize a vacuum."""
StateVacuumEntity.__init__(self)
RoborockCoordinatedEntity.__init__(self, unique_id, coordinator)
RoborockCoordinatedEntity.__init__(
self,
unique_id,
coordinator,
listener_request=[
RoborockDataProtocol.FAN_POWER,
RoborockDataProtocol.STATE,
],
)
self._attr_fan_speed_list = self._device_status.fan_power_options
self.api.add_listener(
RoborockDataProtocol.FAN_POWER, self._update_from_listener, self.api.cache
)
self.api.add_listener(
RoborockDataProtocol.STATE, self._update_from_listener, self.api.cache
)
@property
def state(self) -> str | None:

View File

@@ -0,0 +1,22 @@
{
"config": {
"flow_title": "[%key:component::bluetooth::config::flow_title%]",
"step": {
"user": {
"description": "[%key:component::bluetooth::config::step::user::description%]",
"data": {
"address": "[%key:common::config_flow::data::device%]"
}
},
"bluetooth_confirm": {
"description": "[%key:component::bluetooth::config::step::bluetooth_confirm::description%]"
}
},
"abort": {
"not_supported": "Device not supported",
"no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]",
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]",
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
}
}
}

View File

@@ -39,7 +39,7 @@
"samsungctl[websocket]==0.7.1",
"samsungtvws[async,encrypted]==2.6.0",
"wakeonlan==2.1.0",
"async-upnp-client==0.38.1"
"async-upnp-client==0.38.2"
],
"ssdp": [
{

View File

@@ -173,9 +173,9 @@ async def async_setup_entry(
platform.async_register_entity_service(
SERVICE_ENABLE_CLIMATE_REACT,
{
vol.Required(ATTR_HIGH_TEMPERATURE_THRESHOLD): float,
vol.Required(ATTR_HIGH_TEMPERATURE_THRESHOLD): vol.Coerce(float),
vol.Required(ATTR_HIGH_TEMPERATURE_STATE): dict,
vol.Required(ATTR_LOW_TEMPERATURE_THRESHOLD): float,
vol.Required(ATTR_LOW_TEMPERATURE_THRESHOLD): vol.Coerce(float),
vol.Required(ATTR_LOW_TEMPERATURE_STATE): dict,
vol.Required(ATTR_SMART_TYPE): vol.In(
["temperature", "feelsLike", "humidity"]

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