TakSan0のアイコン画像

obniz と M5カメラで遠隔操作ロボ

TakSan0 2021年05月16日に作成  (2021年05月16日に更新)

1. はじめに

キャタピュラ式の車輪を持つ"カムロボ"を "obniz" で制御。
それに、WiFiで映像を送れる Webカメラを構成できる"M5カメラ" を取り付け、遠隔制御ロボットのシステムを実現しました。

まずは、完成形紹介動画をご覧ください。

ここに動画が表示されます

※動画撮影は便宜上同じ部屋を使ってやりましたが、実際は別の部屋にあるカムロボを制御する形で開発しました。
2階から1階のカムロボを制御してもほぼ同程度の遅延で制御することはできています。

2. システム構成

主に以下の2つを組み合わせたシステム構成となっています。
(1) obniz でロボットの制御をおこなう
(2) M5カメラで映像を飛ばし obniz での操作画面の横に表示する (LAN 内 WiFi)

システム構成
(1) は図中の オレンジ色の線でデータの流れを示していて、obnizクラウドを通じて通信されます。ここで扱う制御データとは、ブラウザの表示用データや obniz 制御のためのデータとなります。
obniz はカムロボに取り付けられた、モーター、LED、距離センサー等を制御します。

(2) は図中の紫色の線でデータの流れを示していて、WiFi 経由で LAN 内でルータを介して通信されます。
ここで扱う映像データとは、ブラウザ表示用の映像ストリームとなります。実際はM5カメラはWebカメラサーバとなっています。

これらの仕組みにて、図のように2階の部屋にある PC から1階の別の部屋にあるカムロボを遠隔操作できるようなシステムとなります。

3. ハードウェア

3-1. 使用部品

一部組付けてしまっているものもありますが、全部品並べると大体こんな感じとなります。
使用部品

表にすると以下の通りです。

品名 型番 or SPEC 個数 参考価格 備考
obniz基板 obniz 1Y 1 8,000 contest提供品を使用
カムプログラムロボット TAMIYA 楽しい工作シリーズ No. 227 1 3,170 通称カムロボ
M5カメラ M5STACK-U038 1 2,050
距離センサ RCWL-1601 1 950 HC-SR04互換
白色LED 不明 2 50
抵抗 100Ω 2 10
モバイルバッテリ Cheero Canvas 3200mAh 1 2,500 手持ちの物で可 ※1
M3ビス/ワッシャ/ナット M3x6 9 90
M2ナット/ワッシャ/ナット M2x15 2 30
スペーサー M3x25 4 230
スペーサー M2x7 2 30
基板固定スペーサ 1 30
コネクタセット GROVE 4pin コネクタ 1 60 半分に切って使用
ヘッダピン セット ヘッダピン + コネクタ 1 25 距離センサ延長接続用
L型金具 不明 4 110
下敷き PET製が良 1 110 (A4サイズをカット) 基板固定用※2

参考価格で複数個入りの物は個数で割って必要数を乗算した価格です。

※1モバイルバッテリは、収まるのであれば手持ちのもので大丈夫ですが、ある程度の容量が必要になります。
DAISO で 300円で売っていたこのモバイルバッテリでは容量不足で動きませんでした。

あかんかった DAISO 300円バッテリ

※2下敷きはある程度強度があって、絶縁出来て、加工もしやすい(はさみで切れる、今回は使っていませんがホットボンドも乗りやすい)のでお手軽に使えてお勧めです。

3-2. 組み立て

3-2-1. カムロボ

手順が多く複雑ですが頑張って作っていきます。
カムロボは本来プログラムロボットという位置づけで、プログラムバーという機構で物理的に進行方向を制御する仕組みがあります。
今回は、obniz によりモーターを直接電気的に制御するため、プログラムバーは不要でかつ、スペースの邪魔になる為、その部分は省いて組み立てます。
具体的には、公式組み立て説明書の以下の手順が不要となります。

  • ①プログラムバーギアケースの取り付け
  • ③サイドフレームサポートの取り付け
    ⇒obnizを乗せた土台やモバイルバッテリが干渉するので省きます。
  • ⑥ステアリングレッグの組み立て
  • ⑦ステアリングレッグの取り付け
  • ⑧シャフトの取り付け
  • ⑬ルーフの組み立て の左半分(電池ボックス部分)
  • ⑮配線の前に
  • ⑯コードの配線
  • ⑰バッテリーの取り付け

こんな感じです。
カムロボフレーム
カムロボ底面

3-2-2. ベースボード

obniz や、M5カメラ固定、距離センサを取り付けるためのベースボードを作成します。
あまり重たい部品はなく、可動部ではないため強度も不要です。
最低限部品を支えるだけの強度があればいいので、プラ板(下敷き)に穴を開け切って制作しました。

こんな感じの型紙を
CADで型紙作成

CADで作成し、両面テープでプラ板に張り付けた状態で穴あけたり切り取ったりすると、寸法もばっちり合うのでお勧めです。(色々試行錯誤したので、記事内写真のベースボードには余分な穴も開いていますが...)
印刷すると実寸になる PDFファイルを提供しようと思いましたが、このサイトは記事に PDFを張り付けれませんでしたので断念して画像にしました。やってみた人は、サイズを合わせ込んで印刷するなり、同じようにCADで描くなりしてください。

3-2-3. ベースボード組み立て

ベースボードにまずは obniz を取り付けます。
一応絶縁の為、下側2本はポリカワッシャーをかましています。透明なので見にくいですが。
obniz 1y の取り付け

次にM5 カメラと距離センサを取り付けます。距離センサーは 1mm の穴が開いていますが 1mm のビスやスペーサーは手に入らなかったので、0.7mm のアルミ針金とスペーサーで固定しました。(力業!?)
M5 カメラと距離センサを取り付け

L型金具で組付けます
L型金具で組付け

3-3. 接続

接続はこんな感じです。
但し、fritzing 用の obniz 部品が 1y 用の物がありませんでしたので、電源用の 3pin ヘッダの部分に強引に部品をくっつけて作画しています。ですので LCD の位置や ESP32の向きなど微妙に異なりますし、線色も決められないところがあって写真とは異なっています。
1y ではない方の obniz を使用する場合は、左側の マイクロB-USBコネクタ横の J1が使用できます。その際は+端子を間違えないようにだけ注意です。

接続図

具体的な接続先は下記のとおりです。

obniz端子 接続先 端子 線色 備考
0 左モータ +
1 左モータ -
2 右モータ +
3 右モータ -
4 左LED アノード側抵抗
5 左LED カソード
6 右LED アノード側抵抗
7 右LED カソード
8 HC-SR04 GND
9 HC-SR04 ECHO
10 HC-SR04 TRIGGER
11 HC-SR04 vcc
バッテリ GND GROVEコネクタ経由でM5-Camera
3.3V 未接続
バッテリ 5V SW, GROVEコネクタ経由でM5-Camera

表中の線色は、写真に合わせた色となりますが、電源の赤黒以外は手持ちのものを自由に決めてもらっていいと思います。

先にモーター以外を配線してしまいます
モーター以外を配線

3-4. 全体組み立て

モーターも配線しながら、カムロボ本体に組付けていきます。
カムロボ本体に組付け

組付けたら電源入れて軽くテストをします。
キャプションを入力できます

4. ソフトウェア

プログラムとしては、 M5カメラのプログラム (c/c++) と obniz のプログラム (html + java script) の2段構成です。
それ以外にも、webページ側のアイコンイメージデータが必要です。

4-1. M5カメラプログラム

Arduino環境(ESP32 or M5シリーズ用環境設定済み)で書き込みます。
ボード設定は [ツール] - [ボード:xxxx] - [Esp32 Arduino] - [ESP32 Wrover Module] を選びます。
Arduino のボード設定は "ESP32 Wrover Module"
M5カメラ接続語 [ツール] - [シリアルポート] でポートを選択しておきます。

ソフトは [ファイル] - [スケッチ例] - [ESP32] - [Camera] - [Camera Web Server] で選択したスケッチ例をベースにします。
Arduinoでスケッチ例を選択

基本的にサンプルソフトのまま、 WiFi 環境だけ自分の物に書き換えます。
具体的には以下の設定 "ssid" と "password" を自宅のルーターに設定したものに置き換えます。

.CameraWebServer.c

・・・中略・・・ const char* ssid = "*********"; const char* password = "*********"; ・・・中略・・・

※ 公式サンプルベースの為、変更が必要な部分だけを抜粋しています。

4-2. obniz 側ソース

obniz の基本制御画面にカメラ画像をインラインフレーム ("iframe" )で埋め込んだ構成となっています。
自分のクラウドスペースにプログラムを作成し、以下を張り付けて編集します。
変更箇所は以下の通り、

  • obniz ID
    "// ⇐自分の obniz の ID を入力" と書いている部分を自分の obniz の IDに置き換えます。

  • M5Cemara の IPアドレス
    src="http://192.168.xxx.xxx:81/stream"
    の部分の xxx のところを、検出されたIPアドレスで置き換えます。
    IPアドレスは、M5カメラを PCに繋いで、シリアル通信ソフトや Arduino のシリアルモニター(115200bps設定) で確認できます。赤枠のところに出力されるのが IPアドレスです。

IPアドレスを確認

M5のサンプルプログラムは DHCP用なので、自動的にIPが決まりますが、環境によっては毎回変わる可能性もあります。(私のところは毎回同じになりますが)
その場合はM5のプログラム側を静的IPに改造するか、 IP Scanner ソフトで調べるといいです。
IP Scanner ソフトで確認

CamRobot_WebCam_RemoteControl.html

<html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" /> <link rel="stylesheet" href="/css/starter-sample.css" /> <script src="https://code.jquery.com/jquery-3.2.1.min.js"></script> <script src="https://unpkg.com/obniz@3.13.0/obniz.js" crossorigin="anonymous" ></script> </head> <body> <div id="obniz-debug"></div> <table border="0"> <tr> <th> <div class="Control"> <h3 class="Control">Control</h3> <table border="0"> <tr> <td><input type="image" id="go-foreleft" src="./Arrow-ForwardLeft.jpg" width="25%" alt="左前"> </td> <td><input type="image" id="go-forward" src="./Arrow-Up.jpg" width="25%" alt="前進"> </td> <td><input type="image" id="go-foreright" src="./Arrow-ForwardRight.jpg" width="25%" alt="右前"> </td> <td><input type="image" id="led-on" src="./Led-On.jpg" width="25%" alt="LEDオン"> </td> </tr> <tr> <td><input type="image" id="turn-left" src="./Arrow-TurnLeft.jpg" width="25%" alt="左回転"> </td> <td><img type="image" id="empty1" src="./Empty.jpg" width="25%" alt=""> </td> <td><input type="image" id="turn-right" src="./Arrow-TurnRight.jpg" width="25%" alt="右回転"> </td> <td><img type="image" id="empty2" src="./Empty.jpg" width="25%" alt=""> </td> </tr> <tr> <td><input type="image" id="go-backleft" src="./Arrow-BackwardLeft.jpg" width="25%" alt="左後"> </td> <td><input type="image" id="go-backward" src="./Arrow-Down.jpg" width="25%" alt="後進"> </td> <td><input type="image" id="go-backright" src="./Arrow-BackwardRight.jpg" width="25%" alt="右後"> </td> <td><input type="image" id="led-off" src="./Led-Off.jpg" width="25%" alt="LEDオフ"> </td> </tr> <tr> </tr> </table> </div> </th> <th> <center> <iframe id="camera_inline" width="325" height="245" src="http://192.168.xxx.xxx:81/stream" allow-scripts > </iframe> </center> </th> <tr> </Table> <script> var obniz = new Obniz("xxxx-xxxx"); // ⇐自分の obniz の ID を入力 obniz.onconnect = async function() { // ===== [変数初期化] ===== var stopping_dist = false; var moving_forward = false; // ===== [端子初期設定] ===== var motorL = obniz.wired("DCMotor", {forward:1, back:0}); var motorR = obniz.wired("DCMotor", {forward:2, back:3}); var ledL = obniz.wired("LED", { anode: 4, cathode: 5 }); var ledR = obniz.wired("LED", { anode: 6, cathode: 7 }); var dist_sensor = obniz.wired("HC-SR04", {gnd:8, echo:9, trigger:10, vcc:11}); // ===== [LED初期化] ===== ledL.off(); ledR.off(); // ===== [モーター初期化] ===== motorL.power(80); motorR.power(80); motorL.move(false); motorR.move(false); stop(); // ===== [ディスプレイ初期化] ===== obniz.display.clear(); obniz.display.print("Connected"); // ===== [停止] ===== function stop() { obniz.display.clear(); obniz.display.print("STOP"); motorL.stop(); motorR.stop(); //ledL.off(); //ledR.off(); } // ===== [左前] ===== $("#go-foreleft").on("mousedown", function() { obniz.display.clear(); obniz.display.print("ForwardLeft"); if ( stopping_dist == true ) { // 障害物検知で止める stop(); } else { moving_forward = true; motorL.power(40); motorR.power(80); motorL.forward(); motorR.forward(); } }); $("#go-foreleft").on("mouseup", function() { stop(); }); // ===== [前進] ===== $("#go-forward").on("mousedown", function() { obniz.display.clear(); obniz.display.print("Forward"); if ( stopping_dist == true ) { // 障害物検知で止める stop(); } else { moving_forward = true; motorL.power(80); motorR.power(80); motorL.forward(); motorR.forward(); } }); $("#go-forward").on("mouseup", function() { stop(); }); // ===== [右前進] ===== $("#go-foreright").on("mousedown", function() { obniz.display.clear(); obniz.display.print("ForwardRight"); if ( stopping_dist == true ) { // 障害物検知で止める stop(); } else { moving_forward = true; motorL.power(80); motorR.power(40); motorL.forward(); motorR.forward(); } }); $("#go-foreright").on("mouseup", function() { stop(); }); // ===== [左回転] ===== $("#turn-left").on("mousedown", function() { obniz.display.clear(); obniz.display.print("TurnLeft"); motorL.power(60); motorR.power(60); motorL.reverse(); motorR.forward(); }); $("#turn-left").on("mouseup", function() { stop(); }); // ===== [右回転] ===== $("#turn-right").on("mousedown", function() { obniz.display.clear(); obniz.display.print("TurnRight"); motorL.power(60); motorR.power(60); motorL.forward(); motorR.reverse(); }); $("#turn-right").on("mouseup", function() { stop(); }); // ===== [左後進] ===== $("#go-backleft").on("mousedown", function() { obniz.display.clear(); obniz.display.print("Left"); motorL.power(40); motorR.power(80); motorL.reverse(); motorR.reverse(); }); $("#go-backleft").on("mouseup", function() { stop(); }); // ===== [後進] ===== $("#go-backward").on("mousedown", function() { obniz.display.clear(); obniz.display.print("Back"); motorL.power(80); motorR.power(80); motorL.reverse(); motorR.reverse(); }); $("#go-backward").on("mouseup", function() { stop(); }); // ===== [右後進] ===== $("#go-backright").on("mousedown", function() { obniz.display.clear(); obniz.display.print("Right"); motorL.power(80); motorR.power(40); motorL.reverse(); motorR.reverse(); }); $("#go-backright").on("mouseup", function() { stop(); }); // ===== [LEDオフ] ===== $("#led-off").on("mousedown", function() { obniz.display.clear(); obniz.display.print("LedOff"); ledL.off(); ledR.off(); }); // ===== [LEDオン] ===== $("#led-on").on("mousedown", function() { obniz.display.clear(); obniz.display.print("LedOn"); ledL.on(); ledR.on(); }); // ===== [距離センサーチェック処理] ===== obniz.onloop = async function(){ // 距離を測定 const dist = await dist_sensor.measureWait(); if (dist) { if (dist < 150) // 15cm 未満になったら { stopping_dist = true; if (moving_forward) { // 前進中なら止める moving_forward = false; stop(); } } else { stopping_dist = false; } } await obniz.wait(100); } } </script> </body> </html>

4-3. アイコンイメージ

気に入ったアイコンが見つからなかったり使用条件など厳しいところも多かったりと、そんなことに縛られるのも嫌だったため、アイコンイメージは Excel の作図機能で作成して画像処理ソフトで加工(回転サイズ・位置揃え程度)した自作オリジナルのものです。

使ってもらえるように、画像を張り付けてみたのですが記事自体が冗長になってしまいました。(すみません)

記事内の画像を右クリックし、[名前を付けて画像を保存] を選択し、画像の左上部緑バックのキャプションについている名前を付けて保存します。
それらのデータを、obniz クラウドの html ファイルを置いたのと同じ場所に保存します。
但し、これらのアイコンデータをこの記事に関連するもの以外の目的に無断で使用するのはご遠慮ください。
Arrow-ForwardLeft.jpg
Arrow-Up.jpg
Arrow-ForwardRight.jpg
Arrow-TurnLeft.jpg
Empty.jpg
Arrow-TurnRight.jpg
Arrow-BackwardLeft.jpg
Arrow-Down.jpg
Arrow-BackwardRight.jpg
Led-On.jpg
Led-Off.jpg

5. 操作説明

5-1. 起動方法

obniz のクラウドに保存している html を開き、
① [実行] の横の ▼ を押して
② [新しいタブで実行] を選ぶと立ち上がります。
起動方法

5-2. 操作方法

起動すると以下の写真のような操作画面が表示されます。

操作画面

青枠部分が操作アイコンで、赤枠部分が M5カメラからの映像となります。(カメラ画像には怪しい人物が映り込んでいるのは気にしないでください(゚Д゚;))
直感的に操作できるようなアイコンにはしていますが、番号を振ってそれぞれ説明します。
まずは動かしてみたほうが早いとは思いますが…

① 左前進します
② 前進します
③ 右前進します
④ 左回転(反時計回り)します
⑤ 右回転(時計回り)します
⑥ 左後進します
⑦ 後進します
⑧ 右後進します
⑨ LEDを点けます
⑩ LEDを消します

①~⑧は押し続けている間動作し続け離すと止まります。
離した位置がアイコンから離れてしまうと止まりませんので(課題)ご注意ください。

6. 残課題

今回はまず、obniz を使ってカメラ画像を見ながらロボットの遠隔操作ができるところを目指しました。
基本的な動作は実装・確認できましたが、コンテスト締め切りまでの限られた時間の中で今回は断念した以下の課題があります。

折をみてやってみたいと思いますが、本記事を見た方の方で、色々試行錯誤して挑戦いただくのも面白いかと思います。

① 押しっぱなし状態の件
離した位置がアイコンから離れてしまうと止まらなくなります。

② スマホでの操作
LED オン/オフ 等単発で動作するものはスマホでも動作しましたが、制御アイコンの方は押し続けるとメニューをポップアップしてしまうという、スマホに特化した使用の為制御できませんでした。別途対策が必要です。

③ カメラ映像はローカルネットワーク通信
カメラの映像はローカルLANないでの通信になりますが、外部ネットワークにつなげれると外出先から家の中のロボットを操作できるようになります。(夢!?)
外部ネットワークにもつなぎに行くにはある程度の帯域幅が必要なのとプロトコルをどうするかや、M5 カメラ側のソフトの作り直しが必要です。

④ カメラ映像側は非暗号化通信
最近のブラウザは、どれも同じ html ページ内に SSL 対応(暗号化対応)済みの物と、SSL非対応の物を混在できないようになっています。M5カメラ側のサンプルは SSL非対応の物でしたので、必然的に obniz 側の通信も非対応になっています。対応サウルには M5カメラ側の改造も必要ですが、何よりもSSL対応により暗号化処理の為カメラ側の負荷が高くなる可能性があります。

⑤ 後側センサー
前向きにはセンサーが付いていて、障害物を検知すると止まりますが、後ろ向きだと止まってくれず、色々なものをなぎ倒していきます。後ろ側にもセンサが欲しいところです。まずは、複数ある GND 端子を共通化してピンの空きを作る等しないといけないです。

⑥ 他の車体にも挑戦
色んな車体を試行錯誤する中で、一番安定して動作したカムロボを今回は最終的に選びました。他にも Zoomo や RoverC を使ってもみましたが、そちらも安定した動作が出来たら記事にしてみたいと思います。

⑦ ジョイステックでの操作
もっと簡単に操作したいので、ジョイスティックに対応したいと思っています。もう一台の obniz 使うか、M5StickC あたりを使えばできそうですが、時間が足りませんでしたので、残課題としています。

TakSan0のアイコン画像
大阪在住の本業組込み系SEです。ハードに近いところのソフト屋さんです。 電子工作は趣味でやっています。 よろしくお願いします。 ▼こちらにも別の作品を投稿しています。参考まで。 https://protopedia.net/prototyper/taksan
  • TakSan0 さんが 2021/05/16 に 編集 をしました。 (メッセージ: 初版)
  • TakSan0 さんが 2021/05/16 に 編集 をしました。 (メッセージ: 図の追加と追加。本文の誤記少し修正)
  • TakSan0 さんが 2021/05/16 に 編集 をしました。 (メッセージ: システム構成への図と説明を追加、他修正)
ログインしてコメントを投稿する