From bb89d6996b2ae24d5c528abda3b014853568e64a Mon Sep 17 00:00:00 2001 From: fewieden Date: Wed, 22 Mar 2017 13:40:28 +0100 Subject: [PATCH 1/4] add linters --- .codeclimate.yml | 27 +++++ .editorconfig | 13 +++ .eslintignore | 1 + .eslintrc | 18 ++++ .mdlrc | 2 + .stylelintrc | 6 ++ .travis.yml | 11 ++ DEVELOPER.md | 35 ++++--- MMM-voice.css | 15 ++- MMM-voice.js | 164 ++++++++++++++--------------- README.md | 70 +++++++------ installers/dependencies.sh | 6 +- node_helper.js | 205 +++++++++++++++++++------------------ package.json | 12 ++- 14 files changed, 353 insertions(+), 232 deletions(-) create mode 100644 .codeclimate.yml create mode 100644 .editorconfig create mode 100644 .eslintignore create mode 100644 .eslintrc create mode 100644 .mdlrc create mode 100644 .stylelintrc create mode 100644 .travis.yml diff --git a/.codeclimate.yml b/.codeclimate.yml new file mode 100644 index 0000000..db42018 --- /dev/null +++ b/.codeclimate.yml @@ -0,0 +1,27 @@ +engines: + stylelint: + enabled: true + duplication: + enabled: true + config: + languages: + - javascript + eslint: + enabled: true + channel: "eslint-3" + checks: + import/no-unresolved: + enabled: false + fixme: + enabled: true + markdownlint: + enabled: true +ratings: + paths: + - "**.js" + - "**.css" + - "**.md" +exclude_paths: [ + "node_modules/**/*", + "Bytes.js" +] diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..15d51b0 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,13 @@ +root = true + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +insert_final_newline = true +charset = utf-8 +indent_style = space +indent_size = 4 + +[{*.json, *.yml}] +indent_style = space +indent_size = 2 diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..35f6bcc --- /dev/null +++ b/.eslintignore @@ -0,0 +1 @@ +Bytes.js diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..532082c --- /dev/null +++ b/.eslintrc @@ -0,0 +1,18 @@ +{ + "extends": "airbnb-base", + "rules": { + "comma-dangle": 0, + "indent": [2, 4], + "max-len": [2, 120, { "ignoreStrings": true }], + "radix": [2, "as-needed"], + "no-console": 0 + }, + "settings": { + "import/core-modules": [ "node_helper" ] + }, + "env": { + "browser": true, + "node": true, + "es6": true + } +} diff --git a/.mdlrc b/.mdlrc new file mode 100644 index 0000000..54d0111 --- /dev/null +++ b/.mdlrc @@ -0,0 +1,2 @@ +all +rules "~MD013", "~MD026", "~MD033" diff --git a/.stylelintrc b/.stylelintrc new file mode 100644 index 0000000..6449c3f --- /dev/null +++ b/.stylelintrc @@ -0,0 +1,6 @@ +{ + "extends": "stylelint-config-standard", + "rules": { + "indentation": 4 + } +} diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..b89f2aa --- /dev/null +++ b/.travis.yml @@ -0,0 +1,11 @@ +language: node_js +node_js: + - "stable" + - "7" + - "6" + - "5" +script: + - npm run lint +cache: + directories: + - node_modules diff --git a/DEVELOPER.md b/DEVELOPER.md index 6a47bc9..bdec5d5 100644 --- a/DEVELOPER.md +++ b/DEVELOPER.md @@ -3,18 +3,23 @@ This document describes the way to support your own MagicMirror² module with voice control. ## Mode + Use an unique mode, which is not already taken from one of the other modules in this [list](https://github.com/fewieden/MMM-voice/wiki/Supported-Modules). ## COMMANDS + Try to avoid short words like `ON`, `TO`, etc. as far as possible ## Register your commands + As soon as you receive the notification `ALL_MODULES_STARTED` from the core system, register your voice commands by sending the following notification - * notification: `REGISTER_VOICE_MODULE` - * payload: Object with `mode` (string) and `sentence` (array) properties + +* notification: `REGISTER_VOICE_MODULE` +* payload: Object with `mode` (string) and `sentence` (array) properties ### Example -````javascript + +```javascript notificationReceived: function (notification, payload, sender) { if(notification === "ALL_MODULES_STARTED"){ this.sendNotification("REGISTER_VOICE_MODULE", { @@ -28,15 +33,18 @@ notificationReceived: function (notification, payload, sender) { }); } } -```` +``` ## Handle recognized data + When the user is in the mode of your module, you will receive the following notification - * notification: `VOICE_YOURMODE` - * payload: String with all detected words. + +* notification: `VOICE_YOURMODE` +* payload: String with all detected words. ### Example -````javascript + +```javascript notificationReceived: function (notification, payload, sender) { ... if(notification === "VOICE_FOOTBALL" && sender.name === "MMM-voice"){ @@ -51,21 +59,24 @@ checkCommands: function(data){ } ... } -```` +``` ## React on mode change + When the mode of MMM-voice gets changed it will send a broadcast `VOICE_MODE_CHANGED` - * notification: `VOICE_MODE_CHANGED` - * payload: Object with `old` (string) and `new` (string) mode as properties + +* notification: `VOICE_MODE_CHANGED` +* payload: Object with `old` (string) and `new` (string) mode as properties This gets handy e.g. to revert your manipulations on the DOM. ### Example -````javascript + +```javascript notificationReceived: function (notification, payload, sender) { ... if(notification === "VOICE_MODE_CHANGED" && sender.name === "MMM-voice" && payload.old === "FOOTBALL"){ // do your magic } } -```` \ No newline at end of file +``` diff --git a/MMM-voice.css b/MMM-voice.css index 723f088..18fc5a2 100644 --- a/MMM-voice.css +++ b/MMM-voice.css @@ -1,11 +1,12 @@ @-webkit-keyframes MMM-voice-pulse { - 0% {color: #999999;} - 50% {color: #000000;} - 100% {color: #999999;} + 0% { color: #999; } + 50% { color: #000; } + 100% { color: #999; } } .MMM-voice .pulse { -webkit-animation: MMM-voice-pulse 1s infinite; + animation: MMM-voice-pulse 1s infinite; } .MMM-voice .icon { @@ -14,6 +15,7 @@ .MMM-voice-blur { -webkit-filter: blur(2px) brightness(50%); + filter: blur(2px) brightness(50%); } .MMM-voice .modal { @@ -22,8 +24,11 @@ left: 50%; top: 50%; -webkit-transform: translate(-50%, -50%); + -moz-transform: translate(-50%, -50%); + -o-transform: translate(-50%, -50%); + transform: translate(-50%, -50%); } .MMM-voice .modal ul { - margin: 0px; -} \ No newline at end of file + margin: 0; +} diff --git a/MMM-voice.js b/MMM-voice.js index 2594461..0db5587 100644 --- a/MMM-voice.js +++ b/MMM-voice.js @@ -5,21 +5,23 @@ * MIT Licensed. */ -Module.register("MMM-voice",{ +/* global Module Log MM */ - icon: "fa-microphone-slash", +Module.register('MMM-voice', { + + icon: 'fa-microphone-slash', pulsing: true, help: false, voice: { - mode: "VOICE", + mode: 'VOICE', sentences: [ - "HIDE MODULES", - "SHOW MODULES", - "WAKE UP", - "GO TO SLEEP", - "OPEN HELP", - "CLOSE HELP" + 'HIDE MODULES', + 'SHOW MODULES', + 'WAKE UP', + 'GO TO SLEEP', + 'OPEN HELP', + 'CLOSE HELP' ] }, @@ -27,63 +29,65 @@ Module.register("MMM-voice",{ defaults: { timeout: 15, - keyword: "MAGIC MIRROR", + keyword: 'MAGIC MIRROR', debug: false }, - start: function(){ - this.mode = this.translate("INIT"); + start() { + Log.info(`Starting module: ${this.name}`); + this.mode = this.translate('INIT'); this.modules.push(this.voice); - Log.log(this.name + " is started!"); - Log.info(this.name + " is waiting for voice modules"); + Log.info(`${this.name} is waiting for voice command registrations.`); }, - getStyles: function() { - return ["font-awesome.css", "MMM-voice.css"]; + getStyles() { + return ['font-awesome.css', 'MMM-voice.css']; }, - getTranslations: function() { + getTranslations() { return { - en: "translations/en.json", - de: "translations/de.json", - id: "translations/id.json" + en: 'translations/en.json', + de: 'translations/de.json', + id: 'translations/id.json' }; }, - getDom: function() { - var wrapper = document.createElement("div"); - var voice = document.createElement("div"); - voice.classList.add("small", "align-left"); - var i = document.createElement("i"); - i.classList.add("fa", this.icon, "icon"); - if(this.pulsing){ - i.classList.add("pulse"); + getDom() { + const wrapper = document.createElement('div'); + const voice = document.createElement('div'); + voice.classList.add('small', 'align-left'); + + const icon = document.createElement('i'); + icon.classList.add('fa', this.icon, 'icon'); + if (this.pulsing) { + icon.classList.add('pulse'); } - voice.appendChild(i); - var modeSpan = document.createElement("span"); + voice.appendChild(icon); + + const modeSpan = document.createElement('span'); modeSpan.innerHTML = this.mode; voice.appendChild(modeSpan); - if(this.config.debug){ - var debug = document.createElement("div"); + if (this.config.debug) { + const debug = document.createElement('div'); debug.innerHTML = this.debugInformation; voice.appendChild(debug); } - var modules = document.querySelectorAll(".module"); - for (var i = 0; i < modules.length; i++) { - if(!modules[i].classList.contains(this.name)){ - if(this.help){ - modules[i].classList.add(this.name + "-blur"); + const modules = document.querySelectorAll('.module'); + for (let i = 0; i < modules.length; i += 1) { + if (!modules[i].classList.contains(this.name)) { + if (this.help) { + modules[i].classList.add(`${this.name}-blur`); } else { - modules[i].classList.remove(this.name + "-blur"); + modules[i].classList.remove(`${this.name}-blur`); } } } - if(this.help){ - voice.classList.add(this.name + "-blur"); - var modal = document.createElement("div"); - modal.classList.add("modal"); + if (this.help) { + voice.classList.add(`${this.name}-blur`); + const modal = document.createElement('div'); + modal.classList.add('modal'); this.appendHelp(modal); wrapper.appendChild(modal); } @@ -93,81 +97,81 @@ Module.register("MMM-voice",{ return wrapper; }, - notificationReceived: function(notification, payload, sender){ - if(notification === "DOM_OBJECTS_CREATED"){ - this.sendSocketNotification("START", {config: this.config, modules: this.modules}); - } else if(notification === "REGISTER_VOICE_MODULE"){ - if(payload.hasOwnProperty("mode") && payload.hasOwnProperty("sentences")){ + notificationReceived(notification, payload) { + if (notification === 'DOM_OBJECTS_CREATED') { + this.sendSocketNotification('START', { config: this.config, modules: this.modules }); + } else if (notification === 'REGISTER_VOICE_MODULE') { + if (Object.prototype.hasOwnProperty.call(payload, 'mode') && Object.prototype.hasOwnProperty.call(payload, 'sentences')) { this.modules.push(payload); } } }, - socketNotificationReceived: function(notification, payload){ - if(notification === "READY"){ - this.icon = "fa-microphone"; - this.mode = this.translate("NO_MODE"); + socketNotificationReceived(notification, payload) { + if (notification === 'READY') { + this.icon = 'fa-microphone'; + this.mode = this.translate('NO_MODE'); this.pulsing = false; - } else if(notification === "LISTENING"){ + } else if (notification === 'LISTENING') { this.pulsing = true; - } else if(notification === "SLEEPING"){ + } else if (notification === 'SLEEPING') { this.pulsing = false; - } else if(notification === "ERROR"){ + } else if (notification === 'ERROR') { this.mode = notification; - } else if(notification === "VOICE"){ - for(var i = 0; i < this.modules.length; i++){ - if(payload.mode === this.modules[i].mode){ - if(this.mode !== payload.mode) { + } else if (notification === 'VOICE') { + for (let i = 0; i < this.modules.length; i += 1) { + if (payload.mode === this.modules[i].mode) { + if (this.mode !== payload.mode) { this.help = false; - this.sendNotification(notification + '_MODE_CHANGED', {old: this.mode, new: payload.mode}); + this.sendNotification(`${notification}_MODE_CHANGED`, { old: this.mode, new: payload.mode }); this.mode = payload.mode; } - if(this.mode !== "VOICE"){ - this.sendNotification(notification + '_' + payload.mode, payload.sentence); + if (this.mode !== 'VOICE') { + this.sendNotification(`${notification}_${payload.mode}`, payload.sentence); } break; } } - } else if(notification === "BYTES"){ - this.sendNotification("MMM-TTS", payload); - } else if(notification === "HIDE"){ + } else if (notification === 'BYTES') { + this.sendNotification('MMM-TTS', payload); + } else if (notification === 'HIDE') { MM.getModules().enumerate((module) => { module.hide(1000); }); - } else if(notification === "SHOW"){ + } else if (notification === 'SHOW') { MM.getModules().enumerate((module) => { module.show(1000); }); - } else if(notification === "OPEN_HELP"){ + } else if (notification === 'OPEN_HELP') { this.help = true; - } else if(notification === "CLOSE_HELP"){ + } else if (notification === 'CLOSE_HELP') { this.help = false; - } else if(notification === "DEBUG"){ + } else if (notification === 'DEBUG') { this.debugInformation = payload; } this.updateDom(300); }, - appendHelp: function(appendTo){ - var title = document.createElement("h1"); - title.classList.add("medium"); - title.innerHTML = this.name + " - " + this.translate("COMMAND_LIST"); + appendHelp(appendTo) { + const title = document.createElement('h1'); + title.classList.add('medium'); + title.innerHTML = `${this.name} - ${this.translate('COMMAND_LIST')}`; appendTo.appendChild(title); - var mode = document.createElement("div"); - mode.innerHTML = this.translate("MODE") + ": " + this.voice.mode; + const mode = document.createElement('div'); + mode.innerHTML = `${this.translate('MODE')}: ${this.voice.mode}`; appendTo.appendChild(mode); - var listLabel = document.createElement("div"); - listLabel.innerHTML = this.translate("VOICE_COMMANDS") + ":"; + const listLabel = document.createElement('div'); + listLabel.innerHTML = `${this.translate('VOICE_COMMANDS')}:`; appendTo.appendChild(listLabel); - var list = document.createElement("ul"); - for(var i = 0; i < this.voice.sentences.length; i++){ - var item = document.createElement("li"); + const list = document.createElement('ul'); + for (let i = 0; i < this.voice.sentences.length; i += 1) { + const item = document.createElement('li'); item.innerHTML = this.voice.sentences[i]; list.appendChild(item); } appendTo.appendChild(list); } -}); \ No newline at end of file +}); diff --git a/README.md b/README.md index af6e2ff..293393b 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ -# MMM-voice +# MMM-voice [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](https://raw.githubusercontent.com/fewieden/MMM-voice/master/LICENSE) [![Build Status](https://travis-ci.org/fewieden/MMM-voice.svg?branch=master)](https://travis-ci.org/fewieden/MMM-voice) [![Code Climate](https://codeclimate.com/github/fewieden/MMM-voice/badges/gpa.svg?style=flat)](https://codeclimate.com/github/fewieden/MMM-voice) [![Known Vulnerabilities](https://snyk.io/test/github/fewieden/mmm-voice/badge.svg)](https://snyk.io/test/github/fewieden/mmm-voice) + Voice Recognition Module for MagicMirror2 ## Information + This voice recognition works offline. To protect your privacy, no one will record what's going on in your room all day long. So keep in mind that there is no huge server farm, that handles your voice commands. The raspberry is just a small device and this is a cpu intensive task. Also the dictionairy has only the words specified by the modules, so there is a chance for false positives. @@ -9,32 +11,35 @@ Also the dictionairy has only the words specified by the modules, so there is a If you can live with latency, bugged detections and want to have data privacy, feel free to use this module. ## Dependencies - * An installation of [MagicMirror2](https://github.com/MichMich/MagicMirror) - * Packages: bison libasound2-dev autoconf automake libtool python-dev swig python-pip - * [SphinxBase](https://github.com/cmusphinx/sphinxbase) - * [PocketSphinx](https://github.com/cmusphinx/pocketsphinx) - * A microphone - * npm - * [PocketSphinx-continuous](https://www.npmjs.com/package/pocketsphinx-continuous) - * [lmtool](https://www.npmjs.com/package/lmtool) + +* An installation of [MagicMirror2](https://github.com/MichMich/MagicMirror) +* Packages: bison libasound2-dev autoconf automake libtool python-dev swig python-pip +* [SphinxBase](https://github.com/cmusphinx/sphinxbase) +* [PocketSphinx](https://github.com/cmusphinx/pocketsphinx) +* A microphone +* npm +* [PocketSphinx-continuous](https://www.npmjs.com/package/pocketsphinx-continuous) +* [lmtool](https://www.npmjs.com/package/lmtool) ## Installation - 1. Clone this repo into `~/MagicMirror/modules` directory. - 2. Run command `bash dependencies.sh` in `~/MagicMirror/modules/MMM-voice/installers` directory, to install all dependencies. This will need a couple of minutes. - 3. Configure your `~/MagicMirror/config/config.js`: - - ``` - { - module: 'MMM-voice', - position: 'bottom_bar', - config: { - microphone: 1, - ... - } - } - ``` + +1. Clone this repo into `~/MagicMirror/modules` directory. +1. Run command `bash dependencies.sh` in `~/MagicMirror/modules/MMM-voice/installers` directory, to install all dependencies. This will need a couple of minutes. +1. Configure your `~/MagicMirror/config/config.js`: + + ``` + { + module: 'MMM-voice', + position: 'bottom_bar', + config: { + microphone: 1, + ... + } + } + ``` ## Config Options + | **Option** | **Default** | **Description** | | --- | --- | --- | | `microphone` | REQUIRED | Id of microphone shown in the installer. | @@ -42,23 +47,28 @@ If you can live with latency, bugged detections and want to have data privacy, f | `timeout` | `15` | time the keyword should be active without saying something | ## Usage + You need to say your KEYWORD (Default: MAGIC MIRROR), when the KEYWORD is recognized the microphone will start to flash and as long as the microphone is flashing (timeout config option) the mirror will recognize COMMANDS or MODES (Keep in mind that the recognition will take a while, so when you say your COMMAND right before the microphone stops flashing the COMMAND will propably not recognized). Mode of this module: `VOICE` -COMMANDS: - * HIDE MODULES - * SHOW MODULES - * WAKE UP - * GO TO SLEEP - * OPEN HELP - * CLOSE HELP +COMMANDS: + +* HIDE MODULES +* SHOW MODULES +* WAKE UP +* GO TO SLEEP +* OPEN HELP +* CLOSE HELP ### Select Mode + To select a MODE, the specfic MODE has to be the first word of a COMMAND or right after the KEYWORD, when the microphone stopped flashing. ## Supported modules + List of all supported modules in the [Wiki](https://github.com/fewieden/MMM-voice/wiki/Supported-Modules). ## Developers Guide + If you want to support your own module, check out the [Documentation](https://github.com/fewieden/MMM-voice/blob/master/DEVELOPER.md) and add it to the [Wiki](https://github.com/fewieden/MMM-voice/wiki/Supported-Modules). diff --git a/installers/dependencies.sh b/installers/dependencies.sh index f24080d..883edf9 100644 --- a/installers/dependencies.sh +++ b/installers/dependencies.sh @@ -104,7 +104,7 @@ echo -e "\e[32m[STEP 4/6] Exporting paths | Done\e[0m" # installing npm dependencies echo -e "\e[96m[STEP 5/6] Installing npm dependencies\e[90m" cd ~/MagicMirror/modules/MMM-voice -if npm install ; +if npm install --productive; then echo -e "\e[32m[STEP 5/6] Installing npm dependencies | Done\e[0m" else @@ -132,5 +132,5 @@ fi # displaying audio devices -echo -e "\e[96m[INFO] Possible Audio Devices to set in config.js\n" -cat /proc/asound/cards \ No newline at end of file +echo -e "\e[96m[INFO] Possible Audio Devices to set in config.js\n\e[0m" +cat /proc/asound/cards diff --git a/node_helper.js b/node_helper.js index 363ffb0..6f8a53c 100644 --- a/node_helper.js +++ b/node_helper.js @@ -5,12 +5,14 @@ * MIT Licensed. */ -const Psc = require("pocketsphinx-continuous"); -const fs = require("fs"); -const exec = require("child_process").exec; -const lmtool = require("lmtool"); -const bytes = require("./Bytes.js"); -const NodeHelper = require("node_helper"); +/* eslint-env node */ + +const Psc = require('pocketsphinx-continuous'); +const fs = require('fs'); +const exec = require('child_process').exec; +const lmtool = require('lmtool'); +const bytes = require('./Bytes.js'); +const NodeHelper = require('node_helper'); module.exports = NodeHelper.create({ @@ -20,8 +22,8 @@ module.exports = NodeHelper.create({ help: false, words: [], - socketNotificationReceived: function(notification, payload){ - if(notification === "START"){ + socketNotificationReceived(notification, payload) { + if (notification === 'START') { this.config = payload.config; this.modules = payload.modules; @@ -30,22 +32,22 @@ module.exports = NodeHelper.create({ } }, - fillWords: function(){ - //create array - var array = this.config.keyword.split(" "); - var temp = bytes.q.split(" "); - for(var i = 0; i < temp.length; i++){ + fillWords() { + // create array + const array = this.config.keyword.split(' '); + const temp = bytes.q.split(' '); + for (let i = 0; i < temp.length; i += 1) { array.push(temp[i]); } - for(var i = 0; i < this.modules.length; i++){ - var mode = this.modules[i].mode.split(" "); - for(var m = 0; m < mode.length; m++){ + for (let i = 0; i < this.modules.length; i += 1) { + const mode = this.modules[i].mode.split(' '); + for (let m = 0; m < mode.length; m += 1) { array.push(mode[m]); } - for(var n = 0; n < this.modules[i].sentences.length; n++){ - var sentence = this.modules[i].sentences[n].split(" "); - for(var x = 0; x < sentence.length; x++){ - array.push(sentence[x]); + for (let n = 0; n < this.modules[i].sentences.length; n += 1) { + const sentences = this.modules[i].sentences[n].split(' '); + for (let x = 0; x < sentences.length; x += 1) { + array.push(sentences[x]); } } } @@ -53,11 +55,11 @@ module.exports = NodeHelper.create({ // sort array array.sort(); - //filter duplicates - var i = 0; - while(i < array.length) { - if(array[i] === array[i+1]) { - array.splice(i+1,1); + // filter duplicates + let i = 0; + while (i < array.length) { + if (array[i] === array[i + 1]) { + array.splice(i + 1, 1); } else { i += 1; } @@ -66,14 +68,14 @@ module.exports = NodeHelper.create({ this.words = array; }, - checkFiles: function(){ - console.log("MMM-voice: Checking files."); - fs.stat("modules/MMM-voice/words.json", (err, stats) => { - if(!err && stats.isFile()){ - fs.readFile("modules/MMM-voice/words.json", "utf8", (err, data) => { - if(!err){ - var words = JSON.parse(data).words; - if(this.arraysEqual(this.words, words)){ + checkFiles() { + console.log(`${this.name}: Checking files.`); + fs.stat('modules/MMM-voice/words.json', (error, stats) => { + if (!error && stats.isFile()) { + fs.readFile('modules/MMM-voice/words.json', 'utf8', (err, data) => { + if (!err) { + const words = JSON.parse(data).words; + if (this.arraysEqual(this.words, words)) { this.startPocketsphinx(); return; } @@ -86,16 +88,16 @@ module.exports = NodeHelper.create({ }); }, - arraysEqual: function(a, b){ - if(! (a instanceof Array) || ! (b instanceof Array)){ + arraysEqual(a, b) { + if (!(a instanceof Array) || !(b instanceof Array)) { return false; } - if(a.length !== b.length){ + if (a.length !== b.length) { return false; } - for (var i = 0; i < a.length; i++) { + for (let i = 0; i < a.length; i += 1) { if (a[i] !== b[i]) { return false; } @@ -104,129 +106,130 @@ module.exports = NodeHelper.create({ return true; }, - generateDicLM: function(){ - console.log("MMM-voice: Generating dictionairy and language model."); + generateDicLM() { + console.log(`${this.name}: Generating dictionairy and language model.`); - fs.writeFile("modules/MMM-voice/words.json", JSON.stringify({words: this.words}), (err) => { - if (err){ - console.log("MMM-voice: Couldn't save words.json!"); + fs.writeFile('modules/MMM-voice/words.json', JSON.stringify({ words: this.words }), (err) => { + if (err) { + console.log(`${this.name}: Couldn't save words.json!`); } else { - console.log("MMM-voice: Saved words.json successfully."); + console.log(`${this.name}: Saved words.json successfully.`); } }); lmtool(this.words, (err, filename) => { - if(err){ - this.sendSocketNotification("ERROR", "Couldn't create necessary files!"); + if (err) { + this.sendSocketNotification('ERROR', 'Couldn\'t create necessary files!'); } else { - fs.renameSync(filename + ".dic", "modules/MMM-voice/MMM-voice.dic"); - fs.renameSync(filename + ".lm", "modules/MMM-voice/MMM-voice.lm"); + fs.renameSync(`${filename}.dic`, 'modules/MMM-voice/MMM-voice.dic'); + fs.renameSync(`${filename}.lm`, 'modules/MMM-voice/MMM-voice.lm'); this.startPocketsphinx(); - fs.unlink(filename + ".log_pronounce"); - fs.unlink(filename + ".sent"); - fs.unlink(filename + ".vocab"); - fs.unlink("TAR" + filename + ".tgz"); + fs.unlink(`${filename}.log_pronounce`); + fs.unlink(`${filename}.sent`); + fs.unlink(`${filename}.vocab`); + fs.unlink(`TAR${filename}.tgz`); } }); }, - startPocketsphinx: function(){ - console.log("MMM-voice: Starting pocketsphinx."); + startPocketsphinx() { + console.log(`${this.name}: Starting pocketsphinx.`); this.time = this.config.timeout * 1000; this.ps = new Psc({ - setId: "MMM-voice", + setId: this.name, verbose: true, microphone: this.config.microphone }); - this.ps.on("data", (data) => { - if(typeof data === "string"){ - if(this.config.debug){ + this.ps.on('data', (data) => { + if (typeof data === 'string') { + if (this.config.debug) { console.log(data); - this.sendSocketNotification("DEBUG", data); + this.sendSocketNotification('DEBUG', data); } - if(data.indexOf(this.config.keyword) !== -1 || this.listening){ + if (data.includes(this.config.keyword) || this.listening) { this.listening = true; - this.sendSocketNotification("LISTENING"); - if(this.timer){ + this.sendSocketNotification('LISTENING'); + if (this.timer) { clearTimeout(this.timer); } this.timer = setTimeout(() => { this.listening = false; - this.sendSocketNotification("SLEEPING"); + this.sendSocketNotification('SLEEPING'); }, this.time); } else { return; } - data = this.cleanData(data); + let cleanData = this.cleanData(data); - for(var i = 0; i < this.modules.length; i++){ - var n = data.indexOf(this.modules[i].mode); - if(n === 0){ + for (let i = 0; i < this.modules.length; i += 1) { + const n = cleanData.indexOf(this.modules[i].mode); + if (n === 0) { this.mode = this.modules[i].mode; - data = data.substr(n + this.modules[i].mode.length).trim(); + cleanData = cleanData.substr(n + this.modules[i].mode.length).trim(); break; } } - if(this.mode){ - this.sendSocketNotification("VOICE", {mode: this.mode, sentence: data}); - if(this.mode === "VOICE"){ - this.checkCommands(data); + if (this.mode) { + this.sendSocketNotification('VOICE', { mode: this.mode, sentence: cleanData }); + if (this.mode === 'VOICE') { + this.checkCommands(cleanData); } } } }); - if(this.config.debug){ - this.ps.on("debug", (data) => { - fs.appendFile("modules/MMM-voice/debug.log", data); + if (this.config.debug) { + this.ps.on('debug', (data) => { + fs.appendFile('modules/MMM-voice/debug.log', data); }); } - this.ps.on("error", (error) => { - if(error){ - fs.appendFile("modules/MMM-voice/error.log", error); - this.sendSocketNotification("ERROR", error); + this.ps.on('error', (error) => { + if (error) { + fs.appendFile('modules/MMM-voice/error.log', error); + this.sendSocketNotification('ERROR', error); } }); - this.sendSocketNotification("READY"); + this.sendSocketNotification('READY'); }, - cleanData: function(data){ - var i = data.indexOf(this.config.keyword); + cleanData(data) { + let temp = data; + const i = temp.indexOf(this.config.keyword); if (i !== -1) { - data = data.substr(i + this.config.keyword.length); + temp = temp.substr(i + this.config.keyword.length); } - data = data.replace(/ +/g, " ").trim(); - return data; + temp = temp.replace(/ {2,}/g, ' ').trim(); + return temp; }, - checkCommands: function(data){ - if(bytes.r[0].test(data) && bytes.r[1].test(data)){ - this.sendSocketNotification("BYTES", bytes.a); - } else if(/(WAKE)/g.test(data) && /(UP)/g.test(data)){ - exec("/opt/vc/bin/tvservice -p && sudo chvt 6 && sudo chvt 7", null); + checkCommands(data) { + if (bytes.r[0].test(data) && bytes.r[1].test(data)) { + this.sendSocketNotification('BYTES', bytes.a); + } else if (/(WAKE)/g.test(data) && /(UP)/g.test(data)) { + exec('/opt/vc/bin/tvservice -p && sudo chvt 6 && sudo chvt 7', null); this.hdmi = true; - } else if(/(GO)/g.test(data) && /(SLEEP)/g.test(data)){ - exec("/opt/vc/bin/tvservice -o", null); + } else if (/(GO)/g.test(data) && /(SLEEP)/g.test(data)) { + exec('/opt/vc/bin/tvservice -o', null); this.hdmi = false; - } else if(/(SHOW)/g.test(data) && /(MODULES)/g.test(data)){ - this.sendSocketNotification("SHOW"); - } else if(/(HIDE)/g.test(data) && /(MODULES)/g.test(data)){ - this.sendSocketNotification("HIDE"); - } else if(/(HELP)/g.test(data)){ - if(/(CLOSE)/g.test(data) || this.help && !/(OPEN)/g.test(data)){ - this.sendSocketNotification("CLOSE_HELP"); + } else if (/(SHOW)/g.test(data) && /(MODULES)/g.test(data)) { + this.sendSocketNotification('SHOW'); + } else if (/(HIDE)/g.test(data) && /(MODULES)/g.test(data)) { + this.sendSocketNotification('HIDE'); + } else if (/(HELP)/g.test(data)) { + if (/(CLOSE)/g.test(data) || (this.help && !/(OPEN)/g.test(data))) { + this.sendSocketNotification('CLOSE_HELP'); this.help = false; - } else if(/(OPEN)/g.test(data) || !this.help && !/(CLOSE)/g.test(data)){ - this.sendSocketNotification("OPEN_HELP"); + } else if (/(OPEN)/g.test(data) || (!this.help && !/(CLOSE)/g.test(data))) { + this.sendSocketNotification('OPEN_HELP'); this.help = true; } } } -}); \ No newline at end of file +}); diff --git a/package.json b/package.json index aba271d..c6e3df9 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,9 @@ "name": "mmm-voice", "version": "1.0.0", "description": "Voice Recognition Module for MagicMirror2", + "scripts": { + "lint": "./node_modules/.bin/eslint . && ./node_modules/.bin/stylelint ." + }, "repository": { "type": "git", "url": "git+https://github.com/fewieden/MMM-voice.git" @@ -17,8 +20,15 @@ "url": "https://github.com/fewieden/MMM-voice/issues" }, "homepage": "https://github.com/fewieden/MMM-voice#readme", + "devDependencies": { + "eslint": "^3.14.1", + "eslint-config-airbnb-base": "^11.0.1", + "eslint-plugin-import": "^2.2.0", + "stylelint": "^7.8.0", + "stylelint-config-standard": "^16.0.0" + }, "dependencies": { "lmtool": "2.0.3", "pocketsphinx-continuous": "1.1.0" } -} \ No newline at end of file +} From ec1d34304083e18614e08b5c48d63ab916adce30 Mon Sep 17 00:00:00 2001 From: fewieden Date: Thu, 27 Apr 2017 15:09:53 +0200 Subject: [PATCH 2/4] created fork to avoid code manipulation --- installers/dependencies.sh | 50 ++++++++++++-------------------------- package.json | 4 +-- 2 files changed, 18 insertions(+), 36 deletions(-) diff --git a/installers/dependencies.sh b/installers/dependencies.sh index 883edf9..9856467 100644 --- a/installers/dependencies.sh +++ b/installers/dependencies.sh @@ -32,24 +32,24 @@ echo -e "\e[0m" # installing packages -echo -e "\e[96m[STEP 1/6] Installing Packages\e[90m" +echo -e "\e[96m[STEP 1/5] Installing Packages\e[90m" if sudo apt-get install bison libasound2-dev autoconf automake libtool python-dev swig python-pip -y ; then - echo -e "\e[32m[STEP 1/6] Installing Packages | Done\e[0m" + echo -e "\e[32m[STEP 1/5] Installing Packages | Done\e[0m" else - echo -e "\e[31m[STEP 1/6] Installing Packages | Failed\e[0m" + echo -e "\e[31m[STEP 1/5] Installing Packages | Failed\e[0m" exit; fi # installing sphinxbase -echo -e "\e[96m[STEP 2/6] Installing sphinxbase\e[90m" +echo -e "\e[96m[STEP 2/5] Installing sphinxbase\e[90m" cd ~ if [ ! -d "$HOME/sphinxbase" ] ; then if ! git clone https://github.com/cmusphinx/sphinxbase.git ; then - echo -e "\e[31m[STEP 2/6] Installing sphinxbase | Failed\e[0m" + echo -e "\e[31m[STEP 2/5] Installing sphinxbase | Failed\e[0m" exit; fi fi @@ -57,7 +57,7 @@ fi cd sphinxbase if ! git pull ; then - echo -e "\e[31m[STEP 2/6] Installing sphinxbase | Failed\e[0m" + echo -e "\e[31m[STEP 2/5] Installing sphinxbase | Failed\e[0m" exit; fi @@ -65,17 +65,17 @@ fi ./configure --enable-fixed make sudo make install -echo -e "\e[32m[STEP 2/6] Installing sphinxbase | Done\e[0m" +echo -e "\e[32m[STEP 2/5] Installing sphinxbase | Done\e[0m" # installing pocketsphinx -echo -e "\e[96m[STEP 3/6] Installing pocketsphinx\e[90m" +echo -e "\e[96m[STEP 3/5] Installing pocketsphinx\e[90m" cd ~ if [ ! -d "$HOME/pocketsphinx" ] ; then if ! git clone https://github.com/cmusphinx/pocketsphinx.git ; then - echo -e "\e[31m[STEP 3/6] Installing pocketsphinx | Failed\e[0m" + echo -e "\e[31m[STEP 3/5] Installing pocketsphinx | Failed\e[0m" exit; fi fi @@ -83,7 +83,7 @@ fi cd pocketsphinx if ! git pull ; then - echo -e "\e[31m[STEP 3/6] Installing pocketsphinx | Failed\e[0m" + echo -e "\e[31m[STEP 3/5] Installing pocketsphinx | Failed\e[0m" exit; fi @@ -91,42 +91,24 @@ fi ./configure make sudo make install -echo -e "\e[32m[STEP 3/6] Installing pocketsphinx | Done\e[0m" +echo -e "\e[32m[STEP 3/5] Installing pocketsphinx | Done\e[0m" # exporting paths -echo -e "\e[96m[STEP 4/6] Exporting paths\e[0m" +echo -e "\e[96m[STEP 4/5] Exporting paths\e[0m" echo "export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib" >> ~/.bashrc echo "export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig" >> ~/.bashrc -echo -e "\e[32m[STEP 4/6] Exporting paths | Done\e[0m" +echo -e "\e[32m[STEP 4/5] Exporting paths | Done\e[0m" # installing npm dependencies -echo -e "\e[96m[STEP 5/6] Installing npm dependencies\e[90m" +echo -e "\e[96m[STEP 5/5] Installing npm dependencies\e[90m" cd ~/MagicMirror/modules/MMM-voice if npm install --productive; then - echo -e "\e[32m[STEP 5/6] Installing npm dependencies | Done\e[0m" + echo -e "\e[32m[STEP 5/5] Installing npm dependencies | Done\e[0m" else - echo -e "\e[31m[STEP 5/6] Installing npm dependencies | Failed\e[0m" - exit; -fi - - -# manipulating dependencies -echo -e "\e[96m[STEP 6/6] Manipulating dependencies\e[90m" -cd ~/MagicMirror/modules/MMM-voice/node_modules/pocketsphinx-continuous -if sed \ --e "/this.verbose = config.verbose;/ a\ this.microphone = config.microphone;" \ --e "/-inmic/ i\ '-adcdev'," \ --e "/-inmic/ i\ 'plughw:' \+ this.microphone," \ --e "/-lm/ a\ 'modules/MMM-voice/' \+" \ --e "/-dict/ a\ 'modules/MMM-voice/' \+" \ -index.js -i; -then - echo -e "\e[32m[STEP 6/6] Manipulating dependencies | Done\e[0m" -else - echo -e "\e[31m[STEP 6/6] Manipulating dependencies | Failed\e[0m" + echo -e "\e[31m[STEP 5/5] Installing npm dependencies | Failed\e[0m" exit; fi diff --git a/package.json b/package.json index c6e3df9..4d0b1ff 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mmm-voice", - "version": "1.0.0", + "version": "1.0.1", "description": "Voice Recognition Module for MagicMirror2", "scripts": { "lint": "./node_modules/.bin/eslint . && ./node_modules/.bin/stylelint ." @@ -29,6 +29,6 @@ }, "dependencies": { "lmtool": "2.0.3", - "pocketsphinx-continuous": "1.1.0" + "pocketsphinx-continuous": "git://github.com/fewieden/pocketsphinx-continuous-node.git" } } From f06577d7ce4cdcd4c268a18fa30bc2c36f1a03ca Mon Sep 17 00:00:00 2001 From: fewieden Date: Thu, 27 Apr 2017 15:10:59 +0200 Subject: [PATCH 3/4] fix code climate, created helper methods --- DEVELOPER.md | 6 +-- node_helper.js | 136 ++++++++++++++++++++++++++----------------------- 2 files changed, 74 insertions(+), 68 deletions(-) diff --git a/DEVELOPER.md b/DEVELOPER.md index bdec5d5..cf5e3e9 100644 --- a/DEVELOPER.md +++ b/DEVELOPER.md @@ -17,7 +17,7 @@ As soon as you receive the notification `ALL_MODULES_STARTED` from the core syst * notification: `REGISTER_VOICE_MODULE` * payload: Object with `mode` (string) and `sentence` (array) properties -### Example +### Example commands registration ```javascript notificationReceived: function (notification, payload, sender) { @@ -42,7 +42,7 @@ When the user is in the mode of your module, you will receive the following noti * notification: `VOICE_YOURMODE` * payload: String with all detected words. -### Example +### Example commands recognition ```javascript notificationReceived: function (notification, payload, sender) { @@ -70,7 +70,7 @@ When the mode of MMM-voice gets changed it will send a broadcast `VOICE_MODE_CHA This gets handy e.g. to revert your manipulations on the DOM. -### Example +### Example mode change ```javascript notificationReceived: function (notification, payload, sender) { diff --git a/node_helper.js b/node_helper.js index 6f8a53c..1e7bd26 100644 --- a/node_helper.js +++ b/node_helper.js @@ -22,6 +22,11 @@ module.exports = NodeHelper.create({ help: false, words: [], + start() { + console.log(`Starting module helper: ${this.name}`); + this.time = this.config.timeout * 1000; + }, + socketNotificationReceived(notification, payload) { if (notification === 'START') { this.config = payload.config; @@ -34,38 +39,25 @@ module.exports = NodeHelper.create({ fillWords() { // create array - const array = this.config.keyword.split(' '); + let words = this.config.keyword.split(' '); const temp = bytes.q.split(' '); - for (let i = 0; i < temp.length; i += 1) { - array.push(temp[i]); - } + words = words.concat(temp); for (let i = 0; i < this.modules.length; i += 1) { const mode = this.modules[i].mode.split(' '); - for (let m = 0; m < mode.length; m += 1) { - array.push(mode[m]); - } + words = words.concat(mode); for (let n = 0; n < this.modules[i].sentences.length; n += 1) { const sentences = this.modules[i].sentences[n].split(' '); - for (let x = 0; x < sentences.length; x += 1) { - array.push(sentences[x]); - } + words = words.concat(sentences); } } - // sort array - array.sort(); - // filter duplicates - let i = 0; - while (i < array.length) { - if (array[i] === array[i + 1]) { - array.splice(i + 1, 1); - } else { - i += 1; - } - } + words = words.filter((item, index, data) => data.indexOf(item) === index); - this.words = array; + // sort array + words.sort(); + + this.words = words; }, checkFiles() { @@ -136,67 +128,81 @@ module.exports = NodeHelper.create({ startPocketsphinx() { console.log(`${this.name}: Starting pocketsphinx.`); - this.time = this.config.timeout * 1000; + this.ps = new Psc({ setId: this.name, verbose: true, microphone: this.config.microphone }); - this.ps.on('data', (data) => { - if (typeof data === 'string') { - if (this.config.debug) { - console.log(data); - this.sendSocketNotification('DEBUG', data); - } - if (data.includes(this.config.keyword) || this.listening) { - this.listening = true; - this.sendSocketNotification('LISTENING'); - if (this.timer) { - clearTimeout(this.timer); - } - this.timer = setTimeout(() => { - this.listening = false; - this.sendSocketNotification('SLEEPING'); - }, this.time); - } else { - return; - } + this.ps.on('data', this.handleData); - let cleanData = this.cleanData(data); + if (this.config.debug) { + this.ps.on('debug', this.logDebug); + } - for (let i = 0; i < this.modules.length; i += 1) { - const n = cleanData.indexOf(this.modules[i].mode); - if (n === 0) { - this.mode = this.modules[i].mode; - cleanData = cleanData.substr(n + this.modules[i].mode.length).trim(); - break; - } + this.ps.on('error', this.logError); + + this.sendSocketNotification('READY'); + }, + + handleData(data) { + if (typeof data === 'string') { + if (this.config.debug) { + console.log(`${this.name} has recognized: ${data}`); + this.sendSocketNotification('DEBUG', data); + } + if (data.includes(this.config.keyword) || this.listening) { + this.listening = true; + this.sendSocketNotification('LISTENING'); + if (this.timer) { + clearTimeout(this.timer); } + this.timer = setTimeout(() => { + this.listening = false; + this.sendSocketNotification('SLEEPING'); + }, this.time); + } else { + return; + } - if (this.mode) { - this.sendSocketNotification('VOICE', { mode: this.mode, sentence: cleanData }); - if (this.mode === 'VOICE') { - this.checkCommands(cleanData); - } + let cleanData = this.cleanData(data); + + for (let i = 0; i < this.modules.length; i += 1) { + const n = cleanData.indexOf(this.modules[i].mode); + if (n === 0) { + this.mode = this.modules[i].mode; + cleanData = cleanData.substr(n + this.modules[i].mode.length).trim(); + break; } } - }); - if (this.config.debug) { - this.ps.on('debug', (data) => { - fs.appendFile('modules/MMM-voice/debug.log', data); - }); + if (this.mode) { + this.sendSocketNotification('VOICE', { mode: this.mode, sentence: cleanData }); + if (this.mode === 'VOICE') { + this.checkCommands(cleanData); + } + } } + }, - this.ps.on('error', (error) => { - if (error) { - fs.appendFile('modules/MMM-voice/error.log', error); - this.sendSocketNotification('ERROR', error); + logDebug(data) { + fs.appendFile('modules/MMM-voice/debug.log', data, (err) => { + if (err) { + console.log(`${this.name}: Couldn't save error to log file!`); } }); + }, - this.sendSocketNotification('READY'); + logError(error) { + if (error) { + fs.appendFile('modules/MMM-voice/error.log', `${error}\n`, (err) => { + if (err) { + console.log(`${this.name}: Couldn't save error to log file!`); + } + this.sendSocketNotification('ERROR', error); + }); + } }, cleanData(data) { From c0d4cdc965c15ea1806c877c36f5e1e28c2ea21f Mon Sep 17 00:00:00 2001 From: fewieden Date: Fri, 28 Apr 2017 00:49:54 +0200 Subject: [PATCH 4/4] add documentation, guidelines --- .doclets.yml | 10 +++ .github/CONTRIBUTING.md | 8 ++ .github/ISSUE_TEMPLATE.md | 9 +++ .github/PULL_REQUEST_TEMPLATE.md | 5 ++ .gitignore | 8 ++ CHANGELOG.md | 21 +++++ MMM-voice.js | 96 +++++++++++++++++++++- README.md | 15 +++- jsdoc.json | 19 +++++ node_helper.js | 134 +++++++++++++++++++++++++++++-- package.json | 4 +- 11 files changed, 314 insertions(+), 15 deletions(-) create mode 100644 .doclets.yml create mode 100644 .github/CONTRIBUTING.md create mode 100644 .github/ISSUE_TEMPLATE.md create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .gitignore create mode 100644 CHANGELOG.md create mode 100644 jsdoc.json diff --git a/.doclets.yml b/.doclets.yml new file mode 100644 index 0000000..a301381 --- /dev/null +++ b/.doclets.yml @@ -0,0 +1,10 @@ +dir: . +packageJson: package.json +articles: + - Overview: README.md + - Developer: DEVELOPER.md + - Changelog: CHANGELOG.md + - License: LICENSE +branches: + - master + - develop diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 0000000..5956f32 --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,8 @@ +# Contribution Guidelines + +Thanks for contributing to this module! + +Please create pull requests to the branch `develop`. + +To hold one code style and standard there are several linters and tools in this project set. Make sure you fullfill the requirements. +Also there will be automatically analysis performed once you created the pull request. diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..5bbae68 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,9 @@ +Platform (Hardware/OS): + +Node version: + +MagicMirror version: + +Module version: + +Description of the issue: diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..63fa699 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,5 @@ +Please create pull requests to the branch `develop`. + +* Does the pull request solve an issue (add a reference)? +* What are the features of this pr? +* Add screenshots for visual changes. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2a19034 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +*.log +npm-debug.log* + +node_modules/ + +.idea/ + +docs/ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..75bf570 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,21 @@ +# MMM-voice Changelog + +## [1.0.1] + +### Added + +* Code linter +* Documentation +* [Doclets.io](https://doclets.io/fewieden/MMM-voice/master) integration +* Contributing guidelines +* Issue template +* Pull request template +* Gitignore + +### Changed + +* Dependency manipulation exchanged to fork + +## [1.0.0] + +Initial version diff --git a/MMM-voice.js b/MMM-voice.js index 0db5587..a66da27 100644 --- a/MMM-voice.js +++ b/MMM-voice.js @@ -1,18 +1,51 @@ -/* Magic Mirror - * Module: MMM-voice +/** + * @file MMM-voice.js * - * By fewieden https://github.com/fewieden/MMM-voice - * MIT Licensed. + * @author fewieden + * @license MIT + * + * @see https://github.com/fewieden/MMM-voice */ /* global Module Log MM */ +/** + * @external Module + * @see https://github.com/MichMich/MagicMirror/blob/master/js/module.js + */ + +/** + * @external Log + * @see https://github.com/MichMich/MagicMirror/blob/master/js/logger.js + */ + +/** + * @external MM + * @see https://github.com/MichMich/MagicMirror/blob/master/js/main.js + */ + +/** + * @module MMM-voice + * @description Frontend for the module to display data. + * + * @requires external:Module + * @requires external:Log + * @requires external:MM + */ Module.register('MMM-voice', { + /** @member {string} icon - Microphone icon. */ icon: 'fa-microphone-slash', + /** @member {boolean} pulsing - Flag to indicate listening state. */ pulsing: true, + /** @member {boolean} help - Flag to switch between render help or not. */ help: false, + /** + * @member {Object} voice - Defines the default mode and commands of this module. + * @property {string} mode - Voice mode of this module. + * @property {string[]} sentences - List of voice commands of this module. + */ voice: { mode: 'VOICE', sentences: [ @@ -25,14 +58,26 @@ Module.register('MMM-voice', { ] }, + /** @member {Object[]} modules - Set of all modules with mode and commands. */ modules: [], + /** + * @member {Object} defaults - Defines the default config values. + * @property {int} timeout - Seconds to active listen for commands. + * @property {string} keyword - Keyword to activate active listening. + * @property {boolean} debug - Flag to enable debug information. + */ defaults: { timeout: 15, keyword: 'MAGIC MIRROR', debug: false }, + /** + * @function start + * @description Sets mode to initialising. + * @override + */ start() { Log.info(`Starting module: ${this.name}`); this.mode = this.translate('INIT'); @@ -40,10 +85,24 @@ Module.register('MMM-voice', { Log.info(`${this.name} is waiting for voice command registrations.`); }, + /** + * @function getStyles + * @description Style dependencies for this module. + * @override + * + * @returns {string[]} List of the style dependency filepaths. + */ getStyles() { return ['font-awesome.css', 'MMM-voice.css']; }, + /** + * @function getTranslations + * @description Translations for this module. + * @override + * + * @returns {Object.} Available translations for this module (key: language code, value: filepath). + */ getTranslations() { return { en: 'translations/en.json', @@ -52,6 +111,13 @@ Module.register('MMM-voice', { }; }, + /** + * @function getDom + * @description Creates the UI as DOM for displaying in MagicMirror application. + * @override + * + * @returns {Element} + */ getDom() { const wrapper = document.createElement('div'); const voice = document.createElement('div'); @@ -97,6 +163,14 @@ Module.register('MMM-voice', { return wrapper; }, + /** + * @function notificationReceived + * @description Handles incoming broadcasts from other modules or the MagicMirror core. + * @override + * + * @param {string} notification - Notification name + * @param {*} payload - Detailed payload of the notification. + */ notificationReceived(notification, payload) { if (notification === 'DOM_OBJECTS_CREATED') { this.sendSocketNotification('START', { config: this.config, modules: this.modules }); @@ -107,6 +181,14 @@ Module.register('MMM-voice', { } }, + /** + * @function socketNotificationReceived + * @description Handles incoming messages from node_helper. + * @override + * + * @param {string} notification - Notification name + * @param {*} payload - Detailed payload of the notification. + */ socketNotificationReceived(notification, payload) { if (notification === 'READY') { this.icon = 'fa-microphone'; @@ -152,6 +234,12 @@ Module.register('MMM-voice', { this.updateDom(300); }, + /** + * @function appendHelp + * @description Creates the UI for the voice command SHOW HELP. + * + * @param {Element} appendTo - DOM Element where the UI gets appended as child. + */ appendHelp(appendTo) { const title = document.createElement('h1'); title.classList.add('medium'); diff --git a/README.md b/README.md index 293393b..f1222ce 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# MMM-voice [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](https://raw.githubusercontent.com/fewieden/MMM-voice/master/LICENSE) [![Build Status](https://travis-ci.org/fewieden/MMM-voice.svg?branch=master)](https://travis-ci.org/fewieden/MMM-voice) [![Code Climate](https://codeclimate.com/github/fewieden/MMM-voice/badges/gpa.svg?style=flat)](https://codeclimate.com/github/fewieden/MMM-voice) [![Known Vulnerabilities](https://snyk.io/test/github/fewieden/mmm-voice/badge.svg)](https://snyk.io/test/github/fewieden/mmm-voice) +# MMM-voice [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](https://raw.githubusercontent.com/fewieden/MMM-voice/master/LICENSE) [![Build Status](https://travis-ci.org/fewieden/MMM-voice.svg?branch=master)](https://travis-ci.org/fewieden/MMM-voice) [![Code Climate](https://codeclimate.com/github/fewieden/MMM-voice/badges/gpa.svg?style=flat)](https://codeclimate.com/github/fewieden/MMM-voice) [![Known Vulnerabilities](https://snyk.io/test/github/fewieden/mmm-voice/badge.svg)](https://snyk.io/test/github/fewieden/mmm-voice) [![API Doc](https://doclets.io/fewieden/MMM-voice/master.svg)](https://doclets.io/fewieden/MMM-voice/master) Voice Recognition Module for MagicMirror2 @@ -69,6 +69,15 @@ To select a MODE, the specfic MODE has to be the first word of a COMMAND or righ List of all supported modules in the [Wiki](https://github.com/fewieden/MMM-voice/wiki/Supported-Modules). -## Developers Guide +## Developer -If you want to support your own module, check out the [Documentation](https://github.com/fewieden/MMM-voice/blob/master/DEVELOPER.md) and add it to the [Wiki](https://github.com/fewieden/MMM-voice/wiki/Supported-Modules). +* `npm run lint` - Lints JS and CSS files. +* `npm run docs` - Generates documentation. + +### Documentation + +The documentation can be found [here](https://doclets.io/fewieden/MMM-voice/master) + +### Developers Guide + +If you want to support your own module, check out the [Guide](DEVELOPER.md) and add it to the [Wiki](https://github.com/fewieden/MMM-voice/wiki/Supported-Modules). diff --git a/jsdoc.json b/jsdoc.json new file mode 100644 index 0000000..5c73e9f --- /dev/null +++ b/jsdoc.json @@ -0,0 +1,19 @@ +{ + "tags": { + "dictionaries": ["jsdoc"] + }, + "source": { + "include": [ + "package.json", + "README.md" + ], + "exclude": [ + "node_modules", + "Bytes.js" + ] + }, + "opts": { + "destination": "docs", + "recurse": true + } +} diff --git a/node_helper.js b/node_helper.js index 1e7bd26..f219dfe 100644 --- a/node_helper.js +++ b/node_helper.js @@ -1,35 +1,100 @@ -/* Magic Mirror - * Module: MMM-voice +/** + * @file node_helper.js * - * By fewieden https://github.com/fewieden/MMM-voice - * MIT Licensed. + * @author fewieden + * @license MIT + * + * @see https://github.com/fewieden/MMM-voice */ -/* eslint-env node */ - +/** + * @external pocketsphinx-continuous + * @see https://github.com/fewieden/pocketsphinx-continuous-node + */ const Psc = require('pocketsphinx-continuous'); + +/** + * @external fs + * @see https://nodejs.org/api/fs.html + */ const fs = require('fs'); + +/** + * @external child_process + * @see https://nodejs.org/api/child_process.html + */ const exec = require('child_process').exec; + +/** + * @external lmtool + * @see https://www.npmjs.com/package/lmtool + */ const lmtool = require('lmtool'); + +/** + * @module Bytes + * @description Pure Magic + */ const bytes = require('./Bytes.js'); + +/** + * @external node_helper + * @see https://github.com/MichMich/MagicMirror/blob/master/modules/node_modules/node_helper/index.js + */ const NodeHelper = require('node_helper'); +/** + * @module node_helper + * @description Backend for the module to query data from the API providers. + * + * @requires external:pocketsphinx-continuous + * @requires external:fs + * @requires external:child_process + * @requires external:lmtool + * @requires Bytes + * @requires external:node_helper + */ module.exports = NodeHelper.create({ + /** @member {boolean} listening - Flag to indicate listen state. */ listening: false, + + /** @member {(boolean|string)} mode - Contains active module mode. */ mode: false, + + /** @member {boolean} hdmi - Flag to indicate hdmi output state. */ hdmi: true, + + /** @member {boolean} help - Flag to toggle help modal. */ help: false, + + /** @member {string[]} words - List of all words that are registered by the modules. */ words: [], + /** + * @function start + * @description Logs a start message to the console. + * @override + */ start() { console.log(`Starting module helper: ${this.name}`); - this.time = this.config.timeout * 1000; }, + /** + * @function socketNotificationReceived + * @description Receives socket notifications from the module. + * @override + * + * @param {string} notification - Notification name + * @param {*} payload - Detailed payload of the notification. + */ socketNotificationReceived(notification, payload) { if (notification === 'START') { + /** @member {Object} config - Module config. */ this.config = payload.config; + /** @member {number} time - Time to listen after keyword. */ + this.time = this.config.timeout * 1000; + /** @member {Object} modules - List of modules with their modes and commands. */ this.modules = payload.modules; this.fillWords(); @@ -37,6 +102,11 @@ module.exports = NodeHelper.create({ } }, + /** + * @function fillwords + * @description Sets {@link node_helper.words} with all needed words for the registered + * commands by the modules. This list has unique items and is sorted by alphabet. + */ fillWords() { // create array let words = this.config.keyword.split(' '); @@ -60,6 +130,10 @@ module.exports = NodeHelper.create({ this.words = words; }, + /** + * @function checkFiles + * @description Checks if words.json exists or has different entries as this.word. + */ checkFiles() { console.log(`${this.name}: Checking files.`); fs.stat('modules/MMM-voice/words.json', (error, stats) => { @@ -80,6 +154,14 @@ module.exports = NodeHelper.create({ }); }, + /** + * @function arraysEqual + * @description Compares two arrays. + * + * @param {string[]} a - First array + * @param {string[]} b - Second array + * @returns {boolean} Are the arrays equal or not. + */ arraysEqual(a, b) { if (!(a instanceof Array) || !(b instanceof Array)) { return false; @@ -98,6 +180,10 @@ module.exports = NodeHelper.create({ return true; }, + /** + * @function generateDicLM + * @description Generates new Dictionairy and Language Model. + */ generateDicLM() { console.log(`${this.name}: Generating dictionairy and language model.`); @@ -126,6 +212,10 @@ module.exports = NodeHelper.create({ }); }, + /** + * @function startPocketsphinx + * @description Starts Pocketsphinx binary. + */ startPocketsphinx() { console.log(`${this.name}: Starting pocketsphinx.`); @@ -146,6 +236,12 @@ module.exports = NodeHelper.create({ this.sendSocketNotification('READY'); }, + /** + * @function handleData + * @description Helper method to handle recognized data. + * + * @param {string} data - Recognized data + */ handleData(data) { if (typeof data === 'string') { if (this.config.debug) { @@ -186,6 +282,12 @@ module.exports = NodeHelper.create({ } }, + /** + * @function logDebug + * @description Logs debug information into debug log file. + * + * @param {string} data - Debug information + */ logDebug(data) { fs.appendFile('modules/MMM-voice/debug.log', data, (err) => { if (err) { @@ -194,6 +296,12 @@ module.exports = NodeHelper.create({ }); }, + /** + * @function logError + * @description Logs error information into error log file. + * + * @param {string} data - Error information + */ logError(error) { if (error) { fs.appendFile('modules/MMM-voice/error.log', `${error}\n`, (err) => { @@ -205,6 +313,13 @@ module.exports = NodeHelper.create({ } }, + /** + * @function cleanData + * @description Removes prefix/keyword and multiple spaces. + * + * @param {string} data - Recognized data to clean. + * @returns {string} Cleaned data + */ cleanData(data) { let temp = data; const i = temp.indexOf(this.config.keyword); @@ -215,6 +330,11 @@ module.exports = NodeHelper.create({ return temp; }, + /** + * @function checkCommands + * @description Checks for commands of voice module + * @param {string} data - Recognized data + */ checkCommands(data) { if (bytes.r[0].test(data) && bytes.r[1].test(data)) { this.sendSocketNotification('BYTES', bytes.a); diff --git a/package.json b/package.json index 4d0b1ff..0931fcf 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,8 @@ "version": "1.0.1", "description": "Voice Recognition Module for MagicMirror2", "scripts": { - "lint": "./node_modules/.bin/eslint . && ./node_modules/.bin/stylelint ." + "lint": "./node_modules/.bin/eslint . && ./node_modules/.bin/stylelint .", + "docs": "./node_modules/.bin/jsdoc -c jsdoc.json ." }, "repository": { "type": "git", @@ -24,6 +25,7 @@ "eslint": "^3.14.1", "eslint-config-airbnb-base": "^11.0.1", "eslint-plugin-import": "^2.2.0", + "jsdoc": "^3.4.3", "stylelint": "^7.8.0", "stylelint-config-standard": "^16.0.0" },