Skip to content

Commit

Permalink
upload
Browse files Browse the repository at this point in the history
  • Loading branch information
icaksh committed Mar 12, 2023
0 parents commit 964648e
Show file tree
Hide file tree
Showing 12 changed files with 1,417 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .env
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'
81 changes: 81 additions & 0 deletions README.md
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)
48 changes: 48 additions & 0 deletions credentials.js
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();
126 changes: 126 additions & 0 deletions index.js
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();
Loading

0 comments on commit 964648e

Please sign in to comment.