/*
 *	kuro_rs-pseudo_firmware.c
 *
 *	KURO-RS実機の挙動から推測した、KURO-RSファームウェアの疑似ソース
 *	※コンパイルは通りません。説明のために作成した、疑似ソースです。
 *
 *	* Tue Nov 27 22:51:32 JST 2007 Naoyuki Sawa
 *	- 1st リリース。
 */

/****************************************************************************
 *	別途定義されていると仮定するサブルーチン
 ****************************************************************************/

extern int recv_from_pc();		/* PCから1バイト受信する。(0x00〜0xFF = 受信データ, -1 = 受信データ無し) */
extern void send_to_pc(int data);	/* PCへ1バイト送信する。 */
extern int get_0_1ms_timer();		/* 現在時刻を0.1ミリ秒単位で取得する。 */
extern int pd_stat();			/* 本体の左側面に内蔵されている『受光部』(Photo Diode)の受光状態を取得する。(0 = 受光していない, 1 = 受光している) */
extern void ir_on(int port, int on);	/* 赤外線送信部の先に付いている『発光部』の発光状態を設定する。(0 = 消光させる, 1 = 発光させる) */
extern void access_blink(int type);	/* 本体の右上に内蔵されている『アクセスランプ』を点滅させる。(type = 点滅パターン) */

/****************************************************************************
 *	ローカル関数宣言
 ****************************************************************************/

static void rx_mode();	/* 学習モード */
static void tx_mode();	/* 送信モード */
static void id_mode();	/* 識別モード */

/****************************************************************************
 *	モード監視状態
 ****************************************************************************/

void
main()
{
	int c;

	for(;;) {
		c = recv_from_pc();
		if(c >= 0) {
			switch(c) {
			case 'r':
				rx_mode();	/* 学習モード */
				break;
			case 't':
				tx_mode();	/* 送信モード */
				break;
			case 'i':
				id_mode();	/* 識別モード */
				break;
			}
		}
	}
}

/****************************************************************************
 *	学習モード
 ****************************************************************************/

static void
rx_mode()
{
	int c;
	int i;
	int j;
	int t;
	unsigned char buf[240];

	/* PCへ'Y'を送信する。 */
	send_to_pc('Y');

	/* 赤外線を受光するか、キャンセルされるまで待ちます。 */
	while(!pd_stat()) {
		if(recv_from_pc() >= 0) {
			/* PCへ'Y'を送信し、モード監視状態に戻ります。
			 * ※『赤外線コントロール仕様書』では、キャンセルコマンドは'c'となっていますが、
			 *   実際には、'c'でも'c'以外でも何でもキャンセルコマンドと見なされるようです。
			 * ※キャンセル時の'Y'の送信の有無が、受信モードと送信モードで異なるようです。
			 */
			send_to_pc('Y');
			return;
		}
	}

	/* 0.1ミリ秒間隔で受光状態をサンプリングし、リモコンデータとして格納します。 */
	t = get_0_1ms_timer();
	for(i = 0; i < 240; i++) {
		buf[i] = 0;
		for(j = 0; j < 8; j++) {
			if(pd_stat()) {
				buf[i] |= (1 << j);	/* LSB先行 */
			}
			/* 0.1ミリ秒間隔のタイミング待ち。 */
			while(get_0_1ms_timer() == t) { /** no job **/ }
			t++;
		}
	}

	/* PCへ'S'を送信します。 */
	send_to_pc('S');

	/* PCへリモコンデータを送信します。 */
	for(i = 0; i < 240; i++) {
		send_to_pc(buf[i]);
	}

	/* アクセスランプをゆっくり3回点滅させます。
	 * ※PCへ'E'を送信する前にアクセスランプを点滅させます。点滅が完了するまで'E'は送信しません。
	 *   送信モードや識別モードの場合は、すべての送信が完了したあとにアクセスランプを点滅させて、
	 *   点滅が終了したら直ちににモード監視状態に戻っているのと較べると、学習モードだけ特殊です。
	 */
	access_blink(3);

	/* PCへ'E'を送信します。 */
	send_to_pc('E');

	/* モード監視状態に戻ります。 */
}

/****************************************************************************
 *	送信モード
 ****************************************************************************/

static void
tx_mode()
{
	int c;
	int port;
	int i;
	int j;
	int t;
	unsigned char buf[240 + 2];	/* ※「+2」はKURO-RS実機の挙動から推測しました。以下のコメントを参照してください。 */

	/* PCへ'Y'を送信する。 */
	send_to_pc('Y');

	/* PCからポート番号を受信します。 */
	for(;;) {
		c = recv_from_pc();
		if(c >= 0) {
			if((c >= '1') && (c <= '4')) {
				port = c - '0';
				/* PCへ'Y'を送信し、リモコンデータ受信待ちへ移行します。 */
				send_to_pc('Y');
				break;
			} else {
				/* PCへ何も送信せずに、モード監視状態に戻ります。
				 * ※『赤外線コントロール仕様書』では、キャンセルコマンドは'c'となっていますが、
				 *   実際には、'1'〜'4'以外ならば何でもキャンセルコマンドと見なされるようです。
				 * ※キャンセル時の'Y'の送信の有無が、受信モードと送信モードで異なるようです。
				 */
				return;
			}
		}
	}
	/* ※これ以降、送信モードを終了するまで、もうキャンセルはできません。
	 *   PCは、リモコンデータを240バイト送信して、正規の手順で送信モードを完了させるしかありません。
	 *   もし発光させたくないならば、0x00を240バイト送信すれば発光しないはずなので、実質キャンセルできるかも知れません。(未検証)
	 */

	/* PCからリモコンデータを240バイト受信します。
	 * ※KURO-RSにバグがあり、243バイト以上受信すると、ハングアップしてしまうようです。
	 *   239バイト以下 -> 合計240バイト以上になるまで、次の受信を待ちます。
	 *   240バイト     -> 受信を完了します。
	 *   241バイト     -> 余分に受信した1バイトを捨てて、受信を完了します。
	 *   242バイト     -> 余分に受信した2バイトを捨てて、受信を完了します。
	 *   243バイト以上 -> ハングアップします。KURO-RSをつなぎ直さないと、復旧しません。
	 */
	i = 0;
	for(;;) {
		c = recv_from_pc();
		if(c >= 0) {
			buf[i++] = c;
			continue;	/* ※バグ(KURO-RS実機の挙動から推測)。この「continue;」を削れば、正しい動作になります。 */
		}
		if(i >= 240) {
			break;
		}
	}

	/* リモコンデータに従って、0.1ミリ秒間隔で発光、または、消光します。 */
	t = get_0_1ms_timer();
	for(i = 0; i < 240; i++) {
		for(j = 0; j < 8; j++) {
			if(buf[i] & (1 << j)) {		/* LSB先行 */
				ir_on(port, 1);
			} else {
				ir_on(port, 0);
			}
			/* 0.1ミリ秒間隔のタイミング待ち。 */
			while(get_0_1ms_timer() == t) { /** no job **/ }
			t++;
		}
	}

	/* 確実に発光部を消光します。 */
	ir_on(port, 0);

	/* PCへ'E'を送信します。 */
	send_to_pc('E');

	/* アクセスランプをすばやく2回点滅させます。 */
	access_blink(2);

	/* モード監視状態に戻ります。 */
}

/****************************************************************************
 *	識別モード
 ****************************************************************************/

static void
id_mode()
{
	/* PCへ'O'を送信します。 */
	send_to_pc('O');

	/* アクセスランプをゆっくり1回点滅させます。 */
	access_blink(1);

	/* モード監視状態に戻ります。 */
}

