Skip to content

Commit

Permalink
hours to minutes for LED dark cycle. Resolves #416 - thanks @c-bun
Browse files Browse the repository at this point in the history
  • Loading branch information
CamDavidsonPilon committed Jul 17, 2023
1 parent 48dd89a commit b0c1d50
Show file tree
Hide file tree
Showing 3 changed files with 43 additions and 47 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
- improved sensitivity of self-test `test_REF_is_in_correct_position`.
- executing experiment profiles now check for required plugins.
- Plugins can be built with a flag file LEADER_ONLY to only be installed on the leader Pioreactor.
- Light/Dark cycle accepts fractional hours as well as integer hours. Note we changed the API for this automation slightly!
- **Breaking**: Light/Dark cycle LED automation uses minutes instead of hours!

### 23.6.27

Expand Down
38 changes: 17 additions & 21 deletions pioreactor/automations/led/light_dark_cycle.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

class LightDarkCycle(LEDAutomationJob):
"""
Follows as h light / h dark cycle. Starts light ON.
Follows as min light / min dark cycle. Starts light ON.
"""

automation_name: str = "light_dark_cycle"
Expand All @@ -21,29 +21,24 @@ class LightDarkCycle(LEDAutomationJob):
"unit": "min",
},
"light_intensity": {"datatype": "float", "settable": True, "unit": "%"},
"light_duration_hours": {"datatype": "integer", "settable": True, "unit": "h"},
"dark_duration_hours": {"datatype": "integer", "settable": True, "unit": "h"},
"light_duration_minutes": {"datatype": "integer", "settable": True, "unit": "min"},
"dark_duration_minutes": {"datatype": "integer", "settable": True, "unit": "min"},
}

def __init__(
self,
light_intensity: float | str,
light_duration_hours: int | str,
dark_duration_hours: int | str,
light_duration_minutes: int | str,
dark_duration_minutes: int | str,
**kwargs,
):
super().__init__(**kwargs)
self.minutes_online: int = -1
self.light_active: bool = False
self.channels: list[LedChannel] = ["D", "C"]
self.set_light_intensity(light_intensity)
self.light_duration_hours = float(light_duration_hours)
self.dark_duration_hours = float(dark_duration_hours)

if 0 < self.light_duration_hours < 1 / 60.0 or 0 < self.dark_duration_hours < 1 / 60.0:
# users can input 0 - that's fine and deliberate. It's when they put in 0.01 that it makes no sense.
self.logger.error("Durations must be at least 1 minute long.")
raise ValueError("Durations must be at least 1 minute long.")
self.light_duration_minutes = float(light_duration_minutes)
self.dark_duration_minutes = float(dark_duration_minutes)

def execute(self) -> Optional[events.AutomationEvent]:
# runs every minute
Expand All @@ -64,36 +59,37 @@ def trigger_leds(self, minutes: int) -> Optional[events.AutomationEvent]:
An instance of AutomationEvent, indicating that LEDs' status might have changed.
Returns None if the LEDs' state didn't change.
"""
cycle_duration_min = int((self.light_duration_hours + self.dark_duration_hours) * 60)
cycle_duration_min = int(self.light_duration_minutes + self.dark_duration_minutes)

if ((minutes % cycle_duration_min) < (self.light_duration_hours * 60)) and (
if ((minutes % cycle_duration_min) < (self.light_duration_minutes)) and (
not self.light_active
):
self.light_active = True

for channel in self.channels:
self.set_led_intensity(channel, self.light_intensity)

return events.ChangedLedIntensity(f"{minutes/60:.1f}h: turned on LEDs.")
return events.ChangedLedIntensity(f"{minutes:.1f}min: turned on LEDs.")

elif ((minutes % cycle_duration_min) >= (self.light_duration_hours * 60)) and (
elif ((minutes % cycle_duration_min) >= (self.light_duration_minutes)) and (
self.light_active
):
self.light_active = False
for channel in self.channels:
self.set_led_intensity(channel, 0)
return events.ChangedLedIntensity(f"{minutes/60:.1f}h: turned off LEDs.")
return events.ChangedLedIntensity(f"{minutes:.1f}min: turned off LEDs.")

else:
return None

def set_dark_duration_hours(self, hours: int):
self.dark_duration_hours = hours
# minutes setters
def set_dark_duration_minutes(self, minutes: int):
self.dark_duration_minutes = minutes

self.trigger_leds(self.minutes_online)

def set_light_duration_hours(self, hours: int):
self.light_duration_hours = hours
def set_light_duration_minutes(self, minutes: int):
self.light_duration_minutes = minutes

self.trigger_leds(self.minutes_online)

Expand Down
50 changes: 25 additions & 25 deletions pioreactor/tests/test_led_control.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,8 @@ def test_light_dark_cycle_starts_on() -> None:
"light_dark_cycle",
duration=60,
light_intensity=50,
light_duration_hours=16,
dark_duration_hours=8,
light_duration_minutes=60 * 16,
dark_duration_minutes=8 * 60,
unit=unit,
experiment=experiment,
) as lc:
Expand All @@ -133,8 +133,8 @@ def test_light_dark_cycle_turns_off_after_N_cycles() -> None:
"light_dark_cycle",
duration=0.01,
light_intensity=50,
light_duration_hours=16,
dark_duration_hours=8,
light_duration_minutes=60 * 16,
dark_duration_minutes=8 * 60,
unit=unit,
experiment=experiment,
) as lc:
Expand All @@ -158,8 +158,8 @@ def test_dark_duration_hour_to_zero() -> None:
"light_dark_cycle",
duration=0.005,
light_intensity=50,
light_duration_hours=16,
dark_duration_hours=8,
light_duration_minutes=60 * 16,
dark_duration_minutes=8 * 60,
unit=unit,
experiment=experiment,
) as lc:
Expand All @@ -172,7 +172,7 @@ def test_dark_duration_hour_to_zero() -> None:

assert not lc.automation_job.light_active
pause()
lc.automation_job.set_dark_duration_hours(0)
lc.automation_job.set_dark_duration_minutes(0 * 60)
pause()
assert lc.automation_job.light_active

Expand All @@ -188,28 +188,28 @@ def test_light_duration_hour_to_zero() -> None:
"light_dark_cycle",
duration=0.01,
light_intensity=50,
light_duration_hours=16,
dark_duration_hours=8,
light_duration_minutes=60 * 16,
dark_duration_minutes=8 * 60,
unit=unit,
experiment=experiment,
) as lc:
pause(6)
assert lc.automation_job.light_active

lc.automation_job.set_light_duration_hours(0)
lc.automation_job.set_light_duration_minutes(60 * 0)

assert not lc.automation_job.light_active


def test_add_dark_duration_hours() -> None:
experiment = "test_add_dark_duration_hours"
def test_add_dark_duration_minutes() -> None:
experiment = "test_add_dark_duration_minutes * 60"
unit = get_unit_name()
with LEDController(
"light_dark_cycle",
duration=0.01,
light_intensity=50,
light_duration_hours=16,
dark_duration_hours=8,
light_duration_minutes=60 * 16,
dark_duration_minutes=8 * 60,
unit=unit,
experiment=experiment,
) as lc:
Expand All @@ -222,7 +222,7 @@ def test_add_dark_duration_hours() -> None:

assert not lc.automation_job.light_active

lc.automation_job.set_dark_duration_hours(10)
lc.automation_job.set_dark_duration_minutes(10 * 60)

assert not lc.automation_job.light_active

Expand All @@ -231,15 +231,15 @@ def test_add_dark_duration_hours() -> None:
assert c["C"] == 0.0


def test_remove_dark_duration_hours() -> None:
experiment = "test_remove_dark_duration_hours"
def test_remove_dark_duration_minutes() -> None:
experiment = "test_remove_dark_duration_minutes * 60"
unit = get_unit_name()
with LEDController(
"light_dark_cycle",
duration=0.005,
light_intensity=50,
light_duration_hours=16,
dark_duration_hours=8,
light_duration_minutes=60 * 16,
dark_duration_minutes=8 * 60,
unit=unit,
experiment=experiment,
) as lc:
Expand All @@ -256,7 +256,7 @@ def test_remove_dark_duration_hours() -> None:

assert not lc.automation_job.light_active

lc.automation_job.set_dark_duration_hours(3)
lc.automation_job.set_dark_duration_minutes(3 * 60)

assert lc.automation_job.light_active

Expand All @@ -272,8 +272,8 @@ def test_fractional_hours() -> None:
"light_dark_cycle",
duration=0.005,
light_intensity=50,
light_duration_hours=0.9,
dark_duration_hours=0.1,
light_duration_minutes=60 * 0.9,
dark_duration_minutes=0.1 * 60,
unit=unit,
experiment=experiment,
) as lc:
Expand All @@ -300,8 +300,8 @@ def light_dark_cycle():
unit=get_unit_name(),
experiment="test_light_dark_cycle",
light_intensity=100,
light_duration_hours=1,
dark_duration_hours=1,
light_duration_minutes=60,
dark_duration_minutes=60,
)


Expand Down Expand Up @@ -341,7 +341,7 @@ def test_light_turns_off_in_dark_period(light_dark_cycle):
# Check that the LEDs were turned off
assert isinstance(event, events.ChangedLedIntensity)
assert "turned off LEDs" in event.message
assert light_dark_cycle.light_active
assert not light_dark_cycle.light_active


def test_light_stays_off_in_dark_period(light_dark_cycle):
Expand Down

0 comments on commit b0c1d50

Please sign in to comment.