スマートフォンのGPSデータをRaspberry Piへ送る ~ UDP通信
今まで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で有効を確認しています。