二足歩行ロボット Rapiro を Node.js で制御 [3] 10個の基本動作を実装

# やりたいこと

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

## 今回 [3] の目標
* [Rapiro の標準ファームウェア](https://github.com/Ishiwatari/RAPIRO)に実装された10個の基本動作を全て移植・再現する
 * 完全に正確な再現ではないが…


# 方法

## 機材（[前回](http://qiita.com/mkoku/items/490606b5c70eeb9e5ea3)とほぼ同じ）
* [Rapiro](http://www.rapiro.com/ja/)
 * 電源を入れる前に、Arduino IDE を用いて、制御ボードに StandardFirmata を書き込んでおく
 * いつでも [Rapiro の標準ファームウェア](https://github.com/Ishiwatari/RAPIRO)に戻せるので問題なし
* Raspberry Pi 3 Model B
 * Rapiro 内部に搭載済（[こちらの手順](http://qiita.com/mkoku/items/191ead1b62693003bf64)で）
 * Node.js インストール済（[こちらの手順](http://qiita.com/mkoku/items/111e6ec21395065f0c28)で）
* エネループ（単3）5本 または [Rapiro 用 ACアダプタ](https://www.switch-science.com/catalog/1940/)
* PC（Windows10、Raspberry Pi に SSH や FTP できればなんでも良い）
* スマホまたはタブレット
* 無線LAN環境

## 手続き

### 準備
* Rapiro 内の Raspberry Pi に SSH でアクセスし、npm で [johnny-five](http://johnny-five.io/)、[socket.io](http://socket.io/)、[express](http://expressjs.com/ja/) をインストール

```
pi@raspberrypi:~ $ npm install johnny-five socket.io express
```

### コーディング
* 以下の三つのコードを入力し、同じ階層（e.g. /home/pi/rapiro/）に置く
 * __rapiro-cfg.js__ : Rapiro の各種設定を格納するオブジェクト
 * __app.js__ : 制御プログラム本体
 * __index.html__ : 操作インタフェース
* 実際には、PC の エディタ（[Microsoft Visual Studio Code](https://code.visualstudio.com/)）で書いて、Raspberry Pi に FTP しました


#### Rapiro の各種設定を格納するオブジェクト
以下の4つのオブジェクトを設定しました。制御プログラム本体（app.js）内に書くと見にくくなるので、別ファイルに分けて、app.jsからは require() で読み込むようにしました。

 * servo : 12個の各サーボのピン番号、開始角度、可動域、オフセット
 * motion : 10個の標準動作のアニメーションの設定
 * led : 3色のLEDのピン番号
 * face : LEDのアニメーションの設定

```js:rapiro-cfg.js
// Rapiroの設定や動作等を格納するオブジェクト
// メインのjs内で例えば cfg = require('./rapiro-cfg'); のように参照
// Rapiro標準の10モーション実装
// 2017.01.17 by mkoku

'use strict';

// サーボの諸設定
// servoという名前で外部から読めるようにexports
exports.servo = {
    head: {     // 0. head
        pin:     10,        // ピン番号
        startAt: 90,        // 開始角度
        range:   [0, 180],  // 可動域
        offset:  -7         // オフセット（トリム）
    },
    waist: {    // 1. waist
        pin:     11,
        startAt: 90,
        range:   [0, 180],
        offset:  2
    },
    r_s_r: {    // 2. right shoulder roll [up / down]
        pin:     9,
        startAt: 0,
        range:   [0, 180],
        offset:  0
    },
    r_s_p: {    // 3. right shoulder pitch [open / close]
        pin:     8,
        startAt: 130,
        range:   [40, 130],
        offset:  0
    },
    r_h_g: {    // 4. right hand grip
        pin:     7,
        startAt: 90,
        range:   [50, 110],
        offset:  0
    },
    l_s_r: {    // 5. left shoulder roll [up / down]
        pin:     12,
        startAt: 180,
        range:   [0, 180],
        offset:  0
    },
    l_s_p: {    // 6. left shoulder pitch [open /close]
        pin:     13,
        startAt: 50,
        range:   [40, 130],
        offset:  0
    },
    l_h_g: {    // 7. left hand grip
        pin:     14,
        startAt: 90,
        range:   [70, 130],
        offset:  0
    },
    r_f_y: {    // 8. right foot yaw
        pin:     4,
        startAt: 90,
        range:   [45, 135],
        offset:  -10
    },
    r_f_p: {    // 9. right foot pitch
        pin:     2,
        startAt: 90,
        range:   [40, 125],
        offset:  6
    },
    l_f_y: {    // 10. left foot yaw
        pin:     15,
        startAt: 90,
        range:   [45, 135],
        offset:  9
    },
    l_f_p: {    // 11. left foot pitch
        pin:     16,
        startAt: 90,
        range:   [55, 140],
        offset:  -12
    }
};

// モーション（動作アニメーション）の諸設定
// motionという名前で外部から読めるようにexports
exports.motion = {
    // 0. stop
    stop: {
        duration: 1000,         // 再生時間[ms]
        loop: false,            // ループ再生有無
        cuePoints: [0.0, 1.0],  // キューポイント
        keyFrames: [            // キーフレーム（行:サーボ12個 × 列:キューポイントごとの角度とイージングの種類）
            [null,  {value: this.servo.head.startAt,  easing:'inOutSine'}], // 頭
            [null,  {value: this.servo.waist.startAt, easing:'inOutSine'}], // 腰
            [null,  {value: this.servo.r_s_r.startAt, easing:'inOutSine'}], // 右肩ロール（上下）
            [null,  {value: this.servo.r_s_p.startAt, easing:'inOutSine'}], // 右肩ピッチ（開閉）
            [null,  {value: this.servo.r_h_g.startAt, easing:'inOutSine'}], // 右手
            [null,  {value: this.servo.l_s_r.startAt, easing:'inOutSine'}], // 左肩ロール（上下）
            [null,  {value: this.servo.l_s_p.startAt, easing:'inOutSine'}], // 左肩ピッチ（開閉）
            [null,  {value: this.servo.l_h_g.startAt, easing:'inOutSine'}], // 左手
            [null,  {value: this.servo.r_f_y.startAt, easing:'inOutSine'}], // 右足ヨー（開閉）
            [null,  {value: this.servo.r_f_p.startAt, easing:'inOutSine'}], // 右足ピッチ（内外）
            [null,  {value: this.servo.l_f_y.startAt, easing:'inOutSine'}], // 左足ヨー（開閉）
            [null,  {value: this.servo.l_f_p.startAt, easing:'inOutSine'}]  // 左足ピッチ（内外）
        ]
    },
    // 1. forward
    forward: {
        duration: 3000,                             // 再生時間[ms]
        loop: true,                                 // ループ再生有無
        cuePoints: [0.0, 0.25, 0.5, 0.75, 1.0],     // キューポイント
        keyFrames: [                                // キーフレーム（行:サーボ12個 × 列:キューポイントごとの角度とイージングの種類）
            [null,  {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}],
            [null,  {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}],
            [null,  {value:  0, easing:'inOutSine'}, {value:  0, easing:'inOutSine'}, {value:  0, easing:'inOutSine'}, {value:  0, easing:'inOutSine'}],
            [null,  {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}],
            [null,  {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}],
            [null,  {value:180, easing:'inOutSine'}, {value:180, easing:'inOutSine'}, {value:180, easing:'inOutSine'}, {value:180, easing:'inOutSine'}],
            [null,  {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}],
            [null,  {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}],
            [null,  {value: 80, easing:'inOutSine'}, {value: 70, easing:'inOutSine'}, {value:100, easing:'inOutSine'}, {value:110, easing:'inOutSine'}],
            [null,  {value:110, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 60, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}],
            [null,  {value: 80, easing:'inOutSine'}, {value: 70, easing:'inOutSine'}, {value:100, easing:'inOutSine'}, {value:110, easing:'inOutSine'}],
            [null,  {value:120, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 70, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}]
        ]
    },
    // 2. back
    back: {
        duration: 3000,                             // 再生時間[ms]
        loop: true,                                 // ループ再生有無
        cuePoints: [0.0, 0.25, 0.5, 0.75, 1.0],     // キューポイント
        keyFrames: [                                // キーフレーム（行:サーボ12個 × 列:キューポイントごとの角度とイージングの種類）
            [null,  {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}],
            [null,  {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}],
            [null,  {value:  0, easing:'inOutSine'}, {value:  0, easing:'inOutSine'}, {value:  0, easing:'inOutSine'}, {value:  0, easing:'inOutSine'}],
            [null,  {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}],
            [null,  {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}],
            [null,  {value:180, easing:'inOutSine'}, {value:180, easing:'inOutSine'}, {value:180, easing:'inOutSine'}, {value:180, easing:'inOutSine'}],
            [null,  {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}],
            [null,  {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}],
            [null,  {value:100, easing:'inOutSine'}, {value:120, easing:'inOutSine'}, {value: 80, easing:'inOutSine'}, {value: 70, easing:'inOutSine'}],
            [null,  {value:110, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 30, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}],
            [null,  {value:100, easing:'inOutSine'}, {value:120, easing:'inOutSine'}, {value: 80, easing:'inOutSine'}, {value: 70, easing:'inOutSine'}],
            [null,  {value:120, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 70, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}]
        ]
    },
    // 3. right
    right: {
        duration: 3000,                             // 再生時間[ms]
        loop: true,                                 // ループ再生有無
        cuePoints: [0.0, 0.25, 0.5, 0.75, 1.0],     // キューポイント
        keyFrames: [                                // キーフレーム（行:サーボ12個 × 列:キューポイントごとの角度とイージングの種類）
            [null,  {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}],
            [null,  {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}],
            [null,  {value:  0, easing:'inOutSine'}, {value:  0, easing:'inOutSine'}, {value:  0, easing:'inOutSine'}, {value:  0, easing:'inOutSine'}],
            [null,  {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}],
            [null,  {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}],
            [null,  {value:180, easing:'inOutSine'}, {value:180, easing:'inOutSine'}, {value:180, easing:'inOutSine'}, {value:180, easing:'inOutSine'}],
            [null,  {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}],
            [null,  {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}],
            [null,  {value: 95, easing:'inOutSine'}, {value:100, easing:'inOutSine'}, {value: 85, easing:'inOutSine'}, {value: 80, easing:'inOutSine'}],
            [null,  {value: 60, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value:110, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}],
            [null,  {value: 85, easing:'inOutSine'}, {value: 80, easing:'inOutSine'}, {value: 95, easing:'inOutSine'}, {value:100, easing:'inOutSine'}],
            [null,  {value: 70, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value:120, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}]
        ]
    },
    // 4. left
    left: {
        duration: 3000,                             // 再生時間[ms]
        loop: true,                                 // ループ再生有無
        cuePoints: [0.0, 0.25, 0.5, 0.75, 1.0],     // キューポイント
        keyFrames: [                                // キーフレーム（行:サーボ12個 × 列:キューポイントごとの角度とイージングの種類）
            [null,  {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}],
            [null,  {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}],
            [null,  {value:  0, easing:'inOutSine'}, {value:  0, easing:'inOutSine'}, {value:  0, easing:'inOutSine'}, {value:  0, easing:'inOutSine'}],
            [null,  {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}],
            [null,  {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}],
            [null,  {value:180, easing:'inOutSine'}, {value:180, easing:'inOutSine'}, {value:180, easing:'inOutSine'}, {value:180, easing:'inOutSine'}],
            [null,  {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}],
            [null,  {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}],
            [null,  {value: 95, easing:'inOutSine'}, {value:100, easing:'inOutSine'}, {value: 85, easing:'inOutSine'}, {value: 80, easing:'inOutSine'}],
            [null,  {value:110, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 60, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}],
            [null,  {value: 85, easing:'inOutSine'}, {value: 80, easing:'inOutSine'}, {value: 95, easing:'inOutSine'}, {value:100, easing:'inOutSine'}],
            [null,  {value:120, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 70, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}]
        ]
    },
    // 5. green
    green: {
        duration: 1500,                             // 再生時間[ms]
        loop: true,                                 // ループ再生有無
        cuePoints: [0.0, 0.4, 0.6, 0.8, 1.0],       // キューポイント
        keyFrames: [                                // キーフレーム（行:サーボ12個 × 列:キューポイントごとの角度とイージングの種類）
            [null,  {value: 90, easing:'inOutSine'}, {value:100, easing:'inOutSine'}, {value: 80, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}],
            [null,  {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}],
            [null,  {value:120, easing:'inOutSine'}, {value:120, easing:'inOutSine'}, {value:120, easing:'inOutSine'}, {value:120, easing:'inOutSine'}],
            [null,  {value: 90, easing:'inOutSine'}, {value:130, easing:'inOutSine'}, {value:130, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}],
            [null,  {value: 90, easing:'inOutSine'}, {value:110, easing:'inOutSine'}, {value:110, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}],
            [null,  {value: 60, easing:'inOutSine'}, {value: 60, easing:'inOutSine'}, {value: 60, easing:'inOutSine'}, {value: 60, easing:'inOutSine'}],
            [null,  {value: 90, easing:'inOutSine'}, {value: 50, easing:'inOutSine'}, {value: 50, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}],
            [null,  {value: 90, easing:'inOutSine'}, {value: 70, easing:'inOutSine'}, {value: 70, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}],
            [null,  {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}],
            [null,  {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}],
            [null,  {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}],
            [null,  {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}]
        ]
    },
    // 6. yellow
    yellow: {
        duration: 1500,                             // 再生時間[ms]
        loop: true,                                 // ループ再生有無
        cuePoints: [0.0, 0.5, 1.0],                 // キューポイント
        keyFrames: [                                // キーフレーム（行:サーボ12個 × 列:キューポイントごとの角度とイージングの種類）
            [null,  {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}],
            [null,  {value:120, easing:'inOutSine'}, {value:120, easing:'inOutSine'}],
            [null,  {value:120, easing:'inOutSine'}, {value:120, easing:'inOutSine'}],
            [null,  {value:130, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}],
            [null,  {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}],
            [null,  {value:180, easing:'inOutSine'}, {value:180, easing:'inOutSine'}],
            [null,  {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}],
            [null,  {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}],
            [null,  {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}],
            [null,  {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}],
            [null,  {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}],
            [null,  {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}]
        ]
    },
    // 7. blue
    blue: {
        duration: 5000,                             // 再生時間[ms]
        loop: true,                                 // ループ再生有無
        cuePoints: [0.0, 0.2, 0.3, 0.4, 0.5, 0.7, 0.8, 1.0],     // キューポイント
        keyFrames: [                                // キーフレーム（行:サーボ12個 × 列:キューポイントごとの角度とイージングの種類）
            [null,  {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}],
            [null,  {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}],
            [null,  {value:120, easing:'inOutSine'}, {value:120, easing:'inOutSine'}, {value:120, easing:'inOutSine'}, {value:120, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value:120, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}],
            [null,  {value:130, easing:'inOutSine'}, {value:130, easing:'inOutSine'}, {value:130, easing:'inOutSine'}, {value:130, easing:'inOutSine'}, {value:130, easing:'inOutSine'}, {value:130, easing:'inOutSine'}, {value:130, easing:'inOutSine'}],
            [null,  {value: 70, easing:'inOutSine'}, {value:110, easing:'inOutSine'}, {value: 70, easing:'inOutSine'}, {value:110, easing:'inOutSine'}, {value:110, easing:'inOutSine'}, {value:110, easing:'inOutSine'}, {value:110, easing:'inOutSine'}],
            [null,  {value: 60, easing:'inOutSine'}, {value: 60, easing:'inOutSine'}, {value: 60, easing:'inOutSine'}, {value: 60, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 60, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}],
            [null,  {value: 50, easing:'inOutSine'}, {value: 50, easing:'inOutSine'}, {value: 50, easing:'inOutSine'}, {value: 50, easing:'inOutSine'}, {value: 50, easing:'inOutSine'}, {value: 50, easing:'inOutSine'}, {value: 50, easing:'inOutSine'}],
            [null,  {value:110, easing:'inOutSine'}, {value: 70, easing:'inOutSine'}, {value:110, easing:'inOutSine'}, {value: 70, easing:'inOutSine'}, {value: 70, easing:'inOutSine'}, {value: 70, easing:'inOutSine'}, {value: 70, easing:'inOutSine'}],
            [null,  {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}],
            [null,  {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}],
            [null,  {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}],
            [null,  {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}]
        ]
    },
    // 8. red
    red: {
        duration: 1500,                             // 再生時間[ms]
        loop: true,                                 // ループ再生有無
        cuePoints: [0.0, 0.5, 1.0],                 // キューポイント
        keyFrames: [                                // キーフレーム（行:サーボ12個 × 列:キューポイントごとの角度とイージングの種類）
            [null,  {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}],
            [null,  {value: 60, easing:'inOutSine'}, {value: 60, easing:'inOutSine'}],
            [null,  {value:  0, easing:'inOutSine'}, {value:  0, easing:'inOutSine'}],
            [null,  {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}],
            [null,  {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}],
            [null,  {value: 60, easing:'inOutSine'}, {value: 60, easing:'inOutSine'}],
            [null,  {value: 50, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}],
            [null,  {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}],
            [null,  {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}],
            [null,  {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}],
            [null,  {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}],
            [null,  {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}]
        ]
    },
    // 9. push
    push: {
        duration: 5000,                             // 再生時間[ms]
        loop: true,                                 // ループ再生有無
        cuePoints: [0.0, 0.2, 0.4, 0.6, 1.0],       // キューポイント
        keyFrames: [                                // キーフレーム（行:サーボ12個 × 列:キューポイントごとの角度とイージングの種類）
            [null,  {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 40, easing:'inOutSine'}, null],
            [null,  {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value:140, easing:'inOutSine'}, null],
            [null,  {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, null],
            [null,  {value:130, easing:'inOutSine'}, {value:130, easing:'inOutSine'}, {value: 70, easing:'inOutSine'}, null],
            [null,  {value:110, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, null],
            [null,  {value:180, easing:'inOutSine'}, {value:180, easing:'inOutSine'}, {value:180, easing:'inOutSine'}, null],
            [null,  {value: 50, easing:'inOutSine'}, {value: 50, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, null],
            [null,  {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, null],
            [null,  {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, null],
            [null,  {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, null],
            [null,  {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, null],
            [null,  {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, {value: 90, easing:'inOutSine'}, null]
        ]
    }
};

// LEDの諸設定
// ledという名前で外部から読めるようにexports
exports.led = { R: {pin: 6}, G: {pin: 5}, B: {pin: 3} };

// 表情の諸設定
// faceという名前で外部から読めるようにexports
exports.face = {
    white: {
        duration: 3000,                         // 再生時間
        loop: true,                             // ループ再生有無
        cuePoints: [0.0, 0.4, 0.5, 0.9, 1.0],   // キューポイント
        keyFrames: [                            // キーフレーム（行:LED3個 × 列:キューポイントごとの明るさとイージングの種類）
            [{value:0, easing:'linear'}, {value:255, easing:'linear'}, {value:0, easing:'linear'}, {value:255, easing:'linear'}, {value:0, easing:'linear'}],
            [{value:0, easing:'linear'}, {value:255, easing:'linear'}, {value:0, easing:'linear'}, {value:255, easing:'linear'}, {value:0, easing:'linear'}],
            [{value:0, easing:'linear'}, {value:255, easing:'linear'}, {value:0, easing:'linear'}, {value:255, easing:'linear'}, {value:0, easing:'linear'}]
        ]
    },
    red: {
        duration: 1500,
        loop: true,
        cuePoints: [0.0, 1.0],
        keyFrames: [
            [{value:0, easing:'linear'}, {value:255, easing:'linear'}],
            [{value:0, easing:'linear'}, {value:  0, easing:'linear'}],
            [{value:0, easing:'linear'}, {value:  0, easing:'linear'}]
        ]
    },
    green: {
        duration: 2000,
        loop: true,
        cuePoints: [0.0, 0.25, 0.5, 0.75, 1.0],
        keyFrames: [
            [{value:0, easing:'linear'}, {value:  0, easing:'linear'}, {value:0, easing:'linear'}, {value:  0, easing:'linear'}, {value:0, easing:'linear'}],
            [{value:0, easing:'linear'}, {value:255, easing:'linear'}, {value:0, easing:'linear'}, {value:255, easing:'linear'}, {value:0, easing:'linear'}],
            [{value:0, easing:'linear'}, {value:  0, easing:'linear'}, {value:0, easing:'linear'}, {value:  0, easing:'linear'}, {value:0, easing:'linear'}]
        ]
    },
    blue: {
        duration: 3000,
        loop: true,
        cuePoints: [0.0, 0.4, 0.5, 0.9, 1.0],
        keyFrames: [
            [{value:0, easing:'linear'}, {value:  0, easing:'linear'}, {value:0, easing:'linear'}, {value:  0, easing:'linear'}, {value:0, easing:'linear'}],
            [{value:0, easing:'linear'}, {value:  0, easing:'linear'}, {value:0, easing:'linear'}, {value:  0, easing:'linear'}, {value:0, easing:'linear'}],
            [{value:0, easing:'linear'}, {value:255, easing:'linear'}, {value:0, easing:'linear'}, {value:255, easing:'linear'}, {value:0, easing:'linear'}]
        ]
    },
    yellow: {
        duration: 1500,
        loop: true,
        cuePoints: [0.0, 1.0],
        keyFrames: [
            [{value:0, easing:'linear'}, {value:255, easing:'linear'}],
            [{value:0, easing:'linear'}, {value:255, easing:'linear'}],
            [{value:0, easing:'linear'}, {value:  0, easing:'linear'}]
        ]
    },
};
```

#### 制御プログラム本体
基本部分は [johnny-five のサーボアニメーション](http://johnny-five.io/examples/servo-animation-leg/) を参考に書きました。
加えて、socket.io によるブラウザからの制御と、現在状態をブラウザに送る処理を書きました。

```js:app.js
// Rapiro を 内蔵 Raspberry Pi から Node.js + johnny-five で制御
// Rapiro標準の10モーション実装
// socket.ioによりブラウザから制御
// 2017.01.17 by mkoku

'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 rapiro = {                                // Rapiroの設定や動作等を格納するオブジェクト
    ready: false                                // Rapiroの準備状態（初期値false）
};


// 制御ボードの準備ができたら
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')");

    // 終了時の処理
    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で送る
            socket.emit('response', {
                motion: rapiro.currentMotionName,   // 動作名
                face:   rapiro.currentFaceName      // 表情名
            });
        }
    });
});
```

#### 操作インタフェース
10個の基本動作用のボタンを配置して、ボタンに応じて動作（サーボのアニメーション）の名前と、表情（LEDのアニメーション）の名前を、socket で Rapiro に送っています。
また、Rapiro 側から返される現在の動作名と表情名が表示されます。

```html:index.html
<!DOCTYPE html>
<!--
// Rapiro制御インタフェース
// Rapiro標準の10モーション実装
// 2017.01.17 by mkoku
-->
<html>
<head>
    <meta charset='utf-8'>
    <meta name='viewport' content='width=device-width,initial-scale=1'>
    <title>Rapiro Walking Test</title>
    <style>
        td.center {text-align: center}
    </style>
</head>

<body>
    <table>
        <!-- Rapiroからのレスポンスを表示する場所 -->
        <tr>
            <td class='center' colspan='3' id='msg'>motion/face</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;  // 動作と表情を表示
        });

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

### 動作確認
* 上記3つのコードを Rapiro 内部の Raspberry Pi に FTP
 * e.g. /home/pi/rapiro/ 内に
* SSH で Rapiro 内部の Raspberry Pi にアクセスし、node で app.js を実行
 * 今回はこの SSH もスマホから試してみました（後述の動画をご参考）
 * 準備ができると Rapiro のサーボに通電され直立状態となり、目は白色にフェードする

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

#### ブラウザから

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

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

* ブラウザ画面の10個のボタンで Rapiro を動かして遊ぶ

#### SSH のコマンドラインから

* rapiro.execMotion('forward') や rapiro.execFace('blue') などのコマンドをうつ

 * rapiro.execMotion('動作名')
 * rapiro.execFace('表情名')

```
pi@raspberrypi:~/rapiro $ node app.js
1484623993712 Connected /dev/ttyAMA0
1484623998794 Repl Initialized
>> Type rapiro.execMotion('name') or rapiro.execFace('name')
>> rapiro.execMotion('forward')
undefined
```


# まとめ
以下の動画のようにできました。それぞれの動作は完全なコピーではありませんが、概ね、標準の10動作を実装することができました。

[![Rapiro を node.js (johnny-five + socket.io) で制御](http://img.youtube.com/vi/RkjwCuKvvt0/0.jpg)](https://youtu.be/RkjwCuKvvt0)

無線制御といっても Bluetooth ではありませんので、やりようによっては地球の裏側からでも Rapiro を制御できるはずです（現状はLAN内限定です）。

なお、johnny-five の サーボアニメーションの機能を使えばわりと簡単かと思って作り始めましたが、結局、さほど効率的なコードにはならなかったと思います。

いずれにせよ、これで最終目標の 1 は達成できました。次回以降、可能な限り、[書籍「二足歩行ロボット 工作＆プログラミング（リックテレコム）」](http://www.ric.co.jp/book/contents/book_995.html)の改造内容（例えば以下）も移植していきたい所存です。

* 距離センサの搭載
* 静電容量タッチセンサの搭載など

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