Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Tessel Salesforce Chatter Camera Climate
/*
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 = "reid@labs.1029";
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