Last active
August 29, 2015 14:03
-
-
Save ReidCarlberg/f415641392f5cd0f3632 to your computer and use it in GitHub Desktop.
Tessel Salesforce Chatter Camera Climate
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
by Reid Carlberg | |
Last update: November 20, 2014 | |
Questions? Ask me on Twitter. https://Twitter.com/ReidCarlberg | |
Basic use case: monitor range finder. When something is close, snap a picture, post to Salesforce Chatter. | |
Sign up for a free developer edition to try this code | |
http://developer.salesforce.com/signup | |
You'll need to setup a Connected App in your developer edition for OAuth2 stuff. Detailed instructions: | |
http://reidcarlberg.com/2014/02/21/philips-hue-raspberry-pi-node-js-salesforce-and-you/ | |
You'll also need a Tessel. Buy one. | |
https://www.trycelery.com/shop/TEC | |
Assumes you have a range finder on the GPIO port | |
When something is relatively close (distance < 40) it takes a picture and uploads that picture to Chatter. | |
I'm using this one: | |
https://www.adafruit.com/products/172 | |
Notes about camera base64 encoding times: | |
https://forums.tessel.io/t/camera-image-base64-encoding-time | |
Notes about climate data calibration: | |
https://forums.tessel.io/t/how-are-these-calibrated-before-shipping/332 | |
Notes about uploading a VGA pic and Error 256 message: | |
https://forums.tessel.io/t/error-256-colony-modules-dns-js-30/346 | |
*/ | |
// 2014-11-20 - This now needs to be at the top or the camera module doesn't respond | |
var https = require('https'); | |
//var fs = require('fs'); //only need this is you're testing with the filesystem instead of the camera | |
var tessel = require('tessel'); | |
var camera = require('camera-vc0706').use(tessel.port['A'], { resolution: 'qvga' }); //small because base64 encoding takes forever | |
var climatelib = require('climate-si7005'); | |
var climate = climatelib.use(tessel.port['B']); | |
var gpio = tessel.port['GPIO']; | |
var pin = gpio.pin['A1']; | |
var notificationLED = tessel.led[1]; // Set up an LED to notify when we're taking a picture | |
var statusCode = 200; | |
var count = 1; | |
var sfuser = "[email protected]"; | |
var sfpass = "hello1234"; | |
var clientid = "3MVG9A2kN3Bn17hsFC3SC.lI0cKhSarI1T0yqrEL27wkOJ48aGNEXuin_MGdReQJ9.TjBiEKlVumcCfh8oqkK"; | |
var clientsecret = "7462680620119491012"; | |
var image; | |
var orgResults; | |
var content; | |
var base64content; | |
var intervalManager; | |
var currentClimateText; | |
/* | |
set this to TRUE to use the ApexREST API instead of the standard platform REST API | |
advantage is that you post a picture with 1 API call with ApexREST vs 3 with standard REST. | |
note that you must have an org configured with an Apex REST service. | |
Package to install a sample ApexREST service as used here into your developer edition: | |
https://login.salesforce.com/packaging/installPackage.apexp?p0=04ti0000000Cw5G | |
Note: thanks to Cloud Catamaran Maxim Fesenko who's code I shamelessly cribbed for the ApexREST service. | |
http://cloudcatamaran.com/2014/04/file-upload-to-chatter-with-using-connectapi-class/ | |
That's nice work! | |
*/ | |
var useApexREST = false; | |
/* | |
set this to TRUE if you want to see the various debug outputs. False suppresses them. | |
*/ | |
var debug = true; | |
/* | |
Body | |
*/ | |
log('Welcome to Reid\'s First Tessel App'); | |
//kick off the whole thing | |
start(); | |
function start() { | |
//handleOauth(); //kicks everything off | |
//readfile(); //testing -- kicks everything off without a tessel | |
pollDistance(); | |
} | |
function pollDistance() { | |
intervalManager = setInterval(function() { readPin(); }, 1000); | |
} | |
/* | |
functions -- snaps a picture, reads climate, posts to chatter, logs out | |
*/ | |
function handleOauth() { | |
log('handleOauth'); | |
var body = 'grant_type=password&client_id='+clientid+'&client_secret='+clientsecret+ | |
'&username='+sfuser+'&password='+sfpass | |
var options = buildRequestOptions('/services/oauth2/token', body, 'application/x-www-form-urlencoded'); | |
options.hostname = 'login.salesforce.com'; | |
log(options); | |
handleHttpsRequest(options, body, handleOauthCallback); | |
} | |
function handleOauthCallback(data) { | |
orgResults = JSON.parse(data); | |
if (useApexREST) { | |
handleApexREST(); | |
} else { | |
handleContentVersion(); | |
} | |
} | |
/* | |
Thanks to @ccoenraets and @metadaddy who pointed me in the ContentVersion direction. | |
*/ | |
function handleContentVersion() { | |
log('in ContentVersion'); | |
var contentVersion = { | |
Origin: 'H', // 'H' for a Chatter File, 'C' for a Content document | |
PathOnClient: 'image.jpg', // Hint as to type of data | |
VersionData: base64content // Base64 encoded file data | |
}; | |
contentVersion = JSON.stringify(contentVersion); | |
var options = buildRequestOptions('/services/data/v30.0/sobjects/ContentVersion', | |
contentVersion, 'application/json'); | |
handleHttpsRequest(options, contentVersion, contentVersionCallback); | |
} | |
function contentVersionCallback(data) { | |
//do something with d | |
var contentVersionResults = JSON.parse(data); | |
var query = "Select Id, ContentDocumentId From ContentVersion where Id = '" + contentVersionResults.id + "'"; | |
handleSimpleQuery(query, contentDocumentCallback); | |
} | |
function contentDocumentCallback(data) { | |
var contentVersionResults = JSON.parse(data); | |
log(contentVersionResults); | |
handleChatter(contentVersionResults.records[0]); | |
} | |
function handleSimpleQuery(queryString, callback) { | |
queryString = encodeURIComponent(queryString); | |
var options = buildRequestOptions('/services/data/v30.0/query?q=' + queryString, null, null, 'GET'); | |
handleHttpsRequest(options, null, callback); | |
} | |
function handleChatter(contentVersion) { | |
var body = { | |
attachment: { | |
attachmentType: "ExistingContent", | |
contentDocumentId: contentVersion.ContentDocumentId | |
}, | |
body: { | |
messageSegments: [ | |
{ | |
type: 'Text', | |
text: 'Here is a current picture. \nClimate: ' + currentClimateText | |
} | |
] | |
} | |
}; | |
body = JSON.stringify(body); | |
log(body); | |
var chatterOptions = buildRequestOptions('/services/data/v30.0/chatter/feeds/record/me/feed-items', body, 'application/json'); | |
log(chatterOptions); | |
handleHttpsRequest(chatterOptions, body, handleChatterCallback); | |
} | |
function handleChatterCallback(data) { | |
log(data); | |
logout(); | |
} | |
/* | |
optional method -- ApexREST -- One API Call instead of 3 | |
*/ | |
function handleApexREST() { | |
log('in Apex REST'); | |
var body = { | |
"text": "This is a test - Apex REST \nClimate: " + currentClimateText, | |
"imageName": "test1.jpg", | |
"imageBase64": base64content | |
}; | |
body = JSON.stringify(body); | |
var options = buildRequestOptions('/services/apexrest/TesselImage', body, 'application/json'); | |
handleHttpsRequest(options, body, apexRESTCallback); | |
} | |
function apexRESTCallback(data) { | |
log('in Apex REST callback'); | |
logout(); | |
} | |
function logout() { | |
var body = "token=" + orgResults.access_token; | |
log(body); | |
var options = buildRequestOptions('/services/oauth2/revoke', body, 'application/x-www-form-urlencoded'); | |
options.hostname = "login.salesforce.com"; | |
handleHttpsRequest(options,body,logoutCallback); | |
} | |
function logoutCallback(data) { | |
log(data); | |
log('done'); | |
stopActivityLight(); | |
reset(); | |
} | |
function reset() { | |
stopActivityLight(); | |
image = null; | |
content = null; | |
base64content = null; | |
start(); | |
} | |
function setCurrentClimateText() { | |
climate.readTemperature('f', function (err, temp) { | |
climate.readHumidity(function (err, humid) { | |
currentClimateText = 'Degrees: ' + temp.toFixed(4) + 'F, Humidity: ' + humid.toFixed(4) + ' %RH'; | |
handleOauth(); | |
}); | |
}); | |
} | |
/* | |
Camera | |
*/ | |
function handleTakePicture() { | |
startActivityLight(); | |
log('in handleTakePicture'); | |
camera.takePicture(function(err, image) { | |
if (err) { | |
log('error taking image', err); | |
} else { | |
var startDate = new Date(); | |
log('starting encoding...' + startDate); | |
base64content = image.toString('base64'); | |
var stopDate = new Date(); | |
log('encoding complete...' + stopDate); | |
log('encoding time ' + (stopDate - startDate)); | |
setCurrentClimateText(); | |
} | |
}); | |
} | |
// Wait for the camera module to say it's ready | |
camera.on('ready', function() { | |
log('camera is ready'); | |
}); | |
camera.on('error', function(err) { | |
log('camera reports error'); | |
console.error(err); | |
}); | |
tessel.button.on('press', function(time) { | |
log('button was released', time); | |
handleTakePicture(); | |
}); | |
function readPin() { | |
var distance = Math.round(pin.read() * 1000); | |
log('reading pin ' + distance); | |
//log(distance); | |
if (distance < 40 && distance > 5) { | |
log(distance); | |
handleTakePicture(); | |
clearInterval(intervalManager); | |
} | |
} | |
function startActivityLight() { | |
notificationLED.output(1); | |
} | |
function stopActivityLight() { | |
notificationLED.output(0); | |
} | |
/* | |
Utility - HTTPS | |
*/ | |
function buildRequestOptions(path, body, contentType, method) { | |
var options = { | |
"port": 443, | |
"path": path, | |
"method": 'POST', | |
"headers": { | |
'Accept': 'application/json' | |
} | |
}; | |
if (method) { | |
options.method = method; | |
} | |
if (body) { | |
options.headers["Content-Type"] = contentType; | |
options.headers["Content-Length"] = body.length; | |
} | |
if (orgResults) { | |
options.hostname = orgResults.instance_url.substring(8); | |
options.headers.Authorization = "OAuth " + orgResults.access_token; | |
} | |
return options; | |
} | |
function handleHttpsRequest(options, body, dataCallback) { | |
var req = https.request(options, function(res) { | |
var dataString = ''; | |
res.on('data', function(d) { | |
log('data event'); | |
dataString+=d.toString(); | |
}); | |
res.on('end', function() { | |
log('end event'); | |
if (dataCallback) { | |
dataCallback(dataString); | |
} | |
}) | |
}); | |
req.on('error', function(e) { | |
log('Error: ' + e); | |
log('Aborting'); | |
}); | |
//execute | |
if (body) { | |
req.write(body); | |
req.write('\n'); | |
} | |
log('2'); | |
req.end(); | |
} | |
function log(something) { | |
if (debug && something) { | |
console.log(something); | |
} | |
} | |
/* | |
for testing off of the tessel | |
*/ | |
/* | |
function readfile() { | |
fs.readFile('./small.jpg', function read(err, data) { | |
if (err) { | |
throw err; | |
} | |
content = data; | |
base64content = content.toString('base64'); | |
log('converted to base64'); | |
handleOauth(); | |
}); | |
} | |
*/ | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment