Arduino
IoT
SORACOM
ESP32
3GIM

3GIMを利用してjson形式のセンサデータをHTTPPOSTする

この記事はCPS Lab Advent Calendarの9日目の記事です。
CPS Labo Advent Calendar 2018 - Adventar[CPS Labo Advent Calendar 2018 - Adventar]

8日目の記事はこちら
LabMart(のシステム)を作った話 - unotoviveのメモ

*記事を書いている時手元にハードがなかったので後日写真を載せます

何でこれを書いたか

  • 公式のマニュアルにHTTPPOSTの例がtwitterを利用した事例はあったものの,jsonを送る事例がかなり見つけにくいところにあった.
  • 公式マニュアルのjson形式での送る説明が分かりずらい.
  • 使ってる人がそんなにいなく,ネットに記事が転がってない.(マニュアルが優秀だからかもしれない)

目標

センサデータをjson形式に変えて,httppostするところまでやっていきたいと思います.
今回は利用しませんが3gimのarduinolibraryです.参考までに https://github.com/openwireless/3gim

最終的な外観

IMG_7816.JPG
外での実験中での写真しかなくてすみません.

使用機材

  • ESP32 or Arduino
  • ジャンパー線
  • ブレッドボード
  • 3GIM v2.2
  • soracom社のsimカード

モジュール

esp32---3GIM(simカードはsoracom sim)

準備

  • ボーレートの設定(任意)
  • simカードの設定
  • 3Gネットワーク経由でデータを取得してみる.

配線など

下記接続を元に配線.ブレボあたりでいいと思う.参考リンクにマニュアルがあるのでそこに詳しく記載されているのでそちらをみてください.
*画像は公式より

スクリーンショット 2018-12-09 12.51.06.png

スクリーンショット 2018-12-09 12.44.58.png

スクリーンショット 2018-12-09 12.44.40.png

スクリーンショット 2018-12-09 13.47.35.png

Arduino/ESP32 3GIM
GND GND
3.3V VCC(+)
3.3v IOREF
RX(4)/RX(16) TX
TX(5)/TX(17) RX
GPIO7 POWER_ON

ボーレートの設定(任意)

*この作業は Arduinoで行いました.ESP32でもできるかもしれないけどなぜか出来なかった.
それではやっていき,下記のプログラムをArduinoかesp32に書き込みます.

_3gim_Arduino_Serial_Test
#include <SoftwareSerial.h>
SoftwareSerial iemSerial(4, 5);
const unsigned long baudrate = 9600;
void setup() {
 Serial.begin(baudrate);
 iemSerial.begin(baudrate);
 pinMode(7,OUTPUT);
 digitalWrite(7,HIGH);
 digitalWrite(7,LOW); 
 Serial.println("Ready.");
}
void loop() {
 if (iemSerial.available() > 0) {
 char c = iemSerial.read();
 Serial.print(c);
 }
 if (Serial.available() > 0) {
 char c = Serial.read();
 Serial.print(c); // Echo back
 iemSerial.print(c);
 }
}
_3gim_ESP32_Serial_test
HardwareSerial Serial3g(1);

const unsigned long baudrate = 9600;
void setup() {
  Serial.begin(baudrate);
  Serial3g.begin(baudrate, SERIAL_8N1, 16, 17);
  pinMode(7, OUTPUT);
  digitalWrite(7, HIGH); delay(5);
  digitalWrite(7, LOW); //3GIMシールド電源ON
  Serial.println("Ready.");
}
void loop() {
  if (Serial3g.available() > 0) {
    char c = Serial3g.read();
    Serial.print(c);
  }
  if (Serial.available() > 0) {
    char c = Serial.read();
    Serial.print(c); // Echo back
    Serial3g.print(c);
  }
}

このプログラムはシリアル通信で3gimに送ったものをPC側のシリアル通信に表示したり,逆に3gimがarduinoにシリアル通信でコールバックしたものを,PCに表示するプログラムです.
これを使ってボーレート設定を行っていきます.

参考画像スクリーンショット 2018-12-09 13.15.48.png

プログラムを書き込んだらシリアルモニタボーレート9600を開きます.
プログラムを走らせてから15秒程度たつと,3gimの緑色のLEDが点滅し
Welcome to 3GIM(v2.2)
とシリアルモニタに表示されるはずです.

表示されない場合は

  • 配線(特にUARTのTxとRxの接続)が間違っている
  • 不具合で初期ボーレートが変わっている.もしくは誰かにボーレートを変えられた
  • 電源の供給が足りていない

の線を当たってみてください.

公式マニュアルにも記載されていますが,一度ボーレートを変更してしまうと,ボーレート一つ一つ探していかないといけないので,大変です.

Welcome to 3GIM(v2.2)
が表示されたら.

$YB 任意のボーレート
を入力するとレスポンスが帰ってきて,3GIMのボーレートが変更されます.

simカードの設定

はじめ,3GIMを触っててRSSIなどが取れるのにGETやPOSTが出来ない問題に当たりました.原因としてはsimカードの設定を行っていなかったからです.それではやっていき.

プログラムは先ほどと同じものを使用します.

参考画像
スクリーンショット 2018-12-09 13.34.32.png

スクリーンショット 2018-12-09 13.43.26.png

シリアルモニタに
$PS
と入力するとデフォルトのsimカードのプロファイルをみることがみることが出来ます.

僕がsoracom simを利用した時は
$PS=OK "iijmio.jp","mio@iij","iij"
と帰ってきました.このままではsoracom simは使えないので設定を変更します.

$PS "soracom.io","sora","sora"

と,入力します.
$PS=OK "soracom.io","sora","sora"
となればOKです.

一応
$PS
ともう一度入力して,先ほどのレスポンスが帰って来れば成功です.

3Gネットワーク経由でデータを取得してみる

プログラムは先ほどと同じものを使用します.

参考画像

スクリーンショット 2018-12-09 13.50.38.png

スクリーンショット 2018-12-09 13.50.47.png

$WG http://tabrain.jp/demo/httpGET_test.txt

と入力して

Tabrain Web site  Complete access from 3GIM
が帰って来れば成功です

3G通信が出来ています.出来ていない場合はもう一度 simカードのプロファイルの方を見直してください

ようやく本番(post)

ArduinoとESP32の違いはsoftwareserialかhardwareserialかの違いだけです

参考画像
スクリーンショット 2018-12-09 14.18.13.png

この解説が分かりずらいんじゃ...

これを元にしました
ここでは温湿度を測って送ってますが,温湿度センサーがなくても問題ないです.

_3gim_Arduino_httppost_test
#include <SoftwareSerial.h>
SoftwareSerial iemSerial(4, 5);

// リスト②:温度センサ値を 3 分ごとに m2x クラウドにアップ
const unsigned long baudrate = 9600;
#define LIMITTIME 35000 // ms (3G module start time)
String URL = "ポスト先URL";//最後のスペースは必要
//String HEADER = "\"Host: api-m2x.att.com$r$nX-M2X-KEY: x-m2x-key$r$nContent-Type:application/json$r$n\"";
String HEADER = "\"Content-Type:application/json$r$n\"";
void setup() {
  while (!Serial);
  Serial.begin(baudrate);
  Serial.println(">Ready. Initilaizing...");
  while ( !_3Gsetup() ) {
    Serial.println(" Connect Error ... Stop");
    while (1);
  }
  Serial.println("Connected");
}
void loop () {
  unsigned long tim = millis();
  //-------------------- TEMP -----------------
 float temp = analogRead(A1)*0.322-60.0;
 Serial.println("TEMP = " + String(temp));
 String body1 = "\"{\$\"values\$\" : {\$\"TEMP\$\" : [{ \$\"timestamp\$\" : \$\"";
 String body2 = "\$\" , \$\"value\$\" : \$\"" + String(temp) +"\$\"}]}}\" ";
 if(_3G_WP("$WP " + URL + body1+ datetime()+ body2 + HEADER)){
 Serial.println(Serial1.readStringUntil('\n')); }
 else Serial.println("Data Update false...");
 while(millis()-tim<120000); //waiting 

}
// =================== datetime ==============
// Get Date & Time (3GIM command)
// return --> string "2015-12-23T01:23:45%2B09:00"
// 2015-09-20T14:05:12.345Z\$\" , \$\"value\$\" : \$\"32.0\$\"}]}}\" ";
// + "2015-09-02T11:59:00%2B09:00&csv=TEMP01,2015-09-02T10:00:00%2B09:00LIGHT01,24.2"
String datetime() {
  String dtime;
  do {
    SendDataSerial.println("$YT");
    while (!SendDataSerial.available());
    dtime = SendDataSerial.readStringUntil('\n');
    Serial.println(dtime);
    delay(1000);
  } while ( !(dtime.indexOf("201") > 0) );
  dtime.replace(" ", "T"); dtime.replace("/", "-");
  return (dtime.substring(7) + "$+09:00");
}
//============== 3G setup =================
boolean _3Gsetup() {
  Serial.println("start _3Gsetup()");
  pinMode(7, OUTPUT);
  digitalWrite(7, HIGH); delay(100);
  digitalWrite(7, LOW); // 3G shield --> digitalWrite(7,HIGH);
  //------- 3G module begin & connect ---------
  String str;
  unsigned long tim = millis();
  do {
    while (!SendDataSerial.available());
    str = SendDataSerial.readStringUntil('\n');
  } while (!(str.indexOf("3GIM") > 0) && (millis() - tim) < LIMITTIME);
  if ( millis() - tim >= LIMITTIME) {
    return false;
  } else return true;
}
//============== $WP command =================
boolean _3G_WP(String command) {
  delay(10);
  Serial.println(command); // debug
  delay(10);
  SendDataSerial.println(command);
  String rstr;
  unsigned long tim = millis(); // time set(ms)
  do {
    while (!SendDataSerial.available());
    rstr = SendDataSerial.readStringUntil('\n');
    Serial.println(rstr); //debug print....
  } while (!(rstr.indexOf("$WP=") == 0) && (millis() - tim) < LIMITTIME); // $WP return check
  return (rstr.indexOf("$WP=") == 0);
}
_3gim_esp32_httppost_test
HardwareSerial SendDataSerial(1);

// リスト②:温度センサ値を 3 分ごとに m2x クラウドにアップ
const unsigned long baudrate = 9600;
#define LIMITTIME 35000 // ms (3G module start time)
String URL = "ポスト先URL";//最後のスペースは必要
//String HEADER = "\"Host: api-m2x.att.com$r$nX-M2X-KEY: x-m2x-key$r$nContent-Type:application/json$r$n\"";
String HEADER = "\"Content-Type:application/json$r$n\"";
void setup() {
  while (!Serial);
  Serial.begin(baudrate);
  Serial.println(">Ready. Initilaizing...");
  while ( !_3Gsetup() ) {
    Serial.println(" Connect Error ... Stop");
    while (1);
  }
  Serial.println("Connected");
}
void loop () {
  unsigned long tim = millis();
  //-------------------- TEMP -----------------
 float temp = analogRead(A1)*0.322-60.0;
 Serial.println("TEMP = " + String(temp));
 String body1 = "\"{\$\"values\$\" : {\$\"TEMP\$\" : [{ \$\"timestamp\$\" : \$\"";
 String body2 = "\$\" , \$\"value\$\" : \$\"" + String(temp) +"\$\"}]}}\"";
 if(_3G_WP("$WP" + " " + URL + body1+ datetime()+ body2+ " " + HEADER)){
 Serial.println(Serial1.readStringUntil('\n')); }
 else Serial.println("Data Update false...");
 while(millis()-tim<120000); //waiting 
}
// =================== datetime ==============
// Get Date & Time (3GIM command)
// return --> string "2015-12-23T01:23:45%2B09:00"
// 2015-09-20T14:05:12.345Z\$\" , \$\"value\$\" : \$\"32.0\$\"}]}}\" ";
// + "2015-09-02T11:59:00%2B09:00&csv=TEMP01,2015-09-02T10:00:00%2B09:00LIGHT01,24.2"
String datetime() {
  String dtime;
  do {
    SendDataSerial.println("$YT");
    while (!SendDataSerial.available());
    dtime = SendDataSerial.readStringUntil('\n');
    Serial.println(dtime);
    delay(1000);
  } while ( !(dtime.indexOf("201") > 0) );
  dtime.replace(" ", "T"); dtime.replace("/", "-");
  return (dtime.substring(7) + "$+09:00");
}
//============== 3G setup =================
boolean _3Gsetup() {
  Serial.println("start _3Gsetup()");
  pinMode(7, OUTPUT);
  digitalWrite(7, HIGH); delay(100);
  digitalWrite(7, LOW);
  SendDataSerial.begin(9600, SERIAL_8N1, 16, 17);
  //------- 3G module begin & connect ---------
  String str;
  unsigned long tim = millis();
  do {
    while (!SendDataSerial.available());
    str = SendDataSerial.readStringUntil('\n');
  } while (!(str.indexOf("3GIM") > 0) && (millis() - tim) < LIMITTIME);
  if ( millis() - tim >= LIMITTIME) {
    return false;
  } else return true;
}
//============== $WP command =================
boolean _3G_WP(String command) {
  delay(10);
  Serial.println(command); // debug
  delay(10);
  SendDataSerial.println(command);
  String rstr;
  unsigned long tim = millis(); // time set(ms)
  do {
    while (!SendDataSerial.available());
    rstr = SendDataSerial.readStringUntil('\n');
    Serial.println(rstr); //debug print....
  } while (!(rstr.indexOf("$WP=") == 0) && (millis() - tim) < LIMITTIME); // $WP return check
  return (rstr.indexOf("$WP=") == 0);
}

解説

関数

  • _3G_setup
    • 3GIMをsetupする関数,と言っても3GIMからのシリアル通信を受け取って,一定時間接続がないとタイムアウトする
  • _3G_WP
    • ポストする関数.送信するコマンド文字列を渡してあげる
  • datetime
    • $YTコマンドで時間をとってくる

データ形式(重要)

3GIMではjsonの書き方と,ヘッダーファイルが少し異なります.

jsonの記述

 "\"{\$\"values\$\" : {\$\"TEMP\$\" : [{ \$\"timestamp\$\" : \$\""20190101"\$\" , \$\"value\$\" : \$\"" + String(temp) +"\$\"}]}}\";

プログラムから抜粋するとこんな感じになってます.意味わからん ;;

もう少し小さくみてみる

 "\"{\$\"value\$\" : \$\"hoge\$\"}\";

こんな感じ,冒頭の 「 "\」はエスケープシーケンス,ここで特殊なのがキーと値の前に「$」をつけます
プログラムにするときはエスケープシーケンスが必要なので「/$」,またjson形式はダブルクォーテーションで囲わないといけないので「/"」でくくるとこんな感じになります.

試してないのですがシリアルモニタから$WPコマンドで飛ばす時は
$WP "URL" "{$"value$" : $"hoge$"}" "HEADER"
になるんじゃないかなと思います

HEADERの記述

こんな感じ

"\"Host:api-m2x.att.com$r$nX-M2X-KEY: x-m2x-key$r$nContent-Type:application/json$r$n\"";

まぁわからない;;;

細かくしてみてみる

"\"Content-Type:application/json$r$n\"";

分かりやすい,3GIMのHEADERは最後に「\$r$n」を後ろにひっつけます.二つ以上のHEADERは普通にくっつければ問題ないようです.

まとめ

今回は3GIMのセットアップからHTTPPOSTまで行いました.

  • simカードの設定を行うこと
  • ボーレートは一度変更してわからなくなったら1つ1つ探さないといけない.
  • jsonをhttppostする時は jsonのキー,値には前後に「$」を,HEADERには「\$r$n」をつける

参考リンク


株式会社タブレイン
3GIM V2.2R01manual
Part4 Arduino and 3GIM
start [3GIM Wiki]