20
20
from typing import List , Optional , Callable , Any , Type , Union , Tuple , Iterable , Generic , TypeVar
21
21
22
22
from .base import Subscribable , LogicHandler , Readable , Writable , T , UninitializedError , Reading
23
- from .datatypes import RangeFloat1 , RangeUInt8 , RangeInt0To100 , HSVFloat1 , RGBUInt8 , RGBWUInt8
23
+ from .datatypes import RangeFloat1 , RangeUInt8 , RangeInt0To100 , HSVFloat1 , RGBUInt8 , RGBWUInt8 , FadeStep , AbstractStep
24
24
from .expressions import ExpressionWrapper
25
25
26
26
logger = logging .getLogger (__name__ )
@@ -754,6 +754,17 @@ class AbstractRamp(Readable[T], Subscribable[T], Reading[T], Writable[T], Generi
754
754
"""
755
755
Abstract base class for all ramp generators
756
756
757
+ All ramp generators create smooth transitions from incoming value updates by splitting publishing multiple timed
758
+ updates each doing a small step towards the target value. They are *Readable* and *Subscribable* to be used in
759
+ :ref:`expressions`.
760
+
761
+ In addition, the Ramp generators are *Writable* and *Reading* in order to connect them to a stateful object (like a
762
+ :class:`Variable <shc.variables.Variable>`) which also receives value updates from other sources. For this to work
763
+ flawlessly, the Ramp generator will stop the current ramp in progress, when it receives a value (via :meth:`write`)
764
+ from the connected object and it will *read* the current value of the connected object and use it as the start value
765
+ for a ramp instead of the last value received from the wrapped object. Both of these features are optional, so the
766
+ Ramp generator can also be connected to non-readable and non-subscribable objects.
767
+
757
768
Different derived ramp generator classes for different datatypes exist:
758
769
759
770
- :class:`IntRamp` (for :class:`int`, :class:`shc.datatypes.RangeUInt8` and :class:`shc.datatypes.RangeInt0To100`)
@@ -763,18 +774,16 @@ class AbstractRamp(Readable[T], Subscribable[T], Reading[T], Writable[T], Generi
763
774
- :class:`RGBWHSVRamp` (for :class:`shc.datatypes.RGBWUInt8`; doing a linear ramp in HSV color space plus simple
764
775
linear ramp for the white channel)
765
776
766
- All ramp generators create smooth transitions from incoming value updates by splitting publishing multiple timed
767
- updates each doing a small step towards the target value. They are *Readable* and *Subscribable* to be used in
768
- :ref:`expressions` .
777
+ All of these ramp generator types allow to wrap a Subscribable object of one of their value types to subscribe to
778
+ its value updates to turn them into ramps. Alternatively, they can be initialized with only the concrete value type
779
+ only, so the :meth:`ramp_to` method can be triggered manually from logic handlers etc .
769
780
770
- In addition, the Ramp generators are *Writable* and *Reading* in order to connect them to a stateful object (like a
771
- :class:`Variable <shc.variables.Variable>`) which also receives value updates from other sources. For this to work
772
- flawlessly, the Ramp generator will stop the current ramp in progress, when it receives a value (via :meth:`write`)
773
- from the connected object and it will *read* the current value of the connected object and use it as the start value
774
- for a ramp instead of the last value recived from the wrapped object. Both of these features are optional, so the
775
- Ramp generator can also be connected to non-readable and non-subscribable objects.
781
+ As a special case, there's the :class:`FadeStepRamp`, which is a RangeFloat1-typed Connectable object, which wraps
782
+ a Subscribable object of type :class:`shc.datatypes.FadeStep` and creates ramps from the received fade steps
783
+ (similar to :class:`shc.misc.FadeStepAdapter`).
784
+
785
+ In addition, all of them take the following init parameters:
776
786
777
- :param wrapped: The subscribable object from which the value updates are transformed into smooth ramps.
778
787
:param ramp_duration: The duration of the generated ramp/transition. Depending on `dynamic_duration` this is either
779
788
the fixed duration of each ramp or it is the duration of a ramp across the full value range, which is
780
789
dynamically lowered for smaller ramps.
@@ -792,11 +801,10 @@ class AbstractRamp(Readable[T], Subscribable[T], Reading[T], Writable[T], Generi
792
801
"""
793
802
is_reading_optional = False
794
803
795
- def __init__ (self , wrapped : Subscribable [T ], ramp_duration : datetime .timedelta , dynamic_duration : bool = True ,
804
+ def __init__ (self , type_ : Type [T ], ramp_duration : datetime .timedelta , dynamic_duration : bool = True ,
796
805
max_frequency : float = 25.0 , enable_ramp : Optional [Readable [bool ]] = None ):
797
- self .type = wrapped . type
806
+ self .type = type_
798
807
super ().__init__ ()
799
- wrapped .trigger (self .ramp_to , synchronous = True )
800
808
self .ramp_duration = ramp_duration
801
809
self .dynamic_duration = dynamic_duration
802
810
self .max_frequency = max_frequency
@@ -843,6 +851,31 @@ async def ramp_to(self, value: T, origin: List[Any]) -> None:
843
851
self .__task = task
844
852
timer_supervisor .add_temporary_task (task )
845
853
854
+ async def ramp_by (self , step : AbstractStep [T ], origin : List [Any ]) -> None :
855
+ """
856
+ Start a new ramp of the given step size
857
+ """
858
+ begin = await self ._from_provider ()
859
+ if begin is not None :
860
+ self ._current_value = begin
861
+ if self ._current_value is None :
862
+ logger .warning ("Cannot apply FadeStep, since current value is not available." )
863
+ return
864
+
865
+ if self .enable_ramp is not None :
866
+ enabled = await self .enable_ramp .read ()
867
+ if not enabled :
868
+ if self .__task :
869
+ self .__task .cancel ()
870
+ await self ._publish_and_wait (step .apply_to (self ._current_value ), origin )
871
+ return
872
+
873
+ self .__new_target_value = step .apply_to (self ._current_value )
874
+ if not self .__task :
875
+ task = asyncio .get_event_loop ().create_task (self ._ramp ())
876
+ self .__task = task
877
+ timer_supervisor .add_temporary_task (task )
878
+
846
879
async def _ramp (self ) -> None :
847
880
step = 0
848
881
num_steps = 0
@@ -937,6 +970,14 @@ def _next_step(self, step: int) -> T:
937
970
938
971
939
972
class IntRamp (AbstractRamp [IntRampT ], Generic [IntRampT ]):
973
+ def __init__ (self , wrapped_or_type : Union [Subscribable [IntRampT ], Type [IntRampT ]], * args , ** kwargs ):
974
+ if isinstance (wrapped_or_type , Subscribable ):
975
+ type_ : Type [IntRampT ] = wrapped_or_type .type
976
+ wrapped_or_type .trigger (self .ramp_to , synchronous = True )
977
+ else :
978
+ type_ = wrapped_or_type
979
+ super ().__init__ (type_ , * args , ** kwargs ) # type: ignore # Mypy does not understand that type_ *is* FloatRampT
980
+
940
981
def _calculate_ramp (self , begin : IntRampT , target : IntRampT ) -> Tuple [float , int ]:
941
982
diff = abs (target - begin )
942
983
height = (diff / 255 if issubclass (self .type , RangeUInt8 ) else
@@ -955,7 +996,7 @@ def _next_step(self, step: int) -> IntRampT:
955
996
FloatRampT = TypeVar ('FloatRampT' , float , RangeFloat1 )
956
997
957
998
958
- class FloatRamp (AbstractRamp [FloatRampT ], Generic [FloatRampT ]):
999
+ class AbstractFloatRamp (AbstractRamp [FloatRampT ], Generic [FloatRampT ]):
959
1000
def _calculate_ramp (self , begin : FloatRampT , target : FloatRampT ) -> Tuple [float , int ]:
960
1001
diff = abs (target - begin )
961
1002
height = diff / 1.0 if issubclass (self .type , RangeFloat1 ) else 1.0
@@ -969,6 +1010,22 @@ def _next_step(self, step: int) -> FloatRampT:
969
1010
return self .type (self ._begin + self ._diff * step )
970
1011
971
1012
1013
+ class FloatRamp (AbstractFloatRamp [FloatRampT ], Generic [FloatRampT ]):
1014
+ def __init__ (self , wrapped_or_type : Union [Subscribable [FloatRampT ], Type [FloatRampT ]], * args , ** kwargs ):
1015
+ if isinstance (wrapped_or_type , Subscribable ):
1016
+ type_ : Type [FloatRampT ] = wrapped_or_type .type
1017
+ wrapped_or_type .trigger (self .ramp_to , synchronous = True )
1018
+ else :
1019
+ type_ = wrapped_or_type
1020
+ super ().__init__ (type_ , * args , ** kwargs ) # type: ignore # Mypy does not understand that type_ *is* FloatRampT
1021
+
1022
+
1023
+ class FadeStepRamp (AbstractFloatRamp [RangeFloat1 ]):
1024
+ def __init__ (self , wrapped : Subscribable [FadeStep ], * args , ** kwargs ):
1025
+ super ().__init__ (RangeFloat1 , * args , ** kwargs )
1026
+ wrapped .trigger (self .ramp_by , synchronous = True )
1027
+
1028
+
972
1029
def _normalize_hsv_ramp (begin : HSVFloat1 , target : HSVFloat1 ) -> Tuple [HSVFloat1 , HSVFloat1 ]:
973
1030
return begin ._replace (hue = begin .hue if begin .saturation != 0 else target .hue ,
974
1031
saturation = begin .saturation if begin .value != 0 else target .saturation ),\
@@ -994,6 +1051,11 @@ def _hsv_step(begin: HSVFloat1, diff: Tuple[float, float, float], step: int) ->
994
1051
995
1052
996
1053
class HSVRamp (AbstractRamp [HSVFloat1 ]):
1054
+ def __init__ (self , wrapped : Optional [Subscribable [HSVFloat1 ]], * args , ** kwargs ):
1055
+ super ().__init__ (HSVFloat1 , * args , ** kwargs )
1056
+ if wrapped is not None :
1057
+ wrapped .trigger (self .ramp_to , synchronous = True )
1058
+
997
1059
def _calculate_ramp (self , begin : HSVFloat1 , target : HSVFloat1 ) -> Tuple [float , int ]:
998
1060
diff = _hsv_diff (begin , target )
999
1061
height = max (abs (diff [0 ] * 2 ), abs (diff [1 ]), abs (diff [2 ]))
@@ -1008,6 +1070,11 @@ def _next_step(self, step: int) -> HSVFloat1:
1008
1070
1009
1071
1010
1072
class RGBHSVRamp (AbstractRamp [RGBUInt8 ]):
1073
+ def __init__ (self , wrapped : Optional [Subscribable [RGBUInt8 ]], * args , ** kwargs ):
1074
+ super ().__init__ (RGBUInt8 , * args , ** kwargs )
1075
+ if wrapped is not None :
1076
+ wrapped .trigger (self .ramp_to , synchronous = True )
1077
+
1011
1078
def _calculate_ramp (self , begin : RGBUInt8 , target : RGBUInt8 ) -> Tuple [float , int ]:
1012
1079
begin_hsv , target_hsv = _normalize_hsv_ramp (HSVFloat1 .from_rgb (begin .as_float ()),
1013
1080
HSVFloat1 .from_rgb (target .as_float ()))
@@ -1028,6 +1095,11 @@ def _next_step(self, step: int) -> RGBUInt8:
1028
1095
1029
1096
1030
1097
class RGBWHSVRamp (AbstractRamp [RGBWUInt8 ]):
1098
+ def __init__ (self , wrapped : Optional [Subscribable [RGBWUInt8 ]], * args , ** kwargs ):
1099
+ super ().__init__ (RGBWUInt8 , * args , ** kwargs )
1100
+ if wrapped is not None :
1101
+ wrapped .trigger (self .ramp_to , synchronous = True )
1102
+
1031
1103
def _calculate_ramp (self , begin : RGBWUInt8 , target : RGBWUInt8 ) -> Tuple [float , int ]:
1032
1104
begin_hsv , target_hsv = _normalize_hsv_ramp (HSVFloat1 .from_rgb (begin .rgb .as_float ()),
1033
1105
HSVFloat1 .from_rgb (target .rgb .as_float ()))
0 commit comments