Nexus7からUSB赤外線リモコンを操る(後編)
「Nexus7からUSB赤外線リモコンを操る(前編)」「(中編)」の続きです。 先に前/中編をみてください。
赤外線リモコンキットのプロトコル
フォーラムにもなぜかプロトコルについての資料がありません。 ファームウェアのソース公開されているので自分で調べろ(or決めろ)って事でしょうか。
仕方ないのでこちらのサイトを参考にファームのソースからプロトコルを調べました。
基本形
パケットは64バイトの固定長です。
要求パケットの1バイト目にコマンドのコードが入り、 応答パケットの1バイト目に同じコマンドのコードが入って戻ってきます。
パケットの2バイト目以降が要求パラメータまたは応答の戻り値となっています。
応答にエラーコードと言うものは無いようなので Timeout で検出と思われます。
家電のリモコンから赤外線データの受信
リモコンから赤外線データを受信するにはデバイスを受信モードにしデータを取得してから受信モードを終了します。
操作 | パケットデータ(64byte固定) | |
---|---|---|
受信モード開始 | ||
(1) | 0x53,0x01,0xff…0xff | |
(2) | 0x53,0x00,0x00…0x00 | |
データ取得(繰り返す) | ||
(3) | 0x52,0xff,0xff…0xff | |
(4) | 0x52,0xXX,0xXX…0xXX | |
受信モード終了 | ||
(5) | 0x53,0x00,0xff…0xff | |
(6) | 0x53,0x00,0x00…0x00 |
リモコンからまだ赤外線データを受け取っていない場合は (4) の応答の2バイト目が 0x00 となる為、データが取得できるまで (3),(4) を繰り返します。
デバイスから赤外線データの送信
取得したデータを送信します。
(4) で受け取ったデータの1バイト目を送信コマンド(0x61)に差し替えて要求するだけです。
投げっぱなしで応答は有りません。
操作 | パケットデータ(64byte固定) | |
---|---|---|
データ送信 | ||
(7) | 0x61,0xXX,0xXX…0xXX |
プロトコルの実装
家電のリモコンから赤外線データの受信
応答が非同期となるのでリスナインターフェースを用意します。 後は、パケットを作って非同期タスクに投げるだけです。
public interface IrrcResponseListener {
public void onIrrcResponse(byte[] data);
}
public void startReceiveIR(IrrcResponseListener listener) {
byte[] buff = initBuffer(new byte[PACKET_SIZE], (byte) 0xff);
buff[0] = RECEIVE_IR_MODE_CMD;
buff[1] = 1;
new RequestAsyncTask(listener).request(buff, true, false);
}
public void endReceiveIR(IrrcResponseListener listener) {
byte[] buff = initBuffer(new byte[PACKET_SIZE], (byte) 0xff);
buff[0] = RECEIVE_IR_MODE_CMD;
buff[1] = 0;
new RequestAsyncTask(listener).request(buff, true, false);
}
public void getReveiveIRData(IrrcResponseListener listener) {
byte[] buff = initBuffer(new byte[PACKET_SIZE], (byte) 0xff);
buff[0] = RECEIVE_IR_DATA_CMD;
new RequestAsyncTask(listener).request(buff, true, true);
}
デバイスから赤外線データの送信
パケットを作って非同期タスクに投げるだけです。
public void sendData(byte[] buff) {
buff[0] = SEND_IR_CMD;
new RequestAsyncTask(null).request(buff, false, false);
}
非同期タスク
応答有無、リトライ有無の指定にしたがってプロトコルにそった送受信を行っているだけです。
異常系やキャンセル処理への考慮は不十分です。
#APIが混乱しているのは仕様ですw
private class RequestAsyncTask extends AsyncTask<byte[], Void, byte[]> {
private IrrcResponseListener listener;
private boolean withResponse = false;
private boolean withRetry = false;
public RequestAsyncTask(IrrcResponseListener listener) {
this.listener = listener;
}
public void request(byte[] buff, boolean withResponse, boolean withRetry) {
this.withResponse = withResponse;
this.withRetry = withRetry;
execute(buff);
}
@Override
protected byte[] doInBackground(byte[]... args) {
Log.d(TAG, "RequestAsyncTask start");
try {
byte[] reqData = args[0];
byte[] resData = null;
boolean isRetry = false;
do {
doRequest(reqData);
if (withResponse) {
resData = doResponse();
if (resData[0] != reqData[0]) {
Log.e(TAG, "Bad resposne code " + resData[0]);
return null;
}
if (withRetry && resData[1] == 0x00) {
sleep(500);
isRetry = true;
} else {
isRetry = false;
}
}
} while (isRetry);
return resData;
} catch (Throwable t) {
Log.e(TAG, t.getMessage(), t);
return null;
}
}
@Override
protected void onPostExecute(byte[] result) {
if (listener != null) {
listener.onIrrcResponse(result);
}
}
private void doRequest(byte[] buff) throws IOException {
…省略(中編参照)
}
private byte[] doResponse() throws IOException {
…省略(中編参照)
}
}
動かしてみる
受信と送信のボタン2つだけの Activity を作って動かしてみました。
Nexus7に繋げるのですがここで一つ問題が。
赤外線リモコンキットのコネクタは Mini-USB なので micro-USB
と直結しようとするとレアなケーブルが必要で手持ちに有りませんでした。
結果こんな事に(笑)
それはそれとして、
アプリの受信ボタンをタップしてからデバイスの受光部分に向けてリモコンを操作します。
電源ボタンを押してみました。
デバイスを家電機器に向けてアプリの送信ボタンをタップすると無事、家電の電源が入って実験成功です。
まとめ
これで Nexus7 から赤外線リモコンキットを操作することが可能になりました。
基本的に Android から USBデバイスを操作するのは同じ流れで行けると思うのですが やはり OSが一部デバイスをアプリに使わせてくれないのは致命的な問題のような気がします。 普通のUSBデバイスはファームの書き換えなんてさせてくれませんから。
追々、リモコンアプリを作って行きたいのですが ボタン配置のカスマイズをできるようにしないといけないので以外に難しそうです。
- 作りました => USB赤外線リモコン アプリ
ソース全体は以下のSVNを参照して下さい。
