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_MOISTURE,
DEVICE_CLASS_OPENING,
DEVICE_CLASS_POWER,
DEVICE_CLASS_PROBLEM,
DEVICE_CLASS_SMOKE,
DEVICE_CLASS_VIBRATION,
@ -18,6 +19,7 @@ from .entity import (
async_setup_entry_attribute_entities,
async_setup_entry_rest,
)
from .utils import is_momentary_input
SENSORS = {
("device", "overtemp"): BlockAttributeDescription(
@ -50,6 +52,24 @@ SENSORS = {
("sensor", "vibration"): BlockAttributeDescription(
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 = {

View file

@ -9,7 +9,7 @@ from homeassistant.helpers import device_registry, entity, update_coordinator
from . import ShellyDeviceRestWrapper, ShellyDeviceWrapper
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(
@ -31,7 +31,17 @@ async def async_setup_entry_attribute_entities(
if getattr(block, sensor_id, None) in (-1, None):
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:
return
@ -77,6 +87,8 @@ class BlockAttributeDescription:
device_class: Optional[str] = None
default_enabled: bool = True
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[
Callable[[aioshelly.Block], Optional[dict]]
] = None

View file

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

View file

@ -1,6 +1,4 @@
"""Sensor for Shelly."""
import logging
from homeassistant.components import sensor
from homeassistant.const import (
CONCENTRATION_PARTS_PER_MILLION,
@ -14,8 +12,7 @@ from homeassistant.const import (
VOLT,
)
from . import ShellyDeviceWrapper, get_device_name
from .const import DATA_CONFIG_ENTRY, DOMAIN, REST, SHAIR_MAX_WORK_HOURS
from .const import SHAIR_MAX_WORK_HOURS
from .entity import (
BlockAttributeDescription,
RestAttributeDescription,
@ -24,17 +21,15 @@ from .entity import (
async_setup_entry_attribute_entities,
async_setup_entry_rest,
)
from .utils import async_remove_entity_by_domain, temperature_unit
_LOGGER = logging.getLogger(__name__)
BATTERY_SENSOR = {
("device", "battery"): BlockAttributeDescription(
name="Battery", unit=PERCENTAGE, device_class=sensor.DEVICE_CLASS_BATTERY
),
}
from .utils import temperature_unit
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(
name="Device Temperature",
unit=temperature_unit,
@ -176,6 +171,7 @@ REST_SENSORS = {
"uptime": RestAttributeDescription(
name="Uptime",
device_class=sensor.DEVICE_CLASS_TIMESTAMP,
default_enabled=False,
path="uptime",
),
}
@ -183,28 +179,6 @@ REST_SENSORS = {
async def async_setup_entry(hass, config_entry, async_add_entities):
"""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(
hass, config_entry, async_add_entities, SENSORS, ShellySensor
)

View file

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

View file

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