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 import asyncio
from hugvey.communication import getTopic, zmqSend, zmqReceive, LOG_BS from hugvey.communication import getTopic, zmqSend, zmqReceive, LOG_BS
from hugvey.panopticon import Panopticon 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.google import GoogleVoiceClient
from hugvey.speech.player import Player from hugvey.speech.player import Player
from hugvey.speech.streamer import AudioStreamer from hugvey.speech.streamer import AudioStreamer
@ -23,8 +23,9 @@ import threading
from hugvey.voice import VoiceStorage from hugvey.voice import VoiceStorage
import multiprocessing import multiprocessing
from hugvey.speech.recorder import Recorder from hugvey.speech.recorder import Recorder
from pythonosc import udp_client from pythonosc import udp_client, osc_server, dispatcher
import copy import copy
from pythonosc.osc_server import AsyncIOOSCUDPServer
mainLogger = logging.getLogger("hugvey") mainLogger = logging.getLogger("hugvey")
@ -68,6 +69,8 @@ class CentralCommand(object):
self.languageFiles = {} self.languageFiles = {}
self.languageConfig = {} self.languageConfig = {}
self.args = args # cli args self.args = args # cli args
self.timer = Stopwatch()
eventLogger.addHandler(logging.handlers.QueueHandler(self.logQueue)) eventLogger.addHandler(logging.handlers.QueueHandler(self.logQueue))
@ -139,6 +142,7 @@ class CentralCommand(object):
def getStatusSummary(self, selected_ids = []): def getStatusSummary(self, selected_ids = []):
status = { status = {
'uptime': "-" if not self.start_time else (time.time() - self.start_time), 'uptime': "-" if not self.start_time else (time.time() - self.start_time),
'loop_timer': self.timer.getElapsed(),
'languages': self.config['languages'], 'languages': self.config['languages'],
'hugvey_ids': self.hugvey_ids, 'hugvey_ids': self.hugvey_ids,
'hugveys': [], 'hugveys': [],
@ -239,6 +243,33 @@ class CentralCommand(object):
logger.warn('Stopping light sender') logger.warn('Stopping light sender')
lightConn._sock.close() 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): async def redLightController(self):
""" """
@ -384,6 +415,8 @@ class CentralCommand(object):
self.catchException(self.commandSender())) self.catchException(self.commandSender()))
self.tasks['lightSender'] = self.loop.create_task( self.tasks['lightSender'] = self.loop.create_task(
self.catchException(self.lightSender())) self.catchException(self.lightSender()))
self.tasks['oscListener'] = self.loop.create_task(
self.catchException(self.oscListener()))
self.tasks['redLightController'] = self.loop.create_task( self.tasks['redLightController'] = self.loop.create_task(
self.catchException(self.redLightController())) self.catchException(self.redLightController()))
@ -680,6 +713,11 @@ class HugveyState(object):
self.logger.log(LOG_BS, f"Send /hugvey {self.lightStatus}") self.logger.log(LOG_BS, f"Send /hugvey {self.lightStatus}")
self.command.commandLight('/hugvey', [self.lightId, 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): def setLightId(self, id):
""" """

View File

@ -610,6 +610,8 @@ class Diversion(object):
if type == 'timeout': if type == 'timeout':
self.method = self._divergeIfTimeout self.method = self._divergeIfTimeout
self.finaliseMethod = self._returnAfterTimeout self.finaliseMethod = self._returnAfterTimeout
if type == 'collective_moment':
self.method = self._divergeIfCollectiveMoment
if type == 'repeat': if type == 'repeat':
self.method = self._divergeIfRepeatRequest self.method = self._divergeIfRepeatRequest
self.regex = re.compile(self.params['regex']) self.regex = re.compile(self.params['regex'])
@ -864,7 +866,39 @@ class Diversion(object):
story.logger.info(f"Finalise diversion: {self.id}") story.logger.info(f"Finalise diversion: {self.id}")
# if self.params['returnAfterStrand']: # if self.params['returnAfterStrand']:
# await story.setCurrentMessage(self.returnMessage) # 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): async def _divergeIfRepeatRequest(self, story, msgFrom, msgTo, direction):
""" """
Participant asks if message can be repeated. Participant asks if message can be repeated.
@ -1054,8 +1088,11 @@ class Stopwatch(object):
self.paused_at = 0 self.paused_at = 0
self.isRunning.set() self.isRunning.set()
def setMark(self, name): def setMark(self, name, overrideValue = None):
self.marks[name] = time.time() """
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): def hasMark(self, name):
return name in self.marks return name in self.marks

View File

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

View File

@ -10,6 +10,7 @@ class Panopticon {
el: "#interface", el: "#interface",
data: { data: {
uptime: 0, uptime: 0,
loop_timer: 0,
languages: [], languages: [],
hugveys: [], hugveys: [],
selectedId: null, selectedId: null,
@ -124,6 +125,7 @@ class Panopticon {
case 'status': case 'status':
this.hugveys.uptime = this.stringToHHMMSS( msg['uptime'] ); this.hugveys.uptime = this.stringToHHMMSS( msg['uptime'] );
this.hugveys.loop_timer = this.stringToHHMMSS( msg['loop_timer'] );
this.hugveys.languages = msg['languages']; this.hugveys.languages = msg['languages'];
this.languages = msg['languages']; this.languages = msg['languages'];
this.hugveys.hugveys = msg['hugveys']; this.hugveys.hugveys = msg['hugveys'];
@ -411,7 +413,12 @@ class Graph {
} }
else if(type == 'repeat') { else if(type == 'repeat') {
div['params']['regex'] = "can you repeat that\\?"; 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); console.log("invalid type", type);
alert('invalid type for diversion'); alert('invalid type for diversion');
} }
@ -437,7 +444,7 @@ class Graph {
let msgEl = document.getElementById( 'msg' ); let msgEl = document.getElementById( 'msg' );
msgEl.innerHTML = ""; msgEl.innerHTML = "";
let divsNoResponse =[], divsRepeat = [], divsReplyContains = [], divsTimeouts = [], divsInterrupts = []; let divsNoResponse =[], divsRepeat = [], divsReplyContains = [], divsTimeouts = [], divsInterrupts = [], divsCollectiveMoment = [];
for(let div of this.diversions) { for(let div of this.diversions) {
let notAfterMsgIdEl = ""; let notAfterMsgIdEl = "";
@ -630,6 +637,70 @@ class Graph {
notAfterMsgIdEl 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') { if(div['type'] == 'timeout') {
let returnAttrs = { let returnAttrs = {
'type': 'checkbox', '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( let divEl = crel(
'div', 'div',
@ -836,6 +907,19 @@ class Graph {
}, },
'New case for timeout' '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', // crel('div',

View File

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