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

この記事はCPS Lab Advent Calendarの9日目の記事です。
[CPS Labo Advent Calendar 2018 - Adventar](http://adventar.org/calendars/3058)[CPS Labo Advent Calendar 2018 \- Adventar]

8日目の記事はこちら
[LabMart(のシステム)を作った話 - unotoviveのメモ](https://unotovive.hatenablog.jp/entry/2018/12/08/225700)

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

##何でこれを書いたか

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


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

###最終的な外観
![IMG_7816.JPG](https://qiita-image-store.s3.amazonaws.com/0/218546/f8236af6-6952-3f85-c689-31530878691c.jpeg)
外での実験中での写真しかなくてすみません．

##使用機材

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

##モジュール~~図~~
```
esp32---3GIM(simカードはsoracom sim)
```

#準備

- ボーレートの設定（任意）
- simカードの設定
- 3Gネットワーク経由でデータを取得してみる．


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

![スクリーンショット 2018-12-09 12.51.06.png](https://qiita-image-store.s3.amazonaws.com/0/218546/9c20d4b5-4131-4dc3-0062-16b5ab25f107.png)

![スクリーンショット 2018-12-09 12.44.58.png](https://qiita-image-store.s3.amazonaws.com/0/218546/53d72a88-afd7-9cdd-779a-c618c9a4714c.png)

![スクリーンショット 2018-12-09 12.44.40.png](https://qiita-image-store.s3.amazonaws.com/0/218546/42d7ac2e-189f-35d9-408e-a796649f9858.png)

![スクリーンショット 2018-12-09 13.47.35.png](https://qiita-image-store.s3.amazonaws.com/0/218546/816c5a98-bac6-9151-3f8b-18b762fb4707.png)

| Arduino/ESP32 | ３GIM |
|:-----------|------------:|
| 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に書き込みます.

```c++:_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);
 }
}
```

```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](https://qiita-image-store.s3.amazonaws.com/0/218546/504055a1-3e83-7bff-263d-f29975a945ed.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カードの設定を行っていなかったからです．それではやっていき．

<b>プログラムは先ほどと同じものを使用します．</b>

参考画像
![スクリーンショット 2018-12-09 13.34.32.png](https://qiita-image-store.s3.amazonaws.com/0/218546/fc92232a-3955-9cfb-9e79-2faba6fa0505.png)

![スクリーンショット 2018-12-09 13.43.26.png](https://qiita-image-store.s3.amazonaws.com/0/218546/c91746a7-7298-b70d-46f7-52f0f251b9df.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ネットワーク経由でデータを取得してみる
<b>プログラムは先ほどと同じものを使用します．</b>

参考画像

![スクリーンショット 2018-12-09 13.50.38.png](https://qiita-image-store.s3.amazonaws.com/0/218546/f21f7368-7268-fb6c-c6d3-82e2e4773075.png)

![スクリーンショット 2018-12-09 13.50.47.png](https://qiita-image-store.s3.amazonaws.com/0/218546/d523d07a-78ce-3fbf-6b4e-b929cff0ac1c.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](https://qiita-image-store.s3.amazonaws.com/0/218546/ff530624-4b85-f085-6ba8-96014d8add17.png)

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

[これを元にしました](http://tabrain.jp/3GIM_V2.0/Part4%20Arduino%20and%203GIM.pdf)
ここでは温湿度を測って送ってますが，温湿度センサーがなくても問題ないです．

```c++:_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);
}
```

```c++:_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の記述

```json
 "\"{\$\"values\$\" : {\$\"TEMP\$\" : [{ \$\"timestamp\$\" : \$\""20190101"\$\" , \$\"value\$\" : \$\"" + String(temp) +"\$\"}]}}\";
```
プログラムから抜粋するとこんな感じになってます．意味わからん　；；

もう少し小さくみてみる

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

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

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

### HEADERの記述

こんな感じ

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

まぁわからない；；；

細かくしてみてみる

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

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

#まとめ

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

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

###参考リンク
***
[株式会社タブレイン](http://tabrain.jp/new/index.html)
[3GIM V2.2R01manual](http://tabrain.jp/3GIM_V2.2/3GIM%20V2.2R01manual.pdf)
[Part4 Arduino and 3GIM](http://tabrain.jp/3GIM_V2.0/Part4%20Arduino%20and%203GIM.pdf)
[start [3GIM Wiki]](https://3gim.wiki/doku.php)