Support for Shelly Input Events (#43479)

This commit is contained in:
Shay Levy 2020-11-25 16:29:01 +02:00 committed by GitHub
parent b3be708db6
commit ebaf143cf6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 81 additions and 27 deletions

View File

@ -27,6 +27,7 @@ from .const import (
COAP,
DATA_CONFIG_ENTRY,
DOMAIN,
INPUTS_EVENTS_DICT,
POLLING_TIMEOUT_MULTIPLIER,
REST,
REST_SENSORS_UPDATE_INTERVAL,
@ -34,6 +35,7 @@ from .const import (
SLEEP_PERIOD_MULTIPLIER,
UPDATE_PERIOD_MULTIPLIER,
)
from .utils import get_device_name
PLATFORMS = ["binary_sensor", "cover", "light", "sensor", "switch"]
_LOGGER = logging.getLogger(__name__)
@ -54,11 +56,6 @@ async def get_coap_context(hass):
return context
def get_device_name(device):
"""Naming for device."""
return device.settings["name"] or device.settings["device"]["hostname"]
async def async_setup(hass: HomeAssistant, config: dict):
"""Set up the Shelly component."""
hass.data[DOMAIN] = {DATA_CONFIG_ENTRY: {}}
@ -113,6 +110,7 @@ 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")
if sleep_mode:
@ -140,6 +138,46 @@ class ShellyDeviceWrapper(update_coordinator.DataUpdateCoordinator):
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._last_input_events_count = dict()
@callback
def _async_input_events_handler(self):
"""Handle device input events."""
for block in self.device.blocks:
if (
"inputEvent" not in block.sensor_ids
or "inputEventCnt" not in block.sensor_ids
):
continue
channel = int(block.channel or 0) + 1
event_type = block.inputEvent
last_event_count = self._last_input_events_count.get(channel)
self._last_input_events_count[channel] = block.inputEventCnt
if last_event_count == block.inputEventCnt or event_type == "":
continue
if event_type in INPUTS_EVENTS_DICT:
self.hass.bus.async_fire(
"shelly.click",
{
"device_id": self.device_id,
"device": self.device.settings["device"]["hostname"],
"channel": channel,
"click_type": INPUTS_EVENTS_DICT[event_type],
},
)
else:
_LOGGER.warning(
"Shelly input event %s for device %s is not supported, please open issue",
event_type,
self.name,
)
async def _async_update_data(self):
"""Fetch data."""
_LOGGER.debug("Polling Shelly Device - %s", self.name)
@ -166,7 +204,7 @@ class ShellyDeviceWrapper(update_coordinator.DataUpdateCoordinator):
"""Set up the wrapper."""
dev_reg = await device_registry.async_get_registry(self.hass)
model_type = self.device.settings["device"]["type"]
dev_reg.async_get_or_create(
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)},
@ -176,10 +214,12 @@ class ShellyDeviceWrapper(update_coordinator.DataUpdateCoordinator):
model=aioshelly.MODEL_NAMES.get(model_type, model_type),
sw_version=self.device.settings["fw"],
)
self.device_id = entry.id
def shutdown(self):
"""Shutdown the wrapper."""
self.device.shutdown()
self._async_remove_input_events_handler()
class ShellyDeviceRestWrapper(update_coordinator.DataUpdateCoordinator):
@ -200,7 +240,7 @@ class ShellyDeviceRestWrapper(update_coordinator.DataUpdateCoordinator):
"""Fetch data."""
try:
async with async_timeout.timeout(5):
_LOGGER.debug("REST update for %s", get_device_name(self.device))
_LOGGER.debug("REST update for %s", self.name)
return await self.device.update_status()
except OSError as err:
raise update_coordinator.UpdateFailed("Error fetching data") from err

View File

@ -22,3 +22,13 @@ UPDATE_PERIOD_MULTIPLIER = 2.2
# Shelly Air - Maximum work hours before lamp replacement
SHAIR_MAX_WORK_HOURS = 9000
# Map Shelly input events
INPUTS_EVENTS_DICT = {
"S": "single",
"SS": "double",
"SSS": "triple",
"L": "long",
"SL": "single_long",
"LS": "long_single",
}

View File

@ -116,7 +116,7 @@ class ShellyBlockEntity(entity.Entity):
"""Initialize Shelly entity."""
self.wrapper = wrapper
self.block = block
self._name = get_entity_name(wrapper, block)
self._name = get_entity_name(wrapper.device, block)
@property
def name(self):
@ -182,7 +182,7 @@ class ShellyBlockAttributeEntity(ShellyBlockEntity, entity.Entity):
self._unit = unit
self._unique_id = f"{super().unique_id}-{self.attribute}"
self._name = get_entity_name(wrapper, block, self.description.name)
self._name = get_entity_name(wrapper.device, block, self.description.name)
@property
def unique_id(self):
@ -255,7 +255,7 @@ class ShellyRestAttributeEntity(update_coordinator.CoordinatorEntity):
self.description = description
self._unit = self.description.unit
self._name = get_entity_name(wrapper, None, self.description.name)
self._name = get_entity_name(wrapper.device, None, self.description.name)
self.path = self.description.path
self._attributes = self.description.attributes

View File

@ -9,7 +9,6 @@ import aioshelly
from homeassistant.components.sensor import DEVICE_CLASS_TIMESTAMP
from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT
from . import ShellyDeviceWrapper
from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
@ -31,22 +30,31 @@ def temperature_unit(block_info: dict) -> str:
return TEMP_CELSIUS
def get_device_name(device: aioshelly.Device) -> str:
"""Naming for device."""
return device.settings["name"] or device.settings["device"]["hostname"]
def get_entity_name(
wrapper: ShellyDeviceWrapper,
device: aioshelly.Device,
block: aioshelly.Block,
description: Optional[str] = None,
):
) -> str:
"""Naming for switch and sensors."""
entity_name = wrapper.name
entity_name = get_device_name(device)
if block:
channels = None
if block.type == "input":
channels = wrapper.device.shelly.get("num_inputs")
# Shelly Dimmer/1L has two input channels and missing "num_inputs"
if device.settings["device"]["type"] in ["SHDM-1", "SHDM-2", "SHSW-L"]:
channels = 2
else:
channels = device.shelly.get("num_inputs")
elif block.type == "emeter":
channels = wrapper.device.shelly.get("num_emeters")
channels = device.shelly.get("num_emeters")
elif block.type in ["relay", "light"]:
channels = wrapper.device.shelly.get("num_outputs")
channels = device.shelly.get("num_outputs")
elif block.type in ["roller", "device"]:
channels = 1
@ -55,21 +63,17 @@ def get_entity_name(
if channels > 1 and block.type != "device":
entity_name = None
mode = block.type + "s"
if mode in wrapper.device.settings:
entity_name = wrapper.device.settings[mode][int(block.channel)].get(
"name"
)
if mode in device.settings:
entity_name = device.settings[mode][int(block.channel)].get("name")
if not entity_name:
if wrapper.model == "SHEM-3":
if device.settings["device"]["type"] == "SHEM-3":
base = ord("A")
else:
base = ord("1")
entity_name = f"{wrapper.name} channel {chr(int(block.channel)+base)}"
# Shelly Dimmer has two input channels and missing "num_inputs"
if wrapper.model in ["SHDM-1", "SHDM-2"] and block.type == "input":
entity_name = f"{entity_name} channel {int(block.channel)+1}"
entity_name = (
f"{get_device_name(device)} channel {chr(int(block.channel)+base)}"
)
if description:
entity_name = f"{entity_name} {description}"