Easy IoT: Associating an Electric Imp Device with a Salesforce User

Learn how to associate an Electric Imp device with a specific Salesforce user, and create custom object and Case records from the Electric Imp agent.

In my blog post last month, I explained how Electric Imp’s Salesforce library allows you to access the Force.com REST API from the imp’s server-side agent component, ‘upserting’ a record in Salesforce at the press of a button on the imp device. This time, I’m going to demonstrate how to associate the device with a specific user, and show you how to create custom object and Case records.

Note that this article assumes some basic knowledge of the OAuth protocol. OAuth allows apps to obtain API tokens from Salesforce and other providers; you can find more information on OAuth in Digging Deeper into OAuth 2.0 on Force.com.

In Electric Imp terminology, a ‘tail’ is a module that plugs into the prototyping board, allowing developers to quickly put together a connected device. Recently, Electric Imp kindly provided me with a couple of sample tails, one of which contained an LSM9DS0 ‘9DOF’ chip – a single package combining a 3D digital linear acceleration sensor, a 3D digital angular rate sensor, a 3D digital magnetic sensor (together, the ‘9 degrees of freedom’ that give the chip its name) and a temperature sensor – telemetry city!

I’ll walk through the interesting portions of the agent code here; the device code simply combines some of the 9DOF samples. The full agent and device source is available in this gist.

Where my first sample used a ‘service account’ to authenticate to Salesforce and access the REST API, this time I decided to get more personal, associating the device with a particular Salesforce user, as would be the case with a wearable product. Since the agent can run a simple web app, it was straightforward to implement the OAuth web server flow. This Squirrel function implements the protocol:

// Load the previously saved OAuth response, if there is one
OAUTH <- server.load();

function webserver(req, res) {
    switch (req.method) {
        case "GET":
            if ("code" in req.query) {
                // We have the OAuth code - exchange it for the access token
                local code = req.query["code"];
                local request = http.post("https://login.salesforce.com/services/oauth2/token", 
                                          { "Content-Type" : "application/x-www-form-urlencoded" }, 
                                          "code="+code+
                                          "&grant_type=authorization_code"+
                                          "&client_id="+CLIENT_ID+
                                          "&client_secret="+CLIENT_SECRET+
                                          "&redirect_uri="+http.agenturl());
                local response = request.sendsync();
                if (response.statuscode == 200) {
                    // All is well - save the OAuth response
                    OAUTH = http.jsondecode(response.body);
                    server.log(OAUTH.access_token);
                    server.save(OAUTH);
                    server.log("Saved OAuth config");
                    listenToDevice();
                } else {
                    server.err(response.body);
                }
                // All done!
                res.send(200, "Authentication complete - you may now close this window");
            } else {
                // We need the OAuth code - redirect to OAuth authorization service
                res.header("Location", 
                           "https://"+LOGIN_HOST+"/services/oauth2/authorize"+
                           "?response_type=code"+
                           "&client_id="+CLIENT_ID+
                           "&redirect_uri="+http.agenturl());
                res.send(302, "Found");
            }
            break;

        // Any other method is an error
        default:
            server.log("Method Not Allowed")
            res.send(405, "Method Not Allowed");
    }
}

In Salesforce setup, I created the corresponding connected app, with the agent endpoint as the callback URL. Note that this approach works just fine for a simple prototype, but would not scale for production. Since each device/agent combination has a unique URL, you would need to create a connected app for every device!

In the production case, we would deploy a simple web app to handle the OAuth callback. When starting the OAuth flow, the agent would pass its endpoint via the OAuth request’s state parameter; the web app (running on the callback URL) would extract the endpoint from state and forward the OAuth authorization code to the correct agent.

Since we don’t want the user to have to log in every time the agent starts, we configure the app with refresh_token scope. The OAuth response will include a persistent refresh token, so the agent saves the response via the Electric Imp server.save() API. When the agent starts, it loads the saved OAuth response, if there is one, and calls the refresh() function to obtain a fresh access token.

// Exchange the refresh token for a new access token
function refresh(cb) {
    local request = http.post("https://login.salesforce.com/services/oauth2/token", 
                              { "Content-Type" : "application/x-www-form-urlencoded" }, 
                              "grant_type=refresh_token"+
                              "&client_id="+CLIENT_ID+
                              "&client_secret="+CLIENT_SECRET+
                              "&refresh_token="+OAUTH.refresh_token);
    local response = request.sendsync();
    if (response.statuscode == 200) {
        OAUTH.access_token = http.jsondecode(response.body).access_token;
        server.log(OAUTH.access_token);
        server.save(OAUTH);
        server.log("Refreshed OAuth config");
        cb();
    } else {
        server.err(response.body);
    }
}

// ... at agent startup ...

if ("refresh_token" in OAUTH) {
    server.log("Loaded OAuth config");
    server.log(http.jsonencode(OAUTH));
    refresh(listenToDevice);
}

To start the OAuth flow in my sample I just open the agent URL in my desktop browser. To ‘claim’ the device in a real deployment, the user would provision the device with wifi settings via a ‘blinkup’-enabled mobile app; on successful blinkup, the app would run the OAuth flow by opening the agent URL in the browser.

Moving on through the agent code, the listenToDevice() function handles two different messages – periodic ‘data’ messages carry accelerometer, gyroscope, magnetometer and temperature readings, while ‘impact’ messages signify that the device has received a ‘tap’. Note how the agent uses the Electric Imp Salesforce library to create an API client object, but then, instead of calling the login() method, sets the token and instance URL to the values it received in the OAuth flow.

function listenToDevice() {
    force <- Salesforce(CLIENT_ID, CLIENT_SECRET);
    // Inject the token and instance URL from the OAuth exchange
    force._token = OAUTH.access_token;
    force._instanceUrl = OAUTH.instance_url;

    // 9DOF reading
    device.on("data", function(data) {
        // Cook the data for Salesforce
        data = {
            Acc_X__c = data.acc.x,
            Acc_Y__c = data.acc.y,
            Acc_Z__c = data.acc.z,
            Gyr_X__c = data.gyr.x,
            Gyr_Y__c = data.gyr.y,
            Gyr_Z__c = data.gyr.z,
            Mag_X__c = data.mag.x,
            Mag_Y__c = data.mag.y,
            Mag_Z__c = data.mag.z,
            Temp__c  = data.temp
        };
        server.log(http.jsonencode(data));
        // Create a 'Reading' record
        force.request("post", "sobjects/Reading__c", 
            http.jsonencode(data), 
            function(err, data) {
                if (err) {
                    server.error("ERROR: " + http.jsonencode(err));
                    return;
                }

                server.log("Created reading with Id "+data.id);
            }
        );
    });

    // 'Tap'
    device.on("impact", function(data) {
        server.log("Impact detected on device " + deviceID);
        force.request("post", "sobjects/Case", 
            http.jsonencode({ 
              Subject = "Impact event!",
              Description = "Impact detected on device " + deviceID
            }), 
            function(err, data) {
              if (err) {
                server.error("ERROR: " + http.jsonencode(err));
                return;
              }

              server.log("Created Case with Id "+data.id);
            }
        );
    });
}

On a ‘data’ message we simply create a ‘Reading’ custom object record with the 10 values from the device, while on the ‘impact’ event, we create a Case with an appropriate subject and description. Here’s a short video of the device in action; you can see the OAuth web server flow in action, periodic data collection, and the interrupt firing on ‘tap’ events.

 

What devices have you integrated with Force.com? What would you like to see integrated? Let me know in the comments!

Published
June 29, 2015
Topics:

Leave your comments...

Easy IoT: Associating an Electric Imp Device with a Salesforce User