diff --git a/CHANGELOG.md b/CHANGELOG.md index 57e295e..73b28f8 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,43 @@ All notable changes to this project will be documented in this file. --- +#### [1.1.4] - 2018-04-25 + +- Added command command to stay awake. This cancels the screen timeout +- Added modulemap can now be a set of modules +- Added command to hide all modules except the selected one. This can be a set from the modulemap so + effectively this can be used to show named pages + +#### [1.1.3] - 2018-04-20 + +- Added Google TTS. Many more languages available than pico2tts + Command conversation can now be in languages other than en-GB,de-DE,es-ES,fr-FR,it-IT like nl-NL +- Added config switch to choose between pico and Google TTS +- Fixed one more bug in GA. Saying "nevermind" just stopped the conversation without returning to listening mode + +#### [1.1.2] - 2018-04-20 + +- Added timeout for screen +- Added screen section in config to specify timeout and screen on/off commands + +#### [1.1.1] - 2018-04-19 + +- Changed handling of config.js so it is now backwards compatible +- fixed some more GA bugs. It should be stable and robust now and handle conversations correctly + +#### [1.1.0] - 2018-04-17 + +- Changed handling of commands in config +- Changed format of models in config.js so an old one will not work (see: assets/config.txt) +- Added commands for screen on and off +- Added commands for showing/hiding a single named module +- Added mapping of spoken module name to actual module name (really helps non-english speakers) +- Added command to play .wav +- Added a single hotword can fire a list of commands and stay in listening mode + for example, a spoken "wake up" can turn on the screen (like PIR) without activating GA or voice command +- Added model specific confirmation sound added in config (parameter "confirm") +- Added translate/nl.json for dutch language + #### [1.0.3] - 2018-03-31 diff --git a/MMM-Assistant.js b/MMM-Assistant.js index d32bb06..10d9c1e 100644 --- a/MMM-Assistant.js +++ b/MMM-Assistant.js @@ -88,7 +88,9 @@ Module.register("MMM-Assistant", start: function() { console.log("[ASSTNT] started!") + this.modulemap = new Map(this.config.modulemap) this.commands = [] +// this.screentimer this.status = "START" this.config = this.configAssignment({}, this.defaults, this.config) this.getCommands( new AssistantCommandRegister(this, this.registerCommand.bind(this)) ) @@ -99,6 +101,7 @@ Module.register("MMM-Assistant", getTranslations: function() { return { en: "translations/en.json", + nl: "translations/nl.json", fr: "translations/fr.json", } }, @@ -128,16 +131,31 @@ Module.register("MMM-Assistant", description: this.translate("CMD_LIST_MODULES_DESCRIPTION"), callback : 'cmd_asstnt_list_modules' }, + { + command: this.translate("CMD_HIDE_ALL_MODULES_EXCEPT"), + description : this.translate("CMD_HIDE_ALL_MODULES_EXCEPT_DESCRIPTION"), + callback : 'cmd_asstnt_hideall_except', + }, { command: this.translate("CMD_HIDE_ALL_MODULES"), description : this.translate("CMD_HIDE_ALL_MODULES_DESCRIPTION"), callback : 'cmd_asstnt_hideall', }, + { + command: this.translate("CMD_HIDE_MODULE"), + description : this.translate("CMD_HIDE_MODULE_DESCRIPTION"), + callback : 'cmd_asstnt_hide_module', + }, { command: this.translate("CMD_SHOW_ALL_MODULES"), description : this.translate("CMD_SHOW_ALL_MODULES_DESCRIPTION"), callback : 'cmd_asstnt_showall', }, + { + command: this.translate("CMD_SHOW_MODULE"), + description : this.translate("CMD_SHOW_MODULE_DESCRIPTION"), + callback : 'cmd_asstnt_show_module', + }, { command: this.translate("CMD_SAY"), description : this.translate("CMD_SAY_DESCRIPTION"), @@ -153,6 +171,21 @@ Module.register("MMM-Assistant", description : this.translate("CMD_REBOOT_DESCRIPTION"), callback : 'cmd_asstnt_reboot', }, + { + command: this.translate("CMD_WAKE_UP"), + description : this.translate("CMD_WAKE_UP_DESCRIPTION"), + callback : 'cmd_asstnt_wakeup', + }, + { + command: this.translate("CMD_STAY_AWAKE"), + description : this.translate("CMD_STAY_AWAKE_DESCRIPTION"), + callback : 'cmd_asstnt_stay_awake', + }, + { + command: this.translate("CMD_GOTO_SLEEP"), + description : this.translate("CMD_GOTO_SLEEP_DESCRIPTION"), + callback : 'cmd_asstnt_gotosleep', + }, ] commands.forEach((c) => { Register.add(c) @@ -174,7 +207,43 @@ Module.register("MMM-Assistant", this.sendSocketNotification('SHUTDOWN') }, + cmd_asstnt_wakeup : function (command, handler) { + if (typeof this.config.screen.on !== 'undefined') { + this.sendSocketNotification('EXECUTE', this.config.screen.on) + } else { + this.sendSocketNotification('EXECUTE', "vcgencmd display_power 1") + } + if (typeof this.config.screen.timeoff !== 'undefined') { + if (typeof this.config.screen.timeoff !== 0) { + clearTimeout(this.screenTimer) + this.screenTimer = setTimeout(() => { + if (typeof this.config.screen.off !== 'undefined') { + this.sendSocketNotification('EXECUTE', this.config.screen.off) + } else { + this.sendSocketNotification('EXECUTE', "vcgencmd display_power 0") + } + }, this.config.screen.timeoff * 1000) + } + } + if (this.status !== "COMMAND_MODE") this.sendSocketNotification("HOTWORD_STANDBY") + }, + + cmd_asstnt_stay_awake : function (command, handler) { + clearTimeout(this.screenTimer) + if (this.status !== "COMMAND_MODE") this.sendSocketNotification("HOTWORD_STANDBY") + }, + + cmd_asstnt_gotosleep : function (command, handler) { + if (typeof this.config.screen.off !== 'undefined') { + this.sendSocketNotification('EXECUTE', this.config.screen.off) + } else { + this.sendSocketNotification('EXECUTE', "vcgencmd display_power 0") + } + if (this.status !== "COMMAND_MODE") this.sendSocketNotification("HOTWORD_STANDBY") + }, + cmd_asstnt_say : function (command, handler) { + this.sendSocketNotification('LOG', {title: "SAY", message: handler.args.something}) handler.response(handler.args.something) }, @@ -182,14 +251,73 @@ Module.register("MMM-Assistant", var text = this.translate("CMD_HIDE_ALL_MODULES_RESULT") var lockString = this.name MM.getModules().enumerate( (m)=> { m.hide(0, {lockString:lockString}) }) - handler.response(text) + if (this.status !== "COMMAND_MODE") { + this.sendSocketNotification("HOTWORD_STANDBY") + } else { + handler.response(text) + } + }, + + cmd_asstnt_hide_module : function (command, handler) { + var text = this.translate("CMD_HIDE_MODULE_RESULT") + var lockString = this.name + var target = handler.args['module'] + var moduleSet = this.modulemap.get(target) + if (typeof moduleSet == 'undefined') moduleSet = target + MM.getModules().withClass(moduleSet).forEach( (m)=> { + m.hide(0, {lockString:lockString}) + }) + if (this.status !== "COMMAND_MODE") { + this.sendSocketNotification("HOTWORD_STANDBY") + } else { + handler.response(text) + } }, cmd_asstnt_showall : function (command, handler) { var text = this.translate("CMD_SHOW_ALL_MODULES_RESULT") var lockString = this.name MM.getModules().enumerate( (m)=> { m.show(0, {lockString:lockString}) }) - handler.response(text) + if (this.status !== "COMMAND_MODE") { + this.sendSocketNotification("HOTWORD_STANDBY") + } else { + handler.response(text) + } + }, + + cmd_asstnt_show_module : function (command, handler) { + var text = this.translate("CMD_SHOW_MODULE_RESULT") + var lockString = this.name + var target = handler.args['module'] + var moduleSet = this.modulemap.get(target) + if (typeof moduleSet == 'undefined') moduleSet = target + MM.getModules().withClass(moduleSet).forEach( (m)=> { + m.show(0, {lockString:lockString}) + }) + if (this.status !== "COMMAND_MODE") { + this.sendSocketNotification("HOTWORD_STANDBY") + } else { + handler.response(text) + } + }, + + cmd_asstnt_hideall_except : function (command, handler) { + var text = this.translate("CMD_HIDE_ALL_MODULES_EXCEPT_RESULT") + var lockString = this.name + var target = handler.args['module'] + var moduleSet = this.modulemap.get(target) + if (typeof moduleSet == 'undefined') moduleSet = target + MM.getModules().exceptWithClass(moduleSet).forEach( (m)=> { + if (m.name != this.name) {m.hide(1000, {lockString:lockString})} + }) + MM.getModules().withClass(moduleSet).forEach( (m)=> { + m.show(1000, {lockString:lockString}) + }) + if (this.status !== "COMMAND_MODE") { + this.sendSocketNotification("HOTWORD_STANDBY") + } else { + handler.response(text) + } }, cmd_asstnt_list_commands : function (command, handler) { @@ -404,16 +532,28 @@ Module.register("MMM-Assistant", this.sendSocketNotification("HOTWORD_STANDBY") } break + case 'NOTIFY': + this.status = "COMMAND_MODE" + if (payload.notification == "COMMAND") { + this.parseCommand(payload.parameter, this.sendSocketNotification.bind(this)) + } else if (payload.notification == "EXECUTE") { + this.sendSocketNotification("EXECUTE", payload.parameter) + } else { + this.sendNotification(payload.notification, payload.parameter) + } + break case 'COMMAND': - this.status = "COMMAND"; console.log("[ASSTNT] Command:", payload) this.parseCommand(payload, this.sendSocketNotification.bind(this)) - //this.sendSocketNotification("HOTWORD_STANDBY") break; case 'ERROR': this.status = "ERROR" console.log("[ASSTNT] Error:", payload) - //this.sendSocketNotification("HOTWORD_STANDBY") + this.sendSocketNotification("HOTWORD_STANDBY") + break + case 'LOG': + // straight back to node helper + this.sendSocketNotification(notification, payload) break case 'MODE': this.status = payload.mode @@ -444,14 +584,36 @@ Module.register("MMM-Assistant", }, hotwordDetected : function (type) { - if (type == 'ASSISTANT') { - this.sendSocketNotification('ACTIVATE_ASSISTANT') - this.status = 'ACTIVATE_ASSISTANT' - } else if (type == 'MIRROR') { - this.sendSocketNotification('ACTIVATE_COMMAND') - this.status = 'ACTIVATE_COMMAND' + // execute commands + if (typeof this.config.snowboy.models[type.index-1].commands !== 'undefined') { + this.config.snowboy.models[type.index-1].commands.forEach( + (command) => { + this.sendSocketNotification('LOG', {title: "[Command] ", message: command}) + if (command.notification == 'ASSISTANT') { + this.sendSocketNotification('ACTIVATE_ASSISTANT') + this.status = 'ACTIVATE_ASSISTANT' + } else if (command.notification == 'MIRROR') { + this.status = 'ACTIVATE_COMMAND' + this.sendSocketNotification('ACTIVATE_COMMAND') + } else { + this.status = "COMMAND_MODE" + this.sendSocketNotification('NOTIFY', command) + } + } + ) + } else if (typeof this.config.snowboy.models[type.index-1].hotwords !== 'undefined') { + if (this.config.snowboy.models[type.index-1].hotwords == 'ASSISTANT') { + this.sendSocketNotification('ACTIVATE_ASSISTANT') + this.status = 'ACTIVATE_ASSISTANT' + } else if (this.config.snowboy.models[type.index-1].hotwords == 'MIRROR') { + this.status = 'ACTIVATE_COMMAND' + this.sendSocketNotification('ACTIVATE_COMMAND') + } + } + // if last command was not a call for assistant or voice command then activate snowboy + if (this.status == "COMMAND_MODE") { + this.sendSocketNotification("NOTIFY", {notification: "HOTWORD_STANDBY"}) } - }, parseCommand: function(msg, cb) { @@ -461,7 +623,7 @@ Module.register("MMM-Assistant", cb("HOTWORD_STANDBY") return } - var msgText = msg + var msgText = msg.toLowerCase() var commandFound = 0 var c for(var i in this.commands) { @@ -526,8 +688,10 @@ Module.register("MMM-Assistant", }, response: function(text, originalCommand, option) { - this.sendSocketNotification('SPEAK', {text:text, option:option, originalCommand:originalCommand} ) - this.status = 'SPEAK' + if (this.status !== "COMMAND_MODE") { + this.sendSocketNotification('SPEAK', {text:text, option:option, originalCommand:originalCommand} ) + this.status = 'SPEAK' + } }, loadCSS: function() { diff --git a/assets/config.txt b/assets/config.txt index 1935753..c1d2b71 100644 --- a/assets/config.txt +++ b/assets/config.txt @@ -17,22 +17,86 @@ }, }, snowboy: { + // major change: every model now starts a list of commands so no more "hotword" parameter models: [ { - file: "resources/smart_mirror.umdl",// This file define your MM wake word. (See doc notes.) + file: "resources/hello.pmdl", // This file defines your MM wake word. (See doc note$ sensitivity: 0.5, - hotwords : "MIRROR" // Default model: "MIRROR". (This is not the wake word!) + commands: [ + { + notification: "COMMAND", + parameter: "show overview" + }, + { + notification: "COMMAND", + parameter: "turn the screen on" + }, + ] }, { - file: "resources/snowboy.umdl", // This file define your GA wake word. (See doc notes.) + file: "resources/u-models/smart_mirror.umdl", // This file defines your MM wake word. (See doc notes.) sensitivity: 0.5, - hotwords : "ASSISTANT" // Default model: "ASSISTANT". (This is not the wake word!) + commands: [ + { + notification: "COMMAND", + parameter: "turn the screen on" + }, + {notification: "MIRROR"} // old hotword + ] + }, + { + file: "resources/u-models/snowboy.umdl", + sensitivity: 0.5, + confirm: "resources/u-hmm-2.wav", + commands: [ // turn the screen on before calling Google Assistant + { + notification: "COMMAND", + parameter: "turn the screen on" + }, + {notification: "ASSISTANT"}, + ] + }, + { + file: "resources/u-models/alexa.umdl", + sensitivity: 0.5, + commands: [ + { + notification: "PLAY", + parameter: "resources/snowboy.wav" + }, + { + notification: "COMMAND", + parameter: "hide all modules" // any spoken command can be sent as text (see translation/??.json for commands) + }, + { + notification: "COMMAND", + parameter: "show module time" // even module name mapping works (see modulemap below) + }, + { + notification: "COMMAND", + parameter: "turn the screen on" + }, + { + notification: "SHOW_ALERT", // any notification can be sent to other modules + parameter: {type: "notification", title: "Important message!", message: "we are awake"} + } + ], } ] }, + screen: { + on: "vcgencmd display_power 1", + off: "vcgencmd display_power 0", + timeoff: 120 // seconds before screen is turned off + }, + modulemap: [ // this table matches spoken module names to their real name + ["time", "clock"], + ["assistant", "MMM-Assistant"], + ["overview", ["clock", "currentweather"]] + ], record: { threshold: 0, // Default. No need to change. - verbose: false, // Deafult: true -- for checking recording status. + verbose: false, // Default: true -- for checking recording status. recordProgram: 'rec', // You can use 'arecord', 'sox', but we recommend 'rec' silence: 2.0 // Default. No need to change. }, @@ -49,6 +113,7 @@ }, }, speak: { + useGoogle: true, // [true] Enable Google TTS useAlert: true, // [true] Enable this to show the understood text of your speech language: 'en-US', // [en-US] To set the default GA speech reply language. }, diff --git a/node_helper.js b/node_helper.js index f34a56a..0b9c061 100644 --- a/node_helper.js +++ b/node_helper.js @@ -19,6 +19,7 @@ const Sound = require('node-aplay') // Deprecated const path = require('path') const fs = require('fs') const record = require('node-record-lpcm16') +const textToSpeech = require('@google-cloud/text-to-speech') const Detector = require('snowboy').Detector const Models = require('snowboy').Models const Speaker = require('speaker') @@ -95,6 +96,23 @@ module.exports = NodeHelper.create({ if(this.pause.size == 0) this.activateSpeak(payload.text, payload.option, payload.originalCommand) } break + case 'NOTIFY': + if (payload.notification !== "HOTWORD_STANDBY") { + if (payload.notification == "PLAY") { + new Sound(path.resolve(__dirname, payload.parameter)).play(); + } else { + this.sendSocketNotification("NOTIFY", payload) + } + } else { + this.status = 'HOTWORD_DETECTED' + this.sendSocketNotification("HOTWORD_STANDBY") + } + break + case 'EXECUTE': + execute(payload, function(callback) { + console.log("[EXECUTE] ", callback) + }) + break case 'REBOOT': execute('sudo reboot now', function(callback) { console.log(callback) @@ -108,11 +126,14 @@ module.exports = NodeHelper.create({ case 'TEST': this.test(payload) break + case 'LOG': + this.consoleLog(payload) + break } }, test: function(test) { - this.sendSocketNotification('COMMAND', test) + console.log("TESTING") }, activateSpeak: function(text, commandOption={}, originalCommand = "") { @@ -120,8 +141,13 @@ module.exports = NodeHelper.create({ option.language = (typeof commandOption.language !== 'undefined') ? commandOption.language : this.config.speak.language option.useAlert = (typeof commandOption.useAlert !== 'undefined') ? commandOption.useAlert : this.config.speak.useAlert option.originalCommand = (originalCommand) ? originalCommand : "" + option.auth = this.config.stt.auth[0] + option.useGoogle = this.config.speak.useGoogle + // Use the small footprint Text-to-Speech (TTS): pico2wave - var commandTmpl = 'pico2wave -l "{{lang}}" -w {{file}} "{{text}}" && aplay {{file}}' + var commandTmpl = 'pico2wave -l "{{lang}}" -w {{file}} "{{text}}" && aplay {{file}}' + var commandGetSpeech = 'pico2wave -l "{{lang}}" -w {{file}} "{{text}}"' + var commandSpeak = 'aplay {{file}}' function getTmpFile() { var random = Math.random().toString(36).slice(2), @@ -135,12 +161,42 @@ module.exports = NodeHelper.create({ text = text.replace(/\"/g, "'") text = text.trim() - var file = getTmpFile(), + var file = getTmpFile() + var command = "" + + if (option.useGoogle) { + let client = new textToSpeech.TextToSpeechClient(option.auth) + + const request = { + input: {text: text}, + voice: {languageCode: lang, ssmlGender: 'NEUTRAL'}, + audioConfig: {audioEncoding: 'LINEAR16'}, + } + + client.synthesizeSpeech(request, (err, response) => { + if (err) { + console.error('ERROR:', err); + return; + } + fs.writeFile(file, response.audioContent, 'binary', err => { + if (err) { + console.error('ERROR:', err); + return; + } + command = commandSpeak.replace(/\{\{file\}\}/g, file) + exec(command, function(err) { + cb && cb(err) + fs.unlink(file, ()=>{}) + }) + }) + }) + } else { command = commandTmpl.replace('{{lang}}', lang).replace('{{text}}', text).replace(/\{\{file\}\}/g, file) exec(command, function(err) { cb && cb(err) fs.unlink(file, ()=>{}) - }) + }) + } } this.sendSocketNotification('MODE', {mode:'SPEAK_STARTED', useAlert:option.useAlert, originalCommand:option.originalCommand, text:text}) @@ -153,14 +209,19 @@ module.exports = NodeHelper.create({ } } else { console.log("[ASSTNT] Speak Error", err) + this.sendSocketNotification('MODE', {mode:'SPEAK_ENDED', useAlert:option.useAlert}) } }) }, + consoleLog: function(payload) { + // helper for logging via notification + // USAGE: sendSocketNotification("LOG", {title: "", message: ""}) + console.log(payload.title, payload.message) + }, activateHotword: function() { console.log('[ASSTNT] Snowboy Activated') - this.sendSocketNotification('MODE', {mode:'HOTWORD_STARTED'}) new Sound(path.resolve(__dirname, 'resources/ding.wav')).play(); @@ -201,8 +262,9 @@ module.exports = NodeHelper.create({ detector.on('hotword', (index, hotword, buffer)=>{ record.stop() - new Sound(path.resolve(__dirname, 'resources/dong.wav')).play() - this.sendSocketNotification('HOTWORD_DETECTED', hotword) + var confirm = (typeof this.config.snowboy.models[index-1].confirm !== 'undefined') ? this.config.snowboy.models[index-1].confirm : 'resources/dong.wav' + new Sound(path.resolve(__dirname, confirm)).play() + this.sendSocketNotification('HOTWORD_DETECTED', {hotword:hotword, index:index}) this.sendSocketNotification('MODE', {mode:'HOTWORD_DETECTED'}) if (this.pause.size > 0) this.sendSocketNotification('PAUSED') return @@ -214,6 +276,7 @@ module.exports = NodeHelper.create({ activateAssistant: function(mode = 'ASSISTANT') { console.log('[ASSTNT] GA Activated') + var endOfSpeech = false var gRQC = this.googleRequestCounter // Added by E3V3A var transcription = "" this.sendSocketNotification('MODE', {mode:'ASSISTANT_STARTED'}) @@ -225,6 +288,7 @@ module.exports = NodeHelper.create({ let spokenResponseLength = 0; let speakerOpenTime = 0; let speakerTimer; + let openMicAgain = false; // This is based on: // ./node_modules/google-assistant/examples/mic-speaker.js @@ -232,27 +296,28 @@ module.exports = NodeHelper.create({ conversation // send the audio buffer to the speaker .on('audio-data', (data) => { - //record.stop() - const now = new Date().getTime() - if (mode == 'ASSISTANT') { - this.sendSocketNotification('MODE', {mode:'ASSISTANT_SPEAKING'}) - speaker.write(data); - spokenResponseLength += data.length; - const audioTime = spokenResponseLength / (this.config.assistant.conversation.audio.sampleRateOut * 16 / 8) * 1000; - clearTimeout(speakerTimer); - speakerTimer = setTimeout(() => { speaker.end(); }, audioTime - Math.max(0, now - speakerOpenTime)); - } else { - //record.stop() - speaker.end() - } + const now = new Date().getTime() + + speaker.write(data) + + // kill the speaker after enough data has been sent to it and then let it flush out + spokenResponseLength += data.length + const audioTime = spokenResponseLength / (24000 * 16 / 8) * 1000 + clearTimeout(speakerTimer) + speakerTimer = setTimeout(() => { + if (endOfSpeech) { // if spech.end was already called we notify here + this.sendSocketNotification('ASSISTANT_FINISHED', mode) + } + endOfSpeech = true + speaker.end() + }, (audioTime - Math.max(0, now - speakerOpenTime)) + 500) }) // done speaking, close the mic .on('end-of-utterance', () => { record.stop() }) // show each word on console as they are understood, while we say it .on('transcription', (text) => { - this.sendSocketNotification('ASSISTANT_TRANSCRIPTION', text) - transcription = text - //console.log("[VOX] GA Transcription: ", transcription) // show entire JS object + endOfSpeech = false +// console.log("[VOX] GA Transcription: ", text) // show entire JS object //--------------------------------------------------------------- // For account/billing purposes: // We check if the transcription is complete and update the request @@ -265,38 +330,32 @@ module.exports = NodeHelper.create({ console.log("[VOX] GA RQC: ", gRQC) } //--------------------------------------------------------------- - //record.stop() - if (mode == 'COMMAND') { - console.log("[ASSTNT] Command understood as: ", transcription) - this.sendSocketNotification('COMMAND', transcription) - } }) // what the assistant answered - .on('response', text => console.log('[VOX] GA Response: ', text)) + .on('response', text => { + // another special case. Uttering "nevermind" produces no response + if (text == "") { + this.sendSocketNotification('ASSISTANT_FINISHED', mode) + } + }) // if we've requested a volume level change, get the percentage of the new level .on('volume-percent', percent => console.log('[VOX] Set Volume [%]: ', percent)) // the device needs to complete an action - //.on('device-action', action => console.log('[VOX] Action:', action)) + .on('device-action', action => console.log('[VOX] Action:', action)) // once the conversation is ended, see if we need to follow up .on('ended', (error, continueConversation) => { - if (this.pause.size > 0) { - record.stop() - speaker.end() - this.sendSocketNotification('PAUSED') - return - } if (error) { + endOfSpeech = true console.log('[ASSTNT] Conversation Ended Error:', error) this.sendSocketNotification('ERROR', 'CONVERSATION ENDED') } else if (continueConversation) { - if (mode == 'ASSISTANT') { - assistant.start() - } else { - //@.@ What? There is no stop-conversation in gRpc ????? - } - } else { - record.stop() + console.log("[ASSTNT - continue conversation]") + openMicAgain = true + endOfSpeech = false + } else if (endOfSpeech) { this.sendSocketNotification('ASSISTANT_FINISHED', mode) + } else { // weird case where speech.end is called but the spech is still heard + endOfSpeech = true } }) .on('error', (error) => { @@ -304,7 +363,7 @@ module.exports = NodeHelper.create({ record.stop() speaker.end() // Added by E3V3A: fix attempt for issue #25 --> Need to check for: "Error: Service unavailable" this.sendSocketNotification('ERROR', 'CONVERSATION') - return // added by E3V3A: Do we also need a return? +// return // added by E3V3A: Do we also need a return? }) // pass the mic audio to the assistant @@ -317,8 +376,12 @@ module.exports = NodeHelper.create({ sampleRate: this.config.assistant.conversation.audio.sampleRateOut, }); speaker - .on('open', () => { speakerOpenTime = new Date().getTime(); }) - .on('close', () => { conversation.end(); }); + .on('open', () => { + clearTimeout(speakerTimer); + spokenResponseLength = 0; + speakerOpenTime = new Date().getTime(); + }) + .on('close', () => { if (openMicAgain) assistant.start(this.config.assistant.conversation); }); }; // Setup the assistant diff --git a/package.json b/package.json index fd43e5c..e71bf96 100644 --- a/package.json +++ b/package.json @@ -24,13 +24,13 @@ "url": "https://github.com/eouia/MMM-Assistant/issues" }, "dependencies": { - "electron-rebuild": "^1.7.3", - "@google-cloud/speech": "^1.3.0", + "@google-cloud/speech": "^1.4.0", + "@google-cloud/text-to-speech": "^0.1.0", "google-assistant": "^0.2.2", "node-aplay": "^1.0.3", "node-record-lpcm16": "^0.3.0", "snowboy": "^1.2.0", - "speaker": "^0.4.0" + "speaker": "^0.4.1" }, "devDependencies": { "electron-rebuild": "^1.7.3", diff --git a/resources/hello.pmdl b/resources/hello.pmdl new file mode 100644 index 0000000..93b1973 Binary files /dev/null and b/resources/hello.pmdl differ diff --git a/translations/en.json b/translations/en.json index 43cbe25..30ce487 100644 --- a/translations/en.json +++ b/translations/en.json @@ -16,14 +16,29 @@ "CMD_HIDE_ALL_MODULES" : "hide all modules", "CMD_HIDE_ALL_MODULES_DESCRIPTION" : "This command will hide all your available modules.", "CMD_HIDE_ALL_MODULES_RESULT" : "All modules will be hidden.", + "CMD_HIDE_ALL_MODULES_EXCEPT" : "show :module", + "CMD_HIDE_ALL_MODULES_EXCEPT_DESCRIPTION" : "This command will hide all modules except the selected module(s)", + "CMD_HIDE_ALL_MODULES_EXCEPT_RESULT" : "All modules will be hidden except the selected ones", + "CMD_HIDE_MODULE" : "hide module :module", + "CMD_HIDE_MODULE_DESCRIPTION" : "This command will hide the specified module.", + "CMD_HIDE_MODULE_RESULT" : "The module will be hidden.", "CMD_SHOW_ALL_MODULES" : "show all modules", "CMD_SHOW_ALL_MODULES_DESCRIPTION" : "This command will show all your available modules", "CMD_SHOW_ALL_MODULES_RESULT" : "All modules will be shown.", + "CMD_SHOW_MODULE" : "show module :module", + "CMD_SHOW_MODULE_DESCRIPTION" : "This command will show the specified module", + "CMD_SHOW_MODULE_RESULT" : "The module will be shown.", "CMD_SAY" : "say :something", "CMD_SAY_DESCRIPTION" : "This command will say anything you want.", "CMD_REBOOT" : "reboot", "CMD_REBOOT_DESCRIPTION" : "This command will reboot the magic mirror instantly.", "CMD_SHUTDOWN" : "shut down", "CMD_SHUTDOWN_DESCRIPTION" : "This command will shutdown the magic mirror instantly.", + "CMD_WAKE_UP" : "turn the screen on", + "CMD_WAKE_UP_DESCRIPTION" : "Turn the screen on", + "CMD_STAY_AWAKE" : "stay awake", + "CMD_STAY_AWAKE_DESCRIPTION" : "Do not turn the screen off", + "CMD_GOTO_SLEEP" : "turn the screen off", + "CMD_GOTO_SLEEP_DESCRIPTION" : "Turn the screen off", "TELBOT_CMD_GA_DESCRIPTION" : "Send text to Google Assistant." } diff --git a/translations/nl.json b/translations/nl.json new file mode 100644 index 0000000..6bd91da --- /dev/null +++ b/translations/nl.json @@ -0,0 +1,44 @@ +{ + "UNSPEAKABLE" : "Sorry, dit kan ik niet uitspreken", + "INVALID_FORMAT" : "Sorry, de vraag antwoord dialoog wordt niet ondersteund in commando modus.", + "INVALID_COMMAND" : "Sorry, ik begrijp niet wat je zegt. Probeer het nog eens", + "ALIAS" : "Dit is een andere benaming voor {command}", + "CMD_HELP" : "help :command", + "CMD_HELP_COMMAND_PROVIDER" : "Deze opdracht wordt u aangeboden door {module}", + "CMD_HELP_DESCRIPTION" : "Dit commando kan een omschrijving geven van een opgegeven commando", + "CMD_HELP_COMMAND_EXAMPLE" : "Zeg help gevolgd door een commando. Zoals: 'help toon beschikbare modules'.", + "CMD_LIST_COMMANDS" : "toon beschikbare commando's", + "CMD_LIST_COMMANDS_DESCRIPTION" : "Dit commando toont alle beschikbare commando's", + "CMD_LIST_COMMANDS_RESULT" : "Dit zijn alle beschikbare commando's", + "CMD_LIST_MODULES" : "toon beschikbare modules", + "CMD_LIST_MODULES_DESCRIPTION" : "Dit commando toont alle beschikbare modules", + "CMD_LIST_MODULES_RESULT" : "Dit zijn alle beschikbare moduels", + "CMD_HIDE_ALL_MODULES" : "verberg alle modules", + "CMD_HIDE_ALL_MODULES_DESCRIPTION" : "Dit commando verbergt alle modules", + "CMD_HIDE_ALL_MODULES_RESULT" : "Alle modules zijn nu verborgen", + "CMD_HIDE_ALL_MODULES_EXCEPT" : "thema :module", + "CMD_HIDE_ALL_MODULES_EXCEPT_DESCRIPTION" : "Dit commando verbergt alle modules behalve de opgegeven module(s)", + "CMD_HIDE_ALL_MODULES_EXCEPT_RESULT" : "Alle modules zijn nu verborgen behalve de opgegeven module(s)", + "CMD_HIDE_MODULE" : "verberg :module", + "CMD_HIDE_MODULE_DESCRIPTION" : "Dit commando verbergt de opgegeven module", + "CMD_HIDE_MODULE_RESULT" : "De opgegeven module is nu verborgen", + "CMD_SHOW_ALL_MODULES" : "toon alle modules", + "CMD_SHOW_ALL_MODULES_DESCRIPTION" : "This command will show all your available modules", + "CMD_SHOW_ALL_MODULES_RESULT" : "Alle modules worden zichtbaar", + "CMD_SHOW_MODULE" : "toon :module", + "CMD_SHOW_MODULE_DESCRIPTION" : "Dit commando maakt de opgegeven module zichtbaar", + "CMD_SHOW_MODULE_RESULT" : "De opgegeven module is nu zichtbaar", + "CMD_SAY" : "zeg :something", + "CMD_SAY_DESCRIPTION" : "Dit commando zegt wat je maar wil", + "CMD_REBOOT" : "herstart", + "CMD_REBOOT_DESCRIPTION" : "Met dit command start de spiegel nieuw op", + "CMD_SHUTDOWN" : "sluit af", + "CMD_SHUTDOWN_DESCRIPTION" : "Dit commando zet de spiegel uit", + "CMD_STAY_AWAKE" : "blijf wakker", + "CMD_STAY_AWAKE_DESCRIPTION" : "Laat het scherm aanstaan", + "CMD_WAKE_UP" : "wakker worden", + "CMD_WAKE_UP_DESCRIPTION" : "Zet het scherm aan", + "CMD_GOTO_SLEEP" : "ga slapen", + "CMD_GOTO_SLEEP_DESCRIPTION" : "Zet het scherm uit", + "TELBOT_CMD_GA_DESCRIPTION" : "Send text to Google Assistant." +}