Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update Alexa integration and add Google Home support #4980

Merged
merged 50 commits into from
Oct 21, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
f81298f
Release 0.12.3
sulkaharo Jul 30, 2019
d767102
Merge pull request #1 from nightscout/master
inventor96 Jul 30, 2019
202402f
Copied work done by mdomox
inventor96 Sep 6, 2019
e36a6f1
Updates and fixes to CONTRIBUTING.md
inventor96 Sep 6, 2019
93f8eb9
Spacing unification
inventor96 Sep 7, 2019
3b183a2
One more fix for CONTRIBUTING.md
inventor96 Sep 7, 2019
a7bf320
Minor code formatting improvements
inventor96 Sep 7, 2019
ce00c94
One more time...
inventor96 Sep 7, 2019
efa35c4
Renamed Alexa stuff to virtAsst for generic-ness
inventor96 Sep 7, 2019
51c8d2c
Corrected missed translate() text
inventor96 Sep 8, 2019
58162c2
Updated googlehome plugin to mimic the alexa plugin
inventor96 Sep 8, 2019
b39eb8f
Changed order of operations
inventor96 Sep 8, 2019
8a20015
Fixed parameter referencing in googlehome
inventor96 Sep 8, 2019
90562a3
Yet another CONTRIBUTING fix
inventor96 Sep 8, 2019
855b64d
Removed extra google stuff
inventor96 Sep 8, 2019
2238067
Migrated standalone intents to MetricNow intent
inventor96 Sep 8, 2019
b3a47b3
Simplified route handling
inventor96 Sep 8, 2019
79f0217
Added logging
inventor96 Sep 8, 2019
cc56ef8
Added forgotten path selector
inventor96 Sep 8, 2019
0b1ce34
Separated instructions for adding virtual assistant support in a plugin
inventor96 Sep 8, 2019
0d85439
A few typo fixes
inventor96 Sep 8, 2019
f74d6a8
Improved logging
inventor96 Sep 8, 2019
c266cab
Updated Google Home plugin instructions
inventor96 Sep 9, 2019
895407d
Attempt to trigger download of template file
inventor96 Sep 9, 2019
478b25b
Small wording tweaks
inventor96 Sep 9, 2019
c562e7d
Updated Alexa plugin documentation
inventor96 Sep 10, 2019
3563158
Updated test files
inventor96 Sep 10, 2019
0a48c1d
Re-added handler count tests so devs are prompted to write tests for …
inventor96 Sep 10, 2019
658ca16
Updated Alexa documentation
inventor96 Sep 10, 2019
2b3d783
Small typo fix
inventor96 Sep 10, 2019
73d4e04
Clarification
inventor96 Sep 10, 2019
f946f4f
Further clarifications and typos
inventor96 Sep 10, 2019
6ff8aa3
Added language info to Google Home plugin doc
inventor96 Sep 12, 2019
92d2f70
URL correction
inventor96 Sep 12, 2019
41c0335
URL fix v2
inventor96 Sep 12, 2019
f188f4f
Wording clarification
inventor96 Sep 12, 2019
071d980
Ugh...
inventor96 Sep 12, 2019
313bb60
Minor instruction fix
inventor96 Sep 12, 2019
67ae84d
Sub steps fix
inventor96 Sep 12, 2019
dd6e691
Fixed Alexa references in Google Home
inventor96 Sep 12, 2019
6032aab
Added a couple steps for improved user experience
inventor96 Sep 12, 2019
87261af
One more forgotten step
inventor96 Sep 12, 2019
450fbc2
Updated pump reservoir handler to handle undefined values
inventor96 Sep 16, 2019
bf57dfe
Updated titles and unknown-value responses
inventor96 Sep 16, 2019
ddd8f63
Modified forecast responses to use translate()
inventor96 Sep 16, 2019
d3346fe
Updated tests
inventor96 Sep 16, 2019
6dee062
Improved training phrases
inventor96 Sep 16, 2019
61884f2
Wording improvements
inventor96 Oct 16, 2019
0fc121c
Google Home setup instruction corrections
inventor96 Oct 19, 2019
3cb3f47
Corrected how metric selection is found
inventor96 Oct 20, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Copied work done by mdomox
  • Loading branch information
inventor96 committed Sep 6, 2019
commit 202402fbc42c08f058174fd35bb97bd9823ee273
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ Community maintained fork of the
- [`override` (Override Mode)](#override-override-mode)
- [`xdrip-js` (xDrip-js)](#xdrip-js-xdrip-js)
- [`alexa` (Amazon Alexa)](#alexa-amazon-alexa)
- [`googlehome` (Google Home/DialogFLow)](#googlehome-google-homedialogflow)
- [`speech` (Speech)](#speech-speech)
- [`cors` (CORS)](#cors-cors)
- [Extended Settings](#extended-settings)
Expand Down Expand Up @@ -511,6 +512,9 @@ To learn more about the Nightscout API, visit https://YOUR-SITE.com/api-docs.htm
##### `alexa` (Amazon Alexa)
Integration with Amazon Alexa, [detailed setup instructions](docs/plugins/alexa-plugin.md)

##### `googlehome` (Google Home/DialogFLow)
Integration with Google Home (via DialogFlow), [detailed setup instructions](docs/plugins/googlehome-plugin.md)

##### `speech` (Speech)
Speech synthesis plugin. When enabled, speaks out the blood glucose values, IOB and alarms. Note you have to set the LANGUAGE setting on the server to get all translated alarms.

Expand Down
34 changes: 34 additions & 0 deletions docs/plugins/googlehome-plugin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
Nightscout Google Home/DialogFlow Plugin
========================================

## Overview

To add Google Home support for your Nightscout site, here's what you need to do:

1. Activate the `googlehome` plugin on your Nightscout site, so your site will respond correctly to Google's requests.
2. Create a custom DialogFlow agent that points at your site and defines certain questions you want to be able to ask. (You'll copy and paste a basic template for this, to keep things simple.)
3. Create desired integrations with DialogFlow


## Activate the Nightscout Google Home Plugin

1. Your Nightscout site needs to be new enough that it supports the `googlehome` plugin. .
2. Add `googlehome` to the list of plugins in your `ENABLE` setting. ([Environment variables](https://github.com/nightscout/cgm-remote-monitor#environment) are set in the configuration section for your monitor. Typically Azure, Heroku, etc.)

## Create Your DialogFlow Agent

### Signin to DialogFlow

- Sign in to DialogFlow with your Google account (https://console.dialogflow.com/api-client/#/login). If you don't already have one, signup with Google.

### Create a new custom DialogFlow agent

1. Select "Create new agent" in the main menu bar.
2. Input a custom name for your agent and click "CREATE".
3. Download the simple agent template : ( https://drive.google.com/drive/folders/18z2kQSEInvH4O_jfjB4Qh8z9508P9Oao?usp=sharing )
4. Select IMPORT FROM ZIP , in order to import the template.
5. SAVE
6. Go to "Fullfillment" menu and enter details about your webhook.
7. SAVE
8. Go to "Integration" menu and select your desired integration.
9. Follow instructions for each desired integration.
115 changes: 115 additions & 0 deletions lib/api/googlehome/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
'use strict';

var _ = require('lodash');
var moment = require('moment');

function configure (app, wares, ctx, env) {
var express = require('express');
var api = express.Router();
var entries = ctx.entries;
var translate = ctx.language.translate;

// invoke common middleware
api.use(wares.sendJSONStatus);
// text body types get handled as raw buffer stream
api.use(wares.bodyParser.raw());
// json body types get handled as parsed json
api.use(wares.bodyParser.json());

ctx.plugins.eachEnabledPlugin(function each(plugin) {
if (plugin.googleHome) {
if (plugin.googleHome.intentHandlers) {
console.log('Plugin ' + plugin.name + ' is Google Home enabled');
_.each(plugin.googleHome.intentHandlers, function (handler) {
if (handler) {
ctx.googleHome.configureIntentHandler(handler.intent, handler.intentHandler, handler.routableSlot, handler.slots);
}
});
}
} else {
console.log('Plugin ' + plugin.name + ' is not Google Home enabled');
}
});

ctx.googleHome.configureIntentHandler('CurrentMetric', function (result, next, sbx) {
entries.list({count: 1}, function(err, records) {
var response = '';
if (records && records.length > 0) {
var direction = '';
if (records[0].direction === 'FortyFiveDown') {
direction = ' and slightly dropping';
} else if (records[0].direction === 'FortyFiveUp') {
direction = ' and slightly rising';
} else if (records[0].direction === 'Flat') {
direction = ' and holding';
} else if (records[0].direction === 'SingleUp') {
direction = ' and rising';
} else if (records[0].direction === 'SingleDown') {
direction = ' and dropping';
} else if (records[0].direction === 'DoubleDown') {
direction = ' and rapidly dropping';
} else if (records[0].direction === 'DoubleUp') {
direction = ' and rapidly rising';
}
response = buildPreamble(result.parameters);
response += sbx.scaleMgdl(records[0].sgv) + direction + ' as of ' + moment(records[0].date).from(moment(sbx.time));
} else {
response = buildPreamble(result.parameters) + 'unknown';
}
next(response);
});
}, 'metric', ['bg', 'blood glucose', 'blood sugar', 'number']);

api.post('/googlehome', ctx.authorization.isPermitted('api:*:read'), function (req, res, next) {
console.log('Incoming request from Google Home');
onIntent(req.body, function (response) {
res.json(ctx.googleHome.buildResponse(response));
next();
});
});

function buildPreamble(parameters) {
var preamble = '';
if (parameters && parameters.givenName) {
preamble = parameters.givenName + '\'s current ';
} else {
preamble = 'Your current ';
}
if (parameters && parameters.readingType) {
preamble += parameters.readingType + ' is ';
} else {
preamble += 'blood glucose is ';
}
return preamble;
}

function onIntent(body, next) {
console.log('Received intent request');
console.log(JSON.stringify(body));
handleIntent(body, next);
}

// https://docs.api.ai/docs/webhook#section-format-of-request-to-the-service
function handleIntent(body, next) {
var displayName = body.queryResult.intent.displayName;
var metric = body.queryResult.parameters ? body.queryResult.parameters.metric : null;
var handler = ctx.googleHome.getIntentHandler(displayName, metric);
if (handler) {
var sbx = initializeSandbox();
handler(body.queryResult, next, sbx);
} else {
next('I\'m sorry I don\'t know what you\'re asking for');
}
}

function initializeSandbox() {
var sbx = require('../../sandbox')();
sbx.serverInit(env, ctx);
ctx.plugins.setProperties(sbx);
return sbx;
}

return api;
}

module.exports = configure;
4 changes: 4 additions & 0 deletions lib/api/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ function create (env, ctx) {
app.all('/alexa*', require('./alexa/')(app, wares, ctx, env));
}

if (ctx.googleHome) {
app.all('/googlehome*', require('./googlehome/')(app, wares, ctx, env));
}

return app;
}

Expand Down
14 changes: 14 additions & 0 deletions lib/plugins/basalprofile.js
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,20 @@ function init (ctx) {
}]
};

function googleHomeCurrentBasalhandler (result, next, sbx) {
var pwd = result.parameters && result.parameters.givenName ? result.parameters.givenName : null;
next(basalMessage(pwd, sbx));
}

basal.googleHome = {
intentHandlers: [{
intent: 'CurrentMetric'
, routableSlot:'metric'
, slots:['basal', 'current basal']
, intentHandler: googleHomeCurrentBasalhandler
}]
};

return basal;
}

Expand Down
19 changes: 19 additions & 0 deletions lib/plugins/cob.js
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,25 @@ function init (ctx) {
}]
};

function googleHomeCOBHandler(result, next, sbx) {
var preamble = result && result.parameters && result.parameters.givenName ? result.parameters.givenName + ' has' : 'You have';
var value = 'no';
if (sbx.properties.cob && sbx.properties.cob.cob !== 0) {
value = Math.round(sbx.properties.cob.cob);
}
var response = preamble + ' ' + value + ' carbohydrates on board';
next(response);
}

cob.googleHome = {
intentHandlers: [{
intent: 'CurrentMetric'
, routableSlot:'metric'
, slots:['cob', 'carbs on board', 'carbohydrates on board', 'carbohydrates']
, intentHandler: googleHomeCOBHandler
}]
};

return cob;

}
Expand Down
59 changes: 59 additions & 0 deletions lib/plugins/googlehome.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@

function init(env, ctx) {

console.log('Configuring Google Home...');

function googleHome() {
return googleHome;
}

var intentHandlers = {};

googleHome.configureIntentHandler = function configureIntentHandler(intent, handler, routableSlot, slotValues) {
if (!intentHandlers[intent]) {
intentHandlers[intent] = {};
}
if (routableSlot && slotValues) {
for (var i = 0, len = slotValues.length; i < len; i++) {
if (!intentHandlers[intent][routableSlot]) {
intentHandlers[intent][routableSlot] = {};
}
if (!intentHandlers[intent][routableSlot][slotValues[i]]) {
intentHandlers[intent][routableSlot][slotValues[i]] = {};
}
intentHandlers[intent][routableSlot][slotValues[i]].handler = handler;
}
} else {
intentHandlers[intent].handler = handler;
}
};

googleHome.getIntentHandler = function getIntentHandler(intentName, metric) {
if (intentName && intentHandlers[intentName]) {
if (metric && intentHandlers[intentName]['metric'] &&
intentHandlers[intentName]['metric'][metric] &&
intentHandlers[intentName]['metric'][metric].handler) {
return intentHandlers[intentName]['metric'][metric].handler;
} else if (intentHandlers[intentName].handler) {
return intentHandlers[intentName].handler;
} else {
return null;
}
} else {
return null;
}

};

googleHome.buildResponse = function buildResponse(output) {
return {
fulfillmentText: output
// , fulfillmentMessages: [output]
, source: 'Nightscout'
};
};

return googleHome;
}

module.exports = init;
15 changes: 15 additions & 0 deletions lib/plugins/iob.js
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,21 @@ function init(ctx) {
}]
};

function googleHomeIOBIntentHandler(result, next, sbx) {
var preamble = result && result.parameters && result.parameters.givenName ? result.parameters.givenName + ' has ' : 'You have ';
var message = preamble + getIob(sbx) + ' insulin on board';
next(message);
}

iob.googleHome = {
intentHandlers: [{
intent: 'CurrentMetric'
, routableSlot: 'metric'
, slots: ['iob', 'insulin on board', 'insulin']
, intentHandler: googleHomeIOBIntentHandler
}]
};

return iob;

}
Expand Down
24 changes: 24 additions & 0 deletions lib/plugins/openaps.js
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,30 @@ function init (ctx) {
}]
};

function googleHomeForecastHandler(response, next, sbx) {
if (sbx.properties.openaps && sbx.properties.openaps.lastEventualBG) {
var response = 'The Open APS eventual BG is ' + sbx.properties.openaps.lastEventualBG;
next(response);
}
}

function googleHomeLastLoopHandler (response, next, sbx) {
var response = 'The last successful loop was ' + moment(sbx.properties.openaps.lastLoopMoment).from(moment(sbx.time));
next(response);
}

openaps.googleHome = {
intentHandlers: [{
intent: 'CurrentMetric'
, routableSlot: 'metric'
, slots: ['openaps', 'openaps forecast', 'forecast']
, intentHandler: googleHomeForecastHandler
}, {
intent: 'LastLoop'
, intentHandler: googleHomeLastLoopHandler
}]
};

function statusClass (prop, prefs, sbx) {
var level = statusLevel(prop, prefs, sbx);
return levels.toStatusClass(level);
Expand Down
4 changes: 4 additions & 0 deletions lib/server/bootevent.js
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,10 @@ function boot (env, language) {
ctx.alexa = require('../plugins/alexa')(env, ctx);
}

if (env.settings.isEnabled('googlehome')) {
ctx.googleHome = require('../plugins/googlehome')(env, ctx);
}

next( );
}

Expand Down