Google's IoT platform has recently been updated with a couple of exciting new tools: Android Things and Weave. Android Things allows developers to use familiar Android dev tools, such as Android Studio, Android SDK and Google Play Services, to build hardware solutions for the IoT. Android Things can be installed on popular boards, such as Raspberry Pi 3 and Intel Edison, and comes with a Developer Preview of the SDK.

I'm totally new to the world of IoT, but I was quite intrigued by the announcement, so I decided to order myself one of the supported boards and tinker with the SDK. My choice fell on the Raspberry Pi 3 with the Rainbow HAT set of peripherals: it's officially supported by the Android Things project, and most of the samples inside the official GitHub repo play really well with this setup. Rainbow HAT is a fun little piece of hardware to program, here's just a subset of niceties it includes:

a set of multicolor LEDs

an alphanumeric display

three capacitive touch buttons

a piezo buzzer

temperature and pressure sensors and more

The device itself comes with a Python library and a number of samples, available on GitHub. I decided to grab the demo.py sample and translate it into an Android Things project. This exercise helped me understand how the SDK works and play with various device drivers. This article will guide you through the process of setting up an Android Things project and adding code to interact with various peripherals. But first, let's see the demo!

Ahoy Rainbow HAT!

Here's what the Rainbow HAT demo looks like:

We're doing a ton of fun stuff here:

playing a melody using the piezo buzzer

blinking RGB LEDs along with the melody

changing the text on the display in response to button touch events

That looks like a lot, so let's break this functionality down into pieces and see how the interaction with different hardware components works.

Adding the libraries

First of all, let's add all necessary libraries to our app/build.gradle file:

provided 'com.google.android.things:androidthings:0.1-devpreview' compile 'com.google.android.things.contrib:driver-button:0.1' compile 'com.google.android.things.contrib:driver-ht16k33:0.1' compile 'com.google.android.things.contrib:driver-pwmspeaker:0.1'

We'll need the main Android Things SDK dependency, and a number of driver modules for hardware components that we'll be using. Notice that we're declaring the SDK dependency as provided instead of the usual compile : this means that the SDK is a compile-only dependency and will not be packaged along with the app, at runtime our code will run against the version of the SDK that's present on the device.

Creating the RainbowHATDemoActivity

Cool thing about the Android Things SDK is that IoT apps follow the same structure as the usual Android apps. This means that with some modifications you can make existing apps run on embedded hardware, which sounds pretty cool. This also means that the entry point to our IoT app will be a good ol' Activity class, so let's create one (code examples are presented in Kotlin, which I hope you're familiar with):

class RainbowHATDemoActivity : Activity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // TODO } }

In AndroidManifest.xml, we'll first need to add a <uses-library> declaration for the Android Things SDK:

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

And then there's a specific <intent-filter> that we have to add to the RainbowHATDemoActivity element, which will instruct the system to start our Activity on device boot:

<activity android:name=".RainbowHATDemoActivity"> <!-- 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>

Now that the Activity is configured, let's create a set of helper classes that will serve as wrappers for the device driver components that we'll be using.

Buttons

Buttons class helps register drivers for Rainbow HAT's touch buttons. Here's the implementation:

class Buttons(private val buttonDrivers: List<ButtonInputDriver> = listOf( registerButtonDriver(Buttons.BUTTON_A_GPIO_PIN, KeyEvent.KEYCODE_A), registerButtonDriver(Buttons.BUTTON_B_GPIO_PIN, KeyEvent.KEYCODE_B), registerButtonDriver(Buttons.BUTTON_C_GPIO_PIN, KeyEvent.KEYCODE_C))) : Closeable { companion object { val BUTTON_A_GPIO_PIN = "BCM21" val BUTTON_B_GPIO_PIN = "BCM20" val BUTTON_C_GPIO_PIN = "BCM16" private fun registerButtonDriver(pin: String, keycode: Int): ButtonInputDriver { val driver = ButtonInputDriver(pin, Button.LogicState.PRESSED_WHEN_LOW, keycode) driver.register() return driver } } override fun close() { buttonDrivers.forEach(ButtonInputDriver::close) } }

We're using the ButtonInputDriver class to map each button, represented by its GPIO pin name, to a keycode value from the KeyEvent class, and register the driver with the system. Names for all Rainbow HAT pins can be found on the official Pinout page. We're also introducing the close() method, since it's important to unregister the drivers when the app doesn't need them anymore. After we instantiate Buttons inside the RainbowHATDemoActivity , we'll be able to listen to key presses inside the onKeyUp() method:

override fun onKeyUp(keyCode: Int, event: KeyEvent?) = when (keyCode) { // TODO }

Display

Next hardware component that we'll connect is the alphanumeric display:

class Display(private val display: AlphanumericDisplay = AlphanumericDisplay(Display.DISPLAY_I2C_BUS)) : Closeable { companion object { val DISPLAY_I2C_BUS = "I2C1" } init { display.setEnabled(true) display.clear() } fun displayMessage(message: String) { display.display(message) } override fun close() { display.clear() display.setEnabled(false) display.close() } }

We're using the AlphanumericDisplay class, which has very straightforward API and can easily display a String . Still, it's important to not forget to clear() the display after we're done, otherwise the text will remain displayed even after our app gets closed.

At this point, we can already publish messages to the display and react to button clicks, so let's put this functionality to use. First let's define some fancy messages inside RainbowHATDemoActivity :

companion object { val MESSAGES = mapOf( KeyEvent.KEYCODE_A to "AHOY", KeyEvent.KEYCODE_B to "YARR", KeyEvent.KEYCODE_C to "GROG") val DEFAULT_MESSAGE = "WJDK" }

In onCreate() we'll instantiate Buttons and Display and will render the default message:

override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) buttons = Buttons() display = Display() display.displayMessage(DEFAULT_MESSAGE) }

And back to the onKeyUp() callback, where we can change the text on the display based on the keyCode value:

override fun onKeyUp(keyCode: Int, event: KeyEvent?) = when (keyCode) { in MESSAGES.keys -> { display.displayMessage(MESSAGES[keyCode]!!) true } else -> super.onKeyUp(keyCode, event) }

That's pretty cool already! But let's continue with the second part of the demo and connect the LEDs and the buzzer.

Leds

Leds will hide away all the details of interacting with Rainbow HAT's LEDs, here's how it looks:

class Leds(peripheralManagerService: PeripheralManagerService = PeripheralManagerService()) : Closeable { companion object { val LED_RED_GPIO_PIN = "BCM6" val LED_GREEN_GPIO_PIN = "BCM19" val LED_BLUE_GPIO_PIN = "BCM26" val LED_RED = 0 val LED_GREEN = 1 val LED_BLUE = 2 val LEDS = arrayOf(LED_RED, LED_GREEN, LED_BLUE) } private val leds: List<Gpio> init { leds = listOf( openGpio(peripheralManagerService, LED_RED_GPIO_PIN), openGpio(peripheralManagerService, LED_GREEN_GPIO_PIN), openGpio(peripheralManagerService, LED_BLUE_GPIO_PIN)) } private fun openGpio(service: PeripheralManagerService, pin: String): Gpio { val led = service.openGpio(pin) led.setDirection(Gpio.DIRECTION_OUT_INITIALLY_LOW) return led } fun setLed(led: Int, on: Boolean) = with(leds[led]) { value = on } fun toggleLed(led: Int) = with(leds[led]) { value = !value } override fun close() { leds.forEach(Gpio::close) } }

Each LED is represented by an instance of Gpio , and we use PeripheralManagerService to obtain instances using GPIO pin names of each of the LEDs. Once we've got those, we set their initial state to Gpio.DIRECTION_OUT_INITIALLY_LOW . Then, turning an LED on or off is just a matter of setting its value to either true or false . Same as with the other devices, it's important to disconnect them after they're not needed anymore, therefore we introduce the close() method which closes each of the LEDs.

We're almost there, let's now connect the last piece of hardware - the buzzer.

Buzzer

Here's the implementation of Buzzer :

class Buzzer(private val speaker: Speaker = Speaker(Buzzer.SPEAKER_PWM_PIN), private val stopHandler: Handler = Handler()) : Closeable { companion object { val SPEAKER_PWM_PIN = "PWM1" } private var stopRunnable: Runnable? = null init { stopRunnable = Runnable { stop() } } fun play(frequency: Double) { speaker.play(frequency) } fun play(frequency: Double, duration: Double) { speaker.play(frequency) stopHandler.postDelayed(stopRunnable, duration.toLong()) } fun stop() { speaker.stop() } override fun close() { stopHandler.removeCallbacks(stopRunnable) speaker.stop() speaker.close() } }

We rely on the Speaker class to implement the functionality. Speaker has a play() method, which takes a frequency value and plays a sound at that frequency. The sound will play until stop() method is called. Since we'd like to have pauses between sounds in our demo, we'll introduce another version of play() that takes a duration parameter, and uses a Handler to post a stopRunnable . close() contains code for stopping and closing the speaker instance.

Putting everything together

Now that all our drivers are ready, we can play a melody at startup and blink the LEDs. First, let's instantiate remaining classes in onCreate() :

override fun onCreate(savedInstanceState: Bundle?) { ... buzzer = Buzzer() leds = Leds() ... playMelodyWithLeds() }

And here's the code for the playback functionality:

private fun playMelodyWithLeds() { playbackRunnable = Runnable { buzzer.play(NOTES[noteIndex].toDouble(), TIMES[timeIndex] * 0.8) leds.setLed(Leds.LEDS[ledIndex], on = true) leds.setLed(Leds.LEDS[prevIndex(ledIndex, Leds.LEDS.size)], on = false) if (noteIndex == NOTES.size - 1) { leds.setLed(Leds.LEDS[ledIndex], on = false) buzzer.close() display.displayMessage(DEFAULT_MESSAGE) } else { playHandler.postDelayed(playbackRunnable, TIMES[timeIndex].toLong()) timeIndex++ noteIndex++ ledIndex = nextIndex(ledIndex, Leds.LEDS.size) } } playHandler.post(playbackRunnable) }

NOTES and TIMES contain sound frequencies and playback durations respectively. We're using a Handler and looping until all the notes are played. Each iteration will also turn the LEDs on and off, one after the other. Job well done!

Last but not least, we have to close all resources we've been using when our Activity gets destroyed:

override fun onDestroy() { playHandler.removeCallbacks(playbackRunnable) arrayOf(leds, buttons, buzzer, display).forEach(Closeable::close) super.onDestroy() }

This code will make sure we're leaving the hardware in the same state as it was before our app was started.

The complete source code for this project is available on GitHub.

Conclusion

This article described the process of setting up an Android Things project and programming various hardware components to perform a number of different tasks. What's great is that we achieved it by using familiar Android development tools, thanks to the Android Things SDK and the driver libraries.

Playing with Rainbow HAT is super fun, and its wide range of peripherals provides a great opportunity for learning about different Android Things driver libraries. If you're thinking about ordering hardware to play with Android Things during the holidays - I can highly recommend the Raspberry Pi 3 + Rainbow HAT setup. Enjoy Android Things, and Happy New Year!

If you've enjoyed this article, please don't forget to share it with your network using one of the buttons below. If you've noticed any mistakes in the code or the terminology used in the article, please don't hesitate to add a comment - I highly appreciate your feedback!