Add basic light and sensor support to Shelly (#39288)

* Add basic light platform

* Add sensor support

* Bump aioshelly to 0.2.1

* Lint

* Use UNIT_PERCENTAGE

Co-authored-by: Maciej Bieniek <bieniu@users.noreply.github.com>

* Format sensor.py

Co-authored-by: Maciej Bieniek <bieniu@users.noreply.github.com>
This commit is contained in:
Paulus Schoutsen 2020-08-28 17:33:34 +02:00 committed by GitHub
parent d9f3bdea53
commit 4b8217777e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 181 additions and 16 deletions

View file

@ -754,6 +754,8 @@ omit =
homeassistant/components/shiftr/* homeassistant/components/shiftr/*
homeassistant/components/shodan/sensor.py homeassistant/components/shodan/sensor.py
homeassistant/components/shelly/__init__.py homeassistant/components/shelly/__init__.py
homeassistant/components/shelly/light.py
homeassistant/components/shelly/sensor.py
homeassistant/components/shelly/switch.py homeassistant/components/shelly/switch.py
homeassistant/components/sht31/sensor.py homeassistant/components/sht31/sensor.py
homeassistant/components/sigfox/sensor.py homeassistant/components/sigfox/sensor.py

View file

@ -20,7 +20,7 @@ from homeassistant.helpers import (
from .const import DOMAIN from .const import DOMAIN
PLATFORMS = ["switch"] PLATFORMS = ["switch", "light", "sensor"]
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -129,11 +129,12 @@ class ShellyBlockEntity(entity.Entity):
"""Initialize Shelly entity.""" """Initialize Shelly entity."""
self.wrapper = wrapper self.wrapper = wrapper
self.block = block self.block = block
self._name = f"{self.wrapper.name} - {self.block.description.replace('_', ' ')}"
@property @property
def name(self): def name(self):
"""Name of entity.""" """Name of entity."""
return f"{self.wrapper.name} - {self.block.description}" return self._name
@property @property
def should_poll(self): def should_poll(self):
@ -155,7 +156,7 @@ class ShellyBlockEntity(entity.Entity):
@property @property
def unique_id(self): def unique_id(self):
"""Return unique ID of entity.""" """Return unique ID of entity."""
return f"{self.wrapper.mac}-{self.block.index}" return f"{self.wrapper.mac}-{self.block.description}"
async def async_added_to_hass(self): async def async_added_to_hass(self):
"""When entity is added to HASS.""" """When entity is added to HASS."""

View file

@ -61,7 +61,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
try: try:
device_info = await validate_input(self.hass, user_input) device_info = await validate_input(self.hass, user_input)
except asyncio.TimeoutError: except HTTP_CONNECT_ERRORS:
errors["base"] = "cannot_connect" errors["base"] = "cannot_connect"
except Exception: # pylint: disable=broad-except except Exception: # pylint: disable=broad-except
_LOGGER.exception("Unexpected exception") _LOGGER.exception("Unexpected exception")
@ -103,7 +103,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
if user_input is not None: if user_input is not None:
try: try:
device_info = await validate_input(self.hass, {"host": self.host}) device_info = await validate_input(self.hass, {"host": self.host})
except asyncio.TimeoutError: except HTTP_CONNECT_ERRORS:
errors["base"] = "cannot_connect" errors["base"] = "cannot_connect"
except Exception: # pylint: disable=broad-except except Exception: # pylint: disable=broad-except
_LOGGER.exception("Unexpected exception") _LOGGER.exception("Unexpected exception")

View file

@ -0,0 +1,74 @@
"""Light for Shelly."""
from aioshelly import Block
from homeassistant.components.light import SUPPORT_BRIGHTNESS, LightEntity
from homeassistant.core import callback
from . import ShellyBlockEntity, ShellyDeviceWrapper
from .const import DOMAIN
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up lights for device."""
wrapper = hass.data[DOMAIN][config_entry.entry_id]
blocks = [block for block in wrapper.device.blocks if block.type == "light"]
if not blocks:
return
async_add_entities(ShellyLight(wrapper, block) for block in blocks)
class ShellyLight(ShellyBlockEntity, LightEntity):
"""Switch that controls a relay block on Shelly devices."""
def __init__(self, wrapper: ShellyDeviceWrapper, block: Block) -> None:
"""Initialize light."""
super().__init__(wrapper, block)
self.control_result = None
self._supported_features = 0
if hasattr(block, "brightness"):
self._supported_features |= SUPPORT_BRIGHTNESS
@property
def is_on(self) -> bool:
"""If light is on."""
if self.control_result:
return self.control_result["ison"]
return self.block.output
@property
def brightness(self):
"""Brightness of light."""
if self.control_result:
brightness = self.control_result["brightness"]
else:
brightness = self.block.brightness
return int(brightness / 100 * 255)
@property
def supported_features(self):
"""Supported features."""
return self._supported_features
async def async_turn_on(
self, brightness=None, **kwargs
): # pylint: disable=arguments-differ
"""Turn on light."""
params = {"turn": "on"}
if brightness is not None:
params["brightness"] = int(brightness / 255 * 100)
self.control_result = await self.block.set_state(**params)
self.async_write_ha_state()
async def async_turn_off(self, **kwargs):
"""Turn off light."""
self.control_result = await self.block.set_state(turn="off")
self.async_write_ha_state()
@callback
def _update_callback(self):
"""When device updates, clear control result that overrides state."""
self.control_result = None
super()._update_callback()

View file

@ -3,7 +3,7 @@
"name": "Shelly", "name": "Shelly",
"config_flow": true, "config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/shelly2", "documentation": "https://www.home-assistant.io/integrations/shelly2",
"requirements": ["aioshelly==0.1.2"], "requirements": ["aioshelly==0.2.1"],
"zeroconf": ["_http._tcp.local."], "zeroconf": ["_http._tcp.local."],
"codeowners": ["@balloob"] "codeowners": ["@balloob"]
} }

View file

@ -0,0 +1,83 @@
"""Sensor for Shelly."""
import aioshelly
from homeassistant.components import sensor
from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT, UNIT_PERCENTAGE
from homeassistant.helpers.entity import Entity
from . import ShellyBlockEntity, ShellyDeviceWrapper
from .const import DOMAIN
SENSORS = {
"extTemp": [None, sensor.DEVICE_CLASS_TEMPERATURE],
"humidity": [UNIT_PERCENTAGE, sensor.DEVICE_CLASS_HUMIDITY],
}
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up sensors for device."""
wrapper = hass.data[DOMAIN][config_entry.entry_id]
sensors = []
for block in wrapper.device.blocks:
if block.type != "sensor":
continue
for attr in SENSORS:
if not hasattr(block, attr):
continue
sensors.append(ShellySensor(wrapper, block, attr))
if sensors:
async_add_entities(sensors)
class ShellySensor(ShellyBlockEntity, Entity):
"""Switch that controls a relay block on Shelly devices."""
def __init__(
self,
wrapper: ShellyDeviceWrapper,
block: aioshelly.Block,
attribute: str,
) -> None:
"""Initialize sensor."""
super().__init__(wrapper, block)
self.attribute = attribute
unit, device_class = SENSORS[attribute]
info = block.info(attribute)
if info[aioshelly.BLOCK_VALUE_TYPE] == aioshelly.BLOCK_VALUE_TYPE_TEMPERATURE:
if info[aioshelly.BLOCK_VALUE_UNIT] == "C":
unit = TEMP_CELSIUS
else:
unit = TEMP_FAHRENHEIT
self._unit = unit
self._device_class = device_class
@property
def unique_id(self):
"""Return unique ID of entity."""
return f"{super().unique_id}-{self.attribute}"
@property
def name(self):
"""Name of sensor."""
return f"{self.wrapper.name} - {self.attribute}"
@property
def state(self):
"""Value of sensor."""
return getattr(self.block, self.attribute)
@property
def unit_of_measurement(self):
"""Return unit of sensor."""
return self._unit
@property
def device_class(self):
"""Device class of sensor."""
return self._device_class

View file

@ -1,22 +1,25 @@
"""Switch for Shelly.""" """Switch for Shelly."""
from homeassistant.components.shelly import ShellyBlockEntity from aioshelly import RelayBlock
from homeassistant.components.switch import SwitchEntity from homeassistant.components.switch import SwitchEntity
from homeassistant.core import callback from homeassistant.core import callback
from . import ShellyBlockEntity, ShellyDeviceWrapper
from .const import DOMAIN from .const import DOMAIN
async def async_setup_entry(hass, config_entry, async_add_entities): async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up switches for device.""" """Set up switches for device."""
wrapper = hass.data[DOMAIN][config_entry.entry_id] wrapper = hass.data[DOMAIN][config_entry.entry_id]
if wrapper.model == "SHSW-25" and wrapper.device.settings["mode"] != "relay":
return
relay_blocks = [block for block in wrapper.device.blocks if block.type == "relay"] relay_blocks = [block for block in wrapper.device.blocks if block.type == "relay"]
if not relay_blocks: if not relay_blocks:
return return
if wrapper.model == "SHSW-25" and wrapper.device.settings["mode"] != "relay":
return
multiple_blocks = len(relay_blocks) > 1 multiple_blocks = len(relay_blocks) > 1
async_add_entities( async_add_entities(
RelaySwitch(wrapper, block, multiple_blocks=multiple_blocks) RelaySwitch(wrapper, block, multiple_blocks=multiple_blocks)
@ -27,9 +30,11 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
class RelaySwitch(ShellyBlockEntity, SwitchEntity): class RelaySwitch(ShellyBlockEntity, SwitchEntity):
"""Switch that controls a relay block on Shelly devices.""" """Switch that controls a relay block on Shelly devices."""
def __init__(self, *args, multiple_blocks) -> None: def __init__(
self, wrapper: ShellyDeviceWrapper, block: RelayBlock, multiple_blocks
) -> None:
"""Initialize relay switch.""" """Initialize relay switch."""
super().__init__(*args) super().__init__(wrapper, block)
self.multiple_blocks = multiple_blocks self.multiple_blocks = multiple_blocks
self.control_result = None self.control_result = None
@ -56,12 +61,12 @@ class RelaySwitch(ShellyBlockEntity, SwitchEntity):
async def async_turn_on(self, **kwargs): async def async_turn_on(self, **kwargs):
"""Turn on relay.""" """Turn on relay."""
self.control_result = await self.block.turn_on() self.control_result = await self.block.set_state(turn="on")
self.async_write_ha_state() self.async_write_ha_state()
async def async_turn_off(self, **kwargs): async def async_turn_off(self, **kwargs):
"""Turn off relay.""" """Turn off relay."""
self.control_result = await self.block.turn_off() self.control_result = await self.block.set_state(turn="off")
self.async_write_ha_state() self.async_write_ha_state()
@callback @callback

View file

@ -221,7 +221,7 @@ aiopvpc==2.0.2
aiopylgtv==0.3.3 aiopylgtv==0.3.3
# homeassistant.components.shelly # homeassistant.components.shelly
aioshelly==0.1.2 aioshelly==0.2.1
# homeassistant.components.switcher_kis # homeassistant.components.switcher_kis
aioswitcher==1.2.0 aioswitcher==1.2.0

View file

@ -131,7 +131,7 @@ aiopvpc==2.0.2
aiopylgtv==0.3.3 aiopylgtv==0.3.3
# homeassistant.components.shelly # homeassistant.components.shelly
aioshelly==0.1.2 aioshelly==0.2.1
# homeassistant.components.switcher_kis # homeassistant.components.switcher_kis
aioswitcher==1.2.0 aioswitcher==1.2.0