Raspberry Pi で取得したデータをM5StickCへ ~ UDPソケット通信

2020年8月15日

前回まで、スマートフォンで取得したデータをM5StickCへUDPソケット通信で送っていましたが、スマートフォンのUDPソケット通信プログラムに後述のような問題があるため、Raspberry PiからM5StickCへデータを送ってみました。

1.Raspberry Piで用意するハードウェア

Raspberry Piで取得しなければならないデータは、現在時刻高度です。このうち時刻はRaspberry Pi本体で取得できるので、高度のデータのみセンサーを追加します。今回も、秋月電子通商さんのGPSセンサーを使います。

2.Raspberry Piのプログラミング

今までもGPSセンサーを使うプログラムは作ってきているので、そのプログラムにUDPソケット通信のコードを追加します。

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#include <wiringPi.h>
#include <wiringSerial.h>
#include <string.h>
#include <unistd.h>

#include <sys/socket.h> 
#include <arpa/inet.h> 

#define ON 1
#define OFF 0

#define STOP_PIN	26
#define INTERRUPT_PIN	18
#define SERIAL_PORT "/dev/serial0"
#define ONE_LINE 1024

int g_fd ; // serial id
char g_oneline[ONE_LINE] ;

char g_ido[128] ;
char g_keido[128] ;
char g_speedbar[32] ;
char g_speedchr[32] ;
char g_altitude[32] ;
char g_altitude2[32] ;

void GetTime(char *timestr) {
	time_t current = time(NULL);
	struct tm* timer = localtime(&current);

	sprintf(timestr, "%02d-%02d %02d:%02d", timer->tm_mon+1, timer->tm_mday,
				timer->tm_hour, timer->tm_min) ;
}

void MySplit() {
	int counter = 0;
	int onelineLength = strlen(g_oneline) ;
	char buff[256] ;
	int point = 0 ;
	float value = 0 ;

	int _do ;
	int _hun ;
	int _byou ;
	float amari ;

	memset(buff, 0x00, 256) ;
	for (int i=0;i<onelineLength;i++) {
		if (g_oneline[i] == ',') {
			switch(point) {
			case 2 :
				// ido
				value = atof(buff) ;
				_do = (int)(value / 100) ;
				_hun = (int)(value - (_do * 100)) ;
				amari = value - _do * 100 - _hun ;
				_byou = (int)(amari * 60) ;
				amari = amari * 60 - (float)_byou ;
				// printf("do=%d hun=%d byou=%d amari=%f\n", _do, _hun, _byou, amari) ;
				sprintf(g_ido, "%03d'%02d'%02d'%1d", _do, _hun, _byou, (int)(amari * (float)10)) ;

				break ;
			case 4 :
				// keido
				_do = (int)(value / 100) ;
				_hun = (int)(value - (_do * 100)) ;
				amari = value - _do * 100 - _hun ;
				_byou = (int)(amari * 60) ;
				amari = amari * 60 - (float)_byou ;
				// printf("do=%d hun=%d byou=%d amari=%f\n", _do, _hun, _byou, amari) ;
				sprintf(g_keido, "%03d'%02d'%02d'%1d", _do, _hun, _byou, (int)(amari * (float)10)) ;

				break ;
				
			case 9 :
				// altitude
				value = atof(buff) ;
				// printf("altitude=%4.1f\n", value) ;
				sprintf(g_altitude, "%4.1f", value) ;
				
				break ;

			case 11 :
				// altitude2
				value = atof(buff) ;
				// printf("altitude2=%4.1f\n", value) ;
				sprintf(g_altitude2, "%4.1f", value) ;
				
				break ;

			default :
				break ;
			}
			point ++ ;
			memset(buff, 0x00, 256) ;
			counter = 0 ;
			
		} else {
			buff[counter] = g_oneline[i] ;
			counter ++ ; 
		}
	}


}

void MySplit2() {
	int counter = 0;
	int onelineLength = strlen(g_oneline) ;
	char buff[256] ;
	int point = 0 ;
	float value = 0 ;

	memset(buff, 0x00, 256) ;
	for (int i=0;i<onelineLength;i++) {
		if (g_oneline[i] == ',') {
			switch(point) {
			case 7 :
				// speed
				// printf("speedchr=%3.1f\n", value) ;
				sprintf(g_speedchr, "%3.1f km", value) ;

				break ;
			
			default :
				break ;
			}
			point ++ ;
			memset(buff, 0x00, 256) ;
			counter = 0 ;
			
		} else {
			buff[counter] = g_oneline[i] ;
			counter ++ ; 
		}
	}
}

void nmeacommand() {
	// GPGGA ONLY
	if (strncmp(g_oneline, "$GPGGA", 6) == 0) {
		MySplit() ;

	} else if (strncmp(g_oneline, "$GPVTG", 6) == 0) {
		MySplit2() ;
		
	}

}

void get_gps_data() {
	char buff[1024] ;
	int read_length ;
	int onelinepointer ;

	onelinepointer = strlen(g_oneline) ;

	read_length = serialDataAvail(g_fd) ;

	/*  受信データをバッファに入れる */
	for (int i=0;i<read_length;i++) {
		if (i+onelinepointer >= ONE_LINE) {
			break ;
		}
		g_oneline[onelinepointer] = serialGetchar(g_fd) ;
		if (g_oneline[onelinepointer] == 0x0a) { 
			// printf("%s", oneline) ;
			nmeacommand() ;
			memset(g_oneline, 0x00, ONE_LINE) ;
			onelinepointer = 0 ;
		} else {
			onelinepointer++ ;
		}

	}

}

int init() {
	int i = 0 ;

	if (wiringPiSetupGpio() == -1) {
		return -1 ;
	}

	g_fd = serialOpen(SERIAL_PORT, 9600); 

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

	pinMode(INTERRUPT_PIN, INPUT) ;
	pullUpDnControl(INTERRUPT_PIN, PUD_UP) ;
	wiringPiISR(INTERRUPT_PIN, INT_EDGE_FALLING, get_gps_data) ;
	waitForInterrupt(INTERRUPT_PIN, 2000) ;

	pinMode(STOP_PIN, INPUT) ;
	pullUpDnControl(STOP_PIN, PUD_UP) ;

	memset(g_oneline, 0x00, ONE_LINE);

	return 1 ;
}

int main(void) {
	char timestr[1024] ;

	struct sockaddr_in addr;
	int udfsocket;
	udfsocket = socket(AF_INET, SOCK_DGRAM, 0);

	if(udfsocket < 0) {
	    printf("socket error");
	    return -1;
	}
	addr.sin_family = AF_INET;
	addr.sin_port = htons(60000); // ポート番号
	addr.sin_addr.s_addr = inet_addr("192.168.xxx.xxx"); // Raspberry PiのIPアドレスを指定
	ssize_t socketresult ;
	char udpsendstr[1024] ;

	if (init() == -1) {
		return 1 ;
	}


	while(digitalRead(STOP_PIN) != 0) {

		GetTime(timestr) ;

		sprintf(udpsendstr, "%s,%s", timestr, g_altitude) ;

		socketresult = sendto(udfsocket, udpsendstr , strlen(udpsendstr), 0,
		            (struct sockaddr *)&addr, sizeof(addr) );

		if(socketresult < 0) {
			printf("send error");
			return -1;
		}

		printf("sendstr=%s\n", udpsendstr);

		sleep(5) ;

	}

	printf("Stop udp socket !! \n") ;

	serialClose(g_fd) ;


	return 1 ;
}

余計なコードが残っていることをご了承ください。Androidの時よりもずっとコードが簡単です。ビルドもすぐ終わるし、生産性だけならRaspberry Piの方が10倍はいいです。

3.動かしてみる

プログラムを動作させると、問題なくRaspberry PiのデータをM5StickCで受信できました。

しかし、今までも指摘したとおり、秋月電子通商さんのGPSユニットを使うと、同じ場所でも高度が上がったり下がったりします。取得している「アンテナ高度」は、「ジオイド高さ」を考慮した海抜高度だと思うのですが、どうも不安定なのです。

高度は気圧から取得した方がいいかなと思いますが、今回は外部プログラムを必要としないGPSからの高度でテストしました。

このような結果から、Raspberry Piで取得した各種データをM5StickCへ送る方法が優れていることがわかりました。冬の朝の冷え込みをM5StickCで確認するなんてこともできそうですし、利用できる範囲は多そうです。

(補足)スマートフォンのスリープ、バックグラウンド問題

スマートフォンを使ったセンサーの値取得には、問題があります。

まず最初に、スマートフォンのスリープ問題です。スマートフォンのアプリケーションは、バックグラウンドで動作させないと、スマートフォンがスリープした場合に停止してしまいます。スマートフォンをスリープさせない方法はあるのですが、私の機種(Color OS)ではできません(Android 10からはできるみたい)。

そしてバックグラウンドの動作も、長時間動作していると止められたり、メモリスクリーン系アプリで止められるなど、必ずしもバックグラウンドだから大丈夫とは言えないようです。OSのバージョン、メーカー、機種によって様々な報告があり、すべてに対応するのが難しいです。

そしてAndroid 11では、バックグラウンドで位置情報を取得するアプリケーションをplayストアに登録するには、googleの承認が必要になるらしいです。それは、位置情報を取得するアプリケーションがバッテリーを多く消費してリソースも消費するので、googleとしても放置しておけないようです。

このような経緯で、位置情報を扱うアプリケーションは、社会的に認知された法人でかつ、利用者が多く見込まれる場合でないと承認されないかもしれません。

※バックグラウンドで動作させなくても、
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
でアプリケーションをスリープさせないでおくことができました。

また、テザリングも問題です。私の使っているスマートフォンは、AndroidベースのColor OSを使っています。

このColor OSは、テザリングで未接続の状態が10分続くと、テザリングを終了させてしまいます。純粋なAndroidでは未接続でもテザリング無制限が出来るのですが、派生したOSではできない場合があります。

M5StickCはバッテリー容量が少ないので、Wi-Fi常時接続では1時間くらいしか稼働できません。そのため、普段はWi-Fiを切っておきたいです。

このように、Androidアプリケーションの作成は、バージョンやメーカー、機種などの条件を網羅する必要があって複雑化しています。スマートフォンのセンサー値をM5StickCに送る方法は、見送った方がよさそうです。

※モバイルバッテリーや車のUSB端子を使えば解決できそうです。