Raspberry Pi 3 B+ で、キーボードを押していない状態を、どーしても取りたかった 1

# キーを押していないのが取りたい！！
作成中のカムプログラムロボット（以降カムロボ）を、キーボード操作したくて試行錯誤してました。
なんとか、カーソルキーの上下左右を押したときに、前進、更新、左旋回、右旋回ができるようにできたのですが、何も押していないときは**ストップ**させたかった。

でもこれ、な～んにも押していないという状態が取れず、スペースキーを押したらストップさせるようにしました。
これでもいいはいいんですが、どうしてもキーを離したら**ストップ**させたかった！！

## やりたかったこと
キー入力についてこんなことをしたかった。

|No.|動作|取りたい状態|
|:---:|---|---|
|1|どこかのキーを押した|押したキーのコード|
|2|そのキーを押し続けた|押し続けているという状態|
|3|**キーを離した**|**何も押していないという状態**|

キーコードの取得は ```readchar``` モジュールを使いました。
結果、１と２については、入力されたキーコードと、過去に入力されたキーを比較することで実現できたのですが、３の状態が取れなかった。。:sob:

３の状態が取れないのは ```readchar```モジュールの```readchar.readkey()``` は、キー入力があるまで待ち状態になるためで（当たり前なので悪いわけでは無い）、押していないという状態を取ることができませんでした。

色々と試してできそうな目処が立ったのでまとめてみました。

## OS環境
Raspberry Piの OSはRaspbian 9.8になります。

```bash
pi@raspberrypi:~ $ lsb_release -a
No LSB modules are available.
Distributor ID: Raspbian
Description:    Raspbian GNU/Linux 9.8 (stretch)
Release:        9.8
Codename:       stretch
```

## readchar のインストール
``` python
sudo pip3 install readchar
```
python3に対応した readcharがほしかったので、上記のようにインストールしました。

## キー入力プログラム（テスト版）

テストで利用したキー入力プログラムです。
実行を停止する場合は、'q' を押してください。Ctrl+c でも止まりません。
結局、```readchar.readkey()``` の部分をThread化して対応しました。

``` python:thread_readkey_00.py
import time
import threading

from readchar import readkey, key as inchar

class CheckKey(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
        self.Key = ''
        self.KeyTime = 0.0

    def run(self):
        while(True):
            self.Key = readkey()
            self.KeyTime = time.time()

            if self.Key == 'q':
                break

InputKey = CheckKey() 
InputKey.start()

BeforeKey = ''
BeforeTime = 0.0

while(True):
    if(InputKey.Key == 'q'):
        break
    elif(InputKey.Key == BeforeKey and InputKey.KeyTime != BeforeTime):
        print("Same the key",end="\r\n")
        BeforeTime = InputKey.KeyTime
    elif(InputKey.Key == BeforeKey and InputKey.KeyTime == BeforeTime):
        print("No press the key",end="\r\n")
    elif(InputKey.Key != BeforeKey):
        print("Change the key",end="\r\n")
        BeforeKey = InputKey.Key
        BeforeTime = InputKey.KeyTime

    time.sleep(1)
```

``` class CheckKey() ```で、入力されたキーコードと入力時間を取得して、保存します。
入力された時間を保持するのが重要。

``` python
    def run(self):
        while(True):
            self.Key = readkey()
            self.KeyTime = time.time()
```

キーボード入力用のクラスを生成して、スレッドとして入力待ちの状態を作ります。

``` python
InputKey = CheckKey() 
InputKey.start()
```

前回取得したキーコードと、その入力時間を、メイン側で保持して、キー入力スレッド内の入力キーコードと入力時間を比較して、状態を特定します。

``` Python
BeforeKey = ''
BeforeTime = 0.0

while(True):
    if(InputKey.Key == 'q'):
        break
    elif(InputKey.Key == BeforeKey and InputKey.KeyTime != BeforeTime):
        print("Same the key",end="\r\n")
        BeforeTime = InputKey.KeyTime
    elif(InputKey.Key == BeforeKey and InputKey.KeyTime == BeforeTime):
        print("No press the key",end="\r\n")
    elif(InputKey.Key != BeforeKey):
        print("Change the key",end="\r\n")
        BeforeKey = InputKey.Key
        BeforeTime = InputKey.KeyTime

    time.sleep(1)
```

こんな感じで状態を判断しています。

|No.|動作|取りたい状態|
|:---:|---|---|
|1|どこかのキーを押した|入力されたキーと前回入力されたキーが違う（判断式：**InputKey.Key != BeforeKey**）|
|2|そのキーを押し続けた|入力されたキーと前回入力されたキーが同じで、それぞれの入力時間が違う（判断式：**InputKey.Key == BeforeKey and InputKey.KeyTime != BeforeTime**）|
|3|何も押していない|入力されたキーと前回入力されたキーが同じで、それぞれの入力時間が同じ（判断式：**InputKey.Key == BeforeKey and InputKey.KeyTime == BeforeTime**）|

```readchar.readkey()``` が入力待ちのときは、キーが押された時間も変わらない。この状態を利用してキーが押されていない、という状態を判断しています。

## まだ修正の余地あり。。

実行結果は下記のとおりです。

``` text:実行結果
$ python3 ./thread_readkey_00.py 
No press the key
No press the key
No press the key
Change the key
Same the key
Same the key
Same the key
No press the key
Change the key
Same the key
Same the key
Change the key
Same the key
Same the key
No press the key
No press the key
Change the key
Same the key
Same the key
No press the key
No press the key
```

カーソルキーを押し続けると、"Same the key" で、何も押していないと、"No press the key" になりました。

想定通りの動作になったのですが、最終行の```time.sleep(1)``` を、```time.sleep(0.1)``` にすると微妙な動作をすることが判明。

``` text:time.sleep(0.1)にしたあとの実行結果
Same the key
Same the key
Same the key
Same the key
Change the key
No press the key
No press the key
No press the key
No press the key
Same the key
Same the key
No press the key
No press the key
No press the key
No press the key
```

別のカーソルキーを押して、そのまま押し続けた後に、"No press the key" と何も押していないという状態が続き、その後 "Same the key" となり、離すと "No press the key"となります。

このままだと、変更後に停止し、同じキーが押されていると認識されて、停止状態のままになってしまいます。残念ながらまだ修正の余地がある状態です。

カムロボは動作が遅いので、それほどシビアな判定はいらないのですが、せめて0.5秒程度のタイムラグでキー状態が取れるよう修正を行っています。
修正が終わりましたら、また改めてアップしたいと思います。

## 余談

キー入力プログラムの下の ```print()``` 不思議に思った方もいるかもしれません。

``` python
print("Same the key",end="\r\n")
```

この部分の ```end="\r\n"``` を除くいたり、```end="\n"``` にすると、ターミナルへの出力が改行されないで、謎の空白を伴って出力されます。

``` bash
$ python3 ./thread_readkey.py
No press the key
                No press the key
                                No press the key
                                                No press the key
                                                                Change the key
                                                                              No press the key
                                                                                              No press the key
        Same the key
                    Same the key
                                Same the key
                                            Same the key
                                                        No press the key
                                                                        Change the key
                                                                                      Same the key
                                                                                                  Same the key
        No press the key
                        No press the key
```

リダイレクトして、ファイル出力すると崩れていない。

```
$ python3 ./thread_readkey.py > aaa
$ cat aaa
No press the key
No press the key
No press the key
No press the key
Change the key
Same the key
Change the key
Same the key
Change the key
Change the key
Same the key
No press the key
```

キャレッジリターンで行頭に戻していない感じに見えたので、```end="\r\n"``` を追加したら問題なくなりました。

``` bash
$ python3 ./thread_readkey.py
No press the key
No press the key
No press the key
Change the key
Same the key
Same the key
Same the key
Same the key
No press the key
Change the key
Same the key
Same the key
Same the key
No press the key
No press the key
No press the key
No press the key
No press the key
```

どこのバグなのかわかりませんが、とりあえず治ったので良しとして作業を進めます。