狙い
Node.js, Socket.io, Arduino, Johnny-five などなど、IoT 的なことについて趣味で遊んできたことについて、それらのエッセンスを組み合わせたシンプルな遠隔計測・制御システムを作ってみました。
特に目新しい情報があるわけではありませんが、主に自分の記録のために。
構成・仕様
ハードウェア
- PC(Lenovo IdeaPad U330p, Core i3 4010U, Windows 10 Home Ver.1703)
- PC 内蔵 Web カメラ または USB Web カメラ
- Arduino Uno R3
-
SeeedStudio Grove Starter Kit for Arduino
- LED ソケットと LED のみ使用
- LED は D3(PWM できる端子)に接続
- USB A-B ケーブル
- スマホ(iPhone SE [iOS 11.0.2])
ソフトウェア
- Win64 OpenSSL v1.1.0f(秘密鍵 server.key と自己証明書 server.crt を作成しておく)
- Arduino IDE(Arduino に StandardFirmataPlus を書き込んでおく)
- Node.js 6.11.2
- express 4.15.4
- socket.io 2.0.3
- johnny-five 0.11.6
- 任意のテキストエディタ(Visual Studio Code 等)
- PC側ブラウザ:Google Chrome 61.0.3163.100
- スマホ側ブラウザ:Safari
通信環境
- PC(自宅)側
- au ひかり
- ホームゲートウェイ(NEC Aterm BL170HV)
- 無線 LAN ルータ(Buffalo WXR-1900DHP2、ブリッジモード)
- スマホ側
- 楽天モバイル SIM(DoCoMo 回線)
構成図
コード
- 以下のサーバ側コード(app.js)とクライアント側コード(index.html)を作成し、例えば C:\Users\(ユーザ名)\app 等に配置
- 事前に用意した自己証明書(server.key と server.crt)を例えば C:\Users\(ユーザ名)\ssl に配置
サーバ側
app.js
"use strict"; // 厳格モードにする
// express を使う
const app = require("express")(); // express アプリを作る
app.get("/", (req, res) => { // ルートへのアクセス要求があったら
res.sendFile(__dirname + "/index.html"); // ルートにある index.html を配信
});
// https サーバを立てる
const fs = require("fs"); // fs モジュールを使う
const opt = { // SSL 認証のパラメータ
key: fs.readFileSync("../ssl/server.key"), // 秘密鍵(事前に OpenSSL 等で準備)
cert: fs.readFileSync("../ssl/server.crt"), // 証明書(事前に OpenSSL 等で準備)
};
const svr = require("https").Server(opt, app); // https サーバを立てる
svr.listen(443); // 443番ポートを listen
// ボード(Arduino等)の制御
const five = require("johnny-five"); // johnny-five モジュールを使う
const board = new five.Board({port:"COM3"}); // COM3 に接続した制御ボード(Arduino等)を取得
const pinLed = 3; // LED を接続したピン番号
let led; // LED オブジェクトを入れる箱
board.on("ready", () => { // 制御ボードの準備ができたら
led = new five.Led(pinLed); // LED オブジェクトの生成
});
// socket の送受信
const io = require("socket.io")(svr); // socket.io を https サーバに紐づける
io.on("connection", (socket) => { // socket 接続があったら
socket.on("value", (dat) => { // value という socket を受信したら
if( !board.isReady ) { return; } // 制御ボードの準備ができていなければ抜ける
led.brightness(dat); // LED の明るさを設定
socket.broadcast.emit("value", dat); // データを送信元以外に socket 送信
});
socket.on("video", (dat) => { // video という socket を受信したら
socket.broadcast.emit("video", dat); // データ(映像)を送信元以外に socket 送信
});
});
クライアント側
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>遠隔計測・制御システム</title>
<style>
#vid { display: none; } /* カメラ映像表示用の video 要素を非表示 */
#cvs { display: none; } /* カメラ映像キャプチャ用の canvas 要素を非表示 */
</style>
</head>
<body>
<video id="vid" autoplay></video> <!-- カメラの映像を表示する video 要素 -->
<canvas id="cvs" width="160" height="120"></canvas> <!-- カメラの映像をキャプチャする canvas 要素 -->
<img id="rmt" width="160" height="120"> <!-- 受信した映像を表示する img 要素 -->
<br> <!-- 見た目のための改行 -->
<input id="sld" type="range" min="0" max="255"> <!-- 値を入力するスライダ(遠隔制御用) -->
<script src="/socket.io/socket.io.js"></script> <!-- socket.io.js の読み込み -->
<script>
// socket.io の準備
const socket = io();
// スライダの値の送信
const sld = document.getElementById("sld"); // input 要素 (range) を取得
sld.value = 0; // スライダの値を 0 に初期化
sld.addEventListener("input", () => { // スライダで値が入力されたら
socket.emit("value", sld.value); // スライダの値を socket で送信
});
// スライダの値の受信
socket.on("value", (dat) => { // value という socket を受信したら
sld.value = dat; // スライダの値を変える
});
// 映像の取得と送信
const media = navigator.mediaDevices.getUserMedia({ // メディアデバイスの準備
video: true, // 映像を使う
audio: false, // 音声は使わない
});
media.then((stream) => { // メディアデバイスの準備ができたら
const vid = document.getElementById("vid"); // video 要素の取得
vid.srcObject = stream; // video 要素にメディアストリームを入れる
vid.addEventListener("timeupdate", () => { // video の内容が更新されたら
const cvs = document.getElementById("cvs"); // canvas 要素の取得
const ctx = cvs.getContext("2d"); // canvas の context の取得
ctx.drawImage(vid, 0, 0, cvs.width, cvs.height); // canvas に video 要素の内容を描画
socket.emit("video", cvs.toDataURL()); // canvas の内容を base64 にして socket で送信
});
});
// 映像の受信
socket.on("video", (dat) => { // video という socket を受信したら
const rmt = document.getElementById("rmt"); // img 要素の取得
rmt.src = dat; // 映像を img 要素に表示
});
</script>
</body>
</html>
動作例
- コマンドプロンプト または PowerShell にて、Node.js で app.js を実行
C:\Users\(ユーザ名)\app>node app.js
- PC の Chrome にて、https:// で localhost を開く
- 「この接続ではプライバシーが保護されません」と出るが、自作のサービスなので接続
- カメラへのアクセスを許可する
- Web カメラ映像の配信が始まる
https://localhost
- スマホ(iOS 11 の Safari)にて、https:// で 自宅のホームゲートウェイの URL(ダイナミック DNS に登録した ドメイン名(例:***.mydns.jp)または WAN 側 IP アドレス)を開く
- スマホは自宅の無線LANルータにつながない(Wi-Fi を OFF)
- 「接続はプライベートではありません」と出るが、自作のサービスなので接続
- スマホ側はカメラへのアクセスを許可しなくてよい
https://***.mydns.jp
- スマホで以下のように表示され、スライダを動かすと LED の明るさが変わる
- 以下の写真では、Web カメラの前に電波時計を置いておき、リアルタイムの映像であることを確認しつつ、電波時計に付属の温度計と湿度計の値もチェック
- さらに念入りに、自宅の無線 LAN ルータ電波の圏外まで外出して確認すると、満足感が高まる
- サービス終了は、PC のコマンドプロンプトで「Ctrl+c」を2回入力
まとめ
このシステム自体は何も嬉しいことができませんが、これをベースに、いろいろ遊べると思います。
なお、ホームゲートウェイのポートマッピング(ポート開放)設定や、SSL 自己証明書の作成などを伴っていますから、参考にされる場合は自己責任でお願いします。