この文書ではタクトスイッチとトグルスイッチをPi4Jで制御する方法について述べています.Pi4Jではスイッチの切り替えをイベントとして取り扱いますので,GUIのアプリケーションを作ったことのある人には大変分かりやすいかと思います.反対にイベントを扱ったことがない人にとっては少しとっつきにくいかもしれませんので,なるべくゆっくり解説します.
回路構成
はじめに回路構成を説明します.下の図はタクトスイッチとトグルスイッチ周りの回路図です.タクトスイッチにはプルアップ抵抗がありますので,押すとLow,離すとHighがRPiに伝えられます.また,トグルスイッチはON-ONの2ポジションとなっておりどちらの端子にも繋がらない中間はありません.
下の図はドータボードです.上側にRPi zeroが接続されます.この向きでトグルスイッチを見た場合,上にレバーを倒すとHigh,下にレバーを倒すとLowとなります.
ポーリングによるスイッチ状態の読み取り
スイッチの状態を読み取る方法には2種類あります.まずはポーリングによる方法について説明します.ポーリングでは,スイッチの変化が起こるまでずっと待ち続けますのでプログラムの構造は非常にシンプルです.ではプログラム例を挙げて説明します.下の例は端子の状態がHighの間は待ち続けるプログラムです.1行目でこれまでのLEDの制御と同様にGPIOコントローラのインスタンスを得ます.2,3行目でディジタル入力ピンを得ています.今回はタクトスイッチがGPIO_26とつながっていますので,第1引数はRaspiPin.GPIO_26となります.また,第2引数にピンの名称を,第3引数でプルアップ抵抗を使用しないことを指定します.なお,プルアップ抵抗がドータボードについているため,第3引数ではOFFにしています.もしプルアップ抵抗がない回路構成で,それが必要であればここをONにしてください.さて肝心なのは4行目です.ご覧のとおりwhile文中にメソッドisHighが含まれており,このメソッドは端子の状態がHighのときtrue,Lowのときはfalseが返されますので,結果としてHighの間はこのループから抜けず,Lowになった途端に抜けます.
final GpioController gpio = GpioFactory.getInstance(); final GpioPinDigitalInput sw = gpio.provisionDigitalInputPin(RaspiPin.GPIO_26, "Tact Switch", PinPullResistance.OFF); while(sw.isHigh()) ;
イベントハンドリング
次に,スイッチの変化(イベント)を読み取る方法を説明します.ポーリングによるスイッチ状態の変化を読み取る場合には手軽である反面,待っている間に他の処理が行われません.そこで,変化したときだけ呼び出されるイベントハンドリングについて説明します.Pi4Jではスイッチの変化が起こったときに行いたい処理をあらかじめ登録しておくことができます.このように行いたい処理(メソッド)のことをイベントハンドラ,イベントハンドラをメソッドに持つインタフェースをイベントリスナと言います.
字面だけではピンと来ないと思いますので,スイッチを押されたときにある文字列を表示するプログラムを例として挙げながら説明します.今回は,TactSwitchクラスを作成し,そのコンストラクタで下記の処理を行うものとします.まずイベントリスナとしてPi4Jでは,インタフェースGpioPinListenerDigitalが用意されており,このインタフェースにはイベントハンドラであるメソッドhandleGpioPinDigitalStateChangeEventがあります.さて,クラスGpioPinInputDigitalのインスタンスを生成しておき,そのメソッドにあるaddListenerに対してGpioPinListenerDigitalを実装したインスタンスを引数として渡します.また,handleGpioPinDigitalStateChangeEventにスイッチの状態が変化したときの処理(今回は文字列を表示する処理)を書いておきます.このようにしておくと,イベントが発生したとき,即ちスイッチの状態が変化したとき,handleGpioPinDigitalStateChangeEventが呼び出されますので文字列が表示されるのです.
以上の流れを図で示します.メソッドが呼び出される順番を表すとき,UMLのシーケンス図が用いられます.シーケンス図の詳しい解説をここでは行いませんが,ざっと説明しておきます.シーケンスは上から下に向かって呼び出すメソッドが時間順に書かれています.まずはじめに,クラスTactSwitchのコンストラクタが呼び出されます.その中で,クラスGpioFactoryのスタティックなメソッドgetInstanceを呼び出すことで,クラスGpioControllerのインスタンスgpioを得ます.次に,gpioにあるメソッドprovisionDigitalInputPinを呼び出し,クラスGpioPinDigitalInputのインスタンスtact_swを得ます.その後,インタフェースGpioPinListenerDigitalを実現したクラスGpioPinListenerDigitalImplをインスタンス化(new)します.シーケンス図ではインスタンス化するときCreateメッセージという矢印で書き表すことになっており,下図では1.3にあります.クラスGpioPinListenerDigitalImplをインスタンス化するとき,必ずメソッドhandleGpioPinDigitalStateChangeを実装しておかなければなりません.そして,このメソッドにスイッチが変化したときに行いたい処理を書いておくのです.さて,tact_swにあるメソッドaddListenerの引数として,先ほど得たインスタンスlistenerを渡しておきます.クラスGpioPinDigitalInputでは「スイッチに変化が起こったらlistenerにあるhandleGpioPinDigitalStateChangeEventを呼び出せばいい」というようにあらかじめプログラミングされていますので,結果としてスイッチに変化が起こるとプログラマが先ほど書いておいたGpioPinListenerDigitalImpleのhandleGpioPinDigitalStateChangeEventが呼び出さるのです.
プログラム(イベントリスナ用クラス付き)
以上のシーケンス図をJavaで書き表したものを下に示します.
まずはTactSwitch.javaです.この中でメソッドprovisionDigitalInputPinではタクトスイッチと接続されているピンGPIO_26を第1引数にしています.また,プルアップ抵抗がすでに備えられているので,プルアップ抵抗をオフを表すPinPullREsistance.OFFを第3引数にしています.
package jp.ac.nagano_nct.ashida_lab.tact_sw; import com.pi4j.io.gpio.GpioController; import com.pi4j.io.gpio.GpioFactory; import com.pi4j.io.gpio.GpioPinDigitalInput; import com.pi4j.io.gpio.PinPullResistance; import com.pi4j.io.gpio.RaspiPin; public class TactSwitch { /** * コンストラクタ */ public TactSwitch(){ /* GPIOコントローラを得る */ final GpioController gpio = GpioFactory.getInstance(); /* タクトスイッチとつながっているピンのインスタンスを得る */ GpioPinDigitalInput tact_sw_pin = gpio.provisionDigitalInputPin(RaspiPin.GPIO_26, "Tact Switch", PinPullResistance.OFF); /* イベントリスナをインスタンス化する */ GpioPinListenerDigitalImpl listener = new GpioPinListenerDigitalImpl(); /* イベントリスナを追加する */ tact_sw_pin.addListener(listener); } public static void main(String []args){ /* タクトスイッチをインスタンス化する */ TactSwitch tact_sw = new TactSwitch(); /* 無限ループ */ while(true); } }
次にGpioPinListenerDigitalImpl.javaです.
package jp.ac.nagano_nct.ashida_lab.tact_sw; import com.pi4j.io.gpio.event.GpioPinDigitalStateChangeEvent; import com.pi4j.io.gpio.event.GpioPinListenerDigital; public class GpioPinListenerDigitalImpl implements GpioPinListenerDigital { @Override public void handleGpioPinDigitalStateChangeEvent(GpioPinDigitalStateChangeEvent arg0){ /* スイッチが押された時の処理をここに書く */ } }
匿名クラス
さて,上記のようにすれば確かにスイッチが押された時の処理を書くことができますが,クラスGpioPinListenerDigitalImplを作成しなければならず,やや煩わしいです.この対策として,GpioPinListenerDigitalImplを作らずにインタフェースGpioPinListenerDigitalをインスタンス化する方法を紹介します.インタフェースをインスタンス化するということは本来できないはずです.実はそれを解決する方法があります.それが匿名クラスです.
匿名クラスとは名前がないクラスであり,見た目はインタフェースであり,そのインタフェースをインスタンス化するとき一緒に実装すべきメソッドを書いてしまうのです.具体的には次に示すようなプログラムをTactSwitch.javaのコンストラクタに書けばよいです.10行目から17行目のように,GpioPinListenerDigitalをインスタンス化しつつ,イベントハンドラであるhandleGpioPinDigitalStateChangeEventをここに書くことができます.
public TactSwitch(){ /* GPIOコントローラを得る */ final GpioController gpio = GpioFactory.getInstance(); /* タクトスイッチとつながっているピンのインスタンスを得る */ GpioPinDigitalInput tact_sw_pin = gpio.provisionDigitalInputPin(RaspiPin.GPIO_26, "Tact Switch", PinPullResistance.OFF); /* イベントリスナをインスタンス化する */ //GpioPinListenerDigitalImpl listener = new GpioPinListenerDigitalImpl(); /* 匿名クラスを使ってイベントリスナをインスタンス化する */ GpioPinListenerDigital listener = new GpioPinListenerDigital(){ @Override public void handleGpioPinDigitalStateChangeEvent( GpioPinDigitalStateChangeEvent arg0) { // TODO 自動生成されたメソッド・スタブ } }; /* イベントリスナを追加する */ tact_sw_pin.addListener(listener); }
さらに,匿名クラスをインスタンス化することをaddListener内で行ってしまう書き方があります.下にその例を示します.
public TactSwitch(){ /* GPIOコントローラを得る */ final GpioController gpio = GpioFactory.getInstance(); /* タクトスイッチとつながっているピンのインスタンスを得る */ GpioPinDigitalInput tact_sw_pin = gpio.provisionDigitalInputPin(RaspiPin.GPIO_26, "Tact Switch", PinPullResistance.OFF); /* イベントリスナをインスタンス化する */ //GpioPinListenerDigitalImpl listener = new GpioPinListenerDigitalImpl(); /* 匿名クラスを使ってイベントリスナをインスタンス化する */ /* イベントリスナを追加する */ tact_sw_pin.addListener(new GpioPinListenerDigital(){ @Override public void handleGpioPinDigitalStateChangeEvent( GpioPinDigitalStateChangeEvent arg0) { // TODO 自動生成されたメソッド・スタブ } }); }
演習
タクトスイッチのプログラムを参考にしながら,トグルスイッチ1(SW1)の状態が変化したときにprintlnを使って状態を表示するプログラムを作成してください.