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

DialogFlow APIv2 Integration (GoogleHome) - dev #4035

Closed
wants to merge 60 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
7f3ab74
Merge pull request #1 from nightscout/master
mdomox Mar 23, 2018
1b7f56a
Merge pull request #2 from nightscout/master
mdomox Apr 20, 2018
051af65
Merge pull request #3 from nightscout/dev
mdomox Apr 20, 2018
1b09851
Merge pull request #6 from nightscout/dev
mdomox Jul 29, 2018
5da1217
Greek language update
mdomox Jul 31, 2018
736fa70
Rename boluscalc.js to boluscalc-----.js
mdomox Aug 1, 2018
3d15c6b
Add files via upload
mdomox Aug 1, 2018
7c272ec
Merge pull request #3704 from nightscout/release/0.10.3
sulkaharo Aug 5, 2018
b3f7d65
Tag matafiles with 0.10.3-master-20180805
sulkaharo Aug 5, 2018
b632833
Merge pull request #9 from nightscout/master
mdomox Aug 7, 2018
ca3f518
Fixes the site for iOS 9 and older
sulkaharo Aug 12, 2018
5523622
Name too long, please switch back to BWP
viderehh Aug 14, 2018
8a23db9
Merge pull request #3839 from viderehh/patch-1
PieterGit Aug 26, 2018
fa769b5
Create issue templates for NS repo
danamlewis Oct 4, 2018
b2b8aad
Merge pull request #3970 from nightscout/danamlewis-patch-1
PieterGit Oct 13, 2018
39382aa
Create googlehome.js
mdomox Oct 28, 2018
16a55c6
Delete googlehome.js
mdomox Oct 28, 2018
6e8a9b9
Create googlehome.js
mdomox Oct 28, 2018
6d87b33
Create index.js
mdomox Oct 28, 2018
7ab3c27
Update index.js
mdomox Oct 28, 2018
f590a92
Update bootevent.js
mdomox Oct 28, 2018
0bd9731
Update iob.js
mdomox Oct 28, 2018
542e2e9
Update basalprofile.js
mdomox Oct 28, 2018
dd6c0b6
Update bootevent.js
mdomox Oct 28, 2018
f0f601b
Update cob.js
mdomox Oct 28, 2018
2a94037
Update cob.js
mdomox Oct 28, 2018
be26a15
Update cob.js
mdomox Oct 28, 2018
c4fa77d
Update openaps.js
mdomox Oct 28, 2018
a8e7d26
Update cob.js
mdomox Oct 28, 2018
6ecb5a2
Update googlehome.js
mdomox Oct 30, 2018
5a8b509
Update index.js
mdomox Oct 30, 2018
b435fae
Update index.js
mdomox Oct 30, 2018
bed31ef
Update index.js
mdomox Oct 30, 2018
cf22127
Update googlehome.js
mdomox Oct 30, 2018
c046e86
Update googlehome.js
mdomox Oct 30, 2018
8029ddf
Update googlehome.js
mdomox Oct 30, 2018
577e18f
Update googlehome.js
mdomox Oct 30, 2018
78d0188
Update googlehome.js
mdomox Oct 30, 2018
2935268
Merge pull request #10 from mdomox/GoogleHome_APIv2
mdomox Oct 30, 2018
04dae21
Merge branch 'master' into mdomox-googlehome
mdomox Oct 31, 2018
025561e
Update googlehome.js
mdomox Oct 31, 2018
3f8f7a1
Update language.js
mdomox Oct 31, 2018
2e4f279
Update language.js
mdomox Oct 31, 2018
8a88010
Merge pull request #11 from nightscout/dev
mdomox Oct 31, 2018
5019a98
Update swagger.json
mdomox Oct 31, 2018
8f827cf
Update swagger.yaml
mdomox Oct 31, 2018
742e914
Update npm-shrinkwrap.json
mdomox Oct 31, 2018
9cc12f9
Update package.json
mdomox Oct 31, 2018
163fff0
Merge pull request #12 from mdomox/mdomox-googlehome
mdomox Oct 31, 2018
0ce7964
Create googlehome.md
mdomox Oct 31, 2018
1f8d09a
Update README.md
mdomox Oct 31, 2018
38581d3
Merge pull request #13 from mdomox/dev
mdomox Oct 31, 2018
b544022
Update googlehome.md
mdomox Oct 31, 2018
c72c944
Delete boluscalc-----.js
mdomox Oct 31, 2018
50d3f29
Create googlehome-plugin.md
mdomox Oct 31, 2018
73b2bd5
Delete googlehome.md
mdomox Oct 31, 2018
a353446
Update README.md
mdomox Oct 31, 2018
4ccb7b6
Update boluscalc.js
mdomox Oct 31, 2018
8a40bc6
Update index.js
mdomox Oct 31, 2018
10c4a0c
Update cob.js
mdomox Nov 1, 2018
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
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ Community maintained fork of the
- [`loop` (Loop)](#loop-loop)
- [`xdrip-js` (xDrip-js)](#xdrip-js-xdrip-js)
- [`alexa` (Amazon Alexa)](#alexa-amazon-alexa)
- [`googlehome` (DialogFlow)](#googlehome-dialogflow)
- [`speech` (Speech)](#speech-speech)
- [`cors` (CORS)](#cors-cors)
- [Extended Settings](#extended-settings)
Expand Down Expand Up @@ -469,7 +470,10 @@ 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](lib/plugins/alexa-plugin.md)


##### `googlehome` (DialogFlow)
Integration with Google's GoogleHome-DialogFlow, [detailed setup instructions](/docs/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/googlehome-plugin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
Nightscout GoogleHome - DialogFlow Plugin
======================================

## Overview

To add GoogleHome 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 GoogleHome 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 @@ -64,6 +64,10 @@ function create (env, ctx) {
if (ctx.alexa) {
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
}]
};

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What API call changes are needed to have this live in the Home plugin? basalprofile.js should only contain basalprofile code.

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 @@ -310,6 +310,25 @@ function init(ctx) {
, intentHandler: alexaCOBHandler
}]
};

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);
}
Copy link
Member

@sulkaharo sulkaharo Nov 9, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder how we could change this code (and the Alexa implementation) so that COB plugin would really only have the COB features and any other plugin that needs COB data would contain the COB processing in the plugin itself? I actually failed the Alexa code review - we should aim for clean separation of concerns and cob.js containing any code that has to do with Alexa and Home is architecturally nasty.

Alternatively we could have a single method call here that outputs a speech synthesis friendly string, that gets reused across Home and Alexa


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 @@ -287,6 +287,21 @@ function init(ctx) {
, intentHandler: alexaIOBIntentHandler
}]
};

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
}]
};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here let's aim to restructure both Home and Alexa so the IOB reporting code is in the plugin, not in iob.js

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternatively, would it be possible to add a single method that output a string for speech and reuse that in both Home and Alexa? That'd maybe be the cleanest solution?


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 @@ -491,6 +491,30 @@ function init(ctx) {
, intentHandler: alexaLastLoopHandler
}]
};

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
}]
};

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And same here - Home and Alexa code for creating the speech user interface should be in the plugin itself. What'd be acceptable for this file is a generic API method that returns a string for speech, which is reused in both Home and Alexa speech implementations, but custom method for both adds a lot of maintenance burden

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

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

next( );
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@PieterGit let's try to figure out if there's a way to reduce plugin-specific code in this section

Expand Down