[RELEASE] google-assistant-helper v0.0.3

google-assistant-helper v0.0.3

Impetus

Hi all, I’m a long-time lurker who’s benefited immeasurably from the community, and thought I’d give back now that I have something to share.

My HA is heterogenous, but I’ve been wanting to send announcements via Google Home devices from ST, etc. I haven’t found anything that does what I need well, so I rolled my own.

This project is a Node.js server that provides a helper proxy/middleman for passing commands to the Google Assistant via the published API. It provides the following functionality:

  • Broadcast of text messages to Google Assistant devices using the default broadcast voice
  • Broadcast of audio files to Google Assistant devices (some restrictions apply)
  • Execution of custom commands and broadcast of responses
  • Playback of audio resources via Chromecast
  • Playback of text via Chromecast using the Google Cloud Text-to-Speech API
  • NEW in v0.0.2 - (Limited) control of Chromecast playback - stop, pause, seek, play
  • NEW in v0.0.3 - Broadcast of audio responses returned by the Google Assistant to certain custom commands (e…g “Tell me a joke”)
  • [Optional] TLS Support

The server presents a webhook that takes JSON posts and executes commands. It is configurable, and supports TLS (strongly recommended for any Internet-facing use).

Requirements

This is a Node.js server, and this documentation will assume that Node.js is installed, and that the user has some familiarity with Node.js and npm. Naturally, a server of some type is required, whether an RPi, a regular box, or a NAS that runs Linux or can run Docker.

This project should run on most modern Debian variants (including Raspbian). It may run on other operating systems; YMMV. It is verified to work in a Docker image, provided the necessary packages are installed.

Chromecast functionality requires mDNS; on Debian this requires dbus and avahi-daemon to be running. You may need to install the libavahi-compat-libdnssd-dev package.

How To

Go to the repository on Github and read the documentation, which hopefully is reasonably complete. Clone the repository or download an archive of v0.0.1 to get started.

Notes

Zoned broadcasts are supported (in addition to Chromecast groups). See Step 7. in “Setup Instructions” in the README.md (documentation) on Github for details.

Code quality is important to me, so if any issues arise, please open an issue on Github.

Happy tinkering!

1 Like

Is this similar to this?

I can’t say how similar or dissimilar it is to v2.0 of that project. I did try the initial version and found it to be kind of messy and only supported text broadcasts, and had extraneous requirements like speaker support. This is a superset of the functionality of the initial version of that project, with (hopefully) cleaner code and dependencies.

1 Like

how do you know if a nas can run Docker?
i have a seagate central NAS drvie but never heard of docker

Unfortunately, I don’t think Seagate NAS devices support Docker, which is a project supporting lightweight minimal containers that run virtual environments in userspace. The ones that I know of that do are Synology, QNAP, and FreeNAS (I have this running in a Docker container on Synology).

That being said, if you want a cheap(ish) option, this should run just fine on a Raspberry Pi.

I’ve got an old phone running a cast manager. Just trying to reduce stuff on and the nas is on

Thanks for releasing this! I had a problem on the install. Noooo idea how to fix it.
Thanks for any assistance. =D

pi@raspberrypi:~/google-assistant-helper $ npm install

mdns@2.4.0 install /home/pi/google-assistant-helper/node_modules/mdns
node-gyp rebuild

make: Entering directory ‘/home/pi/google-assistant-helper/node_modules/mdns/build’
CXX(target) Release/obj.target/dns_sd_bindings/src/dns_sd.o
In file included from …/src/dns_sd.cpp:1:0:
…/src/mdns.hpp:32:20: fatal error: dns_sd.h: No such file or directory
#include <dns_sd.h>
^
compilation terminated.
dns_sd_bindings.target.mk:150: recipe for target ‘Release/obj.target/dns_sd_bindings/src/dns_sd.o’ failed
make: *** [Release/obj.target/dns_sd_bindings/src/dns_sd.o] Error 1
make: Leaving directory ‘/home/pi/google-assistant-helper/node_modules/mdns/build’
gyp ERR! build error
gyp ERR! stack Error: make failed with exit code: 2
gyp ERR! stack at ChildProcess.onExit (/opt/nodejs/lib/node_modules/npm/node_modules/node-gyp/lib/build.js:258:23)
gyp ERR! stack at emitTwo (events.js:125:13)
gyp ERR! stack at ChildProcess.emit (events.js:213:7)
gyp ERR! stack at Process.ChildProcess._handle.onexit (internal/child_process.js:200:12)
gyp ERR! System Linux 4.14.70-v7+
gyp ERR! command “/opt/nodejs/bin/node” “/opt/nodejs/lib/node_modules/npm/node_modules/node-gyp/bin/node-gyp.js” “rebuild”
gyp ERR! cwd /home/pi/google-assistant-helper/node_modules/mdns
gyp ERR! node -v v8.8.0
gyp ERR! node-gyp -v v3.6.2
gyp ERR! not ok
npm WARN google-assistant-helper@0.0.1 No repository field.
npm WARN google-assistant-helper@0.0.1 license should be a valid SPDX license expression

npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! mdns@2.4.0 install: node-gyp rebuild
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the mdns@2.4.0 install script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

npm ERR! A complete log of this run can be found in:
npm ERR! /home/pi/.npm/_logs/2018-09-21T01_07_52_888Z-debug.log

You need a library that is used by mdns. Assuming you’re on a Debian variant, it’s

apt-get install libavahi-compat-libdnssd-dev

The library is necessary for Chromecast support.

You sir are brilliant. =D No errors after that!

Glad to hear it’s working out for you. Let me know if you have any issues.

I think I have everything setup, but I’m trying to test with CURL and it keeps failing.
My server is at 10.0.1.101:3001

Here’s my user with a super weak Key (for testing)

“global”: {
“savedTokensPath”: “/root/google-assistant-helper/global-tokens.json”,
“relayKey”: “globalkey”,
“chromecastFriendlyName”: “global”
}

Here’s my tests
curl -H "Content-Type: application/json" -X POST -d {"command": "What time is it","user": "global","relayKey": "globalkey"} http://10.0.1.101:3001

curl -H "Content-Type: application/json" -X POST -d {"command": "What time is it","user": "global","relayKey": "globalkey"} http://10.0.1.101:3001/custom

I’m unsure which url is correct based on the output when I start the program. It shows 5 directories being created, but i don’t know if they are needed. debug: Binding POST route for /broadcast,/broadcastAudio,/custom,/chromecastAudio,/chromecastURL

Here’s my output when I test

root@app01:~/google-assistant-helper# curl -H "Content-Type: application/json" -X POST -d {"command": "What time is it","user": "global","relayKey": "globalkey"} http://10.0.1.101:3001
    curl: (6) Could not resolve host: What time is it,user
    curl: (6) Could not resolve host: global,relayKey
    curl: (3) [globbing] unmatched close brace/bracket in column 10
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="utf-8">
    <title>Error</title>
    </head>
    <body>
    <pre>SyntaxError: Unexpected token c in JSON at position 1<br> &nbsp; &nbsp;at JSON.parse (&lt;anonymous&gt;)<br> &nbsp; &nbsp;at parse (/root/google-assistant-helper/node_modules/body-parser/lib/types/json.js:89:19)<br> &nbsp; &nbsp;at /root/google-assistant-helper/node_modules/body-parser/lib/read.js:121:18<br> &nbsp; &nbsp;at invokeCallback (/root/google-assistant-helper/node_modules/raw-body/index.js:224:16)<br> &nbsp; &nbsp;at done (/root/google-assistant-helper/node_modules/raw-body/index.js:213:7)<br> &nbsp; &nbsp;at IncomingMessage.onEnd (/root/google-assistant-helper/node_modules/raw-body/index.js:273:7)<br> &nbsp; &nbsp;at emitNone (events.js:106:13)<br> &nbsp; &nbsp;at IncomingMessage.emit (events.js:208:7)<br> &nbsp; &nbsp;at endReadableNT (_stream_readable.js:1064:12)<br> &nbsp; &nbsp;at _combinedTickCallback (internal/process/next_tick.js:139:11)</pre>
    </body>
    </html>

For this type of inquiry, the /custom endpoint is the right one. I should go ahead and disallow posts to other endpoints; the error should be somewhat more opaque. Are you not getting audio back on your Chromecasts in response to the query? Can you paste the log when you use the second test?

You don’t need all of the endpoints necessarily; you can turn them off in config.json (each of the relays has an "on" property that you can set to false.)

Here’s everything from one example:
https://pastebin.com/Gv2e0W1r

In your curl request, you need to enclose the whole JSON object in quotes, i.e. -d "{ [...] }". Further, you need to escape all the quotation marks in the JSON, e.g. {\"command\" [...] \"globalkey\"}. It really is much easier to test things like this with Postman.

Ah ha!

root@app01:~/google-assistant-helper# curl -H "Content-Type: application/json" -X POST -d '{"command": "What time is it", "user": "global", "relayKey": "globalkey"}' http://10.0.1.101:3001/custom 
{"result":"Executed What time is it"}

Awesome, I’ll start in on a webcore thing and post it later.

I recall you were attempting to create an alarm clock that turned off after 2 minutes - I’ve added Chromecast control to the server (see README.md) which will allow you to execute a command to STOP an ongoing cast, using the same user. To use via webcore, just add another block with the call with a wait command.

Alternatively, there is a heretofore undocumented delayInSecs argument that the server accepts on most requests, in seconds that you can use. Let me know how it works out for you.

I’m still fighting with Webcore to get my test working.

I created a virtual switch to test starting and stopping a radio station.

Nothing plays, but it does get to the service:

https://pastebin.com/x7wYfTRd

There’s an error in the log: error: Invalid service name, aborting.

Do you have a Chromecast group named global? The name used for Chromecast casting should be the friendly name, which is what the group or device is listed as in the Google Home app. It’s not a special string defined by this server, or anything. If you look at the early part of the log after the service starts, the cast engine should log all the devices it has found and their friendly names.

Look for info: Found service.

Oh, and in case it wasn’t clear, the place to define the correct chromecastFriendlyName is in config.json. See the docs.

Updated to v0.0.3 with added audio response broadcast support! See notes above.