キーを押していないのが取りたい!!
作成中のカムプログラムロボット(以降カムロボ)を、キーボード操作したくて試行錯誤してました。
なんとか、カーソルキーの上下左右を押したときに、前進、更新、左旋回、右旋回ができるようにできたのですが、何も押していないときはストップさせたかった。
でもこれ、な~んにも押していないという状態が取れず、スペースキーを押したらストップさせるようにしました。
これでもいいはいいんですが、どうしてもキーを離したらストップさせたかった!!
やりたかったこと
キー入力についてこんなことをしたかった。
No. | 動作 | 取りたい状態 |
---|---|---|
1 | どこかのキーを押した | 押したキーのコード |
2 | そのキーを押し続けた | 押し続けているという状態 |
3 | キーを離した | 何も押していないという状態 |
キーコードの取得は readchar
モジュールを使いました。
結果、1と2については、入力されたキーコードと、過去に入力されたキーを比較することで実現できたのですが、3の状態が取れなかった。。
3の状態が取れないのは readchar
モジュールのreadchar.readkey()
は、キー入力があるまで待ち状態になるためで(当たり前なので悪いわけでは無い)、押していないという状態を取ることができませんでした。
色々と試してできそうな目処が立ったのでまとめてみました。
OS環境
Raspberry Piの OSはRaspbian 9.8になります。
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 のインストール
sudo pip3 install readchar
python3に対応した readcharがほしかったので、上記のようにインストールしました。
キー入力プログラム(テスト版)
テストで利用したキー入力プログラムです。
実行を停止する場合は、'q' を押してください。Ctrl+c でも止まりません。
結局、readchar.readkey()
の部分をThread化して対応しました。
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()
で、入力されたキーコードと入力時間を取得して、保存します。
入力された時間を保持するのが重要。
def run(self):
while(True):
self.Key = readkey()
self.KeyTime = time.time()
キーボード入力用のクラスを生成して、スレッドとして入力待ちの状態を作ります。
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)
こんな感じで状態を判断しています。
No. | 動作 | 取りたい状態 |
---|---|---|
1 | どこかのキーを押した | 入力されたキーと前回入力されたキーが違う(判断式:InputKey.Key != BeforeKey) |
2 | そのキーを押し続けた | 入力されたキーと前回入力されたキーが同じで、それぞれの入力時間が違う(判断式:InputKey.Key == BeforeKey and InputKey.KeyTime != BeforeTime) |
3 | 何も押していない | 入力されたキーと前回入力されたキーが同じで、それぞれの入力時間が同じ(判断式:InputKey.Key == BeforeKey and InputKey.KeyTime == BeforeTime) |
readchar.readkey()
が入力待ちのときは、キーが押された時間も変わらない。この状態を利用してキーが押されていない、という状態を判断しています。
まだ修正の余地あり。。
実行結果は下記のとおりです。
$ 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)
にすると微妙な動作をすることが判明。
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()
不思議に思った方もいるかもしれません。
print("Same the key",end="\r\n")
この部分の end="\r\n"
を除くいたり、end="\n"
にすると、ターミナルへの出力が改行されないで、謎の空白を伴って出力されます。
$ 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"
を追加したら問題なくなりました。
$ 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
どこのバグなのかわかりませんが、とりあえず治ったので良しとして作業を進めます。