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.
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:
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.
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)
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'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)
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)
Related
I am working on this project with a motion sensor in which I would like to have the monitor turned off when there is no motion after a certain amount of time has passed. But every time there is a motion I would like the timer to reset.
I have the code working for turning the monitor on and off with motion, but how do I add the timer?
Any help will be appreciated. My code:
from gpiozero import MotionSensor
import time
from subprocess import call
pir = MotionSensor(4)
while True:
pir.wait_for_motion()
print("Screen On")
call(["/usr/bin/vcgencmd", "display_power", "1"])
time.sleep(30)
pir.wait_for_no_motion()
print("Screen Off")
call(["/usr/bin/vcgencmd", "display_power", "0"])
time.sleep(1)
As well as wait_for_motion(), gpiozero also provides a variable, motion_detected. The code below sets a variable, startpoint to the current time in seconds since 1/1/1970. It then starts a loop which:
Checks if motion is detected - if so, sets the startpoint variable back to the current time (which, of course, will be different to what it was previously) and turns on the display.
Checks if the startpoint variable is more than 30 seconds before the current time. Because every motion detection resets this variable, we know that there must have been at least 30 seconds since the last motion detection. If so, turns off the display.
startpoint = time.time()
while True:
if pir.motion_detected:
startpoint = time.time()
call(["/usr/bin/vcgencmd", "display_power", "1"])
print("Display on")
elif time.time() > (startpoint+30):
call(["/usr/bin/vcgencmd", "display_power", "0"])
print("Display off")
You could also use threading for this.
from enum import Enum
from gpiozero import MotionSensor
from subprocess import call
from threading import Timer
from time import sleep
from typing import Optional
pir = MotionSensor(4)
timer: Optional[Timer] = None
class MonitorState(Enum):
ON = "1"
OFF = "0"
def set_monitor_state(state: str):
call(["/usr/bin/vcgencmd", "display_power", state.value])
def start_timer():
global timer
if timer:
timer.cancel()
timer = Timer(30, set_monitor_state, (MonitorState.OFF,))
timer.start()
start_timer()
while True:
pir.wait_for_motion()
start_timer()
set_monitor_state(MonitorState.ON)
I'm not sure if the Timer actually counts as being done when the callback returns or before that. In the first case you could run into troubles when the set_monitor_state(MonitorState.ON) get called while the timer runs it's callback on another thread. You might want to use locking in this case.
I have a Raspberry Pi running the Flask framework to make a very application-specific http server. Doing an HTTP GET of a particular page on the Pi server triggers a GPIO action (e.g.: check if button is pressed, LED on, LED off and so on). Because I will poll the /status page to see if the button is pressed at 250ms intervals, I do not want the polling of the /unlock page to use timer.sleep(0.25) to close a relay for 250ms.
I want to set up an action that when called, will wait 250ms, turn the LED off then quietly go away. I tried both threading and sched. Threading works ONCE and throws an error when I pass through the code a second time (raise RuntimeError("threads can only be started once")).
#
# lots of other defines and includes
#
import threading
# set up function to turn de-energize relay (and Green LED)
def LED_Off_Wait():
GPIO.output(GREEN, False)
# set up timed thread
LED_Off = threading.Timer(0.25, LED_Off_Wait)
#
# flask framework page definitions go here
#
# unlock page where we energize and de-energize a lock
#app.route('/unlock')
def thing_unlock():
GPIO.output(GREEN, True) # energize the relay (and green LED)
# time.sleep(0.25) # don't want to do this
# GPIO.output(GREEN, False) #don't want to do this
LED_Off.start() # trigger the non-blocking thread to turn off
# the GPIO pin in 0.25s
# more flask stuff...
response = make_response(render_template('body.html', response='unlocked'))
response.headers['Access-Control-Allow-Origin'] = '*'
return response
I tried sched as well and it does not work for me, although it may be because I'm programming from the inside-out, learning by doing:
import sched
def LED_Off():
GPIO.output(GREEN, False)
# set up scheduled action, as nearly as I can determine from my reading
LED_ON_Delay = sched.scheduler(time.time, time.sleep) # set up scheduler (no sleep!)
LED_ON_Delay.enter( 1, 1, LED_Off,()) # schedule LED_Off for 0.25s in the future
#
# again, more Flask stuff here
#
#app.route('/unlock')
def thing_unlock():
GPIO.output(GREEN, True)
# time.sleep(0.25) # don't want to do this
# GPIO.output(GREEN, False) # don't want to do this
LED_ON_Delay.run() #trigger "non-blocking sleep"
response = make_response(render_template('body.html', response='unlocked'))
response.headers['Access-Control-Allow-Origin'] = '*'
return response
# more Flask stuff...
Bottom Line: how do I spin off a task that will turn the GPIO off after a certain time (or do anything else for that matter) without an error if I try to do it twice?
With the help of a friend, the answer was simpler than I expected: Lambda functions. First, import threading then declare the function that will do what you want (turn off the LED) after a time interval using threading.Timer():
def LED_Off_Wait( wait_time,IO_port ):
threading.Timer(wait_time, lambda: GPIO.output(IO_port, False)).start()
In this case, I need to use it in more than one place so I pass it the delay time and the GPIO pin it needs to act on.
When I need to turn the LED off after a delay inside another function, I call it like this:
#app.route('/unlock')
def prop_unlock():
GPIO.output(GREEN, True)
LED_Off_Wait( 0.5, GREEN )
response = make_response(render_template('body.html', response='unlocked'))
response.headers['Access-Control-Allow-Origin'] = '*'
return response
For completeness, GREEN has been previously defined like this:
GREEN = 24
# Use GPIO pin numbers
GPIO.setmode(GPIO.BCM)
GPIO.setup(GREEN, GPIO.OUT)
Works like a charm.
I have a raspberry pi which I have hooked up with a 4 button keypad. Using the signal stuff from blinker I hooked it up to run some methods.
#sender
while True:
if buttonIsDown == True: signal.send()
#reciever
#signal.connect
def sayHI():
print("1")
time.sleep(10)
print("2")
This works fine, however when I push the button for the second time (Within 10 seconds of the previous button press) it does not fire the method as the thread is paused in the time.sleep(10).
How can I get it to fire the method again while the it is still paused(possibly in another thread)
It is an old question, but still it may be useful for someone else.
You can start a new thread every time the signal is emitted, in that way you will be able to catch all the events as soon as they happen. Remember that in your code, since you have a while True, the signal is never connected to the function, you should have defined them in the opposite order.
Here is a working example, based on your code:
import threading
from blinker import signal
from time import sleep
custom_signal = signal(name='custom')
#custom_signal.connect
def slot(sender):
def say_hello():
print("1")
sleep(10)
print("2")
threading.Thread(target=say_hello).start()
while True:
value = int(input('Press 1 to continue: '))
if value == 1:
custom_signal.send()
else:
break
I am creating a script on my raspberry pi that if you press a button the led on the button need to be flashing till I press the button again.
This is my code:
#!/usr/bin/python
import RPi.GPIO as GPIO
import threading
from time import sleep
GPIO.setmode(GPIO.BCM)
pushbutton = 2
led = 3
GPIO.setup(pushbutton, GPIO.IN)
GPIO.setup(led, GPIO.OUT)
class rpiThread(threading.Thread):
def __init__(self):
self.running = False
super(rpiThread, self).__init__()
def start(self):
self.running = True
super(rpiThread, self).start()
def run(self):
self.running = True
while (self.running == True):
GPIO.output(led, GPIO.HIGH)
print "HIGH"
sleep(0.05)
GPIO.output(led,GPIO.LOW)
print "LOW"
sleep(0.05)
def stop(self):
self.running = False
GPIO.output(led, GPIO.LOW)
def main():
myrpiThread = rpiThread()
pushed = GPIO.input(pushbutton)
try:
while(True):
if(pushed == False):
if(GPIO.input(pushbutton) == False):
sleep(0.5)
if(GPIO.input(pushbutton) == False):
myrpiThread.start()
pushed = True
print "The button is pushed"
else:
if(GPIO.input(pushbutton) == True):
GPIO.output(led, GPIO.LOW)
myrpiThread.stop()
pushed = False
print "The button is not pushed"
except KeyboardInterrupt:
print "QUIT"
main()
Always when I run my script my leds arn't flashing each 0.05 sec. Sometimes it takes 2 seconds before it turns on and sometime it just doesn't flash.
I dont know what I am doing wrong? Can someone please help me to figure out what the problem is?
Is it possible that GPIO pins are not made to use in multithreading?
I followed this tutorial when I needed to use threaded callbacks with GPIO:
how to use interrupts with python on the raspberry pi and rpi gpio. It also has a good discussion in the comments about how to avoid using global variables to pass a value back from the threads.
It would be helpful to know exactly how you have your button wired, and what type of button you are using so that I could answer your problem more specifically.
I wrote the following example code assuming that you have the button wired from GPIO 2 to +3.3v
If you have it wired to ground use "pull_up_down=GPIO.PUD_DOWN" and "GPIO.FALLING"
It also assumes you have a momentary push button. When the button push is detected it flips a global boolean value. That value is checked in the main loop, and the blink(led) function is called if pushed = True.
import RPi.GPIO as GPIO
from time import sleep
GPIO.setmode(GPIO.BCM)
pushbutton = 2
led = 3
GPIO.setup(pushbutton, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
GPIO.setup(led, GPIO.OUT)
pushed = False
# this will run in another thread when the button is pushed
def button_callback(channel):
pushed = not pushed
GPIO.add_event_detect(pushbutton, GPIO.RISING, callback=button_callback)
while True:
if pushed:
blink(led)
def blink(led):
GPIO.output(led, True)
sleep(0.5)
GPIO.output(led, False)
I don't have a raspberry Pi in front of me right now to test this code, but I will check it later. You may have to modify this for your specific set up, but hopefully it gets you in the right direction.
Threading is not working the way I expect it.
I have a working solution where I monitor when a fridge gets opened and closed with a Raspberry Pi and a reed switch (playback of a sound is unpaused and paused). I now wanted to add a timer to do something when the door is kept open too long. I figured starting a thread which would sleep for x seconds before the alerting action would be a good idea. I would kill the thread with a signal when the switch is closed again.
My approach is failing. The CountDown run thread is started but the terminate signal command is executed but has no effect. Additionally, the commands following c.terminate() are not executed. I looked at examples for threading but they seem to be for more complex situations. What am I missing?
The code:
#!/usr/bin/env python2.7
import threading, subprocess, sys, time, syslog
import RPi.GPIO as GPIO
sound = "/home/pi/sounds/fridge_link.mp3" # sound to play while switch is open
cmd = ['mplayer', '-nolirc', '-noconsolecontrols', '-slave', '-quiet', sound] # command to play sound
lim = 10 # seconds until warning
# thread for countdown (should be interruptable)
# based on http://chimera.labs.oreilly.com/books/1230000000393/ch12.html#_solution_197
class CountdownTask:
def __init__(self):
self._running = True
def terminate(self):
self._running = False
print("thread killed")
def run(self, n):
print("start timer")
time.sleep(n)
## action when timer isup
print("timer ended")
c = CountdownTask()
t = threading.Thread(target=c.run, args=(lim,))
t.daemon = True
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE)
p.stdin.write('\npausing_keep pause\n')
REED = 27 # data pin of reed sensor (in)
# GPIO setup
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
GPIO.setup(REED,GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
def edge(channel):
if GPIO.input(REED):
print("detect close")
c.terminate()
p.stdin.write('\npause\n')
pass
else:
print("detect open")
t.start()
p.stdin.write('\npausing_toggle pause\n')
def main():
GPIO.add_event_detect(REED, GPIO.BOTH,callback=edge,bouncetime=1000)
while True:
time.sleep(0.2)
pass
#------------------------------------------------------------
if __name__ == "__main__": main()
New version:
#!/usr/bin/env python2.7
import threading, subprocess, sys, time, syslog
import RPi.GPIO as GPIO
sound = "/home/pi/sounds/fridge_link.mp3" # sound to play while switch is open
cmd = ['mplayer', '-nolirc', '-noconsolecontrols', '-slave', '-quiet', sound] # command to play sound
lim = 10 # seconds until warning
# thread for countdown (should be interruptable)
class CountdownTask:
global dooropen
def __init__(self):
self._running = True
def terminate(self):
self._running = False
print("thread killed")
def run(self, n):
while self._running and dooropen == False:
time.sleep(0.2)
pass
while self._running and dooropen:
print("start timer")
time.sleep(n)
## action when timer isup
print("timer ended")
c = CountdownTask()
t = threading.Thread(target=c.run, args=(lim,))
t.daemon = True
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE)
p.stdin.write('\npausing_keep pause\n')
REED = 27 # data pin of reed sensor (in)
# GPIO setup
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
GPIO.setup(REED,GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
dooropen = False # assuming door's closed when starting
def edge(channel):
global dooropen
if GPIO.input(REED): # * no longer reached
if dooropen == False: # catch fridge compressor spike
print("false close alert")
return
p.stdin.write('\npause\n')
dooropen = False
pass
else:
print("detect open")
if dooropen == True:
print("false open alert")
return
p.stdin.write('\npausing_toggle pause\n')
dooropen = True
def main():
GPIO.add_event_detect(REED, GPIO.BOTH,callback=edge,bouncetime=1000)
t.start()
while True:
time.sleep(0.2)
pass
#------------------------------------------------------------
if __name__ == "__main__": main()
Adjusted section, working now:
def run(self, n):
while self._running and dooropen == False:
time.sleep(0.2)
pass
while self._running and dooropen:
time.sleep(n)
if dooropen:
## action when timer isup
Your programmed thread termination mechanism via self._running is not working, because you are not polling/checking the state of self._running in the run() method of the tread (that is actually done in the example you were referring to).
Regular polling adds complexity that is not necessary here. You should structure your logic in a different way, which is simple and reliable. Example code:
import threading
import time
dooropen = True
def warnafter(timeout):
time.sleep(timeout)
if dooropen:
print("Warning!")
t = threading.Thread(target=warnafter, args=(2,))
t.start()
time.sleep(1)
dooropen = False
t.join()
Change time.sleep(1) to time.sleep(3) and a warning is printed. Why does this work, and how does this translate to your use case?
First of all, let's give things names. You have your main thread and the "warn thread". These are the cornerstones of the architecture in my example code:
Have a shared state between the two threads indicating whether the door is open or not, translating into the fact whether a warning should be issued or not. Let's call this state dooropen, and it can be True or False. It is a variable accessible in both, the scope of your main thread as well as in the scope your warn thread has access to. That is, it lives in shared memory.
This is your convention: dooropen is only written from the main thread. The warn thread only reads it.
Spawn your warn thread whenever you think it is the right time. Make it sleep (the exact sleep time can be unreliable, especially on embedded systems).
The crucial part: right before raising an alarm in the warn thread, make it check the dooropen state. If not dooropen, simply do not raise the alarm!
Do you see the two different paradigms?
Your paradigm is to plant an armed bomb, programmed to explode after a given amount of time. This bomb does not talk back to you anymore. Your hope is that you are able to defuse/destroy the bomb before it can explode, if you do not need it to explode anymore.
The paradigm I am proposing ships a bomb that actually is not armed until it needs to be. At the point in time when your bomb would simply explode, this one asks if it really should do so, and only then arms itself and explodes.
Given the latter paradigm, if the warn thread is told to not perform its action, it silently quits, on its own. The concept of "terminating the thread from the outside" is not needed!
In practice, you would need a little more advanced concept, where a warn thread has its own active switch. That is, your main thread can deactivate single warn threads in a controlled fashion. See this example:
import threading
import time
class WarnThread(threading.Thread):
def __init__(self, timeout, name):
threading.Thread.__init__(self)
self._timeout = timeout
self.active = True
self.name = name
self.start()
def run(self):
self._warnafter()
def _warnafter(self):
time.sleep(self._timeout)
if self.active:
print("WarnThread %s: Warning after timeout" % self.name)
ws = [WarnThread(2, i) for i in range(5)]
# Simulate spending some time doing other things,
# such as responding to external events.
time.sleep(1)
# Selectively deactivate some of the warn threads.
ws[0].active = False
ws[2].active = False
for w in ws:
w.join()
Output:
WarnThread 4: Warning after timeout
WarnThread 1: Warning after timeout
WarnThread 3: Warning after timeout