merged loopaudio

This commit is contained in:
Your Name 2019-05-11 15:42:41 +02:00
commit edd6d8924a
10 changed files with 196 additions and 109 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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})

View file

@ -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]

View file

@ -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

View file

@ -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

View file

@ -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
)); ));
} }

View file

@ -147,4 +147,4 @@ class Timeline{
} }
} }
var tl = new Timeline(ws, document.getElementById('line'), 25); var tl = new Timeline(ws, document.getElementById('line'), 26);

View file

@ -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>