見出し画像

IoT時代に一般化される(?)次世代モニター『省スペースARモニター』作ってみた

概要

・何台もの機器を管理するIoT時代に最適なモニターとは何か考えます。
・『省スペースARモニター』と命名したモニターを紹介します。
・作ったARモニターのデモ映像とその作り方を紹介します。

次世代モニターを考える

パソコンには、パソコン本体だけでなくモニターが必要です。ですがIoT機器の利用が進み、100台以上のIoT機器を利用するようになった場合、各IoT機器にモニタを設置するには、それなりに場所が必要になります。モニターの省スペース化の方法として、1台のモニタを複数の機器で共有する方法がありますが、切り替えが必要ですし100台の切り替えはやりたくないです。同時に見れないのも難点です。そのようなことを考えると、IoT用のモニターには以下の条件が必要そうです。

・省スペースで設置できること
・IoT機器が増えても対応できること

この要件を満たすモニターとして、思いついたのが AR (拡張現実)を使ったモニターになります。つまり、各IoT機器にはモニタを設置せずARマーカーを用意します。そしてARマーカーで表示する3Dオブジェクトをモニターにします。これなら設置に必要なスペースはARマーカーを置く場所だけでよく、電源も必要ありません。(当然ですが、ヘッドマウントディスプレイには電源必要です)

このモニターを『省スペースARモニター』と命名し、実際に作っていきます。

AR (拡張現実)とは?

本題に入る前にARについて紹介します。「ARってなんだ?」という質問に対しては「ポケモンGOです」と答えています。以前は「ドラゴンボールのスカウターみたいなやつです」と答えてました。かなり荒っぽい説明ですが、そんな感じです(笑)

サンプル作ってみた

早速ですが、完成したサンプルを紹介します。
ARモニターに、IoT機器であるラズベリーパイ(以後、ラズパイ)のCPU使用率を表示します。
まずは以下のように、ラズパイの隣にARマーカを用意します。これだけではARモニターは表示されません。

次に、これをARに対応したヘッドマウントディスプレイで見ると、以下のようにARマーカーにラズベリーパイのCPU使用率を表示するモニターが表示されます。↓↓↓ こんな感じ

画像ではわかりにくいと思いデモ動画も作りました。予算の都合でヘッドマウントディスプレイは使っていませんが、雰囲気つたわると思います。

作ったサンプルの構成図

省スペースARモニターの作り方を紹介する前に構成図を紹介します。
ラズパイとWindowsパソコンを同じネットワーク内にして通信できるようにします。
また、ラズパイにはWEBサーバーを構築し、ARコンテンツを配信できるようにします。

作り方

さて、作り方を紹介します。

1. ラズベリーパイのセットアップ

ラズベリーパイのOSインストールから基本セットアップは以前まとめた記事があります。

2. Nodejsインストール

ARのWEBコンテンツを使うためNodejsをインストールします。

3. オレオレ証明書でNodejsをhttps通信

ARのWEBコンテンツは、コンテンツを開く機器(構成図ではWindowsパソコン)のカメラ📷を使用します。カメラ📷を使うには(httpではなく)https通信が必要になります。

4. 必要なデータを作成する

ARコンテンツに必要な以下のデータを用意します。

<用意するデータの説明>

aframe-html-shader.min.js

ARモジュール。ar.html で使用します。
https://github.com/mayognaise/aframe-html-shaderでダウンロードできました。

ar.html

ARコンテンツ。jsonファイル(sample.json)を定期的に読み込みCPU使用率を表示します。

getInfoOutToJson.py

ラズベリーパイのCPU使用率を取得し、jsonファイル(sample.json)として出力します。スクリプトは、Python3 で書いています。

sample.json

json形式で、CPU使用率の情報を記載。getInfoOutToJson.pyで定期的に更新し、ar.html が定期的に読み込みます。

stress.py

ラズベリーパイにランダムに負荷を与えます。CPU使用率が変わるようにします。必要なければ作る必要ありません。

4.1 AR用のサイト(ar.html)

<html>
    <head>
    <meta charset="utf-8">
    <meta name="viewport" content="initial-scale=1.0,user-scalable=no,maximum-scale=1,width=device-width">
    <title>aframe</title>
    <script src="https://aframe.io/releases/0.6.1/aframe.min.js"></script>
    <script src="aframe-html-shader.min.js"></script>
    <script src="https://jeromeetienne.github.io/AR.js/aframe/build/aframe-ar.js"></script>

    <style type="text/css">
        #target {
        width: 128px;
        height: 128px;
        position: absolute;
        background: rgba(200,200,200,1.0);
        }
        #htmlTarget {
        position: absolute;
        z-index: 1;
        height: 100%;
        width: 100%;
        overflow: hidden;
        position: fixed;
        top: 0;
        }
    </style>

    <script>
        //*************************************
        AFRAME.registerComponent('update-html', {
            init: function () {
            this.lettersEntity = document.getElementById('lettersEntity');
            this.lettersIndex = 0;
            this.lettersUpdateDur = 1500 / this.lettersEntity.getAttribute('material').fps;
            this.lettersTime = 0;
            this.pre = document.getElementById('pre');
            },
            tick: function (t, dt) {
            this.lettersTime += dt;
            if (this.lettersTime >= this.lettersUpdateDur) {
                var nxtSrc = "";
                var tmp = getStr();
                document.getElementById('pre').innerHTML = tmp;
                this.lettersTime = 0;
            }
            }
        });
        //*************************************/

        function getStr() {
            var wStr = "";
            var req = new XMLHttpRequest();
            req.onreadystatechange = function() {
                if(req.readyState == 4 && req.status == 200){
                    var data = JSON.parse(req.responseText);
                    var len = data.length;
                    for(var i=0; i<len; i++) {
                    switch( data[i].name ){
                        case 'CLOCK': wStr += data[i].value; break;
                        default : wStr += "<br />" + data[i].name + ":" + data[i].value; break;
                    }
                    }
                }
            };
            req.open("GET", "sample.json", false);
            req.send(null);
            return( wStr );
        }
    </script>
    </head>
    <body>
    <a-scene update-html arjs="debugUIEnabled:false;">
        <a-marker preset="hiro">
        <a-entity
            id="lettersEntity"
            geometry="primitive: box;
                        width: 1.0;
                        height: 1.0;
                        depth: 0.01;"
            rotation="-90 0 0"
            position="0 0 0"
            material="
                shader: html;
                target: #target;
                transparent: true;
                ratio: width;
                fps: 1.5"
        ></a-entity>
        <a-entity position=" 0.6 0.0  0.1" rotation="0 0 0" geometry="primitive:box;width:0.2;height:0.1;depth:1.2;" material="color: #0000FF;roughness:0.1;metalness:0.5;"></a-entity>
        <a-entity position="-0.6 0.0 -0.1" rotation="0 0 0" geometry="primitive:box;width:0.2;height:0.1;depth:1.2;" material="color: #00FF00;roughness:0.1;metalness:0.5;"></a-entity>
        <a-entity position=" 0.1 0.0 -0.6" rotation="0 90 0" geometry="primitive:box;width:0.2;height:0.1;depth:1.2;" material="color: #FF0000;roughness:0.1;metalness:0.5;"></a-entity>
        <a-entity position="-0.1 0.0  0.6" rotation="0 90 0" geometry="primitive:box;width:0.2;height:0.1;depth:1.2;" material="color: #FF00FF;roughness:0.1;metalness:0.5;"></a-entity>
        </a-marker>
        <a-entity camera></a-entity>
    </a-scene>
    <!-- HTML to render as a material. -->
    <div id="htmlTarget" style="width:0px;height:0px;">
        <center>
        <div id="target">
            <div  ID="pre"></div>
        </div>
        </center>
    </div>
    </body>
</html>

4.2 JSONデータ更新スクリプト(getInfoOutToJson.py)

import subprocess
import sys
import datetime
import time

def OutInfo():
    res = subprocess.run( ["vmstat","1","3"], stdout=subprocess.PIPE )
    res = res.stdout
    resLine = res.splitlines(False)
    resArr = str( resLine[4] ).split()

    now = datetime.datetime.now()
    clock = str(now.hour) + ':' + str(now.minute) + ':' + str(now.second)

    print( resLine[0])
    print( resLine[1])
    print( resLine[4])
    print( resArr[13])

    path = 'sample.json'

    with open( path, mode='w' ) as f:
        outStr = ''
        outStr += '['
        outStr += '{"name":"CLOCK","value":"' + str(clock) + '"}'
        outStr += ',{"name":"CPU[%]","value":"' + resArr[13] + '"}'
        outStr += ']'
        f.write( outStr )

for i in range(10000):
    OutInfo()
    time.sleep( 1 )

動かし方

nodejsのWebサービスを開始します。

node index.js

JSONデータを定期的に更新するPython3スクリプトを実行します。

python3 getInfoOutToJson.py

これでラズパイにあるWEBサーバの準備が完了しARコンテンツを配信できるようになりました。
ラズパイと同じネットワークにいるWindowsパソコンでブラウザを起動し ARコンテンツ(ar.html) を開きます。ARコンテンツを開くには以下のようなURLを指定します。

https://{ラズベリーパイのIPアドレス}:{ポート}/ar.html

無事ARコンテンツが開くとWindowsパソコンに設置されたカメラ📷からの映像が表示されます。その映像にARマーカー(Hiroのマーク)を映すと、ラズパイのCPU使用率を表示するモニターが表示されるはずです。

おまけ

省スペースARモニターが動作しても、CPU使用率が一定だと面白くありません。そこで、ラズパイのCPU使用率がコロコロ変えるスクリプト(stress.py)を作りました。ソースは↓こちらです。(python3 stress.py で実行します)

import subprocess
import sys
import random
import time

def doCom():
    coFlg = random.randint(0,5)
    if coFlg == 0:
    hoge = subprocess.run( ["ls","-l"], stdout=subprocess.PIPE )
    elif coFlg == 1:
    hoge = subprocess.run( ["tree","/etc"], stdout=subprocess.PIPE )
    elif coFlg == 2:
    hoge = subprocess.run( ["tree","/home"], stdout=subprocess.PIPE )
    else:
    hoge = subprocess.run( ["ls","-l","-a"], stdout=subprocess.PIPE )

for i in range(10000):
    waitsec = random.randint(0,5)
    lpmax   = random.randint(10,50)
    print("doCom " + str(lpmax) + " [times]")
    for j in range( lpmax ):
    doCom()
    print("wait " + str(waitsec) + " [sec]")
    time.sleep( waitsec )

さいごに

未来のことはわかりませんが、今回紹介したような、ARを使用したモニターの活用は一般的になるのではないかと、サンプルを作ってそう思いました。

こんな弱小ブログでもサポートしてくれる人がいることに感謝です。