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.

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.

So my laptop is failing and my project is sort of hit a wall

My Thinkpad T60 is now 5 years old. The screen is pink at low brightness and produce a buzz sound at max brightness, only to turn itself off after a few minutes. The hard drive is still functional though, but at this rate, in 8-12 months, the screen is going to die. That’s going to be a sad sight.

If you have been reading my blog, which I’m sure you wouldn’t be since at the moment, I’m the only one who is writing and reading, I have been working on a small project to read my Thinkpad’s accelerometer data in an AIR application by making an AIR Native Extension. Through a bunch of cout’s, I’ve discovered that the roll axis (tilting the facing screen to left or right) is actually handled by the y-axis while pitching is on the x-axis. The number in the roll increases when you tilt the laptop to the right and the pitch increases when you lean the laptop’s screen towards you.

However, the sad thing is, I have not been able to package the ANE. I’ve been following Nick Kwiatkowski’s tutorial series on creating ANE with Eclipse and up to the 3rd video, I cannot package the ANE because ADT keeps saying namespace is missing in my descriptor.xml. Any comments on helping out would be great.

 

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;
}

Code for Tribal Wars Report Parser (Assistant Tool)

Even though I’ve stopped working on the project, simply because I quit playing tribal wars, it’s nice to keep the code, and I think it might be able to help someone else who is working on a similar project. The following code is to be put into a class. It can be used to parse a report and chunk out the attacker’s name, attacker’s village, defender’s name, defender’s village, attack status, scouted resources, scouted mine levels and wall level. Not that useful for troops numbers. I’ve found that if you paste a text into Flash’s text field, all the tabs are removed. Hope someone can use this work to work on future Tribal Wars projects.  It’s fully functional and commented.

/* Written by Cong Nguyen 2011 */
package org.flashandrc.tribalWarsTools.dataParser
{
 public final class FarmAssistant
 {
 //regular expression definitions

 public function FarmAssistant()
 {

 }

 public static function parseReport(report:String):Array
 {
 var currentIndex:int = 0;
 //return array layout: attacker; attacker's village; defender; defender's village;
 // attack's status: 0 - lost; 1 - partially win; 2 - win win
 // scouted resources; buildings: timber, clay, iron, wall;
 // haul status: 0 - done; 1 - some leftover
 var returnArray:Array = new Array();
 var tempString:String = new String();
 var tempNum:int = 0;

 //search for attacker
 currentIndex = report.search("Attacker");
 if(currentIndex != -1)
 {
 currentIndex += 9; //currently at attacker's name
 while(report.charCodeAt(currentIndex) != 13) //13 is the character code for new line
 {
 tempString+=report.charAt(currentIndex);
 currentIndex++;
 }
 }

 returnArray[returnArray.length] = tempString; //put attacker's name in the return array
 tempString = "";

 //right now, current index is at the new line character
 currentIndex += 8; //makes current index spot onto the attacker's village
 while(report.charCodeAt(currentIndex) != 13)
 {
 tempString += report.charAt(currentIndex);
 currentIndex++;
 }

 //only take in the last 13 characters
 tempString = getCharFromEnd(tempString, 13);

 returnArray[returnArray.length] = tempString; //put attacker's village into return array
 tempString = "";
//search for defender
 currentIndex = report.search("Defender");
 if(currentIndex != -1)
 {
 currentIndex += 9;
 while(report.charCodeAt(currentIndex) != 13) //13 is the character code for new line
 {
 tempString+=report.charAt(currentIndex);
 currentIndex++;
 }
 }

 returnArray[returnArray.length] = tempString; //put defender's name into return array
 tempString = "";
//right now the current index is at the new line character right after the defender's name

 currentIndex+= 13; //makes current index spot onto the defender's village
 while(report.charCodeAt(currentIndex) != 13)
 {
 tempString+=report.charAt(currentIndex);
 currentIndex++;
 }

 tempString = getCharFromEnd(tempString, 13);
 returnArray[returnArray.length] = tempString; //put defender's village into return array
 tempString = "";

 //attack status
 if(report.search("The defender has won") != -1) //if the defender is won is found, then the attacker lost
 {
 returnArray[returnArray.length] = 0;
 }
 else
 {
 currentIndex = report.search("Losses");
 if(currentIndex != -1)
 {
 currentIndex += 7;
 while(report.charCodeAt(currentIndex)!= 13) //13 is new line
 {
 tempString += report.charAt(currentIndex);
 currentIndex++;
 }
 }
 //put temp string into a number
 tempNum = Number(tempString);

 //if tempNum = 0, the attack is a win, else it's a partial win
 if(tempNum == 0)
 {
 returnArray[returnArray.length] = 2;
 }
 else
 {
 returnArray[returnArray.length] = 1;
 }
 }

 tempNum = 0;
 tempString = ""; 

 //if there is scout, then display these, else no.

 currentIndex = report.search("Espionage");
 if(currentIndex != -1)
 {
 //resources scouted
 currentIndex = report.search("Resources scouted:");
 if(currentIndex != -1)
 {
 currentIndex += 18;

 while(report.charCodeAt(currentIndex) != 13)
 {
 if(report.charCodeAt(currentIndex) != 32) //32 is space
 {
 tempString+=report.charAt(currentIndex);
 }
 else
 {
 tempNum = toNumber(tempString);
 //tempNum = Number(tempString);
 returnArray[returnArray.length] = tempNum;
 tempString = "";
 }
 currentIndex++;
 }

 tempNum = toNumber(tempString); //there is no space at the end but the tempNum was recorded anyway
 //tempNum = Number(tempString);
 returnArray[returnArray.length] = tempNum;
 tempString = "";

 }

 //buildings levels: timber, clay, iron, wall
 currentIndex = report.search("Timber");
 if(currentIndex != -1)
 {
 currentIndex += 19;
 while(report.charCodeAt(currentIndex) != 41) //41 is )
 {
 tempString+=report.charAt(currentIndex);
 currentIndex++;
 }
 tempNum = Number(tempString);
 returnArray[returnArray.length] = tempNum;
 tempString = "";

 }

 currentIndex = report.search("Clay");
 if(currentIndex != -1)
 {
 currentIndex += 16;
 while(report.charCodeAt(currentIndex) != 41) //41 is )
 {
 tempString+=report.charAt(currentIndex);
 currentIndex++;
 }
 tempNum = Number(tempString);
 returnArray[returnArray.length] = tempNum;
 tempString = "";
 }

 currentIndex = report.search("Iron");
 if(currentIndex != -1)
 {
 currentIndex += 17;
 while(report.charCodeAt(currentIndex) != 41) //41 is )
 {
 tempString+=report.charAt(currentIndex);
 currentIndex++;
 }
 tempNum = Number(tempString);
 returnArray[returnArray.length] = tempNum;
 tempString = "";
 }

 currentIndex = report.search("Wall");
 if(currentIndex != -1)
 {
 currentIndex += 12;
 while(report.charCodeAt(currentIndex) != 41) //41 is )
 {
 tempString+=report.charAt(currentIndex);
 currentIndex++;
 }
 tempNum = Number(tempString);
 returnArray[returnArray.length] = tempNum;
 tempString = "";
 }

 //continue farming?
 if((returnArray[6] + returnArray[7] + returnArray[8]) != 0)
 {
 returnArray[returnArray.length] = 1;
 }
 else
 {
 returnArray[returnArray.length] = 0;
 }

 }

 return returnArray;
 }

 public static function toNumber(string:String):Number
 {
 var str:String = string;
 str = str.split(".").join("");
 return Number(str);
 }

 public static function getCharFromEnd(string:String, numberOfCharacters):String
 {
 var startIndex:int = string.length - numberOfCharacters;
 return string.substr(startIndex, 13);
 }
 }
}
 

Bah

Ending the project, incomplete

After step 1, parsing the report into an array that later feeds into an XML, which is stored locally in the user hard drives, as well as proceeding with step 3a, a branch step I worked out in order for the project to complete: organizing the sets.xml, which contains players, worlds, villages, farms, I have decided to cease playing Tribal Wars, thus ending the project.  In reflection, the project if worked at my pace, could have been done under 2 weeks but playing Tribal Wars has become a burden, as I have to log on every few hours to check on the progress. Since I have much to do, I stopped playing. As a result, I no longer have the need to make a farm assistant tool that generate javascript to feed into the forms and show me how much resource my farms have. The project is unfinished, but anyway, let’s move on.

A tool for Tribal Wars – Step 1 Done!

Just yesterday I had no idea what to do and how to parse an attack report into an array, now I’ve done it! Instead of using all the complicated regular expressions, I used charAt and charCodeAt, and string.search(); a lot of them. The program is probably unoptimized, but let’s not worry about that for now.

Anyway, because I had to use a lot of charCodeAt(), I came up with a simple tool to help myself finding the numeric Unicode character code that Flash use, and it’s called: Charcode Finder. You can get it from Mediafire:

Charcode Finder (SWF)

Alternatively, the code is simple enough to re-write it yourself. All you need is 2 text fields, one input and one dynamic. The input has an instance name of “input” and the output has an instance name of “output”, both excluding “”; and a button, in my case, enterBtn.

import flash.events.MouseEvent;
enterBtn.addEventListener(MouseEvent.CLICK, getCode);
function getCode(e:MouseEvent):void
{
 var charCode:Array = new Array();
 var char:Array = new Array();
 var string:String = input.text.slice(0,-1);
 
 for (var i:uint = 0; i < string.length; i++)
 {
 if (char.length == 0)
 {
 char[0] = string.charAt(i);
 }
 else
 {
 char[char.length] = string.charAt(i);
 }
if (charCode.length == 0)
 {
 charCode[0] = string.charCodeAt(i);
 }
 else
 {
 charCode[charCode.length] = string.charCodeAt(i);
 }
 }
output.text = char + "\n";
 output.text += charCode;
}

A tool for Tribal Wars

Summary: My plan to make a tool similar to Tribal Wars farm assistant tool.

I am addicted to playing Tribal Wars. It’s a multi-player real-time online game where each player starts with a small village, striving for power and glory. I’ve been playing this game since 2008 in world 31 and reached my peak in world 49 with 35 villages and a few hundred thousand points. Now that I have holidays, I’ve started playing world 60, and it’s going well.

World 60 has a bunch of new features. The one that I like the most is called the farm assistant. With the farm assistant activated, players can create farming templates (or raiding), have the control with a list of recently attacked villages and an indication whether or not the last attack was successful and whether after the attack there were still resources to continue farming. The farm assistant also allows player to calculate the resources available and the right amount of troops which is necessary to pillage the left-over resources. Of course, this is not always accurate since those villages I farm (raid) will also get farmed by other players, and I also have found that the calculated produced resources is always a bit higher than the actual amount. So it’s about 87% accuracy.

The downside is that to use farm assistant, you have to pay to use it. Although it’s a nice feature indeed, the necessity of reaching for my credit card is way too tormenting and no, thank you.  So, I thought to myself, with my current skills with Actionscript, I might be able to make a nice little tool that function similarly to the farm assistant tool, except for templates, because I’m going to use this offlline.

What I am going to do is:

  1. Sort villages names, troops, buildings levels into an array and store them in a XML file on the local computer. This requires me to having to paste the report from Tribal Wars, meaning I have to somehow find a way to extract information from a big string.
  2. Find the equation that calculate resources each level of mine produce (Done. It’s a rounded value of 25.81*POW(1.163,x) where x is the level of the mine. Though this equation has an accuracy of 85% with x>20)
  3. Store the troops and buildings data into an XML file and retrieve the data from Flash.
  4. Find Tribal Wars server time zone.
  5. Calculate the amount of current resources using building data and building levels found from the latest report, using Tribal Wars server time.
  6. Calculate the amount of troops necessary using troops haul data and the amount of current resources

First step is huge. I actually have no idea where to start. I’ve been having a look at regular expressions and the String class’ methods but it still fairly blank. Furthermore, I’m planning to make this tool usable across many worlds, such as archers, non-archers, paladin, non-paladin, miltia, etc worlds (you’ll understand all these terms if you play the game) so there are going to be options for user to select whether to include those or not.

Let’s go and build this application. Probably take a month or more.

Start blogging, again.

Well, this is the fourth time that I’ve decided to blog, and finally I know for sure what I’m going to blog about: flash and remote control helicopter. Thus the name of this blog: flashandrc, except putting “helicopter” in would make the name a bit long. These are my hobbies and I would love to talk about them.

As for an introduction, flash and RC stuff has never been a big part of my life. I was introduced to these two hobbies quite recently. Flash, since 2008 and RC helicopters, since last year.

I started Flash with Flash 8 and Actionscript 2.0. Then I stopped and move on to life, but a year later, I came back to a hobby, realizing how much potential this tool can give me, I take up Actionscript 3.0 and starts re-learning then. Of course flash blogs are very common and sure there are hundreds of much more skilful flash programmers than I am, I hope to contribute my experience and knowledge somehow to make the development process easier.

About RC stuff, I’m going to focus probably only on helicopters, mostly on building them. My life long goal right now is to build a remote control coaxial collective pitch helicopter. It’s hard and it’s expensive but I’m getting there slowly.

Follow

Get every new post delivered to your Inbox.