SORACOM Inventory Agent のカスタムオブジェクトを実装してみる

 先日の記事では SORACOM Inventory が Public Beta になったということでデバイス管理と SORACOM Harvest との連携を試してみました。

blog.akanumahiroaki.com

 Public Beta になったタイミングで SORACOM Inventory では LwM2M のカスタムオブジェクトが定義できるようになったので、今回は簡単に実装してみました。 SORACOM Inventory の機能概要や LwM2M のリソースモデル等については SORACOM の公式ドキュメントを参照ください。

dev.soracom.io

コアライブラリとサンプル実装のダウンロード

 SORACOM Inventory では下記リポジトリで Java版エージェントのコアライブラリとサンプル実装が提供されています。

github.com

 まずはこちらを clone します。

$ git clone https://github.com/soracom/soracom-inventory-agent-for-java.git
$ cd soracom-inventory-agent-for-java/

環境構築

 プロジェクトのビルドには Gradle が使用されています。私は IDE に Eclipse を使用しているので、下記コマンドで Eclipse 用の設定ファイルを生成します。

$ ./gradlew eclipse

 また、後でコアライブラリの jar ファイルを使用するので、下記コマンドでビルドしておきます。

$ ./gradlew build

 そして Eclipse のプロジェクトとしてインポートします。File メニュー の Import から General > Existing Projects into Workspace と辿って、root directory に soracom-inventory-agent-for-java ディレクトリを選択します。

 次に今回作成するエージェントのプロジェクト用のディレクトリを作成します。

$ mkdir restroom-monitor-agent
$ cd restroom-monitor-agent/

 下記コマンドでプロジェクトを初期化します。

$ gradle init --type java-application

 すると下記のようなファイルが生成されます。

$ ls -l
total 40
-rw-r--r--  1 akanuma  staff   992 May 12 23:52 build.gradle
drwxr-xr-x  3 akanuma  staff    96 May 12 23:52 gradle
-rwxr-xr-x  1 akanuma  staff  5296 May 12 23:52 gradlew
-rw-r--r--  1 akanuma  staff  2260 May 12 23:52 gradlew.bat
-rw-r--r--  1 akanuma  staff   369 May 12 23:52 settings.gradle
drwxr-xr-x  4 akanuma  staff   128 May 12 23:52 src

 build.gradle の内容は下記のように変更します。

$ cat build.gradle 
plugins {
    id 'java'
    id 'eclipse'
    id 'idea'
    id 'application'
}

repositories {
    jcenter()
    maven { url 'https://soracom.github.io/maven-repository/' }
}

def INVENTORY_AGENT_VERSION="0.0.5"

dependencies {
    compile "io.soracom:soracom-inventory-agent-for-java-core:$INVENTORY_AGENT_VERSION"
    testCompile 'junit:junit:4.12'
}

mainClassName = 'com.akanumahiroaki.restroommonitor.agent.RestroomMonitorAgent'

 Eclipse 用の設定ファイルを生成します。

$ ./gradlew eclipse

 そして Eclipse にインポートします。初期化時に生成されたメインクラスは下記のように App.java になっています。

f:id:akanuma-hiroaki:20180513223817p:plain

 これを build.gradle の mainClassName で指定したパッケージとクラス名に合うように変更します。

f:id:akanuma-hiroaki:20180513223853p:plain

 パッケージとクラス名変更後の初期の実装は下記のようになっています。

package com.akanumahiroaki.restroommonitor.agent;
/*
 * This Java source file was generated by the Gradle 'init' task.
 */
public class RestroomMonitorAgent {
    public String getGreeting() {
        return "Hello world.";
    }

    public static void main(String[] args) {
        System.out.println(new RestroomMonitorAgent().getGreeting());
    }
}

 次に必要なライブラリにパスを通します。 LeshanClient を使用するためのライブラリは別途取得する必要があるので、私は下記サイトから依存する jar と共に取得してきました。

jar-download.com

 また、先ほどビルドしたコアライブラリの jar が下記パスに生成されています。

soracom-inventory-agent-for-java/build/libs/soracom-inventory-agent-for-java-0.0.5.jar

 これらの jar ファイルを外部ライブラリとしてビルドパスに追加しておきます。

f:id:akanuma-hiroaki:20180513223933p:plain

カスタムオブジェクト定義

 カスタムオブジェクトを定義するには XML ファイルを作成する必要があります。今回はシンプルに読み取り用のリソースを一つだけ定義してみます。トイレセンサーを作っている想定で、その空室状況を表すステータスです。カスタムオブジェクトの使い方としてこういうステータスを扱うのが正しいのか微妙な気がしましたが、今回は実装を試すのが主目的ということでやってみます。下記のような XML ファイルを作成し、サンプルプロジェクトと同様に src/main/resources 配下に 30000.xml というファイル名で配置します。

<?xml version="1.0" encoding="UTF-8"?>
<LWM2M xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="http://openmobilealliance.org/tech/profiles/LWM2M.xsd">
    <Object ObjectType="MODefinition">
        <Name>Restroom</Name>
        <Description1>Restroom Monitor</Description1>
        <ObjectID>30000</ObjectID>
        <ObjectURN>urn:oma:lwm2m:oma:30000</ObjectURN>
        <MultipleInstances>Single</MultipleInstances>
        <Mandatory>Optional</Mandatory>
        <Resources>
            <Item ID="0">
                <Name>Status</Name>
                <Operations>R</Operations>
                <MultipleInstances>Single</MultipleInstances>
                <Mandatory>Mandatory</Mandatory>
                <Type>String</Type>
                <RangeEnumeration />
                <Units/>
                <Description>Status of a restroom.</Description>
            </Item>
        </Resources>
        <Description2 />
    </Object>
</LWM2M>

実装クラス

 上記で定義したカスタムオブジェクトの実装クラスを下記のように作成します。今回はテキストファイルに現在のステータスが記録されているという前提で、簡単にその内容を読み込んで返すというだけにしてあります。

 AnnotatedLwM2mInstanceEnabler を継承したクラスに @LWM2MObject アノテーションでオブジェクトIDとオブジェクト名を指定します。また、値を返すメソッドには @Resource アノテーションでリソースIDとオペレーション種別を指定します。今回は読み取り専用のリソースなので、オペレーション種別は Operation.Read を指定しています。

package com.akanumahiroaki.restroommonitor.agent.object;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;

import io.soracom.inventory.agent.core.lwm2m.*;

@LWM2MObject(objectId = 30000, name = "Restroom")
public class RestroomObject extends AnnotatedLwM2mInstanceEnabler {
    @Resource(resourceId = 0, operation = Operation.Read)
    public String readStatus() {
        try {
            List<String> lines = Files.readAllLines(Paths.get("/home/pi/work/RestroomMonitor/status.txt"));
            return String.join("", lines);
        } catch (IOException e) {
            e.printStackTrace();
            return "unknown";
        }
    }
}

エージェントクラスの実装

 ここまでで用意したカスタムオブジェクト定義と実装を利用するエージェントクラスを下記のように実装します。

 lwM2mModelBuilder.addPresetObjectModels() でデフォルトのオブジェクト定義を読み込んだ後で、 lwM2mModelBuilder.addObjectModelFromClassPath("/30000.xml") で今回定義したオブジェクト定義を読み込んでいます。また、実装したオブジェクトクラスを initializer.addInstancesForObject(new RestroomObject()) で設定しています。

package com.akanumahiroaki.restroommonitor.agent;

import org.eclipse.leshan.client.californium.LeshanClient;

import com.akanumahiroaki.restroommonitor.agent.object.RestroomObject;

import io.soracom.inventory.agent.core.initialize.InventoryAgentInitializer;
import io.soracom.inventory.agent.core.initialize.LwM2mModelBuilder;
import io.soracom.inventory.agent.core.lwm2m.typed_object.impl.MockDeviceObjectImpl;

public class RestroomMonitorAgent {
    public static void main(String[] args) {
        InventoryAgentInitializer initializer = new InventoryAgentInitializer();
        
        LwM2mModelBuilder lwM2mModelBuilder = new LwM2mModelBuilder();
        lwM2mModelBuilder.addPresetObjectModels();
        lwM2mModelBuilder.addObjectModelFromClassPath("/30000.xml");
        initializer.setLwM2mModel(lwM2mModelBuilder.build());
        
        initializer.addInstancesForObject(new MockDeviceObjectImpl());
        initializer.addInstancesForObject(new RestroomObject());
        LeshanClient client = initializer.buildClient();
        client.start();
    }
}

ユーザコンソールからオブジェクト定義を登録

 ユーザコンソールにもカスタムオブジェクトの定義が反映されるように、オブジェクト定義のXMLをユーザコンソールから登録します。ユーザコンソールの左メニューの SORACOM Inventory の オブジェクトモデル を選択します。

f:id:akanuma-hiroaki:20180513224301p:plain

 オブジェクトモデルの追加 ボタンをクリックします。

f:id:akanuma-hiroaki:20180513224322p:plain

 表示されたフォームにオブジェクトモデル定義XMLの内容を貼り付けて、 追加 ボタンをクリックします。

f:id:akanuma-hiroaki:20180513224344p:plain

 すると下記のようにオブジェクトモデルが追加されます。

f:id:akanuma-hiroaki:20180513224404p:plain

エージェントの実行

 ここまでで一通り実装は完了なので、最後にデバイスへの配布用のアーカイブを下記のコマンドで生成します。

$ ./gradlew distZip

 下記のようにアーカイブが生成されますので、これをエージェントを実行するデバイスへ配布します。

$ ls -ltr build/distributions/
total 2272
-rw-r--r--  1 akanuma  staff  1159199 May 13 01:44 restroom-monitor-agent.zip

 配布先デバイスで下記のようにアーカイブを展開してエージェントを実行します。

$ unzip restroom-monitor-agent.zip 
$ cd restroom-monitor-agent/bin
$ ./restroom-monitor-agent

 すると下記のようにコンソールからカスタムオブジェクトが確認できるようになります。 Observe することも可能です。

f:id:akanuma-hiroaki:20180513224426p:plain

まとめ

 今回試してみたのはシンプルな内容だったので、簡単にカスタムオブジェクトを実装することができました。実際のサービスで使用する場合にはカスタムオブジェクトの前に標準で提供されているリソースモデルに対して正しい値を返すようにエージェントを実装する必要がありますので、取得したい情報全てが取得できるようにするには結構なボリュームの実装が必要そうではあります。ただ、エージェントを実装すればプラットフォームには手をかける必要がないので、うまく使えればとても便利なのではないかと思います。

ミニカメラと Seeeduino でタイムラプス撮影

 下記のミニカメラが面白そうだったので購入してみました。

www.switch-science.com

 基板部分のサイズが 28.5 mm x 17 mm x 4.2 mm、カメラ部分のサイズが 6.2 mm x 6.2 mm x 4.4 mm ととても小さいので、使い方によっては面白いものが作れそうです。今回はとりあえずタイムラプス動画を作る想定で、インターバル撮影を試してみました。

配線の準備

 今回はマイコンに Arduino Uno 互換の Seeeduino を使用します。ミニカメラのワイヤーは下記の写真のようにコネクタが接続されていますが、これだと Seeeduino やブレッドボードに配線することができません。

f:id:akanuma-hiroaki:20180507234443j:plain

 そこで思い切ってコネクタを切断してしまいます。

f:id:akanuma-hiroaki:20180507234517j:plain

 そして先端の被覆を剥がします。

f:id:akanuma-hiroaki:20180507234615j:plain

 次に錫メッキ線を半田付けします。

f:id:akanuma-hiroaki:20180507234812j:plain

 最後に熱収縮チューブでカバーすればひとまず準備完了です。これで Seeeduino やブレッドボードにも配線することができるようになりました。

f:id:akanuma-hiroaki:20180507235014j:plain

 ちなみにですが実は今回使っているミニカメラは2台目でして、1台目の時はコネクタをカットするのではなく基板に半田付けされている部分を外して他のケーブルを繋ごうと思い、下記の写真のような保護剤に覆われていた部分をカッターで削って半田ごてで加熱して外したのですが、細かく回路が密集しているところに結構力をかけてしまったりしたせいか接触の問題か、うまく動作しなくなってしまいました。保護剤を無理に削ったりはするものではないなと思った次第です。

f:id:akanuma-hiroaki:20180508075107j:plain

配線する

 それでは MiniCamera と Seeeduino を配線します。今回は単純にカメラでの撮影だけ試せれば良く、特に他のセンサーも使わないので、ブレッドボードも使わず、直接 Seeeduino に配線してしまいます。赤のケーブルは5V出力に、黒のケーブルは GND に、白のケーブルはトリガー用に13番ピンに接続します。

f:id:akanuma-hiroaki:20180507235619j:plain

 また、 Seeeduino は動作電圧を5Vにするか3.3Vにするかをスイッチで選べるようになっています。今回使用するミニカメラの動作電圧は 3.7V 〜 5V なので、 Seeeduino の動作電圧は 5V にしておきます。

f:id:akanuma-hiroaki:20180508000711j:plain

ファームウェア

 このミニカメラの撮影の仕様としては、トリガーを HIGH の状態から 0.5秒以内で LOW にして HIGH に戻すと写真が撮影され、その際に赤いLEDが短時間点滅します。撮影された写真は micro SD カードに保存され、ファイル名には連番が振られます。連番は999までで、そこに到達してしまうとそれ以降は写真が撮影されなくなります。

 また、動画の撮影も可能で、トリガーを HIGH の状態から 0.5秒以上 LOW にして HIGH に戻すと動画の撮影が開始され、撮影中は赤いLEDが点灯します。再度 0.5 秒以上 LOW にして HIGH に戻すと撮影が停止されます。

 インターバル撮影のコードは下記のようにシンプルなものです。 setup() でトリガーピンのピンモードを OUTPUT に設定し、 HIGH の状態にしておきます。 loop() では LOW にして 50ms 後に HIGH に戻して5秒間待つというのを繰り返します。つまり5秒間隔で写真を撮り続けるということになります。

int trig = 13;
int intervalMs = 5000;

void setup() {                
  Serial.begin(115200);

  pinMode(trig, OUTPUT);         
  digitalWrite(trig, HIGH); 

  Serial.println("MiniCamera is ready.");
}
 
void loop() {
  digitalWrite(trig, LOW);   

  delay(50);               
 
  digitalWrite(trig, HIGH);   
  
  Serial.println("Shoot.");
 
  delay(intervalMs);               
}

 写真の解像度は 1280 × 720 で、例えば下記のような写真が撮れます。

f:id:akanuma-hiroaki:20180508080631j:plain

 ちなみに動画の撮影は例えば下記のようなコードにすると、10秒撮影して10秒待つ、というのを繰り返すことになります。

int trig = 13;
int intervalMs = 10000;

void setup() {                
  Serial.begin(115200);

  pinMode(trig, OUTPUT);         
  digitalWrite(trig, HIGH); 

  Serial.println("MiniCamera is ready for video.");
}
 
void loop() {
  digitalWrite(trig, LOW);   

  delay(600);               
 
  digitalWrite(trig, HIGH);   
  
  Serial.println("Shoot.");
 
  delay(intervalMs);               
}

タイムラプス動画作成

 ミニカメラ + Seeeduino で今回行っているのは写真のインターバル撮影までなので、撮影された写真からタイムラプス動画を作成するには他のアプリなどを使う必要があります。詳細は割愛しますが、例えば Panolapse というアプリを使うと JPEG 画像からタイムラプス動画を作成できます。出力解像度1280×720 HDまでなら無料で使うことができます。

www.panolapse360.com

 例えば10秒間隔で55枚撮影した画像から作成したタイムラプス動画は下記のような感じになりました。

まとめ

 今回はとりあえず筐体もバッテリーもなしでインターバル撮影してみただけでしたが、バッテリーと一緒に小さい筐体に収めればかなりコンパクトになるので、例えばロボットのプラモデルとかに収めて目のところにカメラを配置したら面白そうかなと思いました。撮影枚数上限が1,000枚なのと、保存先がミニカメラの micro SD カードなので、ファームウェアから画像を操作することは難しそうなため、遠隔地に配置して色々やるみたいな用途には向かなそうですが、手元に置いておいて遊ぶデバイスに使う分には楽しそうです。

SORACOM Inventory によるデバイス管理と Harvest 連携を試す

 先日、 SORACOM Inventory が Limited Preview から Public Beta になったという発表がありました。

blog.soracom.jp

 Limited Preview の時に試させてもらった内容を以前会社のブログに書かせてもらいましたが、 Public Beta になって変更になった点や追加になった機能もあるということで、改めて試してみた内容を書いてみたいと思います。

tech.unifa-e.com

 SORACOM Inventory のサービス内容については上記の以前のブログや公式サイトをご参照ください。

soracom.jp

サンプルエージェントのインストール

 SORACOM から提供されているサンプルエージェントには Eclipse Wakaama を使ってC言語で実装されたものと、 Eclipse Leshan を使って Java で実装されたものがあります。今回は Java 版を使ってみます。環境は Raspberry Pi 3 Model B で、 OS のバージョンは下記の通りです。

$ lsb_release -a
No LSB modules are available.
Distributor ID: Raspbian
Description:    Raspbian GNU/Linux 8.0 (jessie)
Release:        8.0
Codename:       jessie

 まずは Java のバージョンを確認します。 Version 7(1.7)以上がインストールされていればOKです。

$ java -version
java version "1.8.0_151"
Java(TM) SE Runtime Environment (build 1.8.0_151-b12)
Java HotSpot(TM) Client VM (build 25.151-b12, mixed mode)

 次にサンプルエージェントをダウンロードします。確認時の最新のバージョンは 0.0.5 だったので、下記の様にダウンロードします。

$ wget https://github.com/soracom/soracom-inventory-agent-for-java/releases/download/0.0.5/soracom-inventory-agent-example-0.0.5.zip

 あとは解凍すればとりあえずサンプルエージェントの準備は完了です。

$ unzip soracom-inventory-agent-example-0.0.5.zip

 ちなみに Limited Preview の時には対象の Sim が属する Sim グループを専用の VPG に紐づける必要がありましたが、 Public Beta では必要なくなっています。

デバイスの登録

 それではエージェントを起動してデバイスを登録します。 SORACOM Air でネットワークに接続した上で、先程解凍したサンプルエージェントを起動します。

$ cd soracom-inventory-agent-example-0.0.5/bin
$ ./soracom-inventory-agent-example-start

 登録されたデバイスの情報はコンソールから確認できます。

f:id:akanuma-hiroaki:20180504122320p:plain

 デバイスを登録することは Bootstrap と言われ、Bootstrap が完了するとエージェントの実行ディレクトリに .soracom-inventory-credentials.dat という認証情報ファイルが作成されます。このファイルがあれば2回目以降は SORACOM の Sim 経由以外のネットワーク接続でも実行可能になります。

デバイスのデータ確認

 エージェントにより登録された情報はコンソールから確認することができます。 SORACOM Inventory のデバイス管理ページから対象のデバイスを選択し、 詳細 ボタンをクリックします。

f:id:akanuma-hiroaki:20180504123330p:plain

 するとそのデバイスの現在の情報が確認できます。

f:id:akanuma-hiroaki:20180504123347p:plain

デバイスデータの Observe

 コンソールからその時点の最新の情報を取得することもできますが、 Observe しておくことで値に更新があった場合に通知を受け取ることができます。 Observe するにはデバイス詳細画面の各データ項目の下記アイコンをクリックすることで Observe が開始され、更新があった場合には自動的に表示が更新されるようになります。

f:id:akanuma-hiroaki:20180504131655p:plain

Observe したデータを Harvest と連携する

 今回 SORACOM Inventory が Public Beta になると同時に、 Observe データを SORACOM Harvest などと連携することができるようになったので試してみます。まずはデバイスグループを作成する必要があります。メニューから デバイスグループ をクリックします。

f:id:akanuma-hiroaki:20180504132420p:plain

 デバイスグループのページでデバイスを新たに作成します。

f:id:akanuma-hiroaki:20180504132452p:plain

 デバイスグループ一覧に戻ったら今作成したグループをクリックして詳細画面に入ります。

f:id:akanuma-hiroaki:20180504132545p:plain

 SORACOM Harvest の設定を ON にして 保存 をクリックします。これでこのグループに属するデバイスの Observe データが Harvest に連携されるようになります。

f:id:akanuma-hiroaki:20180504133138p:plain

 次にデバイス一覧の画面から対象のデバイスについての操作で グループ変更 を選択します。

f:id:akanuma-hiroaki:20180504133451p:plain

 所属させるグループとして先程作成したグループを選択します。

f:id:akanuma-hiroaki:20180504133641p:plain

 これでこのデバイスの Observe データが Harvest に連携されることになります。今回は連携する対象データとして緯度、経度情報を選択してみます。サンプルエージェントでは緯度、経度は実際の値ではなく、擬似的にデータが変化していくようになっています。デバイスの詳細画面から緯度、経度情報を Observe しておきます。

f:id:akanuma-hiroaki:20180504134031p:plain

 これでひとまず設定は終了なので、デバイス一覧から該当のデバイスに対する操作で データを確認 を選択します。

f:id:akanuma-hiroaki:20180504134153p:plain

 すると緯度、経度のデータがグラフで確認できるようになっています。

f:id:akanuma-hiroaki:20180504134234p:plain

SORACOM Harvest で位置情報を表示する

 SORACOM Inventory が Public Beta になったのと同じタイミングで、 SORACOM Harvest の方でも機能追加があり、位置情報の表示がサポートされました。

blog.soracom.jp

 SORACOM Inventory の Harvest 連携と組み合わせることで、 Observe した位置情報データをそのまま Harvest 側で確認することができます。先程の例でも緯度、経度情報を Observe しましたが、個別の Resource をそれぞれ Observe していると、 Observe イベントもそれぞれ独立して飛んでしまうので、 Instance 全体を Observe しておく必要があるとのことです。

 安川さんありがとうございます!ということで下記のように Location オブジェクトの Instance を Observe しておきます。

f:id:akanuma-hiroaki:20180504134936p:plain

 すると下記のように緯度(Latitude)と経度(Longitude)が一つの Observe イベントとして送られ、位置情報が表示されるようになります。

f:id:akanuma-hiroaki:20180504140059p:plain

まとめ

 SORACOM Inventory の今回の機能追加では、カスタムオブジェクトの定義ができるようになり、サンプルエージェントのリポジトリでも作成方法が紹介されていたので試してみたところ、最低限のエージェントまでは簡単に実装することができました。ただ、そのエージェントをサンプルエージェントを動かしたのと同じデバイスで動かしたところ、その後サンプルエージェントを再度実行しても Observe データが Harvest に連携されなくなってしまいました。新しく実装した方に Location オブジェクトを追加して Observe してみたところ Harvest に連携されたので、何かしら情報の持ち方がおかしくなってしまったところがあるのかもしれません。この点を除けばデバイスのデータを簡単に可視化できて、位置情報まで表示できてしまうというのはとても便利ですね。あとはデバイス側でどんなデータをどうやって取得するかというところなので、カスタムオブジェクトの定義やエージェントの実装なども今後もう少し試してみたいと思います。

加速度センサー MMA8452Q の値を Seeeduino で読み取る

 なんとなく興味があったので、加速度センサーを買ってみました。

www.switch-science.com

 Raspberry Pi や BLE Nano から使ってみようかとも思ったのですが、公式のチュートリアルでは Arduino での使い方が書いてあり、ライブラリもあるようだったので今回は Arduino Uno 互換の Seeeduino で加速度センサーの値を読み取ってみます。

チュートリアル
MMA8452Q Accelerometer Breakout Hookup Guide - learn.sparkfun.com

ピンヘッダの実装

 今回買った MMA8452Q の Breakout Board にはピンヘッダが実装されていません。

f:id:akanuma-hiroaki:20180428091145j:plain

 ブレッドボードで色々試してみるにはやはりピンヘッダがあった方が便利なので、自分で半田付けして実装しました。

f:id:akanuma-hiroaki:20180428091206j:plain

動作電圧

 MMA8452Q の動作電圧は 3.3V です。電源供給についてはマイコン側のピンで選べるようになっていることが多いかと思いますが、電源以外のピンの電圧も 3.3V に調整する必要があります。 Arduino Uno だと 5V なので、抵抗をかませるなどで 3.3V に調整しなくてはなりませんが、 Seeeduino は動作電圧を 5V にするか 3.3V にするかをスイッチで選べるようになっています。デフォルトでは 5V になっているので、 3.3V 側にスイッチを切り替えておきます。

f:id:akanuma-hiroaki:20180428093446j:plain

フックアップ

 MMA8452Q の出力インタフェースは I2C なので、 Seeeduino の SCL/SDA をそれぞれ MMA8452Q の SCL/SDA に接続します。 3.3V ピンと GND ピンもそれぞれ接続します。

f:id:akanuma-hiroaki:20180428091310j:plain

ファームウェア実装

 MMA8452Q の Arduino 用ライブラリが下記リポジトリで公開されています。

github.com

 IDE にライブラリをインポートするため、下記 URL からライブラリをダウンロードします。

https://github.com/sparkfun/SparkFun_MMA8452Q_Arduino_Library/archive/master.zip

 今回は IDE に Arduino Web Editor を使用します。 Web Editor の使い方の詳細は割愛しますが、左側メニューの「Libraries」の画面から先ほどダウンロードしたライブラリをインポートします。成功すると下記のような画面が表示されます。

f:id:akanuma-hiroaki:20180428091537p:plain

 コードについてはまずはサンプルをそのまま動かしてみます。ライブラリのリポジトリの下記 URL にてサンプルが公開されていますので、これをそのまま Web Editor でコピペします。

SparkFun_MMA8452Q_Arduino_Library/SparkFun_MMA8452Q_Basic.ino at master · sparkfun/SparkFun_MMA8452Q_Arduino_Library · GitHub

 コピペ後の画面は下記のような感じになります。 Seeeduino を USB で接続して正しく認識されていれば、対象のボードが表示されているかと思います。

f:id:akanuma-hiroaki:20180428091711p:plain

動作確認

 サンプルをそのまま動かすだけであればここまでで準備完了です。 Web Editor で Verify して問題なければ Seeeduino にファームウェアを Upload します。正しく動作すれば、 Web Editor の Serial Monitor に加速度センサーの値と、センサーの向きの情報が表示されていきます。

f:id:akanuma-hiroaki:20180428091743p:plain

傾きに応じて LED を点灯させる

 サンプルにちょっとだけ手を加えて、センサーの傾きに応じて LED を点灯させてみたいと思います。サンプルコードからコメントを削除して、 LED を動作させるようにしたコードを下記に載せておきます。やっていることは単純で、 LED を4つ配置し、サンプルコードで判定しているセンサーの向きに応じて対応する LED を点灯させているだけです。

#include <Wire.h> // Must include Wire library for I2C
#include <SparkFun_MMA8452Q.h> // Includes the SFE_MMA8452Q library

MMA8452Q accel;

const int ledPortraitUp = 13;
const int ledPortraitDown = 12;
const int ledLandscapeRight = 10;
const int ledLandscapeLeft = 11;

void setup()
{
  Serial.begin(9600);
  Serial.println("MMA8452Q Test Code!");
  
  accel.init();
  
  pinMode(ledPortraitUp, OUTPUT);
  pinMode(ledPortraitDown, OUTPUT);
  pinMode(ledLandscapeRight, OUTPUT);
  pinMode(ledLandscapeLeft, OUTPUT);
}

void loop()
{
  if (accel.available())
  {
    accel.read();
    
    printCalculatedAccels();
    
    printOrientation();
    
    Serial.println(); // Print new line every time.
  }
}

void printAccels()
{
  Serial.print(accel.x, 3);
  Serial.print("\t");
  Serial.print(accel.y, 3);
  Serial.print("\t");
  Serial.print(accel.z, 3);
  Serial.print("\t");
}

void printCalculatedAccels()
{ 
  Serial.print(accel.cx, 3);
  Serial.print("\t");
  Serial.print(accel.cy, 3);
  Serial.print("\t");
  Serial.print(accel.cz, 3);
  Serial.print("\t");
}

void printOrientation()
{
  byte pl = accel.readPL();
  switch (pl)
  {
  case PORTRAIT_U:
    digitalWrite(ledPortraitUp, HIGH);
    digitalWrite(ledPortraitDown, LOW);
    digitalWrite(ledLandscapeRight, LOW);
    digitalWrite(ledLandscapeLeft, LOW);
    Serial.print("Portrait Up");
    break;
  case PORTRAIT_D:
    digitalWrite(ledPortraitUp, LOW);
    digitalWrite(ledPortraitDown, HIGH);
    digitalWrite(ledLandscapeRight, LOW);
    digitalWrite(ledLandscapeLeft, LOW);
    Serial.print("Portrait Down");
    break;
  case LANDSCAPE_R:
    digitalWrite(ledPortraitUp, LOW);
    digitalWrite(ledPortraitDown, LOW);
    digitalWrite(ledLandscapeRight, HIGH);
    digitalWrite(ledLandscapeLeft, LOW);
    Serial.print("Landscape Right");
    break;
  case LANDSCAPE_L:
    digitalWrite(ledPortraitUp, LOW);
    digitalWrite(ledPortraitDown, LOW);
    digitalWrite(ledLandscapeRight, LOW);
    digitalWrite(ledLandscapeLeft, HIGH);
    Serial.print("Landscape Left");
    break;
  case LOCKOUT:
    digitalWrite(ledPortraitUp, LOW);
    digitalWrite(ledPortraitDown, LOW);
    digitalWrite(ledLandscapeRight, LOW);
    digitalWrite(ledLandscapeLeft, LOW);
    Serial.print("Flat");
    break;
  }
}

 動作させた時の様子は下記のようになります。傾きに応じて持ち上がっている側の LED が点灯するようになっています。

まとめ

 Arduino 用のライブラリが公開されているおかげで値の読み取りはとても簡単にできてしまいました。 Raspberry Pi 等で扱う場合はどうすれば良いのかも気になるところなので、今後調べてみようと思います。

CdS + Seeeduino + LED で常夜灯をつくる

 子どもが夜中に起きた時に部屋が暗いのが嫌だというので、暗い時だけ LED を点灯させるように CdS セルを使って常夜灯を作ってみました。 CdS セルについては以前 BLE Nano と一緒に使ってみたことがありますが、今回は Seeeduino(Arduino互換機) を使ってみました。

 以前 CdS を使ってみたときの記事はこちらです。

blog.akanumahiroaki.com

回路図

 まずは直接回路を組むのではなく、 Tinkercad でシミュレートしてみます。 Seeeduino は Arduino互換機なので、回路図では Arduino を配置しています。 Tinkercad については以前も下記記事で紹介しました。

blog.akanumahiroaki.com

 LED は一つではさすがに暗いので、とりあえず並べられるだけ並べて12個を並列に配置します。これでどれぐらい明るくなるかはわかりませんがひとまずやってみます。それぞれのアノード側に 4.7Ω の抵抗を配置して、 Seeeduino の13番ピンに接続します。カソード側は GND に接続します。

 CdS の出力はアナログ出力なので、 Seeeduino のアナログ入力で読み取ります。 CdS の一方を GND に接続し、もう一方は 1kΩ の抵抗を挟んで5V出力とアナログ入力の A0 ピンに接続します。

f:id:akanuma-hiroaki:20180420075951p:plain

コーディング

 コードもまずは Tinkercad で実装します。今回はネットワーク接続等は考慮していないのでごくシンプルに、下記のようなコードで実装しました。 analogRead() で13番ピンから CdS の値を読み取り、閾値以上だったら(暗かったら) LED を点灯、閾値未満だったら(明るかったら)消灯しています。

int led_pin = 13;
int lux_pin = 0;
int lux = 0;
int lux_threshold = 900;

void setup()
{
  Serial.begin(9600);
  pinMode(led_pin, OUTPUT);
}

void loop()
{
  lux = analogRead(lux_pin);
  Serial.println(lux);
  if (lux >= lux_threshold) {
    digitalWrite(led_pin, HIGH);
  } else {
    digitalWrite(led_pin, LOW);
  }
  delay(100);
}

シミュレーション実行

 それでは Tinkercad でのシミュレーションを実行してみます。シミュレーションを開始してから CdS を選択するとスライダーが表示され、明るさをコントロールすることができます。今回のコードでは CdS の値をシリアルで出力するようにしているので、Serial Monitor に値が表示されていきます。スライダーで明るさを暗くすると値が大きくなりLEDが点灯し、明るくすると値は小さくなって消灯することが確認できます。

f:id:akanuma-hiroaki:20180420081738p:plain

実物で実行

 では実際の回路を組んで実行してみます。手元にあったもので組んだので、ジャンパーコードの色は回路図とは合ってませんが、回路は同じようにしています。回路を組んだら Tinkercad で書いたコードを Arduino Web Editor にインポートし、 Seeeduino にファームウェアをフラッシュします。するとコードが実行され、以下の動画のように手で CdS を覆って暗くすると LED が点灯します。 Web Editor の Serial Monitor には CdS の値が表示されています。

まとめ

 とりあえず手元にあったもので想定していた動作をさせることはできました。普通の LED では12個並べても明るさは不足してそうですが、暗い部屋での目印暗いニアなりそうです。実際に子ども部屋に置くとなると、電源はAC電源でUSB給電しようと思っているので、コンセントの壁面にコンパクトに設置できるようにする方法を考える必要がありそうです。ケーブルの長さなどもちゃんと調節してスッキリさせたいですね。作ってる過程としては単純にLEDがたくさん点灯するとそれだけで楽しかったりします。それにしても Tinkercad は便利ですね。

 今回は下記の記事を参考にさせていただきました。

deviceplus.jp

BLE の接続シーケンスを Wireshark で確認してみる

 前回 BLE Sniffer と Wireshark で BLE パケットをスニッフィングする方法を書きましたが、 Wireshark で確認できる内容から BLE の接続シーケンスを確認してみたいと思います。手元にあった BLE デバイスで簡単に確認してみた結果を書いてみます。

ADV_IND

 BLE デバイスがアドバタイジングを開始するとアドバタイジング・パケットを送出し始めます。 PDU(プロトコル・データ・ユニット)のタイプとしては4タイプありますが、今回使用したデバイスでは ADV_IND となっていました。

f:id:akanuma-hiroaki:20180414135756p:plain

 ADV_IND を含め、アドバタイジング・パケットのタイプは下記の4タイプになります。

  • ADV_IND:不特定多数のデバイスから接続可能
  • ADV_NONCONN_IND:デバイスからの接続は不可能
  • ADV_SCAN_IND:デバイスからのスキャン可能
  • ADV_DIRECT_IND:特定デバイスから接続可能

SCAN_REQ

 セントラルデバイスがペリフェラルデバイスからアドバタイジング・パケットを受け取った時に、アドバタイジング・パケットのペイロードのデータだけでは情報が不足している場合、 スキャン・リクエスト(SCAN_REQ)パケットを送信します。

f:id:akanuma-hiroaki:20180414135829p:plain

SCAN_RSP

 ペリフェラルデバイスが SCAN_REQ パケットを受け取ると、その応答として SCAN_RSP パケットを送信します。

f:id:akanuma-hiroaki:20180414135845p:plain

CONNECT_REQ

 アドバタイジング・パケットの送受信が完了し、セントラルデバイスからペリフェラルデバイスに対して接続要求が行われると、 CONNECT_REQ パケットが送信され、接続状態に切り替わります。セントラルとペリフェラルはそれぞれマスター(Master)とスレイブ(Slave)になり、 Wireshark での Source と Definition のデバイス表示も、 MAC アドレス表示から Master, Slave に切り替わります。

f:id:akanuma-hiroaki:20180414135907p:plain

LL_VERSION_IND

 リンク層の接続制御に使われるロジカル・リンク・コントロール PDU では、オペコード(Opcode)でその種別が表されています。 LL_VERSION_IND はコントローラのバージョン番号を示すためのものです。

f:id:akanuma-hiroaki:20180414135940p:plain

Exchange MTU Request/Response

 Exchange MTU Request/Reponse はアトリビュート・プロトコルの最大長(MTU: Maximum Transfer Unit)を決定するために使われます。まずはクライアント側から自身が扱える ATT_MTU をサーバ側に通知します。そのレスポンスとしてサーバからはサーバ側で扱える ATT_MTU を返します。

f:id:akanuma-hiroaki:20180414140002p:plain

f:id:akanuma-hiroaki:20180414140113p:plain

Read Request/Response

 サーバのアトリビュートをクライアントから読み取るには Read Request/Response が使われます。 Read リクエストには下記の5種類があります。

  • Read
  • Read Multiple
  • Read Blob
  • Read By Type
  • Read By Group Type

 Read By Group Type ではアトリビュートのグループを読み出します。下記の例では GATT の Primary Service Declaration を読み取っています。

f:id:akanuma-hiroaki:20180414140933p:plain

f:id:akanuma-hiroaki:20180414141110p:plain

 Read By Type ではアトリビュート・タイプを指定した読み出しに使用します。下記は GATT の Characteristic Declaration の例です。

f:id:akanuma-hiroaki:20180414141256p:plain

f:id:akanuma-hiroaki:20180414141334p:plain

 アトリビュートが存在しない場合は下記のように Error Response が返ってくるようです。

f:id:akanuma-hiroaki:20180414142229p:plain

 Read ではアトリビュート・ハンドルを指定して値を読み出します。

f:id:akanuma-hiroaki:20180414143245p:plain

f:id:akanuma-hiroaki:20180414143330p:plain

 長さが ATT_MTU を超えるアトリビュート・バリューを持つアトリビュートはロング・アトリビュートと呼ばれ、その値を読み出す場合には Read Blob が使われます。

f:id:akanuma-hiroaki:20180414143734p:plain

f:id:akanuma-hiroaki:20180414220339p:plain

Find Information Request/Response

 アトリビュート・ハンドルの範囲を指定してアトリビュート・ハンドルとアトリビュート・タイプを読み出すには Find Information が使われます。アトリビュートのタイプとバリューが指定できる Find By Type Value リクエストもありますが、今回は使われていなかったようです。下記は Find Information の例です。

f:id:akanuma-hiroaki:20180414141457p:plain

f:id:akanuma-hiroaki:20180414141531p:plain

Write Request/Response

 アトリビュート・ハンドルを指定して書き込みを行うには Write が使用されます。

f:id:akanuma-hiroaki:20180414141757p:plain

f:id:akanuma-hiroaki:20180414141902p:plain

 Write には他にも下記の4種類がありますが、今回は使われていなかったようです。

  • Prepare Write
  • Execute Write
  • Write Command
  • Signed Write Command

Handle Value Notification

 サーバからクライアントへの通知には Handle Value Notification が使われます。

f:id:akanuma-hiroaki:20180414143716p:plain

Connection Parameter Update Request

 コネクションのパラメータを変更する場合には Connection Parameter Update Request が送信されます。

f:id:akanuma-hiroaki:20180414144317p:plain

 オペコードとしては LL_CONNECTION_UPDATE_REQ が使われます。

f:id:akanuma-hiroaki:20180414144438p:plain

 パラメータが変更されると Connection Parameter Update Response が返されます。

f:id:akanuma-hiroaki:20180414144544p:plain

LL_CHANNEL_MAP_REQ

 コネクションのチャンネル・マッピングを変更する際には LL_CHANNEL_MAP_REQ というオペコードが使われます。

f:id:akanuma-hiroaki:20180414144818p:plain

LL_TERMINATE_IND

 コネクションを切断するには LL_TERMINATE_IND オペコードが使われます。

f:id:akanuma-hiroaki:20180414145133p:plain

まとめ

 ざっとですが手持ちの一デバイスで確認できた内容を書いてみました。 BLE デバイスを使ったサービスを開発する場合に、BLEデバイスとの接続に問題がありそうな場合には、今回のように接続シーケンスを確認することで何か解決への手がかりが見つかるかもしれません。使用方法もとても簡単なので、勉強のためにも今後色々と使ってみたいと思います。

 今回は下記のサイトを参考にさせていただきました。

blog.reinforce-lab.com

dsas.blog.klab.org

BLE Sniffer + Wireshark で BLE パケットをスニッフィングする

 BLE デバイスを使ったシステムを開発していると、BLE デバイスがどんな状態かを確認したいことが時々あります。アドバタイズメントを出しているかは LightBlue などのツールでも確認できますが、通信状態の詳細はわかりませんし、接続後の状態は接続しているデバイス側でデバッグ用の対応を入れる必要があります。

 そこで今回は、 BLE Sniffer を使ってみました。BLE Sniffer を使うと周囲のBLEデバイスの通信状況を確認することができますし、 Wireshark と連携することでさらに詳しくパケットを確認することができます。

 今回使ったのは Adafruit の Blueftuit LE Sniffer です。

Bluefruit LE Sniffer - Bluetooth Low Energy (BLE 4.0) - nRF51822 - v3.0
https://www.adafruit.com/product/2269

f:id:akanuma-hiroaki:20180407194943j:plain:w450

 スイッチサイエンスにも掲載はありましたが在庫切れだったので、 mouser で購入しました。

https://www.mouser.jp/ProductDetail/Adafruit/2269?qs=%2fha2pyFadujCcMq4sbQCcNuQKfV2ZrTBUyTuH62%2f2DAJK9B%252b%2f1bGJ3muPYq2CyZ9

www.switch-science.com

ユーティリティのインストールなど

 基本的な使用手順は下記のイントロダクションページで紹介されています。

Introducing the Adafruit Bluefruit LE Sniffer
https://learn.adafruit.com/introducing-the-adafruit-bluefruit-le-sniffer

 Windowsユーザの方であれば上記ページにて紹介されている手順通りに Nordic の公式ユーティリティをインストールして使用できるのですが、 Mac には対応していないので、 Mac ユーザ向けのページが別途用意されています。

OS X Support
https://learn.adafruit.com/introducing-the-adafruit-bluefruit-le-sniffer/os-x-support

 上記ページで Roland King さん提供の OS X 用のユーティリティが紹介されていますので、こちらを使用します。

nrf-ble-sniffer-osx
https://sourceforge.net/projects/nrfblesnifferosx/

 インストール手順や使用方法は上記プロダクトの Wiki ページに記載されていますので、そちらに従います。

https://sourceforge.net/p/nrfblesnifferosx/wiki/ble_sniffer/

 インストールはプロダクトのページからパッケージインストーラをダウンロードして実行するだけです。

ble-sniffer-osx Download
https://sourceforge.net/projects/nrfblesnifferosx/files/latest/download

Wireshark のインストール

 BLE Sniffer 単体で利用してもある程度の利用状況はわかるのですが、パケットアナライザを使うことでより詳細にパケットの状況を確認することができます。 Bluefruit LE Sniffer では Wireshark と連携することができるようになっています。

Wireshark · Go Deep.

 上記サイトからダウンロードしてインストールするのですが、 Bluefruit LE Sniffer の OS X Support ページには下記のように記載があります。 

Be sure to download Wireshark version 2.0.x NOT the new 2.2.7 that was released June 2017

 Wireshark の新しいバージョンではなく、 2.0系を使うようにということなので、下記から過去のバージョンを探します。

Wireshark All Versions
Index of /pub/network/security/wireshark/osx/all-versions

 2.0系の最新バージョンは 2.0.16 のようなので、こちらをダウンロードしてインストールします。

Wireshark 2.0.16 Intel 64.dmg
http://ftp.yz.yamagata-u.ac.jp/pub/network/security/wireshark/osx/all-versions/Wireshark%202.0.16%20Intel%2064.dmg

 下記のように tshark コマンドが使えるようになっていれば正しくインストールされています。

$ tshark -v
TShark (Wireshark) 2.0.16 (v2.0.16-0-gd60ab50 from refs/heads/master-2.0)

Copyright 1998-2017 Gerald Combs <gerald@wireshark.org> and contributors.
License GPLv2+: GNU GPL version 2 or later <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Compiled (64-bit) with libpcap, without POSIX capabilities, with libz 1.2.5,
with GLib 2.36.0, with SMI 0.4.8, with c-ares 1.12.0, with Lua 5.2, with GnuTLS
3.4.17, with Gcrypt 1.7.7, with MIT Kerberos, with GeoIP.

Running on Mac OS X 10.13.3, build 17D102 (Darwin 17.4.0), with locale
C/UTF-8/C/C/C/C, with libpcap version 1.8.1 -- Apple version 79.20.1, with libz
1.2.11, with GnuTLS 3.4.17, with Gcrypt 1.7.7.
Intel(R) Core(TM) i7-4770HQ CPU @ 2.20GHz (with SSE4.2)

Built using llvm-gcc 4.2.1 (Based on Apple Inc. build 5658) (LLVM build
2336.9.00).

スニッフィング実行

 必要なもののインストールが終わったので、スニッフィングを実行してみたいと思います。ユーティリティは Applications ディレクトリ内で下記のようなアイコンで表示されますので、起動します。

f:id:akanuma-hiroaki:20180403072004p:plain

 BLE Sniffer を接続していないと下記のような表示になります。

f:id:akanuma-hiroaki:20180403000143p:plain

 ここで BLE Sniffer を接続します。接続して少し待つとスニッフィングが始まり、アドバタイズメントを出しているデバイスのリストが表示されます。項目としては、デバイス名、MACアドレス、電波強度(RSSI)、アドバタイズ用の3つのチャネルそれぞれでのインターバルが表示されます。

f:id:akanuma-hiroaki:20180403010717p:plain

 デバイスリストの中から特定のデバイスについて詳しく見たい時には、そのデバイスを選択して Sniff Device をクリックします。

f:id:akanuma-hiroaki:20180407191901p:plain

 すると「Currently Sniffing」というフィールドの表示が追加になり、スニッフィング中のデバイスの情報が表示されます。

f:id:akanuma-hiroaki:20180403010958p:plain

 デバイスの情報までは確認できたので、パケットの状況を詳しく見るために、 Wireshark と連携してみます。 Capture to Wireshark ボタンをクリックします。

f:id:akanuma-hiroaki:20180407192235p:plain

 すると Wireshark が起動し、 スニッフィングしたパケットが Wireshark で表示されます。

f:id:akanuma-hiroaki:20180403011507p:plain

 起動時のデフォルトでは時系列で並んだ一番古いパケットの部分が表示されていますが、メニューの下記ボタンを押すことで常に最新のパケットが表示されるようにスクロールされます。

f:id:akanuma-hiroaki:20180407192436p:plain

 下側のペインではBLEパケットのさらに詳しい情報が表示されます。一つ目はフレームの詳細です。

f:id:akanuma-hiroaki:20180403011538p:plain

 二つ目は BLE Sniffer のメタデータです。

f:id:akanuma-hiroaki:20180403011601p:plain

 三つ目はデータリンク層の情報です。

f:id:akanuma-hiroaki:20180403011625p:plain

まとめ

 BLEデバイスを使うサービスを開発するときは、BLEデバイス側に問題があるのか、アプリ側に問題があるのかの切り分けが難しい場合がありますが、このツールを使うことでアドバタイズメントの状況や詳しいパケットの情報が見られるので、問題の切り分けがしやすくなりそうです。BLEでどういったパケットがどういう順番でやり取りされるかを知らないと見てもわからない部分もありますが、逆にそれを理解するためにもこういったツールは有効かと思います。