今話題のビデオ通話アプリzoomを用いて、「安くてお手軽に作れるテレイグジスタンスロボット」の構築を目指します。第3回目は、ロボット本体のPCをセットアップして、実際に遠隔操縦を試してみます。

第2回はこちら

システム構成(おさらい)

 改めて、本システムの構成について簡単におさらいします。

 ロボットのハードウェアにはメガローバー ver2.1を使い、等身大の目線に合うようにロボットに支柱を立てて、上にモニタ他通話用のデバイスを組み込みます。

 ソフトウェア上の構成は下図の通りで、ロボットとクライアント間はビデオ通話にzoom、ロボット操縦にfirebaseを使います。また、ロボット操作用の操作パネル等もクラウドサーバ上で提供し、クライアント側はプラットフォームに関係なくwebブラウザでアクセスするだけでロボットを利用できます。

 第1回では通話用のzoomアカウント及びzoom SDKのAPIキー情報を取得し、通話設定、第2回ではfirebaseでクラウドサーバの構築まで進めました。

ロボット搭載PCのセットアップ

 ロボット本体のPCを設定し、zoomミーティングの待ち受けやコマンドを受けて走行するようにします。以降はロボット本体のPCを「PC」と表記します。PCはUubuntu18.04LTS及びROS melodicが既にインストールされて、メガローバーが操作可能である前提とします。

zoomアプリのインストール

PCにlinux用zoomアプリをインストールします。Linuxへのzoomアプリのインストールは、下記の公式webページを参考にします。

https://support.zoom.us/hc/ja/articles/204206269-LinuxにZoomをインストールまたは更新する

下記URLにアクセスし、プログラムをダウンロードしてください。

https://zoom.us/download?os=linux

 LinuxタイプはUbuntu、OSアーキテクチャはNUC PCの場合64bit、バージョンは2020年9月現在では最新が16.04+なので、それを選択します。

 ダウンロードしたら、PC上で端末を開き、ファイルをダウンロードした場所にカレントディレクトリを移動し、下記コマンドを入力します。

sudo apt install ./zoom_amd64.deb

 インストールしたら、端末上で「zoom」と入力して、zoomアプリを起動してください。アプリが起動したら「Sign in」をクリックしてください。

 第1回目で作成したzoomアカウントでログインしてください。また、「Keep me signed in」にチェックを入れて、ログイン状態を継続するようにして下さい。

 一度個人ミーティングの待ち受けを開始します。PCより、ブラウザで下記のURLを開いてください。(個人ミーティングID)の部分は、第1回で調べた個人ミーティングIDの数値に置き換えてください。

https://zoom.us/j/(個人ミーティングID)

 URLを開くと、連携して起動するプログラムの選択を表示するので「プログラムの選択」欄からzoomを選び、「今後zoommtgリンクは同様に処理する」にチェックを入れます。これで、以降は同じURLにアクセスすると自動的にzoomの待ち受けを開始します。

 zoomアプリが起動して通話を開始するかの確認画面を表示するので、「Automatically join audio by computer when joining a meeting.」にチェックを入れて「Join with Computer Audio」をクリックします。

 ロボット側で自動的にカメラ・オーディオをONにした状態でミーティングの待ち受けを開始します。カメラやオーディオがONにならない場合は、デバイスが正しく接続されているか、また、ブラウザでzoomのユーザページにアクセスし、設定をご確認ください。

 この段階で、第2回で準備したクラウドサーバのURLにアクセスして、ミーティングIDとパスワードを入力すれば通話ができますが、ロボット内でクライアントからのロボット操縦コマンドを受信するプログラムがまだ実行されていないため、操作パネルは動かせません。


 余談になりますが、今回はブラウザでミーティング待ち受けのURLを開く方法で待ち受けを開始していますが、zoomにはClient URL Schemesという仕組みがあり、シェル上でzoomのコマンドに特定の引数を与えることで、ブラウザを介さず直接ミーティングを開ける仕様があるようです。

参考: https://marketplace.zoom.us/docs/guides/guides/client-url-schemes

 システムに関与するアプリを無駄に増やさないためにも、今回はこちらの方法で実現したかったのですが、色々試してみても「個人ミーティングは開けるがビデオが必ずoff状態で始まる」もしくは「個人ミーティングIDを指定しても、なぜか別の新規ミーティングとして始まってしまう」という問題が解決せず頓挫しました。
 現時点でClient URL Schemesに関する情報も乏しく解決策がわからなかったのですが、もし興味があればこちらの方法で対応できないかお試しください。

nodejs/npmのインストール

 ロボット側のコードはクライアントサーバと同じくnodeで実行します。PCに下記の手順でnode、npmをインストールしてください。第2回で既にロボットのPCに導入済みの場合は飛ばして構いません(手順も全く同じです)。

 PCの端末より下記コマンドを順番に実行してください。

sudo apt install -y nodejs npm
sudo npm install n -g
sudo n stable
sudo apt purge -y nodejs npm

参考リンク:https://qiita.com/seibe/items/36cef7df85fe2cefa3ea

 「npm : 依存: node-gyp (>= 0.10.9) しかし、インストールされようとしていません」とエラーが表示されて失敗する場合、下記のコマンドを実行してください。

sudo apt install nodejs-dev node-gyp libssl1.0-dev

参考リンク: https://blog.gepuro.net/posts/node_gyp_0109/

 node、npmをインストールしたら、一度端末を起動し直してから、下記コマンドでバージョンを確認してください。

node  --version
npm --version

コードのクローンと設定の書き換え

 PCにロボット制御用のサンプルコードを導入します。サンプルコードはgithubの下記リポジトリで公開されています。

https://github.com/vstoneofficial/zoom_robot_server

 コードのクローンは下記コマンドで行います。

git clone  https://github.com/vstoneofficial/zoom_robot_server.git

 クローンしたら、そこにカレントディレクトリを移動し、実行に必要なパッケージをインストールします。

cd zoom_robot_server
npm install

 コードには、各自の環境に合わせていくつかの設定を書き換える箇所があるので、順番にファイルを書き換えてください。

 zoom_start.sh とrosserial.launchに、それぞれUSBデバイス名の記載があるため、PCのメガローバー制御用のUSBデバイス名を確認し、ファイル内の「ttyUSB0」の箇所を書き換えてください。

  • zoom_start.shの該当箇所抜粋
echo pwd | sudo -S chmod 666 /dev/ttyUSB0
roslaunch megarover_samples rosserial.launch &
  • rosserial.launghの該当箇所抜粋
  <node pkg="rosserial_python" type="serial_node.py" name="megarover_rosserial" >
    <param name="port" type="string" value="/dev/ttyUSB0" />
    <param name="baud" value="115200"/>
  </node>

zoom_start.shの「pwd」の所を、PCのルート権限実行時のパスワードに書き換えてください。

  • zoom_start.shの該当箇所抜粋
echo pwd | sudo -S chmod 666 /dev/ttyUSB0

 サンプルコード内の「rosserial.launch」のファイルを、ROSのmegrover_samplesパッケージ内のlaunchディレクトリにコピーしてください。ROS PCオプションでは、コピー先が~/catkin_ws/src/megrover_samples/launch になります。

cp rosserial.launch ~/catkin_ws/src/megrover_samples/launch

 サンプルコードのディレクトリに、meetingidというファイル名で、個人ミーティングIDを記載したファイルを作成してください。

echo (個人ミーティングID) > meetingid

firebase-admin SDKの導入

 ロボットの操縦コマンドはfirestoreを介してやり取りしますが、firestoreは不特定多数のアクセスを防ぐために、firebase cloud functions以外から読み書きできない状態になっています。
 外部からのアクセスを可能にするには、firebase用のサービス アカウント用の秘密鍵ファイルを生成してプログラムに配置する必要があります、

参考リンク:https://firebase.google.com/docs/admin/setup?hl=ja

 秘密鍵ファイルの生成は、ブラウザ上でfirebaseのコンソールより行います。下記URLにアクセスし、現在のプロジェクトを開いてください。

https://console.firebase.google.com

 画面左上の「プロジェクトの概要」の右にある歯車をクリックし、「プロジェクトを設定」をクリックしてください。続いて「サービスアカウント」をクリックしてください。

 クリックすると秘密鍵ファイルの生成とコード上の記載方法についての画面を開きます。「Admin SDK 構成スニペット」の項目で表示されているソースコードは、ほぼ同じものがserverhost.jsの冒頭付近に記載されています。

 サンプルコード内のserverhost.jsの下記の行を、webページで表示されている同じ行の内容に書き換えて下さい。

  • serverhost.jsの該当箇所抜粋
admin.initializeApp({
  credential: admin.credential.cert(serviceAccount),
  databaseURL: "https://(your_databaseURL)"
});

 続いて、「新しい秘密鍵の生成」をクリックしてください。クリックすると上記の画面を開くので「キーを生成」をクリックしてください。

 json形式のキーファイルが生成されてダウンロードされるので、サンプルコードのserverhost.jsと同じディレクトリにコピーしてください。

 サンプルコード内のserverhost.jsの下記の行を、ダウンロードしたファイル名に書き換えて下さい。

  • serverhost.jsの該当箇所抜粋
var serviceAccount = require("./(your_ firebase-adminsdk_keyfile).json");

webブラウザ操作用ツールの導入

 webブラウザでミーティングIDのURLを開く方法では、開くたびにブラウザのタブが増えてしまうため、タブを閉じるための外部ツールをインストールします。

 下記のコマンドをそれぞれ実行してツールをインストールしてください。

sudo apt install wmctrl
sudo apt install xdotool

テスト実行

 ここまでできたら、一度コードをテスト実行してみます。下記コマンドで、zoom_startup.shに実行権限を与えて実行してください。

sudo chmod +x zoom_startup.sh
./zoom_startup.sh

 実行すると、ブラウザで待ち受け用のURLを開き、そこからzoomアプリが起動して自動的にミーティングの待ち受けまで開始します。
 スムーズに待ち受けまで進まず途中で何らかの操作を求められる場合、先程テストで待ち受けを行った際の操作を見直すか、meetingidのファイルが正しく生成されていなかったりミーティングIDが間違っていないかなどをご確認ください。

 ロボット側の準備ができたら、zoomミーティングに接続するクライアント側で、第2回で確認したクラウドサーバのHostind URLをwebブラウザから開いてください。

 URLを開いたら、「ミーティングID」と「パスワード」に、それぞれzoomの個人ミーティングIDとパスワードを入力してください。また、「名前」には通話中の表示名を設定できます。「接続」をクリックすると通話を開始します。

 クライアントの画面に表示されている矢印ボタン(←→↑↓)をクリックすると、ワンショットでロボットが移動します。

 操作パネルをクリックしてもロボットが動かない場合、ロボット側のサンプルコードでFirebaseの秘密鍵ファイル関連の設定を間違えていないか、また、ROS関連の設定(デバイス名の変更等)が正しいか、ROS単体でロボットが動作するかなどをご確認ください。

 なお、ロボット側のプログラムは、firestoreのリアルタイムリスナーによりDBに変更があればすぐに察知できますが、firestoreとしばらく通信が行われなかった場合などは、リアルタイムリスナーが一時的に休止状態に入るようで、最初のコマンド実行に数秒間のタイムラグが発生する場合がります。

待ち受けの自動実行設定

 最後に、ロボットのPCを起動したら自動的に待ち受けまで進めるように、zoom_startup.shをスタートアップに設定します。なお、待ち受けを自動実行するのは「PCのOSが起動したタイミング」や「ssh含めユーザがログインしたタイミング」ではなく、「PCのGUIが起動したタイミング」になります。

 PCのデスクトップ画面左下の全てのアプリケーションを開きます。

 「自動起動するアプリケーションの設定」をクリックしてください。アプリ名の検索で「session」と入力すれば、アプリが見つかります。

 「追加」をクリックします。

 「参照」をクリックしてzoom_start.shのファイルを選択してます。「名前」「説明」にはわかりやすい文言を入力してください。

 スタートアップにzoom_start.shが追加されました。

 設定したら、一度PCを再起動してください。起動後GUIにログインすると、自動的にミーティングの待ち受けを開始します。待ち受けは始まるが、マイクやカメラがoffの場合は、zoomの設定を見直してください。


 これで、ロボットのPCを起動してログインすれば、後は遠隔でミーティングに参加し、ロボットを操縦できるようになりました。 場合によってはログイン自体も自動化すると、ログイン操作のためのキーボード・マウスも不要で電源ONだけで待ち受けできるようになります。これに関する設定は、現場の運用方法に応じてご検討ください。

留意点

 本システムに関する諸注意や、今後こうあってほしいといった展望などについて記述します。

無料アカウント40分制限問題

 zoomミーティングの仕様として、無料アカウントでは、zoomの通話が最大40分の制限があります。本システムでも例外では無く、無料アカウントではミーティング時間が40分を超えるとミーティングが強制的に終了し、ロボット側の待ち受けも解除されてしまいます。

 この時間制限ですが、40分以内にミーティングを終了しても発生することがあり、どうやら「最後に二人以上のミーティングが始まった時間」を起点に40分カウントされるようです。40分経たずにクライアントが抜けてもダメで、逆に一度抜けたクライアントが40分以内に再度通話を開始すると延命できるようです。

ただし、この制限は2020年9月現在のもので、以降Zoom社による仕様変更が行われる可能性があります。

 今回zoom SDKの深いところを探っていないため、「いつ通話が終わったか」「いつ誰がミーティングに参加したか」と言った情報を取得できれば、リカバリのタイミングを計れそうですが、後述の通り情報の取得に割と制限がありそうです。

 この問題を突破するために、下記のようないくつかの方法を考えましたが、主にロボット側の画面表示で思うようにいかず断念しています。

ブラウザで定期的に個人ミーティングの待ち受けURLを開く

 通話終了のタイミングが不明でも、setIntervalメソッドを利用し、定期的にミーティング待ち受けのURLを開けばリカバリできると考えた方法です。ただ、URLを開くときにzoomのウィンドウがブラウザの後ろに隠れてしまうケースが不定期に発生する問題があり、インターバルを短くしてリカバリ回数が増えるほど隠れる確率が上がり、逆に長くすると状況によってリカバリまでの時間が長くなるため、どちらも困ったことになります。

クライアントの操作パネルにZoomの再起動ボタンを設置し、手動で再起動させる

 ロボットの操作は通話と別系統で行われることを利用した方法です。こちらの方が、必要なタイミングでピンポイントに待ち受けを再開できます。ただ、ブラウザに隠れる問題は同様なのと、PC自体のスリープ設定によっては、ミーティングが終了すると間もなくスリープ状態に入ってしまうので、設定でそこを変更しておく必要があります。

 また、いずれの方法でも、ミーティング終了を示すメッセージがビデオウィンドウの上に残り続けて画面表示を阻害するという問題があります。画面表示の乱れは(現場での操作が許容されるのであれば)通話者に対応してもらう方法がありますが、円滑な運用を考えるのであれば有料アカウントにグレードアップすることをお勧めします。


カメラの死角(足元が見えない)

 普通のwebカメラは至近距離の人の顔がフレームインするだけで事足りるため、画角がそこまで広くありません。本システムでも、同様にロボットの目の前で相対する人は映りますが、遠景や足元と言ったところを見る場合には圧倒的に画角が足りません。特に足元が見えないことは操縦に大きな支障をきたします。

 この問題の解決方法として一番容易なのは、複数のカメラを取り付ける方法です。これについては詳細な方法含めて別途下記の記事にまとめました。

https://vstone.co.jp/robotshop/blog/archives/2688

 カメラによる解決方法としては、他に魚眼レンズのようにカメラの画角を大きく広げる・パンチルトカメラを用いるなどが考えられます。前者はカメラを取り換える程度で簡単に実現できますが、後者は遠隔で制御する仕組みが別途必要です。zoomにはパンチルト対応のカメラを遠隔操縦できる機能が備わっているようですが、ブラウザ上の通話ウィンドウではまだその機能に対応していないようです。

 また、カメラに頼らず操縦における死角をカバーする方法としては、台車にLRFを取り付けて水平方向の距離情報を共有したり、車輪をROSで制御していることを利用して、SLAMのNavigation機能を用いて自動的に目的地までナビゲーションさせると言ったソリューションが考えられます。
 LRF情報を共有する場合、フレームレートやデータサイズを考慮すると、現在のFirestore(DB)によるデータのやり取り以外でリアルタイム性を確保できる方法を検討(web socket等)する必要があります。
 SLAMのNavigation機能で実現する場合、現在の操縦コマンドを拡張して地図上の地点を指定できるようにしたり、また誤差や障害物などによる衝突回避のため、やはりLRFなどを活用した障害物検知・回避の仕組みを検討する必要があります。


zoom SDKの仕様

 今回は結果的にzoom SDKを「クラウドサーバで通話ウィンドウを開くため」だけにAPIキーを取得して利用していますが、本来zoom SDKは独自にミーティングを作成したり、チャット等各種機能を制御できる等、多彩な機能を持っています。

参考: https://marketplace.zoom.us/docs/guides

 開発当初は、クラウドサーバを使わずzoomの通話内にロボットの制御情報を付加して制御できないか検討していましたが、残念ながら任意のパケットを送受信する仕組みが見当たりませんでした。またチャットでやり取りできないか考えても、チャットの受信時にコールバックで処理を呼ぶ仕組みが無いためメッセージの更新をポーリングで取得するしかなく、更にポーリング含めwebAPIサーバにアクセスできる頻度(回数)に制限があり、むやみにアクセスを乱発できない事情があり、断念しました。

 現在、zoomはかなりの速度で機能を進化させており、追々これらの問題が解決される時期が来るかもしれません。ただ、今回のfirestoreを用いた方法も、セットアップはかなり容易であり、ロボット・クライアント間の用途に応じて柔軟に仕様をカスタマイズできるので、今回の実装がベストな形の一つなのかもしれません。

操作UI

 通話中の画面は、操作パネルの下にインラインフレームで通話画面が表示されますが、本来はインラインフレームを使わず、通話画面上にオーバーレイで操作ボタンを表示する予定でした。ただ、通話画面に使用しているZoomMtgSDKでうまくオーバーレイを表示できず、現在のようなインラインフレームに落ち着きました。

 これはzoom mtgの仕様と言うより筆者のhtml/cssに不慣れなことが原因です。インラインフレームだと操作パネルとの連携がやりづらく、例えば本来ミーティングの開始・終了に連動して操作パネルの有効・無効を切り替えるべきところができない状態です。

 おそらくオーバーレイによる実装も何らかの解決方法が存在すると思われるので、web制作に詳しい方はUIを修正してみてください。ZoomMtgSDKには、ミーティングの開始時に任意のメソッドをコールバックする仕組みがあるようなので、これと組み合わせることでミーティング開始後に操作UIを有効化できます(終了時にも同様のコールバックが存在するかは未確認です)。

参考URL: https://zoom.github.io/sample-app-web/ZoomMtg.html

firebaseの月次請求

システムの利用頻度や改変などによって、firebaseの無料枠を超過し月次請求が発生する場合があります。firebaseの請求額は、firebaseのコンソール画面からブラウザで確認できます。当月の月半ばでもその時点での請求額を確認できます。
請求額の確認は、firebaseのコンソール画面左上の設定アイコンをクリックし、「使用量と請求額」をクリックします。

クリックすると、月次の請求額がグラフ表示されます。下記は主に本記事の制作の過程でシステムを利用して発生したものですが、一カ月で3円の請求が発生しています。

ページを下にスクロールすると、等各項目別に詳細使用量を確認できます。更に各項目の「使用量を見る」をクリックすると、日ごとの使用量を確認できます。

なお、日ごとの使用量は無料枠内なのに、請求が発生しているケースがあります。これは、日ごとの上限と月ごとの上限のそれぞれの集計によるものと思われます。
請求額が予想より増えている場合は、一時的にfirebase上でアプリを停止するなどして、利用を一時停止してください。


システムの構築はここまでで完了したので、次回は補足としてサンプルコードの解説を行います。

第4回へ