理解 Windows Phone 8 的传感器 API

介绍

尽管加速计,指南针,陀螺仪以及运动传感器等不同的传感器返回不同类型的数据,但它们使用相同的模式来报告(反馈)它们各自返回的数据。在接下来的几页中,你将学会如何读取不同传感器返回数据的技术我们介绍了一些方法,你可依此来构建一些基本的示例应用程序。传感器(Sensors)API的类和接口组成可以在 Windows.Devices.Sensors 命名空间中找到。

你会注意到,在Windows Phone 8 SDK中有两个独立的传感器API。Microsoft.Devices.Sensors 中的API 曾是Windows Phone 7 SDK的一部分,并引入到了Windows Phone 8。另一个传感器的API来自Windows 8 Runtime ,是Windows Phone与Windows 8操作系统共享的。如果你打算的在Windows Phone和Windows 8的应用程序之间共享代码,那么应该考虑使用 Windows 8传感器的API。 Windows Phone 7的传感器API在我们的书中并不涉及。

虽然加速计,罗盘,陀螺仪,重力感应传感器并不共享一个共同的基类,但它们都有一些相同的属性,方法和事件。这些共同的成员描述于表1。

表1 常见的传感器类成员

成员
类型
描述
GetCurrentReading 方法
返回一个只读的对象包含目前可用的传感器数据。
GetDefault 方法
返回一个传感器实例的静态构造方法
MinimumReportInterval 属性
只读的值,属性值指定的最小值,可以通过在ReportIntervalp属性来设置。
ReadingChanged 事件
引发的事件,由当前读取数据发生变化触发。
ReportInterval 属性
设置传感器读取新的数据的频率。每次设置时间间隔时GetCurrentReading 只改变一次并返回数据。


应用程序通过调用GetCurrentReading方法来读取当前传感器的返回。另外,应用程序可以触发ReadingChanged事件接收传感器的数据,只要传感器有新的数据GetCurrentReading方法可以在传感器没有准备好时调用,那么返回的值就为空。

注意:如果 ID_CAP_SENSORS 使用未在WMAppManifest.xml文件中配置,GetDefault for的任何调用将可导致额外的非法访问(UnauthorizedAccessException)。

每个传感器类中的都定义一个静态方法GetDefault。 GetDefault方法允许开发人员来决定传感器的硬件是否安装在特定的设备上以及是否对该应用程序可用。如果该传感器没有安装到该设备或对应用程序不可用,GetDefault方法由于缺少传感器而返回空。

传感器的API 来处理自身应用程序的快速切换。当应用程序从前台切换过来之后,开发人员并不需要 unhook 传感器,。不像摄像头,传感器会自动恢复,并没有提供一个明确的恢复方法。当应用程序恢复,传感器和事件重新建立连接并开始记录数据流。在你学会如何处理来自传感器的数据流之前,你需要了解如何传感器如何返回三维的报告数据。

返回数据的坐标系

每个传感器报告的数据与 x、 y、 z 坐标系统由 Windows Phone 设备定义。该设备的坐标系统固定到设备,并随手机的移动而移动。X轴是指设备的左 --> 右,+X 指设备的右侧。Y 轴指的设备的底部到顶部,+ y 指向顶部。Z 轴指设备的后面-->前面, + z 指设备的前面。图 1 所示即为手机的x,y,z轴的三视图。

图1 Windows Phone的x,y,z坐标系统

由传感器使用的坐标系统,并不一定相匹配与其他 API 所使用的坐标系。其中一个例子是由XAML使用的坐标系统。纵向模式中的XAML,+y轴指出移动设备的底部,-y轴指向相反的方向现在你应该明白传感器所使用坐标系统了,接下来将详细见绍如何从传感器获取数据。

通过事件来读取数据

传感器通过 ReadingChanged 事件来建立一个事件驱动的交互模型。ReadingChanged 事件将一个事件参数类的实例发送到事件处理程序,事件参数类的类型随传感器不同而不同。 加速度计将传递一个传递加速度计ReadingChanged事件参数,指南针将传递一个指南针ReadingChanged的事件参数等。

ReadingChanged 事件处理程序通过后台线程来调用。如果事件处理程序更新用户界面,更新逻辑脚背被调度到UI线程。下面的代码片段显示例子,处理从来自陀螺仪传感器的 ReadingChanged事件:

void sensor_ReadingChanged(object sender,
    GryometerReadingChangedEventArgs e)
{
    GryometerReading reading = e. Reading;
    Dispatcher.BeginInvoke(() =>
    {
        // add logic here to update the UI with data from the reading
        ...
    }
}

书的第10章中我们构建的传感器示例应用程序,没有使用ReadingChanged事件的。 取而代之,示例应用程序样本使用GetCurrentReading 方法来获得数据。

获取数据

应用程序不需要等待传感器来触发一个事件来要求传送数据 。 传感器可以通过调用 GetCurrentReading 来输出数据 。 一旦应用程序发出它需要新的数据的讯息,便 通过调用 GetCurrentReading 方法来获取数据。 例如 ,数据的读取可以通过按钮 单击,计时器滴答事件或后台辅助线程来启动 :

if (compassSensor != null)
{
    CompassReading reading = compassSensor.GetCurrentReading();
    if (reading != null)
    {
        // add logic here to use the data from the reading
        ...
    }
}

 你将通过示例应用程序的计时器滴答(timer tick)事件中来读取传感器数据 。 在实际演示之前,你需要创建一个新的项目并开发一个应用能够显示传感器的数据。

创建示例应用程序

打开 Visual Studio 并创建新的 Windows Phone 应用程序命名为传感器。示例应用程序将从重力、 指南针、 轴摇摆、 测斜仪和方向传感器中读取值。图2中所示的示例应用程序显示一组的彩条为重力、 轴摇摆和 测斜仪中的数据。每个组的酒吧显示传感器读数,x、 y 和 z 坐标。在屏幕的底部,该应用程序显示一个legend和关于传感器的提示信息,即来自方向传感器的读数。

图 2 传感器示例应用程序 (控件项代表 的x、 y 和 z 是由加速度计、 轴摇摆或测斜仪所返回的值)

当传感器返回正值时,将在零线之上绘制一个可缩放标签栏。返回负值是会在零线之下绘制一个标签栏。因为不同传感器拥有不同的取值范围,所以从传感器返回值到转化成以像素表示的标签栏高度的过程中应使用一个可伸缩的系数。当然,首先你得会创建一个可重用的控件栏来表示正数和负数。

创建可重用的控件

为了简化应用程序示例,您就会生成一个可重用的控件,允许您设置比例大小和传感器的值。当属性比例大小或值更改时,该控件应绘制相应的正数或负数控件栏,并显示带有标签的值。控制栏通过使用 Windows Phone 用户控件的模板项来实现,点击项目(Project) >> 中添加新菜单项(Add New Item Menu)。为新的项目栏的命名。如下所示为新控件的 XAML 标记。
表1 标记栏控件

图 3 栏控件与 2.0 的缩放值和当前值为-1.0。

Listing 1 Markup for the Bar control

<Grid x:Name="LayoutRoot">
    <Grid.RowDefinitions>
        <RowDefinition Height="1*" />                         #1
        <RowDefinition Height="1*" />                         #1
    </Grid.RowDefinitions>
    <Rectangle x:Name="positiveBar" VerticalAlignment="Bottom" />
    <Rectangle x:Name="negativeBar" Grid.Row="1" VerticalAlignment="Top" />
    <TextBlock x:Name="label" VerticalAlignment="Center"        #2
        Grid.RowSpan="2" Text="0" TextAlignment="Center" />     #2
</Grid>
#1 Divide control into two rows
#2 Center label 

网格划分成两个半 #1 且每个包含一个矩形。第一个矩形显示正值,而另一个表示负值。放在标签 #2 显示栏中间的显示值为 标签栏的值。图 10.3 演示控件栏的样子,此时缩放值设置=2.0 和当前值设置=1.0。

图3  控件栏的样子,此时缩放值=2.0 和当前值=1.0。

注栏控件的页可以为矩形填充不同的颜色。如下为添加新的属性 BarFill 到在Bar.xaml.cs 文件中的:

public Brush BarFill
{
    get { return positiveBar.Fill; }
    set
    {
        positiveBar.Fill = value;
        negativeBar.Fill = value;
    }
}

通过设置BarFill的属性来为画笔分配矩形栏值为正或负

注: 如果您需要创建可重用的 XAML 控制,BarFill 属性和其他依赖项的相关属性。该控件需要声布模板部件,并将提供作为默认模板的 XAML 标记。  << Silverlight 5 in Action >> 一书中有关于创建可重用 XAML 详细资料。

下一步你将创建设置的缩放值和栏值的属性。由于你不知道值的范围,你需要调用方告诉控件如何缩放到匹配矩形的高度值。假设您需要的栏来显示值 2 和 - 2之间,而Barcontrol 是 200 像素高。值2 将需要将正的栏值设为 100 像素高,而值为 -1 的话将需要负的栏值设为 50 像素高。下一个详细列表介绍如何通过设置 ScaleandValue属性来计算信息栏的高度。

2 计算 栏的缩放 高度 值属性

private double scale;
public double Scale
{
    get { return scale; }
    set
    {
        scale = value;
        Update();                        #1
    }
}
private double barValue;
public double Value
{
    get { return barValue; }
    set
    {
        barValue = value;
        Update(); 
    }
}
private void Update()
{
    int height = (int)(barValue * scale);                  #2
    positiveBar.Height = height > 0 ? height : 0;
    negativeBar.Height = height < 0 ? height * -1 : 0;     #3
    label.Text = barValue.ToString("0.0");   
}
#1 Recalculate when properties change
#2 Calculate height of bar
#3 Invert negative height

缩放范围值和浮动属性通过后面的字段和简单的getter和setter来实现。在每个属性里面可通过调用Update方法#1来重新计算矩形栏的高度并更新用户界面。Update方法内你通过缩放比例的规模和栏的值字段 #2 得到的值就是应绘制在栏的像素。如果计算得到的的高度值大于0,正的栏高度值将更新为新值。如果计算的高度值小于零,你分配负的栏的高度值及计算之前的值取负#3。最后,你可以使用ToString方法来设置格式化的字符串来到该标签的Text属性。

现在,你有一个工具栏控件,您可以创建示例应用的用户界面。 您需要添加一个XML命名空间到MainPage.xaml以便你可以使用新的工具栏控件:

xmlns:l="clr-namespace:Sensors"

你现在可以使用在MainPage.XAML标记中使用控制栏。 你需要在MainPage为每个传感器设置三个控制栏,一共有九个控制栏。

设计主页

观察图 2 中的截图 ,可以看出MainPage.xaml 被划分 成 三行 和若干列。如下所示为内容面板(ContentPanel)的MainPage.xaml标记。

表 3 MainPage.xaml标记

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
  <Grid.RowDefinitions>
    <RowDefinition Height="25" />
    <RowDefinition Height="400" />
    <RowDefinition Height="*" />
  </Grid.RowDefinitions>
  <Grid.ColumnDefinitions>
    <ColumnDefinition Width="40" />    #1
    <ColumnDefinition Width="40" />    #1 
    <ColumnDefinition Width="40" />    #1 
    <ColumnDefinition Width="48" />    #1 
    <ColumnDefinition Width="40" />    #1 
    <ColumnDefinition Width="40" />    #1 
    <ColumnDefinition Width="40" />    #1 
    <ColumnDefinition Width="48" />    #1 
    <ColumnDefinition Width="40" />    #1 
    <ColumnDefinition Width="40" />    #1 
    <ColumnDefinition Width="40" />    #1 
  </Grid.ColumnDefinitions>
  <TextBlock Text="X" Grid.Column="1" />
  <TextBlock Text="Y" Grid.Column="5" />
  <TextBlock Text="Z" Grid.Column="9" />
  <l:Bar x:Name="accelX" Grid.Row="1" Grid.Column="0"   #2
    BarFill="Red" Scale="100" />                        #2 
  <l:Bar x:Name="accelY" Grid.Row="1" Grid.Column="4"   #2 
    BarFill="Red" Scale="100" />                        #2 
  <l:Bar x:Name="accelZ" Grid.Row="1" Grid.Column="8"   #2 
    BarFill="Red" Scale="100" />                        #2 
  <l:Bar x:Name="gyroX" Grid.Row="1" Grid.Column="1" 
    BarFill="Blue" Scale="1.111111" />
  <l:Bar x:Name="gyroY" Grid.Row="1" Grid.Column="5" 
    BarFill="Blue" Scale="1.111111" />
  <l:Bar x:Name="gyroZ" Grid.Row="1" Grid.Column="9" 
    BarFill="Blue" Scale="1.111111" />
  <l:Bar x:Name="inclineX" Grid.Row="1" Grid.Column="2" 
    BarFill="DarkGreen" Scale="1.111111" />
  <l:Bar x:Name="inclineY" Grid.Row="1" Grid.Column="6" 
    BarFill="DarkGreen" Scale="2.222222" />
  <l:Bar x:Name="compassZ" Grid.Row="1" Grid.Column="10" 
    BarFill="DarkGreen" Scale="0.555556" />
  <StackPanel Grid.Row="2" Grid.ColumnSpan="11">                     #3
    <TextBlock Foreground="Red" Text="Accelerometer (g)" />          #3
    <TextBlock Foreground="Blue" Text="Gyrometer (deg/sec)" />
    <TextBlock Foreground="DarkGreen" Text="Inclinometer (degrees)" />
    <TextBlock x:Name="heading" Foreground="Yellow" />
    <TextBlock x:Name="point" Foreground="Violet" />
    <TextBlock x:Name="messageBlock" TextWrapping="Wrap" />
  </StackPanel >
</Grid>
#1 Eleven columns
#2 Three bars for each sensor
#3 Legend and messages

开始时将ContentPanel划分成三行和11列#1。第一列包含三个文本块的形式作为标题的x、y和z坐标。第二列显示三个栏#2每一的重力,轴摇摆,和测斜仪传感器。栏控件为400像素高分为正面和负面的200像素中的每个部分。您需要允许为每个传感器的三个列和两个分隔列,共十一列。#3的最后一列包含一个legeng和消息。

每个栏控件被BarFill颜色填充—红色为加速度计的读数、蓝色为轴摇摆的读数,深绿色为测斜仪的读数。每个控件都分配到一个可缩放值。

使用计时器来轮询传感器数据

在示例应用程序中每个不同的传感器的数据更新屏幕。为了简化逻辑,应用程序将不使用 ReadingChanged事件传感器,改为使用轮询的方法。DispatchTimer一秒钟更新用户界面15次左右。添加一个DispatchTimer字段:

DispatcherTimer timer;
计时器 字段 MainPagecon 结构里 完成初始化 计时器 滴答声 66 毫秒为单位) (每秒 15 左右 )。 应用程序 通过在 方法 timer_Tick 轮询 每个 传感器 数据
public MainPage()
{
    InitializeComponent();
    timer = new DispatcherTimer();
    timer.Tick += timer_Tick;
    timer.Interval = TimeSpan.FromMilliseconds(66);
    start();
}
start_Click 方法 启动 计时器 。一旦 计时器 启动 应用程序 通过更新messageBlock 下的Text字段 来告诉 用户 哪些 传感器 启动
void start()
{
    if (!timer.IsEnabled)
    {
        string runningMessage = "Reading: ";
        
        // Sensors will be initialized here

        timer.Start();
        messageBlock.Text = runningMessage;
    }
}
你也可以在start方法后添加代码来连接其他的传感器,如加速度传感器、 指南针、陀螺仪

总结

我们已经介绍了三个不同的硬件传感器,并涵盖了其他传感器的两个类。加速度计返回作用在设备上的力。指南针告诉我们设备的方位。 陀螺仪则返回设备的转速。但没有任何报告线性速度或角速度加速度记的传感器。也没有传感器告诉我们带这电话我们行走了多远。

测斜仪和方位传感器通过使用从重力、指南针、和陀螺仪传感器的得到数据,进行几个复杂的运算,将设备的坐标系统信息转换成现实世界方位。

应用开发人员可以考虑将一个或多个传感器采集的数据与位置服务结合来构建的应用程序,用数字世界的网格来勾画现实世界。可以建增强的现实应用程序,如标识用户的附近地标位置或星座在夜空中的方位。