12
12
"""
13
13
from __future__ import annotations
14
14
15
+ import asyncio
15
16
from enum import Enum
17
+ from itertools import chain
16
18
import logging
17
19
from typing import TYPE_CHECKING , Any , Awaitable , Callable , Iterator , Tuple , cast
18
20
@@ -114,6 +116,7 @@ def __eq__(self, other: object) -> bool:
114
116
class Light (Device ):
115
117
"""Class for managing a light."""
116
118
119
+ DEBOUNCE_TIMEOUT = 0.2
117
120
DEFAULT_MIN_KELVIN = 2700 # 370 mireds
118
121
DEFAULT_MAX_KELVIN = 6000 # 166 mireds
119
122
@@ -266,7 +269,7 @@ def __init__(
266
269
group_address_brightness_red ,
267
270
group_address_brightness_red_state ,
268
271
sync_state = sync_state ,
269
- after_update_cb = self .after_update ,
272
+ after_update_cb = self ._individual_color_callback_debounce ,
270
273
)
271
274
272
275
self .green = _SwitchAndBrightness (
@@ -278,7 +281,7 @@ def __init__(
278
281
group_address_brightness_green ,
279
282
group_address_brightness_green_state ,
280
283
sync_state = sync_state ,
281
- after_update_cb = self .after_update ,
284
+ after_update_cb = self ._individual_color_callback_debounce ,
282
285
)
283
286
284
287
self .blue = _SwitchAndBrightness (
@@ -290,7 +293,7 @@ def __init__(
290
293
group_address_brightness_blue ,
291
294
group_address_brightness_blue_state ,
292
295
sync_state = sync_state ,
293
- after_update_cb = self .after_update ,
296
+ after_update_cb = self ._individual_color_callback_debounce ,
294
297
)
295
298
296
299
self .white = _SwitchAndBrightness (
@@ -302,14 +305,26 @@ def __init__(
302
305
group_address_brightness_white ,
303
306
group_address_brightness_white_state ,
304
307
sync_state = sync_state ,
305
- after_update_cb = self .after_update ,
308
+ after_update_cb = self ._individual_color_callback_debounce ,
306
309
)
307
310
308
311
self .min_kelvin = min_kelvin
309
312
self .max_kelvin = max_kelvin
313
+ self ._individual_color_debounce_task_name = (
314
+ f"{ id (self )} _individual_color_debounce"
315
+ )
316
+ self ._individual_color_debounce_telegram_counter : int
317
+ self ._reset_individual_color_debounce_telegrams ()
310
318
311
319
def _iter_remote_values (self ) -> Iterator [RemoteValue [Any , Any ]]:
312
320
"""Iterate the devices RemoteValue classes."""
321
+ return chain (
322
+ self ._iter_instant_remote_values (),
323
+ self ._iter_debounce_remote_values (),
324
+ )
325
+
326
+ def _iter_instant_remote_values (self ) -> Iterator [RemoteValue [Any , Any ]]:
327
+ """Iterate the devices RemoteValue classes calling after_update_cb immediately."""
313
328
yield self .switch
314
329
yield self .brightness
315
330
yield self .color
@@ -319,6 +334,9 @@ def _iter_remote_values(self) -> Iterator[RemoteValue[Any, Any]]:
319
334
yield self .xyy_color
320
335
yield self .tunable_white
321
336
yield self .color_temperature
337
+
338
+ def _iter_debounce_remote_values (self ) -> Iterator [RemoteValue [Any , Any ]]:
339
+ """Iterate the devices RemoteValue classes debouncing after_update_cb."""
322
340
for color in self ._iter_individual_colors ():
323
341
yield color .switch
324
342
yield color .brightness
@@ -327,6 +345,36 @@ def _iter_individual_colors(self) -> Iterator[_SwitchAndBrightness]:
327
345
"""Iterate the devices individual colors."""
328
346
yield from (self .red , self .green , self .blue , self .white )
329
347
348
+ def _reset_individual_color_debounce_telegrams (self ) -> None :
349
+ """Reset individual color debounce telegram counter."""
350
+ self ._individual_color_debounce_telegram_counter = sum (
351
+ (
352
+ self .red .switch .initialized or self .red .brightness .initialized ,
353
+ self .green .switch .initialized or self .green .brightness .initialized ,
354
+ self .blue .switch .initialized or self .blue .brightness .initialized ,
355
+ self .white .switch .initialized or self .white .brightness .initialized ,
356
+ )
357
+ )
358
+
359
+ async def _individual_color_callback_debounce (self ) -> None :
360
+ """Run callback after all individual colors were updated or timeout passed."""
361
+
362
+ async def debouncer () -> None :
363
+ await asyncio .sleep (Light .DEBOUNCE_TIMEOUT )
364
+ self ._reset_individual_color_debounce_telegrams ()
365
+ await asyncio .shield (self .after_update ())
366
+
367
+ self ._individual_color_debounce_telegram_counter -= 1
368
+ if self ._individual_color_debounce_telegram_counter > 0 :
369
+ # task registry cancels existing task
370
+ self .xknx .task_registry .register (
371
+ self ._individual_color_debounce_task_name , debouncer ()
372
+ ).start ()
373
+ return
374
+ self .xknx .task_registry .unregister (self ._individual_color_debounce_task_name )
375
+ self ._reset_individual_color_debounce_telegrams ()
376
+ await self .after_update ()
377
+
330
378
@property
331
379
def supports_brightness (self ) -> bool :
332
380
"""Return if light supports brightness."""
@@ -549,8 +597,10 @@ async def set_color_temperature(self, color_temperature: int) -> None:
549
597
550
598
async def process_group_write (self , telegram : "Telegram" ) -> None :
551
599
"""Process incoming and outgoing GROUP WRITE telegram."""
552
- for remote_value in self ._iter_remote_values ():
600
+ for remote_value in self ._iter_instant_remote_values ():
553
601
await remote_value .process (telegram )
602
+ for remote_value in self ._iter_debounce_remote_values ():
603
+ await remote_value .process (telegram , always_callback = True )
554
604
555
605
def __str__ (self ) -> str :
556
606
"""Return object as readable string."""
0 commit comments