Support for Shelly Binary Input Sensors (#43313)

Co-authored-by: Maciej Bieniek <bieniu@users.noreply.github.com>
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
This commit is contained in:
Shay Levy 2020-11-19 12:42:24 +02:00 committed by GitHub
parent 3dbfd2cb70
commit 982624b3ac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 73 additions and 55 deletions

View File

@ -4,6 +4,7 @@ from homeassistant.components.binary_sensor import (
DEVICE_CLASS_GAS, DEVICE_CLASS_GAS,
DEVICE_CLASS_MOISTURE, DEVICE_CLASS_MOISTURE,
DEVICE_CLASS_OPENING, DEVICE_CLASS_OPENING,
DEVICE_CLASS_POWER,
DEVICE_CLASS_PROBLEM, DEVICE_CLASS_PROBLEM,
DEVICE_CLASS_SMOKE, DEVICE_CLASS_SMOKE,
DEVICE_CLASS_VIBRATION, DEVICE_CLASS_VIBRATION,
@ -18,6 +19,7 @@ from .entity import (
async_setup_entry_attribute_entities, async_setup_entry_attribute_entities,
async_setup_entry_rest, async_setup_entry_rest,
) )
from .utils import is_momentary_input
SENSORS = { SENSORS = {
("device", "overtemp"): BlockAttributeDescription( ("device", "overtemp"): BlockAttributeDescription(
@ -50,6 +52,24 @@ SENSORS = {
("sensor", "vibration"): BlockAttributeDescription( ("sensor", "vibration"): BlockAttributeDescription(
name="Vibration", device_class=DEVICE_CLASS_VIBRATION name="Vibration", device_class=DEVICE_CLASS_VIBRATION
), ),
("input", "input"): BlockAttributeDescription(
name="Input",
device_class=DEVICE_CLASS_POWER,
default_enabled=False,
removal_condition=is_momentary_input,
),
("relay", "input"): BlockAttributeDescription(
name="Input",
device_class=DEVICE_CLASS_POWER,
default_enabled=False,
removal_condition=is_momentary_input,
),
("device", "input"): BlockAttributeDescription(
name="Input",
device_class=DEVICE_CLASS_POWER,
default_enabled=False,
removal_condition=is_momentary_input,
),
} }
REST_SENSORS = { REST_SENSORS = {

View File

@ -9,7 +9,7 @@ from homeassistant.helpers import device_registry, entity, update_coordinator
from . import ShellyDeviceRestWrapper, ShellyDeviceWrapper from . import ShellyDeviceRestWrapper, ShellyDeviceWrapper
from .const import COAP, DATA_CONFIG_ENTRY, DOMAIN, REST from .const import COAP, DATA_CONFIG_ENTRY, DOMAIN, REST
from .utils import get_entity_name, get_rest_value_from_path from .utils import async_remove_shelly_entity, get_entity_name, get_rest_value_from_path
async def async_setup_entry_attribute_entities( async def async_setup_entry_attribute_entities(
@ -31,7 +31,17 @@ async def async_setup_entry_attribute_entities(
if getattr(block, sensor_id, None) in (-1, None): if getattr(block, sensor_id, None) in (-1, None):
continue continue
blocks.append((block, sensor_id, description)) # Filter and remove entities that according to settings should not create an entity
if description.removal_condition and description.removal_condition(
wrapper.device.settings, block
):
domain = sensor_class.__module__.split(".")[-1]
unique_id = sensor_class(
wrapper, block, sensor_id, description
).unique_id
await async_remove_shelly_entity(hass, domain, unique_id)
else:
blocks.append((block, sensor_id, description))
if not blocks: if not blocks:
return return
@ -77,6 +87,8 @@ class BlockAttributeDescription:
device_class: Optional[str] = None device_class: Optional[str] = None
default_enabled: bool = True default_enabled: bool = True
available: Optional[Callable[[aioshelly.Block], bool]] = None available: Optional[Callable[[aioshelly.Block], bool]] = None
# Callable (settings, block), return true if entity should be removed
removal_condition: Optional[Callable[[dict, aioshelly.Block], bool]] = None
device_state_attributes: Optional[ device_state_attributes: Optional[
Callable[[aioshelly.Block], Optional[dict]] Callable[[aioshelly.Block], Optional[dict]]
] = None ] = None

View File

@ -19,7 +19,7 @@ from homeassistant.util.color import (
from . import ShellyDeviceWrapper from . import ShellyDeviceWrapper
from .const import COAP, DATA_CONFIG_ENTRY, DOMAIN from .const import COAP, DATA_CONFIG_ENTRY, DOMAIN
from .entity import ShellyBlockEntity from .entity import ShellyBlockEntity
from .utils import async_remove_entity_by_domain from .utils import async_remove_shelly_entity
async def async_setup_entry(hass, config_entry, async_add_entities): async def async_setup_entry(hass, config_entry, async_add_entities):
@ -39,9 +39,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
unique_id = ( unique_id = (
f'{wrapper.device.shelly["mac"]}-{block.type}_{block.channel}' f'{wrapper.device.shelly["mac"]}-{block.type}_{block.channel}'
) )
await async_remove_entity_by_domain( await async_remove_shelly_entity(hass, "switch", unique_id)
hass, "switch", unique_id, config_entry.entry_id
)
if not blocks: if not blocks:
return return

View File

@ -1,6 +1,4 @@
"""Sensor for Shelly.""" """Sensor for Shelly."""
import logging
from homeassistant.components import sensor from homeassistant.components import sensor
from homeassistant.const import ( from homeassistant.const import (
CONCENTRATION_PARTS_PER_MILLION, CONCENTRATION_PARTS_PER_MILLION,
@ -14,8 +12,7 @@ from homeassistant.const import (
VOLT, VOLT,
) )
from . import ShellyDeviceWrapper, get_device_name from .const import SHAIR_MAX_WORK_HOURS
from .const import DATA_CONFIG_ENTRY, DOMAIN, REST, SHAIR_MAX_WORK_HOURS
from .entity import ( from .entity import (
BlockAttributeDescription, BlockAttributeDescription,
RestAttributeDescription, RestAttributeDescription,
@ -24,17 +21,15 @@ from .entity import (
async_setup_entry_attribute_entities, async_setup_entry_attribute_entities,
async_setup_entry_rest, async_setup_entry_rest,
) )
from .utils import async_remove_entity_by_domain, temperature_unit from .utils import temperature_unit
_LOGGER = logging.getLogger(__name__)
BATTERY_SENSOR = {
("device", "battery"): BlockAttributeDescription(
name="Battery", unit=PERCENTAGE, device_class=sensor.DEVICE_CLASS_BATTERY
),
}
SENSORS = { SENSORS = {
("device", "battery"): BlockAttributeDescription(
name="Battery",
unit=PERCENTAGE,
device_class=sensor.DEVICE_CLASS_BATTERY,
removal_condition=lambda settings, _: settings.get("external_power") == 1,
),
("device", "deviceTemp"): BlockAttributeDescription( ("device", "deviceTemp"): BlockAttributeDescription(
name="Device Temperature", name="Device Temperature",
unit=temperature_unit, unit=temperature_unit,
@ -176,6 +171,7 @@ REST_SENSORS = {
"uptime": RestAttributeDescription( "uptime": RestAttributeDescription(
name="Uptime", name="Uptime",
device_class=sensor.DEVICE_CLASS_TIMESTAMP, device_class=sensor.DEVICE_CLASS_TIMESTAMP,
default_enabled=False,
path="uptime", path="uptime",
), ),
} }
@ -183,28 +179,6 @@ REST_SENSORS = {
async def async_setup_entry(hass, config_entry, async_add_entities): async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up sensors for device.""" """Set up sensors for device."""
wrapper: ShellyDeviceWrapper = hass.data[DOMAIN][DATA_CONFIG_ENTRY][
config_entry.entry_id
][REST]
if (
"external_power" in wrapper.device.settings
and wrapper.device.settings["external_power"] == 1
):
_LOGGER.debug(
"Removed battery sensor [externally powered] for %s",
get_device_name(wrapper.device),
)
unique_id = f'{wrapper.device.shelly["mac"]}-battery'
await async_remove_entity_by_domain(
hass, "sensor", unique_id, config_entry.entry_id
)
else:
await async_setup_entry_attribute_entities(
hass, config_entry, async_add_entities, BATTERY_SENSOR, ShellySensor
)
await async_setup_entry_attribute_entities( await async_setup_entry_attribute_entities(
hass, config_entry, async_add_entities, SENSORS, ShellySensor hass, config_entry, async_add_entities, SENSORS, ShellySensor
) )

View File

@ -7,7 +7,7 @@ from homeassistant.core import callback
from . import ShellyDeviceWrapper from . import ShellyDeviceWrapper
from .const import COAP, DATA_CONFIG_ENTRY, DOMAIN from .const import COAP, DATA_CONFIG_ENTRY, DOMAIN
from .entity import ShellyBlockEntity from .entity import ShellyBlockEntity
from .utils import async_remove_entity_by_domain from .utils import async_remove_shelly_entity
async def async_setup_entry(hass, config_entry, async_add_entities): async def async_setup_entry(hass, config_entry, async_add_entities):
@ -32,11 +32,10 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
unique_id = ( unique_id = (
f'{wrapper.device.shelly["mac"]}-{block.type}_{block.channel}' f'{wrapper.device.shelly["mac"]}-{block.type}_{block.channel}'
) )
await async_remove_entity_by_domain( await async_remove_shelly_entity(
hass, hass,
"light", "light",
unique_id, unique_id,
config_entry.entry_id,
) )
if not relay_blocks: if not relay_blocks:

View File

@ -8,24 +8,20 @@ import aioshelly
from homeassistant.components.sensor import DEVICE_CLASS_TIMESTAMP from homeassistant.components.sensor import DEVICE_CLASS_TIMESTAMP
from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT
from homeassistant.helpers import entity_registry
from . import ShellyDeviceWrapper from . import ShellyDeviceWrapper
from .const import DOMAIN
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
async def async_remove_entity_by_domain(hass, domain, unique_id, config_entry_id): async def async_remove_shelly_entity(hass, domain, unique_id):
"""Remove entity by domain.""" """Remove a Shelly entity."""
entity_reg = await hass.helpers.entity_registry.async_get_registry() entity_reg = await hass.helpers.entity_registry.async_get_registry()
for entry in entity_registry.async_entries_for_config_entry( entity_id = entity_reg.async_get_entity_id(domain, DOMAIN, unique_id)
entity_reg, config_entry_id if entity_id:
): _LOGGER.debug("Removing entity: %s", entity_id)
if entry.domain == domain and entry.unique_id == unique_id: entity_reg.async_remove(entity_id)
entity_reg.async_remove(entry.entity_id)
_LOGGER.debug("Removed %s domain for %s", domain, entry.original_name)
break
def temperature_unit(block_info: dict) -> str: def temperature_unit(block_info: dict) -> str:
@ -92,4 +88,23 @@ def get_rest_value_from_path(status, device_class, path: str):
last_boot = datetime.utcnow() - timedelta(seconds=attribute_value) last_boot = datetime.utcnow() - timedelta(seconds=attribute_value)
attribute_value = last_boot.replace(microsecond=0).isoformat() attribute_value = last_boot.replace(microsecond=0).isoformat()
if "new_version" in path:
attribute_value = attribute_value.split("/")[1].split("@")[0]
return attribute_value return attribute_value
def is_momentary_input(settings: dict, block: aioshelly.Block) -> bool:
"""Return true if input button settings is set to a momentary type."""
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"]