Dynamics CRM and the Internet of Things - part 5

This is the fifth and final post in a five-part series on how I integrated a Raspberry Pi with Microsoft Dynamics CRM to recognize contacts using automobile license plates. Although the code samples are focused on license plate recognition, the solution architecture I used is applicable to any Dynamics CRM + Internet of Things (IoT) integration. In my previous post, I showed how to execute the license plate recognition and contact search with JavaScript directly in the web resource. Today I will show how to set up a streaming interface so the Raspberry Pi can take a picture, parse the plate number and trigger the web resource to search for and display a contact without any input from the end user.

The approach

As I described in part 1 of this series, in this approach the Raspberry Pi takes a picture, parses the plate number and writes it to a streaming interface using Socket.IO. A web page or client application picks up the plate numbers from that interface, and then it queries CRM for a contact with the returned license plate number to display the details to the user. Essentially this is just a variation on what I described in my "Creating a near real-time streaming interface for Dynamics CRM with Node.js" series in late 2014.

Node.js code

In part 2 I showed the complete Node.js code to support all the scenarios in this series, but I did not discuss the streaming interface. Basically the streaming interface behaves almost exactly the same as the non-streaming interface except instead of writing the JSON result object in the HTTP response, it posts the JSON result object to a Socket.IO interface.

app.get('/check_plate_stream', function (req, res) {
    //generate a guid to use in the captured image file name
    var uuid1 = uuid.v1();

    //tell the webcam to take a picture and store it in the captures directory using the guid as the name
    exec('fswebcam -r 1280x720 --no-banner --quiet ./captures/' + uuid1 + '.jpg',
      function (error, stdout, stderr) {
        if (error !== null) {
          //log any errors
          console.log('exec error: ' + error);
        }
    });

    //now that we have a picture saved, execute parse it with openalpr and return the results as json (the -j switch) 
    exec('alpr -j ./captures/' + uuid1 + '.jpg',
      function (error, stdout, stderr) {
        //create a json object based on the alpr output
        var plateOutput = JSON.parse(stdout.toString());

        //add an "image" attribute to the alpr json that has a path to the captured image
        //this is so the client can view the license plage picture to verify alpr parsed it correctly
        plateOutput.image = '/captures/' + uuid1 + '.jpg';

        //write the json to the socket.io interface
        io.emit('message', plateOutput);

        //return a response to the caller that the message was sent
        res.send('message sent');

        //log the response from alpr
        console.log('alpr response: ' + stdout.toString());

        if (error !== null) {
          //log any errors
          console.log('exec error: ' + error);
        }
    });
});

The triggering mechanism is still a web route, so it can be called in a number of different ways without having to modify the base code. I've written a separate Node.js application that uses an HC-SR04 ultrasonic rangefinder to detect when a license plate moves into view and then call the streaming interface URL to trigger the license plate capture and recognition. The motion detection script is available on GitHub here.

I am running the motion detection script on the same Raspberry Pi as the webcam, but it could easily be run on a separate piece of hardware, too. You could also use the same approach to connect it to any sort of other sensor or input.

Here is a picture of my motion detection rig with my unimpressed Great Dane behind it for scale.
Raspberry Pi + ultrasonic sensor + Diesel the Great Dane

The web resource

The web resource loads the Socket.IO library from a CDN.
<script src="https://cdn.socket.io/socket.io-1.2.0.js"></script>

The web resource then connects to the streaming interface and listens for a message. Once it picks up a license plate number from the streaming interface, it then queries CRM to find a contact just like in part 4.

var piRootPath = "http://192.168.1.112:3000";
var socket = io("http://192.168.1.112:3000");

socket.on('message', function(resultObj){
$("#outputdiv").text("");
if(resultObj.results.length > 0) {
    var plateNum = resultObj.results[0].plate;
    var imgUrl = resultObj.image;
    $("#outputdiv").append("Detected plate number: " + plateNum + "
"); $("#outputdiv").append("<img src='" + piRootPath + imgUrl+ "' width='400' />"); var oDataURI = Xrm.Page.context.getClientUrl() + "/XRMServices/2011/OrganizationData.svc/" + "ContactSet?$select=ContactId,FullName&$filter=lpa_Platenumber eq '" + plateNum +"'"; var req = new XMLHttpRequest(); req.open("GET", encodeURI(oDataURI), true); req.setRequestHeader("Accept", "application/json"); //req.setRequestHeader("Content-Type", "application/json; charset=utf-8"); req.onreadystatechange = function () { if (this.readyState == 4 /* complete */) { req.onreadystatechange = null; //avoids memory leaks if (this.status == 200) { successCrmCallback(JSON.parse(this.responseText).d.results); } else { errorCallback(); } } }; req.send(); } else { $("#outputdiv").append("No plate detected
"); } });

If a matching contact is found, it's displayed. Otherwise a failure message is displayed instead.

function successCrmCallback(contacts) {
    if(contacts.length > 0) {
        var contactUrl = Xrm.Page.context.getClientUrl() + "/main.aspx?etc=2&extraqs=&pagetype=entityrecord&id=%7b" + contacts[0].ContactId + "%7d";
        $("#outputdiv").prepend("Contact: <a href='" + contactUrl + "' target='_blank'>" + contacts[0].FullName + "</a><br />"); 
    }
    else {
        //otherwise display a message that no contact could be found
        $("#outputdiv").prepend("No contact found
"); } $("#checkButton").prop('disabled', false); }

The web resource (lpa_checkplatestream.htm) is included in my sample CRM solution along with the contact entity configured to store the license plate number. The sample CRM solution is available in my GitHub repository here.

Demo

Here's the CRM web resource open a new tab. Note there's no button to trigger the plate capture and recognition.
Before checking the plate

This is the web page used to trigger the plate capture and recognition opened in Chrome.
The trigger page When using my motion detection script, this page is never shown to the end user.

Finally here is the web resource once it gets the message from the stream and looks up the contact details. If the plate recognition is triggered again, the page will update itself.
After checking the plate

Wrapping up

I hope you've enjoyed reading this series as much as I've enjoyed writing it. Through the process of working out the various scenarios I've certainly learned a lot about how the Raspberry Pi can be combined with Dynamics CRM to support novel business processes.

Here are links to all the previous posts in this series.

  1. Part 1 - Series introduction
  2. Part 2 - Node.js application
  3. Part 3 - CRM custom assembly trigger
  4. Part 4 - JavaScript in CRM web resource trigger

Do you have plans to integrate your Dynamics CRM system with the Internet of Things? If so, how? Let us know in the comments!

comments powered by Disqus