merged loopaudio
This commit is contained in:
commit
edd6d8924a
10 changed files with 196 additions and 109 deletions
|
@ -93,7 +93,7 @@ chown=pi:pi
|
||||||
## Deploy / usefull commands
|
## Deploy / usefull commands
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
for i in {1..6}; do rsync -av ~/hugvey/ pi@hugvey$i.local:/home/pi/hugvey/ --exclude=www --exclude=venv --exclude=local --exclude=*.pyc --exclude=.git; done
|
for i in {1..26}; do echo $i; rsync -av ~/hugvey/ pi@hugvey$i.local:/home/pi/hugvey/ --exclude=www --exclude=venv --exclude=local --exclude=*.pyc --exclude=.git --exclude=recordings --exclude=/voice* --exclude=/pd; done
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
|
@ -5,7 +5,7 @@ voice:
|
||||||
input_rate: 44100
|
input_rate: 44100
|
||||||
target_rate: 16000
|
target_rate: 16000
|
||||||
port: 4444
|
port: 4444
|
||||||
input_name: 'AK5371'
|
input_name: 'USB Audio Device'
|
||||||
output_name: 'USB Audio Device'
|
output_name: 'USB Audio Device'
|
||||||
input_mixer: 'Mic'
|
input_mixer: 'Mic'
|
||||||
output_mixer: 'PCM'
|
output_mixer: 'PCM'
|
||||||
|
@ -13,6 +13,3 @@ voice:
|
||||||
output_volume: 30
|
output_volume: 30
|
||||||
file_address: "http://hugveycmd.local:8888"
|
file_address: "http://hugveycmd.local:8888"
|
||||||
output_driver: pulseaudio
|
output_driver: pulseaudio
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@ 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
|
||||||
|
import copy
|
||||||
|
|
||||||
mainLogger = logging.getLogger("hugvey")
|
mainLogger = logging.getLogger("hugvey")
|
||||||
|
|
||||||
|
@ -119,6 +120,7 @@ class CentralCommand(object):
|
||||||
|
|
||||||
status['status'] = hv.getStatus()
|
status['status'] = hv.getStatus()
|
||||||
status['language'] = hv.language_code
|
status['language'] = hv.language_code
|
||||||
|
status['light_id'] = hv.lightId
|
||||||
status['msg'] = hv.story.currentMessage.id if hv.story and hv.story.currentMessage else None
|
status['msg'] = hv.story.currentMessage.id if hv.story and hv.story.currentMessage else None
|
||||||
# status['finished'] = hv.story.isFinished()
|
# status['finished'] = hv.story.isFinished()
|
||||||
status['history'] = {} if isSelected is False or not hv.story else hv.story.getLogSummary()
|
status['history'] = {} if isSelected is False or not hv.story else hv.story.getLogSummary()
|
||||||
|
@ -232,6 +234,23 @@ class CentralCommand(object):
|
||||||
logger.warn('Stopping light sender')
|
logger.warn('Stopping light sender')
|
||||||
lightConn._sock.close()
|
lightConn._sock.close()
|
||||||
|
|
||||||
|
async def redLightController(self):
|
||||||
|
"""
|
||||||
|
Every second, check if no hugveys are available. If so, the red light should be
|
||||||
|
overruled to be on. If any is available, send a 0 to release the override.
|
||||||
|
"""
|
||||||
|
currentCode = None
|
||||||
|
while self.isRunning.is_set():
|
||||||
|
statusses = [hv.getStatus() for hv in self.hugveys.values()]
|
||||||
|
lightOn = HugveyState.STATE_AVAILABLE not in statusses
|
||||||
|
lightCode = 1 if lightOn else 0
|
||||||
|
if lightCode != currentCode:
|
||||||
|
self.commandLight('/red', [lightCode])
|
||||||
|
currentCode = lightCode
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
|
||||||
|
logger.warn('Stopping red light controller')
|
||||||
|
|
||||||
def instantiateHugvey(self, hugvey_id):
|
def instantiateHugvey(self, hugvey_id):
|
||||||
'''
|
'''
|
||||||
Start a HugveyState, according to a show_yourself reply
|
Start a HugveyState, according to a show_yourself reply
|
||||||
|
@ -356,6 +375,9 @@ 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['redLightController'] = self.loop.create_task(
|
||||||
|
self.catchException(self.redLightController()))
|
||||||
|
|
||||||
for hid in self.hugvey_ids:
|
for hid in self.hugvey_ids:
|
||||||
self.tasks['voiceListener'] = self.loop.create_task(
|
self.tasks['voiceListener'] = self.loop.create_task(
|
||||||
self.catchException(self.voiceListener(hid)))
|
self.catchException(self.voiceListener(hid)))
|
||||||
|
@ -396,6 +418,7 @@ class HugveyState(object):
|
||||||
|
|
||||||
def __init__(self, id: int, command: CentralCommand):
|
def __init__(self, id: int, command: CentralCommand):
|
||||||
self.id = id
|
self.id = id
|
||||||
|
self.lightId = id
|
||||||
self.command = command
|
self.command = command
|
||||||
self.logger = mainLogger.getChild(f"{self.id}").getChild("command")
|
self.logger = mainLogger.getChild(f"{self.id}").getChild("command")
|
||||||
self.loop = asyncio.new_event_loop()
|
self.loop = asyncio.new_event_loop()
|
||||||
|
@ -544,6 +567,8 @@ class HugveyState(object):
|
||||||
|
|
||||||
if event['event'] == 'change_language':
|
if event['event'] == 'change_language':
|
||||||
self.setLanguage(event['lang_code'])
|
self.setLanguage(event['lang_code'])
|
||||||
|
if event['event'] == 'change_light':
|
||||||
|
self.setLightId(event['light_id'])
|
||||||
if event['event'] == 'play_msg':
|
if event['event'] == 'play_msg':
|
||||||
self.logger.info(f"Play given message {event['msg_id']}")
|
self.logger.info(f"Play given message {event['msg_id']}")
|
||||||
if not self.story:
|
if not self.story:
|
||||||
|
@ -621,7 +646,13 @@ class HugveyState(object):
|
||||||
status = 1 if on else 0
|
status = 1 if on else 0
|
||||||
self.logger.log(LOG_BS, f"Send /hugvey {status}")
|
self.logger.log(LOG_BS, f"Send /hugvey {status}")
|
||||||
|
|
||||||
self.command.commandLight('/hugvey', [self.id, status])
|
self.command.commandLight('/hugvey', [self.lightId, status])
|
||||||
|
|
||||||
|
def setLightId(self, id):
|
||||||
|
"""
|
||||||
|
Connect hugvey to another light
|
||||||
|
"""
|
||||||
|
self.lightId = id
|
||||||
|
|
||||||
def gone(self):
|
def gone(self):
|
||||||
'''Status to 'gone' as in, shutdown/crashed/whatever
|
'''Status to 'gone' as in, shutdown/crashed/whatever
|
||||||
|
@ -678,7 +709,7 @@ class HugveyState(object):
|
||||||
await asyncio.sleep(1)
|
await asyncio.sleep(1)
|
||||||
|
|
||||||
self.streamer.triggerStart()
|
self.streamer.triggerStart()
|
||||||
self.story.setStoryData(self.command.languages[self.language_code])
|
self.story.setStoryData(copy.deepcopy(self.command.languages[self.language_code]))
|
||||||
self.setLightStatus(False)
|
self.setLightStatus(False)
|
||||||
await self.story.run(startMsgId)
|
await self.story.run(startMsgId)
|
||||||
# self.story = None
|
# self.story = None
|
||||||
|
|
|
@ -66,6 +66,8 @@ def getWebSocketHandler(central_command):
|
||||||
self.msgFinish(msg['hugvey'])
|
self.msgFinish(msg['hugvey'])
|
||||||
elif msg['action'] == 'change_language':
|
elif msg['action'] == 'change_language':
|
||||||
self.msgChangeLanguage(msg['hugvey'], msg['lang_code'])
|
self.msgChangeLanguage(msg['hugvey'], msg['lang_code'])
|
||||||
|
elif msg['action'] == 'change_light':
|
||||||
|
self.msgChangeLightId(msg['hugvey'], int(msg['light_id']))
|
||||||
elif msg['action'] == 'play_msg':
|
elif msg['action'] == 'play_msg':
|
||||||
self.msgPlayMsg(msg['hugvey'], msg['msg_id'])
|
self.msgPlayMsg(msg['hugvey'], msg['msg_id'])
|
||||||
else:
|
else:
|
||||||
|
@ -120,6 +122,9 @@ def getWebSocketHandler(central_command):
|
||||||
def msgChangeLanguage(self, hv_id, lang_code):
|
def msgChangeLanguage(self, hv_id, lang_code):
|
||||||
central_command.hugveys[hv_id].eventQueue.put_nowait({'event': 'change_language', 'lang_code': lang_code})
|
central_command.hugveys[hv_id].eventQueue.put_nowait({'event': 'change_language', 'lang_code': lang_code})
|
||||||
|
|
||||||
|
def msgChangeLightId(self, hv_id, lightId):
|
||||||
|
central_command.hugveys[hv_id].eventQueue.put_nowait({'event': 'change_light', 'light_id': lightId})
|
||||||
|
|
||||||
def msgPlayMsg(self, hv_id, msg_id):
|
def msgPlayMsg(self, hv_id, msg_id):
|
||||||
central_command.hugveys[hv_id].eventQueue.put_nowait({'event': 'play_msg', 'msg_id': msg_id})
|
central_command.hugveys[hv_id].eventQueue.put_nowait({'event': 'play_msg', 'msg_id': msg_id})
|
||||||
|
|
||||||
|
|
|
@ -692,16 +692,14 @@ class Diversion(object):
|
||||||
# if self.params['returnAfterStrand']:
|
# if self.params['returnAfterStrand']:
|
||||||
# await story.setCurrentMessage(self.returnMessage)
|
# await story.setCurrentMessage(self.returnMessage)
|
||||||
|
|
||||||
async def _divergeIfReplyContains(self, story, msgFrom, msgTo, direction):
|
async def _divergeIfReplyContains(self, story, msgFrom, msgTo, _):
|
||||||
"""
|
"""
|
||||||
Participant doesn't speak for x consecutive replies (has had timeout)
|
Participant doesn't speak for x consecutive replies (has had timeout)
|
||||||
"""
|
"""
|
||||||
':type story: Story'
|
':type story: Story'
|
||||||
# TODO: disable check on msgFrom/msgTo to allow for own timing (2 sec)
|
|
||||||
# use story.currentReply.getTimeSinceLastUtterance() > 2
|
# use story.currentReply.getTimeSinceLastUtterance() > 2
|
||||||
if story.currentDiversion or not msgFrom or not msgTo:
|
if story.currentDiversion: # or not msgFrom or not msgTo:
|
||||||
# don't do nested diversions
|
# don't do nested diversions
|
||||||
# if we remove this, don't forget to double check 'returnMessage'
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if self.hasHit:
|
if self.hasHit:
|
||||||
|
@ -711,6 +709,22 @@ class Diversion(object):
|
||||||
if story.currentReply is None or not self.regex:
|
if story.currentReply is None or not self.regex:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
direction = story.getDefaultDirectionForMsg(story.currentMessage)
|
||||||
|
if not direction:
|
||||||
|
# ignore the direction argument, and only check if the current message has a valid default
|
||||||
|
return
|
||||||
|
|
||||||
|
msgTo = direction.msgTo
|
||||||
|
|
||||||
|
if not direction:
|
||||||
|
return
|
||||||
|
|
||||||
|
waitTime = 1.8 if 'waitTime' not in self.params else float(self.params['waitTime'])
|
||||||
|
timeSince = story.currentReply.getTimeSinceLastUtterance()
|
||||||
|
if timeSince < waitTime:
|
||||||
|
story.logger.log(LOG_BS, f"Waiting for replyContains: {timeSince} (needs {waitTime})")
|
||||||
|
return
|
||||||
|
|
||||||
r = self.regex.search(story.currentReply.getText())
|
r = self.regex.search(story.currentReply.getText())
|
||||||
if r is None:
|
if r is None:
|
||||||
return
|
return
|
||||||
|
@ -756,7 +770,6 @@ class Diversion(object):
|
||||||
return
|
return
|
||||||
|
|
||||||
r = self.regex.search(story.currentReply.getText())
|
r = self.regex.search(story.currentReply.getText())
|
||||||
print('repeat?', r)
|
|
||||||
if r is None:
|
if r is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -1536,3 +1549,18 @@ class Story(object):
|
||||||
|
|
||||||
return self.calculateFinishesForMsg(msg.id)
|
return self.calculateFinishesForMsg(msg.id)
|
||||||
|
|
||||||
|
def getDefaultDirectionForMsg(self, msg):
|
||||||
|
"""
|
||||||
|
There is only a default direction (for reply contains diversion) if it has
|
||||||
|
one, and only one, direction to go. If there's more, it should do nothing.
|
||||||
|
"""
|
||||||
|
if not msg.id in self.directionsPerMsg:
|
||||||
|
# is finish
|
||||||
|
return None
|
||||||
|
|
||||||
|
if len(self.directionsPerMsg[msg.id]) > 1:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# TODO: should the direction have at least a timeout condition set, or not perse?
|
||||||
|
|
||||||
|
return self.directionsPerMsg[msg.id][0]
|
|
@ -1,6 +1,6 @@
|
||||||
apt-get update
|
apt-get update
|
||||||
|
|
||||||
apt-get install -y munin-node bc supervisor libsox-fmt-pulse
|
apt-get install -y munin-node bc supervisor
|
||||||
cp installation/rpi-internal-temp /usr/share/munin/plugins
|
cp installation/rpi-internal-temp /usr/share/munin/plugins
|
||||||
ln -sf /usr/share/munin/plugins/rpi-internal-temp /etc/munin/plugins/rpi-internal-temp
|
ln -sf /usr/share/munin/plugins/rpi-internal-temp /etc/munin/plugins/rpi-internal-temp
|
||||||
rm /etc/munin/plugins/irqstats
|
rm /etc/munin/plugins/irqstats
|
||||||
|
|
|
@ -7,7 +7,7 @@ voice:
|
||||||
port: 4444
|
port: 4444
|
||||||
chunk: 2972
|
chunk: 2972
|
||||||
google_credentials: "../test_googlespeech/My First Project-0c7833e0d5fa.json"
|
google_credentials: "../test_googlespeech/My First Project-0c7833e0d5fa.json"
|
||||||
hugveys: 25
|
hugveys: 26
|
||||||
languages:
|
languages:
|
||||||
- code: en-GB
|
- code: en-GB
|
||||||
file: story_en.json
|
file: story_en.json
|
||||||
|
|
|
@ -14,6 +14,7 @@ class Panopticon {
|
||||||
hugveys: [],
|
hugveys: [],
|
||||||
selectedId: null,
|
selectedId: null,
|
||||||
logbook: "",
|
logbook: "",
|
||||||
|
logbookId: null,
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
time_passed: function( hugvey, property ) {
|
time_passed: function( hugvey, property ) {
|
||||||
|
@ -60,6 +61,12 @@ class Panopticon {
|
||||||
hv.status = "loading";
|
hv.status = "loading";
|
||||||
return panopticon.change_language(hv.id, lang_code);
|
return panopticon.change_language(hv.id, lang_code);
|
||||||
},
|
},
|
||||||
|
change_light: function(e) {
|
||||||
|
let hv_id = parseInt(e.target.dataset.hvid);
|
||||||
|
let light_id = parseInt(e.target.value);
|
||||||
|
console.log(hv_id, light_id, this);
|
||||||
|
return panopticon.change_light_id(hv_id, light_id);
|
||||||
|
},
|
||||||
showHugvey: function(hv) {
|
showHugvey: function(hv) {
|
||||||
panopticon.hugveys.selectedId = hv.language ? hv.id : null;
|
panopticon.hugveys.selectedId = hv.language ? hv.id : null;
|
||||||
panopticon.hugveys.logbook = [];
|
panopticon.hugveys.logbook = [];
|
||||||
|
@ -222,6 +229,10 @@ class Panopticon {
|
||||||
change_language( hv_id, lang_code ) {
|
change_language( hv_id, lang_code ) {
|
||||||
this.send( { action: 'change_language', hugvey: hv_id, lang_code: lang_code } );
|
this.send( { action: 'change_language', hugvey: hv_id, lang_code: lang_code } );
|
||||||
}
|
}
|
||||||
|
change_light_id( hv_id, light_id ) {
|
||||||
|
console.log("Light", hv_id, light_id);
|
||||||
|
this.send( { action: 'change_light', hugvey: hv_id, light_id: light_id } );
|
||||||
|
}
|
||||||
|
|
||||||
playFromSelected(msg_id) {
|
playFromSelected(msg_id) {
|
||||||
if(!this.hugveys.selectedId) {
|
if(!this.hugveys.selectedId) {
|
||||||
|
@ -366,6 +377,7 @@ class Graph {
|
||||||
div['params']['returnAfterStrand'] = true;
|
div['params']['returnAfterStrand'] = true;
|
||||||
div['params']['msgId'] = "";
|
div['params']['msgId'] = "";
|
||||||
div['params']['notForColor'] = "";
|
div['params']['notForColor'] = "";
|
||||||
|
div['params']['waitTime'] = 1.8;
|
||||||
}
|
}
|
||||||
else if(type == 'interrupt') {
|
else if(type == 'interrupt') {
|
||||||
div['params']['msgId'] = "";
|
div['params']['msgId'] = "";
|
||||||
|
@ -574,6 +586,16 @@ class Graph {
|
||||||
'change': (e) => div['params']['msgId'] = e.target.value
|
'change': (e) => div['params']['msgId'] = e.target.value
|
||||||
}}, ...msgOptions)
|
}}, ...msgOptions)
|
||||||
),
|
),
|
||||||
|
crel('label', 'Wait time',
|
||||||
|
crel('input', {
|
||||||
|
'type': 'number',
|
||||||
|
'step': 0.1,
|
||||||
|
'value': div['params']['waitTime'],
|
||||||
|
'on': {
|
||||||
|
'change': (e) => div['params']['waitTime'] = parseFloat(e.target.value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
),
|
||||||
notAfterMsgIdEl
|
notAfterMsgIdEl
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
|
@ -147,4 +147,4 @@ class Timeline{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var tl = new Timeline(ws, document.getElementById('line'), 25);
|
var tl = new Timeline(ws, document.getElementById('line'), 26);
|
||||||
|
|
|
@ -39,6 +39,9 @@
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
{{ hv.language }}
|
{{ hv.language }}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- <div v-if="hv.awaiting != false"><img class='icon' :src="'/images/icon-finished.svg'" title="Finished"> {{timer(hv,
|
<!-- <div v-if="hv.awaiting != false"><img class='icon' :src="'/images/icon-finished.svg'" title="Finished"> {{timer(hv,
|
||||||
'finished')}}</div> -->
|
'finished')}}</div> -->
|
||||||
<div class='stats'>
|
<div class='stats'>
|
||||||
|
@ -61,9 +64,10 @@
|
||||||
<div class='btn' v-if="hv.status == 'running'" @click.stop="finish(hv)">Finish</div> <!-- to available state -->
|
<div class='btn' v-if="hv.status == 'running'" @click.stop="finish(hv)">Finish</div> <!-- to available state -->
|
||||||
<div class='btn' v-if="hv.status == 'running'" @click.stop="pause(hv)">Pause</div>
|
<div class='btn' v-if="hv.status == 'running'" @click.stop="pause(hv)">Pause</div>
|
||||||
<div class='btn' v-if="hv.status == 'paused'" @click.stop="resume(hv)">Resume</div>
|
<div class='btn' v-if="hv.status == 'paused'" @click.stop="resume(hv)">Resume</div>
|
||||||
<div class='light'>
|
<!-- <div class='light'>
|
||||||
{{ hv.light }}
|
{{ hv.light }}
|
||||||
</div>
|
</div> -->
|
||||||
|
<div class='light'>Light: <input type="number" step="1" :value="hv.light_id" @change="change_light" :data-hvid="hv.id" v-on:click.stop></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -72,7 +76,7 @@
|
||||||
<h1>Log of {{logbookId}}</h1>
|
<h1>Log of {{logbookId}}</h1>
|
||||||
<div v-for="log in logbook" class='log'>
|
<div v-for="log in logbook" class='log'>
|
||||||
<div class='time'>{{formatted(log.time)}}</div>
|
<div class='time'>{{formatted(log.time)}}</div>
|
||||||
<div class='content {{log.origin}}'>
|
<div :class="['content', log.origin]">
|
||||||
<span class='origin'>{{log.origin}}</span>
|
<span class='origin'>{{log.origin}}</span>
|
||||||
<span class='msg'>{{log.msg}}</span>
|
<span class='msg'>{{log.msg}}</span>
|
||||||
<span v-if="log.extra" class='extra'>( {{log.extra}} )</span>
|
<span v-if="log.extra" class='extra'>( {{log.extra}} )</span>
|
||||||
|
|
Loading…
Reference in a new issue