コマンドラインプログラム(gvc_cmd.c)

// --------------------------------------------------
// Global Versatile Controler http://www.gvc-on.net/
// --------------------------------------------------
// --------------------------------------------------
// Revision Memo (Y.M.D Editor/Memo)
// --------------------------------------------------
// 
// 2013.06.12 メッセージキュー部分の実装(移植)をする
// 
// 2013.06.10 T.Kabu
// 汎用制御装置 Grobal Versatile Controller CLI (gvc_cmd)
// 
// コンパイルの際には -lpcre をつけること
// sync;gcc -O2 -Wall -lm -lpcre ./gvc_cmd.c -o gvc_cmd
// 
// 参考URLいろいろ
// http://pinka99.ddo.jp/nanao/work/daemon.html
// http://d.hatena.ne.jp/rero/20041002/p1
// http://linuxmag.sourceforge.jp/Japanese/March2003/article287.shtml
// http://www.geocities.co.jp/Athlete-Samos/7760/study/msgkyu1.html
// http://www.geocities.jp/taka_owl2005/job/UNIX/kernel/ipc.html
// http://d.hatena.ne.jp/ka2yan/20090327
//
// ------------------------------
// BASE
// ------------------------------
// 2012.02.06 T.Kabu gvc_cmd    gvcdに対してコマンド送信をする
// 2012.03.09 T.Kabu gvc_cmd2   暫定的にスイッチON対応(GVC側はタイマーONを想定)
// 2013.06.13 T.Kabu gvcd_20130610		Rev.2用に色々修正
// 2013.07.18 T.Kabu gvcd_20130717		赤外線データ(つまりリモコン)の送受信保存再送が出来るようになったのでいったんFix
// 2013.12.20 T.Kabu 					清書と、赤外線データのサイズの関係で扱えるデータサイズをヘッダ込みで最大1600バイトに統一する

//---------------------------------------------------
// include
//---------------------------------------------------
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <syslog.h>
#include <termios.h>
#include <time.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/stat.h>
#include <sys/time.h>
//#include <linux/ipc.h>
//#include <linux/msg.h>
#include <errno.h>

// VineLinuxでは<pcre/pcre.h>、Raspbianなど、Debian系?では<pcre.h>
#include <pcre/pcre.h>
//#include <pcre.h>

#include "gvcd.h"

// --------------------------------------------------
// Const Define
// --------------------------------------------------

// --------------------------------------------------
// Structure 
// --------------------------------------------------
// --------------------------------
// グローバル変数定義
// --------------------------------
int gvc_cmd = 0;										// 0:通常モード -1:終了モード
int dev_num = 1;										// 対象GVCモジュール番号(1=マスターコントローラー)

// --------------------------------------------------
// Sub Routine
// --------------------------------------------------

// --------------------------------------------------
// Main Routine
// --------------------------------------------------
int main(int argc, char *argv[])
{
	int argnum;											// 引数カウント用
	
	GVC_MESSAGE_QUEUE_t rcv_message_queue;				// 受信メッセージキュー
	GVC_MESSAGE_QUEUE_t send_message_queue;				// 送信メッセージキュー
	int message_qid;									// メッセージキューID
	int msgq_length;									// メッセージの長さ
	key_t msgq_key;										// メッセージキューのキー
	int msgq_result;									// メッセージキュー送受信結果
	
	pcre *re[16];										// 検索パターンの内部処理用データ、パターンが増えたら配列数も増やすこと
	const char *errptr;
	int erroffset = 0;
	int capture_count;
	int *matches = NULL;
	int n_matches = 0;
	int offset = 0;
	int pcount = 0;
	
	FILE *datafp = NULL;								// データ格納用ファイル
	struct stat irdatastat;								// 赤外線データファイル情報
	
	// 引数検索パターン
	const char *pattern[16] = { "^end$",				//	0
								"^ver",	 				//	1
								"^list$",				//	2
								"^res",					//	3
								"^stop$",				//	4
								"^sw1on$",				//	5
								"^sw2on$",				//	6
								"^sw1off$",				//	7
								"^sw2off$",				//	8
								"^irrx$",				//	9
								"^irtx$",				//	10
								"^irget$",				//	11
								"^irset$",				//	12
								"^irdel$",				//	13
								"^irclear$",				//	14
								""};
	
	// 検索パターンが存在する限りループ
	while(strlen(pattern[pcount]))
	{
		// 検索パターンを内部形式にコンパイル(大文字小文字判別せず、UTF-8で)
		re[pcount] = pcre_compile(pattern[pcount], PCRE_CASELESS | PCRE_UTF8, &errptr, &erroffset, NULL);
		// エラーなら
		if (re[pcount] == NULL)
		{
			// errptrに可読エラーメッセージ、erroffsetに場所(先頭からn文字目)が入る
			fprintf(stderr, "%s at %d\n", errptr, erroffset);
			// 終わり
			exit(1);
		}
		
		// 結果を受け取る配列(matches)のサイズを計算する
		pcre_fullinfo(re[pcount], NULL, PCRE_INFO_CAPTURECOUNT, &capture_count);
		// 一時領域も含めて3倍必要らしい
		matches = malloc(sizeof(int) * (capture_count + 1) * 3);
		
		// 次の検索パターンに
		pcount ++;
	}
	
	// ----------------
	// 引数確認
	// ----------------
	// 引数を検査
	for (argnum = 1; argnum < argc; argnum++)
	{
		// 引数の中にhelpがあるなら
		if (strcasecmp("help", argv[argnum]) == 0 ||
			strcasecmp("-h", argv[argnum]) == 0 ||
			strcasecmp("-?", argv[argnum]) == 0)
		{
			// ソフト名と使い方を表示
			printf("%s %s\n\n", SOFTNAME, VERSION);
			printf("%s [version|list|reset|stop|end|sw?on|sw?off|irrx|irtx|irget (filename)|irset filename|irdel]\n\n", argv[0]);
			printf("	* MAX IR DATA LENGTH = %d\n\n", GVC_IR_DATALEN_MAX);
			// 終わり
			exit(1);
		}
	}
	// 検索パターン番号初期化
	pcount = 0;
	// 検索パターンが存在する限りループ
	while(strlen(pattern[pcount]))
	{
		// 検索実行
		n_matches = pcre_exec(re[pcount], NULL, argv[1], strlen(argv[1]), offset, 0, matches, (capture_count + 1) * 3);
		// 検索結果があるなら
		if (n_matches > 0)
		{
			// 引数の中にend(0番目)があるなら
			if (pcount == 0)
			{
				// 命令コマンドを終了(0xff)にする
				gvc_cmd = 0xff;
			}
			// 引数の中にver(1番目)があるなら
			if (pcount == 1)
			{
				// 命令コマンドをバージョン要求(0x01)にする
				gvc_cmd = 0x01;
			}
			// 引数の中にlist(2番目)があるなら
			if (pcount == 2)
			{
				// 命令コマンドを終了(0x02)にする
				gvc_cmd = 0x02;
			}
			// 引数の中にres(3番目)があるなら
			if (pcount == 3)
			{
				// 命令コマンドをリセット/リスタート要求(0x7e)にする
				gvc_cmd = 0x7e;
			}
			// 引数の中にstop(4番目)があるなら
			if (pcount == 4)
			{
				// 命令コマンドをGVC停止(0x7f)にする
				gvc_cmd = 0x7f;
			}
			// 引数の中にsw1on(5番目)があるなら
			if (pcount == 5)
			{
				// dev_numで指定したモジュールのスイッチをONにする
				dev_num = 0x30;
				gvc_cmd = 0x21;
			}
			// 引数の中にsw2on(6番目)があるなら
			if (pcount == 6)
			{
				// dev_numで指定したモジュールのスイッチをONにする
				dev_num = 0x38;
				gvc_cmd = 0x21;
			}
			// 引数の中にsw1off(7番目)があるなら
			if (pcount == 7)
			{
				// dev_numで指定したモジュールのスイッチをONにする
				dev_num = 0x30;
				gvc_cmd = 0x20;
			}
			// 引数の中にsw2off(8番目)があるなら
			if (pcount == 8)
			{
				// dev_numで指定したモジュールのスイッチをONにする
				dev_num = 0x38;
				gvc_cmd = 0x20;
			}
			// 引数の中にirrx(9番目)があるなら
			if (pcount == 9)
			{
				// dev_numで指定したモジュールで赤外線データを受信する
				dev_num = 0x10;
				gvc_cmd = 0x92;
			}
			// 引数の中にirtx(10番目)があるなら
			if (pcount == 10)
			{
				// dev_numで指定したモジュールで赤外線データを送信する
				dev_num = 0x10;
				gvc_cmd = 0x91;
			}
			// 引数の中にirget(11番目)があるなら
			if (pcount == 11)
			{
				// dev_numで指定したモジュールから赤外線データを取得する
				dev_num = 0x10;
				gvc_cmd = 0x94;
				// 何らかのオプションがもうひとつついているなら
				if (argc == 3)
				{
					// そのファイルが開けるか試してみる
					datafp = fopen(argv[2], "wb");
					// ファイルが開けなかったら
					if (datafp == NULL)
					{
						// エラー吐いて終わり
						fprintf(stderr, "%s : %s\n", argv[2], strerror(errno));
						// 終わり
						exit(EXIT_FAILURE);
					}
					// ファイルが普通に開けたなら
					else
					{
						// ファイルを閉じる
						fclose(datafp);
					}
				}
			}
			// 引数の中にirset(12番目)があるなら
			if (pcount == 12)
			{
				// 何らかのオプションがもうひとつついているなら
				if (argc == 3)
				{
					// dev_numで指定したモジュールに赤外線データを設定する
					dev_num = 0x10;
					gvc_cmd = 0x93;
					
					// 赤外線データファイルがないなら
					if (stat(argv[2], &irdatastat) == -1)
					{
						// エラー吐いて終わり
						fprintf(stderr, "%s : %s\n", argv[2], strerror(errno));
						// 終わり
						exit(EXIT_FAILURE);
					}
					// 赤外線データファイルのサイズがGVC_IR_DATALEN_MAXを超えていたら
					if (irdatastat.st_size > GVC_IR_DATALEN_MAX)
					{
						// エラー吐いて終わり
						fprintf(stderr, "%s : size over\n", argv[2]);
						// 終わり
						exit(EXIT_FAILURE);
					}
					// そのファイルが開けるか試してみる
					datafp = fopen(argv[2], "rb");
					// ファイルが開けなかったら
					if (datafp == NULL)
					{
						// エラー吐いて終わり
						fprintf(stderr, "%s : %s\n", argv[2], strerror(errno));
						// 終わり
						exit(EXIT_FAILURE);
					}
					// ファイルが普通に開けたなら
					else
					{
						// 実際にはこの後↓で処理
					}
				}
			}
			// 引数の中にirdel(13番目)かirclear(14番目)があるなら
			if (pcount == 13 || pcount == 14)
			{
				// dev_numで指定したモジュールで赤外線データを受信する
				dev_num = 0x10;
				gvc_cmd = 0x95;
			}
		}
		// 次の検索パターンに
		pcount ++;
	}
	// もしgvc_cmdが0のままなら
	if (gvc_cmd == 0)
	{
		// ソフト名と使い方を表示
		printf("%s %s\n\n", SOFTNAME, VERSION);
		printf("%s [version|list|reset|stop|end|sw?on|sw?off|irrx|irtx|irget (filename)|irset filename|irdel]\n\n", argv[0]);
		printf("	* MAX IR DATA LENGTH = %d\n\n", GVC_IR_DATALEN_MAX);
		// 終わり
		exit(1);
	}
	
	// 乱数初期化
	srand(time(0));
	
	// メッセージキューのメッセージの長さを設定
	msgq_length = sizeof(GVC_MESSAGE_QUEUE_t) - sizeof(long);
	
	// パス名とプロジェクト識別子を System V IPC キーに変換する(命令キュー)
	// gvcdが動いていることが前提なので、gvcdのPIDファイルから生成すればOK。
	msgq_key = ftok(GVC_PID_FILENAME, 'w');
	
	// メッセージキューIDを取得
///	message_qid = msgget(msgq_key, 0660);
	message_qid = msgget(msgq_key, 0666);
	
	// メッセージキューIDが取得できたなら(message_qid!=-1)
	if (message_qid != -1)
	{
		// 作成したキューIDを出力
		printf("QID = %d\n", message_qid);
	}
	// メッセージキューIDが取得できなかったら(message_qid=-1)
	else
	{
		// エラーを出力
		fprintf(stderr, "Message Queue make error : %s\n", strerror(errno));
		// 終わり
		exit(EXIT_FAILURE);
	}
	
	// なんらかの命令があるなら
	if (gvc_cmd != 0)
	{
		// メッセージの初期化
		memset((void *)&send_message_queue.q, sizeof(GVC_QUEUE_MESSAGE_t), 0x00);
		
		// メッセージ設定…ここでは命令を送信する
		send_message_queue.qtype = COMMAND_Q;				// メッセージキュータイプ設定(要求)
		send_message_queue.q.gvc_num = 0x01;				// 対象GVC番号設定(1=とりあえず…TBD)
		send_message_queue.q.msg_type = GVC_MSG_ENQ;		// メッセージタイプ設定(GVCへの各種問い合わせ)
		send_message_queue.q.dev_num = dev_num;				// 接続GVC番号設定(1=マスターコントローラー)
		send_message_queue.q.format = 0x01;					// コマンドフォーマット設定
		send_message_queue.q.cmd = gvc_cmd;					// コマンド設定
		// dev_numで指定したモジュールから赤外線データを取得する、という命令で、かつ保存先ファイル名の指定があるなら
		if (gvc_cmd == 0x94 && argc == 3)
		{
			// 保存先ファイル名をコピー(引数確認のところでファイルが開けるか調べているのでコピーするだけでOK)
			strcpy((char *)send_message_queue.q.data, argv[2]);
			// データ長設定
			send_message_queue.q.data_len = strlen((char *)send_message_queue.q.data);
		}
		// dev_numで指定したモジュールへ赤外線データを設定する、という命令で、かつデータファイル名の指定があって、ファイルが開けているなら
		else if (gvc_cmd == 0x93 && argc == 3 && datafp != NULL)
		{
			// データファイルからデータを読出、かつデータ長設定
			send_message_queue.q.data_len = fread(send_message_queue.q.data, sizeof(char), BUFF_SIZE, datafp);
			// データファイルを閉じる
			fclose(datafp);
		}
		else
		{
			// それ以外は今のところ特にデータはないので
			send_message_queue.q.data_len = 0;					// データ長設定
		}
		
		// CRCの設定は…
		// メッセージキュー、というかLinuxレベルになるとテーブル構造体のアラインメントが行われるから実質できない。
		
		// メッセージ送信
		msgq_result = msgsnd(message_qid, &send_message_queue, msgq_length, 0);
		// メッセージが送信できたなら(=0)
		if (msgq_result == 0)
		{
			// 画面に出力
			printf("COMMAND SEND ... \n");
			printf("	qtype = 0x%02lx\n", send_message_queue.qtype);
			printf("	q.gvc_num = 0x%02x\n", send_message_queue.q.gvc_num);
			printf("	q.msg_type = 0x%02x\n", send_message_queue.q.msg_type);
			printf("	q.dev_num = 0x%02x\n", send_message_queue.q.dev_num);
			printf("	q.format = 0x%02x\n", send_message_queue.q.format);
			printf("	q.cmd = 0x%02x\n", send_message_queue.q.cmd);
			printf("	q.data_len = %0d\n", send_message_queue.q.data_len);
			printf("	q.data = %s\n", send_message_queue.q.data);
			// 終わり
			exit(EXIT_SUCCESS);
		}
		// メッセージが送信できなかったら(!=0)
		else
		{
			// エラーを出力
			fprintf(stderr, "COMMAND SEND ERROR : %s(ERRNO=%d, message_qid=%d, msgq_length=%d)\n", strerror(errno), errno, message_qid, msgq_length);
			// 終わり
			exit(EXIT_FAILURE);
		}
		
		sleep(1);
	}
	
	// メッセージ受信
	// 「求めているメッセージがない」こともある.msgflgとしてIPC_NOWAITが指定されてい
	// る場合にはすぐにエラーを返すが,そうでない場合には求めるメッセージが得られるか,
	// キューが消えるか,シグナルで捕獲されるまで待つ.
	
	msgq_result = msgrcv(message_qid, &rcv_message_queue, msgq_length, RESULT_Q, IPC_NOWAIT);
	// メッセージが受信できたなら(!=-1)
	if (msgq_result != -1)
	{
		// データを出力
		printf("MESSAGE RECEIVED ... qtype = %ld\n", rcv_message_queue.qtype);
	}
	// メッセージが受信できなかったら(=-1)
	else
	{
		// とりあえずコマンドを送るだけなので何もしない
//		// エラーを出力
//		fprintf(stderr, "Message Queue receieved error : %s\n", strerror(errno));
//		// 終わり
//		exit(EXIT_FAILURE);
	}
	// 終わり
	exit(EXIT_SUCCESS);
}