Automatic, for the People
Update: I've created an unmanaged package for this Force.com application, which can be installed from this link. Also, I added some information on necessary remote site settings that I forgot when I originally wrote this article.
Folks are increasingly moving towards the quantified self: fitness wearables, smart scales, exercise monitoring apps, and scores of other, sometimes crazy ideas. It's only natural that people will start wanting to quantify other parts of their lives, and what better place to start than in their cars, where we all (unfortunately) spend a large amount of our time? The folks at Automatic have just the solution for us in their smart driving assistant.
The Automatic system consists of a device that plugs in to the ODB-II port of your car, and a companion smart phone (iOS released, and Android in beta) app. The app is quite lovely, and helps keep track of mileage, car problems, and poor driving techniques. It can even place a 911 call for you automatically in the case of an accident.
The problem is that this treasure trove of data is locked up inside this app, on your tiny phone screen, unable to be accessed or manipulated. Luckily for us, the folks at Automatic have released an API to let us at all of this data. The Automatic API features both a REST endpoint, to manually retrieve data, and webhooks, to have data automatically sent to a third party endpoint. In this post, we'll learn how to access data with a Force.com app using this API.
Architecture
Our app will use webhooks to receive data in real-time from Automatic to Salesforce. You can think of webhooks as something akin to Apex Triggers: when something happens (a record is inserted, or in this case, a car drive has completed), a notification is sent, usually with data about the event, and that data can be acted on. On the Force.com platform, we write Apex Triggers to handle the data the platform sends us. For webhooks we generally write REST endpoints that the third-party implementing the webhook (Automatic, in our case) can POST to.
Before we can start receiving webhooks, we need to provide a way for our users to log in to their Automatic accounts, and authorize Automatic to make the webhook callouts to our endpoints. To start the authorization flow, our user will navigate to a Visualforce page, which will notify the user of what's about to happen. The user will click a button on that page, which will then redirect their browser to the Automatic OAuth authorization page, prompting for a login if necessary. After successful authorization, the user is redirected BACK to a Visualforce page (the URL for which we provide to Automatic). The controller for this Visualforce page will receive the URL-enclosed temporary token, and exchange it, via Automatic's OAuth endpoint, for a final token.
We'll use Apex REST to create an endpoint, implementing only the POST method. This endpoint will receive data from Automatic, check to see if we've built a handler for the webhook type (in our example we'll only handle trip:finished), and save the data to two new objects we'll create, Vehicle and Trip.
The code and resources for this project are available here on github.
Automatic.com Setup
Before we create our developer account on Automatic, we'll need a Force.com Site set up to anonymously expose our endpoint (which we'll create later). We need to provide Automatic some information about this Site in the developer sign-up process. The instructions creating a Site are here, but we only need to follow steps one through three. We'll be creating a Visualforce page for the Site later.
We have to create an app on the Automatic site. and to do that, we need to get a developer's account at Automatic. (Well, first you need to buy an Automatic device and get a regular user account, but that's a given.) Currently, while the API is in alpha, the developer sign-up is a very manual process: you fill out a Google Doc, which I won't link directly to, since I'm sure this process is very fluid. You can start by going to the “My Apps” section of their developer site, and signing up for API access there. The Google Doc will ask for things like your name and email, but the two most important fields are “Redirect URI” and “Webhook URI”.
For “Redirect URI”, enter “https://c.SALESFORCEPODNAME.visual.force.com/apex/AutomaticOAuth”, replacing SALESFORCEPODNAME with the pod name of your Salesforce instance, such as na15, or cs8. You can find the pod name of your instance in the url of any Salesforce page when you are logged in to your instance.
For “Webhook URI”, enter “https://SITEURL/services/apexrest/AutomaticWebhook”, replacing SITEURL with the full Site URL for the Site you created above. The Site URL can be found in the description of your site. Be sure to include the path of your Site in the URL, if one exists.
Once you submit the Google Doc, you will be emailed when they've set everything up, and returning to the “My Apps” link will show you your client secret and id, which we'll need later.
Salesforce Objects and Custom Settings
We'll use a Custom Setting to store our Automatic application and authentication information. Create a new hierarchical Custom Setting, naming it “AutomaticAPI__c”, with the following fields:
Let's create our Salesforce SObjects next. Vehicle (Vehicls__c) will contain information about the vehicles we hook up to Automatic, and is configured like this:
Don't worry about the formula and roll-up summary fields. They are purely informational. You can get the specific information about these from the github repo linked earlier, and again at the end of this post.
Our Trip object (Trip__c) will contain information about completed trips from automatic. Trips are defined by automatic as the route taken by a vehicle from ignition start to ignition stop. Our Trip object is defined like this:
Again, don't worry about the formula field right now.
Authentication
For the authentication flow, we'll need two Visualforce pages and controllers: one, AutomaticAuthentication, to fill the user in on what's going on, and to forward the user to Automatic to grant access. After access is granted, Automatic will redirect to our second page, AutomaticOAuth, which will finish the authorization flow.
We're using a hierarchical custom setting (AutomaticAPI__c) to store our tokens (and Automatic user ids) at the user level, and to store our Automatic app client id and secret (discussed later) at the org level.
The most interesting part of this flow is the controller for AutomaticOAuth, which receives a temporary token in the URL, and exchanges it via an Automatic endpoint for a real OAuth token:
// Get the current running user's Automatic auth info from our custom setting
AutomaticAPI__c a=AutomaticAPI__c.getInstance(UserInfo.getUserId());
ApexPages.Message failMsg;
// This is the temporary token that we'll exchange for an oauth token
String tempCode=ApexPages.currentPage().getParameters().get('code');
// If it's not there, something's gone wrong, and we need to notify the user
if (tempCode == null) {
failMsg=new ApexPages.Message(ApexPages.Severity.FATAL,'Invalid code.');
ApexPages.addMessage(failMsg);
return;
}
// Here's the state that we sent to the original Automatic OAuth page
String oAuthState=ApexPages.currentPage().getParameters().get('state');
// Let's confirm that what we got back is the same as what we sent....
if (oAuthState != a.state__c) {
a.state__c=null;
upsert a;
// ...and notify the user if it isn't
failMsg=new ApexPages.Message(ApexPages.Severity.FATAL,'States do not match. Possible attack.');
ApexPages.addMessage(failMsg);
return;
}
a.state__c=null;
// Now we turn around and call back out to Automatic's OAuth endpoint to get our final tokens
Http h = new Http();
HttpRequest req = new HttpRequest();
req.setMethod('POST');
String requestParams='client_id='+a.client_id__c;
requestParams+='&client_secret='+a.client_secret__c;
requestParams+='&code='+tempCode;
requestParams+='&grant_type=authorization_code';
req.setEndpoint('https://www.automatic.com/oauth/access_token');
req.setBody(requestParams);
req.setHeader('Content-Type','application/x-www-form-urlencoded');
HttpResponse res;
// Do the callout///
try {
res = h.send(req);
}
// ...and notify the user if there was a problem
catch (CalloutException e) {
failMsg=new ApexPages.Message(ApexPages.Severity.FATAL,e.getMessage());
ApexPages.addMessage(failMsg);
return;
}
String tokenResponse=res.getBody();
Integer tokenResponseCode=res.getStatusCode();
In lines 33-35, we're taking the client id and secret, stored in our custom setting, and sending them, along with our temporary token, to the OAuth endpoint.
Once we successfully get the response, we save the final token to our custom setting:
// Otherwise, deserialize the result....
Map<string, object=""> responseMap=(Map<string, object="">)JSON.deserializeUntyped(tokenResponse);
//... clear out the client secret and id (which we only want stored at the org level)
a.client_secret__c=null;
a.client_id__c=null;
//... set our OAuth token
a.token__c=(String)responseMap.get('access_token');
//... our refresh token
a.refresh__c=(String)responseMap.get('refresh_token');
//... and this user's Automatic id
a.automatic_id__c=(String)((Map<string,object>)responseMap.get('user')).get('id');
upsert a;
And now our user has authorized Salesforce to access Automatic, and has enabled Automatic to make webhook callouts to our REST endpoint.
Webhook Endpoint
Next we need to build our REST endpoint with an Apex class called AutomaticWebhookHandler.cls. This class will receive the webhook call out, deserialize it into an object, and pass it off to a specific handler class, depending on the webhook type. (We are only going to implement the “trip:finished” type here.)
Map<string, object=""> autoResponse;
try {
autoResponse = (Map<string, object="">)JSON.deserializeUntyped(req.requestBody.toString());
}
catch (Exception e) {
// Indicate a failure if we couldn't even deserialize the request. Currently, Automatic does
// nothing with this failure information
res.statusCode = 500;
return;
}
We're using a map of Objects rather than a dedicated class because of the alpha (and possibly changing) nature of the Automatic API. This choice means we have to cast everything we get from the response, but it also means we can withstand changes to the request object better. If there's a problem with deserialization, we return a failure to Automatic.
To handle the actual processing of the incoming data, we look at the “type” key of the returned data, and use its value to instantiate a handler class:
System.Type webhookHandlerType = Type.forName('Automatic'+((String)autoResponse.get('type')).replaceAll('[^a-zA-Z0-9\\s]','')+'Handler');
IAutomaticHandler autoHandler;
// Let's try and grab a handler for the type of webhook callout this is
try {
autoHandler=(IAutomaticHandler)webhookHandlerType.newInstance();
}
// If we can't, let's assume that we don't want to handle whatever type of callout this is, and return a success
// Alternatively, we could return a 500 to flag an error on the Automatic end. At the moment, both choices result
// in the same thing, nothing is created on the Salesforce end
catch (Exception e) {
res.statusCode = 200;
return;
}
// If our handler had a problem, return a 500
if (!autoHandler.handleWebhook(autoResponse)) {
res.statusCode = 500;
return;
}
In the first line, we generate a string from the “type” value (removing any non-alphanumeric characters), prepend and append with “Automatic” and “Handler” respectively, and instantiate a class that conforms to our “IAutomaticHandler” interface. This interface has a single method called “handleWebhook” that returns a true if the method successfully handled the data, or false if not. Based on that boolean, we return a 200 (success) or 500 (failure) to Automatic.
Trip Finished Handler
Since we are going to handle the “trip:finished” type, we'll build a class called “AutomaticTripFinishedHandler.cls”. First we get objects for the various parts of the parent “Trip” object:
Map<string, object=""> vehicle=(Map<string, object="">)webhookRequestBody.get('vehicle');
Map<string, object=""> trip=(Map<string, object="">)webhookRequestBody.get('trip');
Map<string, object=""> autoUser=(Map<string, object="">)webhookRequestBody.get('user');
String vehicleId=(String)vehicle.get('id');
String autoUserId=(String)autoUser.get('id');
The other interesting part of this handler class is how we identify the Salesforce user from the Automatic userId that is sent. When we received our OAuth token, and saved it to our AutomaticAPI custom setting, we also received the Automatic userId, which was also saved to that custom setting. Now we can query AutomaticAPI__c for this userId, and get the Salesforce user id from there:
String userId;
try {
AutomaticAPI__c aAPI= [select id, SetupOwnerId from AutomaticAPI__c where Automatic_Id__c = :autoUserId];
userId=aAPI.SetupOwnerId;
}
// For our purposes, we won't handle the case where we can't find a user, and will just leave this info blank in our
// final saved record
Catch (Exception e) {
}
Wrap up
The last piece we've built is a Visualforce page that does nothing but show a Google map of the route taken for a particular trip, which we put on the Trip__c layout.
Now that we have all of our pieces in place, we need to do a few last things to prepare for our first webhook call out. First, get the client id and client secret from the “My App” page on the Automatic developer site. Put these in the org-wide defaults values for the AutomaticAPI__c custom setting.
Next, we need to enable our AutomaticWebhookHandler class on the Site we built. To do this, go to the detail page for your Site, and click on the “Public Access Settings” button. Scroll down on the resulting page, and look for “Enabled Apex Class Access”, and add our webhook handler class.
Finally, we need to add Automatic to the allowed callout endpoints. Go to “Setup”, then “Security Controls”, then “Remote Site Settings.” Click the “New Remote Site”, and add “https://www.automatic.com” as the remote site url.
At this point, everything should be all set. Go to your Automatic developer dashboard, and issue a test webhook of type “trip:finished”. Back in your Salesforce org, you should have a new Vehicle record, as well as a child Trip record.
So now every time you take a trip in your Automatic-enabled car, it'll show up, fully quantified, in your Salesforce org!
From here, it would be simple to implement more Automatic Handlers to do things like assign “hard brakes” or “rapid accelerations” to Trips. It might also be useful to access Automatic's REST API, and use it to backfill with past Trip data. In fact, it would be pretty straightforward to build a lightweight fleet management app from just Salesforce and Automatic.
My next plan is to eventually build an Apex wrapper for the Automatic API. I'll post about it when I do.
Again, all the code and resources for the project are on github.
Comments ()