倒数计时器不工作
Countdown Timer doesn't work
目标是:如果有运动,记录开始,计数器 (x) 开始每 1 秒递减一次,但如果同时有另一个运动,计数器重新开始到 x(例如: 5 秒)。
实际上这是行不通的,更具体地说,如果在录制过程中有动作,计数器不会重置,所以每个视频都有 5 秒的长度。
from picamera import PiCamera
from time import sleep
camera = PiCamera()
sensor = MotionSensor(7)
camera.hflip = True
name = "video.h264"
x = 5 #seconds of record
def countdown(count):
while (count >= 0):
print (count)
count -= 1
sleep(1)
if sensor.when_motion == True:
count = x
def registra_video():
print ("recording started")
#camera.start_preview()
camera.start_recording(name)
countdown(x)
def stop_video():
camera.stop_recording()
#camera.stop_preview()
print ("recording stopped")
print("Waiting...")
sensor.when_motion = registra_video
sensor.when_no_motion = stop_video
pause()
P.s我知道我必须做一个功能来给每个视频取不同的名字,但我会随后做。
简介
首先,我很确定这个问题最好用 multi-threaded 方法解决,原因有二。首先,事件处理程序通常是一小段代码,运行 在单个线程中非常快速。其次,您的特定代码以我将在下面描述的方式自我阻塞。
当前行为
在提出解决方案之前,让我们先看一下您的代码,看看为什么它不起作用。
你有一个运动传感器,它在检测到运动的开始和结束时输出事件。无论您的代码正在做什么,这些事件都会发生。正如您正确指出的那样,只要运动停止,就会 MotionSensor
object will call when_motion
every time it goes into active state (i.e., when a new motion is detected). Similarly, it will call when_no_motion
。这些方法的调用方式是将事件添加到队列并在专用线程中处理 one-by-one。无法排队的事件(因为队列已满)将被丢弃并且永远不会处理。默认情况下,队列长度为一,这意味着在另一个事件等待处理时发生的任何事件都将被丢弃。
考虑到所有这些,让我们看看当您获得新的运动事件时会发生什么。首先,事件将被排队。然后它会导致 registra_video
几乎立即被调用。 registra_video
无论发生什么其他事件,都会阻塞五秒钟。完成后,另一个事件将从队列中弹出并进行处理。如果下一个事件是在五秒等待期间发生的 stop-motion 事件,则相机将在 stop_video
后关闭。唯一不会调用 stop_video
的方法是传感器连续检测到运动超过五秒。如果您的队列长度大于 1,则另一个事件可能会在阻塞时间内发生并且仍会得到处理。假设这是在五秒块期间发生的另一个 start-motion 事件。它将重新启动相机并创建另一个五秒的视频,但增加队列长度不会改变第一个视频正好是五秒长的事实。
希望您现在明白为什么在事件处理程序中等待视频的整个持续时间不是一个好主意。它会阻止您及时对以下事件做出反应。在您的特定情况下,当计时器仍在 运行ning 时您无法重新启动计时器,因为您不允许任何其他代码 运行 当计时器阻塞您的事件处理线程时。
设计
所以这是一个可能的解决方案:
- 当检测到新动作时(
when_motion
被调用),如果尚未 运行ning,则启动相机。
- 当检测到 stop-motion 时(
when_no_motion
被调用),您有两个选择:
- 如果倒计时不是 运行ning,请开始。我不建议在
when_motion
开始倒计时,因为在调用 when_no_motion
之前运动将一直进行。
- 如果倒计时已经运行,请重新开始。
计时器将运行放在后台线程中,不会干扰事件处理线程。 "timer" 线程可以只设置开始时间,休眠五秒钟并再次检查开始时间。如果它醒来时超过开始时间五秒以上,它就会关闭相机。如果开始时间被另一个 when_motion
调用重置,线程将返回休眠 new_start_time + five seconds - current_time
。如果计时器在调用另一个 when_motion
之前超时,请关闭相机。
一些线程概念
让我们回顾一下使设计的解决方案发挥作用所需的一些构建基块。
首先,您将更改值并从至少两个不同的 threads 读取它们。我指的值是相机的状态(开或关),它会告诉你定时器何时到期,需要在运动中重新启动,以及倒计时的开始时间。
您不希望 运行 设置了 "camera is off" 标志,但在定时器线程中尚未完成关闭相机的情况,而事件处理线程获得对 when_motion
的新调用,并决定在您关闭相机时重新启动相机。为避免这种情况,您使用 locks.
锁是一个对象,它会让线程等待直到它可以获取它。因此,您可以将整个 camera-off 操作锁定为一个单元,直到它完成,然后才允许事件处理线程检查标志的值。
我会避免在代码中使用除基本线程和锁之外的任何东西。
代码
这里是一个示例,说明如何修改代码以使用我一直抱怨的令人作呕的概念。我尽可能地保留了一般结构,但请记住,全局变量通常不是一个好主意。我使用它们是为了避免陷入不得不解释 类 的困境。事实上,我已经尽可能多地剥离出来,只介绍一般的想法,如果线程对你来说是新的,这将花费你足够长的时间来处理它
from picamera import PiCamera
from time import sleep
from datetime import datetime
from threading import Thread, RLock
camera = PiCamera()
sensor = MotionSensor(7)
camera.hflip = True
video_prefix = "video"
video_ext = ".h264"
record_time = 5
# This is the time from which we measure 5 seconds.
start_time = None
# This tells you if the camera is on. The camera can be on
# even when start_time is None if there is movement in progress.
camera_on = False
# This is the lock that will be used to access start_time and camera_on.
# Again, bad idea to use globals for this, but it should work fine
# regardless.
thread_lock = RLock()
def registra_video():
global camera_on, start_time
with thread_lock:
if not camera_on:
print ("recording started")
camera.start_recording('{}.{:%Y%m%d_%H%M%S}.{}'.format(video_prefix, datetime.now(), video_ext))
camera_on = True
# Clear the start_time because it needs to be reset to
# x seconds after the movement stops
start_time = None
def stop_video():
global camera_on
with thread_lock:
if camera_on:
camera.stop_recording()
camera_on = False
print ("recording stopped")
def motion_stopped():
global start_time
with thread_lock:
# Ignore this function if it gets called before the camera is on somehow
if camera_on:
now = datetime.now()
if start_time is None:
print('Starting {} second count-down'.format(record_time))
Thread(target=timer).start()
else:
print('Recording to be extended by {:.1f} seconds'.format((now - start_time).total_seconds()))
start_time = now
def timer():
duration = record_time
while True:
# Notice that the sleep happens outside the lock. This allows
# other threads to modify the locked data as much as they need to.
sleep(duration)
with thread_lock:
if start_time is None:
print('Timer expired during motion.')
break
else:
elapsed = datetime.now() - start_time
if elapsed.total_seconds() >= record_time:
print('Timer expired. Stopping video.')
stop_video() # This here is why I am using RLock instead of plain Lock. I will leave it up to the reader to figure out the details.
break
else:
# Compute how much longer to wait to make it five seconds
duration = record_time - elapsed
print('Timer expired, but sleeping for another {}'.format(duration))
print("Waiting...")
sensor.when_motion = registra_video
sensor.when_no_motion = motion_stopped
pause()
作为额外奖励,我添加了一个片段,该片段将在您的视频名称后附加 date-time。您可以阅读所有您需要的关于字符串格式化的内容 here and here。第二个 link 是一个很好的快速参考。
目标是:如果有运动,记录开始,计数器 (x) 开始每 1 秒递减一次,但如果同时有另一个运动,计数器重新开始到 x(例如: 5 秒)。
实际上这是行不通的,更具体地说,如果在录制过程中有动作,计数器不会重置,所以每个视频都有 5 秒的长度。
from picamera import PiCamera
from time import sleep
camera = PiCamera()
sensor = MotionSensor(7)
camera.hflip = True
name = "video.h264"
x = 5 #seconds of record
def countdown(count):
while (count >= 0):
print (count)
count -= 1
sleep(1)
if sensor.when_motion == True:
count = x
def registra_video():
print ("recording started")
#camera.start_preview()
camera.start_recording(name)
countdown(x)
def stop_video():
camera.stop_recording()
#camera.stop_preview()
print ("recording stopped")
print("Waiting...")
sensor.when_motion = registra_video
sensor.when_no_motion = stop_video
pause()
P.s我知道我必须做一个功能来给每个视频取不同的名字,但我会随后做。
简介
首先,我很确定这个问题最好用 multi-threaded 方法解决,原因有二。首先,事件处理程序通常是一小段代码,运行 在单个线程中非常快速。其次,您的特定代码以我将在下面描述的方式自我阻塞。
当前行为
在提出解决方案之前,让我们先看一下您的代码,看看为什么它不起作用。
你有一个运动传感器,它在检测到运动的开始和结束时输出事件。无论您的代码正在做什么,这些事件都会发生。正如您正确指出的那样,只要运动停止,就会 MotionSensor
object will call when_motion
every time it goes into active state (i.e., when a new motion is detected). Similarly, it will call when_no_motion
。这些方法的调用方式是将事件添加到队列并在专用线程中处理 one-by-one。无法排队的事件(因为队列已满)将被丢弃并且永远不会处理。默认情况下,队列长度为一,这意味着在另一个事件等待处理时发生的任何事件都将被丢弃。
考虑到所有这些,让我们看看当您获得新的运动事件时会发生什么。首先,事件将被排队。然后它会导致 registra_video
几乎立即被调用。 registra_video
无论发生什么其他事件,都会阻塞五秒钟。完成后,另一个事件将从队列中弹出并进行处理。如果下一个事件是在五秒等待期间发生的 stop-motion 事件,则相机将在 stop_video
后关闭。唯一不会调用 stop_video
的方法是传感器连续检测到运动超过五秒。如果您的队列长度大于 1,则另一个事件可能会在阻塞时间内发生并且仍会得到处理。假设这是在五秒块期间发生的另一个 start-motion 事件。它将重新启动相机并创建另一个五秒的视频,但增加队列长度不会改变第一个视频正好是五秒长的事实。
希望您现在明白为什么在事件处理程序中等待视频的整个持续时间不是一个好主意。它会阻止您及时对以下事件做出反应。在您的特定情况下,当计时器仍在 运行ning 时您无法重新启动计时器,因为您不允许任何其他代码 运行 当计时器阻塞您的事件处理线程时。
设计
所以这是一个可能的解决方案:
- 当检测到新动作时(
when_motion
被调用),如果尚未 运行ning,则启动相机。 - 当检测到 stop-motion 时(
when_no_motion
被调用),您有两个选择:- 如果倒计时不是 运行ning,请开始。我不建议在
when_motion
开始倒计时,因为在调用when_no_motion
之前运动将一直进行。 - 如果倒计时已经运行,请重新开始。
- 如果倒计时不是 运行ning,请开始。我不建议在
计时器将运行放在后台线程中,不会干扰事件处理线程。 "timer" 线程可以只设置开始时间,休眠五秒钟并再次检查开始时间。如果它醒来时超过开始时间五秒以上,它就会关闭相机。如果开始时间被另一个 when_motion
调用重置,线程将返回休眠 new_start_time + five seconds - current_time
。如果计时器在调用另一个 when_motion
之前超时,请关闭相机。
一些线程概念
让我们回顾一下使设计的解决方案发挥作用所需的一些构建基块。
首先,您将更改值并从至少两个不同的 threads 读取它们。我指的值是相机的状态(开或关),它会告诉你定时器何时到期,需要在运动中重新启动,以及倒计时的开始时间。
您不希望 运行 设置了 "camera is off" 标志,但在定时器线程中尚未完成关闭相机的情况,而事件处理线程获得对 when_motion
的新调用,并决定在您关闭相机时重新启动相机。为避免这种情况,您使用 locks.
锁是一个对象,它会让线程等待直到它可以获取它。因此,您可以将整个 camera-off 操作锁定为一个单元,直到它完成,然后才允许事件处理线程检查标志的值。
我会避免在代码中使用除基本线程和锁之外的任何东西。
代码
这里是一个示例,说明如何修改代码以使用我一直抱怨的令人作呕的概念。我尽可能地保留了一般结构,但请记住,全局变量通常不是一个好主意。我使用它们是为了避免陷入不得不解释 类 的困境。事实上,我已经尽可能多地剥离出来,只介绍一般的想法,如果线程对你来说是新的,这将花费你足够长的时间来处理它
from picamera import PiCamera
from time import sleep
from datetime import datetime
from threading import Thread, RLock
camera = PiCamera()
sensor = MotionSensor(7)
camera.hflip = True
video_prefix = "video"
video_ext = ".h264"
record_time = 5
# This is the time from which we measure 5 seconds.
start_time = None
# This tells you if the camera is on. The camera can be on
# even when start_time is None if there is movement in progress.
camera_on = False
# This is the lock that will be used to access start_time and camera_on.
# Again, bad idea to use globals for this, but it should work fine
# regardless.
thread_lock = RLock()
def registra_video():
global camera_on, start_time
with thread_lock:
if not camera_on:
print ("recording started")
camera.start_recording('{}.{:%Y%m%d_%H%M%S}.{}'.format(video_prefix, datetime.now(), video_ext))
camera_on = True
# Clear the start_time because it needs to be reset to
# x seconds after the movement stops
start_time = None
def stop_video():
global camera_on
with thread_lock:
if camera_on:
camera.stop_recording()
camera_on = False
print ("recording stopped")
def motion_stopped():
global start_time
with thread_lock:
# Ignore this function if it gets called before the camera is on somehow
if camera_on:
now = datetime.now()
if start_time is None:
print('Starting {} second count-down'.format(record_time))
Thread(target=timer).start()
else:
print('Recording to be extended by {:.1f} seconds'.format((now - start_time).total_seconds()))
start_time = now
def timer():
duration = record_time
while True:
# Notice that the sleep happens outside the lock. This allows
# other threads to modify the locked data as much as they need to.
sleep(duration)
with thread_lock:
if start_time is None:
print('Timer expired during motion.')
break
else:
elapsed = datetime.now() - start_time
if elapsed.total_seconds() >= record_time:
print('Timer expired. Stopping video.')
stop_video() # This here is why I am using RLock instead of plain Lock. I will leave it up to the reader to figure out the details.
break
else:
# Compute how much longer to wait to make it five seconds
duration = record_time - elapsed
print('Timer expired, but sleeping for another {}'.format(duration))
print("Waiting...")
sensor.when_motion = registra_video
sensor.when_no_motion = motion_stopped
pause()
作为额外奖励,我添加了一个片段,该片段将在您的视频名称后附加 date-time。您可以阅读所有您需要的关于字符串格式化的内容 here and here。第二个 link 是一个很好的快速参考。