tafuji's blog

C#, Xamarin, Azure DevOps を中心に書いています。

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

Qiita より転載

はじめに

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

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

準備したもの

サンプルコード

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

配線図

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

LED-Diagram

この図は、Fritzing を使って作成しました。

アプリケーションの作成

0. Solution 構成

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

Solution

  • 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. 画面表示部分を作る
  2. FCM を受信する部分を作る
  3. LED を点灯させる部分を作る

1. 画面表示部分を作る

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

1.1 NuGet パッケージの追加

Xamarin.Android のプロジェクトに Xamarin.Android.Things の NuGet パッケージを追加します。Android Things そのものが、Developer Preview なので、NuGet も prerelease 版にチェックを入れて、検索を行う必要があります。

AndroidThingsNuget

1.2 Manifest ファイルの修正

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

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

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

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

[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 します。

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

1.5 実行!

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

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

LaunchAndroidThings

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

WellcomeScreenShot

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

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

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

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

2.1 Firebase プロジェクトの準備

ここに記載されている手順に従って、Firebase のプロジェクトの作成を行い、google-services.json ファイルをプロジェクトに追加します。

Google-Service-Json

2.2 Android Manifest の設定

ここの手順に従って、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

2.3 FCM の初期化処理

ここの手順に従って、初期化処理を実装します。ネイティブ(Push.Sample.Android)プロジェクトで作業を行いました。

  • カスタム Application クラス(AndroidThingsApplication.cs)
[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)
[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 に実装しました。

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 を使いました。

Run-With-Fiddler-First

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

FCM-ServerKey

  • HTTP Body

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

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

FCM-DeviceLog

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

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

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

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

  • PeripheralManagerService.OpenGpio
    • GPIO との接続を確立します
    • OpenGpio のポート名は、以下のサイトを参照してください
    • 今回は、"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 を点灯させるサンプルコードを示します。

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 に追記します。

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

  • HTTP Body

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

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

Run-Result

さいごに

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