🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
while

Whileは多くの言語で使われるコントロール構造であり、特定の条件が満たされる限り一連の命令を繰り返し実行します。

Raspberry Pi

Raspberry Piは、ラズベリーパイ財団が開発した、名刺サイズのLinuxコンピュータです。 学校で基本的なコンピュータ科学の教育を促進することを意図しています。

Q&A

解決済

1回答

1506閲覧

QThreadを用いたコードを添削してください

pythonnoob1

総合スコア18

while

Whileは多くの言語で使われるコントロール構造であり、特定の条件が満たされる限り一連の命令を繰り返し実行します。

Raspberry Pi

Raspberry Piは、ラズベリーパイ財団が開発した、名刺サイズのLinuxコンピュータです。 学校で基本的なコンピュータ科学の教育を促進することを意図しています。

0グッド

2クリップ

投稿2020/06/01 08:29

編集2020/06/02 04:59

環境

Python 3.x
RaspberryPi 3 B
PyQt5

目標

サブスレッドを作り、サブスレッドで while文 を使う

やりたいこと

「GUI上で自動ドアを操作したい」

自動ボタンを押す
①焦電センサでモノを検知
②リードスイッチ2が反応するまでモーターを正転
③モーター停止
④リードスイッチ1が反応するまでモータを逆転
⑤モーター停止
①~⑤がループ

停止ボタンを押す
モーターが停止する

現状

tetatailにて、お優しい方に教えてもらい、QThread を用いた GUI のコードを作成してみました。
しかし、現在、動作確認できる環境になく、初心者の私が作ったコードですので、あっているかどうか、エラーが出るかどうかが全く分かりません。
どうか確認していただけないでしょうか?

python

1#ファイル名「autodoor1.py」 2 3import sys 4from PyQt5.QtWidgets import * 5from PyQt5.QtGui import * 6from PyQt5.QtCore import * 7import subprocess 8import time 9import numpy as np 10import cv2 11import RPi.GPIO as GPIO 12 13import autodoor2 as auto 14 15Shoden_PIN=22 16 17Motor_PIN_1=24 18Motor_PIN_2=25 19 20Reed_PIN_1=17 21Reed_PIN_2=27 22 23GPIO.setmode(GPIO.BCM) 24GPIO.setup(Shoden_PIN, GPIO.IN) 25GPIO.setup(Reed_PIN_1, GPIO.IN) 26GPIO.setup(Reed_PIN_2, GPIO.IN) 27 28def _init_GPIO(): 29 GPIO.setmode(GPIO.BCM) 30 GPIO.setup(Motor_PIN_1, GPIO.OUT) 31 GPIO.setup(Motor_PIN_2, GPIO.OUT) 32 33def Motor_Forward(): 34 _init_GPIO() 35 GPIO.output(Motor_PIN_1, 1) 36 GPIO.output(Motor_PIN_2, 0) 37 38def Motor_Backward(): 39 _init_GPIO() 40 GPIO.output(Motor_PIN_1, 0) 41 GPIO.output(Motor_PIN_2, 1) 42 43def Motor_Stop(): 44 _init_GPIO() 45 GPIO.output(Motor_PIN_1, 0) 46 GPIO.output(Motor_PIN_2, 0) 47 48 49class Tab1Widget(QWidget): 50 51 def __init__(self): 52 super().__init__() 53 self.title = "GUI test" 54 self.left = 10 55 self.top = 10 56 self.width = 640 57 self.height = 480 58 self.initUI() 59 self.counter = 0 60 61 def initUI(self): 62 63 64 super(Tab1Widget, self).__init__() 65 66 67 btn1 = QPushButton("自動", self) 68 btn2 = QPushButton("停止", self) 69 70 btn1.clicked.connect(self.auto ) 71 btn2.clicked.connect(self.stop ) 72 73 self.textbox4 = QLineEdit(self) 74 75 76 label3 = QLabel("自動ドア") 77 label4 = QLabel("ドアの状態") 78 79 layoutA = QGridLayout() 80 layoutA.addWidget(label4,0,0) 81 layoutA.addWidget(self.textbox4,0,1) 82 layoutA.addWidget(btn1,1,0) 83 layoutA.addWidget(btn2,1,1) 84 85 layoutB = QVBoxLayout() 86 layoutB.addWidget(label3) 87 layoutB.addLayout(layoutA) 88 89 self.setLayout(layoutB) 90 91 self.show() 92 93 def auto(self): 94 95 self.thread = QThread() 96 97 self.worker = auto.Worker() 98 self.worker.moveToThread(self.worker.process) 99 self.worker.doorOpened.connect(self.func) 100 101 self.thread.start() 102 103 def func(self): 104 105 self.textbox4.setText("OPEN") 106 107 108 def stop(self): 109 110 auto.M_Stop() 111 112 113 114 115if __name__ == "__main__": 116 app = QApplication(sys.argv) 117 ex = Tab1Widget() 118 sys.exit(app.exec_()) 119

python

1#ファイル名「autodoor2.py」 2 3import sys 4from PyQt5.QtWidgets import * 5from PyQt5.QtGui import * 6from PyQt5.QtCore import * 7import subprocess 8import time 9import numpy as np 10import cv2 11import RPi.GPIO as GPIO 12 13 14Shoden_PIN=22 15 16Motor_PIN_1=24 17Motor_PIN_2=25 18 19Reed_PIN_1=17 20Reed_PIN_2=27 21 22GPIO.setmode(GPIO.BCM) 23GPIO.setup(Shoden_PIN, GPIO.IN) 24GPIO.setup(Reed_PIN_1, GPIO.IN) 25GPIO.setup(Reed_PIN_2, GPIO.IN) 26 27def _init_GPIO(): 28 GPIO.setmode(GPIO.BCM) 29 GPIO.setup(Motor_PIN_1, GPIO.OUT) 30 GPIO.setup(Motor_PIN_2, GPIO.OUT) 31 32def Motor_Forward(): 33 _init_GPIO() 34 GPIO.output(Motor_PIN_1, 1) 35 GPIO.output(Motor_PIN_2, 0) 36 37def Motor_Backward(): 38 _init_GPIO() 39 GPIO.output(Motor_PIN_1, 0) 40 GPIO.output(Motor_PIN_2, 1) 41 42def Motor_Stop(): 43 _init_GPIO() 44 GPIO.output(Motor_PIN_1, 0) 45 GPIO.output(Motor_PIN_2, 0) 46 47 48def M_Stop(): 49 Motor_Stop() 50 time.sleep(1) 51 GPIO.cleanup() 52 53 54 55class Worker(QObject): 56 doorOpened = pyqtSignal() 57 58 def __init__(self.parent = None): 59 QObject.__init__(self, parent = parent) 60 61 def process(self): 62 while 1: 63 if GPIO.input(Shoden_PIN)==GPIO.HIGH: 64 Motor_Forward() 65 while 1: 66 if GPIO.input(Reed_PIN_2)==GPIO.HIGH: 67 GPIO.cleanup() 68 Motor_Stop() 69 time.sleep(1) 70 GPIO.cleanup() 71 Motor_Backward() 72 while 1: 73 if GPIO.input(Reed_PIN_1)==GPIO.HIGH: 74 self.textbox4.setText("CLOSE") 75 GPIO.cleanup() 76 Motor_Stop() 77 GPIO.cleanup() 78 break 79 80 else: 81 pass 82 83 break 84 85 else: 86 pass 87 else: 88 pass 89 90 91 self.doorOpened.emit() 92

補足

QThread を用いるにあたって幾つか分からないことがあります。これらについても教えてくださると幸いです。
①停止ボタンを押したら、きちんとモーターが止まるようになっていますか。もし、なっていなかったらどのように直せばよいでしょうか?
②テキストボックス4に、ドアが開いていたら「OPEN」、閉まっていたら「CLOSE」と出力されるようにしたいです。どのようしたらよいのでしょうか?
③「autodoor2.py(下のコード)」にある、「 doorOpened=pyqtSignal() 」の意味と何のために入れるのか、教えてください。
④同じく「autodoor.py」にある、「def init(self.parent=None)」と「QObject.init(self.parent=parent)」の意味についても教えてください。
⑤同じく「autodoor.py」にある、「self.doorOpened.emit()」の意味についても教えてください。

分からないことが多く、知識不足も重々承知しておりますが、一つでも教えていただけると幸いです。
どうかよろしくお願いします!

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

  • QThreadについて
  • Qt のイベントループとシグナル&スロット
  • GPIO 自動の制御部分 (worker.process)
  • ドアの停止について

実行はしてませんが、明らかなコード上の間違いは以下の二つ

self.worker.moveToThread(self.worker.process)

moveToThreadにはQThreadオブジェクトを渡します。
スレッド開始時に呼び出すのは、thread.started シグナルを processスロットへ接続します。

python

1self.worker.moveToThread(self.thread) 2self.thread.started.connect(self.workrer.process)

python

1 2def process(self): 3 while 1: 4 # 省略 5 else: 6 pass 7 8 # インデントを確認して見て下さい 9 # 一番外側のループを抜ける break がないので 10 # コードがここに到達することはありません。 11 12 self.doorOpened.emit()

条件を達成した時にループを抜けるようにすれば
while ループの3重ネストは不要になるはずです。

def process(self): while 1: # ①~⑤がループ # ①焦電センサでモノを検知 while GPIO.input(Shoden_PIN) != GPIO.HIGH: time.sleep(0.5) # 適度にウェイトを入れる。取り零しが出ない範囲で # ②リードスイッチ2が反応するまでモーターを正転 Motor_Forward() while GPIO.input(Reed_PIN_2) != GPIO.HIGH: time.sleep(0.5) # ③モーター停止 (以下略) # ここで ドア open を通知 self.doorOpened.emit() # ④リードスイッチ1が反応するまでモータを逆転 # ⑤モーター停止 # ここで ドア close を通知

ドアの停止については、M_Stop -> Motor_Stop() 呼び出し迄は大丈夫そうですが、
スレッドが稼働し続けている点に注意してください。

例えば、M_Stop() が Motor_Stop() を呼び出した後にも、
スレッドは稼働し続けているので Motor_Forward() ~を呼び出す、等。
※ 実デバイス側で停止の扱いがどうなってるのか迄は解りませんが、
少なくとも、スレッドの入出力が残ってるのは問題になりそうです。

また、GPIO に対しての入出力が、メインスレッドとサブスレッドからとなるので、
スレッドセーフになっているかどうかも確認が必要です。

GPIOライブラリ内部でどうなっているのかは解りませんが、
少なくともPythonのコード上は、別々のスレッドから同時に同じIOを扱うのは安全ではありません。

解決策:

  • 停止時にはスレッドを止めてから M_Stop()

実際にデバイスを扱えない環境であれば、シミュレートをお勧めします。
GPIOを扱う部分の実装を別ければ、開閉のロジック自体はテスト可能です。
自動ドア開閉を模倣するクラスを作り、期待通りの動作になるか試してみると良いです。

投稿2020/06/03 08:07

編集2020/06/03 11:48
teamikl

総合スコア8744

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

pythonnoob1

2020/06/03 11:41

添削ありがとうございます。 正直、上のコードで分からないことはすごくたくさんあります。 質問の補足の欄に書いてある ①、②は teamiklさんのおかげで何とかなりそうです。 ただ、③~⑤に関して分かりません。 自分なりにシグナルスロットについて調べてみたのですが、いまいち理解が深まりませんでした。 そもそもシグナルとスロットとは一体何なのでしょうか? また、調べたら「シグナルを作成するにはQObjectを継承したクラスのクラス変数として、pyqtSignal()を定義し、emit()を呼ぶことでシグナルが発行される」あったのですが、どういうことか理解できませんでした。 teamiklさんが添削してくださったコードを見るところ、「self.doorOpened.emit() でそこまでの実行結果を一旦メインスレッドに通知している。」という解釈でよいのでしょうか?
teamikl

2020/06/03 12:15

イベント処理の一種で、 - 「マウスをクリックすれば」〇〇関数を呼び出す - 「ドアが開閉されれば」△△関数を呼び出す シグナルは「」の条件・イベント種別にあたる部分で スロットは後半の関連付けされる処理 ○〇関数等に当たります。 emitはそのイベントを発生されることになります。 注意点は、イベントループについても同時に理解が必要で、 emit の時点で必ず関連付けされた関数が実行されるわけではありません イベントループがそのイベント(シグナル)のキューを処理するタイミングで 各々の処理が呼び出されます。 それぞれの関数の中では、時間のかかる処理をすると イベントループが他のイベントを処理できなくなるので、これが メインスレッド上だと、ループを使うとGUIがフリーズする原因に繋がります。 通常は、スロットは迅速に処理を終えて イベントループに制御を戻さなくてはなりません。 今回の、QThread でのループですが、 これも同様に QThread のイベントループを止める処理に相当します。 完全に間違った使い方ではありませんが、QThreadを使う利点がなくなります。 スレッド→メインへのシグナル(doorOpened等)は問題ありませんが、 メイン→スレッドへ逆向きにデータのやり取りをしたいといった場合は、 このシグナル機構は使えません。 QThreadのイベントループが、worker.processのループに阻害されている為、イベントが処理されない。
pythonnoob1

2020/06/03 13:38 編集

ありがとうございます。シグナルとスロットに関して、簡単にではありますが理解できました。 私のやりたいことは、「メインスレッドでGUIを動かし、自動ボタンを押すとサブスレッドが開き、ループする関数が実行される。そして、停止ボタンを押すとスレッドが停止してすべてのセンサ、モーターが停止する」ということです。 おまけとして、できるならばドアの開閉状態をGUI上に出力出来たらな考えていました。 ですので、「メイン→スレッド」へのデータのやり取りはないと思います。 「メインスレッドでwhileが使えないので、ボタンを押したら別スレッドが開きループする関数を実行し、ボタンを押したら別スレッドが閉じる」ということをしたい! こういった場合、イベントループが動いているQThreadを使うのは、得策なのでしょうか? 別の方法はないのでしょうか? また、teamiklさんの回答にありました「停止時にはスレッドを止めてから M_Stop() 」について、スレッドを停止するにはどうしたらよいのでしょうか?
teamikl

2020/06/03 15:17 編集

>こういった場合、イベントループが動いているQThreadを使うのは、得策なのでしょうか? 別の方法はないのでしょうか? Pythonの通常のthreadingモジュールも使えますが、 敢えて変更する利点も、特に思いつきません。 別の方法は、幾つか有りますが (1) 以前の回答にもした、そもそもループは使わず、 GPIO側から通知してもらい任意の関数を呼び出す方法ですね。 (2)他に非同期IOを用いる方法もありますが、 スレッドとは異なるアプローチを1から学びなおす事になります。 (3) ループの処理をQTimer を使って書く、です。 少し話題を広げすぎな感じもするので、 とりあえず、目的達成までは今の方針(スレッド内でループ) で行って、後々改良という形で試すと良いと思います。 ---- >停止時にはスレッドを止めてから M_Stop() 」について、スレッドを停止するにはどうしたらよいのでしょうか? ループ中にフラグでループを抜けるようにしておいて、 停止したいときにそのフラグを変更します。 「「メイン→スレッド」へのデータのやり取り」に相当するので、 現在の実装ではここで シグナル&スロットを使えず、妥協案としては 普通に、Workerクラスの属性で行うことになります。 補足追記: ↑ここはスレッドのイベントループを使わなくてもよいので、 メインスレッドでの実行になりますが、スロット呼出自体は可能でした。 スレッド側から安全にフラグを操作したい場合に問題になります。 (※↑コードを参照しながらでないと説明が難しい部分) 例えば Workerクラス内で while self.is_running: とループを組んだ場合、 他スレッドからworker.is_running = False と変更しループを抜ける。 注意点は、待機ループが3カ所ある点 -> 3 箇所でフラグ判定が必要 と、ネストされたループでは break で外側のループを抜けられないので、 while の中身を別関数にして return や 例外を使うなどの工夫が必要です。 ---- スレッドの中断が、QThreadのquitやexitではない点にも注意。 QThreadのquit()はスレッドのイベントループの終了に使います。 今回抜けたいループは自分で組んだループなので、 終了や中断の処理も自分で実装する必要があります。
pythonnoob1

2020/06/04 01:27

>少し話題を広げすぎな感じもするので、 そうですね。申し訳ありません。とりあえずは今の方針で頑張ります。 現状、今のところはteamiklさんの添削部分を合わせると、とりあえずメインスレッドからサブスレッドは作成できていて、とりあえず直さないといけないところは、QThread内のwhileの3重ネストと、QThreadの中断・停止の処理をメインスレッドに実装することでしょうか? 少し話が変わってしまいますが、「QThreadの使い方を教えてください」の質問で作成してくださったコードについて、分からないところがあり、質問したいので、相互フォローをしていただき回答依頼をさせてはいただけないでしょうか?
teamikl

2020/06/04 05:03

>こういった場合、イベントループが動いているQThreadを使うのは、得策なのでしょうか? 別の方法はないのでしょうか? 若干訂正なのですが、実際にコード書いてみて、いくつか問題がありました。 Workerのループを抜ける処理 + 追加でスレッドの終了が必要になります。 (Thread と Workerが別れている為) QThreadの使い方には大きく2通りあり - QThread を継承し run() をオーバーライドする方法。(イベントループは使わない) - moveToThreadを使い、スレッドのイベントループを使う方法 現状のコードでは、後者の方法でスレッドを作っているが、 独自のループを使う(イベントループを使わない)為、 スレッドの終了はループを抜けるだけでは不十分で、 スレッド自体も自分で終了させる必要があります。 (前者であればループを抜ける→スレッドの停止でした) コードと合わせて説明しないと解り難い部分なので、ここは追々説明するとして (後で検討するとしても、「スレッドでループ」の枠内なので、少しの修正で済みます) まずは、「QThreadの中断・停止の処理」ですね。 一点、疑問なのですが、ここでの「停止」は、「一時停止(途中から再開可能)」なのでしょうか それとも完全にスレッドごと停止で問題ないのでしょうか? ---- フォローの件、承知しました。
pythonnoob1

2020/06/04 10:25

ありがとうございます。 >一点、疑問なのですが、ここでの「停止」は、「一時停止(途中から再開可能)」なのでしょうか それとも完全にスレッドごと停止で問題ないのでしょうか? 完全にスレッドごと停止してもらって構いません。 >フォローの件、承知しました。 ありがとうございます。早速質問させていただきます。 >フォローの件、承知しました。 ありがとうございます! 早速質問させていただきます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問