Fix #60 - collective moment implementation
This commit is contained in:
parent
4ae8c20b92
commit
36353286c7
5 changed files with 170 additions and 11 deletions
|
@ -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")
|
||||
|
||||
|
@ -69,6 +70,8 @@ class CentralCommand(object):
|
|||
self.languageConfig = {}
|
||||
self.args = args # cli args
|
||||
|
||||
self.timer = Stopwatch()
|
||||
|
||||
eventLogger.addHandler(logging.handlers.QueueHandler(self.logQueue))
|
||||
|
||||
def loadConfig(self, filename):
|
||||
|
@ -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': [],
|
||||
|
@ -240,6 +244,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):
|
||||
"""
|
||||
Every second, check if no hugveys are available. If so, the red light should be
|
||||
|
@ -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()))
|
||||
|
||||
|
@ -681,6 +714,11 @@ class HugveyState(object):
|
|||
|
||||
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):
|
||||
"""
|
||||
Connect hugvey to another light
|
||||
|
|
|
@ -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,6 +866,38 @@ 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):
|
||||
"""
|
||||
|
@ -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
|
||||
|
|
|
@ -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'>
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in a new issue