使用移动传感器 API

作者:Vikram Goyal

本文介绍移动传感器 API(适用于 Java Platform Micro Edition 的 JSR 256)如何使 Java MIDlet 能够与设备上可用的物理和虚拟传感器通信。

2010 年 9 月发布


下载:
下载 Java ME

移动传感器 API(适用于 Java Platform Micro Edition [Java ME] 的 JSR 256)能够使 Java MIDlet 与设备上可用的物理和虚拟传感器通信。此 API 特别有用,因为通过这个 API,MIDlet 不仅可以评估设备的运行状况,而且还可以评估外部物理环境的状况,这让我们可以开发出一些非常有趣的应用程序。当然,与所有 API 一样,设备制造商对此 API 的实现决定了实际可用的传感器。

在本文中,我将介绍此 API,并提供一些简单代码来确定设备上的可用传感器。然后,创建一个示例 MIDlet,该 MIDlet 使用加速计来计算出设备当前的倾斜度。利用倾斜方向(axisXaxisYaxisZ),Java 设备可确定用户握持设备的方式以便相应地操作用户界面。

介绍移动传感器 API

移动传感器 API 源于 JSR 256。通过此 API 提供的方法可以从物理和虚拟传感器中统一获取和监视数据。最简单的传感器示例是电池监视器,它会告诉 MIDlet 电池电量级别。使用此 API,如果您先创建一个传感器连接,然后注册以便监听源自此传感器的事件,您就可以在 MIDlet 中就电池传感器告诉您的信息做出明智的决策。

传感器可能允许,也可能不允许用户通过编程接口控制传感器。举例来说,如果一个连接到设备的物理传感器允许用户测量当前温度,则此传感器还可能提供对物理启动、停止或校准温度计的控制。为此,移动传感器 API 分为两部分:

  • 传感器部分,包含在 javax.microedition.sensor 包中
  • 控制部分,包含在 javax.microedition.sensor.control 包中

传感器部分只负责从传感器(物理或虚拟)获取信息。控制部分提供控制接口。

定义传感器

在移动传感器 API 的上下文中,使用两个参数定义传感器:

  • Quantity
  • Context

可以将 quantity 看作要测量的实际项,例如电池电量。context(或使用准确的术语 context type)是指在其中测量或监视 quantity 的环境,例如设备。因此,如果传感器要为您提供设备的电池电量,则监视或测量的 quantity 为电池电量,context 为 device

就参数而言,如果您想知道电动车辆的电池电量(通过连接到设备的传感器),可能会使用这样定义的传感器:quantity 为电池电量,context 为 vehicle。我们继续简单地依此类推,如果用户持有可测量电池电量的传感器,则 quantity 为电池电量,context 为 user。最后一个 context type 名为 ambient,它用于定义在周围环境中对某物进行测量。(我可以说检测周围环境的电池电量,但这太愚蠢了。)

因此,有四个用于定义传感器的 context type:

  • Ambient
  • User
  • Device
  • Vehicle

如果 context type 定义传感器的“位置”,则 quantity 定义“内容”。传感器将测量设备上(即某个位置)的电池电量(即某种内容)。

如您所料,quantity 可以有数百种,其中许多已由 API 标准化并在 javax.microedition.sensor.SensorInfo 接口中定义。公司可以引入他们自己的 quantity,只要这些参数不同于设备上的现有参数即可。

此 API 指定将有关传感器的信息封装在 SensorInfo 接口中。此接口提供有关传感器的信息,它须提供要测量的 quantity、context type、以及传感器的连接类型(嵌入、有线、短程无线或远程)、传感器型号、传感器的 URL、传感器的描述,并以通道的形式(单位小数点位数范围 等)提供有关传感器的元信息。一个 SensorInfo 对象需要许多信息。从 API 方面来说,所有信息都是必需的,并且不能为空。(遗憾的是,与大多数 API 定义一样,此要求在 API 自身中的多个地方相互矛盾;似乎真正需要的属性只有 quantity 和 context type。)除了这些属性之外,API 还定义了一些可选参数。

如何定义传感器的最后一个关键部分通过 SensorConnection 接口来解决。通过此接口的实现,可以连接到实际传感器并从中收集数据,这既可以同步执行,也可以异步执行。如您所料,异步通知需要您在接收器类中实现 DataListener 接口。通过此接口中的两个 getData 方法,可以从底层传感器获得实际数据。

查找传感器

为了识别和使用传感器,此 API 提供了一个名为 SensorManager 的管理器类,它有两种方法可查找设备中的传感器:

  • findSensors(String quantity, String contextType) 返回与给定 quantity 和 context type 匹配的一组 SensorInfo 对象。
  • findSensors(String url) 需要您知道现有传感器的 URL。URL 的定义类似于 HTTP URL,以 sensor 取代 HTTP。因此,sensor:battery_charge;context_type=device 是有效的 URL。此方法返回一组可能的 SensorInfo 对象。

查找设备中的所有传感器

要查找设备中所有可用的传感器,只需调用 findSensors(String quantity, String contextType) 方法,让两个参数均为空值。在理论上,这会返回所有可用的传感器。以下是一个 MIDlet 的代码,该 MIDlet 可打印出设备上的可用传感器。

public class MobileSensorFinder extends MIDlet implements CommandListener {

  // the main display
  Display display;

  // the canvas on which we will show the sensor information
  Canvas canvas;

  // the command
  Command exitCommand;

  public MobileSensorFinder() {

    // get the current display
    display = Display.getDisplay(this);
  }

  public void startApp() {

    // first, find out the sensor version supported by the device
    // if no version information can be found, then sensors are probably not
    // supported
    String version = System.getProperty("microedition.sensor.version");
    if(version != null) {

      // create the sensor - which initiates looking for sensors
      canvas = new SensorCanvas();

      // create the exit command and attach to Canvas
      exitCommand = new Command("Exit", Command.EXIT, 1);

      canvas.addCommand(exitCommand);
      canvas.setCommandListener(this);

      display.setCurrent(canvas);

    } else {
      Alert alert =
        new Alert("Error",
                  "No version info found. Sensors are probably not supported!",
                  null, AlertType.ERROR);
      display.setCurrent(alert);
    }
  }

  public void pauseApp() {
  }

  public void destroyApp(boolean unconditional) {
  }

  public void commandAction(Command c, Displayable d) {
    // handle the exit command
    if (c == exitCommand) {
      destroyApp(true);
      notifyDestroyed();
      return;
    }
  }
}

class SensorCanvas extends Canvas {

  // the array of SensorInfo objects
  SensorInfo foundSensors[] = null;

  public SensorCanvas() {
    searchSensors(); // look for them
  }

  protected void paint(Graphics g) {

    // clear the screen
    g.setGrayScale(255);
    g.fillRect(0, 0, getWidth(), getHeight());

    g.setColor(0x000000);

    // let's get the version of the sensors
    g.drawString("Sensor Version: " +
      System.getProperty("microedition.sensor.version"), 0, 0, 0);

    if (foundSensors == null || foundSensors.length == 0) {
      g.drawString("No Sensors found", 0, 2, 0); // nothing found?
    } else {
      int noOfSensors = foundSensors.length; // how many?
      int ydraw = 15; // to help with drawing
      for (int i = 0; i < noOfSensors; i++) {
        g.drawString(
          "Sensor [" + (i + 1) + "]:\r\n " +
          foundSensors[i].getDescription(), 0, ydraw , 0);
        ydraw = ydraw + 30;
      }
    }

  }

  private void searchSensors() {
    // simple enough, find all the sensors
    foundSensors = SensorManager.findSensors(null, null);
  }
}

SensorCanvassearchSensors 方法搜索传感器,并使用 paint 方法在屏幕上绘制有关传感器的信息。在创建 SensorCanvas 之前,MIDlet 通过尝试查找 microedition.sensor.version 系统属性来检查当前设备是否支持移动传感器 API。如果此属性为空,则 MIDlet 不会再执行下去。

处理传感器信息

找到感兴趣的传感器之后,您如何处理它生成的数据?如上一节中所述,您针对此传感器使用 SensorInfo 实现,并在获得传感器的 SensorConnection 之后,使用此连接直接或通过监听器获得数据。

实际数据封装在 Data 接口中。由于传感器发送的数据可能不只一个测量单位,因此数据通过通道传递。例如,温度计传感器发送的数据是一个单位(温度),但测量设备倾斜度的传感器发送的数据至少具有三个通道(X、Y 和 Z),它们一起组成传感器发送的数据。因此,检索的数据在 Data 对象数组中,每个对象都有自己的通道。ChannelInfo 接口提供一些方法来查询有关通道的信息,例如名称、数据类型、精度等。

Data 接口提供三个方法来检索数据值:

  • getIntValues
  • getDoubleValues
  • getObjectValues

您可以使用 ChannelInfo.getDataType 方法获得所需数据的类型。

使用我们目前为止已有的信息,可以轻松地创建一个简单的 MIDlet 以回显当前设备倾斜度。以下代码段展示捕获并显示此信息的 TiltCanvas 类。

class TiltCanvas extends Canvas implements DataListener {

  // the sensor
  SensorInfo tiltSensor = null;

  // the connection to it
  SensorConnection connection = null;

  // parameters used to find current tilt
  double axisX, axisY, axisZ;

  public TiltCanvas() {

    // let's find this particular sensor
    SensorInfo sensors[] =
      SensorManager.findSensors(
      "acceleration", SensorInfo.CONTEXT_TYPE_DEVICE);

    if (sensors == null || sensors.length == 0) {
      System.err.println("Nothing found!");
    } else if (sensors.length > 1) {
      System.err.println("Too many sensors found!");
    } else {
      tiltSensor = sensors[0];
      try {
        connection = (SensorConnection) Connector.open(tiltSensor.getUrl());
        connection.setDataListener(this, 10);
      } catch (IOException ex) {
        ex.printStackTrace();
      }
    }

  }

  protected void paint(Graphics g) {

    // clear the screen
    g.setGrayScale(255);
    g.fillRect(0, 0, getWidth(), getHeight());

    g.setColor(0x000000);

    if (tiltSensor == null) { // sensor not found
      g.drawString("No valid Tilt Sensor found!", 0, 0, 0);
      return;
    } else if (connection == null) { // sensor found, but could not connect
      g.drawString("Can't connect to the Sensor!", 0, 0, 0);
      return;
    } else { // sensor found, and active connection
      g.drawString("Connected and getting data", 0, 0, 0);
      g.drawString("Axis X: " + axisX, 0, 30, 0);
      g.drawString("Axis Y: " + axisY, 0, 60, 0);
      g.drawString("Axis Z: " + axisZ, 0, 90, 0);

    }
  }

  public void dataReceived(SensorConnection conn, Data[] data, boolean lost) {
    axisX = data[0].getDoubleValues()[0];
    axisY = data[1].getDoubleValues()[0];
    axisZ = data[2].getDoubleValues()[0];
    repaint();
  }
}

如您所见,tilt 画布需要对 quantity 使用关键字 acceleration,对 context type 使用 SensorInfo.CONTEXT_TYPE_DEVICE 来查找 tilt 传感器。这是适用于 Java ME SDK 3.0 的唯一组合,如果您在其他设备或模拟平台上运行此代码,则需要使用先前的 SensorFinder MIDlet 来查找要使用的正确值。该画布实现 dataReceived 方法来捕获连接到仿真器的加速传感器发送的数据事件。由于我们知道哪个通道用于哪个轴(我们可以通过反复试验得知,我就是这样做的,或者通过仿真器附带的文档得知),因此可以将通道数据分配给正确的轴。在此例中,data[0] 为 X 轴,data[1] 为 Y 轴,data[2] 为 Z 轴。

图 1 显示了在仿真器中运行的此 MIDlet。


图 1:MIDlet 捕获外部传感器事件以提供设备的当前倾斜度

总结

本文介绍了移动传感器 API (JSR 256),它由来已久,并在 Java ME 平台上的应用程序中越来越受欢迎。我们了解了此 API 及其接口的详细信息,也学习了如何在设备或仿真器中查找所有传感器。最后,我们通过一个简单的示例了解了如何使用传感器返回的信息,此示例从加速器中检索数据来得出设备的当前倾斜度。

另请参见

关于作者

Vikram Goyal 是 Apress 出版的《Pro Java ME MMAPI:Mobile Media API for Java Micro Edition》一书的作者。该书介绍如何向支持 Java 技术的手机添加多媒体功能。Vikram 还是《Jakarta Commons Online Bookshelf》一书的作者,他还帮助管理免费的 craft projects 网站

false