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/shodan/sensor.py
homeassistant/components/shelly/__init__.py
homeassistant/components/shelly/light.py
homeassistant/components/shelly/sensor.py
homeassistant/components/shelly/switch.py
homeassistant/components/sht31/sensor.py
homeassistant/components/sigfox/sensor.py

View file

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

View file

@ -61,7 +61,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
try:
device_info = await validate_input(self.hass, user_input)
except asyncio.TimeoutError:
except HTTP_CONNECT_ERRORS:
errors["base"] = "cannot_connect"
except Exception: # pylint: disable=broad-except
_LOGGER.exception("Unexpected exception")
@ -103,7 +103,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
if user_input is not None:
try:
device_info = await validate_input(self.hass, {"host": self.host})
except asyncio.TimeoutError:
except HTTP_CONNECT_ERRORS:
errors["base"] = "cannot_connect"
except Exception: # pylint: disable=broad-except
_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",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/shelly2",
"requirements": ["aioshelly==0.1.2"],
"requirements": ["aioshelly==0.2.1"],
"zeroconf": ["_http._tcp.local."],
"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."""
from homeassistant.components.shelly import ShellyBlockEntity
from aioshelly import RelayBlock
from homeassistant.components.switch import SwitchEntity
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 switches for device."""
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"]
if not relay_blocks:
return
if wrapper.model == "SHSW-25" and wrapper.device.settings["mode"] != "relay":
return
multiple_blocks = len(relay_blocks) > 1
async_add_entities(
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):
"""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."""
super().__init__(*args)
super().__init__(wrapper, block)
self.multiple_blocks = multiple_blocks
self.control_result = None
@ -56,12 +61,12 @@ class RelaySwitch(ShellyBlockEntity, SwitchEntity):
async def async_turn_on(self, **kwargs):
"""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()
async def async_turn_off(self, **kwargs):
"""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()
@callback

View file

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

View file

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