LoginSignup
7
11

More than 5 years have passed since last update.

PC+Webカメラ+Arduino+Node.jsで作る遠隔計測・制御システム

Last updated at Posted at 2017-10-08

狙い

Node.js, Socket.io, Arduino, Johnny-five などなど、IoT 的なことについて趣味で遊んできたことについて、それらのエッセンスを組み合わせたシンプルな遠隔計測・制御システムを作ってみました。

特に目新しい情報があるわけではありませんが、主に自分の記録のために。

構成・仕様

ハードウェア

ソフトウェア

通信環境

  • PC(自宅)側
    • au ひかり
    • ホームゲートウェイ(NEC Aterm BL170HV
      • TCP のポート 80 と 443 を PC にポートマッピング(方法
        • PC の IP アドレスは固定しておく
      • MyDNS.JP でダイナミック DNS 登録(***.mydns.jp でつながる)
        • またはホームゲートウェイの WAN 側 IP アドレスを調べておく
    • 無線 LAN ルータ(Buffalo WXR-1900DHP2、ブリッジモード)
  • スマホ側
    • 楽天モバイル SIM(DoCoMo 回線)

構成図

構成図.png

コード

  • 以下のサーバ側コード(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 カメラの前に電波時計を置いておき、リアルタイムの映像であることを確認しつつ、電波時計に付属の温度計と湿度計の値もチェック

IMG_1021.png

  • さらに念入りに、自宅の無線 LAN ルータ電波の圏外まで外出して確認すると、満足感が高まる
  • サービス終了は、PC のコマンドプロンプトで「Ctrl+c」を2回入力

まとめ

このシステム自体は何も嬉しいことができませんが、これをベースに、いろいろ遊べると思います。
なお、ホームゲートウェイのポートマッピング(ポート開放)設定や、SSL 自己証明書の作成などを伴っていますから、参考にされる場合は自己責任でお願いします。

7
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
11