Reputation: 77
The target is: if there is a motion, the recording starts and the counter (x) begins to decrement every 1 second, but if in the meantime there is another motion, the counter restart to x (for example: 5 second).
Actually this doesn't work, more specifically, the counter doesn't reset if there's a motion during the recording, so every video has 5secs lenght.
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 i know that i have to do a function that name every video differently, but i will do it subsequently.
Upvotes: 0
Views: 242
Reputation: 114230
To begin with, I am pretty sure that this problem is best solved with a multi-threaded approach for two reasons. First of all, event handlers in general are intended to be small snippets of code that run very quickly in a single thread. Secondly, your specific code is blocking itself in the manner I will describe below.
Before presenting a solution, let's take a look at your code to see why it does not work.
You have a motion sensor that is outputting events when it detects the start and end of a motion. These events happen regardless of anything your code is doing. As you correctly indicated, a 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
whenever the motion stops. The way that these methods are called is that events are added to a queue and processed one-by-one in a dedicated thread. Events that can not be queued (because the queue is full) are dropped and never processed. By default, the queue length is one, meaning that any events that occur while another event is waiting to be processed are dropped.
Given all that, let's see what happens when you get a new motion event. First, the event will be queued. It will then cause registra_video
to be called almost immediately. registra_video
will block for five seconds no matter what other events occurred. Once it is done, another event will be popped off the queue and processed. If the next event is a stop-motion event that occurred during the five second wait, the camera will be turned off by stop_video
. The only way stop_video
will not be called is if the sensor continuously detects motion for more than five seconds. If you had a queue length of greater than one, another event could occur during the blocking time and still get processed. Let's say this is another start-motion event that occurred during the five second block. It will restart the camera and create another five second video, but increasing the queue length will not alter the fact that the first video will be exactly five seconds long.
Hopefully by now you get the idea of why it is not a good idea to wait for the entire duration of the video within your event handler. It prevents you from reacting to the following events on time. In your particular case, you have no way to restart the timer when it is still running since you do not allow any other code to run while the timer is blocking your event processing thread.
So here is a possible solution:
when_motion
gets called), start the camera if it is not already running.when_no_motion
gets called), you have two options:
when_motion
, since the motion will be in progress until when_no_motion
is called.The timer will run in a background thread, which will not interfere with the event processing thread. The "timer" thread can just set the start time, sleep for five seconds and check the start time again. If it is more than five seconds past the start time when it wakes up, it turns off the camera. If the start time was reset by another when_motion
call, the thread will go back to sleep for new_start_time + five seconds - current_time
. If the timer expires before another when_motion
is called, turn off the camera.
Let's go over some of the building blocks you will need to get the designed solution working.
First of all, you will be changing values and reading them from at least two different threads. The values I am referring to is the state of the camera (on or off), which will tell you when the timer has expired and needs to be restarted on motion, and the start time of your countdown.
You do not want to run into a situation when you have set the "camera is off" flag, but are not finished turning off the camera in your timer thread, while the event processing thread gets a new call to when_motion
and decides to restart the camera as you are turning it off. To avoid this, you use locks.
A lock is an object that will make a thread wait until it can obtain it. So you can lock the entire camera-off operation as a unit until it completes before allowing the event processing thread to check the value of the flag.
I will avoid using anything besides basic threads and locks in the code.
Here is an example of how you can modify your code to work with the concepts I have been ranting about ad nauseum. I have kept the general structure as much as I could, but keep in mind that global variables are generally not a good idea. I am using them to avoid going down the rabbit hole of having to explain classes. In fact, I have stripped away as much as I could to present just the general idea, which will take you long enough to process as it is if threading is new to you:
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()
As an extra bonus, I threw in a snippet that will append a date-time to your video names. You can read all you need about string formatting here and here. The second link is a great quick reference.
Upvotes: 1