倒数计时器不起作用

时间:2017-02-10 11:18:42

标签: python-3.x raspbian raspberry-pi3

目标是:如果有动作,则记录开始,计数器(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我知道我必须做一个以不同方式命名每个视频的功能,但我会随后进行。

1 个答案:

答案 0 :(得分:1)

INTRO

首先,我很确定这个问题最好通过多线程方法解决,原因有两个。首先,事件处理程序通常是在单个线程中非常快速运行的小代码片段。其次,您的特定代码会以我将在下面描述的方式阻止自己。

当前行为

在提出解决方案之前,让我们看看您的代码,看看它为什么不起作用。

您有一个运动传感器在检测到运动的开始和结束时输出事件。无论您的代码是在做什么,这些事件都会发生。正确指出,MotionSensor对象每次进入活动状态时(即检测到新动作时)都会调用when_motion。同样,只要动作停止,它就会调用when_no_motion。调用这些方法的方式是将事件添加到队列中并在专用线程中逐个处理。无法排队的事件(因为队列已满)将被删除并且从不处理。默认情况下,队列长度为1,这意味着在另一个事件等待处理时发生的任何事件都将被删除。

考虑到这一切,让我们看看当你得到一个新的动作事件时会发生什么。首先,该事件将排队。然后它会立即调用registra_video。无论发生了什么其他事件,registra_video都会阻止五秒钟。完成后,将从队列中弹出另一个事件并进行处理。如果下一个事件是在五秒钟等待期间发生的停止动作事件,则stop_video将关闭相机。如果传感器连续检测到运动超过五秒,则stop_video不会被调用的唯一方法。如果队列长度大于1,则在阻塞时间内可能会发生另一个事件并仍然处理。让我们说这是在五秒块期间发生的另一个开始运动事件。它将重新启动摄像机并创建另外五秒钟的视频,但增加队列长度不会改变第一个视频将恰好五秒钟的事实。

希望到现在为止,您可以了解为什么在事件处理程序中等待视频的整个持续时间并不是一个好主意。它会阻止您按时响应以下事件。在您的特定情况下,当计时器仍在运行时,您无法重新启动计时器,因为在计时器阻止事件处理线程时您不允许运行任何其他代码。

设计

所以这是一个可能的解决方案:

  1. 检测到新动作时(when_motion被调用),启动相机(如果尚未运行)。
  2. 当检测到停止动作(when_no_motion被调用)时,您有两个选择:
    1. 如果倒计时未运行,请启动它。我不建议在when_motion开始倒计时,因为动议将在调用when_no_motion之前进行。
    2. 如果倒计时已在运行,请重新开始。
  3. 计时器将在后台线程中运行,这不会干扰事件处理线程。 "计时器"线程只需设置开始时间,休眠五秒钟并再次检查开始时间。如果超过唤醒开始时间超过五秒,则会关闭相机。如果通过另一个when_motion调用重置了开始时间,则线程将返回休眠状态new_start_time + five seconds - current_time。如果计时器在调用另一个when_motion之前到期,请关闭相机。

    一些线程概念

    让我们了解一下您需要的一些构建模块,以使设计的解决方案正常运行。

    首先,您将更改值并从至少两个不同的threads中读取它们。我所指的值是相机的状态(打开或关闭),它会告诉您计时器何时到期并需要重新开始运动,以及倒计时的开始时间。

    当您设置了#34;相机关闭时,您不希望遇到这种情况。标志,但没有完成关闭计时器线程中的相机,而事件处理线程获得when_motion的新呼叫,并决定重新启动相机,因为你关闭它。为避免这种情况,请使用locks

    一个锁是一个对象,它会使一个线程等到它可以获得它。因此,您可以将整个摄像机关闭操作作为一个单元锁定,直到它完成,然后允许事件处理线程检查标志的值。

    我将避免在代码中使用除基本线程和锁之外的任何东西。

    代码

    以下是一个示例,说明如何修改代码以使用我一直在讨论广告恶意的概念。我尽可能地保留了一般结构,但请记住,全局变量通常不是一个好主意。我正在使用它们以避免在必须解释课程的兔子洞。事实上,我已经尽可能地去掉了一般性的想法,这将花费你足够长的时间来处理,如果线程对你来说是新的:

    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()
    

    作为额外的奖励,我投入了一个片段,它会在您的视频名称中附加日期时间。您可以阅读有关字符串格式herehere的所有信息。第二个链接是一个很好的快速参考。