Skip to content

Commit 0091f06

Browse files
authored
always_callback for NumericValue and RawValue (#789)
1 parent fe52041 commit 0091f06

File tree

8 files changed

+105
-10
lines changed

8 files changed

+105
-10
lines changed

changelog.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
## Unreleased changes
44

5-
### Internals
5+
### Devices
6+
7+
- Added `always_callback` option to NumericValue and RawValue
68

79
## 0.18.11 Task Registry 2021-10-16
810

docs/numeric_value.md

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ NumericValue devices send values to the KNX bus. Received values update the devi
2020
- `respond_to_read` if `True` GroupValueRead requests to the `group_address` are answered. Defaults to `False`
2121
- `sync_state` defines if and how often the value should be actively read from the bus. If `False` no GroupValueRead telegrams will be sent to its group address. Defaults to `True`
2222
- `value_type` controls how the value should be encoded / decoded. The attribut may have may have parseable value types representing numeric values.
23+
- `always_callback` defines if a callback shall be triggered for consecutive GroupValueWrite telegrams with same payload. Defaults to `False`
2324
- `device_updated_cb` awaitable callback for each update.
2425

2526
## [](#header-2)Example

docs/raw_value.md

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ RawValue devices send uint values to the KNX bus. Received values update the dev
2020
- `group_address_state` is the KNX group address of the raw value device.
2121
- `respond_to_read` if `True` GroupValueRead requests to the `group_address` are answered. Defaults to `False`
2222
- `sync_state` defines if and how often the value should be actively read from the bus. If `False` no GroupValueRead telegrams will be sent to its group address. Defaults to `True`
23+
- `always_callback` defines if a callback shall be triggered for consecutive GroupValueWrite telegrams with same payload. Defaults to `False`
2324
- `device_updated_cb` awaitable callback for each update.
2425

2526
## [](#header-2)Example

docs/sensor.md

+7-7
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,13 @@ Sensors are monitoring temperature, air humidity, pressure etc. from KNX bus.
1313

1414
## [](#header-2)Interface
1515

16-
* `xknx` is the XKNX object.
17-
* `name` is the name of the object.
18-
* `group_address_state` is the KNX group address of the sensor device.
19-
* `sync_state` defines if the value should be actively read from the bus. If `False` no GroupValueRead telegrams will be sent to its group address. Defaults to `True`
20-
* `always_callback` defines if a callback/update should always be triggered no matter if the previous and the new state are identical.
21-
* `value_type` controls how the value should be rendered in a human readable representation. The attribut may have may have the values `percent`, `temperature`, `illuminance`, `speed_ms` or `current`.
22-
* `device_updated_cb` awaitable callback for each update.
16+
- `xknx` is the XKNX object.
17+
- `name` is the name of the object.
18+
- `group_address_state` is the KNX group address of the sensor device.
19+
- `sync_state` defines if the value should be actively read from the bus. If `False` no GroupValueRead telegrams will be sent to its group address. Defaults to `True`
20+
- `always_callback` defines if a callback shall be triggered for consecutive GroupValueWrite telegrams with same payload. Defaults to `False`
21+
- `value_type` controls how the value should be rendered in a human readable representation. The attribut may have may have the values `percent`, `temperature`, `illuminance`, `speed_ms` or `current`.
22+
- `device_updated_cb` awaitable callback for each update.
2323

2424
## [](#header-2)Example
2525

test/devices_tests/numeric_value_test.py

+30
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,36 @@ async def test_process_callback(self):
256256
await sensor.process(telegram)
257257
after_update_callback.assert_called_with(sensor)
258258
assert sensor.last_telegram == telegram
259+
# consecutive telegrams with same payload shall only trigger one callback
260+
after_update_callback.reset_mock()
261+
await sensor.process(telegram)
262+
after_update_callback.assert_not_called()
263+
264+
async def test_process_callback_always(self):
265+
"""Test process / reading telegrams from telegram queue. Test if callback is called."""
266+
267+
xknx = XKNX()
268+
sensor = NumericValue(
269+
xknx,
270+
"TestSensor",
271+
group_address="1/2/3",
272+
value_type="temperature",
273+
always_callback=True,
274+
)
275+
after_update_callback = AsyncMock()
276+
sensor.register_device_updated_cb(after_update_callback)
277+
278+
telegram = Telegram(
279+
destination_address=GroupAddress("1/2/3"),
280+
payload=GroupValueWrite(DPTArray((0x01, 0x02))),
281+
)
282+
await sensor.process(telegram)
283+
after_update_callback.assert_called_with(sensor)
284+
assert sensor.last_telegram == telegram
285+
# every telegram shall trigger callback
286+
after_update_callback.reset_mock()
287+
await sensor.process(telegram)
288+
after_update_callback.assert_called_with(sensor)
259289

260290
async def test_process_callback_set(self):
261291
"""Test setting value. Test if callback is called."""

test/devices_tests/raw_value_test.py

+57
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
"""Unit test for RawValue objects."""
2+
from unittest.mock import AsyncMock
3+
24
import pytest
35
from xknx import XKNX
46
from xknx.devices import RawValue
@@ -137,6 +139,61 @@ async def test_respond_to_read(self):
137139
)
138140
assert xknx.telegrams.qsize() == 0
139141

142+
#
143+
# TEST PROCESS CALLBACK
144+
#
145+
146+
async def test_process_callback(self):
147+
"""Test process / reading telegrams from telegram queue. Test if callback is called."""
148+
149+
xknx = XKNX()
150+
sensor = RawValue(
151+
xknx,
152+
"TestSensor",
153+
2,
154+
group_address="1/2/3",
155+
)
156+
after_update_callback = AsyncMock()
157+
sensor.register_device_updated_cb(after_update_callback)
158+
159+
telegram = Telegram(
160+
destination_address=GroupAddress("1/2/3"),
161+
payload=GroupValueWrite(DPTArray((0x01, 0x02))),
162+
)
163+
await sensor.process(telegram)
164+
after_update_callback.assert_called_with(sensor)
165+
assert sensor.last_telegram == telegram
166+
# consecutive telegrams with same payload shall only trigger one callback
167+
after_update_callback.reset_mock()
168+
await sensor.process(telegram)
169+
after_update_callback.assert_not_called()
170+
171+
async def test_process_callback_always(self):
172+
"""Test process / reading telegrams from telegram queue. Test if callback is called."""
173+
174+
xknx = XKNX()
175+
sensor = RawValue(
176+
xknx,
177+
"TestSensor",
178+
2,
179+
group_address="1/2/3",
180+
always_callback=True,
181+
)
182+
after_update_callback = AsyncMock()
183+
sensor.register_device_updated_cb(after_update_callback)
184+
185+
telegram = Telegram(
186+
destination_address=GroupAddress("1/2/3"),
187+
payload=GroupValueWrite(DPTArray((0x01, 0x02))),
188+
)
189+
await sensor.process(telegram)
190+
after_update_callback.assert_called_with(sensor)
191+
assert sensor.last_telegram == telegram
192+
# every telegram shall trigger callback
193+
after_update_callback.reset_mock()
194+
await sensor.process(telegram)
195+
after_update_callback.assert_called_with(sensor)
196+
140197
#
141198
# TEST SET
142199
#

xknx/devices/numeric_value.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,12 @@ def __init__(
3232
respond_to_read: bool = False,
3333
sync_state: bool | int | float | str = True,
3434
value_type: int | str | None = None,
35+
always_callback: bool = False,
3536
device_updated_cb: DeviceCallbackType | None = None,
3637
):
3738
"""Initialize Sensor class."""
3839
super().__init__(xknx, name, device_updated_cb)
40+
self.always_callback = always_callback
3941
self.respond_to_read = respond_to_read
4042
self.sensor_value = RemoteValueNumeric(
4143
xknx,
@@ -58,7 +60,7 @@ def last_telegram(self) -> Telegram | None:
5860

5961
async def process_group_write(self, telegram: "Telegram") -> None:
6062
"""Process incoming and outgoing GROUP WRITE telegram."""
61-
await self.sensor_value.process(telegram)
63+
await self.sensor_value.process(telegram, always_callback=self.always_callback)
6264

6365
async def process_group_read(self, telegram: "Telegram") -> None:
6466
"""Process incoming GroupValueResponse telegrams."""

xknx/devices/raw_value.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,12 @@ def __init__(
3232
group_address_state: GroupAddressesType | None = None,
3333
respond_to_read: bool = False,
3434
sync_state: bool | int | float | str = True,
35+
always_callback: bool = False,
3536
device_updated_cb: DeviceCallbackType | None = None,
3637
):
3738
"""Initialize Sensor class."""
3839
super().__init__(xknx, name, device_updated_cb)
40+
self.always_callback = always_callback
3941
self.respond_to_read = respond_to_read
4042
self.remote_value = RemoteValueRaw(
4143
xknx,
@@ -58,7 +60,7 @@ def last_telegram(self) -> Telegram | None:
5860

5961
async def process_group_write(self, telegram: "Telegram") -> None:
6062
"""Process incoming and outgoing GROUP WRITE telegram."""
61-
await self.remote_value.process(telegram)
63+
await self.remote_value.process(telegram, always_callback=self.always_callback)
6264

6365
async def process_group_read(self, telegram: "Telegram") -> None:
6466
"""Process incoming GroupValueResponse telegrams."""

0 commit comments

Comments
 (0)