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:
parent
d9f3bdea53
commit
4b8217777e
9 changed files with 181 additions and 16 deletions
|
@ -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
|
||||||
|
|
|
@ -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."""
|
||||||
|
|
|
@ -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")
|
||||||
|
|
74
homeassistant/components/shelly/light.py
Normal file
74
homeassistant/components/shelly/light.py
Normal 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()
|
|
@ -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"]
|
||||||
}
|
}
|
||||||
|
|
83
homeassistant/components/shelly/sensor.py
Normal file
83
homeassistant/components/shelly/sensor.py
Normal 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
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Add table
Reference in a new issue