add janus, remove camera, and merge webInterface into parent process
This commit is contained in:
parent
a93f116aae
commit
06a8a4443f
|
@ -113,6 +113,7 @@ class KeypadListener(Thread):
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# TODO: these are not threadsafe
|
||||||
# TODO: this code gets really confused if the pipe is deleted
|
# TODO: this code gets really confused if the pipe is deleted
|
||||||
class PipeListener(Thread):
|
class PipeListener(Thread):
|
||||||
def __init__(self, callback, path):
|
def __init__(self, callback, path):
|
||||||
|
|
21
main.py
21
main.py
|
@ -3,7 +3,7 @@
|
||||||
import sys, os, time, signal, traceback
|
import sys, os, time, signal, traceback
|
||||||
import RPi.GPIO as GPIO
|
import RPi.GPIO as GPIO
|
||||||
from queue import Queue
|
from queue import Queue
|
||||||
from multiprocessing.managers import BaseManager, DictProxy
|
from multiprocessing.managers import BaseManager
|
||||||
|
|
||||||
def clean():
|
def clean():
|
||||||
GPIO.cleanup()
|
GPIO.cleanup()
|
||||||
|
@ -13,11 +13,6 @@ def clean():
|
||||||
except NameError:
|
except NameError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
try:
|
|
||||||
webInterface.stop() # Kill process 1
|
|
||||||
except NameError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
logger.info('Terminated root process - PID: %s', os.getpid())
|
logger.info('Terminated root process - PID: %s', os.getpid())
|
||||||
logger.stop()
|
logger.stop()
|
||||||
|
@ -38,11 +33,7 @@ class ResourceManager(BaseManager):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
from camera import Camera
|
|
||||||
from microphone import Microphone
|
|
||||||
self.register('Camera', Camera)
|
|
||||||
self.register('Queue', Queue)
|
self.register('Queue', Queue)
|
||||||
self.register('Dict', dict, DictProxy)
|
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
self.shutdown()
|
self.shutdown()
|
||||||
|
@ -58,8 +49,6 @@ if __name__ == '__main__':
|
||||||
manager.start() # Child process 1
|
manager.start() # Child process 1
|
||||||
|
|
||||||
loggerQueue = manager.Queue() # used to buffer logs
|
loggerQueue = manager.Queue() # used to buffer logs
|
||||||
camera = manager.Camera(loggerQueue)
|
|
||||||
stateDict = manager.Dict() # used to hold state info
|
|
||||||
ttsQueue = manager.Queue() # used as buffer for TTS Engine
|
ttsQueue = manager.Queue() # used as buffer for TTS Engine
|
||||||
|
|
||||||
from sharedLogging import MasterLogger
|
from sharedLogging import MasterLogger
|
||||||
|
@ -68,12 +57,10 @@ if __name__ == '__main__':
|
||||||
from notifier import criticalError
|
from notifier import criticalError
|
||||||
|
|
||||||
from stateMachine import StateMachine
|
from stateMachine import StateMachine
|
||||||
stateMachine = StateMachine(camera, ttsQueue, stateDict)
|
stateMachine = StateMachine()
|
||||||
|
|
||||||
from webInterface import WebInterface
|
|
||||||
webInterface = WebInterface(camera, stateDict, ttsQueue, loggerQueue)
|
|
||||||
webInterface.start() # Child process 2
|
|
||||||
|
|
||||||
|
# TODO: segfaults are annoying :(
|
||||||
|
#~ signal.signal(signal.SIGSEGV, sig_handler)
|
||||||
signal.signal(signal.SIGTERM, sigtermHandler)
|
signal.signal(signal.SIGTERM, sigtermHandler)
|
||||||
|
|
||||||
while 1:
|
while 1:
|
||||||
|
|
|
@ -8,6 +8,9 @@ def SlaveLogger(name, level, queue):
|
||||||
logger.propagate = False
|
logger.propagate = False
|
||||||
return logger
|
return logger
|
||||||
|
|
||||||
|
#TODO: need to add mounting code here for gluster. since this app is the only
|
||||||
|
# gluster user, (un)mounting should be handled here instead of by systemd
|
||||||
|
|
||||||
class MasterLogger():
|
class MasterLogger():
|
||||||
def __init__(self, name, level, queue):
|
def __init__(self, name, level, queue):
|
||||||
consoleFormat = logging.Formatter('[%(name)s] [%(levelname)s] %(message)s')
|
consoleFormat = logging.Formatter('[%(name)s] [%(levelname)s] %(message)s')
|
||||||
|
|
43
soundLib.py
43
soundLib.py
|
@ -3,6 +3,7 @@ from pygame import mixer
|
||||||
from subprocess import call
|
from subprocess import call
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from auxilary import async
|
from auxilary import async
|
||||||
|
from queue import Queue
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -66,7 +67,7 @@ class SoundLib:
|
||||||
|
|
||||||
_sentinel = None
|
_sentinel = None
|
||||||
|
|
||||||
def __init__(self, ttsQueue):
|
def __init__(self):
|
||||||
mixer.pre_init(frequency=44100, size=-16, channels=2, buffer=1024)
|
mixer.pre_init(frequency=44100, size=-16, channels=2, buffer=1024)
|
||||||
mixer.init()
|
mixer.init()
|
||||||
|
|
||||||
|
@ -88,7 +89,7 @@ class SoundLib:
|
||||||
self.volume = 100
|
self.volume = 100
|
||||||
self._applyVolumesToSounds(self.volume)
|
self._applyVolumesToSounds(self.volume)
|
||||||
|
|
||||||
self._ttsQueue = ttsQueue
|
self._ttsQueue = Queue()
|
||||||
self._stop = threading.Event()
|
self._stop = threading.Event()
|
||||||
self._startMonitor()
|
self._startMonitor()
|
||||||
|
|
||||||
|
@ -101,21 +102,7 @@ class SoundLib:
|
||||||
self._applyVolumesToSounds(0)
|
self._applyVolumesToSounds(0)
|
||||||
|
|
||||||
def speak(self, text):
|
def speak(self, text):
|
||||||
basename = hashlib.md5(text.encode()).hexdigest()
|
self._ttsQueue.put_nowait(text)
|
||||||
|
|
||||||
if basename in self._ttsSounds:
|
|
||||||
self._ttsSounds.move_to_end(basename)
|
|
||||||
else:
|
|
||||||
path = '/tmp/' + basename
|
|
||||||
call(['espeak', '-a180', '-g8', '-p75', '-w', path, text])
|
|
||||||
self._ttsSounds[basename] = TTSSound(path)
|
|
||||||
|
|
||||||
self._fader(
|
|
||||||
lowerVolume=0.1,
|
|
||||||
totalDuration=self._ttsSounds[basename].get_length()
|
|
||||||
)
|
|
||||||
self._ttsSounds[basename].play()
|
|
||||||
logger.debug('TTS engine received "%s"', text)
|
|
||||||
|
|
||||||
@async(daemon=False)
|
@async(daemon=False)
|
||||||
def _fader(self, lowerVolume, totalDuration, fadeDuration=0.2, stepSize=5):
|
def _fader(self, lowerVolume, totalDuration, fadeDuration=0.2, stepSize=5):
|
||||||
|
@ -156,6 +143,7 @@ class SoundLib:
|
||||||
for name, sound in s.items():
|
for name, sound in s.items():
|
||||||
sound.set_volume(v)
|
sound.set_volume(v)
|
||||||
|
|
||||||
|
# TODO: maybe could simply now that we are not using MP for TTS
|
||||||
def _ttsMonitor(self):
|
def _ttsMonitor(self):
|
||||||
q = self._ttsQueue
|
q = self._ttsQueue
|
||||||
has_task_done = hasattr(q, 'task_done')
|
has_task_done = hasattr(q, 'task_done')
|
||||||
|
@ -164,7 +152,7 @@ class SoundLib:
|
||||||
text = self._ttsQueue.get(True)
|
text = self._ttsQueue.get(True)
|
||||||
if text is self._sentinel:
|
if text is self._sentinel:
|
||||||
break
|
break
|
||||||
self.speak(text)
|
self._playSpeech(text)
|
||||||
if has_task_done:
|
if has_task_done:
|
||||||
q.task_done()
|
q.task_done()
|
||||||
except queue.Empty:
|
except queue.Empty:
|
||||||
|
@ -175,12 +163,29 @@ class SoundLib:
|
||||||
text = self._ttsQueue.get(False)
|
text = self._ttsQueue.get(False)
|
||||||
if text is self._sentinel:
|
if text is self._sentinel:
|
||||||
break
|
break
|
||||||
self.speak(text)
|
self._playSpeech(text)
|
||||||
if has_task_done:
|
if has_task_done:
|
||||||
q.task_done()
|
q.task_done()
|
||||||
except queue.Empty:
|
except queue.Empty:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
def _playSpeech(self, text):
|
||||||
|
basename = hashlib.md5(text.encode()).hexdigest()
|
||||||
|
|
||||||
|
if basename in self._ttsSounds:
|
||||||
|
self._ttsSounds.move_to_end(basename)
|
||||||
|
else:
|
||||||
|
path = '/tmp/' + basename
|
||||||
|
call(['espeak', '-a180', '-g8', '-p75', '-w', path, text])
|
||||||
|
self._ttsSounds[basename] = TTSSound(path)
|
||||||
|
|
||||||
|
self._fader(
|
||||||
|
lowerVolume=0.1,
|
||||||
|
totalDuration=self._ttsSounds[basename].get_length()
|
||||||
|
)
|
||||||
|
self._ttsSounds[basename].play()
|
||||||
|
logger.debug('TTS engine received "%s"', text)
|
||||||
|
|
||||||
def _startMonitor(self):
|
def _startMonitor(self):
|
||||||
self._thread = t = threading.Thread(target=self._ttsMonitor, daemon=True)
|
self._thread = t = threading.Thread(target=self._ttsMonitor, daemon=True)
|
||||||
t.start()
|
t.start()
|
||||||
|
|
104
stateMachine.py
104
stateMachine.py
|
@ -11,6 +11,7 @@ from notifier import intruderAlert
|
||||||
from listeners import KeypadListener, PipeListener
|
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
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -36,17 +37,17 @@ class State:
|
||||||
|
|
||||||
def entry(self):
|
def entry(self):
|
||||||
logger.debug('entering ' + self.name)
|
logger.debug('entering ' + self.name)
|
||||||
#~ if self.sound:
|
if self.sound:
|
||||||
#~ self.sound.play()
|
self.sound.play()
|
||||||
#~ self.stateMachine.LED.blink = self.blinkLED
|
self.stateMachine.LED.blink = self.blinkLED
|
||||||
#~ self.stateMachine.keypadListener.resetBuffer()
|
self.stateMachine.keypadListener.resetBuffer()
|
||||||
for c in self.entryCallbacks:
|
for c in self.entryCallbacks:
|
||||||
c()
|
c()
|
||||||
|
|
||||||
def exit(self):
|
def exit(self):
|
||||||
logger.debug('exiting ' + self.name)
|
logger.debug('exiting ' + self.name)
|
||||||
#~ if self.sound:
|
if self.sound:
|
||||||
#~ self.sound.stop()
|
self.sound.stop()
|
||||||
for c in self.exitCallbacks:
|
for c in self.exitCallbacks:
|
||||||
c()
|
c()
|
||||||
|
|
||||||
|
@ -64,9 +65,8 @@ class State:
|
||||||
return hash(self.name)
|
return hash(self.name)
|
||||||
|
|
||||||
class StateMachine:
|
class StateMachine:
|
||||||
def __init__(self, camera, ttsQueue, sharedState):
|
def __init__(self):
|
||||||
self.sharedState = sharedState
|
self.soundLib = SoundLib()
|
||||||
self.soundLib = SoundLib(ttsQueue)
|
|
||||||
self._cfg = ConfigFile('config.yaml')
|
self._cfg = ConfigFile('config.yaml')
|
||||||
|
|
||||||
def startTimer(t, sound):
|
def startTimer(t, sound):
|
||||||
|
@ -131,7 +131,7 @@ class StateMachine:
|
||||||
|
|
||||||
self._lock = Lock()
|
self._lock = Lock()
|
||||||
|
|
||||||
#~ self.LED = Blinkenlights(17)
|
self.LED = Blinkenlights(17)
|
||||||
|
|
||||||
def action():
|
def action():
|
||||||
if self.currentState == self.armed:
|
if self.currentState == self.armed:
|
||||||
|
@ -142,17 +142,17 @@ class StateMachine:
|
||||||
self.selectState(SIGNALS.TRIGGER)
|
self.selectState(SIGNALS.TRIGGER)
|
||||||
while GPIO.input(pin):
|
while GPIO.input(pin):
|
||||||
path = '/mnt/glusterfs/pyledriver/images/%s.jpg'
|
path = '/mnt/glusterfs/pyledriver/images/%s.jpg'
|
||||||
with open(path % datetime.now(), 'wb') as f:
|
#~ with open(path % datetime.now(), 'wb') as f:
|
||||||
f.write(camera.getFrame())
|
#~ f.write(camera.getFrame())
|
||||||
time.sleep(0.2)
|
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)
|
||||||
#~ setupMotionSensor(26, 'Laura\'s room', action)
|
setupMotionSensor(26, 'Laura\'s room', action)
|
||||||
#~ setupMotionSensor(6, 'deck window', partial(actionVideo, 6))
|
setupMotionSensor(6, 'deck window', partial(actionVideo, 6))
|
||||||
#~ setupMotionSensor(13, 'kitchen bar', partial(actionVideo, 13))
|
setupMotionSensor(13, 'kitchen bar', partial(actionVideo, 13))
|
||||||
|
|
||||||
#~ setupDoorSensor(22, action, self.soundLib.soundEffects['door'])
|
setupDoorSensor(22, action, self.soundLib.soundEffects['door'])
|
||||||
|
|
||||||
secretTable = {
|
secretTable = {
|
||||||
"dynamoHum": partial(self.selectState, SIGNALS.DISARM),
|
"dynamoHum": partial(self.selectState, SIGNALS.DISARM),
|
||||||
|
@ -160,35 +160,36 @@ class StateMachine:
|
||||||
"imTheSlime": partial(self.selectState, SIGNALS.INSTANT_ARM)
|
"imTheSlime": partial(self.selectState, SIGNALS.INSTANT_ARM)
|
||||||
}
|
}
|
||||||
|
|
||||||
#~ def secretCallback(secret, logger):
|
def secretCallback(secret, logger):
|
||||||
#~ if secret in secretTable:
|
if secret in secretTable:
|
||||||
#~ secretTable[secret]()
|
secretTable[secret]()
|
||||||
#~ logger.debug('Secret pipe listener received: \"%s\"', secret)
|
logger.debug('Secret pipe listener received: \"%s\"', secret)
|
||||||
#~ elif logger:
|
elif logger:
|
||||||
#~ logger.error('Secret pipe listener received invalid secret')
|
logger.error('Secret pipe listener received invalid secret')
|
||||||
|
|
||||||
#~ self.secretListener = PipeListener(
|
self.secretListener = PipeListener(
|
||||||
#~ callback = secretCallback,
|
callback = secretCallback,
|
||||||
#~ path = '/tmp/secret'
|
path = '/tmp/secret'
|
||||||
#~ )
|
)
|
||||||
|
|
||||||
#~ def ttsCallback(text, logger):
|
def ttsCallback(text, logger):
|
||||||
#~ self.soundLib.speak(text)
|
self.soundLib.speak(text)
|
||||||
#~ logger.debug('TTS pipe listener received text: \"%s\"', text)
|
logger.debug('TTS pipe listener received text: \"%s\"', text)
|
||||||
|
|
||||||
#~ self.ttsListener = PipeListener(
|
self.ttsListener = PipeListener(
|
||||||
#~ callback = ttsCallback,
|
callback = ttsCallback,
|
||||||
#~ path = '/tmp/tts'
|
path = '/tmp/tts'
|
||||||
#~ )
|
)
|
||||||
#~ self.keypadListener = KeypadListener(
|
self.keypadListener = KeypadListener(
|
||||||
#~ stateMachine = self,
|
stateMachine = self,
|
||||||
#~ callbackDisarm = partial(self.selectState, 'disarm'),
|
callbackDisarm = partial(self.selectState, 'disarm'),
|
||||||
#~ callbackArm = partial(self.selectState, 'arm'),
|
callbackArm = partial(self.selectState, 'arm'),
|
||||||
#~ soundLib = self.soundLib,
|
soundLib = self.soundLib,
|
||||||
#~ passwd = '5918462'
|
passwd = '5918462'
|
||||||
#~ )
|
)
|
||||||
|
|
||||||
|
initWebInterface(self)
|
||||||
|
|
||||||
self.sharedState['name'] = self.currentState.name
|
|
||||||
self.currentState.entry()
|
self.currentState.entry()
|
||||||
|
|
||||||
def selectState(self, signal):
|
def selectState(self, signal):
|
||||||
|
@ -201,7 +202,6 @@ class StateMachine:
|
||||||
self.currentState.entry()
|
self.currentState.entry()
|
||||||
finally:
|
finally:
|
||||||
self._lock.release()
|
self._lock.release()
|
||||||
self.sharedState['name'] = self.currentState.name
|
|
||||||
|
|
||||||
self._cfg['state'] = self.currentState.name
|
self._cfg['state'] = self.currentState.name
|
||||||
self._cfg.sync()
|
self._cfg.sync()
|
||||||
|
@ -209,22 +209,14 @@ class StateMachine:
|
||||||
logger.info('state changed to %s', self.currentState)
|
logger.info('state changed to %s', self.currentState)
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
try:
|
if hasattr(self, 'LED'):
|
||||||
self.LED.__del__()
|
self.LED.__del__()
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
try:
|
if hasattr(self, 'soundLib'):
|
||||||
self.soundLib.__del__()
|
self.soundLib.__del__()
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
try:
|
if hasattr(self, 'pipeListener'):
|
||||||
self.pipeListener.__del__()
|
self.pipeListener.__del__()
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
try:
|
if hasattr(self, 'keypadListener'):
|
||||||
self.keypadListener.__del__()
|
self.keypadListener.__del__()
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,25 @@
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
.navbar-text {
|
||||||
|
margin-left: 10px !important;
|
||||||
|
}
|
||||||
|
.form-inline .form-group {
|
||||||
|
display: inline-block;
|
||||||
|
margin-bottom: 0;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
.form-inline .form-control {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
.nav > li {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-form {
|
||||||
|
margin-left: 0;
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-collapse .navbar-nav.navbar-right:last-child {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1 @@
|
||||||
|
(function(t,e){if(typeof exports=="object")module.exports=e();else if(typeof define=="function"&&define.amd)define(e);else t.Spinner=e()})(this,function(){"use strict";var t=["webkit","Moz","ms","O"],e={},i;function o(t,e){var i=document.createElement(t||"div"),o;for(o in e)i[o]=e[o];return i}function n(t){for(var e=1,i=arguments.length;e<i;e++)t.appendChild(arguments[e]);return t}var r=function(){var t=o("style",{type:"text/css"});n(document.getElementsByTagName("head")[0],t);return t.sheet||t.styleSheet}();function s(t,o,n,s){var a=["opacity",o,~~(t*100),n,s].join("-"),f=.01+n/s*100,l=Math.max(1-(1-t)/o*(100-f),t),u=i.substring(0,i.indexOf("Animation")).toLowerCase(),d=u&&"-"+u+"-"||"";if(!e[a]){r.insertRule("@"+d+"keyframes "+a+"{"+"0%{opacity:"+l+"}"+f+"%{opacity:"+t+"}"+(f+.01)+"%{opacity:1}"+(f+o)%100+"%{opacity:"+t+"}"+"100%{opacity:"+l+"}"+"}",r.cssRules.length);e[a]=1}return a}function a(e,i){var o=e.style,n,r;i=i.charAt(0).toUpperCase()+i.slice(1);for(r=0;r<t.length;r++){n=t[r]+i;if(o[n]!==undefined)return n}if(o[i]!==undefined)return i}function f(t,e){for(var i in e)t.style[a(t,i)||i]=e[i];return t}function l(t){for(var e=1;e<arguments.length;e++){var i=arguments[e];for(var o in i)if(t[o]===undefined)t[o]=i[o]}return t}function u(t){var e={x:t.offsetLeft,y:t.offsetTop};while(t=t.offsetParent)e.x+=t.offsetLeft,e.y+=t.offsetTop;return e}function d(t,e){return typeof t=="string"?t:t[e%t.length]}var p={lines:12,length:7,width:5,radius:10,rotate:0,corners:1,color:"#000",direction:1,speed:1,trail:100,opacity:1/4,fps:20,zIndex:2e9,className:"spinner",top:"auto",left:"auto",position:"relative"};function c(t){if(typeof this=="undefined")return new c(t);this.opts=l(t||{},c.defaults,p)}c.defaults={};l(c.prototype,{spin:function(t){this.stop();var e=this,n=e.opts,r=e.el=f(o(0,{className:n.className}),{position:n.position,width:0,zIndex:n.zIndex}),s=n.radius+n.length+n.width,a,l;if(t){t.insertBefore(r,t.firstChild||null);l=u(t);a=u(r);f(r,{left:(n.left=="auto"?l.x-a.x+(t.offsetWidth>>1):parseInt(n.left,10)+s)+"px",top:(n.top=="auto"?l.y-a.y+(t.offsetHeight>>1):parseInt(n.top,10)+s)+"px"})}r.setAttribute("role","progressbar");e.lines(r,e.opts);if(!i){var d=0,p=(n.lines-1)*(1-n.direction)/2,c,h=n.fps,m=h/n.speed,y=(1-n.opacity)/(m*n.trail/100),g=m/n.lines;(function v(){d++;for(var t=0;t<n.lines;t++){c=Math.max(1-(d+(n.lines-t)*g)%m*y,n.opacity);e.opacity(r,t*n.direction+p,c,n)}e.timeout=e.el&&setTimeout(v,~~(1e3/h))})()}return e},stop:function(){var t=this.el;if(t){clearTimeout(this.timeout);if(t.parentNode)t.parentNode.removeChild(t);this.el=undefined}return this},lines:function(t,e){var r=0,a=(e.lines-1)*(1-e.direction)/2,l;function u(t,i){return f(o(),{position:"absolute",width:e.length+e.width+"px",height:e.width+"px",background:t,boxShadow:i,transformOrigin:"left",transform:"rotate("+~~(360/e.lines*r+e.rotate)+"deg) translate("+e.radius+"px"+",0)",borderRadius:(e.corners*e.width>>1)+"px"})}for(;r<e.lines;r++){l=f(o(),{position:"absolute",top:1+~(e.width/2)+"px",transform:e.hwaccel?"translate3d(0,0,0)":"",opacity:e.opacity,animation:i&&s(e.opacity,e.trail,a+r*e.direction,e.lines)+" "+1/e.speed+"s linear infinite"});if(e.shadow)n(l,f(u("#000","0 0 4px "+"#000"),{top:2+"px"}));n(t,n(l,u(d(e.color,r),"0 0 1px rgba(0,0,0,.1)")))}return t},opacity:function(t,e,i){if(e<t.childNodes.length)t.childNodes[e].style.opacity=i}});function h(){function t(t,e){return o("<"+t+' xmlns="urn:schemas-microsoft.com:vml" class="spin-vml">',e)}r.addRule(".spin-vml","behavior:url(#default#VML)");c.prototype.lines=function(e,i){var o=i.length+i.width,r=2*o;function s(){return f(t("group",{coordsize:r+" "+r,coordorigin:-o+" "+-o}),{width:r,height:r})}var a=-(i.width+i.length)*2+"px",l=f(s(),{position:"absolute",top:a,left:a}),u;function p(e,r,a){n(l,n(f(s(),{rotation:360/i.lines*e+"deg",left:~~r}),n(f(t("roundrect",{arcsize:i.corners}),{width:o,height:i.width,left:i.radius,top:-i.width>>1,filter:a}),t("fill",{color:d(i.color,e),opacity:i.opacity}),t("stroke",{opacity:0}))))}if(i.shadow)for(u=1;u<=i.lines;u++)p(u,-2,"progid:DXImageTransform.Microsoft.Blur(pixelradius=2,makeshadow=1,shadowopacity=.3)");for(u=1;u<=i.lines;u++)p(u);return n(e,l)};c.prototype.opacity=function(t,e,i,o){var n=t.firstChild;o=o.shadow&&o.lines||0;if(n&&e+o<n.childNodes.length){n=n.childNodes[e+o];n=n&&n.firstChild;n=n&&n.firstChild;if(n)n.opacity=i}}}var m=f(o("group"),{behavior:"url(#default#VML)"});if(!a(m,"transform")&&m.adj)h();else i=a(m,"animation");return c});
|
|
@ -0,0 +1,245 @@
|
||||||
|
// We make use of this 'server' variable to provide the address of the
|
||||||
|
// REST Janus API. By default, in this example we assume that Janus is
|
||||||
|
// co-located with the web server hosting the HTML pages but listening
|
||||||
|
// on a different port (8088, the default for HTTP in Janus), which is
|
||||||
|
// why we make use of the 'window.location.hostname' base address. Since
|
||||||
|
// Janus can also do HTTPS, and considering we don't really want to make
|
||||||
|
// use of HTTP for Janus if your demos are served on HTTPS, we also rely
|
||||||
|
// on the 'window.location.protocol' prefix to build the variable, in
|
||||||
|
// particular to also change the port used to contact Janus (8088 for
|
||||||
|
// HTTP and 8089 for HTTPS, if enabled).
|
||||||
|
// In case you place Janus behind an Apache frontend (as we did on the
|
||||||
|
// online demos at http://janus.conf.meetecho.com) you can just use a
|
||||||
|
// relative path for the variable, e.g.:
|
||||||
|
//
|
||||||
|
// var server = "/janus";
|
||||||
|
//
|
||||||
|
// which will take care of this on its own.
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// If you want to use the WebSockets frontend to Janus, instead, you'll
|
||||||
|
// have to pass a different kind of address, e.g.:
|
||||||
|
//
|
||||||
|
// var server = "ws://" + window.location.hostname + ":8188";
|
||||||
|
//
|
||||||
|
// Of course this assumes that support for WebSockets has been built in
|
||||||
|
// when compiling the gateway. WebSockets support has not been tested
|
||||||
|
// as much as the REST API, so handle with care!
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// If you have multiple options available, and want to let the library
|
||||||
|
// autodetect the best way to contact your gateway (or pool of gateways),
|
||||||
|
// you can also pass an array of servers, e.g., to provide alternative
|
||||||
|
// means of access (e.g., try WebSockets first and, if that fails, fall
|
||||||
|
// back to plain HTTP) or just have failover servers:
|
||||||
|
//
|
||||||
|
// var server = [
|
||||||
|
// "ws://" + window.location.hostname + ":8188",
|
||||||
|
// "/janus"
|
||||||
|
// ];
|
||||||
|
//
|
||||||
|
// This will tell the library to try connecting to each of the servers
|
||||||
|
// in the presented order. The first working server will be used for
|
||||||
|
// the whole session.
|
||||||
|
//
|
||||||
|
//~ var server = null;
|
||||||
|
//~ if(window.location.protocol === 'http:')
|
||||||
|
//~ server = "http://" + window.location.hostname + ":8088/janus";
|
||||||
|
//~ else
|
||||||
|
//~ server = "https://" + window.location.hostname + ":8089/janus";
|
||||||
|
|
||||||
|
var server = "/janus";
|
||||||
|
|
||||||
|
var janus = null;
|
||||||
|
var streaming = null;
|
||||||
|
var started = false;
|
||||||
|
var spinner = null;
|
||||||
|
|
||||||
|
var selectedStream = null;
|
||||||
|
|
||||||
|
|
||||||
|
$(document).ready(function() {
|
||||||
|
// Initialize the library (all console debuggers enabled)
|
||||||
|
Janus.init({debug: "all", callback: function() {
|
||||||
|
// Use a button to start the demo
|
||||||
|
//~ $('#start').click(function() {
|
||||||
|
if(started)
|
||||||
|
return;
|
||||||
|
started = true;
|
||||||
|
$(this).attr('disabled', true).unbind('click');
|
||||||
|
// Make sure the browser supports WebRTC
|
||||||
|
if(!Janus.isWebrtcSupported()) {
|
||||||
|
bootbox.alert("No WebRTC support... ");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Create session
|
||||||
|
janus = new Janus(
|
||||||
|
{
|
||||||
|
server: server,
|
||||||
|
success: function() {
|
||||||
|
// Attach to streaming plugin
|
||||||
|
janus.attach(
|
||||||
|
{
|
||||||
|
plugin: "janus.plugin.streaming",
|
||||||
|
success: function(pluginHandle) {
|
||||||
|
//~ $('#details').remove();
|
||||||
|
streaming = pluginHandle;
|
||||||
|
Janus.log("Plugin attached! (" + streaming.getPlugin() + ", id=" + streaming.getId() + ")");
|
||||||
|
// Setup streaming session
|
||||||
|
$('#update-streams').click(updateStreamsList);
|
||||||
|
updateStreamsList();
|
||||||
|
//~ $('#start').removeAttr('disabled').html("Stop")
|
||||||
|
//~ .click(function() {
|
||||||
|
//~ $(this).attr('disabled', true);
|
||||||
|
//~ janus.destroy();
|
||||||
|
//~ $('#streamslist').attr('disabled', true);
|
||||||
|
//~ $('#watch').attr('disabled', true).unbind('click');
|
||||||
|
//~ $('#start').attr('disabled', true).html("Bye").unbind('click');
|
||||||
|
//~ });
|
||||||
|
},
|
||||||
|
error: function(error) {
|
||||||
|
Janus.error(" -- Error attaching plugin... ", error);
|
||||||
|
bootbox.alert("Error attaching plugin... " + error);
|
||||||
|
},
|
||||||
|
onmessage: function(msg, jsep) {
|
||||||
|
Janus.debug(" ::: Got a message :::");
|
||||||
|
Janus.debug(JSON.stringify(msg));
|
||||||
|
var result = msg["result"];
|
||||||
|
if(result !== null && result !== undefined) {
|
||||||
|
if(result["status"] !== undefined && result["status"] !== null) {
|
||||||
|
var status = result["status"];
|
||||||
|
if(status === 'starting')
|
||||||
|
$('#status').removeClass('hide').text("Starting, please wait...").show();
|
||||||
|
else if(status === 'started')
|
||||||
|
$('#status').removeClass('hide').text("Started").show();
|
||||||
|
else if(status === 'stopped')
|
||||||
|
stopStream();
|
||||||
|
}
|
||||||
|
} else if(msg["error"] !== undefined && msg["error"] !== null) {
|
||||||
|
bootbox.alert(msg["error"]);
|
||||||
|
stopStream();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(jsep !== undefined && jsep !== null) {
|
||||||
|
Janus.debug("Handling SDP as well...");
|
||||||
|
Janus.debug(jsep);
|
||||||
|
// Answer
|
||||||
|
streaming.createAnswer(
|
||||||
|
{
|
||||||
|
jsep: jsep,
|
||||||
|
media: { audioSend: false, videoSend: false }, // We want recvonly audio/video
|
||||||
|
success: function(jsep) {
|
||||||
|
Janus.debug("Got SDP!");
|
||||||
|
Janus.debug(jsep);
|
||||||
|
var body = { "request": "start" };
|
||||||
|
streaming.send({"message": body, "jsep": jsep});
|
||||||
|
$('#watch').html("Stop").removeAttr('disabled').click(stopStream);
|
||||||
|
},
|
||||||
|
error: function(error) {
|
||||||
|
Janus.error("WebRTC error:", error);
|
||||||
|
bootbox.alert("WebRTC error... " + JSON.stringify(error));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onremotestream: function(stream) {
|
||||||
|
Janus.debug(" ::: Got a remote stream :::");
|
||||||
|
Janus.debug(JSON.stringify(stream));
|
||||||
|
if($('#remotevideo').length === 0)
|
||||||
|
$('#stream').append('<video class="centered hide" id="remotevideo" width=640 height=480 autoplay/>');
|
||||||
|
// Show the stream and hide the spinner when we get a playing event
|
||||||
|
$("#remotevideo").bind("playing", function () {
|
||||||
|
$('#waitingvideo').remove();
|
||||||
|
$('#remotevideo').removeClass('hide');
|
||||||
|
if(spinner !== null && spinner !== undefined)
|
||||||
|
spinner.stop();
|
||||||
|
spinner = null;
|
||||||
|
});
|
||||||
|
Janus.attachMediaStream($('#remotevideo').get(0), stream);
|
||||||
|
},
|
||||||
|
oncleanup: function() {
|
||||||
|
Janus.log(" ::: Got a cleanup notification :::");
|
||||||
|
$('#waitingvideo').remove();
|
||||||
|
$('#remotevideo').remove();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
error: function(error) {
|
||||||
|
Janus.error(error);
|
||||||
|
bootbox.alert(error, function() {
|
||||||
|
window.location.reload();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
destroyed: function() {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
//~ });
|
||||||
|
}});
|
||||||
|
});
|
||||||
|
|
||||||
|
function updateStreamsList() {
|
||||||
|
$('#update-streams').unbind('click').addClass('fa-spin');
|
||||||
|
var body = { "request": "list" };
|
||||||
|
Janus.debug("Sending message (" + JSON.stringify(body) + ")");
|
||||||
|
streaming.send({"message": body, success: function(result) {
|
||||||
|
setTimeout(function() {
|
||||||
|
$('#update-streams').removeClass('fa-spin').click(updateStreamsList);
|
||||||
|
}, 500);
|
||||||
|
if(result === null || result === undefined) {
|
||||||
|
bootbox.alert("Got no response to our query for available streams");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(result["list"] !== undefined && result["list"] !== null) {
|
||||||
|
//~ $('#streams').removeClass('hide').show();
|
||||||
|
$('#streamslist').empty();
|
||||||
|
$('#watch').attr('disabled', true).unbind('click');
|
||||||
|
var list = result["list"];
|
||||||
|
Janus.log("Got a list of available streams");
|
||||||
|
Janus.debug(list);
|
||||||
|
for(var mp in list) {
|
||||||
|
Janus.debug(" >> [" + list[mp]["id"] + "] " + list[mp]["description"] + " (" + list[mp]["type"] + ")");
|
||||||
|
$('#streamslist').append("<li><a href='#' id='" + list[mp]["id"] + "'>" + list[mp]["description"] + " (" + list[mp]["type"] + ")" + "</a></li>");
|
||||||
|
}
|
||||||
|
$('#streamslist a').unbind('click').click(function() {
|
||||||
|
selectedStream = $(this).attr("id");
|
||||||
|
$('#streamset').html($(this).html()).parent().removeClass('open');
|
||||||
|
return false;
|
||||||
|
|
||||||
|
});
|
||||||
|
$('#watch').removeAttr('disabled').click(startStream);
|
||||||
|
}
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
|
||||||
|
function startStream() {
|
||||||
|
Janus.log("Selected video id #" + selectedStream);
|
||||||
|
if(selectedStream === undefined || selectedStream === null) {
|
||||||
|
bootbox.alert("Select a stream from the list");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$('#streamset').attr('disabled', true);
|
||||||
|
$('#streamslist').attr('disabled', true);
|
||||||
|
$('#watch').attr('disabled', true).unbind('click');
|
||||||
|
var body = { "request": "watch", id: parseInt(selectedStream) };
|
||||||
|
streaming.send({"message": body});
|
||||||
|
// No remote video yet
|
||||||
|
$('#stream').append('<video class="centered" id="waitingvideo" width=640 height=480 />');
|
||||||
|
if(spinner == null) {
|
||||||
|
var target = document.getElementById('stream');
|
||||||
|
spinner = new Spinner({top:100}).spin(target);
|
||||||
|
} else {
|
||||||
|
spinner.spin();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopStream() {
|
||||||
|
$('#watch').attr('disabled', true).unbind('click');
|
||||||
|
var body = { "request": "stop" };
|
||||||
|
streaming.send({"message": body});
|
||||||
|
streaming.hangup();
|
||||||
|
$('#streamset').removeAttr('disabled');
|
||||||
|
$('#streamslist').removeAttr('disabled');
|
||||||
|
$('#watch').html("Start").removeAttr('disabled').click(startStream);
|
||||||
|
$('#status').empty().hide();
|
||||||
|
}
|
|
@ -1,35 +1,69 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<link rel="stylesheet" href="{{ url_for('camPage.static', filename='index.css') }}">
|
|
||||||
<head>
|
<head>
|
||||||
<title>Camera 1</title>
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Pyledriver</title>
|
||||||
|
|
||||||
|
<link rel="stylesheet" type="text/css" href="{{ url_for('siteRoot.static', filename='css/bootstrap.min.css') }}">
|
||||||
|
<link rel="stylesheet" type="text/css" href="{{ url_for('siteRoot.static', filename='css/pyledriver.css') }}">
|
||||||
|
|
||||||
|
<script type="text/javascript" src="{{ url_for('siteRoot.static', filename='jquery.min.js') }}"></script>
|
||||||
|
<script type="text/javascript" src="{{ url_for('siteRoot.static', filename='js/bootstrap.min.js') }}"></script>
|
||||||
|
<script type="text/javascript" src="{{ url_for('siteRoot.static', filename='js/bootbox.min.js') }}"></script>
|
||||||
|
<script type="text/javascript" src="{{ url_for('siteRoot.static', filename='js/spin.min.js') }}"></script>
|
||||||
|
<script type="text/javascript" src="{{ url_for('siteRoot.static', filename='janus.js') }}"></script>
|
||||||
|
<script type="text/javascript" src="{{ url_for('siteRoot.static', filename='streamingtest.js') }}"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="titlebar">
|
<div class="navbar navbar-inverse navbar-static-top">
|
||||||
<b>Pyledriver Status: </b><span id='state'>{{ state }}</span>
|
<div class="container-fluid">
|
||||||
|
<div class="navbar-header">
|
||||||
|
<span class="navbar-text"><b>Status: </b><span>{{ state }}</span></span>
|
||||||
|
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#navRight">
|
||||||
|
<span class="icon-bar"></span>
|
||||||
|
<span class="icon-bar"></span>
|
||||||
|
<span class="icon-bar"></span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="camera_container">
|
<div class="collapse navbar-collapse" id="navRight">
|
||||||
<div class="camera">
|
<ul class="nav navbar-nav">
|
||||||
<img id="bg" src="{{ url_for('camPage.videoFeed') }}">
|
<form action="{{ url_for('siteRoot.index') }}" method="post" name="text_to_speech" class="navbar-form" role="search">
|
||||||
|
{{ ttsForm.hidden_tag() }}
|
||||||
|
<div class="form-inline">
|
||||||
|
<div class="form-group">{{ ttsForm.tts(class_="form-control") }}</div>
|
||||||
|
{{ ttsForm.submitTTS(class_="btn btn-default") }}
|
||||||
</div>
|
</div>
|
||||||
<div class="controls">
|
|
||||||
<form action="{{ url_for('camPage.index') }}" method='post' name='framerate'>
|
|
||||||
{{ camForm.hidden_tag() }}
|
|
||||||
<p>Current Framerate: <span id='FPSvalue'>{{ fps }}</span></p>
|
|
||||||
<p>Set Framerate {{ camForm.fps }}</p>
|
|
||||||
<p>{{ camForm.submitFPS }}</p>
|
|
||||||
</form>
|
|
||||||
<form action="{{ url_for('camPage.index') }}" method='post' name='text_to_speech'>
|
|
||||||
{{ ttsForm.hidden_tag() }}
|
|
||||||
<p>Speak your mind</p>
|
|
||||||
<p>{{ ttsForm.tts }}</p>
|
|
||||||
<p>{{ ttsForm.submitTTS }}</p>
|
|
||||||
</form>
|
|
||||||
<form action="{{ url_for('camPage.index') }}" method='post' name='text_to_speech'>
|
|
||||||
{{ ttsForm.hidden_tag() }}
|
|
||||||
<p>If something goes wrong...</p>
|
|
||||||
<p>{{ resetForm.submitReset }}</p>
|
|
||||||
</form>
|
</form>
|
||||||
|
</ul>
|
||||||
|
<ul class="nav navbar-nav navbar-right">
|
||||||
|
<li class="dropdown">
|
||||||
|
<a id="streamset" class="dropdown-toggle" data-toggle="dropdown">
|
||||||
|
Pick Stream<span class="caret"></span>
|
||||||
|
</a>
|
||||||
|
<ul id="streamslist" class="dropdown-menu" role="menu"></ul>
|
||||||
|
</li>
|
||||||
|
<button class="btn btn-default navbar-btn" autocomplete="off" id="watch">Start</button>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="container-fluid body-content">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<!--
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<h3 class="panel-title">Stream <span class="label label-info hide" id="status"></h3>
|
||||||
|
</div>
|
||||||
|
-->
|
||||||
|
<div id="stream"></div>
|
||||||
|
<p class="hide" id="status">
|
||||||
|
<!--
|
||||||
|
</div>
|
||||||
|
-->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -1,84 +1,48 @@
|
||||||
from multiprocessing import Process
|
import logging
|
||||||
from sharedLogging import SlaveLogger
|
from auxilary import async
|
||||||
from flask import Flask, render_template, Response, Blueprint, redirect, url_for
|
from flask import Flask, render_template, Response, Blueprint, redirect, url_for
|
||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm
|
||||||
from wtforms.fields import SelectField, StringField, SubmitField
|
from wtforms.fields import StringField, SubmitField
|
||||||
from wtforms.validators import InputRequired
|
from wtforms.validators import InputRequired
|
||||||
|
|
||||||
class CamForm(FlaskForm):
|
logger = logging.getLogger(__name__)
|
||||||
fps = SelectField(choices=[(i, '%s fps' % i) for i in range(10, 31, 5)], coerce=int)
|
|
||||||
submitFPS = SubmitField('Set')
|
# gag the flask logger unless it has something useful to say
|
||||||
|
werkzeug = logging.getLogger('werkzeug')
|
||||||
|
werkzeug.setLevel(logging.ERROR)
|
||||||
|
|
||||||
class TTSForm(FlaskForm):
|
class TTSForm(FlaskForm):
|
||||||
tts = StringField(validators=[InputRequired()])
|
tts = StringField(validators=[InputRequired()])
|
||||||
submitTTS = SubmitField('Speak')
|
submitTTS = SubmitField('Speak')
|
||||||
|
|
||||||
class ResetForm(FlaskForm):
|
|
||||||
submitReset = SubmitField('Reset')
|
|
||||||
|
|
||||||
# TODO: fix random connection fails (might be an nginx thing)
|
# TODO: fix random connection fails (might be an nginx thing)
|
||||||
# TODO: show camera failed status here somewhere
|
|
||||||
|
|
||||||
class WebInterface(Process):
|
@async(daemon=True)
|
||||||
def __init__(self, camera, stateDict, ttsQueue, loggerQueue):
|
def initWebInterface(stateMachine):
|
||||||
self._moduleLogger = SlaveLogger(__name__, 'INFO', loggerQueue)
|
siteRoot = Blueprint('siteRoot', __name__, static_folder='static', static_url_path='')
|
||||||
self._flaskLogger = SlaveLogger('werkzeug', 'ERROR', loggerQueue)
|
|
||||||
|
|
||||||
camPage = Blueprint('camPage', __name__, static_folder='static', template_folder='templates')
|
@siteRoot.route('/', methods=['GET', 'POST'])
|
||||||
|
@siteRoot.route('/index', methods=['GET', 'POST'])
|
||||||
def generateFrame():
|
|
||||||
while 1:
|
|
||||||
yield (b'--frame\r\n' b'Content-Type: image/jpeg\r\n\r\n' + camera.getFrame() + b'\r\n')
|
|
||||||
|
|
||||||
@camPage.route('/', methods=['GET', 'POST'])
|
|
||||||
@camPage.route('/index', methods=['GET', 'POST'])
|
|
||||||
def index():
|
def index():
|
||||||
props = camera.getProps('FPS')
|
|
||||||
fps = int(props['FPS'])
|
|
||||||
camForm = CamForm(fps = props['FPS'])
|
|
||||||
ttsForm = TTSForm()
|
ttsForm = TTSForm()
|
||||||
resetForm = ResetForm()
|
|
||||||
|
|
||||||
if camForm.validate_on_submit() and camForm.submitFPS.data:
|
|
||||||
camera.setProps(FPS=camForm.fps.data)
|
|
||||||
return redirect(url_for('camPage.index'))
|
|
||||||
|
|
||||||
if ttsForm.validate_on_submit() and ttsForm.submitTTS.data:
|
if ttsForm.validate_on_submit() and ttsForm.submitTTS.data:
|
||||||
ttsQueue.put_nowait(ttsForm.tts.data)
|
stateMachine.soundLib.speak(ttsForm.tts.data)
|
||||||
return redirect(url_for('camPage.index'))
|
#~ ttsQueue.put_nowait(ttsForm.tts.data)
|
||||||
|
return redirect(url_for('siteRoot.index'))
|
||||||
if resetForm.validate_on_submit() and resetForm.submitReset.data:
|
|
||||||
camera.reset()
|
|
||||||
return redirect(url_for('camPage.index'))
|
|
||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
'index.html',
|
'index.html',
|
||||||
camForm=camForm,
|
|
||||||
ttsForm=ttsForm,
|
ttsForm=ttsForm,
|
||||||
resetForm=resetForm,
|
state=stateMachine.currentState
|
||||||
fps=fps,
|
|
||||||
state=stateDict['name']
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@camPage.route('/videoFeed')
|
app = Flask(__name__)
|
||||||
def videoFeed():
|
app.secret_key = '3276d68dac56985bea352325125641ff'
|
||||||
return Response(generateFrame(), mimetype='multipart/x-mixed-replace; boundary=frame')
|
app.register_blueprint(siteRoot, url_prefix='/pyledriver')
|
||||||
|
|
||||||
self._app = Flask(__name__)
|
|
||||||
self._app.secret_key = '3276d68dac56985bea352325125641ff'
|
|
||||||
self._app.register_blueprint(camPage, url_prefix='/pyledriver')
|
|
||||||
|
|
||||||
super().__init__(daemon=True)
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
# TODO: not sure exactly how threaded=True works, intended to enable
|
# TODO: not sure exactly how threaded=True works, intended to enable
|
||||||
# multiple connections. May want to use something more robust w/ camera
|
# multiple connections. May want to use something more robust w/ camera
|
||||||
# see here: https://blog.miguelgrinberg.com/post/video-streaming-with-flask
|
# see here: https://blog.miguelgrinberg.com/post/video-streaming-with-flask
|
||||||
|
logger.info('Starting web interface')
|
||||||
self._moduleLogger.info('Started web interface')
|
app.run(debug=False, threaded=True)
|
||||||
self._app.run(debug=False, threaded=True)
|
|
||||||
|
|
||||||
def stop(self):
|
|
||||||
self.terminate()
|
|
||||||
self.join()
|
|
||||||
self._moduleLogger.info('Terminated web interface')
|
|
||||||
|
|
Loading…
Reference in New Issue