キュウリの記事を書いててこっちの方が受けがいいかなと思ったので追加。
モチベーション
キュウリの選別機じゃなくてガチプラスチック選別機なるもの作ってて、導入後一番大変なのが選別機に入ってる分析装置の設定。設定で1週間なんてざら、なにせ50台もあるし。それ一台一台調整していくとなるとマジシンドイ。ということで、閾値の設定位なら人がついておかなくてもできるだろということで作ってみた。おかげて、閾値調整だけで2日くらいかかる作業が2時間くらいで終わるようになった。
というのが5年くらい前で、ときどきRのバージョンが変わって読み込めなくなったりするんで、その時はコンパイルしなおす感じ。
装置構成
というわけで、基本的にはTCP通信できるようなRのパッケージを作ればOK。まずは、シンプルにここを参考にパッケージを作ってみる。環境はcore2duoのmacbookpro。あとは、R。エディタの代わりのXcode。
##コード
特にデータ型の扱いに癖が強すぎて結構大変なんです。という話は置いといて、ここを参考にソースを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);
}
一番面倒なところ
型とかメモリ確保とか意味不明すぎる。なにせ自前のソフトで利用も自分だけと思うと、なんでもいいから動けばいいというロジックが優勢になって、いろいろテキト=で、雑~な感じ。
それでも何かの参考になれば。