IoTサービスのラベル取得 (M5Stamp PICO + CAT-M Module)

サンプルソースコード

概要

IIJ IoTの「ラベル機能」を使用してみます。

前回のサンプル に対して、ラベル機能を使用するような機能追加をしています。
特定のラベルが設定されていた場合は、そのラベルの値を取得し、その数値を使ってデータ送信間隔を変更します。

つまり、ラベル機能を使ってサーバーからクライアント側に向けて指示を出すようなイメージです。

ソースコード
m5stamp_iijiot_label.ino
#define TINY_GSM_MODEM_SIM7080
//#define TINY_GSM_DEBUG Serial 
#define SerialMon Serial
#define SerialAT Serial1

#include <TinyGsmClient.h>
#include <ArduinoHttpClient.h>
#include <ArduinoJson.h>

//--- TODO: 使用するSIMのAPN情報に書き換えてください。
const char apn[]      = "iot.iijmobile.jp"; // "h.iijmobile.biz";
const char lte_user[] = "mobile@iot";
const char lte_pass[] = "iot";
//--- 

const int rx_pin = 18;
const int tx_pin = 19;

const char* iij_iot_host = "gw.iot.iij.jp";
const int   iij_iot_port = 80;
const char* iij_iot_path = "/v1";
const char* iij_iot_label_path = "/v1/dvc-state/json/label-contents";
const char* sec_label_name = "delay_sec";

//Global
TinyGsm        modem(SerialAT);
TinyGsmClient client(modem);
int sleep_sec = 60;

/*
  setup関数
*/
void setup() {
  delay(500);
  SerialMon.begin(115200);
  SerialAT.begin(115200, SERIAL_8N1, rx_pin, tx_pin);
  delay(3000);

  SerialMon.println("Wait...");
  //モデム初期化
  SerialMon.println("Initializing modem...");
  modem.init();
  String modemInfo = modem.getModemInfo();
  SerialMon.println("Modem Info: " + modemInfo);
  //接続
  SerialMon.println("Connecting to "+ String(apn));
  while (!modem.gprsConnect(apn, lte_user, lte_pass)) {
    SerialMon.println("NG.");
    delay(10000);
    SerialMon.println("retry");
  }
  SerialMon.println("OK.");
  //接続まち
  SerialMon.print("Waiting for network...");
  if (!modem.waitForNetwork()) {
    SerialMon.println(" fail");
    delay(10000);
    return;
  }
  SerialMon.println(" success");

  bool res = modem.isGprsConnected();
  SerialMon.printf("GPRS status: %s\n", res ? "connected" : "not connected");

  //各種情報を出力
  String ccid = modem.getSimCCID();
  SerialMon.println("CCID: "+ ccid);
  String imei = modem.getIMEI();
  SerialMon.println("IMEI: "+ imei);
  String cop = modem.getOperator();
  SerialMon.println("Operator: "+ cop);
  IPAddress local = modem.localIP();
  SerialMon.print("Local IP: ");
  SerialMon.println(local);
  int csq = modem.getSignalQuality();
  SerialMon.print("Signal quality: ");
  SerialMon.println(csq);
  
  //乱数を初期化
  randomSeed(analogRead(25));
}

/*
  IIJ IoTサービスにデータ送信する
    引数
      const char* ns     : 送信する namespace値
      const char* name   : 送信する name値
      const double value : 送信する value値
    戻り値
      bool true:送信成功 false:送信失敗
*/
bool send_data(const char* ns, const char* name, const double value) {
  //JSONオブジェクトを作成
  StaticJsonDocument<JSON_OBJECT_SIZE(3)> body;
  if (ns != nullptr && strlen(ns) > 0) {
    body["namespace"] = ns;
  }
  body["name"] = name;
  body["value"] = value;

  //JSONを文字列に変換
  String body_string;
  serializeJson(body, body_string);

  SerialMon.println("POST: " + body_string);

  //JSON文字列をIIJ IoTサービスにPOST
  HttpClient http(client, iij_iot_host, iij_iot_port);
  http.beginRequest();
  http.post(iij_iot_path);
  http.sendHeader("Content-type", "application/json");
  http.sendHeader("Content-length", body_string.length());
  http.beginBody();
  http.print(body_string);
  http.endRequest();
  int status_code = http.responseStatusCode();
  return status_code >= 200 && status_code < 300;
}

/*
  IIJ IoTサービスに設定されているラベルのJSON文字列を取得する
    引数
      String& labels     : 取得したラベル文字列を格納するString参照
    戻り値
      String labelsを返す
*/
String get_label_string(String& labels) {
  HttpClient http(client, iij_iot_host, iij_iot_port);
  http.get(iij_iot_label_path);
  
  int status_code = http.responseStatusCode();
  if(status_code == 200) {
    labels = http.responseBody();
  } else {
    labels = "{}";
  }
  return labels;
}

/*
  ラベルJSON文字列内から、keyに対応する値を返す
    引数
      const String& labels     : 取得したラベル文字列を格納するString参照
      const char* key          : 値を取得するkey
      char* value              : 取得した値(文字列)を格納する変数
      size_t size              : value配列のサイズ
    戻り値
      char*  valueを返す
*/
char* get_label(const String& labels, const char* key, char* value, size_t size) {
  //ラベルのJSON文字列を解析する
  DynamicJsonDocument root(labels.length() * 3);
  deserializeJson(root, labels);

  //ラベルJSON内から、keyの値を取得する(keyが存在しない場合は空文字列)
  const char* src = root[key];
  memset(value, '\0', size);
  if(src != nullptr) {
    strncpy(value, src,  size-1);
  }
  //value値を返す
  return value;
}

/*
  IIJ IoTからラベルを取得し、keyに対応する値を返す
    引数
      const char* key          : 値を取得するkey
      char* value              : 取得した値(文字列)を格納する変数
      size_t size              : value配列のサイズ
    戻り値
      char*  valueを返す
*/
char* get_label(const char* key, char* value, size_t size) {
  // IIJ IoTからラベルを取得する
  String labels;
  get_label_string(labels);
  // 値を取得する
  return get_label(labels, key, value, size);
}

/*
  loop関数
*/
void loop() {
  //乱数を生成する
  int value = random(10000);
  //データ送信する
  bool ok = send_data("m5stamp", "test", value);
  SerialMon.println(ok ? "OK" : "NG");

  //ラベル取得
  char label_value[32];
  get_label(sec_label_name, label_value, sizeof(label_value));
  int sec = String(label_value).toInt();
  //"delay_sec"のラベルが存在したら、送信間隔の時間をそのラベル値の数値に変更する
  if (sec > 0 && sec != sleep_sec ) {
    sleep_sec = sec;
    SerialMon.printf("update sleep sec: %d sec.\n", sleep_sec);
  }
 
  //待つ
  delay(sleep_sec * 1000L);
}
実行結果

IoTサービスのコントロールパネルから、ラベルを設定することで、送信間隔を変更することができます。

コントロールパネルの左側メニューから「デバイス」を選択し、続いて一覧の中から使用しているSIMを選択し、画面上の「編集」ボタンをクリックします。

表示された画面から、ラベル名に delay_sec 、値に適当な数値を入力し、保存します。

M5Stamp 側で、設定したラベル値が読み込まれ、送信間隔が変更されます。
ラベル値に 30 と設定した場合は、下記の通りシリアルモニタに「update sleep sec: 30 sec」と表示され、次回以降は30秒間隔でデータ送信されます。

Wait...
Initializing modem...
Modem Info: R1951.04
Connecting to iot.iijmobile.jp
OK.
Waiting for network... success
GPRS status: connected
CCID: XXXXXXXXXXXXXXXXXXXXXX
IMEI: YYYYYYYYYYYYYYYY
Operator: NTT DOCOMO
Local IP: 10.ZZZ.ZZZ.ZZZ
Signal quality: 15
POST: {"namespace":"m5stamp","name":"test","value":4637}
OK
update sleep sec: 30 sec.
POST: {"namespace":"m5stamp","name":"test","value":3907}
OK

各種情報へのリンク

IIJ IoTサービスマニュアル

M5Stamp

TinyGSM

Arduino_JSON

ArduinoHttpClient