Skip to content

Commit

Permalink
Improved GPIO-based dashcam saving
Browse files Browse the repository at this point in the history
  • Loading branch information
connervieira committed May 2, 2024
1 parent 3bddc3c commit 3f9df2f
Show file tree
Hide file tree
Showing 7 changed files with 89 additions and 16 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -472,7 +472,7 @@ This update focuses on improving the reliability of Predator, especially when op
- Added per-device resolution configuration.
- Added the ability to disable capture devices without removing them from the configuration entirely.
- Predator now changes the status light color when a video is being locked.
- Added standalone tool for triggering dashcam saving via a GPIO input.
- Dashcam video save events can now be triggered using buttons via GPIO pins.
- Moved the status lighting configuration to the "general" section.
- Updated configuration back-end.
- Predator can now automatically update the configuration file between versions when configuration values are added or removed.
2 changes: 2 additions & 0 deletions assets/support/configdefault.json
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,8 @@
"saving": {
"directory": "saved_dashcam",
"trigger": "dashcam_lock_trigger",
"trigger_gpio": {
},
"segment_length": 60,
"looped_recording": {
"mode": "manual",
Expand Down
1 change: 1 addition & 0 deletions assets/support/configoutline.json
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@
"saving": {
"directory": "str",
"trigger": "str",
"trigger_gpio": "dict",
"segment_length": "+float",
"looped_recording": {
"mode": ["automatic", "manual"],
Expand Down
6 changes: 3 additions & 3 deletions config.py
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ def highest_different_index(config_active, config_default, index): # This functi
return []


ignore_indexes = [["realtime", "image", "camera", "devices"], ["dashcam", "stamps", "relay", "triggers"], ["dashcam", "capture", "video", "devices"]] # Configure values specified here will not be checked by the check_defaults_missing() and check_defaults_extra() functions.
ignore_indexes = [["realtime", "image", "camera", "devices"], ["dashcam", "saving", "trigger_gpio"], ["dashcam", "stamps", "relay", "triggers"], ["dashcam", "capture", "video", "devices"]] # Configure values specified here will not be checked by the check_defaults_missing() and check_defaults_extra() functions.

# This function checks for values that exist in the default config that aren't present in the active config.
def check_defaults_missing(config_defaults, config_active, index=[], missing_values=[]):
Expand Down Expand Up @@ -358,14 +358,14 @@ def update_config():
extra_values = check_defaults_extra(config_default, config_active)

if (len(missing_values) > 0):
print(style.yellow + "The following values were present in the default configuration, but not the active configuration. They may have been added in an update. The default values have been inserted into the active configuration." + style.end)
print(style.yellow + "The following values were present in the default configuration, but not the active configuration. They may have been added in an update. The default values will be inserted into the active configuration." + style.end)
for value in missing_values:
print(" " + '>'.join(map(str, value)))
print(style.faint + "Continuing in 5 seconds" + style.end)
time.sleep(5)

if (len(extra_values) > 0):
print(style.yellow + "The following values were present in the active configuration, but not the default configuration. They may have been removed in an update. These values have been removed from the active configuration." + style.end)
print(style.yellow + "The following values were present in the active configuration, but not the default configuration. They may have been removed in an update. These values will be removed from the active configuration." + style.end)
for value in extra_values:
index = highest_different_index(config_default, config_active, value)
if (get_nested_value(index, config_active) != None): # Check to see if this index hasn't already been removed.
Expand Down
49 changes: 48 additions & 1 deletion dashcam.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,15 @@
import lighting # Import the lighting.py script.
update_status_lighting = lighting.update_status_lighting # Load the status lighting update function from the lighting script.

must_import_gpiozero = False
for stamp in config["dashcam"]["stamps"]["relay"]["triggers"]: # Check to see if there are any GPIO relay stamps active.
if (config["dashcam"]["stamps"]["relay"]["triggers"][stamp]["enabled"] == True): # Check to see if at least one relay stamp is enabled.
from gpiozero import Button # Import GPIOZero
must_import_gpiozero = True
break # Exit the loop, since GPIOZero has already been imported.
if (len(config["dashcam"]["saving"]["trigger_gpio"]) > 0):
must_import_gpiozero = True
if (must_import_gpiozero == True):
from gpiozero import Button # Import GPIOZero



Expand Down Expand Up @@ -145,6 +150,41 @@




trigger_file_location = config["general"]["interface_directory"] + "/" + config["dashcam"]["saving"]["trigger"] # Define the path of the dashcam lock trigger file.
trigger_file_location = trigger_file_location.replace("//", "/") # Remove any duplicate slashes in the file path.

last_trigger_file_created = 0
def create_trigger_file():
global last_trigger_file_created
if (time.time() - last_trigger_file_created < 1):
if (os.path.isdir(config["general"]["interface_directory"]) == False): # Check to see if the interface directory has not yet been created.
os.system("mkdir -p '" + str(config["general"]["interface_directory"]) + "'")
os.system("chmod -R 777 '" + str(config["general"]["interface_directory"]) + "'")
os.system("touch '" + trigger_file_location + "'")
else:
print("Supressed duplicate trigger file")
last_strigger_file_created

def watch_button(pin, hold_time=0.2, event=create_trigger_file):
print("Watching pin " + str(pin))
button = Button(pin)
time_pressed = 0
last_triggered = 0
while True:
if (button.is_pressed and time_pressed == 0): # Check to see if the button was just pressed.
print("Pressed" + str(pin))
time_pressed = time.time()
elif (button.is_pressed and time.time() - time_pressed >= hold_time): # Check to see if the button is being held, and the time threshold has been reached.
print("Triggered " + str(pin))
event()
elif (button.is_pressed == False): # If the button is not pressed, reset the timer.
time_pressed = 0

time.sleep(hold_time/10)



def merge_audio_video(video_file, audio_file, output_file, audio_offset=0):
debug_message("Merging audio and video files")

Expand Down Expand Up @@ -762,6 +802,13 @@ def start_dashcam_recording(dashcam_devices, directory, background=False): # Thi

update_status_lighting("normal") # Initialize the status lighting to normal.



button_watch_threads = {}
for pin in config["dashcam"]["saving"]["trigger_gpio"]:
button_watch_threads[int(pin)] = threading.Thread(target=watch_button, args=[int(pin)], name="ButtonWatch" + str(pin))
button_watch_threads[int(pin)].start()

dashcam_process = [] # Create a placeholder list to store the dashcam processes.
iteration_counter = 0 # Set the iteration counter to 0 so that we can increment it for each recording device specified.
global parked
Expand Down
6 changes: 5 additions & 1 deletion docs/CONFIGURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,11 @@ This document describes the configuration values found `config.json`.
- `trigger` is the name of a file inside the interface directory that will trigger Predator to save the current and previous dashcam segments.
- To trigger a save, create this file in the interface directory. Predator will save the video then automatically remove the trigger file.
- When this file is created, Predator will immediately save the previous and current dashcam video segments. Once the current segment is done recording, Predator will re-save it, such that the saved video doesn't cut off at the moment the saved was triggered.
- If Predator is terminated between the initial save and the second save, only video captured after the save trigger shouldn't be saved.
- If Predator is terminated between the initial save and the second save, only video captured after the save trigger won't be saved.
- `trigger_gpio` contains GPIO pins that will be monitored for button presses to create the dashcam save trigger file.
- This allows hardwired buttons to trigger dashcam video saves.
- Each entry in this configuration section uses the GPIO pin number as a key, and contains the following values:
- `name` is a human friendly name for the pin, and can be set to any plain text string.
- `segment_length` is a number that sets how many seconds long each video segment will be before another segment is created.
- `looped_recording` contains settings that control how and when Predator will erase old dashcam segments to make space for new ones.
- `mode` determines the method by which Predator determines how to erase old files. This can only be set to one of 3 strings:
Expand Down
39 changes: 29 additions & 10 deletions tools/gpio_dashcam_save_trigger.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import os # Required to interact with certain operating system functions
import json # Required to process JSON data
import time
import threading

from gpiozero import Button
from signal import pause
Expand All @@ -28,20 +29,38 @@
trigger_file_location = trigger_file_location.replace("//", "/") # Remove any duplicate slashes in the file path.
if (os.path.isdir(config["general"]["interface_directory"]) == False): # Check to see if the interface directory has not yet been created.
os.system("mkdir -p '" + str(config["general"]["interface_directory"]) + "'")
os.system("chmod -R 777 '" + str(config["general"]["interface_directory"]) + "'")


last_trigger_time = 0
def create_trigger_file():
global last_trigger_time
if (time.time() - last_trigger_time > 1): # Check to see if at least 1 second has passed since the last dash-cam save trigger.
os.system("touch '" + trigger_file_location + "'")
last_trigger_time = time.time()
os.system("touch '" + trigger_file_location + "'")

def watch_button(pin, hold_time=0.2, event=create_trigger_file):
print("Watching pin " + str(pin))
button = Button(pin)

buttons = []
time_pressed = 0
last_triggered = 0
while True:
if (button.is_pressed and time_pressed == 0): # Check to see if the button was just pressed.
print("Pressed" + str(pin))
time_pressed = time.time()
elif (button.is_pressed and time.time() - time_pressed < hold_time): # Check to see if the button is being held, but the time threshold hasn't been reached.
pass
#print("Holding")
elif (button.is_pressed and time.time() - time_pressed >= hold_time): # Check to see if the button is being held, and the time threshold has been reached.
if (time.time() - last_triggered > 1):
print("Triggered " + str(pin))
event()
last_triggered = time.time()
elif (button.is_pressed == False): # If the button is not pressed, reset the timer.
time_pressed = 0

time.sleep(0.02)


button_watch_threads = {}
for pin in gpio_pins:
buttons.append(Button(pin))
for button in buttons:
button.when_pressed = create_trigger_file
button_watch_threads[pin] = threading.Thread(target=watch_button, args=[pin], name="ButtonWatch" + str(pin))
button_watch_threads[pin].start()

pause()

0 comments on commit 3f9df2f

Please sign in to comment.