-
-
Notifications
You must be signed in to change notification settings - Fork 71
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* refactor(stepper): refactor stepper to be more isolated this will enable the integration of a bouncing stepper * refactor(stepper): create StepperOutput with next_direction * feat(light_controller): add click/hold modes related to #204
- Loading branch information
Showing
30 changed files
with
861 additions
and
475 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,69 +1,90 @@ | ||
import abc | ||
from typing import Tuple, Union | ||
from typing import Optional | ||
|
||
from attr import dataclass | ||
from cx_const import Number | ||
|
||
|
||
class MinMax: | ||
def __init__(self, min_: float, max_: float, margin=0.05) -> None: | ||
self._min = min_ | ||
self._max = max_ | ||
self.margin_dist = (max_ - min_) * margin | ||
def __init__(self, min: Number, max: Number, margin: float = 0.05) -> None: | ||
self._min = min | ||
self._max = max | ||
self.margin_dist = (max - min) * margin | ||
|
||
@property | ||
def min(self) -> float: | ||
def min(self) -> Number: | ||
return self._min | ||
|
||
@property | ||
def max(self) -> float: | ||
def max(self) -> Number: | ||
return self._max | ||
|
||
def is_min(self, value: float) -> bool: | ||
def is_min(self, value: Number) -> bool: | ||
return self._min == value | ||
|
||
def is_max(self, value: float) -> bool: | ||
def is_max(self, value: Number) -> bool: | ||
return self._max == value | ||
|
||
def is_between(self, value: float) -> bool: | ||
def is_between(self, value: Number) -> bool: | ||
return self._min < value < self._max | ||
|
||
def in_min_boundaries(self, value: float) -> bool: | ||
def in_min_boundaries(self, value: Number) -> bool: | ||
return self._min <= value <= (self._min + self.margin_dist) | ||
|
||
def in_max_boundaries(self, value: float) -> bool: | ||
def in_max_boundaries(self, value: Number) -> bool: | ||
return (self._max - self.margin_dist) <= value <= self._max | ||
|
||
def clip(self, value: float) -> float: | ||
def clip(self, value: Number) -> Number: | ||
return max(self._min, min(value, self._max)) | ||
|
||
def __repr__(self) -> str: | ||
return f"MinMax({self.min}, {self.max})" | ||
|
||
|
||
@dataclass | ||
class StepperOutput: | ||
next_value: Number | ||
next_direction: Optional[str] | ||
|
||
@property | ||
def exceeded(self) -> bool: | ||
return self.next_direction is None | ||
|
||
|
||
class Stepper(abc.ABC): | ||
UP = "up" | ||
DOWN = "down" | ||
TOGGLE_UP = "toggle_up" | ||
TOGGLE_DOWN = "toggle_down" | ||
TOGGLE = "toggle" | ||
sign_mapping = {UP: 1, DOWN: -1, TOGGLE_UP: 1, TOGGLE_DOWN: -1} | ||
sign_mapping = {UP: 1, DOWN: -1} | ||
|
||
def __init__(self) -> None: | ||
self.previous_direction = Stepper.TOGGLE_DOWN | ||
previous_direction: str = DOWN | ||
min_max: MinMax | ||
steps: Number | ||
|
||
def get_direction(self, value: float, direction: str) -> str: | ||
@staticmethod | ||
def invert_direction(direction: str) -> str: | ||
return Stepper.UP if direction == Stepper.DOWN else Stepper.DOWN | ||
|
||
@staticmethod | ||
def sign(direction: str) -> int: | ||
return Stepper.sign_mapping[direction] | ||
|
||
def __init__(self, min_max: MinMax, steps: Number) -> None: | ||
self.min_max = min_max | ||
self.steps = steps | ||
|
||
def get_direction(self, value: Number, direction: str) -> str: | ||
if direction == Stepper.TOGGLE: | ||
direction = ( | ||
Stepper.TOGGLE_UP | ||
if self.previous_direction == Stepper.TOGGLE_DOWN | ||
else Stepper.TOGGLE_DOWN | ||
) | ||
direction = Stepper.invert_direction(self.previous_direction) | ||
self.previous_direction = direction | ||
return direction | ||
|
||
def sign(self, direction: str) -> int: | ||
return Stepper.sign_mapping[direction] | ||
|
||
@abc.abstractmethod | ||
def step(self, value: float, direction: str) -> Tuple[Union[int, float], bool]: | ||
def step(self, value: Number, direction: str) -> StepperOutput: | ||
""" | ||
This function updates the value according to the steps | ||
that needs to take and returns the new value and True | ||
if the step exceeds the boundaries. | ||
that needs to take and returns the new value together with | ||
the new direction it will need to go. If next_direction is | ||
None, the loop will stop executing. | ||
""" | ||
raise NotImplementedError |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
from cx_const import Number | ||
from cx_core.stepper import Stepper, StepperOutput | ||
|
||
|
||
class BounceStepper(Stepper): | ||
def step(self, value: Number, direction: str) -> StepperOutput: | ||
value = self.min_max.clip(value) | ||
sign = Stepper.sign(direction) | ||
max_ = self.min_max.max | ||
min_ = self.min_max.min | ||
step = (max_ - min_) / self.steps | ||
|
||
new_value = value + sign * step | ||
if self.min_max.is_between(new_value): | ||
return StepperOutput(round(new_value, 3), next_direction=direction) | ||
else: | ||
new_value = 2 * self.min_max.clip(new_value) - new_value | ||
return StepperOutput( | ||
round(new_value, 3), next_direction=Stepper.invert_direction(direction) | ||
) |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
from cx_const import Number | ||
from cx_core.stepper import MinMax, Stepper, StepperOutput | ||
|
||
|
||
class IndexLoopStepper(Stepper): | ||
def __init__(self, size: int) -> None: | ||
super().__init__(MinMax(0, size - 1), size) | ||
|
||
def step(self, value: Number, direction: str) -> StepperOutput: | ||
value = self.min_max.clip(value) | ||
sign = self.sign(direction) | ||
# We add +1 to make the max be included | ||
max_ = int(self.min_max.max) + 1 | ||
min_ = int(self.min_max.min) | ||
step = (max_ - min_) // self.steps | ||
|
||
new_value = (int(value) + step * sign) % (max_ - min_) + min_ | ||
return StepperOutput(new_value, next_direction=direction) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
from cx_const import Number | ||
from cx_core.stepper import Stepper, StepperOutput | ||
|
||
|
||
class LoopStepper(Stepper): | ||
def step(self, value: Number, direction: str) -> StepperOutput: | ||
value = self.min_max.clip(value) | ||
sign = Stepper.sign(direction) | ||
# We add +1 to include `max` | ||
max_ = self.min_max.max | ||
min_ = self.min_max.min | ||
step = (max_ - min_) / self.steps | ||
|
||
new_value = (((value + step * sign) - min_) % (max_ - min_)) + min_ | ||
new_value = round(new_value, 3) | ||
return StepperOutput(new_value, next_direction=direction) |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
from cx_const import Number | ||
from cx_core.stepper import Stepper, StepperOutput | ||
|
||
|
||
class StopStepper(Stepper): | ||
def get_direction(self, value: Number, direction: str) -> str: | ||
value = self.min_max.clip(value) | ||
if direction == Stepper.TOGGLE and self.min_max.in_min_boundaries(value): | ||
self.previous_direction = Stepper.UP | ||
return self.previous_direction | ||
if direction == Stepper.TOGGLE and self.min_max.in_max_boundaries(value): | ||
self.previous_direction = Stepper.DOWN | ||
return self.previous_direction | ||
return super().get_direction(value, direction) | ||
|
||
def step(self, value: Number, direction: str) -> StepperOutput: | ||
value = self.min_max.clip(value) | ||
sign = Stepper.sign(direction) | ||
max_ = self.min_max.max | ||
min_ = self.min_max.min | ||
step = (max_ - min_) / self.steps | ||
|
||
new_value = value + sign * step | ||
new_value = round(new_value, 3) | ||
if self.min_max.is_between(new_value): | ||
return StepperOutput(new_value, next_direction=direction) | ||
else: | ||
new_value = self.min_max.clip(new_value) | ||
return StepperOutput(new_value, next_direction=None) |
Oops, something went wrong.