add filedump functionality

This commit is contained in:
petrucci4prez 2017-06-02 00:15:05 -04:00
parent 4c2dd985a4
commit a4a1671650
4 changed files with 99 additions and 40 deletions

View File

@ -1,6 +1,6 @@
gmail: gmail:
username: natedwarshuis@gmail.com
passwd: bwsasfxqjbookmed passwd: bwsasfxqjbookmed
recipientList: recipientList:
- natedwarshuis@gmail.com - natedwarshuis@gmail.com
username: natedwarshuis@gmail.com
state: disarmed state: disarmed

View File

@ -40,7 +40,7 @@ class GlusterFSHandler(TimedRotatingFileHandler):
if not os.path.exists(mountpoint): if not os.path.exists(mountpoint):
raise FileNotFoundError raise FileNotFoundError
self._mountpoint = mountpoint self.mountpoint = mountpoint
self._server = server self._server = server
self._volume = volume self._volume = volume
self._options = options self._options = options
@ -56,18 +56,20 @@ class GlusterFSHandler(TimedRotatingFileHandler):
self.setFormatter(fmt) self.setFormatter(fmt)
def _mount(self): def _mount(self):
if os.path.ismount(self._mountpoint): if os.path.ismount(self.mountpoint):
# this assumes that the already-mounted device is the one intended # this assumes that the already-mounted device is the one intended
logger.warning('Device already mounted at {}'.format(self._mountpoint)) logger.warning('Device already mounted at {}'.format(self.mountpoint))
else: else:
dst = self._server + ':/' + self._volume dst = self._server + ':/' + self._volume
cmd = ['mount', '-t', 'glusterfs', dst, self._mountpoint] cmd = ['mount', '-t', 'glusterfs', dst, self.mountpoint]
if self._options: if self._options:
cmd[1:1] = ['-o', self._options] cmd[1:1] = ['-o', self._options]
self._run(cmd) self._run(cmd)
self.isMounted = True
def _unmount(self): def _unmount(self):
self._run(['umount', self._mountpoint]) self._run(['umount', self.mountpoint])
self.isMounted = False
def _run(self, cmd): def _run(self, cmd):
try: try:

View File

@ -1,6 +1,5 @@
import RPi.GPIO as GPIO import RPi.GPIO as GPIO
import time, logging import time, logging
from datetime import datetime
from threading import Lock from threading import Lock
from functools import partial from functools import partial
from collections import namedtuple from collections import namedtuple
@ -12,7 +11,7 @@ from listeners import KeypadListener, PipeListener
from blinkenLights import Blinkenlights from blinkenLights import Blinkenlights
from soundLib import SoundLib from soundLib import SoundLib
from webInterface import initWebInterface from webInterface import initWebInterface
from stream import initCamera from stream import initCamera, FileDump
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -142,15 +141,16 @@ class StateMachine:
if self.currentState == self.states.armed: if self.currentState == self.states.armed:
self.selectState(SIGNALS.TRIGGER) self.selectState(SIGNALS.TRIGGER)
fileDump = FileDump()
sensitiveStates = (self.states.armed, self.states.armedCountdown, self.states.triggered)
def actionVideo(pin): def actionVideo(pin):
if self.currentState in (self.states.armed, self.states.armedCountdown, self.states.triggered): if self.currentState in sensitiveStates:
self.selectState(SIGNALS.TRIGGER) self.selectState(SIGNALS.TRIGGER)
while GPIO.input(pin): fileDump.addInitiator(pin)
# TODO: check that this path exists while GPIO.input(pin) and self.currentState in sensitiveStates:
path = '/mnt/glusterfs/pyledriver/images/%s.jpg' time.sleep(0.1)
#~ with open(path % datetime.now(), 'wb') as f: fileDump.removeInitiator(pin)
#~ f.write(camera.getFrame())
time.sleep(0.2)
setupMotionSensor(5, 'Nate\'s room', action) setupMotionSensor(5, 'Nate\'s room', action)
setupMotionSensor(19, 'front door', action) setupMotionSensor(19, 'front door', action)

107
stream.py
View File

@ -12,10 +12,12 @@ From a logging an error handling standpoint, all 'errors' here are logged as
'critical' which will shut down the entire program and send an email. 'critical' which will shut down the entire program and send an email.
""" """
from auxilary import async, waitForPath import gi, time, os, logging
from threading import Thread from datetime import datetime
from threading import Thread, Lock
import gi, time, logging from auxilary import async, waitForPath, mkdirSafe
from sharedLogging import gluster
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO) logger.setLevel(logging.INFO)
@ -72,9 +74,10 @@ def eventLoop(pipeline, block=True, doProgress=False, targetState=Gst.State.PLAY
pName = pipeline.get_name() pName = pipeline.get_name()
bus = pipeline.get_bus() bus = pipeline.get_bus()
# TODO: add killswitch to exit this thread?
while 1: while 1:
msg = bus.timed_pop(1e18 if block else 0) msg = bus.timed_pop(1e18 if block else 0)
if not msg: if not msg:
return return
@ -87,9 +90,8 @@ def eventLoop(pipeline, block=True, doProgress=False, targetState=Gst.State.PLAY
# messages that involve manipulating the pipeline # messages that involve manipulating the pipeline
if msgType == Gst.MessageType.REQUEST_STATE: if msgType == Gst.MessageType.REQUEST_STATE:
state = msg.parse_request_state() state = msg.parse_request_state()
logger.info('Setting state to %s as requested by %s', logger.info('Setting state to %s as requested by %s', state.value_name, msgSrcName)
Gst.Element.get_state_name(state), msgSrcName)
pipeline.set_state(state) pipeline.set_state(state)
@ -218,7 +220,10 @@ def eventLoop(pipeline, block=True, doProgress=False, targetState=Gst.State.PLAY
elif msgType == Gst.MessageType.UNKNOWN: elif msgType == Gst.MessageType.UNKNOWN:
gstPrintMsg(pName, 'Unknown message', sName=msgSrcname) gstPrintMsg(pName, 'Unknown message', sName=msgSrcname)
@async(daemon=True) @async(daemon=True)
def mainLoop(pipeline):
eventLoop(pipeline, block=True, doProgress=False, targetState=Gst.State.PLAYING)
def startPipeline(pipeline, play=True): def startPipeline(pipeline, play=True):
pName = pipeline.get_name() pName = pipeline.get_name()
stateChange = pipeline.set_state(Gst.State.PAUSED) stateChange = pipeline.set_state(Gst.State.PAUSED)
@ -259,7 +264,7 @@ def startPipeline(pipeline, play=True):
# ...we end up here and loop until Tool releases their next album # ...we end up here and loop until Tool releases their next album
try: try:
eventLoop(pipeline, block=True, doProgress=False, targetState=Gst.State.PLAYING) mainLoop(pipeline)
except: except:
raise GstException raise GstException
@ -319,47 +324,99 @@ def initCamera(video=True, audio=True):
startPipeline(pipeline) startPipeline(pipeline)
class FileDump: class FileDump:
'''
Pipeline that takes audio and input from two udp ports, muxes them, and
dumps the result to a file. Intended to work with the Camera above. Will
init to a paused state, then will transition to a playing state (which will
dump the file) when at least one initiator registers with the class.
Initiators are represented by unique identifiers held in a list. The current
use case is that each identifier is for the pin of the IR sensor that
detects motion, and thus adding a pin number to the list signifies that
video/audio should be recorded
'''
def __init__(self): def __init__(self):
self._initiators = []
self._lock = Lock()
if not gluster.isMounted:
logger.error('Attempting to init FileDump without gluster mounted. Aborting')
raise SystemExit
self._savePath = os.path.join(gluster.mountpoint + '/video')
mkdirSafe(self._savePath, logger)
self.pipeline = Gst.Pipeline.new('filedump') self.pipeline = Gst.Pipeline.new('filedump')
aSource = Gst.ElementFactory.make('udpsrc', 'audioSource') aSource = Gst.ElementFactory.make('udpsrc', 'audioSource')
aParse = Gst.ElementFactory.make('opusparse', 'audioParse') aJitBuf = Gst.ElementFactory.make('rtpjitterbuffer', 'audioJitterBuffer')
aDepay = Gst.ElementFactory.make('rtpopusdepay', 'audioDepay')
aQueue = Gst.ElementFactory.make('queue', 'audioQueue') aQueue = Gst.ElementFactory.make('queue', 'audioQueue')
aCaps = Gst.Caps.from_string('application/x-rtp,encoding-name=OPUS,payload=96')
vSource = Gst.ElementFactory.make('udpsrc', 'videoSource') vSource = Gst.ElementFactory.make('udpsrc', 'videoSource')
vJitBuf = Gst.ElementFactory.make('rtpjitterbuffer', 'videoJitterBuffer')
vDepay = Gst.ElementFactory.make('rtph264depay', 'videoDepay')
vParse = Gst.ElementFactory.make('h264parse', 'videoParse') vParse = Gst.ElementFactory.make('h264parse', 'videoParse')
vQueue = Gst.ElementFactory.make('queue', 'videoQueue') vQueue = Gst.ElementFactory.make('queue', 'videoQueue')
vCaps = Gst.Caps.from_string('application/x-rtp,encoding-name=H264,payload=96')
mux = Gst.ElementFactory.make('matroskamux', 'mux') mux = Gst.ElementFactory.make('matroskamux', 'mux')
self.sink = Gst.ElementFactory.make('filesink', 'sink') self.sink = Gst.ElementFactory.make('filesink', 'sink')
self.sink.set_property('location', '/dev/null') self.sink.set_property('location', '/dev/null')
aSource.set_property('port', 8000) aSource.set_property('port', 8002)
vSource.set_property('port', 9000) vSource.set_property('port', 9002)
self.pipeline.add(aSource, aParse, aQueue, vSource, vParse, vQueue, mux, self.sink) self.pipeline.add(aSource, aJitBuf, aDepay, aQueue,
vSource, vJitBuf, vDepay, vParse, vQueue, mux, self.sink)
linkElements(vSource, vParse)
linkElements(aSource, aJitBuf, aCaps)
linkElements(aJitBuf, aDepay)
linkElements(aDepay, aQueue)
linkElements(aQueue, mux)
linkElements(vSource, vJitBuf, vCaps)
linkElements(vJitBuf, vDepay)
linkElements(vDepay, vParse)
linkElements(vParse, vQueue) linkElements(vParse, vQueue)
linkElements(vQueue, mux) linkElements(vQueue, mux)
linkElements(aSource, aParse)
linkElements(aParse, aQueue)
linkElements(aQueue, mux)
linkElements(mux, self.sink) linkElements(mux, self.sink)
startPipeline(self.pipeline, play=False) # TODO: there is probably a better way to init than starting up to PAUSE
# and then dropping back down to NULL
startPipeline(self.pipeline, play=False)
def setPath(self, path): self.pipeline.post_message(Gst.Message.new_request_state(self.pipeline, Gst.State.NULL))
self.sink.set_property('location', path)
def play(self): def addInitiator(self, identifier):
self.pipeline.set_state(Gst.State.PLAYING) with self._lock:
if identifier in self._initiators:
logger.warn('Identifier \'%s\' already in FileDump initiator list', identifier)
else:
self._initiators.append(identifier)
if self.pipeline.get_state(Gst.CLOCK_TIME_NONE).state == Gst.State.NULL:
filePath = os.path.join(self._savePath, '{}.mkv'.format(datetime.now()))
self.sink.set_property('location', filePath)
# TODO: cannot post messages from null to change state, is this bad?
self.pipeline.set_state(Gst.State.PLAYING)
def pause(self): def removeInitiator(self, identifier):
self.pipeline.set_state(Gst.State.PAUSED) with self._lock:
try:
self._initiators.remove(identifier)
except ValueError:
logger.warn('Attempted to remove nonexistant identifier \'%s\'', identifier)
if len(self._initiators) == 0:
self.pipeline.set_state(Gst.State.NULL)
Gst.init(None) Gst.init(None)