mirror of
https://github.com/home-assistant/core.git
synced 2026-04-09 00:45:19 +00:00
Compare commits
59 Commits
drop-ignor
...
ring_ding_
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
280dda6ea9 | ||
|
|
57568fdc2c | ||
|
|
4c8a660b2d | ||
|
|
b0511519a1 | ||
|
|
038b583888 | ||
|
|
018c130988 | ||
|
|
462e9965d7 | ||
|
|
ea4d85f96c | ||
|
|
1a4d518ef2 | ||
|
|
a48a770ca4 | ||
|
|
e4aeee9d85 | ||
|
|
726edf3a3b | ||
|
|
b98aa0ad91 | ||
|
|
f82b8cb7c7 | ||
|
|
d6342d51cc | ||
|
|
1eead15c24 | ||
|
|
2e6137325c | ||
|
|
8d3d4a1b5c | ||
|
|
3e5132bf85 | ||
|
|
65e4b26006 | ||
|
|
13f1a42d69 | ||
|
|
5be48affcf | ||
|
|
8994f501f1 | ||
|
|
7f49ecffd3 | ||
|
|
a560967861 | ||
|
|
82202ee1c2 | ||
|
|
b697b3a54e | ||
|
|
6cf5bbe2f5 | ||
|
|
c0c61533e6 | ||
|
|
15e434431d | ||
|
|
0452bb91c7 | ||
|
|
5620fc9e96 | ||
|
|
1a5ef199da | ||
|
|
e98eec113e | ||
|
|
c74d4047d8 | ||
|
|
f5ae250720 | ||
|
|
bea4eea871 | ||
|
|
f4f202a8a1 | ||
|
|
c30ccf3750 | ||
|
|
0b8390cf21 | ||
|
|
1a048a7845 | ||
|
|
08097c67eb | ||
|
|
550e53d192 | ||
|
|
09ee76c265 | ||
|
|
f7b2f5e8f1 | ||
|
|
a1414717ad | ||
|
|
2f0889ac02 | ||
|
|
323b3a4d96 | ||
|
|
8aa0e9f6c3 | ||
|
|
906475249c | ||
|
|
354b5860bb | ||
|
|
74957969f7 | ||
|
|
b52ce22ee7 | ||
|
|
920ffdb9b5 | ||
|
|
4a454dff02 | ||
|
|
481eb66bc5 | ||
|
|
b76627a442 | ||
|
|
1aa214fb61 | ||
|
|
6e30de3a1c |
4
.github/workflows/builder.yml
vendored
4
.github/workflows/builder.yml
vendored
@@ -47,10 +47,6 @@ 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]
|
||||
|
||||
4
.github/workflows/ci.yaml
vendored
4
.github/workflows/ci.yaml
vendored
@@ -709,7 +709,7 @@ jobs:
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
python --version
|
||||
pylint homeassistant
|
||||
pylint --ignore-missing-annotations=y 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 $(printf "homeassistant/components/%s " ${INTEGRATIONS_GLOB})
|
||||
pylint --ignore-missing-annotations=y $(printf "homeassistant/components/%s " ${INTEGRATIONS_GLOB})
|
||||
|
||||
pylint-tests:
|
||||
name: Check pylint on tests
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
"""The Actron Air integration."""
|
||||
|
||||
from actron_neo_api import (
|
||||
ActronAirACSystem,
|
||||
ActronAirAPI,
|
||||
ActronAirAPIError,
|
||||
ActronAirAuthError,
|
||||
)
|
||||
from actron_neo_api import ActronAirAPI, ActronAirAPIError, ActronAirAuthError
|
||||
from actron_neo_api.models.system import ActronAirSystemInfo
|
||||
|
||||
from homeassistant.const import CONF_API_TOKEN, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
@@ -25,7 +21,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ActronAirConfigEntry) ->
|
||||
"""Set up Actron Air integration from a config entry."""
|
||||
|
||||
api = ActronAirAPI(refresh_token=entry.data[CONF_API_TOKEN])
|
||||
systems: list[ActronAirACSystem] = []
|
||||
systems: list[ActronAirSystemInfo] = []
|
||||
|
||||
try:
|
||||
systems = await api.get_ac_systems()
|
||||
@@ -44,9 +40,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ActronAirConfigEntry) ->
|
||||
system_coordinators: dict[str, ActronAirSystemCoordinator] = {}
|
||||
for system in systems:
|
||||
coordinator = ActronAirSystemCoordinator(hass, entry, api, system)
|
||||
_LOGGER.debug("Setting up coordinator for system: %s", system["serial"])
|
||||
_LOGGER.debug("Setting up coordinator for system: %s", system.serial)
|
||||
await coordinator.async_config_entry_first_refresh()
|
||||
system_coordinators[system["serial"]] = coordinator
|
||||
system_coordinators[system.serial] = coordinator
|
||||
|
||||
entry.runtime_data = ActronAirRuntimeData(
|
||||
api=api,
|
||||
|
||||
@@ -18,7 +18,7 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from .coordinator import ActronAirConfigEntry, ActronAirSystemCoordinator
|
||||
from .entity import ActronAirAcEntity, ActronAirZoneEntity, handle_actron_api_errors
|
||||
from .entity import ActronAirAcEntity, ActronAirZoneEntity, actron_air_command
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
@@ -136,19 +136,19 @@ class ActronSystemClimate(ActronAirAcEntity, ActronAirClimateEntity):
|
||||
"""Return the target temperature."""
|
||||
return self._status.user_aircon_settings.temperature_setpoint_cool_c
|
||||
|
||||
@handle_actron_api_errors
|
||||
@actron_air_command
|
||||
async def async_set_fan_mode(self, fan_mode: str) -> None:
|
||||
"""Set a new fan mode."""
|
||||
api_fan_mode = FAN_MODE_MAPPING_HA_TO_ACTRONAIR.get(fan_mode)
|
||||
await self._status.user_aircon_settings.set_fan_mode(api_fan_mode)
|
||||
|
||||
@handle_actron_api_errors
|
||||
@actron_air_command
|
||||
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
|
||||
"""Set the HVAC mode."""
|
||||
ac_mode = HVAC_MODE_MAPPING_HA_TO_ACTRONAIR.get(hvac_mode)
|
||||
await self._status.ac_system.set_system_mode(ac_mode)
|
||||
|
||||
@handle_actron_api_errors
|
||||
@actron_air_command
|
||||
async def async_set_temperature(self, **kwargs: Any) -> None:
|
||||
"""Set the temperature."""
|
||||
temp = kwargs.get(ATTR_TEMPERATURE)
|
||||
@@ -212,13 +212,13 @@ class ActronZoneClimate(ActronAirZoneEntity, ActronAirClimateEntity):
|
||||
"""Return the target temperature."""
|
||||
return self._zone.temperature_setpoint_cool_c
|
||||
|
||||
@handle_actron_api_errors
|
||||
@actron_air_command
|
||||
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
|
||||
"""Set the HVAC mode."""
|
||||
is_enabled = hvac_mode != HVACMode.OFF
|
||||
await self._zone.enable(is_enabled)
|
||||
|
||||
@handle_actron_api_errors
|
||||
@actron_air_command
|
||||
async def async_set_temperature(self, **kwargs: Any) -> None:
|
||||
"""Set the temperature."""
|
||||
await self._zone.set_temperature(temperature=kwargs.get(ATTR_TEMPERATURE))
|
||||
|
||||
@@ -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[ActronAirACSystem]):
|
||||
class ActronAirSystemCoordinator(DataUpdateCoordinator[ActronAirStatus]):
|
||||
"""System coordinator for Actron Air integration."""
|
||||
|
||||
def __init__(
|
||||
@@ -46,7 +46,7 @@ class ActronAirSystemCoordinator(DataUpdateCoordinator[ActronAirACSystem]):
|
||||
hass: HomeAssistant,
|
||||
entry: ActronAirConfigEntry,
|
||||
api: ActronAirAPI,
|
||||
system: ActronAirACSystem,
|
||||
system: ActronAirSystemInfo,
|
||||
) -> None:
|
||||
"""Initialize the coordinator."""
|
||||
super().__init__(
|
||||
@@ -57,7 +57,7 @@ class ActronAirSystemCoordinator(DataUpdateCoordinator[ActronAirACSystem]):
|
||||
config_entry=entry,
|
||||
)
|
||||
self.system = system
|
||||
self.serial_number = system["serial"]
|
||||
self.serial_number = system.serial
|
||||
self.api = api
|
||||
self.status = self.api.state_manager.get_status(self.serial_number)
|
||||
self.last_seen = dt_util.utcnow()
|
||||
|
||||
35
homeassistant/components/actron_air/diagnostics.py
Normal file
35
homeassistant/components/actron_air/diagnostics.py
Normal file
@@ -0,0 +1,35 @@
|
||||
"""Diagnostics support for Actron Air."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.components.diagnostics import async_redact_data
|
||||
from homeassistant.const import CONF_API_TOKEN
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .coordinator import ActronAirConfigEntry
|
||||
|
||||
TO_REDACT = {CONF_API_TOKEN, "master_serial", "serial_number", "serial"}
|
||||
|
||||
|
||||
async def async_get_config_entry_diagnostics(
|
||||
hass: HomeAssistant,
|
||||
entry: ActronAirConfigEntry,
|
||||
) -> dict[str, Any]:
|
||||
"""Return diagnostics for a config entry."""
|
||||
coordinators: dict[int, Any] = {}
|
||||
for idx, coordinator in enumerate(entry.runtime_data.system_coordinators.values()):
|
||||
coordinators[idx] = {
|
||||
"system": async_redact_data(
|
||||
coordinator.system.model_dump(mode="json"), TO_REDACT
|
||||
),
|
||||
"status": async_redact_data(
|
||||
coordinator.data.model_dump(mode="json", exclude={"last_known_state"}),
|
||||
TO_REDACT,
|
||||
),
|
||||
}
|
||||
return {
|
||||
"entry_data": async_redact_data(entry.data, TO_REDACT),
|
||||
"coordinators": coordinators,
|
||||
}
|
||||
@@ -14,10 +14,14 @@ from .const import DOMAIN
|
||||
from .coordinator import ActronAirSystemCoordinator
|
||||
|
||||
|
||||
def handle_actron_api_errors[_EntityT: ActronAirEntity, **_P](
|
||||
def actron_air_command[_EntityT: ActronAirEntity, **_P](
|
||||
func: Callable[Concatenate[_EntityT, _P], Coroutine[Any, Any, Any]],
|
||||
) -> Callable[Concatenate[_EntityT, _P], Coroutine[Any, Any, None]]:
|
||||
"""Decorate Actron Air API calls to handle ActronAirAPIError exceptions."""
|
||||
"""Decorator for Actron Air API calls.
|
||||
|
||||
Handles ActronAirAPIError exceptions, and requests a coordinator update
|
||||
to update the status of the devices as soon as possible.
|
||||
"""
|
||||
|
||||
@wraps(func)
|
||||
async def wrapper(self: _EntityT, *args: _P.args, **kwargs: _P.kwargs) -> None:
|
||||
@@ -30,6 +34,7 @@ def handle_actron_api_errors[_EntityT: ActronAirEntity, **_P](
|
||||
translation_key="api_error",
|
||||
translation_placeholders={"error": str(err)},
|
||||
) from err
|
||||
self.coordinator.async_set_updated_data(self.coordinator.data)
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
@@ -13,5 +13,5 @@
|
||||
"integration_type": "hub",
|
||||
"iot_class": "cloud_polling",
|
||||
"quality_scale": "silver",
|
||||
"requirements": ["actron-neo-api==0.4.1"]
|
||||
"requirements": ["actron-neo-api==0.5.0"]
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ rules:
|
||||
|
||||
# Gold
|
||||
devices: done
|
||||
diagnostics: todo
|
||||
diagnostics: done
|
||||
discovery-update-info:
|
||||
status: exempt
|
||||
comment: This integration uses DHCP discovery, however is cloud polling. Therefore there is no information to update.
|
||||
|
||||
@@ -10,7 +10,7 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from .coordinator import ActronAirConfigEntry, ActronAirSystemCoordinator
|
||||
from .entity import ActronAirAcEntity, handle_actron_api_errors
|
||||
from .entity import ActronAirAcEntity, actron_air_command
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
@@ -105,12 +105,12 @@ class ActronAirSwitch(ActronAirAcEntity, SwitchEntity):
|
||||
"""Return true if the switch is on."""
|
||||
return self.entity_description.is_on_fn(self.coordinator)
|
||||
|
||||
@handle_actron_api_errors
|
||||
@actron_air_command
|
||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||
"""Turn the switch on."""
|
||||
await self.entity_description.set_fn(self.coordinator, True)
|
||||
|
||||
@handle_actron_api_errors
|
||||
@actron_air_command
|
||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||
"""Turn the switch off."""
|
||||
await self.entity_description.set_fn(self.coordinator, False)
|
||||
|
||||
@@ -36,13 +36,15 @@ 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: 0,
|
||||
CONF_THINKING_BUDGET: MIN_THINKING_BUDGET,
|
||||
CONF_THINKING_EFFORT: "low",
|
||||
CONF_TOOL_SEARCH: False,
|
||||
CONF_WEB_SEARCH: False,
|
||||
@@ -50,8 +52,6 @@ DEFAULT = {
|
||||
CONF_WEB_SEARCH_MAX_USES: 5,
|
||||
}
|
||||
|
||||
MIN_THINKING_BUDGET = 1024
|
||||
|
||||
NON_THINKING_MODELS = [
|
||||
"claude-3-haiku",
|
||||
]
|
||||
|
||||
@@ -8,6 +8,6 @@
|
||||
"integration_type": "service",
|
||||
"iot_class": "calculated",
|
||||
"quality_scale": "internal",
|
||||
"requirements": ["cronsim==2.7", "securetar==2026.2.0"],
|
||||
"requirements": ["cronsim==2.7", "securetar==2026.4.1"],
|
||||
"single_config_entry": true
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ from securetar import (
|
||||
SecureTarFile,
|
||||
SecureTarReadError,
|
||||
SecureTarRootKeyContext,
|
||||
get_archive_max_ciphertext_size,
|
||||
)
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
@@ -383,9 +384,12 @@ def _encrypt_backup(
|
||||
if prefix not in expected_archives:
|
||||
LOGGER.debug("Unknown inner tar file %s will not be encrypted", obj.name)
|
||||
continue
|
||||
output_archive.import_tar(
|
||||
input_tar.extractfile(obj), obj, derived_key_id=inner_tar_idx
|
||||
)
|
||||
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)
|
||||
inner_tar_idx += 1
|
||||
|
||||
|
||||
@@ -419,7 +423,7 @@ class _CipherBackupStreamer:
|
||||
hass: HomeAssistant,
|
||||
backup: AgentBackup,
|
||||
open_stream: Callable[[], Coroutine[Any, Any, AsyncIterator[bytes]]],
|
||||
password: str | None,
|
||||
password: str,
|
||||
) -> None:
|
||||
"""Initialize."""
|
||||
self._workers: list[_CipherWorkerStatus] = []
|
||||
@@ -431,7 +435,9 @@ class _CipherBackupStreamer:
|
||||
|
||||
def size(self) -> int:
|
||||
"""Return the maximum size of the decrypted or encrypted backup."""
|
||||
return self._backup.size + self._num_tar_files() * tarfile.RECORDSIZE
|
||||
return get_archive_max_ciphertext_size(
|
||||
self._backup.size, SECURETAR_CREATE_VERSION, self._num_tar_files()
|
||||
)
|
||||
|
||||
def _num_tar_files(self) -> int:
|
||||
"""Return the number of inner tar files."""
|
||||
|
||||
@@ -20,7 +20,7 @@ from homeassistant.helpers.typing import ConfigType
|
||||
from homeassistant.util import dt as dt_util
|
||||
from homeassistant.util.hass_dict import HassKey
|
||||
|
||||
from .const import ATTR_EVENT_TYPE, ATTR_EVENT_TYPES, DOMAIN
|
||||
from .const import ATTR_EVENT_TYPE, ATTR_EVENT_TYPES, DOMAIN, DoorbellEventType
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
DATA_COMPONENT: HassKey[EntityComponent[EventEntity]] = HassKey(DOMAIN)
|
||||
@@ -44,6 +44,7 @@ __all__ = [
|
||||
"DOMAIN",
|
||||
"PLATFORM_SCHEMA",
|
||||
"PLATFORM_SCHEMA_BASE",
|
||||
"DoorbellEventType",
|
||||
"EventDeviceClass",
|
||||
"EventEntity",
|
||||
"EventEntityDescription",
|
||||
@@ -189,6 +190,21 @@ class EventEntity(RestoreEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_)
|
||||
async def async_internal_added_to_hass(self) -> None:
|
||||
"""Call when the event entity is added to hass."""
|
||||
await super().async_internal_added_to_hass()
|
||||
|
||||
if (
|
||||
self.device_class == EventDeviceClass.DOORBELL
|
||||
and DoorbellEventType.RING not in self.event_types
|
||||
):
|
||||
report_issue = self._suggest_report_issue()
|
||||
_LOGGER.warning(
|
||||
"Entity %s is a doorbell event entity but does not support "
|
||||
"the '%s' event type. This will stop working in "
|
||||
"Home Assistant 2027.4, please %s",
|
||||
self.entity_id,
|
||||
DoorbellEventType.RING,
|
||||
report_issue,
|
||||
)
|
||||
|
||||
if (
|
||||
(state := await self.async_get_last_state())
|
||||
and state.state is not None
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
"""Provides the constants needed for the component."""
|
||||
|
||||
from enum import StrEnum
|
||||
|
||||
DOMAIN = "event"
|
||||
ATTR_EVENT_TYPE = "event_type"
|
||||
ATTR_EVENT_TYPES = "event_types"
|
||||
|
||||
|
||||
class DoorbellEventType(StrEnum):
|
||||
"""Standard event types for doorbell device class."""
|
||||
|
||||
RING = "ring"
|
||||
|
||||
@@ -15,7 +15,14 @@
|
||||
"name": "Button"
|
||||
},
|
||||
"doorbell": {
|
||||
"name": "Doorbell"
|
||||
"name": "Doorbell",
|
||||
"state_attributes": {
|
||||
"event_type": {
|
||||
"state": {
|
||||
"ring": "Ring"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"motion": {
|
||||
"name": "Motion"
|
||||
|
||||
@@ -2,18 +2,14 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_API_KEY, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .coordinator import FlussDataUpdateCoordinator
|
||||
from .coordinator import FlussConfigEntry, FlussDataUpdateCoordinator
|
||||
|
||||
PLATFORMS: list[Platform] = [Platform.BUTTON]
|
||||
|
||||
|
||||
type FlussConfigEntry = ConfigEntry[FlussDataUpdateCoordinator]
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: FlussConfigEntry,
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
"""Support for Fluss Devices."""
|
||||
|
||||
from homeassistant.components.button import ButtonEntity
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from .coordinator import FlussApiClientError, FlussDataUpdateCoordinator
|
||||
from .coordinator import FlussApiClientError, FlussConfigEntry
|
||||
from .entity import FlussEntity
|
||||
|
||||
type FlussConfigEntry = ConfigEntry[FlussDataUpdateCoordinator]
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
|
||||
@@ -38,6 +38,7 @@ PLATFORMS: list[Platform] = [
|
||||
Platform.SELECT,
|
||||
Platform.SENSOR,
|
||||
Platform.SWITCH,
|
||||
Platform.TEXT,
|
||||
Platform.VALVE,
|
||||
]
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
12
homeassistant/components/gardena_bluetooth/icons.json
Normal file
12
homeassistant/components/gardena_bluetooth/icons.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"entity": {
|
||||
"text": {
|
||||
"contour_name": {
|
||||
"default": "mdi:vector-polygon"
|
||||
},
|
||||
"position_name": {
|
||||
"default": "mdi:map-marker-radius"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -154,6 +154,14 @@
|
||||
"state": {
|
||||
"name": "[%key:common::state::open%]"
|
||||
}
|
||||
},
|
||||
"text": {
|
||||
"contour_name": {
|
||||
"name": "Contour {number}"
|
||||
},
|
||||
"position_name": {
|
||||
"name": "Position {number}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
88
homeassistant/components/gardena_bluetooth/text.py
Normal file
88
homeassistant/components/gardena_bluetooth/text.py
Normal file
@@ -0,0 +1,88 @@
|
||||
"""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,4 +11,8 @@ CONF_LISTENING_PORT_DEFAULT = 4002
|
||||
CONF_DISCOVERY_INTERVAL_DEFAULT = 60
|
||||
|
||||
SCAN_INTERVAL = timedelta(seconds=30)
|
||||
# A device is considered unavailable if we have not heard a status response
|
||||
# from it for three consecutive poll cycles. This tolerates a single dropped
|
||||
# UDP response plus some jitter before flapping the entity state.
|
||||
DEVICE_TIMEOUT = SCAN_INTERVAL * 3
|
||||
DISCOVERY_TIMEOUT = 5
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
@@ -22,7 +23,7 @@ from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from .const import DOMAIN, MANUFACTURER
|
||||
from .const import DEVICE_TIMEOUT, DOMAIN, MANUFACTURER
|
||||
from .coordinator import GoveeLocalApiCoordinator, GoveeLocalConfigEntry
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@@ -118,6 +119,19 @@ class GoveeLight(CoordinatorEntity[GoveeLocalApiCoordinator], LightEntity):
|
||||
serial_number=device.fingerprint,
|
||||
)
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
"""Return if the device is reachable.
|
||||
|
||||
The underlying library updates ``lastseen`` whenever the device
|
||||
replies to a status request. The coordinator polls every
|
||||
``SCAN_INTERVAL``, so if we have not heard back within
|
||||
``DEVICE_TIMEOUT`` we consider the device offline.
|
||||
"""
|
||||
if not super().available:
|
||||
return False
|
||||
return datetime.now() - self._device.lastseen < DEVICE_TIMEOUT
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
"""Return true if device is on (brightness above 0)."""
|
||||
@@ -205,8 +219,8 @@ class GoveeLight(CoordinatorEntity[GoveeLocalApiCoordinator], LightEntity):
|
||||
|
||||
@callback
|
||||
def _update_callback(self, device: GoveeDevice) -> None:
|
||||
if self.hass:
|
||||
self.async_write_ha_state()
|
||||
"""Handle device state updates pushed by the library."""
|
||||
self.async_write_ha_state()
|
||||
|
||||
def _save_last_color_state(self) -> None:
|
||||
color_mode = self.color_mode
|
||||
|
||||
@@ -98,7 +98,9 @@ class HabiticaCalendarEntity(HabiticaBase, CalendarEntity):
|
||||
start_date, end_date - timedelta(days=1), inc=True
|
||||
)
|
||||
# if no end_date is given, return only the next recurrence
|
||||
return [recurrences.after(start_date, inc=True)]
|
||||
if (next_date := recurrences.after(start_date, inc=True)) is None:
|
||||
return []
|
||||
return [next_date]
|
||||
|
||||
|
||||
class HabiticaTodosCalendarEntity(HabiticaCalendarEntity):
|
||||
|
||||
@@ -5,5 +5,5 @@
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/holiday",
|
||||
"iot_class": "local_polling",
|
||||
"requirements": ["holidays==0.93", "babel==2.15.0"]
|
||||
"requirements": ["holidays==0.94", "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,
|
||||
scan_serial_ports,
|
||||
async_scan_serial_ports,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
@@ -163,7 +163,7 @@ async def async_migrate_entry(
|
||||
key not in config_entry.data
|
||||
for key in (VID, PID, MANUFACTURER, PRODUCT, SERIAL_NUMBER)
|
||||
):
|
||||
serial_ports = await hass.async_add_executor_job(scan_serial_ports)
|
||||
serial_ports = await async_scan_serial_ports(hass)
|
||||
serial_ports_info = {port.device: port for port in serial_ports}
|
||||
device = config_entry.data[DEVICE]
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
"""Provide info to system health."""
|
||||
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.components import system_health
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
|
||||
@@ -14,7 +16,7 @@ def async_register(
|
||||
register.async_register_info(system_health_info)
|
||||
|
||||
|
||||
async def system_health_info(hass):
|
||||
async def system_health_info(hass: HomeAssistant) -> dict[str, Any]:
|
||||
"""Get info for the info page."""
|
||||
return {
|
||||
"api_endpoint_reachable": system_health.async_check_can_reach_url(
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"integration_type": "hub",
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["pylutron_caseta"],
|
||||
"requirements": ["pylutron-caseta==0.27.0"],
|
||||
"requirements": ["pylutron-caseta==0.28.0"],
|
||||
"zeroconf": [
|
||||
{
|
||||
"properties": {
|
||||
|
||||
@@ -10,10 +10,12 @@ See https://modelcontextprotocol.io/docs/concepts/architecture#implementation-ex
|
||||
from collections.abc import Callable, Sequence
|
||||
import json
|
||||
import logging
|
||||
from typing import Any
|
||||
from typing import Any, cast
|
||||
|
||||
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
|
||||
|
||||
@@ -25,6 +27,16 @@ 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
|
||||
@@ -90,6 +102,47 @@ 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,7 +49,11 @@ if TYPE_CHECKING:
|
||||
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
PLATFORMS = [Platform.BUTTON, Platform.MEDIA_PLAYER]
|
||||
PLATFORMS = [
|
||||
Platform.BUTTON,
|
||||
Platform.MEDIA_PLAYER,
|
||||
Platform.NUMBER,
|
||||
]
|
||||
|
||||
CONNECT_TIMEOUT = 10
|
||||
LISTEN_READY_TIMEOUT = 30
|
||||
|
||||
@@ -80,3 +80,5 @@ 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,8 +6,9 @@ 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
|
||||
from music_assistant_models.player import Player, PlayerOption
|
||||
|
||||
from homeassistant.const import EntityCategory
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
||||
@@ -84,3 +85,45 @@ 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."""
|
||||
|
||||
127
homeassistant/components/music_assistant/number.py
Normal file
127
homeassistant/components/music_assistant/number.py
Normal file
@@ -0,0 +1,127 @@
|
||||
"""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,6 +53,35 @@
|
||||
"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,12 +104,8 @@ async def async_setup_entry(
|
||||
def _create_entity(device: dict) -> MyNeoSelect:
|
||||
"""Create a select entity for a device."""
|
||||
if device["model"] == "EWS":
|
||||
# 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", {}):
|
||||
state = device.get("state") or {}
|
||||
if state.get("deviceType") == 0:
|
||||
description = SELECT_TYPES["relais"]
|
||||
else:
|
||||
description = SELECT_TYPES["pilote"]
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import Any
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
import pymystrom
|
||||
from pymystrom.exceptions import MyStromConnectionError
|
||||
@@ -11,6 +11,7 @@ import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
|
||||
from homeassistant.const import CONF_HOST, CONF_NAME
|
||||
from homeassistant.helpers.service_info.dhcp import DhcpServiceInfo
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
@@ -31,6 +32,8 @@ class MyStromConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
|
||||
VERSION = 1
|
||||
|
||||
_host: str | None = None
|
||||
|
||||
async def async_step_user(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
@@ -51,3 +54,38 @@ class MyStromConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
|
||||
schema = self.add_suggested_values_to_schema(STEP_USER_DATA_SCHEMA, user_input)
|
||||
return self.async_show_form(step_id="user", data_schema=schema, errors=errors)
|
||||
|
||||
async def async_step_dhcp(
|
||||
self, discovery_info: DhcpServiceInfo
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle DHCP discovery."""
|
||||
mac_address = discovery_info.macaddress.upper()
|
||||
await self.async_set_unique_id(mac_address)
|
||||
self._abort_if_unique_id_configured(updates={CONF_HOST: discovery_info.ip})
|
||||
|
||||
try:
|
||||
await pymystrom.get_device_info(discovery_info.ip)
|
||||
except MyStromConnectionError:
|
||||
return self.async_abort(reason="cannot_connect")
|
||||
|
||||
self._host = discovery_info.ip
|
||||
self.context["title_placeholders"] = {"host": discovery_info.ip}
|
||||
return await self.async_step_discovery_confirm()
|
||||
|
||||
async def async_step_discovery_confirm(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle discovery confirmation."""
|
||||
if user_input is not None:
|
||||
return self.async_create_entry(
|
||||
title=DEFAULT_NAME,
|
||||
data={CONF_HOST: self._host},
|
||||
)
|
||||
|
||||
self._set_confirm_only()
|
||||
if TYPE_CHECKING:
|
||||
assert self._host is not None
|
||||
return self.async_show_form(
|
||||
step_id="discovery_confirm",
|
||||
description_placeholders={CONF_HOST: self._host},
|
||||
)
|
||||
|
||||
@@ -4,6 +4,14 @@
|
||||
"codeowners": ["@fabaff"],
|
||||
"config_flow": true,
|
||||
"dependencies": ["http"],
|
||||
"dhcp": [
|
||||
{
|
||||
"hostname": "mystrom-*"
|
||||
},
|
||||
{
|
||||
"registered_devices": true
|
||||
}
|
||||
],
|
||||
"documentation": "https://www.home-assistant.io/integrations/mystrom",
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_polling",
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
|
||||
},
|
||||
"step": {
|
||||
"discovery_confirm": {
|
||||
"description": "Do you want to set up the myStrom device at {host}?"
|
||||
},
|
||||
"user": {
|
||||
"data": {
|
||||
"host": "[%key:common::config_flow::data::host%]",
|
||||
|
||||
@@ -168,7 +168,7 @@ class NumberDeviceClass(StrEnum):
|
||||
CURRENT = "current"
|
||||
"""Current.
|
||||
|
||||
Unit of measurement: `A`, `mA`
|
||||
Unit of measurement: `A`, `mA`, `μA`
|
||||
"""
|
||||
|
||||
DATA_RATE = "data_rate"
|
||||
|
||||
@@ -46,6 +46,7 @@ from .const import (
|
||||
CONF_MAX_TOKENS,
|
||||
CONF_PROMPT,
|
||||
CONF_REASONING_EFFORT,
|
||||
CONF_STORE_RESPONSES,
|
||||
CONF_TEMPERATURE,
|
||||
CONF_TOP_P,
|
||||
DEFAULT_AI_TASK_NAME,
|
||||
@@ -58,6 +59,7 @@ from .const import (
|
||||
RECOMMENDED_CHAT_MODEL,
|
||||
RECOMMENDED_MAX_TOKENS,
|
||||
RECOMMENDED_REASONING_EFFORT,
|
||||
RECOMMENDED_STORE_RESPONSES,
|
||||
RECOMMENDED_STT_OPTIONS,
|
||||
RECOMMENDED_TEMPERATURE,
|
||||
RECOMMENDED_TOP_P,
|
||||
@@ -208,7 +210,9 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
CONF_TEMPERATURE, RECOMMENDED_TEMPERATURE
|
||||
),
|
||||
"user": call.context.user_id,
|
||||
"store": False,
|
||||
"store": conversation_subentry.data.get(
|
||||
CONF_STORE_RESPONSES, RECOMMENDED_STORE_RESPONSES
|
||||
),
|
||||
}
|
||||
|
||||
if model.startswith("o"):
|
||||
|
||||
@@ -55,6 +55,7 @@ from .const import (
|
||||
CONF_REASONING_SUMMARY,
|
||||
CONF_RECOMMENDED,
|
||||
CONF_SERVICE_TIER,
|
||||
CONF_STORE_RESPONSES,
|
||||
CONF_TEMPERATURE,
|
||||
CONF_TOP_P,
|
||||
CONF_TTS_SPEED,
|
||||
@@ -82,6 +83,7 @@ from .const import (
|
||||
RECOMMENDED_REASONING_EFFORT,
|
||||
RECOMMENDED_REASONING_SUMMARY,
|
||||
RECOMMENDED_SERVICE_TIER,
|
||||
RECOMMENDED_STORE_RESPONSES,
|
||||
RECOMMENDED_STT_MODEL,
|
||||
RECOMMENDED_STT_OPTIONS,
|
||||
RECOMMENDED_TEMPERATURE,
|
||||
@@ -357,6 +359,10 @@ 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:
|
||||
@@ -641,7 +647,9 @@ class OpenAISubentryFlowHandler(ConfigSubentryFlow):
|
||||
"strict": False,
|
||||
}
|
||||
},
|
||||
store=False,
|
||||
store=self.options.get(
|
||||
CONF_STORE_RESPONSES, RECOMMENDED_STORE_RESPONSES
|
||||
),
|
||||
)
|
||||
location_data = location_schema(json.loads(response.output_text) or {})
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ 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"
|
||||
@@ -42,6 +43,7 @@ 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,6 +75,7 @@ from .const import (
|
||||
CONF_REASONING_EFFORT,
|
||||
CONF_REASONING_SUMMARY,
|
||||
CONF_SERVICE_TIER,
|
||||
CONF_STORE_RESPONSES,
|
||||
CONF_TEMPERATURE,
|
||||
CONF_TOP_P,
|
||||
CONF_VERBOSITY,
|
||||
@@ -94,6 +95,7 @@ from .const import (
|
||||
RECOMMENDED_REASONING_EFFORT,
|
||||
RECOMMENDED_REASONING_SUMMARY,
|
||||
RECOMMENDED_SERVICE_TIER,
|
||||
RECOMMENDED_STORE_RESPONSES,
|
||||
RECOMMENDED_STT_MODEL,
|
||||
RECOMMENDED_TEMPERATURE,
|
||||
RECOMMENDED_TOP_P,
|
||||
@@ -508,7 +510,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=False,
|
||||
store=options.get(CONF_STORE_RESPONSES, RECOMMENDED_STORE_RESPONSES),
|
||||
stream=True,
|
||||
)
|
||||
|
||||
@@ -611,8 +613,10 @@ 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,9 +51,13 @@
|
||||
"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": {
|
||||
@@ -109,9 +113,13 @@
|
||||
"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 running containers
|
||||
running_containers = [
|
||||
# Separately fetch stats for active containers
|
||||
active_containers = [
|
||||
container
|
||||
for container in containers
|
||||
if container.state
|
||||
in (DockerContainerState.RUNNING, DockerContainerState.PAUSED)
|
||||
]
|
||||
if running_containers:
|
||||
if active_containers:
|
||||
container_stats = dict(
|
||||
zip(
|
||||
(
|
||||
self._get_container_name(container.names[0])
|
||||
for container in running_containers
|
||||
for container in active_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 running_containers
|
||||
for container in active_containers
|
||||
)
|
||||
),
|
||||
strict=False,
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"integration_type": "service",
|
||||
"iot_class": "local_polling",
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["pyportainer==1.0.37"]
|
||||
"requirements": ["pyportainer==1.0.38"]
|
||||
}
|
||||
|
||||
@@ -189,6 +189,14 @@ async def async_migrate_entry(hass: HomeAssistant, entry: ProxmoxConfigEntry) ->
|
||||
# Migration for additional configuration options added to support API tokens
|
||||
if entry.version < 3:
|
||||
data = dict(entry.data)
|
||||
# If CONF_REALM wasn't there yet, extract from username
|
||||
if CONF_REALM not in data:
|
||||
data[CONF_REALM] = DEFAULT_REALM
|
||||
if "@" in data.get(CONF_USERNAME, ""):
|
||||
username, realm = data[CONF_USERNAME].split("@", 1)
|
||||
data[CONF_USERNAME] = username
|
||||
data[CONF_REALM] = realm.lower()
|
||||
|
||||
realm = data[CONF_REALM].lower()
|
||||
|
||||
# If the realm is one of the base providers, set the provider to match the realm.
|
||||
|
||||
@@ -5,21 +5,17 @@ from __future__ import annotations
|
||||
from rabbitair import Client, UdpClient
|
||||
|
||||
from homeassistant.components import zeroconf
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_ACCESS_TOKEN, CONF_HOST, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import RabbitAirDataUpdateCoordinator
|
||||
from .coordinator import RabbitAirConfigEntry, RabbitAirDataUpdateCoordinator
|
||||
|
||||
PLATFORMS: list[Platform] = [Platform.FAN]
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: RabbitAirConfigEntry) -> bool:
|
||||
"""Set up Rabbit Air from a config entry."""
|
||||
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
|
||||
host: str = entry.data[CONF_HOST]
|
||||
token: str = entry.data[CONF_ACCESS_TOKEN]
|
||||
|
||||
@@ -30,7 +26,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
|
||||
await coordinator.async_config_entry_first_refresh()
|
||||
|
||||
hass.data[DOMAIN][entry.entry_id] = coordinator
|
||||
entry.runtime_data = coordinator
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
@@ -39,14 +35,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: RabbitAirConfigEntry) -> bool:
|
||||
"""Unload a config entry."""
|
||||
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
|
||||
hass.data[DOMAIN].pop(entry.entry_id)
|
||||
|
||||
return unload_ok
|
||||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
|
||||
|
||||
async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
||||
async def update_listener(hass: HomeAssistant, entry: RabbitAirConfigEntry) -> None:
|
||||
"""Handle options update."""
|
||||
await hass.config_entries.async_reload(entry.entry_id)
|
||||
|
||||
@@ -12,6 +12,8 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.debounce import Debouncer
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||
|
||||
type RabbitAirConfigEntry = ConfigEntry[RabbitAirDataUpdateCoordinator]
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -43,10 +45,10 @@ class RabbitAirDebouncer(Debouncer[Coroutine[Any, Any, None]]):
|
||||
class RabbitAirDataUpdateCoordinator(DataUpdateCoordinator[State]):
|
||||
"""Class to manage fetching data from single endpoint."""
|
||||
|
||||
config_entry: ConfigEntry
|
||||
config_entry: RabbitAirConfigEntry
|
||||
|
||||
def __init__(
|
||||
self, hass: HomeAssistant, config_entry: ConfigEntry, device: Client
|
||||
self, hass: HomeAssistant, config_entry: RabbitAirConfigEntry, device: Client
|
||||
) -> None:
|
||||
"""Initialize global data updater."""
|
||||
self.device = device
|
||||
|
||||
@@ -7,13 +7,12 @@ from typing import Any
|
||||
|
||||
from rabbitair import Model
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_MAC
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import RabbitAirDataUpdateCoordinator
|
||||
from .coordinator import RabbitAirConfigEntry, RabbitAirDataUpdateCoordinator
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -31,7 +30,7 @@ class RabbitAirBaseEntity(CoordinatorEntity[RabbitAirDataUpdateCoordinator]):
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: RabbitAirDataUpdateCoordinator,
|
||||
entry: ConfigEntry,
|
||||
entry: RabbitAirConfigEntry,
|
||||
) -> None:
|
||||
"""Initialize the entity."""
|
||||
super().__init__(coordinator)
|
||||
|
||||
@@ -7,7 +7,6 @@ from typing import Any
|
||||
from rabbitair import Mode, Model, Speed
|
||||
|
||||
from homeassistant.components.fan import FanEntity, FanEntityFeature
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.util.percentage import (
|
||||
@@ -15,8 +14,7 @@ from homeassistant.util.percentage import (
|
||||
percentage_to_ordered_list_item,
|
||||
)
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import RabbitAirDataUpdateCoordinator
|
||||
from .coordinator import RabbitAirConfigEntry, RabbitAirDataUpdateCoordinator
|
||||
from .entity import RabbitAirBaseEntity
|
||||
|
||||
SPEED_LIST = [
|
||||
@@ -40,12 +38,11 @@ PRESET_MODES = {
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: ConfigEntry,
|
||||
entry: RabbitAirConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up a config entry."""
|
||||
coordinator: RabbitAirDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
|
||||
async_add_entities([RabbitAirFanEntity(coordinator, entry)])
|
||||
async_add_entities([RabbitAirFanEntity(entry.runtime_data, entry)])
|
||||
|
||||
|
||||
class RabbitAirFanEntity(RabbitAirBaseEntity, FanEntity):
|
||||
@@ -61,7 +58,7 @@ class RabbitAirFanEntity(RabbitAirBaseEntity, FanEntity):
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: RabbitAirDataUpdateCoordinator,
|
||||
entry: ConfigEntry,
|
||||
entry: RabbitAirConfigEntry,
|
||||
) -> None:
|
||||
"""Initialize the entity."""
|
||||
super().__init__(coordinator, entry)
|
||||
|
||||
@@ -8,13 +8,11 @@ from urllib.error import URLError
|
||||
|
||||
from radiotherm.validate import RadiothermTstatError
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_HOST, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import RadioThermUpdateCoordinator
|
||||
from .coordinator import RadioThermConfigEntry, RadioThermUpdateCoordinator
|
||||
from .data import async_get_init_data
|
||||
from .util import async_set_time
|
||||
|
||||
@@ -38,7 +36,7 @@ async def _async_call_or_raise_not_ready[_T](
|
||||
raise ConfigEntryNotReady(msg) from ex
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: RadioThermConfigEntry) -> bool:
|
||||
"""Set up Radio Thermostat from a config entry."""
|
||||
host = entry.data[CONF_HOST]
|
||||
init_coro = async_get_init_data(hass, host)
|
||||
@@ -54,21 +52,20 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
time_coro = async_set_time(hass, init_data.tstat)
|
||||
await _async_call_or_raise_not_ready(time_coro, host)
|
||||
|
||||
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
|
||||
entry.runtime_data = coordinator
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
entry.async_on_unload(entry.add_update_listener(_async_update_listener))
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def _async_update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
||||
async def _async_update_listener(
|
||||
hass: HomeAssistant, entry: RadioThermConfigEntry
|
||||
) -> None:
|
||||
"""Handle options update."""
|
||||
await hass.config_entries.async_reload(entry.entry_id)
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: RadioThermConfigEntry) -> bool:
|
||||
"""Unload a config entry."""
|
||||
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
|
||||
hass.data[DOMAIN].pop(entry.entry_id)
|
||||
|
||||
return unload_ok
|
||||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
|
||||
@@ -17,13 +17,11 @@ from homeassistant.components.climate import (
|
||||
HVACAction,
|
||||
HVACMode,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import ATTR_TEMPERATURE, PRECISION_HALVES, UnitOfTemperature
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from . import DOMAIN
|
||||
from .coordinator import RadioThermUpdateCoordinator
|
||||
from .coordinator import RadioThermConfigEntry, RadioThermUpdateCoordinator
|
||||
from .entity import RadioThermostatEntity
|
||||
|
||||
ATTR_FAN_ACTION = "fan_action"
|
||||
@@ -101,12 +99,11 @@ def round_temp(temperature):
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: ConfigEntry,
|
||||
entry: RadioThermConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up climate for a radiotherm device."""
|
||||
coordinator: RadioThermUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
|
||||
async_add_entities([RadioThermostat(coordinator)])
|
||||
async_add_entities([RadioThermostat(entry.runtime_data)])
|
||||
|
||||
|
||||
class RadioThermostat(RadioThermostatEntity, ClimateEntity):
|
||||
|
||||
@@ -14,6 +14,8 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, Upda
|
||||
|
||||
from .data import RadioThermInitData, RadioThermUpdate, async_get_data
|
||||
|
||||
type RadioThermConfigEntry = ConfigEntry[RadioThermUpdateCoordinator]
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
UPDATE_INTERVAL = timedelta(seconds=15)
|
||||
@@ -22,12 +24,12 @@ UPDATE_INTERVAL = timedelta(seconds=15)
|
||||
class RadioThermUpdateCoordinator(DataUpdateCoordinator[RadioThermUpdate]):
|
||||
"""DataUpdateCoordinator to gather data for radio thermostats."""
|
||||
|
||||
config_entry: ConfigEntry
|
||||
config_entry: RadioThermConfigEntry
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
config_entry: RadioThermConfigEntry,
|
||||
init_data: RadioThermInitData,
|
||||
) -> None:
|
||||
"""Initialize DataUpdateCoordinator."""
|
||||
|
||||
@@ -5,12 +5,10 @@ from __future__ import annotations
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.components.switch import SwitchEntity
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import RadioThermUpdateCoordinator
|
||||
from .coordinator import RadioThermConfigEntry, RadioThermUpdateCoordinator
|
||||
from .entity import RadioThermostatEntity
|
||||
|
||||
PARALLEL_UPDATES = 1
|
||||
@@ -18,12 +16,11 @@ PARALLEL_UPDATES = 1
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: ConfigEntry,
|
||||
entry: RadioThermConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up switches for a radiotherm device."""
|
||||
coordinator: RadioThermUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
|
||||
async_add_entities([RadioThermHoldSwitch(coordinator)])
|
||||
async_add_entities([RadioThermHoldSwitch(entry.runtime_data)])
|
||||
|
||||
|
||||
class RadioThermHoldSwitch(RadioThermostatEntity, SwitchEntity):
|
||||
|
||||
@@ -2,29 +2,27 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import EagleDataCoordinator
|
||||
from .coordinator import EagleDataCoordinator, RainforestEagleConfigEntry
|
||||
|
||||
PLATFORMS = [Platform.SENSOR]
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant, entry: RainforestEagleConfigEntry
|
||||
) -> bool:
|
||||
"""Set up Rainforest Eagle from a config entry."""
|
||||
coordinator = EagleDataCoordinator(hass, entry)
|
||||
await coordinator.async_config_entry_first_refresh()
|
||||
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
|
||||
entry.runtime_data = coordinator
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
async def async_unload_entry(
|
||||
hass: HomeAssistant, entry: RainforestEagleConfigEntry
|
||||
) -> bool:
|
||||
"""Unload a config entry."""
|
||||
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
if unload_ok:
|
||||
hass.data[DOMAIN].pop(entry.entry_id)
|
||||
|
||||
return unload_ok
|
||||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
|
||||
@@ -23,17 +23,21 @@ from .const import (
|
||||
)
|
||||
from .data import UPDATE_100_ERRORS
|
||||
|
||||
type RainforestEagleConfigEntry = ConfigEntry[EagleDataCoordinator]
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class EagleDataCoordinator(DataUpdateCoordinator):
|
||||
"""Get the latest data from the Eagle device."""
|
||||
|
||||
config_entry: ConfigEntry
|
||||
config_entry: RainforestEagleConfigEntry
|
||||
eagle100_reader: Eagle100Reader | None = None
|
||||
eagle200_meter: aioeagle.ElectricMeter | None = None
|
||||
|
||||
def __init__(self, hass: HomeAssistant, config_entry: ConfigEntry) -> None:
|
||||
def __init__(
|
||||
self, hass: HomeAssistant, config_entry: RainforestEagleConfigEntry
|
||||
) -> None:
|
||||
"""Initialize the data object."""
|
||||
if config_entry.data[CONF_TYPE] == TYPE_EAGLE_100:
|
||||
self.model = "EAGLE-100"
|
||||
|
||||
@@ -5,22 +5,19 @@ from __future__ import annotations
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.components.diagnostics import async_redact_data
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .const import CONF_CLOUD_ID, CONF_INSTALL_CODE, DOMAIN
|
||||
from .coordinator import EagleDataCoordinator
|
||||
from .const import CONF_CLOUD_ID, CONF_INSTALL_CODE
|
||||
from .coordinator import RainforestEagleConfigEntry
|
||||
|
||||
TO_REDACT = {CONF_CLOUD_ID, CONF_INSTALL_CODE}
|
||||
|
||||
|
||||
async def async_get_config_entry_diagnostics(
|
||||
hass: HomeAssistant, config_entry: ConfigEntry
|
||||
hass: HomeAssistant, config_entry: RainforestEagleConfigEntry
|
||||
) -> dict[str, Any]:
|
||||
"""Return diagnostics for a config entry."""
|
||||
coordinator: EagleDataCoordinator = hass.data[DOMAIN][config_entry.entry_id]
|
||||
|
||||
return {
|
||||
"config_entry": async_redact_data(config_entry.as_dict(), TO_REDACT),
|
||||
"data": coordinator.data,
|
||||
"data": config_entry.runtime_data.data,
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ from homeassistant.components.sensor import (
|
||||
SensorEntityDescription,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import UnitOfEnergy, UnitOfPower
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
@@ -17,7 +16,7 @@ from homeassistant.helpers.typing import StateType
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import EagleDataCoordinator
|
||||
from .coordinator import EagleDataCoordinator, RainforestEagleConfigEntry
|
||||
|
||||
SENSORS = (
|
||||
SensorEntityDescription(
|
||||
@@ -46,11 +45,11 @@ SENSORS = (
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: ConfigEntry,
|
||||
entry: RainforestEagleConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up a config entry."""
|
||||
coordinator = hass.data[DOMAIN][entry.entry_id]
|
||||
coordinator = entry.runtime_data
|
||||
entities = [EagleSensor(coordinator, description) for description in SENSORS]
|
||||
|
||||
if coordinator.data.get("zigbee:Price") not in (None, "invalid"):
|
||||
|
||||
@@ -2,30 +2,25 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import RDWDataUpdateCoordinator
|
||||
from .coordinator import RDWConfigEntry, RDWDataUpdateCoordinator
|
||||
|
||||
PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR]
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: RDWConfigEntry) -> bool:
|
||||
"""Set up RDW from a config entry."""
|
||||
coordinator = RDWDataUpdateCoordinator(hass, entry)
|
||||
await coordinator.async_config_entry_first_refresh()
|
||||
|
||||
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
|
||||
entry.runtime_data = coordinator
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: RDWConfigEntry) -> bool:
|
||||
"""Unload RDW config entry."""
|
||||
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
if unload_ok:
|
||||
del hass.data[DOMAIN][entry.entry_id]
|
||||
return unload_ok
|
||||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
|
||||
@@ -12,14 +12,13 @@ from homeassistant.components.binary_sensor import (
|
||||
BinarySensorEntity,
|
||||
BinarySensorEntityDescription,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import RDWDataUpdateCoordinator
|
||||
from .coordinator import RDWConfigEntry, RDWDataUpdateCoordinator
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
@@ -46,11 +45,11 @@ BINARY_SENSORS: tuple[RDWBinarySensorEntityDescription, ...] = (
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: ConfigEntry,
|
||||
entry: RDWConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up RDW binary sensors based on a config entry."""
|
||||
coordinator: RDWDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
|
||||
coordinator = entry.runtime_data
|
||||
async_add_entities(
|
||||
RDWBinarySensorEntity(
|
||||
coordinator=coordinator,
|
||||
|
||||
@@ -11,13 +11,15 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||
|
||||
from .const import CONF_LICENSE_PLATE, DOMAIN, LOGGER, SCAN_INTERVAL
|
||||
|
||||
type RDWConfigEntry = ConfigEntry[RDWDataUpdateCoordinator]
|
||||
|
||||
|
||||
class RDWDataUpdateCoordinator(DataUpdateCoordinator[Vehicle]):
|
||||
"""Class to manage fetching RDW data."""
|
||||
|
||||
config_entry: ConfigEntry
|
||||
config_entry: RDWConfigEntry
|
||||
|
||||
def __init__(self, hass: HomeAssistant, config_entry: ConfigEntry) -> None:
|
||||
def __init__(self, hass: HomeAssistant, config_entry: RDWConfigEntry) -> None:
|
||||
"""Initialize the coordinator."""
|
||||
super().__init__(
|
||||
hass,
|
||||
|
||||
@@ -4,17 +4,14 @@ from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import RDWDataUpdateCoordinator
|
||||
from .coordinator import RDWConfigEntry
|
||||
|
||||
|
||||
async def async_get_config_entry_diagnostics(
|
||||
hass: HomeAssistant, entry: ConfigEntry
|
||||
hass: HomeAssistant, entry: RDWConfigEntry
|
||||
) -> dict[str, Any]:
|
||||
"""Return diagnostics for a config entry."""
|
||||
coordinator: RDWDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
|
||||
data: dict[str, Any] = coordinator.data.to_dict()
|
||||
data: dict[str, Any] = entry.runtime_data.data.to_dict()
|
||||
return data
|
||||
|
||||
@@ -13,14 +13,13 @@ from homeassistant.components.sensor import (
|
||||
SensorEntity,
|
||||
SensorEntityDescription,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from .const import CONF_LICENSE_PLATE, DOMAIN
|
||||
from .coordinator import RDWDataUpdateCoordinator
|
||||
from .coordinator import RDWConfigEntry, RDWDataUpdateCoordinator
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
@@ -48,11 +47,11 @@ SENSORS: tuple[RDWSensorEntityDescription, ...] = (
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: ConfigEntry,
|
||||
entry: RDWConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up RDW sensors based on a config entry."""
|
||||
coordinator: RDWDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
|
||||
coordinator = entry.runtime_data
|
||||
async_add_entities(
|
||||
RDWSensorEntity(
|
||||
coordinator=coordinator,
|
||||
|
||||
@@ -9,19 +9,20 @@ from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
from .const import CONF_PLACE_ID, CONF_SERVICE_ID, DOMAIN, LOGGER
|
||||
from .coordinator import ReCollectWasteDataUpdateCoordinator
|
||||
from .const import CONF_PLACE_ID, CONF_SERVICE_ID, LOGGER
|
||||
from .coordinator import RecollectWasteConfigEntry, ReCollectWasteDataUpdateCoordinator
|
||||
|
||||
PLATFORMS = [Platform.CALENDAR, Platform.SENSOR]
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant, entry: RecollectWasteConfigEntry
|
||||
) -> bool:
|
||||
"""Set up ReCollect Waste as config entry."""
|
||||
coordinator = ReCollectWasteDataUpdateCoordinator(hass, entry)
|
||||
|
||||
await coordinator.async_config_entry_first_refresh()
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
hass.data[DOMAIN][entry.entry_id] = coordinator
|
||||
entry.runtime_data = coordinator
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
@@ -30,18 +31,18 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
async def async_reload_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
||||
async def async_reload_entry(
|
||||
hass: HomeAssistant, entry: RecollectWasteConfigEntry
|
||||
) -> None:
|
||||
"""Handle an options update."""
|
||||
await hass.config_entries.async_reload(entry.entry_id)
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
async def async_unload_entry(
|
||||
hass: HomeAssistant, entry: RecollectWasteConfigEntry
|
||||
) -> bool:
|
||||
"""Unload an ReCollect Waste config entry."""
|
||||
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
if unload_ok:
|
||||
hass.data[DOMAIN].pop(entry.entry_id)
|
||||
|
||||
return unload_ok
|
||||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
|
||||
|
||||
async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
|
||||
@@ -7,19 +7,17 @@ import datetime
|
||||
from aiorecollect.client import PickupEvent
|
||||
|
||||
from homeassistant.components.calendar import CalendarEntity, CalendarEvent
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import ReCollectWasteDataUpdateCoordinator
|
||||
from .coordinator import RecollectWasteConfigEntry, ReCollectWasteDataUpdateCoordinator
|
||||
from .entity import ReCollectWasteEntity
|
||||
from .util import async_get_pickup_type_names
|
||||
|
||||
|
||||
@callback
|
||||
def async_get_calendar_event_from_pickup_event(
|
||||
entry: ConfigEntry, pickup_event: PickupEvent
|
||||
entry: RecollectWasteConfigEntry, pickup_event: PickupEvent
|
||||
) -> CalendarEvent:
|
||||
"""Get a HASS CalendarEvent from an aiorecollect PickupEvent."""
|
||||
pickup_type_string = ", ".join(
|
||||
@@ -36,13 +34,11 @@ def async_get_calendar_event_from_pickup_event(
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: ConfigEntry,
|
||||
entry: RecollectWasteConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up ReCollect Waste sensors based on a config entry."""
|
||||
coordinator: ReCollectWasteDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
|
||||
|
||||
async_add_entities([ReCollectWasteCalendar(coordinator, entry)])
|
||||
async_add_entities([ReCollectWasteCalendar(entry.runtime_data, entry)])
|
||||
|
||||
|
||||
class ReCollectWasteCalendar(ReCollectWasteEntity, CalendarEntity):
|
||||
@@ -54,7 +50,7 @@ class ReCollectWasteCalendar(ReCollectWasteEntity, CalendarEntity):
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: ReCollectWasteDataUpdateCoordinator,
|
||||
entry: ConfigEntry,
|
||||
entry: RecollectWasteConfigEntry,
|
||||
) -> None:
|
||||
"""Initialize the ReCollect Waste entity."""
|
||||
super().__init__(coordinator, entry)
|
||||
|
||||
@@ -8,17 +8,13 @@ from aiorecollect.client import Client
|
||||
from aiorecollect.errors import RecollectError
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import (
|
||||
ConfigEntry,
|
||||
ConfigFlow,
|
||||
ConfigFlowResult,
|
||||
OptionsFlow,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult, OptionsFlow
|
||||
from homeassistant.const import CONF_FRIENDLY_NAME
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers import aiohttp_client
|
||||
|
||||
from .const import CONF_PLACE_ID, CONF_SERVICE_ID, DOMAIN, LOGGER
|
||||
from .coordinator import RecollectWasteConfigEntry
|
||||
|
||||
DATA_SCHEMA = vol.Schema(
|
||||
{vol.Required(CONF_PLACE_ID): str, vol.Required(CONF_SERVICE_ID): str}
|
||||
@@ -33,7 +29,7 @@ class RecollectWasteConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
@staticmethod
|
||||
@callback
|
||||
def async_get_options_flow(
|
||||
config_entry: ConfigEntry,
|
||||
config_entry: RecollectWasteConfigEntry,
|
||||
) -> RecollectWasteOptionsFlowHandler:
|
||||
"""Define the config flow to handle options."""
|
||||
return RecollectWasteOptionsFlowHandler()
|
||||
|
||||
@@ -14,15 +14,19 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, Upda
|
||||
|
||||
from .const import CONF_PLACE_ID, CONF_SERVICE_ID, LOGGER
|
||||
|
||||
type RecollectWasteConfigEntry = ConfigEntry[ReCollectWasteDataUpdateCoordinator]
|
||||
|
||||
DEFAULT_UPDATE_INTERVAL = timedelta(days=1)
|
||||
|
||||
|
||||
class ReCollectWasteDataUpdateCoordinator(DataUpdateCoordinator[list[PickupEvent]]):
|
||||
"""Class to manage fetching ReCollect Waste data."""
|
||||
|
||||
config_entry: ConfigEntry
|
||||
config_entry: RecollectWasteConfigEntry
|
||||
|
||||
def __init__(self, hass: HomeAssistant, config_entry: ConfigEntry) -> None:
|
||||
def __init__(
|
||||
self, hass: HomeAssistant, config_entry: RecollectWasteConfigEntry
|
||||
) -> None:
|
||||
"""Initialize the coordinator."""
|
||||
super().__init__(
|
||||
hass,
|
||||
|
||||
@@ -6,12 +6,11 @@ import dataclasses
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.components.diagnostics import async_redact_data
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_UNIQUE_ID
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .const import CONF_PLACE_ID, DOMAIN
|
||||
from .coordinator import ReCollectWasteDataUpdateCoordinator
|
||||
from .const import CONF_PLACE_ID
|
||||
from .coordinator import RecollectWasteConfigEntry
|
||||
|
||||
CONF_AREA_NAME = "area_name"
|
||||
CONF_TITLE = "title"
|
||||
@@ -26,15 +25,13 @@ TO_REDACT = {
|
||||
|
||||
|
||||
async def async_get_config_entry_diagnostics(
|
||||
hass: HomeAssistant, entry: ConfigEntry
|
||||
hass: HomeAssistant, entry: RecollectWasteConfigEntry
|
||||
) -> dict[str, Any]:
|
||||
"""Return diagnostics for a config entry."""
|
||||
coordinator: ReCollectWasteDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
|
||||
|
||||
return async_redact_data(
|
||||
{
|
||||
"entry": entry.as_dict(),
|
||||
"data": [dataclasses.asdict(event) for event in coordinator.data],
|
||||
"data": [dataclasses.asdict(event) for event in entry.runtime_data.data],
|
||||
},
|
||||
TO_REDACT,
|
||||
)
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
"""Define a base ReCollect Waste entity."""
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from .const import CONF_PLACE_ID, CONF_SERVICE_ID, DOMAIN
|
||||
from .coordinator import ReCollectWasteDataUpdateCoordinator
|
||||
from .coordinator import RecollectWasteConfigEntry, ReCollectWasteDataUpdateCoordinator
|
||||
|
||||
|
||||
class ReCollectWasteEntity(CoordinatorEntity[ReCollectWasteDataUpdateCoordinator]):
|
||||
@@ -16,7 +15,7 @@ class ReCollectWasteEntity(CoordinatorEntity[ReCollectWasteDataUpdateCoordinator
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: ReCollectWasteDataUpdateCoordinator,
|
||||
entry: ConfigEntry,
|
||||
entry: RecollectWasteConfigEntry,
|
||||
) -> None:
|
||||
"""Initialize the sensor."""
|
||||
super().__init__(coordinator)
|
||||
|
||||
@@ -9,12 +9,11 @@ from homeassistant.components.sensor import (
|
||||
SensorEntity,
|
||||
SensorEntityDescription,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from .const import DOMAIN, LOGGER
|
||||
from .coordinator import ReCollectWasteDataUpdateCoordinator
|
||||
from .const import LOGGER
|
||||
from .coordinator import RecollectWasteConfigEntry, ReCollectWasteDataUpdateCoordinator
|
||||
from .entity import ReCollectWasteEntity
|
||||
from .util import async_get_pickup_type_names
|
||||
|
||||
@@ -38,14 +37,12 @@ SENSOR_DESCRIPTIONS = (
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: ConfigEntry,
|
||||
entry: RecollectWasteConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up ReCollect Waste sensors based on a config entry."""
|
||||
coordinator: ReCollectWasteDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
|
||||
|
||||
async_add_entities(
|
||||
ReCollectWasteSensor(coordinator, entry, description)
|
||||
ReCollectWasteSensor(entry.runtime_data, entry, description)
|
||||
for description in SENSOR_DESCRIPTIONS
|
||||
)
|
||||
|
||||
@@ -63,7 +60,7 @@ class ReCollectWasteSensor(ReCollectWasteEntity, SensorEntity):
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: ReCollectWasteDataUpdateCoordinator,
|
||||
entry: ConfigEntry,
|
||||
entry: RecollectWasteConfigEntry,
|
||||
description: SensorEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize."""
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
},
|
||||
"services": {
|
||||
"delete_command": {
|
||||
"description": "Deletes a command or a list of commands from the database.",
|
||||
"description": "Deletes a command or a list of commands from a remote's database.",
|
||||
"fields": {
|
||||
"command": {
|
||||
"description": "The single command or the list of commands to be deleted.",
|
||||
@@ -52,10 +52,10 @@
|
||||
"name": "Device"
|
||||
}
|
||||
},
|
||||
"name": "Delete command"
|
||||
"name": "Delete remote command"
|
||||
},
|
||||
"learn_command": {
|
||||
"description": "Learns a command or a list of commands from a device.",
|
||||
"description": "Teaches a remote a command or 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 command"
|
||||
"name": "Learn remote command"
|
||||
},
|
||||
"send_command": {
|
||||
"description": "Sends a command or a list of commands to a device.",
|
||||
@@ -104,15 +104,15 @@
|
||||
"name": "Repeats"
|
||||
}
|
||||
},
|
||||
"name": "Send command"
|
||||
"name": "Send remote command"
|
||||
},
|
||||
"toggle": {
|
||||
"description": "Sends the toggle command.",
|
||||
"name": "[%key:common::action::toggle%]"
|
||||
"name": "Toggle via remote"
|
||||
},
|
||||
"turn_off": {
|
||||
"description": "Sends the turn off command.",
|
||||
"name": "[%key:common::action::turn_off%]"
|
||||
"name": "Turn off via remote"
|
||||
},
|
||||
"turn_on": {
|
||||
"description": "Sends the turn on command.",
|
||||
@@ -122,7 +122,7 @@
|
||||
"name": "Activity"
|
||||
}
|
||||
},
|
||||
"name": "[%key:common::action::turn_on%]"
|
||||
"name": "Turn on via remote"
|
||||
}
|
||||
},
|
||||
"title": "Remote",
|
||||
|
||||
@@ -3,12 +3,10 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import cast
|
||||
|
||||
from renault_api.kamereon.models import KamereonVehicleDataAttributes
|
||||
|
||||
from homeassistant.helpers.entity import Entity, EntityDescription
|
||||
from homeassistant.helpers.typing import StateType
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from .coordinator import RenaultDataUpdateCoordinator
|
||||
@@ -54,10 +52,6 @@ class RenaultDataEntity[T: KamereonVehicleDataAttributes](
|
||||
super().__init__(vehicle.coordinators[description.coordinator])
|
||||
RenaultEntity.__init__(self, vehicle, description)
|
||||
|
||||
def _get_data_attr(self, key: str) -> StateType:
|
||||
"""Return the attribute value from the coordinator data."""
|
||||
return cast(StateType, getattr(self.coordinator.data, key))
|
||||
|
||||
@property
|
||||
def assumed_state(self) -> bool:
|
||||
"""Return True if unable to access real state of the entity."""
|
||||
|
||||
@@ -88,15 +88,6 @@ class RenaultSensor[T: KamereonVehicleDataAttributes](
|
||||
return self.entity_description.value_lambda(self)
|
||||
|
||||
|
||||
def _get_charging_power(
|
||||
entity: RenaultSensor[KamereonVehicleBatteryStatusData],
|
||||
) -> StateType:
|
||||
"""Return the charging_power of this entity."""
|
||||
if (data := entity.coordinator.data.chargingInstantaneousPower) is None:
|
||||
return None
|
||||
return data / 1000
|
||||
|
||||
|
||||
def _get_charge_state_formatted(
|
||||
entity: RenaultSensor[KamereonVehicleBatteryStatusData],
|
||||
) -> str | None:
|
||||
@@ -190,9 +181,10 @@ SENSOR_TYPES: tuple[RenaultSensorEntityDescription[Any], ...] = (
|
||||
condition_lambda=lambda a: a.details.reports_charging_power_in_watts(),
|
||||
coordinator="battery",
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
native_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||
native_unit_of_measurement=UnitOfPower.WATT,
|
||||
suggested_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value_lambda=_get_charging_power,
|
||||
value_lambda=lambda e: e.coordinator.data.chargingInstantaneousPower,
|
||||
translation_key="charging_power",
|
||||
),
|
||||
RenaultSensorEntityDescription[KamereonVehicleBatteryStatusData](
|
||||
|
||||
@@ -2,17 +2,13 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
from renson_endura_delta.renson import RensonVentilation
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_HOST, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import RensonCoordinator
|
||||
from .coordinator import RensonConfigEntry, RensonCoordinator, RensonData
|
||||
|
||||
PLATFORMS = [
|
||||
Platform.BINARY_SENSOR,
|
||||
@@ -25,15 +21,7 @@ PLATFORMS = [
|
||||
]
|
||||
|
||||
|
||||
@dataclass
|
||||
class RensonData:
|
||||
"""Renson data class."""
|
||||
|
||||
api: RensonVentilation
|
||||
coordinator: RensonCoordinator
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: RensonConfigEntry) -> bool:
|
||||
"""Set up Renson from a config entry."""
|
||||
|
||||
api = RensonVentilation(entry.data[CONF_HOST])
|
||||
@@ -44,7 +32,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
|
||||
await coordinator.async_config_entry_first_refresh()
|
||||
|
||||
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = RensonData(
|
||||
entry.runtime_data = RensonData(
|
||||
api,
|
||||
coordinator,
|
||||
)
|
||||
@@ -54,9 +42,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: RensonConfigEntry) -> bool:
|
||||
"""Unload a config entry."""
|
||||
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
|
||||
hass.data[DOMAIN].pop(entry.entry_id)
|
||||
|
||||
return unload_ok
|
||||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
|
||||
@@ -21,13 +21,11 @@ from homeassistant.components.binary_sensor import (
|
||||
BinarySensorEntity,
|
||||
BinarySensorEntityDescription,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import EntityCategory
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import RensonCoordinator
|
||||
from .coordinator import RensonConfigEntry, RensonCoordinator
|
||||
from .entity import RensonEntity
|
||||
|
||||
|
||||
@@ -85,15 +83,13 @@ BINARY_SENSORS: tuple[RensonBinarySensorEntityDescription, ...] = (
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
config_entry: RensonConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Call the Renson integration to setup."""
|
||||
|
||||
api: RensonVentilation = hass.data[DOMAIN][config_entry.entry_id].api
|
||||
coordinator: RensonCoordinator = hass.data[DOMAIN][
|
||||
config_entry.entry_id
|
||||
].coordinator
|
||||
api = config_entry.runtime_data.api
|
||||
coordinator = config_entry.runtime_data.coordinator
|
||||
|
||||
async_add_entities(
|
||||
RensonBinarySensor(description, api, coordinator)
|
||||
|
||||
@@ -12,13 +12,11 @@ from homeassistant.components.button import (
|
||||
ButtonEntity,
|
||||
ButtonEntityDescription,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import EntityCategory
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from . import RensonCoordinator, RensonData
|
||||
from .const import DOMAIN
|
||||
from .coordinator import RensonConfigEntry, RensonCoordinator
|
||||
from .entity import RensonEntity
|
||||
|
||||
|
||||
@@ -53,12 +51,12 @@ ENTITY_DESCRIPTIONS: tuple[RensonButtonEntityDescription, ...] = (
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
config_entry: RensonConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the Renson button platform."""
|
||||
|
||||
data: RensonData = hass.data[DOMAIN][config_entry.entry_id]
|
||||
data = config_entry.runtime_data
|
||||
|
||||
entities = [
|
||||
RensonButton(description, data.api, data.coordinator)
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from dataclasses import dataclass
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
from typing import Any
|
||||
@@ -15,18 +16,29 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
type RensonConfigEntry = ConfigEntry[RensonData]
|
||||
|
||||
|
||||
@dataclass
|
||||
class RensonData:
|
||||
"""Renson data class."""
|
||||
|
||||
api: RensonVentilation
|
||||
coordinator: RensonCoordinator
|
||||
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class RensonCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
"""Data update coordinator for Renson."""
|
||||
|
||||
config_entry: ConfigEntry
|
||||
config_entry: RensonConfigEntry
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
config_entry: RensonConfigEntry,
|
||||
api: RensonVentilation,
|
||||
) -> None:
|
||||
"""Initialize my coordinator."""
|
||||
|
||||
@@ -16,7 +16,6 @@ from renson_endura_delta.renson import Level, RensonVentilation
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.fan import FanEntity, FanEntityFeature
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers import config_validation as cv, entity_platform
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
@@ -27,8 +26,7 @@ from homeassistant.util.percentage import (
|
||||
)
|
||||
from homeassistant.util.scaling import int_states_in_range
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import RensonCoordinator
|
||||
from .coordinator import RensonConfigEntry, RensonCoordinator
|
||||
from .entity import RensonEntity
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@@ -84,15 +82,13 @@ SPEED_RANGE: tuple[float, float] = (1, 4)
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
config_entry: RensonConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the Renson fan platform."""
|
||||
|
||||
api: RensonVentilation = hass.data[DOMAIN][config_entry.entry_id].api
|
||||
coordinator: RensonCoordinator = hass.data[DOMAIN][
|
||||
config_entry.entry_id
|
||||
].coordinator
|
||||
api = config_entry.runtime_data.api
|
||||
coordinator = config_entry.runtime_data.coordinator
|
||||
|
||||
async_add_entities([RensonFan(api, coordinator)])
|
||||
|
||||
|
||||
@@ -12,13 +12,11 @@ from homeassistant.components.number import (
|
||||
NumberEntity,
|
||||
NumberEntityDescription,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import EntityCategory, UnitOfTime
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import RensonCoordinator
|
||||
from .coordinator import RensonConfigEntry, RensonCoordinator
|
||||
from .entity import RensonEntity
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@@ -39,15 +37,13 @@ RENSON_NUMBER_DESCRIPTION = NumberEntityDescription(
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
config_entry: RensonConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the Renson number platform."""
|
||||
|
||||
api: RensonVentilation = hass.data[DOMAIN][config_entry.entry_id].api
|
||||
coordinator: RensonCoordinator = hass.data[DOMAIN][
|
||||
config_entry.entry_id
|
||||
].coordinator
|
||||
api = config_entry.runtime_data.api
|
||||
coordinator = config_entry.runtime_data.coordinator
|
||||
|
||||
async_add_entities([RensonNumber(RENSON_NUMBER_DESCRIPTION, api, coordinator)])
|
||||
|
||||
|
||||
@@ -34,7 +34,6 @@ from homeassistant.components.sensor import (
|
||||
SensorEntityDescription,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
CONCENTRATION_PARTS_PER_MILLION,
|
||||
PERCENTAGE,
|
||||
@@ -45,9 +44,7 @@ from homeassistant.const import (
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from . import RensonData
|
||||
from .const import DOMAIN
|
||||
from .coordinator import RensonCoordinator
|
||||
from .coordinator import RensonConfigEntry, RensonCoordinator
|
||||
from .entity import RensonEntity
|
||||
|
||||
|
||||
@@ -271,12 +268,12 @@ class RensonSensor(RensonEntity, SensorEntity):
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
config_entry: RensonConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the Renson sensor platform."""
|
||||
|
||||
data: RensonData = hass.data[DOMAIN][config_entry.entry_id]
|
||||
data = config_entry.runtime_data
|
||||
|
||||
entities = [
|
||||
RensonSensor(description, data.api, data.coordinator) for description in SENSORS
|
||||
|
||||
@@ -9,12 +9,10 @@ from renson_endura_delta.field_enum import CURRENT_LEVEL_FIELD, DataType
|
||||
from renson_endura_delta.renson import Level, RensonVentilation
|
||||
|
||||
from homeassistant.components.switch import SwitchDeviceClass, SwitchEntity
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from . import RensonCoordinator
|
||||
from .const import DOMAIN
|
||||
from .coordinator import RensonConfigEntry, RensonCoordinator
|
||||
from .entity import RensonEntity
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@@ -67,14 +65,12 @@ class RensonBreezeSwitch(RensonEntity, SwitchEntity):
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
config_entry: RensonConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Call the Renson integration to setup."""
|
||||
|
||||
api: RensonVentilation = hass.data[DOMAIN][config_entry.entry_id].api
|
||||
coordinator: RensonCoordinator = hass.data[DOMAIN][
|
||||
config_entry.entry_id
|
||||
].coordinator
|
||||
api = config_entry.runtime_data.api
|
||||
coordinator = config_entry.runtime_data.coordinator
|
||||
|
||||
async_add_entities([RensonBreezeSwitch(api, coordinator)])
|
||||
|
||||
@@ -10,14 +10,11 @@ from renson_endura_delta.field_enum import DAYTIME_FIELD, NIGHTTIME_FIELD, Field
|
||||
from renson_endura_delta.renson import RensonVentilation
|
||||
|
||||
from homeassistant.components.time import TimeEntity, TimeEntityDescription
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import EntityCategory
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from . import RensonData
|
||||
from .const import DOMAIN
|
||||
from .coordinator import RensonCoordinator
|
||||
from .coordinator import RensonConfigEntry, RensonCoordinator
|
||||
from .entity import RensonEntity
|
||||
|
||||
|
||||
@@ -49,15 +46,14 @@ ENTITY_DESCRIPTIONS: tuple[RensonTimeEntityDescription, ...] = (
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
config_entry: RensonConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the Renson time platform."""
|
||||
|
||||
data: RensonData = hass.data[DOMAIN][config_entry.entry_id]
|
||||
|
||||
coordinator = config_entry.runtime_data.coordinator
|
||||
entities = [
|
||||
RensonTime(description, data.coordinator) for description in ENTITY_DESCRIPTIONS
|
||||
RensonTime(description, coordinator) for description in ENTITY_DESCRIPTIONS
|
||||
]
|
||||
|
||||
async_add_entities(entities)
|
||||
|
||||
@@ -9,17 +9,17 @@ from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
from .const import DOMAIN, LOGGER, SENSOR_TYPE_NEXT_PICKUP
|
||||
from .coordinator import RidwellDataUpdateCoordinator
|
||||
from .const import LOGGER, SENSOR_TYPE_NEXT_PICKUP
|
||||
from .coordinator import RidwellConfigEntry, RidwellDataUpdateCoordinator
|
||||
|
||||
PLATFORMS: list[Platform] = [Platform.CALENDAR, Platform.SENSOR, Platform.SWITCH]
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: RidwellConfigEntry) -> bool:
|
||||
"""Set up Ridwell from a config entry."""
|
||||
coordinator = RidwellDataUpdateCoordinator(hass, entry)
|
||||
await coordinator.async_initialize()
|
||||
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
|
||||
entry.runtime_data = coordinator
|
||||
entry.async_on_unload(entry.add_update_listener(options_update_listener))
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
@@ -27,17 +27,16 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
async def options_update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
||||
async def options_update_listener(
|
||||
hass: HomeAssistant, entry: RidwellConfigEntry
|
||||
) -> None:
|
||||
"""Handle options update."""
|
||||
await hass.config_entries.async_reload(entry.entry_id)
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: RidwellConfigEntry) -> bool:
|
||||
"""Unload a config entry."""
|
||||
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
|
||||
hass.data[DOMAIN].pop(entry.entry_id)
|
||||
|
||||
return unload_ok
|
||||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
|
||||
|
||||
async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
|
||||
@@ -7,7 +7,6 @@ import datetime
|
||||
from aioridwell.model import PickupCategory, RidwellAccount, RidwellPickupEvent
|
||||
|
||||
from homeassistant.components.calendar import CalendarEntity, CalendarEvent
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
@@ -16,15 +15,14 @@ from .const import (
|
||||
CALENDAR_TITLE_ROTATING,
|
||||
CALENDAR_TITLE_STATUS,
|
||||
CONF_CALENDAR_TITLE,
|
||||
DOMAIN,
|
||||
)
|
||||
from .coordinator import RidwellDataUpdateCoordinator
|
||||
from .coordinator import RidwellConfigEntry, RidwellDataUpdateCoordinator
|
||||
from .entity import RidwellEntity
|
||||
|
||||
|
||||
@callback
|
||||
def async_get_calendar_event_from_pickup_event(
|
||||
pickup_event: RidwellPickupEvent, config_entry: ConfigEntry
|
||||
pickup_event: RidwellPickupEvent, config_entry: RidwellConfigEntry
|
||||
) -> CalendarEvent:
|
||||
"""Get a HASS CalendarEvent from an aioridwell PickupEvent."""
|
||||
pickup_items = []
|
||||
@@ -66,11 +64,11 @@ def async_get_calendar_event_from_pickup_event(
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: ConfigEntry,
|
||||
entry: RidwellConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up Ridwell calendars based on a config entry."""
|
||||
coordinator: RidwellDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
|
||||
coordinator = entry.runtime_data
|
||||
|
||||
async_add_entities(
|
||||
RidwellCalendar(coordinator, account)
|
||||
|
||||
@@ -9,7 +9,7 @@ from aioridwell import async_get_client
|
||||
from aioridwell.errors import InvalidCredentialsError, RidwellError
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult
|
||||
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
|
||||
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers import aiohttp_client, config_validation as cv, selector
|
||||
@@ -19,6 +19,7 @@ from homeassistant.helpers.schema_config_entry_flow import (
|
||||
)
|
||||
|
||||
from .const import CALENDAR_TITLE_OPTIONS, CONF_CALENDAR_TITLE, DOMAIN, LOGGER
|
||||
from .coordinator import RidwellConfigEntry
|
||||
|
||||
STEP_REAUTH_CONFIRM_DATA_SCHEMA = vol.Schema(
|
||||
{
|
||||
@@ -107,7 +108,7 @@ class RidwellConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
@staticmethod
|
||||
@callback
|
||||
def async_get_options_flow(
|
||||
config_entry: ConfigEntry,
|
||||
config_entry: RidwellConfigEntry,
|
||||
) -> SchemaOptionsFlowHandler:
|
||||
"""Get options flow for this handler."""
|
||||
try:
|
||||
|
||||
@@ -19,6 +19,8 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, Upda
|
||||
|
||||
from .const import LOGGER
|
||||
|
||||
type RidwellConfigEntry = ConfigEntry[RidwellDataUpdateCoordinator]
|
||||
|
||||
UPDATE_INTERVAL = timedelta(hours=1)
|
||||
|
||||
|
||||
@@ -27,9 +29,9 @@ class RidwellDataUpdateCoordinator(
|
||||
):
|
||||
"""Class to manage fetching data from single endpoint."""
|
||||
|
||||
config_entry: ConfigEntry
|
||||
config_entry: RidwellConfigEntry
|
||||
|
||||
def __init__(self, hass: HomeAssistant, config_entry: ConfigEntry) -> None:
|
||||
def __init__(self, hass: HomeAssistant, config_entry: RidwellConfigEntry) -> None:
|
||||
"""Initialize."""
|
||||
# These will be filled in by async_initialize; we give them these defaults to
|
||||
# avoid arduous typing checks down the line:
|
||||
|
||||
@@ -6,12 +6,10 @@ import dataclasses
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.components.diagnostics import async_redact_data
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_PASSWORD, CONF_UNIQUE_ID, CONF_USERNAME
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import RidwellDataUpdateCoordinator
|
||||
from .coordinator import RidwellConfigEntry
|
||||
|
||||
CONF_TITLE = "title"
|
||||
|
||||
@@ -25,17 +23,15 @@ TO_REDACT = {
|
||||
|
||||
|
||||
async def async_get_config_entry_diagnostics(
|
||||
hass: HomeAssistant, entry: ConfigEntry
|
||||
hass: HomeAssistant, entry: RidwellConfigEntry
|
||||
) -> dict[str, Any]:
|
||||
"""Return diagnostics for a config entry."""
|
||||
coordinator: RidwellDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
|
||||
|
||||
return async_redact_data(
|
||||
{
|
||||
"entry": entry.as_dict(),
|
||||
"data": [
|
||||
dataclasses.asdict(event)
|
||||
for events in coordinator.data.values()
|
||||
for events in entry.runtime_data.data.values()
|
||||
for event in events
|
||||
],
|
||||
},
|
||||
|
||||
@@ -13,12 +13,11 @@ from homeassistant.components.sensor import (
|
||||
SensorEntity,
|
||||
SensorEntityDescription,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from .const import DOMAIN, SENSOR_TYPE_NEXT_PICKUP
|
||||
from .coordinator import RidwellDataUpdateCoordinator
|
||||
from .const import SENSOR_TYPE_NEXT_PICKUP
|
||||
from .coordinator import RidwellConfigEntry, RidwellDataUpdateCoordinator
|
||||
from .entity import RidwellEntity
|
||||
|
||||
ATTR_CATEGORY = "category"
|
||||
@@ -35,11 +34,11 @@ SENSOR_DESCRIPTION = SensorEntityDescription(
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: ConfigEntry,
|
||||
entry: RidwellConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up Ridwell sensors based on a config entry."""
|
||||
coordinator: RidwellDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
|
||||
coordinator = entry.runtime_data
|
||||
|
||||
async_add_entities(
|
||||
RidwellSensor(coordinator, account, SENSOR_DESCRIPTION)
|
||||
|
||||
@@ -8,13 +8,11 @@ from aioridwell.errors import RidwellError
|
||||
from aioridwell.model import EventState, RidwellAccount
|
||||
|
||||
from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import RidwellDataUpdateCoordinator
|
||||
from .coordinator import RidwellConfigEntry, RidwellDataUpdateCoordinator
|
||||
from .entity import RidwellEntity
|
||||
|
||||
SWITCH_DESCRIPTION = SwitchEntityDescription(
|
||||
@@ -25,11 +23,11 @@ SWITCH_DESCRIPTION = SwitchEntityDescription(
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: ConfigEntry,
|
||||
entry: RidwellConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up Ridwell sensors based on a config entry."""
|
||||
coordinator: RidwellDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
|
||||
coordinator = entry.runtime_data
|
||||
|
||||
async_add_entities(
|
||||
RidwellSwitch(coordinator, account, SWITCH_DESCRIPTION)
|
||||
|
||||
@@ -7,6 +7,7 @@ from ring_doorbell import RingCapability, RingEvent as RingAlert
|
||||
from ring_doorbell.const import KIND_DING, KIND_INTERCOM_UNLOCK, KIND_MOTION
|
||||
|
||||
from homeassistant.components.event import (
|
||||
DoorbellEventType,
|
||||
EventDeviceClass,
|
||||
EventEntity,
|
||||
EventEntityDescription,
|
||||
@@ -34,7 +35,7 @@ EVENT_DESCRIPTIONS: tuple[RingEventEntityDescription, ...] = (
|
||||
key=KIND_DING,
|
||||
translation_key=KIND_DING,
|
||||
device_class=EventDeviceClass.DOORBELL,
|
||||
event_types=[KIND_DING],
|
||||
event_types=[DoorbellEventType.RING],
|
||||
capability=RingCapability.DING,
|
||||
),
|
||||
RingEventEntityDescription(
|
||||
@@ -100,7 +101,10 @@ class RingEvent(RingBaseEntity[RingListenCoordinator, RingDeviceT], EventEntity)
|
||||
@callback
|
||||
def _handle_coordinator_update(self) -> None:
|
||||
if (alert := self._get_coordinator_alert()) and not alert.is_update:
|
||||
self._async_handle_event(alert.kind)
|
||||
if alert.kind == KIND_DING:
|
||||
self._async_handle_event(DoorbellEventType.RING)
|
||||
else:
|
||||
self._async_handle_event(alert.kind)
|
||||
super()._handle_coordinator_update()
|
||||
|
||||
@property
|
||||
|
||||
@@ -73,7 +73,14 @@
|
||||
},
|
||||
"event": {
|
||||
"ding": {
|
||||
"name": "Ding"
|
||||
"name": "Ding",
|
||||
"state_attributes": {
|
||||
"event_type": {
|
||||
"state": {
|
||||
"ring": "[%key:component::event::entity_component::doorbell::state_attributes::event_type::state::ring%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"intercom_unlock": {
|
||||
"name": "Intercom unlock"
|
||||
|
||||
@@ -26,15 +26,13 @@ from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from .const import (
|
||||
CONF_CONCURRENCY,
|
||||
DATA_COORDINATOR,
|
||||
DEFAULT_CONCURRENCY,
|
||||
DOMAIN,
|
||||
EVENTS_COORDINATOR,
|
||||
SYSTEM_UPDATE_SIGNAL,
|
||||
TYPE_LOCAL,
|
||||
)
|
||||
from .coordinator import RiscoDataUpdateCoordinator, RiscoEventsDataUpdateCoordinator
|
||||
from .models import LocalData
|
||||
from .models import CloudData, LocalData, RiscoConfigEntry, RiscoData
|
||||
from .services import async_setup_services
|
||||
|
||||
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
|
||||
@@ -58,7 +56,7 @@ def zone_update_signal(zone_id: int) -> str:
|
||||
return f"risco_zone_update_{zone_id}"
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: RiscoConfigEntry) -> bool:
|
||||
"""Set up Risco from a config entry."""
|
||||
if is_local(entry):
|
||||
return await _async_setup_local_entry(hass, entry)
|
||||
@@ -66,7 +64,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
return await _async_setup_cloud_entry(hass, entry)
|
||||
|
||||
|
||||
async def _async_setup_local_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
async def _async_setup_local_entry(
|
||||
hass: HomeAssistant, entry: RiscoConfigEntry
|
||||
) -> bool:
|
||||
data = entry.data
|
||||
concurrency = entry.options.get(CONF_CONCURRENCY, DEFAULT_CONCURRENCY)
|
||||
risco = RiscoLocal(
|
||||
@@ -120,14 +120,15 @@ async def _async_setup_local_entry(hass: HomeAssistant, entry: ConfigEntry) -> b
|
||||
|
||||
entry.async_on_unload(entry.add_update_listener(_update_listener))
|
||||
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
hass.data[DOMAIN][entry.entry_id] = local_data
|
||||
entry.runtime_data = RiscoData(local_data=local_data)
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def _async_setup_cloud_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
async def _async_setup_cloud_entry(
|
||||
hass: HomeAssistant, entry: RiscoConfigEntry
|
||||
) -> bool:
|
||||
data = entry.data
|
||||
risco = RiscoCloud(data[CONF_USERNAME], data[CONF_PASSWORD], data[CONF_PIN])
|
||||
try:
|
||||
@@ -143,11 +144,12 @@ async def _async_setup_cloud_entry(hass: HomeAssistant, entry: ConfigEntry) -> b
|
||||
|
||||
entry.async_on_unload(entry.add_update_listener(_update_listener))
|
||||
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
hass.data[DOMAIN][entry.entry_id] = {
|
||||
DATA_COORDINATOR: coordinator,
|
||||
EVENTS_COORDINATOR: events_coordinator,
|
||||
}
|
||||
entry.runtime_data = RiscoData(
|
||||
cloud_data=CloudData(
|
||||
coordinator=coordinator,
|
||||
events_coordinator=events_coordinator,
|
||||
)
|
||||
)
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
await events_coordinator.async_refresh()
|
||||
@@ -155,20 +157,16 @@ async def _async_setup_cloud_entry(hass: HomeAssistant, entry: ConfigEntry) -> b
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: RiscoConfigEntry) -> bool:
|
||||
"""Unload a config entry."""
|
||||
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
if unload_ok:
|
||||
if is_local(entry):
|
||||
local_data: LocalData = hass.data[DOMAIN][entry.entry_id]
|
||||
await local_data.system.disconnect()
|
||||
|
||||
hass.data[DOMAIN].pop(entry.entry_id)
|
||||
if unload_ok and (local_data := entry.runtime_data.local_data):
|
||||
await local_data.system.disconnect()
|
||||
|
||||
return unload_ok
|
||||
|
||||
|
||||
async def _update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
||||
async def _update_listener(hass: HomeAssistant, entry: RiscoConfigEntry) -> None:
|
||||
"""Handle options update."""
|
||||
await hass.config_entries.async_reload(entry.entry_id)
|
||||
|
||||
|
||||
@@ -15,19 +15,16 @@ from homeassistant.components.alarm_control_panel import (
|
||||
AlarmControlPanelState,
|
||||
CodeFormat,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_PIN
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from . import LocalData, is_local
|
||||
from .const import (
|
||||
CONF_CODE_ARM_REQUIRED,
|
||||
CONF_CODE_DISARM_REQUIRED,
|
||||
CONF_HA_STATES_TO_RISCO,
|
||||
CONF_RISCO_STATES_TO_HA,
|
||||
DATA_COORDINATOR,
|
||||
DEFAULT_OPTIONS,
|
||||
DOMAIN,
|
||||
RISCO_ARM,
|
||||
@@ -36,6 +33,7 @@ from .const import (
|
||||
)
|
||||
from .coordinator import RiscoDataUpdateCoordinator
|
||||
from .entity import RiscoCloudEntity
|
||||
from .models import RiscoConfigEntry
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -49,13 +47,13 @@ STATES_TO_SUPPORTED_FEATURES = {
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
config_entry: RiscoConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the Risco alarm control panel."""
|
||||
options = {**DEFAULT_OPTIONS, **config_entry.options}
|
||||
if is_local(config_entry):
|
||||
local_data: LocalData = hass.data[DOMAIN][config_entry.entry_id]
|
||||
risco_data = config_entry.runtime_data
|
||||
if local_data := risco_data.local_data:
|
||||
async_add_entities(
|
||||
RiscoLocalAlarm(
|
||||
local_data.system.id,
|
||||
@@ -67,10 +65,8 @@ async def async_setup_entry(
|
||||
)
|
||||
for partition_id, partition in local_data.system.partitions.items()
|
||||
)
|
||||
else:
|
||||
coordinator: RiscoDataUpdateCoordinator = hass.data[DOMAIN][
|
||||
config_entry.entry_id
|
||||
][DATA_COORDINATOR]
|
||||
elif cloud_data := risco_data.cloud_data:
|
||||
coordinator = cloud_data.coordinator
|
||||
async_add_entities(
|
||||
RiscoCloudAlarm(
|
||||
coordinator, partition_id, config_entry.data[CONF_PIN], options
|
||||
|
||||
@@ -15,16 +15,15 @@ from homeassistant.components.binary_sensor import (
|
||||
BinarySensorEntity,
|
||||
BinarySensorEntityDescription,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from . import LocalData, is_local
|
||||
from .const import DATA_COORDINATOR, DOMAIN, SYSTEM_UPDATE_SIGNAL
|
||||
from .const import DOMAIN, SYSTEM_UPDATE_SIGNAL
|
||||
from .coordinator import RiscoDataUpdateCoordinator
|
||||
from .entity import RiscoCloudZoneEntity, RiscoLocalZoneEntity
|
||||
from .models import RiscoConfigEntry
|
||||
|
||||
SYSTEM_ENTITY_DESCRIPTIONS = [
|
||||
BinarySensorEntityDescription(
|
||||
@@ -72,12 +71,12 @@ SYSTEM_ENTITY_DESCRIPTIONS = [
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
config_entry: RiscoConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the Risco alarm control panel."""
|
||||
if is_local(config_entry):
|
||||
local_data: LocalData = hass.data[DOMAIN][config_entry.entry_id]
|
||||
risco_data = config_entry.runtime_data
|
||||
if local_data := risco_data.local_data:
|
||||
zone_entities = (
|
||||
entity
|
||||
for zone_id, zone in local_data.system.zones.items()
|
||||
@@ -96,10 +95,8 @@ async def async_setup_entry(
|
||||
)
|
||||
|
||||
async_add_entities(chain(system_entities, zone_entities))
|
||||
else:
|
||||
coordinator: RiscoDataUpdateCoordinator = hass.data[DOMAIN][
|
||||
config_entry.entry_id
|
||||
][DATA_COORDINATOR]
|
||||
elif cloud_data := risco_data.cloud_data:
|
||||
coordinator = cloud_data.coordinator
|
||||
async_add_entities(
|
||||
RiscoCloudBinarySensor(coordinator, zone_id, zone)
|
||||
for zone_id, zone in coordinator.data.zones.items()
|
||||
|
||||
@@ -10,12 +10,7 @@ from pyrisco import CannotConnectError, RiscoCloud, RiscoLocal, UnauthorizedErro
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.alarm_control_panel import AlarmControlPanelState
|
||||
from homeassistant.config_entries import (
|
||||
ConfigEntry,
|
||||
ConfigFlow,
|
||||
ConfigFlowResult,
|
||||
OptionsFlow,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult, OptionsFlow
|
||||
from homeassistant.const import (
|
||||
CONF_HOST,
|
||||
CONF_PASSWORD,
|
||||
@@ -42,6 +37,7 @@ from .const import (
|
||||
RISCO_STATES,
|
||||
TYPE_LOCAL,
|
||||
)
|
||||
from .models import RiscoConfigEntry
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -121,12 +117,12 @@ class RiscoConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Init the config flow."""
|
||||
self._reauth_entry: ConfigEntry | None = None
|
||||
self._reauth_entry: RiscoConfigEntry | None = None
|
||||
|
||||
@staticmethod
|
||||
@callback
|
||||
def async_get_options_flow(
|
||||
config_entry: ConfigEntry,
|
||||
config_entry: RiscoConfigEntry,
|
||||
) -> RiscoOptionsFlowHandler:
|
||||
"""Define the config flow to handle options."""
|
||||
return RiscoOptionsFlowHandler(config_entry)
|
||||
@@ -218,7 +214,7 @@ class RiscoConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
class RiscoOptionsFlowHandler(OptionsFlow):
|
||||
"""Handle a Risco options flow."""
|
||||
|
||||
def __init__(self, config_entry: ConfigEntry) -> None:
|
||||
def __init__(self, config_entry: RiscoConfigEntry) -> None:
|
||||
"""Initialize."""
|
||||
self._data = {**DEFAULT_OPTIONS, **config_entry.options}
|
||||
|
||||
|
||||
@@ -1,11 +1,39 @@
|
||||
"""Models for Risco integration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from pyrisco import RiscoLocal
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .coordinator import (
|
||||
RiscoDataUpdateCoordinator,
|
||||
RiscoEventsDataUpdateCoordinator,
|
||||
)
|
||||
|
||||
type RiscoConfigEntry = ConfigEntry[RiscoData]
|
||||
|
||||
|
||||
@dataclass
|
||||
class RiscoData:
|
||||
"""Runtime data for the Risco integration."""
|
||||
|
||||
local_data: LocalData | None = None
|
||||
cloud_data: CloudData | None = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class CloudData:
|
||||
"""A data class for cloud data passed to the platforms."""
|
||||
|
||||
coordinator: RiscoDataUpdateCoordinator
|
||||
events_coordinator: RiscoEventsDataUpdateCoordinator
|
||||
|
||||
|
||||
@dataclass
|
||||
class LocalData:
|
||||
|
||||
@@ -10,17 +10,16 @@ from pyrisco.cloud.event import Event
|
||||
|
||||
from homeassistant.components.binary_sensor import DOMAIN as BS_DOMAIN
|
||||
from homeassistant.components.sensor import SensorDeviceClass, SensorEntity
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from . import is_local
|
||||
from .const import DOMAIN, EVENTS_COORDINATOR
|
||||
from .const import DOMAIN
|
||||
from .coordinator import RiscoEventsDataUpdateCoordinator
|
||||
from .entity import zone_unique_id
|
||||
from .models import RiscoConfigEntry
|
||||
|
||||
CATEGORIES = {
|
||||
2: "Alarm",
|
||||
@@ -45,17 +44,15 @@ EVENT_ATTRIBUTES = [
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
config_entry: RiscoConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up sensors for device."""
|
||||
if is_local(config_entry):
|
||||
if not (cloud_data := config_entry.runtime_data.cloud_data):
|
||||
# no events in local comm
|
||||
return
|
||||
|
||||
coordinator: RiscoEventsDataUpdateCoordinator = hass.data[DOMAIN][
|
||||
config_entry.entry_id
|
||||
][EVENTS_COORDINATOR]
|
||||
coordinator = cloud_data.events_coordinator
|
||||
sensors = [
|
||||
RiscoSensor(coordinator, category_id, [], name, config_entry.entry_id)
|
||||
for category_id, name in CATEGORIES.items()
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user