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 to connect to hardware peripherals. In this codelab, you will learn the basics of working with Peripheral I/O to connect low-level hardware to your apps.

What you'll build

In this codelab, you're going to build a simple app that connects with peripherals on the Rainbow HAT. The app will read the state of a button input and control an LED output.

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

'A' Button

BCM21

GPIO_174

Red LED

BCM6

GPIO_34

Click the following link to download the solution steps for this codelab:

Download solutions

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

$ git clone https://github.com/googlecodelabs/androidthings-peripherals

In this section, you will create a new Android Studio project targeting Android Things.

  1. Open Android Studio and select Start a new Android Studio project.
  2. Enter a name and location for your project, then click Next.
  3. Select the form factor for Android Things, choose API 26 as the Minimum SDK, and click Next.
  4. Choose Android Things Empty Activity and click Next.
  5. Name the activity HomeActivity, uncheck Generate a UI layout File, then click Finish.

Explore the Android Things project

Let's quickly review what makes an Android Things project different from a traditional Phone and Tablet project.

  1. Android Things support library dependency added to the app-level build.gradle file
    This artifact contains the stubs required to compile your Java code. The compileOnly keyword ensures that the build tools don't copy the stubs into the final APK.

build.gradle

dependencies {
    ...
    compileOnly 'com.google.android.things:androidthings:+'
}
  1. Shared library entry for the Android Things support library in the manifest file
    The <uses-library> element makes this prebuilt library available to the app's classpath at run time.

AndroidManifest.xml

<application ...>
    <uses-library android:name="com.google.android.things" />
    ...
</application>
  1. IoT launcher <intent-filter> on the primary activity
    Android Things does not include a user-facing launcher app. Any app intending to run on an embedded device must declare an activity in its manifest as the main entry point after the device boots by attaching an <intent-filter> containing the IOT_LAUNCHER category.

AndroidManifest.xml

<application ...>
    ...

    <activity android:name=".HomeActivity">
        <!-- Launch activity as default from Android Studio -->
        <intent-filter>
            <action android:name="android.intent.action.MAIN"/>
            <category android:name="android.intent.category.LAUNCHER"/>
        </intent-filter>

        <!-- Launch activity automatically on boot -->
        <intent-filter>
            <action android:name="android.intent.action.MAIN"/>
            <category android:name="android.intent.category.IOT_LAUNCHER"/>
            <category android:name="android.intent.category.DEFAULT"/>
        </intent-filter>
    </activity>
</application>
  1. Open HomeActivity.java and locate the empty onCreate() method.
  2. Use the PeripheralManagerService to list the available GPIO ports with getGpioList(). Write the result to logcat.

HomeActivity.java

import com.google.android.things.pio.PeripheralManagerService;

public class HomeActivity extends Activity {
    private static final String TAG = "HomeActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        PeripheralManagerService service = new PeripheralManagerService();
        Log.d(TAG, "Available GPIO: " + service.getGpioList());
    }
}
  1. Deploy the app to the device by selecting Run → Run 'app' from the menu.
  2. Select the Android Monitor tab (typically at the bottom of the window) to see the logcat output inside Android Studio.

Verify that you can see the list of all the GPIO port names in the device log:

... D HomeActivity: Available GPIO: [...]

General Purpose Input/Output (GPIO) pins provide a programmable interface to read the state of a binary input device (such as a pushbutton switch) or control the on/off state of a binary output device (such as an LED). You can configure GPIO pins as an input or output with either a high or low state. As an input, an external source determines the state, and your app can read the current value or react to changes in state. As an output, your app configures the state of the pin.

Now it's time to configure a GPIO pin on the device to act as an input and notify your app when the pin changes state. To receive events when a button connected to the GPIO pin is pressed:

  1. Declare the pin name as a constant, and create a Gpio field for the input connection.

HomeActivity.java

import com.google.android.things.pio.Gpio;
import com.google.android.things.pio.GpioCallback;
import com.google.android.things.pio.PeripheralManagerService;

public class HomeActivity extends Activity {
    private static final String TAG = "HomeActivity";
    private static final String BUTTON_PIN_NAME = "...";

    // GPIO connection to button input
    private Gpio mButtonGpio;

    ...
}
  1. Use PeripheralManagerService to open a connection with the GPIO pin wired to the button within onCreate().
  2. Configure the pin as an input with Gpio.DIRECTION_IN.
  3. Generate callback events on all transitions with Gpio.EDGE_BOTH.

HomeActivity.java

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    PeripheralManagerService service = new PeripheralManagerService();
    Log.d(TAG, "Available GPIO: " + service.getGpioList());
        
    try {
        // Create GPIO connection.
        mButtonGpio = service.openGpio(BUTTON_PIN_NAME);

        // Configure as an input, trigger events on every change.
        mButtonGpio.setDirection(Gpio.DIRECTION_IN);
        mButtonGpio.setEdgeTriggerType(Gpio.EDGE_BOTH);
        // Value is true when the pin is LOW
        mButtonGpio.setActiveType(Gpio.ACTIVE_LOW);
    } catch (IOException e) {
        Log.w(TAG, "Error opening GPIO", e);
    }
}
  1. Declare a GpioCallback to receive edge trigger events.
  2. Return true within onGpioEdge() to continue receiving future edge trigger events.

HomeActivity.java

private GpioCallback mCallback = new GpioCallback() {
    @Override
    public boolean onGpioEdge(Gpio gpio) {
        try {
            Log.i(TAG, "GPIO changed, button " + gpio.getValue());
        } catch (IOException e) {
            Log.w(TAG, "Error reading GPIO");
        }

        // Return true to keep callback active.
        return true;
    }
};
  1. Register this callback with the GPIO pin.

HomeActivity.java

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    ...

    PeripheralManagerService service = new PeripheralManagerService();
    try {
        ...
        // Register the event callback.
        mButtonGpio.registerGpioCallback(mCallback);
    } catch (IOException e) {
        Log.w(TAG, "Error opening GPIO", e);
    }
}
  1. When the application no longer needs the GPIO connection, close the Gpio resource.

HomeActivity.java

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

    // Close the button
    if (mButtonGpio != null) {
        mButtonGpio.unregisterGpioCallback(mCallback);
        try {
            mButtonGpio.close();
        } catch (IOException e) {
            Log.w(TAG, "Error closing GPIO", e);
        }
    }
}
  1. Deploy the app to the device by selecting Run → Run 'app' from the menu.
  2. Select the Android Monitor tab (typically at the bottom of the window) to see the logcat output inside Android Studio.

Verify that messages are inserted into the device log each time you press and release the button:

... I HomeActivity: GPIO changed, button true
... I HomeActivity: GPIO changed, button false
... I HomeActivity: GPIO changed, button true
... I HomeActivity: GPIO changed, button false

Log messages aren't the most interesting way of reporting a state change. In this section, you will connect a second GPIO pin to a light emitting diode (LED) that changes state as the button is pressed.

  1. Declare the pin name as a constant, and create a Gpio field for the LED connection.

HomeActivity.java

public class HomeActivity extends Activity {
    private static final String TAG = "HomeActivity";
    private static final String BUTTON_PIN_NAME = "...";
    private static final String LED_PIN_NAME = "...";

    // GPIO connection to button input
    private Gpio mButtonGpio;
    // GPIO connection to LED output
    private Gpio mLedGpio;

    ...
}
  1. Use PeripheralManagerService to open a connection with the GPIO pin wired to the LED.
  2. Configure the pin as an output with Gpio.DIRECTION_OUT_INITIALLY_LOW. This will set the initial level of the pin to zero volts.

HomeActivity.java

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    PeripheralManagerService service = new PeripheralManagerService();
    try {
        ...

        mLedGpio = service.openGpio(LED_PIN_NAME);
        // Configure as an output.
        mLedGpio.setDirection(Gpio.DIRECTION_OUT_INITIALLY_LOW);
    } catch (IOException e) {
        Log.e(TAG, "Error opening GPIO", e);
    }
}
  1. Set the state of the LED to match the state of the button for each GpioCallback transition.

HomeActivity.java

private GpioCallback mCallback = new GpioCallback() {
    @Override
    public boolean onGpioEdge(Gpio gpio) {
        try {
            boolean buttonValue = gpio.getValue();
            mLedGpio.setValue(buttonValue);
        } catch (IOException e) {
            Log.w(TAG, "Error reading GPIO");
        }

        // Return true to keep callback active.
        return true;
    }
};
  1. When the application no longer needs the GPIO connection, close the Gpio resource.

HomeActivity.java

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

    ...

    // Close the LED.
    if (mLedGpio != null) {
        try {
            mLedGpio.close();
        } catch (IOException e) {
            Log.e(TAG, "Error closing GPIO", e);
        }
    }
}
  1. Deploy the app to the device by selecting Run → Run 'app' from the menu.

Verify that the Red LED turns on and off as you tap the button.

Now that you have some working peripherals, it's time to explore adding a user driver to your app. In this section, you will replace the raw GPIO connection to your button with a ButtonDriver from the Peripheral Driver Library. Google provides this library as an open-source repository of pre-written drivers for common peripherals.

The button driver implements an input user driver to bind button transitions to key events in the Android framework. It also employs some additional features commonly required when interfacing with a button input, such as signal debounce.

  1. Add the driver artifact dependency to your app-level build.gradle file.

build.gradle

dependencies {
    ...
    compile 'com.google.android.things.contrib:driver-button:+'
}
  1. Add the MANAGE_INPUT_DRIVERS permission to your app's manifest file. This permission is required to register any new input device 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_INPUT_DRIVERS" />
    ...

</manifest>
  1. Replace the button's Gpio instance with a ButtonInputDriver.

HomeActivity.java

import com.google.android.things.contrib.driver.button.Button;
import com.google.android.things.contrib.driver.button.ButtonInputDriver;
import com.google.android.things.pio.Gpio;
import com.google.android.things.pio.PeripheralManagerService;

public class HomeActivity extends Activity {
    private static final String TAG = "HomeActivity";
    private static final String BUTTON_PIN_NAME = "...";
    private static final String LED_PIN_NAME = "...";

    // Driver for the GPIO button
    private ButtonInputDriver mButtonInputDriver;
    // GPIO connection to LED output
    private Gpio mLedGpio;

    ...
}
  1. Initialize the button driver using the same GPIO name for the button instance. Configure the driver to emit KEYCODE_SPACE on events.
  2. Register the driver to attach it to the input framework.

HomeActivity.java

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    ...

    try {
        // Initialize button driver to emit SPACE key events
        mButtonInputDriver = new ButtonInputDriver(
                BUTTON_PIN_NAME,
                Button.LogicState.PRESSED_WHEN_LOW,
                KeyEvent.KEYCODE_SPACE);
        // Register with the framework
        mButtonInputDriver.register();
    } catch (IOException e) {
        Log.e(TAG, "Error opening button driver", e);
    }
}
  1. Replace the GpioCallback implementation with onKeyUp() and onKeyDown() in the activity. Use these methods to toggle the LED state.

HomeActivity.java

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
    if (keyCode == KeyEvent.KEYCODE_SPACE) {
        // Turn on the LED
        setLedValue(true);
        return true;
    }

    return super.onKeyDown(keyCode, event);
}

@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
    if (keyCode == KeyEvent.KEYCODE_SPACE) {
        // Turn off the LED
        setLedValue(false);
        return true;
    }

    return super.onKeyUp(keyCode, event);
}

/**
 * Update the value of the LED output.
 */
private void setLedValue(boolean value) {
    try {
        mLedGpio.setValue(value);
    } catch (IOException e) {
        Log.e(TAG, "Error updating GPIO value", e);
    }
}
  1. When your app no longer needs the resource, unregister the driver and close the connection.

HomeActivity.java

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

    // Unregister the driver and close
    if (mButtonInputDriver != null) {
        mButtonInputDriver.unregister();
        try {
            mButtonInputDriver.close();
        } catch (IOException e) {
            Log.e(TAG, "Error closing Button driver", e);
        }
    }

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

Verify that the LED state still changes with each button press as before. Do you notice the slight lag between the button press and the LED change? This is because the button driver applies a small amount of delay, or "debounce", by default. This is to protect against false signals caused by the button input cycling rapidly as the button is pressed or released.

Congratulations! You've successfully built your first Android Things device using the Rainbow HAT! Here are some things you can do to go deeper.

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. The Rainbow HAT driver also provides a quick and easy way to connect with all the peripheral devices on the HAT in a new application project.

Modify the debounce

The button driver applies 100ms of debounce to the input by default to avoid multiple event triggers that can be caused by the mechanical switch inside a button "bouncing" as it opens and closes. You can tune this value with the setDebounceDelay() method. Try out some different values to see how the behavior of the code changes. What happens when you disable debounce altogether?

Blink the LED

Add a Handler to the activity as a timer to blink the LED, similar to the code provided in the Simple PIO Blink example. Can you make the LED blink only while the button is pressed?

What we've covered