Compare commits

...

54 Commits

Author SHA1 Message Date
TimL
d292aa2e90 Add missing exception string from smlight IR platform (#167784) 2026-04-09 13:50:30 +02:00
epenet
87f44a67be Use runtime_data in sharkiq integration (#167741)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 13:45:44 +02:00
epenet
efb0e80577 Use runtime_data in smart_meter_texas integration (#167743)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 13:44:34 +02:00
epenet
11c34c7ddf Use runtime_data in snapcast integration (#167744)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 13:40:11 +02:00
epenet
4b820a0204 Use runtime_data in somfy_mylink integration (#167745)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 13:39:19 +02:00
epenet
0c98f01b07 Use runtime_data in starline integration (#167746)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 13:37:59 +02:00
epenet
aa50822a82 Use runtime_data in Subaru integration (#167747)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 13:36:45 +02:00
epenet
f634525798 Use runtime_data in syncthing integration (#167748)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 13:34:42 +02:00
Denis Shulyaka
047500af42 Anthropic pretty device model name (#167772)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-09 13:33:02 +02:00
MoonDevLT
db589f7318 Bump lunatone-rest-api-client to 0.9.0 (#167762) 2026-04-09 13:20:13 +02:00
TimL
3ea15f2743 Refactor Ultima fixtures to reduce duplication (#167731) 2026-04-09 13:20:01 +02:00
Maciej Bieniek
8e430d9f26 Bump brother to 6.1.0 (#167768) 2026-04-09 13:19:05 +02:00
Michael
075b47b5f9 Set proper state for the internet_access switches in FRITZ!Box Tools (#167767) 2026-04-09 12:46:34 +02:00
wollew
949c907407 Bump pyvlx to 0.2.33 (#167764) 2026-04-09 11:55:32 +02:00
Franck Nijhof
326799209c Extract config entry template functions into a config entry Jinja2 extension (#167360)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Joostlek <joostlek@outlook.com>
2026-04-09 11:49:56 +02:00
Simone Chemelli
65bc7c9ea7 Allow force alarm actions for Comelit (#167202) 2026-04-09 11:16:05 +02:00
dependabot[bot]
86d443f8c6 Bump pypa/gh-action-pypi-publish from 1.13.0 to 1.14.0 (#167648)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-09 10:29:18 +02:00
epenet
19ae7e722e Bump pybotvac to 0.0.29 (#167758) 2026-04-09 09:51:03 +02:00
Abílio Costa
57568fdc2c Add standard event type for doorbell event entities (#167630) 2026-04-09 00:02:05 +01:00
Oluwatobi Mustapha
4c8a660b2d Redact Z-Wave add-on options sensitive error details (#167239)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-08 21:17:26 +02:00
puddly
b0511519a1 Expose async serial port scanning helper in USB integration (#167706) 2026-04-08 14:29:27 -04:00
Marc Mueller
038b583888 Update types packages (#167700) 2026-04-08 19:20:57 +02:00
Raphael Hehl
018c130988 Update UniFi Access quality scale: mark documentation rules as done (#166898)
Co-authored-by: RaHehl <rahehl@users.noreply.github.com>
2026-04-08 16:18:19 +02:00
David Bishop
462e9965d7 Mark Govee local devices unavailable when they stop responding (#167566)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 14:18:55 +01:00
Franck Nijhof
ea4d85f96c Extract arithmetic template filters into the math Jinja2 extension (#167309)
Co-authored-by: Joostlek <joostlek@outlook.com>
2026-04-08 14:27:22 +02:00
Petro31
1a4d518ef2 Update template fan tests to use new framework (#167625) 2026-04-08 13:51:15 +02:00
TimL
a48a770ca4 Add Infrared platform to SMLIGHT (#167568) 2026-04-08 12:35:48 +01:00
Tom
e4aeee9d85 Fix ProxmoxVE migration causing reauthentication (#167624) 2026-04-08 13:22:25 +02:00
Raphael Hehl
726edf3a3b Unifi access protect api key hint (#167404)
Co-authored-by: RaHehl <rahehl@users.noreply.github.com>
2026-04-08 13:21:54 +02:00
epenet
b98aa0ad91 Use runtime_data in rdw integration (#167654)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 13:18:26 +02:00
David
f82b8cb7c7 Bump pylutron-caseta to 0.28.0 (#167642) 2026-04-08 13:17:45 +02:00
epenet
d6342d51cc Use runtime_data in radiotherm (#167650)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 13:16:21 +02:00
epenet
1eead15c24 Use runtime_data in Rabbit Air (#167649)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 13:15:46 +02:00
epenet
2e6137325c Use runtime_data in ridwell integration (#167658)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 13:14:23 +02:00
Kurt Chrisford
8d3d4a1b5c Add diagnostics to Actron Air (#167145) 2026-04-08 13:12:56 +02:00
Tomer
3e5132bf85 Victron GX reauthentication-flow (#167614)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2026-04-08 12:58:16 +02:00
epenet
65e4b26006 Use suggested uom in Renault charging power sensor (#167646) 2026-04-08 12:32:26 +02:00
epenet
13f1a42d69 Use runtime_data in roon integration (#167660)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 12:16:32 +02:00
epenet
5be48affcf Use runtime_data in rova integration (#167661)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 12:15:59 +02:00
epenet
8994f501f1 Use runtime_data in rympro integration (#167663)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 12:15:34 +02:00
epenet
7f49ecffd3 Use runtime_data in romy integration (#167665)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 12:14:29 +02:00
epenet
a560967861 Use runtime_data in roomba integration (#167667)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 12:13:44 +02:00
epenet
82202ee1c2 Use runtime_data in ruckus_unleashed integration (#167662)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 12:13:26 +02:00
Franck Nijhof
b697b3a54e Extract version template function into a version Jinja2 extension (#167172)
Co-authored-by: Joostlek <joostlek@outlook.com>
2026-04-08 12:10:22 +02:00
Kurt Chrisford
6cf5bbe2f5 Bump actronneoapi to 0.5.0 (#167669) 2026-04-08 12:06:48 +02:00
epenet
c0c61533e6 Use runtime_data in risco integration (#167659)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 12:05:04 +02:00
epenet
15e434431d Use runtime_data in renson integration (#167664)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 11:58:10 +02:00
epenet
0452bb91c7 Cleanup unused renault base entity method (#167643) 2026-04-08 11:57:55 +02:00
epenet
5620fc9e96 Use runtime_data in recollect_waste integration (#167655)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 11:57:44 +02:00
epenet
1a5ef199da Remove duplicated FlussConfigEntry type aliases (#167676)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 11:48:02 +02:00
Joost Lekkerkerker
e98eec113e Add DHCP discovery to MyStrom (#167084)
Co-authored-by: Ariel Ebersberger <31776703+justanotherariel@users.noreply.github.com>
2026-04-08 11:36:20 +02:00
Mattheinrichs
c74d4047d8 Add diagnostics support to tplink_omada (#166802)
Co-authored-by: Ariel Ebersberger <31776703+justanotherariel@users.noreply.github.com>
2026-04-08 10:37:01 +02:00
epenet
f5ae250720 Improve type hints in ipma system_health (#167670) 2026-04-08 10:30:27 +02:00
epenet
bea4eea871 Use runtime_data in rainforest_eagle integration (#167652)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 08:42:10 +02:00
209 changed files with 4557 additions and 2272 deletions

View File

@@ -499,7 +499,7 @@ jobs:
python -m build
- name: Upload package to PyPI
uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
with:
skip-existing: true

View File

@@ -1,11 +1,7 @@
"""The Actron Air integration."""
from actron_neo_api import (
ActronAirACSystem,
ActronAirAPI,
ActronAirAPIError,
ActronAirAuthError,
)
from actron_neo_api import ActronAirAPI, ActronAirAPIError, ActronAirAuthError
from actron_neo_api.models.system import ActronAirSystemInfo
from homeassistant.const import CONF_API_TOKEN, Platform
from homeassistant.core import HomeAssistant
@@ -25,7 +21,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ActronAirConfigEntry) ->
"""Set up Actron Air integration from a config entry."""
api = ActronAirAPI(refresh_token=entry.data[CONF_API_TOKEN])
systems: list[ActronAirACSystem] = []
systems: list[ActronAirSystemInfo] = []
try:
systems = await api.get_ac_systems()
@@ -44,9 +40,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ActronAirConfigEntry) ->
system_coordinators: dict[str, ActronAirSystemCoordinator] = {}
for system in systems:
coordinator = ActronAirSystemCoordinator(hass, entry, api, system)
_LOGGER.debug("Setting up coordinator for system: %s", system["serial"])
_LOGGER.debug("Setting up coordinator for system: %s", system.serial)
await coordinator.async_config_entry_first_refresh()
system_coordinators[system["serial"]] = coordinator
system_coordinators[system.serial] = coordinator
entry.runtime_data = ActronAirRuntimeData(
api=api,

View File

@@ -18,7 +18,7 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .coordinator import ActronAirConfigEntry, ActronAirSystemCoordinator
from .entity import ActronAirAcEntity, ActronAirZoneEntity, handle_actron_api_errors
from .entity import ActronAirAcEntity, ActronAirZoneEntity, actron_air_command
PARALLEL_UPDATES = 0
@@ -136,19 +136,19 @@ class ActronSystemClimate(ActronAirAcEntity, ActronAirClimateEntity):
"""Return the target temperature."""
return self._status.user_aircon_settings.temperature_setpoint_cool_c
@handle_actron_api_errors
@actron_air_command
async def async_set_fan_mode(self, fan_mode: str) -> None:
"""Set a new fan mode."""
api_fan_mode = FAN_MODE_MAPPING_HA_TO_ACTRONAIR.get(fan_mode)
await self._status.user_aircon_settings.set_fan_mode(api_fan_mode)
@handle_actron_api_errors
@actron_air_command
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
"""Set the HVAC mode."""
ac_mode = HVAC_MODE_MAPPING_HA_TO_ACTRONAIR.get(hvac_mode)
await self._status.ac_system.set_system_mode(ac_mode)
@handle_actron_api_errors
@actron_air_command
async def async_set_temperature(self, **kwargs: Any) -> None:
"""Set the temperature."""
temp = kwargs.get(ATTR_TEMPERATURE)
@@ -212,13 +212,13 @@ class ActronZoneClimate(ActronAirZoneEntity, ActronAirClimateEntity):
"""Return the target temperature."""
return self._zone.temperature_setpoint_cool_c
@handle_actron_api_errors
@actron_air_command
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
"""Set the HVAC mode."""
is_enabled = hvac_mode != HVACMode.OFF
await self._zone.enable(is_enabled)
@handle_actron_api_errors
@actron_air_command
async def async_set_temperature(self, **kwargs: Any) -> None:
"""Set the temperature."""
await self._zone.set_temperature(temperature=kwargs.get(ATTR_TEMPERATURE))

View File

@@ -38,10 +38,10 @@ class ActronAirConfigFlow(ConfigFlow, domain=DOMAIN):
_LOGGER.error("OAuth2 flow failed: %s", err)
return self.async_abort(reason="oauth2_error")
self._device_code = device_code_response["device_code"]
self._user_code = device_code_response["user_code"]
self._verification_uri = device_code_response["verification_uri_complete"]
self._expires_minutes = str(device_code_response["expires_in"] // 60)
self._device_code = device_code_response.device_code
self._user_code = device_code_response.user_code
self._verification_uri = device_code_response.verification_uri_complete
self._expires_minutes = str(device_code_response.expires_in // 60)
async def _wait_for_authorization() -> None:
"""Wait for the user to authorize the device."""

View File

@@ -6,12 +6,12 @@ from dataclasses import dataclass
from datetime import timedelta
from actron_neo_api import (
ActronAirACSystem,
ActronAirAPI,
ActronAirAPIError,
ActronAirAuthError,
ActronAirStatus,
)
from actron_neo_api.models.system import ActronAirSystemInfo
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
@@ -38,7 +38,7 @@ class ActronAirRuntimeData:
type ActronAirConfigEntry = ConfigEntry[ActronAirRuntimeData]
class ActronAirSystemCoordinator(DataUpdateCoordinator[ActronAirACSystem]):
class ActronAirSystemCoordinator(DataUpdateCoordinator[ActronAirStatus]):
"""System coordinator for Actron Air integration."""
def __init__(
@@ -46,7 +46,7 @@ class ActronAirSystemCoordinator(DataUpdateCoordinator[ActronAirACSystem]):
hass: HomeAssistant,
entry: ActronAirConfigEntry,
api: ActronAirAPI,
system: ActronAirACSystem,
system: ActronAirSystemInfo,
) -> None:
"""Initialize the coordinator."""
super().__init__(
@@ -57,7 +57,7 @@ class ActronAirSystemCoordinator(DataUpdateCoordinator[ActronAirACSystem]):
config_entry=entry,
)
self.system = system
self.serial_number = system["serial"]
self.serial_number = system.serial
self.api = api
self.status = self.api.state_manager.get_status(self.serial_number)
self.last_seen = dt_util.utcnow()

View File

@@ -0,0 +1,35 @@
"""Diagnostics support for Actron Air."""
from __future__ import annotations
from typing import Any
from homeassistant.components.diagnostics import async_redact_data
from homeassistant.const import CONF_API_TOKEN
from homeassistant.core import HomeAssistant
from .coordinator import ActronAirConfigEntry
TO_REDACT = {CONF_API_TOKEN, "master_serial", "serial_number", "serial"}
async def async_get_config_entry_diagnostics(
hass: HomeAssistant,
entry: ActronAirConfigEntry,
) -> dict[str, Any]:
"""Return diagnostics for a config entry."""
coordinators: dict[int, Any] = {}
for idx, coordinator in enumerate(entry.runtime_data.system_coordinators.values()):
coordinators[idx] = {
"system": async_redact_data(
coordinator.system.model_dump(mode="json"), TO_REDACT
),
"status": async_redact_data(
coordinator.data.model_dump(mode="json", exclude={"last_known_state"}),
TO_REDACT,
),
}
return {
"entry_data": async_redact_data(entry.data, TO_REDACT),
"coordinators": coordinators,
}

View File

@@ -14,10 +14,14 @@ from .const import DOMAIN
from .coordinator import ActronAirSystemCoordinator
def handle_actron_api_errors[_EntityT: ActronAirEntity, **_P](
def actron_air_command[_EntityT: ActronAirEntity, **_P](
func: Callable[Concatenate[_EntityT, _P], Coroutine[Any, Any, Any]],
) -> Callable[Concatenate[_EntityT, _P], Coroutine[Any, Any, None]]:
"""Decorate Actron Air API calls to handle ActronAirAPIError exceptions."""
"""Decorator for Actron Air API calls.
Handles ActronAirAPIError exceptions, and requests a coordinator update
to update the status of the devices as soon as possible.
"""
@wraps(func)
async def wrapper(self: _EntityT, *args: _P.args, **kwargs: _P.kwargs) -> None:
@@ -30,6 +34,7 @@ def handle_actron_api_errors[_EntityT: ActronAirEntity, **_P](
translation_key="api_error",
translation_placeholders={"error": str(err)},
) from err
self.coordinator.async_set_updated_data(self.coordinator.data)
return wrapper

View File

@@ -13,5 +13,5 @@
"integration_type": "hub",
"iot_class": "cloud_polling",
"quality_scale": "silver",
"requirements": ["actron-neo-api==0.4.1"]
"requirements": ["actron-neo-api==0.5.0"]
}

View File

@@ -41,7 +41,7 @@ rules:
# Gold
devices: done
diagnostics: todo
diagnostics: done
discovery-update-info:
status: exempt
comment: This integration uses DHCP discovery, however is cloud polling. Therefore there is no information to update.

View File

@@ -10,7 +10,7 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .coordinator import ActronAirConfigEntry, ActronAirSystemCoordinator
from .entity import ActronAirAcEntity, handle_actron_api_errors
from .entity import ActronAirAcEntity, actron_air_command
PARALLEL_UPDATES = 0
@@ -105,12 +105,12 @@ class ActronAirSwitch(ActronAirAcEntity, SwitchEntity):
"""Return true if the switch is on."""
return self.entity_description.is_on_fn(self.coordinator)
@handle_actron_api_errors
@actron_air_command
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the switch on."""
await self.entity_description.set_fn(self.coordinator, True)
@handle_actron_api_errors
@actron_air_command
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the switch off."""
await self.entity_description.set_fn(self.coordinator, False)

View File

@@ -5,7 +5,6 @@ from __future__ import annotations
from collections.abc import Mapping
import json
import logging
import re
from typing import TYPE_CHECKING, Any, cast
import anthropic
@@ -71,6 +70,7 @@ from .const import (
WEB_SEARCH_UNSUPPORTED_MODELS,
PromptCaching,
)
from .coordinator import model_alias
if TYPE_CHECKING:
from . import AnthropicConfigEntry
@@ -112,25 +112,13 @@ async def get_model_list(client: anthropic.AsyncAnthropic) -> list[SelectOptionD
except anthropic.AnthropicError:
models = []
_LOGGER.debug("Available models: %s", models)
model_options: list[SelectOptionDict] = []
short_form = re.compile(r"[^\d]-\d$")
for model_info in models:
# Resolve alias from versioned model name:
model_alias = (
model_info.id[:-9]
if model_info.id != "claude-3-haiku-20240307"
and model_info.id[-2:-1] != "-"
else model_info.id
return [
SelectOptionDict(
label=model_info.display_name,
value=model_alias(model_info.id),
)
if short_form.search(model_alias):
model_alias += "-0"
model_options.append(
SelectOptionDict(
label=model_info.display_name,
value=model_alias,
)
)
return model_options
for model_info in models
]
class AnthropicConfigFlow(ConfigFlow, domain=DOMAIN):

View File

@@ -2,7 +2,8 @@
from __future__ import annotations
from datetime import timedelta
import datetime
import re
import anthropic
@@ -15,13 +16,28 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, Upda
from .const import DOMAIN, LOGGER
UPDATE_INTERVAL_CONNECTED = timedelta(hours=12)
UPDATE_INTERVAL_DISCONNECTED = timedelta(minutes=1)
UPDATE_INTERVAL_CONNECTED = datetime.timedelta(hours=12)
UPDATE_INTERVAL_DISCONNECTED = datetime.timedelta(minutes=1)
type AnthropicConfigEntry = ConfigEntry[AnthropicCoordinator]
class AnthropicCoordinator(DataUpdateCoordinator[None]):
_model_short_form = re.compile(r"[^\d]-\d$")
@callback
def model_alias(model_id: str) -> str:
"""Resolve alias from versioned model name."""
if model_id == "claude-3-haiku-20240307" or model_id.endswith("-preview"):
return model_id
if model_id[-2:-1] != "-":
model_id = model_id[:-9]
if _model_short_form.search(model_id):
return model_id + "-0"
return model_id
class AnthropicCoordinator(DataUpdateCoordinator[list[anthropic.types.ModelInfo]]):
"""DataUpdateCoordinator which uses different intervals after successful and unsuccessful updates."""
client: anthropic.AsyncAnthropic
@@ -42,16 +58,16 @@ class AnthropicCoordinator(DataUpdateCoordinator[None]):
)
@callback
def async_set_updated_data(self, data: None) -> None:
def async_set_updated_data(self, data: list[anthropic.types.ModelInfo]) -> None:
"""Manually update data, notify listeners and update refresh interval."""
self.update_interval = UPDATE_INTERVAL_CONNECTED
super().async_set_updated_data(data)
async def async_update_data(self) -> None:
async def async_update_data(self) -> list[anthropic.types.ModelInfo]:
"""Fetch data from the API."""
try:
self.update_interval = UPDATE_INTERVAL_DISCONNECTED
await self.client.models.list(timeout=10.0)
result = await self.client.models.list(timeout=10.0)
self.update_interval = UPDATE_INTERVAL_CONNECTED
except anthropic.APITimeoutError as err:
raise TimeoutError(err.message or str(err)) from err
@@ -67,6 +83,7 @@ class AnthropicCoordinator(DataUpdateCoordinator[None]):
translation_key="api_error",
translation_placeholders={"message": err.message},
) from err
return result.data
def mark_connection_error(self) -> None:
"""Mark the connection as having an error and reschedule background check."""
@@ -76,3 +93,23 @@ class AnthropicCoordinator(DataUpdateCoordinator[None]):
self.async_update_listeners()
if self._listeners and not self.hass.is_stopping:
self._schedule_refresh()
@callback
def get_model_info(self, model_id: str) -> anthropic.types.ModelInfo:
"""Get model info for a given model ID."""
# First try: exact name match
for model in self.data or []:
if model.id == model_id:
return model
# Second try: match by alias
alias = model_alias(model_id)
for model in self.data or []:
if model_alias(model.id) == alias:
return model
# Model not found, return safe defaults
return anthropic.types.ModelInfo(
type="model",
id=model_id,
created_at=datetime.datetime(1970, 1, 1, tzinfo=datetime.UTC),
display_name=model_id,
)

View File

@@ -689,12 +689,17 @@ class AnthropicBaseLLMEntity(CoordinatorEntity[AnthropicCoordinator]):
super().__init__(entry.runtime_data)
self.entry = entry
self.subentry = subentry
coordinator = entry.runtime_data
self.model_info = coordinator.get_model_info(
subentry.data.get(CONF_CHAT_MODEL, DEFAULT[CONF_CHAT_MODEL])
)
self._attr_unique_id = subentry.subentry_id
self._attr_device_info = dr.DeviceInfo(
identifiers={(DOMAIN, subentry.subentry_id)},
name=subentry.title,
manufacturer="Anthropic",
model=subentry.data.get(CONF_CHAT_MODEL, DEFAULT[CONF_CHAT_MODEL]),
model=self.model_info.display_name,
model_id=self.model_info.id,
entry_type=dr.DeviceEntryType.SERVICE,
)
@@ -969,7 +974,7 @@ class AnthropicBaseLLMEntity(CoordinatorEntity[AnthropicCoordinator]):
) from err
except anthropic.AnthropicError as err:
# Non-connection error, mark connection as healthy
coordinator.async_set_updated_data(None)
coordinator.async_set_updated_data(coordinator.data)
LOGGER.error("Error while talking to Anthropic: %s", err)
raise HomeAssistantError(
translation_domain=DOMAIN,
@@ -982,7 +987,7 @@ class AnthropicBaseLLMEntity(CoordinatorEntity[AnthropicCoordinator]):
) from err
if not chat_log.unresponded_tool_results:
coordinator.async_set_updated_data(None)
coordinator.async_set_updated_data(coordinator.data)
break

View File

@@ -9,7 +9,7 @@
"iot_class": "local_polling",
"loggers": ["brother", "pyasn1", "pysmi", "pysnmp"],
"quality_scale": "platinum",
"requirements": ["brother==6.0.0"],
"requirements": ["brother==6.1.0"],
"zeroconf": [
{
"name": "brother*",

View File

@@ -112,7 +112,7 @@ class ComelitAlarmEntity(
@property
def available(self) -> bool:
"""Return True if alarm is available."""
if self._area.human_status in [AlarmAreaState.ANOMALY, AlarmAreaState.UNKNOWN]:
if self._area.human_status == AlarmAreaState.UNKNOWN:
return False
return super().available
@@ -151,7 +151,7 @@ class ComelitAlarmEntity(
if code != str(self.coordinator.api.device_pin):
return
await self.coordinator.api.set_zone_status(
self._area.index, ALARM_ACTIONS[DISABLE]
self._area.index, ALARM_ACTIONS[DISABLE], self._area.anomaly
)
await self._async_update_state(
AlarmAreaState.DISARMED, ALARM_AREA_ARMED_STATUS[DISABLE]
@@ -160,7 +160,7 @@ class ComelitAlarmEntity(
async def async_alarm_arm_away(self, code: str | None = None) -> None:
"""Send arm away command."""
await self.coordinator.api.set_zone_status(
self._area.index, ALARM_ACTIONS[AWAY]
self._area.index, ALARM_ACTIONS[AWAY], self._area.anomaly
)
await self._async_update_state(
AlarmAreaState.ARMED, ALARM_AREA_ARMED_STATUS[AWAY]
@@ -169,7 +169,7 @@ class ComelitAlarmEntity(
async def async_alarm_arm_home(self, code: str | None = None) -> None:
"""Send arm home command."""
await self.coordinator.api.set_zone_status(
self._area.index, ALARM_ACTIONS[HOME]
self._area.index, ALARM_ACTIONS[HOME], self._area.anomaly
)
await self._async_update_state(
AlarmAreaState.ARMED, ALARM_AREA_ARMED_STATUS[HOME_P1]
@@ -178,7 +178,7 @@ class ComelitAlarmEntity(
async def async_alarm_arm_night(self, code: str | None = None) -> None:
"""Send arm night command."""
await self.coordinator.api.set_zone_status(
self._area.index, ALARM_ACTIONS[NIGHT]
self._area.index, ALARM_ACTIONS[NIGHT], self._area.anomaly
)
await self._async_update_state(
AlarmAreaState.ARMED, ALARM_AREA_ARMED_STATUS[NIGHT]

View File

@@ -20,7 +20,7 @@ from homeassistant.helpers.typing import ConfigType
from homeassistant.util import dt as dt_util
from homeassistant.util.hass_dict import HassKey
from .const import ATTR_EVENT_TYPE, ATTR_EVENT_TYPES, DOMAIN
from .const import ATTR_EVENT_TYPE, ATTR_EVENT_TYPES, DOMAIN, DoorbellEventType
_LOGGER = logging.getLogger(__name__)
DATA_COMPONENT: HassKey[EntityComponent[EventEntity]] = HassKey(DOMAIN)
@@ -44,6 +44,7 @@ __all__ = [
"DOMAIN",
"PLATFORM_SCHEMA",
"PLATFORM_SCHEMA_BASE",
"DoorbellEventType",
"EventDeviceClass",
"EventEntity",
"EventEntityDescription",
@@ -189,6 +190,21 @@ class EventEntity(RestoreEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_)
async def async_internal_added_to_hass(self) -> None:
"""Call when the event entity is added to hass."""
await super().async_internal_added_to_hass()
if (
self.device_class == EventDeviceClass.DOORBELL
and DoorbellEventType.RING not in self.event_types
):
report_issue = self._suggest_report_issue()
_LOGGER.warning(
"Entity %s is a doorbell event entity but does not support "
"the '%s' event type. This will stop working in "
"Home Assistant 2027.4, please %s",
self.entity_id,
DoorbellEventType.RING,
report_issue,
)
if (
(state := await self.async_get_last_state())
and state.state is not None

View File

@@ -1,5 +1,13 @@
"""Provides the constants needed for the component."""
from enum import StrEnum
DOMAIN = "event"
ATTR_EVENT_TYPE = "event_type"
ATTR_EVENT_TYPES = "event_types"
class DoorbellEventType(StrEnum):
"""Standard event types for doorbell device class."""
RING = "ring"

View File

@@ -15,7 +15,14 @@
"name": "Button"
},
"doorbell": {
"name": "Doorbell"
"name": "Doorbell",
"state_attributes": {
"event_type": {
"state": {
"ring": "Ring"
}
}
}
},
"motion": {
"name": "Motion"

View File

@@ -2,18 +2,14 @@
from __future__ import annotations
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_API_KEY, Platform
from homeassistant.core import HomeAssistant
from .coordinator import FlussDataUpdateCoordinator
from .coordinator import FlussConfigEntry, FlussDataUpdateCoordinator
PLATFORMS: list[Platform] = [Platform.BUTTON]
type FlussConfigEntry = ConfigEntry[FlussDataUpdateCoordinator]
async def async_setup_entry(
hass: HomeAssistant,
entry: FlussConfigEntry,

View File

@@ -1,16 +1,13 @@
"""Support for Fluss Devices."""
from homeassistant.components.button import ButtonEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .coordinator import FlussApiClientError, FlussDataUpdateCoordinator
from .coordinator import FlussApiClientError, FlussConfigEntry
from .entity import FlussEntity
type FlussConfigEntry = ConfigEntry[FlussDataUpdateCoordinator]
async def async_setup_entry(
hass: HomeAssistant,

View File

@@ -453,10 +453,13 @@ class FritzBoxTools(DataUpdateCoordinator[UpdateCoordinatorDataType]):
if not attributes.get("MACAddress"):
continue
wan_access_result = None
if (wan_access := attributes.get("X_AVM-DE_WANAccess")) is not None:
wan_access_result = "granted" in wan_access
else:
wan_access_result = None
# wan_access can be "granted", "denied", "unknown" or "error"
if "granted" in wan_access:
wan_access_result = True
elif "denied" in wan_access:
wan_access_result = False
hosts[attributes["MACAddress"]] = Device(
name=attributes["HostName"],

View File

@@ -11,4 +11,8 @@ CONF_LISTENING_PORT_DEFAULT = 4002
CONF_DISCOVERY_INTERVAL_DEFAULT = 60
SCAN_INTERVAL = timedelta(seconds=30)
# A device is considered unavailable if we have not heard a status response
# from it for three consecutive poll cycles. This tolerates a single dropped
# UDP response plus some jitter before flapping the entity state.
DEVICE_TIMEOUT = SCAN_INTERVAL * 3
DISCOVERY_TIMEOUT = 5

View File

@@ -2,6 +2,7 @@
from __future__ import annotations
from datetime import datetime
import logging
from typing import Any
@@ -22,7 +23,7 @@ from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN, MANUFACTURER
from .const import DEVICE_TIMEOUT, DOMAIN, MANUFACTURER
from .coordinator import GoveeLocalApiCoordinator, GoveeLocalConfigEntry
_LOGGER = logging.getLogger(__name__)
@@ -118,6 +119,19 @@ class GoveeLight(CoordinatorEntity[GoveeLocalApiCoordinator], LightEntity):
serial_number=device.fingerprint,
)
@property
def available(self) -> bool:
"""Return if the device is reachable.
The underlying library updates ``lastseen`` whenever the device
replies to a status request. The coordinator polls every
``SCAN_INTERVAL``, so if we have not heard back within
``DEVICE_TIMEOUT`` we consider the device offline.
"""
if not super().available:
return False
return datetime.now() - self._device.lastseen < DEVICE_TIMEOUT
@property
def is_on(self) -> bool:
"""Return true if device is on (brightness above 0)."""
@@ -205,8 +219,8 @@ class GoveeLight(CoordinatorEntity[GoveeLocalApiCoordinator], LightEntity):
@callback
def _update_callback(self, device: GoveeDevice) -> None:
if self.hass:
self.async_write_ha_state()
"""Handle device state updates pushed by the library."""
self.async_write_ha_state()
def _save_last_color_state(self) -> None:
color_mode = self.color_mode

View File

@@ -98,7 +98,9 @@ class HabiticaCalendarEntity(HabiticaBase, CalendarEntity):
start_date, end_date - timedelta(days=1), inc=True
)
# if no end_date is given, return only the next recurrence
return [recurrences.after(start_date, inc=True)]
if (next_date := recurrences.after(start_date, inc=True)) is None:
return []
return [next_date]
class HabiticaTodosCalendarEntity(HabiticaCalendarEntity):

View File

@@ -13,7 +13,7 @@ from homeassistant.components.homeassistant_hardware.util import guess_firmware_
from homeassistant.components.usb import (
USBDevice,
async_register_port_event_callback,
scan_serial_ports,
async_scan_serial_ports,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
@@ -163,7 +163,7 @@ async def async_migrate_entry(
key not in config_entry.data
for key in (VID, PID, MANUFACTURER, PRODUCT, SERIAL_NUMBER)
):
serial_ports = await hass.async_add_executor_job(scan_serial_ports)
serial_ports = await async_scan_serial_ports(hass)
serial_ports_info = {port.device: port for port in serial_ports}
device = config_entry.data[DEVICE]

View File

@@ -1,5 +1,7 @@
"""Provide info to system health."""
from typing import Any
from homeassistant.components import system_health
from homeassistant.core import HomeAssistant, callback
@@ -14,7 +16,7 @@ def async_register(
register.async_register_info(system_health_info)
async def system_health_info(hass):
async def system_health_info(hass: HomeAssistant) -> dict[str, Any]:
"""Get info for the info page."""
return {
"api_endpoint_reachable": system_health.async_check_can_reach_url(

View File

@@ -69,7 +69,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: LunatoneConfigEntry) ->
"""Set up Lunatone from a config entry."""
auth_api = Auth(async_get_clientsession(hass), entry.data[CONF_URL])
info_api = Info(auth_api)
devices_api = Devices(auth_api)
devices_api = Devices(info_api)
coordinator_info = LunatoneInfoDataUpdateCoordinator(hass, entry, info_api)
await coordinator_info.async_config_entry_first_refresh()

View File

@@ -2,7 +2,6 @@
from __future__ import annotations
import asyncio
from typing import Any
from lunatone_rest_api_client import DALIBroadcast
@@ -28,7 +27,6 @@ from .coordinator import (
)
PARALLEL_UPDATES = 0
STATUS_UPDATE_DELAY = 0.04
async def async_setup_entry(
@@ -149,8 +147,6 @@ class LunatoneLight(
)
else:
await self._device.switch_on()
await asyncio.sleep(STATUS_UPDATE_DELAY)
await self.coordinator.async_refresh()
async def async_turn_off(self, **kwargs: Any) -> None:
@@ -161,8 +157,6 @@ class LunatoneLight(
await self._device.fade_to_brightness(0)
else:
await self._device.switch_off()
await asyncio.sleep(STATUS_UPDATE_DELAY)
await self.coordinator.async_refresh()
@@ -221,13 +215,9 @@ class LunatoneLineBroadcastLight(
await self._broadcast.fade_to_brightness(
brightness_to_value(self.BRIGHTNESS_SCALE, kwargs.get(ATTR_BRIGHTNESS, 255))
)
await asyncio.sleep(STATUS_UPDATE_DELAY)
await self._coordinator_devices.async_refresh()
async def async_turn_off(self, **kwargs: Any) -> None:
"""Instruct the line to turn off."""
await self._broadcast.fade_to_brightness(0)
await asyncio.sleep(STATUS_UPDATE_DELAY)
await self._coordinator_devices.async_refresh()

View File

@@ -7,5 +7,5 @@
"integration_type": "hub",
"iot_class": "local_polling",
"quality_scale": "silver",
"requirements": ["lunatone-rest-api-client==0.7.0"]
"requirements": ["lunatone-rest-api-client==0.9.0"]
}

View File

@@ -10,7 +10,7 @@
"integration_type": "hub",
"iot_class": "local_push",
"loggers": ["pylutron_caseta"],
"requirements": ["pylutron-caseta==0.27.0"],
"requirements": ["pylutron-caseta==0.28.0"],
"zeroconf": [
{
"properties": {

View File

@@ -3,7 +3,7 @@
from __future__ import annotations
import logging
from typing import Any
from typing import TYPE_CHECKING, Any
import pymystrom
from pymystrom.exceptions import MyStromConnectionError
@@ -11,6 +11,7 @@ import voluptuous as vol
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_HOST, CONF_NAME
from homeassistant.helpers.service_info.dhcp import DhcpServiceInfo
from .const import DOMAIN
@@ -31,6 +32,8 @@ class MyStromConfigFlow(ConfigFlow, domain=DOMAIN):
VERSION = 1
_host: str | None = None
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
@@ -51,3 +54,38 @@ class MyStromConfigFlow(ConfigFlow, domain=DOMAIN):
schema = self.add_suggested_values_to_schema(STEP_USER_DATA_SCHEMA, user_input)
return self.async_show_form(step_id="user", data_schema=schema, errors=errors)
async def async_step_dhcp(
self, discovery_info: DhcpServiceInfo
) -> ConfigFlowResult:
"""Handle DHCP discovery."""
mac_address = discovery_info.macaddress.upper()
await self.async_set_unique_id(mac_address)
self._abort_if_unique_id_configured(updates={CONF_HOST: discovery_info.ip})
try:
await pymystrom.get_device_info(discovery_info.ip)
except MyStromConnectionError:
return self.async_abort(reason="cannot_connect")
self._host = discovery_info.ip
self.context["title_placeholders"] = {"host": discovery_info.ip}
return await self.async_step_discovery_confirm()
async def async_step_discovery_confirm(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle discovery confirmation."""
if user_input is not None:
return self.async_create_entry(
title=DEFAULT_NAME,
data={CONF_HOST: self._host},
)
self._set_confirm_only()
if TYPE_CHECKING:
assert self._host is not None
return self.async_show_form(
step_id="discovery_confirm",
description_placeholders={CONF_HOST: self._host},
)

View File

@@ -4,6 +4,14 @@
"codeowners": ["@fabaff"],
"config_flow": true,
"dependencies": ["http"],
"dhcp": [
{
"hostname": "mystrom-*"
},
{
"registered_devices": true
}
],
"documentation": "https://www.home-assistant.io/integrations/mystrom",
"integration_type": "device",
"iot_class": "local_polling",

View File

@@ -1,12 +1,16 @@
{
"config": {
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
},
"step": {
"discovery_confirm": {
"description": "Do you want to set up the myStrom device at {host}?"
},
"user": {
"data": {
"host": "[%key:common::config_flow::data::host%]",

View File

@@ -8,5 +8,5 @@
"integration_type": "hub",
"iot_class": "cloud_polling",
"loggers": ["pybotvac"],
"requirements": ["pybotvac==0.0.28"]
"requirements": ["pybotvac==0.0.29"]
}

View File

@@ -189,6 +189,14 @@ async def async_migrate_entry(hass: HomeAssistant, entry: ProxmoxConfigEntry) ->
# Migration for additional configuration options added to support API tokens
if entry.version < 3:
data = dict(entry.data)
# If CONF_REALM wasn't there yet, extract from username
if CONF_REALM not in data:
data[CONF_REALM] = DEFAULT_REALM
if "@" in data.get(CONF_USERNAME, ""):
username, realm = data[CONF_USERNAME].split("@", 1)
data[CONF_USERNAME] = username
data[CONF_REALM] = realm.lower()
realm = data[CONF_REALM].lower()
# If the realm is one of the base providers, set the provider to match the realm.

View File

@@ -5,21 +5,17 @@ from __future__ import annotations
from rabbitair import Client, UdpClient
from homeassistant.components import zeroconf
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_ACCESS_TOKEN, CONF_HOST, Platform
from homeassistant.core import HomeAssistant
from .const import DOMAIN
from .coordinator import RabbitAirDataUpdateCoordinator
from .coordinator import RabbitAirConfigEntry, RabbitAirDataUpdateCoordinator
PLATFORMS: list[Platform] = [Platform.FAN]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_setup_entry(hass: HomeAssistant, entry: RabbitAirConfigEntry) -> bool:
"""Set up Rabbit Air from a config entry."""
hass.data.setdefault(DOMAIN, {})
host: str = entry.data[CONF_HOST]
token: str = entry.data[CONF_ACCESS_TOKEN]
@@ -30,7 +26,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
await coordinator.async_config_entry_first_refresh()
hass.data[DOMAIN][entry.entry_id] = coordinator
entry.runtime_data = coordinator
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
@@ -39,14 +35,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: RabbitAirConfigEntry) -> bool:
"""Unload a config entry."""
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
async def update_listener(hass: HomeAssistant, entry: RabbitAirConfigEntry) -> None:
"""Handle options update."""
await hass.config_entries.async_reload(entry.entry_id)

View File

@@ -12,6 +12,8 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.debounce import Debouncer
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
type RabbitAirConfigEntry = ConfigEntry[RabbitAirDataUpdateCoordinator]
_LOGGER = logging.getLogger(__name__)
@@ -43,10 +45,10 @@ class RabbitAirDebouncer(Debouncer[Coroutine[Any, Any, None]]):
class RabbitAirDataUpdateCoordinator(DataUpdateCoordinator[State]):
"""Class to manage fetching data from single endpoint."""
config_entry: ConfigEntry
config_entry: RabbitAirConfigEntry
def __init__(
self, hass: HomeAssistant, config_entry: ConfigEntry, device: Client
self, hass: HomeAssistant, config_entry: RabbitAirConfigEntry, device: Client
) -> None:
"""Initialize global data updater."""
self.device = device

View File

@@ -7,13 +7,12 @@ from typing import Any
from rabbitair import Model
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_MAC
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN
from .coordinator import RabbitAirDataUpdateCoordinator
from .coordinator import RabbitAirConfigEntry, RabbitAirDataUpdateCoordinator
_LOGGER = logging.getLogger(__name__)
@@ -31,7 +30,7 @@ class RabbitAirBaseEntity(CoordinatorEntity[RabbitAirDataUpdateCoordinator]):
def __init__(
self,
coordinator: RabbitAirDataUpdateCoordinator,
entry: ConfigEntry,
entry: RabbitAirConfigEntry,
) -> None:
"""Initialize the entity."""
super().__init__(coordinator)

View File

@@ -7,7 +7,6 @@ from typing import Any
from rabbitair import Mode, Model, Speed
from homeassistant.components.fan import FanEntity, FanEntityFeature
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.util.percentage import (
@@ -15,8 +14,7 @@ from homeassistant.util.percentage import (
percentage_to_ordered_list_item,
)
from .const import DOMAIN
from .coordinator import RabbitAirDataUpdateCoordinator
from .coordinator import RabbitAirConfigEntry, RabbitAirDataUpdateCoordinator
from .entity import RabbitAirBaseEntity
SPEED_LIST = [
@@ -40,12 +38,11 @@ PRESET_MODES = {
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
entry: RabbitAirConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up a config entry."""
coordinator: RabbitAirDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
async_add_entities([RabbitAirFanEntity(coordinator, entry)])
async_add_entities([RabbitAirFanEntity(entry.runtime_data, entry)])
class RabbitAirFanEntity(RabbitAirBaseEntity, FanEntity):
@@ -61,7 +58,7 @@ class RabbitAirFanEntity(RabbitAirBaseEntity, FanEntity):
def __init__(
self,
coordinator: RabbitAirDataUpdateCoordinator,
entry: ConfigEntry,
entry: RabbitAirConfigEntry,
) -> None:
"""Initialize the entity."""
super().__init__(coordinator, entry)

View File

@@ -8,13 +8,11 @@ from urllib.error import URLError
from radiotherm.validate import RadiothermTstatError
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from .const import DOMAIN
from .coordinator import RadioThermUpdateCoordinator
from .coordinator import RadioThermConfigEntry, RadioThermUpdateCoordinator
from .data import async_get_init_data
from .util import async_set_time
@@ -38,7 +36,7 @@ async def _async_call_or_raise_not_ready[_T](
raise ConfigEntryNotReady(msg) from ex
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_setup_entry(hass: HomeAssistant, entry: RadioThermConfigEntry) -> bool:
"""Set up Radio Thermostat from a config entry."""
host = entry.data[CONF_HOST]
init_coro = async_get_init_data(hass, host)
@@ -54,21 +52,20 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
time_coro = async_set_time(hass, init_data.tstat)
await _async_call_or_raise_not_ready(time_coro, host)
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
entry.runtime_data = coordinator
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
entry.async_on_unload(entry.add_update_listener(_async_update_listener))
return True
async def _async_update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
async def _async_update_listener(
hass: HomeAssistant, entry: RadioThermConfigEntry
) -> None:
"""Handle options update."""
await hass.config_entries.async_reload(entry.entry_id)
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: RadioThermConfigEntry) -> bool:
"""Unload a config entry."""
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)

View File

@@ -17,13 +17,11 @@ from homeassistant.components.climate import (
HVACAction,
HVACMode,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_TEMPERATURE, PRECISION_HALVES, UnitOfTemperature
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import DOMAIN
from .coordinator import RadioThermUpdateCoordinator
from .coordinator import RadioThermConfigEntry, RadioThermUpdateCoordinator
from .entity import RadioThermostatEntity
ATTR_FAN_ACTION = "fan_action"
@@ -101,12 +99,11 @@ def round_temp(temperature):
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
entry: RadioThermConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up climate for a radiotherm device."""
coordinator: RadioThermUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
async_add_entities([RadioThermostat(coordinator)])
async_add_entities([RadioThermostat(entry.runtime_data)])
class RadioThermostat(RadioThermostatEntity, ClimateEntity):

View File

@@ -14,6 +14,8 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, Upda
from .data import RadioThermInitData, RadioThermUpdate, async_get_data
type RadioThermConfigEntry = ConfigEntry[RadioThermUpdateCoordinator]
_LOGGER = logging.getLogger(__name__)
UPDATE_INTERVAL = timedelta(seconds=15)
@@ -22,12 +24,12 @@ UPDATE_INTERVAL = timedelta(seconds=15)
class RadioThermUpdateCoordinator(DataUpdateCoordinator[RadioThermUpdate]):
"""DataUpdateCoordinator to gather data for radio thermostats."""
config_entry: ConfigEntry
config_entry: RadioThermConfigEntry
def __init__(
self,
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: RadioThermConfigEntry,
init_data: RadioThermInitData,
) -> None:
"""Initialize DataUpdateCoordinator."""

View File

@@ -5,12 +5,10 @@ from __future__ import annotations
from typing import Any
from homeassistant.components.switch import SwitchEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .const import DOMAIN
from .coordinator import RadioThermUpdateCoordinator
from .coordinator import RadioThermConfigEntry, RadioThermUpdateCoordinator
from .entity import RadioThermostatEntity
PARALLEL_UPDATES = 1
@@ -18,12 +16,11 @@ PARALLEL_UPDATES = 1
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
entry: RadioThermConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up switches for a radiotherm device."""
coordinator: RadioThermUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
async_add_entities([RadioThermHoldSwitch(coordinator)])
async_add_entities([RadioThermHoldSwitch(entry.runtime_data)])
class RadioThermHoldSwitch(RadioThermostatEntity, SwitchEntity):

View File

@@ -2,29 +2,27 @@
from __future__ import annotations
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from .const import DOMAIN
from .coordinator import EagleDataCoordinator
from .coordinator import EagleDataCoordinator, RainforestEagleConfigEntry
PLATFORMS = [Platform.SENSOR]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_setup_entry(
hass: HomeAssistant, entry: RainforestEagleConfigEntry
) -> bool:
"""Set up Rainforest Eagle from a config entry."""
coordinator = EagleDataCoordinator(hass, entry)
await coordinator.async_config_entry_first_refresh()
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
entry.runtime_data = coordinator
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(
hass: HomeAssistant, entry: RainforestEagleConfigEntry
) -> bool:
"""Unload a config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok:
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)

View File

@@ -23,17 +23,21 @@ from .const import (
)
from .data import UPDATE_100_ERRORS
type RainforestEagleConfigEntry = ConfigEntry[EagleDataCoordinator]
_LOGGER = logging.getLogger(__name__)
class EagleDataCoordinator(DataUpdateCoordinator):
"""Get the latest data from the Eagle device."""
config_entry: ConfigEntry
config_entry: RainforestEagleConfigEntry
eagle100_reader: Eagle100Reader | None = None
eagle200_meter: aioeagle.ElectricMeter | None = None
def __init__(self, hass: HomeAssistant, config_entry: ConfigEntry) -> None:
def __init__(
self, hass: HomeAssistant, config_entry: RainforestEagleConfigEntry
) -> None:
"""Initialize the data object."""
if config_entry.data[CONF_TYPE] == TYPE_EAGLE_100:
self.model = "EAGLE-100"

View File

@@ -5,22 +5,19 @@ from __future__ import annotations
from typing import Any
from homeassistant.components.diagnostics import async_redact_data
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from .const import CONF_CLOUD_ID, CONF_INSTALL_CODE, DOMAIN
from .coordinator import EagleDataCoordinator
from .const import CONF_CLOUD_ID, CONF_INSTALL_CODE
from .coordinator import RainforestEagleConfigEntry
TO_REDACT = {CONF_CLOUD_ID, CONF_INSTALL_CODE}
async def async_get_config_entry_diagnostics(
hass: HomeAssistant, config_entry: ConfigEntry
hass: HomeAssistant, config_entry: RainforestEagleConfigEntry
) -> dict[str, Any]:
"""Return diagnostics for a config entry."""
coordinator: EagleDataCoordinator = hass.data[DOMAIN][config_entry.entry_id]
return {
"config_entry": async_redact_data(config_entry.as_dict(), TO_REDACT),
"data": coordinator.data,
"data": config_entry.runtime_data.data,
}

View File

@@ -8,7 +8,6 @@ from homeassistant.components.sensor import (
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import UnitOfEnergy, UnitOfPower
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceInfo
@@ -17,7 +16,7 @@ from homeassistant.helpers.typing import StateType
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN
from .coordinator import EagleDataCoordinator
from .coordinator import EagleDataCoordinator, RainforestEagleConfigEntry
SENSORS = (
SensorEntityDescription(
@@ -46,11 +45,11 @@ SENSORS = (
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
entry: RainforestEagleConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up a config entry."""
coordinator = hass.data[DOMAIN][entry.entry_id]
coordinator = entry.runtime_data
entities = [EagleSensor(coordinator, description) for description in SENSORS]
if coordinator.data.get("zigbee:Price") not in (None, "invalid"):

View File

@@ -2,30 +2,25 @@
from __future__ import annotations
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from .const import DOMAIN
from .coordinator import RDWDataUpdateCoordinator
from .coordinator import RDWConfigEntry, RDWDataUpdateCoordinator
PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_setup_entry(hass: HomeAssistant, entry: RDWConfigEntry) -> bool:
"""Set up RDW from a config entry."""
coordinator = RDWDataUpdateCoordinator(hass, entry)
await coordinator.async_config_entry_first_refresh()
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
entry.runtime_data = coordinator
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: RDWConfigEntry) -> bool:
"""Unload RDW config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok:
del hass.data[DOMAIN][entry.entry_id]
return unload_ok
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)

View File

@@ -12,14 +12,13 @@ from homeassistant.components.binary_sensor import (
BinarySensorEntity,
BinarySensorEntityDescription,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN
from .coordinator import RDWDataUpdateCoordinator
from .coordinator import RDWConfigEntry, RDWDataUpdateCoordinator
@dataclass(frozen=True, kw_only=True)
@@ -46,11 +45,11 @@ BINARY_SENSORS: tuple[RDWBinarySensorEntityDescription, ...] = (
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
entry: RDWConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up RDW binary sensors based on a config entry."""
coordinator: RDWDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
coordinator = entry.runtime_data
async_add_entities(
RDWBinarySensorEntity(
coordinator=coordinator,

View File

@@ -11,13 +11,15 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from .const import CONF_LICENSE_PLATE, DOMAIN, LOGGER, SCAN_INTERVAL
type RDWConfigEntry = ConfigEntry[RDWDataUpdateCoordinator]
class RDWDataUpdateCoordinator(DataUpdateCoordinator[Vehicle]):
"""Class to manage fetching RDW data."""
config_entry: ConfigEntry
config_entry: RDWConfigEntry
def __init__(self, hass: HomeAssistant, config_entry: ConfigEntry) -> None:
def __init__(self, hass: HomeAssistant, config_entry: RDWConfigEntry) -> None:
"""Initialize the coordinator."""
super().__init__(
hass,

View File

@@ -4,17 +4,14 @@ from __future__ import annotations
from typing import Any
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from .const import DOMAIN
from .coordinator import RDWDataUpdateCoordinator
from .coordinator import RDWConfigEntry
async def async_get_config_entry_diagnostics(
hass: HomeAssistant, entry: ConfigEntry
hass: HomeAssistant, entry: RDWConfigEntry
) -> dict[str, Any]:
"""Return diagnostics for a config entry."""
coordinator: RDWDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
data: dict[str, Any] = coordinator.data.to_dict()
data: dict[str, Any] = entry.runtime_data.data.to_dict()
return data

View File

@@ -13,14 +13,13 @@ from homeassistant.components.sensor import (
SensorEntity,
SensorEntityDescription,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import CONF_LICENSE_PLATE, DOMAIN
from .coordinator import RDWDataUpdateCoordinator
from .coordinator import RDWConfigEntry, RDWDataUpdateCoordinator
@dataclass(frozen=True, kw_only=True)
@@ -48,11 +47,11 @@ SENSORS: tuple[RDWSensorEntityDescription, ...] = (
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
entry: RDWConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up RDW sensors based on a config entry."""
coordinator: RDWDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
coordinator = entry.runtime_data
async_add_entities(
RDWSensorEntity(
coordinator=coordinator,

View File

@@ -9,19 +9,20 @@ from homeassistant.const import Platform
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import entity_registry as er
from .const import CONF_PLACE_ID, CONF_SERVICE_ID, DOMAIN, LOGGER
from .coordinator import ReCollectWasteDataUpdateCoordinator
from .const import CONF_PLACE_ID, CONF_SERVICE_ID, LOGGER
from .coordinator import RecollectWasteConfigEntry, ReCollectWasteDataUpdateCoordinator
PLATFORMS = [Platform.CALENDAR, Platform.SENSOR]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_setup_entry(
hass: HomeAssistant, entry: RecollectWasteConfigEntry
) -> bool:
"""Set up ReCollect Waste as config entry."""
coordinator = ReCollectWasteDataUpdateCoordinator(hass, entry)
await coordinator.async_config_entry_first_refresh()
hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][entry.entry_id] = coordinator
entry.runtime_data = coordinator
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
@@ -30,18 +31,18 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
return True
async def async_reload_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
async def async_reload_entry(
hass: HomeAssistant, entry: RecollectWasteConfigEntry
) -> None:
"""Handle an options update."""
await hass.config_entries.async_reload(entry.entry_id)
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(
hass: HomeAssistant, entry: RecollectWasteConfigEntry
) -> bool:
"""Unload an ReCollect Waste config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok:
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:

View File

@@ -7,19 +7,17 @@ import datetime
from aiorecollect.client import PickupEvent
from homeassistant.components.calendar import CalendarEntity, CalendarEvent
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .const import DOMAIN
from .coordinator import ReCollectWasteDataUpdateCoordinator
from .coordinator import RecollectWasteConfigEntry, ReCollectWasteDataUpdateCoordinator
from .entity import ReCollectWasteEntity
from .util import async_get_pickup_type_names
@callback
def async_get_calendar_event_from_pickup_event(
entry: ConfigEntry, pickup_event: PickupEvent
entry: RecollectWasteConfigEntry, pickup_event: PickupEvent
) -> CalendarEvent:
"""Get a HASS CalendarEvent from an aiorecollect PickupEvent."""
pickup_type_string = ", ".join(
@@ -36,13 +34,11 @@ def async_get_calendar_event_from_pickup_event(
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
entry: RecollectWasteConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up ReCollect Waste sensors based on a config entry."""
coordinator: ReCollectWasteDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
async_add_entities([ReCollectWasteCalendar(coordinator, entry)])
async_add_entities([ReCollectWasteCalendar(entry.runtime_data, entry)])
class ReCollectWasteCalendar(ReCollectWasteEntity, CalendarEntity):
@@ -54,7 +50,7 @@ class ReCollectWasteCalendar(ReCollectWasteEntity, CalendarEntity):
def __init__(
self,
coordinator: ReCollectWasteDataUpdateCoordinator,
entry: ConfigEntry,
entry: RecollectWasteConfigEntry,
) -> None:
"""Initialize the ReCollect Waste entity."""
super().__init__(coordinator, entry)

View File

@@ -8,17 +8,13 @@ from aiorecollect.client import Client
from aiorecollect.errors import RecollectError
import voluptuous as vol
from homeassistant.config_entries import (
ConfigEntry,
ConfigFlow,
ConfigFlowResult,
OptionsFlow,
)
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult, OptionsFlow
from homeassistant.const import CONF_FRIENDLY_NAME
from homeassistant.core import callback
from homeassistant.helpers import aiohttp_client
from .const import CONF_PLACE_ID, CONF_SERVICE_ID, DOMAIN, LOGGER
from .coordinator import RecollectWasteConfigEntry
DATA_SCHEMA = vol.Schema(
{vol.Required(CONF_PLACE_ID): str, vol.Required(CONF_SERVICE_ID): str}
@@ -33,7 +29,7 @@ class RecollectWasteConfigFlow(ConfigFlow, domain=DOMAIN):
@staticmethod
@callback
def async_get_options_flow(
config_entry: ConfigEntry,
config_entry: RecollectWasteConfigEntry,
) -> RecollectWasteOptionsFlowHandler:
"""Define the config flow to handle options."""
return RecollectWasteOptionsFlowHandler()

View File

@@ -14,15 +14,19 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, Upda
from .const import CONF_PLACE_ID, CONF_SERVICE_ID, LOGGER
type RecollectWasteConfigEntry = ConfigEntry[ReCollectWasteDataUpdateCoordinator]
DEFAULT_UPDATE_INTERVAL = timedelta(days=1)
class ReCollectWasteDataUpdateCoordinator(DataUpdateCoordinator[list[PickupEvent]]):
"""Class to manage fetching ReCollect Waste data."""
config_entry: ConfigEntry
config_entry: RecollectWasteConfigEntry
def __init__(self, hass: HomeAssistant, config_entry: ConfigEntry) -> None:
def __init__(
self, hass: HomeAssistant, config_entry: RecollectWasteConfigEntry
) -> None:
"""Initialize the coordinator."""
super().__init__(
hass,

View File

@@ -6,12 +6,11 @@ import dataclasses
from typing import Any
from homeassistant.components.diagnostics import async_redact_data
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_UNIQUE_ID
from homeassistant.core import HomeAssistant
from .const import CONF_PLACE_ID, DOMAIN
from .coordinator import ReCollectWasteDataUpdateCoordinator
from .const import CONF_PLACE_ID
from .coordinator import RecollectWasteConfigEntry
CONF_AREA_NAME = "area_name"
CONF_TITLE = "title"
@@ -26,15 +25,13 @@ TO_REDACT = {
async def async_get_config_entry_diagnostics(
hass: HomeAssistant, entry: ConfigEntry
hass: HomeAssistant, entry: RecollectWasteConfigEntry
) -> dict[str, Any]:
"""Return diagnostics for a config entry."""
coordinator: ReCollectWasteDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
return async_redact_data(
{
"entry": entry.as_dict(),
"data": [dataclasses.asdict(event) for event in coordinator.data],
"data": [dataclasses.asdict(event) for event in entry.runtime_data.data],
},
TO_REDACT,
)

View File

@@ -1,11 +1,10 @@
"""Define a base ReCollect Waste entity."""
from homeassistant.config_entries import ConfigEntry
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import CONF_PLACE_ID, CONF_SERVICE_ID, DOMAIN
from .coordinator import ReCollectWasteDataUpdateCoordinator
from .coordinator import RecollectWasteConfigEntry, ReCollectWasteDataUpdateCoordinator
class ReCollectWasteEntity(CoordinatorEntity[ReCollectWasteDataUpdateCoordinator]):
@@ -16,7 +15,7 @@ class ReCollectWasteEntity(CoordinatorEntity[ReCollectWasteDataUpdateCoordinator
def __init__(
self,
coordinator: ReCollectWasteDataUpdateCoordinator,
entry: ConfigEntry,
entry: RecollectWasteConfigEntry,
) -> None:
"""Initialize the sensor."""
super().__init__(coordinator)

View File

@@ -9,12 +9,11 @@ from homeassistant.components.sensor import (
SensorEntity,
SensorEntityDescription,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .const import DOMAIN, LOGGER
from .coordinator import ReCollectWasteDataUpdateCoordinator
from .const import LOGGER
from .coordinator import RecollectWasteConfigEntry, ReCollectWasteDataUpdateCoordinator
from .entity import ReCollectWasteEntity
from .util import async_get_pickup_type_names
@@ -38,14 +37,12 @@ SENSOR_DESCRIPTIONS = (
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
entry: RecollectWasteConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up ReCollect Waste sensors based on a config entry."""
coordinator: ReCollectWasteDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
async_add_entities(
ReCollectWasteSensor(coordinator, entry, description)
ReCollectWasteSensor(entry.runtime_data, entry, description)
for description in SENSOR_DESCRIPTIONS
)
@@ -63,7 +60,7 @@ class ReCollectWasteSensor(ReCollectWasteEntity, SensorEntity):
def __init__(
self,
coordinator: ReCollectWasteDataUpdateCoordinator,
entry: ConfigEntry,
entry: RecollectWasteConfigEntry,
description: SensorEntityDescription,
) -> None:
"""Initialize."""

View File

@@ -3,12 +3,10 @@
from __future__ import annotations
from dataclasses import dataclass
from typing import cast
from renault_api.kamereon.models import KamereonVehicleDataAttributes
from homeassistant.helpers.entity import Entity, EntityDescription
from homeassistant.helpers.typing import StateType
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .coordinator import RenaultDataUpdateCoordinator
@@ -54,10 +52,6 @@ class RenaultDataEntity[T: KamereonVehicleDataAttributes](
super().__init__(vehicle.coordinators[description.coordinator])
RenaultEntity.__init__(self, vehicle, description)
def _get_data_attr(self, key: str) -> StateType:
"""Return the attribute value from the coordinator data."""
return cast(StateType, getattr(self.coordinator.data, key))
@property
def assumed_state(self) -> bool:
"""Return True if unable to access real state of the entity."""

View File

@@ -88,15 +88,6 @@ class RenaultSensor[T: KamereonVehicleDataAttributes](
return self.entity_description.value_lambda(self)
def _get_charging_power(
entity: RenaultSensor[KamereonVehicleBatteryStatusData],
) -> StateType:
"""Return the charging_power of this entity."""
if (data := entity.coordinator.data.chargingInstantaneousPower) is None:
return None
return data / 1000
def _get_charge_state_formatted(
entity: RenaultSensor[KamereonVehicleBatteryStatusData],
) -> str | None:
@@ -190,9 +181,10 @@ SENSOR_TYPES: tuple[RenaultSensorEntityDescription[Any], ...] = (
condition_lambda=lambda a: a.details.reports_charging_power_in_watts(),
coordinator="battery",
device_class=SensorDeviceClass.POWER,
native_unit_of_measurement=UnitOfPower.KILO_WATT,
native_unit_of_measurement=UnitOfPower.WATT,
suggested_unit_of_measurement=UnitOfPower.KILO_WATT,
state_class=SensorStateClass.MEASUREMENT,
value_lambda=_get_charging_power,
value_lambda=lambda e: e.coordinator.data.chargingInstantaneousPower,
translation_key="charging_power",
),
RenaultSensorEntityDescription[KamereonVehicleBatteryStatusData](

View File

@@ -2,17 +2,13 @@
from __future__ import annotations
from dataclasses import dataclass
from renson_endura_delta.renson import RensonVentilation
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from .const import DOMAIN
from .coordinator import RensonCoordinator
from .coordinator import RensonConfigEntry, RensonCoordinator, RensonData
PLATFORMS = [
Platform.BINARY_SENSOR,
@@ -25,15 +21,7 @@ PLATFORMS = [
]
@dataclass
class RensonData:
"""Renson data class."""
api: RensonVentilation
coordinator: RensonCoordinator
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_setup_entry(hass: HomeAssistant, entry: RensonConfigEntry) -> bool:
"""Set up Renson from a config entry."""
api = RensonVentilation(entry.data[CONF_HOST])
@@ -44,7 +32,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
await coordinator.async_config_entry_first_refresh()
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = RensonData(
entry.runtime_data = RensonData(
api,
coordinator,
)
@@ -54,9 +42,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: RensonConfigEntry) -> bool:
"""Unload a config entry."""
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)

View File

@@ -21,13 +21,11 @@ from homeassistant.components.binary_sensor import (
BinarySensorEntity,
BinarySensorEntityDescription,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .const import DOMAIN
from .coordinator import RensonCoordinator
from .coordinator import RensonConfigEntry, RensonCoordinator
from .entity import RensonEntity
@@ -85,15 +83,13 @@ BINARY_SENSORS: tuple[RensonBinarySensorEntityDescription, ...] = (
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: RensonConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Call the Renson integration to setup."""
api: RensonVentilation = hass.data[DOMAIN][config_entry.entry_id].api
coordinator: RensonCoordinator = hass.data[DOMAIN][
config_entry.entry_id
].coordinator
api = config_entry.runtime_data.api
coordinator = config_entry.runtime_data.coordinator
async_add_entities(
RensonBinarySensor(description, api, coordinator)

View File

@@ -12,13 +12,11 @@ from homeassistant.components.button import (
ButtonEntity,
ButtonEntityDescription,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import RensonCoordinator, RensonData
from .const import DOMAIN
from .coordinator import RensonConfigEntry, RensonCoordinator
from .entity import RensonEntity
@@ -53,12 +51,12 @@ ENTITY_DESCRIPTIONS: tuple[RensonButtonEntityDescription, ...] = (
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: RensonConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the Renson button platform."""
data: RensonData = hass.data[DOMAIN][config_entry.entry_id]
data = config_entry.runtime_data
entities = [
RensonButton(description, data.api, data.coordinator)

View File

@@ -3,6 +3,7 @@
from __future__ import annotations
import asyncio
from dataclasses import dataclass
from datetime import timedelta
import logging
from typing import Any
@@ -15,18 +16,29 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from .const import DOMAIN
type RensonConfigEntry = ConfigEntry[RensonData]
@dataclass
class RensonData:
"""Renson data class."""
api: RensonVentilation
coordinator: RensonCoordinator
_LOGGER = logging.getLogger(__name__)
class RensonCoordinator(DataUpdateCoordinator[dict[str, Any]]):
"""Data update coordinator for Renson."""
config_entry: ConfigEntry
config_entry: RensonConfigEntry
def __init__(
self,
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: RensonConfigEntry,
api: RensonVentilation,
) -> None:
"""Initialize my coordinator."""

View File

@@ -16,7 +16,6 @@ from renson_endura_delta.renson import Level, RensonVentilation
import voluptuous as vol
from homeassistant.components.fan import FanEntity, FanEntityFeature
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import config_validation as cv, entity_platform
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
@@ -27,8 +26,7 @@ from homeassistant.util.percentage import (
)
from homeassistant.util.scaling import int_states_in_range
from .const import DOMAIN
from .coordinator import RensonCoordinator
from .coordinator import RensonConfigEntry, RensonCoordinator
from .entity import RensonEntity
_LOGGER = logging.getLogger(__name__)
@@ -84,15 +82,13 @@ SPEED_RANGE: tuple[float, float] = (1, 4)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: RensonConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the Renson fan platform."""
api: RensonVentilation = hass.data[DOMAIN][config_entry.entry_id].api
coordinator: RensonCoordinator = hass.data[DOMAIN][
config_entry.entry_id
].coordinator
api = config_entry.runtime_data.api
coordinator = config_entry.runtime_data.coordinator
async_add_entities([RensonFan(api, coordinator)])

View File

@@ -12,13 +12,11 @@ from homeassistant.components.number import (
NumberEntity,
NumberEntityDescription,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import EntityCategory, UnitOfTime
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .const import DOMAIN
from .coordinator import RensonCoordinator
from .coordinator import RensonConfigEntry, RensonCoordinator
from .entity import RensonEntity
_LOGGER = logging.getLogger(__name__)
@@ -39,15 +37,13 @@ RENSON_NUMBER_DESCRIPTION = NumberEntityDescription(
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: RensonConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the Renson number platform."""
api: RensonVentilation = hass.data[DOMAIN][config_entry.entry_id].api
coordinator: RensonCoordinator = hass.data[DOMAIN][
config_entry.entry_id
].coordinator
api = config_entry.runtime_data.api
coordinator = config_entry.runtime_data.coordinator
async_add_entities([RensonNumber(RENSON_NUMBER_DESCRIPTION, api, coordinator)])

View File

@@ -34,7 +34,6 @@ from homeassistant.components.sensor import (
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
CONCENTRATION_PARTS_PER_MILLION,
PERCENTAGE,
@@ -45,9 +44,7 @@ from homeassistant.const import (
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import RensonData
from .const import DOMAIN
from .coordinator import RensonCoordinator
from .coordinator import RensonConfigEntry, RensonCoordinator
from .entity import RensonEntity
@@ -271,12 +268,12 @@ class RensonSensor(RensonEntity, SensorEntity):
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: RensonConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the Renson sensor platform."""
data: RensonData = hass.data[DOMAIN][config_entry.entry_id]
data = config_entry.runtime_data
entities = [
RensonSensor(description, data.api, data.coordinator) for description in SENSORS

View File

@@ -9,12 +9,10 @@ from renson_endura_delta.field_enum import CURRENT_LEVEL_FIELD, DataType
from renson_endura_delta.renson import Level, RensonVentilation
from homeassistant.components.switch import SwitchDeviceClass, SwitchEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import RensonCoordinator
from .const import DOMAIN
from .coordinator import RensonConfigEntry, RensonCoordinator
from .entity import RensonEntity
_LOGGER = logging.getLogger(__name__)
@@ -67,14 +65,12 @@ class RensonBreezeSwitch(RensonEntity, SwitchEntity):
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: RensonConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Call the Renson integration to setup."""
api: RensonVentilation = hass.data[DOMAIN][config_entry.entry_id].api
coordinator: RensonCoordinator = hass.data[DOMAIN][
config_entry.entry_id
].coordinator
api = config_entry.runtime_data.api
coordinator = config_entry.runtime_data.coordinator
async_add_entities([RensonBreezeSwitch(api, coordinator)])

View File

@@ -10,14 +10,11 @@ from renson_endura_delta.field_enum import DAYTIME_FIELD, NIGHTTIME_FIELD, Field
from renson_endura_delta.renson import RensonVentilation
from homeassistant.components.time import TimeEntity, TimeEntityDescription
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import RensonData
from .const import DOMAIN
from .coordinator import RensonCoordinator
from .coordinator import RensonConfigEntry, RensonCoordinator
from .entity import RensonEntity
@@ -49,15 +46,14 @@ ENTITY_DESCRIPTIONS: tuple[RensonTimeEntityDescription, ...] = (
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: RensonConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the Renson time platform."""
data: RensonData = hass.data[DOMAIN][config_entry.entry_id]
coordinator = config_entry.runtime_data.coordinator
entities = [
RensonTime(description, data.coordinator) for description in ENTITY_DESCRIPTIONS
RensonTime(description, coordinator) for description in ENTITY_DESCRIPTIONS
]
async_add_entities(entities)

View File

@@ -9,17 +9,17 @@ from homeassistant.const import Platform
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import entity_registry as er
from .const import DOMAIN, LOGGER, SENSOR_TYPE_NEXT_PICKUP
from .coordinator import RidwellDataUpdateCoordinator
from .const import LOGGER, SENSOR_TYPE_NEXT_PICKUP
from .coordinator import RidwellConfigEntry, RidwellDataUpdateCoordinator
PLATFORMS: list[Platform] = [Platform.CALENDAR, Platform.SENSOR, Platform.SWITCH]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_setup_entry(hass: HomeAssistant, entry: RidwellConfigEntry) -> bool:
"""Set up Ridwell from a config entry."""
coordinator = RidwellDataUpdateCoordinator(hass, entry)
await coordinator.async_initialize()
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
entry.runtime_data = coordinator
entry.async_on_unload(entry.add_update_listener(options_update_listener))
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
@@ -27,17 +27,16 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
return True
async def options_update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
async def options_update_listener(
hass: HomeAssistant, entry: RidwellConfigEntry
) -> None:
"""Handle options update."""
await hass.config_entries.async_reload(entry.entry_id)
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: RidwellConfigEntry) -> bool:
"""Unload a config entry."""
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:

View File

@@ -7,7 +7,6 @@ import datetime
from aioridwell.model import PickupCategory, RidwellAccount, RidwellPickupEvent
from homeassistant.components.calendar import CalendarEntity, CalendarEvent
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
@@ -16,15 +15,14 @@ from .const import (
CALENDAR_TITLE_ROTATING,
CALENDAR_TITLE_STATUS,
CONF_CALENDAR_TITLE,
DOMAIN,
)
from .coordinator import RidwellDataUpdateCoordinator
from .coordinator import RidwellConfigEntry, RidwellDataUpdateCoordinator
from .entity import RidwellEntity
@callback
def async_get_calendar_event_from_pickup_event(
pickup_event: RidwellPickupEvent, config_entry: ConfigEntry
pickup_event: RidwellPickupEvent, config_entry: RidwellConfigEntry
) -> CalendarEvent:
"""Get a HASS CalendarEvent from an aioridwell PickupEvent."""
pickup_items = []
@@ -66,11 +64,11 @@ def async_get_calendar_event_from_pickup_event(
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
entry: RidwellConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up Ridwell calendars based on a config entry."""
coordinator: RidwellDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
coordinator = entry.runtime_data
async_add_entities(
RidwellCalendar(coordinator, account)

View File

@@ -9,7 +9,7 @@ from aioridwell import async_get_client
from aioridwell.errors import InvalidCredentialsError, RidwellError
import voluptuous as vol
from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import callback
from homeassistant.helpers import aiohttp_client, config_validation as cv, selector
@@ -19,6 +19,7 @@ from homeassistant.helpers.schema_config_entry_flow import (
)
from .const import CALENDAR_TITLE_OPTIONS, CONF_CALENDAR_TITLE, DOMAIN, LOGGER
from .coordinator import RidwellConfigEntry
STEP_REAUTH_CONFIRM_DATA_SCHEMA = vol.Schema(
{
@@ -107,7 +108,7 @@ class RidwellConfigFlow(ConfigFlow, domain=DOMAIN):
@staticmethod
@callback
def async_get_options_flow(
config_entry: ConfigEntry,
config_entry: RidwellConfigEntry,
) -> SchemaOptionsFlowHandler:
"""Get options flow for this handler."""
try:

View File

@@ -19,6 +19,8 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, Upda
from .const import LOGGER
type RidwellConfigEntry = ConfigEntry[RidwellDataUpdateCoordinator]
UPDATE_INTERVAL = timedelta(hours=1)
@@ -27,9 +29,9 @@ class RidwellDataUpdateCoordinator(
):
"""Class to manage fetching data from single endpoint."""
config_entry: ConfigEntry
config_entry: RidwellConfigEntry
def __init__(self, hass: HomeAssistant, config_entry: ConfigEntry) -> None:
def __init__(self, hass: HomeAssistant, config_entry: RidwellConfigEntry) -> None:
"""Initialize."""
# These will be filled in by async_initialize; we give them these defaults to
# avoid arduous typing checks down the line:

View File

@@ -6,12 +6,10 @@ import dataclasses
from typing import Any
from homeassistant.components.diagnostics import async_redact_data
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_PASSWORD, CONF_UNIQUE_ID, CONF_USERNAME
from homeassistant.core import HomeAssistant
from .const import DOMAIN
from .coordinator import RidwellDataUpdateCoordinator
from .coordinator import RidwellConfigEntry
CONF_TITLE = "title"
@@ -25,17 +23,15 @@ TO_REDACT = {
async def async_get_config_entry_diagnostics(
hass: HomeAssistant, entry: ConfigEntry
hass: HomeAssistant, entry: RidwellConfigEntry
) -> dict[str, Any]:
"""Return diagnostics for a config entry."""
coordinator: RidwellDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
return async_redact_data(
{
"entry": entry.as_dict(),
"data": [
dataclasses.asdict(event)
for events in coordinator.data.values()
for events in entry.runtime_data.data.values()
for event in events
],
},

View File

@@ -13,12 +13,11 @@ from homeassistant.components.sensor import (
SensorEntity,
SensorEntityDescription,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .const import DOMAIN, SENSOR_TYPE_NEXT_PICKUP
from .coordinator import RidwellDataUpdateCoordinator
from .const import SENSOR_TYPE_NEXT_PICKUP
from .coordinator import RidwellConfigEntry, RidwellDataUpdateCoordinator
from .entity import RidwellEntity
ATTR_CATEGORY = "category"
@@ -35,11 +34,11 @@ SENSOR_DESCRIPTION = SensorEntityDescription(
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
entry: RidwellConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up Ridwell sensors based on a config entry."""
coordinator: RidwellDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
coordinator = entry.runtime_data
async_add_entities(
RidwellSensor(coordinator, account, SENSOR_DESCRIPTION)

View File

@@ -8,13 +8,11 @@ from aioridwell.errors import RidwellError
from aioridwell.model import EventState, RidwellAccount
from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .const import DOMAIN
from .coordinator import RidwellDataUpdateCoordinator
from .coordinator import RidwellConfigEntry, RidwellDataUpdateCoordinator
from .entity import RidwellEntity
SWITCH_DESCRIPTION = SwitchEntityDescription(
@@ -25,11 +23,11 @@ SWITCH_DESCRIPTION = SwitchEntityDescription(
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
entry: RidwellConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up Ridwell sensors based on a config entry."""
coordinator: RidwellDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
coordinator = entry.runtime_data
async_add_entities(
RidwellSwitch(coordinator, account, SWITCH_DESCRIPTION)

View File

@@ -26,15 +26,13 @@ from homeassistant.helpers.typing import ConfigType
from .const import (
CONF_CONCURRENCY,
DATA_COORDINATOR,
DEFAULT_CONCURRENCY,
DOMAIN,
EVENTS_COORDINATOR,
SYSTEM_UPDATE_SIGNAL,
TYPE_LOCAL,
)
from .coordinator import RiscoDataUpdateCoordinator, RiscoEventsDataUpdateCoordinator
from .models import LocalData
from .models import CloudData, LocalData, RiscoConfigEntry, RiscoData
from .services import async_setup_services
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
@@ -58,7 +56,7 @@ def zone_update_signal(zone_id: int) -> str:
return f"risco_zone_update_{zone_id}"
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_setup_entry(hass: HomeAssistant, entry: RiscoConfigEntry) -> bool:
"""Set up Risco from a config entry."""
if is_local(entry):
return await _async_setup_local_entry(hass, entry)
@@ -66,7 +64,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
return await _async_setup_cloud_entry(hass, entry)
async def _async_setup_local_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def _async_setup_local_entry(
hass: HomeAssistant, entry: RiscoConfigEntry
) -> bool:
data = entry.data
concurrency = entry.options.get(CONF_CONCURRENCY, DEFAULT_CONCURRENCY)
risco = RiscoLocal(
@@ -120,14 +120,15 @@ async def _async_setup_local_entry(hass: HomeAssistant, entry: ConfigEntry) -> b
entry.async_on_unload(entry.add_update_listener(_update_listener))
hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][entry.entry_id] = local_data
entry.runtime_data = RiscoData(local_data=local_data)
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
async def _async_setup_cloud_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def _async_setup_cloud_entry(
hass: HomeAssistant, entry: RiscoConfigEntry
) -> bool:
data = entry.data
risco = RiscoCloud(data[CONF_USERNAME], data[CONF_PASSWORD], data[CONF_PIN])
try:
@@ -143,11 +144,12 @@ async def _async_setup_cloud_entry(hass: HomeAssistant, entry: ConfigEntry) -> b
entry.async_on_unload(entry.add_update_listener(_update_listener))
hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][entry.entry_id] = {
DATA_COORDINATOR: coordinator,
EVENTS_COORDINATOR: events_coordinator,
}
entry.runtime_data = RiscoData(
cloud_data=CloudData(
coordinator=coordinator,
events_coordinator=events_coordinator,
)
)
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
await events_coordinator.async_refresh()
@@ -155,20 +157,16 @@ async def _async_setup_cloud_entry(hass: HomeAssistant, entry: ConfigEntry) -> b
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: RiscoConfigEntry) -> bool:
"""Unload a config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok:
if is_local(entry):
local_data: LocalData = hass.data[DOMAIN][entry.entry_id]
await local_data.system.disconnect()
hass.data[DOMAIN].pop(entry.entry_id)
if unload_ok and (local_data := entry.runtime_data.local_data):
await local_data.system.disconnect()
return unload_ok
async def _update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
async def _update_listener(hass: HomeAssistant, entry: RiscoConfigEntry) -> None:
"""Handle options update."""
await hass.config_entries.async_reload(entry.entry_id)

View File

@@ -15,19 +15,16 @@ from homeassistant.components.alarm_control_panel import (
AlarmControlPanelState,
CodeFormat,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_PIN
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import LocalData, is_local
from .const import (
CONF_CODE_ARM_REQUIRED,
CONF_CODE_DISARM_REQUIRED,
CONF_HA_STATES_TO_RISCO,
CONF_RISCO_STATES_TO_HA,
DATA_COORDINATOR,
DEFAULT_OPTIONS,
DOMAIN,
RISCO_ARM,
@@ -36,6 +33,7 @@ from .const import (
)
from .coordinator import RiscoDataUpdateCoordinator
from .entity import RiscoCloudEntity
from .models import RiscoConfigEntry
_LOGGER = logging.getLogger(__name__)
@@ -49,13 +47,13 @@ STATES_TO_SUPPORTED_FEATURES = {
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: RiscoConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the Risco alarm control panel."""
options = {**DEFAULT_OPTIONS, **config_entry.options}
if is_local(config_entry):
local_data: LocalData = hass.data[DOMAIN][config_entry.entry_id]
risco_data = config_entry.runtime_data
if local_data := risco_data.local_data:
async_add_entities(
RiscoLocalAlarm(
local_data.system.id,
@@ -67,10 +65,8 @@ async def async_setup_entry(
)
for partition_id, partition in local_data.system.partitions.items()
)
else:
coordinator: RiscoDataUpdateCoordinator = hass.data[DOMAIN][
config_entry.entry_id
][DATA_COORDINATOR]
elif cloud_data := risco_data.cloud_data:
coordinator = cloud_data.coordinator
async_add_entities(
RiscoCloudAlarm(
coordinator, partition_id, config_entry.data[CONF_PIN], options

View File

@@ -15,16 +15,15 @@ from homeassistant.components.binary_sensor import (
BinarySensorEntity,
BinarySensorEntityDescription,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import LocalData, is_local
from .const import DATA_COORDINATOR, DOMAIN, SYSTEM_UPDATE_SIGNAL
from .const import DOMAIN, SYSTEM_UPDATE_SIGNAL
from .coordinator import RiscoDataUpdateCoordinator
from .entity import RiscoCloudZoneEntity, RiscoLocalZoneEntity
from .models import RiscoConfigEntry
SYSTEM_ENTITY_DESCRIPTIONS = [
BinarySensorEntityDescription(
@@ -72,12 +71,12 @@ SYSTEM_ENTITY_DESCRIPTIONS = [
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: RiscoConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the Risco alarm control panel."""
if is_local(config_entry):
local_data: LocalData = hass.data[DOMAIN][config_entry.entry_id]
risco_data = config_entry.runtime_data
if local_data := risco_data.local_data:
zone_entities = (
entity
for zone_id, zone in local_data.system.zones.items()
@@ -96,10 +95,8 @@ async def async_setup_entry(
)
async_add_entities(chain(system_entities, zone_entities))
else:
coordinator: RiscoDataUpdateCoordinator = hass.data[DOMAIN][
config_entry.entry_id
][DATA_COORDINATOR]
elif cloud_data := risco_data.cloud_data:
coordinator = cloud_data.coordinator
async_add_entities(
RiscoCloudBinarySensor(coordinator, zone_id, zone)
for zone_id, zone in coordinator.data.zones.items()

View File

@@ -10,12 +10,7 @@ from pyrisco import CannotConnectError, RiscoCloud, RiscoLocal, UnauthorizedErro
import voluptuous as vol
from homeassistant.components.alarm_control_panel import AlarmControlPanelState
from homeassistant.config_entries import (
ConfigEntry,
ConfigFlow,
ConfigFlowResult,
OptionsFlow,
)
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult, OptionsFlow
from homeassistant.const import (
CONF_HOST,
CONF_PASSWORD,
@@ -42,6 +37,7 @@ from .const import (
RISCO_STATES,
TYPE_LOCAL,
)
from .models import RiscoConfigEntry
_LOGGER = logging.getLogger(__name__)
@@ -121,12 +117,12 @@ class RiscoConfigFlow(ConfigFlow, domain=DOMAIN):
def __init__(self) -> None:
"""Init the config flow."""
self._reauth_entry: ConfigEntry | None = None
self._reauth_entry: RiscoConfigEntry | None = None
@staticmethod
@callback
def async_get_options_flow(
config_entry: ConfigEntry,
config_entry: RiscoConfigEntry,
) -> RiscoOptionsFlowHandler:
"""Define the config flow to handle options."""
return RiscoOptionsFlowHandler(config_entry)
@@ -218,7 +214,7 @@ class RiscoConfigFlow(ConfigFlow, domain=DOMAIN):
class RiscoOptionsFlowHandler(OptionsFlow):
"""Handle a Risco options flow."""
def __init__(self, config_entry: ConfigEntry) -> None:
def __init__(self, config_entry: RiscoConfigEntry) -> None:
"""Initialize."""
self._data = {**DEFAULT_OPTIONS, **config_entry.options}

View File

@@ -1,11 +1,39 @@
"""Models for Risco integration."""
from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass, field
from typing import Any
from typing import TYPE_CHECKING, Any
from pyrisco import RiscoLocal
from homeassistant.config_entries import ConfigEntry
if TYPE_CHECKING:
from .coordinator import (
RiscoDataUpdateCoordinator,
RiscoEventsDataUpdateCoordinator,
)
type RiscoConfigEntry = ConfigEntry[RiscoData]
@dataclass
class RiscoData:
"""Runtime data for the Risco integration."""
local_data: LocalData | None = None
cloud_data: CloudData | None = None
@dataclass
class CloudData:
"""A data class for cloud data passed to the platforms."""
coordinator: RiscoDataUpdateCoordinator
events_coordinator: RiscoEventsDataUpdateCoordinator
@dataclass
class LocalData:

View File

@@ -10,17 +10,16 @@ from pyrisco.cloud.event import Event
from homeassistant.components.binary_sensor import DOMAIN as BS_DOMAIN
from homeassistant.components.sensor import SensorDeviceClass, SensorEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from homeassistant.util import dt as dt_util
from . import is_local
from .const import DOMAIN, EVENTS_COORDINATOR
from .const import DOMAIN
from .coordinator import RiscoEventsDataUpdateCoordinator
from .entity import zone_unique_id
from .models import RiscoConfigEntry
CATEGORIES = {
2: "Alarm",
@@ -45,17 +44,15 @@ EVENT_ATTRIBUTES = [
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: RiscoConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up sensors for device."""
if is_local(config_entry):
if not (cloud_data := config_entry.runtime_data.cloud_data):
# no events in local comm
return
coordinator: RiscoEventsDataUpdateCoordinator = hass.data[DOMAIN][
config_entry.entry_id
][EVENTS_COORDINATOR]
coordinator = cloud_data.events_coordinator
sensors = [
RiscoSensor(coordinator, category_id, [], name, config_entry.entry_id)
for category_id, name in CATEGORIES.items()

View File

@@ -4,26 +4,26 @@ from datetime import datetime
import voluptuous as vol
from homeassistant.const import ATTR_CONFIG_ENTRY_ID, ATTR_TIME, CONF_TYPE
from homeassistant.const import ATTR_CONFIG_ENTRY_ID, ATTR_TIME
from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers import config_validation as cv, service
from .const import DOMAIN, SERVICE_SET_TIME, TYPE_LOCAL
from .models import LocalData
from .const import DOMAIN, SERVICE_SET_TIME
from .models import RiscoConfigEntry
async def async_setup_services(hass: HomeAssistant) -> None:
"""Create the Risco Services/Actions."""
async def _set_time(service_call: ServiceCall) -> None:
entry = service.async_get_config_entry(
entry: RiscoConfigEntry = service.async_get_config_entry(
service_call.hass, DOMAIN, service_call.data[ATTR_CONFIG_ENTRY_ID]
)
time = service_call.data.get(ATTR_TIME)
# Validate config entry is local (not cloud)
if entry.data.get(CONF_TYPE) != TYPE_LOCAL:
if not (local_data := entry.runtime_data.local_data):
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="not_local_entry",
@@ -33,8 +33,6 @@ async def async_setup_services(hass: HomeAssistant) -> None:
if time is None:
time_to_send = datetime.now()
local_data: LocalData = hass.data[DOMAIN][entry.entry_id]
await local_data.system.set_time(time_to_send)
hass.services.async_register(

View File

@@ -7,33 +7,29 @@ from typing import Any
from pyrisco.common import Zone
from homeassistant.components.switch import SwitchEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import LocalData, is_local
from .const import DATA_COORDINATOR, DOMAIN
from .coordinator import RiscoDataUpdateCoordinator
from .entity import RiscoCloudZoneEntity, RiscoLocalZoneEntity
from .models import RiscoConfigEntry
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: RiscoConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the Risco switch."""
if is_local(config_entry):
local_data: LocalData = hass.data[DOMAIN][config_entry.entry_id]
risco_data = config_entry.runtime_data
if local_data := risco_data.local_data:
async_add_entities(
RiscoLocalSwitch(local_data.system.id, zone_id, zone)
for zone_id, zone in local_data.system.zones.items()
)
else:
coordinator: RiscoDataUpdateCoordinator = hass.data[DOMAIN][
config_entry.entry_id
][DATA_COORDINATOR]
elif cloud_data := risco_data.cloud_data:
coordinator = cloud_data.coordinator
async_add_entities(
RiscoCloudSwitch(coordinator, zone_id, zone)
for zone_id, zone in coordinator.data.zones.items()

View File

@@ -2,15 +2,14 @@
import romy
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, CONF_PASSWORD
from homeassistant.core import HomeAssistant
from .const import DOMAIN, LOGGER, PLATFORMS
from .coordinator import RomyVacuumCoordinator
from .const import LOGGER, PLATFORMS
from .coordinator import RomyConfigEntry, RomyVacuumCoordinator
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
async def async_setup_entry(hass: HomeAssistant, config_entry: RomyConfigEntry) -> bool:
"""Initialize the ROMY platform via config entry."""
new_romy = await romy.create_romy(
@@ -20,7 +19,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
coordinator = RomyVacuumCoordinator(hass, config_entry, new_romy)
await coordinator.async_config_entry_first_refresh()
hass.data.setdefault(DOMAIN, {})[config_entry.entry_id] = coordinator
config_entry.runtime_data = coordinator
await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS)
@@ -29,14 +28,12 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: RomyConfigEntry) -> bool:
"""Handle removal of an entry."""
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
async def update_listener(hass: HomeAssistant, config_entry: ConfigEntry) -> None:
async def update_listener(hass: HomeAssistant, config_entry: RomyConfigEntry) -> None:
"""Handle options update."""
LOGGER.debug("update_listener")
await hass.config_entries.async_reload(config_entry.entry_id)

View File

@@ -5,12 +5,10 @@ from homeassistant.components.binary_sensor import (
BinarySensorEntity,
BinarySensorEntityDescription,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .const import DOMAIN
from .coordinator import RomyVacuumCoordinator
from .coordinator import RomyConfigEntry, RomyVacuumCoordinator
from .entity import RomyEntity
BINARY_SENSORS: list[BinarySensorEntityDescription] = [
@@ -38,12 +36,12 @@ BINARY_SENSORS: list[BinarySensorEntityDescription] = [
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: RomyConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up ROMY vacuum cleaner."""
coordinator: RomyVacuumCoordinator = hass.data[DOMAIN][config_entry.entry_id]
coordinator = config_entry.runtime_data
async_add_entities(
RomyBinarySensor(coordinator, entity_description)

View File

@@ -8,14 +8,16 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from .const import DOMAIN, LOGGER, UPDATE_INTERVAL
type RomyConfigEntry = ConfigEntry[RomyVacuumCoordinator]
class RomyVacuumCoordinator(DataUpdateCoordinator[None]):
"""ROMY Vacuum Coordinator."""
config_entry: ConfigEntry
config_entry: RomyConfigEntry
def __init__(
self, hass: HomeAssistant, config_entry: ConfigEntry, romy: RomyRobot
self, hass: HomeAssistant, config_entry: RomyConfigEntry, romy: RomyRobot
) -> None:
"""Initialize."""
super().__init__(

View File

@@ -6,7 +6,6 @@ from homeassistant.components.sensor import (
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
PERCENTAGE,
SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
@@ -18,8 +17,7 @@ from homeassistant.const import (
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .const import DOMAIN
from .coordinator import RomyVacuumCoordinator
from .coordinator import RomyConfigEntry, RomyVacuumCoordinator
from .entity import RomyEntity
SENSORS: list[SensorEntityDescription] = [
@@ -76,12 +74,12 @@ SENSORS: list[SensorEntityDescription] = [
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: RomyConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up ROMY vacuum cleaner."""
coordinator: RomyVacuumCoordinator = hass.data[DOMAIN][config_entry.entry_id]
coordinator = config_entry.runtime_data
async_add_entities(
RomySensor(coordinator, entity_description)

View File

@@ -11,12 +11,11 @@ from homeassistant.components.vacuum import (
VacuumActivity,
VacuumEntityFeature,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .const import DOMAIN, LOGGER
from .coordinator import RomyVacuumCoordinator
from .const import LOGGER
from .coordinator import RomyConfigEntry, RomyVacuumCoordinator
from .entity import RomyEntity
FAN_SPEED_NONE = "default"
@@ -50,13 +49,11 @@ SUPPORT_ROMY_ROBOT = (
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: RomyConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up ROMY vacuum cleaner."""
coordinator: RomyVacuumCoordinator = hass.data[DOMAIN][config_entry.entry_id]
async_add_entities([RomyVacuumEntity(coordinator)])
async_add_entities([RomyVacuumEntity(config_entry.runtime_data)])
class RomyVacuumEntity(RomyEntity, StateVacuumEntity):

View File

@@ -9,7 +9,6 @@ from typing import Any
from roombapy import Roomba, RoombaConnectionError, RoombaFactory
from homeassistant import exceptions
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
CONF_DELAY,
CONF_HOST,
@@ -19,13 +18,15 @@ from homeassistant.const import (
)
from homeassistant.core import HomeAssistant
from .const import CONF_BLID, CONF_CONTINUOUS, DOMAIN, PLATFORMS, ROOMBA_SESSION
from .models import RoombaData
from .const import CONF_BLID, CONF_CONTINUOUS, PLATFORMS, ROOMBA_SESSION
from .models import RoombaConfigEntry, RoombaData
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
async def async_setup_entry(
hass: HomeAssistant, config_entry: RoombaConfigEntry
) -> bool:
"""Set the config entry up."""
# Set up roomba platforms with config entry
@@ -62,8 +63,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _async_disconnect_roomba)
)
domain_data = RoombaData(roomba, config_entry.data[CONF_BLID])
hass.data.setdefault(DOMAIN, {})[config_entry.entry_id] = domain_data
config_entry.runtime_data = RoombaData(roomba, config_entry.data[CONF_BLID])
await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS)
@@ -108,20 +108,22 @@ async def async_disconnect_or_timeout(hass: HomeAssistant, roomba: Roomba) -> No
await hass.async_add_executor_job(roomba.disconnect)
async def async_update_options(hass: HomeAssistant, config_entry: ConfigEntry) -> None:
async def async_update_options(
hass: HomeAssistant, config_entry: RoombaConfigEntry
) -> None:
"""Update options."""
await hass.config_entries.async_reload(config_entry.entry_id)
async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
async def async_unload_entry(
hass: HomeAssistant, config_entry: RoombaConfigEntry
) -> bool:
"""Unload a config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(
config_entry, PLATFORMS
)
if unload_ok:
domain_data: RoombaData = hass.data[DOMAIN][config_entry.entry_id]
await async_disconnect_or_timeout(hass, roomba=domain_data.roomba)
hass.data[DOMAIN].pop(config_entry.entry_id)
await async_disconnect_or_timeout(hass, roomba=config_entry.runtime_data.roomba)
return unload_ok

View File

@@ -1,23 +1,21 @@
"""Roomba binary sensor entities."""
from homeassistant.components.binary_sensor import BinarySensorEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import roomba_reported_state
from .const import DOMAIN
from .entity import IRobotEntity
from .models import RoombaData
from .models import RoombaConfigEntry
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: RoombaConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the iRobot Roomba vacuum cleaner."""
domain_data: RoombaData = hass.data[DOMAIN][config_entry.entry_id]
domain_data = config_entry.runtime_data
roomba = domain_data.roomba
blid = domain_data.blid
status = roomba_reported_state(roomba).get("bin", {})

View File

@@ -11,12 +11,7 @@ from roombapy.discovery import RoombaDiscovery
from roombapy.getpassword import RoombaPassword
import voluptuous as vol
from homeassistant.config_entries import (
ConfigEntry,
ConfigFlow,
ConfigFlowResult,
OptionsFlow,
)
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult, OptionsFlow
from homeassistant.const import CONF_DELAY, CONF_HOST, CONF_NAME, CONF_PASSWORD
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.service_info.dhcp import DhcpServiceInfo
@@ -31,6 +26,7 @@ from .const import (
DOMAIN,
ROOMBA_SESSION,
)
from .models import RoombaConfigEntry
ROOMBA_DISCOVERY_LOCK = "roomba_discovery_lock"
ALL_ATTEMPTS = 2
@@ -90,7 +86,7 @@ class RoombaConfigFlow(ConfigFlow, domain=DOMAIN):
@staticmethod
@callback
def async_get_options_flow(
config_entry: ConfigEntry,
config_entry: RoombaConfigEntry,
) -> RoombaOptionsFlowHandler:
"""Get the options flow for this handler."""
return RoombaOptionsFlowHandler()

View File

@@ -6,6 +6,10 @@ from dataclasses import dataclass
from roombapy import Roomba
from homeassistant.config_entries import ConfigEntry
type RoombaConfigEntry = ConfigEntry[RoombaData]
@dataclass
class RoombaData:

View File

@@ -11,15 +11,13 @@ from homeassistant.components.sensor import (
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import PERCENTAGE, EntityCategory, UnitOfArea, UnitOfTime
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.typing import StateType
from .const import DOMAIN
from .entity import IRobotEntity, roomba_reported_state
from .models import RoombaData
from .models import RoombaConfigEntry
@dataclass(frozen=True, kw_only=True)
@@ -142,11 +140,11 @@ SENSORS: list[RoombaSensorEntityDescription] = [
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: RoombaConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the iRobot Roomba vacuum cleaner."""
domain_data: RoombaData = hass.data[DOMAIN][config_entry.entry_id]
domain_data = config_entry.runtime_data
roomba = domain_data.roomba
blid = domain_data.blid

View File

@@ -12,16 +12,14 @@ from homeassistant.components.vacuum import (
VacuumActivity,
VacuumEntityFeature,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.util import dt as dt_util
from homeassistant.util.unit_system import METRIC_SYSTEM
from . import roomba_reported_state
from .const import DOMAIN
from .entity import IRobotEntity
from .models import RoombaData
from .models import RoombaConfigEntry
SUPPORT_IROBOT = (
VacuumEntityFeature.PAUSE
@@ -87,11 +85,11 @@ SUPPORT_BRAAVA = SUPPORT_IROBOT | VacuumEntityFeature.FAN_SPEED
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: RoombaConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the iRobot Roomba vacuum cleaner."""
domain_data: RoombaData = hass.data[DOMAIN][config_entry.entry_id]
domain_data = config_entry.runtime_data
roomba = domain_data.roomba
blid = domain_data.blid

View File

@@ -10,6 +10,8 @@ from .const import CONF_ROON_NAME, DOMAIN
from .server import RoonServer
from .services import async_setup_services
type RoonConfigEntry = ConfigEntry[RoonServer]
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
PLATFORMS = [Platform.EVENT, Platform.MEDIA_PLAYER]
@@ -20,10 +22,8 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
return True
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_setup_entry(hass: HomeAssistant, entry: RoonConfigEntry) -> bool:
"""Set up a roonserver from a config entry."""
hass.data.setdefault(DOMAIN, {})
# fallback to using host for compatibility with older configs
name = entry.data.get(CONF_ROON_NAME, entry.data[CONF_HOST])
@@ -32,7 +32,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
if not await roonserver.async_setup():
return False
hass.data[DOMAIN][entry.entry_id] = roonserver
entry.runtime_data = roonserver
device_registry = dr.async_get(hass)
device_registry.async_get_or_create(
config_entry_id=entry.entry_id,
@@ -47,10 +47,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: RoonConfigEntry) -> bool:
"""Unload a config entry."""
if not await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
return False
roonserver = hass.data[DOMAIN].pop(entry.entry_id)
return await roonserver.async_reset()
return await entry.runtime_data.async_reset()

View File

@@ -4,12 +4,12 @@ import logging
from typing import cast
from homeassistant.components.event import EventDeviceClass, EventEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import RoonConfigEntry
from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
@@ -17,11 +17,11 @@ _LOGGER = logging.getLogger(__name__)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: RoonConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up Roon Event from Config Entry."""
roon_server = hass.data[DOMAIN][config_entry.entry_id]
roon_server = config_entry.runtime_data
event_entities = set()
@callback

View File

@@ -15,7 +15,6 @@ from homeassistant.components.media_player import (
MediaType,
RepeatMode,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import DEVICE_DEFAULT_NAME
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.device_registry import DeviceInfo
@@ -27,6 +26,7 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.util import convert
from homeassistant.util.dt import utcnow
from . import RoonConfigEntry
from .const import DOMAIN
from .media_browser import browse_media
@@ -45,11 +45,11 @@ REPEAT_MODE_MAPPING_TO_ROON = {
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: RoonConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up Roon MediaPlayer from Config Entry."""
roon_server = hass.data[DOMAIN][config_entry.entry_id]
roon_server = config_entry.runtime_data
media_players = set()
@callback

View File

@@ -5,19 +5,18 @@ from __future__ import annotations
from requests.exceptions import ConnectTimeout, HTTPError
from rova.rova import Rova
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryError, ConfigEntryNotReady
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
from .const import CONF_HOUSE_NUMBER, CONF_HOUSE_NUMBER_SUFFIX, CONF_ZIP_CODE, DOMAIN
from .coordinator import RovaCoordinator
from .coordinator import RovaConfigEntry, RovaCoordinator
PLATFORMS: list[Platform] = [Platform.SENSOR]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_setup_entry(hass: HomeAssistant, entry: RovaConfigEntry) -> bool:
"""Set up ROVA from a config entry."""
api = Rova(
@@ -50,15 +49,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
await coordinator.async_config_entry_first_refresh()
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
entry.runtime_data = coordinator
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: RovaConfigEntry) -> bool:
"""Unload ROVA config entry."""
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)

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