Alexa integration from some friends of Nightscout

This commit is contained in:
Jason Calabrese
2016-11-06 00:41:15 -07:00
parent fd55fe13bd
commit a47d8e0cbf
13 changed files with 636 additions and 6 deletions
+163
View File
@@ -0,0 +1,163 @@
'use strict';
var moment = require('moment');
var expand = require('expand-braces');
var _ = require('lodash');
function configure (app, wares, ctx, env) {
var entries = ctx.entries;
var express = require('express')
, api = express.Router( )
;
// 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.alexa) {
if (plugin.alexa.intentHandlers) {
console.log(plugin.name + ' is Alexa enabled');
_.each(plugin.alexa.intentHandlers, function (route) {
if (route) {
ctx.alexa.configureIntentHandler(route.intent, route.intentHandler, route.routableSlot, route.slots);
}
});
}
if (plugin.alexa.rollupHandlers) {
console.log(plugin.name + ' is Alexa rollup enabled');
_.each(plugin.alexa.rollupHandlers, function (route) {
console.log('Route');
console.log(route);
if (route) {
ctx.alexa.addToRollup(route.rollupGroup, route.rollupHandler, route.rollupName);
}
});
}
} else {
console.log('Plugin ' + plugin.name + ' is not Alexa enabled');
}
});
api.post('/alexa', function (req, res, next) {
console.log('Incoming request from Alexa');
switch (req.body.request.type) {
case 'IntentRequest':
onIntent(req.body.request.intent, function (title, response) {
res.json(ctx.alexa.buildSpeechletResponse(title, response, '', 'true'));
next( );
});
break;
case 'LaunchRequest':
onLaunch(req.body.request.intent, function (title, response) {
res.json(ctx.alexa.buildSpeechletResponse(title, response, '', 'true'));
next( );
});
break;
case 'SessionEndedRequest':
onSessionEnded(req.body.request.intent, function (alexaResponse) {
res.json(alexaResponse);
next( );
});
break;
}
});
ctx.alexa.addToRollup('Status', function bgRollupHandler(sbx, slots, callback) {
entries.list({count: 1}, function(err, records) {
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';
} else {
direction = records[0].direction;
}
var status = records[0].sgv + direction + ' as of ' + moment(records[0].date).fromNow() + '.';
callback(null, {results: status, priority: -1});
});
// console.log('BG results called');
// callback(null, 'BG results');
}, 'BG Status');
ctx.alexa.configureIntentHandler('MetricNow', function (callback, slots, sbx) {
entries.list({count: 1}, function(err, records) {
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';
} else {
direction = records[0].direction;
}
var status = records[0].sgv + direction + ' as of ' + moment(records[0].date).fromNow();
callback('Current blood glucose', status);
});
}, 'metric', ['bg', 'blood glucose', 'number']);
ctx.alexa.configureIntentHandler('NSStatus', function(callback, slots, sbx) {
ctx.alexa.getRollup('Status', sbx, slots, function (status) {
callback('Full status', status);
});
});
function onLaunch() {
console.log('Session launched');
}
function onIntent(intent, next) {
console.log('Received intent request');
console.log(JSON.stringify(intent));
handleIntent(intent.name, intent.slots, next);
}
function onSessionEnded() {
console.log('Session ended');
}
function handleIntent(intentName, slots, next) {
var handler = ctx.alexa.getIntentHandler(intentName, slots);
if (handler){
var sbx = initializeSandbox();
handler(next, slots, sbx);
} else {
next('Unknown Intent', '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;
+2
View File
@@ -62,6 +62,8 @@ function create (env, ctx) {
// Status first
app.all('/status*', require('./status')(app, wares, env, ctx));
app.all('/alexa*', require('./alexa/')(app, wares, ctx, env));
return app;
}
+1
View File
@@ -97,6 +97,7 @@ function boot (env, language) {
ctx.ddata = require('./data/ddata')();
ctx.dataloader = require('./data/dataloader')(env, ctx);
ctx.notifications = require('./notifications')(env, ctx);
ctx.alexa = require('./plugins/alexa')(env, ctx);
next( );
}
+141
View File
@@ -0,0 +1,141 @@
**Table of Contents**
- [Nightscout/Alexa](#nightscoutalexa)
- [Setup](#setup)
- [Adding alexa support to a plugin](#Adding-alexa-support-to-a-plugin)
- [Intent Handlers](#Intent-Handlers)
- [Rollup handlers](#Rollup-handlers)
Nightscout/Alexa
======================================
##Setup
Sign in to https://developer.amazon.com/ and navigate to the "Alexa" tab. Select "Getting started" in "Alexa Skills Kit"
Click on "Add a new skill". Fill in "Nightscout" as the name and "nightscout" as the invocation name (feel free to use other names as you see fit).
This skill will not use the "Audio Player".
Click Next
Enter the following in the "Intent schema" box
```javascript
{
"intents" : [
{
"intent": "NSStatus"
},{
"intent": "UploaderBattery"
},{
"intent": "PumpBattery"
},{
"intent": "LastLoop"
},{
"intent" : "MetricNow",
"slots" : [{
"name" : "metric",
"type" : "LIST_OF_METRICS"
},{
"name" : "pwd",
"type": "AMAZON.US_FIRST_NAME"
}]
}, {
"intent": "InsulinRemaining",
"slots" : [{
"name" : "pwd",
"type": "AMAZON.US_FIRST_NAME"
}]
}
]
}
```
Add a custom slot named "LIST_OF_METRICS" with the following values
```
bg
blood glucose
number
iob
insulin on board
current basal
basal
cob
carbs on board
carbohydrates on board
loop forecast
ar2 forecast
forecast
raw bg
raw blood glucose
```
Enter the following for "Sample Utterances"
```
NSStatus How am I doing
UploaderBattery How is my uploader battery
PumpBattery How is my pump battery
MetricNow What is my {metric}
MetricNow What my {metric} is
MetricNow What is {pwd} {metric}
InsulinRemaining How much insulin do I have left
InsulinRemaining How much insulin do I have remaining
InsulinRemaining How much insulin does {pwd} have left
InsulinRemaining How much insulin does {pwd} have remaining
LastLoop When was my last loop
```
Click next. Select "HTTPS" for your service endpoint. In the HTTPS URL enter the following
``https://<your nightscout host>/api/v1/alexa``
Select "No" for Account Linking.
Select the appropriate certificate type. For heroku this is typically "My development endpoint is a sub-domain of a domain that has a wildcard certificate from a certificate authority"
Click "Next"
Enable the skill if it isn't already.
You should now be able to interact with Nightscout via Alexa.
##Adding alexa support to a plugin
This document assumes some familiarity with the Alexa interface. You can find more information [here](https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/getting-started-guide).
To add alexa support to a plugin the ``init`` should return an object that contains an "alexa" key. Here is an example:
```javascript
var iob = {
name: 'iob'
, label: 'Insulin-on-Board'
, pluginType: 'pill-major'
, alexa : {
rollupHandlers: [{
rollupGroup: "Status"
, rollupName: "current iob"
, rollupHandler: alexaIOBRollupHandler
}]
, intentHandlers: [{
intent: "MetricNow"
, routableSlot: "metric"
, slots: ["iob", "insulin on board"]
, intentHandler: alexaIOBIntentHandler
}]
}
};
```
There are 2 types of handlers that you will need to supply:
* Intent handler - enables you to "teach" Alexa how to respond to a user's question.
* A rollup handler - enables you to create a command that aggregates information from multiple plugins. This would be akin to the Alexa "flash briefing". An example would be a status report that contains your current bg, iob, and your current basal.
###Intent Handlers
A plugin can expose multiple intent handlers.
+ ``intent`` - this is the intent in the "intent schema" above
+ ``routeableSlot`` - This enables routing by a slot name to the appropriate intent handler for overloaded intents e.g. "What is my <metric>" - iob, bg, cob, etc. This value should match the slot named in the "intent schema"
+ ``slots`` - These are the values of the slots. Make sure to add these values to the appropriate custom slot
+ ``intenthandler`` - this is a callback function that receives 3 arguments
- ``callback`` Call this at the end of your function. It requires 2 arguments
- ``title`` - Title of the handler. This is the value that will be displayed on the Alexa card
- ``text`` - This is text that Alexa should speak.
- ``slots`` - these are the slots that Alexa detected
- ``sandbox`` - This is the nightscout sandbox that allows access to various functions.
###Rollup handlers
A plugin can also expose multiple rollup handlers
+ ``rollupGroup`` - This is the key that is used to aggregate the responses when the intent is invoked
+ ``rollupName`` - This is the name of the handler. Primarily used for debugging
+ ``rollupHandler`` - this is a callback function that receives 3 arguments
- ``slots`` - These are the values of the slots. Make sure to add these values to the appropriate custom slot
- ``sandbox`` - This is the nightscout sandbox that allows access to various functions.
- ``callback`` -
- ``error`` - This would be an error message
- ``response`` - A simple object that expects a ``results`` string and a ``priority`` integer. Results should be the text (speech) that is added to the rollup and priority affects where in the rollup the text should be added. The lowest priority is spoken first. An example callback:
```javascript
callback(null, {results: "Hello world", priority: 1});
```
+113
View File
@@ -0,0 +1,113 @@
var _ = require('lodash');
var async = require('async');
function init(env, ctx) {
console.log('Configuring Alexa.');
function alexa() {
return alexa;
}
var intentHandlers = {};
var rollup = {};
// This configures a router/handler. A routable slot the name of a slot that you wish to route on and the slotValues
// are the values that determine the routing. This allows for specific intent handlers based on the value of a
// specific slot. Routing is only supported on one slot for now.
// There is no protection for a previously configured handler - one plugin can overwrite the handler of another
// plugin.
alexa.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;
}
};
// This function retrieves a handler based on the intent name and slots requested.
alexa.getIntentHandler = function getIntentHandler(intentName, slots) {
if (intentName && intentHandlers[intentName]) {
if (slots) {
var slotKeys = Object.keys(slots);
for (var i = 0, len = slotKeys.length; i < len; i++) {
if (intentHandlers[intentName][slotKeys[i]] && slots[slotKeys[i]].value &&
intentHandlers[intentName][slotKeys[i]][slots[slotKeys[i]].value] &&
intentHandlers[intentName][slotKeys[i]][slots[slotKeys[i]].value].handler) {
return intentHandlers[intentName][slotKeys[i]][slots[slotKeys[i]].value].handler;
}
}
}
if (intentHandlers[intentName].handler) {
return intentHandlers[intentName].handler;
}
return null;
} else {
return null;
}
};
alexa.addToRollup = function(rollupGroup, handler, rollupName) {
if (!rollup[rollupGroup]) {
console.log('Creating the rollup group: ', rollupGroup);
rollup[rollupGroup] = [];
}
rollup[rollupGroup].push({handler: handler, name: rollupName});
// status = _.orderBy(status, ['priority'], ['asc'])
};
alexa.getRollup = function(rollupGroup, sbx, slots, callback) {
var handlers = _.map(rollup[rollupGroup], 'handler');
console.log('Rollup array for ', rollupGroup);
console.log(rollup[rollupGroup]);
var nHandlers = [];
_.each(handlers, function (handler) {
nHandlers.push(handler.bind(null, slots, sbx));
});
async.parallelLimit(nHandlers, 10, function(err, results) {
if (err) {
console.error('Error: ', err);
}
callback(_.map(_.orderBy(results, ['priority'], ['asc']), 'results').join(' '));
});
};
// This creates the expected alexa response
alexa.buildSpeechletResponse = function buildSpeechletResponse(title, output, repromptText, shouldEndSession) {
return {
version: '1.0',
response: {
outputSpeech: {
type: 'PlainText',
text: output
},
card: {
type: 'Simple',
title: title,
content: output
},
reprompt: {
outputSpeech: {
type: 'PlainText',
text: repromptText
}
},
shouldEndSession: shouldEndSession
}
};
};
return alexa;
}
module.exports = init;
+34 -1
View File
@@ -3,6 +3,7 @@
var _ = require('lodash');
var levels = require('../levels');
var times = require('../times');
var moment = require('moment');
var BG_REF = 140; //Central tendency
var BG_MIN = 36; //Not 39, but why?
@@ -19,10 +20,42 @@ function init (ctx) {
var translate = ctx.language.translate;
var alexaAr2Handler = function(next, slots, sbx) {
if (sbx.properties.ar2.forecast.predicted) {
var forecast = sbx.properties.ar2.forecast.predicted;
var max = forecast[0].mgdl;
var min = forecast[0].mgdl;
var maxForecastMills = forecast[0].mills;
for (var i = 1, len = forecast.length; i < len; i++) {
if (forecast[i].mgdl > max) {
max = forecast[i].mgdl;
}
if (forecast[i].mgdl < min) {
min = forecast[i].mgdl;
}
if (forecast[i].mills > maxForecastMills) {
maxForecastMills = forecast[i].mills;
}
}
var response = 'You are expected to be between ' + min + ' and ' + max + ' over the ' + moment(maxForecastMills).fromNow();
next('AR2 Forecast', response);
} else {
next('AR2 Forecast', 'AR2 plugin does not seem to be enabled');
}
};
var ar2 = {
name: 'ar2'
, label: 'AR2'
, pluginType: 'forecast'
, alexa : {
intentHandlers: [{
intent: 'MetricNow'
, routableSlot:'metric'
, slots:['ar2 forecast', 'forecast']
, intentHandler: alexaAr2Handler
}]
}
};
function buildTitle(prop, sbx) {
@@ -282,4 +315,4 @@ function buildDebug (prop, sbx) {
function log10(val) { return Math.log(val) / Math.LN10; }
module.exports = init;
module.exports = init;
+37 -1
View File
@@ -1,14 +1,50 @@
'use strict';
var times = require('../times');
var moment = require('moment');
function init (ctx) {
var translate = ctx.language.translate;
var alexaCurrentBasalhandler = function (next, slots, sbx) {
next('Current Basal', basalMessage(slots, sbx));
};
function basalMessage(slots, sbx) {
var basalValue = sbx.data.profile.getTempBasal(Date.now());
var response = 'Unable to determine current basal';
var preamble = '';
if (basalValue.treatment) {
preamble = (slots && slots.pwd && slots.pwd.value) ? slots.pwd.value + ' has a ' : 'Your ';
var minutesLeft = moment(basalValue.treatment.endmills).fromNow();
response = preamble + 'temp basal of ' + basalValue.totalbasal + ' units per hour will end ' + minutesLeft;
} else {
preamble = (slots && slots.pwd && slots.pwd.value) ? slots.pwd.value + ' ' : 'Your ';
response = preamble + 'current basal is ' + basalValue.totalbasal + ' units per hour';
}
return response;
}
var alexaRollupCurrentBasalHandler = function (slots, sbx, callback) {
callback(null, {results: basalMessage(slots, sbx), priority: 1})
};
var basal = {
name: 'basal'
, label: 'Basal Profile'
, pluginType: 'pill-minor'
, alexa : {
rollupHandlers: [{
rollupGroup: 'Status'
, rollupName: 'current basal'
, rollupHandler: alexaRollupCurrentBasalHandler
}],
intentHandlers: [{
intent: 'MetricNow'
, routableSlot:'metric'
, slots:['basal', 'current basal']
, intentHandler: alexaCurrentBasalhandler
}]}
};
basal.setProperties = function setProperties (sbx) {
@@ -103,4 +139,4 @@ function init (ctx) {
}
module.exports = init;
module.exports = init;
+19 -1
View File
@@ -7,10 +7,28 @@ function init(ctx) {
var translate = ctx.language.translate;
var iob = require('./iob')(ctx);
var alexaCOBHandler = function (next, slots, sbx) {
var preamble = (slots && slots.pwd && slots.pwd.value) ? slots.pwd.value.replace('\'s', '') + ' has' : 'You have';
var value = 'no';
if (sbx.properties.cob.cob && sbx.properties.cob.cob != 0) {
value = sbx.properties.cob.cob;
}
var response = preamble + ' ' + value + ' carbohydrates on board';
next('Current COB', response);
};
var cob = {
name: 'cob'
, label: 'Carbs-on-Board'
, pluginType: 'pill-minor'
, alexa : {
intentHandlers: [{
intent: 'MetricNow'
, routableSlot:'metric'
, slots:['cob', 'carbs on board', 'carbohydrates on board']
, intentHandler: alexaCOBHandler
}]
}
};
cob.setProperties = function setProperties(sbx) {
@@ -73,7 +91,7 @@ function init(ctx) {
if (decaysin_hr > 0) {
//console.info('Adding ' + delayMinutes + ' minutes to decay of ' + treatment.carbs + 'g bolus at ' + treatment.mills);
totalCOB += Math.min(Number(treatment.carbs), decaysin_hr * profile.getCarbAbsorptionRate(treatment.mills, spec_profile));
//console.log("cob:", Math.min(cCalc.initialCarbs, decaysin_hr * profile.getCarbAbsorptionRate(treatment.mills)),cCalc.initialCarbs,decaysin_hr,profile.getCarbAbsorptionRate(treatment.mills));
//console.log('cob:', Math.min(cCalc.initialCarbs, decaysin_hr * profile.getCarbAbsorptionRate(treatment.mills)),cCalc.initialCarbs,decaysin_hr,profile.getCarbAbsorptionRate(treatment.mills));
isDecaying = cCalc.isDecaying;
} else {
totalCOB = 0;
+31
View File
@@ -8,10 +8,41 @@ var _ = require('lodash')
function init(ctx) {
var translate = ctx.language.translate;
var alexaIOBIntentHandler = function (callback, slots, sbx) {
var preamble = (slots && slots.pwd && slots.pwd.value) ? slots.pwd.value.replace('\'s', '') + ' has ' : 'You have ';
var message = preamble + getIob(sbx) + ' insulin on board';
callback('Current IOB', message);
};
var alexaIOBRollupHandler = function (slots, sbx, callback) {
var message = 'and you have ' + getIob(sbx) + ' insulin on board.';
callback(null, {results: message, priority: 2})
};
function getIob(sbx) {
if (sbx.properties.iob.iob && sbx.properties.iob.iob != 0) {
return sbx.properties.iob.iob + ' units of';
}
return 'no';
}
var iob = {
name: 'iob'
, label: 'Insulin-on-Board'
, pluginType: 'pill-major'
, alexa : {
rollupHandlers: [{
rollupGroup: 'Status'
, rollupName: 'current iob'
, rollupHandler: alexaIOBRollupHandler
}]
, intentHandlers: [{
intent: 'MetricNow'
, routableSlot: 'metric'
, slots: ['iob', 'insulin on board']
, intentHandler: alexaIOBIntentHandler
}]
}
};
iob.RECENCY_THRESHOLD = times.mins(30).msecs;
+51
View File
@@ -10,10 +10,60 @@ var levels = require('../levels');
function init(ctx) {
var timeago = require('./timeago')(ctx);
var alexaForecastHandler = function (next, slots, sbx) {
if (sbx.properties.loop.lastLoop.predicted) {
var forecast = sbx.properties.loop.lastLoop.predicted.values;
var max = forecast[0];
var min = forecast[0];
var maxForecastIndex = Math.min(6, forecast.length);
var startPrediction = moment(sbx.properties.loop.lastLoop.predicted.startDate);
var endPrediction = startPrediction.add(maxForecastIndex * 5, 'minutes');
if (endPrediction.valueOf() < moment()) {
next("Loop Forecast", "Unable to forecast with the data that is available");
} else {
for (var i = 1, len = forecast.slice(0, maxForecastIndex).length; i < len; i++) {
if (forecast[i] > max) {
max = forecast[i];
}
if (forecast[i] < min) {
min = forecast[i];
}
}
var value = "";
if (min === max) {
value = "around " + max;
} else {
value = "between " + min + " and " + max;
}
var response = "According to the loop forecast you are expected to be " + value + " over the next " + moment(endPrediction).fromNow(true);
next("Loop Forecast", response);
}
} else {
next("Loop forecast", "Loop plugin does not seem to be enabled");
}
};
var alexaLastLoopHandler = function(next, slots, sbx) {
console.log(JSON.stringify(sbx.properties.loop.lastLoop));
var response = "The last successful loop was " + moment(sbx.properties.loop.lastOkMoment).fromNow();
next("Last loop", response);
};
var loop = {
name: 'loop'
, label: 'Loop'
, pluginType: 'pill-status'
, alexa : {
intentHandlers: [{
intent: "MetricNow"
, routableSlot: "metric"
, slots: ["loop forecast", "forecast"]
, intentHandler: alexaForecastHandler
}, {
intent: "LastLoop"
, intentHandler: alexaLastLoopHandler
}]
}
};
var firstPrefs = true;
@@ -228,6 +278,7 @@ function init(ctx) {
function concatCOB (valueParts) {
if (prop.lastLoop && prop.lastLoop.cob) {
var cob = prop.lastLoop.cob;
var cob = prop.lastLoop.cob.cob;
cob = Math.round(cob);
valueParts = valueParts.concat([
+19 -1
View File
@@ -12,10 +12,28 @@ function init (ctx) {
var timeago = require('./timeago')(ctx);
var openaps = require('./openaps')(ctx);
var alexaReservoirHandler = function(next, slots, sbx) {
var response = 'You have ' + sbx.properties.pump.pump.reservoir + ' units remaining';
next('Remaining insulin', response);
};
var alexaBatteryHandler = function(next, slots, sbx) {
var response = 'Your battery is at ' + sbx.properties.pump.pump.battery.percent + ' percent';
next('Pump battery', response);
};
var pump = {
name: 'pump'
, label: 'Pump'
, pluginType: 'pill-status'
, alexa : {
intentHandlers:[{
intent: 'InsulinRemaining',
intentHandler: alexaReservoirHandler
}, {
intent: 'PumpBattery',
intentHandler: alexaBatteryHandler
}]
}
};
pump.getPrefs = function getPrefs (sbx) {
@@ -296,4 +314,4 @@ function init (ctx) {
return pump;
}
module.exports = init;
module.exports = init;
+14 -1
View File
@@ -6,11 +6,24 @@ function init (ctx) {
var translate = ctx.language.translate;
var alexaRawBGHandler = function (next, slots, sbx) {
var response = 'Your raw bg is ' + sbx.properties.rawbg.mgdl;
next('Current Raw BG', response);
};
var rawbg = {
name: 'rawbg'
, label: 'Raw BG'
, pluginType: 'bg-status'
, pillFlip: true
, alexa : {
intentHandlers: [{
intent: 'MetricNow'
, routableSlot:'metric'
, slots:['raw bg', 'raw blood glucose']
, intentHandler: alexaRawBGHandler
}]
}
};
rawbg.setProperties = function setProperties (sbx) {
@@ -110,4 +123,4 @@ function cleanValues (sgv, cal) {
};
}
module.exports = init;
module.exports = init;
+11 -1
View File
@@ -5,12 +5,22 @@ var times = require('../times');
var levels = require('../levels');
function init() {
var alexaUploaderBatteryHandler = function(next, slots, sbx) {
var response = 'Your uploader battery is at ' + sbx.properties.upbat.level + ' percent';
next('Uploader battery', response);
};
var upbat = {
name: 'upbat'
, label: 'Uploader Battery'
, pluginType: 'pill-status'
, pillFlip: true
, alexa : {
intentHandlers: [{
intent: 'UploaderBattery'
, intentHandler: alexaUploaderBatteryHandler
}]
}
};
upbat.getPrefs = function getPrefs(sbx) {
@@ -217,4 +227,4 @@ function init() {
}
module.exports = init;
module.exports = init;