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!

Version

  • Kotlin 1.3, Android 8.1, Android Studio 3

Android Things is an Android-based operating system which Google manages that enables you to build hardware devices for the Internet of Things (IoT). In the broadest sense, the Internet of Things is a network of connected “things”. This includes everything from microwave ovens to the jet engine of an airplane. If a device has a switch or measures something, chances are you can adapt it, and expose it over an IoT network.

In this tutorial you’ll use Android Things to solve a real life problem by creating an app that simulates a doorbell. In the process, you’ll learn:

  • What Android Things is and how it works.
  • How hardware and software communicate.
  • How to integrate Firebase with Android Things.

Note: This tutorial assumes that you have solid knowledge of Android development. If you’re completely new to Android development, please check out our Beginning Android Development with Kotlin series first. Other things that are used in this tutorial are the ADB, Firebase and LiveData. If you’re not familiar with these concepts please check out our tutorials for them as well.

This tutorial focuses on the using the Raspberry Pi. To complete the exercises, you will need the following hardware:

You will also need to have a Google account and access to a Wi-Fi network that allows for device-to-device communication.

Alternatively, you can complete this tutorial using the Pico Pi based Starter Kit for Android Things. If you are using this board/kit, you have the option of either connecting to your board via a Wi-Fi network/ethernet connection or directly with the USB cord. If you use the USB cord you can skip the connect to the Internet steps of this tutorial. Also, you will need to follow the instructions that come with the kit to assemble it before starting this tutorial.

Developing for Android Things

Developing for Android Things is similar to developing mobile Android apps, but there are some key differences. It consists of these four things:

  1. Hardware – hardware is based on System-on-Module (SOM) design. SOM is a fully integrated component with a CPU, memory, Wi-Fi, Bluetooth and flash storage that is ready to use. You can think of using SOM as using libraries when writing software. Google certified the SOM, and provided a board support package that includes the kernel, drivers and libraries to support Android. With the Android Things 1.0 release, Google announced support for SOMs based on designs from NXP, Qualcomm and MediaTek, certifying them for production use.
  2. Android for IoT – Android Things is an optimized version of Android that can run with lower compute and storage requirements. Security features are built-in and ON by default. A fundamental part of security are timely, Over the Air (OTA), software updates. This means the device only needs network access with Wi-Fi or Ethernet to get updates, such as stability fixes and security patches which Google provides. Automatic updates are enabled for all devices by default. Because Android Things is optimized for embedded devices, there are fourteen sets of APIs that are currently not supported, including ones relating to telephony.
  3. Apps and services – with Android Things you can build apps using the rich framework the Android SDK provides, and Google Play Services. You can also easily integrate your apps with popular Google services like Firebase, which you’ll see later, Tensorflow or ML Kit. You can do this by using existing Android client libraries. Since Android Things is Android, you can use all of its languages and tools right out of the box. In this tutorial, you’ll be using Android Studio and Kotlin.
  4. Android Things console – management console is where you control what exactly goes into your devices. It’s where you get the access to the board support package that Google and board vendors are supporting, where you create full product builds with all of your apps and services included and where you manage releases and update channels over time.

Getting Started

While there are a number of different supported boards, this tutorial focuses on using Raspberry Pi 3 and, to a lesser extent, the Pico Pi. Using one of these two boards allows you to use a peripheral HAT(hardware on top) board with a Raspberry Pi standard pinout. This avoids the need to mess around with soldering, bare wires and breadboards that can make it easy to short out or cause smoke/fire to appear and ruin your Raspberry or Pico Pi. While some makers would consider smoke and fire to be a right of IOT passage, for this tutorial you will simply plug the hat into the Pi.

Connect the Hardware

Before you start, you need to assemble the hardware together.

    1. Plug the Rainbow HAT in to your Raspberry Pi. Make sure the pins are lined up and not off by one row.

Raspberry Pi with Rainbow HAT attached

    1. Connect your Raspberry Pi to the power supply. Use a separate power supply, do not try to run it off your computer because that’s not recommended for Raspberry Pi 3’s. The power supply output you’re using should be rated at 5V 2.0A, at least. Check the small print on the power supply and make sure you’re using one with adequate specs.

Install Android Things

In order to flash Android Things to the development board, you’ll use the Android Things Setup Utility tool. Most Android Things boards, including the Pico Pi, can be flashed by plugging them directly into the computer via USB. The Raspberry Pi requires you to flash an SD card. The steps stay the same, but you will need to mount an SD card.

    1. First, go to the Android Things console.

    1. Next, click on the hamburger menu at the top-left corner and select Tools from the menu.

    1. Now, click on the Download button to download the Setup Utility tool.

    1. Finally, unzip the downloaded file and you’ll see three scripts for flashing Android Things images, for Linux, Mac OS and Windows. Choose the one appropriate to your environment.

    1. In this example, we used the Mac script. Open the Terminal and navigate to the folder with the scripts. If you are using a Raspberry Pi, mount your SD card. If your using a Pico Pi connect it to your USB port. Now, run the script. For example, on a mac it will be:
      sudo ~/android-things-setup-utility/android-things-setup-utility-macos

      The script will present you with the following menu:

    1. Select option one to install Android Things by typing 1 and pressing Enter.

    1. Select the hardware you are using.

    1. Now you’ll see an option that allows you to choose a custom image which you can create in the Android Things console. A custom image is more flexible and is for more experienced developers. For this tutorial, you’ll use the default image. Select option one to download the default image.
    2. Once you download the image, you’ll see the following screen. Make sure you plugged in your SD card, into the computer and then press Enter.

Note: These prompts will be slightly different if you are using a Pico Pi.
    1. Select the drive by using arrow keys and press Enter. Type y to confirm your selection and press Enter.

    1. Once it’s completed, it’ll say Setup Complete. Type n in the terminal to select that you don’t want to set up the Wi-Fi. You’ll connect the board to the Wi-Fi later. You can safely eject the SD card from the computer now.

Connect to the Internet

If you are using a Raspberry Pi you will need an HDMI monitor, HDMI cable and a mouse. If you are using a Pico Pi as part the Android Things kit make sure you follow the instructions that came with it to attach the included screen.

    1. If you are using a Raspberry Pi insert your SD card into it and connect your external monitor to the board via the HDMI cable. Now, connect your mouse to a USB port and make sure the board is connected to the power supply
    2. If you are using a Pico Pi, power it up via a USB-C cable.
    3. You’ll see this screen. Click Continue using your mouse on a Raspberry Pi or touch screen if using the Pico Pi Android Things Kit

    1. You’ll see this screen:

    1. Download Android Things Toolkit from Google Play Store to your mobile phone. This is a companion app for mobile devices that can help you get started quickly. Open the app after you install it.

    1. Set up your account and accept Terms of Use. After you do that, you’ll see this screen:

    1. Tap on Pair your device.

    1. Tap YES.

    1. Tap NEXT.

    1. Tap SEARCH.

    1. Enter the device code that you see on your monitor. Tap PAIR after that.
    2. Select your Wi-Fi network from the list. Your computer and the board need to be on the same network.
    3. Enter the Wi-Fi password and press CONNECT. Once you connect it, you’ll see this screen:

    1. You can test the Rainbow HAT peripheral here, but for some users these tests don’t always run from the app. For now, tap FINISH.

    1. A dialog window will pop up. Tap NOT NOW.
    2. You’ll see this screen on your monitor:

  1. Under the Network section there’s a network name that you’re connected to, and the IP address. Remember that IP address.
  2. Open the terminal and enter the following command:
    adb connect ip-address

    Replace ip-address text with your ip address and press Enter. You should get the message that you’ve connected successfully.

Congratulations! You have connected to your board. :]

Before you move on to the code, it is a good idea to test your Rainbow HAT to make sure that it’s up and running. The screen on your device should look like this:

Click or tap on the … More option.

Next, select Rainbow Hat

Finally click or tap the Test button.

If you conneted your HAT correctly, it should light up and flash “hello world”. If you’re using a Raspberri Pi, you can optionally unplug the HDMI cable from the monitor since you’re not going to use it for the rest of the tutorial. Your development board is ready to use. :]

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.

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! :]

Connect with Firebase

You’ll create a simple infrastructure that will simulate a doorbell. To do that, you need three things:

  1. Doorbell – a button tap on a board will simulate a doorbell.
  2. Database – ring events will be sent to the Firebase database.
  3. Mobile app – ring events will be displayed on your mobile app. Using this app, you’ll control whether you’ll let the person at the door in or not.

This is how it looks graphically:

Note: The logic for saving ring events to the Firebase is implemented for you. You need to create a project in Firebase, ensure that a database has been created for it and that it has public permissions for read and write. Once you have done that, create an app for it and download the google-services.json file for the app. If you are unfamiliar with how to do this, refer on our Firebase tutorial, which is linked at the beginning of this tutorial.

Capture the Ring Event

First, add RingEvent singleton class to the bottom of the BoardManager class by pasting the following:

object RingEvent

Next, add a ringEvent field of MutableLiveData type:

private val ringEvent = MutableLiveData()

Now, add a function which will expose ring events:

fun listenForRingEvents() = ringEvent

Next, replace the initializeButtonCallback() function with the following:

private fun initializeButtonCallback() {
  buttonCallback = GpioCallback {
    try {
      val buttonValue = it.value
      blueLedGpio.value = buttonValue
      // call the database
      ringEvent.postValue(RingEvent)
    } catch (exception: IOException) {
      handleError(exception)
    }
    true
  }
}

When you press a button on the board, a ring event will be sent to the database.

Then, in your MainActivity class paste in the following code:

private fun onRingEvent(ringEvent: RingEvent?) = ringRepository.saveRingEvent()

This function will handle ring events, by notifying the ringRepository.

Now, replace your MainActivity‘s initialize() with the following:

private fun initialize() {
  boardManager = BoardManager()
  // initialize your ring repository
  ringRepository = RingRepository()
  boardManager.initialize()
  // listen for ring events
  boardManager
   .listenForRingEvents()
   .observe(this, Observer { ringEvent ->
     onRingEvent(ringEvent)
    })
}

This listens for ring events coming from your board and triggers your onRingEvent function.

Finally, add the Google Play Services plug-in to your app level build.gradle.

apply plugin: 'com.google.gms.google-services'

Notify the User

In your materials there is also a knockknockmobile project. Open it in Android Studio and copy the google-services.json file that you have in the knockknock project to the knockknockmobile project.
Open the google-services.json file and search for the package_name attribute and replace its value with com.raywenderlich.knockknockmobile package name.

Build and run the mobile app on your mobile device. You’ll see this screen:

Build and run your Android Things app on the board. Tap on a button and check the Firebase database data. You should see ring event there:

Your mobile app will also change and you should now see the following screen:

By tapping the YES or NO buttons you will decide whether to let the person in or not. The answer is sent to Firebase.

Handle Response

Right now your board doesn’t do anything with the response, but you’re going to fix that!

To begin, paste the following into your BoardManager class:

private lateinit var redLedGpio: Gpio
private lateinit var greenLedGpio: Gpio

This adds the GPIO fields for the red and green LEDs. Next paste the following into the same class:

fun turnRedLedLightOn() {
  redLedGpio.value = true
}

fun turnRedLedLightOff() {
  redLedGpio.value = false
}

fun turnGreenLedLightOn() {
  greenLedGpio.value = true
}

fun turnGreenLedLightOff() {
  greenLedGpio.value = false
}

This adds methods for turning red and green LED lights ON and OFF. Now, add the following to your MainActivity:

private fun turnLightsOffAfterDelay() {
  Handler().postDelayed({
    boardManager.turnGreenLedLightOff()
    boardManager.turnRedLedLightOff()
  }, LIGHT_OFF_DELAY_MILLIS)
}

This adds a turnLightsOffAfterDelay() function which turns LED lights off after a small delay. Next, paste the following in the same class.

private fun onRingResponseReceived(dataSnapshot: DataSnapshot?) {
  //1
  val unlockDoor = dataSnapshot?.value as Boolean
  //2
  if (unlockDoor) 
    boardManager.turnGreenLedLightOn() 
  else 
    boardManager.turnRedLedLightOn()
  //3
  turnLightsOffAfterDelay()
}

This adds a onRingResponseReceived(dataSnapshot: DataSnapshot?) function which you call on every ring response event.

Going through it:

  1. When you receive a response boolean it indicates whether to unlock the door or not. You get this boolean by using dataSnapshot?.value as Boolean
  2. If you should unlock the door, you’ll turn on the green LED light on the board, otherwise you turn on the red LED light.
  3. This function turns the LED light off after 3 seconds.

Now, replace the initialize function of your MainActivity with:

private fun initialize() {
  boardManager = BoardManager()
  ringRepository = RingRepository()
  boardManager.initialize()
  boardManager
    .listenForRingEvents()
    .observe(this, Observer { ringEvent ->
      onRingEvent(ringEvent)
    })
  // ringRepository listener
  ringRepository
    .listenForRingResponseEvents()
    .observe(this, Observer { dataSnapshot ->
      onRingResponseReceived(dataSnapshot)
    })
}

This adds an observer to your ringRepository to observe ring response events. Now there are two more things you will need to do to button this up.

First, replace initializeLedLights with the following in your BoardManager class:

private fun initializeLedLights() {
  try {
     // initialize red LEDs
    redLedGpio = peripheralManager.openGpio(RED_LED_PIN_NAME)
    redLedGpio.apply { setDirection(Gpio.DIRECTION_OUT_INITIALLY_LOW) }

    // initialize green LEDs
    greenLedGpio = peripheralManager.openGpio(GREEN_LED_PIN_NAME)
    greenLedGpio.apply { setDirection(Gpio.DIRECTION_OUT_INITIALLY_LOW) }
  
    blueLedGpio = peripheralManager.openGpio(BLUE_LED_PIN_NAME)
    blueLedGpio.apply { setDirection(Gpio.DIRECTION_OUT_INITIALLY_LOW) }
  } catch (exception: IOException) {
    handleError(exception)
  }
}

This initializes the red and green LED lights.

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

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

This updates your array with the red and green LED GPIOs so that they are closed properly.

Build and run the app. Press the button on the board and use mobile app to either let the person in or deny them access. The green LED will light up if you let them in and the red one will light up if you deny access. You can find the final project in the downloaded materials.

Where to Go From Here?

As an Android developer, Android Things is a great way to begin to explore the world of the IOT and hardware. While you are exploring the platform for your own projects, this list is a great resource with additional tutorials, drivers and example projects. You can also visit this site to play around with an extensive list of resources from Google, or visit these interesting projects on hackster.io.

If you are new to electronics, Adafuit has a number or tutorials ranging from electronics basics to intermediate and advanced topics.

I hope you enjoyed this tutorial. If you have any questions or comments, please join the forum discussion below!

Contributors

Comments