見出し画像

阿波踊り大会の課題を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みたいなデバイスと通信することで、映像などもひろうことができるので、いろんなことに活用できるかな、なんておもいます。

こういう要素技術を手の内化しながら「実現できること」を増やし、自分たちがやれる幅を拡大しながら、課題を解決していけたらいいな、とおもっています。

この記事が気に入ったらサポートをしてみませんか?