IoTプラレールを作ったよ

インターネットから操作可能な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つしか搭載できなかったので、前方カメラだけと相成りました。

 

ラズパイ・オーディオにチャレンジ!Pi:ゼロから始めるハイレゾ対応ポータブルアンプ生活

WP_002
WP_003
WP_004
 
低価格シングルコンピュータRaspberry piを使って、ハイレゾ・オーディオシステムを作成する「ラズパイ・オーディオ」が、オーディオファンの間で流行っているようですね。低価格でネットワーク対応の超高音質オーディオマシンが試せるのが魅力のようです。
5ドルのラズパイRaspberryPi Zeroが出た時に同好会メンバーそれぞれで購入したものの、何に使うか思いつかないまま放置していました。そのうちカメラ接続用のI/Fが搭載されたVersion1.3がリリースされ、これもまた同好会メンバーで即購入。最初に購入したゼロが完全に余ってしまいましたので、ちょっと改良して、ラズパイ・オーディオを楽しむことにしましょう。
しかもこのサイズ!これはもう、コンパクトなオーディオを作るしかないですよね!ということで次の目標を立てました。

  1. 超小型ラズパイ・オーディオ
  2. 当然ハイレゾ対応
  3. バッテリー内蔵

それではこれらをさくっと作ってしまいましょう。
まずはDAC選び。DAC(ダック)というのは、Digital/Analog converterの略。ラズパイに保存されたデジタルデータはそのままでは再生できないので、スピーカーで再生できるようにするために、デジタル信号をアナログ信号に変換するのがDACの仕事。このときの変換が音の決め手になります。
当然サイズの小さいながらも、高音質のものをチョイスしたいところ。ちょうど手元に、Zeroと一緒に購入したhatの中に(hatというのはHardware Attached on Topの事で、ラズパイにぽんと乗っければ使えるようになっている基盤の事です)pHAT DACがあったのでこれにしましょう。
pHAT DAC
そうそう、ゼロにはオーディオアウトが(HDMIだけしか)ないので不便だと思って買っておいたのでした。
このDACがなかなかの高性能で、筆者が愛用しているTEAC HA-P50-Bというポタアン(ポタアンってのはポータブル・アンプの事。持ち歩けるアンプですね)にも使われている、BurrBrown PCM5102の後継PCM5102Aがつかわれているのです。ってことは音の味付けはTEACのポタアンとほぼ同じって事です。どんな風に音の違いがでるか楽しみです。
また、このDACはI2S接続でラズパイとつながります。I2S(IC間サウンド)とは、IC間でデジタル音声データをシリアル転送するための規格で、通常、DACやADCといった、音響機器内部のIC間で音声デジタル信号を直接やり取りするための規格。
で、これが何がすごいのかというと、通常PCでハイレゾ・オーディオを楽しむためには、パソコンと外付けのDAC、そしてアンプとスピーカーを用意して、パソコンとDACを「USB」接続します。しかし、ラズパイゼロとpHAT DACを接続するのにUSBは使いません。ここがすごいところ。元々PCで外付けDACでは、やれUSBケーブルの相性だの、OSドライバがアシンクロナスモード対応してないだの、ドライバの出来に左右されるだの、といった色々面倒な気遣いが必要だったんですが、ラズパイ・オーディオではUSBを使わずIC間で直接やり取りするもっと「生」なデータで転送ができるわけで、より高品質なオーディオ再生が期待できるわけです。
次に、ポタアン化にあたり、ヘッドホンアンプが必要になります。値段も手頃でそこそこ音のいいものをチョイスしたいところ。外で気軽に高音質というのがコンセプトのポタアンにオーバースペックなアンプは必要ないでしょう。ちょうど秋月にポータブルヘッドホン用のアンプキットがあるのでこれを使用することにしました。
ポータブルヘッドフォンアンプキット
電源を用意するのは手間なので、適当なモバイルバッテリーと合わせる事にします。Zeroへの給電はUSBケーブル経由だと全体のサイズが大きくなってしまうため、モバイルバッテリーのUSBソケットの根本からラズパイに直接電源を接続します。電源のOFFをどうすのか、という点が心配になりますが、これはモバイルバッテリーに電源のON/OFFがあるため、問題ありません。

IMG_3601
赤いコードが+

ちょこっとここで欲が出てきました。できれば、ディスプレイ表示も欲しいですよね。という訳で、LCDを購入。
Raspberry Pi用I2C接続のLCDキット
この製品はI2Cインタフェースになっており比較的に簡単に接続が可能です。
という訳で、準備が整いました。さて、組み上げて行きましょう。
まずはRaspberry pi zero と pHAT DACの接続です。
全部のピンを接続しちゃってもいいのですが、基盤を見ながら、必要そうなやつだけ半田付けしていきます。まずはI2Sは必須ですね、あとは電源。具体的には以下のピンをハンダ付けします。
Pin 1 (3.3V)
Pin 2 (5V)
Pin 12 (GPIO 18)
Pin 35 (GPIO 19)
Pin 39 (Ground)
Pin 40 (GPIO 21)
LCDもサイトの説明通りハンダ付けすればOK。I2Cなので接続する本数は信号線2本、電源2本の合計4本だけで済みます。
IMG_3599
後はDACのLINE OUTをヘッドフォンアンプのLINE INへ接続します。
IMG_3600
では、肝心のOS部分とプログラミング部分です。今回オーディオ用ディストリビューションとしてVolumioを採用しました。ただここで問題が発生。現状VolumioのイメージはRaspberry Pi Zeroに対応しておらず、起動しないことがわかりました。
対応としては、まずRaspberry Pi 2で起動し、アップデートを済ませてからZeroで起動する必要があります。結構面倒ですね。また、バージョンアップによりSambaのバージョンが上がり、動作しなくなります。これも面倒な修正が必要です。
まずは、Volumio 1.55 のイメージをダウンロードしましょう。
https://sourceforge.net/projects/volumio/files/Raspberry%20PI/1.5/Volumio1.55PI.img.zip/download
ダウンロードしたイメージを16GB SDHCカードに書き込みます。次はMacの場合の例です。rdisk2はSDカードの場所です。環境にあわせて変更する必要があります。間違った場所を指定すると、PCのデータが消えてしまうので十分に注意しましょう。SDカードを指す前と、差した後にそれぞれdf -hを実行して、差分がSDカードだとわかります。

df -h
sudo diskutil unmount /dev/disk2s1
sudo dd bs=1m if=Volumio1.55PI.img of=/dev/rdisk2

このSDカードとRaspberry Pi 2で起動します。

su -

コマンドで rootになります。パスワードは volumio です。
次のコマンドを実行します。

apt-get -y update && apt-get -y upgrade && apt-get dist-upgrade && apt-get -y autoremove && apt-get -y autoclean

binutilsをインストールします。

apt-get install binutils

rpi-updateを実行します。

rpi-update

再起動します。しかし、エラーが発生してSMBが起動しません。次のコマンドで修正します。

apt-get install samba samba-doc samba-common smbclient
apt-get remove samba samba-doc samba-common smbclient --purge
apt-get autoclean
apt-get autoremove
apt-get install samba samba-doc samba-common smbclient

これで下準備はOKです。poweroff後Raspberry Pi 2からSDカードを抜き、Pi Zeroに挿し直します。
無事起動したら、pHAT DACの設定を行います。

su -
curl -sS get.pimoroni.com/phatdac | bash

パイゼロと同じネットワーク上のPCから、ラズパイのアドレスを打ち込んで volumioの画面を開きます。
設定します。
MENU > Systemを開きます。
I2S driverの項目で I2S DACにHifiberryを選択します。
スクリーンショット 2016-09-09 19.28.19
MENU > Turn offを開きます。
REBOOTします。
 
16GBのSDHCを使いましたが、ほとんど使われていません。これを使えるようにしましょう。

volumio@volumio:~$ df -h
 Filesystem Size Used Avail Use% Mounted on
 /dev/root 1.5G 1.1G 379M 74% /
 devtmpfs 237M 0 237M 0% /dev
 tmpfs 49M 260K 48M 1% /run
 tmpfs 5.0M 4.0K 5.0M 1% /run/lock
 Ramdisk 256M 0 256M 0% /run/shm
 /dev/mmcblk0p1 75M 30M 46M 40% /boot

パーティションを用意

root@volumio:~# fdisk /dev/mmcblk0

 

root@volumio:~# fdisk /dev/mmcblk0
Command (m for help): p(現在のパーティションを確認)
Disk /dev/mmcblk0: 15.8 GB, 15819866112 bytes
4 heads, 16 sectors/track, 482784 cylinders, total 30898176 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x00043284
        Device Boot      Start         End      Blocks   Id  System
/dev/mmcblk0p1   *        2048      155647       76800    b  W95 FAT32
/dev/mmcblk0p3          155648     3411967     1628160   83  Linux
Command (m for help): n(新しいパーティションを作成)
Partition type:
   p   primary (2 primary, 0 extended, 2 free)
   e   extended
Select (default p): p(プライマリ)
Partition number (1-4, default 2): 4(パーティション4)
First sector (3411968-30898175, default 3411968): エンター押す
Using default value 3411968
Last sector, +sectors or +size{K,M,G} (3411968-30898175, default 30898175): エンター押す
Using default value 30898175
Command (m for help): p(結果を確認)
Disk /dev/mmcblk0: 15.8 GB, 15819866112 bytes
4 heads, 16 sectors/track, 482784 cylinders, total 30898176 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x00043284
        Device Boot      Start         End      Blocks   Id  System
/dev/mmcblk0p1   *        2048      155647       76800    b  W95 FAT32
/dev/mmcblk0p3          155648     3411967     1628160   83  Linux
/dev/mmcblk0p4         3411968    30898175    13743104   83  Linux
Command (m for help): w(作業を書き込んで終了)
The partition table has been altered!
Calling ioctl() to re-read partition table.
WARNING: Re-reading the partition table failed with error 16: Device or resource busy.
The kernel still uses the old table. The new table will be used at
the next reboot or after you run partprobe(8) or kpartx(8)
Syncing disks.

再起動します。

reboot

再起動したら、次のコマンドで新規パーティションをフォーマットします。

mkfs.ext4 /dev/mmcblk0p4
volumio@volumio:~$ su -
Password:
root@volumio:~# mkfs.ext4 /dev/mmcblk0p4
mke2fs 1.42.12 (29-Aug-2014)
Discarding device blocks: done
Creating filesystem with 3435776 4k blocks and 860160 inodes
Filesystem UUID: ce7a4f52-e1e6-4858-9cb4-27b43a74956a
Superblock backups stored on blocks:
	32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632, 2654208
Allocating group tables: done
Writing inode tables: done
Creating journal (32768 blocks): done
Writing superblocks and filesystem accounting information: done

パーティションのマウント設定を行います。

root@volumio:~# vi /etc/fstab
# /etc/fstab: static file system information.
#
#/dev/mmcblk0p3 /               ext4    noatime,discard,data=writeback,journal_async_commit,nouser_xattr,barrier=0,errors=remount-ro 0       1
/dev/mmcblk0p3 /               ext4    noatime,nouser_xattr,errors=remount-ro 0       1
/dev/mmcblk0p1  /boot        vfat    utf8,user,rw,umask=111,dmask=000            0       0
/dev/mmcblk0p4  /mnt/USB  ext4     defaults   0  0
Ramdisk   /run/shm        tmpfs   defaults,size=256M,noexec,nodev,nosuid        0       0

設定を有効にするため再起動します。

reboot

13GBの領域が確保されました。

volumio@volumio:~$ df -h
Filesystem      Size  Used Avail Use% Mounted on
/dev/root       1.5G  1.1G  379M  74% /
devtmpfs        237M     0  237M   0% /dev
tmpfs            49M  264K   48M   1% /run
tmpfs           5.0M  4.0K  5.0M   1% /run/lock
Ramdisk         256M     0  256M   0% /run/shm
/dev/mmcblk0p1   75M   30M   46M  40% /boot
/dev/mmcblk0p4   13G   33M   13G   1% /mnt/USB

 
samba設定ファイルの変更
sambaの再インストールにより設定ファイルが書き換わっています。先ほど拡張したmicroUSBの領域が他のコンピュータからアクセスできるように設定しましょう。
# nano /etc/samba/smb.conf

[share]
 comment = MPD directory (/mnt/USB/)
 path = /mnt/USB/
 guest ok = yes
 read only = no
 writable = yes
 available = yes
 browsable = yes
 public = yes
 follow symlinks = yes
 wide links = yes
 create mode = 0777
 directory mode = 0777
 share modes = yes

LCDのインストール

sudo apt-get install python-smbus
sudo apt-get install i2c-tools
volumio@volumio:~$ sudo i2cdetect -y 1
 0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: 20 -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

20が表示されていれば接続はOK

サンプルコードの実行
sudo apt-get update
sudo apt-get install build-essential python-dev python-smbus python-pip git
sudo pip install RPi.GPIO
su -
git clone https://github.com/adafruit/Adafruit_Python_CharLCD.git
cd Adafruit_Python_CharLCD
sudo python setup.py install

実行

cd examples
sudo python char_lcd_plate.py

サンプルが実行されます。このサンプルを見ながら、LCDディスプレイに現在表示中の曲名を表示するプログラムを作成していきます。

ポイントは、Music Player Daemon(mpc)コマンドです。このコマンドで曲名の取得、次曲、前曲、再生、一時停止などを行います。

コードは次のようになりました。突貫で作成したので所々無駄なコードが残っています。

#!/usr/bin/python
# Example using a character LCD plate.
import time
import os
import sys
import threading
import commands
import Adafruit_CharLCD as LCD
import logging
import time
import subprocess
import signal
isLcdOn = True
runFlg = True
lcdTimer = None
#logging.basicConfig(level=logging.DEBUG,
#                    format='(%(threadName)-10s) %(message)s',
#                    )
# Initialize the LCD using the pins
lcd = LCD.Adafruit_CharLCDPlate()
def lcd_off():
    global lcdTimer
    lcd.set_backlight(0)
    lcdTimer=None
def show_song():
    while runFlg:
        # show song name. wait change event by 'mpc idle' command
        global idle
        global lcdTimer
        idle = subprocess.Popen('mpc idle player', shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        sts = idle.communicate()
        if sts !='':
            st = commands.getoutput('mpc | kakasi -Jk -Hk -Kk -Ea -s -i utf-8 -o sjis')
            st = st.replace('(kigou)','-')
            spl=st.splitlines(True)
            if len(spl)== 3:
                song=spl[0].split(" - ",1)
                lcd.clear()
                lcd.message(song[1][0:16].strip() + '\n' + song[0][0:16].strip())
                if not(lcdTimer is None):
                    lcdTimer.cancel()
                lcdTimer=threading.Timer(5,lcd_off)
                lcdTimer.start()
    print('stop show_song thread')
# create some custom characters
lcd.create_char(1, [2, 3, 2, 2, 14, 30, 12, 0])
lcd.create_char(2, [0, 1, 3, 22, 28, 8, 0, 0])
lcd.create_char(3, [0, 14, 21, 23, 17, 14, 0, 0])
lcd.create_char(4, [31, 17, 10, 4, 10, 17, 31, 0])
lcd.create_char(5, [8, 12, 10, 9, 10, 12, 8, 0])
lcd.create_char(6, [2, 6, 10, 18, 10, 6, 2, 0])
lcd.create_char(7, [31, 17, 21, 21, 21, 21, 17, 31])
# Show some basic colors.
lcd.set_color(1.0, 0.0, 0.0)
lcd.clear()
# Make list of button value, text, and backlight color.
buttons = ( (LCD.SELECT, 'Play/Pause', 'mpc toggle'),
            (LCD.LEFT,   'Prev'  , 'mpc prev'),
            (LCD.UP,     'LCD ON/OFF', ' '),
            (LCD.DOWN,   'POWER OFF ?\nYES           NO'  , 'mpc seek +00:00:10'),
            (LCD.RIGHT,  'Next' , 'mpc next') )
print('Press Ctrl-C to quit.')
t=threading.Thread(target=show_song)
t.daemon = True
t.start()
# do finish process when pressed Ctrl+C
def func_stop(signal, handler):
    lcd.clear()
    lcd.set_color(0.0, 0.0, 0.0)
    runFlg = False
    idle.terminate()
    print('ext')
    time.sleep(1)
    sys.exit(0)
signal.signal(signal.SIGINT, func_stop)
signal.signal(signal.SIGTERM, func_stop)
while True:
    # Loop through each button and check if it is pressed.
    for button in buttons:
        if lcd.is_pressed(button[0]):
            lcd.set_backlight(1)
            # Button is pressed, change the message and backlight.
            logging.debug(button[1])
            if button[0]!=LCD.UP:
                lcd.clear()
                lcd.message(button[1])
            #lcd.set_color(button[2][0], button[2][1], button[2][2])
            if button[0]==LCD.UP:
                if isLcdOn==True:
                    lcd.set_backlight(0)
                    isLcdOn=False
                    print('LCD OFF')
                else:
                    lcd.set_backlight(1)
                    isLcdOn=True
                    print('LCD ON')
            elif button[0]==LCD.DOWN:
                while True:
                    if lcd.is_pressed(LCD.LEFT):
                        lcd.message('power off')
                        time.sleep(0.5)
                        lcd.set_backlight(0)
                        os.system('poweroff')
                    elif lcd.is_pressed(LCD.RIGHT):
                        break
            else:
                os.system(button[2])
            time.sleep(1.0)

Raspberry Pi Zeroの起動時にこのスクリプトを起動するようにしておけばバッチリです。
最初はアクリル板で作成していましたが、同好会メンバーが木箱を作ってくれました。木箱にあわせてアクリルをミラー調スプレーで塗装すると、かなりいい雰囲気がでました!

WP_005
木製のケースに収めている所。ミラー調のアクリルと合わせていい雰囲気です。

音楽再生アプリケーションとしては定番のVolumioを使っています。そのため、Volumioでできることはひと通り出来ます。持ち歩かない場合にはネットワークオーディオとして楽しむことが出来ます。
・SDカード内の音楽ファイル再生
・USB接続されたストレージ内の音楽再生
・NAS上の音楽ファイル再生
・ネットラジオ
・DLNA対応
・Airplay対応
・ブラウザ等からコントロール可能
IMG_5987_small
PCのブラウザやiPhoneから操作することも可能

さて、これで一通り完成です!今回はLCDディスプレイが結構大きくて、採用すべきかどうか最後まで悩みました。最終的には利便性を優先して採用しましたが、全体の半分近いサイズを占有しており、まだ満足してません。今後はLCDディスプレイを「電子ペーパー」に置き換え、劇的にコンパクトなプレイヤーに仕上げたいと画策中です!
 

Raspberry Pi 2 model B に Windows10 IoT Core をのせてLチカするまで

米Microsoftが4月29日より提供を開始した「Windows 10 IoT Core Insider Preview」をRaspberry Pi 2 model B にインストールしLEDを光らせてみるまでの手順です。

環境および物品

  • Raspberry Pi 2 Model B
  • 赤色LED
  • 1kΩ抵抗
  • ブレッドボードとジャンパコード2本
  • ホストコンピュータ:iMac
  • VM:Parallels Desktop 10.2

Windows10を用意する

Raspberry Pi用のWindows10 IoTをSDカードに書き込むために、 Windows10を用意します。今回手元にWindowsマシンがないためMacのバーチャルマシン上にWindows10をインストールします。今回はParallels Desktopを使用しましたが、他のVMでも可能だと思われます。

スクリーンショット 2015-05-06 16.29.22
ParallelsのWindows10インストール機能でインストールされるバージョンは若干古いバージョンになってしまう

Parallels DesktopにはWindows10を自動的にダウンロード、インストールする機能がありますが、これは失敗でした。インストールされるWindows10が若干古く、Windows 10 IoTをSDカードに書き込めません。Microsoftより最新のWindows10をダウンロードしてそのイメージを使用してインストールするようにしてください。
http://windows.microsoft.com/ja-jp/windows/preview-iso
スクリーンショット 2015-05-06 16.33.39
Parallelsの新規仮想マシンから、「DVD/イメージファイルからWindows/その他OSをインストール」を選択し、ダウンロードしたWindows10のイメージを選択し、OSをインストールします。
スクリーンショット 2015-05-06 16.35.27
 

Windows10IoTをSDカードに書き込む

インストールが完了したらMacにSDカードを挿入、Windows10に接続します。
Windows10IoTをダウンロードするにはサインインが必要です。
https://connect.microsoft.com/windowsembeddedIoT
下記URLよりWindows10IoTをダウンロードします。
https://connect.microsoft.com/windowsembeddedIoT/Downloads/
Windows_IoT_Core_RPI2_BUILD.zipをダウンロードしてください。
Windows10でコマンドプロンプトを管理者として実行します。次のコマンドを実行し、SDカードの位置を確認します。

diskpart
list disk
exit
C:\Windows\system32>diskpart
Microsoft DiskPart バージョン 10.0.10074
Copyright (C) 1999-2013 Microsoft Corporation.
コンピューター: WIN-HIAMN2HUDSC
DISKPART> list disk
  ディスク      状態           サイズ   空き   ダイナ GPT
  ###                                          ミック
  ------------  -------------  -------  -------  ---  ---
  ディスク 0    オンライン            64 GB      0 B
  ディスク 1    オンライン            14 GB  5120 KB
DISKPART> exit
DiskPart を終了しています...
C:\Windows\system32>

上記より、ディスク1がSDカードだと判ります。
ダウンロードしたZIPファイルを展開します。コマンドプロンプトで展開したフォルダ(flash.ffuがあるフォルダ)に移動します。そして次のコマンドを実行します。PhysicalDriveNのNに、先ほど判明したディスク番号を指定します。

dism.exe /Apply-Image /ImageFile:flash.ffu /ApplyDrive:\\.\PhysicalDrive1 /SkipPlatformCheck

これでSDカードにWindows10 IoTがインストールされます。
dism.exeでエラー:87 が表示される場合、Windows10のバージョンが古くないか確認してください。

>dism.exe /Apply-Image /ImageFile:flash.ffu /ApplyDrive:\\.\PhysicalDrive1 /SkipPlatformCheck
展開イメージのサービスと管理ツール
バージョン: 10.0.9926.0
エラー: 87
/applydrive オプションはこのコンテキストでは認識されません。
詳細については、ヘルプを参照してください。
DISM ログ ファイルは C:\Windows\Logs\DISM\dism.log にあります

Windows 10 IoTの起動

Windows 10 IoTを入れたSDカードをRaspberry Pi 2に差し込み、Raspberry Pi2にLANケーブルを接続した後、電源を接続して起動します。次のような画面になりIPアドレスが割り当てられていればOKです。
IMG_0773
 

接続の確認

Windows8.1にWindows_IoT_Core_RPI2_BUILD.zipに含まれるWindowsDeveloperProgramForIoT.msiをインストールします。起動すると接続されているWindows10IoTCoreとIPアドレスが表示されればOKです。
 
スクリーンショット 2015-05-06 17.42.13
 
右クリックからWeb Browser Hearを開くと、ステータスなどを表示することができます。
スクリーンショット 2015-05-06 17.48.42

開発環境の準備

開発環境にはVMにインストールしたWindows8.1を使用します。ますVisual Studio 2015 RC  Community Editonをインストールします。
https://www.visualstudio.com/en-us/downloads/visual-studio-2015-downloads-vs.aspx
スクリーンショット 2015-05-06 17.29.35
インストールできたら起動できることを確認しておきましょう。起動時にライセンス確認のためユーザーIDを求められます。
スクリーンショット 2015-05-04 7.11.18

回路を用意する

それでは、Microsoftのサンプル通りにLEDを光らせてみましょう。サンプルはBlinky Sampleです。
サンプル通り、GPIOの5番(ピン29)と3.3V Pwr(ピン1)を使用し、抵抗とLEDを配置します。
Untitled Sketch 2_ブレッドボード
それでは、新しいプロジェクトを作成しましょう。「新しいプロジェクト…」をクリックします。
スクリーンショット 2015-05-04 5.21.52
 
Windows Universalより、Blank Appを選択してください。アプリケーション名は今回App1(デフォルト)としました。
スクリーンショット 2015-05-04 5.23.11
 
プロジェクトに参照を追加します。ソリューションエクスプローラーの参照から、参照の追加を開きます。
スクリーンショット 2015-05-04 5.26.12
 
Windows IoT Extension SDKにチェックを入れます。
スクリーンショット 2015-05-04 5.27.01
ユーザーインタフェースを作成するため、MainPage.xamlを編集します。

<Page
    x:Class="App1.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:App1"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">
    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
            <Ellipse x:Name="LED" Fill="LightGray" Stroke="White" Width="100" Height="100" Margin="10"/>
            <TextBlock x:Name="DelayText" Text="500ms" Margin="10" TextAlignment="Center" FontSize="26.667" />
            <Slider x:Name="Delay" Width="200" Value="500" Maximum="1000" LargeChange="100" SmallChange="10" Margin="10" ValueChanged="Delay_ValueChanged" StepFrequency="10"/>
            <TextBlock x:Name="GpioStatus" Text="Waiting to initialize GPIO..." Margin="10,50,10,10" TextAlignment="Center" FontSize="26.667" />
        </StackPanel>
    </Grid>
</Page>

スクリーンショット 2015-05-06 17.58.54
MainPage.xaml.csを編集します。

using System;
using Windows.Devices.Gpio;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Media;
namespace App1
{
    public sealed partial class MainPage : Page
    {
        public MainPage()
        {
            InitializeComponent();
            this.timer = new DispatcherTimer();
            this.timer.Interval = TimeSpan.FromMilliseconds(500);
            this.timer.Tick += Timer_Tick;
            this.timer.Start();
            Unloaded += MainPage_Unloaded;
            InitGPIO();
        }
        /// <summary>
        /// GPIOを初期化します。
        /// </summary>
        private void InitGPIO()
        {
            var gpio = GpioController.GetDefault();
            // GPIOコントローラが存在しない場合はエラーを表示します
            if (gpio == null)
            {
                pin = null;
                GpioStatus.Text = "There is no GPIO controller on this device.";
                return;
            }
            // LED_PIN(=5)を開きます。
            pin = gpio.OpenPin(LED_PIN);
            // ピンが正しく初期化されなかった場合はエラーを表示します
            if (pin == null)
            {
                GpioStatus.Text = "There were problems initializing the GPIO pin.";
                return;
            }
            // LED_PIN(=5)をHighに設定
            pin.Write(GpioPinValue.High);
            // 出力に設定します
            pin.SetDriveMode(GpioPinDriveMode.Output);
            GpioStatus.Text = "GPIO pin initialized correctly.";
        }
        private void MainPage_Unloaded(object sender, object args)
        {
            // クリーンアップ
            pin.Dispose();
        }
        /// <summary>
        /// LEDを反転する
        /// </summary>
        private void FlipLED()
        {
            if (LEDStatus == 0)
            {
                LEDStatus = 1;
                if (pin != null)
                {
                    // LEDを点灯するために、PINを'low'に設定する
                    pin.Write(GpioPinValue.Low);
                }
                LED.Fill = redBrush;
            }
            else
            {
                LEDStatus = 0;
                if (pin != null)
                {
                    pin.Write(GpioPinValue.High);
                }
                LED.Fill = grayBrush;
            }
        }
        /// <summary>
        /// LEDを消灯します
        /// </summary>
        private void TurnOffLED()
        {
            if (LEDStatus == 1)
            {
                FlipLED();
            }
        }
        /// <summary>
        /// タイマーでLEDを点灯・消灯を繰り返します
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Timer_Tick(object sender, object e)
        {
            FlipLED();
        }
        /// <summary>
        /// 画面に表示します
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Delay_ValueChanged(object sender, RangeBaseValueChangedEventArgs e)
        {
            if (timer == null)
            {
                return;
            }
            if (e.NewValue == Delay.Minimum)
            {
                DelayText.Text = "Stopped";
                timer.Stop();
                TurnOffLED();
            }
            else
            {
                DelayText.Text = e.NewValue + "ms";
                timer.Interval = TimeSpan.FromMilliseconds(e.NewValue);
                timer.Start();
            }
        }
        private int LEDStatus = 0;
        private const int LED_PIN = 5;
        private GpioPin pin;
        private DispatcherTimer timer;
        private SolidColorBrush redBrush = new SolidColorBrush(Windows.UI.Colors.Red);
        private SolidColorBrush grayBrush = new SolidColorBrush(Windows.UI.Colors.LightGray);
    }
}

実行する
では実行してみましょう。ARMを選択し、デバイスからリモートコンピュータを選択します。
スクリーンショット 2015-05-04 7.28.33
アドレスにサーバ名のデフォルト値minwinpcを指定します。
認証モードはなしに設定します。
スクリーンショット 2015-05-04 7.28.56
 
実行した様子です。

 感想

使い慣れた環境と言語を使って、簡単にGPIOの制御と、ユーザーインタフェースを簡単に作成できる点は魅力的だと思います。

raspberry pi 2 model bにいろいろ繋いでみる

raspberry pi 2 model b が到着!
早速いろんなセンサー類を繋いでみました。
まずはLチカから。トランジスタをスイッチとして使用します。ベースへ電圧をかけるとLEDが光り、Lowにすると消えます。
IMG_0652
 
単純にLチカじゃなくてLピカするなら1番ピン(3.3V)と6番ピン(GND)をつなぐだけです。
IMG_0643
 
次は、温度センサーADT7410の値を読み取ります。シリアルインタフェースのI2Cを使ってデータを読み取ります。
最近のraspberry piではI2Cを有効にする方法が変わっていてraspi-configから有効にできます。
IMG_0650
 
次に、モータードライーバーを使ってDCモーターを駆動します。モーターの正転と逆転を試します。
IMG_0656
 
電圧スピーカーでビープ音を鳴らします。昔、学研の科学の付録で聞いた、懐かしい音がなります。
IMG_0655
 
7セグLEDです。要はLEDがたくさんあるってだけです。
IMG_0653
最後にカメラの動作テスト。画質はまずまず。
IMG_0658
一通り動かして、以外と簡単にセンサー類を繋げられるのにびっくり!
あとはアイディア次第ですね。