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

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

詳細はこちら
Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Raspberry Pi

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

Q&A

解決済

2回答

1983閲覧

QTheadでの情報のやり取りについてご教授ください。

pythonnoob1

総合スコア18

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Raspberry Pi

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

0グッド

0クリップ

投稿2020/11/30 06:09

編集2020/11/30 08:40

環境

raspberry pi 3 B
python 3.x
PyQt5

###質問内容
QTheadの使用方法について、
Qtheadでサブスレッドを作成し関数を実行してる際、メインスレッドからセンサの取得情報を渡すことは可能でしょうか?

作成してみたコード

コードについて、おかしいところや気持ちの悪いところ(def auto(self)の中身等)ありますが、気にしないでください。

python

1import sys 2from PyQt5.QtWidgets import * 3from PyQt5.QtGui import * 4from PyQt5.QtCore import * 5import subprocess 6import time 7import numpy as np 8import cv2 9import RPi.GPIO as GPIO 10 11 12Shoden_PIN=22 13 14Motor_PIN_1=24 15Motor_PIN_2=25 16 17Reed_PIN_1=17 18Reed_PIN_2=27 19 20GPIO.setmode(GPIO.BCM) 21GPIO.setup(Shoden_PIN, GPIO.IN) 22GPIO.setup(Reed_PIN_1, GPIO.IN) 23GPIO.setup(Reed_PIN_2, GPIO.IN) 24 25def setMotor(): 26 GPIO.setmode(GPIO.BCM) 27 GPIO.setup(Motor_PIN_1, GPIO.OUT) 28 GPIO.setup(Motor_PIN_2, GPIO.OUT) 29 30def Motor_Forward(): 31 GPIO.output(Motor_PIN_1, 1) 32 GPIO.output(Motor_PIN_2, 0) 33 34def Motor_Backward(): 35 GPIO.output(Motor_PIN_1, 0) 36 GPIO.output(Motor_PIN_2, 1) 37 38def Motor_Stop(): 39 GPIO.output(Motor_PIN_1, 0) 40 GPIO.output(Motor_PIN_2, 0) 41 42def Clean(): 43 GPIO.cleanup(24) 44 GPIO.cleanup(25) 45 46 47class Tab1Widget(QWidget): 48 49 def __init__(self, parent=None): 50 super().__init__(parent) 51 52 self.qt_thread = None 53 #self.running = False 54 self.title = "GUI test" 55 self.left = 10 56 self.top = 10 57 self.width = 640 58 self.height = 480 59 self.initUI() 60 self.counter = 0 61 62 def initUI(self): 63 64 65 btn1 = self.auto_button = QPushButton("自動", self) 66 btn2 = self.stop_button = QPushButton("停止", self) 67 68 btn1.clicked.connect(self.auto ) 69 btn2.clicked.connect(self.stop ) 70 71 self.textbox4 = QLineEdit(self) 72 73 74 label3 = QLabel("自動ドア") 75 label4 = QLabel("ドアの状態") 76 77 #GUIのレイアウト(省略) 78 79 def closeEvent(self, event): 80 self.stop() 81 82 if self.qt_thread: 83 self.qt_thread.wait(2000) 84 85 super().closeEvent(event) 86 87 def stop(self): 88 if self.qt_thread: 89 self.qt_thread.requestInterruption() 90 91 92 93 def setCount(self, alpha): 94 self.textbox4.setText("{}".format(alpha)) 95 96 97 def auto(self): 98 99 class MyQtThread(QThread): 100 101 sendString = pyqtSignal(str) 102 103 def run(self): 104 while 1: 105 if self.isInterruptionRequested(): 106 break 107 108 if GPIO.input(Shoden_PIN)==GPIO.HIGH: #物体検知 109 door="OPENED" 110 self.sendString.emit(door) 111 setMotor() 112 Motor_Forward() #ドアが開く 113 114 while 1: 115 116 if self.isInterruptionRequested(): 117 return 118 119 if GPIO.input(Reed_PIN_2)==GPIO.HIGH: #ドアが開ききる 120 Motor_Stop() 121 time.sleep(1) 122 123 if self.isInterruptionRequested(): 124 return 125 126 Motor_Backward() #ドア閉まる 127 128 while 1: 129 if self.isInterruptionRequested(): 130 return 131 132 if GPIO.input(Reed_PIN_1)==GPIO.HIGH: #ドアが閉まりきる 133 Motor_Stop() 134 door="CLOSED" 135 self.sendString.emit(door) 136 time.sleep(1) 137 break 138 139 else: 140 pass 141 142 break 143 144 else: 145 pass 146 break 147 148 else: 149 pass 150 151 Clean() 152 153 154 thread = self.qt_thread = MyQtThread() 155 156 thread.sendString.connect(self.setCount) 157 158 self.stop_button.clicked.connect(self.stop) 159 160 thread.finished.connect(lambda: print("Qt Thread Finished")) 161 thread.start() 162 163 164if __name__ == "__main__": 165 app = QApplication(sys.argv) 166 ex = Tab1Widget() 167 sys.exit(app.exec_())

行いたいこと

上のコードでは、すべての動作(センサからの情報取得、モーターの駆動)をサブスレッド内に入れています。
私の行いたいことは、「メインスレッドでQTimerを用いて、常にセンサ情報を取得、それをサブスレッドへ渡してサブスレッドでモーターを回す」ということです。
QTheadでメインスレッドとサブスレッドの情報の受け渡しは可能なのでしょうか?
どうか、ご教授お願いいたします。

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

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

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

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

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

guest

回答2

0

調べてもあまり例がないと思うので、
ループ関数の書き換え例を載せておきます。

  • イベントループ内でループ関数は使えない
  • スレッドでシグナルを受けるにはイベントループが必要

という要点は、抑えられてると思うので
既存のループ関数をどのようにイベントループに適応させるかについて、
QTimer を使う方法3通り。

  • func1 元のループ関数。問題点: イベントループと共存が出来ない
  • func2 タイマーを使う方法1 ループを再帰呼び出しっぽく書き換える必要がある
  • func3 タイマーを使う方法2
  • func4 ジェネレーターを使う方法

func2, func3 の違いは、終了か継続かを条件で判別してますが
問題点としては、ローカル変数がたくさんある場合、
クラスを作りインスタンス変数にするなどの対応が必要になります。

func4 のジェネレーターを使ったアプローチでは、
元のループでのコードの形を保ったまま、
タイマーでループの中身を1回ずつ実行といったことが実現できます。

python

1 2import sys 3import time 4from PyQt5.QtCore import QTimer, QCoreApplication 5 6def func1(count=10): 7 # XXX: time.sleep はイベントループを止める 8 for num in range(count): 9 print("func1", num) 10 time.sleep(1) 11 12def func2(count=10, num=0): 13 # QTimer.singleShot を使った書き換え例 14 15 def tick(): 16 nonlocal num 17 print("func2", num) 18 num += 1 19 if num < count: 20 QTimer.singleShot(1000, tick) 21 tick() 22 23def func3(parent, count=10, num=0): 24 # QTimer を使った書き換え例 25 26 timer = QTimer(parent) 27 28 def tick(): 29 nonlocal num 30 print("func3", num) 31 num += 1 32 if num >= count: 33 timer.stop() 34 35 timer.timeout.connect(tick) 36 timer.start(1000) 37 return timer 38 39 40def func4(count=10): 41 # ジェネレーターを使った書き換え例 42 # time.sleep(sec) -> yield msec にする 43 44 for num in range(count): 45 print("func4", num) 46 yield 1000 47 48def genTimer(gen, done=None): 49 # ジェネレーターを実行するタイマー 50 # next(gen) は yield の値を返す 51 def _next(): 52 interval = next(gen, None) 53 if interval is not None: 54 QTimer.singleShot(interval, _next) 55 else: 56 if done: 57 done() 58 _next() 59 60 61def main(): 62 app = QCoreApplication(sys.argv) 63 64 # func1(count=10) 65 # func2(count=10) 66 # func3(app, count=10) 67 68 genTimer(func4(count=10), done=app.quit) 69 70 ## 12秒後に終了 71 # QTimer.singleShot(1000*12, app.quit) 72 73 sys.exit(app.exec_()) 74 75 76if __name__ == '__main__': 77 main() 78 79

投稿2020/12/02 08:48

teamikl

総合スコア8742

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

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

pythonnoob1

2020/12/02 09:18

teamikl様、すごく丁寧にありがとうございます。 ぜひ参考にさせて頂き、私自身のコードに落とし込んでいきたいと思います! 本当にありがとうございます!!
guest

0

ベストアンサー

私の行いたいことは、「メインスレッドでQTimerを用いて、常にセンサ情報を取得、それをサブスレッドへ渡してサブスレッドでモーターを回す」ということです。

センサ情報は外部デバイスとの I/O ですよね?ここは改善可能な点で
QTimer よりも、スレッドで処理すべきだと思います。

QTimer では、GUIのイベントループ内で処理されるため、
センサーとのやり取りに何か問題がある場合、
GUIが応答なしに陥りやすくなります。

QTheadでメインスレッドとサブスレッドの情報の受け渡しは可能なのでしょうか?

シグナル&スロットでデータを別スレッドのイベントループへ送れます

質問のコード内でも、やりたいこととは逆向きですが、
別スレッドからメインスレッド(のイベントループ)へ受け渡しを行ってます。

  • self.sendString.emit(door) で door 情報を送って
  • thread.sendString.connect(self.setCount) emit 時に setCount を呼び出してます。

コードについて、おかしいところや気持ちの悪いところ(def auto(self)の中身等)ありますが、気にしないでください。

ここがいちばん重要なところで、コードの見た目の問題ではなく

QThread を使って「シグナル&スロット機構」を使うには
イベントループが必要な為、run()をオーバーライドではなく、
もうひとつの QThread の使い方で実装する必要があります。

この QThread の使い方では、サブスレッドのイベントループが働いてないので、
スレッド間のデータ受け渡しではなく、同スレッド内での関数呼び出しになります。
(ログを取って、どのスレッドで実行されているかを要確認)

※ 詳細は、以前の質問で説明した覚えがあります。
参考: https://teratail.com/questions/267587


注意点: GUI の描画更新処理は必ず
(GUIのイベントループを実行してる)メインスレッドで行う必要があります。

改善案:

  • センサーとモーターの処理にそれぞれ個別にスレッドを割り当てて、

 センサーでの定期的な情報所得をシグナルにし、
モーターやメイン(GUI)のスロットへconnectする。

  • シグナルは、複数のスロットにconnect することも出来ます。
  • センサーやモーターで定期的に情報を読み取る場合は、スレッドでのタイマー利用を検討。

投稿2020/11/30 16:28

編集2020/11/30 23:39
teamikl

総合スコア8742

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

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

pythonnoob1

2020/12/01 07:35 編集

teamikl様、数か月前に続き、回答ありがとうございます。 訳あって自動ドアのコード作成から離れてしまい様々なことを忘れていました。大変申し訳ございません。 解釈としては、 シグナル&スロットを使うことでデータのやり取りは可能。 ただ、現状はサブスレッド内の関数がループ関数であり、イベントループが働いていないためできない。 ということであっているのでしょうか? メインスレッドでセンサ情報の取得(QTimerを用いる+try-except文で例外処理を行えばセンサーがエラーを起こしても再度情報を取得できる?) ・メインスレッドの動き サブスレッドでメインスレッドからセンサ情報を取得しモーターを駆動 ・サブスレッド内の動き (QTimerを用いる) if文を用いて、優先度順にモーターを動かす(優先度⓵~) 変数をAとし、リードスイッチで「ドアが閉まった(リードスイッチ1が反応)」らA=0、「ドアが開ききった(リードスイッチ2が反応)」らA=2 ⓵物体検知したら1秒(未定)ドアを開く、A=1にする ②A=1ならドアを開く ③A=2ならドアを閉める ③A=0ならモーターを停止 すこし分かりにくいですが、今現在こんな感じのを頭の中で構成しています。
teamikl

2020/12/01 08:43

>シグナル&スロットを使うことでデータのやり取りは可能。 >現状はサブスレッド内の関数がループ関数であり、イベントループが働いていないためできない。 ということであっているのでしょうか? 残りの部分は把握しきれてませんが、概ねその通りです。 厳密には、エラーにはならないので、スロットの呼び出し自体は出来ます。 が、シグナルの受け取り側にはイベントループが必要なので、 サブスレッドのイベントループがないと、 同スレッド内のイベントループでの呼び出しになります。 (スレッド間のデータ受け渡しにならない) === なぜイベントループが必要なのかについて Qt のシグナル&スロット機構を使わない場合、 一般的にはスレッド間のデータのやり取りにはキューを用います。 スレッド側ではキューからデータを取り出すループを持つのですが、 その部分が、シグナル&スロットではイベントループに相当します。
pythonnoob1

2020/12/01 13:27

teamikl様、返信ありがとうございます。 QTheadの使い方をより深く学ぶことができました。 実際にコードを作成してみて、また調べても分からないことなどがありましたら質問させて頂くかも知れません。その時はどうぞよろしくお願い致します。 とりあえず数ヶ月前の質問を見返しながら自力で取り組んでみたいと思います。 ありがとうございました。
teamikl

2020/12/02 05:37

そうですね、サンプルコードとしては以前の回答の moveToThread を使うコードで示せてると思います。 質問の回答として、まとめると メインスレッドから別スレッドへのデータの受け渡しは、 - シグナル&スロットを使って可能 - 受け取り側にはイベントループが必要 具体的な実装手順として 1)シグナルを受け取るスレッドは、QThread継承ではなく  moveToThreadを使う方法で QThread を利用する。 2) イベントループを使うため、  ループ関数はQTimer を用いるように実装する ---- 少し質問から外れますが、 ループ関数を最小の変更でタイマーで動かす方法もあります。 Tkinter内でのループ処理 https://teratail.com/questions/299389 コードは Qt ではありませんが、ループ関数とイベントループの共存方法 「ループ関数をジェネレーターにすると」タイマーを使って、 イベントループで1ループずつ実行ということも実現できます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.36%

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

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

質問する

関連した質問