diff --git a/app.js b/app.js index 71a64c1..527a0a2 100644 --- a/app.js +++ b/app.js @@ -90,7 +90,10 @@ var flash = require('connect-flash'), Limiter = require('ratelimiter'), requesttracker = require('./requesttracker'), routes = require('./routes'), - MongoConnect = require('./system/mongoconnect'); + MongoConnect = require('./system/mongoconnect'), + uuid = require('uuid/v4'), + request = require('request'), + async = require('async'); // MongoDB connection settings var mongoose = require('mongoose'); @@ -979,6 +982,80 @@ function notifyOpenHABStatusChange(openhab, status) { }); } +if (config.ifttt.enableRealtimeNotifications) { + var lastIftttRealtimeCheck = new Date(); + + setInterval(function() { + var triggerIdentities = []; + + // Find all events updated since our last update and get their trigger identity strings. + Event.find({when: { $gte: lastIftttRealtimeCheck }}) + .sort({when: 'desc'}) + .lean() + .exec(function (error, events) { + lastIftttRealtimeCheck = new Date(); + if (!error) { + var associatedItems = []; + for (var i = 0; i < events.length; i++) { + var event = events[i]; + var itemLookupFn = function(callback) { + Item.find({ name: event.source, openhab: event.openhab }) + .lean() + .exec(function (error, items) { + if (!error) { + for (var i = 0; i < items.length; i++) { + triggerIdentities = triggerIdentities.concat(items[i].ifttt_trigger_identities); + } + } else { + logger.error('openHAB-cloud: Error retrieving updated items for IFTTT realtime notifications'); + } + callback(error); + }); + }; + associatedItems.push(itemLookupFn); + } + + async.parallel(associatedItems, function(error, result) { + triggerIdentities.slice(0, 1000); // IFTTT will only accept up to 1000 realtime notification events. + if (!error) { + if (triggerIdentities.length > 0) { + var requestSettings = { + url: 'https://realtime.ifttt.com/v1/notifications', + method: 'POST', + headers: { + 'IFTTT-Service-Key': config.ifttt.iftttChannelKey, + 'Accept': 'application/json', + 'Accept-Charset': 'utf-8', + 'Accept-Encoding': 'gzip, deflate', + 'X-Request-ID': uuid() + }, + json: true, + body: { + data: triggerIdentities.map(function(t) { + return { trigger_identity: t } + }) + } + }; + + request(requestSettings, function(error, response, body) { + if (!error) { + logger.info('openHAB-cloud: Notified IFTTT realtime API about item updates') + } else { + logger.error('openHAB-cloud: Unable to notify IFTTT realtime API about item updates: ' + error) + } + }); + } + } else { + logger.error('openHAB-cloud: Error retrieving updated items for IFTTT realtime notifications'); + } + }); + } else { + logger.error('openHAB-cloud: Error retrieving events for IFTTT realtime notifications'); + } + }); + }, config.ifttt.realtimeNotificationFreqMsecs); +} + function shutdown() { // TODO: save current request id? logger.info('openHAB-cloud: Stopping every5min statistics job'); diff --git a/config-development.json b/config-development.json index 80872c3..c04db58 100644 --- a/config-development.json +++ b/config-development.json @@ -36,7 +36,9 @@ }, "ifttt" : { "iftttChannelKey" : "key", - "iftttTestToken" : "token" + "iftttTestToken" : "token", + "enableRealtimeNotifications": false, + "realtimeNotificationFreqMsecs": 5000 }, "legal": { "terms" : "", diff --git a/config-production.json b/config-production.json index 6fa525a..e3a9c5c 100644 --- a/config-production.json +++ b/config-production.json @@ -27,7 +27,9 @@ }, "ifttt" : { "iftttChannelKey" : "key", - "iftttTestToken" : "token" + "iftttTestToken" : "token", + "enableRealtimeNotifications": false, + "realtimeNotificationFreqMsecs": 5000 }, "mail": { "host" : "smtp", diff --git a/models/item.js b/models/item.js index 172c553..569ab81 100644 --- a/models/item.js +++ b/models/item.js @@ -3,18 +3,20 @@ var mongoose = require('mongoose'), var ObjectId = mongoose.SchemaTypes.ObjectId; var ItemSchema = new Schema({ - openhab: ObjectId, // openHAB this item belongs to - name: String, // Item name - type: String, // Item type (Group, Switch, Number, etc) - label: String, // Item label ("Dinner lights") - groups: [ObjectId], // An array of ObjectIds of Group typed Items this Item belongs to - icon: String, // icon name for this Item - status: String, // Current Item status - prev_status: String, // Previous status value - last_update: Date, // Date/time of last Item status update - last_change: Date, // Date/time of last Item change - states: [Schema.Types.Mixed] // We cache last X (50?) states of the item in this array - // in a form of {when: Date, value: String}, latest values first in array + openhab: ObjectId, // openHAB this item belongs to + name: String, // Item name + type: String, // Item type (Group, Switch, Number, etc) + label: String, // Item label ("Dinner lights") + groups: [ObjectId], // An array of ObjectIds of Group typed Items this Item belongs to + icon: String, // icon name for this Item + status: String, // Current Item status + prev_status: String, // Previous status value + last_update: Date, // Date/time of last Item status update + last_change: Date, // Date/time of last Item change + states: [Schema.Types.Mixed], // We cache last X (50?) states of the item in this array + // in a form of {when: Date, value: String}, latest values first in array + ifttt_trigger_identities: + {type: [String], default: []} // IFTTT Trigger Identities associated with this item }, { versionKey: false, safe: false, diff --git a/package.json b/package.json index b06973d..58ad68a 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ }, "dependencies": { "apn": "1.7.6", + "async": "^3.1.0", "bcrypt-cache": "^1.0.2", "bcrypt": "1.0.2", "body-parser": "^1.17.2", diff --git a/routes/ifttt.js b/routes/ifttt.js index f8e6262..a83ebe0 100644 --- a/routes/ifttt.js +++ b/routes/ifttt.js @@ -13,6 +13,8 @@ var system = require('../system'); var iftttChannelKey = app.config.ifttt.iftttChannelKey // IFTTT access token for testing the API var iftttTestToken = app.config.ifttt.iftttTestToken +// IFTTT enable realtime notifications +var enableRealtimeNotifications = app.config.ifttt.enableRealtimeNotifications function ensureIFTTTChannelKey (req, res, next) { if (!req.headers.hasOwnProperty('ifttt-channel-key')) { @@ -195,6 +197,17 @@ exports.v1triggeritemstate = [ var itemStatus = req.body.triggerFields.status; Item.findOne({openhab: openhab._id, name: itemName}, function (error, item) { if (!error && item) { + if (enableRealtimeNotifications) { + var triggerIdentity = req.body.trigger_identity; + if(item.ifttt_trigger_identities.indexOf(triggerIdentity) === -1) { + item.ifttt_trigger_identities.push(triggerIdentity); + item.save(function(error) { + if (error) { + return res.status(400).json({errors: [{message: "Failed to save trigger"}]}); + } + }); + } + } if (eventLimit > 0) { Event.find({openhab: openhab._id, source: item.name, status: itemStatus}) .sort({when: 'desc'}) @@ -251,6 +264,17 @@ exports.v1triggeritem_raised_above = [ var value = req.body.triggerFields.value; Item.findOne({openhab: openhab._id, name: itemName}, function (error, item) { if (!error && item) { + if (enableRealtimeNotifications) { + var triggerIdentity = req.body.trigger_identity; + if(item.ifttt_trigger_identities.indexOf(triggerIdentity) === -1) { + item.ifttt_trigger_identities.push(triggerIdentity); + item.save(function(error) { + if (error) { + return res.status(400).json({errors: [{message: "Failed to save trigger"}]}); + } + }); + } + } if (eventLimit > 0) { // console.log(value); Event.find({openhab: openhab._id, source: item.name}) @@ -310,6 +334,17 @@ exports.v1triggeritem_dropped_below = [ var value = req.body.triggerFields.value; Item.findOne({openhab: openhab._id, name: itemName}, function (error, item) { if (!error && item) { + if (enableRealtimeNotifications) { + var triggerIdentity = req.body.trigger_identity; + if(item.ifttt_trigger_identities.indexOf(triggerIdentity) === -1) { + item.ifttt_trigger_identities.push(triggerIdentity); + item.save(function(error) { + if (error) { + return res.status(400).json({errors: [{message: "Failed to save trigger"}]}); + } + }); + } + } if (eventLimit > 0) { // console.log(value); Event.find({openhab: openhab._id, source: item.name})