Shake, Sign, Deliver, It's Yours!

Michael Toews -
12 MIN READ
5

Using the Rhodes Sensor API

Among the numerous APIs that Rhodes exposes for your device, lies the Sensor API which you can utilize to connect to, and read from, the many different sensors that your device(s) has. In this post I'll be demonstrating the use of our Sensor API by using the accelerometer on a device to detect a device shake. Once the shake is detected, I'll navigate to a page that would house a signature capture box which can be used and then the signature can be accepted by either a button click or an additional shake of the device.

I will also be using a third party tool called Bootstrap, which you may have seen in other blog posts and tutorials around Rho products. Although the use of Bootstrap is optional, I highly recommend its usage as it makes design and layout a breeze.

If you'd like to download this app to follow along simply clone this repo https://github.com/rhomobile/sensorapi_example.git.

git clone https://github.com/rhomobile/sensorapi_example.git

That having been said, let's get started!

A Note About the Sensor API

While there are many different sensors exposed through the Sensor API, not all sensors will work with all devices. For example, a Samsung Galaxy S4 has an ambient temperature sensor (hardware) built into the phone so I could use the ambient temperature sensor on the Sensor API to tell the ambient temperature in the area. This will not work on an Apple iPhone 5S however since it does not have the hardware necessary to make this API usable. To fully understand which sensors your device has, you must research your specific device for a list of available sensor hardware.

This also holds true for the software components on your device. For example, the temperature sensor for Android was introduced in Android API level 14 so, if you are building your app with a version lower than this, you will not be able to use the temperature sensor even if you have the right hardware. Before using the Sensor API it is important to know the limitations of your device's hardware AND software so that your hard work does not end in frustration.

Once you have researched the capabilities of your device, you'll want to check out our documentation on the Sensor API here. In this documentation you'll see how this API is implemented in either Ruby or JavaScript and the different sensors that you can read from. For this example app I am using the accelerometer sensor since almost every device on the market today will have an accelerometer and the necessary software to listen to it.

Step 1 - Defining the Sensor

Before using the Sensor API, you have to create a Sensor object of a specific type. Each type of sensor will have different attributes depending on the type of sensor. You do this by calling the Sensor.makeSensorByType() method. This method takes a single parameter to describe the sensor that you are trying to use. Typically you will use one of the SENSOR_TYPE constants for this parameter value. If the sensor does not exist, then it will return null. Otherwise you will have a Rho.Sensor object and will be able to use the methods and properties associated with this API.

Step 2 - Getting Sensor Information

Now that you have a Sensor defined you will want to get values from the sensor. This is accomplished in two ways.


A) Reading values - To read values synchronously, you can use the Sensor.readData method. This method will return an object that will contain properties representing values for that sensor. The property names will depend on the type of sensor you have created. For example

myTempSensor = Rho.Sensor.makeSensorByType(Rho.Sensor.SENSOR_TYPE_TEMPERATURE);

myTempData = myTempSensor.readData();

if (myTempData.status =='ok')

     {

     currentTemp = myTempData.temperature_value

B) Asynchronous Callback method - In most cases you want to continuously get values from the sensor. To do this you will setup an asynchronous callback when you call the Sensor.start method. This callback method will be called at a specified interval. This interval is controlled by the Sensor.minimumGap property. By default the interval is 200 milliseconds. For example, the code snippet below defines an ambient light sensor which will poll every 200 milliseconds and a temperature sensor which will poll every 10 minutes:

myTempSensor = Rho.Sensor.makeSensorByType(Rho.Sensor.SENSOR_TYPE_TEMPERATURE);

myTempSensor.minimumGap = 10000 ; //10 minutes

myTempSensor.start(tempCallback);

myLightSensor = Rho.Sensor.makeSensorByType(Rho.Sensor.SENSOR_TYPE_AMBIENT_LIGHT);

myLightSensor.minimumGap = 200 ; //This is default

myLightSensor.start(lightCallback);

Step 3 - Handling Results

Different sensors will contain different properties in the callback object. For example, the accelerometer will return accelerometer_x, accelerometer_y, and accelerometer_z, (all float type) whereas the temperature sensor will return temperature_value (string). To see all the different values that are returned see the callback tab start() method in the Sensor API. Note that there is a property 'status' and 'message' to indicate if there was an error in getting the values.

Screen Shot 2014-01-17 at 10.15.42 AM.jpg

Defining Shake Detection

Since I would like to reuse the ability to detect shake events, I am going to create my own a 'shake' api/object that will expose two methods:


  1. startWatch (onShake) - method to start detecting shakes which takes a parameter that defines the callback function for the shake event
  2. stopWatch - a method to stop listening for shake events.


The code below is the complete shake object which we will now breakdown and explain.

/public/js/application.js

  // Define shake detecting JS

  var shake = (function () {

  var shake = {},

  watchId = null,

  options = { "minimumGap": "300" },

  previousAcceleration = { x: null, y: null, z: null },

  shakeCallBack = null;

  // Start watching the accelerometer for a shake gesture

  shake.startWatch = function (onShake) {

    if (onShake) {

      shakeCallBack = onShake;

    }

    watchId = Rho.Sensor.makeSensorByType(Rho.Sensor.SENSOR_TYPE_ACCELEROMETER);

    if (watchId !== null) {

      watchId.setProperties(options);

      console.log('starting detection');

      watchId.start(assessCurrentAcceleration);

    }

    else

    {

      handleError();

    }

  };

    // Stop watching the accelerometer for a shake gesture

    shake.stopWatch = function () {

      if (watchId !== null) {

        console.log('stopping detection');

        watchId.stop();

        watchId = null;

      }

    };

  // Assess the current acceleration parameters to determine a shake

  function assessCurrentAcceleration(acceleration) {

    var accelerationChange = {};

    if (previousAcceleration.x !== null) {

      accelerationChange.x = Math.abs(previousAcceleration.x, acceleration.accelerometer_x);

      accelerationChange.y = Math.abs(previousAcceleration.y, acceleration.accelerometer_y);

      accelerationChange.z = Math.abs(previousAcceleration.z, acceleration.accelerometer_z);

    }

    // console.log('movement detected:' + (accelerationChange.x + accelerationChange.y + accelerationChange.z).toString());

    if (accelerationChange.x + accelerationChange.y + accelerationChange.z > 30) {

      // Shake detected

      console.log('shake detected');

      if (typeof (shakeCallBack) === "function") {

        shakeCallBack();

      }

      shake.stopWatch();

      setTimeout(shake.startWatch, 1000);

      previousAcceleration = {

        x: null,

        y: null,

        z: null

      }

    } else {

      previousAcceleration = {

        x: acceleration.accelerometer_x,

        y: acceleration.accelerometer_y,

        z: acceleration.accelerometer_z

      }

    }

  }

  // Handle errors here

  function handleError() {

  }

  return shake;

  })();

Helper Variables

  var shake = {},

  watchId = null,

  options = { "minimumGap": "300" },

  previousAcceleration = { x: null, y: null, z: null },

  shakeCallBack = null;

We start by defining variables for the different parts of the sensor that we need to track. Most will start as null or blank.

     - shake - The object that contains all the information about the shake event.

     - watchID - The variable that will refer to the sensor itself.

     - options - A hash of options to pass to the Sensor API telling it how we want to see the data. We set minimumGap here which is how long the API will wait, in milliseconds between taking readings.

     - previousAcceleration - Used to calculate acceleration changes.

     - shakeCallBack - A reference to our shake event callback function.

shake.startWatch Method

shake.startWatch = function (onShake) {

  if (onShake) {

    shakeCallBack = onShake;

  }

  watchId = Rho.Sensor.makeSensorByType(Rho.Sensor.SENSOR_TYPE_ACCELEROMETER);

  if (watchId !== null) {

    watchId.setProperties(options);

    console.log('starting detection');


    watchId.start(assessCurrentAcceleration);

  }

  else

  {

    handleError();

  }

};

This is the function that starts the detection for shakes. It creates an anonymous function that accepts a callback function 'onshake' as its parameter. It then sets the sensor from which it would like to gather readings. Here is where we see the first explicit use of the Sensor API:


watchId = Rho.Sensor.makeSensorByType(Rho.Sensor.SENSOR_TYPE_ACCELEROMETER);

watchid will now be a Rho.Sensor object and specifically an Accelerometer sensor. We then check to make sure the device supports this by making sure it is not null. If it is was successful in creating the sensor, then we can then configure it as well as start detecting  events. The setProperties() method,  exposed from our Sensor API, is used to configure a group of properties all in one call. Here, all we are doing is setting the minimumGap property to be 300 ms. We could of equally accomplished this by just doing watchId.minimumGap = options.minimumGap.

We are now ready to enable the accelerometer by using the Sensor.start() method. We want to continually monitor Accelerometer events so we will pass in a callback function so we can handle the data appropriately:

watchId.start(assessCurrentAccleration);

watchId.start(assessCurrentAcceleration);

watchId.start(assessCurrentAcceleration);

Handling Accelerometer Events

// Assess the current acceleration parameters to determine a shake

function assessCurrentAcceleration(acceleration) {

  var accelerationChange = {};

  if (previousAcceleration.x !== null) {

    accelerationChange.x = Math.abs(previousAcceleration.x, acceleration.accelerometer_x);

    accelerationChange.y = Math.abs(previousAcceleration.y, acceleration.accelerometer_y);

    accelerationChange.z = Math.abs(previousAcceleration.z, acceleration.accelerometer_z);

  }

  // console.log('movement detected:' + (accelerationChange.x + accelerationChange.y + accelerationChange.z).toString());

  if (accelerationChange.x + accelerationChange.y + accelerationChange.z > 30) {

    // Shake detected

    console.log('shake detected');

    if (typeof (shakeCallBack) === "function") {

      shakeCallBack();

    }

    shake.stopWatch();

    setTimeout(shake.startWatch, 1000);

    previousAcceleration = {

      x: null,

      y: null,

      z: null

    }

  } else {

    previousAcceleration = {

      x: acceleration.accelerometer_x,

      y: acceleration.accelerometer_y,

      z: acceleration.accelerometer_z

    }

  }

}

This is the function that we use to calculate the acceleration to see if the device is being shaken or not. Using watchId.start(assessCurrentAcceleration); the information from the sensor is passed to the assessCurrentAcceleration() function. The 'acceleration' parameter will be the callback object that is returned from the Sensor.start method. It will contain properties:accelerometer_x, accelerometer_y and accelerometer_z that we will perform some math on to determine if there was a shake or not. We have determined that a difference of 30 or more is detected is suitable for a typical shake. But you may want to play around with this value or better yet expose this as a configurable item in the 'shake' object to set the sensitivity.  We then initiate a call to the callback function which is referenced by shakeCallBack which will in turn call our callback that we define later in the tutorial. After calling the callback we stop the shake detection by using the Sensor.stop() API call and wait for a full second (1000ms) before once again starting the shake detection, setTimeout(shake.startWatch, 1000); and nullifying our x, y, and z vars.

Application Start, Stop, and Callback Functions

/public/js/application.js

// Define Functions that work with shake detection

  function start(){

  shake.startWatch(myShakeCallback);

  }

  function stop(){

  shake.stopWatch();

  }

For our demo application, I defined a start() function which calls shake.startWatch() passing myShakeCallback as the callback function for the shake event. I also defined a stop() function which simply calls shake.stopWatch(); to stop shake detection.

Now that we can start and stop shake detection, we need to do something with the fact that we detected a shake.

myShakeCallback = function() {

  if(currentPage == "package.html")

    currentPage = "sign.html";

else{

    currentPage = "package.html";

    // Capture Signature

  }

  $(".page").load(currentPage, function(){Pace.stop();});

}

As you can see, the callback function will detect which page it is on using a global JS variable. Since I only have two pages, it is simple enough to keep track of the current page in this fashion.

Incorporating Shake Detection Into Your App

Now that I have defined shake detection I need to actually use it in my app. Let's define an index page to start off with. In my index page I need to include JS and CSS sources in order to take advantage of the Rho JS API and the Bootstrap components.

/public/views/index.html

 

  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

 

 

 

 

 

 

 

 

 

 

 

  Choose a Package

 

 

 

 

 

 

You may have noticed that in my code for the index page, I have a strange class "custom-link". This is part of my single-page app design scheme using Bootstrap. I'll cover it's use later on in this post.

Adding Additional Views

If you start your app now, you should have a simple page with some text and a link that doesn't work. Let's make a page for that link. The use case I had in mind when designing this example was a delivery person delivering packages in extremely cold environments. Assuming the person is wearing gloves, it may be difficult or impossible to accurately press buttons on a device's screen, so I propose to give them the option instead of shaking the device. With that in mind, let's make a page with the package details.

/public/views/package.html

 

 

 

   

Package #1

    Tap the button below or shake device to collect Signature.

   

   

   

 

 

 

 

As you can see, I have not filled in much of the logic or detail here because your implementation may vary greatly. Once again, we have a page, with some text and a link that doesn't work to a page called sign.html which I'll define now.

/public/views/sign.html

 

 

   

Sign for Package

    Tap the button below or shake device to accept the signature and go back.

   

   

 

 

 

 

You may also notice the bit of inline JS I have defined at the bottom of the page. This will automatically start my shake detection once this page is loaded into the DOM.

I'll be using an anonymous function akin to Document.ready() to make this app a single-page app. I define this in application.js as such:

/public/js/application.js

  // Document ready

  $(function() {

  currentPage = "package.html";

  //all links handled here

  $('body').on("click",".custom-link",function(e) {

    e.preventDefault();

    // Use Pace for a loading indicator

    Pace.start();

    var that = e.currentTarget;

    var href = $(that).attr("href");

    $(".page").load(href,function(){

      Pace.stop();

    });

    return false;

  });

  })

Now you can see the use of the "custom-link" class. Here we see that when we have a link that is of the class "custom-link", the body of the current page is modified to replace its contents with those of the referenced page. Here I implement a Pace object to add a loading indicator in case of long loading times.

Now you should be able to launch your app and see the following:

Landing Page

index.html

   

After clicking "Package #1 link"

package.html

   

Shake on package.html

sign.html

   

Shake on sign.html

package.html

   

At this point in your app development cycle you would add your logic and stylings to make this app your own. Using Bootstrap it's very easy to make your app look extremely professional in much less time than you might think. Once again if you have never used Bootstrap, I highly recommend looking into it for your web design needs.

I hope this post has given you a bit more insight into our Sensor API and that it will help you understand more what you'll need to do / use in order to get your app to where you want it.

profile

Michael Toews

Please Register or Login to post a reply

Replies