From 76b9de9a8e92d9c27b33b6992738db65e63d2de3 Mon Sep 17 00:00:00 2001 From: Caleb Date: Tue, 20 Oct 2020 02:57:36 -0600 Subject: [PATCH] Use lodash.get() in virtual assistant API endpoints (v2) (#6199) * Fixed #5632 - Improved value resolution * More value resolution improvements * Fixed a couple object paths --- ...add-virtual-assistant-support-to-plugin.md | 5 ++- lib/api/alexa/index.js | 20 ++++------- lib/api/googlehome/index.js | 3 +- lib/plugins/ar2.js | 5 +-- lib/plugins/basalprofile.js | 18 ++++------ lib/plugins/cob.js | 8 +++-- lib/plugins/dbsize.js | 10 ++++-- lib/plugins/iob.js | 5 +-- lib/plugins/loop.js | 12 ++++--- lib/plugins/openaps.js | 11 +++--- lib/plugins/pump.js | 2 +- lib/plugins/rawbg.js | 5 +-- lib/plugins/upbat.js | 5 +-- lib/plugins/xdripjs.js | 35 +++++++++++-------- 14 files changed, 75 insertions(+), 69 deletions(-) diff --git a/docs/plugins/add-virtual-assistant-support-to-plugin.md b/docs/plugins/add-virtual-assistant-support-to-plugin.md index 60ac1d1957b..6c581013b9e 100644 --- a/docs/plugins/add-virtual-assistant-support-to-plugin.md +++ b/docs/plugins/add-virtual-assistant-support-to-plugin.md @@ -36,9 +36,8 @@ There are 2 types of handlers that you can supply: A plugin can expose multiple intent handlers (e.g. useful when it can supply multiple kinds of metrics). Each intent handler should be structured as follows: + `intent` - This is the intent this handler is built for. Right now, the templates used by both Alexa and Google Home use only the `"MetricNow"` intent (used for getting the present value of the requested metric) -+ `metrics` - An array of metric name(s) the handler will supply. e.g. "What is my `metric`" - iob, bg, cob, etc. Make sure to add the metric name and its synonyms to the list of metrics used by the virtual assistant(s). - - **IMPORTANT NOTE:** There is no protection against overlapping metric names, so PLEASE make sure your metric name is unique! - - Note: Although this value *is* an array, you really should only supply one (unique) value, and then add aliases or synonyms to that value in the list of metrics for the virtual assistant. We keep this value as an array for backwards compatibility. ++ `metrics` - An array of metric name(s) the handler will supply. e.g. "What is my `metric`" - iob, bg, cob, etc. Although this value *is* an array, you really should only supply one (unique) value, and then add aliases or synonyms to that value in the list of metrics for the virtual assistant. We keep this value as an array for backwards compatibility. + - **IMPORTANT NOTE:** There is no protection against overlapping metric names, so PLEASE make sure your metric name is unique! + `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 (for devices with a screen). The Google Home response doesn't currently display a card, so it doesn't use this value. diff --git a/lib/api/alexa/index.js b/lib/api/alexa/index.js index 2a5fd4ef6cd..3c31bf1ce00 100644 --- a/lib/api/alexa/index.js +++ b/lib/api/alexa/index.js @@ -1,5 +1,6 @@ 'use strict'; +var _ = require('lodash'); var moment = require('moment'); function configure (app, wares, ctx, env) { @@ -18,7 +19,7 @@ function configure (app, wares, ctx, env) { api.post('/alexa', ctx.authorization.isPermitted('api:*:read'), function (req, res, next) { console.log('Incoming request from Alexa'); - var locale = req.body.request.locale; + var locale = _.get(req, 'body.request.locale'); if(locale){ if(locale.length > 2) { locale = locale.substr(0, 2); @@ -78,19 +79,10 @@ function configure (app, wares, ctx, env) { function handleIntent(intentName, slots, next) { var metric; if (slots) { - if (slots.metric - && slots.metric.resolutions - && slots.metric.resolutions.resolutionsPerAuthority - && slots.metric.resolutions.resolutionsPerAuthority.length - && slots.metric.resolutions.resolutionsPerAuthority[0].status - && slots.metric.resolutions.resolutionsPerAuthority[0].status.code - && slots.metric.resolutions.resolutionsPerAuthority[0].status.code == "ER_SUCCESS_MATCH" - && slots.metric.resolutions.resolutionsPerAuthority[0].values - && slots.metric.resolutions.resolutionsPerAuthority[0].values.length - && slots.metric.resolutions.resolutionsPerAuthority[0].values[0].value - && slots.metric.resolutions.resolutionsPerAuthority[0].values[0].value.name - ){ - metric = slots.metric.resolutions.resolutionsPerAuthority[0].values[0].value.name; + var slotStatus = _.get(slots, 'metric.resolutions.resolutionsPerAuthority[0].status.code'); + var slotName = _.get(slots, 'metric.resolutions.resolutionsPerAuthority[0].values[0].value.name'); + if (slotStatus == "ER_SUCCESS_MATCH" && slotName) { + metric = slotName; } else { next(translate('virtAsstUnknownIntentTitle'), translate('virtAsstUnknownIntentText')); return; diff --git a/lib/api/googlehome/index.js b/lib/api/googlehome/index.js index b44715b25eb..cd42aa0ff37 100644 --- a/lib/api/googlehome/index.js +++ b/lib/api/googlehome/index.js @@ -1,5 +1,6 @@ 'use strict'; +var _ = require('lodash'); var moment = require('moment'); function configure (app, wares, ctx, env) { @@ -18,7 +19,7 @@ function configure (app, wares, ctx, env) { api.post('/googlehome', ctx.authorization.isPermitted('api:*:read'), function (req, res, next) { console.log('Incoming request from Google Home'); - var locale = req.body.queryResult.languageCode; + var locale = _.get(req, 'body.queryResult.languageCode'); if(locale){ if(locale.length > 2) { locale = locale.substr(0, 2); diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index 133c5b8d3c9..ab8675a0342 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -147,8 +147,9 @@ function init (ctx) { }; function virtAsstAr2Handler (next, slots, sbx) { - if (sbx.properties.ar2.forecast.predicted) { - var forecast = sbx.properties.ar2.forecast.predicted; + var predicted = _.get(sbx, 'properties.ar2.forecast.predicted'); + if (predicted) { + var forecast = predicted; var max = forecast[0].mgdl; var min = forecast[0].mgdl; var maxForecastMills = forecast[0].mills; diff --git a/lib/plugins/basalprofile.js b/lib/plugins/basalprofile.js index 4347a7005ec..806dde5859d 100644 --- a/lib/plugins/basalprofile.js +++ b/lib/plugins/basalprofile.js @@ -2,6 +2,7 @@ var times = require('../times'); var moment = require('moment'); var consts = require('../constants'); +var _ = require('lodash'); function init (ctx) { @@ -114,13 +115,13 @@ function init (ctx) { function basalMessage(slots, sbx) { var basalValue = sbx.data.profile.getTempBasal(sbx.time); var response = translate('virtAsstUnknown'); - var preamble = ''; + var pwd = _.get(slots, 'pwd.value'); + var preamble = pwd ? translate('virtAsstPreamble3person', { + params: [ + pwd + ] + }) : translate('virtAsstPreamble'); if (basalValue.treatment) { - preamble = (slots && slots.pwd && slots.pwd.value) ? translate('virtAsstPreamble3person', { - params: [ - slots.pwd.value - ] - }) : translate('virtAsstPreamble'); var minutesLeft = moment(basalValue.treatment.endmills).from(moment(sbx.time)); response = translate('virtAsstBasalTemp', { params: [ @@ -130,11 +131,6 @@ function init (ctx) { ] }); } else { - preamble = (slots && slots.pwd && slots.pwd.value) ? translate('virtAsstPreamble3person', { - params: [ - slots.pwd.value - ] - }) : translate('virtAsstPreamble'); response = translate('virtAsstBasal', { params: [ preamble, diff --git a/lib/plugins/cob.js b/lib/plugins/cob.js index c6d4c4fdf8f..250614ecfd6 100644 --- a/lib/plugins/cob.js +++ b/lib/plugins/cob.js @@ -293,11 +293,13 @@ function init (ctx) { function virtAsstCOBHandler (next, slots, sbx) { var response = ''; - var value = (sbx.properties.cob && sbx.properties.cob.cob) ? sbx.properties.cob.cob : 0; - if (slots && slots.pwd && slots.pwd.value) { + var cob = _.get(sbx, 'properties.cob.cob'); + var pwd = _.get(slots, 'pwd.value'); + var value = cob ? cob : 0; + if (pwd) { response = translate('virtAsstCob3person', { params: [ - slots.pwd.value.replace('\'s', '') + pwd.replace('\'s', '') , value ] }); diff --git a/lib/plugins/dbsize.js b/lib/plugins/dbsize.js index 1698fa050b3..c76e36cb9e8 100644 --- a/lib/plugins/dbsize.js +++ b/lib/plugins/dbsize.js @@ -1,6 +1,7 @@ 'use strict'; var levels = require('../levels'); +var _ = require('lodash'); function init (ctx) { var translate = ctx.language.translate; @@ -117,11 +118,14 @@ function init (ctx) { }; function virtAsstDatabaseSizeHandler (next, slots, sbx) { - if (sbx.properties.dbsize.display) { + var display = _.get(sbx, 'properties.dbsize.display'); + var dataSize = _.get(sbx, 'properties.dbsize.details.dataSize'); + var dataPercentage = _.get(sbx, 'properties.dbsize.dataPercentage'); + if (display) { var response = translate('virtAsstDatabaseSize', { params: [ - sbx.properties.dbsize.details.dataSize - , sbx.properties.dbsize.dataPercentage + dataSize + , dataPercentage ] }); next(translate('virtAsstTitleDatabaseSize'), response); diff --git a/lib/plugins/iob.js b/lib/plugins/iob.js index 96bea03b3ff..cdcc15706e3 100644 --- a/lib/plugins/iob.js +++ b/lib/plugins/iob.js @@ -262,10 +262,11 @@ function init(ctx) { } function getIob(sbx) { - if (sbx.properties.iob && sbx.properties.iob.iob !== 0) { + var iob = _.get(sbx, 'properties.iob.iob'); + if (iob !== 0) { return translate('virtAsstIobUnits', { params: [ - utils.toFixed(sbx.properties.iob.iob) + utils.toFixed(iob) ] }); } diff --git a/lib/plugins/loop.js b/lib/plugins/loop.js index 46d16738787..e7d3ae91c93 100644 --- a/lib/plugins/loop.js +++ b/lib/plugins/loop.js @@ -529,13 +529,14 @@ function init (ctx) { }; function virtAsstForecastHandler (next, slots, sbx) { - if (sbx.properties.loop.lastLoop.predicted) { - var forecast = sbx.properties.loop.lastLoop.predicted.values; + var predicted = _.get(sbx, 'properties.loop.lastLoop.predicted'); + if (predicted) { + var forecast = 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 startPrediction = moment(predicted.startDate); var endPrediction = startPrediction.clone().add(maxForecastIndex * 5, 'minutes'); if (endPrediction.valueOf() < sbx.time) { next(translate('virtAsstTitleLoopForecast'), translate('virtAsstForecastUnavailable')); @@ -573,8 +574,9 @@ function init (ctx) { } function virtAsstLastLoopHandler (next, slots, sbx) { - if (sbx.properties.loop.lastLoop) { - console.log(JSON.stringify(sbx.properties.loop.lastLoop)); + var lastLoop = _.get(sbx, 'properties.loop.lastLoop') + if (lastLoop) { + console.log(JSON.stringify(lastLoop)); var response = translate('virtAsstLastLoop', { params: [ moment(sbx.properties.loop.lastOkMoment).from(moment(sbx.time)) diff --git a/lib/plugins/openaps.js b/lib/plugins/openaps.js index 037680960d2..cf5e240ce99 100644 --- a/lib/plugins/openaps.js +++ b/lib/plugins/openaps.js @@ -560,10 +560,11 @@ function init (ctx) { }; function virtAsstForecastHandler (next, slots, sbx) { - if (sbx.properties.openaps && sbx.properties.openaps.lastEventualBG) { + var lastEventualBG = _.get(sbx, 'properties.openaps.lastEventualBG'); + if (lastEventualBG) { var response = translate('virtAsstOpenAPSForecast', { params: [ - sbx.properties.openaps.lastEventualBG + lastEventualBG ] }); next(translate('virtAsstTitleOpenAPSForecast'), response); @@ -573,11 +574,11 @@ function init (ctx) { } function virtAsstLastLoopHandler (next, slots, sbx) { - if (sbx.properties.openaps.lastLoopMoment) { - console.log(JSON.stringify(sbx.properties.openaps.lastLoopMoment)); + var lastLoopMoment = _.get(sbx, 'properties.openaps.lastLoopMoment'); + if (lastLoopMoment) { var response = translate('virtAsstLastLoop', { params: [ - moment(sbx.properties.openaps.lastLoopMoment).from(moment(sbx.time)) + moment(lastLoopMoment).from(moment(sbx.time)) ] }); next(translate('virtAsstTitleLastLoop'), response); diff --git a/lib/plugins/pump.js b/lib/plugins/pump.js index 7e71c21e1b1..52dc7ade769 100644 --- a/lib/plugins/pump.js +++ b/lib/plugins/pump.js @@ -136,7 +136,7 @@ function init (ctx) { }; function virtAsstReservoirHandler (next, slots, sbx) { - var reservoir = sbx.properties.pump.pump.reservoir; + var reservoir = _.get(sbx, 'properties.pump.pump.reservoir'); if (reservoir || reservoir === 0) { var response = translate('virtAsstReservoir', { params: [ diff --git a/lib/plugins/rawbg.js b/lib/plugins/rawbg.js index 3248126b046..a784f49363f 100644 --- a/lib/plugins/rawbg.js +++ b/lib/plugins/rawbg.js @@ -107,10 +107,11 @@ function init (ctx) { }; function virtAsstRawBGHandler (next, slots, sbx) { - if (sbx.properties.rawbg.mgdl) { + var rawBg = _.get(sbx, 'properties.rawbg.mgdl'); + if (rawBg) { var response = translate('virtAsstRawBG', { params: [ - sbx.properties.rawbg.mgdl + rawBg ] }); next(translate('virtAsstTitleRawBG'), response); diff --git a/lib/plugins/upbat.js b/lib/plugins/upbat.js index dc603054ecb..71547eca6a2 100644 --- a/lib/plugins/upbat.js +++ b/lib/plugins/upbat.js @@ -223,10 +223,11 @@ function init(ctx) { }; function virtAsstUploaderBatteryHandler (next, slots, sbx) { - if (sbx.properties.upbat.display) { + var upBat = _.get(sbx, 'properties.upbat.display'); + if (upBat) { var response = translate('virtAsstUploaderBattery', { params: [ - sbx.properties.upbat.display + upBat ] }); next(translate('virtAsstTitleUploaderBattery'), response); diff --git a/lib/plugins/xdripjs.js b/lib/plugins/xdripjs.js index dc44aad2988..dd5b55eb816 100644 --- a/lib/plugins/xdripjs.js +++ b/lib/plugins/xdripjs.js @@ -324,10 +324,11 @@ function init(ctx) { function virtAsstGenericCGMHandler(translateItem, field, next, sbx) { var response; - if (sbx.properties.sensorState && sbx.properties.sensorState[field]) { + var state = _.get(sbx, 'properties.sensorState.'+field); + if (state) { response = translate('virtAsstCGM'+translateItem, { params:[ - sbx.properties.sensorState[field] + state , moment(sbx.properties.sensorState.lastStateTime).from(moment(sbx.time)) ] }); @@ -355,10 +356,11 @@ function init(ctx) { , metrics: ['cgm session age'] , intentHandler: function(next, slots, sbx){ var response; + var lastSessionStart = _.get(sbx, 'properties.sensorState.lastSessionStart'); // session start is only valid if in a session - if (sbx.properties.sensorState && sbx.properties.sensorState.lastSessionStart) { - if (sbx.properties.sensorState.lastState != 0x1) { - var duration = moment.duration(moment().diff(moment(sbx.properties.sensorState.lastSessionStart))); + if (lastSessionStart) { + if (_.get(sbx, 'properties.sensorState.lastState') != 0x1) { + var duration = moment.duration(moment().diff(moment(lastSessionStart))); response = translate('virtAsstCGMSessAge', { params: [ duration.days(), @@ -384,10 +386,11 @@ function init(ctx) { intent: 'MetricNow' , metrics: ['cgm tx age'] , intentHandler: function(next, slots, sbx){ + var lastTxActivation = _.get(sbx, 'properties.sensorState.lastTxActivation'); next( translate('virtAsstTitleCGMTxAge'), - (sbx.properties.sensorState && sbx.properties.sensorState.lastTxActivation) - ? translate('virtAsstCGMTxAge', {params:[moment().diff(moment(sbx.properties.sensorState.lastTxActivation), 'days')]}) + lastTxActivation + ? translate('virtAsstCGMTxAge', {params:[moment().diff(moment(lastTxActivation), 'days')]}) : translate('virtAsstUnknown') ); } @@ -402,22 +405,24 @@ function init(ctx) { , metrics: ['cgm battery'] , intentHandler: function(next, slots, sbx){ var response; - var sensor = sbx.properties.sensorState; - if (sensor && (sensor.lastVoltageA || sensor.lastVoltageB)) { - if (sensor.lastVoltageA && sensor.lastVoltageB) { + var lastVoltageA = _.get(sbx, 'properties.sensorState.lastVoltageA'); + var lastVoltageB = _.get(sbx, 'properties.sensorState.lastVoltageB'); + var lastBatteryTimestamp = _.get(sbx, 'properties.sensorState.lastBatteryTimestamp'); + if (lastVoltageA || lastVoltageB) { + if (lastVoltageA && lastVoltageB) { response = translate('virtAsstCGMBattTwo', { params:[ - (sensor.lastVoltageA / 100) - , (sensor.lastVoltageB / 100) - , moment(sensor.lastBatteryTimestamp).from(moment(sbx.time)) + (lastVoltageA / 100) + , (lastVoltageB / 100) + , moment(lastBatteryTimestamp).from(moment(sbx.time)) ] }); } else { - var finalValue = sensor.lastVoltageA ? sensor.lastVoltageA : sensor.lastVoltageB; + var finalValue = lastVoltageA ? lastVoltageA : lastVoltageB; response = translate('virtAsstCGMBattOne', { params:[ (finalValue / 100) - , moment(sensor.lastBatteryTimestamp).from(moment(sbx.time)) + , moment(lastBatteryTimestamp).from(moment(sbx.time)) ] }); }