阿波踊り大会の課題をIoTで解決する②
結果を載せる前に、位置マーカーの構成の話を少ししたいと思います。
1.ボード構成
左から、
・M5Stack本体
・GPSモジュール
・SORACOM 3Gボード
・M5Stack ボトム (Battery)
になります。
大体のものは、正規代理販売店である「スイッチサイエンス」で購入が可能です。
ソラコムの3Gボード、基板はどうもスイッチサイエンス製です。通信ボードはスイッチサイエンスから直接買えます。SORACOMにアカウントをつくると、ボードだけではなくSIMも購入することができます。
ちなみに、私は下記のものを買ってつくりました。
2.実は、加工が…
M5Stackは、ESP32というチップをつかっています。ESP32には、ハードウェアシリアル(UART)ポートが3つ付いています。
M5Stackでは、Serial0(G1,G3)がUSBシリアルポートとつながっていて、Serial2(G16,G17)が空きポート、Serial1は他の部品との関係で使えないので、実質2つのポートをうまく使うことになります。
ここで問題になるのが、Soracom 3Gボードも、GPSモジュールも、M5Stackとの通信はすべてUARTをつかっている、ということです。
そして、M5Stackのモジュールは、USBポート干渉の関係から、基本設定がSerial2で設計されています。
つまり、Serial2が干渉していて、そのままでは使えません。今回は、GPSモジュールをSerial0に移動して使うことにしました。
通信チップ右側に、通信ポートを切り替えるためのパッドがあります。図面では0Ωチップがついていることになっていますが、よくよく見ると細い線がパターンされているようにも見えます。
なかなかきれいに剥がれませんので、かるくデザインカッターで削ってあげると、剥がれます(が、やりすぎると傷つくので注意)
そのあと、(G1,G3)のところを半田ブリッジ(もしくは0Ωチップ付け)をすれば、ポート変更が完了します。
(手をあまり加えず汎用性を確保できるのがよいところなので、ほんとはディップSWか、ジャンパーピンで設定できると、もっと良いなと思う。)
3.3G通信をするには…
ここまできたら、通信をする準備をします。3G通信は、シリアルポートにモデムコマンドをやり取りして動かすことになります。
ライブラリとして、Tiny GMS を使えば、基本的なところはカバ―してくれます。これは持ってなければライブラリから導入しておきます。
SORACOM 3Gボードは、UBLOXのチップを使っているので、内部チップ選択用のdefine定義をしておきます。また、通信ポートは初期化の時にオブジェクトを渡すことで指定するので、Serial2を設定しておきます。
#define TINY_GSM_MODEM_UBLOX
#define TINY_GSM_USE_WIFI false
#define TINY_GSM_USE_GPRS true
#include <TinyGsmClient.h>
static TinyGsm modem(Serial2); /* 3G board modem */
つぎに、HTTP(s)でやり取りをしたいので、そのための部品も読み込んでおきましょう。
//TinyGsmClient ctx(modem); // HTTP
TinyGsmClientSecure ctx(modem); // HTTPs
使いたい方をつかってください。HTTPSの場合は、鍵のやり取りをする必要がありますので、対応しているライブラリ(TinyGmsClientSecure)をつかってください。
つぎに、3Gポートを初期化し、回線をつなぎます。
Serial2.begin(115200, SERIAL_8N1, 16, 17);
modem.restart();
String modemInfo = modem.getModemInfo();
while (!modem.waitForNetwork()){delay(1);};
modem.gprsConnect("soracom.io", "sora", "sora");
// SSLを使う時のみ、このコマンドでチェックする
if (!modem.hasSSL()) {
Serial.println(F("SSL is not supported by this modem"));
while(true) { delay(1000); }
}
while (!modem.isNetworkConnected()){delay(1);};
IPAddress ipaddr = modem.localIP();
ここまで実行すると、はじめて3G回線がつかえるようになります。
なお、ここで経験則上のポイントを1つ。M5Stack は、RTOSがのったプラットフォームなので、通信関数でずっと処理がつづくと、ウォッチドッグが働いてCPUリセットが入ることがあります。
特に、3G回線接続待ちのときに起きることがあります。
回避方法にはいくつかありますが、基本的には
・戻ってこない関数を極力つくらない
・どうしても時間がかかるなら、ウォッチドッククリアをする
という方法があります。
前者はアーキテクチャ上の話で、ソフトを書くときに考えてもらうとして、後者は、esp_task_wdt_reset() をいれることで対応ができます。
TinyGMSのソースコードを一部書き換えて対応することで、このリセットに対応できます。私は、waitResponseに書き加えることで対応をしました。
// TODO: Optimize this!
uint8_t waitResponse(uint32_t timeout_ms, String& data,
GsmConstStr r1=GFP(GSM_OK), GsmConstStr r2=GFP(GSM_ERROR),
GsmConstStr r3=GFP(GSM_CME_ERROR), GsmConstStr r4=NULL, GsmConstStr r5=NULL)
{
/*String r1s(r1); r1s.trim();
String r2s(r2); r2s.trim();
String r3s(r3); r3s.trim();
String r4s(r4); r4s.trim();
String r5s(r5); r5s.trim();
DBG("### ..:", r1s, ",", r2s, ",", r3s, ",", r4s, ",", r5s);*/
data.reserve(64);
int index = 0;
unsigned long startMillis = millis();
do {
TINY_GSM_YIELD();
esp_task_wdt_reset(); //追加
while (stream.available() > 0) {
int a = stream.read();
if (a <= 0) continue; // Skip 0x00 bytes, just in case
data += (char)a;
if (r1 && data.endsWith(r1)) {
index = 1;
goto finish;
} else if (r2 && data.endsWith(r2)) {
index = 2;
goto finish;
} else if (r3 && data.endsWith(r3)) {
index = 3;
goto finish;
} else if (r4 && data.endsWith(r4)) {
index = 4;
goto finish;
} else if (r5 && data.endsWith(r5)) {
index = 5;
goto finish;
} else if (data.endsWith(GF(GSM_NL "+UUSORD:"))) {
int mux = stream.readStringUntil(',').toInt();
int len = stream.readStringUntil('\n').toInt();
if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) {
sockets[mux]->got_data = true;
sockets[mux]->sock_available = len;
}
data = "";
DBG("### URC Data Received:", len, "on", mux);
} else if (data.endsWith(GF(GSM_NL "+UUSOCL:"))) {
int mux = stream.readStringUntil('\n').toInt();
if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) {
sockets[mux]->sock_connected = false;
}
data = "";
DBG("### URC Sock Closed: ", mux);
}
esp_task_wdt_reset(); //追加
}
} while (millis() - startMillis < timeout_ms);
finish:
あとは、通信できるようにコマンドをかきます。
if (!ctx.connect("hogehoge.jp", 443,15000)) {
ctx.stop();
}else{
//クエリを作る
char data[1024];
sprintf(data,"param1=%d",1);
//リクエストヘッダ
ctx.print("GET /data.php?");
ctx.print(data);
ctx.println(" HTTP/1.0");
ctx.println("Host: hogehoge.jp");
ctx.println("User-Agent: M5Stack/1.0");
ctx.println("If-Modified-Since: Sat, 13 Apr 2019 02:49:02 GMT");
ctx.println("Accept: */*");
ctx.println();
while (ctx.connected()) {
String line = ctx.readStringUntil('\n');
if (line == "\r") {
break;
}
delay(1);
}
char buf[1 * 1024] = {0};
ctx.readBytes(buf, sizeof(buf)); /* body */
ctx.stop();
こんな感じでHTTP(s)リクエストを出して、結果を受け取ることができます。ここで、1つポイントがあります。HostヘッダにIPアドレスを書きたくなることがありますが、hogehoge.jp:443 みたいな書き方をすると、うまく通信を返してくれないケースがあります。(例えば、Nginxみたいなサーバ。)
HTTPS=443が規定とわかるので、ポートを番号は書かずにやってみてください。
4.通信の程は…
実際に、通信をしてみましたが、ちょっとリクエストを飛ばす程度なら、最低速度の契約でも問題ない体感具合です。写真とかファイルを送るようになると、高速契約の恩恵にあやかれるといった感じでしょうか。
5.まとめ
今まで大手業者や計測器メーカじゃないと作れなかったようなものが、安価に手に入るようになり、(ちょっと手を加える必要はありますが)すぐに使えるようになる世の中になりました。
M5Stackは M5Cameraみたいなデバイスと通信することで、映像などもひろうことができるので、いろんなことに活用できるかな、なんておもいます。
こういう要素技術を手の内化しながら「実現できること」を増やし、自分たちがやれる幅を拡大しながら、課題を解決していけたらいいな、とおもっています。
この記事が気に入ったらサポートをしてみませんか?