LoginSignup
1

More than 3 years have passed since last update.

SORACOM InventoryにESP8266で接続する

Last updated at Posted at 2020-05-20

概要

ESP8266 (Arduino framework) と、WakaamaNodeライブラリを用い、SORACOM Inventoryへ接続するための手順です。

SORACOM Inventoryは、SORACOMが提供するOMA LwM2Mプロトコルで通信を行う、デバイス管理サービスです。

SORACOM上の他のサービス (SORACOM Beam, Funk, Funnel, Harvest Data) と連携させるためのゲートウェイにもなり、一般的なデバイス管理だけでなく、データ収集なども実現できます。

また、事前を登録をしていれば、SIMを接続するが必要なく、例えばWiFiのみを搭載した機器などでSORACOMサービスを利用するときには、特に有用です。

WakaamaNodeは、組み込み向けにLwM2Mプロトコルを実装したライブラリで、デフォルトでESP8266をサポートしていますが、実際の運用のためにはいくつかの注意事項がありますので、ここで手順を追って説明します。

なおESP32でも動けば、M5StickCなどでも使えるのですが、ESP8266とのSDKの差分(mbedtls周り)からフォローできていません。追加情報があれば、まとめたいと思います。

手順

SORACOMでデイバス登録

今回は、実装を簡単にするため、ESP8266からブートストラップは行いません。
事前にSORACOM Console上などでデバイス登録を行います。

SORACOM Console上でデバイス登録時の画面

デバイスIDとシークレットキーは後で利用します。

PlatformIOのセットアップ

WakaamaNodeが、PlatformIO環境でのライブラリとして提供されているため、これを用います。
Arduino IDEでも必要なファイルをコピーしてくれば利用可能ですが、ソースの複雑さなど、ある程度の規模になりますので、PlatformIO環境での開発をおすすめします。

ライブラリのインストール

検索すればヒットするので、そのままインストールします。

Wakaama Nodeライブラリをインストール

ビルドオプションの設定

ライブラリ内で、"wakaama_config.h"を参照しているのですが、PlatformIOのグローバルライブラリとしてインストールすると、プロジェクトソースから参照してくれないのでこれを解決します。

%USERPROFILE%.platformio\lib\WakaamaNode_ID2964\src\include ディレクトリにwakaama_config.hファイルを作成を作成し、内容に意味のないファイルにしてください。

wakaama_config.h
#pragma once

空ファイルだと、そもそも include されず、"No such file なんたら"というエラーになるので注意。

本来、wakaama_config.h内で設定すべきだったビルドオプションを、プロジェクトのビルドフラッグとして追加します。

platformio.ini
; (省略)
[env:ほげほげ]
; ビルドフラッグとして下記を追加
build_flags =
    -DLWM2M_BIG_ENDIAN
    -DLWM2M_WITH_DTLS
    -DLWM2M_CLIENT_MODE
    -DLWM2M_DEVICE_WITH_REBOOT
    -DLWIP
    -D PIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH
    -Wno-pointer-arith

SORACOM Inventoryに接続

SORACOM公開のドキュメントをもとに接続を行います。

SORACOMコンソールで追加したデバイス情報をinclude/credentials.hに、WiFiの接続情報をinclude/wifi_config.hに記載します。

wifi_config.h
#pragma once

#define WIFI_SSID "your_ssid"
#define WIFI_PASSWORD "your_password"

シークレットキーは、登録時の文字列をBase64デコードしたバイナリ列を用います。
変換時のEndian設定によって、結果が変わるので注意してください。
(WSL上にて echo <secretKey> | base64 --decode | hexdump した結果は、さらにEndian変換する必要がありました。)

credentials.h
#pragma once

#define DEVICE_ID "d-xxxxxxxxxxxxxxxxxxx"
#define DEVICE_SECRET_KEY_LEN 16
const char DEVICE_SECRET_KEY_BYTE_ARRAY[DEVICE_SECRET_KEY_LEN] = {
    0x00, 0x01, 0x02, 0x03,
    0x04, 0x05, 0x06, 0x07,
    0x08, 0x09, 0x10, 0x11,
    0x12, 0x13, 0x14, 0x15,};

#define DEVICE_SECRET_KEY DEVICE_SECRET_KEY_BYTE_ARRAY

接続先ホストは、WakaamaNodeライブラリにはDNS Lookup機能がありませんので、事前にIPアドレスを調べたものを利用します。

次のコードを、ESP8266にアップロードすれば、SORACOM Inventoryへ接続できるはずです。
動作状況の把握が困難ですので、デバッグ情報を追加したソースを、こちらのリンクから取得してください。

main.cpp
#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <lwm2m/connect.h>

#include "credentials.h"
#include "wifi_config.h"

// 接続先ホストは、IPアドレスを用いる
char coap_uri[] = "coaps://aaa.bbb.ccc.ddd:5684";

#define shortServerId 123
LwM2MConnect context("connect_soracom_inventory_device");

void setup() {
    // デバイス情報の設定
    context.deviceInstance.manufacturer = "some manufacturer";
    context.deviceInstance.model_name = "the model";
    context.deviceInstance.device_type = "led";
    context.deviceInstance.firmware_ver = "1.0";
    context.deviceInstance.serial_number = "140234-645235-12353";

    // WiFi接続
    WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
    int wifi_process_count = 0;
    while (WiFi.status() != WL_CONNECTED) delay(500);

    // サーバー接続設定
    context.add_server(shortServerId, coap_uri, 100, false);
    context.use_dtls_psk(shortServerId, DEVICE_ID, DEVICE_SECRET_KEY, DEVICE_SECRET_KEY_LEN);

    delay(1000);
}

void loop() {
    // LwM2M通信を処理
    context.process();

    delay(100);
}

デバッグ情報付きのソースであれば、シリアルコンソールで次のように表示されます。

State: STATE_INITIAL -> STATE_REGISTER_REQUIRED2
State: STATE_REGISTER_REQUIRED2 -> STATE_REGISTERING
State: STATE_REGISTERING -> STATE_READY
Connection change to: Established

また、SORACOMコンソール上でもデバイスがオンライン表示になります。

SORACOMコンソール上のオンラインデバイス

注意

LwM2MはCoAPプロトコルを用い、CoAPプロトコルはUDPでの実装になっています。
そのため、不意の通信切断を検知することができず、接続時のライフタイム(サーバ側の設定も関連するかもしれません)の間、セッションが残っているようにふるまいます。
この状態で、コンフィグの異なるデバイスが登録しようとするとSTATE_BOOTSTRAP_REQUIRED状態になることがあります。

State: STATE_INITIAL -> STATE_REGISTER_REQUIRED2
State: STATE_REGISTER_REQUIRED2 -> STATE_REGISTERING
State: STATE_REGISTERING -> STATE_BOOTSTRAP_REQUIRED

この時は、しばらく時間を空けて試すようにしてください。

予約済みオブジェクトモデルの利用

順番が前後しますが、LwM2Mではオブジェクトモデルを用いた通信を行います。

WakaamaNodeライブラリ内で、オブジェクトID 1: LwM2M ServerとID 3: Deviceが登録されるようになっています。

これだけだと使い勝手が悪いので、予約済みオブジェクトID: 3202 Analog Inputを追加します。

コード差分

コード

ID3202オブジェクトを利用するために、ヘッダを読み込みます。
オブジェクトとインスタンスを初期化し、オブジェクトにインスタンスを追加します。
オブジェクトは、どのような内容を通信するかの"規約"に相当し、インスタンスはその実体に相当すると捉えてください。

インスタンスの"R"eadオペレーションで定義された、各メンバの型に応じた初期化を行います。

main.cpp
(略)
+ #include <lwm2mObjects/3202.h>
+ KnownObjects::id3202::object analogInputObject;
+ KnownObjects::id3202::instance analogInputInstance;
+ 
+ KnownObjects::id3202::ApplicationTypeType applicationType;
+ #define ANALOG_INPUT_INSTANCE_ID 0
(略)
void setup() {
(略)
+   analogInputInstance.id = ANALOG_INPUT_INSTANCE_ID;
+   strcpy((char*)applicationType.data, "Sensor Values");
+   analogInputInstance.ApplicationType = applicationType;
+   analogInputInstance.MaxRangeValue = 100.0f;
+   analogInputInstance.MinRangeValue = -100.0f;
(略)
}

"W"riteオペレーションは、オブジェクトのverifyWriteメソッドによって定義します。

main.cpp
(略)
void setup() {
(略)
+   analogInputObject.verifyWrite = [](KnownObjects::id3202::instance* instance, uint16_t resource_id) {
+       // SORACOM Inventoryからデータを受信したときの処理を実装する
+       if (instance->id == ANALOG_INPUT_INSTANCE_ID) {
+           // Writeリクエストを処理したときは true, 拒否するときは false を返す。
+           switch ((KnownObjects::id3202::RESID)resource_id) {
+               case KnownObjects::id3202::RESID::ApplicationType:
+               // 未実装のためfalseを返して終了
+               return false;
+           }
+       }
(略)
}

"E"xecuteオペレーションは、インスタンスのメンバにvoid(Lwm2mObjectInstance*, lwm2m_context_t*)関数ポインタを代入します。

main.cpp
(略)
void setup() {
(略)
+   analogInputInstance.ResetMinandMaxMeasuredValues = [](Lwm2mObjectInstance*, lwm2m_context_t*) {
+       minmaxResetted = true;
+   };
(略)
}

コンテキストにadd_serverする前にオブジェクト、インスタンスを登録します。

main.cpp
(略)
void setup() {
(略)
+   analogInputObject.addInstance(CTX(context), &analogInputInstance);
+   analogInputObject.registerObject(CTX(context), false);
context.add_server(shortServerId, coap_uri, 100, false);
(略)
}

10秒に一回センサー値を更新するなど、接続確立以降に"R"eadオペレーションを行う場合は、インスタンスに新しい値を代入し、オブジェクトのresChangedメソッドを呼び出します。

main.cpp
+ void updateAnalogValue() {
+   // アナログ値としてランダムな値を返す
+   // 実運用ではセンサーからの出力などを用いる
+   float value = (random(20000) - 10000) / 100.0f;
+   analogInputInstance.AnalogInputCurrentValue = value;
+   analogInputObject.resChanged(CTX(context),
+                                analogInputInstance.id,
+                                (uint16_t)KnownObjects::id3202::RESID::AnalogInputCurrentValue);
+ }

無事に登録されるとSORACOMコンソールでもAnalog Inputインスタンスが追加で見れるようになります。

Analog Inputインスタンス

しかし、このままでは正しい数値がとれていません。SORACOMサーバでは、数値データを64bitで処理していますが、WakaamaNodeライブラリでは float(32bit)として処理しているためです。

64bit対応

3202.hmain.cpp内のfloatdoubleに変更することで正常に表示されます。

コード差分

コード

Analog Inputインスタンス (64bit)

DeepSleep対応

先述の注意に記載したように、不意な切断を検知する方法がありません。
DeepSleepを用いるときは、明確に切断してからスリープすることをお勧めします。

main.cpp
loop () {
    context.process();
    delay(100);

+   if (context.is_connected()) {
+       updateAnalogValue();
+       delay(200);
+ 
+       lwm2m_network_close(CTX(context));
+       ESP.deepSleep(10 * 1000 * 1000);
+       delay(100);
+   }
}

カスタムオブジェクトの利用

SORACOM Inventoryでは予約済みのオブジェクト以外にも、カスタムオブジェクトを定義、利用することが可能です。

カスタムオブジェクトの構造は、通常XMLで定義されますが、SORACOMではJSON形式での定義にも独自対応していますのでこれを利用します。

SORACOMコンソール上で、SORACOM Inventory -> オブジェクトモデルと開き、オブジェクトモデルの追加を行います。

custom_object.json
{
    "id": 40001,
    "name": "CustomObjectSample",
    "description": "Sample object",
    "multiple": false,
    "mandatory": false,
    "resources": {
        "1": {
            "id": 1,
            "name": "Message",
            "operations": "W",
            "multiple": false,
            "mandatory": true,
            "type": "STRING",
            "rangeEnumeration": "",
            "units": "",
            "description": "Printing message for device console"
        },
        "2": {
            "id": 2,
            "name": "LED",
            "operations": "RW",
            "multiple": false,
            "mandatory": true,
            "type": "BOOLEAN",
            "rangeEnumeration": "",
            "units": "",
            "description": "Get or configure connected LED On/Off"
        },
        "3": {
            "id": 3,
            "name": "Analog Output",
            "operations": "W",
            "multiple": false,
            "mandatory": true,
            "type": "INTEGER",
            "rangeEnumeration": "0-255",
            "units": "",
            "description": "Analog output without feedback"
        },
        "4": {
            "id": 4,
            "name": "Sensor Value",
            "operations": "R",
            "multiple": false,
            "mandatory": true,
            "type": "FLOAT",
            "rangeEnumeration": "",
            "units": "",
            "description": "Sensor value"
        },
        "5": {
            "id": 5,
            "name": "Update",
            "operations": "E",
            "multiple": false,
            "mandatory": true,
            "type": "STRING",
            "rangeEnumeration": "",
            "units": "",
            "description": "Update sensor value immediately"
        }
    }
}

Data TypeはOMA-TS-LightweightM2M-V1_0_2-20180209-AのAppendix Cに記載されています。
IDも用途によってどの範囲を使うか定義されていますので、空いているところを利用しましょう。

コードからも同じオブジェクトを利用できるようにjsonファイルからCヘッダファイルを生成するスクリプトを作成しました。
mandatorymultiple等の設定には対応していません。

node extra/generate_custom_object_header.js src/custom_object.json
> Generated 'src/custom_object.h'

使い方はAnalog Inputオブジェクトと、同じです。

コード

SORACOMコンソールから、鉛筆アイコン再生アイコンをクリックすることで、Write, Executeオペレーションをデバイスに実行させることができます。

最後にデバイスのシリアルコンソールログを載せます。

 ets Jan  8 2013,rst cause:2, boot mode:(3,6)

load 0x4010f000, len 3456, room 16 
tail 0
chksum 0x84
csum 0x84
va5432625
~ld
WiFi connecting _____.... connected
Add server, Success
Use DTLS, Success
Server uri: coaps://18.179.186.202:5684
Socket count: 1
Connection: Disconnected
State: STATE_INITIAL
Error: COAP_NO_ERROR

-------- First process -------
TargetP: 0x3fff565c
secObjInstID: 0x       0
secInst: 0x3fff53dc
Connecting to coaps://18.179.186.202:5684 on 123
decodeUri: 2
Host: 18.179.186.202, Port: 5684
Security Mode: 3
Network Type: 0
AtoN: 1, 3401233170
Connection: 0x3fff65bc

State: STATE_INITIAL -> STATE_REGISTER_REQUIRED2
State: STATE_REGISTER_REQUIRED2 -> STATE_REGISTERING
Update analog value
value is 87.660004
State: STATE_REGISTERING -> STATE_READY
Connection change to: Established
Update sensor value now
value is -28.540001
Analog output: -32
Receive message: hello

まとめ

ESP8266でSORACOM Inventoryに接続するために次のことを行いました。

  1. SORACOMコンソールでデバイスの追加
  2. カスタムオブジェクトの追加
  3. WakaamaNodeライブラリの利用環境構築
  4. カスタムオブジェクトから、Cヘッダファイルの生成
  5. LwM2Mを用いた通信

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1