Add internet binary sensor to Smlight integration (#125982)

* Add internet sensor updated by events

* Strings for internet sensor

* Update binary_sensor snapshot with internet sensor

* Add test for internet sensor

* Address review comments

---------

Co-authored-by: Tim Lunn <tim@feathertop.org>
This commit is contained in:
TimL 2024-09-16 21:56:44 +10:00 committed by GitHub
parent ac17020cd0
commit 5660d1e48e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 167 additions and 4 deletions

View file

@ -6,6 +6,8 @@ from _collections_abc import Callable
from dataclasses import dataclass
from pysmlight import Sensors
from pysmlight.const import Events as SmEvents
from pysmlight.sse import MessageEvent
from homeassistant.components.binary_sensor import (
BinarySensorDeviceClass,
@ -14,12 +16,15 @@ from homeassistant.components.binary_sensor import (
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import SCAN_INTERNET_INTERVAL
from .coordinator import SmDataUpdateCoordinator
from .entity import SmEntity
SCAN_INTERVAL = SCAN_INTERNET_INTERVAL
@dataclass(frozen=True, kw_only=True)
class SmBinarySensorEntityDescription(BinarySensorEntityDescription):
@ -52,7 +57,13 @@ async def async_setup_entry(
coordinator = entry.runtime_data
async_add_entities(
SmBinarySensorEntity(coordinator, description) for description in SENSORS
[
*(
SmBinarySensorEntity(coordinator, description)
for description in SENSORS
),
SmInternetSensorEntity(coordinator),
]
)
@ -78,3 +89,47 @@ class SmBinarySensorEntity(SmEntity, BinarySensorEntity):
def is_on(self) -> bool:
"""Return the state of the sensor."""
return self.entity_description.value_fn(self.coordinator.data.sensors)
class SmInternetSensorEntity(SmEntity, BinarySensorEntity):
"""Representation of the SLZB internet sensor."""
_attr_translation_key = "internet"
_attr_device_class = BinarySensorDeviceClass.CONNECTIVITY
_attr_entity_category = EntityCategory.DIAGNOSTIC
def __init__(
self,
coordinator: SmDataUpdateCoordinator,
) -> None:
"""Initialize slzb binary sensor."""
super().__init__(coordinator)
self._attr_unique_id = f"{coordinator.unique_id}_{self._attr_translation_key}"
async def async_added_to_hass(self) -> None:
"""Run when entity about to be added to hass."""
await super().async_added_to_hass()
self.async_on_remove(
self.coordinator.client.sse.register_callback(
SmEvents.EVENT_INET_STATE, self.internet_callback
)
)
await self.async_update()
@callback
def internet_callback(self, event: MessageEvent) -> None:
"""Update internet state from event."""
self._attr_is_on = event.data == "ok"
self.async_write_ha_state()
@property
def should_poll(self) -> bool:
"""Poll entity for internet connected updates."""
return True
async def async_update(self) -> None:
"""Update the sensor.
This is an async api, device will respond with EVENT_INET_STATE event.
"""
await self.coordinator.client.get_param("inetState")

View file

@ -9,4 +9,5 @@ ATTR_MANUFACTURER = "SMLIGHT"
LOGGER = logging.getLogger(__package__)
SCAN_INTERVAL = timedelta(seconds=300)
SCAN_INTERNET_INTERVAL = timedelta(minutes=15)
UPTIME_DEVIATION = timedelta(seconds=5)

View file

@ -46,6 +46,9 @@
"ethernet": {
"name": "Ethernet"
},
"internet": {
"name": "Internet"
},
"wifi": {
"name": "Wi-Fi"
}

View file

@ -46,6 +46,53 @@
'state': 'on',
})
# ---
# name: test_all_binary_sensors[binary_sensor.mock_title_internet-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'binary_sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'binary_sensor.mock_title_internet',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <BinarySensorDeviceClass.CONNECTIVITY: 'connectivity'>,
'original_icon': None,
'original_name': 'Internet',
'platform': 'smlight',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'internet',
'unique_id': 'aa:bb:cc:dd:ee:ff_internet',
'unit_of_measurement': None,
})
# ---
# name: test_all_binary_sensors[binary_sensor.mock_title_internet-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'connectivity',
'friendly_name': 'Mock Title Internet',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.mock_title_internet',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_all_binary_sensors[binary_sensor.mock_title_wi_fi-entry]
EntityRegistryEntrySnapshot({
'aliases': set({

View file

@ -1,15 +1,22 @@
"""Tests for the SMLIGHT binary sensor platform."""
from collections.abc import Callable
from unittest.mock import MagicMock
from freezegun.api import FrozenDateTimeFactory
from pysmlight.const import Events
from pysmlight.sse import MessageEvent
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.const import Platform
from homeassistant.components.smlight.const import SCAN_INTERNET_INTERVAL
from homeassistant.const import STATE_ON, STATE_UNKNOWN, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from .conftest import setup_integration
from tests.common import MockConfigEntry, snapshot_platform
from tests.common import MockConfigEntry, async_fire_time_changed, snapshot_platform
pytestmark = [
pytest.mark.usefixtures(
@ -17,6 +24,14 @@ pytestmark = [
)
]
MOCK_INET_STATE = MessageEvent(
type="EVENT_INET_STATE",
message="EVENT_INET_STATE",
data="ok",
origin="http://slzb-06.local",
last_event_id="",
)
@pytest.fixture
def platforms() -> list[Platform]:
@ -36,6 +51,8 @@ async def test_all_binary_sensors(
await snapshot_platform(hass, entity_registry, snapshot, entry.entry_id)
await hass.config_entries.async_unload(entry.entry_id)
async def test_disabled_by_default_sensors(
hass: HomeAssistant,
@ -50,3 +67,43 @@ async def test_disabled_by_default_sensors(
assert (entry := entity_registry.async_get("binary_sensor.mock_title_wi_fi"))
assert entry.disabled
assert entry.disabled_by is er.RegistryEntryDisabler.INTEGRATION
async def test_internet_sensor_event(
hass: HomeAssistant,
freezer: FrozenDateTimeFactory,
mock_config_entry: MockConfigEntry,
mock_smlight_client: MagicMock,
) -> None:
"""Test internet sensor event."""
await setup_integration(hass, mock_config_entry)
state = hass.states.get("binary_sensor.mock_title_internet")
assert state is not None
assert state.state == STATE_UNKNOWN
assert len(mock_smlight_client.get_param.mock_calls) == 1
mock_smlight_client.get_param.assert_called_with("inetState")
freezer.tick(SCAN_INTERNET_INTERVAL)
async_fire_time_changed(hass)
await hass.async_block_till_done()
assert len(mock_smlight_client.get_param.mock_calls) == 2
mock_smlight_client.get_param.assert_called_with("inetState")
event_function: Callable[[MessageEvent], None] = next(
(
call_args[0][1]
for call_args in mock_smlight_client.sse.register_callback.call_args_list
if call_args[0][0] == Events.EVENT_INET_STATE
),
None,
)
event_function(MOCK_INET_STATE)
await hass.async_block_till_done()
state = hass.states.get("binary_sensor.mock_title_internet")
assert state is not None
assert state.state == STATE_ON