Add binary sensors to drop_connect integration (#106248)

This commit is contained in:
Patrick Frazer 2023-12-22 13:26:52 -05:00 committed by GitHub
parent 656d0696bb
commit c41173bb29
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 338 additions and 1 deletions

View File

@ -15,7 +15,7 @@ from .coordinator import DROPDeviceDataUpdateCoordinator
_LOGGER = logging.getLogger(__name__)
PLATFORMS: list[Platform] = [Platform.SENSOR]
PLATFORMS: list[Platform] = [Platform.BINARY_SENSOR, Platform.SENSOR]
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:

View File

@ -0,0 +1,138 @@
"""Support for DROP binary sensors."""
from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass
import logging
from homeassistant.components.binary_sensor import (
BinarySensorDeviceClass,
BinarySensorEntity,
BinarySensorEntityDescription,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import (
CONF_DEVICE_TYPE,
DEV_HUB,
DEV_LEAK_DETECTOR,
DEV_PROTECTION_VALVE,
DEV_PUMP_CONTROLLER,
DEV_RO_FILTER,
DEV_SALT_SENSOR,
DEV_SOFTENER,
DOMAIN,
)
from .coordinator import DROPDeviceDataUpdateCoordinator
from .entity import DROPEntity
_LOGGER = logging.getLogger(__name__)
LEAK_ICON = "mdi:pipe-leak"
NOTIFICATION_ICON = "mdi:bell-ring"
PUMP_ICON = "mdi:water-pump"
SALT_ICON = "mdi:shaker"
WATER_ICON = "mdi:water"
# Binary sensor type constants
LEAK_DETECTED = "leak"
PENDING_NOTIFICATION = "pending_notification"
PUMP_STATUS = "pump"
RESERVE_IN_USE = "reserve_in_use"
SALT_LOW = "salt"
@dataclass(kw_only=True, frozen=True)
class DROPBinarySensorEntityDescription(BinarySensorEntityDescription):
"""Describes DROP binary sensor entity."""
value_fn: Callable[[DROPDeviceDataUpdateCoordinator], bool | None]
BINARY_SENSORS: list[DROPBinarySensorEntityDescription] = [
DROPBinarySensorEntityDescription(
key=LEAK_DETECTED,
translation_key=LEAK_DETECTED,
icon=LEAK_ICON,
device_class=BinarySensorDeviceClass.MOISTURE,
value_fn=lambda device: device.drop_api.leak_detected(),
),
DROPBinarySensorEntityDescription(
key=PENDING_NOTIFICATION,
translation_key=PENDING_NOTIFICATION,
icon=NOTIFICATION_ICON,
value_fn=lambda device: device.drop_api.notification_pending(),
),
DROPBinarySensorEntityDescription(
key=SALT_LOW,
translation_key=SALT_LOW,
icon=SALT_ICON,
value_fn=lambda device: device.drop_api.salt_low(),
),
DROPBinarySensorEntityDescription(
key=RESERVE_IN_USE,
translation_key=RESERVE_IN_USE,
icon=WATER_ICON,
value_fn=lambda device: device.drop_api.reserve_in_use(),
),
DROPBinarySensorEntityDescription(
key=PUMP_STATUS,
translation_key=PUMP_STATUS,
icon=PUMP_ICON,
value_fn=lambda device: device.drop_api.pump_status(),
),
]
# Defines which binary sensors are used by each device type
DEVICE_BINARY_SENSORS: dict[str, list[str]] = {
DEV_HUB: [LEAK_DETECTED, PENDING_NOTIFICATION],
DEV_LEAK_DETECTOR: [LEAK_DETECTED],
DEV_PROTECTION_VALVE: [LEAK_DETECTED],
DEV_PUMP_CONTROLLER: [LEAK_DETECTED, PUMP_STATUS],
DEV_RO_FILTER: [LEAK_DETECTED],
DEV_SALT_SENSOR: [SALT_LOW],
DEV_SOFTENER: [RESERVE_IN_USE],
}
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the DROP binary sensors from config entry."""
_LOGGER.debug(
"Set up binary sensor for device type %s with entry_id is %s",
config_entry.data[CONF_DEVICE_TYPE],
config_entry.entry_id,
)
if config_entry.data[CONF_DEVICE_TYPE] in DEVICE_BINARY_SENSORS:
async_add_entities(
DROPBinarySensor(hass.data[DOMAIN][config_entry.entry_id], sensor)
for sensor in BINARY_SENSORS
if sensor.key in DEVICE_BINARY_SENSORS[config_entry.data[CONF_DEVICE_TYPE]]
)
class DROPBinarySensor(DROPEntity, BinarySensorEntity):
"""Representation of a DROP binary sensor."""
entity_description: DROPBinarySensorEntityDescription
def __init__(
self,
coordinator: DROPDeviceDataUpdateCoordinator,
entity_description: DROPBinarySensorEntityDescription,
) -> None:
"""Initialize the binary sensor."""
super().__init__(entity_description.key, coordinator)
self.entity_description = entity_description
@property
def is_on(self) -> bool:
"""Return the state of the binary sensor."""
return self.entity_description.value_fn(self.coordinator) == 1

View File

@ -25,6 +25,13 @@
"cart1": { "name": "Cartridge 1 life remaining" },
"cart2": { "name": "Cartridge 2 life remaining" },
"cart3": { "name": "Cartridge 3 life remaining" }
},
"binary_sensor": {
"leak": { "name": "Leak detected" },
"pending_notification": { "name": "Notification unread" },
"reserve_in_use": { "name": "Reserve capacity in use" },
"salt": { "name": "Salt low" },
"pump": { "name": "Pump status" }
}
}
}

View File

@ -0,0 +1,192 @@
"""Test DROP binary sensor entities."""
from homeassistant.components.drop_connect.const import DOMAIN
from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNKNOWN
from homeassistant.core import HomeAssistant
from homeassistant.setup import async_setup_component
from .common import (
TEST_DATA_HUB,
TEST_DATA_HUB_RESET,
TEST_DATA_HUB_TOPIC,
TEST_DATA_LEAK,
TEST_DATA_LEAK_RESET,
TEST_DATA_LEAK_TOPIC,
TEST_DATA_PROTECTION_VALVE,
TEST_DATA_PROTECTION_VALVE_RESET,
TEST_DATA_PROTECTION_VALVE_TOPIC,
TEST_DATA_PUMP_CONTROLLER,
TEST_DATA_PUMP_CONTROLLER_RESET,
TEST_DATA_PUMP_CONTROLLER_TOPIC,
TEST_DATA_RO_FILTER,
TEST_DATA_RO_FILTER_RESET,
TEST_DATA_RO_FILTER_TOPIC,
TEST_DATA_SALT,
TEST_DATA_SALT_RESET,
TEST_DATA_SALT_TOPIC,
TEST_DATA_SOFTENER,
TEST_DATA_SOFTENER_RESET,
TEST_DATA_SOFTENER_TOPIC,
)
from tests.common import async_fire_mqtt_message
from tests.typing import MqttMockHAClient
async def test_binary_sensors_hub(
hass: HomeAssistant, config_entry_hub, mqtt_mock: MqttMockHAClient
) -> None:
"""Test DROP binary sensors for hubs."""
config_entry_hub.add_to_hass(hass)
assert await async_setup_component(hass, DOMAIN, {})
await hass.async_block_till_done()
pending_notifications_sensor_name = (
"binary_sensor.hub_drop_1_c0ffee_notification_unread"
)
hass.states.async_set(pending_notifications_sensor_name, STATE_UNKNOWN)
leak_sensor_name = "binary_sensor.hub_drop_1_c0ffee_leak_detected"
hass.states.async_set(leak_sensor_name, STATE_UNKNOWN)
async_fire_mqtt_message(hass, TEST_DATA_HUB_TOPIC, TEST_DATA_HUB_RESET)
await hass.async_block_till_done()
async_fire_mqtt_message(hass, TEST_DATA_HUB_TOPIC, TEST_DATA_HUB)
await hass.async_block_till_done()
pending_notifications = hass.states.get(pending_notifications_sensor_name)
assert pending_notifications.state == STATE_ON
leak = hass.states.get(leak_sensor_name)
assert leak.state == STATE_OFF
async def test_binary_sensors_salt(
hass: HomeAssistant, config_entry_salt, mqtt_mock: MqttMockHAClient
) -> None:
"""Test DROP binary sensors for salt sensors."""
config_entry_salt.add_to_hass(hass)
assert await async_setup_component(hass, DOMAIN, {})
await hass.async_block_till_done()
salt_sensor_name = "binary_sensor.salt_sensor_salt_low"
hass.states.async_set(salt_sensor_name, STATE_UNKNOWN)
async_fire_mqtt_message(hass, TEST_DATA_SALT_TOPIC, TEST_DATA_SALT_RESET)
await hass.async_block_till_done()
async_fire_mqtt_message(hass, TEST_DATA_SALT_TOPIC, TEST_DATA_SALT)
await hass.async_block_till_done()
salt = hass.states.get(salt_sensor_name)
assert salt.state == STATE_ON
async def test_binary_sensors_leak(
hass: HomeAssistant, config_entry_leak, mqtt_mock: MqttMockHAClient
) -> None:
"""Test DROP binary sensors for leak detectors."""
config_entry_leak.add_to_hass(hass)
assert await async_setup_component(hass, DOMAIN, {})
await hass.async_block_till_done()
leak_sensor_name = "binary_sensor.leak_detector_leak_detected"
hass.states.async_set(leak_sensor_name, STATE_UNKNOWN)
async_fire_mqtt_message(hass, TEST_DATA_LEAK_TOPIC, TEST_DATA_LEAK_RESET)
await hass.async_block_till_done()
async_fire_mqtt_message(hass, TEST_DATA_LEAK_TOPIC, TEST_DATA_LEAK)
await hass.async_block_till_done()
leak = hass.states.get(leak_sensor_name)
assert leak.state == STATE_ON
async def test_binary_sensors_softener(
hass: HomeAssistant, config_entry_softener, mqtt_mock: MqttMockHAClient
) -> None:
"""Test DROP binary sensors for softeners."""
config_entry_softener.add_to_hass(hass)
assert await async_setup_component(hass, DOMAIN, {})
await hass.async_block_till_done()
reserve_in_use_sensor_name = "binary_sensor.softener_reserve_capacity_in_use"
hass.states.async_set(reserve_in_use_sensor_name, STATE_UNKNOWN)
async_fire_mqtt_message(hass, TEST_DATA_SOFTENER_TOPIC, TEST_DATA_SOFTENER_RESET)
await hass.async_block_till_done()
async_fire_mqtt_message(hass, TEST_DATA_SOFTENER_TOPIC, TEST_DATA_SOFTENER)
await hass.async_block_till_done()
reserve_in_use = hass.states.get(reserve_in_use_sensor_name)
assert reserve_in_use.state == STATE_ON
async def test_binary_sensors_protection_valve(
hass: HomeAssistant, config_entry_protection_valve, mqtt_mock: MqttMockHAClient
) -> None:
"""Test DROP binary sensors for protection valves."""
config_entry_protection_valve.add_to_hass(hass)
assert await async_setup_component(hass, DOMAIN, {})
await hass.async_block_till_done()
leak_sensor_name = "binary_sensor.protection_valve_leak_detected"
hass.states.async_set(leak_sensor_name, STATE_UNKNOWN)
async_fire_mqtt_message(
hass, TEST_DATA_PROTECTION_VALVE_TOPIC, TEST_DATA_PROTECTION_VALVE_RESET
)
await hass.async_block_till_done()
async_fire_mqtt_message(
hass, TEST_DATA_PROTECTION_VALVE_TOPIC, TEST_DATA_PROTECTION_VALVE
)
await hass.async_block_till_done()
leak = hass.states.get(leak_sensor_name)
assert leak.state == STATE_ON
async def test_binary_sensors_pump_controller(
hass: HomeAssistant, config_entry_pump_controller, mqtt_mock: MqttMockHAClient
) -> None:
"""Test DROP binary sensors for pump controllers."""
config_entry_pump_controller.add_to_hass(hass)
assert await async_setup_component(hass, DOMAIN, {})
await hass.async_block_till_done()
leak_sensor_name = "binary_sensor.pump_controller_leak_detected"
hass.states.async_set(leak_sensor_name, STATE_UNKNOWN)
pump_sensor_name = "binary_sensor.pump_controller_pump_status"
hass.states.async_set(pump_sensor_name, STATE_UNKNOWN)
async_fire_mqtt_message(
hass, TEST_DATA_PUMP_CONTROLLER_TOPIC, TEST_DATA_PUMP_CONTROLLER_RESET
)
await hass.async_block_till_done()
async_fire_mqtt_message(
hass, TEST_DATA_PUMP_CONTROLLER_TOPIC, TEST_DATA_PUMP_CONTROLLER
)
await hass.async_block_till_done()
leak = hass.states.get(leak_sensor_name)
assert leak.state == STATE_ON
pump = hass.states.get(pump_sensor_name)
assert pump.state == STATE_ON
async def test_binary_sensors_ro_filter(
hass: HomeAssistant, config_entry_ro_filter, mqtt_mock: MqttMockHAClient
) -> None:
"""Test DROP binary sensors for RO filters."""
config_entry_ro_filter.add_to_hass(hass)
assert await async_setup_component(hass, DOMAIN, {})
await hass.async_block_till_done()
leak_sensor_name = "binary_sensor.ro_filter_leak_detected"
hass.states.async_set(leak_sensor_name, STATE_UNKNOWN)
async_fire_mqtt_message(hass, TEST_DATA_RO_FILTER_TOPIC, TEST_DATA_RO_FILTER_RESET)
await hass.async_block_till_done()
async_fire_mqtt_message(hass, TEST_DATA_RO_FILTER_TOPIC, TEST_DATA_RO_FILTER)
await hass.async_block_till_done()
leak = hass.states.get(leak_sensor_name)
assert leak.state == STATE_ON