Raspberrypi3
AndroidThings

AndroidThings & Raspberry Pi3 で猫用扇風機を作る(終)

2017年11月18日、19日に実施された「東北Tech道場 郡山道場 第8回」の作業内容の続きをまとめます。

とりあえず、発表会用のRaspbery pi3側のプログラムは完成しましたが、スマホ側がほとんど手付かずです。最初はXamarinでAndroidとiPhoneの両端末に対応させる予定でしたが、もうそんなことは言っていられません。DorebellにAndroid端末用のソースも入っているので、それを一部修正してスマホでFirebeceの内容を確認するだけにしました。

1.スマホ側の処理を作成

Doorbellの「companionApp」をそのまま流用しますが、以下の点を変更しました。

(1) パッケージ名とクラス名の変更

  • パッケージ名を変更
  • DoorbellEntry → CatFansEntry に変更
  • DoorbellEntryAdapter → CatFansEntryAdapter に変更

(2) 注釈の表示を削除

Doorbellで行っている注釈のTextViewへの表示部分を削除する。

(i) CatFansEntryAdapterの修正

TextViewへの表示部分を削除する。なお、同時に時刻の表示を「年/月/日 時:分:秒」形式にする。

CatFansEntryAdapter.java
package xxxxxx.catfans;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.Drawable;
import android.support.v4.content.ContextCompat;
import android.support.v7.widget.RecyclerView;
import android.util.Base64;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;

import com.firebase.ui.database.FirebaseRecyclerAdapter;
import com.google.firebase.database.DatabaseReference;

import java.text.SimpleDateFormat;

/**
 * RecyclerView adapter to populate CatFans entries from Firebase.
 */
public class CatFansEntryAdapter extends FirebaseRecyclerAdapter<CatFansEntry, CatFansEntryAdapter.CatFansEntryViewHolder> {

    /**
     * ViewHolder for each CatFans entry
     */
    public static class CatFansEntryViewHolder extends RecyclerView.ViewHolder {

        public final ImageView image;
        public final TextView time;

        public CatFansEntryViewHolder(View itemView) {
            super(itemView);

            this.image = (ImageView) itemView.findViewById(R.id.imageView1);
            this.time = (TextView) itemView.findViewById(R.id.textView1);
        }
    }

    private Context mApplicationContext;

    public CatFansEntryAdapter(Context context, DatabaseReference ref) {
        super(CatFansEntry.class, R.layout.catfans_entry, CatFansEntryViewHolder.class, ref);

        mApplicationContext = context.getApplicationContext();
    }

    @Override
    protected void populateViewHolder(CatFansEntryViewHolder viewHolder, CatFansEntry model, int position) {
        // Display the timestamp
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
        String tm = sdf.format(model.getTimestamp());
        viewHolder.time.setText(tm);

        // Display the image
        if (model.getImage() != null) {
            // Decode image data encoded by the Cloud Vision library
            byte[] imageBytes = Base64.decode(model.getImage(), Base64.NO_WRAP | Base64.URL_SAFE);
            Bitmap bitmap = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length);
            if (bitmap != null) {
                viewHolder.image.setImageBitmap(bitmap);
            } else {
                Drawable placeholder =
                        ContextCompat.getDrawable(mApplicationContext, R.drawable.ic_image);
                viewHolder.image.setImageDrawable(placeholder);
            }
        }
    }
}

(ii) catfans_entry.xml の修正

以下のように変更して、タイムスパンと画像のみ表示するようにする。

catfans_entry.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingTop="@dimen/activity_vertical_margin"
        android:paddingBottom="@dimen/activity_vertical_margin"
        android:gravity="center_horizontal">

    <TextView
            android:id="@+id/textView1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginLeft="10dp"
            android:layout_weight="1"
            android:textStyle="bold" />

    <ImageView
            android:layout_width="320dp"
            android:layout_height="240dp"
            android:id="@+id/imageView1" />

</LinearLayout>

(iii) 確認画面

スマホで確認すると次のようになる。
32.png

2. ハードの状態

ハードウェアの方も組み立て終わり、出来上がってきました。

(1) 駆動部分

34.jpg

(2) コルクボードで覆ったところ

35.jpg

(3) 扇風機部分を取り付ける

36.jpg

(4) 完成

33.jpg

3. 不具合発生

(1) 「カメラのパーミッションが無い」というエラーに遭遇

システムの方は機能限定ながらも動くようになり、発表会直前で稼働中の動画を撮ろうという段階になって、なんとシステムが動かなくなってしまいました。原因を探ると、「CatFansActivityクラス」の「onCreateメソッド」の以下の部分でreturnしていることがわかりました。

CatFansActivity.java
        // カメラにアクセスするための許可が必要
        if (checkSelfPermission(Manifest.permission.CAMERA)
                != PackageManager.PERMISSION_GRANTED) {
            // 権限を自動付与する際に問題が発生した
            Log.d(TAG, "No permission");
            return;
        }

今まで動いていたわけですから、当然、AndroidManifest.xmlには、
<uses-permission android:name="android.permission.CAMERA" />
の記述があります。なんでパーミッションが無いと判定されるのか全く分かりません。10回実行して1回くらいは成功するという状態になってしまいました。何度も繰り返し実行して、なんとかプレゼン用の動画を撮って発表会を迎えます。

(2) 原因究明

このエラーの原因を探るために以下のことを試しました。

  • Android Things を再起動する。
  • Doorbell を試す。
  • Raspbery pi3 を取り換える。
  • カメラを取り換える。
  • 電源コードを取り換える。
  • Android Thingsを最新バージョン(Android Things DP 0.6.0)にバージョンUPする。

しかし、いずれも失敗に終わりました。オリジナルのDorebellでさえパーミッションが無いとエラー落ちするのです。さっぱり原因がわかりません。

(3) 対処法

でも、このままでは開発を続けられないので、以下のようにコマンドラインで直接Android Thingsにカメラのパーミッションを与えることにしました。

  • 一旦、android studioでプロジェクトを実行する。
  • 1回目はパーミッションが不許可なので、そのままエラー終了する。
  • コマンドプロンプトで以下のコマンドを実行する。

  adb shell pm grant <プロジェクト名> android.permission.CAMERA

  • 再度、android studioでプロジェクトを実行する。すると、パーミッションが許可され、カメラが使えるようになる。(なお、プロジェクトをuninstallすると、再度コマンドの入力が必要。)

4. 今後の作業

東北Tech道場の発表会は終わりましたが、今後、未実装だった温湿による制御と、半端に終わってしまったスマホ側の処理を実装する予定です。

5. おわりに

初めてのIoTの開発でしたが、Android Thingsは資料も少く、手探り状態での作業となり、なかなか進まず大変でした。本当に拙い記事ではありますが、もし、誰かのお役に立てれば幸いです。