class Configurer: # Configurer Properties: # filer The Filer object used to write logs/package output # lights The LightManager used for toggling LEDs # buttons The ButtonManager used for user input # dumper The Dumper object used to dump files to a flash drive # Configurer Constants: # TICK_RATE The time interval (in seconds) at which the configurer # ticks to check for/make updates # WAIT_TIME The time the Configurer waits before automatically going # into dash-cam mode (this is in seconds) # Constructor def __init__(self): # create the filer, light manager, button manager, and dumper self.filer = Filer() self.lights = LightManager() self.buttons = ButtonManager() self.dumper = Dumper("dashdrive") # create constants self.TICK_RATE = 0.125 self.WAIT_TIME = 5.0 # try to log a new config session try: self.filer.log( "---------- New Config Session: " + str(datetime.datetime.now()) + " ----------\n", True) except: # assume the device's storage is full. Wipe it and try again self.wipeFiles(False) self.__init__() # Main function def main(self): # set up loop variables ticks = 0.0 tickSeconds = 0.0 # the terminate codes are as follows: # -1 Don't terminate # 0 Terminate and launch dash cam # 1 Terminate and shut down terminateCode = -1 self.filer.log("Configuration mode...\n") # main loop while (terminateCode < 0): # slowly flash the yellow light (twice every second) self.lights.setLED([0], tickSeconds.is_integer() or (tickSeconds + 0.5).is_integer()) tickString = "[Ticks: {t1:9.2f}] [Running Time: {t2:9.2f}]" tickString = tickString.format(t1=ticks, t2=int(tickSeconds)) tickString = "[config] " + tickString # if the WAIT_TIME has been exceeded, terminate with code 0 if (tickSeconds == self.WAIT_TIME): tickString += " (Wait time exceeded: terminating configuration and launching dash cam...)" terminateCode = 0 # check for user input (red/yellow hold: shut down) if (self.buttons.isPowerPressed() and self.buttons.durations[0] * self.TICK_RATE >= 2.0 and self.buttons.isCapturePressed() and self.buttons.durations[1] * self.TICK_RATE >= 2.0): self.filer.log("Red/Yellow buttons held. Shutting down...") # create a controller and use its shutdown sequence shutdown_pi(self.lights, [self.buttons]) # check for user input (output config) elif (self.buttons.isCapturePressed() and self.buttons.durations[1] * self.TICK_RATE < 1.0 and self.buttons.durations[1] * self.TICK_RATE >= 0.25 and not self.buttons.isPowerPressed()): self.filer.log("Entering output config...\n") # disable yellow LED self.lights.setLED([0], False) self.mainOutput() # reset button durations (so shutdown doesn't trigger) self.buttons.durations = [0, 0] # reset the ticks/tickSeconds ticks = 0.0 tickSeconds = 0.0 # flash yellow LED to indicate mode switch self.lights.flashLED([0], 2) # check for user input (connect config) elif (self.buttons.isPowerPressed() and self.buttons.durations[0] * self.TICK_RATE < 1.0 and self.buttons.durations[0] * self.TICK_RATE >= 0.25 and not self.buttons.isCapturePressed()): self.filer.log("Entering connect config...\n") # disable yellow LED self.lights.setLED([0], False) self.mainConnect() # reset button durations (so shutdown doesn't trigger) self.buttons.durations = [0, 0] # reset the ticks/tickSeconds ticks = 0.0 tickSeconds = 0.0 # flash yellow LED to indicate mode switch self.lights.flashLED([0], 2) # only log the tickString if the ticks are currently on a second if (tickSeconds.is_integer()): self.filer.log(tickString + "\n") # update the ticks ticks += 1 tickSeconds += self.TICK_RATE # sleep for one TICK_RATE sleep(self.TICK_RATE) # the loop was terminated: determine why if (terminateCode == 0): # force GPIO-using classes to clean up self.lights.__del__() self.buttons.__del__() self.filer.log( "--------- Config Session Ended: " + str(datetime.datetime.now()) + " ---------\n\n", True) # create a controller to launch the dash cam cont = Controller() cont.main() # Output Mode main function def mainOutput(self): # set up loop variables ticks = 0.0 tickSeconds = 0.0 # terminate codes are as follows: # -1 Don't terminate # 0 Terminate and return to config terminateCode = -1 # main loop while (terminateCode < 0): # slowly flash the red/blue lights (twice every second) self.lights.setLED([1, 2], tickSeconds.is_integer() or (tickSeconds + 0.5).is_integer()) # create a tick string tickString = "[Ticks: {t1:9.2f}] [Running Time: {t2:9.2f}]" tickString = tickString.format(t1=ticks, t2=int(tickSeconds)) tickString = "[config-output] " + tickString # get button durations before updating them captureDuration = self.buttons.durations[1] powerDuration = self.buttons.durations[0] # call the button methods to update the button durations self.buttons.isCapturePressed() self.buttons.isPowerPressed() # check for red AND yellow button duration if (captureDuration > 0.0 and captureDuration < ticks and powerDuration > 0.0 and powerDuration < ticks): # if the buttons are released, go back if (not self.buttons.isCapturePressed() and not self.buttons.isPowerPressed()): tickString += " (Capture/Power buttons were held)" terminateCode = 0 # check for red button duration elif (captureDuration > 0.0 and captureDuration < ticks and powerDuration == 0.0): # flash at 1.5 seconds (and still being held down) to indicate # that files will be sent to the flash drive upon button release if (captureDuration * self.TICK_RATE >= 1.5 and (captureDuration * self.TICK_RATE) - 1.5 <= self.TICK_RATE and self.buttons.isCapturePressed()): self.lights.setLED([1, 2], False) self.lights.flashLED([0], 1) # if the button is released... if (not self.buttons.isCapturePressed()): # if released under 1.5 seconds, package output if (captureDuration * self.TICK_RATE < 1.5): # disable all lights self.lights.setLED([0, 1, 2], False) # package the output self.filer.packageOutput("output.zip", self.lights) # otherwise, dump to flash drive elif (captureDuration * self.TICK_RATE >= 1.5): # disable all lights self.lights.setLED([0, 1, 2], False) # dump output to flash drive, if it's plugged in if (self.dumper.driveExists()): self.filer.log("Drive found. Dumping files...\n") self.lights.setLED([2], True) # dump files self.dumper.dumpToDrive(self.filer) # flash the blue/red lights to show success self.lights.flashLED([1, 2], 3) # otherwise, flash red light to show the drive wasn't found else: self.filer.log( "Drive not found. Cannot dump files.\n") self.lights.flashLED([1], 3) # check for yellow button (convert videos) elif (powerDuration > 0.0 and powerDuration < ticks and captureDuration == 0.0): # flash at 1.5 seconds (if the button is still held) to indicate # that files will be deleted upon button release if (powerDuration * self.TICK_RATE >= 1.5 and (powerDuration * self.TICK_RATE) - 1.5 <= self.TICK_RATE and self.buttons.isPowerPressed()): self.lights.setLED([1, 2], False) self.lights.flashLED([0], 1) # if the button is released if (not self.buttons.isPowerPressed()): # if released under 1.5 seconds, convert the videos if (powerDuration * self.TICK_RATE < 1.5): self.lights.setLED([0, 1, 2], False) # convert videos to mp4 self.filer.convertVideos(self.lights) # otherwise, delete the output elif (powerDuration * self.TICK_RATE >= 1.5): self.wipeFiles() # log tick string if the tick is on a second if (tickSeconds.is_integer()): self.filer.log(tickString + "\n") # update ticks ticks += 1 tickSeconds += self.TICK_RATE # sleep for one TICK_RATE sleep(self.TICK_RATE) # print termination message if (terminateCode == 0): self.filer.log("Returning to config...\n") # disable blue/red LEDs self.lights.setLED([1, 2], False) # Helper function for mainOutput() that wipes all media files from the device. # Takes in an optional argument of whether or not to toggle the lights when # wiping the files def wipeFiles(self, toggleLights=True): if (toggleLights): # set the red LED to ON while files are deleted self.lights.setLED([0, 1, 2], False) self.lights.setLED([1], True) # invoke system commands to wipe the media/log files os.system("sudo rm -rf ../logs") os.system("sudo rm -rf ../media") sleep(7) # sleep for a short time before attempting anything else # since the current log file was destroyed, write to # a new one stating what happened self.filer.checkDirectories() self.filer.log("[config-output] Wiping all output files...\n") if (toggleLights): # flash red/blue alternating to indicate the files were # permanently deleted self.lights.flashLED([1, 2], 4) self.lights.setLED([1, 2], False) # Connect Mode main function def mainConnect(self): # set up loop variables ticks = 0.0 tickSeconds = 0.0 # terminate codes are as follows: # -1 Don't terminate # 0 Terminate and return to config terminateCode = -1 # main loop while (terminateCode < 0): # slowly flash the blue/yellow lights (twice every second) self.lights.setLED([0, 2], tickSeconds.is_integer() or (tickSeconds + 0.5).is_integer()) # create tick string tickString = "[Ticks: {t1:9.2f}] [Running Time: {t2:9.2f}]" tickString = tickString.format(t1=ticks, t2=int(tickSeconds)) tickString = "[config-connect] " + tickString # check for red/yellow button hold (back to config) if (self.buttons.isCapturePressed() and self.buttons.durations[1] * self.TICK_RATE >= 1.0 and self.buttons.isPowerPressed() and self.buttons.durations[0] * self.TICK_RATE >= 1.0): tickString += " (Capture/Power buttons were held)" terminateCode = 0 # log the tick string if the tickSeconds is on a second if (tickSeconds.is_integer()): self.filer.log(tickString + "\n") # update ticks ticks += 1 tickSeconds += self.TICK_RATE # sleep for one TICK_RATE sleep(self.TICK_RATE) # print termination message if (terminateCode == 0): self.filer.log("Returning to config...\n") # disable blue/yellow LEDs self.lights.setLED([0, 2], False)
class Controller: # Controller properties: # camera The Camera object used to record videos/take pictures # filer The Filer object responsible for managing system files # lights The LightManager object used for toggling LEDs # buttons The ButtonManager used to sense button presses # Controller constants: # TICK_RATE The time interval (in seconds) at which the system ticks # to check for/make updates # PASSIVE_LEN The length (in seconds) of the dash cam's passive videos # Constructor def __init__(self): # create a camera self.camera = DashCam() # create a Filer self.filer = Filer() # create a light manager and turn on the power LED self.lights = LightManager() self.lights.setLED([0], True) # create a button manager self.buttons = ButtonManager() # create constants self.TICK_RATE = 0.125 self.PASSIVE_LEN = 10.0 * 60 # log that a new session has begun self.filer.log( "---------- New Session: " + str(datetime.datetime.now()) + " ----------\n", True) # Destructor def __del__(self): # log that the session has ended self.filer.log( "--------- Session Ended: " + str(datetime.datetime.now()) + " ---------\n\n", True) # Main process function. Loops indefinitely until the program is terminated def main(self): # start passively recording self.passiveRecording(1) # termination code: used to help determine why the main loop # was broken (could be the power button, could be too hot # of a cpu, etc.) # -1 = not terminated # 0 = CPU is too hot # 1 = power button was pressed # 2 = debug terminate (exit program but keep pi powered on) terminateCode = -1 # --------------- main loop --------------- # ticks = 0.0 tickSeconds = 0.0 tickPrintRate = 1 # logs the tickString every 'tickPrintRate' seconds terminate = False while (not terminate): tickOnSecond = tickSeconds.is_integer() # write a tick string to be printed to the log tickString = "Tick: {t1:9.2f} | Running Time: {t2:9.2f}" tickString = tickString.format(t1=ticks, t2=int(tickSeconds)) tickHeader = "[dashcam] " tickHeader += "[LED: " + (str(self.lights.states[0]) + str( self.lights.states[1]) + str(self.lights.states[2])) + "] " tickHeader += "[Button: " + ( str(int(self.buttons.durations[0] * self.TICK_RATE)) + "|" + str(int(self.buttons.durations[1] * self.TICK_RATE)) + "] ") tickString = tickHeader + tickString # check the CPU temperature (terminate if needed) if (tickOnSecond): cpuTemp = self.getCPUTemp() terminate = cpuTemp > 80.0 # add to the tick string tickString += " [CPU Temp: " + str(cpuTemp) + "]" # if the temperature exceeds the threshold, stop the program terminate = cpuTemp > 80 if (terminate): self.filer.log("CPU running too hot! Shutting down...") terminateCode = 0 # perform any mode actions needed tickString += self.update(ticks, tickSeconds) # grab the initial button durations powerDuration = self.buttons.durations[0] captureDuration = self.buttons.durations[1] # call the button-detection methods to update their durations self.buttons.isPowerPressed() self.buttons.isCapturePressed() # check for both buttons being pressed if (self.buttons.isPowerPressed() and powerDuration * self.TICK_RATE >= 2.0 and self.buttons.isCapturePressed() and captureDuration * self.TICK_RATE >= 2.0): # terminate and shut down terminate = True terminateCode = 1 # check for power button press elif (self.buttons.isPowerPressed() and powerDuration * self.TICK_RATE >= 2.0 and not self.buttons.isCapturePressed()): # terminate but don't shut down terminate = True terminateCode = 2 # check for capture button press (TAKE PICTURE) elif (captureDuration * self.TICK_RATE <= 2.0 and captureDuration * self.TICK_RATE > 0.0 and not self.buttons.isCapturePressed()): self.filer.log("Capturing image...") # flash LED and take picture self.lights.flashLED([1], 2) self.camera.takePicture(Filer.makeFileName(1), self.filer.imagePath) # update the camera's overlay text if (self.camera.currVideo != None): self.camera.updateOverlays(datetime.datetime.now()) self.camera.currVideo.duration += self.TICK_RATE # sleep for one tick rate self.camera.picam.wait_recording(self.TICK_RATE) # log the tick string if (tickSeconds % tickPrintRate == 0): self.filer.log(tickString + "\n") # increment ticks ticks += 1 tickSeconds += self.TICK_RATE # ----------------------------------------- # self.filer.log("Terminate Code: " + str(terminateCode) + "\n") # check terminate code: shutdown if needed if (terminateCode == 0 or terminateCode == 1): self.filer.log("Shutting down...\n") shutdown_pi(self.lights, [self.buttons, self]) if (terminateCode == 2): # flash LED to show debug terminate self.lights.setLED([0, 1], False) self.lights.flashLED([0, 1], 5) self.filer.log( "Terminating dash cam, but keeping Pi powered on...\n") # ------------------------ Mode Updates ------------------------ # # Main update method for whatever mode the dash cam is in. Takes in # the main loop's tick and tick-second count. Returns a string to # be added to the "tickString" in the main loop to reflect any # changes that were made in this function def update(self, ticks, tickSeconds): stringAddon = "" tickOnSecond = tickSeconds.is_integer() # if the tick is on a second, toggle the "rolling" LED if (tickOnSecond): self.lights.setLED([1], not self.lights.getLED(1)) # if it's time to save the video, split the recording if (tickSeconds % self.PASSIVE_LEN == 0 and ticks > 0): self.passiveRecording(0) # add to the tick string stringAddon += " (Starting next passive video)" # flash LED self.lights.flashLED([1], 4) # return the string to be added to the tickString return stringAddon # -------------------- File Saving/Deleting -------------------- # # Deletes the oldest passive recording and begins a new one (or splits one # that's already running) def passiveRecording(self, isNew): # first, delete the oldest passive recording self.filer.deleteOldestPassive() # depending on the given input, either START a new video, or STOP the # current one and create a new one if (isNew): self.filer.log("Beginning passive recording...\n") self.camera.startVideo(Filer.makeFileName(0), self.filer.passivePath) else: self.filer.log("Splitting passive recording...\n") self.camera.stopVideo() self.camera.startVideo(Filer.makeFileName(0), self.filer.passivePath) # ---------------------- Helper Functions ---------------------- # # Function that finds the cpu's current temperature and returns the value # as a double def getCPUTemp(self): # run the temp check in the command line and parse the output tempOutput = os.popen( "vcgencmd measure_temp | egrep -o '[0-9]*\.[0-9]*'").readlines() return float(tempOutput[0].replace("\n", ""))