Search code examples
pythonmultithreadingtimerraspberry-pigpio

Threading Timer - NoneType error on raspberry pi - passing variable to another thread


I'm junior python dev, mainly backend Django. One of my first tasks in work to write program on raspberry pi that set state to LOW and after 5 sec to HIGH.

Seems not a problem but.... I think the proper way of waiting 5 sec to change to HIGH is not via time.sleep() but via threading.Timer (I found it on stackoverflow)... It will be connected via web socket so I think one thread must listen and another must change states

My problem is half pythonic, half raspberrian... this is my code:

import RPi.GPIO as GPIO
from threading import Timer
from time import sleep

pin = 22
GPIO.setmode(GPIO.BCM)
GPIO.setup(pin, GPIO.OUT)

def set_high():
    GPIO.output(pin, GPIO.HIGH)

def web_socket_loop():
    GPIO.output(pin, GPIO.LOW)
    t = Timer(5, set_high(GPIO))
    t.start()


web_socket_loop()

GPIO.cleanup()

When I run this code states are changing, but after few sec I also got an error:

Exception in thread Thread-1:
Traceback (most recent call last):
  File "/usr/local/lib/python3.7/threading.py", line 917, in _bootstrap_inner
    self.run()
  File "/usr/local/lib/python3.7/threading.py", line 1158, in run
    self.function(*self.args, **self.kwargs)
TypeError: 'NoneType' object is not callable

I found on stack that I have to do something like this: (but it does not work properly): ;/

t = Timer(5, set_high, args=[GPIO])

And it does repair error about NoneType... but I got another error:

Exception in thread Thread-1:
    Traceback (most recent call last):
      File "/usr/local/lib/python3.7/threading.py", line 917, in _bootstrap_inner
        self.run()
      File "/usr/local/lib/python3.7/threading.py", line 1158, in run
        self.function(*self.args, **self.kwargs)
RuntimeError: Please set pin numbering mode using GPIO.setmode(GPIO.BOARD) or GPIO.setmode(GPIO.BCM)

I probably know why this error is but have no idea how to deal with it. I think that on another thread RPi.GPIO is not configured (GPIO.setmode(GPIO.BCM), GPIO.setup(pin, GPIO.OUT))

Thats why I pass GPIO as a variable to the function set_high(GPIO), without it on another thread it thinks that RPi is not configured... passing this variable is cool but... I got an NoneType error... ;/

When I try second way with Timer(5, set_high, args=[GPIO]) the NoneType error disappears but another occurs (RuntimeError with info about pin setup)... this is the same error which I get when I don't pass GPIO to the function like this:

def set_high(#i_dont_pass_GPIO):
    #things
    pass

What is the proper way of passing the GPIO variable to another thread? So that it will recognize this variable properly and set pin 22 to HIGH.

Or maybe you recommend to try do it another way? Maybe sleep is better? But it will be connected via web socket so I think one must listen and another must change states?

I appreciate any help!!


Solution

  • Your first problem, and your third one, really aren't relevant, because you've already correctly solved them.

    When you call Timer(5, set_high(GPIO)), that calls set_high(GPIO) right now, and passes whatever it returns as the target function for the Timer to call. Since it returns None, 5 seconds later, your Timer tries to call None as a function, hence the TypeError.

    The fix is exactly what you already did:

    Timer(5, set_high, [GPIO])
    

    Now, once you've fixed that, think through the flow of control you're asking for.

    First, you do this:

    GPIO.setmode(GPIO.BCM)
    GPIO.setup(pin, GPIO.OUT)
    

    Then you call web_socket_loop, and it does this:

    GPIO.output(pin, GPIO.LOW)
    

    And then it creates and starts a Timer, and returns. And you do this:

    GPIO.cleanup()
    

    Then, 5 seconds later, the timer does off and calls your set_high function, which does this:

    GPIO.output(pin, GPIO.HIGH)
    

    You can't set the pin high after you've cleaned up the GPIO.

    The error message is a bit misleading:

    RuntimeError: Please set pin numbering mode using GPIO.setmode(GPIO.BOARD) or GPIO.setmode(GPIO.BCM)
    

    This is because the state post-cleanup is the same as the state pre-initialization, so it assumes your problem is that you haven't initialized things by calling setmode yet.


    How can you fix that? Your main thread shouldn't try to clean up until you're done.

    Normally, you have other stuff for the main thread to do. That's why you use a Timer, or a Thread, or some other form of concurrency. But when you do that, you need to give your main thread some way to wait on that background work to finish, or you need to find some way to chain things up so that the cleanup happens only after the last thread is done using GPIO.

    When you add the code that deals with communicating over a websocket, you'll need to do one of those things. But it's impossible to show you what you should do without knowing how you plan to organize that code.

    Meanwhile, you really have nothing else to do but wait for 5 seconds, so you don't need any concurrency; just sleep:

    def web_socket_loop():
        GPIO.output(pin, GPIO.LOW)
        time.sleep(5)
        set_high(GPIO)