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:
parent
3dbfd2cb70
commit
982624b3ac
6 changed files with 73 additions and 55 deletions
|
@ -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 = {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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"]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue