Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add binary sensor platform to VeSync #134221

Merged
merged 31 commits into from
Jan 29, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
fab323f
Added the basics for binary sensor support.
cdnninja Dec 29, 2024
c4d2533
Binary sensor first draft
cdnninja Dec 29, 2024
de6f8da
Update tests
cdnninja Dec 29, 2024
9c44916
Remove unneeded code to improve test coverage.
cdnninja Dec 29, 2024
02dc884
Adjust what sensors are displayed.
cdnninja Dec 29, 2024
a8cab84
Final corrections to handle dict. Clean up too.
cdnninja Dec 29, 2024
cefac79
Align order to sensor for consistancy
cdnninja Jan 2, 2025
de6f5b7
Merge branch 'dev' into binary-sensor
cdnninja Jan 2, 2025
25c5958
unload binary sensor
cdnninja Jan 2, 2025
7e7c0d5
Update homeassistant/components/vesync/binary_sensor.py
cdnninja Jan 2, 2025
faccf26
Update strings.json
cdnninja Jan 2, 2025
66fb73c
Wrong type
cdnninja Jan 2, 2025
33ab7be
Add call backs.
cdnninja Jan 2, 2025
0b50f48
Merge branch 'home-assistant:dev' into binary-sensor
cdnninja Jan 3, 2025
c010230
Align to coordinator
cdnninja Jan 3, 2025
8eaa370
Update homeassistant/components/vesync/binary_sensor.py
cdnninja Jan 3, 2025
412e1ad
Update common.py
cdnninja Jan 3, 2025
0a9331a
Update common.py
cdnninja Jan 3, 2025
3a01836
Remove warning as this mostly hits humidifers
cdnninja Jan 4, 2025
9410726
Merge branch 'dev' into binary-sensor
cdnninja Jan 5, 2025
a7e50e7
Correct ruff.
cdnninja Jan 5, 2025
330457c
format
cdnninja Jan 6, 2025
0699a22
Merge branch 'dev' into binary-sensor
cdnninja Jan 10, 2025
8d9b4ef
Refactor based on parent branch
cdnninja Jan 10, 2025
b4695c9
Merge branch 'dev' into binary-sensor
cdnninja Jan 15, 2025
27d775e
Correct merge issues and initial test corrections
cdnninja Jan 15, 2025
163d18b
Feedback corrections.
cdnninja Jan 15, 2025
b3211ff
Remove is_on value
cdnninja Jan 17, 2025
8eeec71
Move typing as per other feedback
cdnninja Jan 19, 2025
e201eef
Remove commented out code
cdnninja Jan 28, 2025
40de4f1
Re-add is on lambda
cdnninja Jan 29, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions homeassistant/components/vesync/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from .coordinator import VeSyncDataCoordinator

PLATFORMS = [
Platform.BINARY_SENSOR,
Platform.FAN,
Platform.HUMIDIFIER,
Platform.LIGHT,
Expand Down
106 changes: 106 additions & 0 deletions homeassistant/components/vesync/binary_sensor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
"""Binary Sensor for VeSync."""

from __future__ import annotations

from collections.abc import Callable
from dataclasses import dataclass
import logging

from pyvesync.vesyncbasedevice import VeSyncBaseDevice

from homeassistant.components.binary_sensor import (
BinarySensorDeviceClass,
BinarySensorEntity,
BinarySensorEntityDescription,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddEntitiesCallback

from .common import rgetattr
from .const import DOMAIN, VS_COORDINATOR, VS_DEVICES, VS_DISCOVERY
from .coordinator import VeSyncDataCoordinator
from .entity import VeSyncBaseEntity

_LOGGER = logging.getLogger(__name__)


@dataclass(frozen=True, kw_only=True)
class VeSyncBinarySensorEntityDescription(BinarySensorEntityDescription):
"""A class that describes custom binary sensor entities."""

is_on: Callable[[VeSyncBaseDevice], bool]


SENSOR_DESCRIPTIONS: tuple[VeSyncBinarySensorEntityDescription, ...] = (
VeSyncBinarySensorEntityDescription(
key="water_lacks",
translation_key="water_lacks",
is_on=lambda device: device.water_lacks,
device_class=BinarySensorDeviceClass.PROBLEM,
),
VeSyncBinarySensorEntityDescription(
key="details.water_tank_lifted",
translation_key="water_tank_lifted",
is_on=lambda device: device.details["water_tank_lifted"],
device_class=BinarySensorDeviceClass.PROBLEM,
),
)


async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up binary_sensor platform."""

coordinator = hass.data[DOMAIN][VS_COORDINATOR]

@callback
def discover(devices):
"""Add new devices to platform."""
_setup_entities(devices, async_add_entities)

config_entry.async_on_unload(
async_dispatcher_connect(hass, VS_DISCOVERY.format(VS_DEVICES), discover)
)

_setup_entities(hass.data[DOMAIN][VS_DEVICES], async_add_entities, coordinator)


@callback
def _setup_entities(devices, async_add_entities, coordinator):
"""Add entity."""
async_add_entities(
(
VeSyncBinarySensor(dev, description, coordinator)
for dev in devices
for description in SENSOR_DESCRIPTIONS
if rgetattr(dev, description.key) is not None
cdnninja marked this conversation as resolved.
Show resolved Hide resolved
),
)


class VeSyncBinarySensor(BinarySensorEntity, VeSyncBaseEntity):
"""Vesync binary sensor class."""

entity_description: VeSyncBinarySensorEntityDescription

def __init__(
self,
device: VeSyncBaseDevice,
description: VeSyncBinarySensorEntityDescription,
coordinator: VeSyncDataCoordinator,
) -> None:
"""Initialize the sensor."""
super().__init__(device, coordinator)
self.entity_description = description
self._attr_unique_id = f"{super().unique_id}-{description.key}"

@property
def is_on(self) -> bool:
"""Return true if the binary sensor is on."""
_LOGGER.debug(rgetattr(self.device, self.entity_description.key))
return self.entity_description.is_on(self.device)
22 changes: 22 additions & 0 deletions homeassistant/components/vesync/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,28 @@
_LOGGER = logging.getLogger(__name__)


def rgetattr(obj: object, attr: str):
"""Return a string in the form word.1.2.3 and return the item as 3. Note that this last value could be in a dict as well."""
_this_func = rgetattr
sp = attr.split(".", 1)
if len(sp) == 1:
left, right = sp[0], ""
else:
left, right = sp

if isinstance(obj, dict):
obj = obj.get(left)
elif hasattr(obj, left):
obj = getattr(obj, left)
else:
return None

if right:
obj = _this_func(obj, right)

return obj


async def async_generate_device_list(
hass: HomeAssistant, manager: VeSync
) -> list[VeSyncBaseDevice]:
Expand Down
8 changes: 8 additions & 0 deletions homeassistant/components/vesync/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,14 @@
"name": "Current voltage"
}
},
"binary_sensor": {
"water_lacks": {
"name": "Low water"
},
"water_tank_lifted": {
"name": "Water tank lifted"
}
},
"number": {
"mist_level": {
"name": "Mist level"
Expand Down
3 changes: 3 additions & 0 deletions tests/components/vesync/test_diagnostics.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@ async def test_async_get_device_diagnostics__single_fan(
"home_assistant.entities.2.state.last_changed": (str,),
"home_assistant.entities.2.state.last_reported": (str,),
"home_assistant.entities.2.state.last_updated": (str,),
"home_assistant.entities.3.state.last_changed": (str,),
"home_assistant.entities.3.state.last_reported": (str,),
"home_assistant.entities.3.state.last_updated": (str,),
}
)
)
2 changes: 2 additions & 0 deletions tests/components/vesync/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ async def test_async_setup_entry__no_devices(
assert setups_mock.call_count == 1
assert setups_mock.call_args.args[0] == config_entry
assert setups_mock.call_args.args[1] == [
Platform.BINARY_SENSOR,
Platform.FAN,
Platform.HUMIDIFIER,
Platform.LIGHT,
Expand Down Expand Up @@ -78,6 +79,7 @@ async def test_async_setup_entry__loads_fans(
assert setups_mock.call_count == 1
assert setups_mock.call_args.args[0] == config_entry
assert setups_mock.call_args.args[1] == [
Platform.BINARY_SENSOR,
Platform.FAN,
Platform.HUMIDIFIER,
Platform.LIGHT,
Expand Down