オープンソース活用事例2【Raspberry Piプログラム】
前頁の“Genuino101プログラム”に続き、今回は「Raspberry Piプログラム」の作成について紹介を致します。
Raspberry PiのBluetoothライブラリのインストール
Raspberry PiはRaspbian(原稿執筆時のバージョンはMarch 2017)で動作させ、プログラムはPythonで作成します。PythonでBluetoothを使用するにはライブラリをインストールする必要があります。
Bluetoothのライブラリにはいくつか種類がありますが、今回は「bluepy」というライブラリを使用します。ライブラリのインストールは以下の手順で行います。
(1) はじめにライブラリのインストールに必要なパッケージをインストールします。
sudo apt-get install libglib2.0-dev
(2) ライブラリをインストールします。Pythonのバージョンにより実行するコマンドが異なります。
Python2.7の場合
sudo pip install bluepy
Python3.4の場合
sudo pip3 install bluepy
以上でライブラリのインストールは終了です。
Raspberry Piのプログラム
Raspberry Pi側のプログラムでは、Genuino101に対して動作開始、停止の信号の送出、リレーの励磁信号、接点信号の状態、接点の駆動回数の受信を行います。そして励磁信号が変化した場合、一定時間以内に接点信号が変化するかどうかを判定し、変化がなかった場合は制御回路に異常が発生したとして、エラーメッセージを表示します。また接点の駆動回数がリレーの寿命となる回数分動作したかどうかを判定し、寿命間近となったら注意(寿命まで残り100回以下)として残り寿命の表示エリアの背景を黄色、警告(寿命まで残り10回以下)として背景を赤色で表示します。
このプログラムのリストは以下のようになります。
リスト2 Raspberry Piのプログラム(demo.py)
#! /usr/bin/env python3
# -*- coding: utf-8 -*-
# モジュールをインポート
import bluepy.btle as btle
import struct
import tkinter as Tk # Python3.4
import tkinter.messagebox as Msg # Python3.4
#import Tkinter as Tk # Python2.7
#import tkMessageBox as Msg # Python2.7
import sys
# Frameクラスを定義
class Frame(Tk.Frame):
def __init__(self, master=None):
Tk.Frame.__init__(self, master)
# Variableオブジェクトのインスタンスを生成
self.stat = Tk.StringVar()
self.sig = Tk.StringVar()
self.rly = Tk.StringVar()
self.eep = Tk.StringVar()
self.life = Tk.IntVar()
self.rmlife = Tk.StringVar()
# 変数の初期化
self.cnt = 10
self.pcdata = 0
self.prv_data = 0
self.rlydata = 0
self.eepdata = 0
self.lifedata = 100000
self.started = False
# Variableに値を設定
self.var_set()
# ウィンドウのタイトルを設定
self.master.title('Relay Control Demo')
# リレーの状態を表示するフレームを設定
f_display = Tk.Frame(self, relief=Tk.RIDGE, bd=4)
f_display.pack( padx=5, pady=5 )
# リレーの状態を表示するラベルを設定
self.fc_display = Tk.Label(f_display, textvariable=self.stat, width=15, relief=Tk.SUNKEN, bd=2, font=('Helvetica', '24'), bg='white')
self.fc_display.pack()
# リレーの入出力信号の状態を表示するフレームを設定
g_display = Tk.Frame(self, relief=Tk.RIDGE, bd=4)
g_display.pack( padx=5, pady=5 )
# リレーの入力信号の状態を表示するラベルを設定
self.g1_display = Tk.Label(g_display, textvariable=self.sig, width=30, relief=Tk.SUNKEN, bd=2, font=('Helvetica', '12'), bg='white')
self.g1_display.pack()
# リレーの出力信号の状態を表示するラベルを設定
self.g2_display = Tk.Label(g_display, textvariable=self.rly, width=30, relief=Tk.SUNKEN, bd=2, font=('Helvetica', '12'), bg='white')
self.g2_display.pack()
# リレーの寿命を表示するフレームを設定
h_display = Tk.Frame(self, relief=Tk.RIDGE, bd=4)
h_display.pack( padx=5, pady=5 )
# リレーの駆動回数を表示するラベルを設定
self.h1_display = Tk.Label(h_display, textvariable=self.eep, width=30, relief=Tk.SUNKEN, bd=2, font=('Helvetica', '12'), bg='white')
self.h1_display.grid(row=0,column=0, columnspan=2)
# リレーの寿命を表示するラベルを設定
self.h2_display = Tk.Label(h_display, text='Life:', width=15, relief=Tk.SUNKEN, bd=2, font=('Helvetica', '12'), bg='white')
self.h2_display.grid(row=1,column=0)
# リレーの寿命を入力するテキストボックスを設定
self.lifeentry = Tk.Entry(h_display, textvariable=self.life, width=14, justify=Tk.RIGHT, relief=Tk.SUNKEN, bd=2, font=('Helvetica', '12'), bg='white')
self.lifeentry.grid(row=1,column=1)
self.lifeentry.bind('', self.func)
# リレーの残り寿命を表示するラベルを設定
self.h3_display = Tk.Label(h_display, textvariable=self.rmlife, width=30, relief=Tk.SUNKEN, bd=2, font=('Helvetica', '12'), bg='white')
self.h3_display.grid(row=2,column=0, columnspan=2)
# ボタンを表示するフレームを設定
f_button = Tk.Frame(self)
f_button.pack(pady=2)
# スタートボタンを設定
self.b_start = Tk.Button(f_button, text='Start', command=self.start)
self.b_start.pack(side=Tk.LEFT, padx=1)
# ストップボタンを設定
self.b_stop = Tk.Button(f_button, text='Stop', command=self.stop, state=Tk.DISABLED)
self.b_stop.pack(side=Tk.LEFT, padx=1)
# self.measureを実行
self.measure()
# テキストボックスに入力された値を型変換して変数に代入
def func(self, ev):
self.lifedata = int(self.life.get())
# Genuino101からデータを取得してVariableに値を設定
def var_set(self):
# Genuino101からデータを取得
try:
self.pcdata = struct.unpack( 'B', PC.read() ) # 入力信号の状態
self.rlydata = struct.unpack( 'B', Relay.read() ) # 出力信号の状態
self.eepdata = struct.unpack( 'I', EEPROM.read() ) # 駆動回数
# エラー処理
except btle.BTLEException:
Msg.showerror(title = 'Error', message = 'BLE Device fail or not found')
root.destroy()
sys.exit()
# Variableに値を設定
self.stat.set('Relay Status:OFF') # リレーの状態
self.sig.set('Input Signal:%d' % (self.pcdata)) # 入力信号の状態
self.rly.set('Output Signal:%d' % (self.rlydata)) # 出力信号の状態
self.eep.set('Total Count:%d' % (self.eepdata)) # 駆動回数
self.life.set(self.lifedata) # 寿命回数
self.rmlife.set('Remain Life:%d' % (self.lifedata - int(self.eepdata[0]))) # 残り寿命回数
# Startボタンを押したとき
def start(self):
# Genuino101にデータを送信
try:
Switch.write(struct.pack('<b', 1))
# エラー処理
except btle.BTLEException:
Msg.showerror(title = 'Error', message = 'BLE Device fail or not found')
root.destroy()
sys.exit()
self.started = True
self.cnt = 10
# ボタンの状態を変更
self.b_start.configure(state=Tk.DISABLED)
self.b_stop.configure(state=Tk.NORMAL)
# self.countingを実行
self.counting()
# Stopボタンを押したとき
def stop(self):
# Genuino101にデータを送信
try:
Switch.write(struct.pack('<b', 0))
# エラー処理
except btle.BTLEException:
Msg.showerror(title = 'Error', message = 'BLE Device fail or not found')
root.destroy()
sys.exit()
self.started = False
# ボタンの状態を変更
self.b_start.configure(state=Tk.NORMAL)
self.b_stop.configure(state=Tk.DISABLED)
# Variableに値を設定
self.stat.set('Relay Status:OFF')
# Genuino101からデータを取得
def measure(self):
# Genuino101からデータを取得
try:
self.pcdata = struct.unpack( 'B', PC.read() )
self.rlydata = struct.unpack( 'B', Relay.read() )
self.eepdata = struct.unpack( 'I', EEPROM.read() )
# エラー処理
except btle.BTLEException:
Msg.showerror(title = 'Error', message = 'BLE Device fail or not found')
root.destroy()
sys.exit()
# 入力信号が変化したか判定
if self.prv_data != self.pcdata:
self.cnt = 5
self.prv_data = self.pcdata
# Variableに値を設定
self.sig.set('Input Signal:%d' % (self.pcdata))
self.rly.set('Output Signal:%d' % (self.rlydata))
self.eep.set('Total Count:%d' % (self.eepdata))
self.rmlife.set('Remain Life:%d' % (self.lifedata - int(self.eepdata[0])))
# 残り寿命の判定
if (self.lifedata - int(self.eepdata[0])) <
self.h3_display.configure(bg='red')
elif (self.lifedata - int(self.eepdata[0])) <
self.h3_display.configure(bg='yellow')
else:
self.h3_display.configure(bg='white')
# 100m秒後にself.measureを実行
self.after(100, self.measure)
# 入力信号が一定時間内に変化するか判定
def counting(self):
if self.started:
# 入力信号が変化しなかったとき
if self.cnt <=
self.stat.set('Relay Status:NG')
self.fc_display.configure(bg='red')
# 入力信号が変化したとき
else:
self.stat.set('Relay Status:OK')
self.fc_display.configure(bg='white')
self.cnt -=1
# 1秒後にself.countingを実行
self.after(1000, self.counting)
# メインウィンドウを生成
root = Tk.Tk()
# ウィンドウサイズを350×250ドットに指定
root.geometry("350x280")
# メインウィンドウのサイズ変更を禁止
root.resizable(0,0)
# Genuino101と接続
try:
Rly = btle.Peripheral(":4F:EE:0F:CC:7E")
# エラー処理
except btle.BTLEException:
Msg.showerror(title = 'Error', message = 'BLE Device fail or not found')
root.destroy()
sys.exit()
# キャラクタリスティックのリストを取得
chrts = Rly.getCharacteristics()
# UUIDに対応したキャラクタリスティックのインスタンスを生成
for chrt in chrts:
# リレー制御開始用キャラクタリスティック
if chrt.uuid == btle.UUID("19b10011-E8F2-537E-4F6C-D104768A1214"):
Switch = chrt
# リレー接点信号用キャラクタリスティック
elif chrt.uuid == btle.UUID("19B10012-E8F2-537E-4F6C-D104768A1214"):
PC = chrt
# リレー励磁信号用キャラクタリスティック
elif chrt.uuid == btle.UUID("19B10013-E8F2-537E-4F6C-D104768A1214"):
Relay = chrt
# リレー駆動回数用キャラクタリスティック
elif chrt.uuid == btle.UUID("19B10014-E8F2-537E-4F6C-D104768A1214"):
EEPROM = chrt
# Frameクラスのインスタンスを生成
f = Frame(master=root)
# Frameを配置
f.pack()
# mainloopを実行
root.mainloop()
それではこのプログラムを順番に説明します。
- 4~11行目
- モジュールをインポートしています。
- 14行目
- Frameクラスを定義しています。このクラスでウィンドウ画面を作っています。
- 15行目
- Frameクラスのコンストラクタを定義しています。
- 16行目
- Tk.Frameクラスの__init__メソッドで初期化しています。
- 18~24行目
- このあとのTk.Labelなどで使用するVariableオブジェクトのインスタンスを生成しています。
- 26~33行目
- プログラム中で使用する変数を初期化しています。
- 35~36行目
- Variableに値を設定する関数を実行しています。
- 38~39行目
- ウィンドウのタイトルを設定しています。
- 41~43行目
- リレーの状態を表示するフレームを設定しています。
- 45~47行目
- リレーの状態を表示するラベルを設定しています。
- 49~51行目
- リレーの入出力信号の状態を表示するフレームを設定しています。
- 53~55行目
- リレーの入力信号の状態を表示するラベルを設定しています。
- 57~59行目
- リレーの出力信号の状態を表示するラベルを設定しています。
- 61~63行目
- リレーの寿命を表示するフレームを設定しています。
- 65~67行目
- リレーの駆動回数を表示するラベルを設定しています。
- 69~71行目
- リレーの寿命を表示するラベルを設定しています。
- 73~76行目
- リレーの寿命を入力するテキストボックスを設定しています。
- 78~80行目
- リレーの残り寿命を表示するラベルを設定しています。
- 82~84行目
- ボタンを表示するフレームを設定しています。
- 86~88行目
- スタートボタンを設定しています。
- 90~92行目
- ストップボタンを設定しています。
- 94~95行目
- Genuino101からデータを取得する関数を実行しています。
- 98行目
- テキストボックスに入力された値を型変換して変数に代入する関数です。
- 99行目
- テキストボックスの値を取得し、int型に変換して変数に代入しています。
- 102行目
- Genuino101からデータを取得してVariableに値を設定する関数です。
- 103~107行目
- Genuino101からデータを取得しています。try節内で処理しているので例外が発生した場合、エラー処理が行われます。
- 108~112行目
- Genuino101とBluetoothで通信しているときにエラーが発生するとBTLEExceptionが発生します。そのエクセプションが発生した場合の処理を記述しています。具体的にはエラーが発生したことを通知するメッセージボックスを表示し、メッセージボックスのOKボタンが押されたらウィンドウを閉じ、プログラムを終了します。
- 113~119行目
- Variableに値を設定しています。
- 122行目
- スタートボタンが押された時に呼び出される関数です。
- 123~125行目
- Genuino101にデータを送信し、動作を開始させます。try節内で処理しているので例外が発生した場合、エラー処理が行われます。
- 126~130行目
- Bluetoothの通信エラー(BTLEException)が発生した場合の処理を記述しています。
- 131行目
- Genuino101の動作状態を示す変数にTrueを代入します。
- 132行目
- タイムアウトエラーを計測する変数を初期化しています。
- 133~135行目
- スタートボタンをディセーブルに、ストップボタンをイネーブルにしています。
- 136~137行目
- リレーの入力信号が一定時間内に変化するか判定する関数を実行しています。
- 140行目
- ストップボタンが押された時に呼び出される関数です。
- 141~143行目
- Genuino101にデータを送信し、動作を停止させます。try節内で処理しているので例外が発生した場合、エラー処理が行われます。
- 144~148行目
- Bluetoothの通信エラー(BTLEException)が発生した場合の処理を記述しています。
- 149行目
- Genuino101の動作状態を示す変数にFalseを代入します。
- 150~152行目
- スタートボタンをイネーブルにし、ストップボタンをディセーブルにしています。
- 153~154行目
- リレーの動作状態を示すVariableに値を設定しています。
- 157行目
- Genuino101からデータを取得する関数です。
- 158~162行目
- Genuino101からデータを取得します。try節内で処理しているので例外が発生した場合、エラー処理が行われます。
- 163~167行目
- Bluetoothの通信エラー(BTLEException)が発生した場合の処理を記述しています。
- 168~171行目
- リレー接点信号が変化したかどうか判定しています。変化していた場合はタイムアウトエラーをカウントする変数を初期化しています。
- 172~176行目
- Variableに値を設定しています。
- 177~183行目
- 残り寿命の判定をしています。残り寿命が10回以下のとき、残り寿命を表示しているラベルの背景を赤に、100回以下の時は黄色に、それ以上のときは白にしています。
- 184~185行目
- 100m秒後にmeasure関数を実行します。
- 188行目
- 入力信号が一定時間内に変化するか判定する関数です。
- 189行目
- リレーの動作状態がTrueのときに判定を行います。
- 190~193行目
- 一定時間経過しても入力信号が変化しなかった場合の処理です。リレーの状態表示を「NG」とし、背景を赤くします。
- 194~197行目
- 一定時間経過するまでに入力信号が変化した場合の処理です。リレーの状態表示を「OK」とし、背景を白くします。
- 198行目
- 経過時間をカウントダウンしています。
- 199~200行目
- 1秒後にcounting関数を実行します。
- 202~203行目
- メインウィンドウとなるTkインスタンスを生成しています。
- 204~205行目
- ウィンドウサイズを350×280ドットに指定しています。
- 206~207行目
- メインウィンドウのサイズ変更を禁止しています。
- 209~211行目
- Genuino101と接続します。try節内で処理しているので例外が発生した場合、エラー処理が行われます。
- 212~216行目
- Bluetoothの通信エラー(BTLEException)が発生した場合の処理を記述しています。
- 218~219行目
- キャラクタリスティックのリストを取得しています。
- 221~234行目
- 取得したキャラクタリスティックのUUIDを調べて、リスト中のキャラクタリスティックのうち制御に必要なキャラクタリスティックを取り出し、そのインスタンスを生成しています。
- 236~237行目
- Frameクラスのインスタンスを生成しています。
- 238~239行目
- Frameを配置しています。
- 240~241行目
- mainloopを実行しています。
※次頁では、「動作テスト」について紹介します。

