Windows Mobile下的重力感应器(Gravitational Sensor)开发

简介:

背景

重力感应器(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,我不详细介绍了,我主要介绍一下软件开发相关的。

acc3

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

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

acc2

图2 源自于 Windows Mobile Unified Sensor API

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

acc1

图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函数没法取出对象的实例指针了。

acc5

运行于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()函数进行注册。

 

一个统一访问GSensor的类库和实例代码就完成了。由于没有HTC的机器,如果谁能为我提供一个测试,我会衷心感谢他。

 

关于Mobile Sensors API项目

这个项目还是在起步阶段,当前实现了samsung的重力感应器,我把项目host到 Mobile Sensors API - Native unified APIs for Windows Mobile Sensors 了,我会持续改进,把各种sensors的实现到这个项目中。

由于我手头上没有HTC的机器,如果谁有兴趣可以加入到项目中帮我测试HTC设备,由于加入了Unit Test,测试变得很简单,只需要执行程序,参考测试输出文件就可以了,不需要调试。当然这个测试过程是一个不断迭代的过程,只是Unit Test把子过程简单化了。

源代码:http://mobilesensor.codeplex.com/SourceControl/ListDownloadableCommits.aspx

环境:VS2008 + WM 6 professional SDK + Samsung Windows Mobile SDK



    本文转自Jake Lin博客园博客,原文链接:http://www.cnblogs.com/procoder/archive/2009/07/28/1532592.html,如需转载请自行联系原作者


相关文章
|
IDE 关系型数据库 开发工具
使用Visual Basic进行Windows窗体开发
【4月更文挑战第27天】本文介绍了使用Visual Basic进行Windows窗体(WinForms)开发的步骤,从搭建开发环境到创建、设计用户界面,再到编写事件驱动的代码和数据绑定。Visual Basic结合WinForms提供了一种易学易用的桌面应用开发方案。通过调试、优化、部署和维护,开发者可以构建专业应用程序。随着技术发展,掌握最新UI设计和开发工具对于保持竞争力至关重要。本文为初学者提供了基础指导,鼓励进一步探索和学习。
482 0
|
Ubuntu Linux Python
如何利用wsl-Ubuntu里conda用来给Windows的PyCharm开发
如何在WSL(Windows Subsystem for Linux)的Ubuntu环境中使用conda虚拟环境来为Windows上的PyCharm开发设置Python解释器。
1618 1
|
监控 关系型数据库 MySQL
PowerShell 脚本编写 :自动化Windows 开发工作流程
PowerShell 脚本编写 :自动化Windows 开发工作流程
513 0
|
Linux Apache C++
FFmpeg开发笔记(三十五)Windows环境给FFmpeg集成libsrt
该文介绍了如何在Windows环境下为FFmpeg集成SRT协议支持库libsrt。首先,需要安装Perl和Nasm,然后编译OpenSSL。接着,下载libsrt源码并使用CMake配置,生成VS工程并编译生成srt.dll和srt.lib。最后,将编译出的库文件和头文件按照特定目录结构放置,并更新环境变量,重新配置启用libsrt的FFmpeg并进行编译安装。该过程有助于优化直播推流的性能,减少卡顿问题。
440 2
FFmpeg开发笔记(三十五)Windows环境给FFmpeg集成libsrt
|
存储 安全 程序员
Windows任务管理器开发原理与实现
Windows任务管理器开发原理与实现
|
算法 Linux Windows
FFmpeg开发笔记(十七)Windows环境给FFmpeg集成字幕库libass
在Windows环境下为FFmpeg集成字幕渲染库libass涉及多个步骤,包括安装freetype、libxml2、gperf、fontconfig、fribidi、harfbuzz和libass。每个库的安装都需要下载源码、配置、编译和安装,并更新PKG_CONFIG_PATH环境变量。最后,重新配置并编译FFmpeg以启用libass及相关依赖。完成上述步骤后,通过`ffmpeg -version`确认libass已成功集成。
552 1
FFmpeg开发笔记(十七)Windows环境给FFmpeg集成字幕库libass
|
开发者 C# Windows
WPF与游戏开发:当桌面应用遇见游戏梦想——利用Windows Presentation Foundation打造属于你的2D游戏世界,从环境搭建到代码实践全面解析新兴开发路径
【8月更文挑战第31天】随着游戏开发技术的进步,WPF作为.NET Framework的一部分,凭借其图形渲染能力和灵活的UI设计,成为桌面游戏开发的新选择。本文通过技术综述和示例代码,介绍如何利用WPF进行游戏开发。首先确保安装最新版Visual Studio并创建WPF项目。接着,通过XAML设计游戏界面,并在C#中实现游戏逻辑,如玩家控制和障碍物碰撞检测。示例展示了创建基本2D游戏的过程,包括角色移动和碰撞处理。通过本文,WPF开发者可更好地理解并应用游戏开发技术,创造吸引人的桌面游戏。
827 0
|
开发者 iOS开发 C#
Uno Platform 入门超详细指南:从零开始教你打造兼容 Web、Windows、iOS 和 Android 的跨平台应用,轻松掌握 XAML 与 C# 开发技巧,快速上手示例代码助你迈出第一步
【8月更文挑战第31天】Uno Platform 是一个基于 Microsoft .NET 的开源框架,支持使用 C# 和 XAML 构建跨平台应用,适用于 Web(WebAssembly)、Windows、Linux、macOS、iOS 和 Android。它允许开发者共享几乎全部的业务逻辑和 UI 代码,同时保持原生性能。选择 Uno Platform 可以统一开发体验,减少代码重复,降低开发成本。安装时需先配置好 Visual Studio 或 Visual Studio for Mac,并通过 NuGet 或官网下载工具包。
1577 0
|
开发框架 JavaScript .NET
【Azure 应用服务】Azure Mobile App (NodeJS) 的服务端部署在App Service for Windows中出现404 Not Found
【Azure 应用服务】Azure Mobile App (NodeJS) 的服务端部署在App Service for Windows中出现404 Not Found
194 0
|
编解码 Linux Windows
FFmpeg开发笔记(十一)Windows环境给FFmpeg集成vorbis和amr
在Windows环境下,为FFmpeg集成音频编解码库,包括libogg、libvorbis和opencore-amr,涉及下载源码、配置、编译和安装步骤。首先,安装libogg,通过配置、make和make install命令完成,并更新PKG_CONFIG_PATH。接着,安装libvorbis,同样配置、编译和安装,并修改pkgconfig文件。之后,安装opencore-amr。最后,重新配置并编译FFmpeg,启用ogg和amr支持,通过ffmpeg -version检查是否成功。整个过程需确保环境变量设置正确,并根据路径添加相应库。
430 1
FFmpeg开发笔记(十一)Windows环境给FFmpeg集成vorbis和amr