Add Shelly Gen2 polling for sesnors missing push updates (#64171)

This commit is contained in:
Shay Levy 2022-01-17 11:58:54 +02:00 committed by GitHub
parent b66fd820ff
commit 601f3f9c6d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 79 additions and 9 deletions

View file

@ -53,7 +53,9 @@ from .const import (
REST_SENSORS_UPDATE_INTERVAL,
RPC,
RPC_INPUTS_EVENTS_TYPES,
RPC_POLL,
RPC_RECONNECT_INTERVAL,
RPC_SENSORS_POLLING_INTERVAL,
SHBTN_MODELS,
SLEEP_PERIOD_MULTIPLIER,
UPDATE_PERIOD_MULTIPLIER,
@ -253,6 +255,10 @@ async def async_setup_rpc_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool
] = RpcDeviceWrapper(hass, entry, device)
device_wrapper.async_setup()
hass.data[DOMAIN][DATA_CONFIG_ENTRY][entry.entry_id][RPC_POLL] = RpcPollingWrapper(
hass, entry, device
)
hass.config_entries.async_setup_platforms(entry, RPC_PLATFORMS)
return True
@ -768,3 +774,45 @@ class RpcDeviceWrapper(update_coordinator.DataUpdateCoordinator):
"""Handle Home Assistant stopping."""
_LOGGER.debug("Stopping RpcDeviceWrapper for %s", self.name)
await self.shutdown()
class RpcPollingWrapper(update_coordinator.DataUpdateCoordinator):
"""Polling Wrapper for a Shelly RPC based device."""
def __init__(
self, hass: HomeAssistant, entry: ConfigEntry, device: RpcDevice
) -> None:
"""Initialize the RPC polling coordinator."""
self.device_id: str | None = None
device_name = get_rpc_device_name(device) if device.initialized else entry.title
super().__init__(
hass,
_LOGGER,
name=device_name,
update_interval=timedelta(seconds=RPC_SENSORS_POLLING_INTERVAL),
)
self.entry = entry
self.device = device
async def _async_update_data(self) -> None:
"""Fetch data."""
if not self.device.connected:
raise update_coordinator.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 as err:
raise update_coordinator.UpdateFailed("Device disconnected") from err
@property
def model(self) -> str:
"""Model of the device."""
return cast(str, self.entry.data["model"])
@property
def mac(self) -> str:
"""Mac address of the device."""
return cast(str, self.entry.unique_id)

View file

@ -10,6 +10,7 @@ DEVICE: Final = "device"
DOMAIN: Final = "shelly"
REST: Final = "rest"
RPC: Final = "rpc"
RPC_POLL: Final = "rpc_poll"
CONF_COAP_PORT: Final = "coap_port"
DEFAULT_COAP_PORT: Final = 5683
@ -53,6 +54,9 @@ POLLING_TIMEOUT_SEC: Final = 18
# Refresh interval for REST sensors
REST_SENSORS_UPDATE_INTERVAL: Final = 60
# Refresh interval for RPC polling sensors
RPC_SENSORS_POLLING_INTERVAL: Final = 60
# Timeout used for aioshelly calls
AIOSHELLY_DEVICE_TIMEOUT_SEC: Final = 10

View file

@ -24,7 +24,12 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.restore_state import RestoreEntity
from homeassistant.helpers.typing import StateType
from . import BlockDeviceWrapper, RpcDeviceWrapper, ShellyDeviceRestWrapper
from . import (
BlockDeviceWrapper,
RpcDeviceWrapper,
RpcPollingWrapper,
ShellyDeviceRestWrapper,
)
from .const import (
AIOSHELLY_DEVICE_TIMEOUT_SEC,
BLOCK,
@ -32,6 +37,7 @@ from .const import (
DOMAIN,
REST,
RPC,
RPC_POLL,
)
from .utils import (
async_remove_shelly_entity,
@ -160,6 +166,10 @@ async def async_setup_entry_rpc(
config_entry.entry_id
][RPC]
polling_wrapper: RpcPollingWrapper = hass.data[DOMAIN][DATA_CONFIG_ENTRY][
config_entry.entry_id
][RPC_POLL]
entities = []
for sensor_id in sensors:
description = sensors[sensor_id]
@ -178,17 +188,17 @@ async def async_setup_entry_rpc(
unique_id = f"{wrapper.mac}-{key}-{sensor_id}"
await async_remove_shelly_entity(hass, domain, unique_id)
else:
entities.append((key, sensor_id, description))
if description.should_poll:
entities.append(
sensor_class(polling_wrapper, key, sensor_id, description)
)
else:
entities.append(sensor_class(wrapper, key, sensor_id, description))
if not entities:
return
async_add_entities(
[
sensor_class(wrapper, key, sensor_id, description)
for key, sensor_id, description in entities
]
)
async_add_entities(entities)
async def async_setup_entry_rest(
@ -257,6 +267,7 @@ class RpcAttributeDescription:
removal_condition: Callable[[dict, str], bool] | None = None
extra_state_attributes: Callable[[dict, dict], dict | None] | None = None
entity_category: EntityCategory | None = None
should_poll: bool = False
@dataclass
@ -343,7 +354,11 @@ class ShellyBlockEntity(entity.Entity):
class ShellyRpcEntity(entity.Entity):
"""Helper class to represent a rpc entity."""
def __init__(self, wrapper: RpcDeviceWrapper, key: str) -> None:
def __init__(
self,
wrapper: RpcDeviceWrapper | RpcPollingWrapper,
key: str,
) -> None:
"""Initialize Shelly entity."""
self.wrapper = wrapper
self.key = key

View file

@ -287,6 +287,7 @@ RPC_SENSORS: Final = {
state_class=SensorStateClass.MEASUREMENT,
default_enabled=False,
entity_category=EntityCategory.DIAGNOSTIC,
should_poll=True,
),
"rssi": RpcAttributeDescription(
key="wifi",
@ -297,6 +298,7 @@ RPC_SENSORS: Final = {
state_class=SensorStateClass.MEASUREMENT,
default_enabled=False,
entity_category=EntityCategory.DIAGNOSTIC,
should_poll=True,
),
"uptime": RpcAttributeDescription(
key="sys",
@ -306,6 +308,7 @@ RPC_SENSORS: Final = {
device_class=SensorDeviceClass.TIMESTAMP,
default_enabled=False,
entity_category=EntityCategory.DIAGNOSTIC,
should_poll=True,
),
}