UNPKG

taas-client

Version:
579 lines (464 loc) 18.9 kB
var ClientAPI = require('./taas-client') , async = require('async') , util = require('util') ; new ClientAPI.ClientAPI({ steward: { name: '127.0.0.1' } }).on('open', function(channel, loginP) { if (loginP) throw new Error('script should be run locally'); }).on('ready', function(channel, data) { if (channel === 'management') return getToWork(this); if (data.indexOf('read') !== -1) throw new Error('script requires write access'); }).on('close', function(channel) { console.log(channel + ' close'); process.exit(1); }).on('error', function(err, channel) { console.log(channel + ' error: ' + err.message); process.exit(1); }); var activities = { activity1 : { name : 'plugged-in at home' , event : 'condition1' , task : 'action1' } , activity2 : { name : 'plugged-in at charging station' , event : 'condition2' , task : 'action2' } , activity3 : { name : 'charging at charging station' , event : 'condition3' , task : 'action3' } , activity4 : { name : 'completion at charging station' , event : 'condition4' , task : 'action4' } , activity5 : { name : 'vehicle secured when not at home' , event : 'condition5' , task : 'action5' } , activity6 : { name : 'status when stopped' , event : 'condition6' , task : 'action6' } , activity7 : { name : 'status when moving' , event : 'condition7' , task : 'action7' } , activity8 : { name : 'monitor charging status' , event : 'condition8' , task : 'action8' } }; var events = { condition1 : { name : 'charger not plugged in at home, at night' , operator : 'and' , subordinates : [ 'stoppedForAtLeast5m' , 'atNight' , 'needsChargeAtHome' , 'notPluggedIn' ] } , condition2 : { name : 'charger not plugged in at charging station' , operator : 'and' , subordinates : [ 'stoppedForAtLeast5m' , 'needsChargeAway' , 'notPluggedIn' ] } , condition3 : { name : 'charger plugged in, but not charging' , operator : 'and' , subordinates : [ 'needsChargeAway' , 'pluggedInNotCharging' ] } , condition4 : { name : 'charging complete in about 5m' , operator : 'and' , subordinates : [ 'notAtHomeCharger' , 'nowCharging' , 'chargingDoneWithin5m' ] } , condition5 : { name : 'vehicle secured when not at home' , operator : 'and' , subordinates : [ 'notAtHomeCharger' , 'stoppedForAtLeast5m' , 'notLockedAndClosed' ] } , condition6 : { name : 'status when stopped' , operator : 'and' , subordinates : [ 'stoppedForAtLeast5m' ] } , condition7 : { name : 'status when moving' , operator : 'and' , subordinates : [ 'moving' , 'hourly' ] } , condition8 : { name : 'charger status' , operator : 'or' , subordinates : [ 'nowCharging' , 'doneCharging' , 'pluggedInNotCharging' , 'notPluggedIn' ] } , stoppedForAtLeast5m : { operator : 'and' , subordinates : [ 'stopped' , 'sameCycleForAtLeast5m' ] } , moving : { condition : { operator: 'greater-than', operand1: '.[.velocity].', operand2: 0 } } , hourly : { observe : { actor : 'place/1', observe: 'cron', parameter: '@hourly' } } , stopped : { condition : { operator: 'equals', operand1: '.[.velocity].', operand2: 0 } } , sameCycleForAtLeast5m: { condition : { operator: 'greater-than', operand1: '.[.cycleTime.ago].', operand2: 299 } } , atNight : { condition : { operator: 'equals', operand1: '.[.location.solar].', operand2: 'night' } } , atHomeCharger : { condition : { operator: 'less-than', operand1: '.[.distance].', operand2: 1 } } , notAtHomeCharger : { condition : { operator: 'greater-than', operand1: '.[.distance].', operand2: 0 } } , needsChargeAtHome : { operator : 'and' , subordinates : [ 'atHomeCharger' , 'homeChargeLow' ] } , homeChargeLow : { condition : { operator: 'less-than', operand1: '.[.batteryLevel.0].', operand2: '.[.batteryLevel.1].' } } , notPluggedIn : { condition : { operator: 'equals', operand1: '.[.charger].', operand2: 'disconnected' } } , atChargingStation : { condition : { operator: 'less-than', operand1: '.[.station.distance].', operand2: 1 } } , needsChargeAway : { operator : 'and' , subordinates : [ 'atChargingStation' , 'travelChargeLow' ] } , travelChargeLow : { condition : { operator: 'less-than', operand1: '.[.batteryLevel.0].', operand2: '.[.batteryLevel.2].' } } , needsCharge : { operator : 'or' , subordinates : [ 'needsChargeAtHome' , 'needsChargeAway' ] } , pluggedInNotCharging : { condition : { operator: 'equals', operand1: '.[.charger].', operand2: 'connected' } } , nowCharging : { condition : { operator: 'equals', operand1: '.[.charger].', operand2: 'charging' } } , doneCharging : { condition : { operator: 'equals', operand1: '.[.charger].', operand2: 'completed' } } , chargingDoneWithin5m : { condition : { operator: 'less-than', operand1: '.[.batteryLevel.3].', operand2: 301 } } , notLockedAndClosed : { operator : 'or' , subordinates : [ 'doorsNotLocked' , 'frunkOpen' , 'trunkOpen' , 'sunroofOpen' ] } , doorsNotLocked : { condition : { operator: 'not-equals', operand1: '.[.doors].', operand2: 'locked' } } , frunkOpen : { condition : { operator: 'equals', operand1: '.[.frunk].', operand2: 'open' } } , trunkOpen : { condition : { operator: 'equals', operand1: '.[.trunk].', operand2: 'open' } } , sunroofOpen : { condition : { operator: 'equals', operand1: '.[.sunroof].', operand2: 'open' } } // LATER: temperature adjustments, vehicle moving away from associated user(s) }; var tasks = { action1 : { name : 'charger not plugged in at home, at night' , behavior : { perform : 'growl' , parameter : { priority: 'warning', message: '.[.[device]..name]. not plugged into home charger' } } } , action2 : { name : 'charger not plugged in at charging station' , behavior : { perform : 'growl' , parameter : { priority: 'warning', message: '.[.[device]..name]. not plugged in at charging station' } } } , action3 : { name : 'charger plugged in, but not charging' , behavior : { perform : 'growl' , parameter : { priority: 'warning', message: '.[.[device]..name]. charger plugged in, but not charging' } } } , action4 : { name : 'charging complete in about 5m' , behavior : { perform : 'growl' , parameter : { priority: 'warning', message: '.[.[device]..name]. charging complete in about 5m' } } } , action5 : { name : 'vehicle not secured' , behavior : { perform : 'growl' , parameter : { priority: 'warning', message: '.[.[device]..name]. vehicle not secured' } } } , action6 : { name : 'status when stopped' , behavior : { perform : 'growl' , parameter : { message: '.[.[device]..name]. at .[.[device]..physical]., range: .[.[device]..range].km' } } } , action7 : { name : 'status when moving' , behavior : { perform : 'growl' , parameter : { message: '.[.[device]..name]. at .[.[device]..physical]., speed: .[.[device]..velocity].m/s, range: .[.[device]..range].km, charge: .[.[device]..batteryLevel.0].%' } } } , action8 : { name : 'charing status' , behavior : { perform : 'growl' , parameter : { message: '.[.[device]..name]. charger .[.[device]..charger].' } } } }; var getToWork = function(client) { var actor, entry, foundP; if (!preflight(client)) process.exit(1); foundP = false; for (actor in client.actors) { if (!client.actors.hasOwnProperty(actor)) continue; entry = client.actors[actor]; if (entry.whatami !== '/device/motive/tesla/model-s') continue; getToWorkOn(client, entry); foundP = true; } if (foundP) return; console.log('no Tesla Motors sedans found!'); process.exit(1); }; var preflight = function(client) { var activity, activityID, actor, entry, eventID, failP, growler, taskID; for (eventID in events) if (events.hasOwnProperty(eventID)) events[eventID].id = eventID; for (actor in client.actors) { if (!client.actors.hasOwnProperty(actor)) continue; entry = client.actors[actor]; if (entry.whatami.indexOf('/device/indicator/') !== 0) continue; if (entry.whatami.lastIndexOf('/text') !== (entry.whatami.length - 5)) continue; growler = entry.whoami; if (entry.whatami === '/device/indicator/twitter/text') break; } if (!growler) { console.log('no growl performers found!'); return false; } for (taskID in tasks) if (tasks.hasOwnProperty(taskID)) { tasks[taskID].id = taskID; if (!!tasks[taskID].behavior) tasks[taskID].behavior.actor = growler; } failP = false; for (activityID in activities) if (activities.hasOwnProperty(activityID)) { activity = activities[activityID]; activity.id = activityID; if ((!findEvent(events, activity.event)) || (!findTask(tasks, activity.task))) failP = true; } return !failP; }; var active = 0; var uuid = 'b31e1ff0-d63a-11e3-9c1a-0800200c9a66'; var getToWorkOn = function(client, entry) { var activityID, c1, c2; entry.deviceID = entry.whoami.split('/')[1]; c1 = clone(events); c2 = clone(tasks); for (activityID in activities) if (activities.hasOwnProperty(activityID)) { getToWorkOnWithActivity(client, entry, activities[activityID], c1, c2); } }; var getToWorkOnWithActivity = function(client, entry, activity, events, tasks) { var event, task; active++; event = findEvent(events, activity.event); task = findTask(tasks, activity.task); async.series( [ function(callback) { active++; createEvent(client, entry, event, events, function(err) { active--; callback(err); }); } , function(callback) { active++; createTask(client, entry, task, tasks, function(err) { active--; callback(err); }); } , function(callback) { active++; client.createActivity('monitor ' + entry.name + ' ' + activity.name, uuid + ':activity:' + activity.id + ':' + entry.deviceID, event.actor, task.actor, true, '', function(message) { if ((!message.result) && (!message.error)) return; active--; if ((!!message.result) && (!!message.result.activity)) return callback(null, 'activity/' + message.result.activity); if ((!!message.error) && (!!message.error.videlicet)) return callback(null, message.error.videlicet); callback(new Error('create ' + activity.name + ' returns ' + JSON.stringify(message))); }); } ], function(err, results) { if (!!err) return console.error(err); if (!!results) console.log(entry.name + ' ' + activity.name + ': ' + results[results.length - 1]); active--; if (active !== 0) return; console.log('done.'); process.exit(0); }); }; var createGroup = function(client, entry, type, group, parent, callback) { var f, i, subordinates; if (!!group.actor) return callback(null, group.actor); subordinates = []; if (type === 'event') { f = createEvent; for (i = 0; i < group.subordinates.length; i++) subordinates.push(findEvent(parent, group.subordinates[i])); } else { f = createTask; for (i = 0; i < group.subordinates.length; i++) subordinates.push(findTask(parent, group.subordinates[i])); } async.map(subordinates, function(e, cb) { f(client, entry, e, parent, cb); }, function(err, results) { for (i = 0; i < group.subordinates.length; i++) group.subordinates[i].actor = results[i]; client.createGroup(entry.name + ' ' + group.id, uuid + ':group:' + group.id + ':' + entry.deviceID, type, group.operator, results, '', function(message) { if ((!message.result) && (!message.error)) return; if ((!!message.result) && (!!message.result.group)) { group.actor = 'group/' + message.result.group; return callback(null, group.actor); } if ((!!message.error) && (!!message.error.videlicet)) { group.actor = message.error.videlicet; return callback(null, group.actor); } callback(new Error('create ' + group.name + ' returns ' + JSON.stringify(message))); }); }); }; var createEvent = function(client, entry, event, events, callback) { var actor, observe, parameter; if (!!event.operator) return createGroup(client, entry, 'event', event, events, callback); if (!!event.actor) return callback(null, event.actor); if (!!event.condition) { actor = entry.whoami; observe = '.condition'; parameter = event.condition; } else { actor = event.observe.actor; observe = event.observe.observe; parameter = event.observe.parameter; } client.createEvent(entry.name + ' ' + event.id, uuid + ':event:' + event.id + ':' + entry.deviceID, actor, observe, parameter, '', function(message) { if ((!message.result) && (!message.error)) return; if ((!!message.result) && (!!message.result.event)) { event.actor = 'event/' + message.result.event; return callback(null, event.actor); } if ((!!message.error) && (!!message.error.videlicet)) { event.actor = message.error.videlicet; return callback(null, event.actor); } callback(new Error('create ' + event.id + ' returns ' + JSON.stringify(message))); }); }; var createTask = function(client, entry, task, tasks, callback) { var actor, perform, parameter, pattern, replace; if (!!task.operator) return createGroup(client, entry, 'task', task, tasks, callback); if (!!task.actor) return callback(null, task.actor); actor = task.behavior.actor; perform = task.behavior.perform; parameter = task.behavior.parameter; pattern = /\.\[device\]\./g; replace = 'device/'+ entry.deviceID; parameter.message = parameter.message.replace(pattern, replace); parameter.location = '.[.[device]..location].'.replace(pattern, replace); parameter.imageurl = 'http://maps.googleapis.com/maps/api/staticmap?center=.[.[device]..location.0].,.[.[device]..location.1].&zoom=15&size=400x400&maptype=roadmap&markers=icon:http://thethingsystem.com/actors/place-vehicle.png%7Cshadow:false%7C.[.[device]..location.0].,.[.[device]..location.1].&sensor=false'.replace(pattern, replace); client.createTask(entry.name + ' ' + task.id, uuid + ':task:' + task.id + ':' + entry.deviceID, actor, perform, parameter, '', function(message) { if ((!message.result) && (!message.error)) return; if ((!!message.result) && (!!message.result.task)) { task.actor = 'task/' + message.result.task; return callback(null, task.actor); } if ((!!message.error) && (!!message.error.videlicet)) { task.actor = message.error.videlicet; return callback(null, task.actor); } callback(new Error('create ' + task.id + ' returns ' + JSON.stringify(message))); }); }; var findEvent = function(events, eventID) { var event, i; event = events[eventID]; if (!event) { console.log('no eventID: ' + eventID); return; } if (!!event.operator) for (i = 0; i < event.subordinates.length; i++) if (!findEvent(events, event.subordinates[i])) return; return event; }; var findTask = function(tasks, taskID) { var i, task; task = tasks[taskID]; if (!task) { console.log('no taskID: ' + taskID); return; } if (!!task.operator) for (i = 0; i < task.subordinates.length; i++) if (!findTask(tasks, task.subordinates[i])) return; return task; }; var clone = function(o) { var prop, result; if ((!o) || ((typeof o) !== 'object')) return o; result = util.isArray(o) ? [] : {}; for (prop in o) if (o.hasOwnProperty(prop)) result[prop] = clone(o[prop]); return result; };