8
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

LTEモジュールとGCPを利用したGPSロガーの作成

Last updated at Posted at 2020-10-08

記事の概要

GPSロガーを作成したので、備忘録として記録を残します。
自分用のメモなので自明なことは省略していますが、不明点があればお気軽に本記事コメント欄にてご質問ください。

システム概要

通信モジュールEC21-J Mini PCIeにUART通信ケーブルでマイコンと接続し、マイコンでGPSデータを取得します。

GPSデータはGoogle Cloud Platform(以下、「GCP」と略)に記録します。
サーバとの通信はSORACOM IoT Simを用いてMQTT通信を行います。

環境構築

MQTT通信を行うためには以下の準備をしないといけません。

  • 通信モジュールの購入
  • SORACOM SIMの購入と設定
  • GCPとの契約と設定

上記の通信環境構築方法は、以下の記事にて行っているのでご参照ください。

EC21-JとSORACOM IoT SIMによりGoogle IoT CoreでMQTT接続を行うまでの全過程の記録

また、今回はマイコンも必要になります。

  • マイコンとGPSの購入
  • マイコンと通信モジュールのUART通信ケーブル

マイコンには太陽誘電のBLEモジュールnRF52480を使用しました。
UART通信が使えるマイコンならば何でもかまいません。
将来的にスマホとのBLE通信を行いたかったので、BLEモジュールを使用しました。

また、マイコンと通信モジュール間でUART通信するために以下のモジュールで通信ケーブルを作成します。
マイコンのTX端子とRX端子をこのモジュールにハンダ付けするだけの簡単な工作になります。

極小RS232-TTLコンバータモジュール- Dサブ9ピンオスコネクタ(ケースキット付き)

GPSはEC21-JにGNSSが内蔵されているのならば不要になります。
私は複数のGPSを比較したかったので、以下も購入してみました。

Qwiic - NEO-M8U搭載 推測航法GPSモジュール

GPSモジュール GMS6-CR6(9600bps)

M8Uにはアンテナも必要です。

GPSアンテナ 28dB 5m SMAコネクタ型
SMAからU.FLに変換するケーブル

もしくは

Molex フレキシブルGNSS U.FLアンテナ(粘着テープ付き)

などを使用します。

UART端子はLTE通信モジュールとのUART通信に使用していたので、上記のGPSモジュールとはI2C通信しました。

M8UはI2C通信に対応していたので問題なかったのですが、GMS6-CR6はUART通信しかできないので、I2C通信をUART通信に変換するモジュールをマイコンとの間に挟んで使用しました。

SC16IS750 シリアル-I2C ピッチ変換済みモジュール

以下が実際に作成した治具になります。
この治具では、デバッグ用にSDカードにGPS測定データを記録しています。

GPS測定治具.JPG

性能試験が目的なので小型化は考えていませんでしたが、実用に使うならば基板を作成した方がいいと思います。
例えば、以下が参考になるかと思います。
オープンソースデータを流用して通信モジュールを作成する事例が紹介されています。

ESP8266 (ESP-WROOM-02) を SORACOM SIM と通信モジュール UC20-G を使って3G でインターネットに繋ぐ

ファームウェア

ファームウェアはATコマンドをUART送信して、結果をUART受信するだけの単純なプログラムになります。

ネットワークに接続後、MQTT通信により、GPSデータをGCPへPublishします。

使用するATコマンドは以下をご参照ください。
【作業メモ】Quectel社のLTE通信モジュールEC21の使用方法:MQTT通信

参考までに、私の作成したネットワークに接続するファームウェアの一部を以下に羅列します。
ATコマンドを順番に送るだけの単純なプログラムになります。

送信コマンドの結果を受信待機している間にも他の処理を実行できるように、送信と受信を分けています。
ec_protocol_open関数を繰り返し実行し続けることで、順番にコマンドを送受信しています。

ec_protcol_num = PROTOCOL_OPEN;

int main(void)
{
    for (;;)
    {
        ec_exe_protocol();
    }
}
void ec_exe_protocol (void)
{
    uint8_t check_result;
    if(PROTOCOL_NONE != ec_protcol_num)
    {
        ec_select_prtocol();
        check_result = ec_check_response();

        if(UART_SUCCESS == check_result) 
        {
            //Timer Reset
            sys_timer_count[RECEIVE_FAIL_TIMER] = 0;

            //Negate PROCESS_WAIT_RESPONSE bit
            ec_process_num = ec_process_num & ~PROCESS_WAIT_RESPONSE;

            //Proceed to the next process 
            ec_process_num += 1;
        } else if(UART_FAIL == check_result) 
        {            
            //Negate PROCESS_WAIT_RESPONSE bit and Redo the process
            ec_process_num = ec_process_num & ~PROCESS_WAIT_RESPONSE;
        } 

        // TimeOver
        if(true == sys_timer_limit[RECEIVE_FAIL_TIMER])
        {            
            ec_reset_protocol();     
        }
    }
} 
void ec_select_prtocol (void)
{
    switch(ec_protcol_num)
    {
        case PROTOCOL_NONE: 

            break;

        case PROTOCOL_ATE0: 
            ec_protocol_ate0();
            break;

        case PROTOCOL_OPEN: 
            ec_protocol_open();
            break;

        case PROTOCOL_CLOSE: 
            ec_protocol_close();
            break;

        case PROTOCOL_GPSON: 
            ec_protocol_gpson();
            break;

        case PROTOCOL_GPSOFF:
            ec_protocol_gpsoff();
            break;

        case PROTOCOL_XTRA: 
            ec_protocol_xtra();
            break;

        case PROTOCOL_GPSGET: 
            ec_protocol_gpsget();
            break;

        case PROTOCOL_PUB_GPS: 
            ec_protocol_publish_gps();
            break;

        default:

            break;
    }
}
static void ec_protocol_open (void)
{
    // Do nothing as long as the PROCESS_WAIT_RESPONSE bit is asserted
    switch(ec_process_num)
    {
        case 0:
            ec_start_protocol();
            uint8_t qicsgp_on[] ="AT+QICSGP=1,1,\"soracom.io\",\"sora\",\"sora\",0\r\n";
            send_message(&qicsgp_on[0]);
            ec_process_num |= PROCESS_WAIT_RESPONSE;
            ec_check_num = CHECK_OK;
            break;

        case 1:
            ec_buf_clear();
            uint8_t qiact_check[] ="AT+QIACT?\r\n";
            send_message(&qiact_check[0]);
            ec_process_num |= PROCESS_WAIT_RESPONSE;
            ec_check_num = CHECK_QIACT;
            break;

        case 2:
            ec_buf_clear();
            uint8_t qiact_on[] ="AT+QIACT=1\r\n";
            send_message(&qiact_on[0]);
            ec_process_num |= PROCESS_WAIT_RESPONSE;
            ec_check_num = CHECK_OK;
            break;

        case 3:
            ec_buf_clear();
            uint8_t qmtopen[] = "AT+QMTOPEN=0,\"beam.soracom.io\",1883\r\n";
            send_message(&qmtopen[0]);
            ec_process_num |= PROCESS_WAIT_RESPONSE;
            ec_check_num = CHECK_QMTOPEN;
            break;

        case 4:
            ec_buf_clear();
            uint8_t qmtconnect_check[] ="AT+QMTCONN?\r\n";
            send_message(&qmtconnect_check[0]);
            ec_process_num |= PROCESS_WAIT_RESPONSE;
            ec_check_num = CHECK_QMTCONN;
            break;

        case 5:
            ec_buf_clear();
            uint8_t qmtconnect[] = "AT+QMTCONN=0,\"projects/vigilant-router-xxxxxx/locations/asia-east1/registries/xxxx/devices/SOMENAME\"\r\n";
            send_message(&qmtconnect[0]);
            ec_process_num |= PROCESS_WAIT_RESPONSE;
            ec_check_num = CHECK_OK;
            break;

        case 6:
            ec_reset_protocol();
            break;

        default:

            break;
    }
}
uint8_t ec_check_response (void)
{        
    uint8_t next_check = 0;
    uint8_t utc_start = 0;

    switch(ec_check_num)
    {
        case CHECK_OK: 
            for(int i=1; i<RECEIVE_BUF_MAX; i++)
            {
                if(receive_data[i-1] == 'O' && receive_data[i] == 'K')
                {
                    return UART_SUCCESS;
                }
            } 
            break;

        case CHECK_QIACT:
            for(int i=1; i<RECEIVE_BUF_MAX; i++)
            {
                if(receive_data[i-1] == 'O' && receive_data[i] == 'K')
                {
                    next_check = 1;
                }
            }

            if(1 == next_check)
            {
                for(int i=4; i<RECEIVE_BUF_MAX; i++)
                {
                    if(receive_data[i-4] == 'Q' && receive_data[i-3] == 'I' && receive_data[i-2] == 'A' && receive_data[i-1] == 'C' && receive_data[i] == 'T')
                    {
                        ec_process_num += 1;
                        break;
                    } 
                }

                return UART_SUCCESS;
            }

            break;

        case CHECK_QMTOPEN_QUESTION:
            for(int i=1; i<RECEIVE_BUF_MAX; i++)
            {
                if(receive_data[i-1] == 'O' && receive_data[i] == 'K')
                {
                    next_check = 1;
                }
            }

            if(1 == next_check)
            {
                for(int i=6; i<RECEIVE_BUF_MAX; i++)
                {
                     if(receive_data[i-6] == 's' && receive_data[i-5] == 'o' && receive_data[i-4] == 'r' && receive_data[i-3] == 'a' && receive_data[i-2] == 'c' && receive_data[i-1] == 'o' && receive_data[i] == 'm')
                     {
                         ec_process_num += 1;
                         break;
                     } 
                }

                return UART_SUCCESS;
            }

            break;

        case CHECK_QMTOPEN:
            for(int i=6; i<RECEIVE_BUF_MAX; i++)
            {
                 if(receive_data[i-6] == 'Q' && receive_data[i-5] == 'M' && receive_data[i-4] == 'T' && receive_data[i-3] == 'O' && receive_data[i-2] == 'P' && receive_data[i-1] == 'E' && receive_data[i] == 'N')
                 {
                     next_check = 1;
                 } 
            }

            if(1 == next_check)
            {

                for(int i=1; i<RECEIVE_BUF_MAX; i++)
                {
                    if(receive_data[i-1] == 'O' && receive_data[i] == 'K')
                    {
                        return UART_SUCCESS;
                    }
                }

                return UART_FAIL;
            }

            break;


        case CHECK_QMTCONN:
            for(int i=1; i<RECEIVE_BUF_MAX; i++)
            {
                if(receive_data[i-1] == 'O' && receive_data[i] == 'K')
                {
                    next_check = 1;
                }
            }

            if(1 == next_check)
            {
                for(int i=6; i<RECEIVE_BUF_MAX; i++)
                {
                     if(receive_data[i-6] == 'Q' && receive_data[i-5] == 'M' && receive_data[i-4] == 'T' && receive_data[i-3] == 'C' && receive_data[i-2] == 'O' && receive_data[i-1] == 'N' && receive_data[i] == 'N')
                     {
                         ec_process_num += 1;
                         break;
                     } 
                }

                return UART_SUCCESS;
            }

            break;

        case CHECK_CONNECT:
            for(int i=1; i<RECEIVE_BUF_MAX; i++)
            {
                if(receive_data[i-1] == 'O' && receive_data[i] == 'K')
                {
                    next_check = 1;
                }
            }

            if(1 == next_check)
            {
                for(int i=6; i<RECEIVE_BUF_MAX; i++)
                {
                     if(receive_data[i-6] == 'C' && receive_data[i-5] == 'O' && receive_data[i-4] == 'N' && receive_data[i-3] == 'N' && receive_data[i-2] == 'E' && receive_data[i-1] == 'C' && receive_data[i] == 'T')
                     {
                         return UART_SUCCESS;
                     } 
                }

                return UART_FAIL;
            }

            break;

        case CHECK_QHHTPGET:
            for(int i=7; i<RECEIVE_BUF_MAX; i++)
            {
                 if(receive_data[i-7] == 'Q' && receive_data[i-6] == 'H' && receive_data[i-5] == 'H' && receive_data[i-4] == 'T' && receive_data[i-3] == 'P' && receive_data[i-2] == 'G' && receive_data[i-1] == 'E' && receive_data[i] == 'T')
                 {
                     next_check = 1;
                 } 
            }

            if(1 == next_check)
            {

                for(int i=1; i<RECEIVE_BUF_MAX; i++)
                {
                    if(receive_data[i-1] == 'O' && receive_data[i] == 'K')
                    {
                        return UART_SUCCESS;
                    }
                }

                return UART_FAIL;
            }

            break;

        case CHECK_QHTTPREADFILE:
            for(int i=12; i<RECEIVE_BUF_MAX; i++)
            {
                 if(receive_data[i-12] == 'Q' && receive_data[i-11] == 'H' && receive_data[i-10] == 'H' 
                    && receive_data[i-9] == 'T' && receive_data[i-8] == 'P' && receive_data[i-7] == 'R' 
                    && receive_data[i-6] == 'E' && receive_data[i-5] == 'A' && receive_data[i-4] == 'D' 
                    && receive_data[i-3] == 'F' && receive_data[i-2] == 'I' && receive_data[i-1] == 'L' 
                    && receive_data[i] == 'E' ) 
                 {
                     next_check = 1;
                 } 
            }

            if(1 == next_check)
            {

                for(int i=1; i<RECEIVE_BUF_MAX; i++)
                {
                    if(receive_data[i-1] == 'O' && receive_data[i] == 'K')
                    {
                        return UART_SUCCESS;
                    }
                }

                return UART_FAIL;
            }

            break;

        case CHECK_UTC:
            for(int i=3; i<RECEIVE_BUF_MAX; i++)
            {
                 if(receive_data[i-3] == 'Q' && receive_data[i-2] == 'L' && receive_data[i-1] == 'T' && receive_data[i] == 'S')
                 {
                     next_check = 1;
                 } 
            }

            if(1 == next_check)
            {

                for(int i=0; i<RECEIVE_BUF_MAX; i++)
                {
                    if(receive_data[i] == '\"')
                    {
                        utc_data[0] = receive_data[i];
                        utc_start = i+1;
                        next_check = 2;
                        break;
                    }
                }
            } 
            
            if (2 == next_check)
            {
                for(int j=utc_start; j<RECEIVE_BUF_MAX; j++)
                {
                    utc_data[j-utc_start+1] = receive_data[j];
                    if(receive_data[j] == '\"')
                    {
                        utc_data[j-utc_start+2] = '\0';
                        return UART_SUCCESS;
                    }                    
                }
            }

            break;

        case CHECK_GPS:
            for(int i=1; i<RECEIVE_BUF_MAX; i++)
            {
                if(receive_data[i-1] == 'O' && receive_data[i] == 'K')
                {
                    next_check = 1;
                }
            }

            if(1 == next_check)
            {
                for(int i=2; i<RECEIVE_BUF_MAX; i++)
                {
                     if(receive_data[i-2] == 'G' && receive_data[i-1] == 'P' && receive_data[i] == 'S')
                     {
                         calc_gps_message();
                         return UART_SUCCESS;
                     } 
                }

                return UART_FAIL;
            }

            break;

        case CHECK_PUBLISH:
            for(int i=0; i<RECEIVE_BUF_MAX; i++)
            {
                if(receive_data[i] == '>')
                {
                    return UART_SUCCESS;
                }
            }

            break;

        case CHECK_RECEPTION:
            for(int i=1; i<RECEIVE_BUF_MAX; i++)
            {
                if(receive_data[i-1] == '\r' && receive_data[i] == '\n')
                {
                    return UART_SUCCESS;
                }
            }

            break;

        default:

            break;
    }

    for(int i=4; i<RECEIVE_BUF_MAX; i++)
    {
        if(receive_data[i-4] == 'E' && receive_data[i-3] == 'R' && receive_data[i-2] == 'R' && receive_data[i-1] == 'O')
        {
            return UART_FAIL;
        }
    } 

    return UART_NONE;
}

GCPの設定

GCPを設定して、以下のフローを実行します。

GCPフロー図.png

GCPでは以下の設定が必要です。

  • GCPアカウント作成
  • IoT Coreの設定
    • レジストリID
    • リージョン
    • Cloud Pub/Subトピック
      • 選択するトピックがなければ、トピックの作成
    • プロトコルの設定はMQTT
  • 公開鍵・秘密鍵の作成
  • デバイスの作成
  • SORACOM側の設定
  • Pub/Subの確認
  • BigQueryの設定
    • データセット作成
    • テーブル作成
  • Storageの設定
    • バケットの作成
  • Dataflowの設定
    • BigQueryにエクスポート
    • ジョブ作成

以下の記事がとても参考になりました。

GCPのほうのIoTCoreを使ってみる

「SORACOM側の設定」までは以下の記事をご参照ください。

EC21-JとSORACOM IoT SIMによりGoogle IoT CoreでMQTT接続を行うまでの全過程の記録:4. Google IoT Core

「Pub/Subの確認」以降の作業を以下に記録しますが、「GCPのほうのIoTCoreを使ってみる」の内容をなぞるだけなので、そちらを見た方が分かりやすいと思います。

Pub/Subの確認

メニューからPub/Subを選択し、IoT Coreの設定で作成したトピック「test」があることを確認します。

pubsub.png

pubsub2.png

ちなみに「メッセージ表示」からPublishしたデータを確認できます。
まず、サブスクリプションを作成していなければ、作成してください。

例えば以下のように通信モジュールからデータをPublishしたとします。

AT+QMTPUB=0,0,0,0,"/devices/MatsuiEC21J/events"
{"Hello":"World1"}

(Ctrl-Zを押さないとデータ送信されないので、ご注意ください)

するとメッセージ表示画面でPULLをクリックすると、Publishしたデータが確認できます。
pubsub5.png

BigQueryの設定

データセット作成

メニューからBigQueryを選択し、リソースからプロジェクト名を選択して、データセットを作成をクリックします。

BQ1.png

データセット「gps」を作成します。

BQ2.png

テーブル作成

データセット「gps」を選択して、+マークをクリックしてテーブルを作成します。

BQ3.png

テーブル名とフィールドを設定します。フィールドを追加するごとに、テーブルの列が増えます。
「+フィールドを追加」でフィールドの名前、型とモードを設定してください。

BQ4.png

今回は以下のようなテーブル「gps_ueno」を作成しました。
仕事で上野・浅草に行く機会があったので、そこでGPS測定試験をすることにしました。

BQ5.png

Storageの設定

データを一時保存するStorageの設定をします。
メニューからStorageを選択し、「バケットを作成」をクリックします。

バケットの作成

Storage1.png

バケットの名前とデータ保存場所を設定します。
私はMulti-Regionに設定しましたが、実際はRegionで十分だと思います。

Storage2.png

作成したバケット内にフォルダを作成します。

Storage3.png

Dataflowの設定

Storageに蓄積したデータをBigQueryのテーブルに保存するためにはDataFlowを利用します。

BigQueryにエクスポート

Pub/Subのトピックの「...」から「エクスポート先」を「BigQuery」を選択します。

PubSub1.png

他と重複しないジョブ名を設定し、リージョンポイントを選択します。

Dataflow01.png

必須パラメータはトピックとテーブルと一時保存場所です。
トピックはPub/Subで選択したトピックを入力します。
テーブルは先ほどに作成したテーブルを選択します。
一時保存場所は、バケットで作成したフォルダを選択します。

Dataflow02.png

実行をクリックすればジョブの実行が開始します。

Dataflow3.png

ジョブ実行

ジョブの実行に成功すると以下のような画面が表示されます。

Dataflow04.png

ここで、マイコンでGPSデータを取得し、以下のコマンドを送信したとします。(コマンドの最後には、Ctrl-Zとして0x1aを送信します。)

AT+QMTPUB=0,0,0,0,"/devices/MatsuiEC21J/events"
{"gps_name":"EC21J","utc":"71632.0","latitude":"35.708255","longitude":"139.782183"}

するとBigQueryのテーブル「gps_ueno」にデータが保存されていることが確認できます。

Dataflow05.png

試験終了後はジョブを停止するのを忘れないようにお気をつけください。
私はこのジョブの停止を忘れて1週間放置したせいで、無料3万円分の内の1万円ほどが消えていました。

Dataflow06.png

未転送のデータがある場合は、「ドレイン」の設定にして、「STOP JOB」をクリックして終了します。

Dataflow07.png

GPSログの確認

本来ならGPSデータをリアルタイムにスマホアプリに表示したいところですが、今回はPCにテーブルデータをダウンロードして、GoogleMapに反映させる方法を採用します。

Google Mapのサイドメニューからマイプレイスを選びます。

GoogleMap1.jpg

マイマップから「地図を作成」をクリックします。
GoogleMap2.jpg

インポートでダウンロードしたテーブルデータを選択します。
GoogleMap3.png

テーブルデータの列から緯度と経度を選択します。
GoogleMap4.png

GoogleMap5.png

GPSデータの軌跡がマップ上に表示されます

GoogleMap.jpg

参照

8
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?