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. Open Android Studio, and close any existing projects you may have opened with File → Close Project.
  2. Choose Import project from the welcome screen.
  3. Navigate to the project directory you downloaded.
  4. Select the weatherstation-start subdirectory.
  5. Click Open. The project will take a few moments to import and build.
  6. Select Run → Run 'app' from the menu, or click the Run icon in the toolbar.

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 {
    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 Rainbow HAT driver

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

build.gradle

dependencies {
    ...
    implementation 'com.google.android.things.contrib:driver-rainbowhat:1.0'
}

Connect the alphanumeric display and LED strip

Open the WeatherStationActivity class, and declare new fields for an AlphanumericDisplay and Apa102 LED strip:

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;
    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:

WeatherStationActivity.java

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

Clean up your resources

Add the following code to onDestroy() to turn off both output devices and close the connections once they are no longer needed:

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

Verify the new behavior

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 required permission

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>

Connect the temperature sensor

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

        ...
    }
}

Listen for sensor events

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

    ...
}

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

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

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

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

Clean up your resources

Add the following code to onDestroy() to close the driver connection:

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

    ...
}

Verify the new behavior

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.

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