Compare commits

...

120 Commits

Author SHA1 Message Date
Franck Nijhof
c09f15b0e9 Bump version to 2024.10.4 2024-10-25 20:49:36 +02:00
Joost Lekkerkerker
9a44d668d6 Bump nyt_games to 0.4.4 (#129152) 2024-10-25 20:43:16 +02:00
Joost Lekkerkerker
67e0197a7a Fix NYT Games connection max streak (#129149) 2024-10-25 20:43:09 +02:00
Guido Schmitz
a5a8cfa17d Fix adding multiple devices simultaneously to devolo Home Network's device tracker (#129082) 2024-10-25 20:43:02 +02:00
tronikos
60c3e701e9 Partially revert "LLM Tool parameters check (#123621)" (#129064) 2024-10-25 20:42:55 +02:00
Bram Kragten
b9b129dcf5 Update frontend to 20241002.4 (#129049) 2024-10-25 20:42:48 +02:00
Daniel Albers
d882ab236a Remove DHCP match from awair (#129047)
Co-authored-by: Joostlek <joostlek@outlook.com>
2024-10-25 20:42:40 +02:00
Joost Lekkerkerker
140cc0e486 Bump yt-dlp to 2024.10.22 (#129034) 2024-10-25 20:42:17 +02:00
Guido Schmitz
6ac7c0f893 Fix devolo_home_network devices not reporting a MAC address (#129021) 2024-10-25 20:42:11 +02:00
J. Nick Koston
096d50617f Fix cancellation leaking upward from the timeout util (#129003) 2024-10-25 20:42:04 +02:00
Simone Chemelli
9dd8c0cc4f Fix uptime floating values for Vodafone Station (#128974) 2024-10-25 20:41:57 +02:00
Maikel Punie
de0fab86ec Bump pyduotecno to 2024.10.1 (#128968) 2024-10-25 20:39:38 +02:00
Simone Chemelli
ada837ee95 Add diagnostics to Vodafone Station (#128923)
* Add diagnostics to Vodafone Station

* cleanup and exclude props based on date
2024-10-25 20:22:47 +02:00
Daniel Hjelseth Høyer
67e73173f6 Bump pyTibber to 0.30.3 (#128860) 2024-10-25 20:22:40 +02:00
Simone Chemelli
029411d3fa Add diagnostics to Comelit SimpleHome (#128794)
* Add diagnostics to Comelit SimpleHome

* add test

* add missing tests

* introduce SnapshotAssertion

* cleanup

* exclude date based props
2024-10-25 20:12:54 +02:00
Steven B.
6ba033f934 Bump ring-doorbell library to 0.9.8 (#128662) 2024-10-25 20:12:48 +02:00
Steven B.
336742e335 Bump ring-doorbell to 0.9.7 (#127554) 2024-10-25 20:12:41 +02:00
mkmer
4a94430bf0 Handle temprorary hold in Honeywell (#128460) 2024-10-25 20:05:14 +02:00
David Bonnes
cc337f7b1e Fix evohome regression preventing helpful messages when setup fails (#126441)
Co-authored-by: Robert Resch <robert@resch.dev>
2024-10-25 20:05:05 +02:00
Franck Nijhof
515771553f Bump version to 2024.10.3 2024-10-18 13:22:48 +02:00
mvn23
e204812d2b Bump pyotgw to 2.2.2 (#128594) 2024-10-18 13:22:22 +02:00
Petar Petrov
ca703cb858 Increase Z-Wave fallback thermostat range to 0-50 C (#128543)
* Z-Wave JS: Increase fallback thermostat range to 0-50 C

* update test
2024-10-18 13:21:49 +02:00
Louis Christ
b018d4a97d Bump pyblu to 1.0.4 (#128482) 2024-10-18 13:21:46 +02:00
dontinelli
146768ff8a Bump solarlog_cli to 0.3.2 (#128293) 2024-10-18 13:21:43 +02:00
Allen Porter
ea7473ed67 Bump gcal_sync to 6.1.6 (#128270) 2024-10-18 13:21:40 +02:00
Álvaro Fernández Rojas
0e8393766f Update aioairzone to v0.9.5 (#128265)
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
2024-10-18 13:21:36 +02:00
Álvaro Fernández Rojas
7d2536c503 Update aioairzone to v0.9.4 (#127792) 2024-10-18 13:21:31 +02:00
Simon Lamon
f9cbf1b30c Keep the provided name when creating a tag (#128240)
* Keep the name

* Add patch

* Update homeassistant/components/tag/__init__.py

Co-authored-by: G Johansson <goran.johansson@shiftit.se>

---------

Co-authored-by: G Johansson <goran.johansson@shiftit.se>
2024-10-18 13:20:21 +02:00
Adam Petrovic
d66d87d271 Fix daikin entities not refreshing quickly (#128230)
* Fix daikin entities not refreshing quickly

* Update homeassistant/components/daikin/switch.py

---------

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2024-10-18 13:20:18 +02:00
Joost Lekkerkerker
5a8fa6cf38 Bump yt-dlp to 2024.10.07 (#128182) 2024-10-18 13:20:15 +02:00
Chris Talkington
76340035db Fix playing media via roku (#128133)
* re-support playing media via roku

* fixes

* test fixes

* Update test_media_player.py

* always send media type

* add description to options flow
2024-10-18 13:20:12 +02:00
puddly
0a26e68d0c Use the same ZHA database path during startup and when loading device triggers (#128130)
Use the same zigpy database path source as in the radio manager
2024-10-18 13:20:09 +02:00
Marc Hörsken
14127b910f Improve discovery of WMS WebControl pro by updating IP address (#128007) 2024-10-18 13:20:06 +02:00
Michael
ba4d081021 Fix printer uptime fluctuations in IPP (#127725)
* decrease uptime accuracy from seconds to minutes

* adjust tests

* calc uptime timestamp in coordinator

* bump pyipp to 0.17.0

* revert changes, just use the new printer.booted_at property

---------

Co-authored-by: Chris Talkington <chris@talkingtontech.com>
2024-10-18 13:19:43 +02:00
Marc Mueller
18d65d513e Update home-assistant-bluetooth to 1.13.0 (#127691) 2024-10-18 13:18:52 +02:00
Franck Nijhof
9176994947 Bump version to 2024.10.2 2024-10-11 17:54:37 +02:00
Thomas55555
d389b55f40 Fix model in Husqvarna Automower (#128168) 2024-10-11 17:54:24 +02:00
Christopher Fenner
0ccff9fc54 Fix preset handling issue in ViCare (#128167)
* add test case

* fix test case

* fix issue

* change order
2024-10-11 17:54:21 +02:00
tronikos
a8836ca7b6 Remove some redundant code in Opower's coordinator from the fix in #128141 (#128150) 2024-10-11 17:54:17 +02:00
tronikos
f5d04a970f Bump opower to 0.8.3 (#128144) 2024-10-11 17:54:14 +02:00
tronikos
7aec98dafd Fix regression in Opower that was introduced in 2024.10.0 (#128141)
* Avoid KeyError when statistics have gaps

* fix break

* Remove unnecessary check
2024-10-11 17:54:10 +02:00
Marc Mueller
773564d4f5 Fix license script for ftfy (#128138) 2024-10-11 17:54:04 +02:00
Thomas55555
3e2edc1a2d Bump aioautomower to 2024.10.0 (#128137)
Co-authored-by: Marc Mueller <30130371+cdce8p@users.noreply.github.com>
2024-10-11 17:53:59 +02:00
Bram Kragten
9cfc9b9baf Update frontend to 20241002.3 (#128106) 2024-10-11 17:52:03 +02:00
David Knowles
92b67ead83 Increase Hydrawise polling interval to 60 seconds (#128090) 2024-10-11 17:50:35 +02:00
Steven B
ee9525cc00 Fix ring realtime events (#128083) 2024-10-11 17:49:38 +02:00
kingy444
571bfaf5d7 Fix casing on Powerview Gen3 zeroconf discovery (#128076) 2024-10-11 17:37:31 +02:00
Matthias Alphart
a3475607b2 Update xknxproject to 3.8.1 (#128057) 2024-10-11 17:37:28 +02:00
Michael
f0a653d010 Add missing translation string for re-auth flows (#128055) 2024-10-11 17:37:23 +02:00
Steven B.
eecdf66013 Fix missing reauth name translation placeholder in ring integration (#128048) 2024-10-11 17:37:19 +02:00
dontinelli
f99db05a4a Add missing translation string in solarlog (#128015) 2024-10-11 17:37:16 +02:00
Teemu R.
635731421f Increase tplink climate precision (#127996) 2024-10-11 17:37:13 +02:00
Maikel Punie
44743df7d6 Bump pyduotecno to 2024.10.0 (#127979) 2024-10-11 17:37:09 +02:00
epenet
33617694cc Fix firmware version parsing in venstar (#127974) 2024-10-11 17:37:06 +02:00
Raman Gupta
ed445d20b9 Fix zwave_js config validation for values (#127972) 2024-10-11 17:37:02 +02:00
Lenn
66c2fe091b Bump motionblindsble to 0.1.2 (#127954) 2024-10-11 17:36:59 +02:00
Antoine Reversat
8c80f47a35 Fix europe authentication in Fujitsu FGLair (#127947) 2024-10-11 17:36:53 +02:00
Marc Hörsken
e37025c1c7 Update pywmspro to 0.2.1 to fix handling of unknown products (#127942) 2024-10-11 17:36:50 +02:00
Marc Hörsken
0aabde081b Fix discovery of WMS WebControl pro by using IP address (#127939) 2024-10-11 17:36:46 +02:00
Steven B.
a1c9d53474 Bump python-kasa to 0.7.5 (#127934) 2024-10-11 17:36:43 +02:00
Maciej Bieniek
094996ad0c Bump imgw_pib library to version 1.0.6 (#127925)
Bump `imgw_pib`
2024-10-11 17:36:40 +02:00
Manu
ce359a7689 Add support of due date calculation for grey dailies in Habitica integration (#127923)
Fix grey dailies due date calculation
2024-10-11 17:36:36 +02:00
epenet
ee599160b3 Add missing translation string in yamaha_musiccast (#127912) 2024-10-11 17:36:32 +02:00
epenet
dd076f7a13 Add missing translation string in otbr (#127909) 2024-10-11 17:36:29 +02:00
dcmeglio
3021d38b6f Bump pyeconet to 0.1.23 (#127896) 2024-10-11 17:36:25 +02:00
G Johansson
bfcabeaf26 Bump holidays library to 0.58 (#127876) 2024-10-11 17:36:22 +02:00
G Johansson
c31e0336dc Don't error with missing information in systemmonitor diagnostics (#127868) 2024-10-11 17:36:14 +02:00
G Johansson
a1e42cac7a Fix merge_response template not mutate original object (#127865)
* Fix merge_response template not mutate original object

* Add comment
2024-10-11 17:36:11 +02:00
Michael
14a3e5b771 Add missing translation string in AVM Fritz!Smarthome (#127864) 2024-10-11 17:36:07 +02:00
azerty9971
5901c543da Fix wrong DPTypes returned by Tuya's cloud (#127860)
Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
2024-10-11 17:36:04 +02:00
Jon Seager
456b80e6ae Bump pytouchlinesl to 0.1.8 (#127859) 2024-10-11 17:36:00 +02:00
dontinelli
e5644ae011 Reverse unintended change of unique_id for solarlog (#127845) 2024-10-11 17:35:57 +02:00
epenet
41c794c733 Add missing and fix incorrect translation string in duotecno (#127834) 2024-10-11 17:35:53 +02:00
epenet
a481448d46 Fix incorrect translation string in bryant_evolution (#127830) 2024-10-11 17:35:49 +02:00
epenet
2bd7ce618a Add missing translation string in blebox (#127827) 2024-10-11 17:35:45 +02:00
Erik Montnemery
da1ac4f1e9 Correct cleanup of sensor statistics repairs (#127826) 2024-10-11 17:35:42 +02:00
epenet
f0cb638106 Fix incorrect translation string in azure event hub (#127820) 2024-10-11 17:35:38 +02:00
epenet
dad2396d01 Add missing and fix incorrect translation string in aurora (#127818) 2024-10-11 17:35:34 +02:00
epenet
91e4d8b663 Fix incorrect translation string in analytics_insights (#127815) 2024-10-11 17:35:31 +02:00
epenet
e35496133e Add missing and fix incorrect translation string in alarmdecoder (#127814) 2024-10-11 17:35:28 +02:00
epenet
3be808ae1e Fix incorrect string in amberlectric (#127807) 2024-10-11 17:35:23 +02:00
TimL
c5772916a1 Bump pysmlight to v0.1.3 (#127804)
Bump pysmlight v0.1.3

Co-authored-by: Tim Lunn <tim@feathertop.org>
2024-10-11 17:35:18 +02:00
Álvaro Fernández Rojas
8cd63b80b1 Update aioairzone-cloud to v0.6.6 (#127789) 2024-10-11 17:35:14 +02:00
Johan Gustafsson
c087654386 Fix aurora alert sensor always Off (#127780) 2024-10-11 17:35:11 +02:00
Erik Montnemery
60b9e65c78 Bump pychromecast to 14.0.3 (#127778) 2024-10-11 17:35:08 +02:00
J. Nick Koston
79b304a5d2 Bump DoorBirdPy to 3.0.4 (#127760)
changelog: https://gitlab.com/klikini/doorbirdpy/-/compare/3.0.3...eea287316c6fd84b63cc67fd743cc1128ea14568?from_project_id=7409088&straight=false

fixes #126598
2024-10-11 17:35:00 +02:00
Franck Nijhof
bb9fd126e5 Update DoorBirdPy to 3.0.3 (#126949) 2024-10-11 17:34:40 +02:00
dontinelli
bff2d5c26c Bump solarlog_cli to 0.3.1 (#127753) 2024-10-11 17:31:18 +02:00
Ricardo Marques
46d9ac8380 Fix custom account config flow setup (#127750) 2024-10-11 17:31:15 +02:00
Simon Lamon
5da3ca4bb1 Bump python-linkplay to 0.0.15 (#127748) 2024-10-11 17:31:12 +02:00
Johan Gustafsson
2c99fdc092 Fix Aurora integration casts longitude and latitude to integer (#127740)
Fix Aurora integration casts longitude and latitude to integer (#100817)
2024-10-11 17:31:07 +02:00
Raj Laud
31a075fb13 Remove stale references in squeezebox services.yaml (#127739) 2024-10-11 17:31:04 +02:00
René Klomp
1d132d7a1e Migrate SMA unique id to str (#127732) 2024-10-11 17:31:01 +02:00
Michael
3b6f88cfa7 Increase connection timeout in CalDAV (#127727) 2024-10-11 17:30:57 +02:00
Joost Lekkerkerker
b927763d8d Add translation string for Withings wrong account (#127719) 2024-10-11 17:30:54 +02:00
Joost Lekkerkerker
d00e1cb6a5 Bump airgradient to 0.9.1 (#127718) 2024-10-11 17:30:50 +02:00
Joost Lekkerkerker
adf7474edb Bump NYT Games to 0.4.3 (#127717) 2024-10-11 17:30:46 +02:00
Joost Lekkerkerker
041d663cb8 Fix Withings log message (#127716) 2024-10-11 17:30:42 +02:00
Joost Lekkerkerker
37f611a8d3 Fix typo in HDMI CEC (#127714) 2024-10-11 17:30:39 +02:00
David Knowles
be99329efa Fix problems with automatic management of Schlage locks (#127689)
Use the correct identifiers for existing lock devices
2024-10-11 17:30:35 +02:00
Marc Mueller
327cb70bb8 Revert "Fix enum lookup (#125220)" (#127680)
This reverts commit 1bc63a61be.
2024-10-11 17:30:32 +02:00
dontinelli
be2b5a4c3a Bump fyta_cli to 0.6.7 (#127650) 2024-10-11 17:30:29 +02:00
Richard Cox
d1eda9dd73 Update Radarr config flow to standardize ports (#127620) 2024-10-11 17:30:25 +02:00
Brett Adams
b902cb5a13 Fix wake up in Tesla Fleet (#127615) 2024-10-11 17:30:21 +02:00
tronikos
1184ee4a59 Bump opower to 0.8.2 (#127598)
* Bump opower to 0.8.1 to fix enmax

* Update manifest.json

* Update requirements_all.txt

* Update requirements_test_all.txt
2024-10-11 17:30:18 +02:00
Noah Husby
2cf898afcc Bump aiostreammagic to 2.5.0 (#127595) 2024-10-11 17:30:13 +02:00
Louis Christ
df53e19eda Bump pyblu to 1.0.3 (#127571) 2024-10-11 17:28:18 +02:00
Brett Adams
7f79b26341 Fix Island status in Teslemetry (#127504) 2024-10-11 17:28:13 +02:00
Franck Nijhof
2cbf53ad7b Bump version to 2024.10.1 2024-10-04 14:57:14 +02:00
Joost Lekkerkerker
c52607b465 Revert "Bump pychromecast to 14.0.2 (#127333)" (#127555)
This reverts commit 2ab66f62fa.
2024-10-04 14:57:01 +02:00
Joost Lekkerkerker
087566072d Strip the NYT Games token (#127548) 2024-10-04 14:56:58 +02:00
Joost Lekkerkerker
6b814afd39 Create new clientsession for NYT Games (#127547) 2024-10-04 14:56:54 +02:00
Franck Nijhof
ea8aa6b07d Adjust polling rate of Rituals Perfume Genie (#127544) 2024-10-04 14:56:51 +02:00
Paarth Shah
1b0f731e30 Bump matrix-nio to 0.25.2 (#127535) 2024-10-04 14:56:48 +02:00
robinostlund
1ebde4a880 Fix int value in unique_id for Tellduslive (#127526)
Fix int in unique_id
2024-10-04 14:56:45 +02:00
Andrew Jackson
e53bd477b4 Bump aiomealie to 0.9.3 (#127454) 2024-10-04 14:56:41 +02:00
Brett Adams
3f9287c36b Add missing number platform to init of Tesla Fleet (#127406)
Add number platform to init
2024-10-04 14:56:38 +02:00
Erik Montnemery
b2b940fc32 Remove assumption in ConfigEntryItems about unique unique_id (#127399) 2024-10-04 14:56:35 +02:00
TimL
7d9e170512 Bump pysmlight 0.1.2 (#127376)
Co-authored-by: Tim Lunn <tim@feathertop.org>
2024-10-04 14:56:29 +02:00
Paul Bottein
6ab92abe80 Fix device id support for alarm control panel template (#127340) 2024-10-04 14:47:25 +02:00
192 changed files with 2573 additions and 590 deletions

View File

@@ -37,7 +37,7 @@ on:
type: boolean
env:
CACHE_VERSION: 10
CACHE_VERSION: 11
UV_CACHE_VERSION: 1
MYPY_CACHE_VERSION: 9
HA_SHORT_VERSION: "2024.10"

View File

@@ -6,6 +6,6 @@
"documentation": "https://www.home-assistant.io/integrations/airgradient",
"integration_type": "device",
"iot_class": "local_polling",
"requirements": ["airgradient==0.9.0"],
"requirements": ["airgradient==0.9.1"],
"zeroconf": ["_airgradient._tcp.local."]
}

View File

@@ -85,6 +85,7 @@ HVAC_MODE_LIB_TO_HASS: Final[dict[OperationMode, HVACMode]] = {
OperationMode.HEATING: HVACMode.HEAT,
OperationMode.FAN: HVACMode.FAN_ONLY,
OperationMode.DRY: HVACMode.DRY,
OperationMode.AUX_HEATING: HVACMode.HEAT,
OperationMode.AUTO: HVACMode.HEAT_COOL,
}
HVAC_MODE_HASS_TO_LIB: Final[dict[HVACMode, OperationMode]] = {
@@ -157,9 +158,10 @@ class AirzoneClimate(AirzoneZoneEntity, ClimateEntity):
self._attr_temperature_unit = TEMP_UNIT_LIB_TO_HASS[
self.get_airzone_value(AZD_TEMP_UNIT)
]
self._attr_hvac_modes = [
_attr_hvac_modes = [
HVAC_MODE_LIB_TO_HASS[mode] for mode in self.get_airzone_value(AZD_MODES)
]
self._attr_hvac_modes = list(dict.fromkeys(_attr_hvac_modes))
if (
self.get_airzone_value(AZD_SPEED) is not None
and self.get_airzone_value(AZD_SPEEDS) is not None

View File

@@ -11,5 +11,5 @@
"documentation": "https://www.home-assistant.io/integrations/airzone",
"iot_class": "local_polling",
"loggers": ["aioairzone"],
"requirements": ["aioairzone==0.9.3"]
"requirements": ["aioairzone==0.9.5"]
}

View File

@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/airzone_cloud",
"iot_class": "cloud_push",
"loggers": ["aioairzone_cloud"],
"requirements": ["aioairzone-cloud==0.6.5"]
"requirements": ["aioairzone-cloud==0.6.6"]
}

View File

@@ -22,7 +22,8 @@
}
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"unknown": "[%key:common::config_flow::error::unknown%]"
},
"create_entry": {
"default": "Successfully connected to AlarmDecoder."
@@ -37,7 +38,7 @@
"title": "Configure AlarmDecoder",
"description": "What would you like to edit?",
"data": {
"edit_select": "Edit"
"edit_selection": "Edit"
}
},
"arm_settings": {

View File

@@ -10,7 +10,7 @@
},
"site": {
"data": {
"site_nmi": "Site NMI",
"site_id": "Site NMI",
"site_name": "Site Name"
},
"description": "Select the NMI of the site you would like to add"

View File

@@ -17,7 +17,7 @@
"unknown": "[%key:common::config_flow::error::unknown%]"
},
"error": {
"no_integration_selected": "You must select at least one integration to track"
"no_integrations_selected": "You must select at least one integration to track"
}
},
"options": {
@@ -37,7 +37,7 @@
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
},
"error": {
"no_integration_selected": "[%key:component::analytics_insights::config::error::no_integration_selected%]"
"no_integrations_selected": "[%key:component::analytics_insights::config::error::no_integrations_selected%]"
}
},
"entity": {

View File

@@ -4,6 +4,7 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from .const import CONF_THRESHOLD, DEFAULT_THRESHOLD
from .coordinator import AuroraDataUpdateCoordinator
PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR]
@@ -21,9 +22,19 @@ async def async_setup_entry(hass: HomeAssistant, entry: AuroraConfigEntry) -> bo
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
entry.async_on_unload(entry.add_update_listener(update_listener))
return True
async def update_listener(hass: HomeAssistant, entry: AuroraConfigEntry) -> None:
"""Handle options update."""
entry.runtime_data.threshold = int(
entry.options.get(CONF_THRESHOLD, DEFAULT_THRESHOLD)
)
# refresh the state of the visibility alert binary sensor
await entry.runtime_data.async_request_refresh()
async def async_unload_entry(hass: HomeAssistant, entry: AuroraConfigEntry) -> bool:
"""Unload a config entry."""
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)

View File

@@ -38,8 +38,8 @@ class AuroraDataUpdateCoordinator(DataUpdateCoordinator[int]):
)
self.api = AuroraForecast(async_get_clientsession(hass))
self.latitude = int(self.config_entry.data[CONF_LATITUDE])
self.longitude = int(self.config_entry.data[CONF_LONGITUDE])
self.latitude = round(self.config_entry.data[CONF_LATITUDE])
self.longitude = round(self.config_entry.data[CONF_LONGITUDE])
self.threshold = int(
self.config_entry.options.get(CONF_THRESHOLD, DEFAULT_THRESHOLD)
)

View File

@@ -14,14 +14,15 @@
"already_configured": "[%key:common::config_flow::abort::already_configured_service%]"
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"unknown": "[%key:common::config_flow::error::unknown%]"
}
},
"options": {
"step": {
"init": {
"data": {
"threshold": "Threshold (%)"
"forecast_threshold": "Threshold (%)"
}
}
}

View File

@@ -3,11 +3,6 @@
"name": "Awair",
"codeowners": ["@ahayworth", "@danielsjf"],
"config_flow": true,
"dhcp": [
{
"macaddress": "70886B1*"
}
],
"documentation": "https://www.home-assistant.io/integrations/awair",
"iot_class": "local_polling",
"loggers": ["python_awair"],

View File

@@ -38,7 +38,7 @@
},
"options": {
"step": {
"options": {
"init": {
"title": "Options for the Azure Event Hub.",
"data": {
"send_interval": "Interval between sending batches to the hub."

View File

@@ -15,7 +15,9 @@
"description": "Set up your BleBox to integrate with Home Assistant.",
"data": {
"host": "[%key:common::config_flow::data::ip%]",
"port": "[%key:common::config_flow::data::port%]"
"password": "[%key:common::config_flow::data::password%]",
"port": "[%key:common::config_flow::data::port%]",
"username": "[%key:common::config_flow::data::username%]"
},
"title": "Set up your BleBox device"
}

View File

@@ -6,7 +6,7 @@
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/bluesound",
"iot_class": "local_polling",
"requirements": ["pyblu==1.0.2"],
"requirements": ["pyblu==1.0.4"],
"zeroconf": [
{
"type": "_musc._tcp.local."

View File

@@ -493,6 +493,8 @@ class BluesoundPlayer(MediaPlayerEntity):
return None
position = self._status.seconds
if position is None:
return None
if mediastate == MediaPlayerState.PLAYING:
position += (dt_util.utcnow() - self._last_status_update).total_seconds()

View File

@@ -1,7 +1,7 @@
{
"config": {
"step": {
"reconfigure": {
"reconfigure_confirm": {
"data": {
"filename": "[%key:component::bryant_evolution::config::step::user::data::filename%]"
}

View File

@@ -34,7 +34,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
username=entry.data[CONF_USERNAME],
password=entry.data[CONF_PASSWORD],
ssl_verify_cert=entry.data[CONF_VERIFY_SSL],
timeout=10,
timeout=30,
)
try:
await hass.async_add_executor_job(client.principal)

View File

@@ -7,6 +7,6 @@
"integration_type": "device",
"iot_class": "local_push",
"loggers": ["aiostreammagic"],
"requirements": ["aiostreammagic==2.3.1"],
"requirements": ["aiostreammagic==2.5.0"],
"zeroconf": ["_stream-magic._tcp.local.", "_smoip._tcp.local."]
}

View File

@@ -14,6 +14,6 @@
"documentation": "https://www.home-assistant.io/integrations/cast",
"iot_class": "local_polling",
"loggers": ["casttube", "pychromecast"],
"requirements": ["PyChromecast==14.0.2"],
"requirements": ["PyChromecast==14.0.3"],
"zeroconf": ["_googlecast._tcp.local."]
}

View File

@@ -0,0 +1,93 @@
"""Diagnostics support for Comelit integration."""
from __future__ import annotations
from typing import Any
from aiocomelit import (
ComelitSerialBridgeObject,
ComelitVedoAreaObject,
ComelitVedoZoneObject,
)
from aiocomelit.const import BRIDGE
from homeassistant.components.diagnostics import async_redact_data
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_PIN, CONF_TYPE
from homeassistant.core import HomeAssistant
from .const import DOMAIN
from .coordinator import ComelitBaseCoordinator
TO_REDACT = {CONF_PIN}
async def async_get_config_entry_diagnostics(
hass: HomeAssistant, entry: ConfigEntry
) -> dict[str, Any]:
"""Return diagnostics for a config entry."""
coordinator: ComelitBaseCoordinator = hass.data[DOMAIN][entry.entry_id]
dev_list: list[dict[str, Any]] = []
dev_type_list: list[dict[int, Any]] = []
for dev_type in coordinator.data:
dev_type_list = []
for sensor_data in coordinator.data[dev_type].values():
if isinstance(sensor_data, ComelitSerialBridgeObject):
dev_type_list.append(
{
sensor_data.index: {
"name": sensor_data.name,
"status": sensor_data.status,
"human_status": sensor_data.human_status,
"protected": sensor_data.protected,
"val": sensor_data.val,
"zone": sensor_data.zone,
"power": sensor_data.power,
"power_unit": sensor_data.power_unit,
}
}
)
if isinstance(sensor_data, ComelitVedoAreaObject):
dev_type_list.append(
{
sensor_data.index: {
"name": sensor_data.name,
"human_status": sensor_data.human_status.value,
"p1": sensor_data.p1,
"p2": sensor_data.p2,
"ready": sensor_data.ready,
"armed": sensor_data.armed,
"alarm": sensor_data.alarm,
"alarm_memory": sensor_data.alarm_memory,
"sabotage": sensor_data.sabotage,
"anomaly": sensor_data.anomaly,
"in_time": sensor_data.in_time,
"out_time": sensor_data.out_time,
}
}
)
if isinstance(sensor_data, ComelitVedoZoneObject):
dev_type_list.append(
{
sensor_data.index: {
"name": sensor_data.name,
"human_status": sensor_data.human_status.value,
"status": sensor_data.status,
"status_api": sensor_data.status_api,
}
}
)
dev_list.append({dev_type: dev_type_list})
return {
"entry": async_redact_data(entry.as_dict(), TO_REDACT),
"type": entry.data.get(CONF_TYPE, BRIDGE),
"device_info": {
"last_update success": coordinator.last_update_success,
"last_exception": repr(coordinator.last_exception),
"devices": dev_list,
},
}

View File

@@ -159,6 +159,7 @@ class DaikinClimate(DaikinEntity, ClimateEntity):
if values:
await self.device.set(values)
await self.coordinator.async_refresh()
@property
def unique_id(self) -> str:
@@ -261,6 +262,7 @@ class DaikinClimate(DaikinEntity, ClimateEntity):
await self.device.set_advanced_mode(
HA_PRESET_TO_DAIKIN[PRESET_ECO], ATTR_STATE_OFF
)
await self.coordinator.async_refresh()
@property
def preset_modes(self) -> list[str]:
@@ -275,9 +277,11 @@ class DaikinClimate(DaikinEntity, ClimateEntity):
async def async_turn_on(self) -> None:
"""Turn device on."""
await self.device.set({})
await self.coordinator.async_refresh()
async def async_turn_off(self) -> None:
"""Turn device off."""
await self.device.set(
{HA_ATTR_TO_DAIKIN[ATTR_HVAC_MODE]: HA_STATE_TO_DAIKIN[HVACMode.OFF]}
)
await self.coordinator.async_refresh()

View File

@@ -63,10 +63,12 @@ class DaikinZoneSwitch(DaikinEntity, SwitchEntity):
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the zone on."""
await self.device.set_zone(self._zone_id, "zone_onoff", "1")
await self.coordinator.async_refresh()
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the zone off."""
await self.device.set_zone(self._zone_id, "zone_onoff", "0")
await self.coordinator.async_refresh()
class DaikinStreamerSwitch(DaikinEntity, SwitchEntity):
@@ -88,10 +90,12 @@ class DaikinStreamerSwitch(DaikinEntity, SwitchEntity):
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the zone on."""
await self.device.set_streamer("on")
await self.coordinator.async_refresh()
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the zone off."""
await self.device.set_streamer("off")
await self.coordinator.async_refresh()
class DaikinToggleSwitch(DaikinEntity, SwitchEntity):
@@ -112,7 +116,9 @@ class DaikinToggleSwitch(DaikinEntity, SwitchEntity):
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the zone on."""
await self.device.set({})
await self.coordinator.async_refresh()
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the zone off."""
await self.device.set({DAIKIN_ATTR_MODE: "off"})
await self.coordinator.async_refresh()

View File

@@ -51,7 +51,7 @@ async def async_setup_entry(
)
)
tracked.add(station.mac_address)
async_add_entities(new_entities)
async_add_entities(new_entities)
@callback
def restore_entities() -> None:

View File

@@ -9,6 +9,7 @@ from devolo_plc_api.device_api import (
)
from devolo_plc_api.plcnet_api import DataRate, LogicalNetwork
from homeassistant.const import ATTR_CONNECTIONS
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, DeviceInfo
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.update_coordinator import (
@@ -45,7 +46,6 @@ class DevoloEntity(Entity):
self._attr_device_info = DeviceInfo(
configuration_url=f"http://{self.device.ip}",
connections={(CONNECTION_NETWORK_MAC, self.device.mac)},
identifiers={(DOMAIN, str(self.device.serial_number))},
manufacturer="devolo",
model=self.device.product,
@@ -53,6 +53,10 @@ class DevoloEntity(Entity):
serial_number=self.device.serial_number,
sw_version=self.device.firmware_version,
)
if self.device.mac:
self._attr_device_info[ATTR_CONNECTIONS] = {
(CONNECTION_NETWORK_MAC, self.device.mac)
}
self._attr_translation_key = self.entity_description.key
self._attr_unique_id = (
f"{self.device.serial_number}_{self.entity_description.key}"

View File

@@ -7,7 +7,7 @@
"documentation": "https://www.home-assistant.io/integrations/doorbird",
"iot_class": "local_push",
"loggers": ["doorbirdpy"],
"requirements": ["DoorBirdPy==3.0.2"],
"requirements": ["DoorBirdPy==3.0.4"],
"zeroconf": [
{
"type": "_axis-video._tcp.local.",

View File

@@ -7,5 +7,5 @@
"iot_class": "local_push",
"loggers": ["pyduotecno", "pyduotecno-node", "pyduotecno-unit"],
"quality_scale": "silver",
"requirements": ["pyDuotecno==2024.9.0"]
"requirements": ["pyDuotecno==2024.10.1"]
}

View File

@@ -5,18 +5,21 @@
"data": {
"host": "[%key:common::config_flow::data::host%]",
"username": "[%key:common::config_flow::data::username%]",
"password": "[%key:common::config_flow::data::password%]"
"password": "[%key:common::config_flow::data::password%]",
"port": "[%key:common::config_flow::data::port%]"
},
"data_description": {
"host": "The hostname or IP address of your Duotecno device."
}
}
},
"abort": {
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]"
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
"unknown": "[%key:common::config_flow::error::unknown%]",
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]"
"unknown": "[%key:common::config_flow::error::unknown%]"
}
},
"entity": {

View File

@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/econet",
"iot_class": "cloud_push",
"loggers": ["paho_mqtt", "pyeconet"],
"requirements": ["pyeconet==0.1.22"]
"requirements": ["pyeconet==0.1.23"]
}

View File

@@ -223,7 +223,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
config[DOMAIN][CONF_PASSWORD],
)
except evo.AuthenticationFailed as err:
except (evo.AuthenticationFailed, evo.RequestFailed) as err:
handle_evo_exception(err)
return False

View File

@@ -47,6 +47,7 @@
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
},
"error": {
"no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]",
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]"
}
},

View File

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

View File

@@ -9,5 +9,5 @@ DOMAIN = "fujitsu_fglair"
CONF_REGION = "region"
CONF_EUROPE = "is_europe"
REGION_EU = "EU"
REGION_EU = "eu"
REGION_DEFAULT = "default"

View File

@@ -5,5 +5,5 @@
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/fujitsu_fglair",
"iot_class": "cloud_polling",
"requirements": ["ayla-iot-unofficial==1.4.1"]
"requirements": ["ayla-iot-unofficial==1.4.2"]
}

View File

@@ -7,5 +7,5 @@
"integration_type": "hub",
"iot_class": "cloud_polling",
"quality_scale": "platinum",
"requirements": ["fyta_cli==0.6.6"]
"requirements": ["fyta_cli==0.6.7"]
}

View File

@@ -7,5 +7,5 @@
"documentation": "https://www.home-assistant.io/integrations/calendar.google",
"iot_class": "cloud_polling",
"loggers": ["googleapiclient"],
"requirements": ["gcal-sync==6.1.5", "oauth2client==4.1.3", "ical==8.2.0"]
"requirements": ["gcal-sync==6.1.6", "oauth2client==4.1.3", "ical==8.2.0"]
}

View File

@@ -172,10 +172,12 @@ class BaseGoogleCloudProvider:
_LOGGER.error("Error: %s when validating options: %s", err, options)
return None, None
encoding = texttospeech.AudioEncoding(options[CONF_ENCODING])
gender: texttospeech.SsmlVoiceGender | None = texttospeech.SsmlVoiceGender(
encoding: texttospeech.AudioEncoding = texttospeech.AudioEncoding[
options[CONF_ENCODING]
] # type: ignore[misc]
gender: texttospeech.SsmlVoiceGender | None = texttospeech.SsmlVoiceGender[
options[CONF_GENDER]
)
] # type: ignore[misc]
voice = options[CONF_VOICE]
if voice:
gender = None

View File

@@ -21,7 +21,8 @@
"wrong_account": "Wrong account: Please authenticate with the right account.",
"oauth_timeout": "[%key:common::config_flow::abort::oauth2_timeout%]",
"oauth_unauthorized": "[%key:common::config_flow::abort::oauth2_unauthorized%]",
"oauth_failed": "[%key:common::config_flow::abort::oauth2_failed%]"
"oauth_failed": "[%key:common::config_flow::abort::oauth2_failed%]",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
},
"create_entry": {
"default": "[%key:common::config_flow::create_entry::authenticated%]"

View File

@@ -21,7 +21,8 @@
"wrong_account": "Wrong account: Please authenticate with the right account.",
"oauth_timeout": "[%key:common::config_flow::abort::oauth2_timeout%]",
"oauth_unauthorized": "[%key:common::config_flow::abort::oauth2_unauthorized%]",
"oauth_failed": "[%key:common::config_flow::abort::oauth2_failed%]"
"oauth_failed": "[%key:common::config_flow::abort::oauth2_failed%]",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
},
"create_entry": {
"default": "[%key:common::config_flow::create_entry::authenticated%]"

View File

@@ -14,6 +14,9 @@ from homeassistant.util import dt as dt_util
def next_due_date(task: dict[str, Any], last_cron: str) -> datetime.date | None:
"""Calculate due date for dailies and yesterdailies."""
if task["everyX"] == 0 or not task.get("nextDue"): # grey dailies never become due
return None
today = to_date(last_cron)
startdate = to_date(task["startDate"])
if TYPE_CHECKING:

View File

@@ -24,11 +24,11 @@
},
"cmd": {
"name": "Command",
"description": "Command itself. Could be decimal number or string with hexadeximal notation: \"0x10\"."
"description": "Command itself. Could be decimal number or string with hexadecimal notation: \"0x10\"."
},
"dst": {
"name": "Destination",
"description": "Destination for command. Could be decimal number or string with hexadeximal notation: \"0x10\"."
"description": "Destination for command. Could be decimal number or string with hexadecimal notation: \"0x10\"."
},
"raw": {
"name": "Raw",
@@ -36,7 +36,7 @@
},
"src": {
"name": "Source",
"description": "Source of command. Could be decimal number or string with hexadeximal notation: \"0x10\"."
"description": "Source of command. Could be decimal number or string with hexadecimal notation: \"0x10\"."
}
}
},

View File

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

View File

@@ -49,6 +49,10 @@ from .const import (
RETRY,
)
MODE_PERMANENT_HOLD = 2
MODE_TEMPORARY_HOLD = 1
MODE_HOLD = {MODE_PERMANENT_HOLD, MODE_TEMPORARY_HOLD}
ATTR_FAN_ACTION = "fan_action"
ATTR_PERMANENT_HOLD = "permanent_hold"
@@ -175,6 +179,7 @@ class HoneywellUSThermostat(ClimateEntity):
self._cool_away_temp = cool_away_temp
self._heat_away_temp = heat_away_temp
self._away = False
self._away_hold = False
self._retry = 0
self._attr_unique_id = str(device.deviceid)
@@ -323,11 +328,15 @@ class HoneywellUSThermostat(ClimateEntity):
@property
def preset_mode(self) -> str | None:
"""Return the current preset mode, e.g., home, away, temp."""
if self._away:
if self._away and self._is_hold():
self._away_hold = True
return PRESET_AWAY
if self._is_permanent_hold():
if self._is_hold():
return PRESET_HOLD
# Someone has changed the stat manually out of hold in away mode
if self._away and self._away_hold:
self._away = False
self._away_hold = False
return PRESET_NONE
@property
@@ -335,10 +344,15 @@ class HoneywellUSThermostat(ClimateEntity):
"""Return the fan setting."""
return HW_FAN_MODE_TO_HA.get(self._device.fan_mode)
def _is_hold(self) -> bool:
heat_status = self._device.raw_ui_data.get("StatusHeat", 0)
cool_status = self._device.raw_ui_data.get("StatusCool", 0)
return heat_status in MODE_HOLD or cool_status in MODE_HOLD
def _is_permanent_hold(self) -> bool:
heat_status = self._device.raw_ui_data.get("StatusHeat", 0)
cool_status = self._device.raw_ui_data.get("StatusCool", 0)
return heat_status == 2 or cool_status == 2
return MODE_PERMANENT_HOLD in (heat_status, cool_status)
async def _set_temperature(self, **kwargs) -> None:
"""Set new target temperature."""

View File

@@ -23,7 +23,7 @@ _LOGGER = logging.getLogger(__name__)
HAP_SUFFIX = "._hap._tcp.local."
POWERVIEW_G2_SUFFIX = "._powerview._tcp.local."
POWERVIEW_G3_SUFFIX = "._powerview-g3._tcp.local."
POWERVIEW_G3_SUFFIX = "._PowerView-G3._tcp.local."
async def validate_input(hass: HomeAssistant, hub_address: str) -> dict[str, str]:

View File

@@ -19,5 +19,5 @@
"iot_class": "local_polling",
"loggers": ["aiopvapi"],
"requirements": ["aiopvapi==3.1.1"],
"zeroconf": ["_powerview._tcp.local.", "_powerview-g3._tcp.local."]
"zeroconf": ["_powerview._tcp.local.", "_PowerView-G3._tcp.local."]
}

View File

@@ -125,7 +125,9 @@ class AutomowerBaseEntity(CoordinatorEntity[AutomowerDataUpdateCoordinator]):
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, mower_id)},
manufacturer="Husqvarna",
model=self.mower_attributes.system.model,
model=self.mower_attributes.system.model.removeprefix(
"HUSQVARNA "
).removeprefix("Husqvarna "),
name=self.mower_attributes.system.name,
serial_number=self.mower_attributes.system.serial_number,
suggested_area="Garden",

View File

@@ -7,5 +7,5 @@
"documentation": "https://www.home-assistant.io/integrations/husqvarna_automower",
"iot_class": "cloud_push",
"loggers": ["aioautomower"],
"requirements": ["aioautomower==2024.9.3"]
"requirements": ["aioautomower==2024.10.0"]
}

View File

@@ -10,7 +10,7 @@ DEFAULT_WATERING_TIME = timedelta(minutes=15)
MANUFACTURER = "Hydrawise"
SCAN_INTERVAL = timedelta(seconds=30)
SCAN_INTERVAL = timedelta(seconds=60)
SIGNAL_UPDATE_HYDRAWISE = "hydrawise_update"

View File

@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/imgw_pib",
"iot_class": "cloud_polling",
"quality_scale": "platinum",
"requirements": ["imgw_pib==1.0.5"]
"requirements": ["imgw_pib==1.0.6"]
}

View File

@@ -8,6 +8,6 @@
"iot_class": "local_polling",
"loggers": ["deepmerge", "pyipp"],
"quality_scale": "platinum",
"requirements": ["pyipp==0.16.0"],
"requirements": ["pyipp==0.17.0"],
"zeroconf": ["_ipps._tcp.local.", "_ipp._tcp.local."]
}

View File

@@ -4,7 +4,7 @@ from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass
from datetime import datetime, timedelta
from datetime import datetime
from typing import Any
from pyipp import Marker, Printer
@@ -19,7 +19,6 @@ from homeassistant.const import ATTR_LOCATION, PERCENTAGE, EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType
from homeassistant.util.dt import utcnow
from . import IPPConfigEntry
from .const import (
@@ -80,7 +79,7 @@ PRINTER_SENSORS: tuple[IPPSensorEntityDescription, ...] = (
device_class=SensorDeviceClass.TIMESTAMP,
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False,
value_fn=lambda printer: (utcnow() - timedelta(seconds=printer.info.uptime)),
value_fn=lambda printer: printer.booted_at,
),
)

View File

@@ -29,7 +29,8 @@
"invalid_host": "The host entry was not in full URL format, e.g., http://192.168.10.100:80"
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
}
},
"options": {

View File

@@ -24,6 +24,7 @@
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
"unknown": "[%key:common::config_flow::error::unknown%]"
},
"error": {

View File

@@ -12,7 +12,7 @@
"quality_scale": "platinum",
"requirements": [
"xknx==3.2.0",
"xknxproject==3.8.0",
"xknxproject==3.8.1",
"knx-frontend==2024.9.10.221729"
],
"single_config_entry": true

View File

@@ -6,6 +6,7 @@
"documentation": "https://www.home-assistant.io/integrations/linkplay",
"integration_type": "hub",
"iot_class": "local_polling",
"requirements": ["python-linkplay==0.0.12"],
"loggers": ["linkplay"],
"requirements": ["python-linkplay==0.0.15"],
"zeroconf": ["_linkplay._tcp.local."]
}

View File

@@ -5,5 +5,5 @@
"documentation": "https://www.home-assistant.io/integrations/matrix",
"iot_class": "cloud_push",
"loggers": ["matrix_client"],
"requirements": ["matrix-nio==0.25.1", "Pillow==10.4.0"]
"requirements": ["matrix-nio==0.25.2", "Pillow==10.4.0"]
}

View File

@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/mealie",
"integration_type": "service",
"iot_class": "local_polling",
"requirements": ["aiomealie==0.9.2"]
"requirements": ["aiomealie==0.9.3"]
}

View File

@@ -19,7 +19,8 @@
}
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_account%]"
"already_configured": "[%key:common::config_flow::abort::already_configured_account%]",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
},
"error": {
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",

View File

@@ -8,6 +8,6 @@
"iot_class": "calculated",
"loggers": ["yt_dlp"],
"quality_scale": "internal",
"requirements": ["yt-dlp==2024.09.27"],
"requirements": ["yt-dlp==2024.10.22"],
"single_config_entry": true
}

View File

@@ -21,6 +21,7 @@
"no_url_available": "[%key:common::config_flow::abort::oauth2_no_url_available%]",
"user_rejected_authorize": "[%key:common::config_flow::abort::oauth2_user_rejected_authorize%]",
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
"unknown": "[%key:common::config_flow::error::unknown%]",
"wrong_account": "You can only reauthenticate this entry with the same microBees account."
},

View File

@@ -14,5 +14,5 @@
"integration_type": "device",
"iot_class": "assumed_state",
"loggers": ["motionblindsble"],
"requirements": ["motionblindsble==0.1.1"]
"requirements": ["motionblindsble==0.1.2"]
}

View File

@@ -7,7 +7,7 @@ from nyt_games import NYTGamesClient
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_TOKEN, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.aiohttp_client import async_create_clientsession
from .coordinator import NYTGamesCoordinator
@@ -23,7 +23,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: NYTGamesConfigEntry) ->
"""Set up NYTGames from a config entry."""
client = NYTGamesClient(
entry.data[CONF_TOKEN], session=async_get_clientsession(hass)
entry.data[CONF_TOKEN], session=async_create_clientsession(hass)
)
coordinator = NYTGamesCoordinator(hass, client)

View File

@@ -7,7 +7,7 @@ import voluptuous as vol
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_TOKEN
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.aiohttp_client import async_create_clientsession
from .const import DOMAIN, LOGGER
@@ -21,8 +21,9 @@ class NYTGamesConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a flow initialized by the user."""
errors: dict[str, str] = {}
if user_input:
session = async_get_clientsession(self.hass)
client = NYTGamesClient(user_input[CONF_TOKEN], session=session)
session = async_create_clientsession(self.hass)
token = user_input[CONF_TOKEN].strip()
client = NYTGamesClient(token, session=session)
try:
user_id = await client.get_user_id()
except NYTGamesAuthenticationError:
@@ -35,7 +36,9 @@ class NYTGamesConfigFlow(ConfigFlow, domain=DOMAIN):
else:
await self.async_set_unique_id(str(user_id))
self._abort_if_unique_id_configured()
return self.async_create_entry(title="NYT Games", data=user_input)
return self.async_create_entry(
title="NYT Games", data={CONF_TOKEN: token}
)
return self.async_show_form(
step_id="user",
data_schema=vol.Schema({vol.Required(CONF_TOKEN): str}),

View File

@@ -23,7 +23,7 @@ class NYTGamesData:
wordle: Wordle
spelling_bee: SpellingBee | None
connections: Connections
connections: Connections | None
class NYTGamesCoordinator(DataUpdateCoordinator[NYTGamesData]):

View File

@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/nyt_games",
"integration_type": "service",
"iot_class": "cloud_polling",
"requirements": ["nyt_games==0.4.2"]
"requirements": ["nyt_games==0.4.4"]
}

View File

@@ -139,7 +139,7 @@ CONNECTIONS_SENSORS: tuple[NYTGamesConnectionsSensorEntityDescription, ...] = (
state_class=SensorStateClass.TOTAL_INCREASING,
native_unit_of_measurement=UnitOfTime.DAYS,
device_class=SensorDeviceClass.DURATION,
value_fn=lambda connections: connections.current_streak,
value_fn=lambda connections: connections.max_streak,
),
)
@@ -161,10 +161,11 @@ async def async_setup_entry(
NYTGamesSpellingBeeSensor(coordinator, description)
for description in SPELLING_BEE_SENSORS
)
entities.extend(
NYTGamesConnectionsSensor(coordinator, description)
for description in CONNECTIONS_SENSORS
)
if coordinator.data.connections is not None:
entities.extend(
NYTGamesConnectionsSensor(coordinator, description)
for description in CONNECTIONS_SENSORS
)
async_add_entities(entities)
@@ -236,4 +237,5 @@ class NYTGamesConnectionsSensor(ConnectionsEntity, SensorEntity):
@property
def native_value(self) -> StateType | date:
"""Return the state of the sensor."""
assert self.coordinator.data.connections is not None
return self.entity_description.value_fn(self.coordinator.data.connections)

View File

@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/opentherm_gw",
"iot_class": "local_push",
"loggers": ["pyotgw"],
"requirements": ["pyotgw==2.2.1"]
"requirements": ["pyotgw==2.2.2"]
}

View File

@@ -130,20 +130,32 @@ class OpowerCoordinator(DataUpdateCoordinator[dict[str, Forecast]]):
continue
start = cost_reads[0].start_time
_LOGGER.debug("Getting statistics at: %s", start)
stats = await get_instance(self.hass).async_add_executor_job(
statistics_during_period,
self.hass,
start,
start + timedelta(seconds=1),
{cost_statistic_id, consumption_statistic_id},
"hour",
None,
{"sum"},
)
# In the common case there should be a previous statistic at start time
# so we only need to fetch one statistic. If there isn't any, fetch all.
for end in (start + timedelta(seconds=1), None):
stats = await get_instance(self.hass).async_add_executor_job(
statistics_during_period,
self.hass,
start,
end,
{cost_statistic_id, consumption_statistic_id},
"hour",
None,
{"sum"},
)
if stats:
break
if end:
_LOGGER.debug(
"Not found. Trying to find the oldest statistic after %s",
start,
)
# We are in this code path only if get_last_statistics found a stat
# so statistics_during_period should also have found at least one.
assert stats
cost_sum = cast(float, stats[cost_statistic_id][0]["sum"])
consumption_sum = cast(float, stats[consumption_statistic_id][0]["sum"])
last_stats_time = stats[consumption_statistic_id][0]["start"]
assert last_stats_time == start.timestamp()
cost_statistics = []
consumption_statistics = []

View File

@@ -7,5 +7,5 @@
"documentation": "https://www.home-assistant.io/integrations/opower",
"iot_class": "cloud_polling",
"loggers": ["opower"],
"requirements": ["opower==0.8.0"]
"requirements": ["opower==0.8.3"]
}

View File

@@ -13,7 +13,9 @@
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
},
"abort": {
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]"
"already_configured": "The Thread border router is already configured",
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]",
"unknown": "[%key:common::config_flow::error::unknown%]"
}
},
"issues": {

View File

@@ -32,7 +32,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
client_session=async_get_clientsession(hass),
)
if custom_account := entry.data.get(CONF_ACCOUNT) is not None:
if (custom_account := entry.data.get(CONF_ACCOUNT)) is not None:
client.custom_account_id = custom_account
try:
@@ -49,7 +49,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_update_data() -> OVODailyUsage:
"""Fetch data from OVO Energy."""
if custom_account := entry.data.get(CONF_ACCOUNT) is not None:
if (custom_account := entry.data.get(CONF_ACCOUNT)) is not None:
client.custom_account_id = custom_account
async with asyncio.timeout(10):

View File

@@ -46,7 +46,7 @@ class OVOEnergyFlowHandler(ConfigFlow, domain=DOMAIN):
client_session=async_get_clientsession(self.hass),
)
if custom_account := user_input.get(CONF_ACCOUNT) is not None:
if (custom_account := user_input.get(CONF_ACCOUNT)) is not None:
client.custom_account_id = custom_account
try:

View File

@@ -10,6 +10,7 @@ from aiopyarr import exceptions
from aiopyarr.models.host_configuration import PyArrHostConfiguration
from aiopyarr.radarr_client import RadarrClient
import voluptuous as vol
from yarl import URL
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_API_KEY, CONF_URL, CONF_VERIFY_SSL
@@ -54,6 +55,12 @@ class RadarrConfigFlow(ConfigFlow, domain=DOMAIN):
user_input = dict(self.entry.data) if self.entry else None
else:
# aiopyarr defaults to the service port if one isn't given
# this is counter to standard practice where http = 80
# and https = 443.
url = URL(user_input[CONF_URL])
user_input[CONF_URL] = f"{url.scheme}://{url.host}:{url.port}{url.path}"
try:
if result := await validate_input(self.hass, user_input):
user_input[CONF_API_KEY] = result[1]

View File

@@ -10,13 +10,9 @@ import uuid
from ring_doorbell import Auth, Ring, RingDevices
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import APPLICATION_NAME, CONF_TOKEN
from homeassistant.const import APPLICATION_NAME, CONF_DEVICE_ID, CONF_TOKEN
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import (
device_registry as dr,
entity_registry as er,
instance_id,
)
from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import CONF_LISTEN_CREDENTIALS, DOMAIN, PLATFORMS
@@ -38,18 +34,12 @@ class RingData:
type RingConfigEntry = ConfigEntry[RingData]
async def get_auth_agent_id(hass: HomeAssistant) -> tuple[str, str]:
"""Return user-agent and hardware id for Auth instantiation.
def get_auth_user_agent() -> str:
"""Return user-agent for Auth instantiation.
user_agent will be the display name in the ring.com authorised devices.
hardware_id will uniquely describe the authorised HA device.
"""
user_agent = f"{APPLICATION_NAME}/{DOMAIN}-integration"
# Generate a new uuid from the instance_uuid to keep the HA one private
instance_uuid = uuid.UUID(hex=await instance_id.async_get(hass))
hardware_id = str(uuid.uuid5(instance_uuid, user_agent))
return user_agent, hardware_id
return f"{APPLICATION_NAME}/{DOMAIN}-integration"
async def async_setup_entry(hass: HomeAssistant, entry: RingConfigEntry) -> bool:
@@ -69,13 +59,13 @@ async def async_setup_entry(hass: HomeAssistant, entry: RingConfigEntry) -> bool
data={**entry.data, CONF_LISTEN_CREDENTIALS: token},
)
user_agent, hardware_id = await get_auth_agent_id(hass)
user_agent = get_auth_user_agent()
client_session = async_get_clientsession(hass)
auth = Auth(
user_agent,
entry.data[CONF_TOKEN],
token_updater,
hardware_id=hardware_id,
hardware_id=entry.data[CONF_DEVICE_ID],
http_client_session=client_session,
)
ring = Ring(auth)
@@ -138,3 +128,25 @@ async def _migrate_old_unique_ids(hass: HomeAssistant, entry_id: str) -> None:
return None
await er.async_migrate_entries(hass, entry_id, _async_migrator)
async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Migrate old config entry."""
entry_version = entry.version
entry_minor_version = entry.minor_version
new_minor_version = 2
if entry_version == 1 and entry_minor_version == 1:
_LOGGER.debug(
"Migrating from version %s.%s", entry_version, entry_minor_version
)
hardware_id = str(uuid.uuid4())
hass.config_entries.async_update_entry(
entry,
data={**entry.data, CONF_DEVICE_ID: hardware_id},
minor_version=new_minor_version,
)
_LOGGER.debug(
"Migration to version %s.%s complete", entry_version, new_minor_version
)
return True

View File

@@ -3,18 +3,25 @@
from collections.abc import Mapping
import logging
from typing import Any
import uuid
from ring_doorbell import Auth, AuthenticationError, Requires2FAError
import voluptuous as vol
from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_PASSWORD, CONF_TOKEN, CONF_USERNAME
from homeassistant.const import (
CONF_DEVICE_ID,
CONF_NAME,
CONF_PASSWORD,
CONF_TOKEN,
CONF_USERNAME,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from . import get_auth_agent_id
from .const import CONF_2FA, DOMAIN
from . import get_auth_user_agent
from .const import CONF_2FA, CONF_CONFIG_ENTRY_MINOR_VERSION, DOMAIN
_LOGGER = logging.getLogger(__name__)
@@ -23,11 +30,15 @@ STEP_USER_DATA_SCHEMA = vol.Schema(
)
STEP_REAUTH_DATA_SCHEMA = vol.Schema({vol.Required(CONF_PASSWORD): str})
STEP_RECONFIGURE_DATA_SCHEMA = vol.Schema({vol.Required(CONF_PASSWORD): str})
async def validate_input(hass: HomeAssistant, data: dict[str, str]) -> dict[str, Any]:
async def validate_input(
hass: HomeAssistant, hardware_id: str, data: dict[str, str]
) -> dict[str, Any]:
"""Validate the user input allows us to connect."""
user_agent, hardware_id = await get_auth_agent_id(hass)
user_agent = get_auth_user_agent()
auth = Auth(
user_agent,
http_client_session=async_get_clientsession(hass),
@@ -52,8 +63,10 @@ class RingConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Ring."""
VERSION = 1
MINOR_VERSION = CONF_CONFIG_ENTRY_MINOR_VERSION
user_pass: dict[str, Any] = {}
hardware_id: str | None = None
reauth_entry: ConfigEntry | None = None
async def async_step_user(
@@ -64,8 +77,10 @@ class RingConfigFlow(ConfigFlow, domain=DOMAIN):
if user_input is not None:
await self.async_set_unique_id(user_input[CONF_USERNAME])
self._abort_if_unique_id_configured()
if not self.hardware_id:
self.hardware_id = str(uuid.uuid4())
try:
token = await validate_input(self.hass, user_input)
token = await validate_input(self.hass, self.hardware_id, user_input)
except Require2FA:
self.user_pass = user_input
@@ -78,7 +93,11 @@ class RingConfigFlow(ConfigFlow, domain=DOMAIN):
else:
return self.async_create_entry(
title=user_input[CONF_USERNAME],
data={CONF_USERNAME: user_input[CONF_USERNAME], CONF_TOKEN: token},
data={
CONF_DEVICE_ID: self.hardware_id,
CONF_USERNAME: user_input[CONF_USERNAME],
CONF_TOKEN: token,
},
)
return self.async_show_form(
@@ -120,8 +139,13 @@ class RingConfigFlow(ConfigFlow, domain=DOMAIN):
if user_input:
user_input[CONF_USERNAME] = self.reauth_entry.data[CONF_USERNAME]
# Reauth will use the same hardware id and re-authorise an existing
# authorised device.
if not self.hardware_id:
self.hardware_id = self.reauth_entry.data[CONF_DEVICE_ID]
assert self.hardware_id
try:
token = await validate_input(self.hass, user_input)
token = await validate_input(self.hass, self.hardware_id, user_input)
except Require2FA:
self.user_pass = user_input
return await self.async_step_2fa()
@@ -134,6 +158,7 @@ class RingConfigFlow(ConfigFlow, domain=DOMAIN):
data = {
CONF_USERNAME: user_input[CONF_USERNAME],
CONF_TOKEN: token,
CONF_DEVICE_ID: self.hardware_id,
}
self.hass.config_entries.async_update_entry(
self.reauth_entry, data=data
@@ -146,7 +171,8 @@ class RingConfigFlow(ConfigFlow, domain=DOMAIN):
data_schema=STEP_REAUTH_DATA_SCHEMA,
errors=errors,
description_placeholders={
CONF_USERNAME: self.reauth_entry.data[CONF_USERNAME]
CONF_USERNAME: self.reauth_entry.data[CONF_USERNAME],
CONF_NAME: self.reauth_entry.data[CONF_USERNAME],
},
)

View File

@@ -3,6 +3,7 @@
from __future__ import annotations
from datetime import timedelta
from typing import Final
from homeassistant.const import Platform
@@ -31,3 +32,5 @@ SCAN_INTERVAL = timedelta(minutes=1)
CONF_2FA = "2fa"
CONF_LISTEN_CREDENTIALS = "listen_token"
CONF_CONFIG_ENTRY_MINOR_VERSION: Final = 2

View File

@@ -14,5 +14,5 @@
"iot_class": "cloud_polling",
"loggers": ["ring_doorbell"],
"quality_scale": "silver",
"requirements": ["ring-doorbell==0.9.6"]
"requirements": ["ring-doorbell==0.9.8"]
}

View File

@@ -28,7 +28,8 @@
"unknown": "[%key:common::config_flow::error::unknown%]"
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
}
},
"options": {

View File

@@ -12,7 +12,7 @@ from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import ACCOUNT_HASH, DOMAIN
from .const import ACCOUNT_HASH, DOMAIN, UPDATE_INTERVAL
from .coordinator import RitualsDataUpdateCoordinator
PLATFORMS = [
@@ -37,9 +37,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
# Migrate old unique_ids to the new format
async_migrate_entities_unique_ids(hass, entry, account_devices)
# The API provided by Rituals is currently rate limited to 30 requests
# per hour per IP address. To avoid hitting this limit, we will adjust
# the polling interval based on the number of diffusers one has.
update_interval = UPDATE_INTERVAL * len(account_devices)
# Create a coordinator for each diffuser
coordinators = {
diffuser.hublot: RitualsDataUpdateCoordinator(hass, diffuser)
diffuser.hublot: RitualsDataUpdateCoordinator(hass, diffuser, update_interval)
for diffuser in account_devices
}

View File

@@ -45,6 +45,7 @@ class RitualsPerfumeGenieConfigFlow(ConfigFlow, domain=DOMAIN):
try:
await account.authenticate()
except ClientResponseError:
_LOGGER.exception("Unexpected response")
errors["base"] = "cannot_connect"
except AuthenticationException:
errors["base"] = "invalid_auth"

View File

@@ -6,4 +6,8 @@ DOMAIN = "rituals_perfume_genie"
ACCOUNT_HASH = "account_hash"
UPDATE_INTERVAL = timedelta(minutes=2)
# The API provided by Rituals is currently rate limited to 30 requests
# per hour per IP address. To avoid hitting this limit, the polling
# interval is set to 3 minutes. This also gives a little room for
# Home Assistant restarts.
UPDATE_INTERVAL = timedelta(minutes=3)

View File

@@ -1,5 +1,6 @@
"""The Rituals Perfume Genie data update coordinator."""
from datetime import timedelta
import logging
from pyrituals import Diffuser
@@ -7,7 +8,7 @@ from pyrituals import Diffuser
from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from .const import DOMAIN, UPDATE_INTERVAL
from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
@@ -15,14 +16,19 @@ _LOGGER = logging.getLogger(__name__)
class RitualsDataUpdateCoordinator(DataUpdateCoordinator[None]):
"""Class to manage fetching Rituals Perfume Genie device data from single endpoint."""
def __init__(self, hass: HomeAssistant, diffuser: Diffuser) -> None:
def __init__(
self,
hass: HomeAssistant,
diffuser: Diffuser,
update_interval: timedelta,
) -> None:
"""Initialize global Rituals Perfume Genie data updater."""
self.diffuser = diffuser
super().__init__(
hass,
_LOGGER,
name=f"{DOMAIN}-{diffuser.hublot}",
update_interval=UPDATE_INTERVAL,
update_interval=update_interval,
)
async def _async_update_data(self) -> None:

View File

@@ -6,7 +6,7 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, Platform
from homeassistant.core import HomeAssistant
from .const import DOMAIN
from .const import CONF_PLAY_MEDIA_APP_ID, DEFAULT_PLAY_MEDIA_APP_ID, DOMAIN
from .coordinator import RokuDataUpdateCoordinator
PLATFORMS = [
@@ -24,7 +24,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
device_id = entry.entry_id
coordinator = RokuDataUpdateCoordinator(
hass, host=entry.data[CONF_HOST], device_id=device_id
hass,
host=entry.data[CONF_HOST],
device_id=device_id,
play_media_app_id=entry.options.get(
CONF_PLAY_MEDIA_APP_ID, DEFAULT_PLAY_MEDIA_APP_ID
),
)
await coordinator.async_config_entry_first_refresh()
@@ -32,6 +37,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
entry.async_on_unload(entry.add_update_listener(async_reload_entry))
return True
@@ -40,3 +47,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok
async def async_reload_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
"""Reload the config entry when it changed."""
await hass.config_entries.async_reload(entry.entry_id)

View File

@@ -10,12 +10,17 @@ from rokuecp import Roku, RokuError
import voluptuous as vol
from homeassistant.components import ssdp, zeroconf
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.config_entries import (
ConfigEntry,
ConfigFlow,
ConfigFlowResult,
OptionsFlowWithConfigEntry,
)
from homeassistant.const import CONF_HOST, CONF_NAME
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import DOMAIN
from .const import CONF_PLAY_MEDIA_APP_ID, DEFAULT_PLAY_MEDIA_APP_ID, DOMAIN
DATA_SCHEMA = vol.Schema({vol.Required(CONF_HOST): str})
@@ -155,3 +160,36 @@ class RokuConfigFlow(ConfigFlow, domain=DOMAIN):
title=self.discovery_info[CONF_NAME],
data=self.discovery_info,
)
@staticmethod
@callback
def async_get_options_flow(
config_entry: ConfigEntry,
) -> OptionsFlowWithConfigEntry:
"""Create the options flow."""
return RokuOptionsFlowHandler(config_entry)
class RokuOptionsFlowHandler(OptionsFlowWithConfigEntry):
"""Handle Roku options."""
async def async_step_init(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Manage Roku options."""
if user_input is not None:
return self.async_create_entry(title="", data=user_input)
return self.async_show_form(
step_id="init",
data_schema=vol.Schema(
{
vol.Optional(
CONF_PLAY_MEDIA_APP_ID,
default=self.options.get(
CONF_PLAY_MEDIA_APP_ID, DEFAULT_PLAY_MEDIA_APP_ID
),
): str,
}
),
)

View File

@@ -15,3 +15,9 @@ DEFAULT_PORT = 8060
# Services
SERVICE_SEARCH = "search"
# Config
CONF_PLAY_MEDIA_APP_ID = "play_media_app_id"
# Defaults
DEFAULT_PLAY_MEDIA_APP_ID = "15985"

View File

@@ -29,15 +29,12 @@ class RokuDataUpdateCoordinator(DataUpdateCoordinator[Device]):
roku: Roku
def __init__(
self,
hass: HomeAssistant,
*,
host: str,
device_id: str,
self, hass: HomeAssistant, *, host: str, device_id: str, play_media_app_id: str
) -> None:
"""Initialize global Roku data updater."""
self.device_id = device_id
self.roku = Roku(host=host, session=async_get_clientsession(hass))
self.play_media_app_id = play_media_app_id
self.full_update_interval = timedelta(minutes=15)
self.last_full_update = None

View File

@@ -445,17 +445,25 @@ class RokuMediaPlayer(RokuEntity, MediaPlayerEntity):
if attr in extra
}
params = {"t": "a", **params}
params = {"u": media_id, "t": "a", **params}
await self.coordinator.roku.play_on_roku(media_id, params)
await self.coordinator.roku.launch(
self.coordinator.play_media_app_id,
params,
)
elif media_type in {MediaType.URL, MediaType.VIDEO}:
params = {
param: extra[attr]
for (attr, param) in ATTRS_TO_PLAY_ON_ROKU_PARAMS.items()
if attr in extra
}
params["u"] = media_id
params["t"] = "v"
await self.coordinator.roku.play_on_roku(media_id, params)
await self.coordinator.roku.launch(
self.coordinator.play_media_app_id,
params,
)
else:
_LOGGER.error("Media type %s is not supported", original_media_type)
return

View File

@@ -24,6 +24,18 @@
"unknown": "[%key:common::config_flow::error::unknown%]"
}
},
"options": {
"step": {
"init": {
"data": {
"play_media_app_id": "Play Media Roku Application ID"
},
"data_description": {
"play_media_app_id": "The application ID to use when launching media playback. Must support the PlayOnRoku API."
}
}
}
},
"entity": {
"binary_sensor": {
"headphones_connected": {

View File

@@ -14,7 +14,8 @@
"unknown": "[%key:common::config_flow::error::unknown%]"
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
}
},
"entity": {

View File

@@ -90,13 +90,21 @@ class SchlageDataUpdateCoordinator(DataUpdateCoordinator[SchlageData]):
devices = dr.async_entries_for_config_entry(
device_registry, self.config_entry.entry_id
)
previous_locks = {device.id for device in devices}
previous_locks = set()
previous_locks_by_lock_id = {}
for device in devices:
for domain, identifier in device.identifiers:
if domain == DOMAIN:
previous_locks.add(identifier)
previous_locks_by_lock_id[identifier] = device
continue
current_locks = set(self.data.locks.keys())
if removed_locks := previous_locks - current_locks:
LOGGER.debug("Removed locks: %s", ", ".join(removed_locks))
for device_id in removed_locks:
for lock_id in removed_locks:
device_registry.async_update_device(
device_id=device_id,
device_id=previous_locks_by_lock_id[lock_id].id,
remove_config_entry_id=self.config_entry.entry_id,
)

View File

@@ -6,7 +6,6 @@ from collections import defaultdict
from collections.abc import Callable, Iterable
from contextlib import suppress
import datetime
from functools import partial
import itertools
import logging
import math
@@ -39,6 +38,7 @@ from homeassistant.helpers.entity import entity_sources
from homeassistant.helpers.typing import UNDEFINED, UndefinedType
from homeassistant.loader import async_suggest_report_issue
from homeassistant.util import dt as dt_util
from homeassistant.util.async_ import run_callback_threadsafe
from homeassistant.util.enum import try_parse_enum
from homeassistant.util.hass_dict import HassKey
@@ -686,7 +686,6 @@ def list_statistic_ids(
@callback
def _update_issues(
report_issue: Callable[[str, str, dict[str, Any]], None],
clear_issue: Callable[[str, str], None],
sensor_states: list[State],
metadatas: dict[str, tuple[int, StatisticMetaData]],
) -> None:
@@ -707,8 +706,6 @@ def _update_issues(
entity_id,
{"statistic_id": entity_id},
)
else:
clear_issue("state_class_removed", entity_id)
metadata_unit = metadata[1]["unit_of_measurement"]
converter = statistics.STATISTIC_UNIT_TO_UNIT_CONVERTER.get(metadata_unit)
@@ -725,8 +722,6 @@ def _update_issues(
"supported_unit": metadata_unit,
},
)
else:
clear_issue("units_changed", entity_id)
elif numeric and state_unit not in converter.VALID_UNITS:
# The state unit can't be converted to the unit in metadata
valid_units = (unit or "<None>" for unit in converter.VALID_UNITS)
@@ -741,8 +736,6 @@ def _update_issues(
"supported_unit": valid_units_str,
},
)
else:
clear_issue("units_changed", entity_id)
def update_statistics_issues(
@@ -756,36 +749,50 @@ def update_statistics_issues(
instance, session, statistic_source=RECORDER_DOMAIN
)
@callback
def get_sensor_statistics_issues(hass: HomeAssistant) -> set[str]:
"""Return a list of statistics issues."""
issues = set()
issue_registry = ir.async_get(hass)
for issue in issue_registry.issues.values():
if (
issue.domain != DOMAIN
or not (issue_data := issue.data)
or issue_data.get("issue_type")
not in ("state_class_removed", "units_changed")
):
continue
issues.add(issue.issue_id)
return issues
issues = run_callback_threadsafe(
hass.loop, get_sensor_statistics_issues, hass
).result()
def create_issue_registry_issue(
issue_type: str, statistic_id: str, data: dict[str, Any]
) -> None:
"""Create an issue registry issue."""
hass.loop.call_soon_threadsafe(
partial(
ir.async_create_issue,
hass,
DOMAIN,
f"{issue_type}_{statistic_id}",
data=data | {"issue_type": issue_type},
is_fixable=False,
severity=ir.IssueSeverity.WARNING,
translation_key=issue_type,
translation_placeholders=data,
)
)
def delete_issue_registry_issue(issue_type: str, statistic_id: str) -> None:
"""Delete an issue registry issue."""
hass.loop.call_soon_threadsafe(
ir.async_delete_issue, hass, DOMAIN, f"{issue_type}_{statistic_id}"
issue_id = f"{issue_type}_{statistic_id}"
issues.discard(issue_id)
ir.create_issue(
hass,
DOMAIN,
issue_id,
data=data | {"issue_type": issue_type},
is_fixable=False,
severity=ir.IssueSeverity.WARNING,
translation_key=issue_type,
translation_placeholders=data,
)
_update_issues(
create_issue_registry_issue,
delete_issue_registry_issue,
sensor_states,
metadatas,
)
for issue_id in issues:
hass.loop.call_soon_threadsafe(ir.async_delete_issue, hass, DOMAIN, issue_id)
def validate_statistics(
@@ -811,7 +818,6 @@ def validate_statistics(
_update_issues(
create_statistic_validation_issue,
lambda issue_type, statistic_id: None,
sensor_states,
metadatas,
)

View File

@@ -135,3 +135,21 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
data[PYSMA_REMOVE_LISTENER]()
return unload_ok
async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Migrate entry."""
_LOGGER.debug("Migrating from version %s", entry.version)
if entry.version == 1:
# 1 -> 2: Unique ID from integer to string
if entry.minor_version == 1:
minor_version = 2
hass.config_entries.async_update_entry(
entry, unique_id=str(entry.unique_id), minor_version=minor_version
)
_LOGGER.debug("Migration successful")
return True

View File

@@ -40,6 +40,7 @@ class SmaConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a config flow for SMA."""
VERSION = 1
MINOR_VERSION = 2
def __init__(self) -> None:
"""Initialize."""
@@ -76,7 +77,7 @@ class SmaConfigFlow(ConfigFlow, domain=DOMAIN):
errors["base"] = "unknown"
if not errors:
await self.async_set_unique_id(device_info["serial"])
await self.async_set_unique_id(str(device_info["serial"]))
self._abort_if_unique_id_configured(updates=self._data)
return self.async_create_entry(
title=self._data[CONF_HOST], data=self._data

View File

@@ -6,7 +6,7 @@
"documentation": "https://www.home-assistant.io/integrations/smlight",
"integration_type": "device",
"iot_class": "local_push",
"requirements": ["pysmlight==0.1.1"],
"requirements": ["pysmlight==0.1.3"],
"zeroconf": [
{
"type": "_slzb-06._tcp.local."

View File

@@ -38,7 +38,7 @@ class SolarLogCoordinatorEntity(SolarLogBaseEntity):
"""Initialize the SolarLogCoordinator sensor."""
super().__init__(coordinator, description)
self._attr_unique_id = f"{coordinator.unique_id}-{description.key}"
self._attr_unique_id = f"{coordinator.unique_id}_{description.key}"
self._attr_device_info = DeviceInfo(
manufacturer="Solar-Log",
model="Controller",
@@ -59,8 +59,8 @@ class SolarLogInverterEntity(SolarLogBaseEntity):
) -> None:
"""Initialize the SolarLogInverter sensor."""
super().__init__(coordinator, description)
name = f"{coordinator.unique_id}-{slugify(coordinator.solarlog.device_name(device_id))}"
self._attr_unique_id = f"{name}-{description.key}"
name = f"{coordinator.unique_id}_{slugify(coordinator.solarlog.device_name(device_id))}"
self._attr_unique_id = f"{name}_{description.key}"
self._attr_device_info = DeviceInfo(
manufacturer="Solar-Log",
model="Inverter",

View File

@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/solarlog",
"iot_class": "local_polling",
"loggers": ["solarlog_cli"],
"requirements": ["solarlog_cli==0.3.0"]
"requirements": ["solarlog_cli==0.3.2"]
}

View File

@@ -32,7 +32,8 @@
"reconfigure_confirm": {
"title": "Configure SolarLog",
"data": {
"has_password": "[%key:component::solarlog::config::step::user::data::has_password%]"
"has_password": "[%key:component::solarlog::config::step::user::data::has_password%]",
"password": "[%key:common::config_flow::data::password%]"
}
}
},

View File

@@ -27,12 +27,6 @@
},
"call_query": {
"service": "mdi:database"
},
"sync": {
"service": "mdi:sync"
},
"unsync": {
"service": "mdi:sync-off"
}
}
}

View File

@@ -30,19 +30,3 @@ call_query:
advanced: true
selector:
object:
sync:
target:
entity:
integration: squeezebox
domain: media_player
fields:
other_player:
required: true
example: "media_player.living_room"
selector:
text:
unsync:
target:
entity:
integration: squeezebox
domain: media_player

View File

@@ -60,20 +60,6 @@
"description": "[%key:component::squeezebox::services::call_method::fields::parameters::description%]"
}
}
},
"sync": {
"name": "Sync",
"description": "Adds another player to this player's sync group. If the other player is already in a sync group, it will leave it.\n.",
"fields": {
"other_player": {
"name": "Other player",
"description": "Name of the other Squeezebox player to link."
}
}
},
"unsync": {
"name": "Unsync",
"description": "Removes this player from its sync group."
}
},
"entity": {

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