以前、Terminal経由でCUI操作によるラジコンのコントローラーを作りました。
tohutokonsome.hatenablog.com
tohutokonsome.hatenablog.com
動いているところ
www.youtube.com
今回は、ブラウザごしに操作できるようにしていきたいと思います。
構成ですが、以下を予定しています。

ブラウザからのキー入力情報をもとに、ラジコンを操作するのですが、socket.ioを使って実現します。
socket.ioを調べると、node.jsの例がいっぱいでてくるので、それをベースでいろいろと試していました。
ただ、肝心のラズパイを操作する部分を今までPythonで実装してきたので、それはそのまま使いたい気持ちがありsocket.ioの部分はPythonで実装することにしました。
Pythonでのsocket.ioはPythonの簡易WebフレームワークであるFlaskで使えるみたいなので、こちらを使用することにします。
ブラウザ側のsocket.ioですが、これもPython用のものがあるのですが、せっかくnode.jsを使ってきたので、こっちはnode.jsベースで実装します。
なのでラズパイにはnode.jsとFlaskと二つWebサーバを立てることになります。
最終的には外部のネットワークから操作したいのですが、この決断がどんな影響がでるのか、なんともいえません。
PythonのWebフレームワークとしてDjangoをやってきたのですが、ここにきてFlaskを使用することになりちょっと悲しいです。
とはいえ、Djangoでもsocket.ioが使用できないわけでもなさそうなので、どこかでできればなぁと思ってます。
この記事は何回かに分けて書きます。
今回は、とりあえずnode.jsのみでキー入力部分の確認を行います。
node.jsをラズパイにインストールする
まずは、ラズパイにnode.jsをインストールします。
Rasbianにはデフォルトでnode.jsが入っているみたいですが、バージョンが古いので新しくします。
こちらの記事を参考に、インストールを行いました。
第三回 Raspberry Pi 3に最新のNode.jsをインストールする - Qiita
pi@raspberrypi:~ $ sudo apt-get install -y nodejs npm
バージョンを確認。おお、かなり古いっぽい。
※apt-getするまえにやればよかった。もともとラズパイに入ってるバージョンとのこと。
pi@raspberrypi:~ $ node -v
v0.10.29
pi@raspberrypi:~ $ npm -v
1.4.21
pi@raspberrypi:~ $ sudo npm cache clean
pi@raspberrypi:~ $ sudo npm install n -g
pi@raspberrypi:~ $ sudo n stable
バージョンを確認したところ、まだ古い。
pi@raspberrypi:~ $ node -v
v0.10.29
とりあえす再起動したら新しくなりました。
Macの検証環境よりバージョン上がってるけれども、これで進めることにした。
何気に、npmのバージョンもあがってた。
pi@raspberrypi:~ $ node -v
v8.0.0
pi@raspberrypi:~ $ npm -v
5.0.0
コントロールする画面を作成する
とりあえず、node.jsだけを使ってブラウザでキー入力した情報をリアルタイムでサーバーに連携する機能を作成します。

node.jsの基本的な部分は、以下の記事で試しました。
tohutokonsome.hatenablog.com
なにはともあれ、node.jsのプロジェクトの作成していきます。
(raspberry_3.5.1) masao-3:legoCarController konoe_mario$ npm init
name: (legoCarController) nodeapp
version: (1.0.0)
description: legoCarController
entry point: (index.js) server.js
test command:
git repository:
keywords:
author: tohu
license: (ISC)
About to write to /Users/konoe_mario/WebstormProjects/legoCarController/package.json:
{
"name": "nodeapp",
"version": "1.0.0",
"description": "legoCarController",
"main": "server.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "tohu",
"license": "ISC"
}
続いて、エントリーポイントとなるserver.jsを作成します。
「http://ラズパイのIP:6677/controller」にアクセスすると、controllr.htmlを返すだけのものになります。
server.js
var http = require("http");
var server = http.createServer();
//urlディスパッチャに使う
var url = require("url");
//htmlファイルの読み込み
var fs = require("fs");
//socket.ioで使う
var io = require("socket.io")(server);
server.on("request",function(req, res){
console.log("request");
var incomingUrl = url.parse(req.url);
if(incomingUrl.pathname === "/controller"){
fs.readFile("./client/controller.html","utf-8",(err,data)=> {
if (err) {
res.writeHead(404, {'Content-Type': 'text/html'});
res.write('');
res.end("<h1> Not Found </h1>");
}else{
res.writeHead(200, {'Content-Type': 'text/html'});
res.write(data);
res.end();
}
});
}else{
res.writeHead(404,{'Content-Type':'text/html'});
res.write('');
res.end("<h1> Not Found </h1>");
}
});
server.listen(6677);
続いて、「controller.html」はほぼ空っぽですが、こんな感じにしました。
controller.html
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>レゴカーコントローラー</title>
</head>
<body>
<h1>おはんきー</h1>
</body>
</html>
そうしたら、実際に「http://ラズパイのIP:6677/controller」にブラウザでアクセスして表示されることを確認します。

controller以外のところにアクセスするとNot Foundとなりますね。

socketを実装する。
次にserver.jsのsocket.ioを実装します。
その前に、socket.ioのモジュールをインストールしておきます。
(raspberry_3.5.1) masao-3:legoCarController konoe_mario$ npm install socket.io
インストールしたら、server.jsに以下のコードを追加します。
追加する場所に注意が必要です。自分は嵌まりました。
Node.js のsocket.ioでCan't set headers after they are sent - Qiita
server.js(抜粋)
//socket.ioで使う、server.on("request",function)より後がよい
var io = require("socket.io")(server);
//socket確認用のコード
io.sockets.on("connection",function (socket) {
console.log("socket connection");
socket.on("sendMessage",function (data) {
socket.emit('test',data);
});
});
次にcontroller.htmlにも、キー入力情報を受け取る機能と、socket.ioを実装します。
controller.html
<html lang="ja">
<head>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.0/jquery.min.js"></script>
<script src="/socket.io/socket.io.js"></script>
<script>
javascript-の処理を書く->
</sciprt>
<meta charset="UTF-8">
<title>レゴカーコントローラー</title>
</head>
<body>
<h1>おはんきー</h1>
<div id="controller"></div>
</body>
</html>
jqueryとsocket.ioの読み込みを追加しました。
ついでに、キー入力結果を表示するようの「
」も追加しています。
その後に、肝心のjavascirptの処理を追加しています。
controller.html(javascirpt部分を抜粋)
var socket = io.connect();
$(function() {
socket.on('connect', function () {
});
socket.on('test',function(data){
render(data)
});
});
function render(data) {
$("#controller").text(data);
}
//キーが押された場合のイベント
document.onkeydown = function(e){
isSendMessage(e.keyCode,"keydown");
};
//キーが離された場合のイベント
document.onkeyup = function(e){
isSendMessage(e.keyCode,"keyup");
};
//Windowが非アクティブになったら、エンジンが止まるように決め打ちで送信する。
window.onblur = function () {
isSendMessage(87,"keyup");
};
//socket通信をするかどうか制御する関数
function isSendMessage(key_code,keykind){
switch(key_code){
//W
case 87:
if(keykind === "keydown"){
sendMessage("forward");
}else{
sendMessage("break");
};
break;
//S
case 83:
if(keykind === "keydown"){
sendMessage("back");
}else{
sendMessage("break");
};
break;
//D
case 68:
sendMessage("right");
break;
//A
case 65:
sendMessage("left");
break;
default:
break;
}
}
//socket通信
function sendMessage(message) {
socket.emit("sendMessage",message);
}
再度、「http://ラズパイのIP:6677/controller」にアクセスすると、以下のようになるかと思います。

W、A、S、Dを押下すると、それに紐付いた動作名がブラウザに表示されます。
ちなみに、javascrptのキー入力のイベントに関しては、以下のサイトを参考にさせていただきました。
JavaScriptプログラミング講座【キーボード操作について】
処理の順番としては、以下のようなイメージです。

次回は、socket.ioのサーバ側をFlaskに置き換えていきます。