Finally got AIR to retrieve data from the sensor

After series of failures from trying to make an AIR native extension to leverage my Thinkpad’s awesome accelerometer, I turned to an NativeProcess and got it working! NativeProcess enable AIR to start a native application, input and retrieve output from the native application. To keep things simple, I separate the codes in which are used to retrieve accelerometer data into 2 native applications: 1 to get x acceleration and 1 to get y acceleration. In the video, I’m only getting the y-acceleration.

[youtube http://www.youtube.com/watch?v=z65FlPBCej4&w=420&h=315]

Using native process is very simple, but I find that it needs some workarounds to make it works, which I’m going to blog about.

As an introduction, Jeff Swartz’s guide to native process Interacting with native process (link) is highly recommended. What I found complicated, due to my limited knowledge of C is how to create a stream of stdout. For this project, I’m using fwrite (C++ Reference link) function from stdio.h to create a stdout stream, then use fflush to flush the stream to make sure the data is sent out.

This method means that the data in which ever format it was have to be converted to a string in order to be sent out with fwrite. Now in VC++, this is easy because Microsoft’s stdlib.h header file actually includes a function to convert integer to a string itoa(), but this function is not standard, so in order to use one that is similar, you can use this function to make life lots easier. This is one of my working test source file that may demonstrate the use of itoa() and fwrite(), fflush() to create a stdout stream.

#include <stdio.h>
#include <stdlib.h>

int main()
{
    char buffer[10];
    int num = 999;

    itoa(num, buffer, 10);
    //printf("Printf: Number to string: %s.\n", buffer);
    fwrite(buffer, 3, 1, stdout);
    fflush(stdout);
    return 0;
}

Since the data is sent out as a string, in AS3, I then have to convert it back to a Number type. This is relatively easy, but I’ve found that this wouldn’t work.

Number(process.standardOutput.readUTFBytes(process.standardOutput.bytesAvailable));

You would have to create a string in AS3 to store the read UTF bytes, then another Number typed variable to convert the string to Number type.

A very frustrating final problem I found after packaging the source files into an AIR application with native installer, and installing it, is that the AIR application, even though claim to have its native process started, does not have its native process actually started. I might have missed out on something, but it would be great if I don’t have to copy the native application into the installation folder of the application.

Other than that, the result is quite exciting. Now I can make applications or use other people’s code to make applications that use Thinkpad’s accelerometer for more engaging experience.

You can have a try with the AIR file. Remember to copy the folder Native with SensorGetY_VC into the installation folder. You will need Lenovo’s Thinkpad APS Protection drivers and software for this to work. Link to download AIR installer (http://www.mediafire.com/?rrq3yq8ghqs4nak).

Note: This application has only been tested on a Windows XP x86 Thinkpad T60. Use on other OS at own risk.

Pulling data from Thinkpad APS sensor using C++

Recently, I have just discovered that my Thinkpad T60, along with many other Lenovo’s Thinkpad computers have this built-in accelerometer. So it pops up, I want to use the data from the sensor in my AIR application, through the use of native extensions.

So firstly, in order to write a native extension, which will pump out a DLL, I need to be able to use the sensor in a console C++ application first. It took me 2 weeks to get to the Pointers basic section in my C programming book, and about 3 days to learn to load a DLL and use its function.

As of now, I have been able to pull data from the sensor to my native application and calculate the tilt on the Y axis (which is the roll axis).  The tilt is a bit off, but it wouldn’t matter I don’t think. This has only been tested on a Thinkpad T60 Windows XP Sp3, x86.

So here is how I pulled ShockproofGetAccelerometerData from the sensor. If you have any optimizations, please suggest, but please also comment on how that works because I’m still very much new to C++.  Cheers.

/*
 * LoadSensorDLL.cpp
 *
 *  Created on: Feb 11, 2012
 *      Author: CONG NGUYEN
 *        
 */

#include <iostream>
#include <windows.h>
#include <stdio.h>
#include <math.h>

struct AccelData
{
    int status;
    short x; //raw data
    short y; //raw data

    short xx; //avg. of 40ms
    short yy; //avg. of 40ms
    char temp; //raw value
    short x0; //used for auto-center
    short y0; //used for auto-center

};

using namespace std;

typedef void * (__stdcall *ShockproofGetAccelerometerData)(AccelData* accData);

//function prototypes
void calibrate(void);
void calibrateX();
void calibrateY();
short getAccelX();
short getAccelY();
double getRotationRadiansX();
double getRotationDegreesX();
double getRotationRadiansY();
double getRotationDegreesY();

//declare constants
const double PI = 3.141592;

//declare global variables
HINSTANCE hinstDLL;
AccelData accData = {0,0,0,0,0,0,0,0};
ShockproofGetAccelerometerData pfnGetData;
bool isSupported;

//declare global variables used for calibration and movement
short x_hor = 0;
short y_hor = 0;
short x_max = 0;
short y_max = 0;
double x_norm = 0.0;
double y_norm = 0.0;

double test = 0.0;

int main(void)
{
    int loop = 1;

    hinstDLL = LoadLibrary(L"Sensor.dll");

    if(hinstDLL == 0)
    {
        hinstDLL = LoadLibrary(L"sensor.dll");
    }

    if(hinstDLL == 0)
    {
        isSupported = false;
    }
    else
    {
        isSupported = true;
        pfnGetData = (ShockproofGetAccelerometerData) GetProcAddress((HINSTANCE) hinstDLL, "ShockproofGetAccelerometerData");
    }

    calibrate();
    calibrateX();
    calibrateY();

    cout << x_hor << ", " << y_hor << endl;
    cout << x_max << ", " << y_max << endl;

    cout << endl;
    cout << "Calibration completed.\n";
    getchar();
    cout << "TIlt 45 degrees. Press any keys to confirm.\n";
    getchar();
    cout << getRotationDegreesY();

    //end program
    getchar();
    FreeLibrary(hinstDLL);
    return 0;
}

short getAccelX()
{
    pfnGetData(&accData);
    return accData.x;
}

short getAccelY()
{
    pfnGetData(&accData);
    return accData.y;
}

void calibrate(void)
{
    cout << "Lay Thinkpad onto a flat and horizontal ground.\n";
    cout << "Press any key to continue.\n";
    if(getchar() == '\n')
    {
        x_hor = getAccelX();
        y_hor = getAccelY();
    }
}

void calibrateX()
{
    cout << "Pitch your Thinkpad a bit backwards.\n";
    cout << "Press any key, then rotate your Thinkpad back to normal position.\n";
    getchar();
    while(getAccelX() != x_hor)
    {
        if(getAccelX() > x_max)
        {
            x_max = getAccelX();
        }
    }
}

void calibrateY()
{
    cout << "Roll your Thinkpad to the right a bit.\n";
    cout << "Press any key, then rotate your Thinkpad back to normal position.\n";
    getchar();
    while(getAccelY() != y_hor)
    {
        if(getAccelY() > y_max)
        {
            y_max = getAccelY();
        }
    }
}

double getRotationRadiansX()
{
    x_norm = (double)((double)getAccelX() - (double)x_hor) / ((double)x_max - (double)x_hor);
    if(x_norm > 1)
    {
        x_norm = 1/x_norm;
    }
    return acos(x_norm);
}

double getRotationDegreesX()
{
    return getRotationRadiansX() * 180 / PI;
}

double getRotationRadiansY()
{
    y_norm = (double)((double)getAccelY() - (double)y_hor) / ((double)y_max - (double)y_hor);
    if(y_norm > 1)
    {
        y_norm = 1/y_norm;
    }
    return acos(y_norm);
}

double getRotationDegreesY()
{
    return getRotationRadiansY() * 180 / PI;
}