-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 964648e
Showing
12 changed files
with
1,417 additions
and
0 deletions.
There are no files selected for viewing
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
DEVICE_ID=YOUR_DEVICE_ID | ||
DEVICE_MODEL_ID=YOUR_DEVICE_MODEL_ID | ||
LATITUDE=YOUR_LATITUDE | ||
LONGITUDE=YOUR_LONGITUDE | ||
MIC_HW='plughw:1,0' |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
# Google Home Assistant for Armbian | ||
|
||
I build Google Home Assistant for Armbian (S905x) based on NodeJS (because Python eating more resources) | ||
|
||
|
||
# Getting Started | ||
|
||
## Requirement | ||
For installation you need: | ||
1. NodeJS 14 and PM2 | ||
2. Microphone and Speaker | ||
3. Device ID and Device Model ID (Create it on Google Cloud Console) | ||
|
||
|
||
# How it Works | ||
|
||
PM2 monitoring (Start -> Mic -> Detector -> Google Home Assist -> Audio -> Terminate) | ||
|
||
1. PM2 started the app | ||
2. Detecting wakeword/hotword | ||
3. If wakeword/hotword detected, send your voice to Google Assistant API | ||
4. Google Assistant will respons your voice | ||
5. App terminated and PM2 will restart this app | ||
|
||
I don't know the efficient way to run this app. | ||
|
||
# How to Install | ||
|
||
## Installation | ||
|
||
### Audio backend | ||
|
||
Install audio dependencies | ||
```sh | ||
sudo apt install libmagic-dev libatlas-base-dev sox libsox-fmt-all build-essential | ||
``` | ||
|
||
Install `alsalib` or `pulseaudio lib` for compiling backend | ||
```sh | ||
sudo apt install libasound2/pulseaudio-libs-devel | ||
``` | ||
|
||
Install `speaker-arm64` with your audio backend (example pulseaudio) | ||
```sh | ||
npm install speaker-arm64 --mpg123-backend=pulse/alsa | ||
``` | ||
|
||
### Gassist Armbian | ||
Install all dependencies | ||
|
||
```sh | ||
npm install | ||
``` | ||
|
||
Install `pm2` | ||
|
||
```sh | ||
sudo npm install -g pm2 | ||
``` | ||
|
||
## Run | ||
Run with `pm2` | ||
```js | ||
pm2 start index.js | ||
``` | ||
|
||
# Additional Information | ||
|
||
## Warning | ||
This repo is only for personal use. You cannot create a commercial product with this repo because of Google Assistant Policy. | ||
|
||
## Based | ||
This script was based on / modified from: | ||
- [`Endoplasmic's Google Assistant`](https://github.com/endoplasmic/google-assistant) | ||
- [`Dabolus's NodeJS Assistant`](https://github.com/Dabolus/nodejs-assistant) | ||
|
||
## License | ||
Copyright (c) 2023 Palguno Wicaksono | ||
|
||
The copyright holders grant the freedom to copy, modify, convey, adapt, and/or redistribute this work under the terms of the Massachusetts Institute of Technology License. | ||
A copy of that license is available at [`LICENSE`](https://license.icaksh.my.id/MIT) |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
const { writeFileSync } = require('fs'); | ||
const { OAuth2Client } = require('google-auth-library'); | ||
|
||
const getAuthorizationCode = (oAuth2Client) => new Promise((resolve) => { | ||
const authorizeUrl = oAuth2Client.generateAuthUrl({ | ||
access_type: 'offline', | ||
scope: 'https://www.googleapis.com/auth/assistant-sdk-prototype', | ||
}); | ||
|
||
console.log(`Open your browser at: ${authorizeUrl}`) | ||
console.log(`When you're done, paste your authorization code down here:`); | ||
|
||
const stdin = process.openStdin(); | ||
stdin.once('data', (data) => resolve(data.toString().trim())); | ||
}); | ||
|
||
const getRefreshToken = async (oAuth2Client) => { | ||
const code = await getAuthorizationCode(oAuth2Client); | ||
const { tokens } = await oAuth2Client.getToken(code); | ||
return tokens.refresh_token; | ||
} | ||
|
||
const getCredentials = async () => { | ||
try { | ||
return require('./credentials.json'); | ||
} catch (e) { | ||
const { | ||
installed: { client_id, client_secret, redirect_uris }, | ||
} = require('./client_secret.json'); | ||
const oAuth2Client = new OAuth2Client( | ||
client_id, | ||
client_secret, | ||
redirect_uris[0] | ||
|
||
); | ||
const refresh_token = await getRefreshToken(oAuth2Client); | ||
const credentials = { | ||
type: 'authorized_user', | ||
client_id, | ||
client_secret, | ||
refresh_token, | ||
}; | ||
writeFileSync('./credentials.json', JSON.stringify(credentials)); | ||
return credentials; | ||
} | ||
}; | ||
|
||
getCredentials(); |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
const record = require('node-record-lpcm16'); | ||
const Speaker = require('speaker-arm64'); | ||
const path = require('path'); | ||
const GoogleAssistant = require('google-assistant'); | ||
const speakerHelper = require('./speaker-helper'); | ||
const { Detector, Models } = require('@bugsounet/snowboy'); | ||
const process = require('process'); | ||
const wav = require('wav'); | ||
const fs = require('fs'); | ||
|
||
let x = 0; | ||
const config = { | ||
auth: { | ||
keyFilePath: path.resolve(__dirname, 'client_secret.json'), | ||
savedTokensPath: path.resolve(__dirname, 'credentials.json'), | ||
}, | ||
conversation: { | ||
audio: { | ||
sampleRateIn: 16000, | ||
sampleRateOut: 24000, | ||
}, | ||
lang: 'en-US', | ||
deviceModelId: process.env.DEVICE_MODEL_ID, | ||
deviceId: process.env.DEVICE_ID, | ||
deviceLocation: { | ||
coordinates: { | ||
latitude: process.env.LATITUDE, | ||
longitude: process.env.LONGITUDE, | ||
}, | ||
}, | ||
}, | ||
}; | ||
|
||
const startAudioAssistant = () => { | ||
if (x == 0) { | ||
x = 1 | ||
const assistant = new GoogleAssistant(config.auth); | ||
assistant | ||
.on('ready', () => { | ||
assistant.start(config.conversation) | ||
}) | ||
.on('started', startConversation) | ||
.on('error', (error) => { | ||
console.log('Assistant Error:', error); | ||
}); | ||
} else { } | ||
} | ||
|
||
const mic = record.start({ | ||
sampleRate: 16000, | ||
threshold: 0, | ||
recordProgram: 'arecord', | ||
device: process.env.MIC_HW | ||
}); | ||
|
||
const playSound = () => { | ||
var file = fs.createReadStream(__dirname + '/resources/alexa.wav'); | ||
var reader = new wav.Reader(); | ||
reader.on('format', function (format) { | ||
reader.pipe(new Speaker(format)); | ||
}); | ||
file.pipe(reader); | ||
} | ||
|
||
let isDone = false; | ||
const startConversation = (conversation) => { | ||
console.log('Say something!'); | ||
playSound(); | ||
let openMicAgain = false; | ||
mic.on('data', (data) => conversation.write(data)); | ||
conversation | ||
.on('audio-data', (data) => { | ||
speakerHelper.update(data); | ||
}) | ||
.on('end-of-utterance', () => record.stop()) | ||
.on('transcription', data => { console.log('Transcription:', data.transcription, ' --- Done:', data.done); isDone = data.done; }) | ||
.on('device-action', action => console.log('Device Action:', action)) | ||
.on('ended', (error, continueConversation) => { | ||
if (error) console.log('Conversation Ended Error:', error); | ||
else if (continueConversation) openMicAgain = false; | ||
else if (!isDone) process.exit(); | ||
else console.log('Conversation Complete'); | ||
}) | ||
.on('error', (error) => { | ||
console.log('Conversation Error:', error); | ||
}); | ||
|
||
const speaker = new Speaker({ | ||
channels: 1, | ||
sampleRate: config.conversation.audio.sampleRateOut, | ||
}); | ||
speakerHelper.init(speaker); | ||
speaker | ||
.on('open', () => { | ||
console.log('Assistant Speaking'); | ||
speakerHelper.open(); | ||
}) | ||
.on('close', () => { | ||
console.log('Assistant Finished Speaking'); | ||
process.exit() | ||
}); | ||
|
||
}; | ||
|
||
const waitForHotword = () => { | ||
console.log("Detecting Hotword"); | ||
const models = new Models(); | ||
|
||
models.add({ | ||
file: 'resources/alexa.pmdl', | ||
sensitivity: '0.5', | ||
hotwords: 'Alexa' | ||
}) | ||
|
||
const detector = new Detector({ | ||
models, | ||
resource: 'resources/common.res', | ||
audioGain: 1, | ||
applyFrontend: false, | ||
}); | ||
|
||
detector.on('hotword', () => startAudioAssistant(mic)) | ||
mic.pipe(detector); | ||
}; | ||
|
||
waitForHotword(); |
Oops, something went wrong.