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
|
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")
|
||||||
|
|
||||||
|
@ -69,6 +70,8 @@ class CentralCommand(object):
|
||||||
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))
|
||||||
|
|
||||||
def loadConfig(self, filename):
|
def loadConfig(self, filename):
|
||||||
|
@ -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': [],
|
||||||
|
@ -240,6 +244,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):
|
||||||
"""
|
"""
|
||||||
Every second, check if no hugveys are available. If so, the red light should be
|
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.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()))
|
||||||
|
|
||||||
|
@ -681,6 +714,11 @@ class HugveyState(object):
|
||||||
|
|
||||||
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):
|
||||||
"""
|
"""
|
||||||
Connect hugvey to another light
|
Connect hugvey to another light
|
||||||
|
|
|
@ -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,6 +866,38 @@ 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):
|
||||||
"""
|
"""
|
||||||
|
@ -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
|
||||||
|
|
|
@ -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'>
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in a new issue