Skip to content

Commit b572eeb

Browse files
committed
Couple NumberEntities and LightEntities
1 parent fe5f22c commit b572eeb

File tree

3 files changed

+98
-13
lines changed

3 files changed

+98
-13
lines changed

custom_components/dmx/entity/light.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from homeassistant.components.light import LightEntity, ColorMode
99
from homeassistant.core import callback
1010
from homeassistant.helpers.device_registry import DeviceInfo
11+
from homeassistant.helpers.restore_state import RestoreEntity
1112

1213
from custom_components.dmx import DOMAIN
1314
from custom_components.dmx.fixture.channel import Channel
@@ -32,7 +33,7 @@ class AccumulatedLightChannel:
3233
light_channel: LightChannel
3334

3435

35-
class ClaudeLightEntity(LightEntity):
36+
class ClaudeLightEntity(LightEntity, RestoreEntity):
3637
"""DMX/ArtNet light entity that supports various channel configurations."""
3738

3839
def __init__(
@@ -95,6 +96,7 @@ def __init__(
9596
self._setup_turn_on_handlers()
9697

9798
# Register channel listeners
99+
self._is_updating = False
98100
self._register_channel_listeners()
99101

100102
def _setup_turn_on_handlers(self):
@@ -159,7 +161,7 @@ def _register_channel_listeners(self):
159161
for dmx_index in channel_data.dmx_channel_indexes:
160162
channel_data.universe.register_channel_listener(
161163
dmx_index,
162-
partial(self._handle_channel_update, channel_type, dmx_index)
164+
partial(self._handle_channel_update, channel_type)
163165
)
164166

165167
@callback
@@ -331,9 +333,13 @@ async def async_turn_off(self, **kwargs) -> None:
331333
self._state = False
332334

333335
# Turn off all channels
334-
for ch_data in self._channels:
335-
for dmx_index in ch_data.dmx_channel_indexes:
336-
await ch_data.universe.update_value(dmx_index, 0)
336+
self._is_updating = True
337+
try:
338+
for ch_data in self._channels:
339+
for dmx_index in ch_data.dmx_channel_indexes:
340+
await ch_data.universe.update_value(dmx_index, 0)
341+
finally:
342+
self._is_updating = False
337343

338344
# Update state
339345
self.async_write_ha_state()

custom_components/dmx/entity/number.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,17 +57,26 @@ def __init__(self, name: str, capability: Capability,
5757
else:
5858
self._attr_native_value = 0
5959

60+
self._is_updating = False
6061
self.universe.register_channel_listener(dmx_indexes, self.update_value)
6162

62-
def update_value(self, value: int) -> None:
63+
def update_value(self, dmx_index: int, value: int) -> None:
6364
# TODO maybe update self._attr_attribution from source ArtNet node?
65+
if getattr(self, '_is_updating', False):
66+
return
67+
6468
self._attr_native_value = self.dynamic_entity.from_dmx(value)
6569
self.async_schedule_update_ha_state()
6670

6771
async def async_set_native_value(self, value: float) -> None:
6872
self._attr_native_value = value
6973
dmx_value = self.dynamic_entity.to_dmx(value)
70-
await self.universe.update_value(self.dmx_indexes, dmx_value)
74+
75+
self._is_updating = True
76+
try:
77+
await self.universe.update_value(self.dmx_indexes, dmx_value)
78+
finally:
79+
self._is_updating = False
7180

7281
@property
7382
def available(self) -> bool:

custom_components/dmx/io/dmx_io.py

Lines changed: 76 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,85 @@
1+
import asyncio
12
from typing import List, Callable
23

34

45
class Universe:
56
def __init__(self):
6-
pass
7+
# Dictionary to store channel value: {channel_number: current_value}
8+
self._channel_values = {}
9+
10+
# Dictionary to store callbacks: {channel_number: [callback1, callback2, ...]}
11+
self._channel_callbacks = {}
712

813
def register_channel_listener(self, channels: int | List[int],
9-
callback: Callable[[int], None]
10-
) -> None:
11-
pass
14+
callback: Callable[[int, int], None]) -> None:
15+
"""
16+
Register a callback to be called when a channel value changes.
17+
18+
Args:
19+
channels: Single channel number or list of channel numbers
20+
callback: Function to call with channel number and new value
21+
"""
22+
if isinstance(channels, int):
23+
channels = [channels]
24+
25+
for channel in channels:
26+
if channel not in self._channel_callbacks:
27+
self._channel_callbacks[channel] = []
28+
29+
if callback not in self._channel_callbacks[channel]:
30+
self._channel_callbacks[channel].append(callback)
31+
32+
def unregister_channel_listener(self, channels: int | List[int],
33+
callback: Callable[[int, int], None]) -> None:
34+
"""Remove a callback from the registry."""
35+
if isinstance(channels, int):
36+
channels = [channels]
37+
38+
for channel in channels:
39+
if channel in self._channel_callbacks and callback in self._channel_callbacks[channel]:
40+
self._channel_callbacks[channel].remove(callback)
1241

1342
async def update_value(self, channel: int | List[int], value: int) -> None:
14-
print(f"Updating {channel} to {value}")
15-
pass
43+
"""
44+
Update the value of one or more channels and notify all listeners.
45+
46+
Args:
47+
channel: Single channel number or list of channel numbers
48+
value: New value for the channel(s)
49+
"""
50+
# Convert to list if single channel
51+
if isinstance(channel, int):
52+
channels = [channel]
53+
else:
54+
channels = channel
55+
56+
# Update all specified channels
57+
for ch in channels:
58+
# Only process if value has changed or channel is new
59+
if ch not in self._channel_values or self._channel_values[ch] != value:
60+
# Update stored value
61+
self._channel_values[ch] = value
62+
63+
# Notify all listeners for this channel
64+
if ch in self._channel_callbacks:
65+
for callback in self._channel_callbacks[ch]:
66+
try:
67+
# Pass both channel and value to callback
68+
await self._call_callback(callback, ch, value)
69+
except Exception as e:
70+
print(f"Error calling callback for channel {ch}: {e}")
71+
72+
# Perform actual DMX output
73+
print(f"Updating DMX channel {ch} to value {value}")
74+
# Actual DMX output code would go here
75+
76+
async def _call_callback(self, callback, channel, value):
77+
"""Helper method to call callbacks that might be async or regular functions."""
78+
if asyncio.iscoroutinefunction(callback):
79+
await callback(channel, value)
80+
else:
81+
callback(channel, value)
82+
83+
def get_channel_value(self, channel: int) -> int:
84+
"""Get the current value of a channel."""
85+
return self._channel_values.get(channel, 0)

0 commit comments

Comments
 (0)