More elegant way to check for an event/trigger periodically? - python

I have a passive infrared sensor and I wanted to turn off and on my display based on motion. E.g. if there is no motion for 5 minutes, then the display should turn off to save power. However if there is motion don't turn off the display, or turn it back on. (Don't ask why isn't a screensaver good for this. The device I'm making won't have any keyboard or mouse. It only will be a standalone display.)
My idea was to create two threads, a producer, and a consumer. The producer thread (the PIR sensor) puts a message into a queue, which the consumer (which controls the display) reads. This way I can send signals from one to another.
I have a fully functional code below (with some explanation), which completes the previously described. My question is that is there some way to achieve this in a more elegant way? What do you think of my approach, is it okay, is it hackish?
#!/usr/bin/env python
import Queue
from threading import Thread
import RPi.GPIO as gpio
import time
import os
import sys
class PIRSensor:
# PIR sensor's states
current_state = 0
previous_state = 0
def __init__(self, pir_pin, timeout):
# PIR GPIO pin
self.pir_pin = pir_pin
# Timeout between motion detections
self.timeout = timeout
def setup(self):
gpio.setmode(gpio.BCM)
gpio.setup(self.pir_pin, gpio.IN)
# Wait for the PIR sensor to settle
# (loop until PIR output is 0)
while gpio.input(self.pir_pin) == 1:
self.current_state = 0
def report_motion(self, queue):
try:
self.setup()
while True:
self.current_state = gpio.input(self.pir_pin)
if self.current_state == 1 and self.previous_state == 0:
# PIR sensor is triggered
queue.put(True)
# Record previous state
self.previous_state = 1
elif self.current_state == 1 and self.previous_state == 1:
# Feed the queue since there is still motion
queue.put(True)
elif self.current_state == 0 and self.previous_state == 1:
# PIR sensor has returned to ready state
self.previous_state = 0
time.sleep(self.timeout)
except KeyboardInterrupt:
raise
class DisplayControl:
# Display's status
display_on = True
def __init__(self, timeout):
self.timeout = timeout
def turn_off(self):
# Turn off the display
if self.display_on:
os.system("/opt/vc/bin/tvservice -o > /dev/null 2>&1")
self.display_on = False
def turn_on(self):
# Turn on the display
if not self.display_on:
os.system("{ /opt/vc/bin/tvservice -p && chvt 9 && chvt 7 ; } > /dev/null 2>&1")
self.display_on = True
def check_motion(self, queue):
try:
while True:
try:
motion = queue.get(True, self.timeout)
if motion:
self.turn_on()
except Queue.Empty:
self.turn_off()
except KeyboardInterrupt:
raise
if __name__ == "__main__":
try:
pir_sensor = PIRSensor(7, 0.25)
display_control = DisplayControl(300)
queue = Queue.Queue()
producer = Thread(target=pir_sensor.report_motion, args=(queue,))
consumer = Thread(target=display_control.check_motion, args=(queue,))
producer.daemon = True
consumer.daemon = True
producer.start()
consumer.start()
while True:
time.sleep(0.1)
except KeyboardInterrupt:
display_control.turn_on()
# Reset GPIO settings
gpio.cleanup()
sys.exit(0)
The producer thread runs a function (report_motion) of a PIRSensor class instance. The PIRSensor class reads the state of a passive infrared sensor four times per second, and whenever it senses motion puts a message into a queue.
The consumer thread runs a function of (check_motion) of a DisplayControl class instance. It reads the previously mentioned queue in blocking mode with a given timeout. The following can happen:
If the display is on and there is no message in the queue for a given
time, aka the timeout expires, the consumer thread will power off the
display.
If the display is off and a message comes, the thread will
power on the display.

I think the idea is good. The only question I have about your implementation is why have both the consumer and producer in child threads? You could just keep the consumer in the main thread, and then there'd be no need to have this meaningless loop in your main thread.
while True:
time.sleep(0.1)
which is just wasting CPU cycles. Instead you could just call display_motion.check_motion(queue) directly.

I think it is a good solution. The reason being that you have separated the concerns for the different classes. One class handles the PIR sensor. One handles the display. You glue them together by a queue today, that's one approach.
By doing this you can easily test the different classes.
To extend this (read make it extendable) you might introduce a controller. The controller gets events (e.g. from the queue) and acts on the events (e.g. tell the Display Controller to turn off the display). The controller knows about the sensor, and knows about the display. But the sensor should not know about the display or vice versa. (this is very similar to MVC where in this case the data is the model (sensor), the display is the view and the controller sits in between.
This approach makes the design testable, extendable, maintainable. And by that you are not hackish, you are writing real code.

Related

Understanding implementation of parallel programming via threading

Scenarion
Sensor is continuously sending data in an interval of 100 milliseconds ( time needs to be configurable)
One Thread read the data continuously from sensor and write it to a common queue
This process is continuous until keyboard interrupt press happens
Thread 2 locks queue, ( may momentarily block Thread1)
Read full data from queue to temp structure
Release the queue
process the data in it. It is a computational task. While performing this task. Thread 1 should keep on filling the buffer with sensor data.
I have read about threading and GIL, so step 7 cannot afford to have any loss in data sent by the sensor while performing the computational process() on thread 2.
How this can be implemented using Python?
What I started with it is
import queue
from threading import Thread
import queue
from queue import Queue
q = Queue(maxsize=10)
def fun1():
fun2Thread = Thread(target=fun2)
fun2Thread.start()
while True:
try:
q.put(1)
except KeyboardInterrupt:
print("Key Interrupt")
fun2Thread.join()
def fun2():
print(q.get())
def read():
fun1Thread = Thread(target=fun1)
fun1Thread.start()
fun1Thread.join()
read()
The issue I'm facing in this is the terminal is stuck after printing 1. Can someone please guide me on how to implement this scenario?
Here's an example that may help.
We have a main program (driver), a client and a server. The main program manages queue construction and the starting and ending of the subprocesses.
The client sends a range of values via a queue to the client. When the range is exhausted it tells the server to terminate. There's a delay (sleep) in enqueueing the data for demonstration purposes.
Try running it once without any interrupt and note how everything terminates nicely. Then run again and interrupt (Ctrl-C) and again note a clean termination.
from multiprocessing import Queue, Process
from signal import signal, SIGINT, SIG_IGN
from time import sleep
def client(q, default):
signal(SIGINT, default)
try:
for i in range(10):
sleep(0.5)
q.put(i)
except KeyboardInterrupt:
pass
finally:
q.put(-1)
def server(q):
while (v := q.get()) != -1:
print(v)
def main():
q = Queue()
default = signal(SIGINT, SIG_IGN)
(server_p := Process(target=server, args=(q,))).start()
(client_p := Process(target=client, args=(q, default))).start()
client_p.join()
server_p.join()
if __name__ == '__main__':
main()
EDIT:
Edited to ensure that the server process continues to drain the queue if the client is terminated due to a KeyboardInterrupt (SIGINT)

Abort a pending serial.read() called in a parallel thread when the serial port gets closed in the main program

I am currently developing a custom Serial monitor in Python using the Tkinter framework as GUI generator and the Pyserial library to handle low level communication. To receive serial data and plot the received byte on the program window I am using a parallel thread that keeps living till the stop_serial_thread variable is set to True.
This variable is set in the main program when I close the window program since the closeSerialMonitor() function is automatically executed.
If I run the program without a serial Port opened everything runs as expected and the thread exit properly. When a serial communication is ongoing the thread get stuck without never exiting, probably because the ser.read() never returns
I've already tried the followings without any success:
serial read timeout != 0
calling ser.cancel_read() to cancel pending reading
creating a raise_exception method in the serialPlotter class to raise an exception and exiting the thread
What is the right way to properly close the thread and the serial port as soon as I want to close the program?
#comm.py
ser = serial.Serial()
stop_serial_thread = False
class serialPlotter(threading.Thread):
def __init__(self, name, monitor, autoscroll):
threading.Thread.__init__(self)
self.name = name
self.monitor = monitor
self.autoscroll = autoscroll
def receive(self):
if(ser.is_open == True):
chr = ser.read() # <----- blocking read
self.monitor.config(state=NORMAL)
self.monitor.insert(END,chr)
if self.autoscroll.get() == True:
self.monitor.see("end")
self.monitor.config(state=DISABLED)
def run(self):
print ("Starting " + self.name)
while(stop_serial_thread == False):
self.receive()
print("Ending " + self.name)
#serial_console.py
...
def closeSerialMonitor():
ser.cancel_read()
comm.stop_serial_thread = True
ser.close()
window.destroy()
...
When you close your application execute:
import sys # Import the module sys in order to be able to use exit()
sys.exit()
The sys.exit() function allows the developer to exit from Python.
I had the same problem some time ago with serial communication, and that's how I fixed it

How to end properly 2 looping threading? [duplicate]

This question already has an answer here:
Python Threading with Event object
(1 answer)
Closed 3 years ago.
I'm doing a telemetry application using Azure IoT Hub, Azure IoT SDK in Python and a raspberry pi with temperature and humidity sensors.
Humidity + Temperature sensors => Rasperry Pi => Azure IoT Hub
For my application, I send the data with different frequencies using 2 looping threads:
- One loop collect the data of the temperature sensor and send it to Azure IoT Hub every 60 seconds
-One loop collect the data of the humidity sensor and send it to Azure IoT Hub every 600 seconds.
I want to close properly the 2 looping threads. They currently run with no way to break them.
I'm using Python 2.7.
I heard about Event from of the library "threading, Thread", but I can't find some good example of program structure to apply.
How can I use Event to close properly thread? How to end those loops with another method?
Here is the structure of my code using the 2 threads including loop.
from threading import Thread
def send_to_azure_temperature_thread_func:
client = iothub_client_init()
while True:
collect_temperature_data()
send_temperature_data(client)
time.sleep(60)
def send_to_humidity_thread_func():
client = iothub_client_init()
while True:
collect_humidity_data()
send_humidity_data(client)
time.sleep(600)
if __name__ == '__main__':
print("Threads...")
temperature_thread = Thread(target=send_to_azure_temperature_thread_func)
temperature_thread.daemon = True
print("Thread1 init")
humidity_thread = Thread(target=send_to_azure_humidity_thread_func)
humidity_thread.daemon = True
print("Thread2 init")
temperature_thread.start()
humidity_thread.start()
print("Threads start")
temperature_thread.join()
humidity_thread.join()
print("Threads wait")
Event seems like a good approach. Create one and pass it to all threads and replace the sleep() by Event.wait() and check if the loop needs to be left.
In the main thread the event can be set to signal to the threads that they should leave the loop and thus end themselves.
from threading import Event, Thread
def temperature_loop(stop_requested):
client = iothub_client_init()
while True:
collect_temperature_data()
send_temperature_data(client)
if stop_requested.wait(60):
break
def humidity_loop(stop_requested):
client = iothub_client_init()
while True:
collect_humidity_data()
send_humidity_data(client)
if stop_requested.wait(600):
break
def main():
stop_requested = Event()
print('Threads...')
temperature_thread = Thread(target=temperature_loop, args=[stop_requested])
temperature_thread.daemon = True
print('Thread1 init')
humidity_thread = Thread(target=humidity_loop, args=[stop_requested])
humidity_thread.daemon = True
print('Thread2 init')
temperature_thread.start()
humidity_thread.start()
print('Threads start')
time.sleep(2000)
stop_requested.set()
temperature_thread.join()
humidity_thread.join()
print('Threads wait')
if __name__ == '__main__':
main()

Safe way to exit an infinite loop within a Thread Pool for Python3

I am using Python3 modules:
requests for HTTP GET calls to a few Particle Photons which are set up as simple HTTP Servers
As a client I am using the Raspberry Pi (which is also an Access Point) as a HTTP Client which uses multiprocessing.dummy.Pool for making HTTP GET resquests to the above mentioned photons
The polling routine is as follows:
def pollURL(url_of_photon):
"""
pollURL: Obtain the IP Address and create a URL for HTTP GET Request
#param: url_of_photon: IP address of the Photon connected to A.P.
"""
create_request = 'http://' + url_of_photon + ':80'
while True:
try:
time.sleep(0.1) # poll every 100ms
response = requests.get(create_request)
if response.status_code == 200:
# if success then dump the data into a temp dump file
with open('temp_data_dump', 'a+') as jFile:
json.dump(response.json(), jFile)
else:
# Currently just break
break
except KeyboardInterrupt as e:
print('KeyboardInterrupt detected ', e)
break
The url_of_photon values are simple IPv4 Addresses obtained from the dnsmasq.leases file available on the Pi.
the main() function:
def main():
# obtain the IP and MAC addresses from the Lease file
IP_addresses = []
MAC_addresses = []
with open('/var/lib/misc/dnsmasq.leases', 'r') as leases_file:
# split lines and words to obtain the useful stuff.
for lines in leases_file:
fields = lines.strip().split()
# use logging in future
print('Photon with MAC: %s has IP address: %s' %(fields[1],fields[2]))
IP_addresses.append(fields[2])
MAC_addresses.append(fields[1])
# Create Thread Pool
pool = ThreadPool(len(IP_addresses))
results = pool.map(pollURL, IP_addresses)
pool.close()
pool.join()
if __name__ == '__main__':
main()
Problem
The program runs well however when I press CTRL + C the program does not terminate. Upon digging I found that the way to do so is using CTRL + \
How do I use this in my pollURL function for a safe way to exit the program, i.e. perform poll.join() so no leftover processes are left?
notes:
the KeyboardInterrupt is never recognized with the function. Hence I am facing trouble trying to detect CTRL + \.
The pollURL is executed in another thread. In Python, signals are handled only in the main thread. Therefore, SIGINT will raise the KeyboardInterrupt only in the main thread.
From the signal documentation:
Signals and threads
Python signal handlers are always executed in the main Python thread, even if the signal was received in another thread. This means that signals can’t be used as a means of inter-thread communication. You can use the synchronization primitives from the threading module instead.
Besides, only the main thread is allowed to set a new signal handler.
You can implement your solution in the following way (pseudocode).
event = threading.Event()
def looping_function( ... ):
while event.is_set():
do_your_stuff()
def main():
try:
event.set()
pool = ThreadPool()
pool.map( ... )
except KeyboardInterrupt:
event.clear()
finally:
pool.close()
pool.join()

python-xbee cannot read frame in thread

I am using one XBee S2 as coordinator (API mode), 3 XBee S2 as routers (AT mode). The routers are connected to Naze32 board (using MSP).
On the computer side, I have a GUI using wxpython to send out command to request data.
The GUI will send out command to XBee (Coordinator) to request data from the routers every second.
I am using python-xbee library to do the send and receive frame job on computer side. Once new data received, it will notify the GUI to update some labels with the new data.
Currently I am able to send and receive frames outside a thread, but once I move the send and receive functions to a thread, it will never be able to read a frame any more. As I don't want to let the serial stop the GUI or make it not responding. Another thing is if I close the thread, then start new thread with xbee again, it will not work any more.
The communication is controlled by a button on the GUI; once "Turn on" clicked, the "self._serialOn" will set to True, then create new thread; once "Turn off" clicked, "self._serialOn" will set to False and thread is stopped.
How can I fix this problem?
Thanks in advance.
class DataExchange(object):
def __init__(self):
self._observers = []
self._addressList = [['\x00\x13\xA2\x00\x40\xC1\x43\x0F', '\xFF\xFE'],[],[]]
self._serialPort = ''
self._serialOn = False
self.workerSerial = None
# serial switch
def get_serialOn(self):
return self._serialOn
def set_serialOn(self, value):
self._serialOn = value
print(self._serialOn)
if self.serialOn == True:
EVT_ID_VALUE = wx.NewId()
self.workerSerial = WorkerSerialThread(self, EVT_ID_VALUE, self.serialPort, self.addressList)
self.workerSerial.daemon = True
self.workerSerial.start()
elif self.serialOn == False:
self.workerSerial.stop()
del(self.workerSerial)
self.workerSerial = None
serialOn = property(get_serialOn, set_serialOn)
class WorkerSerialThread(threading.Thread):
def __init__(self, notify_window, id, port, addresslist):
threading.Thread.__init__(self)
self.id = id
self.serialPort = port
self.addressList = addresslist
self.counter = 0
self._notify_window = notify_window
self.abort = False
self.sch = SerialCommunication(self.serialPort, self.addressList)
try:
self.sch.PreLoadInfo()
except:
print('failed')
def run(self):
while not self.abort:
self.counter += 1
print('Serial Working on '+str(self.id))
self.sch.RegularLoadInfo()
#wx.PostEvent(self._notify_window, DataEvent(self.counter, self.id))
time.sleep(1)
def stop(self):
self.sch.board.stop()
self.abort = True
This question was finally solved with multiprocessing rather than threading of python.
In the manual of python-xbee, it mentioned that "... Make sure that updates to external state are thread-safe...". Also in the source code, threading was used.
So I guess in this case threading will cause problem.
Anyway, using multiprocessing it finally works.

Categories

Resources