Android Things Tutorial: Getting Started

Did you ever want to tinker with all those little pins on hardware boards? Well in this tutorial, you’ll learn how to do just that, with AndroidThings! By Dean Djermanović.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 3 of 5 of this article. Click here to view the first page.

Download and Explore the Starter Project

Before you start building apps for Android Things make sure you have SDK tools version 27.0.1, or higher, and SDK with Android 8.1 (API 27), or higher, otherwise update them accordingly.

Download the materials for the tutorial by using the Download Materials button at the top or bottom of the page, and import the starter project into Android Studio.

Explore the project and see what makes the Android Things project different from a traditional phone and tablet project.

Open the app-level build.gradle file.

In the dependencies block, you’ll see this line:

compileOnly 'com.google.android.things:androidthings:' + project.ext.androidThingsVersion

The Things support library is a new library which Google is developing to handle the communication with peripherals and drivers. This is a completely new library not present in the Android SDK, and this library is one of the most important features. It exposes a set of Java Interfaces and classes (APIs) that you can use to connect and exchange data with external devices such as sensors, actuators and so on. This library hides the inner communication details, supporting several industry standard protocols (GPIO, I2C, UART, etc.).

Next, open AndroidManifest.xml. You’ll see this line under the Application tag:

<uses-library android:name="com.google.android.things" />

The uses-library element makes this prebuilt library available to the app’s classpath at runtime.

Android Things does not include a user-facing launcher app. Any app intending to run on an embedded device must launch an Activity automatically on boot. In order to do that, it must attach an intent-filter containing the HOME category to the Activity that it’s going to launch. Since you’re not writing a production app, a simple intent-filter with the action MAIN and category LAUNCHER is enough to set which Activity you’ll launch from Android Studio by default.

Build and run the app. Currently, not much has changed, so let’s start communicating with the board.

Communication with Hardware

Android Things provides Peripheral I/O APIs to communicate with sensors and actuators. Peripheral I/O API is a part of the Things Support library which enables apps to use industry standard protocols and interfaces to connect to hardware peripherals.

Manage the Connection

You’ll use the PeripheralManager class to list and open available peripherals. To access any of the PeripheralManager APIs, you need to add USE_PERIPHERAL_IO permission. Open AndroidManifest.xml and add this line to the manifest tag:

<uses-permission android:name="com.google.android.things.permission.USE_PERIPHERAL_IO" />

Next, open the BoardManager class, in the board package. This is the class you’ll use to communicate with the board. Add the peripheralManager field to the class:

private val peripheralManager by lazy { PeripheralManager.getInstance() }

Here, you use the lazy delegate to initialize the field the first time it is accessed.

Next, add a function which will list available peripherals to the BoardManager class, using the getGpioList() method from PeripheralManager class:

fun listPeripherals(): List = peripheralManager.gpioList

Now, go to the MainActivity class and add the boardManager field:

private lateinit var boardManager: BoardManager

Next, replace the initialize() function with the following to create a BoardManager() instance:

private fun initialize() {
    boardManager = BoardManager()
}

Then, replace your onCreate function with the following:

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    initialize()
    Log.d(TAG, "Available GPIO: " + boardManager.listPeripherals())
  }

This calls the initialize() function and uses the boardManager.listPeripherals() call to list out the available peripherals in a log statement. Finally, build and run the app and look at the logcat. You’ll see your peripherals listed as below:

D/MainActivity: Available GPIO: [BCM10, BCM11, BCM12, BCM13, BCM14, BCM15, BCM16, BCM17, BCM18, BCM19, BCM2, BCM20, BCM21, BCM22, BCM23, BCM24, BCM25, BCM26, BCM27, BCM3, BCM4, BCM5, BCM6, BCM7, BCM8, BCM9]
Note: If you are using a Pico board instead of a Raspberry Pi these device labels will be different.

Read from an Input

In the BoardManager class, you’ll find uncommented, predefined, constants for Raspberry Pi board pin names. If you are using a Pico Pi, you will want to comment these and uncomment the NXP i.MX7D board ones. The BUTTON_PIN_NAME constant matches the A button on your Rainbow HAT.

First off, add a field for the button to the BoardManager class:

private lateinit var buttonGpio: Gpio

Second, paste the following initializeButton() function to the same class:

private fun initializeButton() {
    try {
      //1
      buttonGpio = peripheralManager.openGpio(BUTTON_PIN_NAME)
      buttonGpio.apply {
        //2
        setDirection(Gpio.DIRECTION_IN)
        //3
        setEdgeTriggerType(Gpio.EDGE_BOTH)
        //4
        setActiveType(Gpio.ACTIVE_LOW)
      }
    } catch (exception: IOException) {
      handleError(exception)
    }
}

Going through this step by step:

  1. First, you open a connection with the GPIO pin wired to the button. You do that by calling peripheralManager.openGpio(BUTTON_PIN_NAME), passing in the button pin name.
  2. General Purpose Input/Output (GPIO) pins provide a programmable interface to read the state of a binary input device (such as a push button switch) or control the on/off state of a binary output device (such as an LED). To configure GPIO as input, you call setDirection(Gpio.DIRECTION_IN) with the Gpio.DIRECTION_IN constant on the buttonGpio.
  3. In electronics, a signal edge is a transition of a digital signal either from low to high (0 to 1) or from high to low (1 to 0). A rising edge is the transition from low to high. That transition happens when you press the button on board. A falling edge is the high to low transition. That transition happens when you release the button. You are going to be interested in both events, so you are calling the setEdgeTriggerType(Gpio.EDGE_BOTH) function with the Gpio.EDGE_BOTH constant.
  4. Finally, you need to specify the active type. Since the button you are using defaults to a high voltage value (when the button is not pressed) you are setting this to Gpio.ACTIVE_LOW, so that this will return true when the input voltage is low (button is pressed).

Finally, add the initialize() function to your BoardManager class.

fun initialize() {
    initializeButton()
}

You’ll rely on this to initialize the board before usage.

Listen for Input State Changes

To receive edge trigger events you need to configure an edge callback. Add a buttonCallback field to your BoardManager class:

private lateinit var buttonCallback: GpioCallback

Next paste the this initializeButtonCallback() function to initialize buttonCallback:

private fun initializeButtonCallback() {
    buttonCallback = GpioCallback {
      Log.i("BoardManager", "button callback value is " + it.value)
      true
    }
}

This callback triggers every time an input transition occurs that matches the configured edge trigger type. It returns true to continue receiving future edge trigger events.

Now, replace your initializeButton() function with the following:

  private fun initializeButton() {
    // 1
    initializeButtonCallback()
    try {
      buttonGpio = peripheralManager.openGpio(BUTTON_PIN_NAME)
      buttonGpio.apply {
        setDirection(Gpio.DIRECTION_IN)
        setEdgeTriggerType(Gpio.EDGE_BOTH)
        setActiveType(Gpio.ACTIVE_LOW)
        // 2
        registerGpioCallback(buttonCallback)
      }
    } catch (exception: IOException) {
      handleError(exception)
    }
  }

This calls the initializeButtonCallback() method and registers the buttonCallback it created.

Next, add the clear() function to the BoardManager class:

fun clear() {
  buttonGpio.unregisterGpioCallback(buttonCallback)
  try {
    buttonGpio.close()
  } catch (exception: IOException) {
    handleError(exception)
  }
}

Here, you unregister the callback and close the button GPIO connection when the application is done. Now, paste in the following into your MainActivity‘s initialize() function:

boardManager.initialize()

This calls your BoardManager initialize method. Finally, add the following method to the same MainActivity

override fun onDestroy() {
  super.onDestroy()
  boardManager.clear()
}

This calls your boardManager‘s clear method to clean things up when the system destroys the activity. Build and run your app and press the A button. In your log file you will see it log true when you press the button and false when you release it.