ATOMIC GPS ベース(M8030-KT) を ATOMS3 につないで表示と MQTT 送信を整えたメモ

ATOMIC GPS ベース(M8030-KT) を ATOMS3 につないで表示と MQTT 送信を整えたメモ

この記事は M5Stack Qiita Advent Calendar 2024 の 16 日目の記事です。

ATOMIC GPS ベース(M8030-KT) を ATOMS3 につないで表示と MQTT 送信を整えたメモです。

2024/12/15 の時点の情報で進めます。

ATOMIC GPS ベース(M8030-KT)

ATOMIC GPS ベース(M8030-KT) — スイッチサイエンス

ATOMシリーズ用のGPS測位モジュールです。M8030-KT測位ナビゲーションチップ、FLASHメモリを内蔵しています。また、ユーザー設定の電源オフ保持をサポートするボタン電池を内蔵しています。このモジュールは、地理座標の表示、バス停のアナウンス、車両や船舶のナビゲーション、軌跡の追跡などに応用できます。

GPSデータ通信の一般的な規格であるNMEA-0183プロトコルでデータを出力します。GPS、GLONASS、GALILEO、BDS、SBAS、QZSSを含む複数の衛星システムをサポートしています。72チャンネルのサーチ機能により、衛星信号を正確に捕捉・追尾し、正確な測位を実現します。

さらに、MicroSDカードスロットが含まれています。このスロットは、MicroSDカードからGPSやその他のファイルデータを読み込むことができます。例えば、地図ソフトで移動の軌跡を見るために、GPSデータを特定のフォーマットでエクスポートすることができます。また、通常のカードリーダーとしても機能し、ファイルの読み書きが可能です。

ATOM LITE、ATOM Matrix、ATOMS3 LITE、ATOMS3にマウントすることができます。

とのことで ATOM3S を使ってコンパクトに IoT 的に位置情報を扱うデバイスができます。

実際のプログラム

ATOM3S に書き込んだプログラムはこちらです。

Atomic GPS Base ドキュメント を参考にしつつ、
M5AtomS3/examples/AtomicBase/AtomicGPS/GPS/GPS.ino at main · m5stack/M5AtomS3

のサンプルコードで表示と MQTT 送信を整えて ATOM3S に以下のプログラムを書き込みます。

#include "M5AtomS3.h"
#include <TinyGPSPlus.h>

#include <WiFi.h>
#include <WiFiMulti.h>
WiFiMulti wifiMulti;

#include <ArduinoJson.h> // バージョンは v6 で
#include <WiFiClient.h>
#include <PubSubClient.h>

const char* ssid     = "ssid";
const char* password = "password";

// MQTTの接続先のIP
const char *endpoint = "endpoint";
// MQTTのポート
const int port = 1880;
// 今回使いたい MQTT のユーザー名
const char *mqttUsername = "mqttUsername";
// 今回使いたい MQTT のパスワード
const char *mqttPassword = "mqttPassword";

// デバイスID
char deviceID[50];

// トピックを保存するためのバッファを確保
char pubTopic[50];
char subTopic[50];

// プログラム名
#define FW_NAME "AtomS3_GPS_Base_Wifi"
// ファームウェアバージョン
#define FW_VERSION "2024.1.0.2"

// ArduinoJson v6 の記述で JSON を格納する StaticJsonDocument を準備
StaticJsonDocument<512> root;

WiFiClient httpsClient;
PubSubClient mqttClient(httpsClient);
char pubJson[512];

// The TinyGPSPlus object
TinyGPSPlus gps;

int setupFlag = 0;

static void smartDelay(unsigned long ms) {
    unsigned long start = millis();
    do {
        while (Serial2.available()) gps.encode(Serial2.read());
    } while (millis() - start < ms);
}

void setup() {
    auto cfg = M5.config();
    AtomS3.begin(cfg);

    wifiMulti.addAP(ssid, password);

    AtomS3.Display.setTextDatum(TL_DATUM);
    AtomS3.Display.setCursor(0,0);
    AtomS3.Display.setTextSize(1);
    AtomS3.Display.println("[GPS]");
    AtomS3.Display.println(FW_NAME);
    AtomS3.Display.println(FW_VERSION);
    
    delay(1000);
    
    Serial2.begin(9600, SERIAL_8N1, 5, -1);

    WiFi.mode(WIFI_STA);
    WiFi.setSleep(false);

    
    Serial.print("Connecting...");
    AtomS3.Display.println("WiFi ");
    AtomS3.Display.print(".");
    // Serial.println(ssid);
    // Wait for connection
    // while (WiFi.status() != WL_CONNECTED) {
    while (wifiMulti.run() != WL_CONNECTED) {
      delay(500);
      Serial.print(".");
      AtomS3.Display.print(".");
    }

    Serial.println("");
    Serial.print("Connected to ");
    Serial.println(WiFi.SSID());
    Serial.print("IP address: ");
    Serial.println(WiFi.localIP());
    Serial.print("MAC address: ");
    Serial.println(WiFi.macAddress());

    AtomS3.Display.setCursor(0,0);
    AtomS3.Display.clear();
    AtomS3.Display.println("[GPS]");
    AtomS3.Display.println(FW_NAME);
    AtomS3.Display.println(FW_VERSION);
    AtomS3.Display.println("WiFi OK!");
  
    // deviceID
    String macStr = String(WiFi.macAddress());
    macStr.replace(":", "");
    sprintf(deviceID, "%s", macStr);
    
    AtomS3.Display.print("ID ");
    AtomS3.Display.println(deviceID);
    AtomS3.Display.print("IP ");
    AtomS3.Display.println(WiFi.localIP());
    
    // メッセージを知らせるトピック
    sprintf(pubTopic, "device/%s/pub", deviceID);
    // メッセージを待つトピック
    sprintf(subTopic, "device/%s/sub", deviceID);

    AtomS3.Display.println("----");
    AtomS3.Display.print("pubTopic ");
    AtomS3.Display.println(pubTopic);
    AtomS3.Display.println("----");
  
    mqttClient.setServer(endpoint, port);
    mqttClient.setCallback(mqttCallback);
    mqttClient.setBufferSize(512);
    
    connectMQTT();

    delay(1000);
    AtomS3.Display.print("... 5 ");
    delay(1000);
    AtomS3.Display.print("4 ");
    delay(1000);
    AtomS3.Display.print("3 ");
    delay(1000);
    AtomS3.Display.print("2 ");
    delay(1000);
    AtomS3.Display.print("1 > GO!");
    delay(1000);

    setupFlag = 1;
    
}

void connectMQTT() {
  
  // 起動してからの経過時間でシードを設定
  randomSeed(millis());
  
  // MQTT clientID のランダム化(名称重複対策)
  char clientID[40] = "clientID";
  String rndNum = String(random(0xffffff), HEX);
  String deviceIDRandStr = String("client-");
  deviceIDRandStr.concat(deviceID);
  deviceIDRandStr.concat("-");
  deviceIDRandStr.concat(rndNum);
  deviceIDRandStr.toCharArray(clientID, 40);
  Serial.print("clientID ");
  Serial.println(clientID);

  AtomS3.Display.print("MQTT");
  
  while (!mqttClient.connected()) {
    if (mqttClient.connect(clientID,mqttUsername,mqttPassword)) {
      Serial.println("MQTT Connected.");
      int qos = 0;
      mqttClient.subscribe(subTopic, qos);
      Serial.println("MQTT Subscribed.");
      // Connected message
      DynamicJsonDocument doc(512);
      doc["type"] = "connected";
      doc["localIP"] = WiFi.localIP();
      serializeJson(doc, pubJson);
      mqttClient.publish(pubTopic, pubJson);
      AtomS3.Display.println(" > OK!");
    } else {
      Serial.print("Failed. Error state=");
      Serial.println(mqttClient.state());
      // Wait 5 seconds before retrying
      delay(5000);
    }
    
  }
}

void mqttCallback (char* topic, byte* payload, unsigned int length) {
  
    String str = "";
    Serial.print("Received. topic=");
    Serial.println(topic);
    for (int i = 0; i < length; i++) {
        Serial.print((char)payload[i]);
        str += (char)payload[i];
    }
    Serial.print("\n");
 
    // Deserialize the JSON dsocument
    DeserializationError error = deserializeJson(root, str);
 
    // Test if parsing succeeds.
    if (error) {
      Serial.print(F("deserializeJson() failed: "));
      Serial.println(error.f_str());
      return;
    }

    //
}

void mqttLoop() {
    if (!mqttClient.connected()) {
        connectMQTT();
    }
    mqttClient.loop();
}

void loop() {
    // 常にチェックして切断されたら復帰できるように
    mqttLoop();
    
    AtomS3.update();
    
    AtomS3.Display.setCursor(0,0);
    AtomS3.Display.clear();
    AtomS3.Display.println("[GPS]");
    AtomS3.Display.print("sate :");
    AtomS3.Display.println(gps.satellites.value());
    AtomS3.Display.println("lng :");
    AtomS3.Display.println(gps.location.lat());
    AtomS3.Display.println("lng :");
    AtomS3.Display.println(gps.location.lng());
    AtomS3.Display.println("alt :");
    AtomS3.Display.println(gps.altitude.meters());

    
    if (millis() > 5000 && gps.charsProcessed() < 10){
        Serial.println(F("No GPS data received: check wiring"));
        AtomS3.Display.println("[x] No GPS data received");
    }

    smartDelay(1000);

    if (AtomS3.BtnA.wasPressed()) {
        
        DynamicJsonDocument doc(512);
        doc["type"] = "button";
        doc["satellites"] = gps.satellites.value();
        doc["lat"] = gps.location.lat();
        doc["lon"] = gps.location.lng();
        doc["alt"] = gps.altitude.meters();
        serializeJson(doc, pubJson);
        mqttClient.publish(pubTopic, pubJson);
    
        AtomS3.Display.setCursor(0,0);
        AtomS3.Display.clear();
        AtomS3.Display.println("[GPS]");
        AtomS3.Display.println("-> MQTT Send!");
        AtomS3.Display.println("----------");
        AtomS3.Display.print("sate :");
        AtomS3.Display.println(gps.satellites.value());
        AtomS3.Display.println("lng :");
        AtomS3.Display.println(gps.location.lat());
        AtomS3.Display.println("lng :");
        AtomS3.Display.println(gps.location.lng());
        AtomS3.Display.println("alt :");
        AtomS3.Display.println(gps.altitude.meters());
        
        Serial.println("Pressed");

        delay(1000);
    }

    
}

こちらを書き込んだら、

ATOMIC GPS ベース(M8030-KT) を ATOMS3 につないで電源を入れます。Wi-Fi を探して見つかると MQTT につないで GPS 捜索を開始します。

このように動きます。衛星数の sate が 0 なのは、一度ベランダで捉えてから家に戻って撮影しているためです。

画面クリックして位置情報が MQTT で送れる

if (AtomS3.BtnA.wasPressed()) {
        
        DynamicJsonDocument doc(512);
        doc["type"] = "button";
        doc["satellites"] = gps.satellites.value();
        doc["lat"] = gps.location.lat();
        doc["lon"] = gps.location.lng();
        doc["alt"] = gps.altitude.meters();
        serializeJson(doc, pubJson);
        mqttClient.publish(pubTopic, pubJson);
    
        AtomS3.Display.setCursor(0,0);
        AtomS3.Display.clear();
        AtomS3.Display.println("[GPS]");
        AtomS3.Display.println("-> MQTT Send!");
        AtomS3.Display.println("----------");
        AtomS3.Display.print("sate :");
        AtomS3.Display.println(gps.satellites.value());
        AtomS3.Display.println("lng :");
        AtomS3.Display.println(gps.location.lat());
        AtomS3.Display.println("lng :");
        AtomS3.Display.println(gps.location.lng());
        AtomS3.Display.println("alt :");
        AtomS3.Display.println(gps.altitude.meters());
        
        Serial.println("Pressed");

        delay(1000);
    }

だいたい、このあたりのコードですが、画面クリックして位置情報が MQTT で送れます。

このように Node-RED で MQTT を待ち受けます。

クリックすると MAC アドレスがをベースに MQTT トピックを作りつつ、現在の衛星数・緯度・経度・高度を送ってくれます!