WiiリモコンとヌンチャクとバランスボードをMQTTするぞ(1/2)

前回の投稿で、WiiリモコンをNode.jsから触れるようにしました。( [WiiリモコンをNode.jsから操ってみよう](https://qiita.com/poruruba/items/4f0d916703061e6205e8) )

今度は、WiiリモコンをMQTTに接続して、ブラウザから操ってみます。さらに、ヌンチャクやバランスボードにも対応させました。
以下の2回に分けて説明していこうと思います。

１．ヌンチャクとバランスボードにも対応させ、WiiリモコンをMQTTに接続する(今回はこちら)
２．ブラウザからWiiリモコンたちに接続する

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/b6dd2094-fed8-fbea-65c9-fe0f5cd69bbb.png)

完成するとブラウザからこんな感じで見えますが、それは次回の投稿で。先に、Webページのリンクを張っておきます。
　https://poruruba.github.io/WiiRemocon/html/

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/99aa01f6-c1f6-bdeb-442f-cfdccb5f5a10.png)

ソースコードを以下にアップしておきました。

poruruba/WiiRemocon
　https://github.com/poruruba/WiiRemocon

続きの投稿はこちら
　[WiiリモコンとヌンチャクとバランスボードをMQTTするぞ(2/2)](https://qiita.com/poruruba/items/d7afd447f6c9a49b40cf)

#Wiiリモコンからの受信イベント

Wiiリモコンからは、ボタン押下や加速度の変化など、状態が変わるたびに、Bluetoothでイベントが受信されます。その内容は、レポーティングモードによって変わり、設定することで、受信する内容を変えることができます。
ボタン押下イベントだけのレポーティングモードに設定した場合は、Wiiリモコンのボタンを押下したりはなしたりしたときのみイベントが通知されますが、加速度も返るようにレポーティングモードに設定すると、ひっきりなしにイベントが受信されるようになります。

イベントの内容は、以下に記載があります。以降ではデータレポーティングと呼ぶことにします。
http://wiibrew.org/wiki/Wiimote#Data_Reporting

様々な内容が送られてきますので、上記の記載内容に従ってパースする関数を用意しました。```parseReporting(data)``` です。

```js:wiiremocon.js
・・・
  parseReporting(data){
    if( data[0] == WIIREMOTE_REPORTID_STATUS ){
      var report = {
        report_id: data[0],
        btns: (((data[1] << 8) | data[2])) & 0x1f9f,
        leds: data[3] & 0xf0,
        flags: data[3] & 0x0f,
        battery: data[6]
      };
      return report;
    }else
    if( data[0] == WIIREMOTE_REPORTID_READ_DATA ){
・・・
```

レポーティングモード設定する関数は以下の通りです。

```js:wiiremocon.js
  setDataReportingMode(mode){
    var param = 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'));
    return this.l2cap.write(0, param);
  }
```

#拡張コントローラの有効化

Wiiヌンチャクは、Wiiリモコンに接続して使います。
拡張コントローラと呼ばれていて、利用するには有効化が必要です。

```js:wiiremocon.js
  async enableExtension(enable){
    if( enable ){
      await this.writeRegister(0xa400f0, Buffer.from([0x55]));
      await this.writeRegister(0xa400fb, Buffer.from([0x00]));
    }else{
      await this.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の使い方まとめ

〇準備

```js
const WiiRemocon = require('./wiiremocon');
const wii = new WiiRemocon();
```

〇connect(addr, retry = 2)
Wiiリモコンまたはバランスボードへの接続

〇disconnect()
Wiiリモコンまたはバランスボードとの切断

〇readBalanceBoardCalibration()
バランスボードのキャリブレーションの取得

〇calcurateBalanceBoard(data, base)
バランスボードの各センサの重量の計算

〇parseExtension(type, data)
受信イベント内の拡張コントローラ情報の解釈。typeに指定できるのはWIIREMOTE_EXT_TYPE_NUNCHUCKまたはWIIREMOTE_EXT_TYPE_BALANCEBOARDのみ。

〇parseReporting(data)
受信イベントの解釈

〇setLed(led_mask, led_val)
WiiリモコンのLEDの点灯。WIIREMOTE_LED_BIT0～WIIREMOTE_LED_BIT4までのOR指定。

〇setRumble( rumble )
Wiiリモコンの振動の有効化

〇setDataReportingMode(mode)
レポーティングモードの設定。WIIREMOTE_REPORTID_XXXX を指定します。

〇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_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を使っています。

```js:index.js
'use strict';

const WiiRemocon = require('./wiiremocon');
const mqtt = require('mqtt');
require('dotenv').config();

const MQTT_HOST = process.env.MQTT_HOST || '【MQTTブローカのURL】';
const MQTT_CLIENT_ID = process.argv[2] || '【クライアントID】';
const MQTT_TOPIC_CMD = process.argv[3] || '【In方向のトピック名】';
const MQTT_TOPIC_EVT = process.argv[4] || '【Out方向のトピック名】';
console.log("MQTT_CLIENT_ID: " + MQTT_CLIENT_ID);
console.log("MQTT_TOPIC_CMD: " + MQTT_TOPIC_CMD);
console.log("MQTT_TOPIC_EVT: " + MQTT_TOPIC_EVT);

const WIIREMOTE_CMD_EVT = 0x00;
const WIIREMOTE_CMD_ERR = 0xff;
const WIIREMOTE_CMD_CONNECT = 0x01;
const WIIREMOTE_CMD_DISCONNECT = 0x02;
const WIIREMOTE_CMD_WRITE = 0x03;
const WIIREMOTE_CMD_ENABLE_SOUND = 0x04;
const WIIREMOTE_CMD_ENABLE_EXTENSION = 0x05;
const WIIREMOTE_CMD_REQ_REMOTE_ADDRESS = 0x06;
const WIIREMOTE_CMD_READ_REG = 0x07;
const WIIREMOTE_CMD_WRITE_REG = 0x08;
const WIIREMOTE_CMD_REQ_STATUS = 0x09;
const WIIREMOTE_CMD_READ_REG_LONG = 0x0a;

var g_address = null;

const wii = new WiiRemocon();
const client = mqtt.connect(MQTT_HOST, { clientId: MQTT_CLIENT_ID });

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{
    var msg = JSON.parse(message);
    var cmd = msg.cmd;
    if( cmd == WIIREMOTE_CMD_CONNECT ){
      if( g_address ){
        await wii.disconnect();
        g_address = null;
      }
      var address = Uint8Array.from(msg.address);
      console.log(address);
      await wii.connect(address, msg.retry);
      g_address = address;
    }else
    if( cmd == WIIREMOTE_CMD_DISCONNECT ){
      if( g_address ){
        await wii.disconnect();
        g_address = null;
      }
    }else
    if( cmd == WIIREMOTE_CMD_WRITE ){
      await wii.writevalue(Buffer.from(msg.value));
    }else
    if( cmd == WIIREMOTE_CMD_ENABLE_SOUND ){
      await wii.enableSound(msg.enable);
    }else
    if( cmd == WIIREMOTE_CMD_ENABLE_EXTENSION ){
      await wii.enableExtension(msg.enable);
    }else
    if( cmd == WIIREMOTE_CMD_REQ_REMOTE_ADDRESS ){
      var message = {
        rsp: WIIREMOTE_CMD_REQ_REMOTE_ADDRESS,
      };
      if( g_address )
        message.address = [...g_address];
      client.publish(MQTT_TOPIC_EVT, JSON.stringify(message));
    }else
    if( cmd == WIIREMOTE_CMD_READ_REG ){
      var data = await wii.readRegister(msg.offset, msg.len);
      var message = {
        rsp: WIIREMOTE_CMD_READ_REG,
        offset: offset,
        data: [...data]
      }
      client.publish(MQTT_TOPIC_EVT, JSON.stringify(message));
    }else
    if( cmd == WIIREMOTE_CMD_WRITE_REG ){
      await wii.writeRegister(msg.offset, Uint8Array.from(msg.data));
    }else
    if( cmd == WIIREMOTE_CMD_REQ_STATUS ){
      var result = await wii.requestStatus();
      var message = {
        rsp: WIIREMOTE_CMD_REQ_STATUS,
        status: [...result]
      }
      client.publish(MQTT_TOPIC_EVT, JSON.stringify(message));
    }else
    if( cmd == WIIREMOTE_CMD_READ_REG_LONG ){
      var result = await wii.readRegisterLong(msg.offset, msg.len);
      var message = {
        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);
    var message = {
      rsp: WIIREMOTE_CMD_ERR,
      error: error
    }
    client.publish(MQTT_TOPIC_EVT, JSON.stringify(message));
  }
});

async function wiiremote_mqtt(){
  wii.on("data", data =>{
    console.log(data);
    var message = {
      rsp: WIIREMOTE_CMD_EVT,
      evt: [...data]
    }
    client.publish(MQTT_TOPIC_EVT, JSON.stringify(message));
  });

  wii.on("error", data =>{
    console.error("Error", data);
    wii.disconnect();
    var message = {
      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
【クライアントID】
例：server
【In方向のトピック名】
　例：testwii_cmd
【Out方向のトピック名】
　例：testwii_evt

※複数同時に立ち上げる場合は、クライアントID、In方向/Out方向のトピック名は被らないようにしてください。

起動方法です。

```
$ node index.js
または
$ node index.js [クライアントID] [In方向のトピック名] [Out方向のトピック名]
MQTT_CLIENT_ID: [クライアントID]
MQTT_TOPIC_CMD: [In方向のトピック名]
MQTT_TOPIC_EVT: [Out方向のトピック名]
mqtt.connected.
mqtt.subscribed.
```

#補足

MQTTブローカの立ち上げについては以下を参考にしてください。ブラウザから接続する場合には、WebSocket接続も有効にする必要があります。
　[AWS IoTにMosquittoをブリッジにしてつなぐ](https://qiita.com/poruruba/items/084317e01b9fababc02e#mosquitto%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB)

以上