0

I'm trying to use python to turn on some RGB lights after motion is detected. My code is below but I can't get the thread to end and switch off the LEDS. All that happening is I get stuck after keyboard interrupt.

Essentially when I run the code it works to detect movement and switch the lights on but then when I try to end the program with a keyboard interupt exception either the program hangs and doesn't stop or it stops but the LEDs don't switch off and stay lit. I've looked at various pages about how to stop a thread running but none of it has helped. Below are the articles I've looked at. I think my issue is that the thread running the lighting pattern loop isn't stopping so when I try to turn each LED off its turned back on immediately but I'm not sure.

I can't get the thread to stop any way I've tried. Any help would be much appreciated.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Wed Jan  1 10:10:25 2020

@author: thomas
"""

import RPi.GPIO as GPIO
import time
import subprocess
import argparse
from neopixel import *
import datetime as dt
from threading import Thread
from threading import enumerate
from threading import Event as EV
import sys

global stop

GPIO.setmode(GPIO.BCM)

# LED strip configuration:

LED_COUNT      = 288      # Number of LED pixels.
LED_PIN        = 18      # GPIO pin connected to the pixels (must support PWM!).
LED_FREQ_HZ    = 800000  # LED signal frequency in hertz (usually 800khz)
LED_DMA        = 10      # DMA channel to use for generating signal (try 10)
LED_BRIGHTNESS = 255     # Set to 0 for darkest and 255 for brightest
LED_INVERT     = False   # True to invert the signal (when using NPN transistor level shift)
LED_CHANNEL    = 0
LED_STRIP      = ws.SK6812_STRIP_RGBW
#LED_STRIP      = ws.SK6812W_STRIP

# Define functions which animate LEDs in various ways.
def wheel(pos):
        """Generate rainbow colors across 0-255 positions."""
        if pos < 85:
                return Color(pos * 3, 255 - pos * 3, 0)
        elif pos < 170:
                pos -= 85
                return Color(255 - pos * 3, 0, pos * 3)
        else:
                pos -= 170
                return Color(0, pos * 3, 255 - pos * 3)

def colorWipe(strip, color, wait_ms=20):
        """Wipe color across display a pixel at a time."""
        for i in range(strip.numPixels()):
                strip.setPixelColor(i, color)
                strip.show()
                time.sleep(wait_ms/1000.0)

def rainbowCycle(strip, wait_ms=20, iterations=5):
        """Draw rainbow that uniformly distributes itself across all pixels."""
        while not stop:
            for j in range(256*iterations):
                    for i in range(strip.numPixels()):
                            strip.setPixelColor(i, wheel(((i * 256 // strip.numPixels()) + j) & 255))
                    strip.show()
                    time.sleep(wait_ms/1000.0)

class RainbowLights(Thread):
    def __init__(self, name="RainbowLights"):
        self._stopevent = EV()
        self.sleeppreiod = 1.0

        Thread.__init__(self, name=name)

    def run(self):
        while not self._stopevent.isSet():
            rainbowCycle(strip)
            self._stopevent.wait(self._sleepperiod)


if __name__ == '__main__':
        parser = argparse.ArgumentParser()
        parser.add_argument('-c', '--clear', action='store_true', help='clear the display on exit')
        args = parser.parse_args()

        # Create NeoPixel object with appropriate configuration.
        strip = Adafruit_NeoPixel(LED_COUNT, LED_PIN, LED_FREQ_HZ, LED_DMA, LED_INVERT, LED_BRIGHTNESS, LED_CHANNEL, LED_STRIP)
        # Intialize the library (must be called once before other functions).
        strip.begin()

        print ('Press Ctrl-C to quit.')
        if not args.clear:
                print('Use "-c" argument to clear LEDs on exit')

        GPIO.setup(22, GPIO.IN) #PIR

        rta = "none"
        time1 = "none"
        stop = False
        RainbowLights = RainbowLights()

        try:
            time.sleep(2) # to stabilize sensor
            while True:
                trigger = GPIO.input(22)
                if trigger == 1:
                    print("Motion Detected...")
                    if rta == "running":
                        print("Lights Already On")
                    else:
                        RainbowLights.start()
                        rta = "running"
                        time1 = dt.datetime.now()
                    time.sleep(5) #to avoid multiple detection
                elif trigger == 0:
                    print("No Motion...")
                    print("Lights turned on at " + str(time1))
                time.sleep(0.1) #loop delay, should be less than detection delay

        except KeyboardInterrupt:
                 RainbowLights._stopevent.set()
                 stop = True
                 time.sleep(5)
                 if args.clear:
                     colorWipe(strip, Color(0,0,0), 10)
                 GPIO.cleanup()
rohtua
  • 165
  • 1
  • 11
  • 1
    Did you forget parentheses after `rainbowCycle`? Inside the loop of `run`, that is. – cadolphs Jan 02 '20 at 18:49
  • You could use a global shared variable and have the infinite loop in the thread periodically check it and see if it has a value signalling it to stop (and break out of the loop). – martineau Jan 02 '20 at 19:20
  • Threading doesn't look right either. There is no creation of an instance of `RainbowLights`. The line `RainbowLights.start()` should raise an exception. – Mark Tolonen Jan 03 '20 at 08:57
  • @Lagerbaer - Hi thanks for the suggestions. I've edited the original post with the edits I've made to the code. I'm still stuck in the same place :( . When I press Ctrl-C it just hangs and never finishes. From what I can see (the lights still cycle through the rainbow pattern) the thread is still running and doesn't stop until I stop the program with Ctrl-Z – rohtua Jan 03 '20 at 16:28

1 Answers1

2

Reduced to a minimal example so it can run without hardware. Comments added for fixes. The main problem was stop was only checked after 256*iterations*wait_ms/1000.0 or 25.6 seconds, so stopping appeared unresponsive. A few other bugs as well. Global stop is sufficient to end the thread so the Event wasn't needed.

import time
import argparse
from threading import Thread
import sys

global stop

def rainbowCycle(strip, wait_ms=20, iterations=5):
    """Draw rainbow that uniformly distributes itself across all pixels."""
    while not stop:
        for j in range(256*iterations):
            if stop: break                # add stop check here for responsiveness.
            print(f'rainbowCycle')
            time.sleep(wait_ms/1000.0)
    print('stopping...')

class RainbowLights(Thread):
    def __init__(self, name="RainbowLights"):
        print('__init__')
        Thread.__init__(self, name=name)

    def run(self):
        print('run')
        rainbowCycle(strip)
        print('stopped')

if __name__ == '__main__':
    strip = None
    print('Press Ctrl-C to quit.')
    rta = "none"
    stop = False
    rainbow_lights = RainbowLights()   # renamed variable so it doesn't replace the class.

    try:
        time.sleep(2) # to stabilize sensor
        while True:
            trigger = 1      # pretend some motion
            if trigger == 1:
                print("Motion Detected...")
                if rta == "running":
                    print("Lights Already On")
                else:
                    rainbow_lights.start()
                    rta = "running"
                time.sleep(5) #to avoid multiple detection
            elif trigger == 0:
                print("No Motion...")
            time.sleep(0.1) #loop delay, should be less than detection delay

    except KeyboardInterrupt:
        print('KeyboardInterrupt')
        print('stop')
        stop = True
Press Ctrl-C to quit.
__init__
Motion Detected...
run
rainbowCycle
rainbowCycle
rainbowCycle
rainbowCycle
rainbowCycle
rainbowCycle
rainbowCycle
rainbowCycle
rainbowCycle
rainbowCycle
rainbowCycle
rainbowCycle
KeyboardInterrupt
stop
stopping...
stopped
Mark Tolonen
  • 166,664
  • 26
  • 169
  • 251