Is there a relay module/controller

Hm, sounds like the software didn’t clean up something properly. I would ignore it for now, and change the following in the schedule relay file:

    def on_disconnected(self) -> None:
        from contextlib import suppress

        with suppress(AttributeError):
            self.repeated_thread.cancel()
        with suppress(AttributeError):        
            self.pwm.clean_up()

Hmm, thanks, but still no relay.

I see, you want power (I thought it was purposefully off, and the UI was displaying 100% incorrectly).

Hm hm hm do any messages or warnings appear if you run the relay job from the command line?

Can you try deleting the following files from your /tmp:

rm /tmp/.lgd-nfy*

and trying again?


Edit: ^ may stop other PWMs on your Pioreactor temporaily FYI

No apparent change I’m afraid:

pioreactor@pio001:~/.pioreactor/plugins $ pio run schedualed_relay
2024-01-08T19:21:10+0000 DEBUG  [schedualed_relay] Init.
2024-01-08T19:21:11+0000 DEBUG  [PWM@GPIO-16] Initialized GPIO-16 using software-timing, initial frequency = 10 hz.
2024-01-08T19:21:11+0000 INFO   [schedualed_relay] Ready.
2024-01-08T19:21:11+0000 DEBUG  [schedualed_relay] schedualed_relay is blocking until disconnected.
2024-01-08T19:22:11+0000 INFO   [schedualed_relay] Turning on relay for 20s.
^C2024-01-08T19:22:45+0000 DEBUG  [schedualed_relay] Exiting caused by signal Interrupt.
2024-01-08T19:22:46+0000 DEBUG  [PWM@GPIO-16] Cleaned up GPIO-16.
2024-01-08T19:22:46+0000 INFO   [schedualed_relay] Disconnected.
2024-01-08T19:22:47+0000 DEBUG  [schedualed_relay] Disconnected successfully from MQTT.

Aborted!
pioreactor@pio001:~/.pioreactor/plugins $ rm /tmp/.lgd-nfy*
pioreactor@pio001:~/.pioreactor/plugins $ pio run schedualed_relay
2024-01-08T19:23:10+0000 DEBUG  [schedualed_relay] Init.
2024-01-08T19:23:10+0000 DEBUG  [PWM@GPIO-16] Initialized GPIO-16 using software-timing, initial frequency = 10 hz.
2024-01-08T19:23:10+0000 INFO   [schedualed_relay] Ready.
2024-01-08T19:23:10+0000 DEBUG  [schedualed_relay] schedualed_relay is blocking until disconnected.
2024-01-08T19:24:10+0000 INFO   [schedualed_relay] Turning on relay for 20s.
^C2024-01-08T19:24:48+0000 DEBUG  [schedualed_relay] Exiting caused by signal Interrupt.
2024-01-08T19:24:48+0000 DEBUG  [PWM@GPIO-16] Cleaned up GPIO-16.
2024-01-08T19:24:49+0000 INFO   [schedualed_relay] Disconnected.
2024-01-08T19:24:50+0000 DEBUG  [schedualed_relay] Disconnected successfully from MQTT.

Aborted!

hmhmhm. Two tests you can try:

  1. Try a power cycle.
  2. To rule out software issues, see if you can measure the voltage on GPIO pin 16 on the 40pin header (which controls PWM output 3). Try your voltmeter between that pin and any ground pin on the header, and see if you can measure 3.3V when the PWM is set to 100%.

I’ll talk to our hardware team about hardware tests you can try too. Will report back tomorrow.

No joy with the power cycle I’m afraid & I’m seeing 0v between GPIO pin 16 (physical 36) and Ground (physical 9).

Are you joining us for today’s Journal Club - I’d better get reading fast, it’s in 20 min.

huh, so that suggests at least a software issue. Let’s bypass our standard software and use something more direct:

Below, don’t worry about the LED bit, we are just using that to control the pin’s state:

from gpiozero import LED
from time import sleep

led = LED(16)

while True:
    led.on()
    sleep(1)
    led.off()
    sleep(1)

Does that flip the GPIO pin measured from your voltmeter?


I’m a bit stumped as to why this pin stopped working, especially after a power-cycle.

I didn’t even check the voltage, as it started a 1s CO2 sparge cycle… So all the hardware seems fine.

Here’s my full code in case I’ve introduced something stupid:

# -*- coding: utf-8 -*-
from __future__ import annotations
from time import sleep

import click
from pioreactor.background_jobs.base import BackgroundJobContrib
from pioreactor.config import config
from pioreactor.hardware import PWM_TO_PIN
from pioreactor.utils.pwm import PWM
from pioreactor.whoami import get_latest_experiment_name
from pioreactor.whoami import get_unit_name
from pioreactor.utils.timing import RepeatedTimer

__plugin_summary__ = "A relay that turns on for X seconds every hour."
__plugin_version__ = "0.0.1"
__plugin_name__ = "Schedualed Relay"
__plugin_author__ = "Cam DP"


class SchedualedRelay(BackgroundJobContrib):

    job_name = "schedualed_relay"
    
    published_settings = {
        'relay_on_for':  {"datatype": "float", "settable": True, "unit": "s"},
    }

    def __init__(self, unit: str, experiment: str) -> None:
        super().__init__(unit=unit, experiment=experiment, plugin_name="schedualed_relay")

        self.relay_on_for = 20 # seconds

        # looks at config.ini/configuration on UI to match
        self.pwm_pin = PWM_TO_PIN[config.get("PWM_reverse", "relay")]

        self.pwm = PWM(
            self.pwm_pin, hz=10, unit=unit, experiment=experiment
        )  # since we also go 100% high or 0% low, we don't need hz, but some systems don't allow a very low hz (like hz=1).
        self.pwm.lock()

        # this is core logic to turn on every hour: run `turn_relay_on_for_period_of_time` every 60*60 seconds
        self.repeated_thread= RepeatedTimer(
            60, # 5 * 60 * 60, # seconds
            self.turn_relay_on_for_period_of_time,
            job_name=self.job_name
        ).start()

    def turn_relay_on_for_period_of_time(self):
        self.logger.info(f"Turning on relay for {self.relay_on_for}s.")
        self.pwm.change_duty_cycle(100)
        sleep(self.relay_on_for)
        self.pwm.change_duty_cycle(0)

    def on_ready_to_sleeping(self) -> None:
        self.repeated_thread.pause()

    def on_sleeping_to_ready(self) -> None:
        self.repeated_thread.unpause()

    def on_disconnected(self) -> None:
        from contextlib import suppress

        with suppress(AttributeError):
            self.repeated_thread.cancel()
        with suppress(AttributeError):        
            self.pwm.clean_up()


@click.command(name="schedualed_relay")
def click_schedualed_relay() -> None:
    """
    Start the relay
    """
    job = SchedualedRelay(
        unit=get_unit_name(),
        experiment=get_latest_experiment_name(),
    )
    job.block_until_disconnected()

need the pwm.start() that we discussed above? That might be it?

I just ran it again (pasted into nano from VS Code to be sure what I sent you was what was in there) and it immediately started a 1s sparge cycle - that has me thoroughly confused as in addition to the issue you mentioned above* it’s supposed to start by waiting 60s…

*I may have put your original suggestion straight into nano rather than VS Code, hence potentially reverting that issue.

I have now added the pwm.start() and am still getting an immediate 1s on off cycle.

# -*- coding: utf-8 -*-
from __future__ import annotations
from time import sleep

import click
from pioreactor.background_jobs.base import BackgroundJobContrib
from pioreactor.config import config
from pioreactor.hardware import PWM_TO_PIN
from pioreactor.utils.pwm import PWM
from pioreactor.whoami import get_latest_experiment_name
from pioreactor.whoami import get_unit_name
from pioreactor.utils.timing import RepeatedTimer

__plugin_summary__ = "A relay that turns on for X seconds every hour."
__plugin_version__ = "0.0.1"
__plugin_name__ = "Schedualed Relay"
__plugin_author__ = "Cam DP"


class SchedualedRelay(BackgroundJobContrib):

    job_name = "schedualed_relay"
    
    published_settings = {
        'relay_on_for':  {"datatype": "float", "settable": True, "unit": "s"},
    }

    def __init__(self, unit: str, experiment: str) -> None:
        super().__init__(unit=unit, experiment=experiment, plugin_name="schedualed_relay")

        self.relay_on_for = 20 # seconds

        # looks at config.ini/configuration on UI to match
        self.pwm_pin = PWM_TO_PIN[config.get("PWM_reverse", "relay")]

        self.pwm = PWM(
         pwm_pin, hz=10, unit=unit, experiment=experiment
        )  # since we also go 100% high or 0% low, we don't need hz, but some systems don't allow a very low hz (like hz=1).
        self.pwm.lock()
        self.pwm.start(0)

        # this is core logic to turn on every hour: run `turn_relay_on_for_period_of_time` every 60*60 seconds
        self.repeated_thread= RepeatedTimer(
            60, # 5 * 60 * 60, # seconds
            self.turn_relay_on_for_period_of_time,
            job_name=self.job_name
        ).start()

    def turn_relay_on_for_period_of_time(self):
        self.logger.info(f"Turning on relay for {self.relay_on_for}s.")
        self.pwm.change_duty_cycle(100)
        sleep(self.relay_on_for)
        self.pwm.change_duty_cycle(0)

    def on_ready_to_sleeping(self) -> None:
        self.repeated_thread.pause()

    def on_sleeping_to_ready(self) -> None:
        self.repeated_thread.unpause()

    def on_disconnected(self) -> None:
        from contextlib import suppress

        with suppress(AttributeError):
            self.repeated_thread.cancel()
        with suppress(AttributeError):        
            self.pwm.clean_up()


@click.command(name="schedualed_relay")
def click_schedualed_relay() -> None:
    """
    Start the relay
    """
    job = SchedualedRelay(
        unit=get_unit_name(),
        experiment=get_latest_experiment_name(),
    )
    job.block_until_disconnected()

hm, can you post the logs from the run (i.e. the output) too?

LOL - exporting the Logs also started a 1s relay cycle…
It also didn’t want to download them. Here’s attempt 2:

!!! that doesn’t seem possible! Check for a Python file in the .pioreactor/plugins folder that is causing that pin to run.

Just power cycling now, but I put the
while True:
led.on()
sleep(1)
file in the same folder as schedualed_relay.py
I’ll delete it now.

Ah yea. Any python in that folder is run when pio is invoked. You can put python files anywhere, not always in the plugins folder.

I should have suggested the following:

  1. Open a python shell with python3,
  2. Paste in
from gpiozero import LED
from time import sleep

led = LED(16)

while True:
    led.on()
    sleep(1)
    led.off()
    sleep(1)

Ah, sorry, now I’ve deleted it, I think we’ve proven the pin’s OK, back running schedualed_relay.py I’m getting ‘pwm_pin’ is not defined.

should be self.pwm_pin

It also works in shell, but running schedualed_relay.py gives:

pioreactor@pio001:~/.pioreactor/plugins $ pio run schedualed_relay
2024-01-08T23:26:31+0000 DEBUG  [schedualed_relay] Init.
Traceback (most recent call last):
  File "/usr/local/bin/pio", line 8, in <module>
    sys.exit(pio())
             ^^^^^
  File "/usr/local/lib/python3.11/dist-packages/click/core.py", line 1157, in __call__
    return self.main(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/dist-packages/click/core.py", line 1078, in main
    rv = self.invoke(ctx)
         ^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/dist-packages/click/core.py", line 1688, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/dist-packages/click/core.py", line 1688, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/dist-packages/click/core.py", line 1434, in invoke
    return ctx.invoke(self.callback, **ctx.params)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/dist-packages/click/core.py", line 783, in invoke
    return __callback(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/pioreactor/.pioreactor/plugins/schedualed_relay.py", line 75, in click_schedualed_relay
    job = SchedualedRelay(
          ^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/dist-packages/pioreactor/background_jobs/base.py", line 98, in __call__
    obj = type.__call__(cls, *args, **kwargs)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/pioreactor/.pioreactor/plugins/schedualed_relay.py", line 37, in __init__
    pwm_pin, hz=10, unit=unit, experiment=experiment
    ^^^^^^^
NameError: name 'pwm_pin' is not defined
2024-01-08T23:26:32+0000 DEBUG  [schedualed_relay] Exiting caused by Python atexit.
2024-01-08T23:26:32+0000 INFO   [schedualed_relay] Disconnected.
2024-01-08T23:26:32+0000 DEBUG  [schedualed_relay] Disconnected successfully from MQTT.

but we have

self.pwm_pin = PWM_TO_PIN[config.get("PWM_reverse", "relay")]

        self.pwm = PWM(
         pwm_pin, hz=10, unit=unit, experiment=experiment
        )