二足歩行ロボットキット「ダンボット」をArduino（Freaduino）とJavaScript（Node.js + johnny-five + socket.io）でコントロール

#やりたいこと
[浅草技研の二足歩行ロボットキット「ダンボット」](http://www.robotsfx.com/robot/Danbot.html)を、Arduino（互換機のFreaduino）でコントロールしつつ、制御プログラムを Node.js + johnny-five + socket.io で実装することで、ネットワーク越しに操作する。これにより、ダンボットを IoT 的にする。

#方法

##利用機材
* [浅草技研「ダンボット」](http://www.robotsfx.com/robot/Danbot.html)
* ダンボットの組み立てに必要な[工具等](http://www.robotsfx.com/ds.html)
* [Freaduino](https://www.vstone.co.jp/robotshop/index.php?main_page=product_info&cPath=270_360&products_id=4302)（Arduino互換機、サーボがそのまま挿せる）
* 9V電池[006P型]と[電池コネクタ](https://www.switch-science.com/catalog/1912/)（Freaduinoに電源供給できればなんでもよい）
* USBケーブル（FreaduinoをPCに接続できればなんでもよい）
* PC（Windows10、Node.jsが動けばなんでもよい）

##手順

###前準備（先人に学ぶ）
* ダンボットを通常どおり組み立てて、ひとしきり遊ぶ（[ココ](http://www.robotsfx.com/ds.html)と[ココ](http://www.robotsfx.com/robot/ds2.html)）
* 書籍[「二足歩行ロボット 工作＆プログラミング入門（リックテレコム）」](http://www.ric.co.jp/book/contents/book_995.html)の第Ⅰ部で、Arduino単独で制御して遊んでみる
  * 私の場合、左足上側のサーボをD5、左足下側のサーボをD6、右足上側のサーボをD9、右足下側のサーボをD10につなぎました

###ここからオリジナル
* [Arduino IDE](https://www.arduino.cc/en/Main/Software) で Freaduino に StandardFirmata を書き込む
* PCに [Node.js](https://nodejs.org/ja/) をインストールする（私はv4.6.0を使用）
* Node.js コマンドプロンプトから npm で [johnny-five](http://johnny-five.io/), [express](http://expressjs.com/), [socket.io](http://socket.io/) モジュールをインストールする
  * 例として C:\Users\username で作業

```
C:\Users\username> npm install johnny-five express socket.io
```
* サーバ側プログラム「app.js」を以下のように書く
  * 例として C:\Users\username\danbot 内に作成

```Javascript:app.js
// ダンボットをArduino(Freaduino)とNode.jsで動かす
//    D5:  L Leg
//    D6:  L Foot
//    D9:  R Leg
//    D10: R Foot
'use strict';           // 厳格モードにする

// httpサーバ
const express = require('express');             // expressモジュールを使う
const app = express();                          // expressアプリを作る
const http = require('http').Server(app);       // httpサーバを起動しアプリをサーブ
const io = require('socket.io')(http);          // socket.ioモジュールを使う
app.use(express.static(__dirname));             // 静的ファイルへのアクセス許可
app.get('/', function(req, res) {               // リクエストがあったら
    res.sendfile(__dirname + '/index.html');    // index.htmlを返す
});
http.listen(80);                                // 80番ポートで待つ

// johnny-five
const five = require('johnny-five');            // johnny-fiveの読み込み
const arduino = new five.Board({port: 'COM3'}); // Boardオブジェクトのインスタンス
let servos;                                     // 複数サーボオブジェクト
let arduinoReady = false;                       // Arduinoの準備状態

// 各種変数
const SERVONUM = 4;     // サーボの数
const FRAMENUM = 4;     // 動作フレーム数
let frameNo = 0;        // 実行中のフレーム番号
let timerID;            // setIntervalタイマーのID

// 各サーボの補正値（トリム）のテーブル
const trim = [          // これらの値は現物に合わせて調整する
     0,                 // L Leg (Yaw)   +で内股方向
    -3,                 // L Foot(Roll)  +で外側さがる
     6,                 // R Leg (Yaw)   +でガニ股方向
     4                  // R Foot(Roll)  +で外側あがる
];

// モーションのテーブル[動作][フレーム][サーボ]
const motion = [
    [   // 0 停止
        [ 90, 90, 90, 90],
        [ 90, 90, 90, 90],
        [ 90, 90, 90, 90],
        [ 90, 90, 90, 90]
    ],
    [   // 1 前進
        [ 90,120, 90,120],  // 左足を上げる
        [ 60, 90, 60, 90],  // 左足を前に出す
        [ 90, 60, 90, 60],  // 右足を上げる
        [120, 90,120, 90]   // 右足を前に出す
    ],
    [   // 2 後退
        [ 90,120, 90,120],  // 左足を上げる
        [120, 90,120, 90],  // 左足を後ろに引く（右足を前に出す）
        [ 90, 60, 90, 60],  // 右足を上げる
        [ 60, 90, 60, 90]   // 右足を後ろに引く（左足を前に出す）
    ],
    [   // 3 右旋回
        [ 90, 60, 90, 60],  // 右足を上げる
        [ 90, 90,120, 90],  // 右足を外に開く
        [ 90,120, 90,120],  // 左足を上げる
        [120, 90, 90, 90]   // 左足を内に閉じる
    ],
    [   // 4 左旋回
        [ 90,120, 90,120],  // 左足を上げる
        [ 60, 90, 90, 90],  // 左足を外に開く 
        [ 90, 60, 90, 60],  // 右足を上げる
        [ 90, 90, 60, 90]   // 右足を内に閉じる
    ]
];

// サーボを動かす関数(サーボ番号, 角度, 到達時間[ms])
function servoMove(servoNo, angle, ms) {
    angle += trim[servoNo];         // トリム調整
    servos[servoNo].to(angle, ms);  // サーボを動かす
}

// 歩行関数
function walk(motionNo) {
    frameNo = 0;                                // 最初の動作フレームから
    timerID = setInterval(function() {          // タイマースタート
        if(arduinoReady != true) { return; }    // Arduinoの準備がまだなら抜ける
        for(let i = 0; i < SERVONUM; i++) {     // サーボを動かす
            servoMove(i, motion[motionNo][frameNo][i], 350);
        }
        frameNo += 1;                           // 次の動作フレームへ
        if(frameNo >= FRAMENUM) { frameNo = 0; }
    }, 400);                                    // 400ms後に次のフレーム再生
}


// 初期設定
arduino.on('ready', function() {                // Arduinoの準備ができたら
    servos = new five.Servos([5, 6, 9, 10]);    // サーボに接続
    arduinoReady = true;
    walk(0);                                    // サーボを停止状態に動かす
});


// WebSocketによる制御
io.on('connection', function(socket) {          // socket接続がある時
    socket.on('walk', function(data) {          // socketでwalkイベントが来たら
        if(timerID != 0) { clearInterval(timerID); }    // 前の動作を停止
        console.log(data.motion);               // 送られてきた動作をコンソール出力
        if (data.motion == 'stop') {            // stopなら
            walk(0);                            // 停止動作
        } else if (data.motion == 'forward') {  // forwardなら
            walk(1);                            // 前進
        } else if (data.motion == 'back') {     // backなら
            walk(2);                            // 後退
        } else if (data.motion == 'right') {    // rightなら
            walk(3);                            // 右旋回
        } else if (data.motion == 'left') {     // leftなら
            walk(4);                            // 左旋回
        }
    });
});
```

* クライアント側プログラム「index.html」を以下のように書く
  * 例として C:\Users\username\danbot 内に作成

```html:index.html
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width,initial-scale=1">
        <title>ダンボット</title>
    </head>
    <body>
        <input type="button" value="　" id="btnFL">
        <input type="button" value="前" id="btnForward">
        <input type="button" value="　" id="btnFR"><BR>

        <input type="button" value="左" id="btnLeft">
        <input type="button" value="止" id="btnStop">
        <input type="button" value="右" id="btnRight"><BR>

        <input type="button" value="　" id="btnBL">
        <input type="button" value="後" id="btnBack">
        <input type="button" value="　" id="btnBR"><BR>

        <script src="/socket.io/socket.io.js"></script>
        <script>
            const socket = io();

            const btnStop = document.getElementById('btnStop');
            btnStop.addEventListener('click', function() {
                socket.emit('walk', {motion: 'stop'});
            });

            const btnForward = document.getElementById('btnForward');
            btnForward.addEventListener('click', function() {
                socket.emit('walk', {motion: 'forward'});
            });

            const btnBack = document.getElementById('btnBack');
            btnBack.addEventListener('click', function() {
                 socket.emit('walk', {motion: 'back'});
            });

            const btnRight = document.getElementById('btnRight');
            btnRight.addEventListener('click', function() {
                socket.emit('walk', {motion: 'right'});
            });

            const btnLeft = document.getElementById('btnLeft');
            btnLeft.addEventListener('click', function() {
                socket.emit('walk', {motion: 'left'});
            });

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

* Node.js コマンドプロンプトから、サーバ側プログラムを実行

```
C:\Users\username\danbot> node app.js
```

* PCのブラウザで「localhost」を開き、操作する
* PCが自宅などのLANにつながっていて、スマホやタブレットが同じLANに無線でつながっていたら、スマホやタブレットのブラウザにPCのIPアドレスを入れて、スマホやタブレットで操作する。


#考察と所感

浅草技研のダンボットは、それだけでもじゅうぶん楽しいです！　そして、書籍[「二足歩行ロボット 工作＆プログラミング入門（リックテレコム）」](http://www.ric.co.jp/book/contents/book_995.html)に大いに刺激・触発されました。
この書籍では、Arduino に サーボシールドを載せて制御していますが、Freaduino なら直接サーボを接続できて、標準の Servo.h で制御できます。また、書籍では、サーボの現在角度からターゲット角度までの間を計算で補間するアルゴリズムを使っていますが、johnny-five を使うと、Servo.to(angle, ms); のように、ターゲット角度までの時間を指定して、補間を簡単に実現できます。
今回の改造で、ダンボットが IoT 的なものになりました。ただし、同じLAN内からの制御に限定されます。また、PCにケーブルでつながれたままです。PCに代えて Raspberry Pi を使えば、より IoT っぽくできそうです。