Staying away from QFNs, my first Android app plus some Bluetooth

So, exams are finally over, the results are out and I scraped through. No supplements, none failed.

Over the past 2 weeks, I have learnt a valuable lesson: I cannot solder QFN packages. Out of 10 chips, I burnt 9. It’s horrible. It might be that I’m not using enough flux or maybe not enough solder but the solder will not melt below the many of the pins. One of my F550x break-out board arrived yesterday. Double sided, it fits perfectly onto a breadboard.

F550x break-out

 

With that kind of chip loss, I shall be staying away from QFNs in general from now on. The leaded packages are expensive than non-leaded, but the convenience is definitely worth it, for me.

A few weeks ago, my dad asked me to write an application that lets him send his current GPS location to his colleagues using SMS and the like. Of course, there are plenty or maybe hundreds of apps that do this already. To name a few: Viber, Whatsapp, Google Maps, Tango… Location messaging is integrated into the apps. But what if you don’t have internet and you haven’t found this function in your chatting map, or maybe, you just don’t have time to search for it. Introducing GPS Share.

7inch_2

 

With its simplistic design, with a few seconds, you can send your current GPS coordinates via SMS. With internet connection, the application finds out the address of your location and it lets you share this information through e-mails. It also lets you copy the location information to the clipboard so you may paste it into any application you want. The time it takes from opening the app to sending a SMS packed with your current location is 4 seconds. I tried it out today after parking the car. It’s great. Link to Play Store.

And lastly, Bluetooth. This time, I’m taking a break from MSP430 and learn some C2000 and Bluetooth. I’ve got CC2540 mini dev-kit and I made a break-out board for the BC417. Unfortunately, the PIO is the wrong way around. but the code works well enough. The programmer is a clone I got from goodluckbuy. Free shipping. It works beautifully. Before receiving my programmer, I used TIVA C Launchpad and flashed it with CsrUsbSpiDeviceRE TIVA by Richard Aplin. It also worked like a charm. But since I’ve got a programmer, I may just use it and reserve the TIVA for future learning.

BC417-BO board 1

 

So far, I’ve made a Blinky program and a Hello World program. Just the basics and following CSR’s very limited tutorials, but at least there is no time limit on the IDE, unlike the 30-day IAR Embedded Workbench for 8051 for TI CC2540.

FTPDownloader Class – Download folders and files with AS3

Unless I’m trotting this down, I’m going to forget about the changes I made to the source code, and I did. I fixed a few issues with the original FTPDownloader class posted here sometime ago, and I forgot what I changed. I think it had something to do with the file downloading initialization. As of right now, I can only remember vaguely how to use this class so I’m going to trot it down as much as I can.

package
{
    import flash.events.Event;
    import flash.events.EventDispatcher;
    import flash.events.IOErrorEvent;
    import flash.events.ProgressEvent;
    import flash.filesystem.File;
    import flash.filesystem.FileMode;
    import flash.filesystem.FileStream;
    import flash.net.Socket;
    import flash.utils.ByteArray;

    public class FTPDownloader
    {
        public static const LOG_IN_SUCCESSFUL:String = "log in successfully";
        public static const DATA_RECEIVED:String = "data received";
        public static const DOWNLOAD_CAN_START:String = "download can start";
        public static const LIST_AVAILABLE:String = "list available";
        public static const COMMAND_EXECUTED:String = "226";

        private var _dispatcher:EventDispatcher = new EventDispatcher();        
        private var _control:Socket;
        private var _data:Socket;

        private var _host:String;
        private var _port:int;
        private var _username:String;
        private var _password:String;
        private var _process:String;
        private var _remoteFolder:String;
        private var _remoteFile:String;    
        private var _fileData:ByteArray;
        private var _fileStream:FileStream;
        private var _downloadCanStart:Boolean = false;
        private var _localFile:File;
        private var _filesList:Array;
        private var _nameRegex:RegExp;

        public function FTPDownloader(host:String, port:int, userName:String, password:String)
        {
            this._host = host;
            this._port = port;
            this._username = userName;
            this._password = password;

            _nameRegex = /(\.|\w)+$/gm;

            _control = new Socket(host, port);
            _control.addEventListener(Event.CONNECT, onControlConnection);
            _control.addEventListener(ProgressEvent.SOCKET_DATA, onControlDataReceived);
            _control.addEventListener(IOErrorEvent.IO_ERROR, onError);

            _data = new Socket();
            _data.addEventListener(Event.CONNECT, onDataConnection);
            _data.addEventListener(ProgressEvent.SOCKET_DATA, onServerData);
            _data.addEventListener(IOErrorEvent.IO_ERROR, onError);
        }

        public function get filesList():Array
        {
            return _filesList;
        }

        // initDownload() - initialize downloading
        public function downloadFile(remoteFilePath:String):void
        {
            var rf:Array = remoteFilePath.split("/");
            this._remoteFile = rf.pop();
            this._remoteFolder = rf.join("/");

            _localFile = File.applicationStorageDirectory.resolvePath(this._remoteFile);
            _fileStream = new FileStream();
            _fileStream.open(_localFile, FileMode.WRITE);
            _process = "download";
            _control.writeUTFBytes("TYPE I\n");
            _control.writeUTFBytes("PASV\n");
            _control.flush();
        }

        // onPasvConnection() - connected to data port
        protected function onDataConnection(event:Event):void
        {
            //trace("process: " + _process);
            if(_process == "download")
            {

                // start download a file by using the RETR command
                _control.writeUTFBytes("RETR " + this._remoteFolder + "/" + this._remoteFile + "\n");
            }
            else if(_process == "getDirList")
            {
                _control.writeUTFBytes("LIST " + this._remoteFolder + "\n");
            }
            _control.flush();
        }

        // data received from server through data port
        protected function onServerData(event:ProgressEvent):void
        {
            if(_process == "download")
            {
                // temp object to store incoming data
                _fileData = new ByteArray();
                // write data into temp bytearray
                _data.readBytes(_fileData, 0, _data.bytesAvailable);
                // write byte array into file stream
                _fileStream.writeBytes(_fileData, 0, _fileData.bytesAvailable);
            }
            else if(_process == "getDirList")
            {
                //trace(1);
                var filesList:String = _data.readUTFBytes(_data.bytesAvailable);
                ls(null, filesList);
            }
        }

        protected function onError(event:IOErrorEvent):void
        {
            trace(event.errorID + ":" + event.text);
        }

        // FTP returns server codes whenever the 'control' connection sends commands over
        protected function onControlDataReceived(event:ProgressEvent):void
        {
            var d:String = _control.readUTFBytes(_control.bytesAvailable);
            //trace(d);

            if(d.indexOf("220") != -1)
            {
                // connected to server
                _control.writeUTFBytes("USER " + this._username + "\n");
                _control.flush();
            }

            if(d.indexOf("331") != -1)
            {
                // user accepted, input password
                _control.writeUTFBytes("PASS " + this._password + "\n");
                _control.flush();
            }

            if(d.indexOf("230") != -1)
            {
                // login successful
                dispatchEvent(new Event(LOG_IN_SUCCESSFUL));
            }

            var a:int = d.indexOf('227');
            if (a != -1)
            {
                // entered passive mode
                var st:int = d.indexOf("(",a);
                var en:int = d.indexOf(")",a);
                var str:String;
                str = d.substring(st + 1,en);
                var a2:Array = str.split(",");
                var p1:int= a2.pop();
                var p2:int = a2.pop();
                var ip:String = a2.join(".");
                var port:int=(p2*256)+(p1*1);
                _data.connect(ip, port);
            }

            if(d.indexOf("226") != -1)
            {
                dispatchEvent(new Event(COMMAND_EXECUTED));

                if(_process == "download")
                {
                    // download completed
                    dispatchEvent(new Event(DATA_RECEIVED));
                    //_process = "null";
                }
            }

            if(d.indexOf("221") != -1)
            {
                // logged out from server
            }

            if(d.indexOf("150") != -1)
            {
                // data connection is established after PASV
            }
        }

        protected function onControlConnection(event:Event):void
        {
            // connected to FTP server
        }

        public function addEventListener(type:String, listener:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false):void {
            _dispatcher.addEventListener(type, listener, useCapture, priority, useWeakReference);
        }
        public function removeEventListener(type:String, listener:Function, useCapture:Boolean = false):void {
            _dispatcher.removeEventListener(type, listener, useCapture);
        }
        public function dispatchEvent(event:Event):Boolean {
            return _dispatcher.dispatchEvent(event);
        }
        public function hasEventListener(type:String):Boolean {
            return _dispatcher.hasEventListener(type);
        }

        public function ls(remoteFolder:String = null, stringToProcess:String = null):void
        {
            _filesList = new Array();

            if(remoteFolder != null)
            {
                // start getting a string of the list of files in a folder
                _process = "getDirList";
                _remoteFolder = remoteFolder;
                _control.writeUTFBytes("PASV\n");
                _control.flush();
            }
            else if(stringToProcess != null)
            {
                var str_arr:Array = stringToProcess.match(_nameRegex);
                if(str_arr != null)
                {
                    _filesList = str_arr;
                    dispatchEvent(new Event(LIST_AVAILABLE));
                }
            }
        }    
    }
}

Usage: To download a folder,

  1. Create connections to that host by filling in the parameters to FTPDownloader().
  2. Create an event listener to listen to LOG_IN_SUCCESSFUL. In the event handler, use the ls function to list the file names of a folder.
  3. Create an event listener to listen to LIST_AVAILABLE. In the background, ls is going to dispatch this event once a list of file names are retrieved. The files’ names are stored in an array that can be accessed through the getter filesList. In the event handler for LIST_AVAILABLE, copy the list to a local variable.
  4. Everytime a command is executed, an event is dispatched as COMMAND_EXECUTED. Listen to this event.
  5. In the command executed event, only when the list of file names are assigned, start downloading by using for example if(_filesList.length > 0). Inside this conditional block, create a path to the remote file using the a path to the folder + the last element in the local filesList array, eg: var path:String = “tz/data/” + _filesList[_filesList.length-1];
  6. Commence downloading that file by using downloadFile(pathToFile)
  7. Create an event listener to listen to DATA_RECEIVED. This is dispatched whenever a single file  is downloaded. In the handler, pop the last element in the files list array. In the background a command is always executed (successfully or else an error code will pop up), once a file is downloaded, if the local variable containing the files list is not empty, it will continue to download until the list of files is empty.
  8. In the command executed conditional block, place an else block and trace yourself a message that all the files has finished downloading (since the list of files is empty)

Anyhow, if I ever need to know how to use this class, may I understand what I wrote. Enjoy!

Downloading a folder from a FTP server with AS3

In a time like this when semester one of uni is a month away and it’s too hot to go to the gym, then continue working on some coding seems like a nice idea. A week or two ago, thanks to Sathesh, I learnt how to use Socket to download a file from a FTP server. Now a need to download an entire folder has arisen. That or learn ByteArray to decompress a GZip file. I chose to go with the former. May this be an explanation post to myself later on.

I have found that to download a folder, you would need to get a list of file names in the folder, store it into an array or similar and use a download function to download it one-by-one. I also found that Flash can handle downloading multiple files at the same time but I’ve only tried doing that once and with my internet quota capped at the moment. Multiple files downloading may not seem to be a good idea.

Based on the FTP Downloader class from the earlier post, I added a function to get a list of all names. Since the LIST command in FTP returns everything to know about the file: permissions, date, time, file size, etc, I used a regular expression to fix the problem. With http://gskinner.com/RegExr/, I’ve found /(\.|\w)+$/gm works well and able to get only the file names of the files.

This is the ls function to list all the file names of the folder:

public function ls(remoteFolder:String = null, stringToProcess:String = null):void
{
   _filesList = new Array();

   if(remoteFolder != null)
   {
      // start getting a string of the list of files in a folder
      _process = "getDirList";
      _remoteFolder = remoteFolder;
      _control.writeUTFBytes("PASV\n");
      _control.flush();
   }
   else if(stringToProcess != null)
   {
   var str_arr:Array = stringToProcess.match(_nameRegex);
   if(str_arr != null)
   {
      _filesList = str_arr;
      dispatchEvent(new Event(LIST_AVAILABLE));
   }
}

How it works is that firstly, the coder calls ls() and only fills in the remoteFolder parameter, leaving the latter null. The function will then connect to the data port and it will handle listing and returning to the string, which will leave remoteFolder parameter null and fill in stringToProcess. This will applies the regex and dispatch an event to notify the main program that the list is available to grab. _filesList is a private variable with a getter function.

Once the list is grabbed and stored into an array, the easy part comes along. Now using the function downloadFile() from the earlier post, one can download the files individually.

Downloading a file from a FTP server with AS3 (AIR)

Recently, I ran into a project that involves downloading a file from a FTP server. It was daunting that the HTTPService class in Flex didn’t support the FTP scheme neither does the Actionscript 3 framework has a built-in class to connect to a FTP server. Luckily, AS3 has been around long enough that there are tutorials! One of the most helpful tutorials I have found is the source code of a FTP client by Sathesh (http://suzhiyam.wordpress.com/2011/04/23/as3-ftp-client-part-ii-uploaddownload/). There are also some other references that I use, including:

From following Sathesh’s code, I discovered that it is pretty messy so I decided to write a helper class. How it works is that firstly, a socket ‘s’ is created to connect to the server and this socket ‘s’ is used only to send commands and retrieve server feedback, then another socket ‘r’ is created to handle all incoming data from server, data, not return codes. Once logged in, the client enters passive mode and is ready to download files. It will then make an empty file in the application storage directory, fetch the file using the RETR command and start the writing-to-file process. The returned data is handled through an event listener of the ‘r’ socket. It opens a file stream and began writing to the local file created earlier. Once download is completed, FTP server will send the 226 return code. Although the file is completely downloaded, AIR is not closing the file automatically so we need to check for when the data socket is properly disconnected and close the file stream. Timers come in handy and this eliminate “Error #3013: File or directory is in use”. It should be noted that once server returns code 226, the data connection is closed so you would need to make a new one.

Since I only need to download 1 file from the server, I wrote the class to tailor my needs. Modifications are simple enough once you briefly understood how it works.

package
{
 import flash.events.Event;
 import flash.events.EventDispatcher;
 import flash.events.ProgressEvent;
 import flash.events.TimerEvent;
 import flash.filesystem.File;
 import flash.filesystem.FileMode;
 import flash.filesystem.FileStream;
 import flash.net.Socket;
 import flash.utils.ByteArray;
 import flash.utils.Timer;

 public class DownloadFTPFile
 {
 private var s:Socket;
 private var r:Socket;
 private var fileData:ByteArray;
 private var fileStream:FileStream;
 private var file:File;

 private var ip:String;
 private var port:int;

 private var dTimer:Timer;

 private var filesToDownload:Array;
 private var currentIndex:int;

 private static var dispatcher:EventDispatcher = new EventDispatcher();

 public static var FILE_DOWNLOADED:String = "fileDownloaded";

 private var ftp_host:String;
 private var ftp_port:int;
 private var localFileName:String;
 private var remoteFilePath:String;
 private var ftp_user:String;
 private var ftp_pass:String;

 public function DownloadFTPFile(ftp_host:String, ftp_port:int, ftp_userName:String, ftp_password:String, localFileName:String, remoteFilePath:String)
 {
 this.ftp_host = ftp_host;
 this.ftp_port = ftp_port;
 this.localFileName = localFileName;
 this.remoteFilePath = remoteFilePath;
 this.ftp_user = ftp_userName;
 this.ftp_pass = ftp_password;

 s = new Socket(this.ftp_host, this.ftp_port);
 s.addEventListener(ProgressEvent.SOCKET_DATA, sData);

 r = new Socket();
 r.addEventListener(ProgressEvent.SOCKET_DATA, rData);
 r.addEventListener(Event.CONNECT, onPassiveConnection);

 dTimer = new Timer(10, 1);
 dTimer.addEventListener(TimerEvent.TIMER_COMPLETE, checkStream);
 }

 // connect to server data
 protected function sData(event:ProgressEvent):void
 {
 var d:String = s.readUTFBytes(s.bytesAvailable);
 trace(d);
 if(d.indexOf("220") != -1) // service ready for new user
 {
 s.writeUTFBytes("USER " + this.ftp_user + "\n");
 s.writeUTFBytes("PASS " + this.ftp_pass + "\n");
 s.flush();
 }
 if(d.indexOf("230") != -1) // login successful
 {
 s.writeUTFBytes("PASV\n");
 s.flush();
 }
 var a:int = d.indexOf('227');
 if (a != -1)
 {
 var st:int = d.indexOf("(",a);
 var en:int = d.indexOf(")",a);
 var str:String = d.substring(st + 1,en);
 var a2:Array = str.split(",");
 var p1:int = a2.pop();
 var p2:int = a2.pop();
 ip = a2.join(".");
 port =(p2*256)+(p1*1);
 r.connect(ip, port); // onPassiveConnection() is called once connected
 }

 if(d.indexOf("226") != -1) // file send ok
 {
 dTimer.start();
 }
 }

 protected function onPassiveConnection(event:Event):void
 {
 trace("Connected to data port - downloading commenced");

 file = File.applicationStorageDirectory.resolvePath(localFileName);
 fileStream = new FileStream();
 fileStream.open(file, FileMode.WRITE);
 s.writeUTFBytes("RETR " + remoteFilePath + "\n");
 s.flush(); 
 // now data is going to be sent from server, so continue onto rData()
 }

 // returned server data
 protected function rData(event:ProgressEvent):void
 {
 fileData = new ByteArray();
 r.readBytes(fileData, 0, r.bytesAvailable);
 fileStream.writeBytes(fileData, 0, fileData.bytesAvailable);
 }

 protected function checkStream(event:TimerEvent):void
 {
 if(!r.connected && fileStream != null)
 {
 fileStream.close();

 s.writeUTFBytes("QUIT");
 s.flush();

 dispatchEvent(new Event(FILE_DOWNLOADED));

 }
 else
 {
 dTimer.reset();
 dTimer.start();
 }
 }

 public function addEventListener(type:String, listener:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false):void {
 dispatcher.addEventListener(type, listener, useCapture, priority, useWeakReference);
 }
 public function removeEventListener(type:String, listener:Function, useCapture:Boolean = false):void {
 dispatcher.removeEventListener(type, listener, useCapture);
 }
 public function dispatchEvent(event:Event):Boolean {
 return dispatcher.dispatchEvent(event);
 }
 public function hasEventListener(type:String):Boolean {
 return dispatcher.hasEventListener(type);
 }
 }
}

How to use:

  1. Create an instance of the class DownloadFTPFile, enter the parameters correctly.
  2. Make the instance listen to the event DownloadFTPFile.FILE_DOWNLOADED. That’s when the file is downloaded!

Enjoy!

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