前回の投稿で、WiiリモコンをNode.jsから触れるようにしました。( WiiリモコンをNode.jsから操ってみよう )
今度は、WiiリモコンをMQTTに接続して、ブラウザから操ってみます。さらに、ヌンチャクやバランスボードにも対応させました。
以下の2回に分けて説明していこうと思います。
1.ヌンチャクとバランスボードにも対応させ、WiiリモコンをMQTTに接続する(今回はこちら)
2.ブラウザからWiiリモコンたちに接続する
Image may be NSFW.
Clik here to view.
完成するとブラウザからこんな感じで見えますが、それは次回の投稿で。先に、Webページのリンクを張っておきます。
https://poruruba.github.io/WiiRemocon/html/
Image may be NSFW.
Clik here to view.
ソースコードを以下にアップしておきました。(かなり力技です!)
poruruba/WiiRemocon
https://github.com/poruruba/WiiRemocon
Wiiリモコンからの受信イベント
Wiiリモコンからは、ボタン押下や加速度の変化など、状態が変わるたびに、Bluetoothでイベントが受信されます。その内容は、レポーティングモードによって変わり、設定することで、受信する内容を変えることができます。
ボタン押下イベントだけのレポーティングモードに設定した場合は、Wiiリモコンのボタンを押下したりはなしたりしたときのみイベントが通知されますが、加速度も返るようにレポーティングモードに設定すると、ひっきりなしにイベントが受信されるようになります。
イベントの内容は、以下に記載があります。以降ではデータレポーティングと呼ぶことにします。
http://wiibrew.org/wiki/Wiimote#Data_Reporting
様々な内容が送られてきますので、上記の記載内容に従ってパースする関数を用意しました。parseReporting(data)
です。
・・・parseReporting(data){if(data[0]==WIIREMOTE_REPORTID_STATUS){varreport={report_id:data[0],btns:(((data[1]<<8)|data[2]))&0x1f9f,leds:data[3]&0xf0,flags:data[3]&0x0f,battery:data[6]};returnreport;}elseif(data[0]==WIIREMOTE_REPORTID_READ_DATA){・・・
レポーティングモード設定する関数は以下の通りです。
setDataReportingMode(mode){varparam=Buffer.alloc(4);param.writeUInt8(0xa2,0);param.writeUInt8(WIIREMOTE_REPORTID_REPORTINGMODE,1);param.writeUInt8(0x00,2);param.writeUInt8(mode,3);console.log('setDataReportingMode:'+param.toString('hex'));returnthis.l2cap.write(0,param);}
拡張コントローラの有効化
Wiiヌンチャクは、Wiiリモコンに接続して使います。
拡張コントローラと呼ばれていて、利用するには有効化が必要です。
asyncenableExtension(enable){if(enable){awaitthis.writeRegister(0xa400f0,Buffer.from([0x55]));awaitthis.writeRegister(0xa400fb,Buffer.from([0x00]));}else{awaitthis.writeRegister(0xa400f0,Buffer.from([0x00]));}}
Wiiremote/Extension Controllers のThe New Wayのところです。
http://wiibrew.org/wiki/Wiimote/Extension_Controllers#Identification
拡張コントローラのイベント内容
ヌンチャクやバランスボードのイベントの内容は以下にあります。
・ヌンチャク
http://wiibrew.org/wiki/Wiimote/Extension_Controllers/Nunchuck#Data_Format
・バランスボード
http://wiibrew.org/wiki/Wii_Balance_Board#Data_Format
パースする関数は、parseExtension(type, data)
です。
バランスボードに関しては、データレポーティングを解釈するために準備が必要です。
データレポーティングは、バランスボードの四隅のセンサから受信したデータの生値であり、重量に換算するにはキャリブレーションが必要です。
キャリブレーションは、以下に示すレジスタに記録されています。
http://wiibrew.org/wiki/Wii_Balance_Board#Calibration_Data
ということで、あらかじめreadBalanceBoardCalibration()
でキャリブレーションを読み出しておいて、データレポーティングとキャリブレーションを使って、calcurateBalanceBoard(data, base)
で補正することで初めて、バランスボードの各センサでの重量を認識することができます。
ちなみに、バランスボードはWiiリモコンとしてふるまいます。ですので、今回作成したツールでは、バランスボードとWiiリモコンを同時には使えません。
wiiremocon.jsの使い方まとめ
〇準備
constWiiRemocon=require('./wiiremocon');constwii=newWiiRemocon();
〇parseBalanceBoardCalibration(data)
バランスボードのキャリブレーションの取得
〇calcurateBalanceBoard(data, base)
バランスボードの各センサの重量の計算
〇parseExtension(type, data)
受信イベント内の拡張コントローラ情報の解釈。typeに指定できるのはWIIREMOTE_EXT_TYPE_NUNCHUCKまたはWIIREMOTE_EXT_TYPE_BALANCEBOARDのみ。
〇parseReporting(data)
受信イベントの解釈
〇connect(addr, retry = 2)
Wiiリモコンまたはバランスボードへの接続
〇disconnect()
Wiiリモコンまたはバランスボードとの切断
〇setLed(led_mask, led_val)
WiiリモコンのLEDの点灯。WIIREMOTE_LED_BIT0~WIIREMOTE_LED_BIT4までのOR指定。
〇setRumble( rumble )
Wiiリモコンの振動の有効化
〇setDataReportingMode(mode)
レポーティングモードの設定。WIIREMOTE_REPORTID_XXXX を指定します。
〇enableSound(enable)
サウンド再生の有効化(今回説明していませんが)
〇writeSound(value)
サウンドデータの書き込み(今回説明していませんが)
〇requestStatus()
ステータス情報のデータレポーティングの要求
以下のレポートを要求します。
http://wiibrew.org/wiki/Wiimote#0x20:_Status
〇enableExtension(enable)
拡張コントローラの有効化
MQTTに接続
今度は、データレポーティングをMQTTにPublishしてみましょう。そうすることで、いろんなクライアントがWiiリモコンを使えるようになります。
npmモジュールのmqttを使いました。
mqttjs/MQTT.js
https://github.com/mqttjs/MQTT.js
MQTTのトピックとして、データレポーティングなどのWiiリモコンから見てOut方向のトピックと、クライアント側からの要求を受け付けるIn方向のトピックの2つを使います。
Out方向のトピックは、ほぼデータレポーティングなのであまり説明はいりませんが、In方向のトピックについて補足します。
Wiiリモコンとの接続やレポーティングモードの設定などは、クライアント側からの要求によって開始します。そのためのトピックです。
以下の要求を受け付けられるようにしてみました。コマンドコード的なもので区別しています。
〇WIIREMOTE_CMD_CONNECT
Wiiリモコンやバランスボードと接続します。BTアドレスを引数として受け取ります。
〇WIIREMOTE_CMD_DISCONNECT
Wiiリモコンやバランスボードと切断します。
〇WIIREMOTE_CMD_WRITE
レポートIDに対する書き込みをします。何を書くかは、クライアント側で制御します。
〇WIIREMOTE_CMD_ENABLE_SOUND
サウンド再生を有効化します。(今回説明しませんが)
〇WIIREMOTE_CMD_ENABLE_EXTENSION
拡張コントローラを有効化します。
〇WIIREMOTE_CMD_REQ_REMOTE_ADDRESS
接続したWiiリモコンやバランスボードのBTアドレスを取得します。結果は、データレポーティングとして返ってきます。
〇WIIREMOTE_CMD_READ_REG
Wiiリモコンのレジスタから読み出しします。結果は、データレポーティングとして返ってきます。
〇WIIREMOTE_CMD_WRITE_REG
Wiiリモコンのレジスタに書き込みをします。
〇WIIREMOTE_CMD_REQ_STATUS
ステータス情報のデータレポーティングを要求します。結果は、データレポーティングとして返ってきます。
〇WIIREMOTE_CMD_READ_REG_LONG
Wiiリモコンのレジスタから読み出しします。WIIREMOTE_CMD_READ_REG と同様ですが、そちらは最大16バイトまでの読み出しに対し、こちらはそれ以上の長さを読み出します。内部で16バイト読み出しを繰り返しています。
ソースコード
MQTTに接続する部分のソースコードを示します。
npmモジュールのmqttとdotenvを使っています。
'use strict';constWiiRemocon=require('./wiiremocon');constmqtt=require('mqtt');require('dotenv').config();constMQTT_HOST=process.env.MQTT_HOST||'【MQTTブローカのURL】';constMQTT_TOPIC_CMD=process.env.MQTT_TOPIC_CMD||'【In方向のトピック名】';constMQTT_TOPIC_EVT=process.env.MQTT_TOPIC_EVT||'【Out方向のトピック名】';constWIIREMOTE_CMD_EVT=0x00;constWIIREMOTE_CMD_ERR=0xff;constWIIREMOTE_CMD_CONNECT=0x01;constWIIREMOTE_CMD_DISCONNECT=0x02;constWIIREMOTE_CMD_WRITE=0x03;constWIIREMOTE_CMD_ENABLE_SOUND=0x04;constWIIREMOTE_CMD_ENABLE_EXTENSION=0x05;constWIIREMOTE_CMD_REQ_REMOTE_ADDRESS=0x06;constWIIREMOTE_CMD_READ_REG=0x07;constWIIREMOTE_CMD_WRITE_REG=0x08;constWIIREMOTE_CMD_REQ_STATUS=0x09;constWIIREMOTE_CMD_READ_REG_LONG=0x0a;varg_address=null;constwii=newWiiRemocon();constclient=mqtt.connect(MQTT_HOST);client.on('connect',()=>{console.log('mqtt.connected.');client.subscribe(MQTT_TOPIC_CMD,(err,granted)=>{if(err){console.error(err);return;}console.log('mqtt.subscribed.');});});client.on('message',async(topic,message)=>{console.log('on.message','topic:',topic,'message:',message.toString());try{varmsg=JSON.parse(message);varcmd=msg.cmd;if(cmd==WIIREMOTE_CMD_CONNECT){if(g_address){awaitwii.disconnect();g_address=null;}varaddress=Uint8Array.from(msg.address);console.log(address);awaitwii.connect(address,msg.retry);g_address=address;}elseif(cmd==WIIREMOTE_CMD_DISCONNECT){if(g_address){awaitwii.disconnect();g_address=null;}}elseif(cmd==WIIREMOTE_CMD_WRITE){awaitwii.writevalue(Buffer.from(msg.value));}elseif(cmd==WIIREMOTE_CMD_ENABLE_SOUND){awaitwii.enableSound(msg.enable);}elseif(cmd==WIIREMOTE_CMD_ENABLE_EXTENSION){awaitwii.enableExtension(msg.enable);}elseif(cmd==WIIREMOTE_CMD_REQ_REMOTE_ADDRESS){varmessage={rsp:WIIREMOTE_CMD_REQ_REMOTE_ADDRESS,};if(g_address)message.address=[...g_address];client.publish(MQTT_TOPIC_EVT,JSON.stringify(message));}elseif(cmd==WIIREMOTE_CMD_READ_REG){vardata=awaitwii.readRegister(msg.offset,msg.len);varmessage={rsp:WIIREMOTE_CMD_READ_REG,offset:offset,data:[...data]}client.publish(MQTT_TOPIC_EVT,JSON.stringify(message));}elseif(cmd==WIIREMOTE_CMD_WRITE_REG){awaitwii.writeRegister(msg.offset,Uint8Array.from(msg.data));}elseif(cmd==WIIREMOTE_CMD_REQ_STATUS){varresult=awaitwii.requestStatus();varmessage={rsp:WIIREMOTE_CMD_REQ_STATUS,status:[...result]}client.publish(MQTT_TOPIC_EVT,JSON.stringify(message));}elseif(cmd==WIIREMOTE_CMD_READ_REG_LONG){varresult=awaitwii.readRegisterLong(msg.offset,msg.len);varmessage={rsp:WIIREMOTE_CMD_READ_REG_LONG,offset:result.offset,value:[...result.value]}client.publish(MQTT_TOPIC_EVT,JSON.stringify(message));}else{throw"Unknown cmd";}}catch(error){console.error(error);varmessage={rsp:WIIREMOTE_CMD_ERR,error:error}client.publish(MQTT_TOPIC_EVT,JSON.stringify(message));}});asyncfunctionwiiremote_mqtt(){wii.on("data",data=>{console.log(data);varmessage={rsp:WIIREMOTE_CMD_EVT,evt:[...data]}client.publish(MQTT_TOPIC_EVT,JSON.stringify(message));});wii.on("error",data=>{console.error("Error",data);wii.disconnect();varmessage={rsp:WIIREMOTE_CMD_ERR,error:data}client.publish(MQTT_TOPIC_EVT,JSON.stringify(message));});}wiiremote_mqtt().catch(error=>{console.error(error);client.end();});
以下の部分は、環境に合わせて変更してください。
【MQTTブローカのURL】
例:mqtt://test.sample.com:1883
【In方向のトピック名】
例:testwii_cmd
【Out方向のトピック名】
例:testwii_evt
起動方法です。
$ node index.js
mqtt.connected.
mqtt.subscribed.
補足
MQTTブローカの立ち上げについては以下を参考にしてください。ブラウザから接続する場合には、WebSocket接続も有効にする必要があります。
AWS IoTにMosquittoをブリッジにしてつなぐ
以上