この記事はSORACOM Advent Calendar 2023の12日目の記事です。
前日の記事は「Python だけで ソラカメ の MPEG-DASH を再生してみた」でした。
はじめに
ごきげんよう、ソラコムCREの岡田(hisaya)です。
2023年、皆さんはどんな1年だったでしょうか。私の今年のハイライトは「ニューカレドニアに旅行したら滞在中ほぼずっと雨だった」ことと「自転車でコケて右肘(※利き腕)を骨折した」ことの2点でしょうか。実はまだ骨折の固定が外れていない中この記事をしたためていたりするのですが、こんな一年だった分、きっと来年はハチャメチャにいいことがあると思うことにしています。
さて、今年のSORACOM Discovery 2023のプロトタイピングコーナーで展示したスマートコースターでも活用したメタデータサービスですが、展示物の説明に立っていると思いの外既存のソラコムユーザーの方でも「知らなかった」「ぜひ活用したい」といった声を耳にすることがありました。
独立したサービスではなくSORACOM AirやArcの付加機能なので目立つことが少ないのはある意味仕方のないことではあるのですが、使ってみると非常に便利で奥の深い機能になっています。
特にタグやユーザーデータ領域を個体管理や設定データなどの保管・配信に利用するとハードウェアや実装レベルでの変更を抑えることができたり、SORACOM APIなどと組み合わせつつデバイス側で定期的にメタデータサービスにアクセスする機構を実装して擬似的に双方向通信を実現するといった使い方もできます。
今回は主にArduino系の開発環境を利用するハードウェア(LTE-M Shield for Arduinoを接続したArduinoやM5Stackなど)をターゲットとして、メタデータサービスのタグやユーザーデータにアクセスする機構の実装例を紹介します。
なお、メタデータサービス自体の使い方やアクセスするエンドポイントの詳しい使い方はユーザードキュメントを確認してください。
免責事項
本記事内で紹介している実装を株式会社ソラコムが推奨したり、動作を保証するものではありません。実装を参考にする場合はユーザードキュメントや実際の振る舞い、ライブラリのライセンス等を確認のうえ、自身の責任において利用してください。
準備
今回の実装例ではおもに下記ライブラリを利用します。
- TinyGSM
- ArduinoHttpClient
- ArduinoJson (レスポンスのパースに必要な場合)
- WireGuard-ESP32-Arduino (SORACOM Arcを利用する場合)
なお、以降の実装はM5Stack Core2 (ESP32)とUnitCatM+GNSSモジュールの組み合わせで動作を確認しています。Arduino UNOとLTE-M Shield for Arduinoの組み合わせでも動作しましたが、ユースケースによってはメモリ消費量を考慮(調整)しないと正常に動作しない可能性がある点に注意してください。
また、通信モジュールを含むハードウェアの初期化処理などはこの記事での説明の対象外とします。
実装
それでは実装していきます。
HTTPクライアントのベース
メタデータサービスはHTTPのREST APIなので、まずは汎用的に使えるHTTPクライアントを実装していきます。
ここではClient クラス、メソッドやパス、リクエストボディを引数として受け取り、レスポンスボディを文字列として返す関数を定義します。
// ヘッダ行に下記を追加 (Arduino coreに内包)
#include "Client.h"
#define CONSOLE Serial
String httpRequest(Client& client, String host, int port, String path, String method, String contentType, String requestBody) {
CONSOLE.println("Requesting " + host);
char ERR_MSG_BUF[50] = { '\0' };
HttpClient httpClient(client, host, port);
int err = httpClient.startRequest(path.c_str(), method.c_str(), contentType.c_str(), requestBody.length(), (const byte*)requestBody.c_str());
if (err != 0) {
sprintf(ERR_MSG_BUF, "[Failed to get userdata; err code %d]\n", err);
CONSOLE.println(ERR_MSG_BUF);
httpClient.stop();
return "";
}
int statusCode = httpClient.responseStatusCode();
if (!statusCode) {
sprintf(ERR_MSG_BUF, "[Status code does not provided]");
CONSOLE.println(ERR_MSG_BUF);
httpClient.stop();
return "";
}
CONSOLE.print("Status code returned "); CONSOLE.println(statusCode);
CONSOLE.println(F("Response Headers:"));
while (httpClient.headerAvailable()) {
String headerName = httpClient.readHeaderName();
String headerValue = httpClient.readHeaderValue();
CONSOLE.println(" " + headerName + " : " + headerValue);
}
int length = httpClient.contentLength();
if (length >= 0) {
CONSOLE.print(F("Content length is: "));
CONSOLE.println(length);
}
if (httpClient.isResponseChunked()) {
CONSOLE.println(F("The response is chunked"));
}
String responseBody = httpClient.responseBody();
CONSOLE.println(F("Response:"));
CONSOLE.println(responseBody);
CONSOLE.print(F("Body length is: "));
CONSOLE.println(responseBody.length());
CONSOLE.println();
httpClient.stop();
return responseBody;
}
メタデータサービスへのアクセス
定義したHTTPクライアントのベースを利用して、メタデータサービスのリソースにアクセスします。
取得系
固有のタグやSIM情報の取得
「タグ」は、SIMもしくは所属SIMグループに対して設定可能なキーバリューストアです。
String getSubscriberTagValue(Client& client, String tagName) {
CONSOLE.println("Getting specific tag value [" + tagName + "]...");
String responseBody = httpRequest(client, "metadata.soracom.io", 80, "/v1/subscriber.tags." + tagName, "GET", "", "");
return responseBody;
}
ユーザーデータの取得
ユーザーデータは、SIMグループに所属しメタデータサービスを有効化している場合に利用可能です。任意のテキストコンテンツやJSONを配置できるので自由度が高く、SORACOM Harvest Filesなどを有効化しなくても利用できる領域です。
ちなみに、去年のAdvent Calendarでも最初はメタデータサービスのユーザーデータ領域にMicroPythonスクリプトを配置して実行していました(最終的にはSORACOM Harvest Filesにアクセスしてダウンロードしていました)。
ユーザーデータに配置できるコンテンツは、SIMグループ設定全体のサイズ制約の影響を受けます。
https://users.soracom.io/ja-jp/docs/group-configuration/configure-group/
String getUserdata(Client& client) {
CONSOLE.println("Getting userdata...");
String responseBody = httpRequest(client, "metadata.soracom.io", 80, "/v1/userdata", "GET", "", "");
return responseBody;
}
作成・更新
グループ設定で「読み取り専用」のチェックを外すとメタデータサービス経由でタグなどのリソースに対して作成・更新・削除系の操作ができるようになるため、デバイスからメタデータサービスを経由してタグやユーザーデータ領域の内容を変更できます。
下記は指定したタグのキーとバリューを更新(存在しない場合は追加)するリクエスト例です。
String putSubscriberTagValue(Client& client, String tagName, String tagValue) {
CONSOLE.println("Putting specific tag [" + tagName + "] as [" + tagValue + "]...");
String requestBody = "[{\"tagName\": \"" + tagName + "\", \"tagValue\": \"" + tagValue + "\"}]";
String responseBody = httpRequest(client, "metadata.soracom.io", 80, "/v1/subscriber/tags", "PUT", "application/json", requestBody);
return responseBody;
}
削除
指定したキーのタグを削除する場合は以下のようなリクエストとなります。
String deleteSubscriberTag(Client& client, String tagName) {
CONSOLE.println("Deleting specific tag value [" + tagName + "]...");
String responseBody = httpRequest(client, "metadata.soracom.io", 80, "/v1/subscriber/tags/" + tagName, "DELETE", "", "");
return responseBody;
}
動作チェック
ここまでに実装したメソッドを利用して、ユーザーデータやSIMタグの参照・更新・削除を試してみます。
勘のいい方はお気づきかもしれませんが、ここまでに実装してきたメソッドではClient
クラスを引数としていました。この形で引数を取っておくと、たとえばTinyGSMのTinyGsmClient
やESP32でWireGuardを有効化したWiFiClient
を渡すことで、SORACOM AirやArcの種類を問わず同一のHTTPクライアント実装でメタデータサービスにアクセスできるようになり、実装のコンパクト化やコネクティビティの変更に柔軟に対応できるようになります。
サンプルコードの全文は下記Gistに掲載しています。
https://gist.github.com/sayacom/2adcfbec8186b4a91a2b393e2dcacd35
このサンプルではM5Stack Core2をターゲットボードとし、TinyGSMによるSORACOM Airのセルラー接続とESP32-WireGuardによるSORACOM Arcの接続を同時に有効化し、それぞれの回線から同一のクライアント実装を利用してメタデータサービスにアクセスしています。
void useMetadataServiceDemo(Client& client) {
// Get userdata from metadata service
String userData = getUserdata(client);
CONSOLE.print("USERDATA : ");
CONSOLE.println(userData);
// Get subscriber specific tag value
String tagValue = getSubscriberTagValue(client, "TAGS_TO_DEVICE");
CONSOLE.print("TAGS_TO_DEVICE : ");
CONSOLE.println(tagValue);
// Put subscriber specific tag value
tagValue = getSubscriberTagValue(client, "TAGS_FROM_DEVICE");
CONSOLE.print("TAGS_FROM_DEVICE : ");
CONSOLE.println(tagValue);
String testTagValue = String(millis());
String updatedTagResult = putSubscriberTagValue(client, "TAGS_FROM_DEVICE", testTagValue);
CONSOLE.print("Updated TAGS_FROM_DEVICE: ");
CONSOLE.println(updatedTagResult);
// Delete subscriber specific tag value
result = deleteSubscriberTag(client, "TAGS_FROM_DEVICE");
tagValue = getSubscriberTagValue(client, "TAGS_FROM_DEVICE");
CONSOLE.print("TAGS_FROM_DEVICE : ");
CONSOLE.println(tagValue);
}
void setup() {
// TinyGsmClientの初期化(実際の初期化は機器に応じて適宜実装してください)
TinyGsm gsmModem(Serial2);
TinyGsmClient cellularClient = initializeGsmModem(gsmModem);
CONSOLE.println("\n\n");
CONSOLE.println("*********************");
CONSOLE.println("* USE CELLULAR *");
CONSOLE.println("*********************");
useMetadataServiceDemo(cellularClient);
}
実行結果
ここまでの実装踏まえ、サンプルコードを実行した結果の例を示します(一部省略や加工をしています)。
ユーザーデータやTAGS_TO_DEVICE
というタグの内容を読み出したり、TAGS_FROM_DEVICE
というタグが実行時のミリ秒の数字で上書きされている様子が分かると思います。
> Getting userdata...
> Requesting metadata.soracom.io
> Status code returned 200
> Response Headers:
> Content-Type : text/plain
> Content-Length : 44
> Date : Mon, 04 Dec 2023 12:05:12 GMT
> Connection : close
> Content length is: 44
> Response:
> Hello, This is userdata on metadata service!
> Body length is: 44
>
> USERDATA : Hello, This is userdata on metadata service!
> Getting specific tag value [TAGS_TO_DEVICE]...
> Requesting metadata.soracom.io
> Status code returned 200
> Response Headers:
> Content-Type : text/plain
> Date : Mon, 04 Dec 2023 12:05:13 GMT
> Connection : close
> Transfer-Encoding : chunked
> The response is chunked
> Response:
> Hello, this is SIM TAG!
>
> Body length is: 24
>
> TAGS_TO_DEVICE : Hello, this is SIM TAG!
>
> Getting specific tag value [TAGS_FROM_DEVICE]...
> Requesting metadata.soracom.io
> Status code returned 200
> Response Headers:
> Content-Type : text/plain
> Date : Mon, 04 Dec 2023 12:05:16 GMT
> Connection : close
> Transfer-Encoding : chunked
> The response is chunked
> Response:
> 21926
>
> Body length is: 6
>
> TAGS_FROM_DEVICE : 21926
>
> Putting specific tag [TAGS_FROM_DEVICE] as [29941]...
> Requesting metadata.soracom.io
> Status code returned 200
> Response Headers:
> Date : Mon, 04 Dec 2023 12:05:19 GMT
> Content-Type : application/json
> Content-Length : 1440
> Connection : close
> Content length is: 1440
> Response:
> {"imsi":"44010XXXXXXXXXX",......,"tags":{"TAGS_TO_DEVICE":"Hello, this is SIM TAG!","TAGS_FROM_DEVICE":"29941","name":"SIM-1"},......}
> Body length is: 1440
>
> Updated TAGS_FROM_DEVICE: {"imsi":"44010XXXXXXXXXX",......,"tags":{"TAGS_TO_DEVICE":"Hello, this is SIM TAG!","TAGS_FROM_DEVICE":"29941","name":"SIM-1"},......}
おわりに
メタデータサービスを利用するリクエスト実装の例をご紹介しました。今回の例はHTTPリクエストやクライアント(SORACOM AirとSORACOM Arc)の変更なども考慮した実装となっていますので、それぞれの関数の実装だけでもアイデアの実現の参考になれば幸いです。
今回紹介したタグやユーザーデータ以外にもメタデータサービス経由で利用できる機能や使い方はまだたくさんありますので、ぜひユーザードキュメントを参考に有効に活用してみてください!