我有一个被动红外传感器,我想根据动作关闭和显示我的显示器。例如。如果5分钟内没有动作,则应关闭显示屏以节省电量。但是,如果有动作,请勿关闭显示屏,或将其重新打开。 (不要问为什么屏幕保护程序不适合这个。我正在制作的设备没有任何键盘或鼠标。它只是一个独立的显示器。)
我的想法是创建两个线程,一个生产者和一个消费者。生产者线程(PIR传感器)将消息放入队列中,消费者(控制显示器)读取该队列。这样我就可以将信号从一个发送到另一个。
我在下面有一个功能齐全的代码(有一些解释),完成了前面描述的。我的问题是,有没有办法以更优雅的方式实现这一目标?你如何看待我的方法,是否可以,是黑客吗?
#!/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)
生产者线程运行report_motion
类实例的函数(PIRSensor
)。 PIRSensor类每秒读取被动红外传感器的状态四次,每当它感应到运动时,就会将一条消息放入队列中。
使用者线程运行check_motion
类实例的(DisplayControl
)函数。它在具有给定超时的阻塞模式下读取前面提到的队列。可能发生以下情况:
答案 0 :(得分:2)
我认为这个想法很好。我对你的实现的唯一问题是为什么消费者和生产者都在子线程中?您可以将使用者保留在主线程中,然后就不需要在主线程中使用这个无意义的循环。
while True:
time.sleep(0.1)
这只是浪费CPU周期。相反,您可以直接致电display_motion.check_motion(queue)
。
答案 1 :(得分:1)
我认为这是一个很好的解决方案。原因是你已经分开了不同类的关注点。一类处理PIR传感器。一个处理显示器。今天你用队列将它们粘在一起,这是一种方法。
通过这样做,您可以轻松地测试不同的类。
要扩展它(读取使其可扩展),您可能会引入一个控制器。控制器获取事件(例如,从队列中)并对事件起作用(例如,告诉显示控制器关闭显示器)。控制器知道传感器,并了解显示器。但传感器不应该知道显示器,反之亦然。 (这与MVC非常相似,在这种情况下,数据是模型(传感器),显示是视图,控制器位于其间。
这种方法使设计可测试,可扩展,可维护。而且你并不是那么狡猾,而是在编写真正的代码。