Last active
April 26, 2020 21:01
-
-
Save fishfitz/67844a2029fd702692586e0fa7d79b07 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!-- | |
Actually this is a VueJS template (.vue). | |
The recognition itself uses an API that is included in Chrome and only works in Chrome (04/2020). | |
https://developer.mozilla.org/fr/docs/Web/API/Web_Speech_API | |
You may also need the following packages: | |
- npm install ky (http requests) | |
- npm install talisman (fuzzy matching) | |
--> | |
<template> | |
<button class="button" @click="start" :disabled="!recognition"> Start </button> | |
</template> | |
<script> | |
import ky from 'ky'; | |
import { lig3 } from 'talisman/metrics/lig'; | |
export default { | |
data() { | |
const data = { | |
recognition: null, | |
voice: null, | |
endpoint: process.env.HOME_URL, | |
httpConfig: { headers: { authorization: `Bearer ${process.env.HOME_TOKEN}` } }, | |
wakeword: /(brett)|(((be*)|(ma))*lette)/g, // Note: in JS regex are stateful | |
lastWakewordOccurence: null, | |
end: false | |
}; | |
return { | |
...data, | |
actions: [ | |
{ | |
phrase: "éteins l'écran", | |
handler() { | |
this.speak('J\'éteins l\'écran'); | |
setTimeout(() => { | |
ky.post(`${data.endpoint}services/shell_command/monitor_off`, data.httpConfig); | |
}, 2000); | |
} | |
}, | |
{ | |
phrase: "mode casque", | |
async handler() { | |
this.speak('Bip'); | |
await ky.post(`${data.endpoint}services/shell_command/audio_headset`, data.httpConfig); | |
this.speak('Boop'); | |
} | |
}, | |
{ | |
phrase: "mode enceinte", | |
async handler() { | |
this.speak('Boop'); | |
await ky.post(`${data.endpoint}services/shell_command/audio_speakers`, data.httpConfig); | |
this.speak('Bip'); | |
} | |
}, | |
{ | |
phrase: 'désactivation', | |
handler() { | |
this.speak('Désactivation'); | |
this.end = true; | |
} | |
}, | |
{ | |
phrase: 'allume la lumière', | |
async handler() { | |
this.speak('J\'allume la lumière'); | |
await ky.post(`${data.endpoint}services/light/toggle`, { | |
...data.httpConfig, | |
json: { | |
brightness: 255, | |
entity_id: 'light.bedroom' | |
} | |
}); | |
} | |
}, | |
{ | |
phrase: 'éteins la lumière', | |
async handler() { | |
this.speak('J\'éteins la lumière'); | |
await ky.post(`${data.endpoint}services/light/toggle`, { | |
...data.httpConfig, | |
json: { | |
brightness: 0, | |
entity_id: 'light.bedroom' | |
} | |
}); | |
} | |
} | |
] | |
}; | |
}, | |
async mounted() { | |
window.speechSynthesis.onvoiceschanged = () => { // Wait for speechsynthesis to be loaded | |
this.recognition = new window.webkitSpeechRecognition(); | |
this.recognition.continuous = true; | |
this.recognition.lang = 'fr-FR'; | |
this.recognition.interimResults = false; | |
this.recognition.maxAlternatives = 1; | |
this.recognition.onresult = this.handler; | |
this.recognition.onend = () => { | |
setTimeout(this.start, 1500); // When an action was recognized, restart the recognition | |
}; | |
this.voice = speechSynthesis.getVoices().find(v => v.voiceURI === 'Google français'); | |
}; | |
}, | |
methods: { | |
start() { | |
if (!this.recognition) Notification.requestPermission(); | |
if (!this.end) this.recognition.start(); | |
}, | |
speak(text) { | |
const phrase = new SpeechSynthesisUtterance(text); | |
phrase.voice = this.voice; | |
phrase.pitch = 0.6; | |
phrase.rate = 1.2; | |
speechSynthesis.speak(phrase); | |
}, | |
handler(event) { | |
let text = Array.from(event.results).map(r => r[0].transcript).join(' ').replace(/\s{2,}/g, ' '); | |
this.lastWakewordOccurence = this.wakeword.exec(text) || this.lastWakewordOccurence; | |
if (this.lastWakewordOccurence === null) return; // No wake word recognized, idle | |
text = text.slice(this.lastWakewordOccurence.index + this.lastWakewordOccurence[0].length + 1).trim(); | |
console.info('...', text); // Text recognized after the last keyword occurence | |
const triggered = this.actions.find((action) => { | |
if (lig3(text, action.phrase) > 0.80) { // Fuzzy match an action phrase (normalized distance) | |
this.stop(); | |
console.info('> Trigger', action.phrase); | |
new Notification('Belette', { body: action.phrase }); | |
if (action.handler) action.handler.call(this, text); | |
return true; | |
} | |
}); | |
if (triggered || text.length > 4000) this.stop(); // Avoid getting too much text to recognize | |
}, | |
stop() { | |
this.recognition.abort(); | |
this.wakeword.lastIndex = 0; // Reset regex state | |
this.lastWakewordOccurence = null; | |
} | |
} | |
}; | |
</script> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment