インターネットから操作可能なIoTプラレールを作成してみました。その手順を残しておきます。
機能
- インターネット上からプラレールの前方カメラの映像を確認可能
- インターネット上からプラレレールの前進・後進およびその速度を100段階で変則可能。スムーズな発進・停止が可能
- ブラウザによるカメラ映像の確認と操縦が行える。
用意したもの。
- プラレールの車体(ドクターイエロー)
- Raspberry pi zero w
- コンビニで買ったスマホ用バッテリー(ラズパイ及びモーター用電源)
- 余ってたモータードライバ(TA7291A)
- 電圧変換器(http://amzn.to/2rCJZlJ)
- Raspberry pi camera v2
先に言っておきますが、作るぐらいなら買ったほうが・・・という気もしますが。まあ、自分で作ることに意義があるのです。ちなみに本物では車窓からの画像も表示できるようですね。
お約束:当ウェブサイトの情報により生じた、いかなる損害等に関しましても、一切責任を負うものではありません。本サイト掲載情報の利用によって利用者等に何らかの損害が発生したとしても、かかる損害については一切の責任を負うものではありません。
では作っていきます。
車体の作成
まずはカメラがマウントできないと意味がありません。先頭車両にカメラを配置します。使用するのは手元にあったラズパイカメラ。これをいい感じに先頭車両にマウントしてやります。今回は3Dプリンタでカメラマウントを自作、カメラ部分を持ち上げ配置します。これが一番大変な作業でした。
ラズパイzero用のマウンタも作成しラズパイも先頭車両に収めてしまいます。
コンビニで買ってきたモバイルバッテリーは殻割りして中央車両に埋め込みます。できるだけ車体に穴は開けたくないので、充電用のUSBソケットが車両のドアから覗いているような感じで仕上げました。
後部車両には電圧変換器を収めます。モーターの定格電力は1.5vで、モバイルバッテリーが5Vのため、電圧を落としてからモーターに接続します。
ラズパイのセットアップ
Raspberry pi zero wの基本的な設定をしていきます。といっても他のラズパイシリーズとやることは同じです。無線LANで自宅のルーターに接続できるように設定しておきます。
カメラ画像の配信はMJPG-Streamerを使います。これをインストール&セットアップするだけでラズパイカメラの映像をインターネット上から確認することができます。
スピート調整はモータードライバを使います。TA7291Aの詳しい使い方はこちらを参考にしてください。
モーターはTA7291Aに接続しています。GPIOを操作することで、前進、後進が可能で速度を調整することが可能です。
また、ソフトウェアPWMを行いたいので、WiringPiをインストールしておきます。
WiringPiのインストール
sudo apt-get install libi2c-dev sudo apt-get install git sudo apt-get install git-core sudo apt-get install build-essential cd /opt sudo git clone git://git.drogon.net/wiringPi cd wiringPi sudo ./build cd /opt sudo git clone https://github.com/WiringPi/WiringPi-Python.git cd WiringPi-Python sudo git submodule update --init
このインストールは必須ではありません。コマンドラインからGPIOをテストできたり、GPIOの状態を表示したりとデバッグで使います。
GPIOの操作にはWebIOPiを使用します。WebからI/Oを制御できます。PWMの制御も出来ます。正直これ一つあればWebと連動したシステムは簡単に構築できるという優れものです。
Raspberry Pi2 以降でも使えるように、WebIOPiの0.7.1にパッチを当てて使用します。
$ wget http://sourceforge.net/projects/webiopi/files/WebIOPi-0.7.1.tar.gz $ tar xvzf WebIOPi-0.7.1.tar.gz $ cd WebIOPi-0.7.1 $ wget https://raw.githubusercontent.com/doublebind/raspi/master/webiopi-pi2bplus.patch $ patch -p1 -i webiopi-pi2bplus.patch $ sudo ./setup.sh
setup.shを実行するとPythonのインストールも行われます。
途中、
Do you want to access WebIOPi over Internet ? [y/n]
と聞かれた場合は「n」を入力してEnterを押します。
次に、WebIOPiを起動するための設定です。
$ cd /etc/systemd/system/ $ sudo wget https://raw.githubusercontent.com/doublebind/raspi/master/webiopi.service # 起動 $ sudo systemctl start webiopi # 自動起動on $ sudo systemctl enable webiopi
動作確認です。Raspberry Pi のポート8000にアクセスします。認証ダイアログではデフォルトのパスワード
ユーザー名:webiopi
パスワード:raspberry
を入力します。今回はおもちゃとして作成するのでパスワードは邪魔です。このパスワード設定を削除するには/etc/webiopi/passwdを削除してWebIOPiを再起動します。
$ sudo mv /etc/webiopi/passwd /etc/webiopi/passwd.backup $ sudo systemctl restart webiopi
GPIOを制御するプログラムを作成していきましょう。
/home/pi/work/webiopi/script.py を作成します
import webiopi GPIO = webiopi.GPIO VREF = 14 IN1 = 15 IN2 = 18 def setup(): # Set GPIO to PWM GPIO.setFunction(VREF, GPIO.PWM) # Set GPIO to OUT GPIO.setFunction(IN1 , GPIO.OUT ) GPIO.setFunction(IN2 , GPIO.OUT ) # Run GPIO.pwmWrite(VREF , 1) def destroy(): # stop GPIO.pwmWrite(VREF , 0) @webiopi.macro def forwardTrain(): GPIO.digitalWrite(IN1, True ) GPIO.digitalWrite(IN2, False ) @webiopi.macro def backwardTrain(): GPIO.digitalWrite(IN1, False ) GPIO.digitalWrite(IN2, True ) @webiopi.macro def stopTrain(): GPIO.digitalWrite(IN1, False ) GPIO.digitalWrite(IN2, False )
/home/pi/work/webiopi/index.html を作成します
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <meta name="viewport" content="width=device-width"> <title>運転席</title> <script type="text/javascript" src="/webiopi.js"></script> <script type="text/javascript"> var imageNr = 0; // Serial number of current image var finished = new Array(); // References to img objects which have finished downloading var paused = false; function createImageLayer() { var img = new Image(); img.style.position = "absolute"; img.style.zIndex = -1; img.style.marginLeft="-160px"; img.onload = imageOnload; img.onclick = imageOnclick; img.src = "http://192.168.0.10:9000/?action=snapshot&n=" + (++imageNr); var webcam = document.getElementById("webcam"); webcam.insertBefore(img, webcam.firstChild); } // Two layers are always present (except at the very beginning), to avoid flicker function imageOnload() { this.style.zIndex = imageNr; // Image finished, bring to front! while (1 < finished.length) { var del = finished.shift(); // Delete old image(s) from document del.parentNode.removeChild(del); } finished.push(this); if (!paused) createImageLayer(); } function imageOnclick() { // Clicking on the image will pause the stream paused = !paused; if (!paused) createImageLayer(); } var IN1 = 15; var IN2 = 18; webiopi().ready(function() { var parts; parts = webiopi().createRatioSlider(14); $("#vref").append(parts); //初期値を指定 $("#ratio14").val(1); parts =webiopi().createButton("forwardButton","GO",function() { webiopi().callMacro("forwardTrain"); }) $("#forward").append(parts); parts =webiopi().createButton("stopButton","STOP",function() { webiopi().callMacro("stopTrain"); }) $("#stop").append(parts); parts =webiopi().createButton("backwardButton","BACK",function() { webiopi().callMacro("backwardTrain"); }) $("#backward").append(parts); $("#forwardButton").css('background-color','green'); $("#stopButton").css('background-color','red'); $("#backwardButton").css('background-color','blue'); }); </script> <style type="text/css"> input[type="range"] { display: block; width: 150px; height: 30px; -webkit-transform:rotate(-90deg); -moz-transform:rotate(-90deg); -o-transform:rotate(-90deg); transform:rotate(-90deg); transform-origin:right bottom; } input[type="range"]::-webkit-slider-thumb{ -webkit-appearance: none; -moz-appearance: none; appearance: none; background-color: #666; text-align:center; width: 40px; height: 40px; border:1px solid transparent; border-radius:20px; cursor:pointer; -moz-box-sizing:border-box; -webkit-box-sizing:border-box; box-sizing:border-box; } button { display: block; margin: 5px 5px 5px 5px; width: 100px; height: 45px; font-size: 24pt; font-weight: bold; color: black; } </style> </head> <body onload="createImageLayer();" bgcolor="yellow"> <div align="center"> <div id="webcam" style="width: 320px;height: 240px;" > <noscript> <img src="http://192.168.0.10:9000/?action=snapshot" /> </noscript> </div> </div> <div id="content" align="right"> <div id="vref"><label>vref</label></div> </div> <div id="forward" style="float:left;"><label>すすむ</label></div> <div id="stop" style="float:left;"><label>とまる</label></div> <div id="backward" style="float:left;"><label>さがる</label></div> </div> </body> </html>
/etc/webiopi/configを編集して、作成したプログラムを実行するように設定します。
$ sudo vim /etc/webiopi/config
[SCRIPTS] # Load custom scripts syntax : # name = sourcefile # each sourcefile may have setup, loop and destroy functions and macros #myscript = /home/pi/webiopi/examples/scripts/macros/script.py myscript = /home/pi/work/webiopi/script.py
# Use doc-root to change default HTML and resource files location #doc-root = /home/pi/webiopi/examples/scripts/macros doc-root = /home/pi/work/webiopi/ # Use welcome-file to change the default "Welcome" file welcome-file = index.html
再起動します。
sudo systemctl restart webiopi
これでネットワーク内の他のPCやスマホからラズパイにアクセスします。ではhttp://192.168.0.10:8000/にアクセスしましょう。(192.168.0.10はraspbery piに設定したIPアドレスです)
次のような画面が表示されれば成功です。
上部にはプラレールの前方映像がリアルタイムで配信されています。すすむ「GO」、とまる「STOP」、さがる「BACK」をタップしてドクターイエローを操作します。左のスライダーを上にすればパワー100%で最高速度になりスライダーを下にすれば速度が落ちます。
これで完成です。きっかけは息子の一言から。本人曰く、「プラレールの車窓から外をみてみたい」とのご要望でしたがあいにくカメラは1つしか搭載できなかったので、前方カメラだけと相成りました。