Skip to content

Commit 5f0d0dc

Browse files
author
spacemanspiff2007
committed
1.0.1:
- Fixed an issue where consecutive fades would not start from the correct value
1 parent 27c6f2d commit 5f0d0dc

File tree

11 files changed

+149
-106
lines changed

11 files changed

+149
-106
lines changed

readme.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ Docs and examples can be found [here](https://pyartnet.readthedocs.io/en/latest/
1515

1616
# Changelog
1717

18+
#### 1.0.1 (2023-02-20)
19+
- Fixed an issue where consecutive fades would not start from the correct value
20+
- renamed `channel.add_fade` to `channel.set_fade` (`channel.add_fade` will issue a `DeprecationWarning`)
21+
1822
#### 1.0.0 (2023-02-08)
1923
- Complete rework of library (breaking change)
2024
- Add support for sACN and KiNet

src/pyartnet/__version__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = '1.0.0'
1+
__version__ = '1.0.1'

src/pyartnet/base/channel.py

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import logging
2+
import warnings
23
from array import array
34
from logging import DEBUG as LVL_DEBUG
45
from math import ceil
5-
from typing import Any, Callable, Final, Iterable, List, Literal, Optional, Type, Union
6+
from typing import Any, Callable, Collection, Final, List, Literal, Optional, Type, Union
67

78
from pyartnet.errors import ChannelOutOfUniverseError, ChannelValueOutOfBoundsError, \
89
ChannelWidthError, ValueCountDoesNotMatchChannelWidthError
@@ -96,17 +97,20 @@ def get_values(self) -> List[int]:
9697
"""
9798
return self._values_raw.tolist()
9899

99-
def set_values(self, values: Iterable[Union[int, float]]):
100+
def set_values(self, values: Collection[Union[int, float]]):
100101
"""Set values for a channel without a fade
101102
102103
:param values: Iterable of values with the same size as the channel width
103104
"""
104105
# get output correction function
106+
if len(values) != self._width:
107+
raise ValueCountDoesNotMatchChannelWidthError(
108+
f'Not enough fade values specified, expected {self._width} but got {len(values)}!')
109+
105110
correction = self._correction_current
106111
value_max = self._value_max
107112

108113
changed = False
109-
i: int = -1
110114
for i, val in enumerate(values):
111115
raw_new = round(val)
112116
if not 0 <= raw_new <= value_max:
@@ -118,11 +122,6 @@ def set_values(self, values: Iterable[Union[int, float]]):
118122
changed = True
119123
self._values_act[i] = act_new
120124

121-
# check that we passed all values
122-
if i + 1 != self._width:
123-
raise ValueCountDoesNotMatchChannelWidthError(
124-
f'Not enough fade values specified, expected {self._width} but got {i + 1}!')
125-
126125
if changed:
127126
self._parent_universe.channel_changed(self)
128127
return self
@@ -137,15 +136,25 @@ def to_buffer(self, buf: bytearray):
137136
start += byte_size
138137
return self
139138

139+
def add_fade(self, values: Collection[Union[int, FadeBase]], duration_ms: int,
140+
fade_class: Type[FadeBase] = LinearFade):
141+
warnings.warn(
142+
f"{self.set_fade.__name__:s} is deprecated, use {self.set_fade.__name__:s} instead", DeprecationWarning)
143+
return self.set_fade(values, duration_ms, fade_class)
144+
140145
# noinspection PyProtectedMember
141-
def add_fade(self, values: Iterable[Union[int, FadeBase]], duration_ms: int,
146+
def set_fade(self, values: Collection[Union[int, FadeBase]], duration_ms: int,
142147
fade_class: Type[FadeBase] = LinearFade):
143148
"""Add and schedule a new fade for the channel
144149
145150
:param values: Target values for the fade
146151
:param duration_ms: Duration for the fade in ms
147152
:param fade_class: What kind of fade
148153
"""
154+
# check that we passed all values
155+
if len(values) != self._width:
156+
raise ValueCountDoesNotMatchChannelWidthError(
157+
f'Not enough fade values specified, expected {self._width} but got {len(values)}!')
149158

150159
if self._current_fade is not None:
151160
self._current_fade.cancel()
@@ -158,22 +167,16 @@ def add_fade(self, values: Iterable[Union[int, FadeBase]], duration_ms: int,
158167

159168
# build fades
160169
fades: List[FadeBase] = []
161-
i: int = -1
162-
for i, val in enumerate(values): # noqa: B007
170+
for i, target in enumerate(values):
163171
# default is linear
164-
k = fade_class(val) if not isinstance(val, FadeBase) else val
172+
k = fade_class() if not isinstance(target, FadeBase) else target
165173
fades.append(k)
166174

167-
if not 0 <= k.val_target <= self._value_max:
175+
if not 0 <= target <= self._value_max:
168176
raise ChannelValueOutOfBoundsError(
169-
f'Target value out of bounds! 0 <= {k.val_target} <= {self._value_max}')
177+
f'Target value out of bounds! 0 <= {target} <= {self._value_max}')
170178

171-
k.initialize(fade_steps)
172-
173-
# check that we passed all values
174-
if i + 1 != self._width:
175-
raise ValueCountDoesNotMatchChannelWidthError(
176-
f'Not enough fade values specified, expected {self._width} but got {i + 1}!')
179+
k.initialize(self._values_raw[i], target, fade_steps)
177180

178181
# Add to scheduling
179182
self._current_fade = ChannelBoundFade(self, fades)
@@ -182,11 +185,11 @@ def add_fade(self, values: Iterable[Union[int, FadeBase]], duration_ms: int,
182185
# start fade/refresh task if necessary
183186
self._parent_node._process_task.start()
184187

188+
# todo: this on the ChannelBoundFade
185189
if log.isEnabledFor(LVL_DEBUG):
186190
log.debug(f'Added fade with {fade_steps} steps:')
187191
for i, fade in enumerate(fades):
188-
log.debug(f'CH {self._start + i}: {self._values_raw[i]:03d} -> {fade.val_target:03d}'
189-
f' | {fade.debug_initialize():s}')
192+
log.debug(f'CH {self._start + i}: {fade.debug_initialize():s}')
190193
return self
191194

192195
def __await__(self):

src/pyartnet/base/channel_fade.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ def __init__(self, channel: 'pyartnet.base.Channel', fades: Iterable['pyartnet.f
1616
self.channel: 'pyartnet.base.Channel' = channel
1717

1818
self.fades: Tuple['pyartnet.fades.FadeBase', ...] = tuple(fades)
19-
self.values: List[float] = [f.val_current for f in fades]
19+
self.values: List[float] = [0 for _ in fades]
2020

2121
self.is_done = False
2222
self.event: Final = Event()

src/pyartnet/fades/fade_base.py

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,15 @@
1-
import abc
21

2+
class FadeBase:
33

4-
class FadeBase(metaclass=abc.ABCMeta):
5-
6-
def __init__(self, target: int):
7-
self.val_target : int = int(target) # Target Value
8-
self.val_start : int = 0 # Start Value
9-
self.val_current : float = 0.0 # Current Value
10-
4+
def __init__(self):
115
self.is_done = False
126

13-
@abc.abstractmethod
14-
def initialize(self, steps : int):
7+
def initialize(self, current: int, target: int, steps: int):
158
raise NotImplementedError()
169

1710
def debug_initialize(self) -> str:
1811
"""return debug string of the calculated values in initialize fade"""
1912
return ""
2013

21-
@abc.abstractmethod
2214
def calc_next_value(self) -> float:
2315
raise NotImplementedError()

src/pyartnet/fades/fade_linear.py

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,30 @@
33

44
class LinearFade(FadeBase):
55

6-
def __init__(self, target: int):
7-
super().__init__(target)
6+
def __init__(self):
7+
super().__init__()
8+
self.target: int = 0 # Target Value
9+
self.current: float = 0.0 # Current Value
810
self.factor: float = 1.0
911

1012
def debug_initialize(self) -> str:
11-
return f"step: {self.factor:+5.1f}"
13+
return f"{self.current:03.0f} -> {self.target:03d} | step: {self.factor:+5.1f}"
1214

13-
def initialize(self, steps: int):
14-
self.factor = (self.val_target - self.val_start) / steps
15+
def initialize(self, start: int, target: int, steps: int):
16+
self.current = start
17+
self.target = target
18+
self.factor = (self.target - start) / steps
1519

1620
def calc_next_value(self) -> float:
17-
self.val_current += self.factor
21+
self.current += self.factor
1822

1923
# is_done status
20-
curr = round(self.val_current)
24+
curr = round(self.current)
2125
if self.factor <= 0:
22-
if curr <= self.val_target:
26+
if curr <= self.target:
2327
self.is_done = True
2428
else:
25-
if curr >= self.val_target:
29+
if curr >= self.target:
2630
self.is_done = True
2731

28-
return curr
32+
return self.current

tests/channel/test_boundaries.py

Lines changed: 49 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22

33
import pytest
44

5-
from pyartnet.base import BaseUniverse
65
from pyartnet.base.channel import Channel
7-
from pyartnet.errors import ChannelOutOfUniverseError, ChannelValueOutOfBoundsError
6+
from pyartnet.errors import ChannelOutOfUniverseError, \
7+
ChannelValueOutOfBoundsError, ValueCountDoesNotMatchChannelWidthError
88

99

1010
def test_channel_boundaries():
@@ -32,44 +32,58 @@ def test_channel_boundaries():
3232
Channel(univ, 511, 1, byte_size=2)
3333

3434

35-
def test_set_value_invalid():
35+
def get_node_universe_mock():
36+
node = Mock()
37+
node._process_every = 0.001
38+
3639
universe = Mock()
40+
universe._node = node
3741
universe.output_correction = None
42+
return node, universe
3843

39-
b = Channel(universe, 1, 1)
40-
with pytest.raises(ChannelValueOutOfBoundsError) as e:
41-
b.set_values([256])
42-
assert str(e.value) == 'Channel value out of bounds! 0 <= 256 <= 255'
43-
b.set_values([255])
4444

45-
b = Channel(universe, 1, 1, byte_size=2)
46-
with pytest.raises(ChannelValueOutOfBoundsError) as e:
47-
b.set_values([65536])
48-
assert str(e.value) == 'Channel value out of bounds! 0 <= 65536 <= 65535'
49-
b.set_values([65535])
45+
@pytest.mark.parametrize(
46+
('width', 'byte_size', 'invalid', 'valid'),
47+
((1, 1, -1, 255), (1, 1, 256, 255), (3, 1, 256, 255),
48+
(1, 2, -1, 65535), (1, 2, 65536, 65535), (3, 2, 65536, 65535), ))
49+
def test_set_invalid(width, byte_size, invalid, valid):
50+
node, universe = get_node_universe_mock()
5051

51-
b = Channel(universe, 3, 3)
52+
invalid_values = [0] * (width - 1) + [invalid]
53+
valid_values = [0] * (width - 1) + [valid]
54+
55+
# test set_values
56+
c = Channel(universe, 1, width, byte_size=byte_size)
5257
with pytest.raises(ChannelValueOutOfBoundsError) as e:
53-
b.set_values([0, 0, 256])
54-
assert str(e.value) == 'Channel value out of bounds! 0 <= 256 <= 255'
55-
b.set_values([0, 0, 255])
58+
c.set_values(invalid_values)
59+
assert str(e.value) == f'Channel value out of bounds! 0 <= {invalid:d} <= {valid}'
60+
c.set_values(valid_values)
5661

57-
b = Channel(universe, 3, 3, byte_size=2)
62+
# test set_fade
63+
c = Channel(universe, 1, width, byte_size=byte_size)
5864
with pytest.raises(ChannelValueOutOfBoundsError) as e:
59-
b.set_values([0, 0, 65536])
60-
assert str(e.value) == 'Channel value out of bounds! 0 <= 65536 <= 65535'
61-
b.set_values([0, 0, 65535])
62-
63-
64-
def test_values_add_channel(universe: BaseUniverse):
65-
u = universe.add_channel(1, 2, byte_size=3, byte_order='big')
66-
assert u._start == 1
67-
assert u._width == 2
68-
assert u._byte_size == 3
69-
assert u._byte_order == 'big'
70-
71-
u = universe.add_channel(10, 5, byte_size=2, byte_order='little')
72-
assert u._start == 10
73-
assert u._width == 5
74-
assert u._byte_size == 2
75-
assert u._byte_order == 'little'
65+
c.set_fade(invalid_values, 100)
66+
assert str(e.value) == f'Target value out of bounds! 0 <= {invalid:d} <= {valid}'
67+
c.set_fade(valid_values, 100)
68+
69+
70+
async def test_set_missing():
71+
node, universe = get_node_universe_mock()
72+
73+
c = Channel(universe, 1, 1)
74+
with pytest.raises(ValueCountDoesNotMatchChannelWidthError) as e:
75+
c.set_values([0, 0, 255])
76+
assert str(e.value) == 'Not enough fade values specified, expected 1 but got 3!'
77+
78+
with pytest.raises(ValueCountDoesNotMatchChannelWidthError) as e:
79+
c.set_fade([0, 0, 255], 0)
80+
assert str(e.value) == 'Not enough fade values specified, expected 1 but got 3!'
81+
82+
c = Channel(universe, 1, 3)
83+
with pytest.raises(ValueCountDoesNotMatchChannelWidthError) as e:
84+
c.set_values([0, 255])
85+
assert str(e.value) == 'Not enough fade values specified, expected 3 but got 2!'
86+
87+
with pytest.raises(ValueCountDoesNotMatchChannelWidthError) as e:
88+
c.set_fade([0, 255], 0)
89+
assert str(e.value) == 'Not enough fade values specified, expected 3 but got 2!'

tests/channel/test_channel.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from pyartnet.base import BaseUniverse
2+
3+
4+
def test_values_add_channel(universe: BaseUniverse):
5+
u = universe.add_channel(1, 2, byte_size=3, byte_order='big')
6+
assert u._start == 1
7+
assert u._width == 2
8+
assert u._byte_size == 3
9+
assert u._byte_order == 'big'
10+
11+
u = universe.add_channel(10, 5, byte_size=2, byte_order='little')
12+
assert u._start == 10
13+
assert u._width == 5
14+
assert u._byte_size == 2
15+
assert u._byte_order == 'little'

0 commit comments

Comments
 (0)