micro:bit でボタンの判定（単押、連打、押しっぱなし、同時押）

## 概要
[micro:bit](http://microbit.org/ja/)のボタンで以下の判定する方法を考えてみました。

* ボタン１回押す
* ボタン連打
* ボタン押しっぱなし
* AボタンをBボタンを同時に押す

## 背景
[micro:bit](http://microbit.org/ja/)にはAボタン、Bボタンがあります。
「[MicroPython](http://microbit-micropython.readthedocs.io/ja/latest/)」を使用したボタンの制御で意外と時間使ったので、備忘録として作成。

最初、[適当に作ったプログラム](https://qiita.com/tagetage3/items/b58391ad84572c26f376)での状況は以下

| 操作 | 想定している動き | 実際の動き |
|:------------|:-----------|:-----------|
| ボタン１回押す、ボタン連打 |ボタンを押した回数だけ処理Ｘを実行 | ボタンの押下数より処理の実行回数が少ない |
| ボタン押しっぱなし | ボタンを押している間は処理Ｘを高速で繰り返し実行 | １回しか処理Ｘが実行されない。または、処理がまったく実行されない |
| Aボタン、Bボタンを同時に押す | 処理Ｙを実行 | 処理Ｘが実行された後、処理Ｙが実行される |

## 検証
micro:bit用MicroPythonのリファレンスマニュアルにはボタンクラスのメソッドについて以下３つの説明があります。

```
is_pressed()
指定のボタンが押されていれば True を返し、押されていなければ False を返します。
```

```
was_pressed()
デバイスが起動されてから、もしくは前回このメソッドが呼ばれてからボタンが(上から下へ)
押されたかによって True または False を返します。このメソットを呼び出すとボタンが
押されたかの状態がクリアされるので、再度このメソッドが True を返すようにするには、
このメソッドを呼び出す前にボタンが押されなければなりません。
```

```
get_presses()
ボタンを押した回数の合計を返し、返す前に回数をゼロにリセットします。
```

３メソッドの動きを確認するために以下コードを作成

```py
from microbit import *

#---------------------------
# Divid screen
#---------------------------

# left top
div_1_x = 0
div_1_y = 0

# left bottom
div_2_x = 0
div_2_y = 3

# right top
div_3_x = 3
div_3_y = 0

# right bottom
div_4_x = 3
div_4_y = 3

while True:

  #---------------------------
  # I want to detect under operation.
  # Which check is best?
  # 1) Click
  # 2) Press
  # 3) Click button repeatedly
  # 4) Press A + B 
  #---------------------------

  base_x = 0
  base_y = 0
  
  #---------------------------
  # 1. "is_pressed()"
  #---------------------------
  base_x = div_1_x
  base_y = div_1_y

  if button_a.is_pressed() == True:
    display.set_pixel(base_x, base_y,   7)
    display.set_pixel(base_x, base_y+1, 0)
  else:
    display.set_pixel(base_x, base_y,   0)
    display.set_pixel(base_x, base_y+1, 7)

  #---------------------------
  # 2. "was_pressed()"
  #---------------------------
  base_x = div_2_x
  base_y = div_2_y

  if button_a.was_pressed() == True:
    display.set_pixel(base_x, base_y,   7)
    display.set_pixel(base_x, base_y+1, 0)
  else:
    display.set_pixel(base_x, base_y,   0)
    display.set_pixel(base_x, base_y+1, 7)

  #---------------------------
  # 3. "get_presses()"
  #---------------------------
  base_x = div_3_x
  base_y = div_3_y

  press_count = button_a.get_presses()

  if press_count > 0:
    display.set_pixel(base_x,   base_y,   7)
    display.set_pixel(base_x+1, base_y,   press_count)
    display.set_pixel(base_x,   base_y+1, 0)
  else:
    display.set_pixel(base_x,   base_y,   0)
    display.set_pixel(base_x+1, base_y,   0)
    display.set_pixel(base_x,   base_y+1, 7)

  #---------------------------
  # 4. "is_pressed" (A + B)
  #---------------------------
  base_x = div_4_x
  base_y = div_4_y

  if button_a.is_pressed() == True and button_b.is_pressed() == True:
    display.set_pixel(base_x, base_y,   7)
    display.set_pixel(base_x, base_y+1, 0)
  else:
    display.set_pixel(base_x, base_y,   0)
    display.set_pixel(base_x, base_y+1, 7)

  sleep(500)
```

## 検証結果

リファレンス通りですが、実際に動かし以下の通り確認しました。

```
is_pressed()
押しっぱなしでは、ずっとTrueの状態。１回押しや、連打はタイミングによって、
Trueを返さない場合があり。ABボタン同時押はタイミングがずれても検知できた。
```

```
was_pressed()
押しっぱなしでは最初にTrueを返したが、２回目のループ以降はFalseになる。
１回押し、連打では押したループ間に１回でも押されていればTrueを返す。
ABボタン同時押はタイミングがずれると、例えばボタンA側がTrue、B側がFalseになり、
検知できない場合があり。
```

```
is_presses()
押しっぱなしでは最初にTrueを返したが、２回目のループ以降はFalseになる。
１回押し、連打ではループ間隔ごとに押した回数を取得することがでる。
ABボタン同時押はタイミングがずれると、例えばボタンA側がTrue、B側がFalseになり、
検知できない場合があり。
```

## 結論

今回、以下の判断基準で処理するのがだいたい良いかなとなりました。

* １回押し、連打判定
0.01秒ごとにボタンの状態をチェックして、ボタンが離されたときに処理Ｘを実行する。

* 押しっぱなし判定
0.5秒間、押下状態が続いたら処理Ｘを実行する。
ちなみに、0.5秒以内に離されれば１回押しとなる。
逆に、もう片方のボタンも押されれば同時押しとなる。

* ＡＢ同時押し判定
片方のボタンを押している状態でもう片方のボタンを押せば同時押しとして処理Ｙを実行。
なお、押しっぱなし状態で、もう片方のボタンを押しても同時押しになります。
（押しっぱなし状態から同時押しへの移行は要件によって変わりそう）

#### サンプルソース

```py
from microbit import *

# ボタンが押されている間、最大50までカウントアップ。
button_a_is_purresing = 0
button_b_is_purresing = 0

while True:

  if button_a.is_pressed() and button_b.is_pressed():
    
    ## ボタンＡＢ同時押と判定 ##
    ## ここで処理Ｙを実行 ##

    # 変数とイベントをクリア
    button_a_is_purresing = 0
    button_b_is_purresing = 0
    button_a.was_pressed()
    button_b.was_pressed()
    sleep(1000)
  else:

    # -------------------
    # ボタンＡ
    # -------------------
    if button_a.was_pressed() == True:
      # １回押し判定
      button_a_is_purresing += 1
      sleep(10)
      continue
    elif button_a.is_pressed() == True:
      # 押しっぱなし判定
      if button_a_is_purresing <= 50:
        # ＡＢボタン同時押しや、
        # 押しっぱなしの可能性があるので0.5秒待つ
        button_a_is_purresing += 1
        sleep(10)
        continue

      ## 押しっぱなしと判定 ##
      ## ここで処理Ｘを実行 ##

    elif button_a_is_purresing > 0:
      ## １回押し、連続押しと判定 ##
      ## ここで処理Ｘを実行 ##
      button_a_is_purresing = 0

    # -------------------
    # ボタンＢ
    # -------------------

    # 必要な場合、
    # ここにボタンＡと同様に処理を追加

```

## リンク

使用例 : DispNumSoroban.py
https://github.com/soramaru777/DispNumSoroban/blob/master/DispNumSoroban1.py

micro:bit - 日本公式サイト
http://microbit.org/ja/

MicroPython - micro:bit
http://microbit-micropython.readthedocs.io/ja/latest/