概要
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上などでデバイス登録を行います。
デバイスIDとシークレットキーは後で利用します。
PlatformIOのセットアップ
WakaamaNodeが、PlatformIO環境でのライブラリとして提供されているため、これを用います。
Arduino IDEでも必要なファイルをコピーしてくれば利用可能ですが、ソースの複雑さなど、ある程度の規模になりますので、PlatformIO環境での開発をおすすめします。
ライブラリのインストール
検索すればヒットするので、そのままインストールします。
ビルドオプションの設定
ライブラリ内で、"wakaama_config.h"を参照しているのですが、PlatformIOのグローバルライブラリとしてインストールすると、プロジェクトソースから参照してくれないのでこれを解決します。
%USERPROFILE%.platformio\lib\WakaamaNode_ID2964\src\include ディレクトリにwakaama_config.hファイルを作成を作成し、内容に意味のないファイルにしてください。
#pragma once
空ファイルだと、そもそも include されず、"No such file なんたら"というエラーになるので注意。
本来、wakaama_config.h内で設定すべきだったビルドオプションを、プロジェクトのビルドフラッグとして追加します。
; (省略)
[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に記載します。
#pragma once
#define WIFI_SSID "your_ssid"
#define WIFI_PASSWORD "your_password"
シークレットキーは、登録時の文字列をBase64デコードしたバイナリ列を用います。
変換時のEndian設定によって、結果が変わるので注意してください。
(WSL上にて echo <secretKey> | base64 --decode | hexdump
した結果は、さらにEndian変換する必要がありました。)
#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へ接続できるはずです。
動作状況の把握が困難ですので、デバッグ情報を追加したソースを、こちらのリンクから取得してください。
#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コンソール上でもデバイスがオンライン表示になります。
注意
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オペレーションで定義された、各メンバの型に応じた初期化を行います。
(略)
+ #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
メソッドによって定義します。
(略)
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*)
関数ポインタを代入します。
(略)
void setup() {
(略)
+ analogInputInstance.ResetMinandMaxMeasuredValues = [](Lwm2mObjectInstance*, lwm2m_context_t*) {
+ minmaxResetted = true;
+ };
(略)
}
コンテキストにadd_server
する前にオブジェクト、インスタンスを登録します。
(略)
void setup() {
(略)
+ analogInputObject.addInstance(CTX(context), &analogInputInstance);
+ analogInputObject.registerObject(CTX(context), false);
context.add_server(shortServerId, coap_uri, 100, false);
(略)
}
10秒に一回センサー値を更新するなど、接続確立以降に"R"eadオペレーションを行う場合は、インスタンスに新しい値を代入し、オブジェクトのresChanged
メソッドを呼び出します。
+ 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インスタンスが追加で見れるようになります。
しかし、このままでは正しい数値がとれていません。SORACOMサーバでは、数値データを64bitで処理していますが、WakaamaNodeライブラリでは float
(32bit)として処理しているためです。
64bit対応
3202.hとmain.cpp内のfloat
をdouble
に変更することで正常に表示されます。
DeepSleep対応
先述の注意に記載したように、不意な切断を検知する方法がありません。
DeepSleepを用いるときは、明確に切断してからスリープすることをお勧めします。
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 -> オブジェクトモデルと開き、オブジェクトモデルの追加を行います。
{
"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ヘッダファイルを生成するスクリプトを作成しました。
mandatoryやmultiple等の設定には対応していません。
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に接続するために次のことを行いました。
- SORACOMコンソールでデバイスの追加
- カスタムオブジェクトの追加
- WakaamaNodeライブラリの利用環境構築
- カスタムオブジェクトから、Cヘッダファイルの生成
- LwM2Mを用いた通信