Fix #60 - collective moment implementation

This commit is contained in:
Ruben van de Ven 2019-07-03 17:54:14 +02:00
parent 4ae8c20b92
commit 36353286c7
5 changed files with 170 additions and 11 deletions

View file

@ -12,7 +12,7 @@ from zmq.asyncio import Context
import asyncio
from hugvey.communication import getTopic, zmqSend, zmqReceive, LOG_BS
from hugvey.panopticon import Panopticon
from hugvey.story import Story
from hugvey.story import Story, Stopwatch
from hugvey.speech.google import GoogleVoiceClient
from hugvey.speech.player import Player
from hugvey.speech.streamer import AudioStreamer
@ -23,8 +23,9 @@ import threading
from hugvey.voice import VoiceStorage
import multiprocessing
from hugvey.speech.recorder import Recorder
from pythonosc import udp_client
from pythonosc import udp_client, osc_server, dispatcher
import copy
from pythonosc.osc_server import AsyncIOOSCUDPServer
mainLogger = logging.getLogger("hugvey")
@ -68,6 +69,8 @@ class CentralCommand(object):
self.languageFiles = {}
self.languageConfig = {}
self.args = args # cli args
self.timer = Stopwatch()
eventLogger.addHandler(logging.handlers.QueueHandler(self.logQueue))
@ -139,6 +142,7 @@ class CentralCommand(object):
def getStatusSummary(self, selected_ids = []):
status = {
'uptime': "-" if not self.start_time else (time.time() - self.start_time),
'loop_timer': self.timer.getElapsed(),
'languages': self.config['languages'],
'hugvey_ids': self.hugvey_ids,
'hugveys': [],
@ -239,6 +243,33 @@ class CentralCommand(object):
logger.warn('Stopping light sender')
lightConn._sock.close()
def restartTimerHandler(self, address, *args):
"""
See self.oscListener
"""
logger.warn(f"Restart loop timer")
self.timer.reset()
if len(args) > 0 and float(args[0]) > 0:
print(args, args[0])
logger.warn(f"Set timer to custom time: {float(args[0])} seconds ago")
self.timer.setMark('start', time.time() - float(args[0]))
async def oscListener(self):
"""
OSC server, listens for loop restarts
"""
dispatch = dispatcher.Dispatcher()
dispatch.map("/loop", self.restartTimerHandler)
server = osc_server.AsyncIOOSCUDPServer(
("0.0.0.0", 9000), dispatch, asyncio.get_event_loop()
)
logger.info('Start OSC server to receive loop re-starts')
# await server.serve()
transport, protocol = await server.create_serve_endpoint()
# logger.critical(f"{transport}, {protocol}")
# transport.close()
async def redLightController(self):
"""
@ -384,6 +415,8 @@ class CentralCommand(object):
self.catchException(self.commandSender()))
self.tasks['lightSender'] = self.loop.create_task(
self.catchException(self.lightSender()))
self.tasks['oscListener'] = self.loop.create_task(
self.catchException(self.oscListener()))
self.tasks['redLightController'] = self.loop.create_task(
self.catchException(self.redLightController()))
@ -680,6 +713,11 @@ class HugveyState(object):
self.logger.log(LOG_BS, f"Send /hugvey {self.lightStatus}")
self.command.commandLight('/hugvey', [self.lightId, self.lightStatus])
def transitionLight(self, intensity, duration):
self.lightIntensity = intensity
self.logger.log(LOG_BS, f"Send /hugvey_fade {self.lightIntensity} {duration}")
self.command.commandLight('/hugvey_fade', [self.lightId, intensity, duration])
def setLightId(self, id):
"""

View file

@ -610,6 +610,8 @@ class Diversion(object):
if type == 'timeout':
self.method = self._divergeIfTimeout
self.finaliseMethod = self._returnAfterTimeout
if type == 'collective_moment':
self.method = self._divergeIfCollectiveMoment
if type == 'repeat':
self.method = self._divergeIfRepeatRequest
self.regex = re.compile(self.params['regex'])
@ -864,7 +866,39 @@ class Diversion(object):
story.logger.info(f"Finalise diversion: {self.id}")
# if self.params['returnAfterStrand']:
# await story.setCurrentMessage(self.returnMessage)
async def _divergeIfCollectiveMoment(self, story, msgFrom, msgTo, direction):
"""
Central command timer times to a collective moment
"""
#: :var story: Story
if story.currentDiversion or not msgFrom or not msgTo:
return False
if not msgTo.chapterStart:
# only when changing chapter
return
window_open_second = float(self.params['start_minute']) * 60
window_close_second = window_open_second + float(self.params['window'])
now = story.hugvey.command.timer.getElapsed()
if now < window_open_second or now > window_close_second:
return
#open!
msg = story.get(self.params['msgId'])
if msg is None:
story.logger.critical(f"Not a valid message id for diversion: {self.id} {self.params['msgId']}")
return
self.returnMessage = msgTo
self.createReturnDirectionsTo(story, msg, msgTo, direction, inheritTiming=True)
await story.setCurrentMessage(msg)
story.currentDiversion = self
return True
async def _divergeIfRepeatRequest(self, story, msgFrom, msgTo, direction):
"""
Participant asks if message can be repeated.
@ -1054,8 +1088,11 @@ class Stopwatch(object):
self.paused_at = 0
self.isRunning.set()
def setMark(self, name):
self.marks[name] = time.time()
def setMark(self, name, overrideValue = None):
"""
Set a marker to current time. Or , if given, to any float one desires
"""
self.marks[name] = overrideValue if overrideValue else time.time()
def hasMark(self, name):
return name in self.marks

View file

@ -17,8 +17,8 @@
<div id='status'>
<div id='overview'>
<dl>
<dt>Uptime</dt>
<dd>{{uptime}}</dd>
<dt>Timer</dt>
<dd>{{loop_timer}} (up: {{uptime}})</dd>
</dl>
<ul id='languages'>

View file

@ -10,6 +10,7 @@ class Panopticon {
el: "#interface",
data: {
uptime: 0,
loop_timer: 0,
languages: [],
hugveys: [],
selectedId: null,
@ -124,6 +125,7 @@ class Panopticon {
case 'status':
this.hugveys.uptime = this.stringToHHMMSS( msg['uptime'] );
this.hugveys.loop_timer = this.stringToHHMMSS( msg['loop_timer'] );
this.hugveys.languages = msg['languages'];
this.languages = msg['languages'];
this.hugveys.hugveys = msg['hugveys'];
@ -411,7 +413,12 @@ class Graph {
}
else if(type == 'repeat') {
div['params']['regex'] = "can you repeat that\\?";
} else {
}
else if(type == 'collective_moment') {
div['params']['start_minute'] = 20; // minute to start
div['params']['window'] = 60; // how long to wait, in seconds
}
else {
console.log("invalid type", type);
alert('invalid type for diversion');
}
@ -437,7 +444,7 @@ class Graph {
let msgEl = document.getElementById( 'msg' );
msgEl.innerHTML = "";
let divsNoResponse =[], divsRepeat = [], divsReplyContains = [], divsTimeouts = [], divsInterrupts = [];
let divsNoResponse =[], divsRepeat = [], divsReplyContains = [], divsTimeouts = [], divsInterrupts = [], divsCollectiveMoment = [];
for(let div of this.diversions) {
let notAfterMsgIdEl = "";
@ -630,6 +637,70 @@ class Graph {
notAfterMsgIdEl
));
}
if(div['type'] == 'collective_moment') {
let msgOptions = [crel('option',"")];
let starts = this.messages.filter( m => m.hasOwnProperty('start') && m['start'] == true);
for(let startMsg of starts) {
let optionParams = {};
if(div['params']['msgId'] == startMsg['@id']) {
optionParams['selected'] = 'selected';
}
msgOptions.push(crel('option', optionParams , startMsg['@id']));
}
divsCollectiveMoment.push(crel(
'div', {
'class': 'diversion',
'on': {
'mouseover': function(e) {
if(div['params']['msgId'])
document.getElementById(div['params']['msgId']).classList.add('selectedMsg');
},
'mouseout': function(e) {
if(div['params']['msgId'])
document.getElementById(div['params']['msgId']).classList.remove('selectedMsg');
}
}
},
crel('h3', div['@id']),
crel(
'div', {
'class':'btn btn--delete',
'on': {
'click': (e) => this.deleteDiversion(div)
}
}, 'Delete diversion'),
crel('label', 'Start time (minute)',
crel('input', {
'type': 'number',
'max': 59,
'min': 0,
'value': div['params']['start_minute'],
'placeholder': 'time (minute)',
'on': {
'change': (e) => div['params']['start_minute'] = e.target.value
}
})
),
crel('label', 'Duration (seconds)',
crel('input', {
'type': 'number',
'max': 60*15,
'min': 0,
'value': div['params']['window'],
'placeholder': 'seconds',
'on': {
'change': (e) => div['params']['window'] = e.target.value
}
})
),
crel('label', 'Go to (start message)',
crel('select', {'on': {
'change': (e) => div['params']['msgId'] = e.target.value
}}, ...msgOptions)
)
));
}
if(div['type'] == 'timeout') {
let returnAttrs = {
'type': 'checkbox',
@ -777,7 +848,7 @@ class Graph {
}
}
console.log(divsReplyContains, divsNoResponse, divsRepeat, divsTimeouts, divsInterrupts);
console.log(divsReplyContains, divsNoResponse, divsRepeat, divsTimeouts, divsInterrupts, divsCollectiveMoment);
let divEl = crel(
'div',
@ -836,6 +907,19 @@ class Graph {
},
'New case for timeout'
)
),
crel('div',
crel('h2', 'Collective moments'),
...divsCollectiveMoment,
crel('div',
{
'class': 'btn',
'on': {
'click': (e) => this.createDiversion('collective_moment')
}
},
'New collective moment'
)
)
// ,
// crel('div',

View file

@ -17,8 +17,8 @@
<div id='status'>
<div id='overview'>
<dl>
<dt>Uptime</dt>
<dd>{{uptime}}</dd>
<dt>Timer</dt>
<dd>{{loop_timer}} (up: {{uptime}})</dd>
</dl>
</div>