alexatts

Subversion Repositories:
Compare Path: Rev
With Path: Rev
?path1? @ 3  →  ?path2? @ 4
/config.yml.dist
@@ -1,20 +1,24 @@
###########################################################################
## Copyright (C) Wizardry and Steamworks 2018 - License: GNU GPLv3 ##
## Copyright (C) Wizardry and Steamworks 2020 - License: GNU GPLv3 ##
## Please see: http://www.gnu.org/licenses/gpl.html for legal details, ##
## rights of fair usage, the disclaimer and warranty conditions. ##
###########################################################################
 
log: "alexatts.log"
log: 'alexatts.log'
 
mqtt:
url: "mqtt://server.tld"
url: 'mqtt://server.tld'
# The topic to subscribe to.
topic: "alexatts/#"
topic: 'alexatts/#'
 
language: 'en-US'
 
# The audio card to use in Alsa format (aplay should work)
card: 'sysdefault:CARD=audioinjectorpi'
tts:
# Possible values: google, picotts
use: 'google'
google:
maxLength: 100
speed: 1
 
# The GPIO configuration maps the remote control buttons to GPIO pins as
# they are cabled between the device that the software runs on and the
@@ -23,6 +27,6 @@
GPIO:
ptt: 16
 
# This is the name of the Alexa device - the keyword you use to talk to
# This is the name of the Alexa device - the keyword you use to talk to
# the Alexa device.
alexa: 'Alexa'
/main.js
@@ -1,16 +1,24 @@
#!/usr/bin/env nodejs
///////////////////////////////////////////////////////////////////////////
// Copyright (C) Wizardry and Steamworks 2018 - License: GNU GPLv3 //
// Copyright (C) Wizardry and Steamworks 2020 - License: GNU GPLv3 //
// Please see: http://www.gnu.org/licenses/gpl.html for legal details, //
// rights of fair usage, the disclaimer and warranty conditions. //
///////////////////////////////////////////////////////////////////////////
 
// Audioinjector cards: sysdefault:CARD=audioinjectorpi
 
const Gpio = require('onoff').Gpio
const mqtt = require('mqtt')
const YAML = require('yamljs');
const YAML = require('yamljs')
const winston = require('winston')
const picoSpeaker = require('pico-speaker')
const googleTTS = require('google-tts-api')
const player = require('play-sound')((opts = { player: 'mpg123' }))
const https = require('https')
const tmp = require('tmp')
const fs = require('fs')
const lambda = require('was.js').lambda
 
// Load configuration file.
const config = YAML.load('config.yml')
 
@@ -18,55 +26,127 @@
var picoConfig = {
AUDIO_DEVICE: config.card,
LANGUAGE: config.language
};
}
 
// Initialize with config
picoSpeaker.init(picoConfig)
 
// Generate GPIO pins for configuration.
var ATTS = {};
for(var i in config.GPIO) {
if(!config.GPIO.hasOwnProperty(i))
continue;
if(config.GPIO[i] === -1)
continue;
var ATTS = {}
for (var i in config.GPIO) {
if (!config.GPIO.hasOwnProperty(i)) continue
 
if (config.GPIO[i] === -1) continue
 
ATTS[i] = new Gpio(config.GPIO[i], 'out')
}
 
// Set up logger.
winston.add(winston.transports.File, {filename: config.log})
winston.add(winston.transports.File, { filename: config.log })
 
// Initiate connection to MQTT.
const client = mqtt.connect(config.mqtt.url, {queueQoSZero: false})
client.on('connect', function () {
winston.info('Connected to MQTT server')
client.subscribe(config.mqtt.topic)
const client = mqtt.connect(config.mqtt.url)
 
function mqttSubscribeCallback (err, granted) {
if (err) {
winston.info(`Unable to subscribe to MQTT topic ${config.mqtt.topic}`)
return
}
winston.info(`Subscribed to MQTT topic ${config.mqtt.topic}`)
}
 
client.on('connect', () => {
winston.info(`Connected to MQTT server ${config.mqtt.url}`)
client.subscribe(config.mqtt.topic, {}, mqttSubscribeCallback)
})
client.on('message', function (topic, message) {
if(message.length === 0)
return;
 
client.on('error', () => {
winston.error(`Unable to connect to MQTT server ${config.mqtt.url}`)
})
 
client.on('message', (topic, message) => {
if (message.length === 0) return
 
// Remove any retained message.
client.publish(topic, "", {retain: true})
message = message.toString()
winston.info('Received message: ' + message)
client.publish(topic, '', { retain: true })
 
ATTS["ptt"].write(1, (err) => {
if(err) {
winston.err('Unable to press push-to-talk button: ' + err.message)
return;
speakMessage = `${config.alexa}, Simon says, ${message.toString()}`
winston.info(`Received MQTT message: ${message}`)
 
if (config.tts.use === 'google' && speakMessage.length > 100) {
winston.error(
`Total spoken message would be too long: ${speakMessage.length} characters out of ${config.tts.google.maxLength}`
)
return
}
 
winston.info(`Sending to Alexa: ${speakMessage}`)
ATTS['ptt'].write(1, err => {
if (err) {
winston.error(`Unable to press push-to-talk button: ${err}`)
return
}
 
// Send the message.
picoSpeaker.speak(config.alexa + ", Simon says, " + message).then(function() {
winston.info('Message ' + message + ' sent to Alexa.')
lambda.switch(
config.tts.use,
tts => {
winston.error(`Unknown TTS engine selected`)
},
tts => tts === 'google',
tts => {
googleTTS(speakMessage, 'en', config.tts.google.speed)
.then(url => {
winston.info(`Google TTS URL obtained: ${url}`)
 
ATTS["ptt"].write(0)
}.bind(this));
tmp.tmpName((err, path) => {
if (err) {
winston.error(`Unable to create temporary file: ${err}`)
return
}
 
const file = fs
.createWriteStream(path)
.on('error', err => {
winston.error(
`Writing temporary file ${path} failed with error ${err}`
)
})
.on('finish', () => {
winston.info(`Google TTS file stored at ${path}`)
winston.info(`Speaking...`)
player.play(path, err => {
if (err) {
winston.error(`Unable to play the sound file: ${err}`)
}
ATTS['ptt'].write(0)
winston.info(`Done`)
})
})
.on('end', () => {
file.close()
})
 
https.get(url, (res, err) => {
res.pipe(file)
})
})
})
.catch(err => {
winston.error(`Google TTS error: ${err}`)
})
 
return true
},
tts => tts === 'picotts',
tts => {
picoSpeaker.speak(speakMessage).then(() => {
winston.info('Message ' + speakMessage + ' sent to Alexa.')
 
ATTS['ptt'].write(0)
})
 
return true
}
)
})
})
/package.json
@@ -1,25 +1,27 @@
{
"name": "alexatts",
"version": "1.0.0",
"description": "Text-to-speech for Amazon Alexa",
"main": "main.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"alexa",
"tts",
"speech",
"text",
"mqtt"
],
"author": "Wizardry and Steamworks (office@grimore.org)",
"license": "GPL-v3",
"dependencies": {
"mqtt": "^2.17.0",
"onoff": "^3.2.9",
"pico-speaker": "0.0.7",
"winston": "^2.4.2",
"yamljs": "^0.3.0"
}
"name": "alexatts",
"version": "1.0.0",
"description": "Text-to-speech for Amazon Alexa",
"main": "main.js",
"keywords": [
"alexa",
"tts",
"speech",
"text",
"mqtt"
],
"author": "Wizardry and Steamworks (office@grimore.org)",
"license": "GPL-v3",
"dependencies": {
"google-tts-api": "0.0.4",
"mqtt": "^2.17.0",
"onoff": "^3.2.9",
"pico-speaker": "0.0.7",
"play-sound": "^1.1.3",
"request": "^2.88.2",
"tmp": "^0.1.0",
"was.js": "https://files.grimore.org/node_modules/was.js/was.js-1.0.6.tgz",
"winston": "^2.4.2",
"yamljs": "^0.3.0"
}
}