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 4 of this article. Click here to view the first page.

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.

Write to an Output

Next you will turn the Blue LED on the Rainbow HAT ON and OFF on button taps based on the state of your A button.
First open your BoardManager class and add the following:

    private lateinit var blueLedGpio: Gpio

This defines your GPIO field for the blue LED light. Next, paste in the following method:

private fun initializeLedLights() {
  try {
    //1
    blueLedGpio = peripheralManager.openGpio(BLUE_LED_PIN_NAME)
    //2
    blueLedGpio.apply {
      setDirection(Gpio.DIRECTION_OUT_INITIALLY_LOW)
    }
  } catch (exception: IOException) {
    handleError(exception)
  }
}

This adds a initializeLedLights() function that does the following:

  1. You use the PeripheralManager to open a connection with the GPIO pin wired to the Blue LED.
  2. You configure pin as an output using setDirection(Gpio.DIRECTION_OUT_INITIALLY_LOW). This will also set the initial state of the pin to zero volts, which means LED light will be turned off initially.

Now, add a call to initializeLedLights in your initialize() function. Next, replace initializeButtonCallback() with the following:

private fun initializeButtonCallback() {
  buttonCallback = GpioCallback {
    try {
      //1
      val buttonValue = it.value
      //2
      blueLedGpio.value = buttonValue
    } catch (exception: IOException) {
      handleError(exception)
    }
    true
  }
}

This does the following:

  1. Reads the button state with the it.value property. The it field is the buttons GPIO instance.
  2. Sets the LEDs value with the blueLedGpio.value and set it to on if you press the button, and off if you release it.

Finally, replace your clear() function with the following:

fun clear() {
  buttonGpio.unregisterGpioCallback(buttonCallback)
  arrayOf(buttonGpio, blueLedGpio)
    .forEach {
      try {
        it.close()
      } catch (exception: IOException) {
        handleError(exception)
      }
    }
}

This cleans up your resources. Instead of closing GPIO connections individually, you add the blue LED GPIO and button GPIO to an array that you iterate over.

That’s it. Build and run the app. When you press the blue button, the LED light will be turned on. When you release it, it will be turned off.

Great job so far! But turning LED light ON and OFF isn’t that exciting, unless you’re a fish, so let’s connect your app with Firebase! :]