mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 21:27:38 +00:00
Add switch support for RPC device (#56153)
* Add switch support for RPC device * Apply review comments * Apply review comments
This commit is contained in:
parent
f1a88f0563
commit
e9eb76c7db
@ -8,6 +8,7 @@ from typing import Any, Final, cast
|
|||||||
|
|
||||||
import aioshelly
|
import aioshelly
|
||||||
from aioshelly.block_device import BlockDevice
|
from aioshelly.block_device import BlockDevice
|
||||||
|
from aioshelly.rpc_device import RpcDevice
|
||||||
import async_timeout
|
import async_timeout
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
@ -31,7 +32,7 @@ from .const import (
|
|||||||
ATTR_CLICK_TYPE,
|
ATTR_CLICK_TYPE,
|
||||||
ATTR_DEVICE,
|
ATTR_DEVICE,
|
||||||
BATTERY_DEVICES_WITH_PERMANENT_CONNECTION,
|
BATTERY_DEVICES_WITH_PERMANENT_CONNECTION,
|
||||||
COAP,
|
BLOCK,
|
||||||
CONF_COAP_PORT,
|
CONF_COAP_PORT,
|
||||||
DATA_CONFIG_ENTRY,
|
DATA_CONFIG_ENTRY,
|
||||||
DEFAULT_COAP_PORT,
|
DEFAULT_COAP_PORT,
|
||||||
@ -42,6 +43,8 @@ from .const import (
|
|||||||
POLLING_TIMEOUT_SEC,
|
POLLING_TIMEOUT_SEC,
|
||||||
REST,
|
REST,
|
||||||
REST_SENSORS_UPDATE_INTERVAL,
|
REST_SENSORS_UPDATE_INTERVAL,
|
||||||
|
RPC,
|
||||||
|
RPC_RECONNECT_INTERVAL,
|
||||||
SHBTN_MODELS,
|
SHBTN_MODELS,
|
||||||
SLEEP_PERIOD_MULTIPLIER,
|
SLEEP_PERIOD_MULTIPLIER,
|
||||||
UPDATE_PERIOD_MULTIPLIER,
|
UPDATE_PERIOD_MULTIPLIER,
|
||||||
@ -50,10 +53,13 @@ from .utils import (
|
|||||||
get_block_device_name,
|
get_block_device_name,
|
||||||
get_block_device_sleep_period,
|
get_block_device_sleep_period,
|
||||||
get_coap_context,
|
get_coap_context,
|
||||||
|
get_device_entry_gen,
|
||||||
|
get_rpc_device_name,
|
||||||
)
|
)
|
||||||
|
|
||||||
PLATFORMS: Final = ["binary_sensor", "cover", "light", "sensor", "switch"]
|
BLOCK_PLATFORMS: Final = ["binary_sensor", "cover", "light", "sensor", "switch"]
|
||||||
SLEEPING_PLATFORMS: Final = ["binary_sensor", "sensor"]
|
BLOCK_SLEEPING_PLATFORMS: Final = ["binary_sensor", "sensor"]
|
||||||
|
RPC_PLATFORMS: Final = ["light", "switch"]
|
||||||
_LOGGER: Final = logging.getLogger(__name__)
|
_LOGGER: Final = logging.getLogger(__name__)
|
||||||
|
|
||||||
COAP_SCHEMA: Final = vol.Schema(
|
COAP_SCHEMA: Final = vol.Schema(
|
||||||
@ -89,12 +95,17 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if entry.data.get("gen") == 2:
|
|
||||||
return True
|
|
||||||
|
|
||||||
hass.data[DOMAIN][DATA_CONFIG_ENTRY][entry.entry_id] = {}
|
hass.data[DOMAIN][DATA_CONFIG_ENTRY][entry.entry_id] = {}
|
||||||
hass.data[DOMAIN][DATA_CONFIG_ENTRY][entry.entry_id][DEVICE] = None
|
hass.data[DOMAIN][DATA_CONFIG_ENTRY][entry.entry_id][DEVICE] = None
|
||||||
|
|
||||||
|
if get_device_entry_gen(entry) == 2:
|
||||||
|
return await async_setup_rpc_entry(hass, entry)
|
||||||
|
|
||||||
|
return await async_setup_block_entry(hass, entry)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_block_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
|
"""Set up Shelly block based device from a config entry."""
|
||||||
temperature_unit = "C" if hass.config.units.is_metric else "F"
|
temperature_unit = "C" if hass.config.units.is_metric else "F"
|
||||||
|
|
||||||
options = aioshelly.common.ConnectionOptions(
|
options = aioshelly.common.ConnectionOptions(
|
||||||
@ -113,7 +124,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
False,
|
False,
|
||||||
)
|
)
|
||||||
|
|
||||||
dev_reg = await device_registry.async_get_registry(hass)
|
dev_reg = device_registry.async_get(hass)
|
||||||
device_entry = None
|
device_entry = None
|
||||||
if entry.unique_id is not None:
|
if entry.unique_id is not None:
|
||||||
device_entry = dev_reg.async_get_device(
|
device_entry = dev_reg.async_get_device(
|
||||||
@ -135,18 +146,18 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
data["model"] = device.settings["device"]["type"]
|
data["model"] = device.settings["device"]["type"]
|
||||||
hass.config_entries.async_update_entry(entry, data=data)
|
hass.config_entries.async_update_entry(entry, data=data)
|
||||||
|
|
||||||
hass.async_create_task(async_device_setup(hass, entry, device))
|
hass.async_create_task(async_block_device_setup(hass, entry, device))
|
||||||
|
|
||||||
if sleep_period == 0:
|
if sleep_period == 0:
|
||||||
# Not a sleeping device, finish setup
|
# Not a sleeping device, finish setup
|
||||||
_LOGGER.debug("Setting up online device %s", entry.title)
|
_LOGGER.debug("Setting up online block device %s", entry.title)
|
||||||
try:
|
try:
|
||||||
async with async_timeout.timeout(AIOSHELLY_DEVICE_TIMEOUT_SEC):
|
async with async_timeout.timeout(AIOSHELLY_DEVICE_TIMEOUT_SEC):
|
||||||
await device.initialize()
|
await device.initialize()
|
||||||
except (asyncio.TimeoutError, OSError) as err:
|
except (asyncio.TimeoutError, OSError) as err:
|
||||||
raise ConfigEntryNotReady from err
|
raise ConfigEntryNotReady from err
|
||||||
|
|
||||||
await async_device_setup(hass, entry, device)
|
await async_block_device_setup(hass, entry, device)
|
||||||
elif sleep_period is None or device_entry is None:
|
elif sleep_period is None or device_entry is None:
|
||||||
# Need to get sleep info or first time sleeping device setup, wait for device
|
# Need to get sleep info or first time sleeping device setup, wait for device
|
||||||
hass.data[DOMAIN][DATA_CONFIG_ENTRY][entry.entry_id][DEVICE] = device
|
hass.data[DOMAIN][DATA_CONFIG_ENTRY][entry.entry_id][DEVICE] = device
|
||||||
@ -156,34 +167,61 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
device.subscribe_updates(_async_device_online)
|
device.subscribe_updates(_async_device_online)
|
||||||
else:
|
else:
|
||||||
# Restore sensors for sleeping device
|
# Restore sensors for sleeping device
|
||||||
_LOGGER.debug("Setting up offline device %s", entry.title)
|
_LOGGER.debug("Setting up offline block device %s", entry.title)
|
||||||
await async_device_setup(hass, entry, device)
|
await async_block_device_setup(hass, entry, device)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
async def async_device_setup(
|
async def async_block_device_setup(
|
||||||
hass: HomeAssistant, entry: ConfigEntry, device: BlockDevice
|
hass: HomeAssistant, entry: ConfigEntry, device: BlockDevice
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up a device that is online."""
|
"""Set up a block based device that is online."""
|
||||||
device_wrapper = hass.data[DOMAIN][DATA_CONFIG_ENTRY][entry.entry_id][
|
device_wrapper = hass.data[DOMAIN][DATA_CONFIG_ENTRY][entry.entry_id][
|
||||||
COAP
|
BLOCK
|
||||||
] = ShellyDeviceWrapper(hass, entry, device)
|
] = BlockDeviceWrapper(hass, entry, device)
|
||||||
await device_wrapper.async_setup()
|
device_wrapper.async_setup()
|
||||||
|
|
||||||
platforms = SLEEPING_PLATFORMS
|
platforms = BLOCK_SLEEPING_PLATFORMS
|
||||||
|
|
||||||
if not entry.data.get("sleep_period"):
|
if not entry.data.get("sleep_period"):
|
||||||
hass.data[DOMAIN][DATA_CONFIG_ENTRY][entry.entry_id][
|
hass.data[DOMAIN][DATA_CONFIG_ENTRY][entry.entry_id][
|
||||||
REST
|
REST
|
||||||
] = ShellyDeviceRestWrapper(hass, device)
|
] = ShellyDeviceRestWrapper(hass, device)
|
||||||
platforms = PLATFORMS
|
platforms = BLOCK_PLATFORMS
|
||||||
|
|
||||||
hass.config_entries.async_setup_platforms(entry, platforms)
|
hass.config_entries.async_setup_platforms(entry, platforms)
|
||||||
|
|
||||||
|
|
||||||
class ShellyDeviceWrapper(update_coordinator.DataUpdateCoordinator):
|
async def async_setup_rpc_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
"""Wrapper for a Shelly device with Home Assistant specific functions."""
|
"""Set up Shelly RPC based device from a config entry."""
|
||||||
|
options = aioshelly.common.ConnectionOptions(
|
||||||
|
entry.data[CONF_HOST],
|
||||||
|
entry.data.get(CONF_USERNAME),
|
||||||
|
entry.data.get(CONF_PASSWORD),
|
||||||
|
)
|
||||||
|
|
||||||
|
_LOGGER.debug("Setting up online RPC device %s", entry.title)
|
||||||
|
try:
|
||||||
|
async with async_timeout.timeout(AIOSHELLY_DEVICE_TIMEOUT_SEC):
|
||||||
|
device = await RpcDevice.create(
|
||||||
|
aiohttp_client.async_get_clientsession(hass), options
|
||||||
|
)
|
||||||
|
except (asyncio.TimeoutError, OSError) as err:
|
||||||
|
raise ConfigEntryNotReady from err
|
||||||
|
|
||||||
|
device_wrapper = hass.data[DOMAIN][DATA_CONFIG_ENTRY][entry.entry_id][
|
||||||
|
RPC
|
||||||
|
] = RpcDeviceWrapper(hass, entry, device)
|
||||||
|
device_wrapper.async_setup()
|
||||||
|
|
||||||
|
hass.config_entries.async_setup_platforms(entry, RPC_PLATFORMS)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class BlockDeviceWrapper(update_coordinator.DataUpdateCoordinator):
|
||||||
|
"""Wrapper for a Shelly block based device with Home Assistant specific functions."""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, hass: HomeAssistant, entry: ConfigEntry, device: BlockDevice
|
self, hass: HomeAssistant, entry: ConfigEntry, device: BlockDevice
|
||||||
@ -283,7 +321,7 @@ class ShellyDeviceWrapper(update_coordinator.DataUpdateCoordinator):
|
|||||||
# Sleeping device, no point polling it, just mark it unavailable
|
# Sleeping device, no point polling it, just mark it unavailable
|
||||||
raise update_coordinator.UpdateFailed("Sleeping device did not update")
|
raise update_coordinator.UpdateFailed("Sleeping device did not update")
|
||||||
|
|
||||||
_LOGGER.debug("Polling Shelly Device - %s", self.name)
|
_LOGGER.debug("Polling Shelly Block Device - %s", self.name)
|
||||||
try:
|
try:
|
||||||
async with async_timeout.timeout(POLLING_TIMEOUT_SEC):
|
async with async_timeout.timeout(POLLING_TIMEOUT_SEC):
|
||||||
await self.device.update()
|
await self.device.update()
|
||||||
@ -300,16 +338,14 @@ class ShellyDeviceWrapper(update_coordinator.DataUpdateCoordinator):
|
|||||||
"""Mac address of the device."""
|
"""Mac address of the device."""
|
||||||
return cast(str, self.entry.unique_id)
|
return cast(str, self.entry.unique_id)
|
||||||
|
|
||||||
async def async_setup(self) -> None:
|
def async_setup(self) -> None:
|
||||||
"""Set up the wrapper."""
|
"""Set up the wrapper."""
|
||||||
dev_reg = await device_registry.async_get_registry(self.hass)
|
dev_reg = device_registry.async_get(self.hass)
|
||||||
sw_version = self.device.settings["fw"] if self.device.initialized else ""
|
sw_version = self.device.firmware_version if self.device.initialized else ""
|
||||||
entry = dev_reg.async_get_or_create(
|
entry = dev_reg.async_get_or_create(
|
||||||
config_entry_id=self.entry.entry_id,
|
config_entry_id=self.entry.entry_id,
|
||||||
name=self.name,
|
name=self.name,
|
||||||
connections={(device_registry.CONNECTION_NETWORK_MAC, self.mac)},
|
connections={(device_registry.CONNECTION_NETWORK_MAC, self.mac)},
|
||||||
# This is duplicate but otherwise via_device can't work
|
|
||||||
identifiers={(DOMAIN, self.mac)},
|
|
||||||
manufacturer="Shelly",
|
manufacturer="Shelly",
|
||||||
model=aioshelly.const.MODEL_NAMES.get(self.model, self.model),
|
model=aioshelly.const.MODEL_NAMES.get(self.model, self.model),
|
||||||
sw_version=sw_version,
|
sw_version=sw_version,
|
||||||
@ -325,7 +361,7 @@ class ShellyDeviceWrapper(update_coordinator.DataUpdateCoordinator):
|
|||||||
@callback
|
@callback
|
||||||
def _handle_ha_stop(self, _event: Event) -> None:
|
def _handle_ha_stop(self, _event: Event) -> None:
|
||||||
"""Handle Home Assistant stopping."""
|
"""Handle Home Assistant stopping."""
|
||||||
_LOGGER.debug("Stopping ShellyDeviceWrapper for %s", self.name)
|
_LOGGER.debug("Stopping BlockDeviceWrapper for %s", self.name)
|
||||||
self.shutdown()
|
self.shutdown()
|
||||||
|
|
||||||
|
|
||||||
@ -369,8 +405,15 @@ class ShellyDeviceRestWrapper(update_coordinator.DataUpdateCoordinator):
|
|||||||
|
|
||||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
"""Unload a config entry."""
|
"""Unload a config entry."""
|
||||||
if entry.data.get("gen") == 2:
|
if get_device_entry_gen(entry) == 2:
|
||||||
return True
|
unload_ok = await hass.config_entries.async_unload_platforms(
|
||||||
|
entry, RPC_PLATFORMS
|
||||||
|
)
|
||||||
|
if unload_ok:
|
||||||
|
hass.data[DOMAIN][DATA_CONFIG_ENTRY][entry.entry_id][RPC].shutdown()
|
||||||
|
hass.data[DOMAIN][DATA_CONFIG_ENTRY].pop(entry.entry_id)
|
||||||
|
|
||||||
|
return unload_ok
|
||||||
|
|
||||||
device = hass.data[DOMAIN][DATA_CONFIG_ENTRY][entry.entry_id].get(DEVICE)
|
device = hass.data[DOMAIN][DATA_CONFIG_ENTRY][entry.entry_id].get(DEVICE)
|
||||||
if device is not None:
|
if device is not None:
|
||||||
@ -378,15 +421,15 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
device.shutdown()
|
device.shutdown()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
platforms = SLEEPING_PLATFORMS
|
platforms = BLOCK_SLEEPING_PLATFORMS
|
||||||
|
|
||||||
if not entry.data.get("sleep_period"):
|
if not entry.data.get("sleep_period"):
|
||||||
hass.data[DOMAIN][DATA_CONFIG_ENTRY][entry.entry_id][REST] = None
|
hass.data[DOMAIN][DATA_CONFIG_ENTRY][entry.entry_id][REST] = None
|
||||||
platforms = PLATFORMS
|
platforms = BLOCK_PLATFORMS
|
||||||
|
|
||||||
unload_ok = await hass.config_entries.async_unload_platforms(entry, platforms)
|
unload_ok = await hass.config_entries.async_unload_platforms(entry, platforms)
|
||||||
if unload_ok:
|
if unload_ok:
|
||||||
hass.data[DOMAIN][DATA_CONFIG_ENTRY][entry.entry_id][COAP].shutdown()
|
hass.data[DOMAIN][DATA_CONFIG_ENTRY][entry.entry_id][BLOCK].shutdown()
|
||||||
hass.data[DOMAIN][DATA_CONFIG_ENTRY].pop(entry.entry_id)
|
hass.data[DOMAIN][DATA_CONFIG_ENTRY].pop(entry.entry_id)
|
||||||
|
|
||||||
return unload_ok
|
return unload_ok
|
||||||
@ -394,17 +437,94 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
|
|
||||||
def get_device_wrapper(
|
def get_device_wrapper(
|
||||||
hass: HomeAssistant, device_id: str
|
hass: HomeAssistant, device_id: str
|
||||||
) -> ShellyDeviceWrapper | None:
|
) -> BlockDeviceWrapper | RpcDeviceWrapper | None:
|
||||||
"""Get a Shelly device wrapper for the given device id."""
|
"""Get a Shelly device wrapper for the given device id."""
|
||||||
if not hass.data.get(DOMAIN):
|
if not hass.data.get(DOMAIN):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
for config_entry in hass.data[DOMAIN][DATA_CONFIG_ENTRY]:
|
for config_entry in hass.data[DOMAIN][DATA_CONFIG_ENTRY]:
|
||||||
wrapper: ShellyDeviceWrapper | None = hass.data[DOMAIN][DATA_CONFIG_ENTRY][
|
block_wrapper: BlockDeviceWrapper | None = hass.data[DOMAIN][DATA_CONFIG_ENTRY][
|
||||||
config_entry
|
config_entry
|
||||||
].get(COAP)
|
].get(BLOCK)
|
||||||
|
|
||||||
if wrapper and wrapper.device_id == device_id:
|
if block_wrapper and block_wrapper.device_id == device_id:
|
||||||
return wrapper
|
return block_wrapper
|
||||||
|
|
||||||
|
rpc_wrapper: RpcDeviceWrapper | None = hass.data[DOMAIN][DATA_CONFIG_ENTRY][
|
||||||
|
config_entry
|
||||||
|
].get(RPC)
|
||||||
|
|
||||||
|
if rpc_wrapper and rpc_wrapper.device_id == device_id:
|
||||||
|
return rpc_wrapper
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class RpcDeviceWrapper(update_coordinator.DataUpdateCoordinator):
|
||||||
|
"""Wrapper for a Shelly RPC based device with Home Assistant specific functions."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self, hass: HomeAssistant, entry: ConfigEntry, device: RpcDevice
|
||||||
|
) -> None:
|
||||||
|
"""Initialize the Shelly device wrapper."""
|
||||||
|
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_RECONNECT_INTERVAL),
|
||||||
|
)
|
||||||
|
self.entry = entry
|
||||||
|
self.device = device
|
||||||
|
|
||||||
|
entry.async_on_unload(
|
||||||
|
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, self._handle_ha_stop)
|
||||||
|
)
|
||||||
|
|
||||||
|
async def _async_update_data(self) -> None:
|
||||||
|
"""Fetch data."""
|
||||||
|
if self.device.connected:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
_LOGGER.debug("Reconnecting to Shelly RPC Device - %s", self.name)
|
||||||
|
async with async_timeout.timeout(AIOSHELLY_DEVICE_TIMEOUT_SEC):
|
||||||
|
await self.device.initialize()
|
||||||
|
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)
|
||||||
|
|
||||||
|
def async_setup(self) -> None:
|
||||||
|
"""Set up the wrapper."""
|
||||||
|
dev_reg = device_registry.async_get(self.hass)
|
||||||
|
sw_version = self.device.firmware_version if self.device.initialized else ""
|
||||||
|
entry = dev_reg.async_get_or_create(
|
||||||
|
config_entry_id=self.entry.entry_id,
|
||||||
|
name=self.name,
|
||||||
|
connections={(device_registry.CONNECTION_NETWORK_MAC, self.mac)},
|
||||||
|
manufacturer="Shelly",
|
||||||
|
model=aioshelly.const.MODEL_NAMES.get(self.model, self.model),
|
||||||
|
sw_version=sw_version,
|
||||||
|
)
|
||||||
|
self.device_id = entry.id
|
||||||
|
self.device.subscribe_updates(self.async_set_updated_data)
|
||||||
|
|
||||||
|
async def shutdown(self) -> None:
|
||||||
|
"""Shutdown the wrapper."""
|
||||||
|
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)
|
||||||
|
await self.shutdown()
|
||||||
|
@ -4,11 +4,12 @@ from __future__ import annotations
|
|||||||
import re
|
import re
|
||||||
from typing import Final
|
from typing import Final
|
||||||
|
|
||||||
COAP: Final = "coap"
|
BLOCK: Final = "block"
|
||||||
DATA_CONFIG_ENTRY: Final = "config_entry"
|
DATA_CONFIG_ENTRY: Final = "config_entry"
|
||||||
DEVICE: Final = "device"
|
DEVICE: Final = "device"
|
||||||
DOMAIN: Final = "shelly"
|
DOMAIN: Final = "shelly"
|
||||||
REST: Final = "rest"
|
REST: Final = "rest"
|
||||||
|
RPC: Final = "rpc"
|
||||||
|
|
||||||
CONF_COAP_PORT: Final = "coap_port"
|
CONF_COAP_PORT: Final = "coap_port"
|
||||||
DEFAULT_COAP_PORT: Final = 5683
|
DEFAULT_COAP_PORT: Final = 5683
|
||||||
@ -44,6 +45,9 @@ SLEEP_PERIOD_MULTIPLIER: Final = 1.2
|
|||||||
# Multiplier used to calculate the "update_interval" for non-sleeping devices.
|
# Multiplier used to calculate the "update_interval" for non-sleeping devices.
|
||||||
UPDATE_PERIOD_MULTIPLIER: Final = 2.2
|
UPDATE_PERIOD_MULTIPLIER: Final = 2.2
|
||||||
|
|
||||||
|
# Reconnect interval for GEN2 devices
|
||||||
|
RPC_RECONNECT_INTERVAL = 60
|
||||||
|
|
||||||
# Shelly Air - Maximum work hours before lamp replacement
|
# Shelly Air - Maximum work hours before lamp replacement
|
||||||
SHAIR_MAX_WORK_HOURS: Final = 9000
|
SHAIR_MAX_WORK_HOURS: Final = 9000
|
||||||
|
|
||||||
|
@ -18,8 +18,8 @@ from homeassistant.config_entries import ConfigEntry
|
|||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
from . import ShellyDeviceWrapper
|
from . import BlockDeviceWrapper
|
||||||
from .const import COAP, DATA_CONFIG_ENTRY, DOMAIN
|
from .const import BLOCK, DATA_CONFIG_ENTRY, DOMAIN
|
||||||
from .entity import ShellyBlockEntity
|
from .entity import ShellyBlockEntity
|
||||||
|
|
||||||
|
|
||||||
@ -29,7 +29,7 @@ async def async_setup_entry(
|
|||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up cover for device."""
|
"""Set up cover for device."""
|
||||||
wrapper = hass.data[DOMAIN][DATA_CONFIG_ENTRY][config_entry.entry_id][COAP]
|
wrapper = hass.data[DOMAIN][DATA_CONFIG_ENTRY][config_entry.entry_id][BLOCK]
|
||||||
blocks = [block for block in wrapper.device.blocks if block.type == "roller"]
|
blocks = [block for block in wrapper.device.blocks if block.type == "roller"]
|
||||||
|
|
||||||
if not blocks:
|
if not blocks:
|
||||||
@ -43,7 +43,7 @@ class ShellyCover(ShellyBlockEntity, CoverEntity):
|
|||||||
|
|
||||||
_attr_device_class = DEVICE_CLASS_SHUTTER
|
_attr_device_class = DEVICE_CLASS_SHUTTER
|
||||||
|
|
||||||
def __init__(self, wrapper: ShellyDeviceWrapper, block: Block) -> None:
|
def __init__(self, wrapper: BlockDeviceWrapper, block: Block) -> None:
|
||||||
"""Initialize light."""
|
"""Initialize light."""
|
||||||
super().__init__(wrapper, block)
|
super().__init__(wrapper, block)
|
||||||
self.control_result: dict[str, Any] | None = None
|
self.control_result: dict[str, Any] | None = None
|
||||||
|
@ -25,7 +25,7 @@ from homeassistant.const import (
|
|||||||
from homeassistant.core import CALLBACK_TYPE, HomeAssistant
|
from homeassistant.core import CALLBACK_TYPE, HomeAssistant
|
||||||
from homeassistant.helpers.typing import ConfigType
|
from homeassistant.helpers.typing import ConfigType
|
||||||
|
|
||||||
from . import get_device_wrapper
|
from . import RpcDeviceWrapper, get_device_wrapper
|
||||||
from .const import (
|
from .const import (
|
||||||
ATTR_CHANNEL,
|
ATTR_CHANNEL,
|
||||||
ATTR_CLICK_TYPE,
|
ATTR_CLICK_TYPE,
|
||||||
@ -55,6 +55,10 @@ async def async_validate_trigger_config(
|
|||||||
|
|
||||||
# if device is available verify parameters against device capabilities
|
# if device is available verify parameters against device capabilities
|
||||||
wrapper = get_device_wrapper(hass, config[CONF_DEVICE_ID])
|
wrapper = get_device_wrapper(hass, config[CONF_DEVICE_ID])
|
||||||
|
|
||||||
|
if isinstance(wrapper, RpcDeviceWrapper):
|
||||||
|
return config
|
||||||
|
|
||||||
if not wrapper or not wrapper.device.initialized:
|
if not wrapper or not wrapper.device.initialized:
|
||||||
return config
|
return config
|
||||||
|
|
||||||
@ -76,12 +80,15 @@ async def async_get_triggers(
|
|||||||
hass: HomeAssistant, device_id: str
|
hass: HomeAssistant, device_id: str
|
||||||
) -> list[dict[str, Any]]:
|
) -> list[dict[str, Any]]:
|
||||||
"""List device triggers for Shelly devices."""
|
"""List device triggers for Shelly devices."""
|
||||||
triggers = []
|
|
||||||
|
|
||||||
wrapper = get_device_wrapper(hass, device_id)
|
wrapper = get_device_wrapper(hass, device_id)
|
||||||
if not wrapper:
|
if not wrapper:
|
||||||
raise InvalidDeviceAutomationConfig(f"Device not found: {device_id}")
|
raise InvalidDeviceAutomationConfig(f"Device not found: {device_id}")
|
||||||
|
|
||||||
|
if isinstance(wrapper, RpcDeviceWrapper):
|
||||||
|
return []
|
||||||
|
|
||||||
|
triggers = []
|
||||||
|
|
||||||
if wrapper.model in SHBTN_MODELS:
|
if wrapper.model in SHBTN_MODELS:
|
||||||
for trigger in SHBTN_INPUTS_EVENTS_TYPES:
|
for trigger in SHBTN_INPUTS_EVENTS_TYPES:
|
||||||
triggers.append(
|
triggers.append(
|
||||||
|
@ -23,9 +23,13 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
|||||||
from homeassistant.helpers.restore_state import RestoreEntity
|
from homeassistant.helpers.restore_state import RestoreEntity
|
||||||
from homeassistant.helpers.typing import StateType
|
from homeassistant.helpers.typing import StateType
|
||||||
|
|
||||||
from . import ShellyDeviceRestWrapper, ShellyDeviceWrapper
|
from . import BlockDeviceWrapper, RpcDeviceWrapper, ShellyDeviceRestWrapper
|
||||||
from .const import AIOSHELLY_DEVICE_TIMEOUT_SEC, COAP, DATA_CONFIG_ENTRY, DOMAIN, REST
|
from .const import AIOSHELLY_DEVICE_TIMEOUT_SEC, BLOCK, DATA_CONFIG_ENTRY, DOMAIN, REST
|
||||||
from .utils import async_remove_shelly_entity, get_entity_name
|
from .utils import (
|
||||||
|
async_remove_shelly_entity,
|
||||||
|
get_block_entity_name,
|
||||||
|
get_rpc_entity_name,
|
||||||
|
)
|
||||||
|
|
||||||
_LOGGER: Final = logging.getLogger(__name__)
|
_LOGGER: Final = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -38,9 +42,9 @@ async def async_setup_entry_attribute_entities(
|
|||||||
sensor_class: Callable,
|
sensor_class: Callable,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up entities for attributes."""
|
"""Set up entities for attributes."""
|
||||||
wrapper: ShellyDeviceWrapper = hass.data[DOMAIN][DATA_CONFIG_ENTRY][
|
wrapper: BlockDeviceWrapper = hass.data[DOMAIN][DATA_CONFIG_ENTRY][
|
||||||
config_entry.entry_id
|
config_entry.entry_id
|
||||||
][COAP]
|
][BLOCK]
|
||||||
|
|
||||||
if wrapper.device.initialized:
|
if wrapper.device.initialized:
|
||||||
await async_setup_block_attribute_entities(
|
await async_setup_block_attribute_entities(
|
||||||
@ -55,7 +59,7 @@ async def async_setup_entry_attribute_entities(
|
|||||||
async def async_setup_block_attribute_entities(
|
async def async_setup_block_attribute_entities(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddEntitiesCallback,
|
||||||
wrapper: ShellyDeviceWrapper,
|
wrapper: BlockDeviceWrapper,
|
||||||
sensors: dict[tuple[str, str], BlockAttributeDescription],
|
sensors: dict[tuple[str, str], BlockAttributeDescription],
|
||||||
sensor_class: Callable,
|
sensor_class: Callable,
|
||||||
) -> None:
|
) -> None:
|
||||||
@ -99,7 +103,7 @@ async def async_restore_block_attribute_entities(
|
|||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
config_entry: ConfigEntry,
|
config_entry: ConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddEntitiesCallback,
|
||||||
wrapper: ShellyDeviceWrapper,
|
wrapper: BlockDeviceWrapper,
|
||||||
sensors: dict[tuple[str, str], BlockAttributeDescription],
|
sensors: dict[tuple[str, str], BlockAttributeDescription],
|
||||||
sensor_class: Callable,
|
sensor_class: Callable,
|
||||||
) -> None:
|
) -> None:
|
||||||
@ -198,13 +202,13 @@ class RestAttributeDescription:
|
|||||||
|
|
||||||
|
|
||||||
class ShellyBlockEntity(entity.Entity):
|
class ShellyBlockEntity(entity.Entity):
|
||||||
"""Helper class to represent a block."""
|
"""Helper class to represent a block entity."""
|
||||||
|
|
||||||
def __init__(self, wrapper: ShellyDeviceWrapper, block: Block) -> None:
|
def __init__(self, wrapper: BlockDeviceWrapper, block: Block) -> None:
|
||||||
"""Initialize Shelly entity."""
|
"""Initialize Shelly entity."""
|
||||||
self.wrapper = wrapper
|
self.wrapper = wrapper
|
||||||
self.block = block
|
self.block = block
|
||||||
self._name = get_entity_name(wrapper.device, block)
|
self._name = get_block_entity_name(wrapper.device, block)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self) -> str:
|
def name(self) -> str:
|
||||||
@ -263,12 +267,67 @@ class ShellyBlockEntity(entity.Entity):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class ShellyRpcEntity(entity.Entity):
|
||||||
|
"""Helper class to represent a rpc entity."""
|
||||||
|
|
||||||
|
def __init__(self, wrapper: RpcDeviceWrapper, key: str) -> None:
|
||||||
|
"""Initialize Shelly entity."""
|
||||||
|
self.wrapper = wrapper
|
||||||
|
self.key = key
|
||||||
|
self._attr_should_poll = False
|
||||||
|
self._attr_device_info = {
|
||||||
|
"connections": {(device_registry.CONNECTION_NETWORK_MAC, wrapper.mac)}
|
||||||
|
}
|
||||||
|
self._attr_unique_id = f"{wrapper.mac}-{key}"
|
||||||
|
self._attr_name = get_rpc_entity_name(wrapper.device, key)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def available(self) -> bool:
|
||||||
|
"""Available."""
|
||||||
|
return self.wrapper.device.connected
|
||||||
|
|
||||||
|
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))
|
||||||
|
|
||||||
|
async def async_update(self) -> None:
|
||||||
|
"""Update entity with latest info."""
|
||||||
|
await self.wrapper.async_request_refresh()
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def _update_callback(self) -> None:
|
||||||
|
"""Handle device update."""
|
||||||
|
self.async_write_ha_state()
|
||||||
|
|
||||||
|
async def call_rpc(self, method: str, params: Any) -> Any:
|
||||||
|
"""Call RPC method."""
|
||||||
|
_LOGGER.debug(
|
||||||
|
"Call RPC for entity %s, method: %s, params: %s",
|
||||||
|
self.name,
|
||||||
|
method,
|
||||||
|
params,
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
async with async_timeout.timeout(AIOSHELLY_DEVICE_TIMEOUT_SEC):
|
||||||
|
return await self.wrapper.device.call_rpc(method, params)
|
||||||
|
except asyncio.TimeoutError as err:
|
||||||
|
_LOGGER.error(
|
||||||
|
"Call RPC for entity %s failed, method: %s, params: %s, error: %s",
|
||||||
|
self.name,
|
||||||
|
method,
|
||||||
|
params,
|
||||||
|
repr(err),
|
||||||
|
)
|
||||||
|
self.wrapper.last_update_success = False
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
class ShellyBlockAttributeEntity(ShellyBlockEntity, entity.Entity):
|
class ShellyBlockAttributeEntity(ShellyBlockEntity, entity.Entity):
|
||||||
"""Helper class to represent a block attribute."""
|
"""Helper class to represent a block attribute."""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
wrapper: ShellyDeviceWrapper,
|
wrapper: BlockDeviceWrapper,
|
||||||
block: Block,
|
block: Block,
|
||||||
attribute: str,
|
attribute: str,
|
||||||
description: BlockAttributeDescription,
|
description: BlockAttributeDescription,
|
||||||
@ -285,7 +344,7 @@ class ShellyBlockAttributeEntity(ShellyBlockEntity, entity.Entity):
|
|||||||
|
|
||||||
self._unit: None | str | Callable[[dict], str] = unit
|
self._unit: None | str | Callable[[dict], str] = unit
|
||||||
self._unique_id: str = f"{super().unique_id}-{self.attribute}"
|
self._unique_id: str = f"{super().unique_id}-{self.attribute}"
|
||||||
self._name = get_entity_name(wrapper.device, block, self.description.name)
|
self._name = get_block_entity_name(wrapper.device, block, self.description.name)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def unique_id(self) -> str:
|
def unique_id(self) -> str:
|
||||||
@ -346,7 +405,7 @@ class ShellyRestAttributeEntity(update_coordinator.CoordinatorEntity):
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
wrapper: ShellyDeviceWrapper,
|
wrapper: BlockDeviceWrapper,
|
||||||
attribute: str,
|
attribute: str,
|
||||||
description: RestAttributeDescription,
|
description: RestAttributeDescription,
|
||||||
) -> None:
|
) -> None:
|
||||||
@ -355,7 +414,7 @@ class ShellyRestAttributeEntity(update_coordinator.CoordinatorEntity):
|
|||||||
self.wrapper = wrapper
|
self.wrapper = wrapper
|
||||||
self.attribute = attribute
|
self.attribute = attribute
|
||||||
self.description = description
|
self.description = description
|
||||||
self._name = get_entity_name(wrapper.device, None, self.description.name)
|
self._name = get_block_entity_name(wrapper.device, None, self.description.name)
|
||||||
self._last_value = None
|
self._last_value = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -419,7 +478,7 @@ class ShellySleepingBlockAttributeEntity(ShellyBlockAttributeEntity, RestoreEnti
|
|||||||
# pylint: disable=super-init-not-called
|
# pylint: disable=super-init-not-called
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
wrapper: ShellyDeviceWrapper,
|
wrapper: BlockDeviceWrapper,
|
||||||
block: Block | None,
|
block: Block | None,
|
||||||
attribute: str,
|
attribute: str,
|
||||||
description: BlockAttributeDescription,
|
description: BlockAttributeDescription,
|
||||||
@ -440,7 +499,7 @@ class ShellySleepingBlockAttributeEntity(ShellyBlockAttributeEntity, RestoreEnti
|
|||||||
self._unit = self._unit(block.info(attribute))
|
self._unit = self._unit(block.info(attribute))
|
||||||
|
|
||||||
self._unique_id = f"{self.wrapper.mac}-{block.description}-{attribute}"
|
self._unique_id = f"{self.wrapper.mac}-{block.description}-{attribute}"
|
||||||
self._name = get_entity_name(
|
self._name = get_block_entity_name(
|
||||||
self.wrapper.device, block, self.description.name
|
self.wrapper.device, block, self.description.name
|
||||||
)
|
)
|
||||||
elif entry is not None:
|
elif entry is not None:
|
||||||
|
@ -33,10 +33,10 @@ from homeassistant.util.color import (
|
|||||||
color_temperature_mired_to_kelvin,
|
color_temperature_mired_to_kelvin,
|
||||||
)
|
)
|
||||||
|
|
||||||
from . import ShellyDeviceWrapper
|
from . import BlockDeviceWrapper, RpcDeviceWrapper
|
||||||
from .const import (
|
from .const import (
|
||||||
AIOSHELLY_DEVICE_TIMEOUT_SEC,
|
AIOSHELLY_DEVICE_TIMEOUT_SEC,
|
||||||
COAP,
|
BLOCK,
|
||||||
DATA_CONFIG_ENTRY,
|
DATA_CONFIG_ENTRY,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
FIRMWARE_PATTERN,
|
FIRMWARE_PATTERN,
|
||||||
@ -46,11 +46,12 @@ from .const import (
|
|||||||
LIGHT_TRANSITION_MIN_FIRMWARE_DATE,
|
LIGHT_TRANSITION_MIN_FIRMWARE_DATE,
|
||||||
MAX_TRANSITION_TIME,
|
MAX_TRANSITION_TIME,
|
||||||
MODELS_SUPPORTING_LIGHT_TRANSITION,
|
MODELS_SUPPORTING_LIGHT_TRANSITION,
|
||||||
|
RPC,
|
||||||
SHBLB_1_RGB_EFFECTS,
|
SHBLB_1_RGB_EFFECTS,
|
||||||
STANDARD_RGB_EFFECTS,
|
STANDARD_RGB_EFFECTS,
|
||||||
)
|
)
|
||||||
from .entity import ShellyBlockEntity
|
from .entity import ShellyBlockEntity, ShellyRpcEntity
|
||||||
from .utils import async_remove_shelly_entity
|
from .utils import async_remove_shelly_entity, get_device_entry_gen
|
||||||
|
|
||||||
_LOGGER: Final = logging.getLogger(__name__)
|
_LOGGER: Final = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -61,33 +62,75 @@ async def async_setup_entry(
|
|||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up lights for device."""
|
"""Set up lights for device."""
|
||||||
wrapper = hass.data[DOMAIN][DATA_CONFIG_ENTRY][config_entry.entry_id][COAP]
|
if get_device_entry_gen(config_entry) == 2:
|
||||||
|
return await async_setup_rpc_entry(hass, config_entry, async_add_entities)
|
||||||
|
|
||||||
|
return await async_setup_block_entry(hass, config_entry, async_add_entities)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_block_entry(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
config_entry: ConfigEntry,
|
||||||
|
async_add_entities: AddEntitiesCallback,
|
||||||
|
) -> None:
|
||||||
|
"""Set up entities for block device."""
|
||||||
|
wrapper = hass.data[DOMAIN][DATA_CONFIG_ENTRY][config_entry.entry_id][BLOCK]
|
||||||
|
|
||||||
blocks = []
|
blocks = []
|
||||||
|
assert wrapper.device.blocks
|
||||||
for block in wrapper.device.blocks:
|
for block in wrapper.device.blocks:
|
||||||
if block.type == "light":
|
if block.type == "light":
|
||||||
blocks.append(block)
|
blocks.append(block)
|
||||||
elif block.type == "relay":
|
elif block.type == "relay":
|
||||||
appliance_type = wrapper.device.settings["relays"][int(block.channel)].get(
|
app_type = wrapper.device.settings["relays"][int(block.channel)].get(
|
||||||
"appliance_type"
|
"appliance_type"
|
||||||
)
|
)
|
||||||
if appliance_type and appliance_type.lower() == "light":
|
if not app_type or app_type.lower() != "light":
|
||||||
|
continue
|
||||||
|
|
||||||
blocks.append(block)
|
blocks.append(block)
|
||||||
unique_id = (
|
assert wrapper.device.shelly
|
||||||
f'{wrapper.device.shelly["mac"]}-{block.type}_{block.channel}'
|
unique_id = f"{wrapper.mac}-{block.type}_{block.channel}"
|
||||||
)
|
|
||||||
await async_remove_shelly_entity(hass, "switch", unique_id)
|
await async_remove_shelly_entity(hass, "switch", unique_id)
|
||||||
|
|
||||||
if not blocks:
|
if not blocks:
|
||||||
return
|
return
|
||||||
|
|
||||||
async_add_entities(ShellyLight(wrapper, block) for block in blocks)
|
async_add_entities(BlockShellyLight(wrapper, block) for block in blocks)
|
||||||
|
|
||||||
|
|
||||||
class ShellyLight(ShellyBlockEntity, LightEntity):
|
async def async_setup_rpc_entry(
|
||||||
"""Switch that controls a relay block on Shelly devices."""
|
hass: HomeAssistant,
|
||||||
|
config_entry: ConfigEntry,
|
||||||
|
async_add_entities: AddEntitiesCallback,
|
||||||
|
) -> None:
|
||||||
|
"""Set up entities for RPC device."""
|
||||||
|
wrapper = hass.data[DOMAIN][DATA_CONFIG_ENTRY][config_entry.entry_id][RPC]
|
||||||
|
|
||||||
def __init__(self, wrapper: ShellyDeviceWrapper, block: Block) -> None:
|
switch_keys = []
|
||||||
|
for i in range(4):
|
||||||
|
key = f"switch:{i}"
|
||||||
|
if not wrapper.device.status.get(key):
|
||||||
|
continue
|
||||||
|
|
||||||
|
con_types = wrapper.device.config["sys"]["ui_data"].get("consumption_types")
|
||||||
|
if con_types is None or con_types[i] != "lights":
|
||||||
|
continue
|
||||||
|
|
||||||
|
switch_keys.append((key, i))
|
||||||
|
unique_id = f"{wrapper.mac}-{key}"
|
||||||
|
await async_remove_shelly_entity(hass, "switch", unique_id)
|
||||||
|
|
||||||
|
if not switch_keys:
|
||||||
|
return
|
||||||
|
|
||||||
|
async_add_entities(RpcShellyLight(wrapper, key, id_) for key, id_ in switch_keys)
|
||||||
|
|
||||||
|
|
||||||
|
class BlockShellyLight(ShellyBlockEntity, LightEntity):
|
||||||
|
"""Entity that controls a light on block based Shelly devices."""
|
||||||
|
|
||||||
|
def __init__(self, wrapper: BlockDeviceWrapper, block: Block) -> None:
|
||||||
"""Initialize light."""
|
"""Initialize light."""
|
||||||
super().__init__(wrapper, block)
|
super().__init__(wrapper, block)
|
||||||
self.control_result: dict[str, Any] | None = None
|
self.control_result: dict[str, Any] | None = None
|
||||||
@ -369,3 +412,25 @@ class ShellyLight(ShellyBlockEntity, LightEntity):
|
|||||||
self.control_result = None
|
self.control_result = None
|
||||||
self.mode_result = None
|
self.mode_result = None
|
||||||
super()._update_callback()
|
super()._update_callback()
|
||||||
|
|
||||||
|
|
||||||
|
class RpcShellyLight(ShellyRpcEntity, LightEntity):
|
||||||
|
"""Entity that controls a light on RPC based Shelly devices."""
|
||||||
|
|
||||||
|
def __init__(self, wrapper: RpcDeviceWrapper, key: str, id_: int) -> None:
|
||||||
|
"""Initialize light."""
|
||||||
|
super().__init__(wrapper, key)
|
||||||
|
self._id = id_
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_on(self) -> bool:
|
||||||
|
"""If light is on."""
|
||||||
|
return bool(self.wrapper.device.status[self.key]["output"])
|
||||||
|
|
||||||
|
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||||
|
"""Turn on light."""
|
||||||
|
await self.call_rpc("Switch.Set", {"id": self._id, "on": True})
|
||||||
|
|
||||||
|
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||||
|
"""Turn off light."""
|
||||||
|
await self.call_rpc("Switch.Set", {"id": self._id, "on": False})
|
||||||
|
@ -7,7 +7,7 @@ from homeassistant.const import ATTR_DEVICE_ID
|
|||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers.typing import EventType
|
from homeassistant.helpers.typing import EventType
|
||||||
|
|
||||||
from . import get_device_wrapper
|
from . import RpcDeviceWrapper, get_device_wrapper
|
||||||
from .const import (
|
from .const import (
|
||||||
ATTR_CHANNEL,
|
ATTR_CHANNEL,
|
||||||
ATTR_CLICK_TYPE,
|
ATTR_CLICK_TYPE,
|
||||||
@ -29,6 +29,10 @@ def async_describe_events(
|
|||||||
def async_describe_shelly_click_event(event: EventType) -> dict[str, str]:
|
def async_describe_shelly_click_event(event: EventType) -> dict[str, str]:
|
||||||
"""Describe shelly.click logbook event."""
|
"""Describe shelly.click logbook event."""
|
||||||
wrapper = get_device_wrapper(hass, event.data[ATTR_DEVICE_ID])
|
wrapper = get_device_wrapper(hass, event.data[ATTR_DEVICE_ID])
|
||||||
|
|
||||||
|
if isinstance(wrapper, RpcDeviceWrapper):
|
||||||
|
return {}
|
||||||
|
|
||||||
if wrapper and wrapper.device.initialized:
|
if wrapper and wrapper.device.initialized:
|
||||||
device_name = get_block_device_name(wrapper.device)
|
device_name = get_block_device_name(wrapper.device)
|
||||||
else:
|
else:
|
||||||
|
@ -10,10 +10,10 @@ from homeassistant.config_entries import ConfigEntry
|
|||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
from . import ShellyDeviceWrapper
|
from . import BlockDeviceWrapper, RpcDeviceWrapper
|
||||||
from .const import COAP, DATA_CONFIG_ENTRY, DOMAIN
|
from .const import BLOCK, DATA_CONFIG_ENTRY, DOMAIN, RPC
|
||||||
from .entity import ShellyBlockEntity
|
from .entity import ShellyBlockEntity, ShellyRpcEntity
|
||||||
from .utils import async_remove_shelly_entity
|
from .utils import async_remove_shelly_entity, get_device_entry_gen
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
@ -22,7 +22,19 @@ async def async_setup_entry(
|
|||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up switches for device."""
|
"""Set up switches for device."""
|
||||||
wrapper = hass.data[DOMAIN][DATA_CONFIG_ENTRY][config_entry.entry_id][COAP]
|
if get_device_entry_gen(config_entry) == 2:
|
||||||
|
return await async_setup_rpc_entry(hass, config_entry, async_add_entities)
|
||||||
|
|
||||||
|
return await async_setup_block_entry(hass, config_entry, async_add_entities)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_block_entry(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
config_entry: ConfigEntry,
|
||||||
|
async_add_entities: AddEntitiesCallback,
|
||||||
|
) -> None:
|
||||||
|
"""Set up entities for block device."""
|
||||||
|
wrapper = hass.data[DOMAIN][DATA_CONFIG_ENTRY][config_entry.entry_id][BLOCK]
|
||||||
|
|
||||||
# In roller mode the relay blocks exist but do not contain required info
|
# In roller mode the relay blocks exist but do not contain required info
|
||||||
if (
|
if (
|
||||||
@ -32,32 +44,59 @@ async def async_setup_entry(
|
|||||||
return
|
return
|
||||||
|
|
||||||
relay_blocks = []
|
relay_blocks = []
|
||||||
|
assert wrapper.device.blocks
|
||||||
for block in wrapper.device.blocks:
|
for block in wrapper.device.blocks:
|
||||||
if block.type == "relay":
|
if block.type != "relay":
|
||||||
appliance_type = wrapper.device.settings["relays"][int(block.channel)].get(
|
continue
|
||||||
|
|
||||||
|
app_type = wrapper.device.settings["relays"][int(block.channel)].get(
|
||||||
"appliance_type"
|
"appliance_type"
|
||||||
)
|
)
|
||||||
if not appliance_type or appliance_type.lower() != "light":
|
if app_type and app_type.lower() == "light":
|
||||||
|
continue
|
||||||
|
|
||||||
relay_blocks.append(block)
|
relay_blocks.append(block)
|
||||||
unique_id = (
|
unique_id = f"{wrapper.mac}-{block.type}_{block.channel}"
|
||||||
f'{wrapper.device.shelly["mac"]}-{block.type}_{block.channel}'
|
await async_remove_shelly_entity(hass, "light", unique_id)
|
||||||
)
|
|
||||||
await async_remove_shelly_entity(
|
|
||||||
hass,
|
|
||||||
"light",
|
|
||||||
unique_id,
|
|
||||||
)
|
|
||||||
|
|
||||||
if not relay_blocks:
|
if not relay_blocks:
|
||||||
return
|
return
|
||||||
|
|
||||||
async_add_entities(RelaySwitch(wrapper, block) for block in relay_blocks)
|
async_add_entities(BlockRelaySwitch(wrapper, block) for block in relay_blocks)
|
||||||
|
|
||||||
|
|
||||||
class RelaySwitch(ShellyBlockEntity, SwitchEntity):
|
async def async_setup_rpc_entry(
|
||||||
"""Switch that controls a relay block on Shelly devices."""
|
hass: HomeAssistant,
|
||||||
|
config_entry: ConfigEntry,
|
||||||
|
async_add_entities: AddEntitiesCallback,
|
||||||
|
) -> None:
|
||||||
|
"""Set up entities for RPC device."""
|
||||||
|
wrapper = hass.data[DOMAIN][DATA_CONFIG_ENTRY][config_entry.entry_id][RPC]
|
||||||
|
|
||||||
def __init__(self, wrapper: ShellyDeviceWrapper, block: Block) -> None:
|
switch_keys = []
|
||||||
|
for i in range(4):
|
||||||
|
key = f"switch:{i}"
|
||||||
|
if not wrapper.device.status.get(key):
|
||||||
|
continue
|
||||||
|
|
||||||
|
con_types = wrapper.device.config["sys"]["ui_data"].get("consumption_types")
|
||||||
|
if con_types is not None and con_types[i] == "lights":
|
||||||
|
continue
|
||||||
|
|
||||||
|
switch_keys.append((key, i))
|
||||||
|
unique_id = f"{wrapper.mac}-{key}"
|
||||||
|
await async_remove_shelly_entity(hass, "light", unique_id)
|
||||||
|
|
||||||
|
if not switch_keys:
|
||||||
|
return
|
||||||
|
|
||||||
|
async_add_entities(RpcRelaySwitch(wrapper, key, id_) for key, id_ in switch_keys)
|
||||||
|
|
||||||
|
|
||||||
|
class BlockRelaySwitch(ShellyBlockEntity, SwitchEntity):
|
||||||
|
"""Entity that controls a relay on Block based Shelly devices."""
|
||||||
|
|
||||||
|
def __init__(self, wrapper: BlockDeviceWrapper, block: Block) -> None:
|
||||||
"""Initialize relay switch."""
|
"""Initialize relay switch."""
|
||||||
super().__init__(wrapper, block)
|
super().__init__(wrapper, block)
|
||||||
self.control_result: dict[str, Any] | None = None
|
self.control_result: dict[str, Any] | None = None
|
||||||
@ -85,3 +124,25 @@ class RelaySwitch(ShellyBlockEntity, SwitchEntity):
|
|||||||
"""When device updates, clear control result that overrides state."""
|
"""When device updates, clear control result that overrides state."""
|
||||||
self.control_result = None
|
self.control_result = None
|
||||||
super()._update_callback()
|
super()._update_callback()
|
||||||
|
|
||||||
|
|
||||||
|
class RpcRelaySwitch(ShellyRpcEntity, SwitchEntity):
|
||||||
|
"""Entity that controls a relay on RPC based Shelly devices."""
|
||||||
|
|
||||||
|
def __init__(self, wrapper: RpcDeviceWrapper, key: str, id_: int) -> None:
|
||||||
|
"""Initialize relay switch."""
|
||||||
|
super().__init__(wrapper, key)
|
||||||
|
self._id = id_
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_on(self) -> bool:
|
||||||
|
"""If switch is on."""
|
||||||
|
return bool(self.wrapper.device.status[self.key]["output"])
|
||||||
|
|
||||||
|
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||||
|
"""Turn on relay."""
|
||||||
|
await self.call_rpc("Switch.Set", {"id": self._id, "on": True})
|
||||||
|
|
||||||
|
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||||
|
"""Turn off relay."""
|
||||||
|
await self.call_rpc("Switch.Set", {"id": self._id, "on": False})
|
||||||
|
@ -9,6 +9,7 @@ from aioshelly.block_device import BLOCK_VALUE_UNIT, COAP, Block, BlockDevice
|
|||||||
from aioshelly.const import MODEL_NAMES
|
from aioshelly.const import MODEL_NAMES
|
||||||
from aioshelly.rpc_device import RpcDevice
|
from aioshelly.rpc_device import RpcDevice
|
||||||
|
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import EVENT_HOMEASSISTANT_STOP, TEMP_CELSIUS, TEMP_FAHRENHEIT
|
from homeassistant.const import EVENT_HOMEASSISTANT_STOP, TEMP_CELSIUS, TEMP_FAHRENHEIT
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers import singleton
|
from homeassistant.helpers import singleton
|
||||||
@ -81,12 +82,12 @@ def get_number_of_channels(device: BlockDevice, block: Block) -> int:
|
|||||||
return channels or 1
|
return channels or 1
|
||||||
|
|
||||||
|
|
||||||
def get_entity_name(
|
def get_block_entity_name(
|
||||||
device: BlockDevice,
|
device: BlockDevice,
|
||||||
block: Block | None,
|
block: Block | None,
|
||||||
description: str | None = None,
|
description: str | None = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Naming for switch and sensors."""
|
"""Naming for block based switch and sensors."""
|
||||||
channel_name = get_device_channel_name(device, block)
|
channel_name = get_device_channel_name(device, block)
|
||||||
|
|
||||||
if description:
|
if description:
|
||||||
@ -237,3 +238,23 @@ def get_model_name(info: dict[str, Any]) -> str:
|
|||||||
return cast(str, MODEL_NAMES.get(info["model"], info["model"]))
|
return cast(str, MODEL_NAMES.get(info["model"], info["model"]))
|
||||||
|
|
||||||
return cast(str, MODEL_NAMES.get(info["type"], info["type"]))
|
return cast(str, MODEL_NAMES.get(info["type"], info["type"]))
|
||||||
|
|
||||||
|
|
||||||
|
def get_rpc_entity_name(
|
||||||
|
device: RpcDevice, key: str, description: str | None = None
|
||||||
|
) -> str:
|
||||||
|
"""Naming for RPC based switch and sensors."""
|
||||||
|
entity_name: str | None = device.config[key].get("name")
|
||||||
|
|
||||||
|
if entity_name is None:
|
||||||
|
entity_name = f"{get_rpc_device_name(device)} {key.replace(':', '_')}"
|
||||||
|
|
||||||
|
if description:
|
||||||
|
return f"{entity_name} {description}"
|
||||||
|
|
||||||
|
return entity_name
|
||||||
|
|
||||||
|
|
||||||
|
def get_device_entry_gen(entry: ConfigEntry) -> int:
|
||||||
|
"""Return the device generation from config entry."""
|
||||||
|
return entry.data.get("gen", 1)
|
||||||
|
@ -3,12 +3,13 @@ from unittest.mock import AsyncMock, Mock, patch
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant.components.shelly import ShellyDeviceWrapper
|
from homeassistant.components.shelly import BlockDeviceWrapper, RpcDeviceWrapper
|
||||||
from homeassistant.components.shelly.const import (
|
from homeassistant.components.shelly.const import (
|
||||||
COAP,
|
BLOCK,
|
||||||
DATA_CONFIG_ENTRY,
|
DATA_CONFIG_ENTRY,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
EVENT_SHELLY_CLICK,
|
EVENT_SHELLY_CLICK,
|
||||||
|
RPC,
|
||||||
)
|
)
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
|
|
||||||
@ -54,6 +55,13 @@ MOCK_BLOCKS = [
|
|||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
MOCK_CONFIG = {
|
||||||
|
"switch:0": {"name": "test switch_0"},
|
||||||
|
"sys": {"ui_data": {}},
|
||||||
|
"wifi": {
|
||||||
|
"ap": {"ssid": "Test name"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
MOCK_SHELLY = {
|
MOCK_SHELLY = {
|
||||||
"mac": "test-mac",
|
"mac": "test-mac",
|
||||||
@ -62,6 +70,10 @@ MOCK_SHELLY = {
|
|||||||
"num_outputs": 2,
|
"num_outputs": 2,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MOCK_STATUS = {
|
||||||
|
"switch:0": {"output": True},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
def mock_coap():
|
def mock_coap():
|
||||||
@ -104,6 +116,7 @@ async def coap_wrapper(hass):
|
|||||||
blocks=MOCK_BLOCKS,
|
blocks=MOCK_BLOCKS,
|
||||||
settings=MOCK_SETTINGS,
|
settings=MOCK_SETTINGS,
|
||||||
shelly=MOCK_SHELLY,
|
shelly=MOCK_SHELLY,
|
||||||
|
firmware_version="some fw string",
|
||||||
update=AsyncMock(),
|
update=AsyncMock(),
|
||||||
initialized=True,
|
initialized=True,
|
||||||
)
|
)
|
||||||
@ -111,9 +124,43 @@ async def coap_wrapper(hass):
|
|||||||
hass.data[DOMAIN] = {DATA_CONFIG_ENTRY: {}}
|
hass.data[DOMAIN] = {DATA_CONFIG_ENTRY: {}}
|
||||||
hass.data[DOMAIN][DATA_CONFIG_ENTRY][config_entry.entry_id] = {}
|
hass.data[DOMAIN][DATA_CONFIG_ENTRY][config_entry.entry_id] = {}
|
||||||
wrapper = hass.data[DOMAIN][DATA_CONFIG_ENTRY][config_entry.entry_id][
|
wrapper = hass.data[DOMAIN][DATA_CONFIG_ENTRY][config_entry.entry_id][
|
||||||
COAP
|
BLOCK
|
||||||
] = ShellyDeviceWrapper(hass, config_entry, device)
|
] = BlockDeviceWrapper(hass, config_entry, device)
|
||||||
|
|
||||||
await wrapper.async_setup()
|
wrapper.async_setup()
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
async def rpc_wrapper(hass):
|
||||||
|
"""Setups a coap wrapper with mocked device."""
|
||||||
|
await async_setup_component(hass, "shelly", {})
|
||||||
|
|
||||||
|
config_entry = MockConfigEntry(
|
||||||
|
domain=DOMAIN,
|
||||||
|
data={"sleep_period": 0, "model": "SNSW-001P16EU", "gen": 2},
|
||||||
|
unique_id="12345678",
|
||||||
|
)
|
||||||
|
config_entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
device = Mock(
|
||||||
|
call_rpc=AsyncMock(),
|
||||||
|
config=MOCK_CONFIG,
|
||||||
|
shelly=MOCK_SHELLY,
|
||||||
|
status=MOCK_STATUS,
|
||||||
|
firmware_version="some fw string",
|
||||||
|
update=AsyncMock(),
|
||||||
|
initialized=True,
|
||||||
|
shutdown=AsyncMock(),
|
||||||
|
)
|
||||||
|
|
||||||
|
hass.data[DOMAIN] = {DATA_CONFIG_ENTRY: {}}
|
||||||
|
hass.data[DOMAIN][DATA_CONFIG_ENTRY][config_entry.entry_id] = {}
|
||||||
|
wrapper = hass.data[DOMAIN][DATA_CONFIG_ENTRY][config_entry.entry_id][
|
||||||
|
RPC
|
||||||
|
] = RpcDeviceWrapper(hass, config_entry, device)
|
||||||
|
|
||||||
|
wrapper.async_setup()
|
||||||
|
|
||||||
return wrapper
|
return wrapper
|
||||||
|
@ -8,11 +8,11 @@ from homeassistant.components import automation
|
|||||||
from homeassistant.components.device_automation.exceptions import (
|
from homeassistant.components.device_automation.exceptions import (
|
||||||
InvalidDeviceAutomationConfig,
|
InvalidDeviceAutomationConfig,
|
||||||
)
|
)
|
||||||
from homeassistant.components.shelly import ShellyDeviceWrapper
|
from homeassistant.components.shelly import BlockDeviceWrapper
|
||||||
from homeassistant.components.shelly.const import (
|
from homeassistant.components.shelly.const import (
|
||||||
ATTR_CHANNEL,
|
ATTR_CHANNEL,
|
||||||
ATTR_CLICK_TYPE,
|
ATTR_CLICK_TYPE,
|
||||||
COAP,
|
BLOCK,
|
||||||
CONF_SUBTYPE,
|
CONF_SUBTYPE,
|
||||||
DATA_CONFIG_ENTRY,
|
DATA_CONFIG_ENTRY,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
@ -79,10 +79,10 @@ async def test_get_triggers_button(hass):
|
|||||||
hass.data[DOMAIN] = {DATA_CONFIG_ENTRY: {}}
|
hass.data[DOMAIN] = {DATA_CONFIG_ENTRY: {}}
|
||||||
hass.data[DOMAIN][DATA_CONFIG_ENTRY][config_entry.entry_id] = {}
|
hass.data[DOMAIN][DATA_CONFIG_ENTRY][config_entry.entry_id] = {}
|
||||||
coap_wrapper = hass.data[DOMAIN][DATA_CONFIG_ENTRY][config_entry.entry_id][
|
coap_wrapper = hass.data[DOMAIN][DATA_CONFIG_ENTRY][config_entry.entry_id][
|
||||||
COAP
|
BLOCK
|
||||||
] = ShellyDeviceWrapper(hass, config_entry, device)
|
] = BlockDeviceWrapper(hass, config_entry, device)
|
||||||
|
|
||||||
await coap_wrapper.async_setup()
|
coap_wrapper.async_setup()
|
||||||
|
|
||||||
expected_triggers = [
|
expected_triggers = [
|
||||||
{
|
{
|
||||||
|
@ -11,8 +11,8 @@ from homeassistant.const import (
|
|||||||
RELAY_BLOCK_ID = 0
|
RELAY_BLOCK_ID = 0
|
||||||
|
|
||||||
|
|
||||||
async def test_services(hass, coap_wrapper):
|
async def test_block_device_services(hass, coap_wrapper):
|
||||||
"""Test device turn on/off services."""
|
"""Test block device turn on/off services."""
|
||||||
assert coap_wrapper
|
assert coap_wrapper
|
||||||
|
|
||||||
hass.async_create_task(
|
hass.async_create_task(
|
||||||
@ -37,8 +37,8 @@ async def test_services(hass, coap_wrapper):
|
|||||||
assert hass.states.get("switch.test_name_channel_1").state == STATE_OFF
|
assert hass.states.get("switch.test_name_channel_1").state == STATE_OFF
|
||||||
|
|
||||||
|
|
||||||
async def test_update(hass, coap_wrapper, monkeypatch):
|
async def test_block_device_update(hass, coap_wrapper, monkeypatch):
|
||||||
"""Test device update."""
|
"""Test block device update."""
|
||||||
assert coap_wrapper
|
assert coap_wrapper
|
||||||
|
|
||||||
hass.async_create_task(
|
hass.async_create_task(
|
||||||
@ -61,8 +61,8 @@ async def test_update(hass, coap_wrapper, monkeypatch):
|
|||||||
assert hass.states.get("switch.test_name_channel_1").state == STATE_ON
|
assert hass.states.get("switch.test_name_channel_1").state == STATE_ON
|
||||||
|
|
||||||
|
|
||||||
async def test_no_relay_blocks(hass, coap_wrapper, monkeypatch):
|
async def test_block_device_no_relay_blocks(hass, coap_wrapper, monkeypatch):
|
||||||
"""Test device without relay blocks."""
|
"""Test block device without relay blocks."""
|
||||||
assert coap_wrapper
|
assert coap_wrapper
|
||||||
|
|
||||||
monkeypatch.setattr(coap_wrapper.device.blocks[RELAY_BLOCK_ID], "type", "roller")
|
monkeypatch.setattr(coap_wrapper.device.blocks[RELAY_BLOCK_ID], "type", "roller")
|
||||||
@ -73,8 +73,8 @@ async def test_no_relay_blocks(hass, coap_wrapper, monkeypatch):
|
|||||||
assert hass.states.get("switch.test_name_channel_1") is None
|
assert hass.states.get("switch.test_name_channel_1") is None
|
||||||
|
|
||||||
|
|
||||||
async def test_device_mode_roller(hass, coap_wrapper, monkeypatch):
|
async def test_block_device_mode_roller(hass, coap_wrapper, monkeypatch):
|
||||||
"""Test switch device in roller mode."""
|
"""Test block device in roller mode."""
|
||||||
assert coap_wrapper
|
assert coap_wrapper
|
||||||
|
|
||||||
monkeypatch.setitem(coap_wrapper.device.settings, "mode", "roller")
|
monkeypatch.setitem(coap_wrapper.device.settings, "mode", "roller")
|
||||||
@ -83,3 +83,61 @@ async def test_device_mode_roller(hass, coap_wrapper, monkeypatch):
|
|||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert hass.states.get("switch.test_name_channel_1") is None
|
assert hass.states.get("switch.test_name_channel_1") is None
|
||||||
|
|
||||||
|
|
||||||
|
async def test_block_device_app_type_light(hass, coap_wrapper, monkeypatch):
|
||||||
|
"""Test block device in app type set to light mode."""
|
||||||
|
assert coap_wrapper
|
||||||
|
|
||||||
|
monkeypatch.setitem(
|
||||||
|
coap_wrapper.device.settings["relays"][0], "appliance_type", "light"
|
||||||
|
)
|
||||||
|
hass.async_create_task(
|
||||||
|
hass.config_entries.async_forward_entry_setup(coap_wrapper.entry, SWITCH_DOMAIN)
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert hass.states.get("switch.test_name_channel_1") is None
|
||||||
|
|
||||||
|
|
||||||
|
async def test_rpc_device_services(hass, rpc_wrapper, monkeypatch):
|
||||||
|
"""Test RPC device turn on/off services."""
|
||||||
|
assert rpc_wrapper
|
||||||
|
|
||||||
|
hass.async_create_task(
|
||||||
|
hass.config_entries.async_forward_entry_setup(rpc_wrapper.entry, SWITCH_DOMAIN)
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
SWITCH_DOMAIN,
|
||||||
|
SERVICE_TURN_ON,
|
||||||
|
{ATTR_ENTITY_ID: "switch.test_switch_0"},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
assert hass.states.get("switch.test_switch_0").state == STATE_ON
|
||||||
|
|
||||||
|
monkeypatch.setitem(rpc_wrapper.device.status["switch:0"], "output", False)
|
||||||
|
await hass.services.async_call(
|
||||||
|
SWITCH_DOMAIN,
|
||||||
|
SERVICE_TURN_OFF,
|
||||||
|
{ATTR_ENTITY_ID: "switch.test_switch_0"},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
rpc_wrapper.async_set_updated_data("")
|
||||||
|
assert hass.states.get("switch.test_switch_0").state == STATE_OFF
|
||||||
|
|
||||||
|
|
||||||
|
async def test_rpc_device_switch_type_lights_mode(hass, rpc_wrapper, monkeypatch):
|
||||||
|
"""Test RPC device with switch in consumption type lights mode."""
|
||||||
|
assert rpc_wrapper
|
||||||
|
|
||||||
|
monkeypatch.setitem(
|
||||||
|
rpc_wrapper.device.config["sys"]["ui_data"],
|
||||||
|
"consumption_types",
|
||||||
|
["lights"],
|
||||||
|
)
|
||||||
|
hass.async_create_task(
|
||||||
|
hass.config_entries.async_forward_entry_setup(rpc_wrapper.entry, SWITCH_DOMAIN)
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert hass.states.get("switch.test_switch_0") is None
|
||||||
|
Loading…
x
Reference in New Issue
Block a user