SensorCast ~ 指定IPアドレスにGPSデータをUDP送信

2024年6月7日

今までにスマートフォンで取得したGPSデータを指定IPアドレスに送信する記事を出してきましたが、そのスマートフォン側のアプリケーションをGoogle Playに登録しました。

というのも、Raspberry PiやM5StackでGPS受信機を付けるアプリケーションを組んでも、実際に使おうとすると面倒だったり、うまくリンクできなかったりして、あまり使わないことが多いのです。

そこで、Raspberry PiやM5Stackは表示専門に徹してもらって、元データはスマートフォンで処理してみたらと考えたわけです。また、今まで公開しているスマートフォン側のソースが断片的なため、記事を読んだ人が実際にプロジェクトを作ってアプリケーションを作るのは敷居が高いだろうということで、アプリケーションを一般公開することにしました。

とは言っても、IPアドレスとポート番号を指定するだけのアプリケーションです。そして、送信されるUDP送信文字列は

高度,速度,緯度,経度 ※カンマ区切り

という、超シンプルなものです。少しずつ、送信データ項目を増やそうとは思っていますが、需要も少ないと思うので気長にやっていきたいと思います。

Google Playへのリンクは以下のとおりです。

注意事項

(1).当アプリを利用した際の、直接的または間接的な損害・損失・不利益・精神的苦痛などに対して、kunimiyasoftは一切責任を負わないものとします。
(kunimiyasoft assume no responsibility whatsoever for any direct or indirect damage, loss, prejudice or emotional distress caused by use of this Application)

(2).当アプリは位置情報を利用します。位置情報は消費電力が比較的大きく、特にGPS機能がONになっていると多く電力を消費します。利用する際はバッテリー残量に注意してください。

(3).アイコンは、CoPilotにて作成しました。

SensorCastのプライバシーポリシー(privacy policy)

  • 位置情報の使用
    現在位置、高度、速度の取得に位置情報を使用します。
  • インターネットへのアクセス
    UDP送信のために、インターネットへアクセスします。データ通信が使われることをご了承ください。
  • 個人情報
    個人情報は取得しません。個人情報、位置情報をネットを介してkunimiyasoftへ通知する仕組みはありません。

android.os.NetworkOnMainThreadException

Google Playに登録作業をしているのですが、リリース前レポートで

android.os.NetworkOnMainThreadException

が出る機種が報告されて、登録が完了しません。これは、メインスレッドでネットワーク操作を実行しようとしたときに発生する例外です。非同期にしなければならないことは分かっているのですが、非同期のやり方に問題があるようです。しばらく、調査を行います。

⇒「InetAddress.getByName」が非同期である必要がありました。

広告 ID の申告が不完全です

これ、良く分かっていない。使用していないで申告しても、結局はじかれる。「さっぽろ周辺マップ ライト3」とかでは、使用していることにして登録していますが、今回のテスト的なプログラムでも必要なのでしょうか。⇒結局、SensorCastも広告IDを使う設定にしました(何か、いろいろやっているうちにそうなってしまったようです)

サンプルプログラム

SensorCast向けのサンプルプログラムを用意しました。ATOMS3 LITE用のプログラムです。表示器にSSD1306ドライバを使う128×64ドット表示のOLEDディスプレイを使います。接続に関しては、プログラムから判断するか、以下の記事を参照してください。

#include <M5AtomS3.h>
#include <FastLED.h>

#include <WiFi.h>

#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128  // OLED 幅
#define SCREEN_HEIGHT 64  // OLED 高さ
#define OLED_RESET -1     // リセットピン
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

// Wi-Fi環境
const char* ssid = "XXXXXXXXXX";
const char* password = "XXXXXXXXXX";

WiFiClient client;

bool isSignal = false;    // 1度でも信号が来たら、trueのまま
static int dispmode = 0;  // 0 速度 / 1 高度

#include <WiFiUdp.h>
WiFiUDP udp;
const int port = 60000;  // ポート番号
#define BUFSIZE 1024     // UDP受信最大サイズ

#define CBUTTON 41  // ATOMS3 LITE ボタンGP

void Wifi_connect() {
  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    Serial.print('.');
    delay(500);
  }
  Serial.println("\nWi-Fi connected");
  AtomS3.dis.drawpix(0x0000ff);
  AtomS3.update();

  udp.begin(port);
}

void setup() {
  // put your setup code here, to run once:
  AtomS3.begin(true);  // LED Enable
  AtomS3.dis.setBrightness(100);

  Wire.end();          // これをしないと動作しない場合がある
  Wire.begin(38, 39);  // SDA / SCL

  AtomS3.dis.drawpix(0x00ff00);
  AtomS3.update();

  // Wi-Fiの接続
  Wifi_connect();

  // SSD1306 開始
  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    Serial.println(F("SSD1306 allocation failed"));
    for (;;)
      ;
  }

  delay(1000);

  pinMode(CBUTTON, INPUT_PULLUP);  // モード切替

  delay(1000);
}

void split(String data, String* lines) {
  int count = 0;
  int datalength = data.length();
  for (int i = 0; i < datalength; i++) {
    char onec = data.charAt(i);
    if (onec == ',') {
      count++;
      if (count == 4) {
        break;
      }
    } else {
      lines[count] += onec;
    }
  }
}

void loop() {
  char buff[128];
  char packetBuffer[BUFSIZE];
  String lines[4];
  // AtomS3.update();

  int btn = digitalRead(CBUTTON);

  if (btn == LOW) {
    dispmode++;

    if (dispmode >= 2) {
      dispmode = 0;
    }
  } else {
    // nop
  }

  if (WiFi.status() != WL_CONNECTED) {
    // 接続が切れた
    AtomS3.dis.drawpix(0x00ff00);
    AtomS3.update();

    WiFi.disconnect();
    WiFi.reconnect();

    while (WiFi.status() != WL_CONNECTED) {
      Serial.print('R');
      delay(500);
    }
    Serial.println("\nWi-Fi connected");
    AtomS3.dis.drawpix(0x0000ff);

    AtomS3.update();
  }

  switch (dispmode) {
    case 0:
    case 1:
      {
        int packetSize = udp.parsePacket();

        if (packetSize) {
          isSignal = true;

          udp.read(packetBuffer, packetSize);

          if (dispmode == 0) {

            split(packetBuffer, lines);

            Serial.println("received");
            Serial.println(packetBuffer);

            display.clearDisplay();
            display.setTextSize(5);
            display.setTextColor(SSD1306_WHITE);
            display.setCursor(0, 0);
            double wAltitude = lines[0].toDouble();
            sprintf(buff, "%04d", (int)(wAltitude + 0.5));

            display.println(buff);

            display.setTextSize(2);
            display.setCursor(96, 48);
            display.println("m");

          } else if (dispmode == 1) {
            split(packetBuffer, lines);

            Serial.println("received");
            Serial.println(packetBuffer);

            display.clearDisplay();
            display.setTextSize(6);
            display.setTextColor(SSD1306_WHITE);
            display.setCursor(0, 0);
            double wSpeed = lines[1].toDouble();
            sprintf(buff, "%03d", (int)(wSpeed + 0.5));

            display.println(buff);

            display.setTextSize(2);
            display.setCursor(80, 48);
            display.println("km");
          }

          display.display();

        } else {
          // シグナルがない  

          if (isSignal) {
            // nop  
          } else {
            // UDP受信するまでは、IPアドレスを表示しておく
            display.clearDisplay();
            display.setTextSize(2);               // Normal 1:1 pixel scale
            display.setTextColor(SSD1306_WHITE);  // Draw white text
            display.setCursor(0, 0);              // Start at top-left corner
            display.println(WiFi.localIP());
          }

          display.display();
        }
      }
      break;

    default:

      break;
  }

  delay(100);
}

高度と速度を表示するプログラムです。ATOMS3 LITEのボタンを押すごとに、切り替わります。こんな風に、SensorCastを使ってみてください。

動画も作ってみました。