(2021年改訂版) 運良く3年ごとに書き換えのチャンスが巡ってくるみたいです。初版は2015年、次は2018年にこの前書きを書いています。今回、2021年は、宮崎北高校様向けの「Raspberry Pi Zero W」を使ったセンシング技術体験、及び宮崎商業高校様向けの「Raspberry Piを活用したウェブアプリケーション作成」に向けて改訂と加筆を行っています。
一部の内容は、「RaspberryPiを用いたIoT製品制作(2020年9月)」と重複しています。ご了承ください。今回から「CC BY 4.0」ライセンスを巻末に明記しています。どなたでも自由に再利用・再頒布が可能ですので、気になる内容があれば、是非、授業や社内研修に取り入れてみてください。
(2018年版の序文) この資料(電子書籍)は、2018年に実施したクラウドファンド「宮崎の子供達にプログラミングの楽しさを知ってもらう為の設備を拡充したい!」のリターン品の一つとして用意されたものです。当初は既存のPDFをそのまま配布する予定だったのですが、事前に中身を読み返してみると現状と異なる部分が点在することに気が付き、また(これはオフレコですが)、著者の予想に反してこのリターン品を選ばれる方が多かったことで、これを機に現状に即した内容に書き換えることに致しました。かようにインターネットとコンピュータの世界は進歩が早く、やっと覚えた!と思った技術があっという間に過去のものになる、という現象を繰り返しています。
次々に押し寄せる波に翻弄されるのではなく、次から次に波がやってくることを楽しみにしながら、次の波にはうまく乗ってやろう!と前向きに捉えて行きましょう!
(2015年版の序文) この書籍は、著者が宮崎県高千穂町のコワーキングスペース(452)で3回に渡って開催したプログラミング教室で扱った内容を、Raspberry Piを使った基本的なプログラミング学習の入門書として再構成したものです。RubyにPython、それからScratchやMinecraftなどなど、さらにはちょっとした電子工作まで、短い時間で多岐に渡ってしまいましたが、この本の内容を入り口に興味を持った分野をさらに掘り下げていくきっかけになれば幸いです。
当初のターゲットが「高校生向け」ということもあり、基本的な部分のみに着目し、なるべく専門用語を避ける、あるいは用語の解説をつけるように心がけています。それでも、例えばコントロールキーと普通のキーを組み合わせてコマンドを実行するなど、コンピュータを扱う上での基礎的なリテラシはある程度必要になってきます。また、インストールを始めとしてほとんど全ての操作は「英語」で行っています。解説通りに進めば英語の知識はそれほど必要ありませんが、本書の内容を応用したり、予期しないエラーに遭遇してしまう場合など、次のステップに進む際には、ある程度の「英語力」は必要になります。著者がそうしてきたように、好きなことをやりながら辞書を片手(といっても今はPCにインストールできますけど)に一緒に英語も学んでいくスタイルが一石二鳥かと思います。
本書に登場する主な言葉を解説します。コンピュータの仕組みを解説すると、どうしてもカタカナ(英語)の用語が頻繁に登場することになりがちです。なるべく平易な表現を心がけていますが、実際の現場で使われる語彙とかけ離れてしまうのも問題です。幸い、現在では、Wikipediaなどのインターネット上のリソースも豊富です。意味の分からない単語に出会ったらそのままにせず、Googleなどの検索エンジンで一度調べてみることをお勧めします。「◯◯とは」と検索すると用語集などのサイトにヒットしやすいように思います。著者の私は、この分野で10年以上働いていますが、未だに知らない(新しい)単語にしょっちゅう出会います。「知らない」のが普通です。「どうやって知るか」という方法を身につけることが大切です。コンピュータやインターネットの用語集を手元に持っておくのも良いかもしれません。
イギリスで教育用に開発された手のひらサイズの小さなコンピュータ。5000円弱程度の廉価ながら、ディスプレイの入出力や通信機能など、コンピュータとして最低限の機能を備えています。拡張性も高く、様々な応用・工夫を施して利用している人も沢山います。公式サイトに利用事例や子供向けの工作課題などが多数掲載されています。2018年の6月には日本でも「Raspberry Pi 3 B+」モデルが発売され、これまで以上の性能・機能アップが図られています。小型化を図った「Raspberry Pi Zero」シリーズや「Raspberry Pi 3 A+」なども次々に発表され、用途に応じた選択肢の幅も広がっています。さらに翌年の2019年6月には「Raspberry Pi 4 Model B」が発売され、さらに高機能になっています。
Linuxディストリビューションの一つである「Debian」をRaspberry Pi用にカスタマイズしたディストリビューションです。OSとしての基本機能のみならず、35000以上のパッケージが用意されており、カメラモジュールのドライバやコマンドも最初から整っており、ScratchやSonicなどの教育用のアプリが充実している点も魅力です。本書では、RaspberryPiOSをインストールしたRaspberry Piをベースに課題に取り組みます。
専門的な用途に使う場合は、RaspberryPiOS以外にもPidoraやArchLinuxなど様々な選択肢が用意されています。
まつもとゆきひろ氏が開発したプログラミング言語です。ウェブサービスを立ち上げるための言語として有名ですが、その他にもたくさんの機能があって世界中で幅広く使われています。フリーソフトウェアとして配布されています(誰でもお金を払うことなく使えます)。執筆時点での最新バージョンは2.7ですが、RaspberryPiOSにはそれより少し古いバージョン2.5が最初からインストールされています。もちろん新しい方を使うほうがベターなんですが、基本的な用途では多少古いバージョンでも問題なく使えます。
本書の実習では登場しませんが、目的によってはとても有用なため、ここで名前のみ紹介しています。
オランダのGuido van Rossum氏が開発したプログラミング言語です。読みやすさを重視した言語構造が特徴で、Ruby同様、世界中で広く使われています。様々な専門分野のライブラリが充実していることも魅力です。最新のバージョン(3.x)では改善されているものの、日本語(マルチバイト文字)の扱いがRubyに比べると煩雑です。筆者は元々、Pythonを業務に用いてきた経緯がありますが、現在はRubyをメインにしています。Rubyの方が、日本語テキストの処理は容易だったという(日本人ならではの)理由です。
RaspberryPiOSにデフォルトで搭載されたテキストエディタです。Linux(Unix)のテキストエディタといえば、viかemacsが有名ですが、nanoはこれらに比べてシンプルで覚えやすい操作性が特徴です(逆に、viやemacsは習得が困難な代わりに非常に高機能です)。本書ではnanoを使って、各種設定ファイルの編集や、プログラムの記述を行います。本書の後半で基本的な使い方を解説しています。
この章では、Raspberry Piを利用するのに必要な周辺機器とインストールから起動までの手順を解説します。Raspberry Piが出始めた頃は日本で入手するのは難しい時期もありましたが、現在はAmazonなどの大手ネットショップでも比較的簡単に見つかるようです。値段等はまちまちなので、複数比較して納得のいくものを選択すると良いでしょう。筆者はRaspberry Pi Shop by KSYや、SWITCH SCIENCEなどをよく使います。
RaspberryPi 本体(2021年3月現在、4 ModelB/4GBがオススメです)
液晶ディスプレイ(HDMI端子のついたもの)
HDMI - Micro HDMIケーブル
USB TypeCケーブル(電源用)
USB機器に給電可能な電源アダプタ
マウス(USB接続)
キーボード(USB接続)
MicroSDカード(8GB、Class10以上推奨)
また写真には載っていませんが、MicroSDカードにOSのインストーラをコピーするために、MicroSDにデータの書き込みができるPC(MacやWindowsなど)も必要です。
最初の設定時にネットワークに繋いでしまえば、その後はsshやVNCなどのリモートアクセスで利用することもできます。Raspberry Pi自体はそれほど高価なものではありませんが、これら周辺機器を全て買い揃えていると結構な金額(特にディスプレイ)になってしまうことがありますので、インストールの時だけHDMI接続のテレビなどを利用して後はリモートアクセスで開発を進めるなど、状況に応じて工夫してください。
公式サイトで配布されているRaspberry Pi Imagerというアプリケーションを利用して、RaspberryPiOSがインストールされたSDカードを作成する方法をオススメします。Mac、Windows、Ubuntu版が用意されています。
以前はdd
コマンドを使ってSDカードにOSのイメージをコピーしていましたが、引数を間違えると事故につながるため、コマンドの意味が理解できる方以外はあまり使わないほうが良いと思います。
(通常インストールした場合、GUIが自動的に立ち上がる設定になるため、以下は不要です。)
GUIとはGraphical User Interfaceの略で、WindowsやMacのようにマウスでボタンをクリックしてアプリケーションを起動したり操作したりできる環境のことです。今はGUIが当たり前の環境ですけれど、昔はこの黒い画面だけで様々な事務処理や計算を行っていました(もちろん今でも使われています)。GUIを立ち上げるコマンドは「startx」です。
startx
しばらくすると以下のような画面が立ち上がってマウスカーソルが操作できる状態になります。左上の「Menu」ボタンから予めインストールされた様々なアプリを立ち上げてみることができますので、色々と試してみてください。おすすめは、Minecraft Pi Editionです。Minecraftは言わずもがなの世界的に有名なゲームですが、その世界観の一部をすぐに試してみることができます(通常版に比べるとかなり機能は制限されていますが、雰囲気は楽しめます)。また、ウェブブラウザも通常のPCに搭載されているものとそれほど遜色のない機能を持っていて多くのサイトを閲覧することができます。
画面の右上に各種の設定アイコンが並んでいますが、この一番左側(PCが2台重なった絵)がネットワーク接続の設定アイコンです。LANケーブルで有線接続する場合は、そのままでもつながることが多いですが、USB接続の無線LANアダプタを装着している場合はこのアイコンをクリックしてアクセスポイントの選択とパスフレーズの入力をすることができます。
(GUIの場合)左上のメニューから Preferences -> Raspberry Pi Configration と進み Interfaceのタブを選択します。コマンドラインからは以下のコマンドで起動可能です(見た目は異なります)。
sudo raspi-config
必要なインタフェース(接続手段)をEnableに変更してください。これらはRaspberryPiを遠隔から扱う上で便利なものですが、一方でセキュリティ上のリスクを増大させるものでもあります(特にパスワードがインストール時のままだと、誰でも簡単にネットワークの別の場所からログインできてしまうことになります)。パスワードを強固なものにしたりするなどの対策も合わせて検討が必要です。
sshは常時利用可能ですが、VNCはディスプレイが繋がってデスクトップマネージャが起動していないと利用できません。ディスプレイを繋がずにリモートからのみ利用する場合、/boot/config.txt
の以下の設定を有効にします。
hdmi_force_hotplug=1
新しいパッケージを追加する際などに、既存のOSが最新であることを求められる場合があります。以下のコマンドで、OSを更新することが出来ます。
sudo apt update && sudo apt upgrade -y
完了後に再起動が必要な場合もあります。
sudo reboot
セキュリティ上の問題が修正されたりするケースもありますので、時々更新する習慣をつけておくとよいかと思います。
GUIを利用している場合、画面の左上にある黒っぽいアイコンをクリックするか、またはCtrl+Alt+Tを押して起動します。黒いウィンドウの左上に以下のような表示が出てくれば正常に起動できています。
pi@raspberrypi:~ $
この表示をプロンプトと呼びます。RaspberryPiOSを始め、Linux系のOSでは、ここから様々な命令をコンピュータに対して送ることが出来ます。MacやWindowsにも似たような画面が存在します。MacのコマンドはLinuxととてもよく似ていますが、Windowsだけはかなり違った言語を使います。
$
のマークの後ろにカーソルがあることを確認したら、以下の命令を打ってみましょう。文字を打ち終わったらEnterキーを押します。
ls
ls
は今いる場所のファイル及びディレクトリを一覧表示してくれるコマンドです。以下のように
(ファイルやディレクトリの)名前がいくつか表示され、また元のプロンプトが表示されていることを確認してください。
pi@raspberrypi:~ $ ls
Documents Pictures Public..
(略)
pi@raspberrypi:~ $
このターミナルを主に使って、様々な設定やプログラムを書いていきます。
本書では設定ファイルやプログラムの編集に「nano」エディタを使用しています。ここでは基本的な使用方法を紹介します。さらに詳しい使い方はnanoのヘルプページ(起動後に「^G」で表示)や公式サイトを参照してください。
起動は「nano」コマンドに続けて編集したいファイル名を指定します。新規作成の場合も同様です(保存時に指定した名前のファイルが生成されます)。
nano sample.txt
基本的な操作は、おそらくWindowsやMacを使ったことがあれば問題なく行えるのではないかと思います。カーソルキーで移動して、文字をタイプ、Backspaceキーで削除したりReturnキーで改行、などは一般的なテキストエディタと同じです。画面の下部に主要な操作が列挙してあります。白地に黒抜きで表示してある部分が、その操作を実行するためのキーです。「^G」は「Ctrl」キーを押しながら「G」キーを押す、という意味になります。
以下の操作を覚えていれば本書で扱うくらいのプログラムの記述には十分です。
「^O」 - ファイルを保存します。
「^K」 - カーソルがある行をカットします。
「^U」 - カットした行をペーストします。
「^X」 -
終了します。未保存のファイルがある場合は、保存するか確認されます。
シンタックスハイライトや検索の機能もあって、そこそこ使える初心者向けエディタという感じですが、本格的にプログラミングを学びたいという場合には、viやemacsなどの高機能なエディタの使い方を学んでおくことをお勧めします。言語によっては、IDE(例えばNetbeansやEclipse)やAtomなどの後進のエディタ利用も選択肢に入れても良いかもしれません。Raspberry Pi OSには、ThonnyとGeanyという簡易的なIDEも標準でインストールされています。
GPIOやカメラなどRaspberry Piから外部のデバイスを操作するライブラリの多くはPythonという言語で提供されています。他の言語でも扱えないことはないのですが、インターネット上に情報が多く、安心して利用できるメリットがあります。そもそもPython自体が、とても覚えやすいシンプルな言語ですので、RaspberryPiを使いこなそう!と思ったら学んでおいて損はないと思います。ここでは、基本的な構文について幾つか紹介します。
まずは、Pythonを起動する方法と、画面に文字列「Hello, world!」を表示する方法です。
$ python
Python 2.7.10 (default, Feb 7 2017, 00:08:15)
[GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.34)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> print(“Hello, world!")
Hello, world!
上記のサンプルはMacOSのターミナル上で実行しています。PythonはMacOSにも最初から入っているくらいメジャーな言語なんです。もちろん、RaspberryPiOS上でも、同じようにPythonを起動することができます。以降のサンプルはどちらでも同じように動作します。
「>>>(プロンプトと呼びます)」の記号の後ろに、Pythonに対する命令を入力して実行することができます。まず、以下のように数式を打って見ましょう。プロンプトの後ろを実際にタイプします。その次の行に表示されているのが実行結果です。
>>> 1 + 3
4
>>> 5 * 20 / 4
25
ちょっとした電卓がわりにも使うことが出来そうですね。「*(アスタリスク)」は掛け算、「/(スラッシュ)」は割り算の記号として使われます。
Pythonを終了するときは「Ctrl+D(Ctrlキーを押しながらD)」を入力します。
ちょっとした命令なら、上記のように起動、入力(実行)という手作業でも問題ありません、少し複雑なことをしたい場合は、プログラム(命令)を予めファイルに書いておいて、それを実行するという方法を取ることになります。Pythonのプログラムが書かれたファイル(スクリプト、などと呼びます)の名前を現在のディレクトリに置いて、以下のように指定します。
python foo.py
スクリプトの作成と保存は、前述のnanoエディタなどを使うと便利です。RaspberryPiOSで利用できるエディタは他にも色々ありますので、是非調べてみてください。
以降の実例に出てくるように、Pythonの特徴の一つに「インデント(行頭の空白またはタブ)による構文の表現」が挙げられます。例えば、以下の条件分岐の例は、「vが10以上なら、“v is bigger than 10.” と表示、vが10と等しければ、“v equals 10.”、それ以外ならば、“v is smaller than 10.” と表示」するプログラムです(これを実行すると何と表示されるか、分かりますか?)。「if .. elif .. else」のそれぞれに合致した時に実行されるブロック(行のかたまり)は行頭に4つの空白がある部分、として定義されます。言葉で説明すると、非常にややこしいですが、見慣れてくると(括弧を多用する他の言語と比べて)シンプルで分かりやすく感じられると思います。
v = 12
if v > 10:
print("v is bigger than 10.")
elif v == 10:
print("v equals 10.")
else:
print("v is smaller than 10.")
条件分岐の他に、「繰り返し」もプログラム中でよく使う構文です。forとwhileの2つのキーワードがあります。forは「0から9まで(10回)繰り返す」というように回数が定まっている場合に便利です。
for i in range(0,10):
print(i)
一方で、whileは「oooのあいだ」というように、ある条件が成立している(いない)間はずっと繰り返す、という場合に便利です。
i = 0
while i < 10:
print(i)
i += 1
ある処理のまとまりを関数として記述しておくと、後からそれを呼び出して何度も使うことができます。 以下の例ではhelloという名前の関数を定義し、その後、”John”、”Cat” という引数を渡して呼び出しています。引数とは関数の呼び出し時に渡す値のことで、「いんすう」ではなく「ひきすう」と読みます。
def hello(name):
print("Hello, %s." % (name))
hello("John")
hello("Cat")
このプログラムを実行すると、以下のように表示されます。
Hello, John.
Hello, Cat.
モジュールのインポートは本書でも何度か登場します。Minecraftに接続する、GPIOのピンを介した通信をする、など外部とやりとりをしたり、プログラミング言語の基本機能として登場している以外のことをする場合に利用します(要するに現実のプログラミングではほとんど必ず使います!)。
例えば、「os」という名前のモジュールを使用する(=インポートする)ときは以下のように記述します。
import os
インポートされた後は、以下のようにして「os」モジュールが提供する関数やクラスを利用することが出来るようになります。以下のコードは、現在のディレクトリに存在するファイルの一覧を配列で返します。
os.listdir(".")
「os」モジュールには、オペレーティングシステムとやり取りをする際に必要な関数が多数用意されています。主にファイルの操作をする時に利用します。このように、プログラムの中で必要に応じて様々なモジュールをインポートしながら、必要な処理を実装して行きます。予めマシンにインストールいる以外にも沢山のモジュールが公開されており、これらをインターネット経由で導入・利用することも可能です。
配列とは、オブジェクト(数値や文字など)を連続して格納することが出来るデータ型です。 以下のように、定義したい値を[](大括弧、ブレース)で囲んで、コンマで区切ることで定義ができます。
a = [1,2,3]
他にも、範囲を指定して以下のように定義することができます。 rangeで1から4未満の整数の範囲を定義し、それをlistで配列に変換します。
a = list(range(1,4))
print関数で中身を表示することもできます。
>>> print(a)
[1,2,3]
配列の中の個々のデータのことを配列の「要素」と呼びます。 要素は後から追加したり削除したりすることが出来ます。
del(a[0]) # => [2,3]
a += [4] # => [2,3,4]
他にも沢山の配列の操作用の関数や演算子が存在しています。 また、Pythonでは、配列に似た「タプル」というデータ型も存在します。 これは()(括弧、パレンシス)で囲んで定義します。多くの場面で配列と同じように振る舞いますが、 大きな違いとしてタプルは一度定義すると後から要素を変更できない、という点があります。
a = (1,2,3)
PythonにはGUI(グラフィカルなユーザインタフェース)を操作できるライブラリも沢山揃っていますが、それらが使えない環境も存在します。そういう場合にでも、ちょっとしたビジュアライズを行いたい時に便利な制御コードがあります。これを使って、簡単なグラフを描くサンプルです。
import random, sys, time
color = 91 # 90:gray, 91:red , 92:green, 93:yellow, 94:purple
while True:
w = 60
i = int(random.random() * w)
sys.stderr.write("\033[%dm" % (color))
sys.stderr.write('\r' + ('o' * i) + ' ' * (w-i))
sys.stderr.write("\033[0m")
sys.stderr.write(' ' + ('%02d' % (i)))
sys.stderr.flush()
time.sleep(0.3)
% python3 graph.py
oooooooooooooooooooooooooooooooooooo 36
終了は「Ctrl+C」です。
ある程度複雑なプログラムを書けるようになってくると、状況に応じてプログラムの動作を切り替えたくなることがあります。そんな時に便利なのが、argparseモジュールです。これを使うと、一般的なLinuxコマンドが持つような各種のコマンドライン引数を自分のPythonプログラムでも受け取ることが出来るようになります。
import argparse
parser = argparse.ArgumentParser(description='Argparse sample')
parser.add_argument('-f','--flag',action='store_true',help='Set flag')
args = parser.parse_args()
print(args)
上記のプログラムがarg.py
という名前で保存されている場合、以下のように動作します。
% python3 arg.py
Namespace(flag=False)
-f
または--flag
というオプションをコマンド名の後に付けると、フラグの値が変わります(store_trueというアクションの動作です)。
% python3 arg.py -f
Namespace(flag=True)
また、argparseを使うと自動で-h
オプションが使えるようになります。これを指定するとコマンドの利用方法が画面に表示されます。
% python3 arg.py -h
usage: arg.py [-h] [-f]
Argparse demo
optional arguments:
-h, --help show this help message and exit
-f, --flag Set flag
RaspberryPiでruby言語を使うためには、まず以下のコマンドでインストールが必要です。
sudo apt install ruby
2021.10現在、バージョン2.5系のrubyがインストールされますが、同時点の最新版は3.0であり、少し古いです。本書の課題をこなすには十分ですが、最新の機能も導入したい場合は、rbenvを使う方法などを参考にして別途インストールをしてください。
(この章の以降の解説は別著Ruby入門からの抜粋です)
rubyの準備ができたら、まずは一行だけ、hello.rbに以下のように書き込んで保存してください。
puts “Hello, Ruby!”
保存したら、以下のように入力します。
ruby hello.rb
プロンプトの下の行に、
Hello, Ruby!
と表示されたらOKです!ようこそ、Rubyの世界へ!なにかややこしいメッセージが表示されていたりしたら、前述した手順をトレースする過程で、どこかに抜けや間違いがあるはずです。もう一度慎重に見なおしてみてください。
Rubyには対話型のインタプリタも存在します。irb(Interactive RUbyの略)という名前のコマンドです。以下のように利用します。
$ irb
irb(main):001:0> 1 + 2 + 3
=> 6
irb(main):002:0> puts "Hello, Ruby!"
Hello, Ruby!
=> nil
irb(main):003:0> exit
先ほどはhello.rbにプログラムを書いて保存して、それをrubyコマンドで実行という手順をとりましたが、irbを使うと、プロンプト(>)に直接Rubyのプログラムを書いて実行することができます。小さなコードをその場で実行したり、動作確認をする際に役に立ちますし、計算機代わりに使うことも可能です。irbを終了する場合は「exit」とタイプしてください。
if文では「もし〜ならば」という条件分岐を記述することができます。条件が成立しなかった場合の処理はelse句に記述します。最初の
n = 10
は変数の宣言部分です。ここでは「nという名前の変数(値を入れる箱)に10という値を入れる」という動作をします。
n = 10
if n == 10
puts “n is 10.”
else
puts “n is not 10.”
end
unless文はifと逆で、条件が成立しなかった場合に直後のステートメントが実行されます。
unless n == 10
puts “n is not 10.”
else
puts “n is 10.”
end
この構文はRuby以外の言語にはあまり存在しないように思います。ifに否定をつければ同じ動作をするので、冗長な印象を持つこともあるのですが、例えば下記のようにRubyでは実行するステートメントの後部にifやunless付与することが可能であり、ifとunlessをうまく使い分けると、英語の文章のような自然な記述が可能になります。
return unless str.empty?
while文では「ある条件が成立する間」の繰り返し処理を記述することができます。下記のコードでは、nという変数の値が10未満の間、ブロック内のコードが繰り返し実行されます(0から9までの値が画面に表示されます)。「n += 1」を書き忘れると、永遠に条件が成立し続けることになり、いわゆる「無限ループ」にはまってしまいます。while文に限らず、なんらかの理由で処理が終了しなくなった場合は、「Ctrl + C」で強制終了させることができます。
n = 0
while n < 10
puts n
n += 1
end
if・unlessと同様に、whileと逆の意味を持つuntil文も存在します。こちらは「ある条件が成立するまでの間」の繰り返し処理になります。上記のコードをuntilを使って書き直すと以下のようになります。
n = 0
until n == 0
puts n
n += 1
end
繰り返しの回数が予め分かっている場合は、timesメソッドが役に立ちます。以下のコードも(上記のものと同様)、0から9までの数値を表示するものです。ブロック内変数の「i」には、繰り返しの回数が0から順番に渡されてきます。
10.times do |i|
puts i
end
範囲オブジェクト(0…10)を使って表現することもできます。
(0...10).each do |i|
puts i
end
配列で記述すると以下のようになります。列挙可能なオブジェクトのそれぞれの要素に対して何らかの処理を記述するケースは多く、この構文をスムーズに記述・理解できるようになるとRuby言語の習得度は一気に上がるのではないかと思います(ここにブロック引数という重要な概念も含まれています)。
[0,1,2,3,4,5,6,7,8,9].each do |i|
puts i
end
配列は様々なオブジェクトが順序付きで並んだ変数のことです。以下のように[]を使って定義します。
arr = [1,2,3]
arr.count #=> 3
arr.sum #=> 6
ハッシュは「キー:値」のペアの集合です。以下のように{}を使って定義します。
hash = {a:1, b:2, c:3}
hash.keys #=> [:a,:b,:c]
hash.values #=> [1,2,3]
乗算の演算子(*)を使っての配列の宣言は便利ですが、多次元配列にするときには気をつけるポイントがあります。下記のコード例のような動きをします。外側の配列に包含される配列の実体が一つなんですね。気をつけておかないと、思わぬ不具合に繋がってしまいそうです(公式のドキュメントでも繰り返し明言されています)。
arr = [[0] * 2] * 3
arr[1][1] = 1
puts arr
#=> [[0, 1], [0, 1], [0, 1]]
上記の例では一箇所だけ「1」に書き換えたかったのですが、そのような配列を宣言する場合は以下のように記述します。newの一つめの引数は配列のサイズで二つ目が各要素の初期値になります。初期値を設定する引数にブロック({ }で囲まれた中)も使えるようになっているところが肝ですね。
arr = Array.new(3) { [0] * 2 }
arr[1][1] = 1
puts arr
#=> [[0, 0], [0, 1], [0, 0]]
2018年版の冊子に掲載していた課題です。RaspberryPi3とRaspbian(Desktop)の組み合わせが前提のため、最新のRaspberryPiOSで実施する場合、幾らか読み換えが必要かもしれません。
Raspberry Piにカメラモジュールが接続されていれば、簡単なコマンド操作で写真や動画を撮ることができます。写真は、前節でも紹介した「raspistill」コマンドを利用します。「-o」というオプションに続けて出力ファイル名(ここではpic2.jpg)を指定しています。
$ raspistill -o pic2.jpg
コマンドの後ろに続く「ハイフン+アルファベット」の組み合わせはオプション、またはスイッチと呼ばれるもので、「-o」の他にも様々なものが用意されています。例えば「-t」は起動してから撮影するまでの待ち時間をミリ秒で指定できます(指定しない場合は5秒)。「-vf」「-hf」はそれぞれ、垂直反転、水平反転のオプションです。カメラの設置向きによって画像が反転してしまう場合がありますが、そういった際に向きを調節するのに便利な機能です。
$ raspistill -t 1 -vf -hf -o pic3.jpg
動画の記録には「raspivid」というコマンドを使用します。こちらもraspistillと同じように「-o」に続けて出力ファイル名(vid.h264)を指定しています。
$ raspivid -o vid.h264
その他「-vf」や「-hf」も同様に利用できます。「-t」はraspistillの場合、撮影するまでの時間でしたが、raspividでは、動画を撮影する時間(ミリ秒)を指定します。つまり、以下のコマンドでは1分間の長さの動画が保存されることになります。
$ raspivid -o vid.h264 -t 60000
保存された動画はh264コーデック(ここは著者も詳しくないので割愛します)に対応したプレイヤがあれば、WindowsやMacでも再生可能です。Raspberry Piで閲覧する場合は、GUIが立ちあがた状態で、以下のコマンドを実行してください。
$ omxplayer vid.h264 -o hdmi
Raspberry Piにウェブサーバをインストールしてみましょう。ウェブサーバとは、ウェブサイトを構成する文章(HTML)や画像などのデータを要求のあったクライアント(ブラウザ)に対して送信するためのプログラムです。ここではApacheというウェブサーバをインストールしています。最近だとnginxなども流行(?)っていたり、目的に応じて様々なタイプのウェブサーバが存在しています。以下のコマンドでは、「apache2」というパッケージをインストールしています。
$ sudo apt install apache2
実はたったこれだけでお終いです。手元のRaspberry Piが確かにサーバになっていることを確認するために、以下のようにしてindex.htmlを適当に書き換えてみてください(htmlの説明は割愛します)。
$ sudo nano /var/www/html/index.html
ファイルの保存が済んだら、ウェブブラウザを立ち上げ(Raspberry Pi上でも、同一ネットワーク内の別のマシンでもどちらでも構いません)以下のアドレスにアクセスしてみましょう。
http://192.168.1.30/
以下のような「It works!」というメッセージが表示されます。
このまま立ち上げておけば、社内や家庭内向けのウェブページや掲示板などの仕組みを運用することが出来ますね。世界に向けて公開する場合は、グローバルIPアドレスを取得したり、ドメイン名をつけたり、盗聴対策に暗号化の仕組みを取り入れたりと、やるべき設定は多いのですが、もちろん不可能ではありません。
ウェブサーバの次はメールの送受信を行うメールサーバを導入してみましょう。本格的なメールサーバの運用も可能なのですが、これだけでも本が一冊になってしまうくらい複雑なテーマになりますので、ここでは、Gmailという既存のメールサービスを利用してメールの送信のみを行う簡易的なメールサーバを導入します。
$ sudo apt install nullmailer mailutils
インストールは先ほど同様、一行でお終いです。続けて設定ファイルを編集します。
$ sudo nano /etc/nullmailer/remotes
中身は以下のようになります。まず、Gmailのアカウントを一つ取得して頂く必要があります。既存のものを使う手もありますが…セキュリティの観点からあまりお勧めはできません。取得したアカウントの情報でuserとpassの値(xxx@gmail.comとpassword)を書き換えてください。
/etc/nullmailer/remotes
smtp.gmail.com smtp --port=465 --auth-login —user=xxx@gmail.com --pass=password --ssl
(実際は一行です)
設定を更新したら、メールサーバを再起動します。
$ sudo service nullmailer restart
以下のコマンドで送信テストをしてみましょう。最後尾のメールアドレスは、あなた自身のアドレスに書き換えてください。echo の後ろが本文に、 -s の後ろが表題としてメールが送信されます。設定が正しければ、しばらくして指定のアドレスにメールが届きます。
$ echo hi | mail -s 'test mail’ xxxx@youremail.com
写真を撮って、ウェブサーバで公開して、公開完了をメールでお知らせするプログラムを作ってみましょう。基本的には前述の三つのプログラムを組み合わせるだけですが、Ruby言語を使って少し工夫を取り入れてみましょう。
まずはウェブサーバの準備です。「/var/www」がウェブに公開されるファイルを置く場所です。この下に「pictures」という名前のディレクトリを作成し、piユーザ(現在ログインしている一般ユーザ)からアクセス(書き込み)可能な状態にします。
$ sudo mkdir /var/www/pictures
$ sudo chown pi:pi /var/www/pictures/
プログラム本体を作成します。nanoエディタで「take-pictures.rb」というファイルを編集(新規作成)してください。「.rb」はRubyでプログラムが記述されたファイルに使われる拡張子です。
$ nano take-pictures.rb
中身は以下のようになります。ちょっと記号が多く暗号めいた雰囲気もありますが…。IPアドレス(192.168.1.30)とメールアドレス(xxxx@yourmail.com)は適宜、環境に合わせたものに書き換えてください。
take-pictures.rb
10.times do |i|
f = "/var/www/pictures/%03i.jpg" % i
`raspistill -t 1 -vf -hf -o {f}`
end
`echo "See http://192.168.1.30/pictures/" | mail -s 'Pictures are taken!' xxxx@youremail.com`
以下のコマンドで実行します。
$ ruby take-pictures.rb
正しく起動すると、一秒間に1回ずつ写真を撮って、000.jpgから009.jpgまで連番の形式で合計10枚画像を保存します。10枚撮り終わった時点で、写真が撮れたよ(Pictures are taken!)というメッセージを指定したメールアドレスに送信します。撮れた写真はウェブサーバで公開されています。ブラウザから以下のアドレスにアクセスしてください。
http://192.168.1.30/pictures/
上記のようにファイルの一覧が表示されていたら成功です。画像名をクリックすると、撮影した画像を確認できます。ウェブアプリケーションを作って、スライドショーで見せたりサムネイルで一覧表示したりと使いやすくするアイデアは沢山出てきそうです。また、今回は直接コマンドを実行しましたが、この実行のタイミングを一定時間おき、あるいは決まった時刻に、など設定することも簡単です。後述するGPIOを使って各種センサと組み合わせるのも面白いかもしれません。扉が開いたら、人(生き物)が通ったら撮影する、なんてことも実現できます。
「Scratch」は教育用のプログラミング環境です。WindowsやMacでも利用可能ですが、Raspbianには最初からインストールされています。プログラミングといっても、テキストエディタを使ってコードを書く一般的なものと異なり、様々な機能を持ったブロックをマウスで操作して繋げてプログラムを作成します。また、絵を描くためのツールがあったり、音声(音楽)ファイルを追加して効果音を付けたり、プログラミングに必要な素材も様々なものが提供されています。簡単なゲームなどを作ることが可能です。公式サイトには世界中のユーザが作った作品が多数公開されていますので、興味があれば覗いてみてください。
「Sonic Pi」というアプリを使って、Ruby言語で様々な効果音を合成したり音楽を作ったりすることが出来ます。Raspberry Piのアナログ端子にスピーカーを接続している場合は、スピーカーの設定画面で出力先が「Analog」になっていることを確認してください。HDMI端子に繋いだディスプレイ(テレビ)にスピーカが内蔵されている場合はそのままで大丈夫です。
以下は公式サイトに掲載されていたサンプルプログラムです。このコードを「Sonic Pi」上で実行すると、無限に繰り返す不思議なBGMが再生されます。
with_fx :reverb, mix: 0.2 do
loop do
play scale(:Eb2, :major_pentatonic, num_octaves: 3).choose,
release: 0.1, amp: rand
sleep 0.1
end
end
以下に示すサンプルコードも公式のチュートリアルをベースにしています。詳しく知りたい場合はアプリに付属のチュートリアル(英語)を参照してください。
Sonic PiもScratchなどと同様にライブコーディング(その場で書いて、その場で動かす)ができます。あまり大きく構えずにギターを片手に持ったくらいの気持ちで気軽に音を出してみましょう。 以下のサンプルを入力してみましょう。
live_loop :flibble do
sample :bd_haus, rate: 1
sleep 0.5
end
「Run」ボタンを押してみてください。バスドラムの音が聴こえましたか?もし演奏を止めたい場合は「Stop」ボタンを押してください。もしくは、(音を出し続けたままでも)「sleep 0.5」と書かれた箇所を「sleep 1」と書き換えて、もう一度「Run」ボタンを押してみてください。ドラムのテンポが変わりましたか?
これがライブコーディングです。その場で簡単に書き換えて違いを試すことができます。さらにもう少し試して見ましょう。先ほどのコードに「sample :ambi_choir, rate: 0.3」という行を足します(全体では以下のようになります)。
live_loop :flibble do
sample :ambi_choir, rate: 0.3
sample :bd_haus, rate: 1
sleep 1
end
数字を色々と書き換えて変化を試してみてください。ただし、「sleep」の値を0に近づけるとどうなるでしょうか?段々とテンポが速くなるのですが、どこかの時点でSonic Piが演奏しきれなくなりエラーを出してストップしてしまいます。このような場合は、エラーの出ないところまで大きな値に戻してあげる必要があります。
以下のように行の先頭に「#」をつけると、その行が無視されて演奏されるようになります。
live_loop :flibble do
sample :ambi_choir, rate: 0.3
# sample :bd_haus, rate: 1
sleep 1
end
もう一つ、以下のサンプルでは、二つのループが同時に動いています。 ・sleepを調節してテンポを変更してみてください ・コメントを外してみてください
live_loop :guit do
with_fx :echo, mix: 0.3, phase: 0.25 do
sample :guit_em9, rate: 0.5
end
# sample :guit_em9, rate: -0.5
sleep 8
end
live_loop :boom do
with_fx :reverb, room: 1 do
sample :bd_boom, amp: 10, rate: 1
end
sleep 8
end
感触が掴めたでしょうか?思いついたら好きな部分を書き換えてみて、音がどんな風に変化するか確認してみてください。Sonic Piに付属のチュートリアルにはもう少し詳しい解説(英語)が載っています。サンプルも沢山掲載されていますので、それらを参考にするのも良いでしょう。
こちらが本書の最後のサンプルです。どんな音が聞こえそうか想像がつきますか?
play :C
sleep 0.5
play :D
sleep 0.5
play :E
sleep 0.5
play :F
sleep 0.5
play :G
ここまでのサンプルで使われていたのが、冒頭でも紹介したRubyという言語です。この構文を理解するには、シンボルやブロックの概念を理解する必要がありますが、逆にいうとそれ以上の特別なことはしていません。Rubyはとても柔軟性の高い言語で、従来の手続き型のプログラミング、オブジェクト指向の要素を持ちつつ、こうした特殊な用途にもすんなりと対応することができます(詳しく知りたい方は、「DSL:ドメイン固有言語」というキーワードで調べてみてください)。
23年12月時点、最新のOS(bookworm)では、以下のコマンドが利用できます。または
PrtScr
キーを押すだけでもスクリーンショットを撮ることができます(DesktopではなくPicturesフォルダに保存されます)。
grim
以下は、以前のOS(buster)向けの解説です。
Scrotは、Linux上でスクリーンショットを撮るコマンドラインツールです。デスクトップやターミナル、特定のウィンドウのスクリーンショットを撮ることができます。下記のコマンドを実行してインストールします。
sudo apt-get install scrot
ターミナルで「scrot」 と入力し、Enterを押します。ツールバー左上の「FileManager」のpiフォルダに、撮影した日時の名前のファイルができます。「-d」の後に任意の秒数を指定して、撮影するタイミングを遅らせる事ができます。
scrot -d 10 # 10秒後に撮影する。
保存先のファイル名を指定することもできます。
scrot /home/pi/pictures/name.png
Scrotには「-d」以外にも様々なオプションがあります。
-s マウスで画面の中の特定のウィンドウを撮影
-b ウィンドウ枠を含めて撮る
-u ウィンドウ枠を含めずに撮る
-p [1~100] 画像の質を指定する。初期値は75
Minecraftは、世界的に有名なオープンワールドのゲームです。Raspberry Pi向けの無料版では、Pythonのコードを使って物を自動的に生成する事ができます!
2023.11現在、標準のリポジトリ(apt)からは除外されています。githubから直接ダウンロードしてくることで実行が可能です。
https://github.com/MCPI-Revival/minecraft-pi-reborn
以下のように進んでください。
View Documentation -> View Installation -> Click link on “Download Packages here”.
RaspberryPiで利用する場合、 armhf
(64bit版の場合、arm64
)
を選択します。ダウンロード完了後、以下のコマンドを実行して起動します(バージョン番号、アーキテクチャ(armhf,arm64)の部分は適宜、読み替えてください)。
chmod +x minecraft-pi-reborn-client-2.3.9-armhf.AppImage
./minecraft-pi-reborn-client-2.3.9-armhf.AppImage
AppImageを実行するのにFUSEが必要ですとエラーが出ることがあるようです。その場合、以下のコマンドを実行して必要なライブラリを準備してください。
sudo apt install libfuse2
まず、起動したら世界を探検してみましょう。下記のkeyでプレイヤーを動かしてみましょう。主なキー操作は以下の通りです。
w 前進 Space ジャンプ
a 左に進む Spaceを素早く2回 飛ぶ / 落ちる
s 後退 Esc 一時停止
d 右に進む Tab マウスをリリース
e 倉庫を開く
Pythonのmcpiというライブラリが提供されています。以下のコマンドでインストールします。
pip3 install mcpi
任意のエディタが利用できますが、ThonnyというPython用のエディタを使うと、コーディングから実行・デバッグまでを一つのウィンドウで実行できるので便利です。メニューから「Thonny」を起動して以下のようなプログラムを書いて実行してみましょう。
from mcpi import minecraft
mc = minecraft.Minecraft.create()
mc.postToChat("Hello world")
画面の下の方に「Hello world」と表示されたらPythonからminecraftの世界への接続成功です!
・テレポート 今度は、コードを書いて移動してみましょう。試しに下記のコードを実行してみましょう。
pos = mc.player.getPos()
x, y, z = mc.player.getPos()
mc.player.setPos(x, y+100, z)
一瞬で、頭上の空に飛び出します。
・ブロックを配置する 次にブロックを置いてみます。setBlockという関数に、場所とブロックの種類を指定します。
x, y, z = mc.player.getPos()
mc.setBlock(x+1, y, z, 1)
1個の”1”のブロック(=石)が、近くに現れます。
・巨大なブロックの塊を作る 大きなブロックの塊も作れます。先ほどの関数を用います。
stone = 1
x, y, z = mc.player.getPos()
mc.setBlocks(x+1, y+1, z+1, x+11, y+11, z+11, stone)
一辺が 10 (=11-1) の立方体が出現します。
・歩いた後に花を咲かせる 歩いている間、0.1秒毎に花のブロックを自分のいる場所に置いていきます。
from mcpi.minecraft import Minecraft
from time import sleep
mc = Minecraft.create()
flower = 38
while True:
x, y, z = mc.player.getPos()
mc.setBlock(x, y, z, flower)
sleep(0.1)
・歩いている間、足元のブロックのコードを得る。 足元のブロックのコードを出力するには、getBlockという関数を使います。
while True:
x, y, z = mc.player.getPos()
block_beneath = mc.getBlock(x, y-1, z)
print(block_beneath)
・芝生の上にだけ花を配置する。 setBlockと、getBlockを使って足元のブロックが芝生の時にを配置してみましょう。
grass = 2
flower = 38
while True:
x, y, z = mc.player.getPos() # player position (x, y, z)
block_beneath = mc.get
motionというパッケージを使うと、Raspberry Piのカメラに動体検知機能を持たせることができます。視野の中に動くものを見つけ出しすことが出来ます。何か動くものがあったときだけ写真を撮ったり、動画を保存したりということが可能です。
sudo apt -y install motion
/etc/modules に以下の行を追加します(v4l2のlは小文字のLです)。
bcm2835-v4l2
/etc/default/motion を編集してデーモンを有効化します。
start_motion_daemon=yes
/etc/motion/motion.conf の下記の設定を修正します。
width 640
height 480
event_gap 10
output_pictures best
ffmpeg_output_movies off
snapshot_interval 1800
locate_motion_mode on
修正内容の意味は以下のようになります。他のパラメタも必要に応じて適宜調整してください。
画像サイズを640x480に(320x240)
動体を検知し続ける長さを10秒に(60秒)
検知中に最も動きのあったフレームのみ保存(on:全て保存)
検知中のムービー保管を無効化(有効)
30分に1回スナップショットを保存(保存なし)
動体を検知した範囲を矩形で囲む(囲まない)
設定を完了したらRaspberryPiを再起動します。 /var/lib/motion に画像(jpg)が蓄積され始めたら正常に動作しています。この画像をファイルサーバやウェブサーバ経由で必要に応じて参照できるようにすれば簡易的な監視カメラとしての利用も可能です。
RaspberryPiにスピーカを接続して日本語を喋らせることも出来ます(HDMIで接続したモニタに音源が付いている場合でもOKです)。
必要なパッケージを追加します。
sudo apt install open-jtalk open-jtalk-mecab-naist-jdic hts-voice-nitech-jp-atr503-m001
音声合成には関係ないですが、日本語のテストをしやすくするために、フォントとIME(日本語の入力機能)を追加します。
sudo apt install ibus-mozc fonts-takao
辞書と音声ファイルを明示する必要があります。wav形式で保存します。
echo "こんにちは" | open_jtalk -x /var/lib/mecab/dic/open-jtalk/naist-jdic -m /usr/share/hts-voice/nitech-jp-atr503-m001/nitech_jp_atr503_m001.htsvoice -ow test.wav
テストには aplay というコマンドが利用できます。
aplay test.wav
上記でインストールしたのはOpenJTalkに標準で含まれる男性の声です。下記のサイトでは女性の声のサンプルを手にいれることができます。アーカイブを展開した中にある拡張子が .htsvoice のファイルを上記の open_jtalk コマンド実行時に指定してください。
https://sourceforge.net/projects/mmdagent/files/MMDAgent_Example/MMDAgent_Example-1.7/
デジタルサイネージというのは、駅や店頭に設置されたディスプレイに、訪れる人に有益な情報(運行情報、営業時間)や広告などを表示する端末のことです。Raspbianを普通に起動するとWindowsやMacのようなデスクトップ画面が表示されますが、この設定を変更してウェブブラウザだけを固定して表示することで実現できます。今回使用するChroniumブラウザでは「キオスク」モードと呼ばれています(デジタルサイネージと似ていますが、ユーザの操作を受け付けたり、もう少しインタラクティブ性を持ったものの呼称に使われることが多いようです)。
まずは不要なパッケージを消して、必要なパッケージの準備をします。 日本語フォントはnotoを入れていますが、好みで変更してください。
sudo apt remove -y wolfram-engine sonic-pi scratch scratch2 bluej libreoffice idle
sudo apt update
sudo apt upgrade -y
sudo apt install -y unclutter fonts-noto xdotool
.config/lxsession/LXDE-pi/autostart
を下記の内容で書き換えます。
上2行でディスプレイが省エネモードに入らないように設定し、unclutterでマウスカーソルを隠しています。
使用したのはchromiumブラウザのkioskモードです。
@xset s off
@xset -dpms
@unclutter
@chromium-browser --kiosk --incognito https://example.com/path/to/page
crontabを以下のように設定します。この例では1日に1回画面をリロードしています。
1 0 * * * export DISPLAY=":0" && xdotool key F5
すべての設定が終わったら、端末を再起動します。
sudo reboot
chromiumの設定で指定したインターネット上のウェブサイトが全画面表示された状態になって起動します。コンテンツが頻繁に更新されるような場合には、このような使い方が適していますが、固定のコンテンツを繰り返し表示したいような場合には、ローカル(piユーザのホームディレクトリなど)にHTMLのコンテンツを置いてしまって、直接参照するようにしてしまえば、スタンドアロン(=ネットに接続していない状態)でもコンテンツを表示することが可能です。
この章は、GUIの入っていない「Raspberry Pi Zero W」向けの課題になります。
RaspberryOSではwpa_supplicantというデーモンでWi-fiに接続することができます。このデーモンの設定ファイルを直接編集する方法もありますが、ここではraspi-configを利用する方法を紹介します。まず、ターミナルで以下のようにコマンドを実行します。
sudo raspi-config
以下のようなメニュー画面が立ち上がります。
メニューを「2 Network Options -> N2 Wi-fi」の順に遷移し、Wi-fiのSSIDとパスワードを設定します。
続けて「4 Localisation Options -> I4 Change Wi-fi Country」と辿り、国を「Japan」に設定します。RaspberryPiの発する電波が法律に合致しているかどうかは国によって異なりますので、ここが正しく設定されていないとWi-fiが使えないことがあります。
うまく繋がっているかどうか確認をするためのコマンドを幾つか紹介します。「ip addr」コマンドでは、現在の自分自身のIPアドレスを確認することができます(下記例で10.0.1.14の部分)。
$ ip addr
: 省略
2: wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast
:
inet 10.0.1.14/24 brd 10.0.1.255 scope global wlan0
pingコマンドでは、インターネット上の別のサーバと通信が出来るかどうか、またその際の遅延(速度)を確認することができます。
$ ping lumber-mill.co.jp
PING lumber-mill.co.jp (133.167.86.214) 56(84) bytes of data.
64 bytes from os3-373-19710.vs.sakura.ne.jp (133.167.86.214): icmp_seq=1 ttl=54 time=59.9 ms
コマンドを終了するには「Ctrl+C」をタイプしてください。
もう少し専門的なコマンド、tracerouteでは、目的のホストに到達するまでにどんなルータを経由しているかを調べることが出来ます。
$ traceroute 133.167.86.214
traceroute to 133.167.86.214 (133.167.86.214), 30 hops max, 60 byte packets
1 10.0.1.1 (10.0.1.1) 9.808 ms 13.609 ms 13.503 ms
2 softbank221110226074.bbtec.net (221.110.226.74) 13.345 ms 13.162 ms 37.785 ms
: (省略)
追加のインストール(下記コマンド)が必要ですが、whoisコマンドを使うとインターネット上のドメインを誰が所有しているのか、いつ登録されたのか、などを調べることができます。
sudo apt install whois
例えば、google.comを調べると以下のような応答が得られます。
$ whois google.com
Domain Name: GOOGLE.COM
Registry Domain ID: 2138514_DOMAIN_COM-VRSN
Registrar WHOIS Server: whois.markmonitor.com
Registrar URL: http://www.markmonitor.com
Updated Date: 2019-09-09T15:39:04Z
Creation Date: 1997-09-15T04:00:00Z
: (省略)
Linuxにはcrontabというプログラムを決まった時間に起動するための仕組みがあります。
以下のコマンドで起動されるエディタを使って、設定を行います。
初回起動時のみ利用するエディタを聞かれますので、好みのものを選択してください。
ここまで、このテキストを読み進めて来ている方は、nano
がオススメです。
crontab -e
たとえば、毎日15時にhello.pyというPythonのプログラムを起動するには以下のように書きます。
0 15 * * * python3 hello.py
前半の5つの数字(またはアスタリスク)がそれぞれ「分 時 日 月 曜日」となっていて、「*」が指定された場合は、「全て」という意味になります。以下のように書くと、毎月1日の14:30に起動する、という意味になります。また、「#」で始めた行はコメントとして扱われ処理内容に影響しません。メモを残す場合などに利用します。
30 14 1 * * python3 hello.py
# Run only on the first day of each month.
他にも特殊な構文があります。@rebootに続けて書かれたコマンドは、システムが起動した直後に1度だけ呼ばれます。電源を入れたらいつも動かしたいプログラムがある場合は、ここに書いておくと便利です。
@reboot python3 hello.py
実際には、ただ起動するだけだと何が起こっているのか分からなくなることが多いため、以下のようにリダイレクトとパイプを使って(詳細は割愛)、マシンの上に記録(ログ)が残るように運用すると便利です。
@reboot python3 hello.py 2>&1 | logger -p cron.info -t "hello"
ログは /var/log/syslog
というファイルに書き出されています。上記のように hello
というタグをつけた場合、以下のように確認することが出来ます。
sudo grep hello /var/log/syslog
現在の設定を確認するには、「-e」の代わりに「-l」オプションを使用します。
crontab -l
「-r」オプションを指定すると、設定が全て消去されます!
crontab -r # danger!!
以下のスクリプトでは「Pimoroni envirophat」というRaspberryPiのGPIOに装着可能なボードを使って各種のセンサー値を取得、ファイルに保存しています。
#!/usr/bin/env python3
# Based on Pimoroni exsamble all.py.
import sys, time
from envirophat import light, weather, motion, analog
INTERVAL = 1 # sec
def write(line):
print(line)
fn = "envirophat-%s.csv" % (time.strftime("%y%m%d"))
with open(fn, mode='a') as f:
f.write(line+"\n")
try:
while True:
# Temp,Light,Accelerometer X,Y,Z
vs = [round(weather.temperature(),2),light.light()]
vs += [round(x,2) for x in motion.accelerometer()]
write(",".join(map(str,vs)))
time.sleep(INTERVAL)
except KeyboardInterrupt:
pass
2021年2月現在、Enviro pHatは、公式サイトでは既に販売を終了しているようです(在庫が手に入る日本国内の通販サイトはあるかもしれません)。代わりに、Enviro+というセンサーが入手可能です。こちらは、より多くのセンサーの値を取得することができます。Pythonのライブラリを介して同じような手順でデータを集めることができます。詳細は「RaspberryPiを用いたIoT製品制作(2020年9月)」を参照してください。
以下の実習課題は、次章「Pythonの基本」で学ぶ内容と組み合わせて取り組んでみてください。
制御文字を使って、envirophatの値をグラフに表示するように修正してみましょう。
コマンドライン引数を使って、通常のファイル保存モードとグラフ表示モードを切替られるようにしてみましょう。たとえば、python3 enviro.py --graph
と実行した場合のみ、グラフが表示され、それ以外ではファイルにデータを保存する動作をする、などです。
ここでは、インターネットを介してセンサーのデータを収集するための方法を試してみます。IoTと呼ばれる分野では、このアプローチが取られることが多いと思います。例えば、広い農場や工場のあちこちにセンサーを配置して一括で管理する場合、それぞれの端末まで直接データを集めに行くのは効率的ではありません。自動的に一箇所に集めて、いつでも見られるようにする仕組みが必要になります。
これを実現するための仕組みは沢山ありますが、今回は筆者が自分でクラウド上に立ち上げているサービスを利用します(こちらは試験運用中なので、実務に使う場合は、信頼できるサービスを選んでください)。
https://iot.lmlab.net/
上記のサービスにユーザ登録すると、デバイス(センサー)を登録することが出来るようになります。デバイスの登録が済むと、設定画面に以下のようなコマンド例が登場します。ここで利用しているのはcurl
というコマンドです。このコマンドを使うとURL(インターネット上の住所
のようなもの)を指定して、データのやり取りができます。
curl -G -d "id=1" -d "token=secret" --data-urlencode "dt=2021-03-03T09:15:35+09:00" -d "temperature=12.3" -d "pressure=123.4" -d "humidity=12.3" -d "illuminance=123.4" -d "voltage=1.23" "https://iot.lmlab.net:443/temps/upload"
上記の例では、IDやトークン(合言葉)がダミーの状態ですので、実際にご自身で登録したデバイスの画面に表示されているコマンド例で送信を試してみてください。
前述したenvirophatなどはPythonのライブラリが提供されていますので、Pythonでつなぐと便利です。以下はPythonからcurl
を利用する場合のサンプルです。
import datetime, os, time
from envirophat import weather, light
def get_url_params():
t = weather.temperature()
p = weather.pressure(unit='hPa')
h = None
l = light.light()
# Python's datetmie doesn't have timezone info.
# You may need to set system timezone as JST. (hint: sudo raspi-config)
ts = time.strftime("%Y-%m-%dT%H:%M:%S%z")
s = "dt=%s" % (ts)
s += "&temperature=%f" % (t)
s += "&pressure=%f" % (p)
# s += "&humidity=%f" % (h)
s += "&illuminance=%f" % (l)
return s
if __name__ == '__main__':
try:
url = os.environ["MUKOYAMA_URL"]
id = os.environ["MUKOYAMA_ID"]
token = os.environ["MUKOYAMA_TOKEN"]
u = (url+"/temps/upload?id=%s&token=%s&"+get_url_params()) % (id,token)
cmd = 'curl -s -S "'+u+'"'
os.system(cmd)
except KeyboardInterrupt:
pass
サーバのURLやID,TOKENなど、実行する端末ごとに異なる情報は環境変数という仕組みを利用してプログラムの外に記述しています。
実行する場合には、まず、以下のようにそれぞれのデバイスに合わせた値を環境変数にセットします(export
がそれをするためのコマンドです)。
export MUKOYAMA_URL=https://iot.lmlab.net
export MUKOYAMA_ID=1
export MUKOYAMA_TOKEN=secret
その状態でプログラムを起動すると、os.environ
という変数を通して、ここで設定された値が読み込まれ、それぞれの環境に応じた動作をします。
python3 iot.py
前述したcrontabでも、同じ仕組みが利用できます。ここに書く際にはexport
は不要なので注意してください。
MUKOYAMA_URL=https://iot.lmlab.net
MUKOYAMA_ID=1
MUKOYAMA_TOKEN=secret
@reboot python3 iot.py 2>&1 | logger -p cron.info -t "iot"
ここまでの課題で学んだ技術を組み合わせて、RaspberryPiをデータロガーに仕立てます。電源やネットワークの無い複数の箇所に設置して運用することを想定して幾つかの機能改良をしましょう。
専用のディレクトリを用意して新しいスクリプトを書きます。
mkdir enviro-logger
cd enviro-logger
nano main.py
既に、前節の演習問題の回答があれば、それをコピーして再利用しましょう。ない場合は、envirophatのサンプルコードをベースにしてください。
cp ../(前節で作ったpyファイル) ./main.py
少しスクリプトのサイズが大きくなるので、上記のコピーコマンドを使って動いているところまでを保管しておくなど工夫をしながら進めると良いでしょう。以下の解説は演習問題への回答状況によっては、既に実装済みの場合もあるかもしれません。その場合は、適宜スキップして進めてください。
まずは、argparseというライブラリを導入して、スクリプトの動作を実行時に変更することが出来るようにします。importにargparseを追加します。
import argparse, os, sys, time
コマンドライン引数を解析するためのコードを追加します。最終行のprintは動作確認のために追加しています。確認ができたら消してください。
parser = argparse.ArgumentParser(description='Envirophat interface')
parser.add_argument('-g','--graph',action='store_true',help='Show graph')
parser.add_argument('-n','--name',help='Name of CSV')
parser.add_argument('-i','--interval',type=int,default=1)
args = parser.parse_args()
print(args) # for debug. remove it later.
これによって、一気にコマンドらしさが増すと思います。例えば以下のように打つと、使い方のヘルプが表示されます。
python3 main.py -h
以下のように引数を指定して、どんな値が出力されるか確認してみましょう。
python3 main.py -n myroom -i 60
ファイルにデータを保存する箇所は以下のようになります。ファイル名を前述のargparseで取得したnameを使って作成しています。このnameを各端末ごとに重複しないように設定することによって、複数のセンサーからのデータを一箇所に集めても違う名前で共存できるようになります。
def write(line):
fn = os.path.dirname(__file__)
fn += "/%s-%s.csv" % (args.name,time.strftime("%y%m%d"))
sys.stderr.write("File: %s" % (fn))
with open(fn, mode='a') as f:
f.write(line+"\n")
これらをまとめると以下のようになります。ちょっと長いですが、上記の説明を参考にして頂きつつ実装をしてみてください。
#!/usr/bin/env python
# Based on Pimoroni exsample all.py.
# See also: https://lmlab.net/books/2102_raspi/index.html#%E3%83%87%E3%83%BC%E3%82%BF%E3%83%AD%E3%82%AC%E3%83%BC%E4%BD%9C%E3%82%8B%E7%AB%AF%E6%9C%AB%E7%B7%A8
import argparse, os, sys, time
from envirophat import light, weather, motion, analog
def write(line):
fn = os.path.dirname(__file__)
fn += "/data/%s-%s.csv" % (args.name,time.strftime("%y%m%d"))
sys.stderr.write("File: %s\n" % (fn))
with open(fn, mode='a') as f:
f.write(line+"\n")
def draw_graph(v,max_v):
w = 60
i = int(v * (w / max_v))
sys.stderr.write("\033[92m")
sys.stderr.write('\r' + ('o' * i) + ' ' * (60-i))
sys.stderr.write("\033[0m")
sys.stderr.write(' ' + ('%04d' % (v)))
sys.stderr.flush()
parser = argparse.ArgumentParser(description='Envirophat interface')
parser.add_argument('-g','--graph',action='store_true',help='Show graph')
parser.add_argument('-n','--name',help='Name of CSV')
parser.add_argument('-i','--interval',type=int,default=1)
args = parser.parse_args()
try:
while True:
d = time.strftime("%F")
t = time.strftime("%T")
vs = [d,t]
# Temp,Light,Accelerometer X,Y,Z
temp = weather.temperature()
ligh = light.light()
vs += [round(temp,2),ligh]
vs += [round(x,2) for x in motion.accelerometer()]
if args.graph: draw_graph(ligh,5000)
else: write(",".join(map(str,vs)))
time.sleep(args.interval)
except KeyboardInterrupt:
pass
このスクリプトの動作確認(上記の実行例参照)がとれたら、以下のコマンドでcron(自動実行の設定)を編集します。
crontab -e
最後尾に以下のように記述します。
@reboot python3 enviro-logger/main.py -n myroom -i 10
マシンを再起動します。
sudo reboot
enviro-loggerディレクトリに自動的にログが溜まっていることを確認してください。nanoエディタでも確認できますが、tail
というコマンドを使うと、ファイルの最後尾だけを切り取って表示することができ、こうした確認作業には便利です。例えば、以下のように使用します(ファイル名は設定に合わせた値に読み替えてください)。
tail enviro-logger/myroom-210205.csv
うまくいかない場合は、以下のように出力をログに保存する設定を追加して原因究明の手がかりを探します(「プログラムを自動起動する(crontab)」参照)。
@reboot python3 enviro-logger/main.py -n myroom -i 10 2>&1 | logger -p cron.info -t "enviro"
logger
コマンドにリダイレクトされた出力は以下のコマンドで確認することができます。
sudo grep enviro /var/log/syslog
今回、i2cのインタフェースの初期化よりも先にプログラムが起動してしまい「センサが見つからない」というエラーになってしまっていたので、起動をわざと60秒遅くして対応しました。また、上記の設定は動作確認がしやすいように10秒単位でデータを取得していますが、一旦動作確認が済んだら、(長期間設置する場合などログが大きくなり過ぎそうな場合を想定して)適宜間隔を大きくして調整をしてください(-i 600
など)。最終的に、以下のように設定して、正しくファイルが蓄積されるようになりました。
@reboot sleep 60; python3 enviro-logger/main.py -n myroom -i 600 2>&1 | logger -p cron.info -t "enviro"
次に、このデータを集信用のサーバに転送します。次節の「サーバ編」で構築したサーバ(enviro-host)が同一のネットワーク内で稼働していて、ログインパスワードが分かっている前提です。
まずは、他のサーバにアクセスするのに必要な鍵のセットを作成します。幾つか入力を促されますが、全てそのままエンターを押して先に進んでください。
ssh-keygen
鍵は、.ssh
という隠しフォルダに作成されます。これをサーバに登録するには以下のコマンドを実行します。正常に接続できるとパスワードを聞かれますので、このサーバのパスワードを入力してください。
ssh-copy-id -i ~/.ssh/id_rsa.pub enviro-host.local
ここでうまく繋がらない場合、ネットワーク接続に問題がある場合があります。この章の最初に紹介したip addr
やping
などのコマンドを使ってネットワークへの接続状態を確認してください。名前解決に問題がある場合、端末やサーバを再起動することで改善する場合があります。
鍵の登録が出来たら、以下のコマンドで接続が可能かを確認します。サーバの名前(enviro-host)が表示されたら正常です。
ssh enviro-host.local hostname
データをサーバに転送するにはrsyncというコマンドを利用します。
rsync -avz enviro-logger/data/*.csv enviro-host.local:enviro-logger/data/
これもmain.pyと同じようにcronに登録してしまいましょう。 こちらは、main.pyのintervalとのバランスを見て間隔を決めてください。以下は10分おきに転送を起動する例です。
*/10 * * * * rsync -avz enviro-logger/data/*.csv enviro-host.local:enviro-logger/data/
データの蓄積が出来たら、続いて、統計データを作成するためのスクリプトを用意します。こちらは、同じディレクトリの中にstat.py
という名前の別のスクリプトに記述します。
cd enviro-logger
nano stat.py
保存するファイル名はenviro-logger/data/(name)-stat.csv
とします(日付の代わりにstatという固定の名前を入れる格好です)。以下のような流れで処理を実行します。
全体のスクリプトは以下のようになります。
#! /usr/bin/env python
import argparse, os, sys
def stat(fn):
# 日単位の、気温、照度の最低、最高、平均
# date,(temp)min,max,avg,(light)min,max,avg
# ex. yyyy-mm-dd,3,17,10,100,1500,500
results = ["",99.99,0,0,9999,0,0]
n = 0
with open(fn, mode='r') as f:
for line in f:
row = line.strip().split(",")
results[0] = row[0] # date
temp = float(row[2])
light = int(row[3])
if results[1] > temp:
results[1] = temp
if results[2] < temp:
results[2] = temp
results[3] += temp
if results[4] > light:
results[4] = light
if results[5] < light:
results[5] = light
results[6] += light
n += 1
results[3] = round(results[3] / n,2)
results[6] = round(results[6] / n,2)
return ",".join(list(map(str,results)))
parser = argparse.ArgumentParser(description='Envorophat interface')
parser.add_argument('-n','--name',help='Name of CSV')
args = parser.parse_args()
dir = os.path.dirname(__file__) + "/data"
fn = "%s/%s-stat.csv" % (dir,args.name)
sys.stderr.write("File: %s\n" % (fn))
with open(fn, mode='w') as fw:
for f in sorted(os.listdir(dir)):
if not f.endswith(".csv"): continue
if f.endswith("stat.csv"): continue
sys.stderr.write(" %s\n" % (f))
fw.write(stat(dir+"/"+f)+"\n")
こちらもmain.pyやrsyncと同じようにcronに登録します。一日に一回、午前8時に更新するように設定する場合は以下のような書き方になります。実際の利用方法に即して適宜タイミングは検討してください。
0 8 * * * python3 enviro-logger/stat.py -n myroom 2>&1 | logger -p cron.info -t "enviro"
この章の内容は、(Zeroではなく)RaspberryPi4などのよりハイスペックなマシンにWindowManager(GUI)をインストールした状態がオススメです。
Menu -> Preferences -> Raspberry Pi Configuration を起動します。
Systemタブで、Passwordを任意のものに、Hostnameを「enviro-host」に設定します。次にInterfacesタブで、SSHをEnableに変更します。最後に右下の「OK」を押します。この際、再起動が必要になる場合があります。
Terminalを起動し、データ集信用のディレクトリを作成します。2行目のコマンドも併せて実行し、デスクトップにリンクを作っておくと便利かもしれません。
mkdir -p enviro-logger/data
cd Desktop && ln -s ../enviro-logger
ここに集まったデータ(csv)を適宜、Excelなどのツールで解析して必要な知見を取り出すことができます。以下では、軽量なウェブサーバを立ち上げて、収集されたデータをビジュアライズ(グラフ化)する方法を紹介します。こちらは実習向けでなく、データロガーの実習を行う講師(運営)側で一台準備が出来たら十分です。Google Chartを利用しますので、インターネットに繋がっている必要があります。
ファイル構造は以下のようになります。sinatra.rbがウェブサーバ本体、.erbが各ページのソースです。
enviro-logger/
- sinatra.rb
- views/
- index.erb
- files.erb
必要な環境をインストールします。
sudo apt install -y ruby
sudo gem install sinatra
sinatra.rbは以下のようになります。メインのページではCSVファイルを読み込んでグラフを描画します。もう一つ”files”というページでは、データディレクトリに届いたファイルの一覧を表示します。
require 'sinatra'
set :public_folder, __dir__ + '/data'
get '/' do
@names = []
@dates = []
Dir.entries(settings.public_folder).sort.each do |f|
next unless f.end_with? ".csv"
begin
n, d = f.match(/([a-zA-Z0-9]+)-([0-9]+)\.csv/)[1,2]
@names << n unless @names.include? n
@dates << d unless @dates.include? d
rescue
puts "invalid name: #{f}"
end
end
@name = params[:name] || @names.first
@date = params[:date] || @dates.last
@csv1 = settings.public_folder+"/#{@name}-#{@date}.csv"
@csv2 = settings.public_folder+"/#{@name}-stat.csv"
if File.file?(@csv1) && File.file?(@csv2)
erb :index
else
'File not found<br>'+@csv1+"<br>"+@csv2
end
end
get '/files' do
erb :files
end
views/index.erb
は以下のようになります。
<html>
<head>
<title>Envrophat</title>
<style>
body{
font-family: Tahoma, sans-serif;
}
div#header{
display: flex;
}
h1{
font-size: larger;
margin: 0 20px 20px 0;
}
h2{
clear: both;
}
</style>
<!--Load the AJAX API-->
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
<script type="text/javascript">
// Load the Visualization API and the corechart package.
google.charts.load('current', {'packages':['line', 'corechart']});
// Set a callback to run when the Google Visualization API is loaded.
google.charts.setOnLoadCallback(drawChart);
// Callback that creates and populates a data table,
// instantiates the pie chart, passes in the data and
// draws it.
function drawChart() {
// [chart1]
// 直近1日分のグラフを描画します
var data1 = google.visualization.arrayToDataTable([
['Time', 'Temp', 'Light'],
<% File.foreach(@csv1).each do |line| %>
<% row = line.strip.split(",")
y,m,d = row[0].split("-").map {|v| v.to_i}
h,mi,s = row[1].split(":") %>
[<%= "new Date(#{y},#{m-1},#{d},#{h},#{mi},#{s})" %>, <%= row[2] %>, <%= row[3] %>],
<% end %>
]);
// Set chart options
var options1 = {
series: {
// Gives each series an axis name that matches the Y-axis below.
0: {axis: 'Temp'},
1: {axis: 'Light'}
},
axes: {
// Adds labels to each axis; they don't have to match the axis names.
y: {
Temp: {label: 'Temps (Celsius)'},
Light: {label: 'Light'}
}
},
'width':800,
'height':300};
// Instantiate and draw our chart, passing in some options.
var chart1 = new google.charts.Line(document.getElementById('chart1'));
chart1.draw(data1, options1);
// [chart2]
// name-stat.csvから日次のグラフを描画します
// 上記のcharts.Lineでは最大最小の範囲を表現する記法がなかったため、
// 一つ古いバージョン(corechart)のグラフを利用して描画しています。
var data2 = new google.visualization.DataTable();
data2.addColumn('date', 'Time');
data2.addColumn('number', 'Temp');
data2.addColumn({id: "min", type:'number', role:'interval'});
data2.addColumn({id: "max", type:'number', role:'interval'});
data2.addColumn('number', 'Light');
<% File.foreach(@csv2).each do |line| %>
<% row = line.strip.split(",")
y,m,d = row[0].split("-").map {|v| v.to_i}
%>
data2.addRow([<%= "new Date(#{y},#{m-1},#{d})" %>, <%= row[3] %>,<%= row[1] %>,<%= row[2] %>,<%= row[6] %>]);
<% end %>
// Set chart options
var options2 = {
series: {
// Gives each series an axis name that matches the Y-axis below.
0: {targetAxisIndex: 0},
1: {targetAxisIndex: 1}
},
vAxes: {
// Adds labels to each axis; they don't have to match the axis names.
y: {
Temp: {label: 'Temps (Celsius)'},
Light: {label: 'Light'}
}
},
intervals: { 'style':'area' },
'width':800,
'height':300};
var chart2 = new google.visualization.LineChart(document.getElementById('chart2'));
chart2.draw(data2, options2);
}
</script>
</head>
<body>
<div id="header">
<h1>Envirophat</h1>
<form action="/" method="get">
<select name="name">
<% @names.each do |n| %>
<option value="<%= n %>" <%= "selected" if n == params[:name] %>><%= n %></option>
<% end %>
</select>
<select name="date">
<% @dates.each do |d| %>
<option value="<%= d %>" <%= "selected" if d == params[:date] %>><%= d %></option>
<% end %>
</select>
<button>Update</button>
</form>
</div>
<div id="chart1"></div>
<div id="chart2"></div>
<p><a href="files">CSV files</a></p>
</body>
</html>
views/files.erb
は以下のようになります。
<html>
<head>
<title>Envrophat</title>
<style>
body{
font-family: Tahoma, sans-serif;
}
</style>
</head>
<body>
<a href="/">Back to top</a>
<ul>
<% Dir.entries(settings.public_folder).sort.each do |f| %>
<% next unless f.end_with? ".csv" %>
<li><a href="<%= f %>"><%= f %></a></li>
<% end %>
</ul>
<a href="/">Back to top</a>
</body>
</html>
ここではRubyという言語を使っていますので、起動コマンドは以下のようになります。
ruby enviro-logger/sinatra.rb
終了する場合は「Ctrl+C」を入力してください。上記はテストモードで起動する場合で、エラーメッセージが詳細に表示されるなど便利ではありますが、サーバを起動したマシン以外からは閲覧できないという制限があります。同じネットワーク内の他のホストからも見えるようにしたい場合、APP_ENV=production
という環境変数を追加することで起動モードの切り替えが可能です。
マシン起動時に自動起動したい場合は、以下のように書いておくことができます。
@reboot APP_ENV=production ruby enviro-logger/sinatra.rb 2>&1 | logger -p cron.info -t "enviro"
033
(16進数だとx1b
)で始まる特殊記号の後ろに番号を付けることでターミナルへの出力を装飾することができます。グラフを描く例でも出たように、色を付ける場合は以下のように記述します。
import sys
sys.stderr.write("\033[90mHello!\033[0m\n") # gray
sys.stderr.write("\033[91mHello!\033[0m\n") # red
sys.stderr.write("\033[92mHello!\033[0m\n") # green
sys.stderr.write("\033[93mHello!\033[0m\n") # yellow
sys.stderr.write("\033[94mHello!\033[0m\n") # blue
sys.stderr.flush()
他にも様々な装飾が定義されていますので、以下のようなスクリプトを書いて実際の環境でどのように見えるか確認してから使うと良いでしょう。
import sys
for i in range(0,100):
sys.stderr.write("\033[%dm%d Hello!\033[0m\n" % (i,i))
sys.stderr.flush()
Raspberry Piの魅力の一つはGPIOを初めから備えていて、各種電子部品を簡単に接続することができる点です。LEDを光らせたり音を出したり、あるいはモータを駆動させて動き回ったり、逆に、光や音を検知してなんらかのアクションに結びつけたり、コンピュータが現実の世界とのやり取りを出来るようになると活用の幅がぐっと広がります。
GPIOとは、「 General Purpose Input Output」の頭文字をとったもので、日本語にすると汎用の入出力、といった意味になります。Raspberry Pi以外にも多くのマイコンボード(例えばArduinoなど)でも使われている仕組みです。本体の端に沢山のピンが立っている部分がRaspberry PiのGPIOです。次節からGPIOの基本的な使い方をPythonのプログラムを使って確認してゆきます。以下の部品があれば、実際に動かしてみることも可能です。
公式にはPythonを使った説明が多く、恐らく(Pythonの環境が)最も整っていると思われますが、先述のRubyなど別の言語からでも扱うことができます。また、さらに専門的な回路(例えば発振を伴うようなもの)の制御にはC言語が必要になることもあります。
公式サイトに掲載されていたピンの配置図を転載します。
まずは、LEDを点滅させる簡単な回路を組んでみましょう。以下ような回路を組み立ててください。黄色の線はGPIOの4番に、黒の線はGROUND(接地)につながっています。抵抗はどちら向きでも構いませんが、LEDは図に書いた向きでないと通電しませんので注意してください。この回路図は「Fritzing(参考URLに掲載)」というツールを使って描いたものです。
この回路を動かすPythonプログラムは以下のようになります。nanoエディタなどを使ってファイルを作成・保存してください。
gpio-out.py
import time
import RPi.GPIO as GPIO
sensor = 4
GPIO.setmode(GPIO.BCM)
GPIO.setup(sensor,GPIO.OUT)
for i in range(5):
GPIO.output(sensor,GPIO.HIGH)
time.sleep(1)
GPIO.output(sensor,GPIO.LOW)
time.sleep(1)
GPIO.cleanup()
以下のコマンドで実行します。GPIOの操作は管理ユーザ(root)の権限で行う必要があるため、「sudo」に続けてPythonを起動します。
sudo python gpio-out.py
1秒間隔で5回、LEDが点灯したでしょうか?
次は、逆にGPIOのピンに対して入力があった(=電圧がかかった)状態の検出をしてみます。以下のような回路を組んでください。抵抗は先ほどのものと同じで、LEDの代わりにスイッチが配置され、GROUNDへの線(黒色)が無くなり、代わりに赤いジャンパ線が3.3Vの電源ピンに接続されています。ここでは説明のためジャンパ線の色分けをしていますが、もちろん、どの色を使って接続しても構いません。
プログラムは少し複雑になって、以下のようになります。
gpio-in.py
import time
import RPi.GPIO as GPIO
sensor = 4
GPIO.setmode(GPIO.BCM)
GPIO.setup(sensor,GPIO.IN,GPIO.PUD_DOWN)
previous_state = False
current_state = False
while True:
time.sleep(0.1)
previous_state = current_state
current_state = GPIO.input(sensor)
if current_state != previous_state:
if current_state:
print "HIGH"
else:
print "LOW"
0.1秒ごとに、4番ピンの状態を取得し、前回取得した値と異なる結果が得られたら、スイッチが押された(または離された)と判断し、現在の状態を画面に表示します。このプログラムは延々と監視を続けますので、終了したい場合は、「Ctrl」キーを押しながら「C」をタイプしてください。
以下は、ボタンが押された際にSlackというチャットのプラットフォームにメッセージを送信する例です。AmazonがDashボタンという新製品を出した時に、面白そうだと思って真似て作ったのですが、その後、Dashボタンもサービスを終了するに至って、こちらもお蔵入りしてしまっていました。
import time
import RPi.GPIO as GPIO
sensor = 4
GPIO.setmode(GPIO.BCM)
GPIO.setup(sensor,GPIO.IN,GPIO.PUD_DOWN)
previous_state = False
current_state = False
def slack():
import requests, json
WEB_HOOK_URL = "https://..." # Slack の URL
requests.post(WEB_HOOK_URL, data = json.dumps({
'text': 'Notifycation From Python.', #
'username': 'Python-Bot',
'icon_emoji': ':smile_cat:',
'link_names': 1,
}))
while True:
time.sleep(0.1)
previous_state = current_state
current_state = GPIO.input(sensor)
if current_state != previous_state:
if current_state:
print("HIGH")
else:
slack()
※本節は、公式サイトで紹介されている「Parent Detector」の和訳(一部要約)になります。
必要なもの Raspberry Pi本体の他に以下のパーツが必要です。
PIRモーションセンサは、赤外線人感センサなどと呼ばれることがあります。赤外線を使って、人や動物の動きを検知します。街灯や玄関などの照明で、人が通ると勝手に点灯するタイプのものにはこのセンサが使われています。まずは、このセンサをRaspberry PiのGPIOに接続しましょう。画像は公式サイトから引用させて貰っていますが、この図の結線と実際のセンサ上の端子の順番が異なっているので注意してください(細かい型番ごとなどに複数のバリエーションがあるのだと思います)。 「GPIOの5VとPIRセンサのVCC」「GPIOのGROUNDとPIRセンサのGND」「GPIOの4番ピンとPIRセンサのOUT」端子をそれぞれ結線してください。
まずは、この回路が正しく動くか検証するプログラムを書きましょう。nanoエディタで以下のコードを記述して保存してください。
pirtest.py
import RPi.GPIO as GPIO
import time
sensor = 4
GPIO.setmode(GPIO.BCM)
GPIO.setup(sensor, GPIO.IN, GPIO.PUD_DOWN)
previous_state = False
current_state = False
while True:
time.sleep(0.1)
previous_state = current_state
current_state = GPIO.input(sensor)
if current_state != previous_state:
new_state = "HIGH" if current_state else "LOW"
print("GPIO pin %s is %s" % (sensor, new_state))
以下のコマンドで実行します。
sudo python pirtest.py
センサの前で(といってもかなり広範な範囲を拾ってしまうのですが)、体を動かすと以下のような表示が変化したでしょうか。うまく反応しない場合は、センサの設定を調整する必要があります(ここが、このプロジェクトの肝だったりします)。
GPIO pin 4 is HIGH
GPIO pin 4 is LOW
センサの下部に二つ並んだ黄色いツマミで、センサの感度と反応時間を調整します。プログラムを動かしたまま、サイズの合ったプラスドライバでツマミを動かし、思い通りの反応をする位置を探してください。ほんのちょっと動かしただけでも、結果が大きく変わることもありますので、ここは根気よく試行が必要です。
調整が終わったら、「Ctrl + C」でプログラムを終了してください。
次はいよいよ、Parent Detector本体のプログラムです。以下には完成版をそのまま掲載しました。実際には、先ほどのpirtest.pyをコピーして、カメラのプレビュー部分などから少しずつ書き足しながら都度、動作確認して記述してゆく書き方が良いかと思います。プログラミング言語に慣れていない場合、あまり一気に書き上げると、エラーが起こった時に原因箇所を突き止められなくなってしまうことがあります。
pirCamera.py
import RPi.GPIO as GPIO
import time
import picamera
import datetime # new
def get_file_name(): # new
return datetime.datetime.now().strftime("%Y-%m-%d_%H.%M.%S.h264")
sensor = 4
GPIO.setmode(GPIO.BCM)
GPIO.setup(sensor, GPIO.IN, GPIO.PUD_DOWN)
previous_state = False
current_state = False
cam = picamera.PiCamera()
while True:
time.sleep(0.1)
previous_state = current_state
current_state = GPIO.input(sensor)
if current_state != previous_state:
new_state = "HIGH" if current_state else "LOW"
print("GPIO pin %s is %s" % (sensor, new_state))
if current_state:
fileName = get_file_name() # new
cam.start_preview()
cam.start_recording(fileName) # new
else:
cam.stop_preview()
cam.stop_recording() # new
以上で、完成です。このプログラムは起動したらずっと動きっぱなしで待機し、センサに反応があるとカメラを起動してビデオを撮影します。撮影したビデオは日付と時刻がファイル名になっていて、次々に蓄積されてゆきます。終了する場合は、pirtest.pyと同様、「Ctrl + C」です。
公式サイトには、カメラのLEDをOFFにする方法などもう少し詳しい解説があります。URLは次章の活用事例および巻末の参考資料に掲載しています。
サーボモータは、普通の(くるくると回転する)モータとは異なり、角度を指定して軸を回転させることができるモータです。ラジコンのハンドルや舵を制御するのに使われている、と言うとピンと来る方もいらっしゃるかもしれません。ロボットの関節なんかの駆動にも使われたりします。このモータを制御するにはPWM信号というパルスを生成してあげる必要があります。
今回使用したサーボモータは、SG90 MicroServo という機種です(写真参照)。駆動用の電源は単三電池を4つ繋いだ電池パックを使用しています。VccとGroundを電池に接続し、PWMをGPIOの4番ピンに接続します。Groundはさらに、GPIO側のGround端子とも接続が必要です。Raspberry Pi本体の5V端子から電源を取ると、電力不足で本体が落ちてしまうという情報があったので、このような配線にしていますが、短時間の駆動であれば(直接電源を取っても)問題なさそうです。電池パックがない場合、5V端子から電源を取るように配線してください。
PWM = 橙(ほとんど黄)
Vcc = 赤(ほとんど橙)
Ground = 茶
これで準備完了です。プログラムは以下のようになります。
import time
import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BCM)
gp_out = 4
GPIO.setup(gp_out, GPIO.OUT)
servo = GPIO.PWM(gp_out, 50)
servo.start(0.0)
for i in range(10):
servo.ChangeDutyCycle(2.5)
time.sleep(0.5)
servo.ChangeDutyCycle(12.0)
time.sleep(0.5)
GPIO.cleanup()
ChangeDutyCycleというメソッドで回転角を制御します。この値の範囲を求めるにはPWMの仕組みを理解する必要がありますが、とりあえず、今回のモータ(SG90)の場合は、2.5から12の間です。また、同様の理由で、波形を変更した後、モータが目的の位置まで回転する時間だけプログラムを待機してあげる必要があります。このプログラムでは 0.5秒間 ずつ待機しています。
参考URL: http://bufferoverruns.blogspot.jp/2016/08/raspberry-pisg-90.html
ここまでの技術の応用ではありますが、測距センサを使ったプログラムの例も紹介します。写真の右側に写っているのが今回使用しているセンサ(Sharp 2Y0A21)です。数十センチのオーダーにある物体までの距離を測るのが本来の用途ですが、今回はGPIOの4番ピンに測定端子をつないで、ON/OFFのみの検出に利用しています。なぜか、赤い線がGroundで、黒い線が5V端子です。
プログラムは以下のようになります。0.1秒ごとに4番ピンの状態を監視し、ONになった瞬間のみ、カメラを起動し、タイムスタンプを名前に付けて画像を保存しています。
import RPi.GPIO as GPIO
import time, datetime, subprocess
sensor = 4
GPIO.setmode(GPIO.BCM)
GPIO.setup(sensor, GPIO.IN, GPIO.PUD_DOWN)
previous_state = False
current_state = False
while True:
time.sleep(0.1)
previous_state = current_state
current_state = GPIO.input(sensor)
if current_state != previous_state:
new_state = "HIGH" if current_state else "LOW"
print("GPIO pin %s is %s" % (sensor, new_state))
if current_state:
f = datetime.datetime.now().strftime("%FT%T") + ".jpg"
print(f)
cmd = ["raspistill","-o",f,"-vf","-hf","-n","-t","1"]
subprocess.call(cmd)
raspistillの「-t」オプションの値を調整することで、センサ感知後どれだけの間をおいて撮影するかを調整することができます。「-vf」「-hf」は設置位置と向きに応じて適宜調整してください。
動体検知カメラが記録した画像をブラウザ経由で閲覧、統計を見ることが出来るアプリを開発します。 以下の項目を予め理解していることを、前提として記載しています(HTMLの基本以外は本書内にも解説があります)。
以下のような画面構成を想定します。左上のMotion、Streamのリンクは動体検知を実現するmotion
のコントロール画面と動画のストリーム(リアルタイムの映像)を参照するために付けました。続けて、「現在保管されている画像の枚数」「保管場所」「(ページを表示した時点の)現在時刻」が表示されています。右端のReloadは画面を再読込するためのリンクです。
緑色の棒グラフには時間帯別に撮影された写真の枚数を(最大7日間)表示しています。その下の「Motions」が画面の中に動くものを検知した際に撮影された写真、「Snapshots」が1時間ごとに定期的に撮影された写真です。グラフも含めていずれも横にスクロールが可能で、実際には下図に表示されているより多くの情報が掲載されています。
「動体検知機能付きの監視カメラ」の節を参考にして、motion
パッケージをインストールします。 /etc/motion/motion.conf
の設定は以下のように調整してください。
webcontrol_localhost off
stream_localhost off
snapshot_interval 3600 # 1時間に1回スナップショットを保存
output_pictures best # 検知中に最も動きのあったフレームのみ保存
ffmpeg_output_movies off # 検知中のムービー保管を無効化
locate_motion_mode on # 動体を検知した範囲を矩形で囲む
設定完了後、ブラウザで以下のURLにアクセスして現在のカメラ越しの状況を確認することができます。
webcontrol: http://localhost:8080
stream: http://localhost:8081
ウェブアプリケーションと一言で言っても、実は数多くの実装方法が存在します。今回は、シンプルなアプリを作るのに便利なSinatraというフレームワークを利用します。Rubyがインストールされていれば以下のコマンドでインストールが可能です。
sudo gem install sinatra -N
アプリケーションを作る場所を用意します。webappというディレクトリの中にmain.rbというファイルを作成します。
mkdir -p webapp/views && cd webapp
nano main.rb
最初の、main.rbには以下のように記述してください。
require 'sinatra'
get '/' do
erb :index
end
続けて、main.rbから呼び出される views/index.erb というファイルを作成します。
nano views/index.erb
中身は以下のように記述します。基本的なHTMLのタグの中に、<%= ... %>
で囲まれた部分があります。これがerbと呼ばれる記法で、このファイルの拡張子にもなっています。<%= ... %>
の中に、Rubyのコードを直接書くことが出来て、これを用いて、ウェブページに動的な要素を組み込みます。
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Hello, world!</title>
</head>
<body>
<h1>Hello, world!</h1>
<p><%= Time.now %></p>
</body>
</html>
2つのファイルが用意できたら、以下のコマンドでSinatraのWebサーバを起動します。
ruby main.rb
開発用では、自分自身のホストからしか起動中のページを見ることができません。他のホストからもページを見えるようにするには、APP_ENVという環境変数を設定します。
APP_ENV=production ruby motion/main.rb
ブラウザから http://localhost:4567 にアクセスして、「Hello, world!」と書かれたページが見えたら起動成功しています。(アクセスするURLは環境によって異なります。sinatraの出力を読んで確認してください。
今度は、写真を読み込めるようにします。main.rbのrequire 'sinatra'
の下に、以下の一行を追加して、ウェブアプリ側から撮影された写真が見えるように設定します。
set :public_folder, '/var/lib/motion'
以下のように、erbのコードを書いて、画像をページ内に表示することが出来るようになります。
<%
fnames = Dir.children(settings.public_folder)
fnames.each do |f| %>
<img src="<%= f %>" alt=""/>
<% end %>
これを使って撮影画像を任意のレイアウトで表示することが出来るようになりますが、今回はもうひと手間かけて、JavaScriptを経由して別のページで生成された写真とグラフを取得するように作り込んでみます。
main.rbの最下部に以下の設定を追記します。
get '/motions' do
erb :motions
end
あわせて、views/motions.erb
というファイルを新規作成し、先程の画像を表示するerbコードをその中に記載してください。サーバを再起動して、
http://localhost:4567/motions
に撮影済み画像が表示されていれば成功です。
views/index.erbのbodyタグの最後尾に以下のようなコードを追加します。<script>
で囲まれた部分はJavaScriptという言語で記述されています。このコードを差し込むと、再びトップページ(最初のmotionsが付かない方のURL
http://localhost:4567)で画像が表示されるようになります。
<div id="motions"></div>
<script>
function load(id,path){
var r = new XMLHttpRequest();
r.open("GET", path, true);
r.onreadystatechange = function () {
if (r.readyState != 4 || r.status != 200) return;
document.getElementById(id).innerHTML = r.responseText;
};
r.send();
}
load("motions","/motions");
</script>
ちょっと(かなり?)大雑把な説明ではありますが、これらの基本部分を元に、最初の画面にあったような監視カメラの機能を実装していきましょう。HTML・CSS、Ruby、JavaScriptの知識が必要ですが、インターネット上にリファレンスや豊富なサンプルがありますので、それらを適宜参考にしてください。この後に実装例が掲載されていますが、そちらはなるべく見ずに、最初の画面を自分で作るならどうするか?どんな知識が必要か?を一つ一つ考えながら、なるべく手探りで進めてください。
特に、時間帯別のグラフ描画は難易度の高い部分です。motionによって保存される画像ファイル名のパターンをよく観察し、どの部分を抜き出したら「保存された日時」が取り出せるかをまず考える必要があります。
"01-202110101234-snapshot.jpg".split("-")[1].to_i / 10000
沢山の関数が、「.(ドット)」でつながれて一見するととても複雑ですが、ひとつずつ分解して、どんなどうさをするのかirb
などのツールを使って確認しながら理解を深め、実際のコードに応用していきましょう。エラーメッセージも英語ではありますが、重要な情報が含まれています。まずはじっくり読んでみて何を言われているのかを理解するように努めてみてください。
main.rb
require 'sinatra'
set :public_folder, __dir__ + '/var_lib_motion'
configure :production do
# APP_ENV=production
set :public_folder, '/var/lib/motion'
end
get '/' do
erb :index
end
get '/motions' do
erb :motions
end
get '/activities' do
erb :activities
end
views/index.erb: メインページのコードです。写真の枚数や現在時刻などの情報はここで直接表示しています。また、画面のレイアウトに関する設定も主にここにあります。グラフと写真はJavaScriptを使って、別のページの結果を埋め込んでいます。
<html>
<head>
<title>Motion</title>
<style>
body{
font-family: Tahoma, sans-serif;
}
div#activities, div#motions, div#snapshots{
display: flex;
overflow: scroll;
}
div#motions img, div#snapshots img{
max-width: 240px;
}
h2{
clear: both;
}
</style>
</head>
<body>
<a href="http://<%= request.host %>:8080">Motion</a>
<a href="http://<%= request.host %>:8081">Stream</a>
<small><code><%= Dir.children(settings.public_folder).count %> pictures in <%= settings.public_folder %></code></small>
<span><%= Time.now %> <a href="/">Reload</a></span>
<div id="activities" style="height: 120px"></div>
<h2>Motions</h2>
<div id="motions"></div>
<h2>Snapshots</h2>
<div id="snapshots"></div>
<p>To get all pictures:</p>
<pre>
rsync -avz pi@<%= request.host %>:/var/lib/motion/ motion/
</pre>
<script>
function load(id,path){
var r = new XMLHttpRequest();
r.open("GET", path, true);
r.onreadystatechange = function () {
if (r.readyState != 4 || r.status != 200) return;
document.getElementById(id).innerHTML = r.responseText;
};
r.send();
}
function load_motions(id,offset){
var path = "/motions?offset="+offset+"&id="+id;
load(id,path);
}
load_motions("motions",0);
load_motions("snapshots",0);
load("activities","/activities");
</script>
</body>
</html>
views/motions.rb:
撮影された画像を表示する部分です。名前から判別して、動体検知された写真(数字のみ)と、定期撮影された写真(snapshot)のそれぞれを表示するように動作変更をすることが可能です。reverse[0,10]
で「最新の10枚」のみを表示しています。
<%
sp = params[:id] == "snapshots"
fnames = Dir.children(settings.public_folder)
.select {|f| sp ? f.include?("snapshot") : !f.include?("snapshot") }
.sort_by {|f| f.split("-")[1].to_i }.reverse[0,10]
%>
<% fnames.each do |f| %>
<div>
<a href=""><img src="<%= f %>" alt=""/></a>
<small><%= f %></small>
</div>
<% end %>
views/activities.erb: こちらはグラフの表示部分です。1時間単位の撮影枚数を取得する部分のコードがちょっと複雑です。7日分のループを回して横長のグラフを書くようにしています。このグラフの各要素をクリックすると、その時間帯の写真が見える、などのように拡張するのも面白そうです。
<style>
table.activities{
border-collapse:collapse;
}
table.activities td{
vertical-align: bottom;
font-size: 9px;
color: #ccc;
}
table.activities div{
width: 10px;
background-color: LightSeaGreen;
}
</style>
<table class="activities">
<tr>
<%
freqencies = Dir.children(settings.public_folder)
.select {|f| !f.include?("snapshot") }
.map {|f| f.split("-")[1].to_i / 10000 }
.group_by(&:itself)
.map{|k,v| [k,v.count] }.to_h
h = (120 - 1) / freqencies.values.max
t = Time.now
h_offset = t.hour
(24 * 7).times do
i = t.strftime("%Y%m%d%H").to_i
%>
<td>
<div class="activities-box" style="height: <%= freqencies[i].to_i*h+1 %>px"></div>
</td>
<%
t -= 3600
end
%>
</tr>
<tr>
<% t = Time.now %>
<td colspan="<%= h_offset %>"><%= t.strftime("%F") %></td>
<%
t -= 3600 * (h_offset+1)
6.times do
%>
<td colspan="24"><%= t.strftime("%F") %></td>
<%
t -= 3600 * 24
end
%>
</tr>
</table>
ここまでの実装で、sinatraを自分で起動してWebサーバを立ち上げていましたが、運用中は電源を入れたらそのまま使えるようにした方が便利です。cronという自動実行の仕組みを使って設定します。
crontab -e
立ち上がってきたエディタ(最初は選択画面が表示されます)を使って以下のような行を設定します。
@reboot APP_ENV=production ruby webapp/main.rb 2>&1 | logger -p cron.info -t "motion"
「@reboot」は「起動後に実行する」という意味で、その後ろに実行したいコマンドを記述します。「2>&1 |」はリダイレクトとパイプと呼ばれる機能です。これを使って、このプログラムからの出力(エラーも含む)のログ(処理内容の記録)を保管するように設定しています。
https://www.raspberrypi.org/ - Raspberry Pi公式サイト
https://www.raspberrypi.org/learning/parent-detector/ - Parent Detector
https://www.ruby-lang.org/ - Ruby公式サイト
https://www.python.org/ - Python公式サイト
http://blog.muchuu.net/post/113402952220/ - nullmailerのセットアップ
http://lchikaamazon.hatenablog.com/ - Raspberry Pi電子工作
http://fritzing.org/home/ - 電子回路図描画アプリ
http://www.nano-editor.org/ - GNU Nano公式サイト
https://github.com/richardghirst/PiBits/tree/master/ServoBlaster - サーボモータ制御ドライバ
http://www.sharpsma.com/Webfm_Send/1208 - 測距センサ
https://projects.raspberrypi.org/en/projects/getting-started-with-minecraft-pi
Getting Started with Minecraft Pi
https://github.com/lumbermill/takachiho/tree/master/raspi/08-motion - 動体検知motionの使い方
https://qiita.com/lumbermill/items/d4b3450e4ffa4fee1783 - RaspbianでOpenJTalkを使う
https://github.com/lumbermill/takachiho/tree/master/raspi/09-signage - キオスク端末化する
https://faavo.jp/miyazaki/project/3218 - Faavo宮崎
2015年に執筆した入門書をベースにして、様々な課外授業向けに内容を加筆し続けています。
「CC BY 4.0」に則った再利用・再頒布が可能です。本書に関するお問い合わせ、誤字・脱字等のご指摘は同社問い合わせフォームにお寄せ頂ければ幸いです。
https://lumber-mill.co.jp/inquiry.php
著者: ITO Yosei 伊藤陽生 伊藤製作所・株式会社ランバーミル代表。2003年頃からシステム開発に携わっています。Cから始まりJava、JavaScript、Objective-C、PHP、 Python、SQLなどを扱ってきました。現在は主にRubyを業務に用いています。Linuxサーバの構築だったり、ウェブサイトのデザインをしたり、これらの分野の研修講師として働いていたこともあります。技術の移り変わりの速いこの領域においては、それぞれの経験値そのものよりも、いかに新しい技術にうまくキャッチアップし、チームで共有し、身近な問題の解決に結びつけられるか、という、もう一段抽象的な技能こそが重要なのではないかと考えています。
https://lmlab.net https://lumber-mill.co.jp