alexatts – Blame information for rev 4

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 office 1 #!/usr/bin/env nodejs
2 ///////////////////////////////////////////////////////////////////////////
4 office 3 // Copyright (C) Wizardry and Steamworks 2020 - License: GNU GPLv3 //
1 office 4 // Please see: http://www.gnu.org/licenses/gpl.html for legal details, //
5 // rights of fair usage, the disclaimer and warranty conditions. //
6 ///////////////////////////////////////////////////////////////////////////
4 office 7  
8 // Audioinjector cards: sysdefault:CARD=audioinjectorpi
9  
1 office 10 const Gpio = require('onoff').Gpio
11 const mqtt = require('mqtt')
4 office 12 const YAML = require('yamljs')
1 office 13 const winston = require('winston')
2 office 14 const picoSpeaker = require('pico-speaker')
4 office 15 const googleTTS = require('google-tts-api')
16 const player = require('play-sound')((opts = { player: 'mpg123' }))
17 const https = require('https')
18 const tmp = require('tmp')
19 const fs = require('fs')
20 const lambda = require('was.js').lambda
21  
1 office 22 // Load configuration file.
2 office 23 const config = YAML.load('config.yml')
24  
25 // Define configuration for pico TTS.
26 var picoConfig = {
27 AUDIO_DEVICE: config.card,
28 LANGUAGE: config.language
4 office 29 }
2 office 30  
31 // Initialize with config
32 picoSpeaker.init(picoConfig)
33  
1 office 34 // Generate GPIO pins for configuration.
4 office 35 var ATTS = {}
36 for (var i in config.GPIO) {
37 if (!config.GPIO.hasOwnProperty(i)) continue
38  
39 if (config.GPIO[i] === -1) continue
40  
2 office 41 ATTS[i] = new Gpio(config.GPIO[i], 'out')
1 office 42 }
4 office 43  
1 office 44 // Set up logger.
4 office 45 winston.add(winston.transports.File, { filename: config.log })
46  
1 office 47 // Initiate connection to MQTT.
4 office 48 const client = mqtt.connect(config.mqtt.url)
49  
50 function mqttSubscribeCallback (err, granted) {
51 if (err) {
52 winston.info(`Unable to subscribe to MQTT topic ${config.mqtt.topic}`)
53 return
54 }
55 winston.info(`Subscribed to MQTT topic ${config.mqtt.topic}`)
56 }
57  
58 client.on('connect', () => {
59 winston.info(`Connected to MQTT server ${config.mqtt.url}`)
60 client.subscribe(config.mqtt.topic, {}, mqttSubscribeCallback)
1 office 61 })
4 office 62  
63 client.on('error', () => {
64 winston.error(`Unable to connect to MQTT server ${config.mqtt.url}`)
65 })
66  
67 client.on('message', (topic, message) => {
68 if (message.length === 0) return
69  
1 office 70 // Remove any retained message.
4 office 71 client.publish(topic, '', { retain: true })
2 office 72  
4 office 73 speakMessage = `${config.alexa}, Simon says, ${message.toString()}`
74 winston.info(`Received MQTT message: ${message}`)
75  
76 if (config.tts.use === 'google' && speakMessage.length > 100) {
77 winston.error(
78 `Total spoken message would be too long: ${speakMessage.length} characters out of ${config.tts.google.maxLength}`
79 )
80 return
81 }
82  
83 winston.info(`Sending to Alexa: ${speakMessage}`)
84 ATTS['ptt'].write(1, err => {
85 if (err) {
86 winston.error(`Unable to press push-to-talk button: ${err}`)
87 return
1 office 88 }
2 office 89  
4 office 90 lambda.switch(
91 config.tts.use,
92 tts => {
93 winston.error(`Unknown TTS engine selected`)
94 },
95 tts => tts === 'google',
96 tts => {
97 googleTTS(speakMessage, 'en', config.tts.google.speed)
98 .then(url => {
99 winston.info(`Google TTS URL obtained: ${url}`)
2 office 100  
4 office 101 tmp.tmpName((err, path) => {
102 if (err) {
103 winston.error(`Unable to create temporary file: ${err}`)
104 return
105 }
106  
107 const file = fs
108 .createWriteStream(path)
109 .on('error', err => {
110 winston.error(
111 `Writing temporary file ${path} failed with error ${err}`
112 )
113 })
114 .on('finish', () => {
115 winston.info(`Google TTS file stored at ${path}`)
116 winston.info(`Speaking...`)
117 player.play(path, err => {
118 if (err) {
119 winston.error(`Unable to play the sound file: ${err}`)
120 }
121 ATTS['ptt'].write(0)
122 winston.info(`Done`)
123 })
124 })
125 .on('end', () => {
126 file.close()
127 })
128  
129 https.get(url, (res, err) => {
130 res.pipe(file)
131 })
132 })
133 })
134 .catch(err => {
135 winston.error(`Google TTS error: ${err}`)
136 })
137  
138 return true
139 },
140 tts => tts === 'picotts',
141 tts => {
142 picoSpeaker.speak(speakMessage).then(() => {
143 winston.info('Message ' + speakMessage + ' sent to Alexa.')
144  
145 ATTS['ptt'].write(0)
146 })
147  
148 return true
149 }
150 )
1 office 151 })
152 })