Compare commits

..

1 Commits

Author SHA1 Message Date
epenet f2361ef5aa Drop ignore-missing-annotations from pylint 2026-04-07 11:25:00 +00:00
276 changed files with 2411 additions and 6756 deletions
+5 -1
View File
@@ -47,6 +47,10 @@ jobs:
with:
python-version-file: ".python-version"
- name: Get information
id: info
uses: home-assistant/actions/helpers/info@5752577ea7cc5aefb064b0b21432f18fe4d6ba90 # zizmor: ignore[unpinned-uses]
- name: Get version
id: version
uses: home-assistant/actions/helpers/version@master # zizmor: ignore[unpinned-uses]
@@ -499,7 +503,7 @@ jobs:
python -m build
- name: Upload package to PyPI
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0
with:
skip-existing: true
+2 -2
View File
@@ -709,7 +709,7 @@ jobs:
run: |
. venv/bin/activate
python --version
pylint --ignore-missing-annotations=y homeassistant
pylint homeassistant
- name: Run pylint (partially)
if: needs.info.outputs.test_full_suite == 'false'
shell: bash
@@ -718,7 +718,7 @@ jobs:
run: |
. venv/bin/activate
python --version
pylint --ignore-missing-annotations=y $(printf "homeassistant/components/%s " ${INTEGRATIONS_GLOB})
pylint $(printf "homeassistant/components/%s " ${INTEGRATIONS_GLOB})
pylint-tests:
name: Check pylint on tests
@@ -1,7 +1,11 @@
"""The Actron Air integration."""
from actron_neo_api import ActronAirAPI, ActronAirAPIError, ActronAirAuthError
from actron_neo_api.models.system import ActronAirSystemInfo
from actron_neo_api import (
ActronAirACSystem,
ActronAirAPI,
ActronAirAPIError,
ActronAirAuthError,
)
from homeassistant.const import CONF_API_TOKEN, Platform
from homeassistant.core import HomeAssistant
@@ -21,7 +25,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[ActronAirSystemInfo] = []
systems: list[ActronAirACSystem] = []
try:
systems = await api.get_ac_systems()
@@ -40,9 +44,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,
@@ -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, actron_air_command
from .entity import ActronAirAcEntity, ActronAirZoneEntity, handle_actron_api_errors
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
@actron_air_command
@handle_actron_api_errors
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)
@actron_air_command
@handle_actron_api_errors
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)
@actron_air_command
@handle_actron_api_errors
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
@actron_air_command
@handle_actron_api_errors
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)
@actron_air_command
@handle_actron_api_errors
async def async_set_temperature(self, **kwargs: Any) -> None:
"""Set the temperature."""
await self._zone.set_temperature(temperature=kwargs.get(ATTR_TEMPERATURE))
@@ -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."""
@@ -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[ActronAirStatus]):
class ActronAirSystemCoordinator(DataUpdateCoordinator[ActronAirACSystem]):
"""System coordinator for Actron Air integration."""
def __init__(
@@ -46,7 +46,7 @@ class ActronAirSystemCoordinator(DataUpdateCoordinator[ActronAirStatus]):
hass: HomeAssistant,
entry: ActronAirConfigEntry,
api: ActronAirAPI,
system: ActronAirSystemInfo,
system: ActronAirACSystem,
) -> None:
"""Initialize the coordinator."""
super().__init__(
@@ -57,7 +57,7 @@ class ActronAirSystemCoordinator(DataUpdateCoordinator[ActronAirStatus]):
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()
@@ -1,35 +0,0 @@
"""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,
}
@@ -14,14 +14,10 @@ from .const import DOMAIN
from .coordinator import ActronAirSystemCoordinator
def actron_air_command[_EntityT: ActronAirEntity, **_P](
def handle_actron_api_errors[_EntityT: ActronAirEntity, **_P](
func: Callable[Concatenate[_EntityT, _P], Coroutine[Any, Any, Any]],
) -> Callable[Concatenate[_EntityT, _P], Coroutine[Any, Any, None]]:
"""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.
"""
"""Decorate Actron Air API calls to handle ActronAirAPIError exceptions."""
@wraps(func)
async def wrapper(self: _EntityT, *args: _P.args, **kwargs: _P.kwargs) -> None:
@@ -34,7 +30,6 @@ def actron_air_command[_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
@@ -13,5 +13,5 @@
"integration_type": "hub",
"iot_class": "cloud_polling",
"quality_scale": "silver",
"requirements": ["actron-neo-api==0.5.0"]
"requirements": ["actron-neo-api==0.4.1"]
}
@@ -41,7 +41,7 @@ rules:
# Gold
devices: done
diagnostics: done
diagnostics: todo
discovery-update-info:
status: exempt
comment: This integration uses DHCP discovery, however is cloud polling. Therefore there is no information to update.
@@ -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, actron_air_command
from .entity import ActronAirAcEntity, handle_actron_api_errors
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)
@actron_air_command
@handle_actron_api_errors
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the switch on."""
await self.entity_description.set_fn(self.coordinator, True)
@actron_air_command
@handle_actron_api_errors
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the switch off."""
await self.entity_description.set_fn(self.coordinator, False)
@@ -5,6 +5,7 @@ from __future__ import annotations
from collections.abc import Mapping
import json
import logging
import re
from typing import TYPE_CHECKING, Any, cast
import anthropic
@@ -70,7 +71,6 @@ from .const import (
WEB_SEARCH_UNSUPPORTED_MODELS,
PromptCaching,
)
from .coordinator import model_alias
if TYPE_CHECKING:
from . import AnthropicConfigEntry
@@ -112,13 +112,25 @@ async def get_model_list(client: anthropic.AsyncAnthropic) -> list[SelectOptionD
except anthropic.AnthropicError:
models = []
_LOGGER.debug("Available models: %s", models)
return [
SelectOptionDict(
label=model_info.display_name,
value=model_alias(model_info.id),
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
)
for model_info in models
]
if short_form.search(model_alias):
model_alias += "-0"
model_options.append(
SelectOptionDict(
label=model_info.display_name,
value=model_alias,
)
)
return model_options
class AnthropicConfigFlow(ConfigFlow, domain=DOMAIN):
+3 -3
View File
@@ -36,15 +36,13 @@ class PromptCaching(StrEnum):
AUTOMATIC = "automatic"
MIN_THINKING_BUDGET = 1024
DEFAULT = {
CONF_CHAT_MODEL: "claude-haiku-4-5",
CONF_CODE_EXECUTION: False,
CONF_MAX_TOKENS: 3000,
CONF_PROMPT_CACHING: PromptCaching.PROMPT.value,
CONF_TEMPERATURE: 1.0,
CONF_THINKING_BUDGET: MIN_THINKING_BUDGET,
CONF_THINKING_BUDGET: 0,
CONF_THINKING_EFFORT: "low",
CONF_TOOL_SEARCH: False,
CONF_WEB_SEARCH: False,
@@ -52,6 +50,8 @@ DEFAULT = {
CONF_WEB_SEARCH_MAX_USES: 5,
}
MIN_THINKING_BUDGET = 1024
NON_THINKING_MODELS = [
"claude-3-haiku",
]
@@ -2,8 +2,7 @@
from __future__ import annotations
import datetime
import re
from datetime import timedelta
import anthropic
@@ -16,28 +15,13 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, Upda
from .const import DOMAIN, LOGGER
UPDATE_INTERVAL_CONNECTED = datetime.timedelta(hours=12)
UPDATE_INTERVAL_DISCONNECTED = datetime.timedelta(minutes=1)
UPDATE_INTERVAL_CONNECTED = timedelta(hours=12)
UPDATE_INTERVAL_DISCONNECTED = timedelta(minutes=1)
type AnthropicConfigEntry = ConfigEntry[AnthropicCoordinator]
_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]]):
class AnthropicCoordinator(DataUpdateCoordinator[None]):
"""DataUpdateCoordinator which uses different intervals after successful and unsuccessful updates."""
client: anthropic.AsyncAnthropic
@@ -58,16 +42,16 @@ class AnthropicCoordinator(DataUpdateCoordinator[list[anthropic.types.ModelInfo]
)
@callback
def async_set_updated_data(self, data: list[anthropic.types.ModelInfo]) -> None:
def async_set_updated_data(self, data: None) -> 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) -> list[anthropic.types.ModelInfo]:
async def async_update_data(self) -> None:
"""Fetch data from the API."""
try:
self.update_interval = UPDATE_INTERVAL_DISCONNECTED
result = await self.client.models.list(timeout=10.0)
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
@@ -83,7 +67,6 @@ class AnthropicCoordinator(DataUpdateCoordinator[list[anthropic.types.ModelInfo]
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."""
@@ -93,23 +76,3 @@ class AnthropicCoordinator(DataUpdateCoordinator[list[anthropic.types.ModelInfo]
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,
)
+3 -8
View File
@@ -689,17 +689,12 @@ 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=self.model_info.display_name,
model_id=self.model_info.id,
model=subentry.data.get(CONF_CHAT_MODEL, DEFAULT[CONF_CHAT_MODEL]),
entry_type=dr.DeviceEntryType.SERVICE,
)
@@ -974,7 +969,7 @@ class AnthropicBaseLLMEntity(CoordinatorEntity[AnthropicCoordinator]):
) from err
except anthropic.AnthropicError as err:
# Non-connection error, mark connection as healthy
coordinator.async_set_updated_data(coordinator.data)
coordinator.async_set_updated_data(None)
LOGGER.error("Error while talking to Anthropic: %s", err)
raise HomeAssistantError(
translation_domain=DOMAIN,
@@ -987,7 +982,7 @@ class AnthropicBaseLLMEntity(CoordinatorEntity[AnthropicCoordinator]):
) from err
if not chat_log.unresponded_tool_results:
coordinator.async_set_updated_data(coordinator.data)
coordinator.async_set_updated_data(None)
break
@@ -8,6 +8,6 @@
"integration_type": "service",
"iot_class": "calculated",
"quality_scale": "internal",
"requirements": ["cronsim==2.7", "securetar==2026.4.1"],
"requirements": ["cronsim==2.7", "securetar==2026.2.0"],
"single_config_entry": true
}
+5 -11
View File
@@ -22,7 +22,6 @@ from securetar import (
SecureTarFile,
SecureTarReadError,
SecureTarRootKeyContext,
get_archive_max_ciphertext_size,
)
from homeassistant.core import HomeAssistant
@@ -384,12 +383,9 @@ def _encrypt_backup(
if prefix not in expected_archives:
LOGGER.debug("Unknown inner tar file %s will not be encrypted", obj.name)
continue
if (fileobj := input_tar.extractfile(obj)) is None:
LOGGER.debug(
"Non regular inner tar file %s will not be encrypted", obj.name
)
continue
output_archive.import_tar(fileobj, obj, derived_key_id=inner_tar_idx)
output_archive.import_tar(
input_tar.extractfile(obj), obj, derived_key_id=inner_tar_idx
)
inner_tar_idx += 1
@@ -423,7 +419,7 @@ class _CipherBackupStreamer:
hass: HomeAssistant,
backup: AgentBackup,
open_stream: Callable[[], Coroutine[Any, Any, AsyncIterator[bytes]]],
password: str,
password: str | None,
) -> None:
"""Initialize."""
self._workers: list[_CipherWorkerStatus] = []
@@ -435,9 +431,7 @@ class _CipherBackupStreamer:
def size(self) -> int:
"""Return the maximum size of the decrypted or encrypted backup."""
return get_archive_max_ciphertext_size(
self._backup.size, SECURETAR_CREATE_VERSION, self._num_tar_files()
)
return self._backup.size + self._num_tar_files() * tarfile.RECORDSIZE
def _num_tar_files(self) -> int:
"""Return the number of inner tar files."""
@@ -9,7 +9,7 @@
"iot_class": "local_polling",
"loggers": ["brother", "pyasn1", "pysmi", "pysnmp"],
"quality_scale": "platinum",
"requirements": ["brother==6.1.0"],
"requirements": ["brother==6.0.0"],
"zeroconf": [
{
"name": "brother*",
@@ -112,7 +112,7 @@ class ComelitAlarmEntity(
@property
def available(self) -> bool:
"""Return True if alarm is available."""
if self._area.human_status == AlarmAreaState.UNKNOWN:
if self._area.human_status in [AlarmAreaState.ANOMALY, 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.anomaly
self._area.index, ALARM_ACTIONS[DISABLE]
)
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.anomaly
self._area.index, ALARM_ACTIONS[AWAY]
)
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.anomaly
self._area.index, ALARM_ACTIONS[HOME]
)
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.anomaly
self._area.index, ALARM_ACTIONS[NIGHT]
)
await self._async_update_state(
AlarmAreaState.ARMED, ALARM_AREA_ARMED_STATUS[NIGHT]
+1 -17
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, DoorbellEventType
from .const import ATTR_EVENT_TYPE, ATTR_EVENT_TYPES, DOMAIN
_LOGGER = logging.getLogger(__name__)
DATA_COMPONENT: HassKey[EntityComponent[EventEntity]] = HassKey(DOMAIN)
@@ -44,7 +44,6 @@ __all__ = [
"DOMAIN",
"PLATFORM_SCHEMA",
"PLATFORM_SCHEMA_BASE",
"DoorbellEventType",
"EventDeviceClass",
"EventEntity",
"EventEntityDescription",
@@ -190,21 +189,6 @@ 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
-8
View File
@@ -1,13 +1,5 @@
"""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"
+1 -8
View File
@@ -15,14 +15,7 @@
"name": "Button"
},
"doorbell": {
"name": "Doorbell",
"state_attributes": {
"event_type": {
"state": {
"ring": "Ring"
}
}
}
"name": "Doorbell"
},
"motion": {
"name": "Motion"
+5 -1
View File
@@ -2,14 +2,18 @@
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 FlussConfigEntry, FlussDataUpdateCoordinator
from .coordinator import FlussDataUpdateCoordinator
PLATFORMS: list[Platform] = [Platform.BUTTON]
type FlussConfigEntry = ConfigEntry[FlussDataUpdateCoordinator]
async def async_setup_entry(
hass: HomeAssistant,
entry: FlussConfigEntry,
+4 -1
View File
@@ -1,13 +1,16 @@
"""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, FlussConfigEntry
from .coordinator import FlussApiClientError, FlussDataUpdateCoordinator
from .entity import FlussEntity
type FlussConfigEntry = ConfigEntry[FlussDataUpdateCoordinator]
async def async_setup_entry(
hass: HomeAssistant,
@@ -453,13 +453,10 @@ 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 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
wan_access_result = "granted" in wan_access
else:
wan_access_result = None
hosts[attributes["MACAddress"]] = Device(
name=attributes["HostName"],
@@ -38,7 +38,6 @@ PLATFORMS: list[Platform] = [
Platform.SELECT,
Platform.SENSOR,
Platform.SWITCH,
Platform.TEXT,
Platform.VALVE,
]
LOGGER = logging.getLogger(__name__)
@@ -1,12 +0,0 @@
{
"entity": {
"text": {
"contour_name": {
"default": "mdi:vector-polygon"
},
"position_name": {
"default": "mdi:map-marker-radius"
}
}
}
}
@@ -154,14 +154,6 @@
"state": {
"name": "[%key:common::state::open%]"
}
},
"text": {
"contour_name": {
"name": "Contour {number}"
},
"position_name": {
"name": "Position {number}"
}
}
}
}
@@ -1,88 +0,0 @@
"""Support for text entities."""
from __future__ import annotations
from dataclasses import dataclass
from gardena_bluetooth.const import AquaContourContours, AquaContourPosition
from gardena_bluetooth.parse import CharacteristicNullString
from homeassistant.components.text import TextEntity, TextEntityDescription
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .coordinator import GardenaBluetoothConfigEntry
from .entity import GardenaBluetoothDescriptorEntity
@dataclass(frozen=True, kw_only=True)
class GardenaBluetoothTextEntityDescription(TextEntityDescription):
"""Description of entity."""
char: CharacteristicNullString
@property
def context(self) -> set[str]:
"""Context needed for update coordinator."""
return {self.char.uuid}
DESCRIPTIONS = (
*(
GardenaBluetoothTextEntityDescription(
key=f"position_{i}_name",
translation_key="position_name",
translation_placeholders={"number": str(i)},
has_entity_name=True,
char=getattr(AquaContourPosition, f"position_name_{i}"),
native_max=20,
entity_category=EntityCategory.CONFIG,
)
for i in range(1, 6)
),
*(
GardenaBluetoothTextEntityDescription(
key=f"contour_{i}_name",
translation_key="contour_name",
translation_placeholders={"number": str(i)},
has_entity_name=True,
char=getattr(AquaContourContours, f"contour_name_{i}"),
native_max=20,
entity_category=EntityCategory.CONFIG,
)
for i in range(1, 6)
),
)
async def async_setup_entry(
hass: HomeAssistant,
entry: GardenaBluetoothConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up text based on a config entry."""
coordinator = entry.runtime_data
entities = [
GardenaBluetoothTextEntity(coordinator, description, description.context)
for description in DESCRIPTIONS
if description.char.unique_id in coordinator.characteristics
]
async_add_entities(entities)
class GardenaBluetoothTextEntity(GardenaBluetoothDescriptorEntity, TextEntity):
"""Representation of a text entity."""
entity_description: GardenaBluetoothTextEntityDescription
@property
def native_value(self) -> str | None:
"""Return the value reported by the text."""
char = self.entity_description.char
return self.coordinator.get_cached(char)
async def async_set_value(self, value: str) -> None:
"""Change the text."""
char = self.entity_description.char
await self.coordinator.write(char, value)
@@ -11,8 +11,4 @@ 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
@@ -2,7 +2,6 @@
from __future__ import annotations
from datetime import datetime
import logging
from typing import Any
@@ -23,7 +22,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 DEVICE_TIMEOUT, DOMAIN, MANUFACTURER
from .const import DOMAIN, MANUFACTURER
from .coordinator import GoveeLocalApiCoordinator, GoveeLocalConfigEntry
_LOGGER = logging.getLogger(__name__)
@@ -119,19 +118,6 @@ 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)."""
@@ -219,8 +205,8 @@ class GoveeLight(CoordinatorEntity[GoveeLocalApiCoordinator], LightEntity):
@callback
def _update_callback(self, device: GoveeDevice) -> None:
"""Handle device state updates pushed by the library."""
self.async_write_ha_state()
if self.hass:
self.async_write_ha_state()
def _save_last_color_state(self) -> None:
color_mode = self.color_mode
@@ -98,9 +98,7 @@ class HabiticaCalendarEntity(HabiticaBase, CalendarEntity):
start_date, end_date - timedelta(days=1), inc=True
)
# if no end_date is given, return only the next recurrence
if (next_date := recurrences.after(start_date, inc=True)) is None:
return []
return [next_date]
return [recurrences.after(start_date, inc=True)]
class HabiticaTodosCalendarEntity(HabiticaCalendarEntity):
@@ -5,5 +5,5 @@
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/holiday",
"iot_class": "local_polling",
"requirements": ["holidays==0.94", "babel==2.15.0"]
"requirements": ["holidays==0.93", "babel==2.15.0"]
}
@@ -13,7 +13,7 @@ from homeassistant.components.homeassistant_hardware.util import guess_firmware_
from homeassistant.components.usb import (
USBDevice,
async_register_port_event_callback,
async_scan_serial_ports,
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 async_scan_serial_ports(hass)
serial_ports = await hass.async_add_executor_job(scan_serial_ports)
serial_ports_info = {port.device: port for port in serial_ports}
device = config_entry.data[DEVICE]
@@ -1,7 +1,5 @@
"""Provide info to system health."""
from typing import Any
from homeassistant.components import system_health
from homeassistant.core import HomeAssistant, callback
@@ -16,7 +14,7 @@ def async_register(
register.async_register_info(system_health_info)
async def system_health_info(hass: HomeAssistant) -> dict[str, Any]:
async def system_health_info(hass):
"""Get info for the info page."""
return {
"api_endpoint_reachable": system_health.async_check_can_reach_url(
@@ -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(info_api)
devices_api = Devices(auth_api)
coordinator_info = LunatoneInfoDataUpdateCoordinator(hass, entry, info_api)
await coordinator_info.async_config_entry_first_refresh()
@@ -2,6 +2,7 @@
from __future__ import annotations
import asyncio
from typing import Any
from lunatone_rest_api_client import DALIBroadcast
@@ -27,6 +28,7 @@ from .coordinator import (
)
PARALLEL_UPDATES = 0
STATUS_UPDATE_DELAY = 0.04
async def async_setup_entry(
@@ -147,6 +149,8 @@ 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:
@@ -157,6 +161,8 @@ 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()
@@ -215,9 +221,13 @@ 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()
@@ -7,5 +7,5 @@
"integration_type": "hub",
"iot_class": "local_polling",
"quality_scale": "silver",
"requirements": ["lunatone-rest-api-client==0.9.0"]
"requirements": ["lunatone-rest-api-client==0.7.0"]
}
@@ -10,7 +10,7 @@
"integration_type": "hub",
"iot_class": "local_push",
"loggers": ["pylutron_caseta"],
"requirements": ["pylutron-caseta==0.28.0"],
"requirements": ["pylutron-caseta==0.27.0"],
"zeroconf": [
{
"properties": {
+1 -54
View File
@@ -10,12 +10,10 @@ See https://modelcontextprotocol.io/docs/concepts/architecture#implementation-ex
from collections.abc import Callable, Sequence
import json
import logging
from typing import Any, cast
from typing import Any
from mcp import types
from mcp.server import Server
from mcp.server.lowlevel.helper_types import ReadResourceContents
from pydantic import AnyUrl
import voluptuous as vol
from voluptuous_openapi import convert
@@ -27,16 +25,6 @@ from .const import STATELESS_LLM_API
_LOGGER = logging.getLogger(__name__)
SNAPSHOT_RESOURCE_URI = "homeassistant://assist/context-snapshot"
SNAPSHOT_RESOURCE_URL = AnyUrl(SNAPSHOT_RESOURCE_URI)
SNAPSHOT_RESOURCE_MIME_TYPE = "text/plain"
LIVE_CONTEXT_TOOL_NAME = "GetLiveContext"
def _has_live_context_tool(llm_api: llm.APIInstance) -> bool:
"""Return if the selected API exposes the live context tool."""
return any(tool.name == LIVE_CONTEXT_TOOL_NAME for tool in llm_api.tools)
def _format_tool(
tool: llm.Tool, custom_serializer: Callable[[Any], Any] | None
@@ -102,47 +90,6 @@ async def create_server(
],
)
@server.list_resources() # type: ignore[no-untyped-call,untyped-decorator]
async def handle_list_resources() -> list[types.Resource]:
llm_api = await get_api_instance()
if not _has_live_context_tool(llm_api):
return []
return [
types.Resource(
uri=SNAPSHOT_RESOURCE_URL,
name="assist_context_snapshot",
title="Assist context snapshot",
description=(
"A snapshot of the current Assist context, matching the"
" existing GetLiveContext tool output."
),
mimeType=SNAPSHOT_RESOURCE_MIME_TYPE,
)
]
@server.read_resource() # type: ignore[no-untyped-call,untyped-decorator]
async def handle_read_resource(uri: AnyUrl) -> Sequence[ReadResourceContents]:
if str(uri) != SNAPSHOT_RESOURCE_URI:
raise ValueError(f"Unknown resource: {uri}")
llm_api = await get_api_instance()
if not _has_live_context_tool(llm_api):
raise ValueError(f"Unknown resource: {uri}")
tool_response = await llm_api.async_call_tool(
llm.ToolInput(tool_name=LIVE_CONTEXT_TOOL_NAME, tool_args={})
)
if not tool_response.get("success"):
raise HomeAssistantError(cast(str, tool_response["error"]))
return [
ReadResourceContents(
content=cast(str, tool_response["result"]),
mime_type=SNAPSHOT_RESOURCE_MIME_TYPE,
)
]
@server.list_tools() # type: ignore[no-untyped-call,untyped-decorator]
async def list_tools() -> list[types.Tool]:
"""List available time tools."""
@@ -49,11 +49,7 @@ if TYPE_CHECKING:
from homeassistant.helpers.typing import ConfigType
PLATFORMS = [
Platform.BUTTON,
Platform.MEDIA_PLAYER,
Platform.NUMBER,
]
PLATFORMS = [Platform.BUTTON, Platform.MEDIA_PLAYER]
CONNECT_TIMEOUT = 10
LISTEN_READY_TIMEOUT = 30
@@ -80,5 +80,3 @@ ATTR_FANART_IMAGE = "fanart_image"
ATTR_CONF_EXPOSE_PLAYER_TO_HA = "expose_player_to_ha"
LOGGER = logging.getLogger(__package__)
PLAYER_OPTIONS_TRANSLATION_KEY_PREFIX = "player_options."
@@ -6,9 +6,8 @@ from typing import TYPE_CHECKING
from music_assistant_models.enums import EventType
from music_assistant_models.event import MassEvent
from music_assistant_models.player import Player, PlayerOption
from music_assistant_models.player import Player
from homeassistant.const import EntityCategory
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity import Entity
@@ -85,45 +84,3 @@ class MusicAssistantEntity(Entity):
async def async_on_update(self) -> None:
"""Handle player updates."""
class MusicAssistantPlayerOptionEntity(MusicAssistantEntity):
"""Base entity for Music Assistant Player Options."""
_attr_entity_category = EntityCategory.CONFIG
def __init__(
self, mass: MusicAssistantClient, player_id: str, player_option: PlayerOption
) -> None:
"""Initialize MusicAssistantPlayerOptionEntity."""
super().__init__(mass, player_id)
self.mass_option_key = player_option.key
self.mass_type = player_option.type
self.on_player_option_update(player_option)
async def async_added_to_hass(self) -> None:
"""Register callbacks."""
# need callbacks of parent to catch availability
await super().async_added_to_hass()
# main callback for player options
self.async_on_remove(
self.mass.subscribe(
self.__on_mass_player_options_update,
EventType.PLAYER_OPTIONS_UPDATED,
self.player_id,
)
)
def __on_mass_player_options_update(self, event: MassEvent) -> None:
"""Call when we receive an event from MusicAssistant."""
for option in self.player.options:
if option.key == self.mass_option_key:
self.on_player_option_update(option)
self.async_write_ha_state()
break
def on_player_option_update(self, player_option: PlayerOption) -> None:
"""Callback for player option updates."""
@@ -1,127 +0,0 @@
"""Music Assistant Number platform."""
from __future__ import annotations
from typing import Final
from music_assistant_client.client import MusicAssistantClient
from music_assistant_models.player import PlayerOption, PlayerOptionType
from homeassistant.components.number import NumberEntity, NumberEntityDescription
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import MusicAssistantConfigEntry
from .const import PLAYER_OPTIONS_TRANSLATION_KEY_PREFIX
from .entity import MusicAssistantPlayerOptionEntity
from .helpers import catch_musicassistant_error
PLAYER_OPTIONS_TRANSLATION_KEYS_NUMBER: Final[list[str]] = [
"bass",
"dialogue_level",
"dialogue_lift",
"dts_dialogue_control",
"equalizer_high",
"equalizer_low",
"equalizer_mid",
"subwoofer_volume",
"treble",
]
async def async_setup_entry(
hass: HomeAssistant,
entry: MusicAssistantConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up Music Assistant Number Entities (Player Options) from Config Entry."""
mass = entry.runtime_data.mass
def add_player(player_id: str) -> None:
"""Handle add player."""
player = mass.players.get(player_id)
if player is None:
return
entities: list[MusicAssistantPlayerConfigNumber] = []
for player_option in player.options:
if (
not player_option.read_only
and player_option.type
in (
PlayerOptionType.INTEGER,
PlayerOptionType.FLOAT,
)
and not player_option.options # these we map to select
):
# the MA translation key must have the format player_options.<translation key>
# we ignore entities with unknown translation keys.
if (
player_option.translation_key is None
or not player_option.translation_key.startswith(
PLAYER_OPTIONS_TRANSLATION_KEY_PREFIX
)
):
continue
translation_key = player_option.translation_key[
len(PLAYER_OPTIONS_TRANSLATION_KEY_PREFIX) :
]
if translation_key not in PLAYER_OPTIONS_TRANSLATION_KEYS_NUMBER:
continue
entities.append(
MusicAssistantPlayerConfigNumber(
mass,
player_id,
player_option=player_option,
entity_description=NumberEntityDescription(
key=player_option.key,
translation_key=translation_key,
),
)
)
async_add_entities(entities)
# register callback to add players when they are discovered
entry.runtime_data.platform_handlers.setdefault(Platform.NUMBER, add_player)
class MusicAssistantPlayerConfigNumber(MusicAssistantPlayerOptionEntity, NumberEntity):
"""Representation of a Number entity to control player provider dependent settings."""
def __init__(
self,
mass: MusicAssistantClient,
player_id: str,
player_option: PlayerOption,
entity_description: NumberEntityDescription,
) -> None:
"""Initialize MusicAssistantPlayerConfigNumber."""
super().__init__(mass, player_id, player_option)
self.entity_description = entity_description
@catch_musicassistant_error
async def async_set_native_value(self, value: float) -> None:
"""Set a new value."""
_value = round(value) if self.mass_type == PlayerOptionType.INTEGER else value
await self.mass.players.set_option(
self.player_id,
self.mass_option_key,
_value,
)
def on_player_option_update(self, player_option: PlayerOption) -> None:
"""Update on player option update."""
if player_option.min_value is not None:
self._attr_native_min_value = player_option.min_value
if player_option.max_value is not None:
self._attr_native_max_value = player_option.max_value
if player_option.step is not None:
self._attr_native_step = player_option.step
self._attr_native_value = (
player_option.value
if isinstance(player_option.value, (int, float))
else None
)
@@ -53,35 +53,6 @@
"favorite_now_playing": {
"name": "Favorite current song"
}
},
"number": {
"bass": {
"name": "Bass"
},
"dialogue_level": {
"name": "Dialogue level"
},
"dialogue_lift": {
"name": "Dialogue lift"
},
"dts_dialogue_control": {
"name": "DTS dialogue control"
},
"equalizer_high": {
"name": "Equalizer high"
},
"equalizer_low": {
"name": "Equalizer low"
},
"equalizer_mid": {
"name": "Equalizer mid"
},
"subwoofer_volume": {
"name": "Subwoofer volume"
},
"treble": {
"name": "Treble"
}
}
},
"issues": {
@@ -104,8 +104,12 @@ async def async_setup_entry(
def _create_entity(device: dict) -> MyNeoSelect:
"""Create a select entity for a device."""
if device["model"] == "EWS":
state = device.get("state") or {}
if state.get("deviceType") == 0:
# According to the MyNeomitis API, EWS "relais" devices expose a "relayMode"
# field in their state, while "pilote" devices do not. We therefore use the
# presence of "relayMode" as an explicit heuristic to distinguish relais
# from pilote devices. If the upstream API changes this behavior, this
# detection logic must be revisited.
if "relayMode" in device.get("state", {}):
description = SELECT_TYPES["relais"]
else:
description = SELECT_TYPES["pilote"]
@@ -3,7 +3,7 @@
from __future__ import annotations
import logging
from typing import TYPE_CHECKING, Any
from typing import Any
import pymystrom
from pymystrom.exceptions import MyStromConnectionError
@@ -11,7 +11,6 @@ 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
@@ -32,8 +31,6 @@ 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:
@@ -54,38 +51,3 @@ 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},
)
@@ -4,14 +4,6 @@
"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",
@@ -1,16 +1,12 @@
{
"config": {
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
},
"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%]",
+1 -1
View File
@@ -8,5 +8,5 @@
"integration_type": "hub",
"iot_class": "cloud_polling",
"loggers": ["pybotvac"],
"requirements": ["pybotvac==0.0.29"]
"requirements": ["pybotvac==0.0.28"]
}
+1 -1
View File
@@ -168,7 +168,7 @@ class NumberDeviceClass(StrEnum):
CURRENT = "current"
"""Current.
Unit of measurement: `A`, `mA`, `μA`
Unit of measurement: `A`, `mA`
"""
DATA_RATE = "data_rate"
@@ -46,7 +46,6 @@ from .const import (
CONF_MAX_TOKENS,
CONF_PROMPT,
CONF_REASONING_EFFORT,
CONF_STORE_RESPONSES,
CONF_TEMPERATURE,
CONF_TOP_P,
DEFAULT_AI_TASK_NAME,
@@ -59,7 +58,6 @@ from .const import (
RECOMMENDED_CHAT_MODEL,
RECOMMENDED_MAX_TOKENS,
RECOMMENDED_REASONING_EFFORT,
RECOMMENDED_STORE_RESPONSES,
RECOMMENDED_STT_OPTIONS,
RECOMMENDED_TEMPERATURE,
RECOMMENDED_TOP_P,
@@ -210,9 +208,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
CONF_TEMPERATURE, RECOMMENDED_TEMPERATURE
),
"user": call.context.user_id,
"store": conversation_subentry.data.get(
CONF_STORE_RESPONSES, RECOMMENDED_STORE_RESPONSES
),
"store": False,
}
if model.startswith("o"):
@@ -55,7 +55,6 @@ from .const import (
CONF_REASONING_SUMMARY,
CONF_RECOMMENDED,
CONF_SERVICE_TIER,
CONF_STORE_RESPONSES,
CONF_TEMPERATURE,
CONF_TOP_P,
CONF_TTS_SPEED,
@@ -83,7 +82,6 @@ from .const import (
RECOMMENDED_REASONING_EFFORT,
RECOMMENDED_REASONING_SUMMARY,
RECOMMENDED_SERVICE_TIER,
RECOMMENDED_STORE_RESPONSES,
RECOMMENDED_STT_MODEL,
RECOMMENDED_STT_OPTIONS,
RECOMMENDED_TEMPERATURE,
@@ -359,10 +357,6 @@ class OpenAISubentryFlowHandler(ConfigSubentryFlow):
CONF_TEMPERATURE,
default=RECOMMENDED_TEMPERATURE,
): NumberSelector(NumberSelectorConfig(min=0, max=2, step=0.05)),
vol.Optional(
CONF_STORE_RESPONSES,
default=RECOMMENDED_STORE_RESPONSES,
): bool,
}
if user_input is not None:
@@ -647,9 +641,7 @@ class OpenAISubentryFlowHandler(ConfigSubentryFlow):
"strict": False,
}
},
store=self.options.get(
CONF_STORE_RESPONSES, RECOMMENDED_STORE_RESPONSES
),
store=False,
)
location_data = location_schema(json.loads(response.output_text) or {})
@@ -24,7 +24,6 @@ CONF_PROMPT = "prompt"
CONF_REASONING_EFFORT = "reasoning_effort"
CONF_REASONING_SUMMARY = "reasoning_summary"
CONF_RECOMMENDED = "recommended"
CONF_STORE_RESPONSES = "store_responses"
CONF_SERVICE_TIER = "service_tier"
CONF_TEMPERATURE = "temperature"
CONF_TOP_P = "top_p"
@@ -43,7 +42,6 @@ RECOMMENDED_CHAT_MODEL = "gpt-4o-mini"
RECOMMENDED_IMAGE_MODEL = "gpt-image-1.5"
RECOMMENDED_MAX_TOKENS = 3000
RECOMMENDED_REASONING_EFFORT = "low"
RECOMMENDED_STORE_RESPONSES = False
RECOMMENDED_REASONING_SUMMARY = "auto"
RECOMMENDED_SERVICE_TIER = "auto"
RECOMMENDED_STT_MODEL = "gpt-4o-mini-transcribe"
@@ -75,7 +75,6 @@ from .const import (
CONF_REASONING_EFFORT,
CONF_REASONING_SUMMARY,
CONF_SERVICE_TIER,
CONF_STORE_RESPONSES,
CONF_TEMPERATURE,
CONF_TOP_P,
CONF_VERBOSITY,
@@ -95,7 +94,6 @@ from .const import (
RECOMMENDED_REASONING_EFFORT,
RECOMMENDED_REASONING_SUMMARY,
RECOMMENDED_SERVICE_TIER,
RECOMMENDED_STORE_RESPONSES,
RECOMMENDED_STT_MODEL,
RECOMMENDED_TEMPERATURE,
RECOMMENDED_TOP_P,
@@ -510,7 +508,7 @@ class OpenAIBaseLLMEntity(Entity):
max_output_tokens=options.get(CONF_MAX_TOKENS, RECOMMENDED_MAX_TOKENS),
user=chat_log.conversation_id,
service_tier=options.get(CONF_SERVICE_TIER, RECOMMENDED_SERVICE_TIER),
store=options.get(CONF_STORE_RESPONSES, RECOMMENDED_STORE_RESPONSES),
store=False,
stream=True,
)
@@ -613,10 +611,8 @@ class OpenAIBaseLLMEntity(Entity):
if image_model != "gpt-image-1-mini":
image_tool["input_fidelity"] = "high"
tools.append(image_tool)
# Keep image state on OpenAI so follow-up prompts can continue by
# conversation ID without resending the generated image data.
model_args["store"] = True
model_args["tool_choice"] = ToolChoiceTypesParam(type="image_generation")
model_args["store"] = True # Avoid sending image data back and forth
if tools:
model_args["tools"] = tools
@@ -51,13 +51,9 @@
"data": {
"chat_model": "[%key:common::generic::model%]",
"max_tokens": "[%key:component::openai_conversation::config_subentries::conversation::step::advanced::data::max_tokens%]",
"store_responses": "[%key:component::openai_conversation::config_subentries::conversation::step::advanced::data::store_responses%]",
"temperature": "[%key:component::openai_conversation::config_subentries::conversation::step::advanced::data::temperature%]",
"top_p": "[%key:component::openai_conversation::config_subentries::conversation::step::advanced::data::top_p%]"
},
"data_description": {
"store_responses": "[%key:component::openai_conversation::config_subentries::conversation::step::advanced::data_description::store_responses%]"
},
"title": "[%key:component::openai_conversation::config_subentries::conversation::step::advanced::title%]"
},
"init": {
@@ -113,13 +109,9 @@
"data": {
"chat_model": "[%key:common::generic::model%]",
"max_tokens": "Maximum tokens to return in response",
"store_responses": "Store requests and responses in OpenAI",
"temperature": "Temperature",
"top_p": "Top P"
},
"data_description": {
"store_responses": "If enabled, requests and responses are stored by OpenAI and visible in your OpenAI dashboard logs"
},
"title": "Advanced settings"
},
"init": {
@@ -214,19 +214,19 @@ class PortainerCoordinator(DataUpdateCoordinator[dict[int, PortainerCoordinatorD
else None,
)
# Separately fetch stats for active containers
active_containers = [
# Separately fetch stats for running containers
running_containers = [
container
for container in containers
if container.state
in (DockerContainerState.RUNNING, DockerContainerState.PAUSED)
]
if active_containers:
if running_containers:
container_stats = dict(
zip(
(
self._get_container_name(container.names[0])
for container in active_containers
for container in running_containers
),
await asyncio.gather(
*(
@@ -234,7 +234,7 @@ class PortainerCoordinator(DataUpdateCoordinator[dict[int, PortainerCoordinatorD
endpoint_id=endpoint.id,
container_id=container.id,
)
for container in active_containers
for container in running_containers
)
),
strict=False,
@@ -7,5 +7,5 @@
"integration_type": "service",
"iot_class": "local_polling",
"quality_scale": "platinum",
"requirements": ["pyportainer==1.0.38"]
"requirements": ["pyportainer==1.0.37"]
}
@@ -189,14 +189,6 @@ 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.
+13 -6
View File
@@ -5,17 +5,21 @@ 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 .coordinator import RabbitAirConfigEntry, RabbitAirDataUpdateCoordinator
from .const import DOMAIN
from .coordinator import RabbitAirDataUpdateCoordinator
PLATFORMS: list[Platform] = [Platform.FAN]
async def async_setup_entry(hass: HomeAssistant, entry: RabbitAirConfigEntry) -> bool:
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> 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]
@@ -26,7 +30,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: RabbitAirConfigEntry) ->
await coordinator.async_config_entry_first_refresh()
entry.runtime_data = coordinator
hass.data[DOMAIN][entry.entry_id] = coordinator
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
@@ -35,11 +39,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: RabbitAirConfigEntry) ->
return True
async def async_unload_entry(hass: HomeAssistant, entry: RabbitAirConfigEntry) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok
async def update_listener(hass: HomeAssistant, entry: RabbitAirConfigEntry) -> None:
async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
"""Handle options update."""
await hass.config_entries.async_reload(entry.entry_id)
@@ -12,8 +12,6 @@ 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__)
@@ -45,10 +43,10 @@ class RabbitAirDebouncer(Debouncer[Coroutine[Any, Any, None]]):
class RabbitAirDataUpdateCoordinator(DataUpdateCoordinator[State]):
"""Class to manage fetching data from single endpoint."""
config_entry: RabbitAirConfigEntry
config_entry: ConfigEntry
def __init__(
self, hass: HomeAssistant, config_entry: RabbitAirConfigEntry, device: Client
self, hass: HomeAssistant, config_entry: ConfigEntry, device: Client
) -> None:
"""Initialize global data updater."""
self.device = device
+3 -2
View File
@@ -7,12 +7,13 @@ 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 RabbitAirConfigEntry, RabbitAirDataUpdateCoordinator
from .coordinator import RabbitAirDataUpdateCoordinator
_LOGGER = logging.getLogger(__name__)
@@ -30,7 +31,7 @@ class RabbitAirBaseEntity(CoordinatorEntity[RabbitAirDataUpdateCoordinator]):
def __init__(
self,
coordinator: RabbitAirDataUpdateCoordinator,
entry: RabbitAirConfigEntry,
entry: ConfigEntry,
) -> None:
"""Initialize the entity."""
super().__init__(coordinator)
+7 -4
View File
@@ -7,6 +7,7 @@ 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 (
@@ -14,7 +15,8 @@ from homeassistant.util.percentage import (
percentage_to_ordered_list_item,
)
from .coordinator import RabbitAirConfigEntry, RabbitAirDataUpdateCoordinator
from .const import DOMAIN
from .coordinator import RabbitAirDataUpdateCoordinator
from .entity import RabbitAirBaseEntity
SPEED_LIST = [
@@ -38,11 +40,12 @@ PRESET_MODES = {
async def async_setup_entry(
hass: HomeAssistant,
entry: RabbitAirConfigEntry,
entry: ConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up a config entry."""
async_add_entities([RabbitAirFanEntity(entry.runtime_data, entry)])
coordinator: RabbitAirDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
async_add_entities([RabbitAirFanEntity(coordinator, entry)])
class RabbitAirFanEntity(RabbitAirBaseEntity, FanEntity):
@@ -58,7 +61,7 @@ class RabbitAirFanEntity(RabbitAirBaseEntity, FanEntity):
def __init__(
self,
coordinator: RabbitAirDataUpdateCoordinator,
entry: RabbitAirConfigEntry,
entry: ConfigEntry,
) -> None:
"""Initialize the entity."""
super().__init__(coordinator, entry)
@@ -8,11 +8,13 @@ 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 .coordinator import RadioThermConfigEntry, RadioThermUpdateCoordinator
from .const import DOMAIN
from .coordinator import RadioThermUpdateCoordinator
from .data import async_get_init_data
from .util import async_set_time
@@ -36,7 +38,7 @@ async def _async_call_or_raise_not_ready[_T](
raise ConfigEntryNotReady(msg) from ex
async def async_setup_entry(hass: HomeAssistant, entry: RadioThermConfigEntry) -> bool:
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Radio Thermostat from a config entry."""
host = entry.data[CONF_HOST]
init_coro = async_get_init_data(hass, host)
@@ -52,20 +54,21 @@ async def async_setup_entry(hass: HomeAssistant, entry: RadioThermConfigEntry) -
time_coro = async_set_time(hass, init_data.tstat)
await _async_call_or_raise_not_ready(time_coro, host)
entry.runtime_data = coordinator
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = 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: RadioThermConfigEntry
) -> None:
async def _async_update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
"""Handle options update."""
await hass.config_entries.async_reload(entry.entry_id)
async def async_unload_entry(hass: HomeAssistant, entry: RadioThermConfigEntry) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok
@@ -17,11 +17,13 @@ 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 .coordinator import RadioThermConfigEntry, RadioThermUpdateCoordinator
from . import DOMAIN
from .coordinator import RadioThermUpdateCoordinator
from .entity import RadioThermostatEntity
ATTR_FAN_ACTION = "fan_action"
@@ -99,11 +101,12 @@ def round_temp(temperature):
async def async_setup_entry(
hass: HomeAssistant,
entry: RadioThermConfigEntry,
entry: ConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up climate for a radiotherm device."""
async_add_entities([RadioThermostat(entry.runtime_data)])
coordinator: RadioThermUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
async_add_entities([RadioThermostat(coordinator)])
class RadioThermostat(RadioThermostatEntity, ClimateEntity):
@@ -14,8 +14,6 @@ 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)
@@ -24,12 +22,12 @@ UPDATE_INTERVAL = timedelta(seconds=15)
class RadioThermUpdateCoordinator(DataUpdateCoordinator[RadioThermUpdate]):
"""DataUpdateCoordinator to gather data for radio thermostats."""
config_entry: RadioThermConfigEntry
config_entry: ConfigEntry
def __init__(
self,
hass: HomeAssistant,
config_entry: RadioThermConfigEntry,
config_entry: ConfigEntry,
init_data: RadioThermInitData,
) -> None:
"""Initialize DataUpdateCoordinator."""
@@ -5,10 +5,12 @@ 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 .coordinator import RadioThermConfigEntry, RadioThermUpdateCoordinator
from .const import DOMAIN
from .coordinator import RadioThermUpdateCoordinator
from .entity import RadioThermostatEntity
PARALLEL_UPDATES = 1
@@ -16,11 +18,12 @@ PARALLEL_UPDATES = 1
async def async_setup_entry(
hass: HomeAssistant,
entry: RadioThermConfigEntry,
entry: ConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up switches for a radiotherm device."""
async_add_entities([RadioThermHoldSwitch(entry.runtime_data)])
coordinator: RadioThermUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
async_add_entities([RadioThermHoldSwitch(coordinator)])
class RadioThermHoldSwitch(RadioThermostatEntity, SwitchEntity):
@@ -2,27 +2,29 @@
from __future__ import annotations
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from .coordinator import EagleDataCoordinator, RainforestEagleConfigEntry
from .const import DOMAIN
from .coordinator import EagleDataCoordinator
PLATFORMS = [Platform.SENSOR]
async def async_setup_entry(
hass: HomeAssistant, entry: RainforestEagleConfigEntry
) -> bool:
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Rainforest Eagle from a config entry."""
coordinator = EagleDataCoordinator(hass, entry)
await coordinator.async_config_entry_first_refresh()
entry.runtime_data = coordinator
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
async def async_unload_entry(
hass: HomeAssistant, entry: RainforestEagleConfigEntry
) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok:
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok
@@ -23,21 +23,17 @@ 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: RainforestEagleConfigEntry
config_entry: ConfigEntry
eagle100_reader: Eagle100Reader | None = None
eagle200_meter: aioeagle.ElectricMeter | None = None
def __init__(
self, hass: HomeAssistant, config_entry: RainforestEagleConfigEntry
) -> None:
def __init__(self, hass: HomeAssistant, config_entry: ConfigEntry) -> None:
"""Initialize the data object."""
if config_entry.data[CONF_TYPE] == TYPE_EAGLE_100:
self.model = "EAGLE-100"
@@ -5,19 +5,22 @@ 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
from .coordinator import RainforestEagleConfigEntry
from .const import CONF_CLOUD_ID, CONF_INSTALL_CODE, DOMAIN
from .coordinator import EagleDataCoordinator
TO_REDACT = {CONF_CLOUD_ID, CONF_INSTALL_CODE}
async def async_get_config_entry_diagnostics(
hass: HomeAssistant, config_entry: RainforestEagleConfigEntry
hass: HomeAssistant, config_entry: ConfigEntry
) -> 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": config_entry.runtime_data.data,
"data": coordinator.data,
}
@@ -8,6 +8,7 @@ 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
@@ -16,7 +17,7 @@ from homeassistant.helpers.typing import StateType
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN
from .coordinator import EagleDataCoordinator, RainforestEagleConfigEntry
from .coordinator import EagleDataCoordinator
SENSORS = (
SensorEntityDescription(
@@ -45,11 +46,11 @@ SENSORS = (
async def async_setup_entry(
hass: HomeAssistant,
entry: RainforestEagleConfigEntry,
entry: ConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up a config entry."""
coordinator = entry.runtime_data
coordinator = hass.data[DOMAIN][entry.entry_id]
entities = [EagleSensor(coordinator, description) for description in SENSORS]
if coordinator.data.get("zigbee:Price") not in (None, "invalid"):
+10 -5
View File
@@ -2,25 +2,30 @@
from __future__ import annotations
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from .coordinator import RDWConfigEntry, RDWDataUpdateCoordinator
from .const import DOMAIN
from .coordinator import RDWDataUpdateCoordinator
PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR]
async def async_setup_entry(hass: HomeAssistant, entry: RDWConfigEntry) -> bool:
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up RDW from a config entry."""
coordinator = RDWDataUpdateCoordinator(hass, entry)
await coordinator.async_config_entry_first_refresh()
entry.runtime_data = coordinator
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
async def async_unload_entry(hass: HomeAssistant, entry: RDWConfigEntry) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload RDW config entry."""
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok:
del hass.data[DOMAIN][entry.entry_id]
return unload_ok
@@ -12,13 +12,14 @@ 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 RDWConfigEntry, RDWDataUpdateCoordinator
from .coordinator import RDWDataUpdateCoordinator
@dataclass(frozen=True, kw_only=True)
@@ -45,11 +46,11 @@ BINARY_SENSORS: tuple[RDWBinarySensorEntityDescription, ...] = (
async def async_setup_entry(
hass: HomeAssistant,
entry: RDWConfigEntry,
entry: ConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up RDW binary sensors based on a config entry."""
coordinator = entry.runtime_data
coordinator: RDWDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
async_add_entities(
RDWBinarySensorEntity(
coordinator=coordinator,
+2 -4
View File
@@ -11,15 +11,13 @@ 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: RDWConfigEntry
config_entry: ConfigEntry
def __init__(self, hass: HomeAssistant, config_entry: RDWConfigEntry) -> None:
def __init__(self, hass: HomeAssistant, config_entry: ConfigEntry) -> None:
"""Initialize the coordinator."""
super().__init__(
hass,
+6 -3
View File
@@ -4,14 +4,17 @@ from __future__ import annotations
from typing import Any
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from .coordinator import RDWConfigEntry
from .const import DOMAIN
from .coordinator import RDWDataUpdateCoordinator
async def async_get_config_entry_diagnostics(
hass: HomeAssistant, entry: RDWConfigEntry
hass: HomeAssistant, entry: ConfigEntry
) -> dict[str, Any]:
"""Return diagnostics for a config entry."""
data: dict[str, Any] = entry.runtime_data.data.to_dict()
coordinator: RDWDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
data: dict[str, Any] = coordinator.data.to_dict()
return data
+4 -3
View File
@@ -13,13 +13,14 @@ 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 RDWConfigEntry, RDWDataUpdateCoordinator
from .coordinator import RDWDataUpdateCoordinator
@dataclass(frozen=True, kw_only=True)
@@ -47,11 +48,11 @@ SENSORS: tuple[RDWSensorEntityDescription, ...] = (
async def async_setup_entry(
hass: HomeAssistant,
entry: RDWConfigEntry,
entry: ConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up RDW sensors based on a config entry."""
coordinator = entry.runtime_data
coordinator: RDWDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
async_add_entities(
RDWSensorEntity(
coordinator=coordinator,
@@ -9,20 +9,19 @@ 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, LOGGER
from .coordinator import RecollectWasteConfigEntry, ReCollectWasteDataUpdateCoordinator
from .const import CONF_PLACE_ID, CONF_SERVICE_ID, DOMAIN, LOGGER
from .coordinator import ReCollectWasteDataUpdateCoordinator
PLATFORMS = [Platform.CALENDAR, Platform.SENSOR]
async def async_setup_entry(
hass: HomeAssistant, entry: RecollectWasteConfigEntry
) -> bool:
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up ReCollect Waste as config entry."""
coordinator = ReCollectWasteDataUpdateCoordinator(hass, entry)
await coordinator.async_config_entry_first_refresh()
entry.runtime_data = coordinator
hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][entry.entry_id] = coordinator
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
@@ -31,18 +30,18 @@ async def async_setup_entry(
return True
async def async_reload_entry(
hass: HomeAssistant, entry: RecollectWasteConfigEntry
) -> None:
async def async_reload_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
"""Handle an options update."""
await hass.config_entries.async_reload(entry.entry_id)
async def async_unload_entry(
hass: HomeAssistant, entry: RecollectWasteConfigEntry
) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload an ReCollect Waste config entry."""
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok:
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok
async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
@@ -7,17 +7,19 @@ 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 .coordinator import RecollectWasteConfigEntry, ReCollectWasteDataUpdateCoordinator
from .const import DOMAIN
from .coordinator import ReCollectWasteDataUpdateCoordinator
from .entity import ReCollectWasteEntity
from .util import async_get_pickup_type_names
@callback
def async_get_calendar_event_from_pickup_event(
entry: RecollectWasteConfigEntry, pickup_event: PickupEvent
entry: ConfigEntry, pickup_event: PickupEvent
) -> CalendarEvent:
"""Get a HASS CalendarEvent from an aiorecollect PickupEvent."""
pickup_type_string = ", ".join(
@@ -34,11 +36,13 @@ def async_get_calendar_event_from_pickup_event(
async def async_setup_entry(
hass: HomeAssistant,
entry: RecollectWasteConfigEntry,
entry: ConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up ReCollect Waste sensors based on a config entry."""
async_add_entities([ReCollectWasteCalendar(entry.runtime_data, entry)])
coordinator: ReCollectWasteDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
async_add_entities([ReCollectWasteCalendar(coordinator, entry)])
class ReCollectWasteCalendar(ReCollectWasteEntity, CalendarEntity):
@@ -50,7 +54,7 @@ class ReCollectWasteCalendar(ReCollectWasteEntity, CalendarEntity):
def __init__(
self,
coordinator: ReCollectWasteDataUpdateCoordinator,
entry: RecollectWasteConfigEntry,
entry: ConfigEntry,
) -> None:
"""Initialize the ReCollect Waste entity."""
super().__init__(coordinator, entry)
@@ -8,13 +8,17 @@ from aiorecollect.client import Client
from aiorecollect.errors import RecollectError
import voluptuous as vol
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult, OptionsFlow
from homeassistant.config_entries import (
ConfigEntry,
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}
@@ -29,7 +33,7 @@ class RecollectWasteConfigFlow(ConfigFlow, domain=DOMAIN):
@staticmethod
@callback
def async_get_options_flow(
config_entry: RecollectWasteConfigEntry,
config_entry: ConfigEntry,
) -> RecollectWasteOptionsFlowHandler:
"""Define the config flow to handle options."""
return RecollectWasteOptionsFlowHandler()
@@ -14,19 +14,15 @@ 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: RecollectWasteConfigEntry
config_entry: ConfigEntry
def __init__(
self, hass: HomeAssistant, config_entry: RecollectWasteConfigEntry
) -> None:
def __init__(self, hass: HomeAssistant, config_entry: ConfigEntry) -> None:
"""Initialize the coordinator."""
super().__init__(
hass,
@@ -6,11 +6,12 @@ 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
from .coordinator import RecollectWasteConfigEntry
from .const import CONF_PLACE_ID, DOMAIN
from .coordinator import ReCollectWasteDataUpdateCoordinator
CONF_AREA_NAME = "area_name"
CONF_TITLE = "title"
@@ -25,13 +26,15 @@ TO_REDACT = {
async def async_get_config_entry_diagnostics(
hass: HomeAssistant, entry: RecollectWasteConfigEntry
hass: HomeAssistant, entry: ConfigEntry
) -> 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 entry.runtime_data.data],
"data": [dataclasses.asdict(event) for event in coordinator.data],
},
TO_REDACT,
)
@@ -1,10 +1,11 @@
"""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 RecollectWasteConfigEntry, ReCollectWasteDataUpdateCoordinator
from .coordinator import ReCollectWasteDataUpdateCoordinator
class ReCollectWasteEntity(CoordinatorEntity[ReCollectWasteDataUpdateCoordinator]):
@@ -15,7 +16,7 @@ class ReCollectWasteEntity(CoordinatorEntity[ReCollectWasteDataUpdateCoordinator
def __init__(
self,
coordinator: ReCollectWasteDataUpdateCoordinator,
entry: RecollectWasteConfigEntry,
entry: ConfigEntry,
) -> None:
"""Initialize the sensor."""
super().__init__(coordinator)
@@ -9,11 +9,12 @@ 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 LOGGER
from .coordinator import RecollectWasteConfigEntry, ReCollectWasteDataUpdateCoordinator
from .const import DOMAIN, LOGGER
from .coordinator import ReCollectWasteDataUpdateCoordinator
from .entity import ReCollectWasteEntity
from .util import async_get_pickup_type_names
@@ -37,12 +38,14 @@ SENSOR_DESCRIPTIONS = (
async def async_setup_entry(
hass: HomeAssistant,
entry: RecollectWasteConfigEntry,
entry: ConfigEntry,
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(entry.runtime_data, entry, description)
ReCollectWasteSensor(coordinator, entry, description)
for description in SENSOR_DESCRIPTIONS
)
@@ -60,7 +63,7 @@ class ReCollectWasteSensor(ReCollectWasteEntity, SensorEntity):
def __init__(
self,
coordinator: ReCollectWasteDataUpdateCoordinator,
entry: RecollectWasteConfigEntry,
entry: ConfigEntry,
description: SensorEntityDescription,
) -> None:
"""Initialize."""
+8 -8
View File
@@ -41,7 +41,7 @@
},
"services": {
"delete_command": {
"description": "Deletes a command or a list of commands from a remote's database.",
"description": "Deletes a command or a list of commands from the database.",
"fields": {
"command": {
"description": "The single command or the list of commands to be deleted.",
@@ -52,10 +52,10 @@
"name": "Device"
}
},
"name": "Delete remote command"
"name": "Delete command"
},
"learn_command": {
"description": "Teaches a remote a command or list of commands from a device.",
"description": "Learns a command or a list of commands from a device.",
"fields": {
"alternative": {
"description": "If code must be stored as an alternative. This is useful for discrete codes. Discrete codes are used for toggles that only perform one function. For example, a code to only turn a device on. If it is on already, sending the code won't change the state.",
@@ -78,7 +78,7 @@
"name": "Timeout"
}
},
"name": "Learn remote command"
"name": "Learn command"
},
"send_command": {
"description": "Sends a command or a list of commands to a device.",
@@ -104,15 +104,15 @@
"name": "Repeats"
}
},
"name": "Send remote command"
"name": "Send command"
},
"toggle": {
"description": "Sends the toggle command.",
"name": "Toggle via remote"
"name": "[%key:common::action::toggle%]"
},
"turn_off": {
"description": "Sends the turn off command.",
"name": "Turn off via remote"
"name": "[%key:common::action::turn_off%]"
},
"turn_on": {
"description": "Sends the turn on command.",
@@ -122,7 +122,7 @@
"name": "Activity"
}
},
"name": "Turn on via remote"
"name": "[%key:common::action::turn_on%]"
}
},
"title": "Remote",
@@ -3,10 +3,12 @@
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
@@ -52,6 +54,10 @@ 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."""
+11 -3
View File
@@ -88,6 +88,15 @@ 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:
@@ -181,10 +190,9 @@ 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.WATT,
suggested_unit_of_measurement=UnitOfPower.KILO_WATT,
native_unit_of_measurement=UnitOfPower.KILO_WATT,
state_class=SensorStateClass.MEASUREMENT,
value_lambda=lambda e: e.coordinator.data.chargingInstantaneousPower,
value_lambda=_get_charging_power,
translation_key="charging_power",
),
RenaultSensorEntityDescription[KamereonVehicleBatteryStatusData](
+20 -5
View File
@@ -2,13 +2,17 @@
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 .coordinator import RensonConfigEntry, RensonCoordinator, RensonData
from .const import DOMAIN
from .coordinator import RensonCoordinator
PLATFORMS = [
Platform.BINARY_SENSOR,
@@ -21,7 +25,15 @@ PLATFORMS = [
]
async def async_setup_entry(hass: HomeAssistant, entry: RensonConfigEntry) -> bool:
@dataclass
class RensonData:
"""Renson data class."""
api: RensonVentilation
coordinator: RensonCoordinator
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Renson from a config entry."""
api = RensonVentilation(entry.data[CONF_HOST])
@@ -32,7 +44,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: RensonConfigEntry) -> bo
await coordinator.async_config_entry_first_refresh()
entry.runtime_data = RensonData(
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = RensonData(
api,
coordinator,
)
@@ -42,6 +54,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: RensonConfigEntry) -> bo
return True
async def async_unload_entry(hass: HomeAssistant, entry: RensonConfigEntry) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok
@@ -21,11 +21,13 @@ 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 .coordinator import RensonConfigEntry, RensonCoordinator
from .const import DOMAIN
from .coordinator import RensonCoordinator
from .entity import RensonEntity
@@ -83,13 +85,15 @@ BINARY_SENSORS: tuple[RensonBinarySensorEntityDescription, ...] = (
async def async_setup_entry(
hass: HomeAssistant,
config_entry: RensonConfigEntry,
config_entry: ConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Call the Renson integration to setup."""
api = config_entry.runtime_data.api
coordinator = config_entry.runtime_data.coordinator
api: RensonVentilation = hass.data[DOMAIN][config_entry.entry_id].api
coordinator: RensonCoordinator = hass.data[DOMAIN][
config_entry.entry_id
].coordinator
async_add_entities(
RensonBinarySensor(description, api, coordinator)
+5 -3
View File
@@ -12,11 +12,13 @@ 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 .coordinator import RensonConfigEntry, RensonCoordinator
from . import RensonCoordinator, RensonData
from .const import DOMAIN
from .entity import RensonEntity
@@ -51,12 +53,12 @@ ENTITY_DESCRIPTIONS: tuple[RensonButtonEntityDescription, ...] = (
async def async_setup_entry(
hass: HomeAssistant,
config_entry: RensonConfigEntry,
config_entry: ConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the Renson button platform."""
data = config_entry.runtime_data
data: RensonData = hass.data[DOMAIN][config_entry.entry_id]
entities = [
RensonButton(description, data.api, data.coordinator)
+2 -14
View File
@@ -3,7 +3,6 @@
from __future__ import annotations
import asyncio
from dataclasses import dataclass
from datetime import timedelta
import logging
from typing import Any
@@ -16,29 +15,18 @@ 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: RensonConfigEntry
config_entry: ConfigEntry
def __init__(
self,
hass: HomeAssistant,
config_entry: RensonConfigEntry,
config_entry: ConfigEntry,
api: RensonVentilation,
) -> None:
"""Initialize my coordinator."""
+8 -4
View File
@@ -16,6 +16,7 @@ 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
@@ -26,7 +27,8 @@ from homeassistant.util.percentage import (
)
from homeassistant.util.scaling import int_states_in_range
from .coordinator import RensonConfigEntry, RensonCoordinator
from .const import DOMAIN
from .coordinator import RensonCoordinator
from .entity import RensonEntity
_LOGGER = logging.getLogger(__name__)
@@ -82,13 +84,15 @@ SPEED_RANGE: tuple[float, float] = (1, 4)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: RensonConfigEntry,
config_entry: ConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the Renson fan platform."""
api = config_entry.runtime_data.api
coordinator = config_entry.runtime_data.coordinator
api: RensonVentilation = hass.data[DOMAIN][config_entry.entry_id].api
coordinator: RensonCoordinator = hass.data[DOMAIN][
config_entry.entry_id
].coordinator
async_add_entities([RensonFan(api, coordinator)])
+8 -4
View File
@@ -12,11 +12,13 @@ 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 .coordinator import RensonConfigEntry, RensonCoordinator
from .const import DOMAIN
from .coordinator import RensonCoordinator
from .entity import RensonEntity
_LOGGER = logging.getLogger(__name__)
@@ -37,13 +39,15 @@ RENSON_NUMBER_DESCRIPTION = NumberEntityDescription(
async def async_setup_entry(
hass: HomeAssistant,
config_entry: RensonConfigEntry,
config_entry: ConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the Renson number platform."""
api = config_entry.runtime_data.api
coordinator = config_entry.runtime_data.coordinator
api: RensonVentilation = hass.data[DOMAIN][config_entry.entry_id].api
coordinator: RensonCoordinator = hass.data[DOMAIN][
config_entry.entry_id
].coordinator
async_add_entities([RensonNumber(RENSON_NUMBER_DESCRIPTION, api, coordinator)])
+6 -3
View File
@@ -34,6 +34,7 @@ from homeassistant.components.sensor import (
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
CONCENTRATION_PARTS_PER_MILLION,
PERCENTAGE,
@@ -44,7 +45,9 @@ from homeassistant.const import (
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .coordinator import RensonConfigEntry, RensonCoordinator
from . import RensonData
from .const import DOMAIN
from .coordinator import RensonCoordinator
from .entity import RensonEntity
@@ -268,12 +271,12 @@ class RensonSensor(RensonEntity, SensorEntity):
async def async_setup_entry(
hass: HomeAssistant,
config_entry: RensonConfigEntry,
config_entry: ConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the Renson sensor platform."""
data = config_entry.runtime_data
data: RensonData = hass.data[DOMAIN][config_entry.entry_id]
entities = [
RensonSensor(description, data.api, data.coordinator) for description in SENSORS
+8 -4
View File
@@ -9,10 +9,12 @@ 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 .coordinator import RensonConfigEntry, RensonCoordinator
from . import RensonCoordinator
from .const import DOMAIN
from .entity import RensonEntity
_LOGGER = logging.getLogger(__name__)
@@ -65,12 +67,14 @@ class RensonBreezeSwitch(RensonEntity, SwitchEntity):
async def async_setup_entry(
hass: HomeAssistant,
config_entry: RensonConfigEntry,
config_entry: ConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Call the Renson integration to setup."""
api = config_entry.runtime_data.api
coordinator = config_entry.runtime_data.coordinator
api: RensonVentilation = hass.data[DOMAIN][config_entry.entry_id].api
coordinator: RensonCoordinator = hass.data[DOMAIN][
config_entry.entry_id
].coordinator
async_add_entities([RensonBreezeSwitch(api, coordinator)])
+8 -4
View File
@@ -10,11 +10,14 @@ 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 .coordinator import RensonConfigEntry, RensonCoordinator
from . import RensonData
from .const import DOMAIN
from .coordinator import RensonCoordinator
from .entity import RensonEntity
@@ -46,14 +49,15 @@ ENTITY_DESCRIPTIONS: tuple[RensonTimeEntityDescription, ...] = (
async def async_setup_entry(
hass: HomeAssistant,
config_entry: RensonConfigEntry,
config_entry: ConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the Renson time platform."""
coordinator = config_entry.runtime_data.coordinator
data: RensonData = hass.data[DOMAIN][config_entry.entry_id]
entities = [
RensonTime(description, coordinator) for description in ENTITY_DESCRIPTIONS
RensonTime(description, data.coordinator) for description in ENTITY_DESCRIPTIONS
]
async_add_entities(entities)
+10 -9
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 LOGGER, SENSOR_TYPE_NEXT_PICKUP
from .coordinator import RidwellConfigEntry, RidwellDataUpdateCoordinator
from .const import DOMAIN, LOGGER, SENSOR_TYPE_NEXT_PICKUP
from .coordinator import RidwellDataUpdateCoordinator
PLATFORMS: list[Platform] = [Platform.CALENDAR, Platform.SENSOR, Platform.SWITCH]
async def async_setup_entry(hass: HomeAssistant, entry: RidwellConfigEntry) -> bool:
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Ridwell from a config entry."""
coordinator = RidwellDataUpdateCoordinator(hass, entry)
await coordinator.async_initialize()
entry.runtime_data = coordinator
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
entry.async_on_unload(entry.add_update_listener(options_update_listener))
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
@@ -27,16 +27,17 @@ async def async_setup_entry(hass: HomeAssistant, entry: RidwellConfigEntry) -> b
return True
async def options_update_listener(
hass: HomeAssistant, entry: RidwellConfigEntry
) -> None:
async def options_update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
"""Handle options update."""
await hass.config_entries.async_reload(entry.entry_id)
async def async_unload_entry(hass: HomeAssistant, entry: RidwellConfigEntry) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok
async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
+6 -4
View File
@@ -7,6 +7,7 @@ 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
@@ -15,14 +16,15 @@ from .const import (
CALENDAR_TITLE_ROTATING,
CALENDAR_TITLE_STATUS,
CONF_CALENDAR_TITLE,
DOMAIN,
)
from .coordinator import RidwellConfigEntry, RidwellDataUpdateCoordinator
from .coordinator import RidwellDataUpdateCoordinator
from .entity import RidwellEntity
@callback
def async_get_calendar_event_from_pickup_event(
pickup_event: RidwellPickupEvent, config_entry: RidwellConfigEntry
pickup_event: RidwellPickupEvent, config_entry: ConfigEntry
) -> CalendarEvent:
"""Get a HASS CalendarEvent from an aioridwell PickupEvent."""
pickup_items = []
@@ -64,11 +66,11 @@ def async_get_calendar_event_from_pickup_event(
async def async_setup_entry(
hass: HomeAssistant,
entry: RidwellConfigEntry,
entry: ConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up Ridwell calendars based on a config entry."""
coordinator = entry.runtime_data
coordinator: RidwellDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
async_add_entities(
RidwellCalendar(coordinator, account)
@@ -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 ConfigFlow, ConfigFlowResult
from homeassistant.config_entries import ConfigEntry, 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,7 +19,6 @@ 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(
{
@@ -108,7 +107,7 @@ class RidwellConfigFlow(ConfigFlow, domain=DOMAIN):
@staticmethod
@callback
def async_get_options_flow(
config_entry: RidwellConfigEntry,
config_entry: ConfigEntry,
) -> SchemaOptionsFlowHandler:
"""Get options flow for this handler."""
try:
@@ -19,8 +19,6 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, Upda
from .const import LOGGER
type RidwellConfigEntry = ConfigEntry[RidwellDataUpdateCoordinator]
UPDATE_INTERVAL = timedelta(hours=1)
@@ -29,9 +27,9 @@ class RidwellDataUpdateCoordinator(
):
"""Class to manage fetching data from single endpoint."""
config_entry: RidwellConfigEntry
config_entry: ConfigEntry
def __init__(self, hass: HomeAssistant, config_entry: RidwellConfigEntry) -> None:
def __init__(self, hass: HomeAssistant, config_entry: ConfigEntry) -> None:
"""Initialize."""
# These will be filled in by async_initialize; we give them these defaults to
# avoid arduous typing checks down the line:
@@ -6,10 +6,12 @@ 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 .coordinator import RidwellConfigEntry
from .const import DOMAIN
from .coordinator import RidwellDataUpdateCoordinator
CONF_TITLE = "title"
@@ -23,15 +25,17 @@ TO_REDACT = {
async def async_get_config_entry_diagnostics(
hass: HomeAssistant, entry: RidwellConfigEntry
hass: HomeAssistant, entry: ConfigEntry
) -> 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 entry.runtime_data.data.values()
for events in coordinator.data.values()
for event in events
],
},

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