436 lines
14 KiB
Markdown
436 lines
14 KiB
Markdown
+++
|
|
template = "article.html"
|
|
title = "Android Things: first look"
|
|
date = 2017-01-06T10:55:08+01:00
|
|
description = "An introduction to Android Things, Google's IoT platform that brings the Android ecosystem to embedded devices."
|
|
|
|
[taxonomies]
|
|
tags = ["android", "iot"]
|
|
+++
|
|
|
|
## What is Android Things?
|
|
|
|
Android Things is an alternative Android version, announced at Google I/O 2015,
|
|
and released as a first developer preview in December 2016. Its purpose is to
|
|
develop embedded IoT devices, with a known and widely documented Android
|
|
ecosystem basis.
|
|
|
|
It's currently running on three different boards: the Intel Edison, the NXP Pico
|
|
i.MX6UL, and the Raspberry Pi 3. Some higher-end boards are coming soon.
|
|
|
|
On the SDK side, Android Things comes with a specific support library to ease
|
|
low-level hardware usage. It consists in two parts: the Peripheral I/O API,
|
|
which supports GPIO, PWM, I2C, SPI and UART, and the User Driver API, which
|
|
allows a developer to write a hardware-specific, high-level driver, to ease
|
|
hardware reusability by injecting events into the Android framework. Other
|
|
applications can in turn use those events without having to interact with the
|
|
hardware directly.
|
|
|
|
There's a downside: the bundled Android is not as complete as the one you can
|
|
find on a phone. Most of the standard applications aren't installed (Calendar,
|
|
Phone…), and standard content providers are absent too (MediaProvider,
|
|
Dictionary…).
|
|
|
|
Android Things supports displays, with the default Android UI toolkit. However,
|
|
the display is a bit different from what you're used to seeing on an Android
|
|
device: there's no notification bar, navigation bar or anything, the running
|
|
application will use the full display. That is, if it uses it at all: displays
|
|
are purely optional.
|
|
|
|
<!--more-->
|
|
|
|
## Installing Android Things
|
|
|
|
Installation depends on the device you're targeting. Up-to-date, device-specific
|
|
instructions are available in [the official
|
|
documentation](https://developer.android.com/things/hardware/developer-kits.html).
|
|
|
|
Note that there is no emulator available (yet?), you'll need to install Android
|
|
Things on a real board.
|
|
|
|
The next steps of this post assume your local adb is connected to your device
|
|
(that is, `adb devices` is listing it as attached).
|
|
|
|
## Creating a new application
|
|
|
|
Android Things uses the same Activity, Service… lifecycles you're used to
|
|
seeing in any Android application. As so, creating an Android Things project is
|
|
really close to creating an Android one:
|
|
|
|
- create a blank project on Android Studio (selecting a form factor is
|
|
mandatory, and Android Studio doesn't support Things yet, keep Phone
|
|
and Tablet selected), without any activities
|
|
- in the `build.gradle`, remove all the dependencies and add the Things support
|
|
library:
|
|
|
|
```groovy
|
|
apply plugin: 'com.android.application'
|
|
|
|
android {
|
|
compileSdkVersion 24
|
|
buildToolsVersion "25.0.2"
|
|
defaultConfig {
|
|
applicationId "fr.enoent.mything"
|
|
minSdkVersion 24
|
|
targetSdkVersion 24
|
|
versionCode 1
|
|
versionName "1.0"
|
|
}
|
|
buildTypes {
|
|
release {
|
|
minifyEnabled false
|
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
|
}
|
|
}
|
|
}
|
|
|
|
dependencies {
|
|
provided 'com.google.android.things:androidthings:0.1-devpreview'
|
|
}
|
|
```
|
|
|
|
- add a reference to the Things support library in the `AndroidManifest.xml`:
|
|
|
|
```xml
|
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
|
package="fr.enoent.mything">
|
|
|
|
<application
|
|
android:allowBackup="true"
|
|
android:icon="@mipmap/ic_launcher"
|
|
android:label="@string/app_name">
|
|
|
|
<uses-library
|
|
android:name="com.google.android.things"/>
|
|
|
|
</application>
|
|
</manifest>
|
|
```
|
|
|
|
- the last step is to create an Activity: create a new blank, without layout,
|
|
non-support Activity from Android Studio. You'll also need to add a
|
|
Things-specific intent-filter to this Activity in the Manifest, so it will
|
|
start automatically on boot. Keep the Launcher intent-filter to make it easier
|
|
to start this Activity from Android Studio:
|
|
|
|
```xml
|
|
<?xml version="1.0" encoding="utf-8"?>
|
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
|
package="fr.enoent.mything">
|
|
|
|
<application
|
|
android:allowBackup="true"
|
|
android:icon="@mipmap/ic_launcher"
|
|
android:label="@string/app_name">
|
|
|
|
<uses-library
|
|
android:name="com.google.android.things"/>
|
|
|
|
<activity android:name=".MainActivity">
|
|
<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>
|
|
</manifest>
|
|
```
|
|
|
|
Now you have an Activity, which is supposed to start automatically on boot.
|
|
Let's check that by adding a log in the `onCreate` method:
|
|
|
|
*MainActivity.java*
|
|
|
|
```java
|
|
public class MainActivity extends Activity {
|
|
@Override
|
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
|
super.onCreate(savedInstanceState);
|
|
|
|
Log.d("MainActivity", "onCreate");
|
|
}
|
|
}
|
|
```
|
|
|
|
As you can see, it's perfectly standard Android code.
|
|
|
|
You can run it from Android Studio, and you should see the `onCreate` mention in
|
|
the logs.
|
|
|
|
## Lights, action!
|
|
|
|
I won't cover Android UI in this post, as it's something really standard you can
|
|
already find all over the web. Let's do a much more fun UI: a blinking LED to
|
|
indicate the application is running.
|
|
|
|
### Connecting the LED
|
|
|
|
I only have a Raspberry Pi 3, so I won't be able to cover the other boards for
|
|
this part. You can find the Pi pinout details
|
|
[here](https://developer.android.com/things/hardware/raspberrypi-io.html).
|
|
|
|
The circuit is dead simple: connect the LED's cathode to Pi's ground, a small
|
|
resistor in series with your LED's anode (Pi's GPIOs are 3v3), and the other
|
|
side of the resistor to the Pi's BCM6. You can use any pin labelled as GPIO in
|
|
the diagram.
|
|
|
|
### Pimp your 'droid
|
|
|
|
Time to go back to Android Studio. First step: listing the available GPIOs on
|
|
your board. There's a new class in the support library to access the GPIOs:
|
|
`PeripheralManagerService`.
|
|
|
|
*MainActivity.java*
|
|
|
|
```java
|
|
@Override
|
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
|
super.onCreate(savedInstanceState);
|
|
|
|
PeripheralManagerService service = new PeripheralManagerService();
|
|
Log.d("MainActivity", "Available GPIOs: " + service.getGpioList());
|
|
}
|
|
```
|
|
|
|
This code will list all the GPIOs we're allowed to use. On a Pi, hopefully
|
|
you'll find the BCM6 pin you connected the LED to. Here's the output on a Pi 3:
|
|
|
|
```
|
|
Available GPIOs: [BCM12, BCM13, BCM16, BCM17, BCM18, BCM19, BCM20, BCM21,
|
|
BCM22, BCM23, BCM24, BCM25, BCM26, BCM27, BCM4, BCM5, BCM6]
|
|
```
|
|
|
|
The next step is to initialize this GPIO. We'll use the
|
|
`PeripheralManagerService` once more to get a reference to it, then set it up to
|
|
`LOW` (0v) by default:
|
|
|
|
*MainActivity.java*
|
|
|
|
```java
|
|
private static final String GPIO_PIN_NAME_LED = "BCM6";
|
|
|
|
@Override
|
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
|
super.onCreate(savedInstanceState);
|
|
|
|
PeripheralManagerService service = new PeripheralManagerService();
|
|
|
|
try {
|
|
Gpio ledGpio = service.openGpio(GPIO_PIN_NAME_LED);
|
|
ledGpio.setDirection(Gpio.DIRECTION_OUT_INITIALLY_LOW);
|
|
} catch (IOException e) {
|
|
Log.e("MainActivity", "Error on PeripheralIO API", e);
|
|
}
|
|
}
|
|
```
|
|
|
|
Now, the only thing left to do is to toggle the GPIO value. This is a single
|
|
call: `ledGpio.setValue(!ledGpio.getValue());`. Toggling it every second comes
|
|
with a purely Android-oriented solution: a `Handler`, and a delayed
|
|
`Runnable`.
|
|
|
|
*MainActivity.java*
|
|
|
|
```java
|
|
public class MainActivity extends Activity {
|
|
private static final int INTERVAL_BETWEEN_BLINKS_MS = 1000;
|
|
private static final String GPIO_PIN_NAME_LED = "BCM6";
|
|
|
|
private Handler handler = new Handler();
|
|
private Gpio ledGpio;
|
|
|
|
private Runnable blinkRunnable = new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
// Exit if the GPIO is already closed
|
|
if (ledGpio == null) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
ledGpio.setValue(!ledGpio.getValue());
|
|
handler.postDelayed(blinkRunnable, INTERVAL_BETWEEN_BLINKS_MS);
|
|
} catch (IOException e) {
|
|
Log.e("MainActivity", "Error on PeripheralIO API", e);
|
|
}
|
|
}
|
|
};
|
|
|
|
@Override
|
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
|
super.onCreate(savedInstanceState);
|
|
|
|
PeripheralManagerService service = new PeripheralManagerService();
|
|
|
|
try {
|
|
ledGpio = service.openGpio(GPIO_PIN_NAME_LED);
|
|
ledGpio.setDirection(Gpio.DIRECTION_OUT_INITIALLY_LOW);
|
|
|
|
handler.post(blinkRunnable);
|
|
} catch (IOException e) {
|
|
Log.e("MainActivity", "Error on PeripheralIO API", e);
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
## User drivers
|
|
|
|
Google already ships some user-drivers, ready to use, in the form of Gradle
|
|
libraries. Let's take the Button driver as an example.
|
|
|
|
### Installation
|
|
|
|
It's a simple Gradle dependency to add:
|
|
`compile 'com.google.android.things.contrib:driver-button:0.1'`
|
|
|
|
### Usage
|
|
|
|
The button driver provides a simple class which you feed with the GPIO name to
|
|
use, and a callback:
|
|
|
|
*MainActivity.java*
|
|
|
|
```java
|
|
public class MainActivity extends Activity {
|
|
private static final String GPIO_PIN_NAME_BUTTON = "BCM6";
|
|
|
|
Button button;
|
|
|
|
@Override
|
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
|
super.onCreate(savedInstanceState);
|
|
Log.d("MainActivity", "onCreate");
|
|
|
|
try {
|
|
button = new Button(GPIO_PIN_NAME_BUTTON,
|
|
Button.LogicState.PRESSED_WHEN_HIGH
|
|
);
|
|
button.setOnButtonEventListener(new Button.OnButtonEventListener() {
|
|
@Override
|
|
public void onButtonEvent(Button button, boolean pressed) {
|
|
Log.d("MainActivity", "Button has been pressed!");
|
|
}
|
|
});
|
|
} catch (IOException e) {
|
|
Log.e("MainActivity", "Unable to configure the button", e);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void onDestroy() {
|
|
super.onDestroy();
|
|
|
|
try {
|
|
button.close();
|
|
} catch (IOException e) {
|
|
Log.e("MainActivity", "There's been an error while closing the button");
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
Notice that you'll have to close the GPIO when you leave your Activity
|
|
(`button.close()`).
|
|
|
|
And with those simple dozen lines of Java, you can use your hardware button
|
|
to trigger things in your application. The button driver also provides a way to
|
|
bind your hardware button to a software event, then any application can simply
|
|
listen to the software key event. This is an example binding the button to the
|
|
key `A`:
|
|
|
|
*MainActivity.java*
|
|
|
|
```java
|
|
public class MainActivity extends Activity {
|
|
private static final String GPIO_PIN_NAME_BUTTON = "BCM6";
|
|
|
|
ButtonInputDriver inputDriver;
|
|
|
|
@Override
|
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
|
super.onCreate(savedInstanceState);
|
|
|
|
try {
|
|
inputDriver = new ButtonInputDriver(GPIO_PIN_NAME_BUTTON,
|
|
Button.LogicState.PRESSED_WHEN_HIGH,
|
|
KeyEvent.KEYCODE_A // the keycode to send
|
|
);
|
|
inputDriver.register();
|
|
} catch (IOException e) {
|
|
Log.e("MainActivity", "Error while binding button", e);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void onDestroy() {
|
|
super.onDestroy();
|
|
|
|
inputDriver.unregister();
|
|
try {
|
|
inputDriver.close();
|
|
} catch (IOException e) {
|
|
Log.e("MainActivity", "Error while unregistering the button driver", e);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
|
if (keyCode == KeyEvent.KEYCODE_A) {
|
|
Log.d("MainActivity", "Button has been pressed");
|
|
return true;
|
|
}
|
|
return super.onKeyDown(keyCode, event);
|
|
}
|
|
}
|
|
```
|
|
|
|
## Conclusion
|
|
|
|
Even if it may be early to conclude anything from this preview, I have mixed
|
|
feelings regarding the state of Android Things.
|
|
|
|
While having the whole UI
|
|
toolkit available is great for industry-oriented hardware, I don't really see
|
|
the point of it in consumer products. Most of the connected devices uses LEDs or
|
|
a screen with minimal information. From my point of view, a connected device
|
|
should be set up then forgot, and work without requesting anything from me past
|
|
the initial configuration. I don't want to press a button to turn the lights on.
|
|
I don't want to take my phone, unlock it, start the relevant application, then
|
|
press a button to turn the lights on. I want the lights to turn on when I need
|
|
it. Using a motion sensor, location tracking from my phone, whatever. As long as
|
|
I have to interact with the lights, they are not smarter than my good old light
|
|
bulbs with their big button I can use even in the dark with both hands full.
|
|
|
|
The first thing I expected from this preview was Weave integration. Which is
|
|
completely absent (I guess it will come eventually). You can start making a
|
|
connected device powered by Google technologies, but *you can't use the standard
|
|
Google tries pushing forward to control it*. You'll have to write your own
|
|
control interface (probably a REST API, which means integrating a web-server in
|
|
your Android Things application).
|
|
|
|
Having the ability to work on the IDE I'm used to, with an SDK I already know,
|
|
and being able to reuse the ton of existing Java libraries is really great
|
|
though. It makes the entry barrier much lower than usual embedded development.
|
|
That is, when you have someone else to do the hardware part for you.
|
|
|
|
I know computing power and storage comes nearly free nowadays, but being used to
|
|
use AVRs, MSPs…, I can't help thinking a Raspberry Pi 3 is totally
|
|
overkill for that kind of use. I just used a 1.2 GHz, quad-core SoC and 600 MB
|
|
of storage *to blink an LED*. Most of those devices will only be
|
|
remote-controlled and will send data for analysis anyway. An ESP8266 is much
|
|
smaller, uses less power, comes with built-in Wi-Fi, for a couple bucks.
|
|
|
|
In the end, I think Google shouldn't have used Android as a basis for that kind
|
|
of IoT platform. While it surely looks attractive, it comes with multiple
|
|
drawbacks inherent to the idea itself. A Google-branded toolchain for something
|
|
like the ESP8266 with built-in Weave support and first-class Firebase/Google
|
|
Cloud client libraries would have been a much better approach.
|