Android Things + Xamarin.Forms + Google Cloud Messaging で LED をチカチカさせる

## はじめに

この記事は、Raspbery Pi に FCM から Push 通知し、LED を点灯する Android Things のサンプルを Xamarin を使って作成したときのメモです。

年末で比較的時間が取れる状況だったので、まったくやったことがないハードウェア周りのことにトライしてみました。（初めての電子工作でした…）

## 準備したもの

- ハードウェア
    - Raspbery Pi Model 3
        - Android Things をインストール済み
            - Raspbery Pi に Android Things をインストールする手順は、[ここ](https://ht-deko.com/RaspberryPi/android_things.html)を参考にしました
    - ブレッドボード（１個）
    - LED（１個）
    - 抵抗器（１個）
    - ジャンパーワイヤー（２本、片方だけメスのもの）
- ソフトウェア
    - Visual Studio 2017
        - Xamarin.Forms で実装
        - [Xamarin.Android.Things](https://www.nuget.org/packages/Xamarin.Android.Things/)
            - Android Things を Native Binding した NuGet パッケージ
        - [Firebase Push Notification Plugin for Xamarin iOS and Android](https://github.com/CrossGeeks/FirebasePushNotificationPlugin)
            - FCM 関連のライブラリ
    - [Fiddler](https://www.telerik.com/fiddler)
        - FCM のメッセージ送信用に利用

## サンプルコード

今回のサンプルを GitHub に置いておきました。

- [Xamarin-AndroidThings-LED-FCM-Sample](https://github.com/tafuji/Xamarin-AndroidThings-LED-FCM-Sample)

## 配線図

ハードウェアの配線図は以下の通りです。

![LED-Diagram](https://gist.githubusercontent.com/tafuji/34ca7c655384ed9d7ee62badb936ca08/raw/0f99ecce388ca448d41f358ca818b128f7f35191/LED-Wiring-Diagram.png)

この図は、[Fritzing](http://fritzing.org/home/) を使って作成しました。

## アプリケーションの作成

### 0. Solution 構成

サンプルのソリューション構成を以下のスクリーンショットに示します。

![Solution](https://gist.githubusercontent.com/tafuji/34ca7c655384ed9d7ee62badb936ca08/raw/577708c309691533705fd174191986b5a4d97d7c/Solution.png)

- Push.Sample プロジェクト

    |主なファイル|概要|
    |----|----|
    |IBlinkLedService.cs|LED を点灯させるためのインタフェース|
    |ILogService.cs|簡易ログ出力用のインターフェース|
    |MainPage.xaml.cs|FCM メッセージ受信後 LED を点灯する処理を呼び出す|

- Push.Sample.Android プロジェクト

    |主なファイル|概要|
    |----|----|
    |AndroidThingsApplication.cs|FCM ライブラリの初期化処理を実装|
    |MainActivity.cs|FCM ライブラリの初期化処理を実装|
    |BlinkLedService.cs|LED 点灯処理が実装されたクラス|
    |LogService.cs|簡易ログ出力用のクラス|
    |google-services.json|FCM を利用するのに必要な設定値が定義されたファイル。Firebase Console からダウンロードする|

以下のステップでアプリを作りました。

1. 画面表示部分を作る
1. FCM を受信する部分を作る
1. LED を点灯させる部分を作る

### 1. 画面表示部分を作る

最初に、Android Things で Xamarin.Forms が動作するかを確認するためにシンプルに画面表示を行う部分を作成しました。

#### 1.1 NuGet パッケージの追加

Xamarin.Android のプロジェクトに [```Xamarin.Android.Things```](https://www.nuget.org/packages/Xamarin.Android.Things/) の NuGet パッケージを追加します。Android Things そのものが、Developer Preview なので、NuGet も prerelease 版にチェックを入れて、検索を行う必要があります。

![AndroidThingsNuget](https://gist.githubusercontent.com/tafuji/34ca7c655384ed9d7ee62badb936ca08/raw/c7a271a2b5b7103f55669eb3242ae12042069117/AndroidThingsNuget.png)

#### 1.2 Manifest ファイルの修正

AndroidManifest.xml ファイルに Android Things のライブラリ利用に関する設定を追加します。

```xml
<application android:label="Push.Sample.Android">
    <!-- その他の設定... -->
    <uses-library android:name="com.google.android.things" />
</application>
```

#### 1.3 デバイス起動時にアプリを起動する設定を追加

Android Things のデバイスを起動したときに、アプリケーションが自動的に起動する設定を MainActivity に追加します。

```csharp
[IntentFilter(new[] { Intent.ActionMain }, Categories = new[] { Intent.CategoryLauncher })]
[IntentFilter(new[] { Intent.ActionMain }, Categories = new[] { "android.intent.category.IOT_LAUNCHER", "android.intent.category.DEFAULT" })]
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
    // 中略…
}
```

#### 1.4 Deploy

adb でデバイスに接続し、Deploy します。

```cmd
adb connect [IP adress of Android Things device]
adb install [your apk file]
```

#### 1.5 実行！

Deploy が完了したら、```adb reboot``` コマンドでデバイスを再起動します。

起動までしばらく待ちます…

![LaunchAndroidThings](https://gist.githubusercontent.com/tafuji/34ca7c655384ed9d7ee62badb936ca08/raw/68706d1bce9c796291d103673c95221cdbbd2116/LaunchAndroidThingsApps.png)

アプリの起動成功！画面はあっさりしていますが、これで Android Things 上で Xamarin.Forms が動作することが確認できました。

![WellcomeScreenShot](https://gist.githubusercontent.com/tafuji/34ca7c655384ed9d7ee62badb936ca08/raw/b7e2243d366221175513e71c90abed7fae47193e/WelcomeScreenshot.png)

### 2. FCM を受信する部分を作る

Xamarin.Forms で FCM のメッセージを受信するために、以下の NuGet パッケージを Push.Sample.Android プロジェクトに追加しました。

- [Firebase Push Notification Plugin for Xamarin iOS and Android](https://github.com/CrossGeeks/FirebasePushNotificationPlugin)

なお、このライブラリの使用に際して、以下のバージョンのライブラリを利用しています。

|ライブラリ名|バージョン|
|----|----|
|Xamarin.Firebase.Messaging|42.1021.1|
|Xamarin.GooglePlayServices.Basement|42.1021.1|
|Xamarin.GooglePlayServices.Tasks|42.1021.1|

#### 2.1 Firebase プロジェクトの準備

[ここ](https://github.com/CrossGeeks/FirebasePushNotificationPlugin/blob/master/docs/FirebaseSetup.md)に記載されている手順に従って、Firebase のプロジェクトの作成を行い、google-services.json ファイルをプロジェクトに追加します。

![Google-Service-Json](https://gist.githubusercontent.com/tafuji/34ca7c655384ed9d7ee62badb936ca08/raw/92092c5d77d6b667ebf61498d72539a790bc4788/google-service-json.png)

#### 2.2 Android Manifest の設定

[ここ](https://github.com/CrossGeeks/FirebasePushNotificationPlugin/blob/master/docs/GettingStarted.md)の手順に従って、Android Manifest ファイルの設定、google-service.json ファイルの設定を行います。ネイティブ（Push.Sample.Android）プロジェクトで以下の作業を行いました。

- ```<application>``` 要素に以下の ```<receiver>``` の設定を追加

    ```xml
    <application android:label="Push.Sample.Android">
        <receiver android:name="com.google.firebase.iid.FirebaseInstanceIdInternalReceiver" android:exported="false" />
        <receiver android:name="com.google.firebase.iid.FirebaseInstanceIdReceiver" android:exported="true" android:permission="com.google.android.c2dm.permission.SEND">
            <intent-filter>
                <action android:name="com.google.android.c2dm.intent.RECEIVE" />
                <action android:name="com.google.android.c2dm.intent.REGISTRATION" />
                <category android:name="${applicationId}" />
            </intent-filter>
        </receiver>
        <!-- その他の設定... -->
    </application>
    ```

- ```<application>``` 要素に 以下のPsermission の設定の追加

    ```xml
    <uses-permission android:name="android.permission.INTERNET" />
    ```
- google-service.json ファイルのビルドアクションの設定

![Google-Service-Json-Build-Action](https://gist.githubusercontent.com/tafuji/34ca7c655384ed9d7ee62badb936ca08/raw/b0f1f46b4d189c70b044ef7d2fbe0a8ba72e4bab/GoogleServiceJson-BuildAction.png)

#### 2.3 FCM の初期化処理

[ここ](https://github.com/CrossGeeks/FirebasePushNotificationPlugin/blob/master/docs/GettingStarted.md#android-initialization)の手順に従って、初期化処理を実装します。ネイティブ（Push.Sample.Android）プロジェクトで作業を行いました。

- カスタム Application クラス（AndroidThingsApplication.cs）

```csharp
[Application]
public class AndroidThingsApplication : Application
{
    public AndroidThingsApplication(IntPtr handle, JniHandleOwnership transer) : base(handle, transer)
    {
    }

    public override void OnCreate()
    {
        base.OnCreate();
        #if DEBUG
        FirebasePushNotificationManager.Initialize(this, true);
        #else
        FirebasePushNotificationManager.Initialize(this, false);
        #endif
    }
}
```

- MainActivity での初期化処理（MainActivity.cs）

```csharp
[Activity(Label = "Push.Sample", Icon = "@drawable/icon", Theme = "@style/MainTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
[IntentFilter(new[] { Intent.ActionMain }, Categories = new[] { Intent.CategoryLauncher })]
[IntentFilter(new[] { Intent.ActionMain }, Categories = new[] { "android.intent.category.IOT_LAUNCHER", "android.intent.category.DEFAULT" })]
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
    protected override void OnCreate(Bundle bundle)
    {
        TabLayoutResource = Resource.Layout.Tabbar;
        ToolbarResource = Resource.Layout.Toolbar;

        base.OnCreate(bundle);

        global::Xamarin.Forms.Forms.Init(this, bundle);
        LoadApplication(new App());
        FirebasePushNotificationManager.ProcessIntent(Intent);
    }
}
```

#### 2.4 メッセージ受信時の処理

メッセージ受信時の処理は、```CrossFirebasePushNotification.Current.OnNotificationReceived``` イベントハンドラに実装します。今回は、Xamarin.Forms プロジェクトの MainPage.xaml.cs に実装しました。

```csharp
public MainPage()
{
    InitializeComponent();
    CrossFirebasePushNotification.Current.OnNotificationReceived += Current_OnNotificationReceived;
}

private async void Current_OnNotificationReceived(object source, Plugin.FirebasePushNotification.Abstractions.FirebasePushNotificationDataEventArgs e)
{
    App.LogService.Info("Push.Sample", "onMessageReceived");
    foreach (var data in e.Data)
    {
        App.LogService.Info("Push.Sample", $"key: {data.Key}, value: {data.Value}");
    }
    // 後で、LED を点灯させる処理を追加する部分
}
```

#### 2.4 動作確認

FCM メッセージの送信には、[Fiddler](https://www.telerik.com/fiddler) を使いました。

![Run-With-Fiddler-First](https://gist.githubusercontent.com/tafuji/34ca7c655384ed9d7ee62badb936ca08/raw/92092c5d77d6b667ebf61498d72539a790bc4788/Run-With-Fiddler-FirstResult.png)

- 送信先
    - [https://fcm.googleapis.com/fcm/send](https://fcm.googleapis.com/fcm/send)

- HTTP ヘッダ

    |キー|値|
    |----|----|
    |Content-Type|application/json|
    |Authorization|key=[FCM プロジェクトのサーバーキー]|

サーバーキーは、FCM の[プロジェクトの設定]->[クラウドメッセージング] タブで確認できます。

![FCM-ServerKey](https://gist.githubusercontent.com/tafuji/34ca7c655384ed9d7ee62badb936ca08/raw/a8119e3325e7d56c54fda9d84a22b22064ce5470/FCM-ServerKey.png)

- HTTP Body

    ```json
    {
        "to": "/topics/command",
        "data": {
            "message": "hello world"
        }
    }
    ```

Device Log を確認すると、FCM から通知を受信していることが確認できました。

![FCM-DeviceLog](https://gist.githubusercontent.com/tafuji/34ca7c655384ed9d7ee62badb936ca08/raw/6f28f0ba1916334528000ffde68103ec33b89049/FCM-DeviceLog.png)

### 3. LED を点灯させるコード

#### 3.1 LED を点灯させるコードの実装

Raspbery Pi で提供されている汎用入出力ポートを利用して、LED を点灯させる処理を実装しました。

汎用入出力（GPIO）ポートと接続を確立するには、```Android.Things.Pio.PeripheralManagerService``` クラスの ```OpenGpio``` メソッドを利用します。

- ```PeripheralManagerService.OpenGpio```
    - GPIO との接続を確立します
    - ```OpenGpio``` のポート名は、以下のサイトを参照してください
        - [Raspberry Pi I/O](https://developer.android.com/things/hardware/raspberrypi-io.html)
    - 今回は、"BCM24" のポートを利用しています

汎用入出力ポートとの接続が確立されると ```Android.Things.Pio.Gpio``` のインスタンスが返されます。まずは、ポートに対して書き込み処理を行うという設定を行います。

- ```Android.Things.Pio.Gpio.SetDirection``` で GPIO から出力を行う設定をする

出力を行う設定を行った後、 ```Value``` プロパティで LED の ON / OFF を切り替えます。

- ```Android.Things.Pio.Gpio.Value```  プロパティで ON / OFF を設定する

出力が終わったら、接続を閉じます。

- ```Android.Things.Pio.Gpio.Close```  メソッドで接続を閉じます

以下に ```BlinkLed``` メソッドの引数で秒数を受け取って、その秒数の間 LED を点灯させるサンプルコードを示します。

```csharp
public class BlinkLedService : IBlinkLedService
{
    private const string LED = "BCM24";
    private const int INTERVAL_BETWEEN_BLINKS_MS = 500;
    private const string TAG = "Push.Sample";

    public async Task BlinkLed(int interval)
    {
        try
        {
            await Task.Factory.StartNew(() =>
            {
                Gpio ledGpio = null;
                try
                {
                    PeripheralManagerService service = new PeripheralManagerService();
                    ledGpio = service.OpenGpio(LED);
                    ledGpio.SetDirection(Gpio.DirectionOutInitiallyLow);
                    var stopWatch = new Stopwatch();
                    stopWatch.Start();
                    while (true)
                    {
                        ledGpio.Value = !ledGpio.Value;
                        SystemClock.Sleep(INTERVAL_BETWEEN_BLINKS_MS);
                        if (stopWatch.Elapsed.Seconds > interval) break;
                    }
                }
                catch (Exception e)
                {
                    Log.Error(TAG, Java.Lang.Exception.FromException(e), "Error on Peripheral I/O API");
                }
                finally
                {
                    if (ledGpio != null)
                    {
                        Log.Info(TAG, "GPIO Closing");
                        ledGpio.Close();
                        ledGpio = null;
                    }
                }
            });
        }
        catch (Exception e)
        {
            Log.Error(TAG, Java.Lang.Exception.FromException(e) ,"Error on Blinking LED");
        }
    }
}
```

#### 3.2 FCM メッセージ受信時に LED を点灯する処理を呼び出す

Xamarin.Forms の ```DependencyService``` で LED を点灯するクラスのインスタンスを取得して、FCM メッセージから受け取ったパラメータを```BlinkLed``` メソッドに渡す処理を MainPage.xaml.cs に追記します。

```csharp
var intervalKey = "Interval";
int interval = 0;
var service = DependencyService.Get<IBlinkLedService>();
interval = Convert.ToInt32(e.Data[intervalKey]);
await service.BlinkLed(interval);
```

## 動かしてみる

Fiddler でメッセージを送信します。

![Run-With-Fiddler-Final](https://gist.githubusercontent.com/tafuji/34ca7c655384ed9d7ee62badb936ca08/raw/92092c5d77d6b667ebf61498d72539a790bc4788/Run-With-Fiddler-FinalResult.png)

- HTTP Body

    ```json
    {
        "to": "/topics/command",
        "data": {
            "Interval": 10
        }
    }
    ```

FCM からメッセージを受信して、LED を点灯させることができました。

![Run-Result](https://gist.githubusercontent.com/tafuji/34ca7c655384ed9d7ee62badb936ca08/raw/963f780f13547d2a747925b6366be7f2d69f4a51/RunResult.gif)

## さいごに

- 意外とあっさり作れてしまって、ハードウェアの敷居を低くしてくれた Raspbery Pi はすばらしいなと感じました。
- Android Things と FCM などの周辺技術を組み合わせることで、いろいろなものが作れそうな気がしてきました。（その前に、電子工作のことをもっと学ぶ必要がありますが...）
- Android Things 上で Xamarin も動作するということで、「Xamarin はいいぞ」と改めて思いました。