1
mirror of https://github.com/home-assistant/core synced 2024-09-25 00:41:32 +02:00

Add support for Shelly battery operated devices (#45406)

Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
This commit is contained in:
Shay Levy 2021-02-03 18:03:22 +02:00 committed by GitHub
parent fcc14933d0
commit 0875f654c8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 366 additions and 105 deletions

View File

@ -17,12 +17,7 @@ from homeassistant.const import (
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import (
aiohttp_client,
device_registry,
singleton,
update_coordinator,
)
from homeassistant.helpers import aiohttp_client, device_registry, update_coordinator
from .const import (
AIOSHELLY_DEVICE_TIMEOUT_SEC,
@ -32,36 +27,23 @@ from .const import (
BATTERY_DEVICES_WITH_PERMANENT_CONNECTION,
COAP,
DATA_CONFIG_ENTRY,
DEVICE,
DOMAIN,
EVENT_SHELLY_CLICK,
INPUTS_EVENTS_DICT,
POLLING_TIMEOUT_MULTIPLIER,
POLLING_TIMEOUT_SEC,
REST,
REST_SENSORS_UPDATE_INTERVAL,
SLEEP_PERIOD_MULTIPLIER,
UPDATE_PERIOD_MULTIPLIER,
)
from .utils import get_device_name
from .utils import get_coap_context, get_device_name, get_device_sleep_period
PLATFORMS = ["binary_sensor", "cover", "light", "sensor", "switch"]
SLEEPING_PLATFORMS = ["binary_sensor", "sensor"]
_LOGGER = logging.getLogger(__name__)
@singleton.singleton("shelly_coap")
async def get_coap_context(hass):
"""Get CoAP context to be used in all Shelly devices."""
context = aioshelly.COAP()
await context.initialize()
@callback
def shutdown_listener(ev):
context.close()
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, shutdown_listener)
return context
async def async_setup(hass: HomeAssistant, config: dict):
"""Set up the Shelly component."""
hass.data[DOMAIN] = {DATA_CONFIG_ENTRY: {}}
@ -70,6 +52,9 @@ async def async_setup(hass: HomeAssistant, config: dict):
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
"""Set up Shelly from a config entry."""
hass.data[DOMAIN][DATA_CONFIG_ENTRY][entry.entry_id] = {}
hass.data[DOMAIN][DATA_CONFIG_ENTRY][entry.entry_id][DEVICE] = None
temperature_unit = "C" if hass.config.units.is_metric else "F"
ip_address = await hass.async_add_executor_job(gethostbyname, entry.data[CONF_HOST])
@ -83,33 +68,79 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
coap_context = await get_coap_context(hass)
try:
async with async_timeout.timeout(AIOSHELLY_DEVICE_TIMEOUT_SEC):
device = await aioshelly.Device.create(
aiohttp_client.async_get_clientsession(hass),
coap_context,
options,
)
except (asyncio.TimeoutError, OSError) as err:
raise ConfigEntryNotReady from err
device = await aioshelly.Device.create(
aiohttp_client.async_get_clientsession(hass),
coap_context,
options,
False,
)
hass.data[DOMAIN][DATA_CONFIG_ENTRY][entry.entry_id] = {}
coap_wrapper = hass.data[DOMAIN][DATA_CONFIG_ENTRY][entry.entry_id][
dev_reg = await device_registry.async_get_registry(hass)
identifier = (DOMAIN, entry.unique_id)
device_entry = dev_reg.async_get_device(identifiers={identifier}, connections=set())
sleep_period = entry.data.get("sleep_period")
@callback
def _async_device_online(_):
_LOGGER.debug("Device %s is online, resuming setup", entry.title)
hass.data[DOMAIN][DATA_CONFIG_ENTRY][entry.entry_id][DEVICE] = None
if sleep_period is None:
data = {**entry.data}
data["sleep_period"] = get_device_sleep_period(device.settings)
data["model"] = device.settings["device"]["type"]
hass.config_entries.async_update_entry(entry, data=data)
hass.async_create_task(async_device_setup(hass, entry, device))
if sleep_period == 0:
# Not a sleeping device, finish setup
_LOGGER.debug("Setting up online device %s", entry.title)
try:
async with async_timeout.timeout(AIOSHELLY_DEVICE_TIMEOUT_SEC):
await device.initialize(True)
except (asyncio.TimeoutError, OSError) as err:
raise ConfigEntryNotReady from err
await async_device_setup(hass, entry, device)
elif sleep_period is None or device_entry is None:
# 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
_LOGGER.debug(
"Setup for device %s will resume when device is online", entry.title
)
device.subscribe_updates(_async_device_online)
else:
# Restore sensors for sleeping device
_LOGGER.debug("Setting up offline device %s", entry.title)
await async_device_setup(hass, entry, device)
return True
async def async_device_setup(
hass: HomeAssistant, entry: ConfigEntry, device: aioshelly.Device
):
"""Set up a device that is online."""
device_wrapper = hass.data[DOMAIN][DATA_CONFIG_ENTRY][entry.entry_id][
COAP
] = ShellyDeviceWrapper(hass, entry, device)
await coap_wrapper.async_setup()
await device_wrapper.async_setup()
hass.data[DOMAIN][DATA_CONFIG_ENTRY][entry.entry_id][
REST
] = ShellyDeviceRestWrapper(hass, device)
platforms = SLEEPING_PLATFORMS
for component in PLATFORMS:
if not entry.data.get("sleep_period"):
hass.data[DOMAIN][DATA_CONFIG_ENTRY][entry.entry_id][
REST
] = ShellyDeviceRestWrapper(hass, device)
platforms = PLATFORMS
for component in platforms:
hass.async_create_task(
hass.config_entries.async_forward_entry_setup(entry, component)
)
return True
class ShellyDeviceWrapper(update_coordinator.DataUpdateCoordinator):
"""Wrapper for a Shelly device with Home Assistant specific functions."""
@ -117,43 +148,40 @@ class ShellyDeviceWrapper(update_coordinator.DataUpdateCoordinator):
def __init__(self, hass, entry, device: aioshelly.Device):
"""Initialize the Shelly device wrapper."""
self.device_id = None
sleep_mode = device.settings.get("sleep_mode")
sleep_period = entry.data["sleep_period"]
if sleep_mode:
sleep_period = sleep_mode["period"]
if sleep_mode["unit"] == "h":
sleep_period *= 60 # hours to minutes
update_interval = (
SLEEP_PERIOD_MULTIPLIER * sleep_period * 60
) # minutes to seconds
if sleep_period:
update_interval = SLEEP_PERIOD_MULTIPLIER * sleep_period
else:
update_interval = (
UPDATE_PERIOD_MULTIPLIER * device.settings["coiot"]["update_period"]
)
device_name = get_device_name(device) if device.initialized else entry.title
super().__init__(
hass,
_LOGGER,
name=get_device_name(device),
name=device_name,
update_interval=timedelta(seconds=update_interval),
)
self.hass = hass
self.entry = entry
self.device = device
self.device.subscribe_updates(self.async_set_updated_data)
self._async_remove_input_events_handler = self.async_add_listener(
self._async_input_events_handler
self._async_remove_device_updates_handler = self.async_add_listener(
self._async_device_updates_handler
)
self._last_input_events_count = dict()
self._last_input_events_count = {}
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, self._handle_ha_stop)
@callback
def _async_input_events_handler(self):
"""Handle device input events."""
def _async_device_updates_handler(self):
"""Handle device updates."""
if not self.device.initialized:
return
# Check for input events
for block in self.device.blocks:
if (
"inputEvent" not in block.sensor_ids
@ -192,13 +220,9 @@ class ShellyDeviceWrapper(update_coordinator.DataUpdateCoordinator):
async def _async_update_data(self):
"""Fetch data."""
_LOGGER.debug("Polling Shelly Device - %s", self.name)
try:
async with async_timeout.timeout(
POLLING_TIMEOUT_MULTIPLIER
* self.device.settings["coiot"]["update_period"]
):
async with async_timeout.timeout(POLLING_TIMEOUT_SEC):
return await self.device.update()
except OSError as err:
raise update_coordinator.UpdateFailed("Error fetching data") from err
@ -206,18 +230,17 @@ class ShellyDeviceWrapper(update_coordinator.DataUpdateCoordinator):
@property
def model(self):
"""Model of the device."""
return self.device.settings["device"]["type"]
return self.entry.data["model"]
@property
def mac(self):
"""Mac address of the device."""
return self.device.settings["device"]["mac"]
return self.entry.unique_id
async def async_setup(self):
"""Set up the wrapper."""
dev_reg = await device_registry.async_get_registry(self.hass)
model_type = self.device.settings["device"]["type"]
sw_version = self.device.settings["fw"] if self.device.initialized else ""
entry = dev_reg.async_get_or_create(
config_entry_id=self.entry.entry_id,
name=self.name,
@ -225,15 +248,16 @@ class ShellyDeviceWrapper(update_coordinator.DataUpdateCoordinator):
# This is duplicate but otherwise via_device can't work
identifiers={(DOMAIN, self.mac)},
manufacturer="Shelly",
model=aioshelly.MODEL_NAMES.get(model_type, model_type),
sw_version=self.device.settings["fw"],
model=aioshelly.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)
def shutdown(self):
"""Shutdown the wrapper."""
self.device.shutdown()
self._async_remove_input_events_handler()
self._async_remove_device_updates_handler()
@callback
def _handle_ha_stop(self, _):
@ -282,11 +306,23 @@ class ShellyDeviceRestWrapper(update_coordinator.DataUpdateCoordinator):
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
"""Unload a config entry."""
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
device.shutdown()
return True
platforms = SLEEPING_PLATFORMS
if not entry.data.get("sleep_period"):
hass.data[DOMAIN][DATA_CONFIG_ENTRY][entry.entry_id][REST] = None
platforms = PLATFORMS
unload_ok = all(
await asyncio.gather(
*[
hass.config_entries.async_forward_entry_unload(entry, component)
for component in PLATFORMS
for component in platforms
]
)
)

View File

@ -9,6 +9,7 @@ from homeassistant.components.binary_sensor import (
DEVICE_CLASS_PROBLEM,
DEVICE_CLASS_SMOKE,
DEVICE_CLASS_VIBRATION,
STATE_ON,
BinarySensorEntity,
)
@ -17,6 +18,7 @@ from .entity import (
RestAttributeDescription,
ShellyBlockAttributeEntity,
ShellyRestAttributeEntity,
ShellySleepingBlockAttributeEntity,
async_setup_entry_attribute_entities,
async_setup_entry_rest,
)
@ -98,13 +100,25 @@ REST_SENSORS = {
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up sensors for device."""
await async_setup_entry_attribute_entities(
hass, config_entry, async_add_entities, SENSORS, ShellyBinarySensor
)
await async_setup_entry_rest(
hass, config_entry, async_add_entities, REST_SENSORS, ShellyRestBinarySensor
)
if config_entry.data["sleep_period"]:
await async_setup_entry_attribute_entities(
hass,
config_entry,
async_add_entities,
SENSORS,
ShellySleepingBinarySensor,
)
else:
await async_setup_entry_attribute_entities(
hass, config_entry, async_add_entities, SENSORS, ShellyBinarySensor
)
await async_setup_entry_rest(
hass,
config_entry,
async_add_entities,
REST_SENSORS,
ShellyRestBinarySensor,
)
class ShellyBinarySensor(ShellyBlockAttributeEntity, BinarySensorEntity):
@ -123,3 +137,17 @@ class ShellyRestBinarySensor(ShellyRestAttributeEntity, BinarySensorEntity):
def is_on(self):
"""Return true if REST sensor state is on."""
return bool(self.attribute_value)
class ShellySleepingBinarySensor(
ShellySleepingBlockAttributeEntity, BinarySensorEntity
):
"""Represent a shelly sleeping binary sensor."""
@property
def is_on(self):
"""Return true if sensor state is on."""
if self.block is not None:
return bool(self.attribute_value)
return self.last_state == STATE_ON

View File

@ -17,9 +17,9 @@ from homeassistant.const import (
)
from homeassistant.helpers import aiohttp_client
from . import get_coap_context
from .const import AIOSHELLY_DEVICE_TIMEOUT_SEC
from .const import DOMAIN # pylint:disable=unused-import
from .utils import get_coap_context, get_device_sleep_period
_LOGGER = logging.getLogger(__name__)
@ -53,6 +53,8 @@ async def validate_input(hass: core.HomeAssistant, host, data):
return {
"title": device.settings["name"],
"hostname": device.settings["device"]["hostname"],
"sleep_period": get_device_sleep_period(device.settings),
"model": device.settings["device"]["type"],
}
@ -95,7 +97,11 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
else:
return self.async_create_entry(
title=device_info["title"] or device_info["hostname"],
data=user_input,
data={
**user_input,
"sleep_period": device_info["sleep_period"],
"model": device_info["model"],
},
)
return self.async_show_form(
@ -121,7 +127,12 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
else:
return self.async_create_entry(
title=device_info["title"] or device_info["hostname"],
data={**user_input, CONF_HOST: self.host},
data={
**user_input,
CONF_HOST: self.host,
"sleep_period": device_info["sleep_period"],
"model": device_info["model"],
},
)
else:
user_input = {}
@ -172,7 +183,11 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
else:
return self.async_create_entry(
title=device_info["title"] or device_info["hostname"],
data={"host": self.host},
data={
"host": self.host,
"sleep_period": device_info["sleep_period"],
"model": device_info["model"],
},
)
return self.async_show_form(

View File

@ -2,11 +2,12 @@
COAP = "coap"
DATA_CONFIG_ENTRY = "config_entry"
DEVICE = "device"
DOMAIN = "shelly"
REST = "rest"
# Used to calculate the timeout in "_async_update_data" used for polling data from devices.
POLLING_TIMEOUT_MULTIPLIER = 1.2
# Used in "_async_update_data" as timeout for polling data from devices.
POLLING_TIMEOUT_SEC = 18
# Refresh interval for REST sensors
REST_SENSORS_UPDATE_INTERVAL = 60

View File

@ -1,24 +1,49 @@
"""Shelly entity helper."""
from dataclasses import dataclass
import logging
from typing import Any, Callable, Optional, Union
import aioshelly
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import callback
from homeassistant.helpers import device_registry, entity, update_coordinator
from homeassistant.helpers import (
device_registry,
entity,
entity_registry,
update_coordinator,
)
from homeassistant.helpers.restore_state import RestoreEntity
from . import ShellyDeviceRestWrapper, ShellyDeviceWrapper
from .const import COAP, DATA_CONFIG_ENTRY, DOMAIN, REST
from .utils import async_remove_shelly_entity, get_entity_name
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry_attribute_entities(
hass, config_entry, async_add_entities, sensors, sensor_class
):
"""Set up entities for block attributes."""
"""Set up entities for attributes."""
wrapper: ShellyDeviceWrapper = hass.data[DOMAIN][DATA_CONFIG_ENTRY][
config_entry.entry_id
][COAP]
if wrapper.device.initialized:
await async_setup_block_attribute_entities(
hass, async_add_entities, wrapper, sensors, sensor_class
)
else:
await async_restore_block_attribute_entities(
hass, config_entry, async_add_entities, wrapper, sensor_class
)
async def async_setup_block_attribute_entities(
hass, async_add_entities, wrapper, sensors, sensor_class
):
"""Set up entities for block attributes."""
blocks = []
for block in wrapper.device.blocks:
@ -36,9 +61,7 @@ async def async_setup_entry_attribute_entities(
wrapper.device.settings, block
):
domain = sensor_class.__module__.split(".")[-1]
unique_id = sensor_class(
wrapper, block, sensor_id, description
).unique_id
unique_id = f"{wrapper.mac}-{block.description}-{sensor_id}"
await async_remove_shelly_entity(hass, domain, unique_id)
else:
blocks.append((block, sensor_id, description))
@ -54,6 +77,39 @@ async def async_setup_entry_attribute_entities(
)
async def async_restore_block_attribute_entities(
hass, config_entry, async_add_entities, wrapper, sensor_class
):
"""Restore block attributes entities."""
entities = []
ent_reg = await entity_registry.async_get_registry(hass)
entries = entity_registry.async_entries_for_config_entry(
ent_reg, config_entry.entry_id
)
domain = sensor_class.__module__.split(".")[-1]
for entry in entries:
if entry.domain != domain:
continue
attribute = entry.unique_id.split("-")[-1]
description = BlockAttributeDescription(
name="",
icon=entry.original_icon,
unit=entry.unit_of_measurement,
device_class=entry.device_class,
)
entities.append(sensor_class(wrapper, None, attribute, description, entry))
if not entities:
return
async_add_entities(entities)
async def async_setup_entry_rest(
hass, config_entry, async_add_entities, sensors, sensor_class
):
@ -163,7 +219,7 @@ class ShellyBlockEntity(entity.Entity):
class ShellyBlockAttributeEntity(ShellyBlockEntity, entity.Entity):
"""Switch that controls a relay block on Shelly devices."""
"""Helper class to represent a block attribute."""
def __init__(
self,
@ -176,12 +232,11 @@ class ShellyBlockAttributeEntity(ShellyBlockEntity, entity.Entity):
super().__init__(wrapper, block)
self.attribute = attribute
self.description = description
self.info = block.info(attribute)
unit = self.description.unit
if callable(unit):
unit = unit(self.info)
unit = unit(block.info(attribute))
self._unit = unit
self._unique_id = f"{super().unique_id}-{self.attribute}"
@ -320,3 +375,67 @@ class ShellyRestAttributeEntity(update_coordinator.CoordinatorEntity):
return None
return self.description.device_state_attributes(self.wrapper.device.status)
class ShellySleepingBlockAttributeEntity(ShellyBlockAttributeEntity, RestoreEntity):
"""Represent a shelly sleeping block attribute entity."""
# pylint: disable=super-init-not-called
def __init__(
self,
wrapper: ShellyDeviceWrapper,
block: aioshelly.Block,
attribute: str,
description: BlockAttributeDescription,
entry: Optional[ConfigEntry] = None,
) -> None:
"""Initialize the sleeping sensor."""
self.last_state = None
self.wrapper = wrapper
self.attribute = attribute
self.block = block
self.description = description
self._unit = self.description.unit
if block is not None:
if callable(self._unit):
self._unit = self._unit(block.info(attribute))
self._unique_id = f"{self.wrapper.mac}-{block.description}-{attribute}"
self._name = get_entity_name(
self.wrapper.device, block, self.description.name
)
else:
self._unique_id = entry.unique_id
self._name = entry.original_name
async def async_added_to_hass(self):
"""Handle entity which will be added."""
await super().async_added_to_hass()
last_state = await self.async_get_last_state()
if last_state is not None:
self.last_state = last_state.state
@callback
def _update_callback(self):
"""Handle device update."""
if self.block is not None:
super()._update_callback()
return
_, entity_block, entity_sensor = self.unique_id.split("-")
for block in self.wrapper.device.blocks:
if block.description != entity_block:
continue
for sensor_id in block.sensor_ids:
if sensor_id != entity_sensor:
continue
self.block = block
_LOGGER.debug("Entity %s attached to block", self.name)
super()._update_callback()
return

View File

@ -3,7 +3,7 @@
"name": "Shelly",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/shelly",
"requirements": ["aioshelly==0.5.3"],
"requirements": ["aioshelly==0.5.4"],
"zeroconf": [{ "type": "_http._tcp.local.", "name": "shelly*" }],
"codeowners": ["@balloob", "@bieniu", "@thecode", "@chemelli74"]
}

View File

@ -18,6 +18,7 @@ from .entity import (
RestAttributeDescription,
ShellyBlockAttributeEntity,
ShellyRestAttributeEntity,
ShellySleepingBlockAttributeEntity,
async_setup_entry_attribute_entities,
async_setup_entry_rest,
)
@ -185,12 +186,17 @@ REST_SENSORS = {
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up sensors for device."""
await async_setup_entry_attribute_entities(
hass, config_entry, async_add_entities, SENSORS, ShellySensor
)
await async_setup_entry_rest(
hass, config_entry, async_add_entities, REST_SENSORS, ShellyRestSensor
)
if config_entry.data["sleep_period"]:
await async_setup_entry_attribute_entities(
hass, config_entry, async_add_entities, SENSORS, ShellySleepingSensor
)
else:
await async_setup_entry_attribute_entities(
hass, config_entry, async_add_entities, SENSORS, ShellySensor
)
await async_setup_entry_rest(
hass, config_entry, async_add_entities, REST_SENSORS, ShellyRestSensor
)
class ShellySensor(ShellyBlockAttributeEntity):
@ -209,3 +215,15 @@ class ShellyRestSensor(ShellyRestAttributeEntity):
def state(self):
"""Return value of sensor."""
return self.attribute_value
class ShellySleepingSensor(ShellySleepingBlockAttributeEntity):
"""Represent a shelly sleeping sensor."""
@property
def state(self):
"""Return value of sensor."""
if self.block is not None:
return self.attribute_value
return self.last_state

View File

@ -6,8 +6,9 @@ from typing import List, Optional, Tuple
import aioshelly
from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT
from homeassistant.core import HomeAssistant
from homeassistant.const import EVENT_HOMEASSISTANT_STOP, TEMP_CELSIUS, TEMP_FAHRENHEIT
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import singleton
from homeassistant.util.dt import parse_datetime, utcnow
from .const import (
@ -182,3 +183,30 @@ def get_device_wrapper(hass: HomeAssistant, device_id: str):
return wrapper
return None
@singleton.singleton("shelly_coap")
async def get_coap_context(hass):
"""Get CoAP context to be used in all Shelly devices."""
context = aioshelly.COAP()
await context.initialize()
@callback
def shutdown_listener(ev):
context.close()
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, shutdown_listener)
return context
def get_device_sleep_period(settings: dict) -> int:
"""Return the device sleep period in seconds or 0 for non sleeping devices."""
sleep_period = 0
if settings.get("sleep_mode", False):
sleep_period = settings["sleep_mode"]["period"]
if settings["sleep_mode"]["unit"] == "h":
sleep_period *= 60 # hours to minutes
return sleep_period * 60 # minutes to seconds

View File

@ -218,7 +218,7 @@ aiopylgtv==0.3.3
aiorecollect==1.0.1
# homeassistant.components.shelly
aioshelly==0.5.3
aioshelly==0.5.4
# homeassistant.components.switcher_kis
aioswitcher==1.2.1

View File

@ -137,7 +137,7 @@ aiopylgtv==0.3.3
aiorecollect==1.0.1
# homeassistant.components.shelly
aioshelly==0.5.3
aioshelly==0.5.4
# homeassistant.components.switcher_kis
aioswitcher==1.2.1

View File

@ -91,7 +91,11 @@ async def coap_wrapper(hass):
"""Setups a coap wrapper with mocked device."""
await async_setup_component(hass, "shelly", {})
config_entry = MockConfigEntry(domain=DOMAIN, data={})
config_entry = MockConfigEntry(
domain=DOMAIN,
data={"sleep_period": 0, "model": "SHSW-25"},
unique_id="12345678",
)
config_entry.add_to_hass(hass)
device = Mock(
@ -99,6 +103,7 @@ async def coap_wrapper(hass):
settings=MOCK_SETTINGS,
shelly=MOCK_SHELLY,
update=AsyncMock(),
initialized=True,
)
hass.data[DOMAIN] = {DATA_CONFIG_ENTRY: {}}

View File

@ -13,7 +13,8 @@ from tests.common import MockConfigEntry
MOCK_SETTINGS = {
"name": "Test name",
"device": {"mac": "test-mac", "hostname": "test-host"},
"device": {"mac": "test-mac", "hostname": "test-host", "type": "SHSW-1"},
"sleep_period": 0,
}
DISCOVERY_INFO = {
"host": "1.1.1.1",
@ -57,6 +58,8 @@ async def test_form(hass):
assert result2["title"] == "Test name"
assert result2["data"] == {
"host": "1.1.1.1",
"model": "SHSW-1",
"sleep_period": 0,
}
assert len(mock_setup.mock_calls) == 1
assert len(mock_setup_entry.mock_calls) == 1
@ -101,6 +104,8 @@ async def test_title_without_name(hass):
assert result2["title"] == "shelly1pm-12345"
assert result2["data"] == {
"host": "1.1.1.1",
"model": "SHSW-1",
"sleep_period": 0,
}
assert len(mock_setup.mock_calls) == 1
assert len(mock_setup_entry.mock_calls) == 1
@ -149,6 +154,8 @@ async def test_form_auth(hass):
assert result3["title"] == "Test name"
assert result3["data"] == {
"host": "1.1.1.1",
"model": "SHSW-1",
"sleep_period": 0,
"username": "test username",
"password": "test password",
}
@ -369,6 +376,8 @@ async def test_zeroconf(hass):
assert result2["title"] == "Test name"
assert result2["data"] == {
"host": "1.1.1.1",
"model": "SHSW-1",
"sleep_period": 0,
}
assert len(mock_setup.mock_calls) == 1
assert len(mock_setup_entry.mock_calls) == 1
@ -502,6 +511,8 @@ async def test_zeroconf_require_auth(hass):
assert result3["title"] == "Test name"
assert result3["data"] == {
"host": "1.1.1.1",
"model": "SHSW-1",
"sleep_period": 0,
"username": "test username",
"password": "test password",
}