3

I come here after after trying in all directions to come out of this problem without any results, unfortunately.

What I have in mind:

I need a class, named Led, that in the constructor simply accept a GPIO pin and offer method for:

  • Light On
  • Light Off
  • Blinking

What I do:

I have build this class in this way:

import RPi.GPIO as GPIO
import time
import threading
from threading import Thread


class Led(Thread):

    def __init__(self, led_pin):
        Thread.__init__(self)
        self.pin_stop = threading.Event()
        self.__led_pin = led_pin
        GPIO.setmode(GPIO.BCM)
        GPIO.setup(self.__led_pin, GPIO.OUT)

    def low(self, pin):
        GPIO.setup(pin, GPIO.OUT)
        GPIO.output(pin, GPIO.LOW)

    def blink(self, time_on=0.050, time_off=1):
        pin = threading.Thread(name='ledblink',target=self.__blink_pin, args=(time_on, time_off, self.pin_stop))
        pin.start()

    def __blink_pin(self, time_on, time_off, pin_stop):
        while not pin_stop.is_set():
            GPIO.output(self.__led_pin, GPIO.HIGH)
            time.sleep(time_on)
            GPIO.output(self.__led_pin, GPIO.LOW)
            time.sleep(time_off)

    def __stop(self):
        self.pin_stop.set()


    def reset(self):
        GPIO.cleanup()

    def off(self):
        self.__stop()

    def on(self):
        self.__stop()
        GPIO.output(self.__led_pin, GPIO.LOW)
        GPIO.output(self.__led_pin, GPIO.HIGH)

where the blink method is responsible to indefinitely blink the led until an Off or On method call.

And run this simple code:

from classes.leds import Led
import time
from random import randint

Led16 = Led(16)

def main():
    while True:
        if (randint(0, 1) == 1):
            Led16.blink()
        else:
            Led16.off()
        time.sleep(2)


if __name__ == "__main__":
    main()

What happens:

The Led object seem to spawn a new thread every time that a method is called with the effect that GPIO line become shared between multiple threads.

What is my wish:

I want to keep blinking led asynchronous (obviously) and have the control on Led16() object status, maybe without creating new threads each time I cal its method, but at reached this point I am bit confused.

Thank to help me understanding how to reach this goal.

4 Answers4

2

You are creating lots of threads because your blink() creates a new thread every time it is called and the old one isn't stopped.

I suppose there are a couple of options with the thread:

  1. Create the thread just once - for example in __init__() - and it runs continuously on the blinking time intervals (i.e. sleeping most of the time) reading an instance variable and setting the LED correspondingly. To change the led state, the blink(), on() and off() control the led by setting this instance variable to on/off/blinking.

  2. In the blink routine, if the thread is already running either don't create a new thread, or stop the old thread (and wait for it to finish) and then start a new one.

Things you will have to handle are that you want the behaviour to be that:

  • If the led is off then the led turns on as soon as on() or blink() is called
  • If the led is blinking and blink() is called again the blinking on/off sequence is not disturbed
  • If blinking and off() is called I would want the on-cycle if it has started to run to completion, i.e. the led should not be turned off immediately because that might be a very short flash which would look odd.

The catch with creating a new thread is waiting for the old one to finish, and it just feels simplest to create the thread just once in __init__() and have it running continuously. When the led is on or off, the time period is shortened (to value FAST_CYCLE) so that when the led is turned off or on it reacts quickly because the sleep() is for a short time.

Some other points about your code:

  • I don't think you need to make your class inherit from Thread - you are creating a new thread in the pin=... line.
  • If you add comments as you write the code, usually makes it easier to understand what is going on when reading the code.
  • If you keep a reference to the thread (i.e. self.pin = threading.Thread not pin = threading.Thread) then in the reset() you can use join() to make sure it has exited before you continue
  • As the blink time periods can change and the thread has to use the latest values, use self to read them every time rather than pass them as arguments to the __blink_pin() routine, and if doing that you might as well use self to get the pin_stop semaphore too.

Something like this (untested):

import RPi.GPIO as GPIO
import time
import threading
from threading import Thread

class Led(object):

    LED_OFF = 0
    LED_ON = 1
    LED_FLASHING = 2

    # the short time sleep to use when the led is on or off to ensure the led responds quickly to changes to blinking
    FAST_CYCLE = 0.05

    def __init__(self, led_pin):
        # create the semaphore used to make thread exit
        self.pin_stop = threading.Event()
        # the pin for the LED
        self.__led_pin = led_pin
        # initialise the pin and turn the led off
        GPIO.setmode(GPIO.BCM)
        GPIO.setup(self.__led_pin, GPIO.OUT)
        # the mode for the led - off/on/flashing
        self.__ledmode = Led.LED_OFF
        # make sure the LED is off (this also initialises the times for the thread)
        self.off()
        # create the thread, keep a reference to it for when we need to exit
        self.__thread = threading.Thread(name='ledblink',target=self.__blink_pin)
        # start the thread
        self.__thread.start()

    def blink(self, time_on=0.050, time_off=1):
        # blinking will start at the next first period
        # because turning the led on now might look funny because we don't know
        # when the next first period will start - the blink routine does all the
        # timing so that will 'just work'
        self.__ledmode = Led.LED_FLASHING
        self.__time_on = time_on
        self.__time_off = time_off

    def off(self):
        self.__ledmode = LED_OFF
        # set the cycle times short so changes to ledmode are picked up quickly
        self.__time_on = Led.FAST_CYCLE
        self.__time_off = Led.FAST_CYCLE
        # could turn the LED off immediately, might make for a short flicker on if was blinking

    def on(self):
        self.__ledmode = LED_ON
        # set the cycle times short so changes to ledmode are picked up quickly
        self.__time_on = Led.FAST_CYCLE
        self.__time_off = Led.FAST_CYCLE
        # could turn the LED on immediately, might make for a short flicker off if was blinking

    def reset(self):
        # set the semaphore so the thread will exit after sleep has completed
        self.pin_stop.set()
        # wait for the thread to exit
        self.__thread.join()
        # now clean up the GPIO
        GPIO.cleanup()

    ############################################################################
    # below here are private methods
    def __turnledon(self, pin):
        GPIO.output(pin, GPIO.LOW)

    def __turnledoff(self, pin):
        GPIO.output(pin, GPIO.HIGH)

    # this does all the work
    # If blinking, there are two sleeps in each loop
    # if on or off, there is only one sleep to ensure quick response to blink()
    def __blink_pin(self):
        while not self.pin_stop.is_set():
            # the first period is when the LED will be on if blinking
            if self.__ledmode == Led.LED_ON or self.__ledmode == Led.LED_FLASHING: 
                self.__turnledon()
            else:
                self.__turnledoff()
            # this is the first sleep - the 'on' time when blinking
            time.sleep(self.__time_on)
            # only if blinking, turn led off and do a second sleep for the off time
            if self.__ledmode == Led.LED_FLASHING:
                self.__turnledoff()
                # do an extra check that the stop semaphore hasn't been set before the off-time sleep
                if not self.pin_stop.is_set():
                    # this is the second sleep - off time when blinking
                    time.sleep(self.__time_off)
1

For who need this in the future I have do some little adjustment to the proposed code and seem to work fine as expected.

Again thanks to @barny

import RPi.GPIO as GPIO
import time
import threading

class Led(object):

    LED_OFF = 0
    LED_ON = 1
    LED_FLASHING = 2

    # the short time sleep to use when the led is on or off to ensure the led responds quickly to changes to blinking
    FAST_CYCLE = 0.05

    def __init__(self, led_pin):
        # create the semaphore used to make thread exit
        self.pin_stop = threading.Event()
        # the pin for the LED
        self.__led_pin = led_pin
        # initialise the pin and turn the led off
        GPIO.setmode(GPIO.BCM)
        GPIO.setup(self.__led_pin, GPIO.OUT)
        # the mode for the led - off/on/flashing
        self.__ledmode = Led.LED_OFF
        # make sure the LED is off (this also initialises the times for the thread)
        self.off()
        # create the thread, keep a reference to it for when we need to exit
        self.__thread = threading.Thread(name='ledblink',target=self.__blink_pin)
        # start the thread
        self.__thread.start()

    def blink(self, time_on=0.050, time_off=1):
        # blinking will start at the next first period
        # because turning the led on now might look funny because we don't know
        # when the next first period will start - the blink routine does all the
        # timing so that will 'just work'
        self.__ledmode = Led.LED_FLASHING
        self.__time_on = time_on
        self.__time_off = time_off

    def off(self):
        self.__ledmode = self.LED_OFF
        # set the cycle times short so changes to ledmode are picked up quickly
        self.__time_on = Led.FAST_CYCLE
        self.__time_off = Led.FAST_CYCLE
        # could turn the LED off immediately, might make for a short flicker on if was blinking

    def on(self):
        self.__ledmode = self.LED_ON
        # set the cycle times short so changes to ledmode are picked up quickly
        self.__time_on = Led.FAST_CYCLE
        self.__time_off = Led.FAST_CYCLE
        # could turn the LED on immediately, might make for a short flicker off if was blinking

    def reset(self):
        # set the semaphore so the thread will exit after sleep has completed
        self.pin_stop.set()
        # wait for the thread to exit
        self.__thread.join()
        # now clean up the GPIO
        GPIO.cleanup()
  • I would very much like to be able to use this as it is exactly what I need but the code above gives me errors, I'm not sure if I'm calling it incorrectly. I took Alessandro's code and added led=Led(24) at the end as my call but I get an error that says 'int' object is not callable, I also get an error on __blink_pin - any help would be appreciated. – Chris Mar 01 '18 at 22:07
1

I'm not sure it will be helpfull, but I came up with that (using gpiozero)

from gpiozero import LED
import time
import threading

class LEDplus():
    def __init__(self,pinnumber):
        self.led = LED(pinnumber)
        self.__loop = True
        self.__threading = threading.Thread(target=self.__blink)


    def on(self,):
        self.__loop = False
        self.maybejoin()
        self.led.on()

    def off(self, ):
        self.__loop = False
        self.maybejoin()
        self.led.off()

    def maybejoin(self,):
        if self.__threading.isAlive():
            self.__threading.join()

    def blink(self, pitch):
        self.__threading = threading.Thread(target=self.__blink, args=(pitch, ))
        self.__threading.start()

    def __blink(self, pitch=.25):
        self.__loop = True
        while self.__loop:
            self.led.toggle()
            time.sleep(pitch/2)
        self.led.off()

green = LEDplus(18)
green.blink(1)
Laurent R
  • 783
  • 1
  • 6
  • 25
1

On the answer of Alessandro Mendolia, just missing the private methods of the class. Added below with some fixes. The __turnledon() does not need an argument - it can access the self.__led_pin already stored in initialization.

############################################################################
# below here are private methods
def __turnledon(self):
    GPIO.output(self.__led_pin, GPIO.LOW)

def __turnledoff(self):
    GPIO.output(self.__led_pin , GPIO.HIGH)

# this does all the work
# If blinking, there are two sleeps in each loop
# if on or off, there is only one sleep to ensure quick response to blink()
def __blink_pin(self):
    while not self.pin_stop.is_set():
        # the first period is when the LED will be on if blinking
        if self.__ledmode == BlinkerLed.LED_ON or self.__ledmode == BlinkerLed.LED_FLASHING: 
            self.__turnledon()
        else:
            self.__turnledoff()
        # this is the first sleep - the 'on' time when blinking
        time.sleep(self.__time_on)
        # only if blinking, turn led off and do a second sleep for the off time
        if self.__ledmode == BlinkerLed.LED_FLASHING:
            self.__turnledoff()
            # do an extra check that the stop semaphore hasn't been set before the off-time sleep
            if not self.pin_stop.is_set():
                # this is the second sleep - off time when blinking
                time.sleep(self.__time_off)
Adrian Mole
  • 49,934
  • 160
  • 51
  • 83
ruibar
  • 21
  • 1