0

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!!

Adrian Kurzeja
  • 797
  • 1
  • 11
  • 27
  • The problem with `t = Timer(5, set_high(GPIO)` is that it's calling `set_high(GPIO)` right now, then passing the result of that call (which is `None`) as the target function to `Timer`. And you can't call `None` as a function. But your fix to call `t = Timer(5, set_high, args=[GPIO])` solves that. If you get a different error, please edit your question to be about the problem you want help with, instead of being about the problem you already solved and only mentioning your actual problem two thirds of the way down. – abarnert Jul 25 '18 at 21:01

1 Answers1

1

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)
abarnert
  • 354,177
  • 51
  • 601
  • 671
  • Ohhh gosh!!! You are right! I'm cleaningup before timer ends... Thank you! This device will be connected via websocket, on mobile user clicks "DOIT!" then request goes to server and server sends message to raspberry (websocket), when raspbery get message it run this script (set GPIO.LOW), and it takes 15 sec, to complete "task", after this script sets GPIO.HIGH and sends back info to server about success (and server to mobile). – Adrian Kurzeja Jul 25 '18 at 21:25
  • I thought about multithreating becouse what if another person sends click "DOIT!"? Raspberry should react somehow I think (not sure)? lets say "Now i'm bussy" and in the same time it should still do script. Or maybe sleep() won't destroy it at all? – Adrian Kurzeja Jul 25 '18 at 21:25
  • Heh... sorry for spam... but now I found out that I should block sending requests to raspberry directly on server... and block it untill raspberry answers with succes or error. Now I have to confirm on raspberry that "double" request won't hurt it just to be sure. Thanks again for help ;) – Adrian Kurzeja Jul 25 '18 at 21:28
  • @AdrianKurzeja As I said, once you write the code that actually does some websocket stuff, you'll have to work out how to integrate the timer into that. If you're using threaded I/O, you probably want a threaded timer, and just have the main thread wait by `join`ing your main I/O thread. If you're using `asyncio`, you probably want to schedule a timer in that asyncio event loop, and you'll just do the cleanup after the event loop. And so on. – abarnert Jul 25 '18 at 21:48