二足歩行ロボット Rapiro を Node.js で制御 [5] タッチセンサの搭載

# やりたいこと

## 最終目標（[前回](http://qiita.com/mkoku/items/e04289eafd86ea64f28d)と同じ）
1. （[[3]](http://qiita.com/mkoku/items/e04289eafd86ea64f28d)で完了）~~二足歩行ロボット [Rapiro](http://www.rapiro.com/ja/) の制御を、標準の Arduino IDE（C/C++）ベースから、JavaScript（Node.js）ベースに移植し、Rapiro を IoT デバイスっぽくする~~
 * （[[1]]((http://qiita.com/mkoku/items/191ead1b62693003bf64))[[2]](http://qiita.com/mkoku/items/490606b5c70eeb9e5ea3)で完了）~~かつ、PCでの制御ではなく、Rapiro 内部に搭載した Raspberry Pi での制御とし、完全無線化を図る~~
 * （[[3]](http://qiita.com/mkoku/items/e04289eafd86ea64f28d)で完了）~~全機能の移植が難しくとも、最低限、10個の基本動作は移植・再現する~~
2. 可能な限り、[書籍「二足歩行ロボット 工作＆プログラミング（リックテレコム）」](http://www.ric.co.jp/book/contents/book_995.html)の改造内容も移植する
 * （[[4]](http://qiita.com/mkoku/items/d559d7286ecd156f70aa)で完了）~~距離センサの搭載~~
 * 静電容量タッチセンサの搭載など
3. この Rapiro をベースに、さらに賢そうな遊び方を模索する

## 今回 [5] の目標
* 静電容量タッチセンサ（[GROVE I2C MPR121](https://www.switch-science.com/catalog/825/)）を搭載し、触った場所によって様々な動作をさせる
 * [書籍「二足歩行ロボット 工作＆プログラミング（リックテレコム）」](http://www.ric.co.jp/book/contents/book_995.html)の第7章、7.5の内容に相当


# 方法

## 機材
* [Rapiro](http://www.rapiro.com/ja/)
 * 電源を入れる前に、[こちらの手順](http://qiita.com/mkoku/items/253b7b2869f0baba2fa2)で Arduino IDE を用いて、制御ボードに StandardFirmataPlus を書き込んでおく
* Raspberry Pi 3 Model B
 * Rapiro 内部に搭載済（[こちらの手順](http://qiita.com/mkoku/items/191ead1b62693003bf64)で）
 * Node.js インストール済（[こちらの手順](http://qiita.com/mkoku/items/111e6ec21395065f0c28)で）
 * npm で johnny-five, socket.io, express をインストール済
* タッチセンサ [GROVE I2C MPR121](https://www.switch-science.com/catalog/825/)
* タッチセンサ接続用の部品（接続できればよく、以下が必須というわけではない）
    * [ピンヘッダ](https://www.switch-science.com/catalog/92/)（これは必須、Rapiro のメイン基板の JP8 にはんだ付けして I2C を引き出す）
        * はんだ、はんだごて
    * [GROVE 4ピン-ジャンパメスケーブル](https://www.switch-science.com/catalog/1048/)（メイン基板のピンとタッチセンサを接続）
* エネループ（単3）5本 または [Rapiro 用 ACアダプタ](https://www.switch-science.com/catalog/1940/)
* PC（Windows10、Raspberry Pi に SSH や FTP できればなんでも良い）
* スマホまたはタブレット
* 無線LAN環境
* [前回](http://qiita.com/mkoku/items/d559d7286ecd156f70aa)から距離センサは搭載済


## タッチセンサの搭載

* メイン基板の「JP8」の4ピン分の穴にピンヘッダをはんだ付け
* 下の写真がタッチセンサ

![IMG_0094.JPG](https://qiita-image-store.s3.amazonaws.com/0/157570/2663e269-da32-0ba8-bc97-6cd593214eee.jpeg)

* メイン基板の JP8 にはんだ付けしたピンヘッダに、4本のワイヤを以下のように接続

|ワイヤの色|信号|機能|
|:-------:|:-:|:---|
|黄|SCL|I2C の CLOCK 信号|
|白|SDA|I2C の DATA 信号|
|赤|VCC|電源（+5V）|
|黒|GND|グランド|

* Rapiro の角？の隙間からタッチセンサの Feeler を出す

![IMG_0121.JPG](https://qiita-image-store.s3.amazonaws.com/0/157570/b4c2763a-eaa3-51c2-93b3-1ddd38b065b6.jpeg)

![IMG_0122.JPG](https://qiita-image-store.s3.amazonaws.com/0/157570/7d6a8fd6-fdec-ffc2-fb67-663db4b68ed4.jpeg)

* 左右の角の裏と耳に Feeler を貼り付ける（今回は養生テープにて）

|Feeler の位置| Feeler 番号|
|:-------:|:-:|
|右の角|0|
|右の耳|1|
|左の角|2|
|左の耳|3|

![IMG_0124.JPG](https://qiita-image-store.s3.amazonaws.com/0/157570/1ad5064e-1505-a2ad-cbb1-74d022815bb4.jpeg)

![IMG_0125.JPG](https://qiita-image-store.s3.amazonaws.com/0/157570/46182883-eb90-5b6f-9d9d-cce99edcc064.jpeg)

* 取り回しは以下のように

![IMG_0126.JPG](https://qiita-image-store.s3.amazonaws.com/0/157570/f0e35e12-c083-bc75-a1ee-694a46d34a98.jpeg)


## コーディング

### 概要
* 以下の三つのコードを入力し、同じ階層（e.g. /home/pi/rapiro/）に置く
    * __rapiro-cfg.js__ : Rapiro の各種設定を格納するオブジェクト（[前回](http://qiita.com/mkoku/items/d559d7286ecd156f70aa)と同一）
    * __app.js__ : 制御プログラム本体
    * __index.html__ : 操作インタフェース
        * 今回は app.js と index.html のみ[前回](http://qiita.com/mkoku/items/d559d7286ecd156f70aa)から後述のように改編
        * このタッチセンサ単体でのデータ取得方法の詳細は[こちら](http://qiita.com/mkoku/items/16d44a017c6a869b20fd)
* 動作の概要
    * タッチセンサの値は socket で送信されて、操作インタフェース上に常に表示
    * 4つの Feeler にタッチすると、様々な動作・表情を実行（今回の例は下表）

|Feeler の位置| Feeler 番号|動作 / 表情|
|:-------:|:-:|:-:|
|右の角|0|forward / blue|
|右の耳|1|yellow / yellow|
|左の角|2|stop / white|
|左の耳|3|red / red|

### 制御プログラム本体（app.js）
[前回](http://qiita.com/mkoku/items/d559d7286ecd156f70aa)からの主な改編部分は以下

* 32行目：タッチセンサの I2C アドレス
* 33行目：タッチセンサの Feeler の数
* 37～38行目：タッチセンサの状態を格納するプロパティ
* 42～67行目：タッチセンサの初期化の関数
* 184～229行目：タッチセンサデータの取得とタッチが検出された時の処理

```js:app.js
// rapiro.js 05
// 静電容量タッチセンサの搭載
// 拡張用 I2C ピンの利用
// 　・静電容量タッチセンサ（Grove I2C MPR121）を搭載
// 　・TODO: どんな動作？
// 2017.01.28　by Mitsuteru Kokubun
// 詳しくは以下を参照してください
// http://qiita.com/mkoku/items/d559d7286ecd156f70aa

'use strict';                                   // 厳格モードにする

// httpサーバとsocket.ioの設定
const express = require('express');             // expressモジュールを使う
const app     = express();                      // expressでアプリを作る
const server  = require('http').Server(app);    // httpサーバを起動しアプリをサーブ
const io      = require('socket.io')(server);   // サーバにsocket.ioをつなぐ
server.listen(3000);                            // サーバの3000番ポートをリッスン開始
app.use(express.static(__dirname));             // ホームdirにあるファイルを使えるようにする
app.get('/', function (req, res) {              // アクセス要求があったら
    res.sendFile(__dirname + '/index.html');    // index.htmlを送る
});
let   socket = null;                            // socket接続のインスタンス

// johnny-fiveの設定
const five  = require('johnny-five');           // johnny-fiveモジュールの読み込み
const cfg   = require('./rapiro-cfg');          // 設定ファイル'rapiro-cfg.js'の読み込み
const board = new five.Board({                  // Rapiro制御ボードのインスタンス
    port: '/dev/ttyAMA0'                        // シリアルポート名（環境による）
});
const pinServoDC        = 17;                   // サーボへの電源供給ピン番号（17）
const obstacleThreshold = 9;                    // 障害物検出と判定する距離センサの閾値[cm]
const MPR121ADDR        = 0x5A;                 // MPR121静電容量タッチセンサコントローラのアドレス
const feelerNum         = 4;                    // タッチセンサのfeelerの数
const rapiro = {                                // Rapiroの設定や動作等を格納するオブジェクト
    ready:    false,                            // Rapiroの準備状態（初期値false）
    obstacle: false,                            // 障害物の検出状態（初期値false）
    touch:    [0, 0, 0, 0],                     // タッチセンサのタッチ状態（初期値4ch全て0）
    touched:  [0, 0, 0, 0]
};


// MPR121静電容量タッチセンサコントローラの初期化関数
function mpr121Config(device) {
    // Section A: dataがベースラインより大きい時のフィルタリング
    device.i2cWrite(MPR121ADDR, 0x2B, 0x01);    // MHD_R
    device.i2cWrite(MPR121ADDR, 0x2C, 0x01);    // NHD_R
    device.i2cWrite(MPR121ADDR, 0x2D, 0x00);    // NCL_R
    device.i2cWrite(MPR121ADDR, 0x2E, 0x00);    // FDL_R
    // Section B: dataがベースラインより小さい時のフィルタリング
    device.i2cWrite(MPR121ADDR, 0x2F, 0x01);    // MHD_F
    device.i2cWrite(MPR121ADDR, 0x30, 0x01);    // NHD_F
    device.i2cWrite(MPR121ADDR, 0x31, 0xFF);    // NCL_L
    device.i2cWrite(MPR121ADDR, 0x32, 0x02);    // FDL_L
    // Section C: 各電極(ELE0-11)の閾値(T:Touch,R:Release)の設定
    device.i2cWrite(MPR121ADDR, 0x41, 0x0F);    // ELE0_T
    device.i2cWrite(MPR121ADDR, 0x42, 0x0A);    // ELE0_R
    device.i2cWrite(MPR121ADDR, 0x43, 0x0F);    // ELE1_T
    device.i2cWrite(MPR121ADDR, 0x44, 0x0A);    // ELE1_R
    device.i2cWrite(MPR121ADDR, 0x45, 0x0F);    // ELE2_T
    device.i2cWrite(MPR121ADDR, 0x46, 0x0A);    // ELE2_R
    device.i2cWrite(MPR121ADDR, 0x47, 0x0F);    // ELE3_T
    device.i2cWrite(MPR121ADDR, 0x48, 0x0A);    // ELE3_R
    // Section D: フィルタの設定
    device.i2cWrite(MPR121ADDR, 0x5D, 0x04);
    // Section E: 電極の設定
    device.i2cWrite(MPR121ADDR, 0x5E, 0x0C);
}


// 制御ボードの準備ができたら
board.on('ready', function() {

    // 各サーボのServoインスタンスを作成
    rapiro.head  = new five.Servo(cfg.servo.head);  // 頭
    rapiro.waist = new five.Servo(cfg.servo.waist); // 腰
    rapiro.r_s_r = new five.Servo(cfg.servo.r_s_r); // 右肩ロール（上下）
    rapiro.r_s_p = new five.Servo(cfg.servo.r_s_p); // 右肩ピッチ（開閉）
    rapiro.r_h_g = new five.Servo(cfg.servo.r_h_g); // 右手
    rapiro.l_s_r = new five.Servo(cfg.servo.l_s_r); // 左肩ロール（上下）
    rapiro.l_s_p = new five.Servo(cfg.servo.l_s_p); // 左肩ピッチ（開閉）
    rapiro.l_h_g = new five.Servo(cfg.servo.l_h_g); // 左手
    rapiro.r_f_y = new five.Servo(cfg.servo.r_f_y); // 右足ヨー（開閉）
    rapiro.r_f_p = new five.Servo(cfg.servo.r_f_p); // 右足ピッチ（内外）
    rapiro.l_f_y = new five.Servo(cfg.servo.l_f_y); // 左足ヨー（開閉）
    rapiro.l_f_p = new five.Servo(cfg.servo.l_f_p); // 左足ピッチ（内外）
    // 全サーボ（全身）をServosインスタンスに入れる
    rapiro.body = new five.Servos([
        rapiro.head,  rapiro.waist,                 // 頭・腰
        rapiro.r_s_r, rapiro.r_s_p, rapiro.r_h_g,   // 右腕
        rapiro.l_s_r, rapiro.l_s_p, rapiro.l_h_g,   // 左腕
        rapiro.r_f_y, rapiro.r_f_p,                 // 右足
        rapiro.l_f_y, rapiro.l_f_p                  // 左足
    ]);
    // 動作アニメーションのインスタンスを作成し、Servosを紐づける
    const bodyMotion = new five.Animation(rapiro.body);

    // 各LEDのインスタンスを作成
    rapiro.faceR = new five.Led(cfg.led.R);         // 赤
    rapiro.faceG = new five.Led(cfg.led.G);         // 緑
    rapiro.faceB = new five.Led(cfg.led.B);         // 青
    // 全LEDをLedsインスタンスに入れる
    rapiro.face  = new five.Leds([
        rapiro.faceR, rapiro.faceG, rapiro.faceB
    ]);
    // 表情アニメーションのインスタンスを作成し、Ledsを紐づける
    const facialExpression = new five.Animation(rapiro.face);

    // 動作アニメーションを実行する関数（引数：動作名の文字列）
    rapiro.execMotion = function(motionName) {
        const obj = cfg.motion;                                     // motionオブジェクトを取得
        for (let pname in obj) {                                    // motionオブジェクト中の全プロパティについて検討
            if (pname == motionName) {                              // 指定の動作名のプロパティがあったら
                rapiro.currentMotionName     = pname;               // 現在の動作名をその動作名に設定
                rapiro.currentMotionSequence = obj[pname];          // 現在の動作シーケンスにその動作を格納
                bodyMotion.enqueue(rapiro.currentMotionSequence);   // 動作アニメーションの開始
                return;                                             // あとは抜ける
            }
        }
        console.log('Error: unidentified motion argument');         // 指定の動作名のプロパティがなかったらエラー表示
    };

    // 表情アニメーションを実行する関数（引数：表情名の文字列）
    rapiro.execFace = function(faceName) {
        const obj = cfg.face;                                       // faceオブジェクトを取得
        for (let pname in obj) {                                    // faceオブジェクト中の全プロパティについて
            if (pname == faceName) {                                // 指定の表情名のプロパティがあったら
                rapiro.currentFaceName     = pname;                 // 現在の表情名をその表情名に設定
                rapiro.currentFaceSequence = obj[pname];            // 現在の表情シーケンスにその表情を格納
                facialExpression.enqueue(rapiro.currentFaceSequence);   // 表情アニメーションの開始
                return;                                             // あとは抜ける
            }
        }
        console.log('Error: unidentified face argument');           // 指定の表情名のプロパティがなかったらエラー表示
    };

    // サーボ電源をON
    this.pinMode(pinServoDC, five.Pin.OUTPUT);      // 電源供給ピンを出力モードに
    this.digitalWrite(pinServoDC, 1);               // 電源供給ピンに1を出力
    // 初期状態を作る
    rapiro.execMotion('stop');                      // 動作をstopに
    rapiro.execFace('white');                       // 表情をwhiteに
    // Rapiroの準備OK
    rapiro.ready = true;

    // デバッグ用：コマンドラインからモーションと表情を制御
    board.repl.inject({
        rapiro: rapiro
    });
    console.log("Type rapiro.execMotion('name') or rapiro.execFace('name')");

    // 距離センサ（GP2Y0A21YK）の処理
    const proximity = new five.Proximity({
        controller: 'GP2Y0A21YK',   // コントローラに GP2Y0A21YK を指定
        pin: 'A6',                  // A6 ピンを指定
        freq: 250                   // サンプリング間隔[ms]
    });
    // 距離センサのデータが取得されたら
    proximity.on('data', function() {
        const dist = this.cm;                                       // cm or in(inch)
        // 障害物の処理
        if ((dist < obstacleThreshold) && (rapiro.obstacle == false)) {
            // 距離が閾値未満かつ現在障害物が未検出状態だったら
            rapiro.obstacle = true;                                 // 障害物検出状態に
            rapiro.previousMotionName = rapiro.currentMotionName;   // 現在の動作名を一時保管
            rapiro.previousFaceName = rapiro.currentFaceName;       // 現在の表情名を一時補間
            rapiro.execMotion('blue');                              // 動作blueを実行
            rapiro.execFace('red');                                 // 表情redを実行
        } else if ((dist >= obstacleThreshold) && (rapiro.obstacle == true)) {
            // 距離が閾値以上かつ現在障害物が検出状態だったら
            rapiro.obstacle = false;                                // 障害物未検出状態に
            rapiro.execMotion(rapiro.previousMotionName);           // 前の動作を再開
            rapiro.execFace(rapiro.previousFaceName);               // 前の表情を再開
        }
        // 距離の値と現状の動作・表情をsocketで送る
        emitSocket('proximity', {
            distance: dist                      // 距離
        });
        emitSocket('response', {
            motion: rapiro.currentMotionName,   // 動作名
            face:   rapiro.currentFaceName      // 表情名
       });
    });

    // タッチセンサ（Grove I2C MPR121）の処理
    this.i2cConfig();       // I2Cを使うためのjohnny-fiveのおまじない
    mpr121Config(this);     // MPR121静電容量タッチセンサコントローラを初期設定
    // タッチセンサの状態の読み込みとタッチの判定（i2cRead()は繰り返し実行される）
    this.i2cRead(MPR121ADDR, 1, function(bytes){
        (bytes & 0x01) == 0x01 ? rapiro.touch[0] = 1 : rapiro.touch[0] = 0; // ch0を判定
        (bytes & 0x02) == 0x02 ? rapiro.touch[1] = 1 : rapiro.touch[1] = 0; // ch1を判定
        (bytes & 0x04) == 0x04 ? rapiro.touch[2] = 1 : rapiro.touch[2] = 0; // ch2を判定
        (bytes & 0x08) == 0x08 ? rapiro.touch[3] = 1 : rapiro.touch[3] = 0; // ch3を判定
        for (let t = 0; t < feelerNum; t++) {
            if ((rapiro.touch[t] == 1) && (rapiro.touched[t] == 0)) {
                // タッチセンサに触れた時（触れていなかった状態から）
                rapiro.touched[t] = 1;
                switch (t) {
                    case 0:
                        rapiro.execMotion('forward');   // 動作forwardを実行
                        rapiro.execFace('blue');        // 表情blueを実行
                        break;
                    case 1:
                        rapiro.execMotion('yellow');    // 動作yellowを実行
                        rapiro.execFace('yellow');      // 表情yellowを実行
                        break;
                    case 2:
                        rapiro.execMotion('stop');      // 動作stopを実行
                        rapiro.execFace('white');       // 表情whiteを実行
                        break;
                    case 3:
                        rapiro.execMotion('red');       // 動作red実行
                        rapiro.execFace('red');         // 表情redを実行
                        break;
                }
            }
            if ((rapiro.touch[t] == 0) && (rapiro.touched[t] == 1)) {
                // タッチセンサから離した時（触れた状態から）
                rapiro.touched[t] = 0;
            }
        }
        // タッチセンサの値と現状の動作・表情をsocketで送る
        emitSocket('touch', {
            touch: rapiro.touch                 // タッチセンサの状態
        });
        emitSocket('response', {
            motion: rapiro.currentMotionName,   // 動作名
            face:   rapiro.currentFaceName      // 表情名
       });
    });

    // 終了時の処理
    this.on('exit', function() {
        this.digitalWrite(pinServoDC, 0);           // サーボの電源をOFF
    });
});


// WebSocketによる制御
io.on('connection', function(s) {
    socket = s;                                     // socket接続有り
    socket.on('request', function(data) {           // requestイベントが届いたら
        if (rapiro.ready == true) {                 // Rapiroの準備OKなら
            // アニメーションを実行
            rapiro.execMotion(data.motion);         // 動作アニメーション
            rapiro.execFace(data.face);             // 表情アニメーション
            // 現在の状態をresponseイベントとしてsocketで送る
            emitSocket('response', {
                motion: rapiro.currentMotionName,   // 動作名
                face:   rapiro.currentFaceName      // 表情名
            });
        }
    });
});

// ブラウザにsocketでデータを送る
function emitSocket(event, data) {
    if (socket != null) {
        socket.emit(event, data);
    }
}
```

### 操作インタフェース（index.html）

[前回](http://qiita.com/mkoku/items/d559d7286ecd156f70aa)からの主な改編部分は以下

* 30～33行目：タッチセンサの値を表示する場所
* 157～161行目：タッチセンサの値を表示

```html:index.html
<!DOCTYPE html>
<!--
// Rapiro制御インタフェース
// Rapiro標準の10モーション実装
// socket.ioによりブラウザから制御
// 距離センサ（GP2Y0A21YK）による動作制御
// タッチセンサ（Grove I2C MPR121）による動作制御
// 2017.01.28　by Mitsuteru Kokubun
-->
<html>
<head>
    <meta charset='utf-8'>
    <meta name='viewport' content='width=device-width,initial-scale=1'>
    <title>rapiro.js 05: タッチセンサ</title>
    <style>
        td.center {text-align: center}
    </style>
</head>

<body>
    <table>
        <!-- Rapiroからのレスポンスを表示する場所 -->
        <tr>
            <td class='center' colspan='3' id='msg'>motion / face</td>
        </tr>
        <!-- Rapiroの距離センサの値を表示する場所 -->
        <tr>
            <td class='center' colspan='3' id='prox'>proximity [cm]</td>
        </tr>
        <!-- Rapiroのタッチセンサの値を表示する場所 -->
        <tr>
            <td class='center' colspan='3' id='touch'>touch [0, 1, 2, 3]</td>
        </tr>
        <tr><td>&nbsp;</td></tr>
        <!-- 操作ボタン -->
        <tr>
            <td class='center'></td>
            <td class='center'><input type='button' value='↑' id='btnForward'></td>
            <td class='center'></td>
        </tr>
        <tr>
            <td class='center'><input type='button' value='←' id='btnLeft'></td>
            <td class='center'><input type='button' value='■' id='btnStop'></td>
            <td class='center'><input type='button' value='→' id='btnRight'></td>
        </tr>
        <tr>
            <td class='center'></td>
            <td class='center'><input type='button' value='↓' id='btnBack'></td>
            <td class='center'></td>
        </tr>
        <tr><td>&nbsp;</td></tr>
        <tr>
            <td class='center'><input type='button' value='R' id='btnRed'></td>
            <td class='center'><input type='button' value='G' id='btnGreen'></td>
            <td class='center'><input type='button' value='B' id='btnBlue'></td>
        </tr>
        <tr>
            <td class='center'><input type='button' value='Y' id='btnYellow'></td>
            <td class='center'><input type='button' value='P' id='btnPush'></td>
            <td class='center'></td>
        </tr>
    </table>

    <!-- socket.ioライブラリの読み込み（定型） -->
    <script src='/socket.io/socket.io.js'></script>

    <!-- メインスクリプト -->
    <script>
        var socket = io('http://192.168.43.200:3000');          // socket接続

        // 各ボタンオブジェクトの取得
        var btnStop    = document.getElementById('btnStop');    // stopボタン
        var btnForward = document.getElementById('btnForward'); // forwardボタン
        var btnBack    = document.getElementById('btnBack');    // backボタン
        var btnRight   = document.getElementById('btnRight');   // rightボタン
        var btnLeft    = document.getElementById('btnLeft');    // leftボタン
        var btnGreen   = document.getElementById('btnGreen');   // greenボタン
        var btnYellow  = document.getElementById('btnYellow');  // yellowボタン
        var btnBlue    = document.getElementById('btnBlue');    // blueボタン
        var btnRed     = document.getElementById('btnRed');     // redボタン
        var btnPush    = document.getElementById('btnPush');    // pushボタン

        // 各ボタンをクリックした時の処理
        btnStop.addEventListener('click', function() {      // stop
            socket.emit('request', {                        // requestイベントをsocketで送る
                motion: 'stop',                             // 動作名
                face:   'white'                             // 表情名
            });
        });
        btnForward.addEventListener('click', function() {   // forward
            socket.emit('request', {
                motion: 'forward',
                face:   'blue'
            });
        });
        btnBack.addEventListener('click', function() {      // back
            socket.emit('request', {
                motion: 'back',
                face:   'blue'
            });
        });
        btnRight.addEventListener('click', function() {     // right
            socket.emit('request', {
                motion: 'right',
                face:   'blue'
            });
        });
        btnLeft.addEventListener('click', function() {      // left
            socket.emit('request', {
                motion: 'left',
                face:   'blue'
            });
        });
        btnGreen.addEventListener('click', function() {     // green
            socket.emit('request', {
                motion: 'green',
                face:   'green'
            });
        });
        btnYellow.addEventListener('click', function() {    // yellow
            socket.emit('request', {
                motion: 'yellow',
                face:   'yellow'
            });
        });
        btnBlue.addEventListener('click', function() {      // blue
            socket.emit('request', {
                motion: 'blue',
                face:   'blue'
            });
        });
        btnRed.addEventListener('click', function() {       // red
            socket.emit('request', {
                motion: 'red',
                face:   'red'
            });
        });
        btnPush.addEventListener('click', function() {      // push
            socket.emit('request', {
                motion: 'push',
                face:   'blue'
            });
        });

        // Rapiroからのレスポンスの表示
        socket.on('response', function(data) {              // socketでresponseイベントが届いたら
            var msg = document.getElementById('msg');       // div要素を取得
            msg.innerHTML = data.motion + ' / ' + data.face;  // 動作と表情を表示
        });

        // Rapiroの距離センサの値の表示
        socket.on('proximity', function(data) {
            var prox = document.getElementById('prox');
            prox.innerHTML = data.distance + ' cm'
        });

        // Rapiroのタッチセンサの値の表示
        socket.on('touch', function(data) {
            var touch = document.getElementById('touch');
            touch.innerHTML = data.touch
        });

    </script>
</body>
</html>
```


## 動作確認

* 上記3つのコードを Rapiro 内部の Raspberry Pi に FTP
 * e.g. /home/pi/rapiro/ 内に
* SSH で Rapiro 内部の Raspberry Pi にアクセスし、node で app.js を実行
 * 準備ができると Rapiro のサーボに通電され直立状態となり、目は白色にフェードする

```
pi@raspberrypi:~/rapiro $ node app.js
```

* 各 Feeler に触れると、所定の動作・表情が実行されればOK
    * 以下、動作の様子（動画）

[![Rapiro を node.js (johnny-five + socket.io) で制御 － タッチセンサ搭載](http://img.youtube.com/vi/jaMozCgdUgY/0.jpg)](https://youtu.be/jaMozCgdUgY)

* PC や スマホのブラウザで、Raspberry Pi のIPアドレスにアクセス

```
http://192.168.**.***:3000
```

* タッチの状態が表示されていればOK


# まとめ

これで、最終目標の 2 を達成できました。ついに、[書籍「二足歩行ロボット 工作＆プログラミング（リックテレコム）」](http://www.ric.co.jp/book/contents/book_995.html)の改造内容をほぼ移植できました（サウンドディテクタと XBee は除く）。
本当はこの書籍のように Rapiro の内部に Feeler を隠したかったのですが、試したところ、頭部の内側に Feeler を貼った状態では反応しませんでした。角の裏側に貼った状態ならかろうじて（感度は悪いですが）反応してくれました。もしかすると初期設定のパラメータなどで感度を変えられるのかもしれませんが、調べていません。今後の（長期的な）課題とします。

[次回](http://qiita.com/mkoku/items/f978161b5ab247111d8a)以降は、世の中にある Rapiro の楽しげな改造事例を参考にしながら、例えば以下のようなことに挑戦していきたい考えです（あくまで考え）。

* スピーカーの搭載、音声合成
* 天気予報
* 赤外線による家電の操作
* カメラの搭載、ストリーミング
* 音声認識
* 人工知能？による簡単な会話
* etc.

最新コードは以下にあります。
https://github.com/mkokubun/rapiro.js