Android Things による LED 点灯 (Raspberry Pi 3)
[最終更新] (2019/06/03 00:34:36)
最近の投稿
注目の記事

概要

電子工作や製品のプロトタイピング (例『地球規模で遠隔操作できるブルドーザー』) で利用される Raspberry Pi 3 について、こちらのページで構築した環境で Android Things アプリケーションを開発できます。

本ページでは、簡単な例として LED を点灯させるアプリケーションを扱います。より実用的なアプリケーションを開発する際には GitHub (Android Things) のサンプルコードを参考にします。

関連する公式ドキュメント

Raspberry Pi 3 で Android を動かして Wi-Fi 接続

Raspberry Pi 3 に Android Things イメージを書き込み、Wi-Fi 設定を行う方法は環境によって異なります。公式ドキュメントにすべての場合について記載がありますが、特にここでは以下の場合を想定した例を記載します。

  • macOS から書き込む。
  • モバイル Wi-Fi ルータのためイーサネットケーブルが利用できない。
  • HDMI ディスプレイが手元にない。

必要なものは以下のとおりです。

  • 本体
    • Raspberry Pi 3
    • Raspberry Pi 3 電源用 USB ケーブル
  • 書き込み先
    • microSD カード (8 GB 以上)
    • microSD カードリーダー/ライター
  • 通信ケーブル
    • USB to TTL Serial Cable (3.3v、イーサネットケーブルが利用できる場合は不要です)

Android Things イメージのダウンロード

Android Things Console に Google アカウントでログインして「CREATE A PRODUCT」ボタンをクリックします。

Uploaded Image

情報を入力します。SOM (System on Module) type は Raspberry Pi 3 を選択します。また、OEM パーティションには Bundle とよばれるアプリケーション関連ファイル一式が格納されます。

Uploaded Image

登録した Product ページ内の「CREATE BUILD CONFIGURATION」をクリックすると、ページ内下部の Build configuration list に一行追加されます。

Uploaded Image

追加された行内の Download build をクリックすると zip ファイルのダウンロードが開始されます。少々時間がかかるため、待ちます。

Uploaded Image

SD カードへの書き込み

解凍

ダウンロードした zip ファイルを解凍します。unzip コマンドではエラーが出るため、The Unarchiver (Mac OS) を利用します。約 250 MB の zip ファイル myproduct_Raspberry Pi 3_0_OIR1.170720.015_userdebug_build.zip が解凍されて 4 GB 程の iot_rpi3.img になります。

dd コマンド

8 GB 以上の microSD カードを Mac に認識させた後、デバイスの識別番号を確認します。以下の例では disk3 が microSD カードです。

$ diskutil list
/dev/disk0
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:      GUID_partition_scheme                        *121.3 GB   disk0
   1:                        EFI EFI                     209.7 MB   disk0s1
   2:          Apple_CoreStorage                         120.5 GB   disk0s2
   3:                 Apple_Boot Recovery HD             650.0 MB   disk0s3
/dev/disk1
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:                  Apple_HFS Macintosh HD           *120.1 GB   disk1
                                 Logical Volume on disk0s2
                                 XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
                                 Unencrypted
/dev/disk2
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:     Apple_partition_scheme                        *20.0 MB    disk2
   1:        Apple_partition_map                         32.3 KB    disk2s1
   2:                  Apple_HFS Flash Player            20.0 MB    disk2s2
/dev/disk3
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:     FDisk_partition_scheme                        *7.9 GB     disk3
   1:             Windows_FAT_16 RECOVERY                2.2 GB     disk3s1
   2:                      Linux                         33.6 MB    disk3s5
   3:             Windows_FAT_32 boot                    66.1 MB    disk3s6
   4:                      Linux                         5.6 GB     disk3s7

アンマウントします。

$ diskutil unmountDisk /dev/disk3
Unmount of all volumes on disk3 was successful

以下のコマンドで img を書き込みます。時間がかかるため気長に待ちます。

$ sudo dd bs=1m if=iot_rpi3.img of=/dev/rdisk3 conv=sync
4352+0 records in
4352+0 records out
4563402752 bytes transferred in 452.560177 secs (10083527 bytes/sec)

書き込みが終わると Mac からは認識できないためポップアップが表示されますが、そのまま取り出して Raspberry Pi にセットします。

Uploaded Image

シリアルデバッグコンソールによる Raspberry Pi への接続

今回、モバイル Wi-Fi ルータを利用しており、有線 LAN で Raspberry Pi に接続できない場合を想定しているため、初期の Wi-Fi パスワード設定等を別の方法で行う必要があります。ここでは、公式ドキュメントにも記載のあるシリアルデバッグコンソールを利用します。接続のためのケーブルは Amazon 等で安価に入手できます。電気街が近場にある方は実際の店舗で購入するのもよさそうです。その際、TTL 電圧レベルは 5V ではなく 3.3V のものを選択することに注意します。

デバイスドライバのインストール

OS によって USB to TTL Serial Cable のデバイスドライバをインストールする必要があります。上記 Amazon リンクのケーブルは PL2303 です。Adafruit の商品ページにドライバのインストール方法が記載されています。例えば、macOS について以下のように記載されています。

  • こちらのページ にアクセスしてドライバをダウンロードする。
  • ドライバには Prolific ChipsetSiLabs CP210X Drivers の二種類あり、どちらか分からない場合は両方インストールしておけば問題ない。
  • Prolific Chipset については macOS のバージョンによってダウンロードすべきドライバのバージョンが異なるため注意する。

ただし、これらのドライバは OS のバージョンによってはシステムのクラッシュにつながります。特に Prolific Chipset のドライバは不具合報告がなされており、以下のコマンドでドライバを削除できます。

保存場所の確認

$ kextfind -b com.prolific.driver.PL2303
/Library/Extensions/ProlificUsbSerial.kext

適当なディレクトリに移動

$ sudo mv /Library/Extensions/ProlificUsbSerial.kext ~/Desktop/

PC 再起動

$ sudo reboot

Raspberry Pi と接続するピン

以下のページの画像と見比べて作業するなどし、接続するピンを間違えて Raspberry Pi を壊さないように注意します。

黒 → GND ピン、白 → TXD ピン、緑 → RXD ピンにそれぞれ接続します。赤 (5V) は接続しません

Uploaded Image

シリアル通信

デバイスドライバのインストールに成功していれば、USB ケーブルを PC に接続すると以下のコマンドで識別子を確認できます。以下の例では /dev/tty.usbserial と表示されています。

$ ls -l /dev/tty.*
crw-rw-rw-  1 root  wheel   18,   0  8 24 02:17 /dev/tty.Bluetooth-Incoming-Port
crw-rw-rw-  1 root  wheel   18,   2  8 24 02:17 /dev/tty.Bluetooth-Modem
crw-rw-rw-  1 root  wheel   18,   4  8 24 02:31 /dev/tty.usbserial

macOS の場合screen を利用して以下のコマンドで接続できます。何も表示されない場合はエンターキーを押してみます。

screen /dev/tty.usbserial 115200

以下のように表示されれば接続成功です。

rpi3:/ $

別ターミナルから以下のコマンドで pid を確認して終了できます。環境によってはドライバが合わず、ここで正常にデバイスを解放できない可能性があります。別のドライバをインストールするか、後述の Wi-Fi 設定を行ってしまい、シリアル通信は以降利用しないようにして回避します。

screen -ls
screen -S 1983 -X quit

Wi-Fi 認証情報の設定

以下のコマンドで Wi-Fi 認証情報を設定します。特殊な記号がパスワードに含まれる場合は passphrase64 を利用します

rpi3:/ $ am startservice -n com.google.wifisetup/.WifiSetupService -a WifiSetupService.Connect -e ssid 利用するSSID名 -e passphrase パスワード
Starting service: Intent { act=WifiSetupService.Connect cmp=com.google.wifisetup/.WifiSetupService (has extras) }

ログに Successfully connected to と記載されていることを確認します。途中で NTP に接続できたため時刻が現在時刻に変更されていることが確認できます。

rpi3:/ $ logcat -d | grep Wifi | tail
01-01 00:43:52.083   876   876 V WifiWatcher: SSID changed: "SSID_NAME"
01-01 00:43:52.083   876   893 I WifiConfigurator: Successfully connected to SSID_NAME
01-01 00:43:52.089   309   378 E WifiStateMachine: Did not find remoteAddress {192.168.179.1} in /proc/net/arp
01-01 00:43:52.112   309   378 E WifiVendorHal: getSupportedFeatureSet(l.856) failed {.code = ERROR_NOT_AVAILABLE, .description = }
01-01 00:43:52.122   309   378 D WifiNetworkAgent: NetworkAgent: Received signal strength thresholds: []
01-01 00:43:52.125   309   378 E WifiVendorHal: stopRssiMonitoring(l.2115) failed {.code = ERROR_NOT_AVAILABLE, .description = }
01-01 00:43:52.924   309   378 D WifiStateMachine: NETWORK_STATUS_UNWANTED_VALIDATION_FAILED
08-23 17:47:13.903   309   378 W AlarmManager: Unrecognized alarm listener com.android.server.wifi.WifiConfigStore$1@ad923b1
08-23 17:47:13.965   309   378 D WifiConfigStore: Writing to stores completed in 61 ms.
08-23 17:47:14.647   309   540 D WificondControl: Scan result ready event

時刻が同期されていることを確認します。

rpi3:/ $ date
Wed Aug 23 17:49:07 GMT 2017

シリアル通信中は ping コマンドを実行するために root 権限が必要です。8.8.8.8Google Public DNS です。

rpi3:/ $ su
rpi3:/ # ping 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=55 time=197 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=55 time=121 ms
64 bytes from 8.8.8.8: icmp_seq=3 ttl=55 time=120 ms

ip コマンド等で IP アドレスを確認しておきます。以下の例では 192.168.179.7 がポケット Wi-Fi ネットワーク内の IP です。

rpi3:/ $ ip a s
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: sit0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN group default qlen 1
    link/sit 0.0.0.0 brd 0.0.0.0
3: wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether b8:27:eb:79:1e:23 brd ff:ff:ff:ff:ff:ff
    inet 192.168.179.7/24 brd 192.168.179.255 scope global wlan0
       valid_lft forever preferred_lft forever
    inet6 fe80::ba27:ebff:fe79:1e23/64 scope link 
       valid_lft forever preferred_lft forever
4: eth0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc pfifo_fast state DOWN group default qlen 1000
    link/ether b8:27:eb:2c:4b:76 brd ff:ff:ff:ff:ff:ff

Wi-Fi 設定をリセットするためには以下のコマンドを実行します。

am startservice -n com.google.wifisetup/.WifiSetupService -a WifiSetupService.Reset

adb コマンドによる接続

Wi-Fi で無線接続できるようになれば、以降はシリアルデバイスコンソールを利用せずに adb コマンドを利用して Raspberry Pi に接続できます。

Android Studio で SDK と同封されてインストールされる adb (Android Debug Bridge) コマンドを利用すると Android 端末に shell で接続できます。「Preferences → Appearance & Behavior → System Settings → Android SDK → Android SDK Location」で SDK の場所を確認して PATH を通しておきます。

$ /Users/username/Library/Android/sdk/platform-tools/adb connect 192.168.179.7
connected to 192.168.179.7:5555

$ /Users/username/Library/Android/sdk/platform-tools/adb devices
List of devices attached
192.168.179.7:5555      device

$ /Users/username/Library/Android/sdk/platform-tools/adb shell

Android Studio で LED 点灯アプリケーションを開発

SDK tools および SDK のアップデート

Android アプリケーション hello world』にも記載の Android Studio をインストールします。その後、インストールされている「SDK tools」と「SDK Platforms」を SDK Manager でアップデートします

空プロジェクトを作成

新規プロジェクトを作成します。

Uploaded Image

アプリケーション名を入力します。

Uploaded Image

2017/08/19 現在のところ Android Things 専用の項目はなく、Phone and Tablet を選択します。

Uploaded Image

Empty Activity を選択します。

Uploaded Image

Activity Name は MainActivity のままでも問題ありませんが、ここでは Android Things のドキュメントで散見される HomeActivity に変更しています。また、UI を持たないアプリケーション開発を想定しているため Layout File 生成のチェックは外します。更に、モバイルアプリケーション開発で必要になる後方互換性は不要であり Homeactivity は AppCompatActivity ではなく Activity を直接継承すればよいため二つ目のチェックも外しておきます。

Uploaded Image

Android Things アプリ化するための設定を追加

2017/08/19 現在のところ Android Things 専用の項目はなく、Phone and Tablet で生成された内容を流用しているため、Android Things アプリ化するために以下の箇所を手動で設定する必要があります。

app/build.gradle

アプリケーションのルートディレクトリに存在する build.gradle ではなく app ディレクトリ直下の build.gradle です。Bintray / androidthings-supportlibrary で確認できる最新のバージョンを指定して追記します。外部 JAR に依存しているだけでありコンパイル時に APK ファイルに含める必要はないため compile ではなく provided で設定します。Gradle についてはこちらのページもご参照ください。

apply plugin: 'com.android.application'

android {
    ...
}

dependencies {
    ....

    provided 'com.google.android.things:androidthings:0.5-devpreview'  ←追記
}

app/src/main/AndroidManifest.xml

build.gradle で provided 設定した外部 JAR を実行時に参照するための classpath 設定を uses-library で行います。また、Android Things デバイスブート時に起動される Activity であることをインテントフィルタで設定しています。インテントについてはこちらのページもご参照ください。

...
<manifest ...>
    <application ...>

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

        <activity android:name=".HomeActivity">

            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>

            <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>

GPIO 制御処理の記述

汎用の入出力ピンである GPIO ピンを制御して LED を一定間隔で点灯させます

app/src/main/java/com/example/mycompany/myapp/HomeActivity.java

package com.example.mycompany.myapp;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;

import com.google.android.things.pio.Gpio;
import com.google.android.things.pio.PeripheralManagerService;

import java.io.IOException;

public class HomeActivity extends Activity {

    // ログ出力で使用するタグ名
    private static final String TAG = "HomeActivity";

    // LED の点滅間隔
    private static final int INTERVAL_BETWEEN_BLINKS_MS = 1000;

    // LED 用の出力ピン識別子
    // https://developer.android.com/things/hardware/raspberrypi-io.html
    private static final String LED_PIN_NAME = "BCM6";

    // main スレッドとは別のスレッド、およびそのスレッドで順番に実行するためのキューを有します。
    // https://developer.android.com/reference/android/os/Handler.html
    private Handler mHandler = new Handler();

    // GPIO を表現するオブジェクト
    private Gpio mLedGpio;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // 周辺機器との接続を管理するオブジェクト
        PeripheralManagerService service = new PeripheralManagerService();
        try {

            // LED 点灯用の GPIO ピンをオープン
            mLedGpio = service.openGpio(LED_PIN_NAME);

            // 出力ピンとして設定、電圧の初期状態は 0V
            mLedGpio.setDirection(Gpio.DIRECTION_OUT_INITIALLY_LOW);

            // LED 点灯処理を別スレッド実行用のキューに積みます。
            mHandler.post(mBlinkRunnable);
        } catch (IOException e) {
            Log.e(TAG, "Error on PeripheralIO API", e);
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();

        // 終了時は別スレッド実行用のキューを空にします。
        mHandler.removeCallbacks(mBlinkRunnable);

        // GPIO ピンを閉じます。
        if (mLedGpio != null) {
            try {
                mLedGpio.close();
            } catch (IOException e) {
                Log.e(TAG, "Error on PeripheralIO API", e);
            }
        }
    }

    // 別スレッドで実行する LED 点灯処理です。
    private Runnable mBlinkRunnable = new Runnable() {
        @Override
        public void run() {

            // 何らかの原因で GPIO が閉じられている場合はそのまま処理を終えます。
            if (mLedGpio == null) {
                return;
            }

            try {
                // 「点灯」と「消灯」をトグルします。
                mLedGpio.setValue(!mLedGpio.getValue());
                Log.d(TAG, mLedGpio.getValue() ? "ON" : "OFF");

                // 一定時間経過してから実行する設定で再びキューに積みます。
                mHandler.postDelayed(mBlinkRunnable, INTERVAL_BETWEEN_BLINKS_MS);
            } catch (IOException e) {
                Log.e(TAG, "Error on PeripheralIO API", e);
            }
        }
    };
}

アプリケーションの配信 (OTA アップデート)

この続きが気になる方は

Android Things による LED 点灯 (Raspberry Pi 3)

残り文字数は全体の約 13 %
tybot
100 円
関連ページ