Refactor Shelly wrapper to coordinator (#79628)
parent
4d3d22320f
commit
22c68b95bf
|
@ -36,10 +36,10 @@ from .const import (
|
|||
RPC_POLL,
|
||||
)
|
||||
from .coordinator import (
|
||||
BlockDeviceWrapper,
|
||||
RpcDeviceWrapper,
|
||||
RpcPollingWrapper,
|
||||
ShellyDeviceRestWrapper,
|
||||
ShellyBlockCoordinator,
|
||||
ShellyRestCoordinator,
|
||||
ShellyRpcCoordinator,
|
||||
ShellyRpcPollingCoordinator,
|
||||
)
|
||||
from .utils import get_block_device_sleep_period, get_coap_context, get_device_entry_gen
|
||||
|
||||
|
@ -200,17 +200,17 @@ def async_block_device_setup(
|
|||
hass: HomeAssistant, entry: ConfigEntry, device: BlockDevice
|
||||
) -> None:
|
||||
"""Set up a block based device that is online."""
|
||||
device_wrapper = hass.data[DOMAIN][DATA_CONFIG_ENTRY][entry.entry_id][
|
||||
block_coordinator = hass.data[DOMAIN][DATA_CONFIG_ENTRY][entry.entry_id][
|
||||
BLOCK
|
||||
] = BlockDeviceWrapper(hass, entry, device)
|
||||
device_wrapper.async_setup()
|
||||
] = ShellyBlockCoordinator(hass, entry, device)
|
||||
block_coordinator.async_setup()
|
||||
|
||||
platforms = BLOCK_SLEEPING_PLATFORMS
|
||||
|
||||
if not entry.data.get(CONF_SLEEP_PERIOD):
|
||||
hass.data[DOMAIN][DATA_CONFIG_ENTRY][entry.entry_id][
|
||||
REST
|
||||
] = ShellyDeviceRestWrapper(hass, device, entry)
|
||||
] = ShellyRestCoordinator(hass, device, entry)
|
||||
platforms = BLOCK_PLATFORMS
|
||||
|
||||
hass.config_entries.async_setup_platforms(entry, platforms)
|
||||
|
@ -237,14 +237,14 @@ async def async_setup_rpc_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool
|
|||
except (AuthRequired, InvalidAuthError) as err:
|
||||
raise ConfigEntryAuthFailed from err
|
||||
|
||||
device_wrapper = hass.data[DOMAIN][DATA_CONFIG_ENTRY][entry.entry_id][
|
||||
rpc_coordinator = hass.data[DOMAIN][DATA_CONFIG_ENTRY][entry.entry_id][
|
||||
RPC
|
||||
] = RpcDeviceWrapper(hass, entry, device)
|
||||
device_wrapper.async_setup()
|
||||
] = ShellyRpcCoordinator(hass, entry, device)
|
||||
rpc_coordinator.async_setup()
|
||||
|
||||
hass.data[DOMAIN][DATA_CONFIG_ENTRY][entry.entry_id][RPC_POLL] = RpcPollingWrapper(
|
||||
hass, entry, device
|
||||
)
|
||||
hass.data[DOMAIN][DATA_CONFIG_ENTRY][entry.entry_id][
|
||||
RPC_POLL
|
||||
] = ShellyRpcPollingCoordinator(hass, entry, device)
|
||||
|
||||
hass.config_entries.async_setup_platforms(entry, RPC_PLATFORMS)
|
||||
|
||||
|
@ -265,7 +265,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||
|
||||
device = hass.data[DOMAIN][DATA_CONFIG_ENTRY][entry.entry_id].get(DEVICE)
|
||||
if device is not None:
|
||||
# If device is present, device wrapper is not setup yet
|
||||
# If device is present, block coordinator is not setup yet
|
||||
device.shutdown()
|
||||
return True
|
||||
|
||||
|
@ -283,10 +283,10 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||
return unload_ok
|
||||
|
||||
|
||||
def get_block_device_wrapper(
|
||||
def get_block_device_coordinator(
|
||||
hass: HomeAssistant, device_id: str
|
||||
) -> BlockDeviceWrapper | None:
|
||||
"""Get a Shelly block device wrapper for the given device id."""
|
||||
) -> ShellyBlockCoordinator | None:
|
||||
"""Get a Shelly block device coordinator for the given device id."""
|
||||
if not hass.data.get(DOMAIN):
|
||||
return None
|
||||
|
||||
|
@ -296,16 +296,18 @@ def get_block_device_wrapper(
|
|||
if not hass.data[DOMAIN][DATA_CONFIG_ENTRY].get(config_entry):
|
||||
continue
|
||||
|
||||
if wrapper := hass.data[DOMAIN][DATA_CONFIG_ENTRY][config_entry].get(BLOCK):
|
||||
return cast(BlockDeviceWrapper, wrapper)
|
||||
if coordinator := hass.data[DOMAIN][DATA_CONFIG_ENTRY][config_entry].get(
|
||||
BLOCK
|
||||
):
|
||||
return cast(ShellyBlockCoordinator, coordinator)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def get_rpc_device_wrapper(
|
||||
def get_rpc_device_coordinator(
|
||||
hass: HomeAssistant, device_id: str
|
||||
) -> RpcDeviceWrapper | None:
|
||||
"""Get a Shelly RPC device wrapper for the given device id."""
|
||||
) -> ShellyRpcCoordinator | None:
|
||||
"""Get a Shelly RPC device coordinator for the given device id."""
|
||||
if not hass.data.get(DOMAIN):
|
||||
return None
|
||||
|
||||
|
@ -315,7 +317,9 @@ def get_rpc_device_wrapper(
|
|||
if not hass.data[DOMAIN][DATA_CONFIG_ENTRY].get(config_entry):
|
||||
continue
|
||||
|
||||
if wrapper := hass.data[DOMAIN][DATA_CONFIG_ENTRY][config_entry].get(RPC):
|
||||
return cast(RpcDeviceWrapper, wrapper)
|
||||
if coordinator := hass.data[DOMAIN][DATA_CONFIG_ENTRY][config_entry].get(
|
||||
RPC
|
||||
):
|
||||
return cast(ShellyRpcCoordinator, coordinator)
|
||||
|
||||
return None
|
||||
|
|
|
@ -15,10 +15,11 @@ from homeassistant.core import HomeAssistant
|
|||
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
|
||||
from homeassistant.helpers.entity import DeviceInfo, EntityCategory
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
from homeassistant.util import slugify
|
||||
|
||||
from . import BlockDeviceWrapper, RpcDeviceWrapper
|
||||
from .const import BLOCK, DATA_CONFIG_ENTRY, DOMAIN, RPC, SHELLY_GAS_MODELS
|
||||
from .coordinator import ShellyBlockCoordinator, ShellyRpcCoordinator
|
||||
from .utils import get_block_device_name, get_device_entry_gen, get_rpc_device_name
|
||||
|
||||
|
||||
|
@ -42,31 +43,31 @@ BUTTONS: Final = [
|
|||
name="Reboot",
|
||||
device_class=ButtonDeviceClass.RESTART,
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
press_action=lambda wrapper: wrapper.device.trigger_reboot(),
|
||||
press_action=lambda coordinator: coordinator.device.trigger_reboot(),
|
||||
),
|
||||
ShellyButtonDescription(
|
||||
key="self_test",
|
||||
name="Self Test",
|
||||
icon="mdi:progress-wrench",
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
press_action=lambda wrapper: wrapper.device.trigger_shelly_gas_self_test(),
|
||||
supported=lambda wrapper: wrapper.device.model in SHELLY_GAS_MODELS,
|
||||
press_action=lambda coordinator: coordinator.device.trigger_shelly_gas_self_test(),
|
||||
supported=lambda coordinator: coordinator.device.model in SHELLY_GAS_MODELS,
|
||||
),
|
||||
ShellyButtonDescription(
|
||||
key="mute",
|
||||
name="Mute",
|
||||
icon="mdi:volume-mute",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
press_action=lambda wrapper: wrapper.device.trigger_shelly_gas_mute(),
|
||||
supported=lambda wrapper: wrapper.device.model in SHELLY_GAS_MODELS,
|
||||
press_action=lambda coordinator: coordinator.device.trigger_shelly_gas_mute(),
|
||||
supported=lambda coordinator: coordinator.device.model in SHELLY_GAS_MODELS,
|
||||
),
|
||||
ShellyButtonDescription(
|
||||
key="unmute",
|
||||
name="Unmute",
|
||||
icon="mdi:volume-high",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
press_action=lambda wrapper: wrapper.device.trigger_shelly_gas_unmute(),
|
||||
supported=lambda wrapper: wrapper.device.model in SHELLY_GAS_MODELS,
|
||||
press_action=lambda coordinator: coordinator.device.trigger_shelly_gas_unmute(),
|
||||
supported=lambda coordinator: coordinator.device.model in SHELLY_GAS_MODELS,
|
||||
),
|
||||
]
|
||||
|
||||
|
@ -77,54 +78,54 @@ async def async_setup_entry(
|
|||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set buttons for device."""
|
||||
wrapper: RpcDeviceWrapper | BlockDeviceWrapper | None = None
|
||||
coordinator: ShellyRpcCoordinator | ShellyBlockCoordinator | None = None
|
||||
if get_device_entry_gen(config_entry) == 2:
|
||||
if rpc_wrapper := hass.data[DOMAIN][DATA_CONFIG_ENTRY][
|
||||
if rpc_coordinator := hass.data[DOMAIN][DATA_CONFIG_ENTRY][
|
||||
config_entry.entry_id
|
||||
].get(RPC):
|
||||
wrapper = cast(RpcDeviceWrapper, rpc_wrapper)
|
||||
coordinator = cast(ShellyRpcCoordinator, rpc_coordinator)
|
||||
else:
|
||||
if block_wrapper := hass.data[DOMAIN][DATA_CONFIG_ENTRY][
|
||||
if block_coordinator := hass.data[DOMAIN][DATA_CONFIG_ENTRY][
|
||||
config_entry.entry_id
|
||||
].get(BLOCK):
|
||||
wrapper = cast(BlockDeviceWrapper, block_wrapper)
|
||||
coordinator = cast(ShellyBlockCoordinator, block_coordinator)
|
||||
|
||||
if wrapper is not None:
|
||||
if coordinator is not None:
|
||||
entities = []
|
||||
|
||||
for button in BUTTONS:
|
||||
if not button.supported(wrapper):
|
||||
if not button.supported(coordinator):
|
||||
continue
|
||||
entities.append(ShellyButton(wrapper, button))
|
||||
entities.append(ShellyButton(coordinator, button))
|
||||
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
class ShellyButton(ButtonEntity):
|
||||
class ShellyButton(CoordinatorEntity, ButtonEntity):
|
||||
"""Defines a Shelly base button."""
|
||||
|
||||
entity_description: ShellyButtonDescription
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
wrapper: RpcDeviceWrapper | BlockDeviceWrapper,
|
||||
coordinator: ShellyRpcCoordinator | ShellyBlockCoordinator,
|
||||
description: ShellyButtonDescription,
|
||||
) -> None:
|
||||
"""Initialize Shelly button."""
|
||||
super().__init__(coordinator)
|
||||
self.entity_description = description
|
||||
self.wrapper = wrapper
|
||||
|
||||
if isinstance(wrapper, RpcDeviceWrapper):
|
||||
device_name = get_rpc_device_name(wrapper.device)
|
||||
if isinstance(coordinator, ShellyRpcCoordinator):
|
||||
device_name = get_rpc_device_name(coordinator.device)
|
||||
else:
|
||||
device_name = get_block_device_name(wrapper.device)
|
||||
device_name = get_block_device_name(coordinator.device)
|
||||
|
||||
self._attr_name = f"{device_name} {description.name}"
|
||||
self._attr_unique_id = slugify(self._attr_name)
|
||||
self._attr_device_info = DeviceInfo(
|
||||
connections={(CONNECTION_NETWORK_MAC, wrapper.mac)}
|
||||
connections={(CONNECTION_NETWORK_MAC, coordinator.mac)}
|
||||
)
|
||||
|
||||
async def async_press(self) -> None:
|
||||
"""Triggers the Shelly button press service."""
|
||||
await self.entity_description.press_action(self.wrapper)
|
||||
await self.entity_description.press_action(self.coordinator)
|
||||
|
|
|
@ -20,12 +20,12 @@ from homeassistant.components.climate import (
|
|||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
|
||||
from homeassistant.core import HomeAssistant, State, callback
|
||||
from homeassistant.helpers import device_registry, entity_registry, update_coordinator
|
||||
from homeassistant.helpers import device_registry, entity_registry
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.restore_state import RestoreEntity
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from . import BlockDeviceWrapper
|
||||
from .const import (
|
||||
AIOSHELLY_DEVICE_TIMEOUT_SEC,
|
||||
BLOCK,
|
||||
|
@ -34,6 +34,7 @@ from .const import (
|
|||
LOGGER,
|
||||
SHTRV_01_TEMPERATURE_SETTINGS,
|
||||
)
|
||||
from .coordinator import ShellyBlockCoordinator
|
||||
from .utils import get_device_entry_gen
|
||||
|
||||
|
||||
|
@ -47,37 +48,41 @@ async def async_setup_entry(
|
|||
if get_device_entry_gen(config_entry) == 2:
|
||||
return
|
||||
|
||||
wrapper: BlockDeviceWrapper = hass.data[DOMAIN][DATA_CONFIG_ENTRY][
|
||||
coordinator: ShellyBlockCoordinator = hass.data[DOMAIN][DATA_CONFIG_ENTRY][
|
||||
config_entry.entry_id
|
||||
][BLOCK]
|
||||
|
||||
if wrapper.device.initialized:
|
||||
async_setup_climate_entities(async_add_entities, wrapper)
|
||||
if coordinator.device.initialized:
|
||||
async_setup_climate_entities(async_add_entities, coordinator)
|
||||
else:
|
||||
async_restore_climate_entities(hass, config_entry, async_add_entities, wrapper)
|
||||
async_restore_climate_entities(
|
||||
hass, config_entry, async_add_entities, coordinator
|
||||
)
|
||||
|
||||
|
||||
@callback
|
||||
def async_setup_climate_entities(
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
wrapper: BlockDeviceWrapper,
|
||||
coordinator: ShellyBlockCoordinator,
|
||||
) -> None:
|
||||
"""Set up online climate devices."""
|
||||
|
||||
device_block: Block | None = None
|
||||
sensor_block: Block | None = None
|
||||
|
||||
assert wrapper.device.blocks
|
||||
assert coordinator.device.blocks
|
||||
|
||||
for block in wrapper.device.blocks:
|
||||
for block in coordinator.device.blocks:
|
||||
if block.type == "device":
|
||||
device_block = block
|
||||
if hasattr(block, "targetTemp"):
|
||||
sensor_block = block
|
||||
|
||||
if sensor_block and device_block:
|
||||
LOGGER.debug("Setup online climate device %s", wrapper.name)
|
||||
async_add_entities([BlockSleepingClimate(wrapper, sensor_block, device_block)])
|
||||
LOGGER.debug("Setup online climate device %s", coordinator.name)
|
||||
async_add_entities(
|
||||
[BlockSleepingClimate(coordinator, sensor_block, device_block)]
|
||||
)
|
||||
|
||||
|
||||
@callback
|
||||
|
@ -85,7 +90,7 @@ def async_restore_climate_entities(
|
|||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
wrapper: BlockDeviceWrapper,
|
||||
coordinator: ShellyBlockCoordinator,
|
||||
) -> None:
|
||||
"""Restore sleeping climate devices."""
|
||||
|
||||
|
@ -99,16 +104,14 @@ def async_restore_climate_entities(
|
|||
if entry.domain != CLIMATE_DOMAIN:
|
||||
continue
|
||||
|
||||
LOGGER.debug("Setup sleeping climate device %s", wrapper.name)
|
||||
LOGGER.debug("Setup sleeping climate device %s", coordinator.name)
|
||||
LOGGER.debug("Found entry %s [%s]", entry.original_name, entry.domain)
|
||||
async_add_entities([BlockSleepingClimate(wrapper, None, None, entry)])
|
||||
async_add_entities([BlockSleepingClimate(coordinator, None, None, entry)])
|
||||
break
|
||||
|
||||
|
||||
class BlockSleepingClimate(
|
||||
update_coordinator.CoordinatorEntity,
|
||||
RestoreEntity,
|
||||
ClimateEntity,
|
||||
CoordinatorEntity[ShellyBlockCoordinator], RestoreEntity, ClimateEntity
|
||||
):
|
||||
"""Representation of a Shelly climate device."""
|
||||
|
||||
|
@ -124,16 +127,14 @@ class BlockSleepingClimate(
|
|||
|
||||
def __init__(
|
||||
self,
|
||||
wrapper: BlockDeviceWrapper,
|
||||
coordinator: ShellyBlockCoordinator,
|
||||
sensor_block: Block | None,
|
||||
device_block: Block | None,
|
||||
entry: entity_registry.RegistryEntry | None = None,
|
||||
) -> None:
|
||||
"""Initialize climate."""
|
||||
super().__init__(coordinator)
|
||||
|
||||
super().__init__(wrapper)
|
||||
|
||||
self.wrapper = wrapper
|
||||
self.block: Block | None = sensor_block
|
||||
self.control_result: dict[str, Any] | None = None
|
||||
self.device_block: Block | None = device_block
|
||||
|
@ -142,11 +143,11 @@ class BlockSleepingClimate(
|
|||
self._preset_modes: list[str] = []
|
||||
|
||||
if self.block is not None and self.device_block is not None:
|
||||
self._unique_id = f"{self.wrapper.mac}-{self.block.description}"
|
||||
self._unique_id = f"{self.coordinator.mac}-{self.block.description}"
|
||||
assert self.block.channel
|
||||
self._preset_modes = [
|
||||
PRESET_NONE,
|
||||
*wrapper.device.settings["thermostats"][int(self.block.channel)][
|
||||
*coordinator.device.settings["thermostats"][int(self.block.channel)][
|
||||
"schedule_profile_names"
|
||||
],
|
||||
]
|
||||
|
@ -163,7 +164,7 @@ class BlockSleepingClimate(
|
|||
@property
|
||||
def name(self) -> str:
|
||||
"""Name of entity."""
|
||||
return self.wrapper.name
|
||||
return self.coordinator.name
|
||||
|
||||
@property
|
||||
def target_temperature(self) -> float | None:
|
||||
|
@ -184,7 +185,7 @@ class BlockSleepingClimate(
|
|||
"""Device availability."""
|
||||
if self.device_block is not None:
|
||||
return not cast(bool, self.device_block.valveError)
|
||||
return self.wrapper.last_update_success
|
||||
return self.coordinator.last_update_success
|
||||
|
||||
@property
|
||||
def hvac_mode(self) -> HVACMode:
|
||||
|
@ -229,7 +230,9 @@ class BlockSleepingClimate(
|
|||
def device_info(self) -> DeviceInfo:
|
||||
"""Device info."""
|
||||
return {
|
||||
"connections": {(device_registry.CONNECTION_NETWORK_MAC, self.wrapper.mac)}
|
||||
"connections": {
|
||||
(device_registry.CONNECTION_NETWORK_MAC, self.coordinator.mac)
|
||||
}
|
||||
}
|
||||
|
||||
def _check_is_off(self) -> bool:
|
||||
|
@ -244,7 +247,7 @@ class BlockSleepingClimate(
|
|||
LOGGER.debug("Setting state for entity %s, state: %s", self.name, kwargs)
|
||||
try:
|
||||
async with async_timeout.timeout(AIOSHELLY_DEVICE_TIMEOUT_SEC):
|
||||
return await self.wrapper.device.http_request(
|
||||
return await self.coordinator.device.http_request(
|
||||
"get", f"thermostat/{self._channel}", kwargs
|
||||
)
|
||||
except (asyncio.TimeoutError, OSError) as err:
|
||||
|
@ -254,7 +257,7 @@ class BlockSleepingClimate(
|
|||
kwargs,
|
||||
repr(err),
|
||||
)
|
||||
self.wrapper.last_update_success = False
|
||||
self.coordinator.last_update_success = False
|
||||
return None
|
||||
|
||||
async def async_set_temperature(self, **kwargs: Any) -> None:
|
||||
|
@ -302,13 +305,13 @@ class BlockSleepingClimate(
|
|||
@callback
|
||||
def _handle_coordinator_update(self) -> None:
|
||||
"""Handle device update."""
|
||||
if not self.wrapper.device.initialized:
|
||||
if not self.coordinator.device.initialized:
|
||||
self.async_write_ha_state()
|
||||
return
|
||||
|
||||
assert self.wrapper.device.blocks
|
||||
assert self.coordinator.device.blocks
|
||||
|
||||
for block in self.wrapper.device.blocks:
|
||||
for block in self.coordinator.device.blocks:
|
||||
if block.type == "device":
|
||||
self.device_block = block
|
||||
if hasattr(block, "targetTemp"):
|
||||
|
@ -322,11 +325,11 @@ class BlockSleepingClimate(
|
|||
try:
|
||||
self._preset_modes = [
|
||||
PRESET_NONE,
|
||||
*self.wrapper.device.settings["thermostats"][
|
||||
*self.coordinator.device.settings["thermostats"][
|
||||
int(self.block.channel)
|
||||
]["schedule_profile_names"],
|
||||
]
|
||||
except AuthRequired:
|
||||
self.wrapper.entry.async_start_reauth(self.hass)
|
||||
self.coordinator.entry.async_start_reauth(self.hass)
|
||||
else:
|
||||
self.async_write_ha_state()
|
||||
|
|
|
@ -14,8 +14,9 @@ import async_timeout
|
|||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import ATTR_DEVICE_ID, CONF_HOST, EVENT_HOMEASSISTANT_STOP
|
||||
from homeassistant.core import Event, HomeAssistant, callback
|
||||
from homeassistant.helpers import device_registry, update_coordinator
|
||||
from homeassistant.helpers import device_registry
|
||||
from homeassistant.helpers.debounce import Debouncer
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
from .const import (
|
||||
AIOSHELLY_DEVICE_TIMEOUT_SEC,
|
||||
|
@ -44,13 +45,13 @@ from .const import (
|
|||
from .utils import device_update_info, get_block_device_name, get_rpc_device_name
|
||||
|
||||
|
||||
class BlockDeviceWrapper(update_coordinator.DataUpdateCoordinator):
|
||||
"""Wrapper for a Shelly block based device with Home Assistant specific functions."""
|
||||
class ShellyBlockCoordinator(DataUpdateCoordinator):
|
||||
"""Coordinator for a Shelly block based device."""
|
||||
|
||||
def __init__(
|
||||
self, hass: HomeAssistant, entry: ConfigEntry, device: BlockDevice
|
||||
) -> None:
|
||||
"""Initialize the Shelly device wrapper."""
|
||||
"""Initialize the Shelly block device coordinator."""
|
||||
self.device_id: str | None = None
|
||||
|
||||
if sleep_period := entry.data[CONF_SLEEP_PERIOD]:
|
||||
|
@ -186,7 +187,7 @@ class BlockDeviceWrapper(update_coordinator.DataUpdateCoordinator):
|
|||
"""Fetch data."""
|
||||
if sleep_period := self.entry.data.get(CONF_SLEEP_PERIOD):
|
||||
# Sleeping device, no point polling it, just mark it unavailable
|
||||
raise update_coordinator.UpdateFailed(
|
||||
raise UpdateFailed(
|
||||
f"Sleeping device did not update within {sleep_period} seconds interval"
|
||||
)
|
||||
|
||||
|
@ -196,7 +197,7 @@ class BlockDeviceWrapper(update_coordinator.DataUpdateCoordinator):
|
|||
await self.device.update()
|
||||
device_update_info(self.hass, self.device, self.entry)
|
||||
except OSError as err:
|
||||
raise update_coordinator.UpdateFailed("Error fetching data") from err
|
||||
raise UpdateFailed("Error fetching data") from err
|
||||
|
||||
@property
|
||||
def model(self) -> str:
|
||||
|
@ -214,7 +215,7 @@ class BlockDeviceWrapper(update_coordinator.DataUpdateCoordinator):
|
|||
return self.device.firmware_version if self.device.initialized else ""
|
||||
|
||||
def async_setup(self) -> None:
|
||||
"""Set up the wrapper."""
|
||||
"""Set up the coordinator."""
|
||||
dev_reg = device_registry.async_get(self.hass)
|
||||
entry = dev_reg.async_get_or_create(
|
||||
config_entry_id=self.entry.entry_id,
|
||||
|
@ -265,23 +266,23 @@ class BlockDeviceWrapper(update_coordinator.DataUpdateCoordinator):
|
|||
LOGGER.debug("Result of OTA update call: %s", result)
|
||||
|
||||
def shutdown(self) -> None:
|
||||
"""Shutdown the wrapper."""
|
||||
"""Shutdown the coordinator."""
|
||||
self.device.shutdown()
|
||||
|
||||
@callback
|
||||
def _handle_ha_stop(self, _event: Event) -> None:
|
||||
"""Handle Home Assistant stopping."""
|
||||
LOGGER.debug("Stopping BlockDeviceWrapper for %s", self.name)
|
||||
LOGGER.debug("Stopping block device coordinator for %s", self.name)
|
||||
self.shutdown()
|
||||
|
||||
|
||||
class ShellyDeviceRestWrapper(update_coordinator.DataUpdateCoordinator):
|
||||
"""Rest Wrapper for a Shelly device with Home Assistant specific functions."""
|
||||
class ShellyRestCoordinator(DataUpdateCoordinator):
|
||||
"""Coordinator for a Shelly REST device."""
|
||||
|
||||
def __init__(
|
||||
self, hass: HomeAssistant, device: BlockDevice, entry: ConfigEntry
|
||||
) -> None:
|
||||
"""Initialize the Shelly device wrapper."""
|
||||
"""Initialize the Shelly REST device coordinator."""
|
||||
if (
|
||||
device.settings["device"]["type"]
|
||||
in BATTERY_DEVICES_WITH_PERMANENT_CONNECTION
|
||||
|
@ -316,7 +317,7 @@ class ShellyDeviceRestWrapper(update_coordinator.DataUpdateCoordinator):
|
|||
return
|
||||
device_update_info(self.hass, self.device, self.entry)
|
||||
except OSError as err:
|
||||
raise update_coordinator.UpdateFailed("Error fetching data") from err
|
||||
raise UpdateFailed("Error fetching data") from err
|
||||
|
||||
@property
|
||||
def mac(self) -> str:
|
||||
|
@ -324,13 +325,13 @@ class ShellyDeviceRestWrapper(update_coordinator.DataUpdateCoordinator):
|
|||
return cast(str, self.device.settings["device"]["mac"])
|
||||
|
||||
|
||||
class RpcDeviceWrapper(update_coordinator.DataUpdateCoordinator):
|
||||
"""Wrapper for a Shelly RPC based device with Home Assistant specific functions."""
|
||||
class ShellyRpcCoordinator(DataUpdateCoordinator):
|
||||
"""Coordinator for a Shelly RPC based device."""
|
||||
|
||||
def __init__(
|
||||
self, hass: HomeAssistant, entry: ConfigEntry, device: RpcDevice
|
||||
) -> None:
|
||||
"""Initialize the Shelly device wrapper."""
|
||||
"""Initialize the Shelly RPC device coordinator."""
|
||||
self.device_id: str | None = None
|
||||
|
||||
device_name = get_rpc_device_name(device) if device.initialized else entry.title
|
||||
|
@ -413,7 +414,7 @@ class RpcDeviceWrapper(update_coordinator.DataUpdateCoordinator):
|
|||
await self.device.initialize()
|
||||
device_update_info(self.hass, self.device, self.entry)
|
||||
except OSError as err:
|
||||
raise update_coordinator.UpdateFailed("Device disconnected") from err
|
||||
raise UpdateFailed("Device disconnected") from err
|
||||
|
||||
@property
|
||||
def model(self) -> str:
|
||||
|
@ -431,7 +432,7 @@ class RpcDeviceWrapper(update_coordinator.DataUpdateCoordinator):
|
|||
return self.device.firmware_version if self.device.initialized else ""
|
||||
|
||||
def async_setup(self) -> None:
|
||||
"""Set up the wrapper."""
|
||||
"""Set up the coordinator."""
|
||||
dev_reg = device_registry.async_get(self.hass)
|
||||
entry = dev_reg.async_get_or_create(
|
||||
config_entry_id=self.entry.entry_id,
|
||||
|
@ -482,17 +483,17 @@ class RpcDeviceWrapper(update_coordinator.DataUpdateCoordinator):
|
|||
LOGGER.debug("OTA update call successful")
|
||||
|
||||
async def shutdown(self) -> None:
|
||||
"""Shutdown the wrapper."""
|
||||
"""Shutdown the coordinator."""
|
||||
await self.device.shutdown()
|
||||
|
||||
async def _handle_ha_stop(self, _event: Event) -> None:
|
||||
"""Handle Home Assistant stopping."""
|
||||
LOGGER.debug("Stopping RpcDeviceWrapper for %s", self.name)
|
||||
LOGGER.debug("Stopping RPC device coordinator for %s", self.name)
|
||||
await self.shutdown()
|
||||
|
||||
|
||||
class RpcPollingWrapper(update_coordinator.DataUpdateCoordinator):
|
||||
"""Polling Wrapper for a Shelly RPC based device."""
|
||||
class ShellyRpcPollingCoordinator(DataUpdateCoordinator):
|
||||
"""Polling coordinator for a Shelly RPC based device."""
|
||||
|
||||
def __init__(
|
||||
self, hass: HomeAssistant, entry: ConfigEntry, device: RpcDevice
|
||||
|
@ -513,14 +514,14 @@ class RpcPollingWrapper(update_coordinator.DataUpdateCoordinator):
|
|||
async def _async_update_data(self) -> None:
|
||||
"""Fetch data."""
|
||||
if not self.device.connected:
|
||||
raise update_coordinator.UpdateFailed("Device disconnected")
|
||||
raise UpdateFailed("Device disconnected")
|
||||
|
||||
try:
|
||||
LOGGER.debug("Polling Shelly RPC Device - %s", self.name)
|
||||
async with async_timeout.timeout(AIOSHELLY_DEVICE_TIMEOUT_SEC):
|
||||
await self.device.update_status()
|
||||
except (OSError, aioshelly.exceptions.RPCTimeout) as err:
|
||||
raise update_coordinator.UpdateFailed("Device disconnected") from err
|
||||
raise UpdateFailed("Device disconnected") from err
|
||||
|
||||
@property
|
||||
def model(self) -> str:
|
||||
|
|
|
@ -15,8 +15,8 @@ from homeassistant.config_entries import ConfigEntry
|
|||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import BlockDeviceWrapper, RpcDeviceWrapper
|
||||
from .const import BLOCK, DATA_CONFIG_ENTRY, DOMAIN, RPC
|
||||
from .coordinator import ShellyBlockCoordinator, ShellyRpcCoordinator
|
||||
from .entity import ShellyBlockEntity, ShellyRpcEntity
|
||||
from .utils import get_device_entry_gen, get_rpc_key_ids
|
||||
|
||||
|
@ -40,13 +40,13 @@ def async_setup_block_entry(
|
|||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up cover for device."""
|
||||
wrapper = hass.data[DOMAIN][DATA_CONFIG_ENTRY][config_entry.entry_id][BLOCK]
|
||||
blocks = [block for block in wrapper.device.blocks if block.type == "roller"]
|
||||
coordinator = hass.data[DOMAIN][DATA_CONFIG_ENTRY][config_entry.entry_id][BLOCK]
|
||||
blocks = [block for block in coordinator.device.blocks if block.type == "roller"]
|
||||
|
||||
if not blocks:
|
||||
return
|
||||
|
||||
async_add_entities(BlockShellyCover(wrapper, block) for block in blocks)
|
||||
async_add_entities(BlockShellyCover(coordinator, block) for block in blocks)
|
||||
|
||||
|
||||
@callback
|
||||
|
@ -56,14 +56,14 @@ def async_setup_rpc_entry(
|
|||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up entities for RPC device."""
|
||||
wrapper = hass.data[DOMAIN][DATA_CONFIG_ENTRY][config_entry.entry_id][RPC]
|
||||
coordinator = hass.data[DOMAIN][DATA_CONFIG_ENTRY][config_entry.entry_id][RPC]
|
||||
|
||||
cover_key_ids = get_rpc_key_ids(wrapper.device.status, "cover")
|
||||
cover_key_ids = get_rpc_key_ids(coordinator.device.status, "cover")
|
||||
|
||||
if not cover_key_ids:
|
||||
return
|
||||
|
||||
async_add_entities(RpcShellyCover(wrapper, id_) for id_ in cover_key_ids)
|
||||
async_add_entities(RpcShellyCover(coordinator, id_) for id_ in cover_key_ids)
|
||||
|
||||
|
||||
class BlockShellyCover(ShellyBlockEntity, CoverEntity):
|
||||
|
@ -71,14 +71,14 @@ class BlockShellyCover(ShellyBlockEntity, CoverEntity):
|
|||
|
||||
_attr_device_class = CoverDeviceClass.SHUTTER
|
||||
|
||||
def __init__(self, wrapper: BlockDeviceWrapper, block: Block) -> None:
|
||||
def __init__(self, coordinator: ShellyBlockCoordinator, block: Block) -> None:
|
||||
"""Initialize block cover."""
|
||||
super().__init__(wrapper, block)
|
||||
super().__init__(coordinator, block)
|
||||
self.control_result: dict[str, Any] | None = None
|
||||
self._attr_supported_features: int = (
|
||||
CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE | CoverEntityFeature.STOP
|
||||
)
|
||||
if self.wrapper.device.settings["rollers"][0]["positioning"]:
|
||||
if self.coordinator.device.settings["rollers"][0]["positioning"]:
|
||||
self._attr_supported_features |= CoverEntityFeature.SET_POSITION
|
||||
|
||||
@property
|
||||
|
@ -147,9 +147,9 @@ class RpcShellyCover(ShellyRpcEntity, CoverEntity):
|
|||
|
||||
_attr_device_class = CoverDeviceClass.SHUTTER
|
||||
|
||||
def __init__(self, wrapper: RpcDeviceWrapper, id_: int) -> None:
|
||||
def __init__(self, coordinator: ShellyRpcCoordinator, id_: int) -> None:
|
||||
"""Initialize rpc cover."""
|
||||
super().__init__(wrapper, f"cover:{id_}")
|
||||
super().__init__(coordinator, f"cover:{id_}")
|
||||
self._id = id_
|
||||
self._attr_supported_features: int = (
|
||||
CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE | CoverEntityFeature.STOP
|
||||
|
|
|
@ -22,7 +22,7 @@ from homeassistant.core import CALLBACK_TYPE, HomeAssistant
|
|||
from homeassistant.helpers.trigger import TriggerActionType, TriggerInfo
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from . import get_block_device_wrapper, get_rpc_device_wrapper
|
||||
from . import get_block_device_coordinator, get_rpc_device_coordinator
|
||||
from .const import (
|
||||
ATTR_CHANNEL,
|
||||
ATTR_CLICK_TYPE,
|
||||
|
@ -78,23 +78,23 @@ async def async_validate_trigger_config(
|
|||
trigger = (config[CONF_TYPE], config[CONF_SUBTYPE])
|
||||
|
||||
if config[CONF_TYPE] in RPC_INPUTS_EVENTS_TYPES:
|
||||
rpc_wrapper = get_rpc_device_wrapper(hass, config[CONF_DEVICE_ID])
|
||||
if not rpc_wrapper or not rpc_wrapper.device.initialized:
|
||||
rpc_coordinator = get_rpc_device_coordinator(hass, config[CONF_DEVICE_ID])
|
||||
if not rpc_coordinator or not rpc_coordinator.device.initialized:
|
||||
return config
|
||||
|
||||
input_triggers = get_rpc_input_triggers(rpc_wrapper.device)
|
||||
input_triggers = get_rpc_input_triggers(rpc_coordinator.device)
|
||||
if trigger in input_triggers:
|
||||
return config
|
||||
|
||||
elif config[CONF_TYPE] in BLOCK_INPUTS_EVENTS_TYPES:
|
||||
block_wrapper = get_block_device_wrapper(hass, config[CONF_DEVICE_ID])
|
||||
if not block_wrapper or not block_wrapper.device.initialized:
|
||||
block_coordinator = get_block_device_coordinator(hass, config[CONF_DEVICE_ID])
|
||||
if not block_coordinator or not block_coordinator.device.initialized:
|
||||
return config
|
||||
|
||||
assert block_wrapper.device.blocks
|
||||
assert block_coordinator.device.blocks
|
||||
|
||||
for block in block_wrapper.device.blocks:
|
||||
input_triggers = get_block_input_triggers(block_wrapper.device, block)
|
||||
for block in block_coordinator.device.blocks:
|
||||
input_triggers = get_block_input_triggers(block_coordinator.device, block)
|
||||
if trigger in input_triggers:
|
||||
return config
|
||||
|
||||
|
@ -109,24 +109,24 @@ async def async_get_triggers(
|
|||
"""List device triggers for Shelly devices."""
|
||||
triggers: list[dict[str, str]] = []
|
||||
|
||||
if rpc_wrapper := get_rpc_device_wrapper(hass, device_id):
|
||||
input_triggers = get_rpc_input_triggers(rpc_wrapper.device)
|
||||
if rpc_coordinator := get_rpc_device_coordinator(hass, device_id):
|
||||
input_triggers = get_rpc_input_triggers(rpc_coordinator.device)
|
||||
append_input_triggers(triggers, input_triggers, device_id)
|
||||
return triggers
|
||||
|
||||
if block_wrapper := get_block_device_wrapper(hass, device_id):
|
||||
if block_wrapper.model in SHBTN_MODELS:
|
||||
if block_coordinator := get_block_device_coordinator(hass, device_id):
|
||||
if block_coordinator.model in SHBTN_MODELS:
|
||||
input_triggers = get_shbtn_input_triggers()
|
||||
append_input_triggers(triggers, input_triggers, device_id)
|
||||
return triggers
|
||||
|
||||
if not block_wrapper.device.initialized:
|
||||
if not block_coordinator.device.initialized:
|
||||
return triggers
|
||||
|
||||
assert block_wrapper.device.blocks
|
||||
assert block_coordinator.device.blocks
|
||||
|
||||
for block in block_wrapper.device.blocks:
|
||||
input_triggers = get_block_input_triggers(block_wrapper.device, block)
|
||||
for block in block_coordinator.device.blocks:
|
||||
input_triggers = get_block_input_triggers(block_coordinator.device, block)
|
||||
append_input_triggers(triggers, input_triggers, device_id)
|
||||
|
||||
return triggers
|
||||
|
|
|
@ -6,8 +6,8 @@ from homeassistant.config_entries import ConfigEntry
|
|||
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from . import BlockDeviceWrapper, RpcDeviceWrapper
|
||||
from .const import BLOCK, DATA_CONFIG_ENTRY, DOMAIN, RPC
|
||||
from .coordinator import ShellyBlockCoordinator, ShellyRpcCoordinator
|
||||
|
||||
TO_REDACT = {CONF_USERNAME, CONF_PASSWORD}
|
||||
|
||||
|
@ -21,21 +21,21 @@ async def async_get_config_entry_diagnostics(
|
|||
device_settings: str | dict = "not initialized"
|
||||
device_status: str | dict = "not initialized"
|
||||
if BLOCK in data:
|
||||
block_wrapper: BlockDeviceWrapper = data[BLOCK]
|
||||
block_coordinator: ShellyBlockCoordinator = data[BLOCK]
|
||||
device_info = {
|
||||
"name": block_wrapper.name,
|
||||
"model": block_wrapper.model,
|
||||
"sw_version": block_wrapper.sw_version,
|
||||
"name": block_coordinator.name,
|
||||
"model": block_coordinator.model,
|
||||
"sw_version": block_coordinator.sw_version,
|
||||
}
|
||||
if block_wrapper.device.initialized:
|
||||
if block_coordinator.device.initialized:
|
||||
device_settings = {
|
||||
k: v
|
||||
for k, v in block_wrapper.device.settings.items()
|
||||
for k, v in block_coordinator.device.settings.items()
|
||||
if k in ["cloud", "coiot"]
|
||||
}
|
||||
device_status = {
|
||||
k: v
|
||||
for k, v in block_wrapper.device.status.items()
|
||||
for k, v in block_coordinator.device.status.items()
|
||||
if k
|
||||
in [
|
||||
"update",
|
||||
|
@ -51,19 +51,19 @@ async def async_get_config_entry_diagnostics(
|
|||
]
|
||||
}
|
||||
else:
|
||||
rpc_wrapper: RpcDeviceWrapper = data[RPC]
|
||||
rpc_coordinator: ShellyRpcCoordinator = data[RPC]
|
||||
device_info = {
|
||||
"name": rpc_wrapper.name,
|
||||
"model": rpc_wrapper.model,
|
||||
"sw_version": rpc_wrapper.sw_version,
|
||||
"name": rpc_coordinator.name,
|
||||
"model": rpc_coordinator.model,
|
||||
"sw_version": rpc_coordinator.sw_version,
|
||||
}
|
||||
if rpc_wrapper.device.initialized:
|
||||
if rpc_coordinator.device.initialized:
|
||||
device_settings = {
|
||||
k: v for k, v in rpc_wrapper.device.config.items() if k in ["cloud"]
|
||||
k: v for k, v in rpc_coordinator.device.config.items() if k in ["cloud"]
|
||||
}
|
||||
device_status = {
|
||||
k: v
|
||||
for k, v in rpc_wrapper.device.status.items()
|
||||
for k, v in rpc_coordinator.device.status.items()
|
||||
if k in ["sys", "wifi"]
|
||||
}
|
||||
|
||||
|
|
|
@ -11,23 +11,13 @@ import async_timeout
|
|||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers import (
|
||||
device_registry,
|
||||
entity,
|
||||
entity_registry,
|
||||
update_coordinator,
|
||||
)
|
||||
from homeassistant.helpers import device_registry, entity, entity_registry
|
||||
from homeassistant.helpers.entity import DeviceInfo, EntityDescription
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.restore_state import RestoreEntity
|
||||
from homeassistant.helpers.typing import StateType
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from . import (
|
||||
BlockDeviceWrapper,
|
||||
RpcDeviceWrapper,
|
||||
RpcPollingWrapper,
|
||||
ShellyDeviceRestWrapper,
|
||||
)
|
||||
from .const import (
|
||||
AIOSHELLY_DEVICE_TIMEOUT_SEC,
|
||||
BLOCK,
|
||||
|
@ -38,6 +28,12 @@ from .const import (
|
|||
RPC,
|
||||
RPC_POLL,
|
||||
)
|
||||
from .coordinator import (
|
||||
ShellyBlockCoordinator,
|
||||
ShellyRestCoordinator,
|
||||
ShellyRpcCoordinator,
|
||||
ShellyRpcPollingCoordinator,
|
||||
)
|
||||
from .utils import (
|
||||
async_remove_shelly_entity,
|
||||
get_block_entity_name,
|
||||
|
@ -58,20 +54,20 @@ def async_setup_entry_attribute_entities(
|
|||
],
|
||||
) -> None:
|
||||
"""Set up entities for attributes."""
|
||||
wrapper: BlockDeviceWrapper = hass.data[DOMAIN][DATA_CONFIG_ENTRY][
|
||||
coordinator: ShellyBlockCoordinator = hass.data[DOMAIN][DATA_CONFIG_ENTRY][
|
||||
config_entry.entry_id
|
||||
][BLOCK]
|
||||
|
||||
if wrapper.device.initialized:
|
||||
if coordinator.device.initialized:
|
||||
async_setup_block_attribute_entities(
|
||||
hass, async_add_entities, wrapper, sensors, sensor_class
|
||||
hass, async_add_entities, coordinator, sensors, sensor_class
|
||||
)
|
||||
else:
|
||||
async_restore_block_attribute_entities(
|
||||
hass,
|
||||
config_entry,
|
||||
async_add_entities,
|
||||
wrapper,
|
||||
coordinator,
|
||||
sensors,
|
||||
sensor_class,
|
||||
description_class,
|
||||
|
@ -82,16 +78,16 @@ def async_setup_entry_attribute_entities(
|
|||
def async_setup_block_attribute_entities(
|
||||
hass: HomeAssistant,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
wrapper: BlockDeviceWrapper,
|
||||
coordinator: ShellyBlockCoordinator,
|
||||
sensors: Mapping[tuple[str, str], BlockEntityDescription],
|
||||
sensor_class: Callable,
|
||||
) -> None:
|
||||
"""Set up entities for block attributes."""
|
||||
blocks = []
|
||||
|
||||
assert wrapper.device.blocks
|
||||
assert coordinator.device.blocks
|
||||
|
||||
for block in wrapper.device.blocks:
|
||||
for block in coordinator.device.blocks:
|
||||
for sensor_id in block.sensor_ids:
|
||||
description = sensors.get((block.type, sensor_id))
|
||||
if description is None:
|
||||
|
@ -103,10 +99,10 @@ def async_setup_block_attribute_entities(
|
|||
|
||||
# Filter and remove entities that according to settings should not create an entity
|
||||
if description.removal_condition and description.removal_condition(
|
||||
wrapper.device.settings, block
|
||||
coordinator.device.settings, block
|
||||
):
|
||||
domain = sensor_class.__module__.split(".")[-1]
|
||||
unique_id = f"{wrapper.mac}-{block.description}-{sensor_id}"
|
||||
unique_id = f"{coordinator.mac}-{block.description}-{sensor_id}"
|
||||
async_remove_shelly_entity(hass, domain, unique_id)
|
||||
else:
|
||||
blocks.append((block, sensor_id, description))
|
||||
|
@ -116,7 +112,7 @@ def async_setup_block_attribute_entities(
|
|||
|
||||
async_add_entities(
|
||||
[
|
||||
sensor_class(wrapper, block, sensor_id, description)
|
||||
sensor_class(coordinator, block, sensor_id, description)
|
||||
for block, sensor_id, description in blocks
|
||||
]
|
||||
)
|
||||
|
@ -127,7 +123,7 @@ def async_restore_block_attribute_entities(
|
|||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
wrapper: BlockDeviceWrapper,
|
||||
coordinator: ShellyBlockCoordinator,
|
||||
sensors: Mapping[tuple[str, str], BlockEntityDescription],
|
||||
sensor_class: Callable,
|
||||
description_class: Callable[
|
||||
|
@ -152,7 +148,7 @@ def async_restore_block_attribute_entities(
|
|||
description = description_class(entry)
|
||||
|
||||
entities.append(
|
||||
sensor_class(wrapper, None, attribute, description, entry, sensors)
|
||||
sensor_class(coordinator, None, attribute, description, entry, sensors)
|
||||
)
|
||||
|
||||
if not entities:
|
||||
|
@ -170,40 +166,44 @@ def async_setup_entry_rpc(
|
|||
sensor_class: Callable,
|
||||
) -> None:
|
||||
"""Set up entities for REST sensors."""
|
||||
wrapper: RpcDeviceWrapper = hass.data[DOMAIN][DATA_CONFIG_ENTRY][
|
||||
coordinator: ShellyRpcCoordinator = hass.data[DOMAIN][DATA_CONFIG_ENTRY][
|
||||
config_entry.entry_id
|
||||
][RPC]
|
||||
|
||||
polling_wrapper: RpcPollingWrapper = hass.data[DOMAIN][DATA_CONFIG_ENTRY][
|
||||
config_entry.entry_id
|
||||
][RPC_POLL]
|
||||
polling_coordinator: ShellyRpcPollingCoordinator = hass.data[DOMAIN][
|
||||
DATA_CONFIG_ENTRY
|
||||
][config_entry.entry_id][RPC_POLL]
|
||||
|
||||
entities = []
|
||||
for sensor_id in sensors:
|
||||
description = sensors[sensor_id]
|
||||
key_instances = get_rpc_key_instances(wrapper.device.status, description.key)
|
||||
key_instances = get_rpc_key_instances(
|
||||
coordinator.device.status, description.key
|
||||
)
|
||||
|
||||
for key in key_instances:
|
||||
# Filter non-existing sensors
|
||||
if description.sub_key not in wrapper.device.status[
|
||||
if description.sub_key not in coordinator.device.status[
|
||||
key
|
||||
] and not description.supported(wrapper.device.status[key]):
|
||||
] and not description.supported(coordinator.device.status[key]):
|
||||
continue
|
||||
|
||||
# Filter and remove entities that according to settings/status should not create an entity
|
||||
if description.removal_condition and description.removal_condition(
|
||||
wrapper.device.config, wrapper.device.status, key
|
||||
coordinator.device.config, coordinator.device.status, key
|
||||
):
|
||||
domain = sensor_class.__module__.split(".")[-1]
|
||||
unique_id = f"{wrapper.mac}-{key}-{sensor_id}"
|
||||
unique_id = f"{coordinator.mac}-{key}-{sensor_id}"
|
||||
async_remove_shelly_entity(hass, domain, unique_id)
|
||||
else:
|
||||
if description.use_polling_wrapper:
|
||||
if description.use_polling_coordinator:
|
||||
entities.append(
|
||||
sensor_class(polling_wrapper, key, sensor_id, description)
|
||||
sensor_class(polling_coordinator, key, sensor_id, description)
|
||||
)
|
||||
else:
|
||||
entities.append(sensor_class(wrapper, key, sensor_id, description))
|
||||
entities.append(
|
||||
sensor_class(coordinator, key, sensor_id, description)
|
||||
)
|
||||
|
||||
if not entities:
|
||||
return
|
||||
|
@ -220,7 +220,7 @@ def async_setup_entry_rest(
|
|||
sensor_class: Callable,
|
||||
) -> None:
|
||||
"""Set up entities for REST sensors."""
|
||||
wrapper: ShellyDeviceRestWrapper = hass.data[DOMAIN][DATA_CONFIG_ENTRY][
|
||||
coordinator: ShellyRestCoordinator = hass.data[DOMAIN][DATA_CONFIG_ENTRY][
|
||||
config_entry.entry_id
|
||||
][REST]
|
||||
|
||||
|
@ -228,7 +228,7 @@ def async_setup_entry_rest(
|
|||
for sensor_id in sensors:
|
||||
description = sensors.get(sensor_id)
|
||||
|
||||
if not wrapper.device.settings.get("sleep_mode"):
|
||||
if not coordinator.device.settings.get("sleep_mode"):
|
||||
entities.append((sensor_id, description))
|
||||
|
||||
if not entities:
|
||||
|
@ -236,7 +236,7 @@ def async_setup_entry_rest(
|
|||
|
||||
async_add_entities(
|
||||
[
|
||||
sensor_class(wrapper, sensor_id, description)
|
||||
sensor_class(coordinator, sensor_id, description)
|
||||
for sensor_id, description in entities
|
||||
]
|
||||
)
|
||||
|
@ -270,7 +270,7 @@ class RpcEntityDescription(EntityDescription, RpcEntityRequiredKeysMixin):
|
|||
available: Callable[[dict], bool] | None = None
|
||||
removal_condition: Callable[[dict, dict, str], bool] | None = None
|
||||
extra_state_attributes: Callable[[dict, dict], dict | None] | None = None
|
||||
use_polling_wrapper: bool = False
|
||||
use_polling_coordinator: bool = False
|
||||
supported: Callable = lambda _: False
|
||||
|
||||
|
||||
|
@ -282,32 +282,32 @@ class RestEntityDescription(EntityDescription):
|
|||
extra_state_attributes: Callable[[dict], dict | None] | None = None
|
||||
|
||||
|
||||
class ShellyBlockEntity(entity.Entity):
|
||||
class ShellyBlockEntity(CoordinatorEntity[ShellyBlockCoordinator]):
|
||||
"""Helper class to represent a block entity."""
|
||||
|
||||
def __init__(self, wrapper: BlockDeviceWrapper, block: Block) -> None:
|
||||
def __init__(self, coordinator: ShellyBlockCoordinator, block: Block) -> None:
|
||||
"""Initialize Shelly entity."""
|
||||
self.wrapper = wrapper
|
||||
super().__init__(coordinator)
|
||||
self.block = block
|
||||
self._attr_name = get_block_entity_name(wrapper.device, block)
|
||||
self._attr_name = get_block_entity_name(coordinator.device, block)
|
||||
self._attr_should_poll = False
|
||||
self._attr_device_info = DeviceInfo(
|
||||
connections={(device_registry.CONNECTION_NETWORK_MAC, wrapper.mac)}
|
||||
connections={(device_registry.CONNECTION_NETWORK_MAC, coordinator.mac)}
|
||||
)
|
||||
self._attr_unique_id = f"{wrapper.mac}-{block.description}"
|
||||
self._attr_unique_id = f"{coordinator.mac}-{block.description}"
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
"""Available."""
|
||||
return self.wrapper.last_update_success
|
||||
return self.coordinator.last_update_success
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""When entity is added to HASS."""
|
||||
self.async_on_remove(self.wrapper.async_add_listener(self._update_callback))
|
||||
self.async_on_remove(self.coordinator.async_add_listener(self._update_callback))
|
||||
|
||||
async def async_update(self) -> None:
|
||||
"""Update entity with latest info."""
|
||||
await self.wrapper.async_request_refresh()
|
||||
await self.coordinator.async_request_refresh()
|
||||
|
||||
@callback
|
||||
def _update_callback(self) -> None:
|
||||
|
@ -327,7 +327,7 @@ class ShellyBlockEntity(entity.Entity):
|
|||
kwargs,
|
||||
repr(err),
|
||||
)
|
||||
self.wrapper.last_update_success = False
|
||||
self.coordinator.last_update_success = False
|
||||
return None
|
||||
|
||||
|
||||
|
@ -336,36 +336,36 @@ class ShellyRpcEntity(entity.Entity):
|
|||
|
||||
def __init__(
|
||||
self,
|
||||
wrapper: RpcDeviceWrapper | RpcPollingWrapper,
|
||||
coordinator: ShellyRpcCoordinator | ShellyRpcPollingCoordinator,
|
||||
key: str,
|
||||
) -> None:
|
||||
"""Initialize Shelly entity."""
|
||||
self.wrapper = wrapper
|
||||
self.coordinator = coordinator
|
||||
self.key = key
|
||||
self._attr_should_poll = False
|
||||
self._attr_device_info = {
|
||||
"connections": {(device_registry.CONNECTION_NETWORK_MAC, wrapper.mac)}
|
||||
"connections": {(device_registry.CONNECTION_NETWORK_MAC, coordinator.mac)}
|
||||
}
|
||||
self._attr_unique_id = f"{wrapper.mac}-{key}"
|
||||
self._attr_name = get_rpc_entity_name(wrapper.device, key)
|
||||
self._attr_unique_id = f"{coordinator.mac}-{key}"
|
||||
self._attr_name = get_rpc_entity_name(coordinator.device, key)
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
"""Available."""
|
||||
return self.wrapper.device.connected
|
||||
return self.coordinator.device.connected
|
||||
|
||||
@property
|
||||
def status(self) -> dict:
|
||||
"""Device status by entity key."""
|
||||
return cast(dict, self.wrapper.device.status[self.key])
|
||||
return cast(dict, self.coordinator.device.status[self.key])
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""When entity is added to HASS."""
|
||||
self.async_on_remove(self.wrapper.async_add_listener(self._update_callback))
|
||||
self.async_on_remove(self.coordinator.async_add_listener(self._update_callback))
|
||||
|
||||
async def async_update(self) -> None:
|
||||
"""Update entity with latest info."""
|
||||
await self.wrapper.async_request_refresh()
|
||||
await self.coordinator.async_request_refresh()
|
||||
|
||||
@callback
|
||||
def _update_callback(self) -> None:
|
||||
|
@ -382,7 +382,7 @@ class ShellyRpcEntity(entity.Entity):
|
|||
)
|
||||
try:
|
||||
async with async_timeout.timeout(AIOSHELLY_DEVICE_TIMEOUT_SEC):
|
||||
return await self.wrapper.device.call_rpc(method, params)
|
||||
return await self.coordinator.device.call_rpc(method, params)
|
||||
except asyncio.TimeoutError as err:
|
||||
LOGGER.error(
|
||||
"Call RPC for entity %s failed, method: %s, params: %s, error: %s",
|
||||
|
@ -391,7 +391,7 @@ class ShellyRpcEntity(entity.Entity):
|
|||
params,
|
||||
repr(err),
|
||||
)
|
||||
self.wrapper.last_update_success = False
|
||||
self.coordinator.last_update_success = False
|
||||
return None
|
||||
|
||||
|
||||
|
@ -402,18 +402,20 @@ class ShellyBlockAttributeEntity(ShellyBlockEntity, entity.Entity):
|
|||
|
||||
def __init__(
|
||||
self,
|
||||
wrapper: BlockDeviceWrapper,
|
||||
coordinator: ShellyBlockCoordinator,
|
||||
block: Block,
|
||||
attribute: str,
|
||||
description: BlockEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize sensor."""
|
||||
super().__init__(wrapper, block)
|
||||
super().__init__(coordinator, block)
|
||||
self.attribute = attribute
|
||||
self.entity_description = description
|
||||
|
||||
self._attr_unique_id: str = f"{super().unique_id}-{self.attribute}"
|
||||
self._attr_name = get_block_entity_name(wrapper.device, block, description.name)
|
||||
self._attr_name = get_block_entity_name(
|
||||
coordinator.device, block, description.name
|
||||
)
|
||||
|
||||
@property
|
||||
def attribute_value(self) -> StateType:
|
||||
|
@ -442,40 +444,42 @@ class ShellyBlockAttributeEntity(ShellyBlockEntity, entity.Entity):
|
|||
return self.entity_description.extra_state_attributes(self.block)
|
||||
|
||||
|
||||
class ShellyRestAttributeEntity(update_coordinator.CoordinatorEntity):
|
||||
class ShellyRestAttributeEntity(CoordinatorEntity[ShellyBlockCoordinator]):
|
||||
"""Class to load info from REST."""
|
||||
|
||||
entity_description: RestEntityDescription
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
wrapper: BlockDeviceWrapper,
|
||||
coordinator: ShellyBlockCoordinator,
|
||||
attribute: str,
|
||||
description: RestEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize sensor."""
|
||||
super().__init__(wrapper)
|
||||
self.wrapper = wrapper
|
||||
super().__init__(coordinator)
|
||||
self.block_coordinator = coordinator
|
||||
self.attribute = attribute
|
||||
self.entity_description = description
|
||||
self._attr_name = get_block_entity_name(wrapper.device, None, description.name)
|
||||
self._attr_unique_id = f"{wrapper.mac}-{attribute}"
|
||||
self._attr_name = get_block_entity_name(
|
||||
coordinator.device, None, description.name
|
||||
)
|
||||
self._attr_unique_id = f"{coordinator.mac}-{attribute}"
|
||||
self._attr_device_info = DeviceInfo(
|
||||
connections={(device_registry.CONNECTION_NETWORK_MAC, wrapper.mac)}
|
||||
connections={(device_registry.CONNECTION_NETWORK_MAC, coordinator.mac)}
|
||||
)
|
||||
self._last_value = None
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
"""Available."""
|
||||
return self.wrapper.last_update_success
|
||||
return self.block_coordinator.last_update_success
|
||||
|
||||
@property
|
||||
def attribute_value(self) -> StateType:
|
||||
"""Value of sensor."""
|
||||
if callable(self.entity_description.value):
|
||||
self._last_value = self.entity_description.value(
|
||||
self.wrapper.device.status, self._last_value
|
||||
self.block_coordinator.device.status, self._last_value
|
||||
)
|
||||
return self._last_value
|
||||
|
||||
|
@ -486,7 +490,7 @@ class ShellyRestAttributeEntity(update_coordinator.CoordinatorEntity):
|
|||
return None
|
||||
|
||||
return self.entity_description.extra_state_attributes(
|
||||
self.wrapper.device.status
|
||||
self.block_coordinator.device.status
|
||||
)
|
||||
|
||||
|
||||
|
@ -497,18 +501,18 @@ class ShellyRpcAttributeEntity(ShellyRpcEntity, entity.Entity):
|
|||
|
||||
def __init__(
|
||||
self,
|
||||
wrapper: RpcDeviceWrapper,
|
||||
coordinator: ShellyRpcCoordinator,
|
||||
key: str,
|
||||
attribute: str,
|
||||
description: RpcEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize sensor."""
|
||||
super().__init__(wrapper, key)
|
||||
super().__init__(coordinator, key)
|
||||
self.attribute = attribute
|
||||
self.entity_description = description
|
||||
|
||||
self._attr_unique_id = f"{super().unique_id}-{attribute}"
|
||||
self._attr_name = get_rpc_entity_name(wrapper.device, key, description.name)
|
||||
self._attr_name = get_rpc_entity_name(coordinator.device, key, description.name)
|
||||
self._last_value = None
|
||||
|
||||
@property
|
||||
|
@ -516,13 +520,13 @@ class ShellyRpcAttributeEntity(ShellyRpcEntity, entity.Entity):
|
|||
"""Value of sensor."""
|
||||
if callable(self.entity_description.value):
|
||||
self._last_value = self.entity_description.value(
|
||||
self.wrapper.device.status[self.key].get(
|
||||
self.coordinator.device.status[self.key].get(
|
||||
self.entity_description.sub_key
|
||||
),
|
||||
self._last_value,
|
||||
)
|
||||
else:
|
||||
self._last_value = self.wrapper.device.status[self.key][
|
||||
self._last_value = self.coordinator.device.status[self.key][
|
||||
self.entity_description.sub_key
|
||||
]
|
||||
|
||||
|
@ -537,7 +541,7 @@ class ShellyRpcAttributeEntity(ShellyRpcEntity, entity.Entity):
|
|||
return available
|
||||
|
||||
return self.entity_description.available(
|
||||
self.wrapper.device.status[self.key][self.entity_description.sub_key]
|
||||
self.coordinator.device.status[self.key][self.entity_description.sub_key]
|
||||
)
|
||||
|
||||
@property
|
||||
|
@ -546,11 +550,11 @@ class ShellyRpcAttributeEntity(ShellyRpcEntity, entity.Entity):
|
|||
if self.entity_description.extra_state_attributes is None:
|
||||
return None
|
||||
|
||||
assert self.wrapper.device.shelly
|
||||
assert self.coordinator.device.shelly
|
||||
|
||||
return self.entity_description.extra_state_attributes(
|
||||
self.wrapper.device.status[self.key][self.entity_description.sub_key],
|
||||
self.wrapper.device.shelly,
|
||||
self.coordinator.device.status[self.key][self.entity_description.sub_key],
|
||||
self.coordinator.device.shelly,
|
||||
)
|
||||
|
||||
|
||||
|
@ -560,7 +564,7 @@ class ShellySleepingBlockAttributeEntity(ShellyBlockAttributeEntity, RestoreEnti
|
|||
# pylint: disable=super-init-not-called
|
||||
def __init__(
|
||||
self,
|
||||
wrapper: BlockDeviceWrapper,
|
||||
coordinator: ShellyBlockCoordinator,
|
||||
block: Block | None,
|
||||
attribute: str,
|
||||
description: BlockEntityDescription,
|
||||
|
@ -570,20 +574,22 @@ class ShellySleepingBlockAttributeEntity(ShellyBlockAttributeEntity, RestoreEnti
|
|||
"""Initialize the sleeping sensor."""
|
||||
self.sensors = sensors
|
||||
self.last_state: StateType = None
|
||||
self.wrapper = wrapper
|
||||
self.coordinator = coordinator
|
||||
self.attribute = attribute
|
||||
self.block: Block | None = block # type: ignore[assignment]
|
||||
self.entity_description = description
|
||||
|
||||
self._attr_should_poll = False
|
||||
self._attr_device_info = DeviceInfo(
|
||||
connections={(device_registry.CONNECTION_NETWORK_MAC, wrapper.mac)}
|
||||
connections={(device_registry.CONNECTION_NETWORK_MAC, coordinator.mac)}
|
||||
)
|
||||
|
||||
if block is not None:
|
||||
self._attr_unique_id = f"{self.wrapper.mac}-{block.description}-{attribute}"
|
||||
self._attr_unique_id = (
|
||||
f"{self.coordinator.mac}-{block.description}-{attribute}"
|
||||
)
|
||||
self._attr_name = get_block_entity_name(
|
||||
self.wrapper.device, block, self.entity_description.name
|
||||
self.coordinator.device, block, self.entity_description.name
|
||||
)
|
||||
elif entry is not None:
|
||||
self._attr_unique_id = entry.unique_id
|
||||
|
@ -603,7 +609,7 @@ class ShellySleepingBlockAttributeEntity(ShellyBlockAttributeEntity, RestoreEnti
|
|||
"""Handle device update."""
|
||||
if (
|
||||
self.block is not None
|
||||
or not self.wrapper.device.initialized
|
||||
or not self.coordinator.device.initialized
|
||||
or self.sensors is None
|
||||
):
|
||||
super()._update_callback()
|
||||
|
@ -611,9 +617,9 @@ class ShellySleepingBlockAttributeEntity(ShellyBlockAttributeEntity, RestoreEnti
|
|||
|
||||
_, entity_block, entity_sensor = self._attr_unique_id.split("-")
|
||||
|
||||
assert self.wrapper.device.blocks
|
||||
assert self.coordinator.device.blocks
|
||||
|
||||
for block in self.wrapper.device.blocks:
|
||||
for block in self.coordinator.device.blocks:
|
||||
if block.description != entity_block:
|
||||
continue
|
||||
|
||||
|
|
|
@ -25,7 +25,6 @@ from homeassistant.util.color import (
|
|||
color_temperature_mired_to_kelvin,
|
||||
)
|
||||
|
||||
from . import BlockDeviceWrapper, RpcDeviceWrapper
|
||||
from .const import (
|
||||
BLOCK,
|
||||
DATA_CONFIG_ENTRY,
|
||||
|
@ -44,6 +43,7 @@ from .const import (
|
|||
SHBLB_1_RGB_EFFECTS,
|
||||
STANDARD_RGB_EFFECTS,
|
||||
)
|
||||
from .coordinator import ShellyBlockCoordinator, ShellyRpcCoordinator
|
||||
from .entity import ShellyBlockEntity, ShellyRpcEntity
|
||||
from .utils import (
|
||||
async_remove_shelly_entity,
|
||||
|
@ -77,28 +77,28 @@ def async_setup_block_entry(
|
|||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up entities for block device."""
|
||||
wrapper = hass.data[DOMAIN][DATA_CONFIG_ENTRY][config_entry.entry_id][BLOCK]
|
||||
coordinator = hass.data[DOMAIN][DATA_CONFIG_ENTRY][config_entry.entry_id][BLOCK]
|
||||
|
||||
blocks = []
|
||||
assert wrapper.device.blocks
|
||||
for block in wrapper.device.blocks:
|
||||
assert coordinator.device.blocks
|
||||
for block in coordinator.device.blocks:
|
||||
if block.type == "light":
|
||||
blocks.append(block)
|
||||
elif block.type == "relay":
|
||||
if not is_block_channel_type_light(
|
||||
wrapper.device.settings, int(block.channel)
|
||||
coordinator.device.settings, int(block.channel)
|
||||
):
|
||||
continue
|
||||
|
||||
blocks.append(block)
|
||||
assert wrapper.device.shelly
|
||||
unique_id = f"{wrapper.mac}-{block.type}_{block.channel}"
|
||||
assert coordinator.device.shelly
|
||||
unique_id = f"{coordinator.mac}-{block.type}_{block.channel}"
|
||||
async_remove_shelly_entity(hass, "switch", unique_id)
|
||||
|
||||
if not blocks:
|
||||
return
|
||||
|
||||
async_add_entities(BlockShellyLight(wrapper, block) for block in blocks)
|
||||
async_add_entities(BlockShellyLight(coordinator, block) for block in blocks)
|
||||
|
||||
|
||||
@callback
|
||||
|
@ -108,22 +108,22 @@ def async_setup_rpc_entry(
|
|||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up entities for RPC device."""
|
||||
wrapper = hass.data[DOMAIN][DATA_CONFIG_ENTRY][config_entry.entry_id][RPC]
|
||||
switch_key_ids = get_rpc_key_ids(wrapper.device.status, "switch")
|
||||
coordinator = hass.data[DOMAIN][DATA_CONFIG_ENTRY][config_entry.entry_id][RPC]
|
||||
switch_key_ids = get_rpc_key_ids(coordinator.device.status, "switch")
|
||||
|
||||
switch_ids = []
|
||||
for id_ in switch_key_ids:
|
||||
if not is_rpc_channel_type_light(wrapper.device.config, id_):
|
||||
if not is_rpc_channel_type_light(coordinator.device.config, id_):
|
||||
continue
|
||||
|
||||
switch_ids.append(id_)
|
||||
unique_id = f"{wrapper.mac}-switch:{id_}"
|
||||
unique_id = f"{coordinator.mac}-switch:{id_}"
|
||||
async_remove_shelly_entity(hass, "switch", unique_id)
|
||||
|
||||
if not switch_ids:
|
||||
return
|
||||
|
||||
async_add_entities(RpcShellyLight(wrapper, id_) for id_ in switch_ids)
|
||||
async_add_entities(RpcShellyLight(coordinator, id_) for id_ in switch_ids)
|
||||
|
||||
|
||||
class BlockShellyLight(ShellyBlockEntity, LightEntity):
|
||||
|
@ -131,9 +131,9 @@ class BlockShellyLight(ShellyBlockEntity, LightEntity):
|
|||
|
||||
_attr_supported_color_modes: set[str]
|
||||
|
||||
def __init__(self, wrapper: BlockDeviceWrapper, block: Block) -> None:
|
||||
def __init__(self, coordinator: ShellyBlockCoordinator, block: Block) -> None:
|
||||
"""Initialize light."""
|
||||
super().__init__(wrapper, block)
|
||||
super().__init__(coordinator, block)
|
||||
self.control_result: dict[str, Any] | None = None
|
||||
self._attr_supported_color_modes = set()
|
||||
self._attr_min_mireds = MIRED_MIN_VALUE
|
||||
|
@ -144,7 +144,7 @@ class BlockShellyLight(ShellyBlockEntity, LightEntity):
|
|||
if hasattr(block, "red") and hasattr(block, "green") and hasattr(block, "blue"):
|
||||
self._attr_max_mireds = MIRED_MAX_VALUE_COLOR
|
||||
self._min_kelvin = KELVIN_MIN_VALUE_COLOR
|
||||
if wrapper.model in RGBW_MODELS:
|
||||
if coordinator.model in RGBW_MODELS:
|
||||
self._attr_supported_color_modes.add(ColorMode.RGBW)
|
||||
else:
|
||||
self._attr_supported_color_modes.add(ColorMode.RGB)
|
||||
|
@ -161,8 +161,8 @@ class BlockShellyLight(ShellyBlockEntity, LightEntity):
|
|||
if hasattr(block, "effect"):
|
||||
self._attr_supported_features |= LightEntityFeature.EFFECT
|
||||
|
||||
if wrapper.model in MODELS_SUPPORTING_LIGHT_TRANSITION:
|
||||
match = FIRMWARE_PATTERN.search(wrapper.device.settings.get("fw", ""))
|
||||
if coordinator.model in MODELS_SUPPORTING_LIGHT_TRANSITION:
|
||||
match = FIRMWARE_PATTERN.search(coordinator.device.settings.get("fw", ""))
|
||||
if (
|
||||
match is not None
|
||||
and int(match[0]) >= LIGHT_TRANSITION_MIN_FIRMWARE_DATE
|
||||
|
@ -215,7 +215,7 @@ class BlockShellyLight(ShellyBlockEntity, LightEntity):
|
|||
def color_mode(self) -> ColorMode:
|
||||
"""Return the color mode of the light."""
|
||||
if self.mode == "color":
|
||||
if self.wrapper.model in RGBW_MODELS:
|
||||
if self.coordinator.model in RGBW_MODELS:
|
||||
return ColorMode.RGBW
|
||||
return ColorMode.RGB
|
||||
|
||||
|
@ -268,7 +268,7 @@ class BlockShellyLight(ShellyBlockEntity, LightEntity):
|
|||
if not self.supported_features & LightEntityFeature.EFFECT:
|
||||
return None
|
||||
|
||||
if self.wrapper.model == "SHBLB-1":
|
||||
if self.coordinator.model == "SHBLB-1":
|
||||
return list(SHBLB_1_RGB_EFFECTS.values())
|
||||
|
||||
return list(STANDARD_RGB_EFFECTS.values())
|
||||
|
@ -284,7 +284,7 @@ class BlockShellyLight(ShellyBlockEntity, LightEntity):
|
|||
else:
|
||||
effect_index = self.block.effect
|
||||
|
||||
if self.wrapper.model == "SHBLB-1":
|
||||
if self.coordinator.model == "SHBLB-1":
|
||||
return SHBLB_1_RGB_EFFECTS[effect_index]
|
||||
|
||||
return STANDARD_RGB_EFFECTS[effect_index]
|
||||
|
@ -334,7 +334,7 @@ class BlockShellyLight(ShellyBlockEntity, LightEntity):
|
|||
if ATTR_EFFECT in kwargs and ATTR_COLOR_TEMP not in kwargs:
|
||||
# Color effect change - used only in color mode, switch device mode to color
|
||||
set_mode = "color"
|
||||
if self.wrapper.model == "SHBLB-1":
|
||||
if self.coordinator.model == "SHBLB-1":
|
||||
effect_dict = SHBLB_1_RGB_EFFECTS
|
||||
else:
|
||||
effect_dict = STANDARD_RGB_EFFECTS
|
||||
|
@ -346,13 +346,13 @@ class BlockShellyLight(ShellyBlockEntity, LightEntity):
|
|||
LOGGER.error(
|
||||
"Effect '%s' not supported by device %s",
|
||||
kwargs[ATTR_EFFECT],
|
||||
self.wrapper.model,
|
||||
self.coordinator.model,
|
||||
)
|
||||
|
||||
if (
|
||||
set_mode
|
||||
and set_mode != self.mode
|
||||
and self.wrapper.model in DUAL_MODE_LIGHT_MODELS
|
||||
and self.coordinator.model in DUAL_MODE_LIGHT_MODELS
|
||||
):
|
||||
params["mode"] = set_mode
|
||||
|
||||
|
@ -385,15 +385,15 @@ class RpcShellyLight(ShellyRpcEntity, LightEntity):
|
|||
_attr_color_mode = ColorMode.ONOFF
|
||||
_attr_supported_color_modes = {ColorMode.ONOFF}
|
||||
|
||||
def __init__(self, wrapper: RpcDeviceWrapper, id_: int) -> None:
|
||||
def __init__(self, coordinator: ShellyRpcCoordinator, id_: int) -> None:
|
||||
"""Initialize light."""
|
||||
super().__init__(wrapper, f"switch:{id_}")
|
||||
super().__init__(coordinator, f"switch:{id_}")
|
||||
self._id = id_
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
"""If light is on."""
|
||||
return bool(self.wrapper.device.status[self.key]["output"])
|
||||
return bool(self.coordinator.device.status[self.key]["output"])
|
||||
|
||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||
"""Turn on light."""
|
||||
|
|
|
@ -8,7 +8,7 @@ from homeassistant.const import ATTR_DEVICE_ID
|
|||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.typing import EventType
|
||||
|
||||
from . import get_block_device_wrapper, get_rpc_device_wrapper
|
||||
from . import get_block_device_coordinator, get_rpc_device_coordinator
|
||||
from .const import (
|
||||
ATTR_CHANNEL,
|
||||
ATTR_CLICK_TYPE,
|
||||
|
@ -37,15 +37,15 @@ def async_describe_events(
|
|||
input_name = f"{event.data[ATTR_DEVICE]} channel {channel}"
|
||||
|
||||
if click_type in RPC_INPUTS_EVENTS_TYPES:
|
||||
rpc_wrapper = get_rpc_device_wrapper(hass, device_id)
|
||||
if rpc_wrapper and rpc_wrapper.device.initialized:
|
||||
rpc_coordinator = get_rpc_device_coordinator(hass, device_id)
|
||||
if rpc_coordinator and rpc_coordinator.device.initialized:
|
||||
key = f"input:{channel-1}"
|
||||
input_name = get_rpc_entity_name(rpc_wrapper.device, key)
|
||||
input_name = get_rpc_entity_name(rpc_coordinator.device, key)
|
||||
|
||||
elif click_type in BLOCK_INPUTS_EVENTS_TYPES:
|
||||
block_wrapper = get_block_device_wrapper(hass, device_id)
|
||||
if block_wrapper and block_wrapper.device.initialized:
|
||||
device_name = get_block_device_name(block_wrapper.device)
|
||||
block_coordinator = get_block_device_coordinator(hass, device_id)
|
||||
if block_coordinator and block_coordinator.device.initialized:
|
||||
device_name = get_block_device_name(block_coordinator.device)
|
||||
input_name = f"{device_name} channel {channel}"
|
||||
|
||||
return {
|
||||
|
|
|
@ -119,7 +119,7 @@ class BlockSleepingNumber(ShellySleepingBlockAttributeEntity, NumberEntity):
|
|||
LOGGER.debug("Setting state for entity %s, state: %s", self.name, params)
|
||||
try:
|
||||
async with async_timeout.timeout(AIOSHELLY_DEVICE_TIMEOUT_SEC):
|
||||
return await self.wrapper.device.http_request("get", path, params)
|
||||
return await self.coordinator.device.http_request("get", path, params)
|
||||
except (asyncio.TimeoutError, OSError) as err:
|
||||
LOGGER.error(
|
||||
"Setting state for entity %s failed, state: %s, error: %s",
|
||||
|
|
|
@ -32,8 +32,8 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
|||
from homeassistant.helpers.entity_registry import RegistryEntry
|
||||
from homeassistant.helpers.typing import StateType
|
||||
|
||||
from . import BlockDeviceWrapper
|
||||
from .const import CONF_SLEEP_PERIOD, SHAIR_MAX_WORK_HOURS
|
||||
from .coordinator import ShellyBlockCoordinator
|
||||
from .entity import (
|
||||
BlockEntityDescription,
|
||||
RestEntityDescription,
|
||||
|
@ -355,7 +355,7 @@ RPC_SENSORS: Final = {
|
|||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_registry_enabled_default=False,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
use_polling_wrapper=True,
|
||||
use_polling_coordinator=True,
|
||||
),
|
||||
"temperature_0": RpcSensorDescription(
|
||||
key="temperature:0",
|
||||
|
@ -376,7 +376,7 @@ RPC_SENSORS: Final = {
|
|||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_registry_enabled_default=False,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
use_polling_wrapper=True,
|
||||
use_polling_coordinator=True,
|
||||
),
|
||||
"uptime": RpcSensorDescription(
|
||||
key="sys",
|
||||
|
@ -386,7 +386,7 @@ RPC_SENSORS: Final = {
|
|||
device_class=SensorDeviceClass.TIMESTAMP,
|
||||
entity_registry_enabled_default=False,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
use_polling_wrapper=True,
|
||||
use_polling_coordinator=True,
|
||||
),
|
||||
"humidity_0": RpcSensorDescription(
|
||||
key="humidity:0",
|
||||
|
@ -465,13 +465,13 @@ class BlockSensor(ShellyBlockAttributeEntity, SensorEntity):
|
|||
|
||||
def __init__(
|
||||
self,
|
||||
wrapper: BlockDeviceWrapper,
|
||||
coordinator: ShellyBlockCoordinator,
|
||||
block: Block,
|
||||
attribute: str,
|
||||
description: BlockSensorDescription,
|
||||
) -> None:
|
||||
"""Initialize sensor."""
|
||||
super().__init__(wrapper, block, attribute, description)
|
||||
super().__init__(coordinator, block, attribute, description)
|
||||
|
||||
self._attr_native_unit_of_measurement = description.native_unit_of_measurement
|
||||
if unit_fn := description.unit_fn:
|
||||
|
@ -512,7 +512,7 @@ class BlockSleepingSensor(ShellySleepingBlockAttributeEntity, SensorEntity):
|
|||
|
||||
def __init__(
|
||||
self,
|
||||
wrapper: BlockDeviceWrapper,
|
||||
coordinator: ShellyBlockCoordinator,
|
||||
block: Block | None,
|
||||
attribute: str,
|
||||
description: BlockSensorDescription,
|
||||
|
@ -520,7 +520,7 @@ class BlockSleepingSensor(ShellySleepingBlockAttributeEntity, SensorEntity):
|
|||
sensors: Mapping[tuple[str, str], BlockSensorDescription] | None = None,
|
||||
) -> None:
|
||||
"""Initialize the sleeping sensor."""
|
||||
super().__init__(wrapper, block, attribute, description, entry, sensors)
|
||||
super().__init__(coordinator, block, attribute, description, entry, sensors)
|
||||
|
||||
self._attr_native_unit_of_measurement = description.native_unit_of_measurement
|
||||
if block and (unit_fn := description.unit_fn):
|
||||
|
|
|
@ -10,8 +10,8 @@ from homeassistant.config_entries import ConfigEntry
|
|||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import BlockDeviceWrapper, RpcDeviceWrapper
|
||||
from .const import BLOCK, DATA_CONFIG_ENTRY, DOMAIN, RPC
|
||||
from .coordinator import ShellyBlockCoordinator, ShellyRpcCoordinator
|
||||
from .entity import ShellyBlockEntity, ShellyRpcEntity
|
||||
from .utils import (
|
||||
async_remove_shelly_entity,
|
||||
|
@ -41,31 +41,31 @@ def async_setup_block_entry(
|
|||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up entities for block device."""
|
||||
wrapper = hass.data[DOMAIN][DATA_CONFIG_ENTRY][config_entry.entry_id][BLOCK]
|
||||
coordinator = hass.data[DOMAIN][DATA_CONFIG_ENTRY][config_entry.entry_id][BLOCK]
|
||||
|
||||
# In roller mode the relay blocks exist but do not contain required info
|
||||
if (
|
||||
wrapper.model in ["SHSW-21", "SHSW-25"]
|
||||
and wrapper.device.settings["mode"] != "relay"
|
||||
coordinator.model in ["SHSW-21", "SHSW-25"]
|
||||
and coordinator.device.settings["mode"] != "relay"
|
||||
):
|
||||
return
|
||||
|
||||
relay_blocks = []
|
||||
assert wrapper.device.blocks
|
||||
for block in wrapper.device.blocks:
|
||||
assert coordinator.device.blocks
|
||||
for block in coordinator.device.blocks:
|
||||
if block.type != "relay" or is_block_channel_type_light(
|
||||
wrapper.device.settings, int(block.channel)
|
||||
coordinator.device.settings, int(block.channel)
|
||||
):
|
||||
continue
|
||||
|
||||
relay_blocks.append(block)
|
||||
unique_id = f"{wrapper.mac}-{block.type}_{block.channel}"
|
||||
unique_id = f"{coordinator.mac}-{block.type}_{block.channel}"
|
||||
async_remove_shelly_entity(hass, "light", unique_id)
|
||||
|
||||
if not relay_blocks:
|
||||
return
|
||||
|
||||
async_add_entities(BlockRelaySwitch(wrapper, block) for block in relay_blocks)
|
||||
async_add_entities(BlockRelaySwitch(coordinator, block) for block in relay_blocks)
|
||||
|
||||
|
||||
@callback
|
||||
|
@ -75,31 +75,31 @@ def async_setup_rpc_entry(
|
|||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up entities for RPC device."""
|
||||
wrapper = hass.data[DOMAIN][DATA_CONFIG_ENTRY][config_entry.entry_id][RPC]
|
||||
coordinator = hass.data[DOMAIN][DATA_CONFIG_ENTRY][config_entry.entry_id][RPC]
|
||||
|
||||
switch_key_ids = get_rpc_key_ids(wrapper.device.status, "switch")
|
||||
switch_key_ids = get_rpc_key_ids(coordinator.device.status, "switch")
|
||||
|
||||
switch_ids = []
|
||||
for id_ in switch_key_ids:
|
||||
if is_rpc_channel_type_light(wrapper.device.config, id_):
|
||||
if is_rpc_channel_type_light(coordinator.device.config, id_):
|
||||
continue
|
||||
|
||||
switch_ids.append(id_)
|
||||
unique_id = f"{wrapper.mac}-switch:{id_}"
|
||||
unique_id = f"{coordinator.mac}-switch:{id_}"
|
||||
async_remove_shelly_entity(hass, "light", unique_id)
|
||||
|
||||
if not switch_ids:
|
||||
return
|
||||
|
||||
async_add_entities(RpcRelaySwitch(wrapper, id_) for id_ in switch_ids)
|
||||
async_add_entities(RpcRelaySwitch(coordinator, id_) for id_ in switch_ids)
|
||||
|
||||
|
||||
class BlockRelaySwitch(ShellyBlockEntity, SwitchEntity):
|
||||
"""Entity that controls a relay on Block based Shelly devices."""
|
||||
|
||||
def __init__(self, wrapper: BlockDeviceWrapper, block: Block) -> None:
|
||||
def __init__(self, coordinator: ShellyBlockCoordinator, block: Block) -> None:
|
||||
"""Initialize relay switch."""
|
||||
super().__init__(wrapper, block)
|
||||
super().__init__(coordinator, block)
|
||||
self.control_result: dict[str, Any] | None = None
|
||||
|
||||
@property
|
||||
|
@ -130,15 +130,15 @@ class BlockRelaySwitch(ShellyBlockEntity, SwitchEntity):
|
|||
class RpcRelaySwitch(ShellyRpcEntity, SwitchEntity):
|
||||
"""Entity that controls a relay on RPC based Shelly devices."""
|
||||
|
||||
def __init__(self, wrapper: RpcDeviceWrapper, id_: int) -> None:
|
||||
def __init__(self, coordinator: ShellyRpcCoordinator, id_: int) -> None:
|
||||
"""Initialize relay switch."""
|
||||
super().__init__(wrapper, f"switch:{id_}")
|
||||
super().__init__(coordinator, f"switch:{id_}")
|
||||
self._id = id_
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
"""If switch is on."""
|
||||
return bool(self.wrapper.device.status[self.key]["output"])
|
||||
return bool(self.coordinator.device.status[self.key]["output"])
|
||||
|
||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||
"""Turn on relay."""
|
||||
|
|
|
@ -17,8 +17,8 @@ from homeassistant.core import HomeAssistant
|
|||
from homeassistant.helpers.entity import EntityCategory
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import BlockDeviceWrapper, RpcDeviceWrapper
|
||||
from .const import BLOCK, CONF_SLEEP_PERIOD, DATA_CONFIG_ENTRY, DOMAIN
|
||||
from .coordinator import ShellyBlockCoordinator, ShellyRpcCoordinator
|
||||
from .entity import (
|
||||
RestEntityDescription,
|
||||
RpcEntityDescription,
|
||||
|
@ -67,7 +67,7 @@ REST_UPDATES: Final = {
|
|||
name="Firmware Update",
|
||||
key="fwupdate",
|
||||
latest_version=lambda status: status["update"]["new_version"],
|
||||
install=lambda wrapper: wrapper.async_trigger_ota_update(),
|
||||
install=lambda coordinator: coordinator.async_trigger_ota_update(),
|
||||
device_class=UpdateDeviceClass.FIRMWARE,
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
entity_registry_enabled_default=False,
|
||||
|
@ -76,7 +76,7 @@ REST_UPDATES: Final = {
|
|||
name="Beta Firmware Update",
|
||||
key="fwupdate",
|
||||
latest_version=lambda status: status["update"].get("beta_version"),
|
||||
install=lambda wrapper: wrapper.async_trigger_ota_update(beta=True),
|
||||
install=lambda coordinator: coordinator.async_trigger_ota_update(beta=True),
|
||||
device_class=UpdateDeviceClass.FIRMWARE,
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
entity_registry_enabled_default=False,
|
||||
|
@ -91,7 +91,7 @@ RPC_UPDATES: Final = {
|
|||
latest_version=lambda status: status.get("stable", {"version": None})[
|
||||
"version"
|
||||
],
|
||||
install=lambda wrapper: wrapper.async_trigger_ota_update(),
|
||||
install=lambda coordinator: coordinator.async_trigger_ota_update(),
|
||||
device_class=UpdateDeviceClass.FIRMWARE,
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
entity_registry_enabled_default=False,
|
||||
|
@ -101,7 +101,7 @@ RPC_UPDATES: Final = {
|
|||
key="sys",
|
||||
sub_key="available_updates",
|
||||
latest_version=lambda status: status.get("beta", {"version": None})["version"],
|
||||
install=lambda wrapper: wrapper.async_trigger_ota_update(beta=True),
|
||||
install=lambda coordinator: coordinator.async_trigger_ota_update(beta=True),
|
||||
device_class=UpdateDeviceClass.FIRMWARE,
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
entity_registry_enabled_default=False,
|
||||
|
@ -140,18 +140,18 @@ class RestUpdateEntity(ShellyRestAttributeEntity, UpdateEntity):
|
|||
|
||||
def __init__(
|
||||
self,
|
||||
wrapper: BlockDeviceWrapper,
|
||||
block_coordinator: ShellyBlockCoordinator,
|
||||
attribute: str,
|
||||
description: RestEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize update entity."""
|
||||
super().__init__(wrapper, attribute, description)
|
||||
super().__init__(block_coordinator, attribute, description)
|
||||
self._in_progress_old_version: str | None = None
|
||||
|
||||
@property
|
||||
def installed_version(self) -> str | None:
|
||||
"""Version currently in use."""
|
||||
version = self.wrapper.device.status["update"]["old_version"]
|
||||
version = self.block_coordinator.device.status["update"]["old_version"]
|
||||
if version is None:
|
||||
return None
|
||||
|
||||
|
@ -161,7 +161,7 @@ class RestUpdateEntity(ShellyRestAttributeEntity, UpdateEntity):
|
|||
def latest_version(self) -> str | None:
|
||||
"""Latest version available for install."""
|
||||
new_version = self.entity_description.latest_version(
|
||||
self.wrapper.device.status,
|
||||
self.block_coordinator.device.status,
|
||||
)
|
||||
if new_version not in (None, ""):
|
||||
return cast(str, new_version)
|
||||
|
@ -177,12 +177,12 @@ class RestUpdateEntity(ShellyRestAttributeEntity, UpdateEntity):
|
|||
self, version: str | None, backup: bool, **kwargs: Any
|
||||
) -> None:
|
||||
"""Install the latest firmware version."""
|
||||
config_entry = self.wrapper.entry
|
||||
block_wrapper = self.hass.data[DOMAIN][DATA_CONFIG_ENTRY][
|
||||
config_entry = self.block_coordinator.entry
|
||||
block_coordinator = self.hass.data[DOMAIN][DATA_CONFIG_ENTRY][
|
||||
config_entry.entry_id
|
||||
].get(BLOCK)
|
||||
self._in_progress_old_version = self.installed_version
|
||||
await self.entity_description.install(block_wrapper)
|
||||
await self.entity_description.install(block_coordinator)
|
||||
|
||||
|
||||
class RpcUpdateEntity(ShellyRpcAttributeEntity, UpdateEntity):
|
||||
|
@ -195,28 +195,28 @@ class RpcUpdateEntity(ShellyRpcAttributeEntity, UpdateEntity):
|
|||
|
||||
def __init__(
|
||||
self,
|
||||
wrapper: RpcDeviceWrapper,
|
||||
coordinator: ShellyRpcCoordinator,
|
||||
key: str,
|
||||
attribute: str,
|
||||
description: RpcEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize update entity."""
|
||||
super().__init__(wrapper, key, attribute, description)
|
||||
super().__init__(coordinator, key, attribute, description)
|
||||
self._in_progress_old_version: str | None = None
|
||||
|
||||
@property
|
||||
def installed_version(self) -> str | None:
|
||||
"""Version currently in use."""
|
||||
if self.wrapper.device.shelly is None:
|
||||
if self.coordinator.device.shelly is None:
|
||||
return None
|
||||
|
||||
return cast(str, self.wrapper.device.shelly["ver"])
|
||||
return cast(str, self.coordinator.device.shelly["ver"])
|
||||
|
||||
@property
|
||||
def latest_version(self) -> str | None:
|
||||
"""Latest version available for install."""
|
||||
new_version = self.entity_description.latest_version(
|
||||
self.wrapper.device.status[self.key][self.entity_description.sub_key],
|
||||
self.coordinator.device.status[self.key][self.entity_description.sub_key],
|
||||
)
|
||||
if new_version is not None:
|
||||
return cast(str, new_version)
|
||||
|
@ -233,4 +233,4 @@ class RpcUpdateEntity(ShellyRpcAttributeEntity, UpdateEntity):
|
|||
) -> None:
|
||||
"""Install the latest firmware version."""
|
||||
self._in_progress_old_version = self.installed_version
|
||||
await self.entity_description.install(self.wrapper)
|
||||
await self.entity_description.install(self.coordinator)
|
||||
|
|
Loading…
Reference in New Issue