技术开发 频道

Windows Mobile下的重力感应器开发

【IT168技术文档】

 背景

 重力感应器(Gravitational Sensor,简称为GSensor),类似于Accelerometer和Tilt Sensor, 用于测量倾斜度的感应器。严格定义来说,Accelerometer和Tilt Sensor是有区别的,Accelerometer可以测量三维,而Tilt Sensor只能测量二维。术语的定义见 Accelerometer 和 Tilt_sensor 。

 Accelerometer被广泛用于手机等移动设备上,同时用于Wii的手柄上,Wii游戏的移动就是根据Accelerometer测量的数据进行移动的。

 简介

 重力感应器(Gravitational Sensor, Accelerometer)已经被广泛应用于Windows Mobile设备上,可是由于MS没有官方定义和提供统一的API,为重力感应器的开发带来不便,本文讲述如何在HTC和Samsung设备上进行重力感应器的开发,实现统一访问了GSensor的类库,在实现过程中使用了Singleton,Simple Factory和Observer模式。

 原理

 根据设备的内在能力,Accelerometer能够测量一维,二维或者三维的重力加速度。关于 Accelerometer的原理可以参考wikipedia的文章Accelerometer,我不详细介绍了,我主要介绍一下软件开发相关的。

 图1 源自于《Samsung Mobile Innovator Windows Mobile API Programming Guide》

 从上图可以看出重力信息只是和设备本身有关,和设备在相对位置无关。例如设备平放在水平的桌面上,对这长的屏幕前后移动设备(如下图2),Y轴会发生变化。

 图2 源自于 Windows Mobile Unified Sensor API

 另外的情况,设备长的屏幕垂直放(如下图3),上下移动,也是Y轴在发生变化。

 图3 源自于 Windows Mobile Unified Sensor API

 Samsung 的手机可以输入 *#0*# 启动LCD Test 程序来测试Accelerometer的运行状况。

 原罪

 Windows Mobile 6.5及以下的设备,是没有统一的API操作GSensor,MS一直没有统一包括GSensor在内的所有Sensor的接口(其他Sensors包括Light Sensor,Stylus Sensor等等),甚至连WM7也没有官方答复,关于MS的答复可以参考下面链接 Windows Mobile finally getting a Unified Sensor API, support for capacitive screens? Update: Answer – No.

 没有统一的API,各个手机硬件厂商都需要开发自己的API,其中以HTC和samsung最为出名。开始的时候,各个厂商都不公开自己的API,导致Windows Mobile的开发人员只能通过反向工程(Reverse Engineering)等非正当手段获取API,哪怕获取了厂商的API,开发的程序也不能同时支持多种硬件设备。幸运的是Koush 封装了一个托管版本的 Windows Mobile Unified Sensor API ,同时支持HTC和Samsung。下面我会介绍如何使用native c++分别调用HTC和Samsung的GSensor API。

 Samsung GSensor API

 Samsung已经公开了自己的API,可以在 Samsung Mobile Innovator Windows Mobile SDK 1.1 注册下载和安装。里面包含的Samsung官方的GSensor API。 使用Samsung的API需要安装一个Cab。Cab在 C:\Program Files\Samsung Windows Mobile SDK\redist\smi_wm_sdk_redist_1_1_0.cab

 GSensor API定义见 C:\Program Files\Samsung Windows Mobile SDK\inc\smiAccelerometer.h

 取GVector信息

  GVector SamsungGSensor::GetGVector()

 {

 SmiAccelerometerVector accel;

 if(SmiAccelerometerGetVector(&accel) == SMI_SUCCESS)

 {

 GVector gVector;

 gVector.x = accel.x;

 gVector.y = accel.y;

 gVector.z = accel.z;

 return gVector;

 }

 throw;

 }

 调用SmiAccelerometerGetVector() API取出GVector信息。

 订阅GVector信息

 Samsung的API提供订阅功能。

 

 void SamsungGSensor::Register()

 {

 SmiAccelerometerCapabilities cap;

 if( SmiAccelerometerGetCapabilities(&cap) != SMI_SUCCESS)

 {

 throw;

 }

 SmiAccelerometerHandler h = &GetVectorHandler;

 if(SmiAccelerometerRegisterHandler(1000, h) != SMI_SUCCESS)

 {

 throw;

 }

 //Execute the task every second.

 //Start(1000);

 }

 SmiAccelerometerGetCapabilities()函数检查GSensor的情况,SmiAccelerometerRegisterHandler()注册GetVectorHandler处理函数定期取出GVector信息,SmiAccelerometerRegisterHandler()的第一个参数为interval(取数据的间隔),第二个为回调处理函数,该函数只能为static。

  void SamsungGSensor::Unregister()

 {

 SmiAccelerometerUnregisterHandler();

 //Stop();

 }

 上面是反注册函数。

 void SamsungGSensor::GetVectorHandler(SmiAccelerometerVector accel)

 {

 GVector gVector;

 gVector.x = accel.x;

 gVector.y = accel.y;

 gVector.z = accel.z;

 SamsungGSensor::GetInstance()->GVectorChanged(gVector);

 }

 这是回调函数,定义如下:

  private:

 static void GetVectorHandler(SmiAccelerometerVector accel);

 由于SmiAccelerometerRegisterHandler()注册的回调函数只能是static的,所以我在开发SamsungGSensor的时候不得不把这个类做成Singleton,否则static函数没法取出对象的实例指针了。

 运行于Samsung机器的界面

 HTC GSensor API

 目前为止(2009年7月),HTC还没有公开Sensor的APIs,所以这些API都是通过反向工程(Reverse Engineering)出来的,使用有风险,自己承担。

 API的定义生成和清理

 

 private:

 // The following PInvokes were ported from the results of the reverse engineering done

 // by Scott at scottandmichelle.net.

 // Blog post: http://scottandmichelle.net/scott/comments.html?entry=784

 typedef HANDLE (WINAPI * PFN_HTCSensorOpen)(DWORD);

 typedef void (WINAPI * PFN_HTCSensorClose)(HANDLE);

 typedef DWORD (WINAPI * PFN_HTCSensorGetDataOutput)(HANDLE, PSENSORDATA);

 PFN_HTCSensorOpen           pfnHTCSensorOpen;

 PFN_HTCSensorClose          pfnHTCSensorClose;

 PFN_HTCSensorGetDataOutput  pfnHTCSensorGetDataOutput;

 #define SENSOR_DLL      L"HTCSensorSDK.dll"

 HTCGSensor::HTCGSensor(void)

 {

 HMODULE hSensorLib = LoadLibrary(SENSOR_DLL);

 if (hSensorLib == NULL)

 {

 printf("Unable to load HTC Sensor DLL");

 throw;

 }

 pfnHTCSensorOpen = (PFN_HTCSensorOpen)

 GetProcAddress(hSensorLib, L"HTCSensorOpen");

 pfnHTCSensorClose = (PFN_HTCSensorClose)

 GetProcAddress(hSensorLib, L"HTCSensorClose");

 pfnHTCSensorGetDataOutput = (PFN_HTCSensorGetDataOutput)

 GetProcAddress(hSensorLib, L"HTCSensorGetDataOutput");

 if (pfnHTCSensorOpen == NULL ||

 pfnHTCSensorClose == NULL ||

 pfnHTCSensorGetDataOutput == NULL)

 {

 printf("Unable to find entry point");

 throw;

 }

 sensorHandle = NULL;

 sensorHandle = pfnHTCSensorOpen(HTC_GSensor);

 }

 HTCGSensor* HTCGSensor::Create()

 {

 HANDLE hEvent = CreateEvent(NULL, TRUE, FALSE, L"HTC_GSENSOR_SERVICESTART");

 if (hEvent == NULL || GetLastError() != ERROR_ALREADY_EXISTS)

 {

 printf("Unable to create Sensor Event");

 throw;

 }

 SetEvent(hEvent);

 CloseHandle(hEvent);

 return new HTCGSensor();

 }

 HTCGSensor::~HTCGSensor(void)

 {

 if(sensorHandle != NULL)

 {

 pfnHTCSensorClose(sensorHandle);

 sensorHandle = NULL;

 }

 HANDLE hEvent = CreateEvent(NULL, TRUE, FALSE, L"HTC_GSENSOR_SERVICESTOP");

 if (hEvent == NULL || GetLastError() != ERROR_ALREADY_EXISTS)

 {

 printf("Unable to stop Sensor Event");

 throw;

 }

 SetEvent(hEvent);

 CloseHandle(hEvent);

 }

 HTCGSensor()构造函数加载DLL和生成函数调用的入口指针。Create()函数启动Sensor。~HTCGSensor()释放资源。

 取GVector信息

 

 GVector HTCGSensor::GetGVector()

 {

 GVector gVector;

 SENSORDATA data;

 pfnHTCSensorGetDataOutput(sensorHandle, &data);

 // HTC's Sensor returns a vector which is around 1000 in length on average..

 // but it really depends on how the device is oriented.

 // When simply face up, my Diamond returns a vector of around 840 in length.

 // While face down, it returns a vector of around 1200 in length.

 // The vector direction is fairly accurate, however, the length is clearly not extremely precise.

 float htcScaleFactor = 1.0 / 1000.0 * 9.8;

 gVector.x = data.TiltX * htcScaleFactor;

 gVector.y = data.TiltY * htcScaleFactor;

 gVector.z = data.Orientation * htcScaleFactor;

 return gVector;

 }

 订阅GVector信息

 由于HTC的API不提供订阅功能,所以我封装了一个ThreadTask(线程任务)类,负责生成一个线程,该线程定期执行任务,在这个场景下定期任务用于取GVector信息。

 

 #include <Windows.h>

 class ThreadTask

 {

 public:

 ThreadTask();

 ~ThreadTask(void);

 private:

 HANDLE    mProcEvent;

 HANDLE    mThreadHnd;

 DWORD    mThreadId;

 bool    mThreadHalt;

 int     mInterval;

 bool    mStarted;

 public:

 void ProcessTask();

 void Start(int interval);

 void Stop();

 virtual void Process() {};

 };

 //    Thread methods

 DWORD WINAPI ProcessThread(void *param)

 {

 if (param)

 {

 ThreadTask* thread = (ThreadTask*)param;

 thread->ProcessTask();

 }

 return 0;

 }

 ThreadTask::ThreadTask() :

 mProcEvent(INVALID_HANDLE_VALUE),

 mThreadHnd(NULL),

 mThreadId(0),

 mThreadHalt(false),

 mInterval(0),

 mStarted(false)

 {

 }

 ThreadTask::~ThreadTask(void)

 {

 Stop();

 }

 void ThreadTask::Start(int interval)

 {

 if(!mStarted)

 {

 mStarted = true;

 mInterval = interval;

 mProcEvent = CreateEvent(NULL, true, false, NULL);            //    manual reset, initial state reset

 mThreadHnd = CreateThread(NULL, 0, &ProcessThread, this, CREATE_SUSPENDED, &mThreadId);

 if (mThreadHnd)

 {

 SetThreadPriority(mThreadHnd,THREAD_PRIORITY_NORMAL);

 ResumeThread(mThreadHnd);

 }

 }

 }

 void ThreadTask::Stop()

 {

 if(mStarted)

 {

 mThreadHalt = true;

 // Signal the event

 SetEvent(mProcEvent);

 // Wait for the Thread to Die

 WaitForSingleObject(mThreadHnd, INFINITE);

 CloseHandle(mThreadHnd);

 CloseHandle(mProcEvent);

 mStarted = false;

 }

 }

 void ThreadTask::ProcessTask()

 {

 while (!mThreadHalt)

 {

 WaitForSingleObject(mProcEvent, mInterval); //INFINITE

 ResetEvent(mProcEvent);

 //process by subclass

 Process();

 }

 }

 作为ThreadTask的子类只需要知道interval来启动Thread,然后重写处理定时任务函数(Override Process() )。ThreadTask可以用于Windows Mobile开发下的很多场景下。

  void HTCGSensor::Register()

 {

 Start(1000);

 }

 void HTCGSensor::Unregister()

 {

 Stop();

 }

 void HTCGSensor::Process()

 {

 GVectorChanged(GetGVector());

 }

 Sensors工厂

 Client不需要知道具体那个厂家(HTC or Samsung)的Sensor,只需要调用工厂类生成Sensor类。

 

 class GSensorFactory

 {

 public:

 static IGSensor* CreateGSensor();

 };

 IGSensor* GSensorFactory::CreateGSensor()

 {

 try

 {

 return SamsungGSensor::GetInstance();

 }

 catch(...)

 {

 }

 try

 {

 return HTCGSensor::Create();

 }

 catch(...)

 {

 }

 return NULL;

 }

 自动生产相应的Sensor的对象。

  LRESULT CSensorTesterView::OnInitDialog(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)

 {

 gSensor = GSensorFactory::CreateGSensor();

 if(NULL == gSensor)

 {

 MessageBox(L"Can not Initialise GSensor.");

 }

 return TRUE;

 }

 Client只是调用工厂类生产Sensor对象。

 Observer模式

 为Client提供一个当GVector发生改变时自动通知更新的功能,这里使用了Observer模式,我使用了一个开源的类 Experiences of Implementing the Observer Design Pattern (Part 3) ,这个类具有很多优点,类型安全(type safe),泛型(generic),任意参数类型和任意参数数量,回传Sender的指针等等, 代码在这里下载http://tse3.sourceforge.net/doc/api/TSE3__Notifier.html

 class IGSensor;

 /**

 *  Oberver interface for Gravitation Sensor.

 *

 */

 class IGSensorListener

 {

 public:

 typedef IGSensor notifier_type;

 virtual void IGSensor_GVectorChanged(IGSensor* gSensor, GVector gVector) {};

 };

  这是Listener,也就是我们常说的Abstract Observer。需要定义notifier_type和定义回调接口。

  /**

 *  Interface of Gravitation Sensor.

 *

 */

 class IGSensor :

 public Notifier<IGSensorListener>,

 public ThreadTask

 {

 public:

 IGSensor(void);

 ~IGSensor(void);

 public:

 virtual GVector GetGVector() = 0;

 virtual void Register() = 0;

 virtual void Unregister() = 0;

 protected:

 void GVectorChanged(GVector gVector);

 };

 这是Notifier也就是Subject,需要继承 Notifier<IGSensorListener>。

 class CSensorTesterView :

 public Listener<IGSensorListener>

 {

 public:

 virtual void IGSensor_GVectorChanged(IGSensor* gSensor, GVector gVector) override;

 LRESULT OnInitDialog(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);

 };

 LRESULT CSensorTesterView::OnInitDialog(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)

 {

 gSensor->Register();

 attachTo(gSensor);

 return TRUE;

 }

  上面显示的是client类,为了演示把一些Observer模式无关的代码删除掉,完整代码可以下载源代码。client类需要继承public Listener<IGSensorListener>,重写IGSensor_GVectorChanged()函数和调用attachTo()函数进行注册。

作者:Jake.NET

查看原文地址

0
相关文章