1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Rと分析装置をsocketでつなげてみた

Last updated at Posted at 2018-02-26

キュウリの記事を書いててこっちの方が受けがいいかなと思ったので追加。

モチベーション

キュウリの選別機じゃなくてガチプラスチック選別機なるもの作ってて、導入後一番大変なのが選別機に入ってる分析装置の設定。設定で1週間なんてざら、なにせ50台もあるし。それ一台一台調整していくとなるとマジシンドイ。ということで、閾値の設定位なら人がついておかなくてもできるだろということで作ってみた。おかげて、閾値調整だけで2日くらいかかる作業が2時間くらいで終わるようになった。
というのが5年くらい前で、ときどきRのバージョンが変わって読み込めなくなったりするんで、その時はコンパイルしなおす感じ。

装置構成

図1.png

というわけで、基本的にはTCP通信できるようなRのパッケージを作ればOK。まずは、シンプルにここを参考にパッケージを作ってみる。環境はcore2duoのmacbookpro。あとは、R。エディタの代わりのXcode。

##コード
特にデータ型の扱いに癖が強すぎて結構大変なんです。という話は置いといて、ここを参考にソースをmysum.cで保存する。参考までにサンプルソース。

mysum.c
 void mysum(double *a, double *b, double *c)
 {
   double sum;
   sum =  *a + *b;
  *c = sum;
 }

コンパイル

ここを参考にターミナルから次のコマンドを実行してコンパイル。そうすると,オブジェクトファイル mysum.o 、共用ライブラリ mysum.so が作成されるのでsoファイルを使う。

# R CMD SHLIB mysum.c

Rへの読み込み

最後もここを参考にRのコンソールで下を実行する、mysum(a,b)みたいな感じをやると答えが表示される。

> dyn.load("./mysum.so")
> mysum <- function(a,b){ 
    .C("mysum",           #Cルーチン名
      arg1=as.double(a), #Cルーチン第1引数の型指定
      arg2=as.double(b), #Cルーチン第2引数
      arg3=double(1)     #Cルーチン第3引数
      )
 }

TCPで通信

ここまでくればあとはsocketで通信してやればOK。設計どうしようかなって思ったけど、シンプルにコマンド毎に関数を作ってやって、引数でコマンドのパラメータを渡す感じにして、レスポンスは戻り値で返すよにした。参考までに実際のコードの一部。本来ならタイムアウトとかなんとかつけてやればいいかもだけど、所詮自前のソフト、使うのも自分だけとなると面倒。

#include <R.h>
#include <Rdefines.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
 




int command_reqest( const char* ip, int port, char* send_cmd, size_t n_send_cmd, char* recv_cmd, size_t n_recv_cmd)
{
	
	int ret;

	/* ソケット関数 */
	int sock;
	struct sockaddr_in server;

	
	
	/* ソケットの作成 */
	sock = socket(AF_INET, SOCK_STREAM, 0);
	if( sock == -1 ){
		return -1;
	}
	
	/* 接続先指定用構造体の準備 */
	server.sin_family = AF_INET;
	server.sin_addr.s_addr = inet_addr( ip );
	server.sin_port = htons(port);
	
	/* サーバに接続 */
	ret = connect(sock, (struct sockaddr *)&server, sizeof(server));
	if( ret == -1 ){
		close(sock);
		return -2; 
	}
	
	/* コマンド送信 */
	ret = write(sock, send_cmd, n_send_cmd);
	if( ret == -1 ){
		close(sock);
		return -3;
	}
	
	usleep(500000);
		// ここは装置のレスポンス次第で適当に

	
	/* サーバーからからデータを受信 */
	ret = read(sock, recv_cmd, n_recv_cmd);
	if( ret == -1 ){
		close(sock);
		return -4;
	}

	
	close(sock);
	return 0;
	
}



/*
 dyn.load("cmd_raman.so")
 com_cmd_pict <- function(channel){ 
 .Call("com_cmd_pict",           	#Cルーチン名
 as.character("192.168.xxx.xxx"),	#Cルーチン第1引数
 as.integer([port num]),			#Cルーチン第2引数
 as.integer(channel)			#Cルーチン第3引数
 )
 }
 com_cmd_pict(1)
 */


/* 
 *	PICT コマンドの送受信をする関数
 *	
 *@param [in]	ip			識別装置のIP
 *@param [in]	port		識別装置のポート
 *@param [in]	channel		識別器のチャンネル
 *@param [out]	result		処理結果
 */ 
SEXP com_cmd_pict(SEXP ip, SEXP port, SEXP channel)
{
	/* 引数ポインタ */
	const char	*lp_ip;
	int		*lp_judge_map;
	int		l_port, l_channel;
	
	/* 結果 */
	SEXP	attr_names, ans;
	SEXP	ccd_data, peak, normalized, result1, result2;
	
	/* ローカル */
	int		i, ret, ptr, val;
	char	buff[33];
	char	send_buff[21]; 
	unsigned char recv_buff[2132];
	
	
	
	/* 引数の割り当て */
	lp_ip = CHAR(STRING_ELT(ip, 0));
	l_port = INTEGER(port)[0];
	l_channel = INTEGER(channel)[0];
	
	
	
	/* 引数のチェック */
	if( l_channel >= 100 ){
		error("channel(%d) should be less than 100.\n", l_channel);
	}
	
	/* 送信コマンド */
	memset( send_buff, '\0', sizeof(send_buff) );
	sprintf( send_buff, "XXXXXXXXPICT000000000000", l_channel );
	
	/* 受信バッファの初期化 */
	memset( recv_buff, '\0', sizeof(recv_buff) );
	
	/* 通信 */	
	ret = command_reqest( lp_ip, l_port, 
						 send_buff, strlen(send_buff),
						 recv_buff, sizeof(recv_buff) 
						 );
	if( ret != 0 ){
		error( "command_reqest() failed: %d", ret );
	}
	
	
	/* 応答ヘッダの確認 */
	memset( buff, '\0', sizeof(buff) );
	sprintf( buff, "XXXXXXPICT000000002112", l_channel );
	if( strncmp( buff, recv_buff, 20 ) != 0 ){
		memset( buff, '\0', sizeof(buff) );
		strncpy( buff, recv_buff, 20 );
		warning("invalid replay headder: %s\n", buff);
	}
	
	
	/* 結果書き込み */
	PROTECT(ans = allocVector(VECSXP, 5));
	
	
	/* CCDデータを保存 */
	PROTECT(ccd_data = NEW_NUMERIC(1024));
	for( i=0; i<1024; i++ ){
		ptr = i * 2 + 0 + 20;
		NUMERIC_POINTER(ccd_data)[i] = recv_buff[ptr]*0x100+recv_buff[ptr+1];
	}
	SET_VECTOR_ELT(ans, 0, ccd_data);
	
	/* 高さデータ1を保存 */
	PROTECT(peak = NEW_NUMERIC(10));
	for( i=0; i<10; i++ ){
		ptr = i * 3 + 2048 + 20;
		
		val = recv_buff[ptr+1]*0x100 + recv_buff[ptr+2];
		if( recv_buff[ptr] == 0x01 )
			val = -1 * val;

		/*warning("data[%d]: %02x %02x %02x => %d\n", 
						ptr, recv_buff[ptr], recv_buff[ptr+1], recv_buff[ptr+2],val);
		 */
		
		NUMERIC_POINTER(peak)[i] = val;
	 }
	SET_VECTOR_ELT(ans, 1, peak);

	
	/* 高さデータ2を保存 */
	PROTECT(normalized = NEW_NUMERIC(10));
	for( i=0; i<10; i++ ){
		ptr = i * 3 + 2078 + 20;
		
		val = recv_buff[ptr+1]*0x100 + recv_buff[ptr+2];
		if( recv_buff[ptr] == 0x01 )
			val = -1 * val;
	 
		/*warning("data[%d]: %02x %02x %02x => %d\n", 
					ptr, recv_buff[ptr], recv_buff[ptr+1], recv_buff[ptr+2],val);
		*/

		NUMERIC_POINTER(normalized)[i] = val;
	 }
	SET_VECTOR_ELT(ans, 2, normalized);

	/* 判定結果データを保存 */
	PROTECT(result1 = NEW_NUMERIC(1));
	ptr = 2108 + 20;
	NUMERIC_POINTER(result1)[0] = recv_buff[ptr+0]*0x100 + recv_buff[ptr+1];
	SET_VECTOR_ELT(ans, 3, result1);
	
	PROTECT(result2 = NEW_NUMERIC(1));
	ptr = 2110 + 20;
	NUMERIC_POINTER(result2)[0] = recv_buff[ptr+0]*0x100 + recv_buff[ptr+1];
	SET_VECTOR_ELT(ans, 4, result2);
	

	/* 名称を設定 */
	PROTECT(attr_names = allocVector(STRSXP, 5));
	SET_STRING_ELT(attr_names, 0, mkChar("data"));
	SET_STRING_ELT(attr_names, 1, mkChar("peak"));
	SET_STRING_ELT(attr_names, 2, mkChar("normalized"));
	SET_STRING_ELT(attr_names, 3, mkChar("result1"));
	SET_STRING_ELT(attr_names, 4, mkChar("result2"));
	setAttrib(ans, R_NamesSymbol, attr_names);
	
	
	UNPROTECT(7);
	return(ans);
	
}


一番面倒なところ

型とかメモリ確保とか意味不明すぎる。なにせ自前のソフトで利用も自分だけと思うと、なんでもいいから動けばいいというロジックが優勢になって、いろいろテキト=で、雑~な感じ。

それでも何かの参考になれば。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?