Update November 20, 2014: Mild update to code — moved the https include to the top in order to eliminate a non-responsive camera module.
TL;DR – I wrote a script on my new Tessel that posts images to Salesforce Chatter and learned a lot along the way. You can find the full Tessel Image Capture script over on Github and you can ask me about it on Twitter @ReidCarlberg. The screen cap above is an automatically captured picture of a 3D printed T-Rex Skull on Salesforce1.
Hello Tessel
The Tessel is a brand new hardware prototyping board that lets you code in Javascript rather than C. I first ran into it during their crowd funding campaign last year, and received my first unit last week. Since then I’ve had a chance to create my first simple app on the device, so today I’m going to share what I’ve learned so far. Tessel Review
If you’re already used to working with hardware prototyping boards, there’s a lot you’re going to like. There are plenty of analog, digital, I2C, SPI and serial ports to go around. You can use these ports to connect any old hardware you already know how to connect, or you can use one of their preconfigured modules. Pretty easy.
One thing that confused me since I first spied the prototype is the positioning of the interface ports. They’re bent at 90 degrees and so rather than facing up, they face out. (Note: in the picture above you can see that my C port is at a weird angle. Bent it somehow.) This design helps a board with a number of modules attached stay flat, which is particularly useful with something like the servo module where you’re going to connect a number of wires in. The GPIO port faces up as you would expect.
Basic interaction is super simple. You access all of the ports via standard Javascript syntax. For example, the blink app (“Hello World”, but for hardware), is just a few lines:
// Import the interface to Tessel hardware var tessel = require('tessel'); // Set the led pins as outputs with initial states // Truthy initial state sets the pin high // Falsy sets it low. var led1 = tessel.led[1].output(1); var led2 = tessel.led[1].output(0); setInterval(function () { console.log("I'm blinking! (Press CTRL + C to stop)"); // Toggle the led states led1.toggle(); led2.toggle(); }, 100);
You can run scripts from the CLI using the command “tessel run myScript.js”. Here’s what running the blink.js example looks like.
$ tessel run blink.js TESSEL! Connected to TM-00-04-f000da30-006a473e-34b02586. WARN No package.json or node_modules found. Deploying just this file. INFO Bundling directory /Users/reid.carlberg/Projects/tessel-blink (~428 bytes) INFO Deploying bundle (4.50 KB)... INFO Running script... I'm blinking! (Press CTRL + C to stop) I'm blinking! (Press CTRL + C to stop) .....
And if you want the script to run headless, you simply use “tessel push myScript.js” and you’re good to go. The script will start automatically as soon as it has power.
The modules are pretty easy to use, and they all come with node libraries and sample code. When you want to use a module, you simply insert it into one of the ports, grab the library via NPM and get to work. Installing the camera module, for example, is as simple as typing “npm install camera-vc0706”. My Tessel Sample App
My sample use case is pretty simple: when something is close enough to the Tessel, snap a picture and upload it to Salesforce. In this example, I connected my own proximity detector and used Tessel camera and climate modules. The first thing I wanted to do was connect to the Salesforce platform the way I usually do, with the excellent nforce library. Now, I’ve used it quite a bit, and have watched the npm install routine probably 100 times without really wondering about all of the components it requires. However, I couldn’t take that same laissez faire attitude when working on the Tessel. Although it is designed to be Node.js compatible, it’s still a limited device and larger, complex modules like nforce are too much for it to handle. This is not that big of a deal. In fact, it was a great excuse for me to get more familiar with some Node.js basics I’d managed to ignore until now, such as the HTTPS request object. Incidentally, before rolling my own OAuth2 and REST interactions, I also attempted to use the excellent request and restler libraries. No luck!
The good news here is that rolling your own autonomous client / username & password flow OAuth2 is pretty easy. Setup your connected app in Salesforce (example), send a few parameters to the endpoint and voila! you have an authorization token.
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); }
You can see I’ve built a few small routines to make this easier, but the main job is done in four lines of code. Add a callback to interpret the results, and I’m off.
Sending an Image to Chatter
The next step is capturing an image and sending it to Chatter. The good people at Tessel have made taking the picture extremely easy. The basic code looks something like:
var camera = require('camera-vc0706').use(tessel.port['A'], { resolution: 'qvga' }); camera.takePicture(function(err, image) { if (err) { log('error taking image', err); } else { //do something with the image base64content = image.toString('base64'); }
On the Chatter side of the fence, if you can send an octet-stream directly to the Chatter API, you can use a single call and post a message and the picture at the same time. In theory, the image buffer should work in here, but I couldn’t get it to work on either the Tessel or my MBP. I ended up taking a note from @Metadaddy and an example from @CCoenraets, and did a 3 step process after base64 encoding the buffer object. Step 1, insert a ContentVersion object. Step 2, lookup the ContentVersion and get the ContentDocumentId. Step 3, insert a FeedItem that references the ContentDocumentId. The final Chatter code looks like this:
var body = { attachment: { attachmentType: "ExistingContent", contentDocumentId: contentVersion.ContentDocumentId }, body: { messageSegments: [{ type: 'Text', text: 'Here is a current picture. \nClimate: ' + currentClimateText }] } }; body = JSON.stringify(body); var chatterOptions = buildRequestOptions('/services/data/v30.0/chatter/feeds/record/me/feed-items', body, 'application/json'); handleHttpsRequest(chatterOptions, body, handleChatterCallback);
The other option for inserting the picture is to create a custom ApexREST web service. This is a good option if you want to minimize API calls, but it adds the extra complexity of creating that bit of Apex. Fortunately, that bit of Apex is easy to create. In fact, I had created something similar for the Share Wonder project I did a while back. Share Wonder, however, inserted the data into an Attachment object rather than a ContentVersion object. Before I wrote the ContentVersion code myself, I search a bit and it turns out that Maxim Fesenko over at Cloud Catamaran had written a blog called File Upload to Chatter Using the ConnectAPI just a few days ago.
My slightly modified version of the code from that blog post looks like this:
/* * Totally copied from * http://cloudcatamaran.com/2014/04/file-upload-to-chatter-with-using-connectapi-class/ */ @RestResource(urlMapping='/TesselImage') global class PostHelper{ @HttpPost global static ConnectApi.FeedItem post(String text, String imageName, String imageBase64) { ConnectApi.MessageBodyInput v_messageInput = new ConnectApi.MessageBodyInput(); v_messageInput.messageSegments = new List(); ConnectApi.TextSegmentInput v_textSegment = new ConnectApi.TextSegmentInput(); v_textSegment.text = text; v_messageInput.messageSegments.add(v_textSegment); ConnectApi.FeedItemInput v_input = new ConnectApi.FeedItemInput(); v_input.body = v_messageInput; ConnectApi.NewFileAttachmentInput v_fileIn = new ConnectApi.NewFileAttachmentInput(); v_fileIn.title = 'title of file'; v_fileIn.description = 'description of file'; v_input.attachment = v_fileIn; ConnectApi.BinaryInput v_feedBinary = new ConnectApi.BinaryInput(EncodingUtil.base64Decode(imageBase64), 'image/jpg', imageName); System.debug('***Just about to insert'); ConnectApi.FeedItem v_feedItem = ConnectApi.ChatterFeeds.postFeedItem(null, ConnectApi.FeedType.Record, 'me', v_input, v_feedBinary); System.debug('***Inserted'); System.debug(v_feedItem); return v_feedItem; } }
And if you’d like to install it in your org, here’s an unmanaged package which yes of course includes full test coverage.
Tessel Lessons Learned
I learned a lot going through this sample app. Some of my key takeaways.
* Base64 encoding takes quite a while. A qqvga image (160 x 120) encodes in about 20 seconds. A larger qvga (320 x 240) takes about 2 minutes. A vga image (640 x 480) takes more like 20 minutes.
* System intensive actions like base64 encoding will block other actions you have setup on an interval. Once the encoding is done, all of the blocked actions will fire.
* Yes, the Tessel uses Javascript, but it’s still a constrained device. You should expect complex, memory intensive operations to be tricky. Every platform has constraints. The Arduino, for example, doesn’t handle JSON parsing well. Just because you can use Javascript on the Tessel, you shouldn’t assume you can use Javascript exactly as you might on more powerful platforms.
* The wifi is a bit tricky. Setup is dead easy — “tessel wifi -n MyNetwork -p MyPassword” — but the Tessel doesn’t have a huge antenna, and reception isn’t the greatest. I found areas of my house that work great for a larger device but not for the Tessel. They actually have a tutorial on how to add an antenna.
Finally, I would like to point out that the crew over at Technical Humans have been super responsive and helpful.
In the End…
In the end, I think the Tessel is a super interesting iteration on the hardware prototyping platform. Coding hardware using Javascript – even if it’s a little different than other places you use Javascript – is very interesting. I hope a lot of people buy one of these boards, because I definitely want to see what the future holds for this extremely interesting team.