into the void

ソフトウェアに関する雑多な調査日記

IRKitとHTML(jquery + bootstrap)でカスタムテレビリモコンを作ってみる(その壱)

Webサーバの準備

JavaScriptとHTMLを使ってIRKitを操作するWebUIを作ってみようと思う。
ローカルHTMLでもいいが、できればLAN内のWebサーバに配置したい。
PCを常時起動しておくのは省エネじゃないので、NAS上に構築できないか調べてみる。

NASはRockDiskNext。rootでログインしていろいろカスタマイズできるいい感じのNAS
telnetでログインしてpsで見てみるとlighttpdが動いている。

[root@xxxxxx]# ps axuw
lighttpd  1242  0.0  0.5   7044  1312 ?        S    Oct18   9:25 /usr/sbin/lighttpd -f /etc/nas/lighttpd.conf

設定ファイルはlighttpd.confのはずなのでfindしてみると/etc/nas/lighttpd.confが見つかった。
内容見てみるとdocument rootは/var/www/nasになっている。
ためしに/var/www/nas/test.htmlを作って、ブラウザからアクセスしてみると問題なく表示できた。
Webサーバはこれでよさそう。
まずはローカルのHTMLでWeb UIを作って、できあがったらNAS上の配置する作戦でいこうと思う。

IRKitの動作確認

AppStoreにあるIRKit標準のシンプルリモコンでIRKitの動作を確認する。
シンプリリモコンアプリに表示される手順通りに実行して、問題なくIRKitと接続できた。
ためしにBDレコーダのリモコンの学習させて、操作してみた。

IPアドレスの固定

次にHTTP RESTをつかってIRKitを操作してみるが、その前にIRKitのアドレスを調べる。
やりかたはIRKitの公式サイトに書いてある。Bonjourを使って検索する。
http://getirkit.com

$ dns-sd -B _irkit._tcp
Browsing for _irkit._tcp
DATE: ---Sat 29 Nov 2014---
23:07:45.773  ...STARTING...
Timestamp     A/R    Flags  if Domain               Service Type         Instance Name
23:07:47.550  Add        2   4 local.               _irkit._tcp.         irkitXXXX

$ dns-sd -G v4 irkitXXXX.local
DATE: ---Sat 29 Nov 2014---
23:08:30.352  ...STARTING...
Timestamp     A/R Flags if Hostname                               Address                                      TTL
23:08:30.971  Add     2  4 irkitXXXX.local.                       192.168.0.2                                  15

その後、アドレスが変更されないようにルータのDHCPサーバ設定をする。
IRKitのMACに割り当てるIPアドレスをさっき調べたものに固定しておく形。

コマンドラインから実行

curlを使ってコマンドラインからREST URLを叩いてIRKitが操作できることを確認する。
まずは赤外線信号をキャプチャ。GET /messagesで最後に受診した赤外線信号情報を取得できる。

$ curl -i "http://192.168.0.2/messages"
HTTP/1.0 200 OK
Access-Control-Allow-Origin: *
Server: IRKit/2.0.2.0.xxxxxxxx
Content-Type: text/plain

{"format":"raw","freq":38,"data":[6881,3341,904,815,904,2537,904,815,968,815,968,815,968,815,968,815,968,815,968,815,968,815,968,815,968,815,968,815,968,2537,904,815,904,815,904,815,904,815,904,815,904,815,904,2537,904,2537,904,815,904,2537,904,815,904,815,904,815,904,815,904,815,904,815,904,815,904,815,904,815,904,815,904,2537,904,815,904,2537,904,2537,904,815,904,815,904,815,904,815,904,2537,904,815,904,815,904,815,904,815,904,2537,935,65535,0,65535,0,17421,6881,3341,904,904,904,2537,904,904,904,904,904,904,904,904,904,904,904,904,904,904,904,904,904,904,904,904,904,904,904,2537,904,904,904,904,904,787,904,787,904,787,904,787,904,2537,904,2537,904,904,904,2537,904,904,904,904,904,904,904,904,904,904,904,904,904,904,904,904,904,787,935,787,935,2537,904,904,904,2537,904,2537,904,815,904,815,904,815,904,815,904,2537,904,904,904,904,904,904,904,904,904,2537,904]}Masashi-no-MacBook-Air:~ shizuku$ 

キャプチャした信号を送信。さっき GET /messagesで受診したコードをPOSTで送る。

$ curl -i "http://192.168.0.2/messages" -d '{"format":"raw","freq":38,"data":[6881,3341,904,815,904,2537,904,815,968,815,968,815,968,815,968,815,968,815,968,815,968,815,968,815,968,815,968,815,968,2537,904,815,904,815,904,815,904,815,904,815,904,815,904,2537,904,2537,904,815,904,2537,904,815,904,815,904,815,904,815,904,815,904,815,904,815,904,815,904,815,904,815,904,2537,904,815,904,2537,904,2537,904,815,904,815,904,815,904,815,904,2537,904,815,904,815,904,815,904,815,904,2537,935,65535,0,65535,0,17421,6881,3341,904,904,904,2537,904,904,904,904,904,904,904,904,904,904,904,904,904,904,904,904,904,904,904,904,904,904,904,2537,904,904,904,904,904,787,904,787,904,787,904,787,904,2537,904,2537,904,904,904,2537,904,904,904,904,904,904,904,904,904,904,904,904,904,904,904,904,904,787,935,787,935,2537,904,904,904,2537,904,2537,904,815,904,815,904,815,904,815,904,2537,904,904,904,904,904,904,904,904,904,2537,904]}'
HTTP/1.0 200 OK
Access-Control-Allow-Origin: *
Server: IRKit/2.0.2.0.xxxxxxxx
Content-Type: text/plain

AJAXから実行

HTML + JavaScriptをつかってREST URLを叩いてIRKitが操作できることを確認してみる。
mashさんのサンプルがあったので、これをベースにカスタマイズしていくことにする。
http://jsdo.it/mash/IRKit-jQuery-Sample

TVのリモコン信号を調査

TVのリモコンが壊れているのキャプチャができない。。。ググって赤外線の送信パターンを入手する。
http://www.256byte.com/2006/10/post_2.html

これをIRKit用のコードに変換してあげる必要がある。
IRKItのデータは赤外線のONとOFFの時間を2MHz単位でカウントした値になっている。

アクオスの赤外線のONとOFFの時間下記の通りらしい。

点灯・消灯時間は以下の表の通りです。
 	点灯時間	消灯時間
リーダー信号	3,500μ秒	1,600μ秒
データ 0	460μ秒	360μ秒
データ 1	460μ秒	1,200μ秒
ストップ信号	460μ秒	70,000μ秒

2MHzは1クロックあたり0.5マイクロ秒なので、
3500マイクロは、7000クロック。
1600マクロ秒は、3200クロック。
460 マイクロ秒は、920クロック。
360マイクロ秒は、720クロック。
1200マイクロ秒は、2400クロック。
...


例えば、音量は下記のコード。

音量 +	55 5A F1 48 28 8F

これをIRKitが解釈できるコードに変換する。
まず2進数にして、その後の1を920,2400に、0を920,720に変換する
あと前後にリーダ信号とストップ信号が必要なので、下記のようになる。

7000,3200,920,720,920,2400,920,720,920,2400,920,720,920,2400,920,720,920,2400,920,720,920,2400,920,720,920,2400,920,2400,920,720,920,2400,920,720,920,2400,920,2400,920,2400,920,2400,920,720,920,720,920,720,920,2400,920,720,920,2400,920,720,920,720,920,2400,920,720,920,720,920,720,920,720,920,720,920,2400,920,720,920,2400,920,720,920,720,920,720,920,2400,920,720,920,720,920,720,920,2400,920,2400,920,2400,920,2400,920,140000

コード変換が面倒なので生成ツールを作った。言語はなんでもよかったんだけどJavaが一番慣れてるので。

/**
 * 16進数表記の赤外線コードをIRKit用のコードに変換する
 * @author shizuku
 *
 */

public class IRKitCodeGenerator {
	
	public final int LEADER_ON_USEC = 3500; //リーダ信号の点灯時間
	public final int LEADER_OFF_USEC = 1600;//リーダ信号の消灯時間
	public final int ZERO_ON_USEC = 460; //0信号の点灯時間
	public final int ZERO_OFF_USEC = 360; //0信号の消灯時間
	public final int ONE_ON_USEC = 460; //1信号の点灯時間
	public final int ONE_OFF_USEC = 1200; //1信号の消灯時間
	public final int STOP_ON_USEC = 460; //STOP信号の点灯時間
	public final int STOP_OFF_USEC = 70000; //STOP信号の消灯時間
	
	/**
	 * 赤外線の点灯/消灯時間(マイクロ秒)からIRKitのコードを生成する
	 * @param on_usec
	 * @param off_usec
	 * @return
	 */
	private String getCode(int on_usec, int off_usec){
		StringBuffer sb = new StringBuffer();
		int clock_num = on_usec * 2;
		sb.append(Integer.toString(clock_num));
		sb.append(",");
		clock_num = off_usec * 2;
		sb.append(Integer.toString(clock_num));
		return sb.toString();
	}
	
	/**
	 * 16進数表記の赤外線データコードからIRKit用のコードを生成する
	 * @param code
	 * @return
	 */
	public String getDataCode(byte code){
		StringBuffer sb = new StringBuffer();
		for(int i=0; i<8; i++){
			byte mask = (byte) (0x01 << (8-(i+1)));
			if((code & mask) == 0){
				sb.append(getCode(ZERO_ON_USEC, ZERO_OFF_USEC));
				sb.append(",");
			}else{
				sb.append(getCode(ONE_ON_USEC, ONE_OFF_USEC));
				sb.append(",");
			}
		}
		return sb.toString();
	}
	
	/**
	 * 16進数表記の赤外線コード列からIRKit用のコードを生成する
	 * @param codeString
	 * @return
	 */
	public String getIRKitCode(String[] codeString){
		StringBuffer sb = new StringBuffer();
		sb.append(getCode(LEADER_ON_USEC, LEADER_OFF_USEC));
		sb.append(",");
		for(int i=0; i<codeString.length; i++){
			int num = Integer.parseInt(codeString[i], 16);
			sb.append(getDataCode((byte)num));
		}
		sb.append(getCode(STOP_ON_USEC, STOP_OFF_USEC));
		return sb.toString();
	}
	
	public static void main(String args[]) throws Exception{
		IRKitCodeGenerator gen = new IRKitCodeGenerator();
		String result = gen.getIRKitCode(args);
		System.out.println(result);
	}
}