Pythonでデバイスを制御しよう【第2回】プッシュボタンを扱う(3)

ポーリング制御でプッシュボタンに反応する

 

プッシュボタンを扱う(1) Pythonでプッシュボタンを扱うには
プッシュボタンを扱う(2) 誤動作の原因となるチャタリングを防止する

 

「プッシュボタンを扱う」のパート3では、タクトスイッチなどのボタンに合わせて処理をする際に用いられる制御方法の一つであるポーリング制御について解説します。
ポーリング制御とは無限ループの中で常にボタンの動きを監視する制御です。シンプルな方法で処理間の同期を取りやすいため電子機器(ハードウェア)の制御によく利用されます。
しかし無限ループで困るのが終了処理です。プログラムをいきなり終了してしまうと、後で問題となる場合があるため「例外」と呼ばれる仕組みを利用してプログラムを安全に停止させます。
ポーリング制御はシンプルな制御方法ですが、常に「今、プログラムで何が起こっているのか」正確に状態を把握していないとその利点が生かされません。そこで電子機器で用いられる状態遷移を使った制御を紹介します。状態遷移はボタンの動きを把握するだけでなく、プログラム同士の連携やデータの管理といったソフトウェアが中心のシステムにも適した制御方法です。

control-device-with-python-vol2-01-01

 

目次

  1. ポーリング制御とは
  2. ポーリング制御を使った実装
    1. 2.1. 本プログラムの構成
    2. 2.2. ポーリング制御でボタンの動きを読み取るPythonプログラム p3-1.py
    3. 2.3. プログラムの説明
    4. 2.4. プログラムの実行と検証
  3. ポーリングの間隔を改善して反応を良くする
    1. 3.1. 本プログラムの構成
    2. 3.2. sleepの時間を1秒から100ミリ秒に変更したPythonプログラム p3-2.py
    3. 3.3. プログラムの説明
    4. 3.4. プログラムの実行と検証
    5. 3.5. 問題点(1) プログラムを適切に終了する
    6. 3.6. 問題点(2) 表示されるメッセージが多い
  4. KeyboardInterruptを検出してPythonプログラムを安全に停止する
    1. 4.1. 本プログラムの構成
    2. 4.2. KeyboardInterruptで安全に停止するPythonプログラム p3-3.py
    3. 4.3. プログラムの説明
    4. 4.4. プログラムの実行と検証
  5. 状態遷移を使った制御
    1. 5.1. 状態遷移を使った制御とは
    2. 5.2. 本プログラムの構成
    3. 5.3. 状態遷移を使ったPythonのプログラム p3-4.py
    4. 5.4. プログラムの説明
    5. 5.5. プログラムの実行と検証
  6. まとめ

 

ポーリング制御とは

ポーリング制御とはコーディング手法の一つです。無限ループの中で常にシステムの状況を監視し、その状況に合わせた処理を行う制御です。

control-device-with-python-vol2-03_02

ポーリング制御とは

 

ポーリング制御は「もしボタンAが押されたら、この処理を実行」「もしボタンBが押されたら、この処理を実行」のように、条件判定と対応する処理を1回のループの中で行う方法です。処理と処理の同期のようなシステム全体のバランスを取りやすいのが特徴です。
毎回のループの中ではsleep()関数を使用したプログラムの短時間休止が必要です。これを行わないとCPUの負荷が大きくなりラズパイの動作が遅くなるなどのトラブルの原因となります。
OSが乗っていないArduinoのようなマイコンボードでは不要ですが、複数のタスクの実行をOSが管理するラズパイではsleep()関数の呼び出しが必要です。

ポーリング制御はループの中にチェックしたい状態の数だけif文を並べるイメージのコーディング手法なので記述としてはシンプルな制御方法です。
一方で判定する状態が増えればループの処理も大きくなり、可読性が悪くなるという欠点もあります。

 

ポーリング制御を使った実装

早速、ポーリング制御でボタンの動きに合わせて処理を行う例を見てみましょう。

本プログラムの構成

以下がポーリング制御でボタンの動きを読み取るPythonのプログラムの構成です。

control-device-with-python-vol2-03_01

システム構成

 

このプログラムの動作にはプッシュボタン1個とライブラリ「RPi.GPIO」が必要です。パート1を参考に配線とインストールを行ってください。

本プログラムはGPIO5に接続したプッシュボタンが押されたら、ターミナル上に
「タイムスタンプ: メッセージ」
を表示します。

ポーリング制御でボタンの動きを読み取るPythonプログラム p3-1.py

以下がポーリング制御でボタンの動きを読み取るPythonのプログラムです。p3-1.pyの名前で保存してください。

Code-Example
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#!/usr/bin/env python
 
import sys
import time
import datetime
 
import RPi.GPIO as GPIO
 
 
# ボタンは"GPIO5"に接続
BUTTON = 5
 
 
# 主処理
def main():
     
    # 操作対象のピンは「GPIOn」の"n"を指定する
    GPIO.setmode(GPIO.BCM)
    # BUTTONがつながるGPIOピンの動作は「入力」「プルアップあり」
    GPIO.setup(BUTTON, GPIO.IN, pull_up_down=GPIO.PUD_UP)
     
    # 無限ループ
    while(True):
        # 1秒間休止
        time.sleep(1)
         
        # ボタンを確認して処理を起動する
        if GPIO.input(BUTTON) == GPIO.LOW:
            print_message("ボタンが押されました")
         
    print("処理を終了します")
     
    # GPIOの設定をリセット
    GPIO.cleanup()
     
    return 0
 
 
# ターミナル上に「日付 時刻.マイクロ秒: メッセージ」を表示する関数
# message: 表示する「メッセージ」
def print_message(message):
     
    # 現在の日付時刻を取得して「年-月-日 時:分:秒.マイクロ秒」にフォーマット
    now = datetime.datetime.now()
    timestamp = now.strftime("%Y-%m-%d %H:%M:%S.%f")
     
    # 引数で送られたメッセージを表示
    print("{}: {}".format(timestamp, message))
 
 
# プログラムの入り口(主処理を起動)
if __name__ == "__main__":
    sys.exit(main())

プログラムの説明

主処理main()関数では、GPIOの設定を行い、無限ループに入ります。
無限ループ内では休止(sleep()関数の呼び出し)後、ラズパイに接続したボタンの状態を監視します。接続したボタンはプルアップして使用するので、ボタンが押されるとGPIOピンの電圧は0V(LOW)に変化します。
ループを休止させる時間は1秒です。「1秒が妥当か?」は、のちほど検討します。
メッセージを表示するための自作関数「print_message()」はパート2で紹介したプログラムでも使用しました。引数で指定された文字列にタイムスタンプを付加してターミナル上に表示するものです。

プログラムの実行と検証

プログラムを保存したディレクトリに移動したら以下のコマンドでプログラムを起動してください。

Code-Example
1
./p3-1.py

ボタンを押すごとに
「2021-02-22 17:44:53.745830: ボタンが押されました」
と表示されます。
プログラムを終了するにはキーボードの「Ctrl+c」を押してください。
いかがでしょう。このプログラムは一応動作します。しかし、何かおかしな点に気が付きませんか?
このプログラムを実行してみると「ボタンの反応が鈍い」と感じるはずです。これはループの中で処理を休止するsleep()関数に設定した1秒が長すぎるためです。次は、この時間をもう少し短くしてみましょう。

 

ポーリングの間隔を改善して反応を良くする

休止(sleep)する時間を短くすることでポーリングの間隔を改善し「ボタンの反応が鈍い」と感じなくなるようにプログラムを修正します。

本プログラムの構成

ポーリングの間隔を改善したPythonのプログラムの構成は、前の『ポーリング制御を使った実装』と全く同じです。
今回もGPIO5に接続したプッシュボタンが押されたら、ターミナル上に
「タイムスタンプ: メッセージ」
を表示します。

sleepの時間を1秒から100ミリ秒に変更したPythonプログラム p3-2.py

以下が休止(sleep)する時間を短くしたPythonのプログラムです。p3-2.pyの名前で保存してください。

Code-Example
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#!/usr/bin/env python
 
import sys
import time
import datetime
 
import RPi.GPIO as GPIO
 
 
# ボタンは"GPIO5"に接続
BUTTON = 5
 
 
# 主処理
def main():
     
    # 操作対象のピンは「GPIOn」の"n"を指定する
    GPIO.setmode(GPIO.BCM)
    # BUTTONがつながるGPIOピンの動作は「入力」「プルアップあり」
    GPIO.setup(BUTTON, GPIO.IN, pull_up_down=GPIO.PUD_UP)
     
    # 無限ループ
    while(True):
        # 100ミリ秒間休止
        time.sleep(0.1)
         
        # ボタンを確認して処理を起動する
        if GPIO.input(BUTTON) == GPIO.LOW:
            print_message("ボタンが押されました")
         
    print("処理を終了します")
     
    # GPIOの設定をリセット
    GPIO.cleanup()
     
    return 0
 
 
# ターミナル上に「日付 時刻.マイクロ秒: メッセージ」を表示する関数
# message: 表示する「メッセージ」
def print_message(message):
     
    # 現在の日付時刻を取得して「年-月-日 時:分:秒.マイクロ秒」にフォーマット
    now = datetime.datetime.now()
    timestamp = now.strftime("%Y-%m-%d %H:%M:%S.%f")
     
    # 引数で送られたメッセージを表示
    print("{}: {}".format(timestamp, message))
 
 
# プログラムの入り口(主処理を起動)
if __name__ == "__main__":
    sys.exit(main())

プログラムの説明

前述のプログラム「p3-1.py」からの変更点は
「time.sleep(1)」

「time.sleep(0.1)」
にした1カ所です。
Pythonのプログラムを休止するsleep()関数は引数に小数を渡すことができます。今回の引数「0.1」は0.1秒(100ミリ秒)を意味します。
ただしラズパイではOSがシステム全体を管理し複数のタスクを切り替えながら動作します。そのため、ほかのタスクとの兼ね合いからsleep()関数が休止する時間の精度はあまり良くありません。
数ミリ秒の誤差が影響ない処理であれば問題ありませんが、sleep()関数を繰り返すような処理を行うと誤差が累積するので、正確な待機処理を行いたい場合には注意が必要です。

プログラムの実行と検証

プログラムを保存したディレクトリに移動したら以下のコマンドでプログラムを起動してください。

Code-Example
1
./p3-2.py

プログラムを終了するにはキーボードの「Ctrl+c」を押してください。処理中のターミナルの様子は以下の通りです。

Code-Example
01
02
03
04
05
06
07
08
09
10
11
2021-02-22 17:34:02.471220: ボタンが押されました
2021-02-22 17:34:06.378203: ボタンが押されました
2021-02-22 17:34:11.487195: ボタンが押されました
2021-02-22 17:34:11.587577: ボタンが押されました
2021-02-22 17:34:17.097161: ボタンが押されました
^CTraceback (most recent call last):
  File "/mnt/usb0/devp/devp-py/button/./p3-2.py", line 48, in <module>
    sys.exit(main())
  File "/mnt/usb0/devp/devp-py/button/./p3-2.py", line 25, in main
    time.sleep(0.1)
KeyboardInterrupt

ボタンの反応も良く、上手く動作しているようです。
ポーリング制御の無限ループ内での休止時間は0.1秒(100ミリ秒)なので問題なさそうです。一般的に休止時間が0.1秒より大きくなると操作時に反応が鈍く感じ、また0.1秒より短いと消費電力やほかのタスクに悪影響を及ぼしかねません。この点を考慮して、作成するシステムごとに適切な休止時間を探すとよいでしょう。

しかしこのプログラムには、2つの問題点があります。
1点目は後半にある数行のメッセージです。明らかにプログラムから出力したものではないメッセージが並んでいるのですぐに気付くでしょう。これは「トレースバック」と呼ばれるものでプログラムが異常終了した通知です。適切に終了処理されずプログラムが停止したことを意味します。
2点目はメッセージからは分かりにくいのですが、ボタンを押した数とメッセージが表示される数が異なることです。想定では
「ボタンを1回押すごとにメッセージが1行表示される」
はずですが、特にゆっくりとボタンを押すと数行のメッセージが表示されます。皆さんも注意して実行結果を確認してください。
以降は、この「プログラムを適切に終了する」「表示されるメッセージが多い」点について説明します。

問題点(1) プログラムを適切に終了する

プログラムの実行結果を見ると
「^CTraceback (most recent call last):」
から最後までの6行は、作成したプログラムが意図的に出力したものではないことが分かります。
想定ではこの部分には
「処理を終了します」
と表示されるはずであり、このメッセージが表示されていないということはcleanup()関数が呼ばれず、それより前にプログラムが終了したことを意味します。
この6行は「トレースバック」と呼ばれるものでプログラムが異常終了した通知です。適切に終了処理されずプログラムが停止したことを意味します。トレースバックは「スタックトレース」などとも呼ばれます。

トレースバックは環境や状況に応じて表示される内容が異なりますが、プログラムが異常終了する際にデバッグのヒントとなり得る情報をできる限り出力するものなので一行ずつ確認していきます。

control-device-with-python-vol2-03_03

出力されたトレースバック

 

1行目は

  • 「^C」: キーボードから入力した「Ctrl+c」が画面上に表示されたもの
  • 「Traceback (most recent call last):」: 以降は関数の呼び出し履歴が続く

ことを意味します。

ラズパイのOSのベースであるLinuxでは「Ctrl+c」はプログラムを終了させるためのショートカットキーのような働きをします。キャレット(^)は「Controlキー」を記号で表現したものです。
関数の呼び出し履歴には上から下へプログラムの流れが記録されています。「File」で始まる行には「プログラム名, 行番号, 関数名」が含まれ上記のメッセージは

  1. プログラム「p3-2.py」の48行目で関数を呼び出した
  2. プログラム「p3-2.py」の25行目で関数を呼び出した

となります。最後の行にある
「KeyboardInterrupt」
が処理を止める原因となった想定外の事象です。

このトレースバックからは
「p3-2.pyの25行目で呼び出したtime.sleep(0.1)の実行中にKeyboardInterruptが発生したので処理を停止した」
と読み取れます。
KeyboardInterruptは「例外」と呼ばれるものでパート5「プログラムをストップさせる例外を捕捉して処理する(例外処理)」・パート6「「raise Exception」で積極的に例外を利用する(例外処理)」で詳細に解説します。
今回のパート3では、続く『KeyboardInterruptを検出してPythonプログラムを安全に停止する』でこの例外が発生した後、安全にプログラムを停止する方法を紹介します。

問題点(2) 表示されるメッセージが多い

プログラムの実行結果、「ボタンが押されました」の前に表示されているタイムスタンプに注目してください。
3行目と4行目のタイムスタンプは以下のようになっています。

Code-Example
1
2
2021-02-22 17:34:11.487195: ボタンが押されました
2021-02-22 17:34:11.587577: ボタンが押されました

まるで0.1秒ごとにボタンが押されたような表示で、この結果は想定と異なります。
これは「ポーリング制御が判別するのはボタンの位置」であることに起因しています。イベント駆動(詳細はパート4「イベント駆動でプッシュボタンに反応する(制御手法)」)は入力がHIGHからLOWに変わるボタンの変化を読み取るのに対し、ポーリング制御ではポーリングするごとに「ボタンが押されている」ことを確認するため、ポーリングの間隔である0.1秒以内に指をボタンから離さない限りメッセージが出力され続けます。
この問題に対処する方法の一つに「状態遷移を使った制御」があります。これは続く『状態遷移を使った制御』で詳細に解説します。

 

KeyboardInterruptを検出してPythonプログラムを安全に停止する

ラズパイのベースとなるLinuxでは、プログラムを停止する際に「Ctrl+c」キーを押します。これによりプログラム内ではKeyboardInterrupt例外が発行されます。
例外処理の詳細についてはパート5の『プログラムをストップさせる例外を捕捉して処理する』で解説します。ここではKeyboardInterrupt例外が発行された際に安全にプログラムを停止する方法を説明します。

本プログラムの構成

KeyboardInterrupt例外が発行された際に安全に停止するPythonのプログラムも、前の『ポーリング制御を使った実装』と全く同じ構成になります。
GPIO5に接続したプッシュボタンが押されたらターミナル上に
「タイムスタンプ: メッセージ」
を表示するという動作ですが、ポイントは「Ctrl+c」でプログラムを終了させる際の挙動です。

KeyboardInterruptで安全に停止するPythonプログラム p3-3.py

以下がKeyboardInterrupt例外が発行された際、安全に停止するPythonのプログラムです。p3-3.pyの名前で保存してください。

Code-Example
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#!/usr/bin/env python
 
import sys
import time
import datetime
 
import RPi.GPIO as GPIO
 
 
# ボタンは"GPIO5"に接続
BUTTON = 5
 
 
# 主処理
def main():
     
    try:
        # 操作対象のピンは「GPIOn」の"n"を指定する
        GPIO.setmode(GPIO.BCM)
        # BUTTONがつながるGPIOピンの動作は「入力」「プルアップあり」
        GPIO.setup(BUTTON, GPIO.IN, pull_up_down=GPIO.PUD_UP)
         
        # 無限ループ
        while(True):
            # 100ミリ秒間休止
            time.sleep(0.1)
             
            # ボタンを確認して処理を起動する
            if GPIO.input(BUTTON) == GPIO.LOW:
                print_message("ボタンが押されました")
             
    # キーボード割り込みを捕捉
    except KeyboardInterrupt:
        print("例外'KeyboardInterrupt'を捕捉")
     
    print("処理を終了します")
     
    # GPIOの設定をリセット
    GPIO.cleanup()
     
    return 0
 
 
# ターミナル上に「日付 時刻.マイクロ秒: メッセージ」を表示する関数
# message: 表示する「メッセージ」
def print_message(message):
     
    # 現在の日付時刻を取得して「年-月-日 時:分:秒.マイクロ秒」にフォーマット
    now = datetime.datetime.now()
    timestamp = now.strftime("%Y-%m-%d %H:%M:%S.%f")
     
    # 引数で送られたメッセージを表示
    print("{}: {}".format(timestamp, message))
 
 
# プログラムの入り口(主処理を起動)
if __name__ == "__main__":
    sys.exit(main())

プログラムの説明

main()関数内の処理の大部分を「try文〜exceptブロック」で囲みました。
tryからexeptの間の処理を実行中にKeyboardInterrupt例外が発行されたら(「Ctrl+c」キーが押されたら)直ちに処理がexceptに続く行に移行します。
「try文〜exceptブロック」の詳細はパート5で解説します。

プログラムの実行と検証

プログラムを保存したディレクトリに移動したら以下のコマンドでプログラムを起動してください。

Code-Example
1
./p3-3.py

プログラムが実行中に「Ctrl+c」キーを押してください。次のようなメッセージがターミナル上に表示されます。

Code-Example
1
2
3
2021-02-23 12:05:35.987872: ボタンが押されました
^C例外'KeyboardInterrupt'を捕捉
処理を終了します

今回は「処理を終了します」とメッセージが表示されていることから「GPIO.cleanup()」が正常に呼び出されていることが分かります。

 

状態遷移を使った制御

ポーリング制御はループの中でボタンの位置を読み取るため、ポーリングの間隔である0.1秒以内に指をボタンから離さない限り何度でも処理を行ってしまいます。
この問題に対処する方法の一つである「状態遷移を使った制御」を紹介します。

状態遷移を使った制御とは

状態遷移を使った制御とは
「あらかじめ定義したシステムの取り得る状態をコントロールすることで一連の処理を実現」
する手法であり実際の電子機器の制御にも用いられる方法です。
例えば単純なプッシュボタンの動作を例に考えてみます。
システムから見ると、プッシュボタンがシステムに影響する状態は「待機中」「復帰中」の2つとなり、それぞれの状態でシステムがボタンに対して行う動作を次のように定義します。

状態 動作
待機中 ボタンが押されるのを監視
復帰中 連続して処理が起動しないための保護時間。ボタンが確実に離されたか監視

待機中はボタンが押されたことをチェック、復帰中はボタンが押されていないことを計測して確実に指が離されたことをチェックする状態と定義し、プログラムの実行中は必ずいずれかの状態にあるようにコーディングします。
ボタンが押されたことを検出したら直ちに状態復帰中に、また、一定時間以上ボタンが反応しない場合は状態待機中に遷移することで不必要にボタンの反応を検出しないようにできます。

このように「状態遷移を使った制御」とはシステムの取り得る状態と、その状態で許可する動作をあらかじめ定義し、プログラムの実行中は状態を適切に遷移させることでシステムをコントロールする手法です。
通常のポーリング制御では単に「もしボタンが押されたら処理を起動する」とコーディングしがちですが、復帰中という状態を作ることでボタンが連打された際などに、連続してプログラムが動作しないようにするための時間(保護時間)を確保できます。
このように仮想の状態を定義することで、通信など複数のプログラムが連携するようなソフトウェアの制御だけでなく、ボタンやモータといった物理的な動作が伴う制御にも柔軟に対応できます。

本プログラムの構成

状態遷移を使って制御を行うPythonのプログラムも、前の『ポーリング制御を使った実装』と全く同じ構成になります。
GPIO5に接続したプッシュボタンが押されたらターミナル上にメッセージを表示させるだけの動作ですが、ボタンの連打や長押しにも適切に対応します。

状態遷移を使ったPythonのプログラム p3-4.py

以下が状態遷移を使って制御を行うPythonのプログラムです。p3-4.pyの名前で保存してください。

Code-Example
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#!/usr/bin/env python
 
import sys
import time
import datetime
 
import RPi.GPIO as GPIO
 
 
# ボタンは"GPIO5"に接続
BUTTON = 5
 
 
# 主処理
def main():
     
    # 現在の状態を管理
    status = 0
     
    # 復帰中に経過時間を計測する
    resume = 0
     
    try:
        # 操作対象のピンは「GPIOn」の"n"を指定する
        GPIO.setmode(GPIO.BCM)
        # BUTTONがつながるGPIOピンの動作は「入力」「プルアップあり」
        GPIO.setup(BUTTON, GPIO.IN, pull_up_down=GPIO.PUD_UP)
         
        # 無限ループ
        while(True):
            # 100ミリ秒間休止
            time.sleep(0.1)
             
            # 「待機中」はボタンを確認して処理を起動する
            if status == 0:
                if GPIO.input(BUTTON) == GPIO.LOW:
                    # 状態を「復帰中」に
                    status = 1
                    print_message("ボタンが押されました。復帰中に遷移")
                 
            # 「復帰中」はボタンが離されて1秒経過するのを待つ
            elif status == 1:
                # ループの回数をカウントすることで何秒間経過したかを判定
                if GPIO.input(BUTTON) == GPIO.LOW:
                    # ボタンが押されている場合はカウントしない
                    resume = 0
                elif 10 <= resume:
                    # 1秒経過したので状態を「待機中」に
                    status = 0
                    resume = 0
                    print_message("1秒経過しました。待機中に遷移")
                else:
                    # ループの回数をカウント
                    resume += 1
         
    # キーボード割り込みを捕捉
    except KeyboardInterrupt:
        print("例外'KeyboardInterrupt'を捕捉")
     
    print("処理を終了します")
     
    # GPIOの設定をリセット
    GPIO.cleanup()
     
    return 0
 
 
# ターミナル上に「日付 時刻.マイクロ秒: メッセージ」を表示する関数
# message: 表示する「メッセージ」
def print_message(message):
     
    # 現在の日付時刻を取得して「年-月-日 時:分:秒.マイクロ秒」にフォーマット
    now = datetime.datetime.now()
    timestamp = now.strftime("%Y-%m-%d %H:%M:%S.%f")
     
    # 引数で送られたメッセージを表示
    print("{}: {}".format(timestamp, message))
 
 
# プログラムの入り口(主処理を起動)
if __name__ == "__main__":
    sys.exit(main())

プログラムの説明

変数「status」でこのシステムが取り得る状態を管理します。0が待機中、1が復帰中です。無限ループの中では必ずstatus変数には0/1のいずれかが格納されるようにコーディングします。
また、ループの中ではif文を使用して待機中/復帰中に相応しい動作を行います。
待機中
「if GPIO.input(BUTTON) == GPIO.LOW:」
でボタンが押されたことを監視し、検出後は直ちにstatus変数の値を1(復帰中)に切り替えます。
復帰中は変数「resume」の値をカウントアップすることでボタンが離された時間を計測します。ポーリング間隔が0.1秒なので10回カウントすることで1秒間待機できます。また
「if GPIO.input(BUTTON) == GPIO.LOW:」
でresume変数をクリアすることでボタンを連打しても適切な時間待機します。

プログラムの実行と検証

プログラムを保存したディレクトリに移動したら以下のコマンドでプログラムを起動してください。

Code-Example
1
./p3-4.py

実行結果は以下の通りです。

Code-Example
1
2
3
4
5
6
7
8
2021-02-23 17:11:30.048405: ボタンが押されました。復帰中に遷移
2021-02-23 17:11:32.553039: 1秒経過しました。待機中に遷移
2021-02-23 17:11:33.254442: ボタンが押されました。復帰中に遷移
2021-02-23 17:11:34.456704: 1秒経過しました。待機中に遷移
2021-02-23 17:11:35.158075: ボタンが押されました。復帰中に遷移
2021-02-23 17:11:36.360672: 1秒経過しました。待機中に遷移
^C例外'KeyboardInterrupt'を捕捉
処理を終了します

今回のプログラムはボタンを離した状態が1秒間続かなければ処理を受け付けません。ボタンの長押しや連打をしても不必要に反応しないことがメッセージからも分かります。

今回は2つの状態「待機中/復帰中」の間を遷移しましたが、実際の処理ではここに処理中などの状態を加えて、より緻密な制御を行います。
次回のパート4ではイベント駆動にこの状態遷移を加えた例として、待機中/処理中/復帰中の3つの状態を遷移してみます。

 

まとめ

ポーリング制御は無限ループの中で常にシステムの状態を監視し、適切な処理を起動する制御方法です。シンプルな方法であり、処理間の同期を取りやすいことから電子機器の制御によく利用されます。
とはいえ、実際にプログラムを作ってみると、何度も処理を起動してしまうこともあり、案外難しいことが分かるでしょう。また、プログラムの終了時には適切に終了処理を行わないと後に問題となる場合もあります。そこで状態遷移を使った制御や例外処理といった点も考慮しないと安定したシステムにはなりません。
状態遷移を使った制御はプッシュボタンやセンサ、LED、モータといった機構部品の組み合わせで構成される機器だけでなく、データの更新や複数のプログラムが連携しながら一連の動作を行うようなシステムの制御に適している方法です。ぜひマスターしましょう。
次回のパート4では、さらにイベント駆動という制御手法を紹介します。

 

今回の連載の流れ

プッシュボタンを扱う(1) Pythonでプッシュボタンを扱うには
プッシュボタンを扱う(2) 誤動作の原因となるチャタリングを防止する
プッシュボタンを扱う(3) ポーリング制御でプッシュボタンに反応する(今回)
プッシュボタンを扱う(4) イベント駆動でプッシュボタンに反応する
プッシュボタンを扱う(5) プログラムをストップさせる例外を捕捉して処理する
プッシュボタンを扱う(6) 「raise Exception」で積極的に例外を利用する

アバター画像

エレクトロニクスやメカトロニクスを愛するみなさんに、深く愛されるサイトを目指してDevice Plusを運営中。

https://deviceplus.jp

高専ロボコン2017解剖計画