From d857950653410daf9f2d2225a2813fe7143f2f26 Mon Sep 17 00:00:00 2001 From: Hugvey Central Command Date: Fri, 10 May 2019 15:11:33 +0200 Subject: [PATCH 1/5] small pd update --- pd/loopaudio.pd | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pd/loopaudio.pd b/pd/loopaudio.pd index a3fda18..bf5df1e 100644 --- a/pd/loopaudio.pd +++ b/pd/loopaudio.pd @@ -1,10 +1,10 @@ -#N canvas 137 370 679 478 10; +#N canvas 223 291 679 478 10; #X obj 155 392 dac~; #X obj 155 342 readsf~; #X msg 81 297 0; #X obj 233 324 bng 15 250 50 0 empty empty empty 17 7 0 10 -262144 -1 -1; -#X text 254 315 (re-)start loop; +#X text 252 322 (re-)start loop; #X obj 239 60 oscparse; #X obj 239 85 list trim; #X obj 239 38 netreceive -u -b 5555; @@ -12,11 +12,11 @@ #X obj 425 162 route 0; #X obj 339 119 route trigger; #X text 69 276 stop loop; -#X obj 508 205 print "starting loop"; -#X obj 509 228 print "stopping loop"; -#X msg 163 258 open testaudio.wav \, 1; -#X text 233 18 listen to osc at port 5555; -#X text 163 233 change audiofile HERE; +#X obj 469 216 print "starting loop"; +#X obj 470 239 print "stopping loop"; +#X msg 155 244 open testaudio.wav \, 1; +#X text 237 17 listen to osc at port 5555; +#X text 155 219 change audiofile HERE; #X connect 1 0 0 0; #X connect 1 0 0 1; #X connect 1 1 3 0; From 664619bab0350ce99a365821108703bb7ba3b075 Mon Sep 17 00:00:00 2001 From: Ruben van de Ven Date: Fri, 10 May 2019 15:14:13 +0200 Subject: [PATCH 2/5] ReplyContains diversoins now have their own timing (defaults to 1.8s) Fix #44 --- hugvey/story.py | 39 ++++++++++++++++++++++++++++++++++----- www/js/hugvey_console.js | 11 +++++++++++ 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/hugvey/story.py b/hugvey/story.py index 71d9a06..994ae98 100644 --- a/hugvey/story.py +++ b/hugvey/story.py @@ -692,16 +692,14 @@ class Diversion(object): # if self.params['returnAfterStrand']: # 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) """ ':type story: Story' - # TODO: disable check on msgFrom/msgTo to allow for own timing (2 sec) # 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 - # if we remove this, don't forget to double check 'returnMessage' return False if self.hasHit: @@ -711,6 +709,22 @@ class Diversion(object): if story.currentReply is None or not self.regex: 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()) if r is None: return @@ -1535,4 +1549,19 @@ class Story(object): return self.strands[msg.id] return self.calculateFinishesForMsg(msg.id) - \ No newline at end of file + + 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] \ No newline at end of file diff --git a/www/js/hugvey_console.js b/www/js/hugvey_console.js index 2515521..7d16bf3 100644 --- a/www/js/hugvey_console.js +++ b/www/js/hugvey_console.js @@ -366,6 +366,7 @@ class Graph { div['params']['returnAfterStrand'] = true; div['params']['msgId'] = ""; div['params']['notForColor'] = ""; + div['params']['waitTime'] = 1.8; } else if(type == 'interrupt') { div['params']['msgId'] = ""; @@ -574,6 +575,16 @@ class Graph { 'change': (e) => div['params']['msgId'] = e.target.value }}, ...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 )); } From 735a63683d9bc94af5b8af9b2b3fde6dc13aa43b Mon Sep 17 00:00:00 2001 From: Ruben van de Ven Date: Fri, 10 May 2019 16:59:14 +0200 Subject: [PATCH 3/5] Allow to change the id of the light per hugvey Fix #41 --- hugvey/central_command.py | 12 +++++++++++- hugvey/panopticon.py | 5 +++++ www/js/hugvey_console.js | 11 +++++++++++ www/panopticon.html | 10 +++++++--- 4 files changed, 34 insertions(+), 4 deletions(-) diff --git a/hugvey/central_command.py b/hugvey/central_command.py index 76b35f8..75fb60a 100644 --- a/hugvey/central_command.py +++ b/hugvey/central_command.py @@ -119,6 +119,7 @@ class CentralCommand(object): status['status'] = hv.getStatus() 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['finished'] = hv.story.isFinished() status['history'] = {} if isSelected is False or not hv.story else hv.story.getLogSummary() @@ -396,6 +397,7 @@ class HugveyState(object): def __init__(self, id: int, command: CentralCommand): self.id = id + self.lightId = id self.command = command self.logger = mainLogger.getChild(f"{self.id}").getChild("command") self.loop = asyncio.new_event_loop() @@ -544,6 +546,8 @@ class HugveyState(object): if event['event'] == 'change_language': self.setLanguage(event['lang_code']) + if event['event'] == 'change_light': + self.setLightId(event['light_id']) if event['event'] == 'play_msg': self.logger.info(f"Play given message {event['msg_id']}") if not self.story: @@ -621,7 +625,13 @@ class HugveyState(object): status = 1 if on else 0 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): '''Status to 'gone' as in, shutdown/crashed/whatever diff --git a/hugvey/panopticon.py b/hugvey/panopticon.py index 9eed054..64b6959 100644 --- a/hugvey/panopticon.py +++ b/hugvey/panopticon.py @@ -66,6 +66,8 @@ def getWebSocketHandler(central_command): self.msgFinish(msg['hugvey']) elif msg['action'] == 'change_language': 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': self.msgPlayMsg(msg['hugvey'], msg['msg_id']) else: @@ -119,6 +121,9 @@ def getWebSocketHandler(central_command): def msgChangeLanguage(self, hv_id, 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): central_command.hugveys[hv_id].eventQueue.put_nowait({'event': 'play_msg', 'msg_id': msg_id}) diff --git a/www/js/hugvey_console.js b/www/js/hugvey_console.js index 7d16bf3..96054fb 100644 --- a/www/js/hugvey_console.js +++ b/www/js/hugvey_console.js @@ -14,6 +14,7 @@ class Panopticon { hugveys: [], selectedId: null, logbook: "", + logbookId: null, }, methods: { time_passed: function( hugvey, property ) { @@ -60,6 +61,12 @@ class Panopticon { hv.status = "loading"; 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) { panopticon.hugveys.selectedId = hv.language ? hv.id : null; panopticon.hugveys.logbook = []; @@ -222,6 +229,10 @@ class Panopticon { change_language( hv_id, 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) { if(!this.hugveys.selectedId) { diff --git a/www/panopticon.html b/www/panopticon.html index 3c6610c..2655d9e 100644 --- a/www/panopticon.html +++ b/www/panopticon.html @@ -39,6 +39,9 @@ {{ hv.language }} + + +
@@ -61,9 +64,10 @@
Finish
Pause
Resume
-
+ +
Light:
@@ -72,7 +76,7 @@

Log of {{logbookId}}

{{formatted(log.time)}}
-
+
{{log.origin}} {{log.msg}} ( {{log.extra}} ) From 484542de8d28627e3056b5db1677cbee10ccfcf6 Mon Sep 17 00:00:00 2001 From: Ruben van de Ven Date: Fri, 10 May 2019 19:35:57 +0200 Subject: [PATCH 4/5] If no hugvey is available, override the red light Fix #42 --- hugvey/central_command.py | 20 ++++++++++++++++++++ hugvey/story.py | 1 - 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/hugvey/central_command.py b/hugvey/central_command.py index 75fb60a..2f27175 100644 --- a/hugvey/central_command.py +++ b/hugvey/central_command.py @@ -232,6 +232,23 @@ class CentralCommand(object): logger.warn('Stopping light sender') 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): ''' @@ -357,6 +374,9 @@ class CentralCommand(object): self.catchException(self.commandSender())) self.tasks['lightSender'] = self.loop.create_task( self.catchException(self.lightSender())) + self.tasks['redLightController'] = self.loop.create_task( + self.catchException(self.redLightController())) + for hid in self.hugvey_ids: self.tasks['voiceListener'] = self.loop.create_task( self.catchException(self.voiceListener(hid))) diff --git a/hugvey/story.py b/hugvey/story.py index 994ae98..0c269ab 100644 --- a/hugvey/story.py +++ b/hugvey/story.py @@ -770,7 +770,6 @@ class Diversion(object): return r = self.regex.search(story.currentReply.getText()) - print('repeat?', r) if r is None: return From 43e3e52f2a8a40d5ab63c47f415403c05d9cc5d6 Mon Sep 17 00:00:00 2001 From: Hugvey Central Command Date: Sat, 11 May 2019 15:23:55 +0200 Subject: [PATCH 5/5] Deepcopy story data and config changes --- README.md | 4 +- client_config.yml | 5 +- hugvey/central_command.py | 151 +++++++++++++++++++------------------- install_server.sh | 2 +- server_config.yml | 6 +- www/js/hugvey_timeline.js | 34 ++++----- 6 files changed, 100 insertions(+), 102 deletions(-) diff --git a/README.md b/README.md index 9c5154c..ed16384 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,7 @@ chown=pi:pi ## Deploy / usefull commands ```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 @@ -204,4 +204,4 @@ times occured/only on n-th instance: determines the order of diversions of the s ## 4G Modem Visit 192.168.5.1 -The password is at the bottom of the device. \ No newline at end of file +The password is at the bottom of the device. diff --git a/client_config.yml b/client_config.yml index a49d591..5ec33d3 100644 --- a/client_config.yml +++ b/client_config.yml @@ -5,7 +5,7 @@ voice: input_rate: 44100 target_rate: 16000 port: 4444 - input_name: 'AK5371' + input_name: 'USB Audio Device' output_name: 'USB Audio Device' input_mixer: 'Mic' output_mixer: 'PCM' @@ -13,6 +13,3 @@ voice: output_volume: 30 file_address: "http://hugveycmd.local:8888" output_driver: pulseaudio - - - diff --git a/hugvey/central_command.py b/hugvey/central_command.py index 2f27175..26d9aa0 100644 --- a/hugvey/central_command.py +++ b/hugvey/central_command.py @@ -24,6 +24,7 @@ from hugvey.voice import VoiceStorage import multiprocessing from hugvey.speech.recorder import Recorder from pythonosc import udp_client +import copy mainLogger = logging.getLogger("hugvey") @@ -67,7 +68,7 @@ class CentralCommand(object): self.languageFiles = {} self.languageConfig = {} self.args = args # cli args - + eventLogger.addHandler(logging.handlers.QueueHandler(self.logQueue)) def loadConfig(self, filename): @@ -85,14 +86,14 @@ class CentralCommand(object): self.hugvey_ids = [i + 1 for i in range(self.config['hugveys'])] self.loadLanguages() - - + + voice_dir = os.path.join(self.config['web']['files_dir'], 'voices') self.voiceStorage = VoiceStorage(voice_dir, self.languageConfig) - + self.panopticon = Panopticon(self, self.config, self.voiceStorage) - - + + def loadLanguages(self): logger.debug('load language files') self.languages = {} @@ -116,7 +117,7 @@ class CentralCommand(object): # if not hv.story: # status['status'] = 'off' # return status - + status['status'] = hv.getStatus() status['language'] = hv.language_code status['light_id'] = hv.lightId @@ -126,7 +127,7 @@ class CentralCommand(object): # status['history'] = hv.story.getLogSummary() # disabled as it is a bit slow. We now have eventLog # status['counts'] = {t: len(a) for t, a in status['history'].items() if t != 'directions' } status['counts'] = {} if not hv.story else hv.story.getLogCounts() - status['duration'] = 0 if not hv.story else hv.story.timer.getElapsed() + status['duration'] = 0 if not hv.story else hv.story.timer.getElapsed() return status @@ -139,10 +140,10 @@ class CentralCommand(object): 'logbookId': None, 'logbook': [], } - + #use this to test if any threads stay open # eg. after killing/dying of a hugvey -# print(threading.enumerate()) +# print(threading.enumerate()) for hv_id in self.hugvey_ids: status['hugveys'].append(self.getHugveyStatus(hv_id, selected_id == hv_id)) @@ -151,7 +152,7 @@ class CentralCommand(object): if self.hugveys[selected_id].recorder: status['logbook'] = self.hugveys[selected_id].recorder.currentLog status['logbookId'] = selected_id - + return status def commandHugvey(self, hv_id, msg): @@ -169,8 +170,8 @@ class CentralCommand(object): def _queueCommand(self, hv_id, msg): self.commandQueue.put_nowait((hv_id, msg)) - - + + def commandLight(self, route, data): """ Buffer light commands @@ -215,13 +216,13 @@ class CentralCommand(object): logger.warn('Stopping command sender') s.close() - - + + async def lightSender(self): lightConn = udp_client.SimpleUDPClient( self.config['light']['ip'], self.config['light']['port']) - + logger.info(f"Ready to send light commands to: {self.config['light']['ip']}:{self.config['light']['port']}") while self.isRunning.is_set(): @@ -232,7 +233,7 @@ class CentralCommand(object): logger.warn('Stopping light sender') lightConn._sock.close() - + async def redLightController(self): """ Every second, check if no hugveys are available. If so, the red light should be @@ -264,7 +265,7 @@ class CentralCommand(object): thread = threading.Thread( target=self.hugveyStateRunner, args=(hugvey_id,), name=f"hugvey#{hugvey_id}") thread.start() - + def hugveyStateRunner(self, hugvey_id): while self.isRunning.is_set(): logger.info(f'Instantiate hugvey #{hugvey_id}') @@ -278,7 +279,7 @@ class CentralCommand(object): return logger.critical(f'Hugvey stopped (crashed?). Reinstantiate after 5 sec') time.sleep(5) - + async def timerEmitter(self): """ This is fixed: a one hour loop with a collective moment 10-15 minutes, @@ -288,25 +289,25 @@ class CentralCommand(object): intervals = [ { 'start_time': 10*60, - 'duration': 5 * 60, + 'duration': 5 * 60, }, { 'start_time': 30*60, - 'duration': 5 * 60, + 'duration': 5 * 60, }, { 'start_time': 50*60, - 'duration': 5 * 60, + 'duration': 5 * 60, } ] self.start_time = time.time() - + # TODO: emit start event - + while self.isRunning.is_set(): - + pass - + async def eventListener(self): s = self.ctx.socket(zmq.SUB) s.bind(self.config['events']['listen_address']) @@ -319,7 +320,7 @@ class CentralCommand(object): while self.isRunning.is_set(): try: hugvey_id, msg = await zmqReceive(s) - + if hugvey_id not in self.hugvey_ids: logger.critical( "Message from alien Hugvey: {}".format(hugvey_id)) @@ -355,7 +356,7 @@ class CentralCommand(object): fn = await self.voiceStorage.requestFile(hv.language_code, text, isVariable) if fn is None: eventLogger.getChild(f"{hugvey_id}").critical("error: No voice file fetched, check logs.") - fn = 'local/crash.wav' + fn = 'local/crash.wav' # TODO: trigger a repeat/crash event. await s.send_string(fn) except Exception as e: @@ -376,7 +377,7 @@ class CentralCommand(object): self.catchException(self.lightSender())) self.tasks['redLightController'] = self.loop.create_task( self.catchException(self.redLightController())) - + for hid in self.hugvey_ids: self.tasks['voiceListener'] = self.loop.create_task( self.catchException(self.voiceListener(hid))) @@ -386,12 +387,12 @@ class CentralCommand(object): self.panopticon_thread = threading.Thread( target=self.panopticon.start, name="Panopticon") self.panopticon_thread.start() - + self.loop.run_forever() def stop(self): self.isRunning.clear() - + async def catchException(self, awaitable): try: # print(awaitable) @@ -405,7 +406,7 @@ class HugveyState(object): """Represents the state of a Hugvey client on the server. Manages server connections & voice parsing etc. """ - + # all statusses can only go up or down, except for gone, which is an error state: # off <-> blocked <-> available <-> running <-> paused STATE_OFF = "off" @@ -424,7 +425,7 @@ class HugveyState(object): self.isConfigured = None self.isRunning = asyncio.Event(loop=self.loop) self.isRunning.clear() - + self.eventQueue = None self.language_code = 'en-GB' self.story = None @@ -435,24 +436,24 @@ class HugveyState(object): self.notShuttingDown = True # TODO: allow shutdown of object self.startMsgId = None self.eventLogger = eventLogger.getChild(f"{self.id}") - + self.setStatus(self.STATE_GONE) - + self.requireRestartAfterStop = None def __del__(self): self.logger.warn("Destroying hugvey object") - + def getStatus(self): return self.status - + def setStatus(self, status): self.status = status lightOn = status in [self.STATE_AVAILABLE, self.STATE_PAUSE] self.setLightStatus(lightOn) self.eventLogger.info(f"status: {self.status}") - - + + def config(self, hostname, ip): self.ip = ip self.hostname = hostname @@ -463,7 +464,7 @@ class HugveyState(object): else: self.logger.info( f"Hugvey {self.id} at {self.ip}, host: {self.hostname}") - + if self.status == self.STATE_GONE: # turn on :-) self.setStatus(self.STATE_BLOCKED) @@ -496,7 +497,7 @@ class HugveyState(object): self.logger.exception(e) self.logger.critical(f"Hugvey crash") self.eventLogger.critical(f"error: {e}") - + # restart # TODO: test proper functioning self.shutdown() @@ -510,18 +511,18 @@ class HugveyState(object): else: # Allow for both the Hugvey Command, or the Story handle the event. self.loop.call_soon_threadsafe(self._queueEvent, msg) - + def _queueEvent(self, msg): """ Put event in both the event loop for the story as well as the Hugvey State handler """ self.logger.debug(f"Queue event in hugvey loop: {msg}") self.eventQueue.put_nowait(msg) - + # connection events don't need to go to the story if msg['event'] == 'connection': return - + if self.story: self.story.events.append(msg) else: @@ -538,7 +539,7 @@ class HugveyState(object): self.logger.error("Hugvey did not send heartbeat.") self.gone() continue - + self.logger.debug("Received: {}".format(event)) if event['event'] == 'connection': # 'event': 'connection', @@ -546,11 +547,11 @@ class HugveyState(object): # 'host': socket.gethostname(), # 'ip': self.getIp(), self.config(event['host'], event['ip']) - - + + if event['event'] == 'language': self.setLanguage(event['code']) - + if event['event'] == 'pause': self.pause() if event['event'] == 'block': @@ -563,7 +564,7 @@ class HugveyState(object): self.story._finish() # finish story AND hugvey state if event['event'] == 'resume': self.resume() - + if event['event'] == 'change_language': self.setLanguage(event['lang_code']) if event['event'] == 'change_light': @@ -577,7 +578,7 @@ class HugveyState(object): # self.restart() if self.story is None: return - + self.startMsgId = event['msg_id'] self.logger.debug(f"Restart from {self.startMsgId}") self.restart() @@ -587,18 +588,18 @@ class HugveyState(object): def setLanguage(self, language_code): if language_code not in self.command.languages: raise Exception("Invalid language {}".format(language_code)) - + self.logger.info(f"set language: {language_code}") self.language_code = language_code - + if self.google: self.google.setLanguage(language_code) - + if self.isRunning.is_set(): self.restart() # self.story.reset() # self.story.setStoryData(self.command.languages[language_code]) - + def pause(self): self.logger.info('Pause') if self.google: @@ -607,7 +608,7 @@ class HugveyState(object): self.story.pause() self.isRunning.clear() self.setStatus(self.STATE_PAUSE) - + def resume(self): """ Start playing without reset""" self.logger.info('Resume') @@ -617,14 +618,14 @@ class HugveyState(object): self.story.resume() self.isRunning.set() self.setStatus(self.STATE_RUNNING) - + def restart(self): """Start playing with reset""" self.logger.info('Restart') if self.story: self.story.stop() self.resume() - + def block(self): """Block a hugvey""" self.logger.info('block') @@ -634,37 +635,37 @@ class HugveyState(object): self.story.finish() self.isRunning.clear() self.setStatus(self.STATE_BLOCKED) - + def available(self): """Put in available mode""" self.logger.info('Finish/Await') self.pause() self.setStatus(self.STATE_AVAILABLE) - + def setLightStatus(self, on): status = 1 if on else 0 self.logger.log(LOG_BS, f"Send /hugvey {status}") - + self.command.commandLight('/hugvey', [self.lightId, status]) - + def setLightId(self, id): """ Connect hugvey to another light """ self.lightId = id - + def gone(self): '''Status to 'gone' as in, shutdown/crashed/whatever ''' self.pause() if self.story: self.story.stop() - + self.logger.warn('Gone') self.eventLogger.warn("Gone") self.isConfigured = None self.setStatus(self.STATE_GONE) - + def shutdown(self, definitive = False): self.logger.info(f"Start shutdown sequence {definitive}") self.eventLogger.critical(f"error: shutting down") @@ -673,7 +674,7 @@ class HugveyState(object): if self.story: self.story.shutdown() self.story = None - + # shutdown for stream consumers already ran. Only clear references if self.google: self.google = None @@ -681,14 +682,14 @@ class HugveyState(object): self.player = None if self.recorder: self.recorder = None - + if self.requireRestartAfterStop is None: # prevent double setting of the same variable # first call sometimes triggers second self.requireRestartAfterStop = not definitive - + self.notShuttingDown = False - + async def playStory(self): while self.notShuttingDown: @@ -706,13 +707,13 @@ class HugveyState(object): self.logger.warn(f"Starting from {startMsgId}") if not self.streamer: await asyncio.sleep(1) - + 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) await self.story.run(startMsgId) # self.story = None - + def getStreamer(self): if not self.streamer: self.streamer = AudioStreamer( @@ -720,19 +721,19 @@ class HugveyState(object): self.ip, int(self.command.config['voice']['port']) + self.id, self.id) - + if self.command.config['voyeur']: self.logger.warn("Debug on: Connecting Audio player") self.player = Player( self.command.config['voice']['src_rate'], self.command.config['voice']['out_rate']) self.streamer.addConsumer(self.player) - + if self.command.config['voice']['record_dir']: self.logger.warn("Record Audio of conversation") self.recorder = Recorder( self.id, self.command.config['voice']['src_rate'], self.command.config['voice']['record_dir']) self.streamer.addConsumer(self.recorder) - + self.logger.debug("Start Speech") self.google = GoogleVoiceClient( hugvey=self, @@ -747,7 +748,7 @@ class HugveyState(object): ''' Start the audio streamer service ''' - + self.logger.debug("Start audio loop") while self.notShuttingDown: diff --git a/install_server.sh b/install_server.sh index d009e02..dc4a2bb 100755 --- a/install_server.sh +++ b/install_server.sh @@ -1,6 +1,6 @@ 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 ln -sf /usr/share/munin/plugins/rpi-internal-temp /etc/munin/plugins/rpi-internal-temp rm /etc/munin/plugins/irqstats diff --git a/server_config.yml b/server_config.yml index e39086c..2d6f3e7 100644 --- a/server_config.yml +++ b/server_config.yml @@ -7,7 +7,7 @@ voice: port: 4444 chunk: 2972 google_credentials: "../test_googlespeech/My First Project-0c7833e0d5fa.json" -hugveys: 25 +hugveys: 26 languages: - code: en-GB file: story_en.json @@ -28,7 +28,7 @@ languages: ms_lang: "fr-FR" web: port: 8888 - files_dir: "local/" + files_dir: "local/" light: ip: "192.168.178.15" - port: 7400 \ No newline at end of file + port: 7400 diff --git a/www/js/hugvey_timeline.js b/www/js/hugvey_timeline.js index 560ba49..4b9c94a 100644 --- a/www/js/hugvey_timeline.js +++ b/www/js/hugvey_timeline.js @@ -18,60 +18,60 @@ class Timeline{ {content: '.', start: new Date(), type: 'point', group: 1} ]); console.log('init timeline'); - + let groups = []; for(let hid = 1; hid<=this.count; hid++) { groups.push({id: parseInt(hid), content: 'Hugvey #'+hid}); this.eventDataSet.add({content: 'initiate', start: new Date(), type: 'point', group: parseInt(hid)}) } - + let dataGroups = new vis.DataSet(groups); let options = { // 'rollingMode': {'follow': true, 'offset': .8 } }; console.log('groups', dataGroups, groups, options); - + this.timeline = new vis.Timeline(this.el, this.eventDataSet, dataGroups, options); - + let tl = this.timeline; let startDate = new Date(); startDate.setMinutes(startDate.getMinutes()-1); let endDate = new Date(); endDate.setMinutes(endDate.getMinutes()+20); setTimeout(function(){ - tl.setWindow(startDate, endDate); + tl.setWindow(startDate, endDate); }, 500); this.moveInterval = setInterval(function(){ // skip movement if not visible tl.moveTo(new Date()); }, 1000); - + ws.addEventListener( 'message', this); } - + handleEvent(e) { console.log('handle', e, this); if(e.type == 'message') { this.wsOnMessage(e) } } - + wsOnMessage(e) { let msg = JSON.parse( e.data ); - + if ( typeof msg['action'] === 'undefined' ) { console.error( "not a valid message: " + e.data ); return; } - + if(msg['action'] != 'log') { return; } - + console.debug(msg, this); - + let hv_id = parseInt(msg['id']); // {'action': 'log', 'id':hugvey_id, 'type': items[0], 'info', 'args'} let d, parts; @@ -91,7 +91,7 @@ class Timeline{ this.eventDataSet.update(d); console.log('update', d); } else { - this.eventDataSet.add({id: mId, content: msgContent, title: `${msgContent} (${msgId})`, start: new Date(), group: hv_id, 'className': 'message'}); + this.eventDataSet.add({id: mId, content: msgContent, title: `${msgContent} (${msgId})`, start: new Date(), group: hv_id, 'className': 'message'}); } break; case 'speaking': @@ -101,7 +101,7 @@ class Timeline{ let id = parts.shift(); let content = parts.join(' '); let scId = 'sc-'+id+'-'+hv_id; - + if(info.startsWith('start')){ this.eventDataSet.add({content: info, start: new Date(), type: 'point', group: hv_id, 'className': 'speech'}); } @@ -115,7 +115,7 @@ class Timeline{ this.eventDataSet.update(d); } else { console.log('add'); - this.eventDataSet.add({id: scId, content: content, title: content, start: new Date(), group: hv_id, 'className': 'speech'}); + this.eventDataSet.add({id: scId, content: content, title: content, start: new Date(), group: hv_id, 'className': 'speech'}); } } if(info.startsWith('end')){ @@ -125,7 +125,7 @@ class Timeline{ this.eventDataSet.update(d); } } - + break; case 'story': // 'info': 'start'/'finished' @@ -147,4 +147,4 @@ class Timeline{ } } -var tl = new Timeline(ws, document.getElementById('line'), 25); +var tl = new Timeline(ws, document.getElementById('line'), 26);