Accelerometer
From WebOS101
The accelerometer measures the acceleration of the device in each of three axes. The X axis runs vertically through the top and bottom of the device (as the screen of the device faces you). The Y axis runs horizontally through the left and right of the device. And the Z axis runs horizontally through the front and the back of the device.
The readouts of the acceleration on these three axes are in Gs, the acceleration due to gravity. One may think that if a device is sitting still on a table that all three axes would read zero acceleration, but this is not the case. Since there is always a gravitational pull on the device, there will always be acceleration in at least one of the axes.
This fact allows the detection of device orientation. As the acceleration of the X axis approaches 1 or -1 the device is being rotated up or down (portrait orientation). As the Y acceleration approaches 1 or -1 the device is being rotated left or right (landscape orientation). And as the Z acceleration approaches 1 or -1 the device is being rotated to lie on its back or face. The numbers between -1 and 1 indication the degree of tilt in any given direction. A great example of tilt detection is the Accelball app developed by Randall Gordon.
For my article, I wrote the PreSabre app as a demonstration of detecting a different accelerometer event - "swinging" the device. Instead of monitoring the slight changes in each axis individually, I needed to detect significant change in the acceleration of all of the axes simultaneously.
To figure out the the algorithm, I envisioned the accelerometer data as being plotted on a 3-d Cartesian graph, using the X, Y, and Z accelerometer readouts as the coordinates in space. Using this method, I could determine the Euclidean distance (in other words, the geometric difference) between the two points. The larger this difference, the faster the device had moved and thus the harder it had been swung.
To figure the distance, we need the two points, p and q, which are the readouts of the accelerometer. At any given time, p is the 3 readouts of the last accelerometer data retrieved and q is the current accelerometer readout.
Let's look at some code to understand this concept better. I will leave out the parts irrelevant to this article. Remember to download the PreSabre source code to follow along.
SabreAssistant.prototype.setup = function() {
// variables to hold the "old" accelerometer data
this.oldX = 0;
this.oldY = 0;
this.oldZ = 0;
These are the values which will hold px, py, and pz.
// set the accelerometer to faster frequency
this.controller.stageController.setWindowProperties("fastAccelerometer");
Exactly what it says. Without this, the device will only output accelerometer data at 4Hz, or 4 times per second. In my testing this is not good enough for much more than orientation detection. Using fastAccelerometer increases the frequency to 30Hz or 30 times per second.
In PreSabre, the accelerometer listener isn't actually set until you tap the screen and turn on the PreSabre. For brevity's sake, I will pretend that the only function of the app is detecting accelerometer data, so we'll assume that the listener is set from the beginning.
// reference for cleanup
this.accelHandle = this.accelData.bind(this);
// event listener
Mojo.Event.listen(document, 'acceleration', this.accelHandle);
The reason for the "reference for cleanup" is simply that you will want to remove all of your event listeners in the .cleanup() function. But the stopListening() function cannot accept a .bind(this) as one of its parameters, so this reference needs to be made. The app will now listen to the document for acceleration data and execute the accelData() function after each readout (30 times per second).
SabreAssistant.prototype.accelData = function(event) {
// set variables to the current accel data
this.newX = event.accelX;
this.newY = event.accelY;
this.newZ = event.accelZ;
An object will be passed to the function containing the event data. Use .accelX and the like to obtain the accelerometer readout passed to the function. These variables will now contain the qx, qy, and qz coordinates.
this.geomDiff = Math.sqrt(
Math.pow( (this.newX - this.oldX), 2) +
Math.pow( (this.newY - this.oldY), 2) +
Math.pow( (this.newZ - this.oldZ), 2)
);
This is the hunk of code that figures the Euclidean distance.
// if geometric difference is greater than 1 but less than 2.2, play a swinging sound
if ( (this.geomDiff > 1) && (this.geomDiff < 2.2) ) {
// do something for swing event
}
// or if it is greater or equal to than 1, play a "hit" sound
else if (this.geomDiff >= 2.2) {
// do something for hit event
}
This part of the code determines whether the Sabre was just swung or whether it actually "hit" something. Note that it does not actually detect if the device hits an object. It determines how big the Euclidean distance is, and if it is between 1.0 and 2.2 that is considered a swing. If it is equal to or greater than 2.2, that is considered a hit. I picked these values through testing.
// set the new data as the old data for the next test
this.oldX = this.newX;
this.oldY = this.newY;
this.oldZ = this.newZ;
}
This stores the q coordinates into p for the next test.
SabreAssistant.prototype.cleanup = function(event) {
Mojo.Event.stopListening(document, 'acceleration', this.accelHandle);
};
And this is the cleanup function which removes the event listener. Remember it is good practice to always remove your event listeners to help keep your app's footprint neat and tidy.
Original article with source code at: JDF-Software.com
Problem sensing tilt forward vs. tilt back when device is upright?
I've writing an app where I need to detect the difference between the user holding the top of device tilted slightly forward and slightly back. To be explicit, power button is on the top and the device is upright in the portrait position. The pitch and roll float values combined with the orientation change position value seem like they would be perfect for this, but I don't believe it provides enough information in the vertical position to distinguish between the two positions.
Here is the data I'm receiving from the pitch and roll values returned from the handleOrientation method:
- The pitch reads -90 if I hold the device exactly straight up.
- Top of device leaning slightly back
- position: 2
- roll: 0.0
- pitch: -75.0
- Top of device leaning slightly forward
- position: 2
- roll: 0.0
- pitch: -75.0
By using the handleAcceleration method instead, you can access the raw accelerometer data from the hardware. In this case, the Z value will be positive when the device is leaning forward and negative when the device is leaning back.
As of Nov 9, 2009, the official online docs[1] have a couple of typos. Here's a working example of all the Accelerometer data:
function MainAssistant() {
}
MainAssistant.prototype.setup = function() {
/* this function is for setup tasks that have to happen when the scene is first created */
/* use Mojo.View.render to render view templates and add them to the scene, if needed. */
// Ensure we can handle rotations smoothly
//if (this.controller.stageController.setWindowOrientation) {
// this.controller.stageController.setWindowOrientation("free");
//}
this.controller.listen(document, 'orientationchange',this.handleOrientation.bindAsEventListener(this));
this.controller.listen(document, 'acceleration', this.handleAcceleration.bindAsEventListener(this));
/* setup widgets here */
/* add event handlers to listen to events from widgets */
};
MainAssistant.prototype.handleOrientation = function(event) {
$('myinfo').update("Orientation change position: "+ event.position + "<br/>pitch: " + event.pitch + "<br/>roll: " + event.roll);
};
MainAssistant.prototype.handleAcceleration = function(event) {
$('accel').update("X: " + event.accelX + "<br/>Y:" + event.accelY + "<br/>Z:" + event.accelZ + "<br/>time: " + event.time);
};
<div id="main" class="palm-hasheader">
<div class="palm-header">Header</div>
<div id="count" class="palm-body-text">Test below here</div>
<div id="myinfo">Info here</div>
<div id="accel">Acceleration here</div>
</div>

