事象発生日:2018-12-26
記事公開日:2018-12-26
アクセス数:6882
Raspberry Piで動いているロボットを,遠隔地のPCから遠隔操縦するためのインターフェイスをつくる.
中継サーバーを介してロボットとPCは通信される.
これによって,中継サーバーのみがグローバルIPをもてば,PCとロボットそれぞれが中継サーバーにつなぎに行くことによって通信経路が確立されるというシステムとなる.
Raspberry Pi 3
Ubuntu Server 16.04.5 LTS (Xenial Xerus)
Node.js v10.14.2
ROS kinetic
UVC対応カメラ:LOAS MCM-15W
Raspberry Pi 3 Model B
Ubuntu Server 16.04.5 LTS (Xenial Xerus)
Node.js v10.14.2
Microsoft Windows 10 Home 1803 (64bit)
Google Chrome 71.0.3578.80 (Official Build) (64bit)
ロボット,中継サーバー,遠隔PC,ともにWiFiによって同一LANにいる.
(原理的には,中継サーバーのみグローバルIPを持っていればよい.)
からイメージファイルをダウンロードしてきて,このページにそってRaspberry PiにUbuntu Serverをインストールすることにより,中継サーバーとする.
ここでは,以下のようにNode.jpの環境を構築しておく.
$ git clone git://github.com/creationix/nvm.git ~/nvm $ ./nvm/install.sh 再ログイン $ nvm install --lts $ npm install socket.io
概要図はこの通り.
ロボットと遠隔PCの間に中継サーバーを挟んでWebSocketで通信する.
これには,
| ロボット,遠隔PCはグローバルIPを必要としない. | |
| 中継サーバー1台あれば,複数の通信セッションも開設できる. | |
| データを中央で管理できる. | |
| LANをまたいだ遠隔操作も可能である. |
のようなメリットがある.
グローバルIPを持つのは中継サーバーのみなので,必然的に,ロボットと遠隔PCがそれぞれクライアントとなり,中継サーバーへ繋げにいくことになる.
遠隔PCは,中継サーバーへブラウザでhttp接続しに行くだけである.
ブラウザに,http://{$server_ip}:3000/と打てば,次のような画面が出てくる.
中継サーバーの役割は,
| ロボットと遠隔PCの通信を仲介する | |
| 遠隔PCのブラウザに対してはWebサーバーとして働く | |
| ロボットと遠隔PCのセッションを管理する(未実装) |
である.
Node.jsのスクリプトである,node_app_relay_server.jsを
$ node node_app_relay_server.js
で走らせ,同ディレクトリにWebサーバーとしてのファイル3つを配置する.
それぞれのコードの概要を載せる.
全て載せると煩雑になるので,詳細実装は「」の過去記事を参照のこと.
(もうちょっと作り込んだら,そのうちGitHubにでも上げます.)
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <title>RaspPi Mouse Console</title> <link rel="stylesheet"href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css"> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> <script type="text/javascript" src="/socket.io/socket.io.js"></script> <script type="text/javascript" src="./pimouse.js"></script> <link href="./pimouse.css" type="text/css" rel="stylesheet"> </head> <body> <div class="container"> <h1>RaspPi Mouse Console</h1> <h4>LED:</h4> <div class="row"> <button type="button" id="ledOn" class="col-md-1 btn btn-danger">LED ON</button> <button type="button" id="ledOff" class="col-md-1 btn btn-danger">LED OFF</button> </div> <h4>カメラ:</h4> <div class="row"> <button type="button" id="cameraOn" class="col-md-1 btn btn-danger">ON</button> <button type="button" id="cameraOff" class="col-md-1 btn btn-danger">OFF</button> </div> <img id="cameraCapture"> <h4>測距センサ:</h4> <div class="row"> <button type="button" id="lightSensorSingle" class="col-md-1 btn btn-danger">シングル</button> <button type="button" id="lightSensorSeqBegin" class="col-md-1 btn btn-danger">連続開始</button> <button type="button" id="lightSensorSeqEnd" class="col-md-1 btn btn-danger">連続終了</button> </div> <div id="lightSensorLogs" class="log"></div> </div> </body> </html>
$(function() {
var socket = io.connect();
socket.on("s2c_LS_DATA", function(data){AppendLsLog(data.value)});
socket.on("s2c_CAMERA_DATA", function(data){UpdateCamera(data.value)});
$("button#ledOn").on('click', function() {
console.log("LED ON");
socket.emit("c2s_LED_ON", null);
});
$("button#ledOff").on('click', function() {
console.log("LED OFF");
socket.emit("c2s_LED_OFF", null);
});
... 中略 ...
});
function AppendLsLog(text) {
console.log(text);
$("#lightSensorLogs").append("" + text + "
");
}
function UpdateCamera(data) {
console.log(data);
$("#cameraCapture").attr('src', data);
}
@charset "utf-8"; ... 中略 ...
var http = require('http');
var socketio = require('socket.io');
var path = require('path');
var fs = require('fs');
var mime = {
".html": "text/html",
".js": "application/javascript",
".css": "text/css",
// 読み取りたいMIMEタイプはここに追記
};
var server_ws_main = http.createServer(function(req, res) {
if (req.url == '/') {
filePath = '/pimouse.html';
} else {
filePath = req.url;
}
var fullPath = __dirname + filePath;
console.log('fullPath : ' + fullPath);
res.writeHead(200, {"Content-Type": mime[path.extname(fullPath)] || "text/plain"});
fs.readFile(fullPath, function(err, data) {
if (err) {
// エラー時の応答
} else {
res.end(data, 'UTF-8');
}
});
}).listen(3000);
console.log('WS Main Server running at http://localhost:3000/');
var io = socketio.listen(server_ws_main);
io.sockets.on('connection', function(socket) {
socket.on('c2s_LED_ON', function(data) {
console.log('c2s_LED_ON');
io.sockets.emit('s2p_LED_ON', null);
});
socket.on('c2s_LED_OFF', function(data) {
console.log('c2s_LED_OFF');
io.sockets.emit('s2p_LED_OFF', null);
});
... 中略 ...
socket.on('p2s_LS_DATA', function(data) {
io.sockets.emit('s2c_LS_DATA', {value : data.value});
});
socket.on('p2s_CAMERA_DATA', function(data) {
io.sockets.emit('s2c_CAMERA_DATA', {value : data.value});
});
});
ロボットは,ブラウザではないがNode.jsでWebSocketのクライアントとして働く.
$ node node_app_pimouse.js
と走らせておけばよい.
コードは以下の通りである.
// web camera
var NodeWebcam = require('node-webcam');
var opts_camera = {
width: 320,
height: 240,
callbackReturn: "base64"
};
var socket = require('socket.io-client')('http://${server_ip}:3000');
// タイマー変数の初期化
var timer_lt = {
id : null,
is_on : 0,
}
var timer_camera = {
id : null,
is_on : 0,
}
socket.on('connection', function(socket) {
});
socket.on('s2p_LED_ON', function(data) {
OnLed();
});
socket.on('s2p_LED_OFF', function(data) {
OffLed();
});
socket.on('s2p_CAMERA_ON', function(data) {
if (timer_camera.is_on == 0) {
timer_camera.id = setInterval(SendCameraCapture, 200);
}
timer_camera.is_on = 1;
});
socket.on('s2p_CAMERA_OFF', function(data) {
if (timer_camera.is_on == 1) {
clearInterval(timer_camera.id);
}
timer_camera.is_on = 0;
});
socket.on('s2p_LS_SINGLE', function(data) {
SendLtValue();
});
socket.on('s2p_LS_SEQ_BEGIN', function(data) {
if (timer_lt.is_on == 0) {
timer_lt.id = setInterval(SendLtValue, 500);
}
timer_lt.is_on = 1;
});
socket.on('s2p_LS_SEQ_END', function(data) {
if (timer_lt.is_on == 1) {
clearInterval(timer_lt.id);
}
timer_lt.is_on = 0;
});
socket.on('disconnect', function() {
if (timer_lt.is_on == 1) {
clearInterval(timer_lt.id);
clearInterval(timer_camera.id);
}
OffLed();
});
// LED ON
function OnLed() {
... 中略 ...
}
// LED OFF
function OffLed() {
... 中略 ...
}
// 光センサの値を読み取って返す
function SendLtValue() {
... 中略 ...
socket.emit('p2s_LS_DATA', {value : ret.value});
}
// Camera
function SendCameraCapture() {
console.log("Capture! outer");
NodeWebcam.capture( "test_picture", opts_camera, function( err, data ) {
... 中略 ...
socket.emit('p2s_CAMERA_DATA', {value : ret.value});
});
}
今回は全データをブロードキャストしてしまっているので,不要な通信を省き,また複数台通信に対応できるようにしていく.
また,動画配信もどうにかしたい.
もっと高画質で,高フレームレートで通信できる方法を考えていきたい.
名前
Email (※公開されることはありません)
コメント