スマートフォンのGPSデータをRaspberry Piへ送る ~ UDP通信

2020年7月9日

今までRaspberry Pi(ラズベリーパイ)にGPS受信機を付けて緯度・経度、速度などを確認するプログラムを作ってきましたが、スマートフォンでもできることから、実際の山歩きではあまり使っていません。

そこで、GPS絡みはスマートフォンを使うことが多いので、Raspberry PiではスマートフォンのGPSデータを転送してもらうのもありだと思っています。屋外でRaspberry Piを使う時は、スマートフォンのテザリングを使うことが多いのですから、スマートフォンで取得できるデータを活用した方が良いはずです。

それでは、スマートフォンのセンサーのデータをRaspberry Piで受信するには、どうすれば良いでしょうか。

1.UDPソケット通信を使う

スマートフォンからRaspberry Piへデータを送る方法の一つとして、UDPソケット通信があります。これを使えば、ローカルなネットワーク上にある端末同士でデータのやり取りができます。

今回は、簡単な文字列(緯度と経度)をスマートフォンからRaspberry Piへ送ってみました。実際にGPSデータを送る場合は複雑な構造のデータが必要ですが、基本的な仕組みは同じです。

今回のプログラム作成は、以下のサイト様の記事を参考にさせて頂きました。

2.スマートフォン側のプログラム

スマートフォン側で用意するUDPソケット通信のコードは以下のとおりです。

    @Override
    public void onLocationChanged(Location location) {
        mLongtitude = location.getLongitude();
        mLatitude = location.getLatitude();

        new Thread(new Runnable() {
            @Override
            public void run() {

                try {
                    DatagramSocket sendUdpSocket = new DatagramSocket();
                    InetAddress IPAddress = InetAddress.getByName(clientIPAddress);
                    String str = format("%f,%f", mLongtitude, mLatitude);

                    byte[] strByte = str.getBytes("UTF-8");

                    DatagramPacket sendPacket = new DatagramPacket(strByte, strByte.length, IPAddress, 60000);

                    sendUdpSocket.send(sendPacket);
                    sendUdpSocket.close();

                } catch (IOException e) {

                    System.out.println(e.getMessage());
                }
            }
        }).start();
    }

緯度・経度は、onLocationChanged関数で取得できるので、その中にUDPソケット通信のコードを書きます。(GPS受信ロジックは割愛します)

送信先のRaspberry Piが持つIPアドレス(clientIPAddress)を指定して、メッセージを送信します。注意することとして、UDPソケット通信の処理は別スレッドで動かします。今回は1つの文字列だけ送るので、処理が簡単です。

3.Raspberry Pi側のプログラム

Raspberry Pi側は、ターミナルから起動するプログラムにしました。

#include <stdio.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>

const int PortNumber = 60000;
const int BufferLength = 1024;

int main(int argc, char** argv)
{
    struct sockaddr_in addr , from_addr;
    int bind_status;
    int sock;
    char buff[BufferLength];
    socklen_t from_addr_size;    

    sock = socket(AF_INET, SOCK_DGRAM, 0);

    if(sock < 0) {
        printf("Couldn't make a socket\n");
        return -1;
    }

    addr.sin_family = AF_INET;
    addr.sin_port = htons(PortNumber);
    addr.sin_addr.s_addr = INADDR_ANY;
    bind_status = bind(sock, (struct sockaddr *)&addr, sizeof(addr) );

    if(bind_status < 0) {
        printf("bind error\n");
        return -1;
    }

    printf("bind success\n");

    memset(buff, 0x00, BufferLength);

    while(1) {
        int recv_status;

        recv_status = recvfrom(sock, buff, BufferLength, 0, (struct sockaddr *)&from_addr, &from_addr_size);

        if (recv_status < 0) {
            continue;
        }    

        buff[BufferLength - 1]= '\0';
        printf("receive data:%s\n", buff);
    }

    close(sock);

    return 0;
}

このように、スマートフォンから送られる文字列を待機して、受信したらターミナルへ出力します。

4.実行してみる

実行してみて、文字列が受信できることを確認しました。

5.M5StickCで受信するプログラムを作成予定

実は今回のテストはRaspberry Piのためではなく、M5StickCを念頭に置いたものです。

M5StickCに合わせた拡張機器である「ハット」にはGPSセンサーがなく(予定はあるようです)、M5Stack用のGPSセンサーはケーブルで接続しなければなりません。また、M5StickCに内蔵されるバッテリー容量は少なく、GPSを動かすとすぐに容量を使い切ってしまいます。

そこで、GPSデータはスマートフォンから送って、M5StickCは表示するだけにしようと考えています。これでもバッテリーがどれだけ持つか分かりませんが、期待しています。

M5StickCでのテスト結果は、近日公開予定です。

補足:Android スマートフォンのスリープ問題

Android スマートフォンは、操作をしないまま指定の時間になるとスリープします。その操作をしない時間の最大値は、たったの30分です。そのため、今回のようなGPSデータを送信するようなプログラムを作っても、スリープになると止まってしまいます。

そのため、バックグラウンドで動作するプログラムを作る必要があります。ところが現時点では、バックグラウンドとUDPソケット通信を同時に行うと、UDPソケット通信部分などで例外処理が発生します。いろいろ試したのですが、どうしてもうまく行きません。

そこで今対処するなら、Androidスマートフォンを充電中の状態で、 管理の中の「開発者向けオプション」でスリープさせない設定にするしかありません。でも、Androidスマートフォンは、充電していない状態で使いたいです。

何か分かったら、追記します。

以下のコードをonCreate関数内で定義すると、スリープにならないようです。

   @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // 常時点灯
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

SdkVersionが33で有効を確認しています。