Пример #1
0
    def cleanup(self) -> None:
        self.stop()
        self.unlock()

        with local_intermittent_storage("pwm_dc") as cache:
            if str(self.pin) in cache:
                del cache[str(self.pin)]

        with local_intermittent_storage("pwm_hz") as cache:
            if str(self.pin) in cache:
                del cache[str(self.pin)]

        gpio_helpers.set_gpio_availability(
            self.pin, gpio_helpers.GPIO_states.GPIO_AVAILABLE)

        if self.using_hardware:
            # `stop` handles cleanup.
            pass
        else:

            import RPi.GPIO as GPIO

            GPIO.setmode(GPIO.BCM)
            GPIO.setup(self.pin, GPIO.OUT, initial=GPIO.LOW)
            GPIO.cleanup(self.pin)

        self.logger.debug(f"Cleaned up PWM-{self.pin}.")
Пример #2
0
def lock_leds_temporarily(channels: list[LedChannel]) -> Iterator[None]:
    try:
        with local_intermittent_storage("led_locks") as cache:
            for c in channels:
                cache[c] = LED_LOCKED
        yield
    finally:
        with local_intermittent_storage("led_locks") as cache:
            for c in channels:
                cache[c] = LED_UNLOCKED
Пример #3
0
def run_around_tests():
    from pioreactor.utils import local_intermittent_storage

    with local_intermittent_storage("pio_jobs_running") as cache:
        for key in cache.keys():
            del cache[key]

    with local_intermittent_storage("led_locks") as cache:
        for key in cache.keys():
            del cache[key]

    yield
Пример #4
0
def test_caches_will_always_save_the_lastest_value_provided():
    with local_intermittent_storage("test") as cache:
        for k in cache.keys():
            del cache[k]

    with local_intermittent_storage("test") as cache1:
        with local_intermittent_storage("test") as cache2:
            cache1["A"] = b"1"
            cache2["A"] = b"0"
            cache2["B"] = b"2"

    with local_intermittent_storage("test") as cache:
        assert cache["A"] == b"0"
        assert cache["B"] == b"2"
Пример #5
0
def test_what_happens_when_an_error_occurs_in_init_but_we_catch_and_disconnect(
) -> None:
    class TestJob(BackgroundJob):
        def __init__(self, unit: str, experiment: str) -> None:
            super(TestJob, self).__init__(job_name="testjob",
                                          unit=unit,
                                          experiment=experiment)
            try:
                1 / 0
            except Exception as e:
                self.logger.error("Error!")
                self.set_state("disconnected")
                raise e

    publish("pioreactor/unit/exp/testjob/$state", None, retain=True)
    state = []

    def update_state(msg: MQTTMessage) -> None:
        state.append(msg.payload.decode())

    subscribe_and_callback(update_state, "pioreactor/unit/exp/testjob/$state")

    with pytest.raises(ZeroDivisionError):
        with TestJob(unit="unit", experiment="exp"):
            pass

    pause()
    assert state[-1] == "disconnected"

    with local_intermittent_storage("pio_jobs_running") as cache:
        assert "testjob" not in cache
Пример #6
0
    def start(self, initial_duty_cycle: float) -> None:
        assert (0.0 <= initial_duty_cycle <=
                100.0), "dc should be between 0 and 100, inclusive."

        with local_intermittent_storage("pwm_dc") as cache:
            cache[str(self.pin)] = str(initial_duty_cycle)

        self.pwm.start(initial_duty_cycle)
Пример #7
0
    def change_duty_cycle(self, dc: float) -> None:
        assert 0 <= dc <= 100, "dc should be between 0 and 100, inclusive."

        with local_intermittent_storage("pwm_dc") as cache:
            cache[str(self.pin)] = str(dc)

        if self.using_hardware:
            self.pwm.change_duty_cycle(dc)
        else:
            self.pwm.ChangeDutyCycle(dc)  # type: ignore
Пример #8
0
def test_that_out_scope_caches_cant_access_keys_created_by_inner_scope_cache():
    """
    You can modify caches, and the last assignment is valid.
    """
    with local_intermittent_storage("test") as cache:
        for k in cache.keys():
            del cache[k]

    with local_intermittent_storage("test") as cache1:
        cache1["A"] = b"0"
        with local_intermittent_storage("test") as cache2:
            assert cache2["A"] == b"0"
            cache2["B"] = b"1"

        assert "B" not in cache1  # note this.
        cache1["B"] = b"2"  # create, and overwritten.

    with local_intermittent_storage("test") as cache:
        assert cache["A"] == b"0"
        assert cache["B"] == b"2"
Пример #9
0
def test_local_cache_is_updated() -> None:

    channel: LedChannel = "B"

    unit = get_unit_name()
    exp = get_latest_experiment_name()

    assert led_intensity(channels=channel,
                         intensities=20,
                         unit=unit,
                         experiment=exp)

    with local_intermittent_storage("leds") as cache:
        assert float(cache["B"]) == 20
Пример #10
0
def test_lock_will_prevent_led_from_updating_single_channel_but_not_others_passed_in(
) -> None:

    unit = get_unit_name()
    exp = get_latest_experiment_name()

    assert led_intensity(channels=["A", "B"],
                         intensities=[20, 20],
                         unit=unit,
                         experiment=exp)

    with local_intermittent_storage("leds") as cache:
        assert float(cache["B"]) == 20
        assert float(cache["A"]) == 20

    with lock_leds_temporarily(["A"]):
        assert not led_intensity(channels=["A", "B"],
                                 intensities=[10, 10],
                                 unit=unit,
                                 experiment=exp)

    with local_intermittent_storage("leds") as cache:
        assert float(cache["B"]) == 10
        assert float(cache["A"]) == 20
Пример #11
0
    def check_on_max(self, value: float) -> None:

        if value > 3.2:
            self.logger.error(
                f"An ADC channel is recording a very high voltage, {round(value, 2)}V. We are shutting down components and jobs to keep the ADC safe."
            )

            unit, exp = get_unit_name(), get_latest_experiment_name()

            with local_intermittent_storage("led_locks") as cache:
                for c in ALL_LED_CHANNELS:
                    cache[c] = LED_UNLOCKED

            # turn off all LEDs that might be causing problems
            # however, ODReader may turn on the IR LED again.
            change_led_intensity(
                channels=ALL_LED_CHANNELS,
                intensities=[0] * len(ALL_LED_CHANNELS),
                source_of_event="ADCReader",
                unit=unit,
                experiment=exp,
                verbose=True,
            )

            publish(
                f"pioreactor/{unit}/{exp}/monitor/flicker_led_with_error_code",
                error_codes.ADC_INPUT_TOO_HIGH,
            )
            # kill ourselves - this will hopefully kill ODReader.
            # we have to send a signal since this is often called in a thread (RepeatedTimer)
            import os
            import signal

            os.kill(os.getpid(), signal.SIGTERM)
            return

        elif value > 3.1:
            self.logger.warning(
                f"An ADC channel is recording a very high voltage, {round(value, 2)}V. It's recommended to keep it less than 3.3V."
            )
            publish(
                f"pioreactor/{get_unit_name()}/{get_latest_experiment_name()}/monitor/flicker_led_with_error_code",
                error_codes.ADC_INPUT_TOO_HIGH,
            )
            return
Пример #12
0
def change_leds_intensities_temporarily(
    channels: list[LedChannel], new_intensities: list[float], **kwargs: Any
) -> Iterator[None]:
    """
    Change the LED referenced in `channels` to some intensity `new_intensities`
    inside the context block. Once the context block has left, change
    back to the old intensities (even if the intensities were changed inside the block.)

    """
    try:
        with local_intermittent_storage("leds") as cache:
            old_state = {c: float(cache.get(c, 0.0)) for c in channels}

        led_intensity(channels, new_intensities, **kwargs)

        yield
    finally:
        led_intensity(list(old_state.keys()), list(old_state.values()), **kwargs)
Пример #13
0
def test_lock_will_prevent_led_from_updating() -> None:

    channel: LedChannel = "A"

    unit = get_unit_name()
    exp = get_latest_experiment_name()

    assert led_intensity(channels=channel,
                         intensities=20,
                         unit=unit,
                         experiment=exp)

    with lock_leds_temporarily([channel]):
        assert not led_intensity(
            channels=channel, intensities=10, unit=unit, experiment=exp)

    with local_intermittent_storage("leds") as cache:
        assert float(cache[channel]) == 20
Пример #14
0
    def __init__(self,
                 pin: GpioPin,
                 hz: float,
                 always_use_software: bool = False) -> None:
        self.logger = create_logger("PWM")
        self.pin = pin
        self.hz = hz

        if self.is_locked():
            self.logger.debug(
                f"GPIO-{self.pin} is currently locked but a task is overwriting it. Either too many jobs are trying to access this pin, or a job didn't clean up properly."
            )

        gpio_helpers.set_gpio_availability(
            self.pin, gpio_helpers.GPIO_states.GPIO_UNAVAILABLE)

        if (not always_use_software) and (pin in self.HARDWARE_PWM_CHANNELS):

            self.pwm = HardwarePWM(self.HARDWARE_PWM_CHANNELS[self.pin],
                                   self.hz)

        else:

            import RPi.GPIO as GPIO  # type: ignore

            GPIO.setmode(GPIO.BCM)
            GPIO.setup(self.pin, GPIO.OUT, initial=GPIO.LOW)

            if self.hz > 2000:
                self.logger.warning(
                    "Setting a PWM to a very high frequency with software. Did you mean to use a hardware PWM?"
                )

            self.pwm = GPIO.PWM(self.pin, self.hz)

        with local_intermittent_storage("pwm_hz") as cache:
            cache[str(self.pin)] = str(self.hz)

        self.logger.debug(
            f"Initialized PWM-{self.pin} with {'hardware' if self.using_hardware else 'software'}, initial frequency is {self.hz}hz."
        )
Пример #15
0
def test_we_respect_any_locks_on_leds_we_want_to_modify() -> None:
    """
    This test works locally, but not in github CI
    """
    with local_intermittent_storage("led_locks") as cache:
        cache["A"] = LED_UNLOCKED
        cache["B"] = LED_UNLOCKED
        cache["C"] = LED_UNLOCKED
        cache["D"] = LED_UNLOCKED

    with LEDAutomation(duration=1, unit=unit, experiment=experiment) as ld:
        pause()
        pause()

        assert ld.set_led_intensity("B", 1)

        # someone else locks channel B
        with lock_leds_temporarily(["B"]):
            assert not ld.set_led_intensity("B", 2)

        assert ld.set_led_intensity("B", 3)
Пример #16
0
def blink() -> None:

    with local_intermittent_storage("pio_jobs_running") as cache:
        monitor_running = cache.get("monitor", b"0") == b"1"

    if not monitor_running:

        import RPi.GPIO as GPIO  # type: ignore

        GPIO.setmode(GPIO.BCM)

        from pioreactor.hardware import PCB_LED_PIN as LED_PIN

        def led_on() -> None:
            GPIO.output(LED_PIN, GPIO.HIGH)

        def led_off() -> None:
            GPIO.output(LED_PIN, GPIO.LOW)

        with temporarily_set_gpio_unavailable(LED_PIN):

            GPIO.setup(LED_PIN, GPIO.OUT)

            for _ in range(4):

                led_on()
                sleep(0.14)
                led_off()
                sleep(0.14)
                led_on()
                sleep(0.14)
                led_off()
                sleep(0.45)

            GPIO.cleanup(LED_PIN)

    else:
        publish(f"pioreactor/{get_unit_name()}/.../monitor/flicker_led_response_okay", 1)
Пример #17
0
def _update_current_state(
    channels: list[LedChannel],
    intensities: list[float],
) -> tuple[dict[LedChannel, float], dict[LedChannel, float]]:
    """
    Previously this used MQTT, but network latency could really cause trouble.
    Eventually I should try to modify the UI to not even need this `state` variable,
    """

    with local_intermittent_storage("leds") as led_cache:
        old_state = {
            channel: float(led_cache.get(channel, 0.0)) for channel in ALL_LED_CHANNELS
        }

        # update cache
        for channel, intensity in zip(channels, intensities):
            led_cache[channel] = str(intensity)

        new_state = {
            channel: float(led_cache.get(channel, 0.0)) for channel in ALL_LED_CHANNELS
        }

        return new_state, old_state
Пример #18
0
def set_gpio_availability(pin: GpioPin, is_in_use: GPIO_states) -> None:
    with local_intermittent_storage("gpio_in_use") as cache:
        cache[str(pin)] = is_in_use.value
Пример #19
0
 def is_locked(self) -> bool:
     with local_intermittent_storage("pwm_locks") as pwm_locks:
         return pwm_locks.get(str(self.pin)) == PWM_LOCKED
Пример #20
0
def is_led_channel_locked(channel: LedChannel) -> bool:
    with local_intermittent_storage("led_locks") as cache:
        return cache.get(channel, LED_UNLOCKED) == LED_LOCKED
Пример #21
0
 def unlock(self) -> None:
     with local_intermittent_storage("pwm_locks") as pwm_locks:
         pwm_locks[str(self.pin)] = PWM_UNLOCKED
Пример #22
0
def is_gpio_available(pin: GpioPin) -> bool:
    with local_intermittent_storage("gpio_in_use") as cache:
        return (cache.get(
            str(pin),
            GPIO_states.GPIO_AVAILABLE) == GPIO_states.GPIO_AVAILABLE)