スマートフォン・ジン | Smartphone-Zine

引っ越し先→ https://smartphone-zine.com/

IoTプラレールを作ったよ

インターネットから操作可能なIoTプラレールを作成してみました。その手順を残しておきます。

機能

  • インターネット上からプラレールの前方カメラの映像を確認可能
  • インターネット上からプラレレールの前進・後進およびその速度を100段階で変則可能。スムーズな発進・停止が可能
  • ブラウザによるカメラ映像の確認と操縦が行える。

用意したもの。

先に言っておきますが、作るぐらいなら買ったほうが・・・という気もしますが。まあ、自分で作ることに意義があるのです。ちなみに本物では車窓からの画像も表示できるようですね。 お約束:当ウェブサイトの情報により生じた、いかなる損害等に関しましても、一切責任を負うものではありません。本サイト掲載情報の利用によって利用者等に何らかの損害が発生したとしても、かかる損害については一切の責任を負うものではありません。 では作っていきます。 車体の作成 まずはカメラがマウントできないと意味がありません。先頭車両にカメラを配置します。使用するのは手元にあったラズパイカメラ。これをいい感じに先頭車両にマウントしてやります。今回は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つしか搭載できなかったので、前方カメラだけと相成りました。