* Add logbook and device trigger platforms to Shelly Add `logbook` platform for describing “shelly.click” event Add `device_trigger` platform for adding automation based on click events: Example of logbook event: Shelly 'single' click event for Test I3 channel 3 was fired. (Test I3 is the name of the device) Example of automation triggers: First button triple clicked First button long clicked and then single clicked First button double clicked First button long clicked First button single clicked and then long clicked First button single clicked Second button triple clicked .. Second button single clicked * Fix codespell * Remove pylint added for debug * Add tests * Rebase * Fix Rebase & Apply PR review suggestions Fix tests after rebasing Use `INPUTS_EVENTS_DICT` for input triggers Apply PR suggestions
181 lines
5.4 KiB
Python
181 lines
5.4 KiB
Python
"""Shelly helpers functions."""
|
|
|
|
from datetime import timedelta
|
|
import logging
|
|
from typing import List, Optional, Tuple
|
|
|
|
import aioshelly
|
|
|
|
from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT
|
|
from homeassistant.core import HomeAssistant
|
|
from homeassistant.util.dt import parse_datetime, utcnow
|
|
|
|
from .const import (
|
|
BASIC_INPUTS_EVENTS_TYPES,
|
|
COAP,
|
|
DATA_CONFIG_ENTRY,
|
|
DOMAIN,
|
|
SHBTN_1_INPUTS_EVENTS_TYPES,
|
|
SHIX3_1_INPUTS_EVENTS_TYPES,
|
|
)
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
|
async def async_remove_shelly_entity(hass, domain, unique_id):
|
|
"""Remove a Shelly entity."""
|
|
entity_reg = await hass.helpers.entity_registry.async_get_registry()
|
|
entity_id = entity_reg.async_get_entity_id(domain, DOMAIN, unique_id)
|
|
if entity_id:
|
|
_LOGGER.debug("Removing entity: %s", entity_id)
|
|
entity_reg.async_remove(entity_id)
|
|
|
|
|
|
def temperature_unit(block_info: dict) -> str:
|
|
"""Detect temperature unit."""
|
|
if block_info[aioshelly.BLOCK_VALUE_UNIT] == "F":
|
|
return TEMP_FAHRENHEIT
|
|
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_number_of_channels(device: aioshelly.Device, block: aioshelly.Block) -> int:
|
|
"""Get number of channels for block type."""
|
|
channels = None
|
|
|
|
if block.type == "input":
|
|
# 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 = device.shelly.get("num_emeters")
|
|
elif block.type in ["relay", "light"]:
|
|
channels = device.shelly.get("num_outputs")
|
|
elif block.type in ["roller", "device"]:
|
|
channels = 1
|
|
|
|
return channels or 1
|
|
|
|
|
|
def get_entity_name(
|
|
device: aioshelly.Device,
|
|
block: aioshelly.Block,
|
|
description: Optional[str] = None,
|
|
) -> str:
|
|
"""Naming for switch and sensors."""
|
|
channel_name = get_device_channel_name(device, block)
|
|
|
|
if description:
|
|
return f"{channel_name} {description}"
|
|
|
|
return channel_name
|
|
|
|
|
|
def get_device_channel_name(
|
|
device: aioshelly.Device,
|
|
block: aioshelly.Block,
|
|
) -> str:
|
|
"""Get name based on device and channel name."""
|
|
entity_name = get_device_name(device)
|
|
|
|
if (
|
|
not block
|
|
or block.type == "device"
|
|
or get_number_of_channels(device, block) == 1
|
|
):
|
|
return entity_name
|
|
|
|
channel_name = None
|
|
mode = block.type + "s"
|
|
if mode in device.settings:
|
|
channel_name = device.settings[mode][int(block.channel)].get("name")
|
|
|
|
if channel_name:
|
|
return channel_name
|
|
|
|
if device.settings["device"]["type"] == "SHEM-3":
|
|
base = ord("A")
|
|
else:
|
|
base = ord("1")
|
|
|
|
return f"{entity_name} channel {chr(int(block.channel)+base)}"
|
|
|
|
|
|
def is_momentary_input(settings: dict, block: aioshelly.Block) -> bool:
|
|
"""Return true if input button settings is set to a momentary type."""
|
|
# Shelly Button type is fixed to momentary and no btn_type
|
|
if settings["device"]["type"] == "SHBTN-1":
|
|
return True
|
|
|
|
button = settings.get("relays") or settings.get("lights") or settings.get("inputs")
|
|
|
|
# Shelly 1L has two button settings in the first channel
|
|
if settings["device"]["type"] == "SHSW-L":
|
|
channel = int(block.channel or 0) + 1
|
|
button_type = button[0].get("btn" + str(channel) + "_type")
|
|
else:
|
|
# Some devices has only one channel in settings
|
|
channel = min(int(block.channel or 0), len(button) - 1)
|
|
button_type = button[channel].get("btn_type")
|
|
|
|
return button_type in ["momentary", "momentary_on_release"]
|
|
|
|
|
|
def get_device_uptime(status: dict, last_uptime: str) -> str:
|
|
"""Return device uptime string, tolerate up to 5 seconds deviation."""
|
|
uptime = utcnow() - timedelta(seconds=status["uptime"])
|
|
|
|
if not last_uptime:
|
|
return uptime.replace(microsecond=0).isoformat()
|
|
|
|
if abs((uptime - parse_datetime(last_uptime)).total_seconds()) > 5:
|
|
return uptime.replace(microsecond=0).isoformat()
|
|
|
|
return last_uptime
|
|
|
|
|
|
def get_input_triggers(
|
|
device: aioshelly.Device, block: aioshelly.Block
|
|
) -> List[Tuple[str, str]]:
|
|
"""Return list of input triggers for block."""
|
|
if "inputEvent" not in block.sensor_ids or "inputEventCnt" not in block.sensor_ids:
|
|
return []
|
|
|
|
if not is_momentary_input(device.settings, block):
|
|
return []
|
|
|
|
triggers = []
|
|
|
|
if block.type == "device" or get_number_of_channels(device, block) == 1:
|
|
subtype = "button"
|
|
else:
|
|
subtype = f"button{int(block.channel)+1}"
|
|
|
|
if device.settings["device"]["type"] == "SHBTN-1":
|
|
trigger_types = SHBTN_1_INPUTS_EVENTS_TYPES
|
|
elif device.settings["device"]["type"] == "SHIX3-1":
|
|
trigger_types = SHIX3_1_INPUTS_EVENTS_TYPES
|
|
else:
|
|
trigger_types = BASIC_INPUTS_EVENTS_TYPES
|
|
|
|
for trigger_type in trigger_types:
|
|
triggers.append((trigger_type, subtype))
|
|
|
|
return triggers
|
|
|
|
|
|
def get_device_wrapper(hass: HomeAssistant, device_id: str):
|
|
"""Get a Shelly device wrapper for the given device id."""
|
|
for config_entry in hass.data[DOMAIN][DATA_CONFIG_ENTRY]:
|
|
wrapper = hass.data[DOMAIN][DATA_CONFIG_ENTRY][config_entry][COAP]
|
|
|
|
if wrapper.device_id == device_id:
|
|
return wrapper
|
|
|
|
return None
|