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.

What you'll build

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.

What you'll learn

What you'll need

Update Android SDK

Before you begin building apps for Things, you must:

Flash Android Things

If you have not already installed Android Things on your development board, follow the official image flashing instructions for your board:

Assemble the hardware

Install the Rainbow HAT on top of your developer board.

Connect to the device

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

BMP280 Environmental Sensor

I2C1

I2C1

HT16K33 Segment Display

I2C1

I2C1

APA102 RGB LED Strip

SPI0.0

SPI3.1

Click the following link to download the starter project for this codelab:

Download source code

...or you can clone the GitHub repository from the command line:

$ git clone https://github.com/googlecodelabs/androidthings-weatherstation
  1. Unpack the downloaded zip file.
  2. Open Android Studio and select Import project.
  3. Select the weatherstation-start directory from within the extracted zip file location.
  4. Once the project import is complete. Deploy the app to the device by selecting Run → Run 'app' from the menu.

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

About the project

The starter project contains the following source files:

The app-level build.gradle file includes a dependency for Android Things support library to enable access to the Peripheral I/O API:

build.gradle

dependencies {
    provided 'com.google.android.things:androidthings:+'
}

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.

  1. Add a dependency for the RainbowHat driver to your app-level build.gradle file:

build.gradle

dependencies {
    ...
    compile 'com.google.android.things.contrib:driver-rainbowhat:+'
}
  1. Open the WeatherStationActivity class.
  2. Declare a new AlphanumericDisplay and initialize it in onCreate():

WeatherStationActivity.java

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;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(TAG, "Weather Station Started");

        // Initialize 7-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);
        }
    }
}
  1. Declare a new Apa102 strip and initialize it in onCreate():

WeatherStationActivity.java

private Apa102 mLedstrip;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Log.d(TAG, "Weather Station Started");

    ...

    // 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);
    }
}
  1. Close the peripheral connections in onDestroy():

WeatherStationActivity.java

@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;
        }
    }
}
  1. Deploy the app to the device by selecting Run → Run 'app' from the menu.

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.

  1. 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.

AndroidManifest.xml

<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>
  1. 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:

WeatherStationActivity.java

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);
        }

        ...
    }
}
  1. Obtain a reference to the SensorManager system service in onCreate():

WeatherStationActivity.java

private SensorManager mSensorManager;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Log.d(TAG, "Weather Station Started");

    mSensorManager = getSystemService(SensorManager.class);

    ...
}
  1. Implement the updateTemperatureDisplay() method to write the temperature sensor value out to the segment display:

WeatherStationActivity.java

private void updateTemperatureDisplay(float temperature) {

    if (mDisplay != null) {
        try {
            mDisplay.display(temperature);
        } catch (IOException e) {
            Log.e(TAG, "Error updating display", e);
        }
    }
}
  1. Implement the updateBarometerDisplay() method to update the RGB LED strip based on the pressure sensor value:

WeatherStationActivity.java

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);
        }
    }
}
  1. Declare a new SensorEventListener to receive events generated for new temperature and pressure values and call the two update methods you just implemented:

WeatherStationActivity.java

// 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);
    }
};
  1. 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:

WeatherStationActivity.java

@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);
}
  1. Close the driver connection in onDestroy():

WeatherStationActivity.java

@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;
        }
    }

    ...
}
  1. Deploy the app to the device by selecting Run → Run 'app' from the menu.

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.

Connect to Google Cloud

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?

Explore Peripheral I/O

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.

What we've covered