Android Things 教程:如何编写驱动:GPS 驱动、按钮驱动、传感器驱动

在本系列的前一篇文章中,您了解了可用于 Android Things 的各种外围输入输出设备和连接。接下来,您可以扩展这些知识来编写新的类,称为驱动程序,它将使与外设交互更加容易。本文将重点介绍可以为 Android Things 编写的驱动程序的类型。

Android Things 用户空间的驱动程序

用户空间驱动程序,允许开发人员在 Android 框架中注入新的硬件,允许他们与已经建立的 Android API 进行交互。虽然您可以使用标准输入/输出 API 直接与设备进行通信,但编写自定义驱动程序将允许您的应用支持各种硬件配置文件,并直接与 Android 操作系统配合使用。此外,您的代码将更加结构化,并轻松支持代码重用。

本文将介绍三种主要的驱动程序分类:GPS 驱动程序,人机输入设备(HID)驱动程序和传感器驱动程序。

GPS 驱动程序

如果您的设备需要位置信息,那么您可能需要将 GPS 设备添加到您的应用程序中。通过注册您的 GPS 外围设备与 UserDriverManager,您将能够将您的设备的位置数据注入 Android 框架,允许 Android 的位置服务使用它。它将结合 GPS 数据与 WiFi 和任何其他位置来源,为您的应用程序提供更准确的数据结果。

通常,您的 GPS 模块将通过 UART 连接连接到 Android Things 设备。在本教程中,我们不会深入介绍 UART,但是您可以在本系列的前一教程中了解所有关于外设的工作原理。

GPS 设备

要使用您的 GPS 模块,您需要创建一个新的 Java 组件来与您的新设备进行通信。 我们将称这个类为 GpsDriverService

为了将您的 GPS 模块将位置数据注册到 Android 框架中,首先您需要在您的 GpsDriverService 中创建一个 GpsDriver 对象。该对象可以通过 registerGpsDriver() 调用注册到 UserDriverManager

private GpsDriver mDriver;

@Override
public void onCreate() {
    super.onCreate();

    mDriver = new GpsDriver();

    UserDriverManager manager = UserDriverManager.getManager();
    manager.registerGpsDriver( mDriver );
}

一旦您的 GPS 模块接收到位置数据,并通过串行 UART 连接将其发送到您的 Android Things 设备,您将需要解析并将其添加到 Location 对象。

大多数 GPS 模块将以 NMEA 格式返回位置数据,但解析这些数据超出了本教程的范围。您的位置对象有四个必需的数据项:精度,时间,纬度和经度。 您还可以选择包含高度,方位和速度(如果设备正在移动)。

private Location parseLocationFromString(String gpsData) {
    Location result = new Location(LocationManager.GPS_PROVIDER);

    //parse gpsData

    //required
    result.setAccuracy( getAccuracyFromGpsData( gpsData ) );
    result.setTime( getTimeFromGpsData( gpsData ) );
    result.setLatitude( getLatitudeFromGpsData( gpsData ) );
    result.setLongitude( getLongitudeFromGpsData( gpsData ) );

    //optional
    result.setAltitude( getAltitudeFromGpsData( gpsData ) );
    result.setBearing( getBearingFromGpsData( gpsData ) );
    result.setSpeed( getSpeedFromGpsData( gpsData ) );

    return result;
}

一旦你填充了你的 Location 对象,你可以通过调用 reportLocation() 把它传递给 GpsDriver

Location location = parseLocationFromString( rawGpsData );
mDriver.reportLocation( location );

一旦你的组件被创建,你将需要实例化它,开始读取数据,并在你的应用程序中监听更新。

private LocationListener mLocationListener = new LocationListener() {
    @Override
    public void onLocationChanged(Location location) {
        Log.v("Test", "Location update: " + location);
    }

    @Override
    public void onStatusChanged(String provider, int status, Bundle extras) {
        Log.e("Test", "onstatuschanged");
    }

    @Override
    public void onProviderEnabled(String provider) {
        Log.e("Test", "onproviderenabled");
    }

    @Override
    public void onProviderDisabled(String provider) {
        Log.e("Test", "onproviderdisabled");
    }
};

...

mLocationManager = (LocationManager) getSystemService(LOCATION_SERVICE);

// Create and start the GPS component
mGpsDriver = new GpsDriverService();
mGpsDriver.register();

// Register for location updates
mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, mLocationListener);

当您的应用程序结束后,您将需要取消注册您的驱动程序,并删除您的位置监听。

@Override
protected void onDestroy() {
    super.onDestroy();

    if (mGpsDriver != null) {
        mGpsDriver.unregister();
        mLocationManager.removeUpdates(mLocationListener);

        try {
            mGpsDriver.close();
        } catch (IOException e) {
        }
    }
}

另外,您需要确保您的应用具有 ACCESS_FINE_LOCATION 权限。

当首次使用 Android Things 中的新权限的应用程序工作时,安装应用程序后需要重新启动设备,以确保授予权限。

人机输入设备

Android 框架内置管道,用于处理用户按钮和动作事件的输入,比如媒体按钮、控制器操纵杆和键盘按键等。通过创建 InputDriver,您可以将您自己的人机交互与标准 Android 输入管道绑定,以便您的设备可以正确地对用户做出反应。

为了简单起见,我们只看按钮输入事件,以及如何将它们绑定到 Android 框架,尽管运动事件以非常相似的方式处理。与本教程的上一部分类似,我们将忽略输入设备的更具体的实现细节,并着重于将接收到的事件绑定到 Android 平台。

按钮事件

当您从 Android Things 设备上连接的按钮发生按钮事件时,您需要记录该事件并通过 Android 管道发送。

你需要做的第一件事是,在一个新的 Service 中创建一个 InputDriver 对象并初始化它。驱动程序可以使用一个构建器进行初始化,该构建器接受输入类型,输入的名称,版本和按钮代表的键码。

private InputDriver mDriver;

@Override
public void onCreate() {
    super.onCreate();

    mDriver = InputDriver.builder(InputDevice.SOURCE_CLASS_BUTTON)
            .setName("ButtonInputDriver")
            .setVersion(1)
            .setKeys(new int[] {KeyEvent.KEYCODE_SPACE})
            .build();
}

一旦你的 InputDriver 被初始化,你可以使用 registerInputDriver() 调用来注册 UserDriverManager

UserDriverManager manager = UserDriverManager.getManager();
manager.registerInputDriver(mDriver);

一旦你注册了 InputDriver,你的驱动程序服务就可以等待事件,从你的按钮实现类发送给它。如果您的自定义按钮被按下,您可以通知服务并创建一个新的 KeyEvent,可以使用 emit(KeyEvent) 方法将其放置在 Android 输入管道上。 如果 KeyEvent 能够被发送到 Android 框架,该方法将返回 true;如果发生错误,则返回 false

if( buttonPressed ) {
    KeyEvent[] events = new KeyEvent[] {new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_SPACE)};

    if (!mDriver.emit(events)) {
        //something went wrong
    }
}

你需要做的最后一件事就是当你的应用程序运行完毕后,从 UserDriverManager 注销你的 InputDriver 对象。

@Override
public void onDestroy() {
    super.onDestroy();

    UserDriverManager manager = UserDriverManager.getManager();
    manager.unregisterInputDriver(mDriver);
}

监听输入事件

现在您可以将按钮输入事件,发送到 Android 输入管道,现在是时候听取它们了。这就是所有将您的新按钮事件,汇集到 Android 框架中的工作所付出的代价。在应用程序的 Activity 中,只需要为 KeyEvent 关闭时添加一个方法,而在 KeyEvent 启动时则需要添加另一个方法。在这些方法中,您可以检查 KeyEvent 的关键代码并适当地处理事件。

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {

    if( event.getKeyCode() == KeyEvent.KEYCODE_SPACE ) {
        //handle it
    }

    return true;
}

@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
    return true;
}

传感器驱动

一些 Android Things 开发板最常用的组件是传感器。由于 Android 具有相当强大的传感器框架,因此我们希望能够将来自外部组件的数据添加到该管道中。

首先,您需要创建一个与硬件传感器交互的新 Java 类。这个类将需要扩展 UserSensorDriver 并实现 read() 方法。此外,如果您的传感器支持低功耗或睡眠模式,则可以覆写 setEnabled() 方法并相应地采取行动。

下面的代码片段是组件类的桩(stubbed)的版本,它将通过 Peripheral I/O API 读取数据以检索 X,Y 和 Z 数据值并返回新的 UserSensorReading。 如果数据当前不可用,您的类可以抛出一个新的 IOException

public class ExampleSensorComponent extends UserSensorDriver {
    float x, y, z;

    @Override
    public UserSensorReading read() throws IOException{
        try {
            // Read data from the sensor hardware and return it
            x = getXValueFromHardware();
            y = getYValueFromHardware();
            z = getZValueFromHardware();

            return new UserSensorReading(new float[]{x, y, z});
        } catch (Exception e) {
            // Error occurred reading the sensor hardware
            throw new IOException("Unable to read sensor");
        }
    }

    //Used if supporting low power/sleep mode
    @Override
    public void setEnabled(boolean enabled) throws IOException {
        super.setEnabled(enabled);
    }
}

一旦创建了组件类,就可以创建一个新的 Service 来实例化它,并创建一个新的 UserSensor 对象,并将其附加到 Android 传感器管道中。

有两种类型的传感器可以添加到这个管道。第一种是预定义的类型,比如陀螺仪、加速度计、光源和接近度,可以像下面这样添加到管道中:

private ExampleSensorComponent mExampleSensor;
private UserSensor mSensor;

private SensorManager mSensorManager;

@Override
public void onCreate() {
    super.onCreate();

    mExampleSensor = new ExampleSensorComponent();

    mSensor = UserSensor.builder()
            .setName("ExampleSensorComponent")
            .setVendor("VendorName")
            .setType(Sensor.TYPE_ACCELEROMETER)
            .setDriver(mExampleSensor)
            .build();

    UserDriverManager manager = UserDriverManager.getManager();

    // Register the new driver with the framework
    manager.registerSensor(mSensor);

    mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
    mSensorManager.registerDynamicSensorCallback(new SensorCallback());
}

private class SensorCallback extends SensorManager.DynamicSensorCallback {
    @Override
    public void onDynamicSensorConnected(Sensor sensor) {
        //Sensor connected
        mSensorManager.registerListener(SensorDriverService.this, sensor,
                SensorManager.SENSOR_DELAY_NORMAL);
    }

    @Override
    public void onDynamicSensorDisconnected(Sensor sensor) {
        //Sensor disconnected
        mSensorManager.unregisterListener(SensorDriverService.this);
    }
}

您会注意到在上例中使用了 SensorManager.DynamicSensorCallback。当传感器可用于框架时,它会通知您的应用程序,因为注册可能需要一些时间,所以框架不会尝试从不可用设备读取数据。

BMP280

第二种类型是自定义,它涵盖了 Android 中尚未支持的任何内容。一些例子包括水的 pH 值、风速、运动检测,或其他任何可以用新硬件测量的东西。

通过用 setCustomType() 替换 setType() 建造者(builder)参数,可以添加设备的名称和报告模式,以控制在管道上的触发方式。

mSensor = UserSensor.builder()
        .setName("ExampleSensorComponent")
        .setVendor("VendorName")
        .setType(Sensor.TYPE_ACCELEROMETER)
        .setCustomType(Sensor.TYPE_DEVICE_PRIVATE_BASE,
                "com.tutsplus.examplesensor",
                Sensor.REPORTING_MODE_ON_CHANGE)
        .setDriver(mExampleSensor)
        .build();

最后,当您的应用程序运行完毕后,您需要从 UserDriverManager 注销新组件。

@Override
public void onDestroy() {
    super.onDestroy();
    UserDriverManager manager = UserDriverManager.getManager();
    manager.unregisterSensor(mSensor);
}

结论

在本教程中,您学习了如何使用外设 I/O API构建组件,并将它们绑定到相应的 Android 框架中,以便在 Android Things 应用程序中使用。

在这个系列的这一点,你有所有必要的工具来创建一些更深入的 Android 东西项目。 除了编写自己的驱动程序之外,您还可以找到已编写的驱动程序并将其实施到自己的项目中。您可以从 Android Things GitHub 库中找到这些驱动程序的源代码,或者使用这些驱动程序查看一些正在运行的示例

在本系列的下一篇文章中,我们将更进一步,创建一个完整的 Android Things 项目,从 Raspberry Pi 拍摄照片并将其上传到 Firebase。

原文链接:https://code.tutsplus.com/tutorials/android-things-understanding-and-writing-drivers--cms-28088

尚未评分
您的评分将帮助我们做出更好的玩法

观光\评论区

Copyright © 2017 玩点什么. All Rights Reserved.