Skip to content

Commit

Permalink
Merge pull request #41 from hostcc/feature/low-battery-attrs
Browse files Browse the repository at this point in the history
feat: Extra attributes for binary sensors
  • Loading branch information
hostcc authored Sep 1, 2024
2 parents 67ecf19 + 4503356 commit b68e400
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 3 deletions.
44 changes: 43 additions & 1 deletion custom_components/gs_alarm/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
Binary sensors for `gs_alarm` integration.
"""
from __future__ import annotations
from typing import List, TYPE_CHECKING
from typing import List, TYPE_CHECKING, Mapping, Any
import logging

from homeassistant.config_entries import ConfigEntry
Expand Down Expand Up @@ -92,10 +92,20 @@ def __init__(
self._g90_sensor = g90_sensor
self._attr_unique_id = f"{hass_data.guid}_sensor_{g90_sensor.index}"
self._attr_name = g90_sensor.name
# Extra attributes over sensor characteristics, useful for
# troubleshooting to identify sensor type and its number as panel
# reports it
self._attr_extra_state_attributes = {
'panel_sensor_number': g90_sensor.index,
'protocol': g90_sensor.protocol.name,
'flags': g90_sensor.user_flag.name,
'wireless': g90_sensor.is_wireless,
}
hass_sensor_type = HASS_SENSOR_TYPES_MAPPING.get(g90_sensor.type, None)
if hass_sensor_type:
self._attr_device_class = hass_sensor_type
g90_sensor.state_callback = self.state_callback
g90_sensor.low_battery_callback = self.low_battery_callback
self._attr_device_info = hass_data.device
self._hass_data = hass_data

Expand All @@ -116,8 +126,40 @@ def state_callback(self, value: bool) -> None:
Invoked by `pyg90alarm` when its sensor changes the state.
"""
_LOGGER.debug('%s: Received state callback: %s', self.unique_id, value)
# Signal HASS to update the sensor's state, which will trigger the
# `is_on()` method
self.schedule_update_ha_state()

def low_battery_callback(self) -> None:
"""
Invoked by `pyg90alarm` when its sensor reports low battery condition.
"""
_LOGGER.debug(
'%s: Received low battery callback', self.unique_id
)
# Signal HASS to update the sensor's attributes, which will trigger the
# `extra_state_attributes()` method
self.schedule_update_ha_state()

@property
def extra_state_attributes(self) -> Mapping[str, Any] | None:
"""
Provides extra state attributes.
"""
extra_attrs = self._attr_extra_state_attributes
# Low battery is only applicable to wireless sensors
if self._g90_sensor.is_wireless:
extra_attrs['low_battery'] = (
self._g90_sensor.is_low_battery
)

_LOGGER.debug(
'%s: Providing extra attributes %s', self.unique_id,
repr(extra_attrs)
)

return extra_attrs

@property
def is_on(self) -> bool:
"""
Expand Down
2 changes: 1 addition & 1 deletion custom_components/gs_alarm/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
],
"quality_scale": "gold",
"requirements": [
"pyg90alarm==1.14.0"
"pyg90alarm==1.15.1"
],
"version": "1.18.1"
}
2 changes: 1 addition & 1 deletion requirements_dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ pytest==8.2.0
pytest-cov==5.0.0
pytest-unordered==0.6.0
mypy[reports]==1.11.1
pyg90alarm==1.14.0
pyg90alarm==1.15.1
36 changes: 36 additions & 0 deletions tests/test_callbacks.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,3 +119,39 @@ async def test_disarm_callback(
panel_state = hass.states.get('alarm_control_panel.dummy_guid')
assert panel_state is not None
assert panel_state.state == STATE_ALARM_DISARMED


async def test_low_battery_callback(
hass: HomeAssistant, mock_g90alarm: AlarmMockT
) -> None:
"""
Tests the binary sensor changes its attributes upon low battery condition
is reported.
"""
config_entry = MockConfigEntry(
domain=DOMAIN,
data={'ip_addr': 'dummy-ip'},
options={},
entry_id="test-disarm-callbacks"
)

config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(config_entry.entry_id)
# Allow Home Assistant to process the setup
await hass.async_block_till_done()

sensor = mock_g90alarm.return_value.get_sensors.return_value[0]
# Ensure the sensor is wireless
assert sensor.is_wireless is True
# Simulate the sensor is low on battery
sensor._set_low_battery(True) # pylint: disable=protected-access
# Simulate the low battery callback is triggered
sensor.low_battery_callback()
# Wait for the callback to be processed
await hass.async_block_till_done()

# Verify sensor state reflects the low battery status
sensor_state = hass.states.get('binary_sensor.dummy_sensor_1')
assert sensor_state is not None
assert sensor_state.attributes != {}
assert sensor_state.attributes.get('low_battery') is True
8 changes: 8 additions & 0 deletions tests/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,14 @@ async def test_setup_unload_and_reload_entry_afresh(
'binary_sensor.gs_alarm_gsm_status'
])

# Verify binary sensor has expected extra attributes
dummy_sensor_1 = hass.states.get('binary_sensor.dummy_sensor_1')
assert dummy_sensor_1 is not None
assert 'wireless' in dummy_sensor_1.attributes
assert 'panel_sensor_number' in dummy_sensor_1.attributes
assert 'protocol' in dummy_sensor_1.attributes
assert 'flags' in dummy_sensor_1.attributes

# Verify options haven't been propagated to `G90Alarm` instance
mock_g90alarm.return_value.sms_alert_when_armed.assert_not_called()
(
Expand Down

0 comments on commit b68e400

Please sign in to comment.