Android Things makes developing connected embedded devices easy by providing the same Android development tools, best-in-class Android framework, and Google APIs that make developers successful on mobile. Android Things extends the core Android framework with additional APIs provided by the Things Support Library. These APIs allow apps to integrate with new types of hardware not found on mobile devices.
One of those new API surfaces is the Peripheral I/O API, which enables apps to use industry standard protocols and interfaces (such as I2C and UART) to connect to hardware peripherals. To bootstrap the getting started process, Google provides the Peripheral Driver Library on GitHub. This library is an open-source repository of pre-written drivers for common peripherals.
In this codelab, you will be using drivers from the Peripheral Driver Library to quickly build a fully-functional application that interacts with multiple peripherals simultaneously. You will learn how to open Peripheral I/O connections and transfer data between the devices.
In this codelab, you're going to build a weather station that reads environmental temperature and pressure data from a BMP280 sensor, and displays the latest reading locally on the Rainbow HAT. |
Before you begin building apps for Things, you must:
If you have not already installed Android Things on your development board, follow the official image flashing instructions for your board:
Install the Rainbow HAT on top of your developer board.
Verify that your development computer is properly connected to your device using the adb tool:
$ adb devices List of devices attached 1b2f21d4e1fe0129 device
The expansion connector on the development board exposes Peripheral I/O signals for application use. The Rainbow HAT sits on top of the expansion connector, providing a variety of inputs and outputs for developers to interact with.
The peripherals on the Rainbow HAT used in this codelab are connected to the following signals. These are also listed on the back of the Rainbow HAT:
Peripheral Device | Raspberry Pi 3 | i.MX7D |
|
| |
|
| |
|
|
Click the following link to download the starter project for this codelab:
...or you can clone the GitHub repository from the command line:
$ git clone https://github.com/googlecodelabs/androidthings-weatherstation
weatherstation-start
subdirectory.Verify that the project has successfully launched on the device by verifying the startup message in the log:
$ adb logcat WeatherStationActivity:V *:S ... D WeatherStationActivity: Started Weather Station
The starter project contains the following source files:
RainbowUtil
: Helper class to compute the Rainbow colors displayed on the Rainbow HAT RGB LED strip. Your code will reference this class to convert sensor readings into the proper display colors.WeatherStationActivity
: Main activity of the application. The code you write in this codelab will be placed here.The app-level build.gradle
file includes a dependency for Android Things support library to enable access to the Peripheral I/O API:
dependencies {
compileOnly 'com.google.android.things:androidthings:1.0'
}
The Rainbow HAT includes an HT16K33 segment display driver connected over the I2C serial bus and a strip of seven APA102 RGB LEDs connected over the SPI serial bus. You will be accessing these devices using drivers provided for the Rainbow HAT.
Add a dependency for the RainbowHat driver to your app-level build.gradle file:
dependencies {
...
implementation 'com.google.android.things.contrib:driver-rainbowhat:1.0'
}
Open the WeatherStationActivity
class, and declare new fields for an AlphanumericDisplay
and Apa102
LED strip:
import com.google.android.things.contrib.driver.apa102.Apa102;
import com.google.android.things.contrib.driver.ht16k33.AlphanumericDisplay;
import com.google.android.things.contrib.driver.rainbowhat.RainbowHat;
public class WeatherStationActivity extends Activity {
...
private AlphanumericDisplay mDisplay;
private Apa102 mLedstrip;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "Weather Station Started");
}
}
Open a connection to both peripherals in onCreate()
using the RainbowHat
driver. Set the AlphanumericDisplay
to show the value 1234, and the Apa102
to display Color.RED
across all the LEDs in the strip:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "Weather Station Started");
// Initialize 14-segment display
try {
mDisplay = RainbowHat.openDisplay();
mDisplay.setEnabled(true);
mDisplay.display("1234");
Log.d(TAG, "Initialized I2C Display");
} catch (IOException e) {
throw new RuntimeException("Error initializing display", e);
}
// Initialize LED strip
try {
mLedstrip = RainbowHat.openLedStrip();
mLedstrip.setBrightness(LEDSTRIP_BRIGHTNESS);
int[] colors = new int[7];
Arrays.fill(colors, Color.RED);
mLedstrip.write(colors);
// Because of a known APA102 issue, write the initial value twice.
mLedstrip.write(colors);
Log.d(TAG, "Initialized SPI LED strip");
} catch (IOException e) {
throw new RuntimeException("Error initializing LED strip", e);
}
}
Add the following code to onDestroy()
to turn off both output devices and close the connections once they are no longer needed:
@Override
protected void onDestroy() {
super.onDestroy();
if (mDisplay != null) {
try {
mDisplay.clear();
mDisplay.setEnabled(false);
mDisplay.close();
} catch (IOException e) {
Log.e(TAG, "Error closing display", e);
} finally {
mDisplay = null;
}
}
if (mLedstrip != null) {
try {
mLedstrip.setBrightness(0);
mLedstrip.write(new int[7]);
mLedstrip.close();
} catch (IOException e) {
Log.e(TAG, "Error closing LED strip", e);
} finally {
mLedstrip = null;
}
}
}
Deploy the app to the device by selecting Run → Run 'app' from the menu, or click the Run icon in the toolbar.
Verify that the display shows "1234" and all the LEDs on the Rainbow HAT are glowing red.
The Rainbow HAT includes a BMP280 temperature and pressure sensor that is connected over the I2C serial bus. This driver handles the low-level I2C communication and binds the new sensor to the Android framework as a user-space driver. Once the driver is registered, your code will retrieve the sensor data using the standard SensorManager
system service.
Add the MANAGE_SENSOR_DRIVERS
permission to your app's manifest file. This permission is required to register any new sensor as a user-space driver.
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.androidthings.weatherstation">
<uses-permission
android:name="com.google.android.things.permission.MANAGE_SENSOR_DRIVERS" />
...
</manifest>
Declare a new Bmx280SensorDriver
and initialize it in onCreate()
. Register the user-space drivers for each individual sensor on the BMP280 with the sensor framework:
import com.google.android.things.contrib.driver.bmx280.Bmx280SensorDriver;
public class WeatherStationActivity extends Activity {
...
private Bmx280SensorDriver mEnvironmentalSensorDriver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "Weather Station Started");
// Initialize temperature/pressure sensors
try {
mEnvironmentalSensorDriver = RainbowHat.createSensorDriver();
// Register the drivers with the framework
mEnvironmentalSensorDriver.registerTemperatureSensor();
mEnvironmentalSensorDriver.registerPressureSensor();
Log.d(TAG, "Initialized I2C BMP280");
} catch (IOException e) {
throw new RuntimeException("Error initializing BMP280", e);
}
...
}
}
Obtain a reference to the SensorManager
system service in onCreate()
:
private SensorManager mSensorManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "Weather Station Started");
mSensorManager = getSystemService(SensorManager.class);
...
}
Implement the updateTemperatureDisplay()
method to write the temperature sensor value out to the segment display:
private void updateTemperatureDisplay(float temperature) {
if (mDisplay != null) {
try {
mDisplay.display(temperature);
} catch (IOException e) {
Log.e(TAG, "Error updating display", e);
}
}
}
Implement the updateBarometerDisplay()
method to update the RGB LED strip based on the pressure sensor value:
private void updateBarometerDisplay(float pressure) {
if (mLedstrip != null) {
try {
int[] colors = RainbowUtil.getWeatherStripColors(pressure);
mLedstrip.write(colors);
} catch (IOException e) {
Log.e(TAG, "Error updating ledstrip", e);
}
}
}
Declare a new SensorEventListener
to receive events generated for new temperature and pressure values and call the two update methods you just implemented:
// Callback when SensorManager delivers new data.
private SensorEventListener mSensorEventListener = new SensorEventListener() {
@Override
public void onSensorChanged(SensorEvent event) {
final float value = event.values[0];
if (event.sensor.getType() == Sensor.TYPE_AMBIENT_TEMPERATURE) {
updateTemperatureDisplay(value);
}
if (event.sensor.getType() == Sensor.TYPE_PRESSURE) {
updateBarometerDisplay(value);
}
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
Log.d(TAG, "accuracy changed: " + accuracy);
}
};
Register the listener for each sensor on the BMP280. User-space drivers register the sensors with the framework as dynamic sensors, so you can access them from the getDynamicSensorList()
method:
@Override
protected void onStart() {
super.onStart();
// Register the BMP280 temperature sensor
Sensor temperature = mSensorManager
.getDynamicSensorList(Sensor.TYPE_AMBIENT_TEMPERATURE).get(0);
mSensorManager.registerListener(mSensorEventListener, temperature,
SensorManager.SENSOR_DELAY_NORMAL);
// Register the BMP280 pressure sensor
Sensor pressure = mSensorManager
.getDynamicSensorList(Sensor.TYPE_PRESSURE).get(0);
mSensorManager.registerListener(mSensorEventListener, pressure,
SensorManager.SENSOR_DELAY_NORMAL);
}
@Override
protected void onStop() {
super.onStop();
mSensorManager.unregisterListener(mSensorEventListener);
}
Add the following code to onDestroy()
to close the driver connection:
@Override
protected void onDestroy() {
super.onDestroy();
if (mEnvironmentalSensorDriver != null) {
try {
mEnvironmentalSensorDriver.close();
} catch (IOException e) {
Log.e(TAG, "Error closing sensors", e);
} finally {
mEnvironmentalSensorDriver = null;
}
}
...
}
Deploy the app to the device by selecting Run → Run 'app' from the menu, or click the Run icon in the toolbar.
Verify that the Rainbow HAT display now shows the current temperature in ℃ and the LEDs show a rainbow gauge that corresponds to the pressure reading.
Congratulations! You've successfully built a weather station using the Rainbow HAT! Here are some things you can do to go deeper.
Review the documentation for Google Cloud IoT. This service makes it easy for users to ingest data from connected devices and integrate them with the Google Cloud Platform. Can you enable your Weatherstation to publish the environmental sensor data to a Pub/Sub topic using MQTT?
Read through the Peripheral I/O API Guides in the Android Things documentation to learn more about all the industry-standard interfaces that you can use to connect hardware to your application project.
Experiment with the other peripherals included on the Rainbow HAT, like the piezo speaker and capacitive buttons. Explore the PWM speaker and other driver samples to get some experience with these elements.