2016年4月5日火曜日

[Intel Edison]Node.JSでモーター制御 ⑥

〜 無線接続とブラウザからのコントロール 〜

今回製作にあたっての取り決めを再確認しておきます。

  • 使用するのはIntel Edison
  • 購入した状態のまま、もしくはFirmwareセットアップ直後のEdisonを使う
  • Edisonからのインターネット接続行わない(Edisonに何もインストールしない)
  • PCへのソフトウェアのインストールを最小限に留める
  • ブラウザ経由でコントロールする

敢えて、この様な作り方をする必要は無いのですが、Intel Edisonが最小限の環境であっても、Webサーバーとして機能させる事も、I/Oコントロールをする事もプロトタイピングレベルであれば十分に可能な環境である事の指標となればと考え、この取り決めで進めています。

Expressなどのフレームワークを入れれば、WebでのUI操作を格段に向上させる事も可能ですし、Socket.IOを入れればリアルタイム相互通信も可能になります。ロボティクスフレームワークのCylon.JSかJohnny-Fiveを入れれば、Arduino IDEで操作するレベルでモーター、サーボ、その他センサー類も簡単に制御出来ます。
逆に、Arduino IDEでWebサーバーを立ち上げる事だって可能です。

これらは、Edisonに限った事でなく、Raspberry PiでもBeagleBone Blackでも、その他マイコンボードでも同様に可能性が広がっています。

しかし、マイコンボードなんぞは、所詮道具でしかありません。
そんな所詮道具なだけのモノでも、発想と工夫でラジコンの試作ぐらいなら出来ちゃうぐらいの便利な道具でもあるわけです。

てな事をご理解の上で、以降を読み進めていただけると幸いです。

WiFiをAPモードで起動させる

前提条件


APモードは、EdisonをWiFiのアクセスポイントとして機能させる事を指します。

APモードで動作させると、Edisonに割り当てたられた名前がSSIDとなり、rootのパスワードがそのSSIDのパスワードにもなります。名前の設定をしていない場合は、"EDISON-"に続きMACアドレスの下二桁を付けたものがSSIDとなります。

パスワードの設定

Edisonは初期状態ではパスワードが設定されていないため、最初にパスワードを設定する必要があります。

"configure_edison --password"

と入力して実行するとパスワードの設定が出来ます。
パスワードは8文字以上でないとエラーとなるので注意して下さい。

※以降、Macのターミナルウィンドウでの解説ですが、Tera Term、PuTTYでも同様の操作です。

Terminal -- bash -- 80x24
root@edison:~# configure_edison --password

Configure Edison: Device Password

Enter a new password (leave empty to abort)
This will be used to connect to the access point and login to the device.
Password: ********
Please enter the password again: ********

パスワードの設定が終わったら、

"configure_edison --enableOneTimeSetup"

と入力してAPモードでの起動を行います。
LEDが点滅し、スキャニングのカウントダウンが開始されます。


Terminal -- bash -- 80x24
root@edison:~# configure_edison --enableOneTimeSetup
Scanning and saving WiFi networks...
Scanning: 1 seconds left  

Restarting WiFi access point. Please wait...

From your PC or laptop, connect to the 'EDISON-XX-XX' network 
and visit 'edison.local' in the browser


カウントダウンが終了し、上記の様な表示が出ればAPモードで起動した状態となります。
ここで表示されている"EDISON-XX-XX"がSSIDとなるので、どこかにメモるか記憶しておいて下さい。

アクセスポイントへの接続

APモード化したEdisonにPCのWiFiを接続します。
このブログを見ているPCで接続する場合は、ネットワークが切れてしまうので、このページを表示したままにしておいて下さい。

Windowsでは、デフォルトでは画面の右下に以下のアイコンを探して下さい。
Macの場合は、メニューの位置を変えていなければ右上に見つかるかと思います。

該当のアイコンが見つかったらクリックして、表示されているアクセスポイントの中から先ほどSSIDと同じものを選び、設定したパスワードを入力します。

接続が完了したら、SSHでログインしてみます。

APモードでのEdisonのIPは、"192.168.42.1"に固定されています。

Windowsの場合

新規にTera Termを起動し、TCP/IPのホスト(T):の項目にEdisonのアドレス"192.168.42.1"を入力し、"OK"ボタンを押します。


接続が通ると、認証を求められるので、
ユーザー名(N): root
パスフレース(P): rootに設定したパスワード
を入力して、"OK"を押します。
電波状態により、暫くかかる事もありますが概ねすぐにEdisonのシェル画面が表示されるかと思います。

Macの場合

新規にターミナルを起動し、sshで接続を行います。

Terminal -- bash -- 80x24
Mac-no-Terminal:~ forEdison$ ssh root@192.168.42.1
The authenticity of host '192.168.42.1 (192.168.42.1)' can't be established.
RSA key fingerprint is xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '192.168.42.1' (RSA) to the list of known hosts.
root@192.168.42.1's password: 
root@Edison:~#

実行するとパスワード認証を求められるので、rootに設定したパスワードを入力すればログインは完了です。

Windows & Mac 共通
あまりオススメ出来る方法ではありませんが、シリアル通信を行っていたウィンドウを閉じ、ケーブル(下図の最下部の箇所)を抜きます。


これで実質、Edisonへの電源供給だけが行われている状態となりました。

EdisonでWebサーバーを起動し、コントローラーを起動させる

制御するスクリプトは、[Intel Edison]Node.JSでモーター制御②をベースに編集します。
以下が、その制御スクリプトです。


Terminal -- bash -- 80x24
root@edison:~# vi ctrl_edison.js             
var mraa = require("mraa");

var L_in1 = new mraa.Gpio(2);
var L_in2 = new mraa.Gpio(7);
var R_in1 = new mraa.Gpio(4);
var R_in2 = new mraa.Gpio(8);
L_in1.dir(mraa.DIR_OUT);
L_in2.dir(mraa.DIR_OUT);
R_in1.dir(mraa.DIR_OUT);
R_in2.dir(mraa.DIR_OUT);

var http = require('http');
var url = require('url');
var ctrl = "/STOP";

function sendResponse( ctrl, remoteIP, response ) {
    var divs = ["FORWARD","TURN LEFT","STOP","TURN RIGHT","BACK"];
    var styles = ["top:5%;left:35%","top:35%;left:5%","top:35%;left:35%","top:35%;left:65%","top:65%;left:35%"];

    response.write('<!DOCTYPE html><html lang="en"><head>');
    response.write('<style>');
    response.write('#main {width: 100%;height: 100%;position: relativeo;text-align: center;line-height: 26vh;}');
    response.write('.btn,.btn_sel {position: absolute;margin: 2%;border-radius: 5%;background: #EEE;color:#111;width: 26%;height: 26%;font-size : 1em;}');
    response.write('.btn_sel {margin: 1%;background:#777;color: red;line-height: 28vh;width: 28%;height: 28%;}');
    response.write('body{background-color:black;overflow: hidden;height: 100%;-webkit-touch-callout:none;-webkit-user-select:none;}');
    response.write('.btn:hover {margin: 0 auto;background:#fff;color:#0f0;line-height: 30vh;width: 30%;height: 30%;}');
    response.write('</style>');
    response.write('<meta charset="utf-8"><meta http-equiv="refresh" content="30" />');
    response.write('<title>Control Panel</title></head>');
    response.write('<body><div id="main">');
    for ( var i = 0; i < 5; i++ ) {
        var classState = ( "/"+divs[i] == ctrl )?"btn_sel":"btn";
        response.write('<div class="'+classState+'" style="'+styles[i]+'">'+divs[i]+'</div>');
    }
    response.write('</div>');
    response.write('<script type="text/javascript">');
    response.write('if ("ontouchstart" in window) { document.addEventListener("touchstart",dragBtn); document.addEventListener("touchend",releaseBtn);');
    response.write('} else { document.addEventListener("mousedown",dragBtn); document.addEventListener("mouseup",releaseBtn); }');
    response.write('function setClass( ctrl ) { var nodes = document.getElementById("main").childNodes; var num = nodes.length;');
    response.write('for ( var i = 0; i < num; i++ ) { if ( nodes[i].innerHTML == ctrl ) { nodes[i].className = "btn_sel";');
    response.write('} else if ( nodes[i].tagName == "DIV" ) { nodes[i].className = "btn"; }; };');
    response.write('location.href = "/"+ctrl; }');
    response.write('function dragBtn(e){ var element = document.elementFromPoint(e.pageX,e.pageY);');
    response.write('if ( element.className == "btn" ) { setClass( element.innerHTML ); } }');
    response.write('function releaseBtn(e){ setClass( "STOP" ); }');
    response.write('</script>');
    response.write('</body></html>');
}

function processRequest(request, response) {

    var pathName = url.parse(request.url).pathname.replace(/%20/,' ');
    var remoteIP = request.headers['X-Forwarded-For'];
    if ( remoteIP == undefined ) { remoteIP = request.connection.remoteAddress };

    console.log(pathName+" : "+remoteIP+" : "+ctrl);

    if ( pathName == "/") { remoteIP = null; }
    if ( pathName != ctrl ) {
        ctrl = ( pathName == "/" || pathName.match('.ico'))?ctrl:pathName;
        if ( ctrl == "/STOP" ) {
            L_in1.write(0);
            L_in2.write(0);
            R_in1.write(0);
            R_in2.write(0);
        } else if ( ctrl == "/FORWARD" ) {
            L_in1.write(1);
            L_in2.write(0);
            R_in1.write(1);
            R_in2.write(0);
        } else if ( ctrl == "/BACK" ) {
            L_in1.write(0);
            L_in2.write(1);
            R_in1.write(0);
            R_in2.write(1);
        } else if ( ctrl == "/TURN LEFT" ) {
            L_in1.write(0);
            L_in2.write(1);
            R_in1.write(1);
            R_in2.write(0);
        } else if ( ctrl == "/TURN RIGHT" ) {
            L_in1.write(1);
            L_in2.write(0);
            R_in1.write(0);
            R_in2.write(1);
        }
        sendResponse (ctrl, remoteIP, response);
    }
}

http.createServer(processRequest).listen(1337);

console.log("Server running at port 1337");


スクリプトは、クライアントの"sendResponse"、サーバーサイドの"processRequest"の二つで構成されています。今回は簡単なスクリプトなのと、Node.JSで出来る事を説明するために1ファイルでまとめています。

冒頭で書いたように制約を設けてあるため、Socket.IOを使ったような通信は出来ないので動的にWebページを作成して、pathnameを参照する手法で情報の交換を行っています。
恐らく、もっと最適な方法もあるかもしれませんが、思いつかなかったのでアドバイスいただけると助かります。

また、スマホやタブレットでのタッチパネル操作は知識がないので、”とりあえず実装”しました程度で動作は結構怪しいです。

以上で車輪が思い通りに動作したようであれば、Node.JSでモーター制御の一連の流れは終了となります。シャーシや補助輪などは、皆さんの思い描いた形状で作成、取付けしていただくのが最良であるかと思いますし、ソースもどんどん改良して下さい。
さらに、ここからの延長線にあるPMW操作でモーターの速度調整、フレームワークの導入、サーボ制御と幅を広げていただくきっかけなればと思っております

0 件のコメント:

コメントを投稿