【IoT】LTE-M Shield for ArduinoでCBOR形式データをSORACOMに送信してみた
SORACOMバイナリーパーサーが、CBORに対応したと情報を得ました。そこで、LTE-M Shield for Arduino(BG96)を用い、CBOR形式データをSORACOMに送信するプログラムを作成しました。また、通信量や消費電力量の比較試験を実施した結果、CBORの特徴が見えてきました。本記事では、プログラム全文、CBORの特徴についてまとめます。
背景と目的
この記事から、SORACOMのバイナリパーサーがCBORに対応したという情報を得ました。本記事執筆時点で、Arduinoマイコンを用いたCBOR送信の事例はあまり出てきません。そこで、LTE-M Shield for Arduinoを用いたCBOR送信を目指します。また、各通信プロトコル、各データ形式ごとに、消費電力量と通信量の比較試験を実施し、CBORの特徴を整理します。
詳細
1. そもそもCBORとは?
詳細はこちらをご覧ください。
実務上での特徴としては、例えば{"temp":23.4}というデータをSORACOMに送りたい場合、従来のバイナリー形式だと、マイコン側で23.4という数値のみを送り、クラウド側のバイナリーパーサーで、"temp"という変数名情報を付与し、JSON形式に変換していました。この変数名情報の付与が、案外手間でした。
一方、CBORの場合は、マイコン側から23.4だけでなく"temp"も送信するのですが、いい感じにデータ量を落としてくれるみたいです。SORACOMバイナリーパーサーで@cborと定義すると、JSON形式に変換できます。簡単。
CBORは、従来バイナリーよりも通信量は若干多くなるものの、変数名の定義が楽という特徴があります。バイナリーとJSONの中間のような形式ですね。
2. ArduinoでCBORを扱うためTinyCBORライブラリを用いる
TinyCBORというライブラリを用いると実装が楽です。
3. ArduinoでCBORを送信するプログラム全文
LTE-M Shield for Arduinoのサンプルプログラム(send_uptime_with_soracom.ino)と、TinyCBORのサンプルプログラム(cbor_encode_sample.ino)を組み合わせ、起動時間をSORACOMにUDPでCBOR送信するプログラムを作りました。
なお、LTE-M Shield for ArduinoxUDP送信については、下記記事をご参照ください。
また、setup()関数を見直し、消費電力量と通信量を低減する方法に関しては、下記記事をご参照ください。
以下は、プログラム全文です。setup()関数を見直しています。
#define CONSOLE Serial
#define INTERVAL_MS (60000)
#define ENDPOINT "uni.soracom.io"
#define SKETCH_NAME "send_uptime_with_soracom_UDP_CBOR"
#define VERSION "1.0"
/* for CBOR */
#include <tinycbor.h> // https://intel.github.io/tinycbor/current/a00046.html
//#include <float.h>
/* for LTE-M Shield for Arduino */
#define RX 10
#define TX 11
#define BAUDRATE 9600
#define BG96_RESET 15
#define TINY_GSM_MODEM_BG96
#include <TinyGsmClient.h>
#include <SoftwareSerial.h>
SoftwareSerial LTE_M_shieldUART(RX, TX);
TinyGsm modem(LTE_M_shieldUART);
TinyGsmClient ctx(modem);
void setup() {
CONSOLE.begin(9600);
CONSOLE.println();
CONSOLE.print(F("Welcome to "));
CONSOLE.print(SKETCH_NAME);
CONSOLE.print(F(" "));
CONSOLE.println(VERSION);
/*
CONSOLE.print(F("resetting module "));
pinMode(BG96_RESET, OUTPUT);
digitalWrite(BG96_RESET, LOW);
delay(300);
digitalWrite(BG96_RESET, HIGH);
delay(300);
digitalWrite(BG96_RESET, LOW);
CONSOLE.println(F(" done."));
*/
LTE_M_shieldUART.begin(BAUDRATE);
/*
CONSOLE.print(F("modem.restart()"));
modem.restart();
CONSOLE.println(F(" done."));
*/
CONSOLE.print(F("modem.init()"));
modem.init();
CONSOLE.println(F(" done."));
/*
CONSOLE.print(F("modem.getModemInfo(): "));
String modemInfo = modem.getModemInfo();
CONSOLE.println(modemInfo);
*/
CONSOLE.print(F("waitForNetwork()"));
while (!modem.waitForNetwork()) CONSOLE.print(".");
CONSOLE.println(F(" Ok."));
CONSOLE.print(F("gprsConnect(soracom.io)"));
modem.gprsConnect("soracom.io", "sora", "sora");
CONSOLE.println(F(" done."));
CONSOLE.print(F("isNetworkConnected()"));
while (!modem.isNetworkConnected()) CONSOLE.print(".");
CONSOLE.println(F(" Ok."));
/*
CONSOLE.print(F("My IP addr: "));
IPAddress ipaddr = modem.localIP();
CONSOLE.println(ipaddr);
*/
}
void loop() {
uint32_t uptime_sec = millis() / 1000;
CONSOLE.print(F("uptime_sec: "));
CONSOLE.println(uptime_sec);
// CBOR
// バイナリパーサーフォーマット: @cbor
uint8_t cborData[128]; // この数字が大きいと、UDP接続がfalseになる。
// Initialize TinyCBOR library.
TinyCBOR.init();
// Assign buffer to encoder.
TinyCBOR.Encoder.init(cborData, sizeof(cborData));
// Typically, the first element is an array or a map.
TinyCBOR.Encoder.create_map(1); //データの数を指定
{
TinyCBOR.Encoder.encode_text_stringz("upTime");
TinyCBOR.Encoder.encode_uint(millis() / 1000);
//TinyCBOR.Encoder.encode_text_stringz("upTime");
//TinyCBOR.Encoder.encode_int(millis() / 1000); // int
//TinyCBOR.Encoder.encode_text_stringz("upTimeFloat");
//TinyCBOR.Encoder.encode_float(millis() / 1000.0); // float
}
TinyCBOR.Encoder.close_container();
CONSOLE.print(F("cborBufferSize: "));
CONSOLE.println(TinyCBOR.Encoder.get_buffer_size());
// SORACOMに接続
int mux = 0;
modem.sendAT(GF("+QIOPEN=1,"), mux, GF(",\"UDP\",\""), ENDPOINT, GF("\","), 23080, GF(",0,0"));
modem.waitResponse();
// 接続の応答を待つ
uint32_t timeout_ms = 15 * 1000; // ライブラリでは150秒だが、長いため15秒とする
if (modem.waitResponse(timeout_ms, GF(GSM_NL "+QIOPEN:")) != 1) {
CONSOLE.println(F("failed."));
delay(3000);
return;
}
// 送信
CONSOLE.println(F("send"));
ctx.write(cborData, TinyCBOR.Encoder.get_buffer_size());
ctx.stop();
delay(INTERVAL_MS);
}
4. プログラムのポイント
送信するデータ数に応じcreate_map()の数字を変化させ、encode_text_stringz("変数名")で変数名を定義し、encode_uint()、encode_int()、encode_float()などで送りたい型に合わせてデータを入れます。変数名と代入される値と型の関係が、比較的わかりやすいですね。
TinyCBOR.Encoder.create_map(1); //データの数を指定
{
TinyCBOR.Encoder.encode_text_stringz("upTime");
TinyCBOR.Encoder.encode_uint(millis() / 1000);
}
TinyCBOR.Encoder.close_container();
5. 消費電力量と通信量の試験結果
通信プロトコルとデータ送信形式による、消費電力量と通信量を知るため、比較試験を実施しました。試験条件は下記4パターンです。
・HTTPxテキスト形式
・TCPxバイナリー
・UDPxバイナリー
・UDPxCBOR(←今回新たに試験実施)
その結果を表にまとめました。
開始〜終了の時間、消費電力量、通信量とも5回平均の値です。
UDPxCBORは、UDPxバイナリ16進数と比較して若干通信量が増加するものの、TCPよりも通信量が小さく、実務上問題にはならないケースが多いと思います。また、消費電力量も差がないですね。CBOR、気に入りました。
まとめと今後の課題
LTE-M Shield for ArduinoでUDPxCBOR送信するプログラムを作成できました。UDPxバイナリ送信と比較し、消費電力量は同等で通信量増加も顕著ではないことがわかりました。
また、SORACOMバイナリーパーサーの記述も@cborのみであり、シンプルでわかりやすいこともわかりました。
従来バイナリーの場合、1回の送信で送るデータフォーマットは同一形式である必要がありましたが、CBORの場合、データフォーマットをマイコン側で自由に設計できます。
これらの特徴を活かし、応用できそうな事例が1つ思い浮かんでおり、実装でき次第、記事にしたいと思います。
参考
・CBORを知ることになった記事です。大変参考になりました。
・LTE-M Shield for ArduinoでUDP送信を実現する方法はこちら。
・LTE-M Shield for Arduinoのsetup()関数を見直した事例です。
・TinyCBORライブラリ。