From 2df29dc83ac3966e9f3c5b63a5d6d3c2ac688bc7 Mon Sep 17 00:00:00 2001 From: Macro Yau Date: Thu, 12 Jan 2017 16:00:51 +0800 Subject: [PATCH 1/7] Add HTS221 driver --- hts221/.gitignore | 1 + hts221/README.md | 131 +++++ hts221/build.gradle | 37 ++ hts221/publish.gradle | 97 ++++ hts221/src/main/AndroidManifest.xml | 22 + .../things/contrib/driver/hts221/Hts221.java | 465 ++++++++++++++++++ .../driver/hts221/Hts221SensorDriver.java | 233 +++++++++ .../contrib/driver/hts221/BitsMatcher.java | 49 ++ .../driver/hts221/BitsMatcherTest.java | 61 +++ .../contrib/driver/hts221/Hts221Test.java | 263 ++++++++++ settings.gradle | 1 + 11 files changed, 1360 insertions(+) create mode 100644 hts221/.gitignore create mode 100644 hts221/README.md create mode 100644 hts221/build.gradle create mode 100644 hts221/publish.gradle create mode 100644 hts221/src/main/AndroidManifest.xml create mode 100644 hts221/src/main/java/com/google/android/things/contrib/driver/hts221/Hts221.java create mode 100644 hts221/src/main/java/com/google/android/things/contrib/driver/hts221/Hts221SensorDriver.java create mode 100644 hts221/src/test/java/com/google/android/things/contrib/driver/hts221/BitsMatcher.java create mode 100644 hts221/src/test/java/com/google/android/things/contrib/driver/hts221/BitsMatcherTest.java create mode 100644 hts221/src/test/java/com/google/android/things/contrib/driver/hts221/Hts221Test.java diff --git a/hts221/.gitignore b/hts221/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/hts221/.gitignore @@ -0,0 +1 @@ +/build diff --git a/hts221/README.md b/hts221/README.md new file mode 100644 index 0000000..e8544c3 --- /dev/null +++ b/hts221/README.md @@ -0,0 +1,131 @@ +HTS221 driver for Android Things +================================ + +This driver supports STMicroelectronics [HTS221][product_hts221] capacitive digital sensor for +relative humidity and temperature. + +NOTE: these drivers are not production-ready. They are offered as sample +implementations of Android Things user space drivers for common peripherals +as part of the Developer Preview release. There is no guarantee +of correctness, completeness or robustness. + +How to use the driver +--------------------- + +### Gradle dependency + +To use the `hts221` driver, simply add the line below to your project's `build.gradle`, +where `` matches the last version of the driver available on [jcenter][jcenter]. + +``` +dependencies { + compile 'com.google.android.things.contrib:driver-hts221:' +} +``` + +### Sample usage + +```java +import com.google.android.things.contrib.driver.hts221.Hts221; + +// Access the environmental sensor: + +Hts221 mHts221; + +try { + mHts221 = new Hts221(i2cBusName); +} catch (IOException e) { + // Couldn't configure the device... +} + +// Read the current humidity: + +try { + float humidity = mHts221.readHumidity(); +} catch (IOException e) { + // Error reading humidity +} + +// Read the current temperature: + +try { + float temperature = mHts221.readTemperature(); +} catch (IOException e) { + // Error reading temperature +} + +// Close the environmental sensor when finished: + +try { + mHts221.close(); +} catch (IOException e) { + // Error closing sensor +} +``` + +If you need to read sensor values continuously, you can register the HTS221 with the system and +listen for sensor values using the [Sensor APIs][sensors]: + +```java +SensorManager mSensorManager = getSystemService(Context.SENSOR_SERVICE); +SensorEventListener mHumidityListener = ...; +SensorEventListener mTemperatureListener = ...; +Hts221SensorDriver mSensorDriver; + +mSensorManager.registerDynamicSensorCallback(new SensorManager.DynamicSensorCallback() { + @Override + public void onDynamicSensorConnected(Sensor sensor) { + if (sensor.getType() == Sensor.TYPE_RELATIVE_HUMIDITY) { + mSensorManager.registerListener(mHumidityListener, sensor, + SensorManager.SENSOR_DELAY_NORMAL); + } else if (sensor.getType() == Sensor.TYPE_AMBIENT_TEMPERATURE) { + mSensorManager.registerListener(mTemperatureListener, sensor, + SensorManager.SENSOR_DELAY_NORMAL); + } + } +}); + +try { + mSensorDriver = new Hts221SensorDriver(i2cBusName); + mSensorDriver.registerHumiditySensor(); + mSensorDriver.registerTemperatureSensor(); +} catch (IOException e) { + // Error configuring sensor +} + +// Unregister and close the driver when finished: + +mSensorManager.unregisterListener(mHumidityListener); +mSensorManager.unregisterListener(mTemperatureListener); +mSensorDriver.unregisterHumiditySensor(); +mSensorDriver.unregisterTemperatureSensor(); +try { + mSensorDriver.close(); +} catch (IOException e) { + // Error closing sensor +} +``` + +License +------- + +Copyright 2016 Macro Yau + +Licensed to the Apache Software Foundation (ASF) under one or more contributor +license agreements. See the NOTICE file distributed with this work for +additional information regarding copyright ownership. The ASF licenses this +file to you under the Apache License, Version 2.0 (the "License"); you may not +use this file except in compliance with the License. You may obtain a copy of +the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +License for the specific language governing permissions and limitations under +the License. + +[product_hts221]: http://www.st.com/en/mems-and-sensors/hts221.html +[jcenter]: https://bintray.com/google/androidthings/contrib-driver-hts221/_latestVersion +[sensors]: https://developer.android.com/guide/topics/sensors/sensors_overview.html diff --git a/hts221/build.gradle b/hts221/build.gradle new file mode 100644 index 0000000..9217d7d --- /dev/null +++ b/hts221/build.gradle @@ -0,0 +1,37 @@ +/* + * Copyright 2016 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +apply plugin: 'com.android.library' + +android { + compileSdkVersion 24 + buildToolsVersion '24.0.3' + + defaultConfig { + minSdkVersion 24 + targetSdkVersion 24 + versionCode 1 + versionName "1.0" + } +} + +dependencies { + provided 'com.google.android.things:androidthings:0.1-devpreview' + compile 'com.android.support:support-annotations:24.2.0' + + testCompile 'junit:junit:4.12' + testCompile 'org.mockito:mockito-core:1.10.19' +} diff --git a/hts221/publish.gradle b/hts221/publish.gradle new file mode 100644 index 0000000..3f73c00 --- /dev/null +++ b/hts221/publish.gradle @@ -0,0 +1,97 @@ +/* + * Copyright 2016 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * To publish on Bintray: + * - set environmental variables BINTRAY_USER and BINTRAY_API_KEY to proper values + * - from this directory: + * ../gradlew -b publish.gradle bintrayUpload + * + */ + +buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:2.2.1' + } +} + +plugins { + id "com.jfrog.bintray" version "1.7" +} + +allprojects { + repositories { + jcenter() + } +} + +apply from: 'build.gradle' +apply plugin: 'maven-publish' + +def packageVersion = '0.1' + +task sourceJar(type: Jar) { + classifier = 'sources' + from android.sourceSets.main.java.sourceFiles +} + +publishing { + publications { + driverPublish(MavenPublication) { + groupId 'com.google.android.things.contrib' + artifactId "driver-$project.name" + version packageVersion + artifacts = configurations.archives.artifacts + artifact sourceJar + pom.withXml { + def dependenciesNode = asNode().appendNode('dependencies') + configurations.compile.allDependencies.each { + if(it.group != null && (it.name != null || "unspecified".equals(it.name)) && it.version != null) + { + def dependencyNode = dependenciesNode.appendNode('dependency') + dependencyNode.appendNode('groupId', it.group) + dependencyNode.appendNode('artifactId', it.name) + dependencyNode.appendNode('version', it.version) + } + } + } + } + } +} + +bintray { + user = System.getenv('BINTRAY_USER') + key = System.getenv('BINTRAY_API_KEY') + publications = ['driverPublish'] + + publish = true + + pkg { + repo = 'androidthings' + name = "contrib-driver-$project.name" + userOrg = 'google' + + version { + name = packageVersion + gpg { + sign = true + } + } + } +} diff --git a/hts221/src/main/AndroidManifest.xml b/hts221/src/main/AndroidManifest.xml new file mode 100644 index 0000000..6a041e1 --- /dev/null +++ b/hts221/src/main/AndroidManifest.xml @@ -0,0 +1,22 @@ + + + + + + + diff --git a/hts221/src/main/java/com/google/android/things/contrib/driver/hts221/Hts221.java b/hts221/src/main/java/com/google/android/things/contrib/driver/hts221/Hts221.java new file mode 100644 index 0000000..cb62e4a --- /dev/null +++ b/hts221/src/main/java/com/google/android/things/contrib/driver/hts221/Hts221.java @@ -0,0 +1,465 @@ +/* + * Copyright 2016 Macro Yau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.things.contrib.driver.hts221; + +import android.support.annotation.IntDef; +import android.support.annotation.VisibleForTesting; + +import com.google.android.things.pio.I2cDevice; +import com.google.android.things.pio.PeripheralManagerService; + +import java.io.IOException; + +/** + * Driver for the HTS221 relative humidity and temperature sensor. + * + * @see HTS221 datasheet + */ +@SuppressWarnings({"unused", "WeakerAccess"}) +public class Hts221 implements AutoCloseable { + + // Sensor constants obtained from the datasheet + /** + * Device ID of the sensor. + */ + public static final int DEVICE_ID = 0xBC; // Value of the WHO_AM_I register + /** + * I2C address of the sensor. + */ + public static final int I2C_ADDRESS = 0x5F; + /** + * Minimum relative humidity that the sensor can measure. + */ + public static final float MIN_HUMIDITY_PERCENT = 0f; + /** + * Maximum relative humidity that the sensor can measure. + */ + public static final float MAX_HUMIDITY_PERCENT = 100f; + /** + * Minimum temperature in Celsius that the sensor can measure. + */ + public static final float MIN_TEMP_C = -40f; + /** + * Maximum temperature in Celsius that the sensor can measure. + */ + public static final float MAX_TEMP_C = 120f; + /** + * Maximum power consumption in micro-amperes when measuring relative humidity and temperature. + */ + public static final float MAX_POWER_CONSUMPTION_UA = 22.5f; + /** + * Maximum frequency of the measurements. + */ + public static final float MAX_FREQ_HZ = 12.5f; + /** + * Minimum frequency of the measurements. + */ + public static final float MIN_FREQ_HZ = 1f; + + /** + * Humidity average configuration. + */ + @IntDef({AV_CONF_AVGH_4, AV_CONF_AVGH_8, AV_CONF_AVGH_16, AV_CONF_AVGH_32, AV_CONF_AVGH_64, + AV_CONF_AVGH_128, AV_CONF_AVGH_256, AV_CONF_AVGH_512}) + public @interface HumidityAverageConfiguration { + } + + public static final int AV_CONF_AVGH_4 = 0b000; + public static final int AV_CONF_AVGH_8 = 0b001; + public static final int AV_CONF_AVGH_16 = 0b010; + public static final int AV_CONF_AVGH_32 = 0b011; // Default + public static final int AV_CONF_AVGH_64 = 0b100; + public static final int AV_CONF_AVGH_128 = 0b101; + public static final int AV_CONF_AVGH_256 = 0b110; + public static final int AV_CONF_AVGH_512 = 0b111; + + /** + * Temperature average configuration. + */ + @IntDef({AV_CONF_AVGT_2, AV_CONF_AVGT_4, AV_CONF_AVGT_8, AV_CONF_AVGT_16, AV_CONF_AVGT_32, + AV_CONF_AVGT_64, AV_CONF_AVGT_128, AV_CONF_AVGT_256}) + public @interface TemperatureAverageConfiguration { + } + + public static final int AV_CONF_AVGT_2 = 0b000; + public static final int AV_CONF_AVGT_4 = 0b001; + public static final int AV_CONF_AVGT_8 = 0b010; + public static final int AV_CONF_AVGT_16 = 0b011; // Default + public static final int AV_CONF_AVGT_32 = 0b100; + public static final int AV_CONF_AVGT_64 = 0b101; + public static final int AV_CONF_AVGT_128 = 0b110; + public static final int AV_CONF_AVGT_256 = 0b111; + + /** + * Power mode. + */ + @IntDef({MODE_POWER_DOWN, MODE_ACTIVE}) + public @interface Mode { + } + + public static final int MODE_POWER_DOWN = 0; + public static final int MODE_ACTIVE = 1; + + /** + * Output data rate configuration. + */ + @IntDef({HTS221_ODR_ONE_SHOT, HTS221_ODR_1_HZ, HTS221_ODR_7_HZ, HTS221_ODR_12_5_HZ}) + public @interface OutputDataRate { + } + + public static final int HTS221_ODR_ONE_SHOT = 0b00; + public static final int HTS221_ODR_1_HZ = 0b01; + public static final int HTS221_ODR_7_HZ = 0b10; + public static final int HTS221_ODR_12_5_HZ = 0b11; + + // Registers + private static final int HTS221_REG_WHO_AM_I = 0x0F; // R + private static final int HTS221_REG_AV_CONF = 0x10; // R/W + private static final int HTS221_REG_CTRL_REG1 = 0x20; // R/W + private static final int HTS221_REG_CTRL_REG2 = 0x21; // R/W + private static final int HTS221_REG_CTRL_REG3 = 0x22; // R/W + private static final int HTS221_REG_STATUS_REG = 0x27; // R + private static final int HTS221_REG_HUMIDITY_OUT_L = 0x28; // R + private static final int HTS221_REG_HUMIDITY_OUT_H = 0x29; // R + private static final int HTS221_REG_TEMP_OUT_L = 0x2A; // R + private static final int HTS221_REG_TEMP_OUT_H = 0x2B; // R + + // Calibration registers + private static final int HTS221_REG_H0_RH_X2 = 0x30; + private static final int HTS221_REG_H1_RH_X2 = 0x31; + private static final int HTS221_REG_T0_DEGC_X8 = 0x32; + private static final int HTS221_REG_T1_DEGC_X8 = 0x33; + private static final int HTS221_REG_T1_T0_MSB = 0x35; + private static final int HTS221_REG_H0_T0_OUT_L = 0x36; + private static final int HTS221_REG_H0_T0_OUT_H = 0x37; + private static final int HTS221_REG_H1_T0_OUT_L = 0x3A; + private static final int HTS221_REG_H1_T0_OUT_H = 0x3B; + private static final int HTS221_REG_T0_OUT_L = 0x3C; + private static final int HTS221_REG_T0_OUT_H = 0x3D; + private static final int HTS221_REG_T1_OUT_L = 0x3E; + private static final int HTS221_REG_T1_OUT_H = 0x3F; + + // Bit masks for CTRL_REG1 + private static final int HTS221_POWER_DOWN_MASK = 0b10000000; + private static final int HTS221_BDU_MASK = 0b00000100; + private static final int HTS221_ODR_MASK = 0b00000011; + + private I2cDevice mDevice; + + private final byte[] mBuffer = new byte[2]; // For reading registers + + private int mMode; + private boolean mBlockDataUpdate; + private int mOutputDataRate; + private float[] mTemperatureCalibration, mHumidityCalibration; // Calibration parameters + + /** + * Creates a new HTS221 sensor driver connected on the given bus. + * + * @param bus the I2C bus the sensor is connected to + * @throws IOException + */ + public Hts221(String bus) throws IOException { + PeripheralManagerService pioService = new PeripheralManagerService(); + I2cDevice device = pioService.openI2cDevice(bus, I2C_ADDRESS); + try { + connect(device); + } catch (IOException | RuntimeException e) { + try { + close(); + } catch (IOException | RuntimeException ignored) { + } + throw e; + } + } + + /** + * Creates a new HTS221 sensor driver connected to the given I2C device. + * + * @param device the I2C device of the sensor + * @throws IOException + */ + /*package*/ Hts221(I2cDevice device) throws IOException { + connect(device); + } + + private void connect(I2cDevice device) throws IOException { + mDevice = device; + + if ((mDevice.readRegByte(HTS221_REG_WHO_AM_I) & 0xFF) != DEVICE_ID) { + throw new IllegalStateException("I2C device is not HTS221 sensor"); + } + + setAveragedSamples(AV_CONF_AVGH_32, AV_CONF_AVGT_16); + setBlockDataUpdate(true); + setOutputDataRate(HTS221_ODR_1_HZ); + setMode(MODE_ACTIVE); + + readCalibration(); + } + + /** + * Sets the power mode of the sensor as power-down or active. + * + * @param mode must be either {@link #MODE_POWER_DOWN} or {@link #MODE_ACTIVE} + * @throws IOException + */ + public void setMode(@Mode int mode) throws IOException { + if (mDevice == null) { + throw new IllegalStateException("I2C device is already closed"); + } + + int regCtrl = mDevice.readRegByte(HTS221_REG_CTRL_REG1) & 0xFF; + if (mode == MODE_POWER_DOWN) { + regCtrl &= (~HTS221_POWER_DOWN_MASK & 0xFF); + } else { + regCtrl |= HTS221_POWER_DOWN_MASK; + } + mDevice.writeRegByte(HTS221_REG_CTRL_REG1, (byte) regCtrl); + mMode = mode; + } + + /** + * Returns the power mode of the sensor. + * + * @return true if the sensor is active + * @see #setMode(int) + */ + public boolean isEnabled() { + return mMode == MODE_ACTIVE; + } + + /** + * Sets the block data update (BDU) bit in the control register 1 (CTRL_REG1) of the sensor. + * Reading MSB and LSB of different samples can be avoided by enabling BDU. + * + * @param enabled enable BDU if true + * @throws IOException + */ + public void setBlockDataUpdate(boolean enabled) throws IOException { + if (mDevice == null) { + throw new IllegalStateException("I2C device is already closed"); + } + + int regCtrl = mDevice.readRegByte(HTS221_REG_CTRL_REG1) & 0xFF; + regCtrl &= (~HTS221_BDU_MASK & 0xFF); + if (enabled) { + regCtrl |= HTS221_BDU_MASK; + } + mDevice.writeRegByte(HTS221_REG_CTRL_REG1, (byte) regCtrl); + mBlockDataUpdate = enabled; + } + + /** + * Returns the status of the block data update mode. + * + * @return true if the block data update mode is enabled + * @see #setBlockDataUpdate(boolean) + */ + public boolean isBlockDataUpdateEnabled() { + return mBlockDataUpdate; + } + + /** + * Configures the output data rate (ODR) of the humidity and temperature measurements. + * + * @param outputDataRate the configuration must be one of {@link #HTS221_ODR_ONE_SHOT}, + * {@link #HTS221_ODR_1_HZ}, {@link #HTS221_ODR_7_HZ} or + * {@link #HTS221_ODR_12_5_HZ} + * @throws IOException + */ + public void setOutputDataRate(@OutputDataRate int outputDataRate) throws IOException { + if (mDevice == null) { + throw new IllegalStateException("I2C device is already closed"); + } + + int regCtrl = mDevice.readRegByte(HTS221_REG_CTRL_REG1) & 0xFF; + regCtrl &= (~HTS221_ODR_MASK & 0xFF); + regCtrl |= outputDataRate; + mDevice.writeRegByte(HTS221_REG_CTRL_REG1, (byte) regCtrl); + mOutputDataRate = outputDataRate; + } + + /** + * Returns the configured output data rate. + * + * @return the output data rate + * @see #setOutputDataRate(int) + */ + public int getOutputDataRate() { + return mOutputDataRate; + } + + /** + * Configures the number of averaged samples for the humidity and temperature measurements. + * + * @param humidityAverage the humidity average configuration must be one of the + * AV_CONF_AVGH* constants + * @param temperatureAverage the temperature average configuration must be one of the + * AV_CONF_AVGT* constants + * @throws IOException + */ + public void setAveragedSamples(@HumidityAverageConfiguration int humidityAverage, + @TemperatureAverageConfiguration int temperatureAverage) + throws IOException { + if (mDevice == null) { + throw new IllegalStateException("I2C device is already closed"); + } + + int regCtrl = mDevice.readRegByte(HTS221_REG_AV_CONF) & 0xC0; + regCtrl |= humidityAverage | (temperatureAverage << 3); + mDevice.writeRegByte(HTS221_REG_AV_CONF, (byte) (regCtrl)); + } + + /** + * Closes the driver and its underlying device. + */ + @Override + public void close() throws IOException { + if (mDevice != null) { + try { + setMode(MODE_POWER_DOWN); + mDevice.close(); + } finally { + mDevice = null; + } + } + } + + private void readCalibration() throws IOException { + if (mDevice == null) { + throw new IllegalStateException("I2C device is already closed"); + } + + mBuffer[0] = mDevice.readRegByte(HTS221_REG_H0_RH_X2); + mBuffer[1] = mDevice.readRegByte(HTS221_REG_H1_RH_X2); + int h0 = (mBuffer[0] & 0xFF); + int h1 = (mBuffer[1] & 0xFF); + + int h0T0Out = (short) readRegister(HTS221_REG_H0_T0_OUT_L); + int h1T0Out = (short) readRegister(HTS221_REG_H1_T0_OUT_L); + + int t0 = mDevice.readRegByte(HTS221_REG_T0_DEGC_X8) & 0xFF; + int t1 = mDevice.readRegByte(HTS221_REG_T1_DEGC_X8) & 0xFF; + int msb = mDevice.readRegByte(HTS221_REG_T1_T0_MSB) & 0x0F; + t0 |= (msb & 0x03) << 8; + t1 |= (msb & 0x0C) << 6; + + int t0Out = (short) readRegister(HTS221_REG_T0_OUT_L); + int t1Out = (short) readRegister(HTS221_REG_T1_OUT_L); + + mHumidityCalibration = calibrateHumidityParameters(h0, h1, h0T0Out, h1T0Out); + mTemperatureCalibration = calibrateTemperatureParameters(t0, t1, t0Out, t1Out); + } + + @VisibleForTesting + static float[] calibrateHumidityParameters(int h0, int h1, int h0T0Out, int h1T0Out) { + float[] humidityParameters = new float[2]; + humidityParameters[0] = ((h1 - h0) / 2.0f) / (h1T0Out - h0T0Out); + humidityParameters[1] = (h0 / 2.0f) - (humidityParameters[0] * h0T0Out); + return humidityParameters; + } + + @VisibleForTesting + static float[] calibrateTemperatureParameters(int t0, int t1, int t0Out, int t1Out) { + float[] temperatureParameters = new float[2]; + temperatureParameters[0] = ((t1 - t0) / 8.0f) / (t1Out - t0Out); + temperatureParameters[1] = (t0 / 8.0f) - (temperatureParameters[0] * t0Out); + return temperatureParameters; + } + + /** + * Reads the current humidity. + * + * @return the current relative humidity + */ + public float readHumidity() throws IOException { + if (mDevice == null) { + throw new IllegalStateException("I2C device is already closed"); + } + + if (isHumidityDataAvailable()) { + int rawHumidity = (short) readRegister(HTS221_REG_HUMIDITY_OUT_L); + return compensateSample(rawHumidity, mHumidityCalibration); + } else { + throw new IOException("Humidity data is not yet available"); + } + } + + /** + * Reads the current temperature. + * + * @return the current temperature in degrees Celsius + */ + public float readTemperature() throws IOException { + if (mDevice == null) { + throw new IllegalStateException("I2C device is already closed"); + } + + if (isTemperatureDataAvailable()) { + int rawTemp = (short) readRegister(HTS221_REG_TEMP_OUT_L); + return compensateSample(rawTemp, mTemperatureCalibration); + } else { + throw new IOException("Temperature data is not yet available"); + } + } + + /** + * Reads 16 bits from two 8-bit registers at the given address. + * + * @param address the address of the least significant byte (LSB) + * @throws IOException + */ + private int readRegister(int address) throws IOException { + mDevice.readRegBuffer(address | 0x80, mBuffer, 2); + return ((mBuffer[1] & 0xFF) << 8) | (mBuffer[0] & 0xFF); + } + + /** + * Validates the availability of updated humidity data. + * + * @return true if the humidity data is updated + * @throws IOException + */ + private boolean isHumidityDataAvailable() throws IOException { + return (mDevice.readRegByte(HTS221_REG_STATUS_REG) & 0x02) != 0; + } + + /** + * Validates the availability of updated temperature data. + * + * @return true if the temperature data is updated + * @throws IOException + */ + private boolean isTemperatureDataAvailable() throws IOException { + return (mDevice.readRegByte(HTS221_REG_STATUS_REG) & 0x01) != 0; + } + + /** + * Returns the sensor reading compensated with the given calibration parameters. + * + * @param rawValue the raw sensor reading value + * @param calibration the calibration parameters, where calibration[0] is the slope + * and calibration[1] is the intercept + * @return the sensor reading compensated with calibration parameters + */ + @VisibleForTesting + static float compensateSample(int rawValue, float[] calibration) { + return rawValue * calibration[0] + calibration[1]; + } + +} diff --git a/hts221/src/main/java/com/google/android/things/contrib/driver/hts221/Hts221SensorDriver.java b/hts221/src/main/java/com/google/android/things/contrib/driver/hts221/Hts221SensorDriver.java new file mode 100644 index 0000000..9c80610 --- /dev/null +++ b/hts221/src/main/java/com/google/android/things/contrib/driver/hts221/Hts221SensorDriver.java @@ -0,0 +1,233 @@ +/* + * Copyright 2016 Macro Yau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.things.contrib.driver.hts221; + +import android.hardware.Sensor; + +import com.google.android.things.userdriver.UserDriverManager; +import com.google.android.things.userdriver.UserSensor; +import com.google.android.things.userdriver.UserSensorDriver; +import com.google.android.things.userdriver.UserSensorReading; + +import java.io.IOException; +import java.util.UUID; + +/** + * User-space driver for interfacing the HTS221 relative humidity and temperature sensor to the + * Android SensorManager framework. + */ +public class Hts221SensorDriver implements AutoCloseable { + + private static final String DRIVER_VENDOR = "STMicroelectronics"; + private static final String DRIVER_NAME = "HTS221"; + private static final int DRIVER_MIN_DELAY_US = Math.round(1000000.0f / Hts221.MAX_FREQ_HZ); + private static final int DRIVER_MAX_DELAY_US = Math.round(1000000.0f / Hts221.MIN_FREQ_HZ); + + private Hts221 mDevice; + + private HumidityUserDriver mHumidityUserDriver; + private TemperatureUserDriver mTemperatureUserDriver; + + /** + * Creates a new framework sensor driver connected on the given bus. + * The driver emits {@link Sensor} with humidity and temperature data when registered. + * + * @param bus the I2C bus the sensor is connected to + * @throws IOException + * @see #registerHumiditySensor() + * @see #registerTemperatureSensor() + */ + public Hts221SensorDriver(String bus) throws IOException { + mDevice = new Hts221(bus); + } + + /** + * Closes the driver and its underlying device. + * + * @throws IOException + */ + @Override + public void close() throws IOException { + unregisterTemperatureSensor(); + unregisterHumiditySensor(); + if (mDevice != null) { + try { + mDevice.close(); + } finally { + mDevice = null; + } + } + } + + /** + * Registers a {@link UserSensor} that pipes humidity readings into the Android SensorManager. + * + * @see #unregisterHumiditySensor() + */ + public void registerHumiditySensor() { + if (mDevice == null) { + throw new IllegalStateException("Cannot register closed driver"); + } + + if (mHumidityUserDriver == null) { + mHumidityUserDriver = new HumidityUserDriver(); + UserDriverManager.getManager().registerSensor(mHumidityUserDriver.getUserSensor()); + } + } + + /** + * Registers a {@link UserSensor} that pipes temperature readings into the Android SensorManager. + * + * @see #unregisterTemperatureSensor() + */ + public void registerTemperatureSensor() { + if (mDevice == null) { + throw new IllegalStateException("Cannot register closed driver"); + } + + if (mTemperatureUserDriver == null) { + mTemperatureUserDriver = new TemperatureUserDriver(); + UserDriverManager.getManager().registerSensor(mTemperatureUserDriver.getUserSensor()); + } + } + + /** + * Unregisters the humidity {@link UserSensor}. + */ + public void unregisterHumiditySensor() { + if (mHumidityUserDriver != null) { + UserDriverManager.getManager().unregisterSensor(mHumidityUserDriver.getUserSensor()); + mHumidityUserDriver = null; + } + } + + /** + * Unregisters the temperature {@link UserSensor}. + */ + public void unregisterTemperatureSensor() { + if (mTemperatureUserDriver != null) { + UserDriverManager.getManager().unregisterSensor(mTemperatureUserDriver.getUserSensor()); + mTemperatureUserDriver = null; + } + } + + private void maybeSleep() throws IOException { + if ((mTemperatureUserDriver == null || !mTemperatureUserDriver.isEnabled()) && + (mHumidityUserDriver == null || !mHumidityUserDriver.isEnabled())) { + mDevice.setMode(Hts221.MODE_POWER_DOWN); + } else { + mDevice.setMode(Hts221.MODE_ACTIVE); + } + } + + private class HumidityUserDriver extends UserSensorDriver { + + private static final float DRIVER_MAX_RANGE = Hts221.MAX_HUMIDITY_PERCENT; + private static final float DRIVER_RESOLUTION = 0.004f; + private static final float DRIVER_POWER = Hts221.MAX_POWER_CONSUMPTION_UA / 1000f; + private static final int DRIVER_VERSION = 1; + private static final String DRIVER_REQUIRED_PERMISSION = ""; + + private boolean mEnabled; + private UserSensor mUserSensor; + + private UserSensor getUserSensor() { + if (mUserSensor == null) { + mUserSensor = UserSensor.builder() + .setType(Sensor.TYPE_RELATIVE_HUMIDITY) + .setName(DRIVER_NAME) + .setVendor(DRIVER_VENDOR) + .setVersion(DRIVER_VERSION) + .setMaxRange(DRIVER_MAX_RANGE) + .setResolution(DRIVER_RESOLUTION) + .setPower(DRIVER_POWER) + .setMinDelay(DRIVER_MIN_DELAY_US) + .setRequiredPermission(DRIVER_REQUIRED_PERMISSION) + .setMaxDelay(DRIVER_MAX_DELAY_US) + .setUuid(UUID.randomUUID()) + .setDriver(this) + .build(); + } + return mUserSensor; + } + + @Override + public UserSensorReading read() throws IOException { + return new UserSensorReading(new float[]{mDevice.readHumidity()}); + } + + @Override + public void setEnabled(boolean enabled) throws IOException { + mEnabled = enabled; + maybeSleep(); + } + + private boolean isEnabled() { + return mEnabled; + } + + } + + private class TemperatureUserDriver extends UserSensorDriver { + + private static final float DRIVER_MAX_RANGE = Hts221.MAX_TEMP_C; + private static final float DRIVER_RESOLUTION = 0.016f; + private static final float DRIVER_POWER = Hts221.MAX_POWER_CONSUMPTION_UA / 1000f; + private static final int DRIVER_VERSION = 1; + private static final String DRIVER_REQUIRED_PERMISSION = ""; + + private boolean mEnabled; + private UserSensor mUserSensor; + + private UserSensor getUserSensor() { + if (mUserSensor == null) { + mUserSensor = UserSensor.builder() + .setType(Sensor.TYPE_AMBIENT_TEMPERATURE) + .setName(DRIVER_NAME) + .setVendor(DRIVER_VENDOR) + .setVersion(DRIVER_VERSION) + .setMaxRange(DRIVER_MAX_RANGE) + .setResolution(DRIVER_RESOLUTION) + .setPower(DRIVER_POWER) + .setMinDelay(DRIVER_MIN_DELAY_US) + .setRequiredPermission(DRIVER_REQUIRED_PERMISSION) + .setMaxDelay(DRIVER_MAX_DELAY_US) + .setUuid(UUID.randomUUID()) + .setDriver(this) + .build(); + } + return mUserSensor; + } + + @Override + public UserSensorReading read() throws IOException { + return new UserSensorReading(new float[]{mDevice.readTemperature()}); + } + + @Override + public void setEnabled(boolean enabled) throws IOException { + mEnabled = enabled; + maybeSleep(); + } + + private boolean isEnabled() { + return mEnabled; + } + + } + +} diff --git a/hts221/src/test/java/com/google/android/things/contrib/driver/hts221/BitsMatcher.java b/hts221/src/test/java/com/google/android/things/contrib/driver/hts221/BitsMatcher.java new file mode 100644 index 0000000..fdf68ce --- /dev/null +++ b/hts221/src/test/java/com/google/android/things/contrib/driver/hts221/BitsMatcher.java @@ -0,0 +1,49 @@ +/* + * Copyright 2016 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.things.contrib.driver.hts221; + +import org.hamcrest.Description; +import org.hamcrest.TypeSafeMatcher; + +/** + * Matcher that checks if necessary bits are set in a byte argument. + */ +public class BitsMatcher extends TypeSafeMatcher { + + private final byte mBits; + + public BitsMatcher(byte bits) { + mBits = bits; + } + + @Override + public void describeTo(Description description) { + description.appendText("Value must be a byte with bits set: ") + .appendText(Integer.toBinaryString(mBits)); + } + + @Override + protected void describeMismatchSafely(Byte item, Description mismatchDescription) { + mismatchDescription.appendText("Value should have bits set: ") + .appendText(Integer.toBinaryString(mBits)); + } + + @Override + protected boolean matchesSafely(Byte item) { + return mBits == 0 ? item == 0 : (item & mBits) == mBits; + } +} + diff --git a/hts221/src/test/java/com/google/android/things/contrib/driver/hts221/BitsMatcherTest.java b/hts221/src/test/java/com/google/android/things/contrib/driver/hts221/BitsMatcherTest.java new file mode 100644 index 0000000..e404d6e --- /dev/null +++ b/hts221/src/test/java/com/google/android/things/contrib/driver/hts221/BitsMatcherTest.java @@ -0,0 +1,61 @@ +/* + * Copyright 2016 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.things.contrib.driver.hts221; + +import org.junit.Test; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class BitsMatcherTest { + + @Test + public void matches_positive() { + BitsMatcher matcher = new BitsMatcher((byte) 10); // 00001010 + + // bits set + assertTrue(matcher.matches((byte) 10)); + assertTrue(matcher.matches((byte) 0b00001011)); + assertTrue(matcher.matches((byte) 0b00011010)); + + // bits not set + assertFalse(matcher.matches((byte) 0)); + assertFalse(matcher.matches((byte) 0b00011001)); + assertFalse(matcher.matches((byte) 0b01000000)); + } + + @Test + public void matches_negative() { + BitsMatcher matcher = new BitsMatcher((byte) -31); // 11100001 + + // bits set + assertTrue(matcher.matches((byte) -31)); + assertTrue(matcher.matches((byte) 0b11111011)); + assertTrue(matcher.matches((byte) 0b11101001)); + + // bits not set + assertFalse(matcher.matches((byte) 0)); + assertFalse(matcher.matches((byte) 0b11100000)); + assertFalse(matcher.matches((byte) 0b11000001)); + } + + @Test + public void matches_zero() { + BitsMatcher matcher = new BitsMatcher((byte) 0); + assertTrue(matcher.matches((byte) 0)); + assertFalse(matcher.matches((byte) 1)); + } +} diff --git a/hts221/src/test/java/com/google/android/things/contrib/driver/hts221/Hts221Test.java b/hts221/src/test/java/com/google/android/things/contrib/driver/hts221/Hts221Test.java new file mode 100644 index 0000000..97dca46 --- /dev/null +++ b/hts221/src/test/java/com/google/android/things/contrib/driver/hts221/Hts221Test.java @@ -0,0 +1,263 @@ +/* + * Copyright 2016 Macro Yau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.things.contrib.driver.hts221; + +import com.google.android.things.pio.I2cDevice; + +import junit.framework.Assert; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.io.IOException; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.byteThat; +import static org.mockito.Matchers.eq; + +public class Hts221Test { + + @Mock + I2cDevice mI2c; + + @Rule + public MockitoRule mMockitoRule = MockitoJUnit.rule(); + + @Rule + public ExpectedException mExpectedException = ExpectedException.none(); + + @Test + public void testCompensateHumidity() { + // Example from the datasheet + // http://www.st.com/resource/en/datasheet/hts221.pdf + int h0T0Out = (short) 0x4000; + int h1T0Out = (short) 0x6000; + int hOut = (short) 0x5000; + int h0 = 40; + int h1 = 80; + + float[] calibration = Hts221.calibrateHumidityParameters(h0, h1, h0T0Out, h1T0Out); + Assert.assertEquals(30.0f, Hts221.compensateSample(hOut, calibration)); + } + + @Test + public void testCompensateTemperature() { + // Example from the datasheet + // http://www.st.com/resource/en/datasheet/hts221.pdf + int t0Out = (short) 300; + int t1Out = (short) 500; + int tOut = (short) 400; + int t0 = 80; + int t1 = 160; + + float[] calibration = Hts221.calibrateTemperatureParameters(t0, t1, t0Out, t1Out); + Assert.assertEquals(15.0f, Hts221.compensateSample(tOut, calibration)); + } + + @Test + public void close() throws IOException { + Mockito.when(mI2c.readRegByte(0x0F)).thenReturn((byte) 0xBC); + Hts221 hts221 = new Hts221(mI2c); + hts221.close(); + Mockito.verify(mI2c).close(); + } + + @Test + public void close_safeToCallTwice() throws IOException { + Mockito.when(mI2c.readRegByte(0x0F)).thenReturn((byte) 0xBC); + Hts221 hts221 = new Hts221(mI2c); + hts221.close(); + hts221.close(); // Should not throw + } + + @Test + public void setMode() throws IOException { + Mockito.when(mI2c.readRegByte(0x0F)).thenReturn((byte) 0xBC); + Hts221 hts221 = new Hts221(mI2c); + + Mockito.reset(mI2c); + + hts221.setMode(Hts221.MODE_ACTIVE); + Mockito.verify(mI2c).writeRegByte(eq(0x20), byteThat(new BitsMatcher((byte) 0x80))); + } + + @Test + public void setMode_throwsIfClosed() throws IOException { + Mockito.when(mI2c.readRegByte(0x0F)).thenReturn((byte) 0xBC); + Hts221 hts221 = new Hts221(mI2c); + hts221.close(); + mExpectedException.expect(IllegalStateException.class); + mExpectedException.expectMessage("I2C device is already closed"); + hts221.setMode(Hts221.MODE_ACTIVE); + } + + @Test + public void setBlockDataUpdate() throws IOException { + Mockito.when(mI2c.readRegByte(0x0F)).thenReturn((byte) 0xBC); + Hts221 hts221 = new Hts221(mI2c); + + // Disable BDU + hts221.setBlockDataUpdate(false); + Mockito.verify(mI2c).writeRegByte(eq(0x20), byteThat(new BitsMatcher((byte) 0x00))); + + Mockito.reset(mI2c); + + // Enable BDU + hts221.setBlockDataUpdate(true); + Mockito.verify(mI2c).writeRegByte(eq(0x20), byteThat(new BitsMatcher((byte) 0x04))); + } + + @Test + public void setBlockDataUpdate_throwsIfClosed() throws IOException { + Mockito.when(mI2c.readRegByte(0x0F)).thenReturn((byte) 0xBC); + Hts221 hts221 = new Hts221(mI2c); + hts221.close(); + mExpectedException.expect(IllegalStateException.class); + mExpectedException.expectMessage("I2C device is already closed"); + hts221.setBlockDataUpdate(true); + } + + @Test + public void setOutputDataRate() throws IOException { + Mockito.when(mI2c.readRegByte(0x0F)).thenReturn((byte) 0xBC); + Hts221 hts221 = new Hts221(mI2c); + + Mockito.reset(mI2c); + + // One-shot + hts221.setOutputDataRate(Hts221.HTS221_ODR_ONE_SHOT); + Mockito.verify(mI2c).writeRegByte(eq(0x20), byteThat(new BitsMatcher((byte) 0x00))); + + Mockito.reset(mI2c); + + // 1 Hz + hts221.setOutputDataRate(Hts221.HTS221_ODR_1_HZ); + Mockito.verify(mI2c).writeRegByte(eq(0x20), byteThat(new BitsMatcher((byte) 0x01))); + + Mockito.reset(mI2c); + + // 7 Hz + hts221.setOutputDataRate(Hts221.HTS221_ODR_7_HZ); + Mockito.verify(mI2c).writeRegByte(eq(0x20), byteThat(new BitsMatcher((byte) 0x02))); + + Mockito.reset(mI2c); + + // 12.5 Hz + hts221.setOutputDataRate(Hts221.HTS221_ODR_12_5_HZ); + Mockito.verify(mI2c).writeRegByte(eq(0x20), byteThat(new BitsMatcher((byte) 0x03))); + } + + @Test + public void setOutputDataRate_throwsIfClosed() throws IOException { + Mockito.when(mI2c.readRegByte(0x0F)).thenReturn((byte) 0xBC); + Hts221 hts221 = new Hts221(mI2c); + hts221.close(); + mExpectedException.expect(IllegalStateException.class); + mExpectedException.expectMessage("I2C device is already closed"); + hts221.setOutputDataRate(Hts221.HTS221_ODR_12_5_HZ); + } + + @Test + public void setAveragedSamples() throws IOException { + Mockito.when(mI2c.readRegByte(0x0F)).thenReturn((byte) 0xBC); + Hts221 hts221 = new Hts221(mI2c); + + // AVGH_64 + AVGT_2 + hts221.setAveragedSamples(Hts221.AV_CONF_AVGH_64, Hts221.AV_CONF_AVGT_2); + Mockito.verify(mI2c).writeRegByte(eq(0x10), byteThat(new BitsMatcher((byte) 0x04))); + + Mockito.reset(mI2c); + + // AVGH_512 + AVGT_256 + hts221.setAveragedSamples(Hts221.AV_CONF_AVGH_512, Hts221.AV_CONF_AVGT_256); + Mockito.verify(mI2c).writeRegByte(eq(0x10), byteThat(new BitsMatcher((byte) 0x3F))); + } + + @Test + public void setAveragedSamples_throwsIfClosed() throws IOException { + Mockito.when(mI2c.readRegByte(0x0F)).thenReturn((byte) 0xBC); + Hts221 hts221 = new Hts221(mI2c); + hts221.close(); + mExpectedException.expect(IllegalStateException.class); + mExpectedException.expectMessage("I2C device is already closed"); + hts221.setAveragedSamples(Hts221.AV_CONF_AVGH_512, Hts221.AV_CONF_AVGT_256); + } + + @Test + public void readHumidity() throws IOException { + Mockito.when(mI2c.readRegByte(0x0F)).thenReturn((byte) 0xBC); + Hts221 hts221 = new Hts221(mI2c); + Mockito.when(mI2c.readRegByte(0x27)).thenReturn((byte) 0x02); + hts221.readHumidity(); + Mockito.verify(mI2c).readRegBuffer(eq(0x28 | 0x80), any(byte[].class), eq(2)); + } + + @Test + public void readHumidity_throwsIfDataNotYetAvailable() throws IOException { + Mockito.when(mI2c.readRegByte(0x0F)).thenReturn((byte) 0xBC); + Hts221 hts221 = new Hts221(mI2c); + Mockito.when(mI2c.readRegByte(0x27)).thenReturn((byte) 0x00); + mExpectedException.expect(IOException.class); + mExpectedException.expectMessage("Humidity data is not yet available"); + hts221.readHumidity(); + } + + @Test + public void readHumidity_throwsIfClosed() throws IOException { + Mockito.when(mI2c.readRegByte(0x0F)).thenReturn((byte) 0xBC); + Hts221 hts221 = new Hts221(mI2c); + hts221.close(); + mExpectedException.expect(IllegalStateException.class); + mExpectedException.expectMessage("I2C device is already closed"); + hts221.readHumidity(); + } + + @Test + public void readTemperature() throws IOException { + Mockito.when(mI2c.readRegByte(0x0F)).thenReturn((byte) 0xBC); + Hts221 hts221 = new Hts221(mI2c); + Mockito.when(mI2c.readRegByte(0x27)).thenReturn((byte) 0x01); + hts221.readTemperature(); + Mockito.verify(mI2c).readRegBuffer(eq(0x2A | 0x80), any(byte[].class), eq(2)); + } + + @Test + public void readTemperature_throwsIfDataNotYetAvailable() throws IOException { + Mockito.when(mI2c.readRegByte(0x0F)).thenReturn((byte) 0xBC); + Hts221 hts221 = new Hts221(mI2c); + Mockito.when(mI2c.readRegByte(0x27)).thenReturn((byte) 0x00); + mExpectedException.expect(IOException.class); + mExpectedException.expectMessage("Temperature data is not yet available"); + hts221.readTemperature(); + } + + @Test + public void readTemperature_throwsIfClosed() throws IOException { + Mockito.when(mI2c.readRegByte(0x0F)).thenReturn((byte) 0xBC); + Hts221 hts221 = new Hts221(mI2c); + hts221.close(); + mExpectedException.expect(IllegalStateException.class); + mExpectedException.expectMessage("I2C device is already closed"); + hts221.readTemperature(); + } + +} diff --git a/settings.gradle b/settings.gradle index 1cadc4c..62364c0 100644 --- a/settings.gradle +++ b/settings.gradle @@ -20,6 +20,7 @@ include ':bmx280' include ':cap12xx' include ':gps' include ':ht16k33' +include ':hts221' include ':mma7660fc' include ':pwmservo' include ':pwmspeaker' From bedafa003c85e44e480f7492e1fd1fb073c4dd02 Mon Sep 17 00:00:00 2001 From: Macro Yau Date: Thu, 12 Jan 2017 16:02:38 +0800 Subject: [PATCH 2/7] Add LPS25H driver --- lps25h/.gitignore | 1 + lps25h/README.md | 130 ++++++ lps25h/build.gradle | 37 ++ lps25h/publish.gradle | 97 +++++ lps25h/src/main/AndroidManifest.xml | 22 + .../things/contrib/driver/lps25h/Lps25h.java | 411 ++++++++++++++++++ .../driver/lps25h/Lps25hSensorDriver.java | 233 ++++++++++ .../contrib/driver/lps25h/BitsMatcher.java | 49 +++ .../driver/lps25h/BitsMatcherTest.java | 61 +++ .../contrib/driver/lps25h/Lps25hTest.java | 241 ++++++++++ settings.gradle | 1 + 11 files changed, 1283 insertions(+) create mode 100644 lps25h/.gitignore create mode 100644 lps25h/README.md create mode 100644 lps25h/build.gradle create mode 100644 lps25h/publish.gradle create mode 100644 lps25h/src/main/AndroidManifest.xml create mode 100644 lps25h/src/main/java/com/google/android/things/contrib/driver/lps25h/Lps25h.java create mode 100644 lps25h/src/main/java/com/google/android/things/contrib/driver/lps25h/Lps25hSensorDriver.java create mode 100644 lps25h/src/test/java/com/google/android/things/contrib/driver/lps25h/BitsMatcher.java create mode 100644 lps25h/src/test/java/com/google/android/things/contrib/driver/lps25h/BitsMatcherTest.java create mode 100644 lps25h/src/test/java/com/google/android/things/contrib/driver/lps25h/Lps25hTest.java diff --git a/lps25h/.gitignore b/lps25h/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/lps25h/.gitignore @@ -0,0 +1 @@ +/build diff --git a/lps25h/README.md b/lps25h/README.md new file mode 100644 index 0000000..f27b5a9 --- /dev/null +++ b/lps25h/README.md @@ -0,0 +1,130 @@ +LPS25H driver for Android Things +================================ + +This driver supports STMicroelectronics [LPS25H][product_lps25h] MEMS pressure sensor. + +NOTE: these drivers are not production-ready. They are offered as sample +implementations of Android Things user space drivers for common peripherals +as part of the Developer Preview release. There is no guarantee +of correctness, completeness or robustness. + +How to use the driver +--------------------- + +### Gradle dependency + +To use the `lps25h` driver, simply add the line below to your project's `build.gradle`, +where `` matches the last version of the driver available on [jcenter][jcenter]. + +``` +dependencies { + compile 'com.google.android.things.contrib:driver-lps25h:' +} +``` + +### Sample usage + +```java +import com.google.android.things.contrib.driver.hts221.Lps25h; + +// Access the environmental sensor: + +Lps25h mLps25h; + +try { + mLps25h = new Lps25h(i2cBusName); +} catch (IOException e) { + // Couldn't configure the device... +} + +// Read the current pressure: + +try { + float pressure = mLps25h.readPressure(); +} catch (IOException e) { + // Error reading pressure +} + +// Read the current temperature: + +try { + float temperature = mLps25h.readTemperature(); +} catch (IOException e) { + // Error reading temperature +} + +// Close the pressure sensor when finished: + +try { + mLps25h.close(); +} catch (IOException e) { + // Error closing sensor +} +``` + +If you need to read sensor values continuously, you can register the LPS25H with the system and +listen for sensor values using the [Sensor APIs][sensors]: + +```java +SensorManager mSensorManager = getSystemService(Context.SENSOR_SERVICE); +SensorEventListener mPressureListener = ...; +SensorEventListener mTemperatureListener = ...; +Lps25hSensorDriver mSensorDriver; + +mSensorManager.registerDynamicSensorCallback(new SensorManager.DynamicSensorCallback() { + @Override + public void onDynamicSensorConnected(Sensor sensor) { + if (sensor.getType() == Sensor.TYPE_PRESSURE) { + mSensorManager.registerListener(mPressureListener, sensor, + SensorManager.SENSOR_DELAY_NORMAL); + } else if (sensor.getType() == Sensor.TYPE_AMBIENT_TEMPERATURE) { + mSensorManager.registerListener(mTemperatureListener, sensor, + SensorManager.SENSOR_DELAY_NORMAL); + } + } +}); + +try { + mSensorDriver = new Lps25hSensorDriver(i2cBusName); + mSensorDriver.registerPressureSensor(); + mSensorDriver.registerTemperatureSensor(); +} catch (IOException e) { + // Error configuring sensor +} + +// Unregister and close the driver when finished: + +mSensorManager.unregisterListener(mPressureListener); +mSensorManager.unregisterListener(mTemperatureListener); +mSensorDriver.unregisterPressureSensor(); +mSensorDriver.unregisterTemperatureSensor(); +try { + mSensorDriver.close(); +} catch (IOException e) { + // Error closing sensor +} +``` + +License +------- + +Copyright 2016 Macro Yau + +Licensed to the Apache Software Foundation (ASF) under one or more contributor +license agreements. See the NOTICE file distributed with this work for +additional information regarding copyright ownership. The ASF licenses this +file to you under the Apache License, Version 2.0 (the "License"); you may not +use this file except in compliance with the License. You may obtain a copy of +the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +License for the specific language governing permissions and limitations under +the License. + +[product_lps25h]: http://www.st.com/en/mems-and-sensors/lps25h.html +[jcenter]: https://bintray.com/google/androidthings/contrib-driver-lps25h/_latestVersion +[sensors]: https://developer.android.com/guide/topics/sensors/sensors_overview.html diff --git a/lps25h/build.gradle b/lps25h/build.gradle new file mode 100644 index 0000000..9217d7d --- /dev/null +++ b/lps25h/build.gradle @@ -0,0 +1,37 @@ +/* + * Copyright 2016 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +apply plugin: 'com.android.library' + +android { + compileSdkVersion 24 + buildToolsVersion '24.0.3' + + defaultConfig { + minSdkVersion 24 + targetSdkVersion 24 + versionCode 1 + versionName "1.0" + } +} + +dependencies { + provided 'com.google.android.things:androidthings:0.1-devpreview' + compile 'com.android.support:support-annotations:24.2.0' + + testCompile 'junit:junit:4.12' + testCompile 'org.mockito:mockito-core:1.10.19' +} diff --git a/lps25h/publish.gradle b/lps25h/publish.gradle new file mode 100644 index 0000000..3f73c00 --- /dev/null +++ b/lps25h/publish.gradle @@ -0,0 +1,97 @@ +/* + * Copyright 2016 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * To publish on Bintray: + * - set environmental variables BINTRAY_USER and BINTRAY_API_KEY to proper values + * - from this directory: + * ../gradlew -b publish.gradle bintrayUpload + * + */ + +buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:2.2.1' + } +} + +plugins { + id "com.jfrog.bintray" version "1.7" +} + +allprojects { + repositories { + jcenter() + } +} + +apply from: 'build.gradle' +apply plugin: 'maven-publish' + +def packageVersion = '0.1' + +task sourceJar(type: Jar) { + classifier = 'sources' + from android.sourceSets.main.java.sourceFiles +} + +publishing { + publications { + driverPublish(MavenPublication) { + groupId 'com.google.android.things.contrib' + artifactId "driver-$project.name" + version packageVersion + artifacts = configurations.archives.artifacts + artifact sourceJar + pom.withXml { + def dependenciesNode = asNode().appendNode('dependencies') + configurations.compile.allDependencies.each { + if(it.group != null && (it.name != null || "unspecified".equals(it.name)) && it.version != null) + { + def dependencyNode = dependenciesNode.appendNode('dependency') + dependencyNode.appendNode('groupId', it.group) + dependencyNode.appendNode('artifactId', it.name) + dependencyNode.appendNode('version', it.version) + } + } + } + } + } +} + +bintray { + user = System.getenv('BINTRAY_USER') + key = System.getenv('BINTRAY_API_KEY') + publications = ['driverPublish'] + + publish = true + + pkg { + repo = 'androidthings' + name = "contrib-driver-$project.name" + userOrg = 'google' + + version { + name = packageVersion + gpg { + sign = true + } + } + } +} diff --git a/lps25h/src/main/AndroidManifest.xml b/lps25h/src/main/AndroidManifest.xml new file mode 100644 index 0000000..3c2f256 --- /dev/null +++ b/lps25h/src/main/AndroidManifest.xml @@ -0,0 +1,22 @@ + + + + + + + diff --git a/lps25h/src/main/java/com/google/android/things/contrib/driver/lps25h/Lps25h.java b/lps25h/src/main/java/com/google/android/things/contrib/driver/lps25h/Lps25h.java new file mode 100644 index 0000000..768e2a0 --- /dev/null +++ b/lps25h/src/main/java/com/google/android/things/contrib/driver/lps25h/Lps25h.java @@ -0,0 +1,411 @@ +/* + * Copyright 2016 Macro Yau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.things.contrib.driver.lps25h; + +import android.support.annotation.IntDef; + +import com.google.android.things.pio.I2cDevice; +import com.google.android.things.pio.PeripheralManagerService; + +import java.io.IOException; + +/** + * Driver for the LPS25H pressure sensor. + * + * @see LPS25H datasheet + */ +@SuppressWarnings({"unused", "WeakerAccess"}) +public class Lps25h implements AutoCloseable { + + // Sensor constants obtained from the datasheet + /** + * Device ID of the sensor. + */ + public static final int DEVICE_ID = 0xBD; // Value of the WHO_AM_I register + /** + * I2C address of the sensor. + */ + public static final int I2C_ADDRESS = 0x5C; + /** + * Minimum pressure in hectopascals that the sensor can measure. + */ + public static final float MIN_PRESSURE_HPA = 260f; + /** + * Maximum pressure in hectopascals that the sensor can measure. + */ + public static final float MAX_PRESSURE_HPA = 1260f; + /** + * Minimum temperature in Celsius that the sensor can measure. + */ + public static final float MIN_TEMP_C = -30f; + /** + * Maximum temperature in Celsius that the sensor can measure. + */ + public static final float MAX_TEMP_C = 105f; + /** + * Maximum power consumption in micro-amperes when measuring pressure and temperature. + */ + public static final float MAX_POWER_CONSUMPTION_UA = 25f; + /** + * Maximum frequency of the measurements. + */ + public static final float MAX_FREQ_HZ = 25f; + /** + * Minimum frequency of the measurements. + */ + public static final float MIN_FREQ_HZ = 1f; + + /** + * Pressure average configuration. + */ + @IntDef({RES_CONF_AVGP_8, RES_CONF_AVGP_32, RES_CONF_AVGP_128, RES_CONF_AVGP_512}) + public @interface PressureAverageConfiguration { + } + + public static final int RES_CONF_AVGP_8 = 0b00; + public static final int RES_CONF_AVGP_32 = 0b01; // Default + public static final int RES_CONF_AVGP_128 = 0b10; + public static final int RES_CONF_AVGP_512 = 0b11; + + /** + * Temperature average configuration. + */ + @IntDef({RES_CONF_AVGT_8, RES_CONF_AVGT_16, RES_CONF_AVGT_32, RES_CONF_AVGT_64}) + public @interface TemperatureAverageConfiguration { + } + + public static final int RES_CONF_AVGT_8 = 0b00; + public static final int RES_CONF_AVGT_16 = 0b01; // Default + public static final int RES_CONF_AVGT_32 = 0b10; + public static final int RES_CONF_AVGT_64 = 0b11; + + /** + * Power mode. + */ + @IntDef({MODE_POWER_DOWN, MODE_ACTIVE}) + public @interface Mode { + } + + public static final int MODE_POWER_DOWN = 0; + public static final int MODE_ACTIVE = 1; + + /** + * Output data rate configuration. + */ + @IntDef({LPS25H_ODR_ONE_SHOT, LPS25H_ODR_1_HZ, LPS25H_ODR_7_HZ, LPS25H_ODR_12_5_HZ, + LPS25H_ODR_25_HZ}) + public @interface OutputDataRate { + } + + public static final int LPS25H_ODR_ONE_SHOT = 0b000; + public static final int LPS25H_ODR_1_HZ = 0b001; + public static final int LPS25H_ODR_7_HZ = 0b010; + public static final int LPS25H_ODR_12_5_HZ = 0b011; + public static final int LPS25H_ODR_25_HZ = 0b100; + + // Registers + private static final int LPS25H_REG_REF_P_XL = 0x08; // R/W + private static final int LPS25H_REG_REF_P_L = 0x09; // R/W + private static final int LPS25H_REG_REF_P_H = 0x0A; // R/W + private static final int LPS25H_REG_WHO_AM_I = 0x0F; // R + private static final int LPS25H_REG_RES_CONF = 0x10; // R/W + private static final int LPS25H_REG_CTRL_REG1 = 0x20; // R/W + private static final int LPS25H_REG_CTRL_REG2 = 0x21; // R/W + private static final int LPS25H_REG_CTRL_REG3 = 0x22; // R/W + private static final int LPS25H_REG_CTRL_REG4 = 0x23; // R/W + private static final int LPS25H_REG_INT_CFG = 0x24; // R/W + private static final int LPS25H_REG_INT_SOURCE = 0x25; // R + private static final int LPS25H_REG_STATUS_REG = 0x27; // R + private static final int LPS25H_REG_PRESS_OUT_XL = 0x28; // R + private static final int LPS25H_REG_PRESS_OUT_L = 0x29; // R + private static final int LPS25H_REG_PRESS_OUT_H = 0x2A; // R + private static final int LPS25H_REG_TEMP_OUT_L = 0x2B; // R + private static final int LPS25H_REG_TEMP_OUT_H = 0x2C; // R + private static final int LPS25H_REG_FIFO_CTRL = 0x2E; // R/W + private static final int LPS25H_REG_FIFO_STATUS = 0x2F; // R + private static final int LPS25H_REG_THS_P_L = 0x30; // R/W + private static final int LPS25H_REG_THS_P_H = 0x31; // R/W + private static final int LPS25H_REG_RPDS_L = 0x39; // R/W + private static final int LPS25H_REG_RPDS_H = 0x3A; // R/W + + // Bit masks for CTRL_REG1 + private static final int LPS25H_POWER_DOWN_MASK = 0b10000000; + private static final int LPS25H_BDU_MASK = 0b00000100; + private static final int LPS25H_ODR_MASK = 0b01110000; + + private I2cDevice mDevice; + + private final byte[] mBuffer = new byte[3]; // For reading registers + + private int mMode; + private boolean mBlockDataUpdate; + private int mOutputDataRate; + + /** + * Creates a new LPS25H sensor driver connected on the given bus. + * + * @param bus the I2C bus the sensor is connected to + * @throws IOException + */ + public Lps25h(String bus) throws IOException { + PeripheralManagerService pioService = new PeripheralManagerService(); + I2cDevice device = pioService.openI2cDevice(bus, I2C_ADDRESS); + try { + connect(device); + } catch (IOException | RuntimeException e) { + try { + close(); + } catch (IOException | RuntimeException ignored) { + } + throw e; + } + } + + /** + * Creates a new LPS25H sensor driver connected to the given I2C device. + * + * @param device the I2C device of the sensor + * @throws IOException + */ + /*package*/ Lps25h(I2cDevice device) throws IOException { + connect(device); + } + + private void connect(I2cDevice device) throws IOException { + mDevice = device; + + if ((mDevice.readRegByte(LPS25H_REG_WHO_AM_I) & 0xFF) != DEVICE_ID) { + throw new IllegalStateException("I2C device is not LPS25H sensor"); + } + + setBlockDataUpdate(true); + setOutputDataRate(LPS25H_ODR_1_HZ); + setMode(MODE_ACTIVE); + + // Suggested configuration in the datasheet + mDevice.writeRegByte(LPS25H_REG_RES_CONF, (byte) 0x05); + mDevice.writeRegByte(LPS25H_REG_FIFO_CTRL, (byte) 0xC0); + mDevice.writeRegByte(LPS25H_REG_CTRL_REG2, (byte) 0x40); + } + + /** + * Sets the power mode of the sensor as power-down or active. + * + * @param mode must be either {@link #MODE_POWER_DOWN} or {@link #MODE_ACTIVE} + * @throws IOException + */ + public void setMode(@Mode int mode) throws IOException { + if (mDevice == null) { + throw new IllegalStateException("I2C device is already closed"); + } + + int regCtrl = mDevice.readRegByte(LPS25H_REG_CTRL_REG1) & 0xFF; + if (mode == MODE_POWER_DOWN) { + regCtrl &= (~LPS25H_POWER_DOWN_MASK & 0xFF); + } else { + regCtrl |= LPS25H_POWER_DOWN_MASK; + } + mDevice.writeRegByte(LPS25H_REG_CTRL_REG1, (byte) regCtrl); + mMode = mode; + } + + /** + * Returns the power mode of the sensor. + * + * @return true if the sensor is active + * @see #setMode(int) + */ + public boolean isEnabled() { + return mMode == MODE_ACTIVE; + } + + /** + * Sets the block data update (BDU) bit in the control register 1 (CTRL_REG1) of the sensor. + * Reading MSB and LSB of different samples can be avoided by enabling BDU. + * + * @param enabled enable BDU if true + * @throws IOException + */ + public void setBlockDataUpdate(boolean enabled) throws IOException { + if (mDevice == null) { + throw new IllegalStateException("I2C device is already closed"); + } + + int regCtrl = mDevice.readRegByte(LPS25H_REG_CTRL_REG1) & 0xFF; + regCtrl &= (~LPS25H_BDU_MASK & 0xFF); + if (enabled) { + regCtrl |= LPS25H_BDU_MASK; + } + mDevice.writeRegByte(LPS25H_REG_CTRL_REG1, (byte) regCtrl); + mBlockDataUpdate = enabled; + } + + /** + * Returns the status of the block data update mode. + * + * @return true if the block data update mode is enabled + * @see #setBlockDataUpdate(boolean) + */ + public boolean isBlockDataUpdateEnabled() { + return mBlockDataUpdate; + } + + /** + * Configures the output data rate (ODR) of the pressure and temperature measurements. + * + * @param outputDataRate the configuration must be one of {@link #LPS25H_ODR_ONE_SHOT}, + * {@link #LPS25H_ODR_1_HZ}, {@link #LPS25H_ODR_7_HZ}, + * {@link #LPS25H_ODR_12_5_HZ} or {@link #LPS25H_ODR_25_HZ} + * @throws IOException + */ + public void setOutputDataRate(@OutputDataRate int outputDataRate) throws IOException { + if (mDevice == null) { + throw new IllegalStateException("I2C device is already closed"); + } + + int regCtrl = mDevice.readRegByte(LPS25H_REG_CTRL_REG1) & 0xFF; + regCtrl &= (~LPS25H_ODR_MASK & 0xFF); + regCtrl |= (outputDataRate << 4); + mDevice.writeRegByte(LPS25H_REG_CTRL_REG1, (byte) regCtrl); + mOutputDataRate = outputDataRate; + } + + /** + * Returns the configured output data rate. + * + * @return the output data rate + * @see #setOutputDataRate(int) + */ + public int getOutputDataRate() { + return mOutputDataRate; + } + + /** + * Configures the number of averaged samples for the pressure and temperature measurements. + * + * @param pressureAverage the pressure average configuration must be one of the + * RES_CONF_AVGP* constants + * @param temperatureAverage the temperature average configuration must be one of the + * RES_CONF_AVGT* constants + * @throws IOException + */ + public void setAveragedSamples(@PressureAverageConfiguration int pressureAverage, + @TemperatureAverageConfiguration int temperatureAverage) + throws IOException { + if (mDevice == null) { + throw new IllegalStateException("I2C device is already closed"); + } + + int regCtrl = mDevice.readRegByte(LPS25H_REG_RES_CONF) & 0xF0; + regCtrl |= pressureAverage | (temperatureAverage << 2); + mDevice.writeRegByte(LPS25H_REG_RES_CONF, (byte) (regCtrl)); + } + + /** + * Closes the driver and its underlying device. + */ + @Override + public void close() throws IOException { + if (mDevice != null) { + try { + setMode(MODE_POWER_DOWN); + mDevice.close(); + } finally { + mDevice = null; + } + } + } + + /** + * Reads the current pressure. + * + * @return the current pressure in hectopascals or millibars + */ + public float readPressure() throws IOException { + if (mDevice == null) { + throw new IllegalStateException("I2C device is already closed"); + } + + if (isPressureDataAvailable()) { + int rawPressure = readThreeRegisters(LPS25H_REG_PRESS_OUT_XL); + return (float) rawPressure / 4096f; + } else { + throw new IOException("Pressure data is not yet available"); + } + } + + /** + * Reads the current temperature. + * + * @return the current temperature in degrees Celsius + */ + public float readTemperature() throws IOException { + if (mDevice == null) { + throw new IllegalStateException("I2C device is already closed"); + } + + if (isTemperatureDataAvailable()) { + int rawTemp = (short) readTwoRegisters(LPS25H_REG_TEMP_OUT_L); + return (float) rawTemp / 480f + 42.5f; + } else { + throw new IOException("Temperature data is not yet available"); + } + } + + /** + * Reads 16 bits from two 8-bit registers at the given address. + * + * @param address the address of the least significant byte (LSB) + * @throws IOException + */ + private int readTwoRegisters(int address) throws IOException { + mDevice.readRegBuffer(address | 0x80, mBuffer, 2); // 0x80 for auto increment in the address + return ((mBuffer[1] & 0xFF) << 8) | (mBuffer[0] & 0xFF); + } + + /** + * Reads 24 bits from three 8-bit registers at the given address. + * + * @param address the address of the least significant byte (LSB) + * @throws IOException + */ + private int readThreeRegisters(int address) throws IOException { + mDevice.readRegBuffer(address | 0x80, mBuffer, 3); // 0x80 for auto increment in the address + return ((mBuffer[2] & 0xFF) << 16) | ((mBuffer[1] & 0xFF) << 8) | (mBuffer[0] & 0xFF); + } + + /** + * Validates the availability of updated pressure data. + * + * @return true if the pressure data is updated + * @throws IOException + */ + private boolean isPressureDataAvailable() throws IOException { + return (mDevice.readRegByte(LPS25H_REG_STATUS_REG) & 0x02) != 0; + } + + /** + * Validates the availability of updated temperature data. + * + * @return true if the temperature data is updated + * @throws IOException + */ + private boolean isTemperatureDataAvailable() throws IOException { + return (mDevice.readRegByte(LPS25H_REG_STATUS_REG) & 0x01) != 0; + } + +} diff --git a/lps25h/src/main/java/com/google/android/things/contrib/driver/lps25h/Lps25hSensorDriver.java b/lps25h/src/main/java/com/google/android/things/contrib/driver/lps25h/Lps25hSensorDriver.java new file mode 100644 index 0000000..f966434 --- /dev/null +++ b/lps25h/src/main/java/com/google/android/things/contrib/driver/lps25h/Lps25hSensorDriver.java @@ -0,0 +1,233 @@ +/* + * Copyright 2016 Macro Yau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.things.contrib.driver.lps25h; + +import android.hardware.Sensor; + +import com.google.android.things.userdriver.UserDriverManager; +import com.google.android.things.userdriver.UserSensor; +import com.google.android.things.userdriver.UserSensorDriver; +import com.google.android.things.userdriver.UserSensorReading; + +import java.io.IOException; +import java.util.UUID; + +/** + * User-space driver for interfacing the LPS25H pressure sensor to the Android SensorManager + * framework. + */ +public class Lps25hSensorDriver implements AutoCloseable { + + private static final String DRIVER_VENDOR = "STMicroelectronics"; + private static final String DRIVER_NAME = "LPS25H"; + private static final int DRIVER_MIN_DELAY_US = Math.round(1000000.0f / Lps25h.MAX_FREQ_HZ); + private static final int DRIVER_MAX_DELAY_US = Math.round(1000000.0f / Lps25h.MIN_FREQ_HZ); + + private Lps25h mDevice; + + private PressureUserDriver mPressureUserDriver; + private TemperatureUserDriver mTemperatureUserDriver; + + /** + * Creates a new framework sensor driver connected on the given bus. + * The driver emits {@link Sensor} with pressure and temperature data when registered. + * + * @param bus the I2C bus the sensor is connected to + * @throws IOException + * @see #registerPressureSensor() + * @see #registerTemperatureSensor() + */ + public Lps25hSensorDriver(String bus) throws IOException { + mDevice = new Lps25h(bus); + } + + /** + * Closes the driver and its underlying device. + * + * @throws IOException + */ + @Override + public void close() throws IOException { + unregisterPressureSensor(); + unregisterTemperatureSensor(); + if (mDevice != null) { + try { + mDevice.close(); + } finally { + mDevice = null; + } + } + } + + /** + * Registers a {@link UserSensor} that pipes pressure readings into the Android SensorManager. + * + * @see #unregisterPressureSensor() + */ + public void registerPressureSensor() { + if (mDevice == null) { + throw new IllegalStateException("Cannot register closed driver"); + } + + if (mPressureUserDriver == null) { + mPressureUserDriver = new PressureUserDriver(); + UserDriverManager.getManager().registerSensor(mPressureUserDriver.getUserSensor()); + } + } + + /** + * Registers a {@link UserSensor} that pipes temperature readings into the Android SensorManager. + * + * @see #unregisterTemperatureSensor() + */ + public void registerTemperatureSensor() { + if (mDevice == null) { + throw new IllegalStateException("Cannot register closed driver"); + } + + if (mTemperatureUserDriver == null) { + mTemperatureUserDriver = new TemperatureUserDriver(); + UserDriverManager.getManager().registerSensor(mTemperatureUserDriver.getUserSensor()); + } + } + + /** + * Unregisters the pressure {@link UserSensor}. + */ + public void unregisterPressureSensor() { + if (mPressureUserDriver != null) { + UserDriverManager.getManager().unregisterSensor(mPressureUserDriver.getUserSensor()); + mPressureUserDriver = null; + } + } + + /** + * Unregisters the temperature {@link UserSensor}. + */ + public void unregisterTemperatureSensor() { + if (mTemperatureUserDriver != null) { + UserDriverManager.getManager().unregisterSensor(mTemperatureUserDriver.getUserSensor()); + mTemperatureUserDriver = null; + } + } + + private void maybeSleep() throws IOException { + if ((mTemperatureUserDriver == null || !mTemperatureUserDriver.isEnabled()) && + (mPressureUserDriver == null || !mPressureUserDriver.isEnabled())) { + mDevice.setMode(Lps25h.MODE_POWER_DOWN); + } else { + mDevice.setMode(Lps25h.MODE_ACTIVE); + } + } + + private class PressureUserDriver extends UserSensorDriver { + + private static final float DRIVER_MAX_RANGE = Lps25h.MAX_PRESSURE_HPA; + private static final float DRIVER_RESOLUTION = 0.0002f; + private static final float DRIVER_POWER = Lps25h.MAX_POWER_CONSUMPTION_UA / 1000f; + private static final int DRIVER_VERSION = 1; + private static final String DRIVER_REQUIRED_PERMISSION = ""; + + private boolean mEnabled; + private UserSensor mUserSensor; + + private UserSensor getUserSensor() { + if (mUserSensor == null) { + mUserSensor = UserSensor.builder() + .setType(Sensor.TYPE_PRESSURE) + .setName(DRIVER_NAME) + .setVendor(DRIVER_VENDOR) + .setVersion(DRIVER_VERSION) + .setMaxRange(DRIVER_MAX_RANGE) + .setResolution(DRIVER_RESOLUTION) + .setPower(DRIVER_POWER) + .setMinDelay(DRIVER_MIN_DELAY_US) + .setRequiredPermission(DRIVER_REQUIRED_PERMISSION) + .setMaxDelay(DRIVER_MAX_DELAY_US) + .setUuid(UUID.randomUUID()) + .setDriver(this) + .build(); + } + return mUserSensor; + } + + @Override + public UserSensorReading read() throws IOException { + return new UserSensorReading(new float[]{mDevice.readPressure()}); + } + + @Override + public void setEnabled(boolean enabled) throws IOException { + mEnabled = enabled; + maybeSleep(); + } + + private boolean isEnabled() { + return mEnabled; + } + + } + + private class TemperatureUserDriver extends UserSensorDriver { + + private static final float DRIVER_MAX_RANGE = Lps25h.MAX_TEMP_C; + private static final float DRIVER_RESOLUTION = 0.002f; + private static final float DRIVER_POWER = Lps25h.MAX_POWER_CONSUMPTION_UA / 1000f; + private static final int DRIVER_VERSION = 1; + private static final String DRIVER_REQUIRED_PERMISSION = ""; + + private boolean mEnabled; + private UserSensor mUserSensor; + + private UserSensor getUserSensor() { + if (mUserSensor == null) { + mUserSensor = UserSensor.builder() + .setType(Sensor.TYPE_AMBIENT_TEMPERATURE) + .setName(DRIVER_NAME) + .setVendor(DRIVER_VENDOR) + .setVersion(DRIVER_VERSION) + .setMaxRange(DRIVER_MAX_RANGE) + .setResolution(DRIVER_RESOLUTION) + .setPower(DRIVER_POWER) + .setMinDelay(DRIVER_MIN_DELAY_US) + .setRequiredPermission(DRIVER_REQUIRED_PERMISSION) + .setMaxDelay(DRIVER_MAX_DELAY_US) + .setUuid(UUID.randomUUID()) + .setDriver(this) + .build(); + } + return mUserSensor; + } + + @Override + public UserSensorReading read() throws IOException { + return new UserSensorReading(new float[]{mDevice.readTemperature()}); + } + + @Override + public void setEnabled(boolean enabled) throws IOException { + mEnabled = enabled; + maybeSleep(); + } + + private boolean isEnabled() { + return mEnabled; + } + + } + +} diff --git a/lps25h/src/test/java/com/google/android/things/contrib/driver/lps25h/BitsMatcher.java b/lps25h/src/test/java/com/google/android/things/contrib/driver/lps25h/BitsMatcher.java new file mode 100644 index 0000000..41b3f76 --- /dev/null +++ b/lps25h/src/test/java/com/google/android/things/contrib/driver/lps25h/BitsMatcher.java @@ -0,0 +1,49 @@ +/* + * Copyright 2016 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.things.contrib.driver.lps25h; + +import org.hamcrest.Description; +import org.hamcrest.TypeSafeMatcher; + +/** + * Matcher that checks if necessary bits are set in a byte argument. + */ +public class BitsMatcher extends TypeSafeMatcher { + + private final byte mBits; + + public BitsMatcher(byte bits) { + mBits = bits; + } + + @Override + public void describeTo(Description description) { + description.appendText("Value must be a byte with bits set: ") + .appendText(Integer.toBinaryString(mBits)); + } + + @Override + protected void describeMismatchSafely(Byte item, Description mismatchDescription) { + mismatchDescription.appendText("Value should have bits set: ") + .appendText(Integer.toBinaryString(mBits)); + } + + @Override + protected boolean matchesSafely(Byte item) { + return mBits == 0 ? item == 0 : (item & mBits) == mBits; + } +} + diff --git a/lps25h/src/test/java/com/google/android/things/contrib/driver/lps25h/BitsMatcherTest.java b/lps25h/src/test/java/com/google/android/things/contrib/driver/lps25h/BitsMatcherTest.java new file mode 100644 index 0000000..69a273a --- /dev/null +++ b/lps25h/src/test/java/com/google/android/things/contrib/driver/lps25h/BitsMatcherTest.java @@ -0,0 +1,61 @@ +/* + * Copyright 2016 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.things.contrib.driver.lps25h; + +import org.junit.Test; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class BitsMatcherTest { + + @Test + public void matches_positive() { + BitsMatcher matcher = new BitsMatcher((byte) 10); // 00001010 + + // bits set + assertTrue(matcher.matches((byte) 10)); + assertTrue(matcher.matches((byte) 0b00001011)); + assertTrue(matcher.matches((byte) 0b00011010)); + + // bits not set + assertFalse(matcher.matches((byte) 0)); + assertFalse(matcher.matches((byte) 0b00011001)); + assertFalse(matcher.matches((byte) 0b01000000)); + } + + @Test + public void matches_negative() { + BitsMatcher matcher = new BitsMatcher((byte) -31); // 11100001 + + // bits set + assertTrue(matcher.matches((byte) -31)); + assertTrue(matcher.matches((byte) 0b11111011)); + assertTrue(matcher.matches((byte) 0b11101001)); + + // bits not set + assertFalse(matcher.matches((byte) 0)); + assertFalse(matcher.matches((byte) 0b11100000)); + assertFalse(matcher.matches((byte) 0b11000001)); + } + + @Test + public void matches_zero() { + BitsMatcher matcher = new BitsMatcher((byte) 0); + assertTrue(matcher.matches((byte) 0)); + assertFalse(matcher.matches((byte) 1)); + } +} diff --git a/lps25h/src/test/java/com/google/android/things/contrib/driver/lps25h/Lps25hTest.java b/lps25h/src/test/java/com/google/android/things/contrib/driver/lps25h/Lps25hTest.java new file mode 100644 index 0000000..ada7a47 --- /dev/null +++ b/lps25h/src/test/java/com/google/android/things/contrib/driver/lps25h/Lps25hTest.java @@ -0,0 +1,241 @@ +/* + * Copyright 2016 Macro Yau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.things.contrib.driver.lps25h; + +import com.google.android.things.pio.I2cDevice; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.io.IOException; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.byteThat; +import static org.mockito.Matchers.eq; + +public class Lps25hTest { + + @Mock + I2cDevice mI2c; + + @Rule + public MockitoRule mMockitoRule = MockitoJUnit.rule(); + + @Rule + public ExpectedException mExpectedException = ExpectedException.none(); + + @Test + public void close() throws IOException { + Mockito.when(mI2c.readRegByte(0x0F)).thenReturn((byte) 0xBD); + Lps25h lps25h = new Lps25h(mI2c); + lps25h.close(); + Mockito.verify(mI2c).close(); + } + + @Test + public void close_safeToCallTwice() throws IOException { + Mockito.when(mI2c.readRegByte(0x0F)).thenReturn((byte) 0xBD); + Lps25h lps25h = new Lps25h(mI2c); + lps25h.close(); + lps25h.close(); // Should not throw + } + + @Test + public void setMode() throws IOException { + Mockito.when(mI2c.readRegByte(0x0F)).thenReturn((byte) 0xBD); + Lps25h lps25h = new Lps25h(mI2c); + + Mockito.reset(mI2c); + + lps25h.setMode(Lps25h.MODE_ACTIVE); + Mockito.verify(mI2c).writeRegByte(eq(0x20), byteThat(new BitsMatcher((byte) 0x80))); + } + + @Test + public void setMode_throwsIfClosed() throws IOException { + Mockito.when(mI2c.readRegByte(0x0F)).thenReturn((byte) 0xBD); + Lps25h lps25h = new Lps25h(mI2c); + lps25h.close(); + mExpectedException.expect(IllegalStateException.class); + mExpectedException.expectMessage("I2C device is already closed"); + lps25h.setMode(Lps25h.MODE_ACTIVE); + } + + @Test + public void setBlockDataUpdate() throws IOException { + Mockito.when(mI2c.readRegByte(0x0F)).thenReturn((byte) 0xBD); + Lps25h lps25h = new Lps25h(mI2c); + + // Disable BDU + lps25h.setBlockDataUpdate(false); + Mockito.verify(mI2c).writeRegByte(eq(0x20), byteThat(new BitsMatcher((byte) 0x00))); + + Mockito.reset(mI2c); + + // Enable BDU + lps25h.setBlockDataUpdate(true); + Mockito.verify(mI2c).writeRegByte(eq(0x20), byteThat(new BitsMatcher((byte) 0x04))); + } + + @Test + public void setBlockDataUpdate_throwsIfClosed() throws IOException { + Mockito.when(mI2c.readRegByte(0x0F)).thenReturn((byte) 0xBD); + Lps25h lps25h = new Lps25h(mI2c); + lps25h.close(); + mExpectedException.expect(IllegalStateException.class); + mExpectedException.expectMessage("I2C device is already closed"); + lps25h.setBlockDataUpdate(true); + } + + @Test + public void setOutputDataRate() throws IOException { + Mockito.when(mI2c.readRegByte(0x0F)).thenReturn((byte) 0xBD); + Lps25h lps25h = new Lps25h(mI2c); + + Mockito.reset(mI2c); + + // One-shot + lps25h.setOutputDataRate(Lps25h.LPS25H_ODR_ONE_SHOT); + Mockito.verify(mI2c).writeRegByte(eq(0x20), byteThat(new BitsMatcher((byte) (0x00 << 4)))); + + Mockito.reset(mI2c); + + // 1 Hz + lps25h.setOutputDataRate(Lps25h.LPS25H_ODR_1_HZ); + Mockito.verify(mI2c).writeRegByte(eq(0x20), byteThat(new BitsMatcher((byte) (0x01 << 4)))); + + Mockito.reset(mI2c); + + // 7 Hz + lps25h.setOutputDataRate(Lps25h.LPS25H_ODR_7_HZ); + Mockito.verify(mI2c).writeRegByte(eq(0x20), byteThat(new BitsMatcher((byte) (0x02 << 4)))); + + Mockito.reset(mI2c); + + // 12.5 Hz + lps25h.setOutputDataRate(Lps25h.LPS25H_ODR_12_5_HZ); + Mockito.verify(mI2c).writeRegByte(eq(0x20), byteThat(new BitsMatcher((byte) (0x03 << 4)))); + + Mockito.reset(mI2c); + + // 25 Hz + lps25h.setOutputDataRate(Lps25h.LPS25H_ODR_25_HZ); + Mockito.verify(mI2c).writeRegByte(eq(0x20), byteThat(new BitsMatcher((byte) (0x04 << 4)))); + } + + @Test + public void setOutputDataRate_throwsIfClosed() throws IOException { + Mockito.when(mI2c.readRegByte(0x0F)).thenReturn((byte) 0xBD); + Lps25h lps25h = new Lps25h(mI2c); + lps25h.close(); + mExpectedException.expect(IllegalStateException.class); + mExpectedException.expectMessage("I2C device is already closed"); + lps25h.setOutputDataRate(Lps25h.LPS25H_ODR_25_HZ); + } + + @Test + public void setAveragedSamples() throws IOException { + Mockito.when(mI2c.readRegByte(0x0F)).thenReturn((byte) 0xBD); + Lps25h lps25h = new Lps25h(mI2c); + + Mockito.reset(mI2c); + + // AVGP_32 + AVGT_16 + lps25h.setAveragedSamples(Lps25h.RES_CONF_AVGP_32, Lps25h.RES_CONF_AVGT_16); + Mockito.verify(mI2c).writeRegByte(eq(0x10), byteThat(new BitsMatcher((byte) 0x05))); + + Mockito.reset(mI2c); + + // AVGP_512 + AVGT_64 + lps25h.setAveragedSamples(Lps25h.RES_CONF_AVGP_512, Lps25h.RES_CONF_AVGT_64); + Mockito.verify(mI2c).writeRegByte(eq(0x10), byteThat(new BitsMatcher((byte) 0x08))); + } + + @Test + public void setAveragedSamples_throwsIfClosed() throws IOException { + Mockito.when(mI2c.readRegByte(0x0F)).thenReturn((byte) 0xBD); + Lps25h lps25h = new Lps25h(mI2c); + lps25h.close(); + mExpectedException.expect(IllegalStateException.class); + mExpectedException.expectMessage("I2C device is already closed"); + lps25h.setAveragedSamples(Lps25h.RES_CONF_AVGP_512, Lps25h.RES_CONF_AVGT_64); + } + + @Test + public void readPressure() throws IOException { + Mockito.when(mI2c.readRegByte(0x0F)).thenReturn((byte) 0xBD); + Lps25h lps25h = new Lps25h(mI2c); + Mockito.when(mI2c.readRegByte(0x27)).thenReturn((byte) 0x02); + lps25h.readPressure(); + Mockito.verify(mI2c).readRegBuffer(eq(0x28 | 0x80), any(byte[].class), eq(3)); + } + + @Test + public void readPressure_throwsIfDataNotYetAvailable() throws IOException { + Mockito.when(mI2c.readRegByte(0x0F)).thenReturn((byte) 0xBD); + Lps25h lps25h = new Lps25h(mI2c); + Mockito.when(mI2c.readRegByte(0x27)).thenReturn((byte) 0x00); + mExpectedException.expect(IOException.class); + mExpectedException.expectMessage("Pressure data is not yet available"); + lps25h.readPressure(); + } + + @Test + public void readPressure_throwsIfClosed() throws IOException { + Mockito.when(mI2c.readRegByte(0x0F)).thenReturn((byte) 0xBD); + Lps25h lps25h = new Lps25h(mI2c); + lps25h.close(); + mExpectedException.expect(IllegalStateException.class); + mExpectedException.expectMessage("I2C device is already closed"); + lps25h.readPressure(); + } + + @Test + public void readTemperature() throws IOException { + Mockito.when(mI2c.readRegByte(0x0F)).thenReturn((byte) 0xBD); + Lps25h lps25h = new Lps25h(mI2c); + Mockito.when(mI2c.readRegByte(0x27)).thenReturn((byte) 0x01); + lps25h.readTemperature(); + Mockito.verify(mI2c).readRegBuffer(eq(0x2B | 0x80), any(byte[].class), eq(2)); + } + + @Test + public void readTemperature_throwsIfDataNotYetAvailable() throws IOException { + Mockito.when(mI2c.readRegByte(0x0F)).thenReturn((byte) 0xBD); + Lps25h lps25h = new Lps25h(mI2c); + Mockito.when(mI2c.readRegByte(0x27)).thenReturn((byte) 0x00); + mExpectedException.expect(IOException.class); + mExpectedException.expectMessage("Temperature data is not yet available"); + lps25h.readTemperature(); + } + + @Test + public void readTemperature_throwsIfClosed() throws IOException { + Mockito.when(mI2c.readRegByte(0x0F)).thenReturn((byte) 0xBD); + Lps25h lps25h = new Lps25h(mI2c); + lps25h.close(); + mExpectedException.expect(IllegalStateException.class); + mExpectedException.expectMessage("I2C device is already closed"); + lps25h.readTemperature(); + } + +} diff --git a/settings.gradle b/settings.gradle index 62364c0..80e4e66 100644 --- a/settings.gradle +++ b/settings.gradle @@ -21,6 +21,7 @@ include ':cap12xx' include ':gps' include ':ht16k33' include ':hts221' +include ':lps25h' include ':mma7660fc' include ':pwmservo' include ':pwmspeaker' From 586eb11ec4651a1b16839a85f6f027d8ccd231e3 Mon Sep 17 00:00:00 2001 From: Macro Yau Date: Thu, 12 Jan 2017 16:04:46 +0800 Subject: [PATCH 3/7] Add Sense HAT joystick driver --- .../contrib/driver/sensehat/Joystick.java | 170 ++++++++++++++++++ .../driver/sensehat/JoystickDriver.java | 146 +++++++++++++++ 2 files changed, 316 insertions(+) create mode 100644 sensehat/src/main/java/com/google/android/things/contrib/driver/sensehat/Joystick.java create mode 100644 sensehat/src/main/java/com/google/android/things/contrib/driver/sensehat/JoystickDriver.java diff --git a/sensehat/src/main/java/com/google/android/things/contrib/driver/sensehat/Joystick.java b/sensehat/src/main/java/com/google/android/things/contrib/driver/sensehat/Joystick.java new file mode 100644 index 0000000..848bc8c --- /dev/null +++ b/sensehat/src/main/java/com/google/android/things/contrib/driver/sensehat/Joystick.java @@ -0,0 +1,170 @@ +/* + * Copyright 2016 Macro Yau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.things.contrib.driver.sensehat; + +import android.support.annotation.VisibleForTesting; +import android.util.Log; + +import com.google.android.things.pio.Gpio; +import com.google.android.things.pio.GpioCallback; +import com.google.android.things.pio.I2cDevice; +import com.google.android.things.pio.PeripheralManagerService; + +import java.io.IOException; + +/** + * Driver for Raspberry Pi Sense HAT 5-button miniature joystick. + * + * @see Sense HAT joystick Linux kernel driver + */ +@SuppressWarnings({"unused", "WeakerAccess"}) +public class Joystick implements AutoCloseable { + + private static final String TAG = "SenseHatJoystick"; + + public static final int KEY_RELEASED = 0; + public static final int KEY_PRESSED_DOWN = 1; + public static final int KEY_PRESSED_RIGHT = 2; + public static final int KEY_PRESSED_UP = 4; + public static final int KEY_PRESSED_ENTER = 8; + public static final int KEY_PRESSED_LEFT = 16; + + private static final int JOYSTICK_REG_KEYS = 0xF2; + + private PeripheralManagerService mPioService; + + private String mI2cBus; + private int mI2cAddress; + private Gpio mInterruptGpio; + private OnButtonEventListener mListener; + private int mPrevKey; + + /** + * Interface definition for a callback to be invoked when a button event occurs. + */ + public interface OnButtonEventListener { + /** + * Called when a button event occurs. + * + * @param key the KEY_PRESSED_* key code of the button for which the event + * occurred + * @param pressed true if the button is now pressed + */ + void onButtonEvent(int key, boolean pressed); + } + + /** + * Creates a new joystick driver. + * + * @param bus the I2C bus the joystick controller is connected to + * @param address the joystick controller I2C device slave address + * @param interruptPin the interrupt GPIO pin the joystick controller is connected to + * @throws IOException + */ + public Joystick(String bus, int address, String interruptPin) throws IOException { + mPioService = new PeripheralManagerService(); + Gpio interruptGpio = mPioService.openGpio(interruptPin); + try { + connect(bus, address, interruptGpio); + } catch (IOException | RuntimeException e) { + close(); + throw e; + } + } + + /** + * Constructor invoked from unit tests. + */ + @VisibleForTesting + /*package*/ Joystick(String bus, int address, Gpio interruptGpio) throws IOException { + connect(bus, address, interruptGpio); + } + + private void connect(String bus, int address, Gpio interruptGpio) throws IOException { + mI2cBus = bus; + mI2cAddress = address; + mInterruptGpio = interruptGpio; + mInterruptGpio.setDirection(Gpio.DIRECTION_IN); + mInterruptGpio.setEdgeTriggerType(Gpio.EDGE_BOTH); + mInterruptGpio.registerGpioCallback(mInterruptCallback); + } + + /** + * Local callback to monitor GPIO edge events. + */ + private GpioCallback mInterruptCallback = new GpioCallback() { + @Override + public boolean onGpioEdge(Gpio gpio) { + try { + boolean trigger = gpio.getValue(); + if (trigger) { + I2cDevice i2c = mPioService.openI2cDevice(mI2cBus, mI2cAddress); + int key = i2c.readRegByte(JOYSTICK_REG_KEYS) & 0x7F; + i2c.close(); + if (key == KEY_RELEASED) { + performButtonEvent(mPrevKey, false); + } else { + performButtonEvent(key, true); + } + mPrevKey = key; + } + } catch (IOException e) { + Log.e(TAG, "Error reading button state", e); + } + + return true; + } + }; + + /** + * Sets the listener to be called when a button event is occurred. + * + * @param listener button event listener to be invoked + */ + public void setOnButtonEventListener(OnButtonEventListener listener) { + mListener = listener; + } + + /** + * Closes the driver and its underlying device. + * + * @throws IOException + */ + @Override + public void close() throws IOException { + mListener = null; + + if (mInterruptGpio != null) { + mInterruptGpio.unregisterGpioCallback(mInterruptCallback); + try { + mInterruptGpio.close(); + } finally { + mInterruptGpio = null; + } + } + } + + /** + * Invokes button event callback. + */ + private void performButtonEvent(int key, boolean state) { + if (mListener != null) { + mListener.onButtonEvent(key, state); + } + } + +} diff --git a/sensehat/src/main/java/com/google/android/things/contrib/driver/sensehat/JoystickDriver.java b/sensehat/src/main/java/com/google/android/things/contrib/driver/sensehat/JoystickDriver.java new file mode 100644 index 0000000..b1d69ff --- /dev/null +++ b/sensehat/src/main/java/com/google/android/things/contrib/driver/sensehat/JoystickDriver.java @@ -0,0 +1,146 @@ +/* + * Copyright 2016 Macro Yau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.things.contrib.driver.sensehat; + +import android.view.InputDevice; +import android.view.KeyEvent; + +import com.google.android.things.userdriver.InputDriver; +import com.google.android.things.userdriver.UserDriverManager; + +import java.io.IOException; + +/** + * User-space driver to process button events from the Raspberry Pi Sense HAT joystick and forward + * them to the Android input framework. + */ +@SuppressWarnings("WeakerAccess") +public class JoystickDriver implements AutoCloseable { + + // Driver parameters + private static final String DRIVER_NAME = "SenseHatJoystick"; + private static final int DRIVER_VERSION = 1; + + // Key code for driver to emulate + private static final int KEY_CODE_UP = KeyEvent.KEYCODE_DPAD_UP; + private static final int KEY_CODE_DOWN = KeyEvent.KEYCODE_DPAD_DOWN; + private static final int KEY_CODE_LEFT = KeyEvent.KEYCODE_DPAD_LEFT; + private static final int KEY_CODE_RIGHT = KeyEvent.KEYCODE_DPAD_RIGHT; + private static final int KEY_CODE_ENTER = KeyEvent.KEYCODE_DPAD_CENTER; + + private Joystick mDevice; + private InputDriver mDriver; + + /** + * Creates a new framework input driver for the joystick. The driver emits + * {@link KeyEvent} with the directional pad key codes when registered. + * + * @param bus the I2C bus the joystick controller is connected to + * @param address the joystick controller I2C device slave address + * @param interruptPin the interrupt GPIO pin the joystick controller is connected to + * @return the new input driver instance + * @throws IOException + * @see #register + */ + public JoystickDriver(String bus, int address, String interruptPin) throws IOException { + mDevice = new Joystick(bus, address, interruptPin); + } + + /** + * Closes the driver and its underlying device. + * + * @throws IOException + */ + @Override + public void close() throws IOException { + unregister(); + if (mDevice != null) { + try { + mDevice.close(); + } finally { + mDevice = null; + } + } + } + + /** + * Registers the driver in the framework. + */ + public void register() { + if (mDevice == null) { + throw new IllegalStateException("Cannot registered closed driver"); + } + + if (mDriver == null) { + mDriver = build(mDevice); + UserDriverManager.getManager().registerInputDriver(mDriver); + } + } + + /** + * Unregisters the driver from the framework. + */ + public void unregister() { + if (mDriver != null) { + UserDriverManager.getManager().registerInputDriver(mDriver); + mDriver = null; + } + } + + static InputDriver build(Joystick joystick) { + final InputDriver inputDriver = InputDriver.builder(InputDevice.SOURCE_CLASS_BUTTON) + .setName(DRIVER_NAME) + .setVersion(DRIVER_VERSION) + .setKeys(new int[]{KEY_CODE_UP, KEY_CODE_DOWN, KEY_CODE_LEFT, KEY_CODE_RIGHT, KEY_CODE_ENTER}) + .build(); + + joystick.setOnButtonEventListener(new Joystick.OnButtonEventListener() { + @Override + public void onButtonEvent(int key, boolean pressed) { + int keyAction = pressed ? KeyEvent.ACTION_DOWN : KeyEvent.ACTION_UP; + int keyCode = -1; + switch (key) { + case Joystick.KEY_PRESSED_UP: + keyCode = KEY_CODE_UP; + break; + case Joystick.KEY_PRESSED_DOWN: + keyCode = KEY_CODE_DOWN; + break; + case Joystick.KEY_PRESSED_LEFT: + keyCode = KEY_CODE_LEFT; + break; + case Joystick.KEY_PRESSED_RIGHT: + keyCode = KEY_CODE_RIGHT; + break; + case Joystick.KEY_PRESSED_ENTER: + keyCode = KEY_CODE_ENTER; + break; + default: + break; + } + if (keyCode != -1) { + inputDriver.emit(new KeyEvent[]{ + new KeyEvent(keyAction, keyCode) + }); + } + } + }); + + return inputDriver; + } + +} From d3b8603a9411611cbedbe16c59c9b2e43a4275bc Mon Sep 17 00:00:00 2001 From: Macro Yau Date: Thu, 12 Jan 2017 16:09:34 +0800 Subject: [PATCH 4/7] Update Sense HAT metadriver --- sensehat/README.md | 13 +++-- sensehat/build.gradle | 3 ++ .../driver/sensehat/SenseHatDeviceTest.java | 28 +++++++++++ .../contrib/driver/sensehat/SenseHat.java | 48 +++++++++++++++++-- 4 files changed, 84 insertions(+), 8 deletions(-) diff --git a/sensehat/README.md b/sensehat/README.md index 12f2219..e39b57a 100644 --- a/sensehat/README.md +++ b/sensehat/README.md @@ -1,10 +1,13 @@ -Sense Hat driver for Android Things +Sense HAT driver for Android Things ===================================== -This driver provides easy access to the peripherals available on the Raspberry Pi [Sense Hat][product]: -- 8x8 LED matrix -- TODO: 5 buttons joystick -- TODO: Sensors +This driver provides easy access to the peripherals available on the Raspberry Pi [Sense HAT][product]: + +- 8×8 RGB LED matrix +- 5-button miniature joystick +- ST LPS25H barometric pressure and temperature sensor +- ST HTS221 relative humidity and temperature sensor +- TODO: ST LSM9DS1 inertial measurement sensor NOTE: these drivers are not production-ready. They are offered as sample diff --git a/sensehat/build.gradle b/sensehat/build.gradle index f9deb71..2ab8623 100644 --- a/sensehat/build.gradle +++ b/sensehat/build.gradle @@ -33,6 +33,9 @@ dependencies { provided 'com.google.android.things:androidthings:0.1-devpreview' androidTestCompile 'com.android.support.test:runner:0.5' androidTestCompile 'com.android.support.test:rules:0.5' + + compile project(':hts221') + compile project(':lps25h') } android { diff --git a/sensehat/src/androidTest/java/com/google/android/things/contrib/driver/sensehat/SenseHatDeviceTest.java b/sensehat/src/androidTest/java/com/google/android/things/contrib/driver/sensehat/SenseHatDeviceTest.java index df80356..c56a57d 100644 --- a/sensehat/src/androidTest/java/com/google/android/things/contrib/driver/sensehat/SenseHatDeviceTest.java +++ b/sensehat/src/androidTest/java/com/google/android/things/contrib/driver/sensehat/SenseHatDeviceTest.java @@ -26,6 +26,10 @@ import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; +import android.util.Log; + +import com.google.android.things.contrib.driver.hts221.Hts221; +import com.google.android.things.contrib.driver.lps25h.Lps25h; import org.junit.Test; import org.junit.runner.RunWith; @@ -35,6 +39,7 @@ @RunWith(AndroidJUnit4.class) @SmallTest public class SenseHatDeviceTest { + @Test public void senseHat_DisplayColor() throws IOException { // Color the LED matrix. @@ -68,4 +73,27 @@ public void senseHat_DisplayGradient() throws IOException { // Close the display when done. display.close(); } + + @Test + public void senseHat_ReadPressureSensor() throws IOException { + // Prints LPS25H barometric pressure and temperature sensor readings to LogCat + Lps25h lps25h = SenseHat.openPressureSensor(); + float pressure = lps25h.readPressure(); + Log.i("LPS25H", String.format("Barometric Pressure: %.1f", pressure) + " hPa"); + float temperature = lps25h.readTemperature(); + Log.i("LPS25H", String.format("Temperature: %.1f", temperature) + " °C"); + lps25h.close(); + } + + @Test + public void senseHat_ReadHumiditySensor() throws IOException { + // Prints HTS221 relative humidity and temperature sensor readings to LogCat + Hts221 hts221 = SenseHat.openHumiditySensor(); + float humidity = hts221.readHumidity(); + Log.i("HTS221", String.format("Relative Humidity: %.1f", humidity) + " %"); + float temperature = hts221.readTemperature(); + Log.i("HTS221", String.format("Temperature: %.1f", temperature) + " °C"); + hts221.close(); + } + } diff --git a/sensehat/src/main/java/com/google/android/things/contrib/driver/sensehat/SenseHat.java b/sensehat/src/main/java/com/google/android/things/contrib/driver/sensehat/SenseHat.java index c3bed66..ddf0c7c 100644 --- a/sensehat/src/main/java/com/google/android/things/contrib/driver/sensehat/SenseHat.java +++ b/sensehat/src/main/java/com/google/android/things/contrib/driver/sensehat/SenseHat.java @@ -16,6 +16,11 @@ package com.google.android.things.contrib.driver.sensehat; +import com.google.android.things.contrib.driver.hts221.Hts221; +import com.google.android.things.contrib.driver.hts221.Hts221SensorDriver; +import com.google.android.things.contrib.driver.lps25h.Lps25h; +import com.google.android.things.contrib.driver.lps25h.Lps25hSensorDriver; + import java.io.IOException; /** @@ -23,12 +28,49 @@ */ @SuppressWarnings({"unused", "WeakerAccess"}) public class SenseHat { - public static final int I2C_ADDRESS = 0x46; - public static final String BUS_DISPLAY = "I2C1"; + + public static final String I2C_BUS = "I2C1"; + public static final int I2C_ADDRESS = 0x46; + public static final int DISPLAY_WIDTH = LedMatrix.WIDTH; public static final int DISPLAY_HEIGHT = LedMatrix.HEIGHT; + public static final String JOYSTICK_INTERRUPT = "BCM23"; // Interrupt pin for joystick events + + // 8×8 RGB LED matrix + public static LedMatrix openDisplay() throws IOException { - return new LedMatrix(BUS_DISPLAY); + return new LedMatrix(I2C_BUS); } + + // 5-button miniature joystick + + public static Joystick openJoystick() throws IOException { + return new Joystick(I2C_BUS, I2C_ADDRESS, JOYSTICK_INTERRUPT); + } + + public static JoystickDriver createJoystickDriver() throws IOException { + return new JoystickDriver(I2C_BUS, I2C_ADDRESS, JOYSTICK_INTERRUPT); + } + + // ST LPS25H barometric pressure and temperature sensor + + public static Lps25h openPressureSensor() throws IOException { + return new Lps25h(I2C_BUS); + } + + public static Lps25hSensorDriver createPressureSensorDriver() throws IOException { + return new Lps25hSensorDriver(I2C_BUS); + } + + // ST HTS221 relative humidity and temperature sensor + + public static Hts221 openHumiditySensor() throws IOException { + return new Hts221(I2C_BUS); + } + + public static Hts221SensorDriver createHumiditySensorDriver() throws IOException { + return new Hts221SensorDriver(I2C_BUS); + } + } From b34eedb7317ad417a598ead7b1d792088412b849 Mon Sep 17 00:00:00 2001 From: Macro Yau Date: Fri, 27 Jan 2017 18:17:46 +0800 Subject: [PATCH 5/7] Clean up LedMatrix --- .../contrib/driver/sensehat/LedMatrix.java | 52 +++++++++++-------- 1 file changed, 29 insertions(+), 23 deletions(-) diff --git a/sensehat/src/main/java/com/google/android/things/contrib/driver/sensehat/LedMatrix.java b/sensehat/src/main/java/com/google/android/things/contrib/driver/sensehat/LedMatrix.java index 67a4ff3..4951de3 100644 --- a/sensehat/src/main/java/com/google/android/things/contrib/driver/sensehat/LedMatrix.java +++ b/sensehat/src/main/java/com/google/android/things/contrib/driver/sensehat/LedMatrix.java @@ -27,19 +27,22 @@ import java.io.IOException; /** - * Driver for the SenseHat LED matrix. + * Driver for Raspberry Pi Sense HAT 8×8 RGB LED matrix. */ public class LedMatrix implements AutoCloseable { + public static final int WIDTH = 8; public static final int HEIGHT = 8; + private static final int BUFFER_SIZE = WIDTH * HEIGHT * 3 + 1; - private byte[] mBuffer = new byte[BUFFER_SIZE]; private I2cDevice mDevice; + private byte[] mBuffer = new byte[BUFFER_SIZE]; /** - * Create a new LED matrix driver connected on the given I2C bus. - * @param bus I2C bus the sensor is connected to. + * Creates a new LED matrix driver connected on the given I2C bus. + * + * @param bus I2C bus the sensor is connected to * @throws IOException */ public LedMatrix(String bus) throws IOException { @@ -52,7 +55,8 @@ public LedMatrix(String bus) throws IOException { } /** - * Close the driver and the underlying device. + * Closes the driver and its underlying device. + * * @throws IOException */ @Override @@ -67,34 +71,35 @@ public void close() throws IOException { } /** - * Draw the given color to the LED matrix. - * @param color Color to draw + * Draws the given color to the LED matrix. + * + * @param color color to draw * @throws IOException */ public void draw(int color) throws IOException { mBuffer[0] = 0; float a = Color.alpha(color) / 255.f; - byte r = (byte)((int)(Color.red(color)*a)>>3); - byte g = (byte)((int)(Color.green(color)*a)>>3); - byte b = (byte)((int)(Color.blue(color)*a)>>3); + byte r = (byte) ((int) (Color.red(color) * a) >> 3); + byte g = (byte) ((int) (Color.green(color) * a) >> 3); + byte b = (byte) ((int) (Color.blue(color) * a) >> 3); for (int y = 0; y < HEIGHT; y++) { for (int x = 0; x < WIDTH; x++) { - mBuffer[1+x+WIDTH*0+3*WIDTH*y] = r; - mBuffer[1+x+WIDTH*1+3*WIDTH*y] = g; - mBuffer[1+x+WIDTH*2+3*WIDTH*y] = b; + mBuffer[1 + x + WIDTH * 0 + 3 * WIDTH * y] = r; + mBuffer[1 + x + WIDTH * 1 + 3 * WIDTH * y] = g; + mBuffer[1 + x + WIDTH * 2 + 3 * WIDTH * y] = b; } } mDevice.write(mBuffer, mBuffer.length); } /** - * Draw the given drawable to the LED matrix. - * @param drawable Drawable to draw + * Draws the given drawable to the LED matrix. + * + * @param drawable drawable to draw * @throws IOException */ public void draw(Drawable drawable) throws IOException { - Bitmap bitmap = Bitmap.createBitmap(WIDTH, HEIGHT, - Bitmap.Config.ARGB_8888); + Bitmap bitmap = Bitmap.createBitmap(WIDTH, HEIGHT, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); drawable.setBounds(0, 0, WIDTH, HEIGHT); drawable.draw(canvas); @@ -102,22 +107,23 @@ public void draw(Drawable drawable) throws IOException { } /** - * Draw the given bitmap to the LED matrix. - * @param bitmap Bitmap to draw + * Draws the given bitmap to the LED matrix. + * + * @param bitmap bitmap to draw * @throws IOException */ public void draw(Bitmap bitmap) throws IOException { - Bitmap dest = Bitmap.createScaledBitmap(bitmap, 8, 8, true); mBuffer[0] = 0; for (int y = 0; y < HEIGHT; y++) { for (int x = 0; x < WIDTH; x++) { int p = bitmap.getPixel(x, y); float a = Color.alpha(p) / 255.f; - mBuffer[1+x+WIDTH*0+3*WIDTH*y] = (byte)((int)(Color.red(p)*a)>>3); - mBuffer[1+x+WIDTH*1+3*WIDTH*y] = (byte)((int)(Color.green(p)*a)>>3); - mBuffer[1+x+WIDTH*2+3*WIDTH*y] = (byte)((int)(Color.blue(p)*a)>>3); + mBuffer[1 + x + WIDTH * 0 + 3 * WIDTH * y] = (byte) ((int) (Color.red(p) * a) >> 3); + mBuffer[1 + x + WIDTH * 1 + 3 * WIDTH * y] = (byte) ((int) (Color.green(p) * a) >> 3); + mBuffer[1 + x + WIDTH * 2 + 3 * WIDTH * y] = (byte) ((int) (Color.blue(p) * a) >> 3); } } mDevice.write(mBuffer, mBuffer.length); } + } From 9dc37017198c485da46a47543609ae03bde06ccf Mon Sep 17 00:00:00 2001 From: Macro Yau Date: Fri, 27 Jan 2017 18:35:19 +0800 Subject: [PATCH 6/7] Refactor Sense HAT driver and API --- .../driver/sensehat/SenseHatDeviceTest.java | 20 ++-- .../contrib/driver/sensehat/Joystick.java | 32 ++--- .../driver/sensehat/JoystickDriver.java | 13 +- .../contrib/driver/sensehat/LedMatrix.java | 41 +------ .../contrib/driver/sensehat/SenseHat.java | 113 ++++++++++++++++-- 5 files changed, 132 insertions(+), 87 deletions(-) diff --git a/sensehat/src/androidTest/java/com/google/android/things/contrib/driver/sensehat/SenseHatDeviceTest.java b/sensehat/src/androidTest/java/com/google/android/things/contrib/driver/sensehat/SenseHatDeviceTest.java index c56a57d..accc035 100644 --- a/sensehat/src/androidTest/java/com/google/android/things/contrib/driver/sensehat/SenseHatDeviceTest.java +++ b/sensehat/src/androidTest/java/com/google/android/things/contrib/driver/sensehat/SenseHatDeviceTest.java @@ -43,35 +43,41 @@ public class SenseHatDeviceTest { @Test public void senseHat_DisplayColor() throws IOException { // Color the LED matrix. - LedMatrix display = SenseHat.openDisplay(); - + SenseHat senseHat = new SenseHat(); + LedMatrix display = senseHat.openDisplay(); display.draw(Color.MAGENTA); + // Close the display when done. - display.close(); + senseHat.close(); } @Test public void senseHat_DisplayDrawable() throws IOException { Context context = InstrumentationRegistry.getTargetContext(); + // Display a drawable on the LED matrix. - LedMatrix display = SenseHat.openDisplay(); + SenseHat senseHat = new SenseHat(); + LedMatrix display = senseHat.openDisplay(); display.draw(context.getDrawable(android.R.drawable.ic_secure)); + // Close the display when done. - display.close(); + senseHat.close(); } @Test public void senseHat_DisplayGradient() throws IOException { // Display a gradient on the LED matrix. - LedMatrix display = SenseHat.openDisplay(); + SenseHat senseHat = new SenseHat(); + LedMatrix display = senseHat.openDisplay(); Bitmap bitmap = Bitmap.createBitmap(SenseHat.DISPLAY_WIDTH, SenseHat.DISPLAY_HEIGHT, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); Paint paint = new Paint(); paint.setShader(new RadialGradient(4, 4, 4, Color.RED, Color.BLUE, Shader.TileMode.CLAMP)); canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), paint); display.draw(bitmap); + // Close the display when done. - display.close(); + senseHat.close(); } @Test diff --git a/sensehat/src/main/java/com/google/android/things/contrib/driver/sensehat/Joystick.java b/sensehat/src/main/java/com/google/android/things/contrib/driver/sensehat/Joystick.java index 848bc8c..0056d4d 100644 --- a/sensehat/src/main/java/com/google/android/things/contrib/driver/sensehat/Joystick.java +++ b/sensehat/src/main/java/com/google/android/things/contrib/driver/sensehat/Joystick.java @@ -21,7 +21,6 @@ import com.google.android.things.pio.Gpio; import com.google.android.things.pio.GpioCallback; -import com.google.android.things.pio.I2cDevice; import com.google.android.things.pio.PeripheralManagerService; import java.io.IOException; @@ -32,7 +31,7 @@ * @see Sense HAT joystick Linux kernel driver */ @SuppressWarnings({"unused", "WeakerAccess"}) -public class Joystick implements AutoCloseable { +public class Joystick { private static final String TAG = "SenseHatJoystick"; @@ -45,10 +44,6 @@ public class Joystick implements AutoCloseable { private static final int JOYSTICK_REG_KEYS = 0xF2; - private PeripheralManagerService mPioService; - - private String mI2cBus; - private int mI2cAddress; private Gpio mInterruptGpio; private OnButtonEventListener mListener; private int mPrevKey; @@ -70,16 +65,14 @@ public interface OnButtonEventListener { /** * Creates a new joystick driver. * - * @param bus the I2C bus the joystick controller is connected to - * @param address the joystick controller I2C device slave address * @param interruptPin the interrupt GPIO pin the joystick controller is connected to * @throws IOException */ - public Joystick(String bus, int address, String interruptPin) throws IOException { - mPioService = new PeripheralManagerService(); - Gpio interruptGpio = mPioService.openGpio(interruptPin); + public Joystick(String interruptPin) throws IOException { + PeripheralManagerService pioService = new PeripheralManagerService(); + Gpio interruptGpio = pioService.openGpio(interruptPin); try { - connect(bus, address, interruptGpio); + connect(interruptGpio); } catch (IOException | RuntimeException e) { close(); throw e; @@ -90,13 +83,11 @@ public Joystick(String bus, int address, String interruptPin) throws IOException * Constructor invoked from unit tests. */ @VisibleForTesting - /*package*/ Joystick(String bus, int address, Gpio interruptGpio) throws IOException { - connect(bus, address, interruptGpio); + /*package*/ Joystick(Gpio interruptGpio) throws IOException { + connect(interruptGpio); } - private void connect(String bus, int address, Gpio interruptGpio) throws IOException { - mI2cBus = bus; - mI2cAddress = address; + private void connect(Gpio interruptGpio) throws IOException { mInterruptGpio = interruptGpio; mInterruptGpio.setDirection(Gpio.DIRECTION_IN); mInterruptGpio.setEdgeTriggerType(Gpio.EDGE_BOTH); @@ -112,9 +103,7 @@ public boolean onGpioEdge(Gpio gpio) { try { boolean trigger = gpio.getValue(); if (trigger) { - I2cDevice i2c = mPioService.openI2cDevice(mI2cBus, mI2cAddress); - int key = i2c.readRegByte(JOYSTICK_REG_KEYS) & 0x7F; - i2c.close(); + int key = SenseHat.i2cDevice.readRegByte(JOYSTICK_REG_KEYS) & 0x7F; if (key == KEY_RELEASED) { performButtonEvent(mPrevKey, false); } else { @@ -144,8 +133,7 @@ public void setOnButtonEventListener(OnButtonEventListener listener) { * * @throws IOException */ - @Override - public void close() throws IOException { + protected void close() throws IOException { mListener = null; if (mInterruptGpio != null) { diff --git a/sensehat/src/main/java/com/google/android/things/contrib/driver/sensehat/JoystickDriver.java b/sensehat/src/main/java/com/google/android/things/contrib/driver/sensehat/JoystickDriver.java index b1d69ff..d05c275 100644 --- a/sensehat/src/main/java/com/google/android/things/contrib/driver/sensehat/JoystickDriver.java +++ b/sensehat/src/main/java/com/google/android/things/contrib/driver/sensehat/JoystickDriver.java @@ -28,8 +28,8 @@ * User-space driver to process button events from the Raspberry Pi Sense HAT joystick and forward * them to the Android input framework. */ -@SuppressWarnings("WeakerAccess") -public class JoystickDriver implements AutoCloseable { +@SuppressWarnings({"unused", "WeakerAccess"}) +public class JoystickDriver { // Driver parameters private static final String DRIVER_NAME = "SenseHatJoystick"; @@ -49,15 +49,13 @@ public class JoystickDriver implements AutoCloseable { * Creates a new framework input driver for the joystick. The driver emits * {@link KeyEvent} with the directional pad key codes when registered. * - * @param bus the I2C bus the joystick controller is connected to - * @param address the joystick controller I2C device slave address * @param interruptPin the interrupt GPIO pin the joystick controller is connected to * @return the new input driver instance * @throws IOException * @see #register */ - public JoystickDriver(String bus, int address, String interruptPin) throws IOException { - mDevice = new Joystick(bus, address, interruptPin); + public JoystickDriver(String interruptPin) throws IOException { + mDevice = new Joystick(interruptPin); } /** @@ -65,8 +63,7 @@ public JoystickDriver(String bus, int address, String interruptPin) throws IOExc * * @throws IOException */ - @Override - public void close() throws IOException { + protected void close() throws IOException { unregister(); if (mDevice != null) { try { diff --git a/sensehat/src/main/java/com/google/android/things/contrib/driver/sensehat/LedMatrix.java b/sensehat/src/main/java/com/google/android/things/contrib/driver/sensehat/LedMatrix.java index 4951de3..2e92026 100644 --- a/sensehat/src/main/java/com/google/android/things/contrib/driver/sensehat/LedMatrix.java +++ b/sensehat/src/main/java/com/google/android/things/contrib/driver/sensehat/LedMatrix.java @@ -21,55 +21,20 @@ import android.graphics.Color; import android.graphics.drawable.Drawable; -import com.google.android.things.pio.I2cDevice; -import com.google.android.things.pio.PeripheralManagerService; - import java.io.IOException; /** * Driver for Raspberry Pi Sense HAT 8×8 RGB LED matrix. */ -public class LedMatrix implements AutoCloseable { +public class LedMatrix { public static final int WIDTH = 8; public static final int HEIGHT = 8; private static final int BUFFER_SIZE = WIDTH * HEIGHT * 3 + 1; - private I2cDevice mDevice; private byte[] mBuffer = new byte[BUFFER_SIZE]; - /** - * Creates a new LED matrix driver connected on the given I2C bus. - * - * @param bus I2C bus the sensor is connected to - * @throws IOException - */ - public LedMatrix(String bus) throws IOException { - PeripheralManagerService pioService = new PeripheralManagerService(); - mDevice = pioService.openI2cDevice(bus, SenseHat.I2C_ADDRESS); - } - - /* package */ LedMatrix(I2cDevice device) { - mDevice = device; - } - - /** - * Closes the driver and its underlying device. - * - * @throws IOException - */ - @Override - public void close() throws IOException { - if (mDevice != null) { - try { - mDevice.close(); - } finally { - mDevice = null; - } - } - } - /** * Draws the given color to the LED matrix. * @@ -89,7 +54,7 @@ public void draw(int color) throws IOException { mBuffer[1 + x + WIDTH * 2 + 3 * WIDTH * y] = b; } } - mDevice.write(mBuffer, mBuffer.length); + SenseHat.i2cDevice.write(mBuffer, mBuffer.length); } /** @@ -123,7 +88,7 @@ public void draw(Bitmap bitmap) throws IOException { mBuffer[1 + x + WIDTH * 2 + 3 * WIDTH * y] = (byte) ((int) (Color.blue(p) * a) >> 3); } } - mDevice.write(mBuffer, mBuffer.length); + SenseHat.i2cDevice.write(mBuffer, mBuffer.length); } } diff --git a/sensehat/src/main/java/com/google/android/things/contrib/driver/sensehat/SenseHat.java b/sensehat/src/main/java/com/google/android/things/contrib/driver/sensehat/SenseHat.java index ddf0c7c..b22e7d9 100644 --- a/sensehat/src/main/java/com/google/android/things/contrib/driver/sensehat/SenseHat.java +++ b/sensehat/src/main/java/com/google/android/things/contrib/driver/sensehat/SenseHat.java @@ -1,5 +1,6 @@ /* * Copyright 2016 Google Inc. + * Copyright 2017 Macro Yau * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,41 +17,129 @@ package com.google.android.things.contrib.driver.sensehat; +import android.graphics.Color; + import com.google.android.things.contrib.driver.hts221.Hts221; import com.google.android.things.contrib.driver.hts221.Hts221SensorDriver; import com.google.android.things.contrib.driver.lps25h.Lps25h; import com.google.android.things.contrib.driver.lps25h.Lps25hSensorDriver; +import com.google.android.things.pio.I2cDevice; +import com.google.android.things.pio.PeripheralManagerService; import java.io.IOException; /** - * Driver factory for the Sense Hat. + * Driver factory for Raspberry Pi Sense HAT. */ @SuppressWarnings({"unused", "WeakerAccess"}) -public class SenseHat { - - public static final String I2C_BUS = "I2C1"; - public static final int I2C_ADDRESS = 0x46; +public class SenseHat implements AutoCloseable { public static final int DISPLAY_WIDTH = LedMatrix.WIDTH; public static final int DISPLAY_HEIGHT = LedMatrix.HEIGHT; - public static final String JOYSTICK_INTERRUPT = "BCM23"; // Interrupt pin for joystick events + private static final String I2C_BUS = "I2C1"; + private static final int I2C_ADDRESS = 0x46; + + private static final String JOYSTICK_INTERRUPT = "BCM23"; // Interrupt pin for joystick events + + private static final int SENSE_HAT_REG_WHO_AM_I = 0xF0; + + protected static I2cDevice i2cDevice; + + private LedMatrix mLedMatrix; + private Joystick mJoystick; + private JoystickDriver mJoystickDriver; + + public SenseHat() throws IOException { + PeripheralManagerService pioService = new PeripheralManagerService(); + i2cDevice = pioService.openI2cDevice(I2C_BUS, I2C_ADDRESS); + + if (!isAttached()) { + throw new IOException("Sense HAT is not attached"); + } + } + + @Override + public void close() throws IOException { + closeDisplay(); + closeJoystick(); + closeJoystickDriver(); + + if (i2cDevice != null) { + try { + i2cDevice.close(); + } finally { + i2cDevice = null; + } + } + } + + /** + * Checks whether Sense HAT is attached to Raspberry Pi correctly. + * + * @return true if attached + * @throws IOException + */ + private boolean isAttached() throws IOException { + return i2cDevice != null && i2cDevice.readRegByte(SENSE_HAT_REG_WHO_AM_I) == 's'; + } // 8×8 RGB LED matrix - public static LedMatrix openDisplay() throws IOException { - return new LedMatrix(I2C_BUS); + public LedMatrix openDisplay() throws IOException { + if (mLedMatrix == null) { + mLedMatrix = new LedMatrix(); + } + + return mLedMatrix; + } + + public void closeDisplay() throws IOException { + if (mLedMatrix != null) { + try { + mLedMatrix.draw(Color.BLACK); + } finally { + mLedMatrix = null; + } + } } // 5-button miniature joystick - public static Joystick openJoystick() throws IOException { - return new Joystick(I2C_BUS, I2C_ADDRESS, JOYSTICK_INTERRUPT); + public Joystick openJoystick() throws IOException { + if (mJoystick == null) { + mJoystick = new Joystick(JOYSTICK_INTERRUPT); + } + + return mJoystick; + } + + public void closeJoystick() throws IOException { + if (mJoystick != null) { + try { + mJoystick.close(); + } finally { + mJoystick = null; + } + } + } + + public JoystickDriver createJoystickDriver() throws IOException { + if (mJoystickDriver == null) { + mJoystickDriver = new JoystickDriver(JOYSTICK_INTERRUPT); + } + + return mJoystickDriver; } - public static JoystickDriver createJoystickDriver() throws IOException { - return new JoystickDriver(I2C_BUS, I2C_ADDRESS, JOYSTICK_INTERRUPT); + public void closeJoystickDriver() throws IOException { + if (mJoystickDriver != null) { + try { + mJoystickDriver.close(); + } finally { + mJoystickDriver = null; + } + } } // ST LPS25H barometric pressure and temperature sensor From 1a4844f64b792e711e1a0eda511dfba3385d6e47 Mon Sep 17 00:00:00 2001 From: Macro Yau Date: Fri, 10 Feb 2017 14:56:38 +0800 Subject: [PATCH 7/7] Migrate BitsMatcher to testingutils library --- hts221/build.gradle | 3 +- .../contrib/driver/hts221/BitsMatcher.java | 49 --------------- .../driver/hts221/BitsMatcherTest.java | 61 ------------------- .../contrib/driver/hts221/Hts221Test.java | 22 ++++--- lps25h/build.gradle | 3 +- .../contrib/driver/lps25h/BitsMatcher.java | 49 --------------- .../driver/lps25h/BitsMatcherTest.java | 61 ------------------- .../contrib/driver/lps25h/Lps25hTest.java | 24 ++++---- 8 files changed, 29 insertions(+), 243 deletions(-) delete mode 100644 hts221/src/test/java/com/google/android/things/contrib/driver/hts221/BitsMatcher.java delete mode 100644 hts221/src/test/java/com/google/android/things/contrib/driver/hts221/BitsMatcherTest.java delete mode 100644 lps25h/src/test/java/com/google/android/things/contrib/driver/lps25h/BitsMatcher.java delete mode 100644 lps25h/src/test/java/com/google/android/things/contrib/driver/lps25h/BitsMatcherTest.java diff --git a/hts221/build.gradle b/hts221/build.gradle index 9217d7d..3d0d039 100644 --- a/hts221/build.gradle +++ b/hts221/build.gradle @@ -29,9 +29,10 @@ android { } dependencies { - provided 'com.google.android.things:androidthings:0.1-devpreview' + provided 'com.google.android.things:androidthings:0.2-devpreview' compile 'com.android.support:support-annotations:24.2.0' + testCompile project(':testingutils') testCompile 'junit:junit:4.12' testCompile 'org.mockito:mockito-core:1.10.19' } diff --git a/hts221/src/test/java/com/google/android/things/contrib/driver/hts221/BitsMatcher.java b/hts221/src/test/java/com/google/android/things/contrib/driver/hts221/BitsMatcher.java deleted file mode 100644 index fdf68ce..0000000 --- a/hts221/src/test/java/com/google/android/things/contrib/driver/hts221/BitsMatcher.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2016 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.google.android.things.contrib.driver.hts221; - -import org.hamcrest.Description; -import org.hamcrest.TypeSafeMatcher; - -/** - * Matcher that checks if necessary bits are set in a byte argument. - */ -public class BitsMatcher extends TypeSafeMatcher { - - private final byte mBits; - - public BitsMatcher(byte bits) { - mBits = bits; - } - - @Override - public void describeTo(Description description) { - description.appendText("Value must be a byte with bits set: ") - .appendText(Integer.toBinaryString(mBits)); - } - - @Override - protected void describeMismatchSafely(Byte item, Description mismatchDescription) { - mismatchDescription.appendText("Value should have bits set: ") - .appendText(Integer.toBinaryString(mBits)); - } - - @Override - protected boolean matchesSafely(Byte item) { - return mBits == 0 ? item == 0 : (item & mBits) == mBits; - } -} - diff --git a/hts221/src/test/java/com/google/android/things/contrib/driver/hts221/BitsMatcherTest.java b/hts221/src/test/java/com/google/android/things/contrib/driver/hts221/BitsMatcherTest.java deleted file mode 100644 index e404d6e..0000000 --- a/hts221/src/test/java/com/google/android/things/contrib/driver/hts221/BitsMatcherTest.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2016 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.google.android.things.contrib.driver.hts221; - -import org.junit.Test; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -public class BitsMatcherTest { - - @Test - public void matches_positive() { - BitsMatcher matcher = new BitsMatcher((byte) 10); // 00001010 - - // bits set - assertTrue(matcher.matches((byte) 10)); - assertTrue(matcher.matches((byte) 0b00001011)); - assertTrue(matcher.matches((byte) 0b00011010)); - - // bits not set - assertFalse(matcher.matches((byte) 0)); - assertFalse(matcher.matches((byte) 0b00011001)); - assertFalse(matcher.matches((byte) 0b01000000)); - } - - @Test - public void matches_negative() { - BitsMatcher matcher = new BitsMatcher((byte) -31); // 11100001 - - // bits set - assertTrue(matcher.matches((byte) -31)); - assertTrue(matcher.matches((byte) 0b11111011)); - assertTrue(matcher.matches((byte) 0b11101001)); - - // bits not set - assertFalse(matcher.matches((byte) 0)); - assertFalse(matcher.matches((byte) 0b11100000)); - assertFalse(matcher.matches((byte) 0b11000001)); - } - - @Test - public void matches_zero() { - BitsMatcher matcher = new BitsMatcher((byte) 0); - assertTrue(matcher.matches((byte) 0)); - assertFalse(matcher.matches((byte) 1)); - } -} diff --git a/hts221/src/test/java/com/google/android/things/contrib/driver/hts221/Hts221Test.java b/hts221/src/test/java/com/google/android/things/contrib/driver/hts221/Hts221Test.java index 97dca46..1d429f6 100644 --- a/hts221/src/test/java/com/google/android/things/contrib/driver/hts221/Hts221Test.java +++ b/hts221/src/test/java/com/google/android/things/contrib/driver/hts221/Hts221Test.java @@ -18,6 +18,8 @@ import com.google.android.things.pio.I2cDevice; +import static com.google.android.things.contrib.driver.testutils.BitsMatcher.hasBitsSet; + import junit.framework.Assert; import org.junit.Rule; @@ -97,7 +99,7 @@ public void setMode() throws IOException { Mockito.reset(mI2c); hts221.setMode(Hts221.MODE_ACTIVE); - Mockito.verify(mI2c).writeRegByte(eq(0x20), byteThat(new BitsMatcher((byte) 0x80))); + Mockito.verify(mI2c).writeRegByte(eq(0x20), byteThat(hasBitsSet((byte) 0x80))); } @Test @@ -117,13 +119,13 @@ public void setBlockDataUpdate() throws IOException { // Disable BDU hts221.setBlockDataUpdate(false); - Mockito.verify(mI2c).writeRegByte(eq(0x20), byteThat(new BitsMatcher((byte) 0x00))); + Mockito.verify(mI2c).writeRegByte(eq(0x20), byteThat(hasBitsSet((byte) 0x00))); Mockito.reset(mI2c); // Enable BDU hts221.setBlockDataUpdate(true); - Mockito.verify(mI2c).writeRegByte(eq(0x20), byteThat(new BitsMatcher((byte) 0x04))); + Mockito.verify(mI2c).writeRegByte(eq(0x20), byteThat(hasBitsSet((byte) 0x04))); } @Test @@ -145,25 +147,25 @@ public void setOutputDataRate() throws IOException { // One-shot hts221.setOutputDataRate(Hts221.HTS221_ODR_ONE_SHOT); - Mockito.verify(mI2c).writeRegByte(eq(0x20), byteThat(new BitsMatcher((byte) 0x00))); + Mockito.verify(mI2c).writeRegByte(eq(0x20), byteThat(hasBitsSet((byte) 0x00))); Mockito.reset(mI2c); // 1 Hz hts221.setOutputDataRate(Hts221.HTS221_ODR_1_HZ); - Mockito.verify(mI2c).writeRegByte(eq(0x20), byteThat(new BitsMatcher((byte) 0x01))); + Mockito.verify(mI2c).writeRegByte(eq(0x20), byteThat(hasBitsSet((byte) 0x01))); Mockito.reset(mI2c); // 7 Hz hts221.setOutputDataRate(Hts221.HTS221_ODR_7_HZ); - Mockito.verify(mI2c).writeRegByte(eq(0x20), byteThat(new BitsMatcher((byte) 0x02))); + Mockito.verify(mI2c).writeRegByte(eq(0x20), byteThat(hasBitsSet((byte) 0x02))); Mockito.reset(mI2c); // 12.5 Hz hts221.setOutputDataRate(Hts221.HTS221_ODR_12_5_HZ); - Mockito.verify(mI2c).writeRegByte(eq(0x20), byteThat(new BitsMatcher((byte) 0x03))); + Mockito.verify(mI2c).writeRegByte(eq(0x20), byteThat(hasBitsSet((byte) 0x03))); } @Test @@ -183,13 +185,13 @@ public void setAveragedSamples() throws IOException { // AVGH_64 + AVGT_2 hts221.setAveragedSamples(Hts221.AV_CONF_AVGH_64, Hts221.AV_CONF_AVGT_2); - Mockito.verify(mI2c).writeRegByte(eq(0x10), byteThat(new BitsMatcher((byte) 0x04))); + Mockito.verify(mI2c).writeRegByte(eq(0x10), byteThat(hasBitsSet((byte) 0x04))); Mockito.reset(mI2c); // AVGH_512 + AVGT_256 hts221.setAveragedSamples(Hts221.AV_CONF_AVGH_512, Hts221.AV_CONF_AVGT_256); - Mockito.verify(mI2c).writeRegByte(eq(0x10), byteThat(new BitsMatcher((byte) 0x3F))); + Mockito.verify(mI2c).writeRegByte(eq(0x10), byteThat(hasBitsSet((byte) 0x3F))); } @Test @@ -259,5 +261,5 @@ public void readTemperature_throwsIfClosed() throws IOException { mExpectedException.expectMessage("I2C device is already closed"); hts221.readTemperature(); } - + } diff --git a/lps25h/build.gradle b/lps25h/build.gradle index 9217d7d..3d0d039 100644 --- a/lps25h/build.gradle +++ b/lps25h/build.gradle @@ -29,9 +29,10 @@ android { } dependencies { - provided 'com.google.android.things:androidthings:0.1-devpreview' + provided 'com.google.android.things:androidthings:0.2-devpreview' compile 'com.android.support:support-annotations:24.2.0' + testCompile project(':testingutils') testCompile 'junit:junit:4.12' testCompile 'org.mockito:mockito-core:1.10.19' } diff --git a/lps25h/src/test/java/com/google/android/things/contrib/driver/lps25h/BitsMatcher.java b/lps25h/src/test/java/com/google/android/things/contrib/driver/lps25h/BitsMatcher.java deleted file mode 100644 index 41b3f76..0000000 --- a/lps25h/src/test/java/com/google/android/things/contrib/driver/lps25h/BitsMatcher.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2016 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.google.android.things.contrib.driver.lps25h; - -import org.hamcrest.Description; -import org.hamcrest.TypeSafeMatcher; - -/** - * Matcher that checks if necessary bits are set in a byte argument. - */ -public class BitsMatcher extends TypeSafeMatcher { - - private final byte mBits; - - public BitsMatcher(byte bits) { - mBits = bits; - } - - @Override - public void describeTo(Description description) { - description.appendText("Value must be a byte with bits set: ") - .appendText(Integer.toBinaryString(mBits)); - } - - @Override - protected void describeMismatchSafely(Byte item, Description mismatchDescription) { - mismatchDescription.appendText("Value should have bits set: ") - .appendText(Integer.toBinaryString(mBits)); - } - - @Override - protected boolean matchesSafely(Byte item) { - return mBits == 0 ? item == 0 : (item & mBits) == mBits; - } -} - diff --git a/lps25h/src/test/java/com/google/android/things/contrib/driver/lps25h/BitsMatcherTest.java b/lps25h/src/test/java/com/google/android/things/contrib/driver/lps25h/BitsMatcherTest.java deleted file mode 100644 index 69a273a..0000000 --- a/lps25h/src/test/java/com/google/android/things/contrib/driver/lps25h/BitsMatcherTest.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2016 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.google.android.things.contrib.driver.lps25h; - -import org.junit.Test; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -public class BitsMatcherTest { - - @Test - public void matches_positive() { - BitsMatcher matcher = new BitsMatcher((byte) 10); // 00001010 - - // bits set - assertTrue(matcher.matches((byte) 10)); - assertTrue(matcher.matches((byte) 0b00001011)); - assertTrue(matcher.matches((byte) 0b00011010)); - - // bits not set - assertFalse(matcher.matches((byte) 0)); - assertFalse(matcher.matches((byte) 0b00011001)); - assertFalse(matcher.matches((byte) 0b01000000)); - } - - @Test - public void matches_negative() { - BitsMatcher matcher = new BitsMatcher((byte) -31); // 11100001 - - // bits set - assertTrue(matcher.matches((byte) -31)); - assertTrue(matcher.matches((byte) 0b11111011)); - assertTrue(matcher.matches((byte) 0b11101001)); - - // bits not set - assertFalse(matcher.matches((byte) 0)); - assertFalse(matcher.matches((byte) 0b11100000)); - assertFalse(matcher.matches((byte) 0b11000001)); - } - - @Test - public void matches_zero() { - BitsMatcher matcher = new BitsMatcher((byte) 0); - assertTrue(matcher.matches((byte) 0)); - assertFalse(matcher.matches((byte) 1)); - } -} diff --git a/lps25h/src/test/java/com/google/android/things/contrib/driver/lps25h/Lps25hTest.java b/lps25h/src/test/java/com/google/android/things/contrib/driver/lps25h/Lps25hTest.java index ada7a47..3acdb4e 100644 --- a/lps25h/src/test/java/com/google/android/things/contrib/driver/lps25h/Lps25hTest.java +++ b/lps25h/src/test/java/com/google/android/things/contrib/driver/lps25h/Lps25hTest.java @@ -18,6 +18,8 @@ import com.google.android.things.pio.I2cDevice; +import static com.google.android.things.contrib.driver.testutils.BitsMatcher.hasBitsSet; + import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -67,7 +69,7 @@ public void setMode() throws IOException { Mockito.reset(mI2c); lps25h.setMode(Lps25h.MODE_ACTIVE); - Mockito.verify(mI2c).writeRegByte(eq(0x20), byteThat(new BitsMatcher((byte) 0x80))); + Mockito.verify(mI2c).writeRegByte(eq(0x20), byteThat(hasBitsSet((byte) 0x80))); } @Test @@ -87,13 +89,13 @@ public void setBlockDataUpdate() throws IOException { // Disable BDU lps25h.setBlockDataUpdate(false); - Mockito.verify(mI2c).writeRegByte(eq(0x20), byteThat(new BitsMatcher((byte) 0x00))); + Mockito.verify(mI2c).writeRegByte(eq(0x20), byteThat(hasBitsSet((byte) 0x00))); Mockito.reset(mI2c); // Enable BDU lps25h.setBlockDataUpdate(true); - Mockito.verify(mI2c).writeRegByte(eq(0x20), byteThat(new BitsMatcher((byte) 0x04))); + Mockito.verify(mI2c).writeRegByte(eq(0x20), byteThat(hasBitsSet((byte) 0x04))); } @Test @@ -115,31 +117,31 @@ public void setOutputDataRate() throws IOException { // One-shot lps25h.setOutputDataRate(Lps25h.LPS25H_ODR_ONE_SHOT); - Mockito.verify(mI2c).writeRegByte(eq(0x20), byteThat(new BitsMatcher((byte) (0x00 << 4)))); + Mockito.verify(mI2c).writeRegByte(eq(0x20), byteThat(hasBitsSet((byte) (0x00 << 4)))); Mockito.reset(mI2c); // 1 Hz lps25h.setOutputDataRate(Lps25h.LPS25H_ODR_1_HZ); - Mockito.verify(mI2c).writeRegByte(eq(0x20), byteThat(new BitsMatcher((byte) (0x01 << 4)))); + Mockito.verify(mI2c).writeRegByte(eq(0x20), byteThat(hasBitsSet((byte) (0x01 << 4)))); Mockito.reset(mI2c); // 7 Hz lps25h.setOutputDataRate(Lps25h.LPS25H_ODR_7_HZ); - Mockito.verify(mI2c).writeRegByte(eq(0x20), byteThat(new BitsMatcher((byte) (0x02 << 4)))); + Mockito.verify(mI2c).writeRegByte(eq(0x20), byteThat(hasBitsSet((byte) (0x02 << 4)))); Mockito.reset(mI2c); // 12.5 Hz lps25h.setOutputDataRate(Lps25h.LPS25H_ODR_12_5_HZ); - Mockito.verify(mI2c).writeRegByte(eq(0x20), byteThat(new BitsMatcher((byte) (0x03 << 4)))); + Mockito.verify(mI2c).writeRegByte(eq(0x20), byteThat(hasBitsSet((byte) (0x03 << 4)))); Mockito.reset(mI2c); // 25 Hz lps25h.setOutputDataRate(Lps25h.LPS25H_ODR_25_HZ); - Mockito.verify(mI2c).writeRegByte(eq(0x20), byteThat(new BitsMatcher((byte) (0x04 << 4)))); + Mockito.verify(mI2c).writeRegByte(eq(0x20), byteThat(hasBitsSet((byte) (0x04 << 4)))); } @Test @@ -161,13 +163,13 @@ public void setAveragedSamples() throws IOException { // AVGP_32 + AVGT_16 lps25h.setAveragedSamples(Lps25h.RES_CONF_AVGP_32, Lps25h.RES_CONF_AVGT_16); - Mockito.verify(mI2c).writeRegByte(eq(0x10), byteThat(new BitsMatcher((byte) 0x05))); + Mockito.verify(mI2c).writeRegByte(eq(0x10), byteThat(hasBitsSet((byte) 0x05))); Mockito.reset(mI2c); // AVGP_512 + AVGT_64 lps25h.setAveragedSamples(Lps25h.RES_CONF_AVGP_512, Lps25h.RES_CONF_AVGT_64); - Mockito.verify(mI2c).writeRegByte(eq(0x10), byteThat(new BitsMatcher((byte) 0x08))); + Mockito.verify(mI2c).writeRegByte(eq(0x10), byteThat(hasBitsSet((byte) 0x08))); } @Test @@ -237,5 +239,5 @@ public void readTemperature_throwsIfClosed() throws IOException { mExpectedException.expectMessage("I2C device is already closed"); lps25h.readTemperature(); } - + }