From 27de3bd1f15c3dfe64bfbe9202e7dafe47413c95 Mon Sep 17 00:00:00 2001 From: Yuki Yabumoto Date: Mon, 19 Feb 2024 16:10:28 +0900 Subject: [PATCH 1/5] =?UTF-8?q?=E5=9B=9E=E8=BB=A2=E5=8F=B0=E7=94=A8?= =?UTF-8?q?=E3=81=AE=E3=82=B3=E3=83=B3=E3=83=88=E3=83=AD=E3=83=BC=E3=83=A9?= =?UTF-8?q?=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- rotationtable_controller.py | 173 ++++++++++++++++++++++++++++++++++++ 1 file changed, 173 insertions(+) create mode 100644 rotationtable_controller.py diff --git a/rotationtable_controller.py b/rotationtable_controller.py new file mode 100644 index 0000000..95eaeaa --- /dev/null +++ b/rotationtable_controller.py @@ -0,0 +1,173 @@ +from serial import Serial +from serial.tools import list_ports +from threading import Thread +from time import sleep + + +class RotationTableController(): + def __init__(self): + self.port='' + self.baud = 115200 + self.ser = None + self.isConnect = False + self.isStart=False + + def start(self): + if self.isStart:return + self.isStart=True + Thread(target=self.connectin_loop,daemon=True).start() + + def end(self): + if not self.isStart:return + + self.isStart=False + + if self.isConnect: + self.ser.close() + self.ser=None + self.port='' + self.isConnect=False + + #シリアルポートにコマンドを送って反応を見る + def chk_connection(self,port:str): + try: + ser = Serial(port,self.baud,timeout=1.0,write_timeout=0.1,inter_byte_timeout=0.1) + cmd='ping\r\n'#死活確認コマンド + ser.write(cmd.encode()) + sleep(0.1) + res = ser.read_all() + ser.close() + if res.decode().rsplit('\r\n')[1]=='True': + return True + else: + return False + except: + return False + + #シリアルの接続監視 + #制御基板との接続を検出して自動接続する + def connectin_loop(self): + while self.isStart: + #Comポートのリスト生成 + ports = list_ports.comports() + devices = [info.device for info in ports] + #print(devices) + if self.isConnect:#切断されたポートがないかチェック + sleep(0.1) + if not self.port in devices: + print('disconnect',self.port) + self.port='' + self.isConnect=False + else: + continue + + #それぞれのポートに接続してテーブル制御ポートを見つける + for port in devices: + if self.chk_connection(port): + self.port=port + break + + #接続 + if self.port != '': + print('connect',self.port) + self.ser = Serial(self.port,self.baud,timeout=1.0) + self.isConnect=True + + + #コマンド送信 + def send_cmd(self,cmd:str): + #print(cmd) + self.ser.reset_output_buffer() + self.ser.reset_input_buffer() + self.ser.write(cmd.encode()) + sleep(0.2) + res = self.ser.read_all() + #print(res) + try: + ret = eval(res.decode().rsplit('\r\n')[1]) + except: + ret=False + + return ret + + + #回転指令 指定した角度分テーブルを回転する + #引数 回転角度[deg] 値をマイナスにすると逆回転 + #戻り値 正常:True 異常:False + def rotate(self,deg:float): + if not self.isConnect: + return False + + cmd = f'rot:{deg}\r\n' + + return self.send_cmd(cmd) + + #停止指令 テーブルの回転を即座に停止させる + #戻り値 正常:True 異常:False + def stop(self): + if not self.isConnect: + return False + + cmd = 'stop\r\n' + + return self.send_cmd(cmd) + + #速度変更指令 テーブルの回転速度を変更する(テーブル回転中は変更不可) + #引数 回転速度[Hz] 1~25000[Hz] 周波数が高いほど速度が速くなる + #戻り値 正常:True 異常:False + def set_speed(self,speed:int): + if not self.isConnect: + return False + + if 0>speed: + return False + + speed_us = int(1/speed * 1000000) + + cmd = f'set_speed:{speed_us}\r\n' + + return self.send_cmd(cmd) + + #加速度変更 テーブルの回転加速度を変更する(テーブル回転中は変更不可) + #引数 0で最大加速度、値を大きくすると加速度が小さくなり台形制御になる + #戻り値 正常:True 異常:False + def set_acc(self,acc:int): + if not self.isConnect: + return False + + cmd = f'set_acc:{acc}\r\n' + + return self.send_cmd(cmd) + + #移動ステータス テーブルの動作状態を取得する  + #戻り値 移動中:True 停止中/異常:False + def get_status(self): + if not self.isConnect: + return False + + cmd = 'get_mov\r\n' + + return self.send_cmd(cmd) + + #####以下開発用コマンドにつき通常は使用しない###### + #テーブル回転のパラメータ設定 + #引数 テーブル1回転のパルス数設定 + #戻り値 正常:True 異常:False + def set_step_per_rev(self,step:int): + if not self.isConnect: + return False + + cmd = f'set_step:{step}\r\n' + + return self.send_cmd(cmd) + + #パルス出力ピン設定(設定後に再起動必要) + #引数 pin番号(基板pin):0(6), 1(5), 2(8), 3(10), 4(9), 6(4), 7(5), 26(0), 27(1), 28(2), 29(3) + #戻り値 正常:True 異常:False + def set_pin_config(self,pls_pin:int,dir_pin:int): + if not self.isConnect: + return False + + cmd = f'set_pin:{pls_pin},{dir_pin}\r\n' + + return self.send_cmd(cmd) From 0e6a4d00d49fa4e2d8078e4e6248462a7b67de09 Mon Sep 17 00:00:00 2001 From: Yuki Yabumoto Date: Mon, 19 Feb 2024 16:10:44 +0900 Subject: [PATCH 2/5] =?UTF-8?q?=E5=9B=9E=E8=BB=A2=E5=8F=B0=E3=82=92?= =?UTF-8?q?=E5=9B=9E=E8=BB=A2=E3=81=99=E3=82=8B=E3=82=B3=E3=83=9E=E3=83=B3?= =?UTF-8?q?=E3=83=89=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- action_parser.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/action_parser.py b/action_parser.py index f5b1af1..1013c77 100644 --- a/action_parser.py +++ b/action_parser.py @@ -72,6 +72,8 @@ def action_parser(raw_action: str): (action[1] == 'x' or action[1] == 'y' or action[1] == 'z') and \ isfloat(action[2]): # NOTE: add velocity? action_list.append(action) + elif action[0] == 'rot' and len(action) == 2 and action[1].isdigit(): + action_list.append(action) elif action[0] == 'shutter' and len(action) == 1: action_list.append(action) else: From 5e3bcca835da63ebe268bc31b346e656ff29010e Mon Sep 17 00:00:00 2001 From: Yuki Yabumoto Date: Mon, 19 Feb 2024 16:11:15 +0900 Subject: [PATCH 3/5] =?UTF-8?q?=E5=9B=9E=E8=BB=A2=E5=8F=B0=E3=82=92?= =?UTF-8?q?=E5=9B=9E=E8=BB=A2=E3=81=99=E3=82=8B=E3=82=B3=E3=83=9E=E3=83=B3?= =?UTF-8?q?=E3=83=89=E3=82=92=E5=87=A6=E7=90=86=E3=81=99=E3=82=8B=E9=83=A8?= =?UTF-8?q?=E5=88=86=E3=82=92=E5=AE=9F=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- exec_actions.py | 39 ++++++++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/exec_actions.py b/exec_actions.py index 3c752b4..45a0e5f 100644 --- a/exec_actions.py +++ b/exec_actions.py @@ -2,12 +2,23 @@ import time import signal import subprocess +from typing import Union from StackShot3X_API_for_Python.commdefs import * from action_parser import action_parser from StackShot3X_API_for_Python.stackshot_controller import StackShotController - -def exec_actions(stop_flag, raw_actions: str, controller: StackShotController, brackets: int, doFocusStacking: bool, doMetashape: bool, moveSpeedPercent: int, config): +from rotationtable_controller import RotationTableController + +def exec_actions( + stop_flag, + raw_actions: str, + stackshot_controller: StackShotController, + rotationtable_controller: RotationTableController, + brackets: int, + doFocusStacking: bool, + doMetashape: bool, + moveSpeedPercent: int, + config): # validation and parse actions try: action_queue = action_parser(raw_actions) @@ -31,9 +42,9 @@ def exec_actions(stop_flag, raw_actions: str, controller: StackShotController, b try: # send stop command in advance to prevent infinite move, - controller.stop(RailAxis.X) - controller.stop(RailAxis.Y) - controller.stop(RailAxis.Z) + stackshot_controller.stop(RailAxis.X) + stackshot_controller.stop(RailAxis.Y) + stackshot_controller.stop(RailAxis.Z) # execute all actions for action in action_queue: @@ -42,7 +53,7 @@ def exec_actions(stop_flag, raw_actions: str, controller: StackShotController, b if stop_flag.value == 1: # true return - # move axis + # move axis (StackShot) if action[0] == 'move': axis = None if action[1] == 'x': @@ -52,19 +63,25 @@ def exec_actions(stop_flag, raw_actions: str, controller: StackShotController, b elif action[1] == 'z': axis = RailAxis.Z - controller.move_at_speed(axis, RailDir.FWD, float(action[2]), moveSpeedPercent/100.0) + stackshot_controller.move_at_speed(axis, RailDir.FWD, float(action[2]), moveSpeedPercent/100.0) # wait for rail stop - while controller.get_status(axis) != RailStatus.IDLE: + while stackshot_controller.get_status(axis) != RailStatus.IDLE: time.sleep(0.2) + # rotate (Rotation Table) + elif action[0] == 'rot': + angle = int(action[1]) + rotationtable_controller.rotate(angle) + while rotationtable_controller.get_status(): + time.sleep(0.1) - # shoot camera + # shoot camera (StackShot) elif action[0] == 'shutter': - controller.shutter(1, 1., 2.) # NOTE + stackshot_controller.shutter(1, 1., 2.) # NOTE # wait for finish shutter - while controller.get_status(RailAxis.ANY) != RailStatus.IDLE: + while stackshot_controller.get_status(RailAxis.ANY) != RailStatus.IDLE: time.sleep(0.2) while len(os.listdir(image_src_folder)) < brackets: time.sleep(0.2) From ebfb280b6e6dd0279cc1613c5f67eab911fd9751 Mon Sep 17 00:00:00 2001 From: Yuki Yabumoto Date: Mon, 19 Feb 2024 16:11:35 +0900 Subject: [PATCH 4/5] =?UTF-8?q?=E5=9B=9E=E8=BB=A2=E5=8F=B0=E7=94=A8?= =?UTF-8?q?=E3=82=B3=E3=83=B3=E3=83=88=E3=83=AD=E3=83=BC=E3=83=A9=E3=82=92?= =?UTF-8?q?=E6=8E=A5=E7=B6=9A/=E5=88=87=E6=96=AD=E3=81=99=E3=82=8B?= =?UTF-8?q?=E5=87=A6=E7=90=86=E3=82=92=E5=AE=9F=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gui.py | 41 +++++++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/gui.py b/gui.py index bcbed93..bccd063 100644 --- a/gui.py +++ b/gui.py @@ -21,6 +21,7 @@ from StackShot3X_API_for_Python.stackshot_controller import StackShotController from action_parser import action_parser from exec_actions import exec_actions +from rotationtable_controller import RotationTableController def isValidValue(s, min, max): @@ -36,16 +37,16 @@ def isValidValue(s, min, max): return False -def moveAxis(controller, axis, dir, dist, speedPercent): +def moveAxis(stackshot_controller, axis, dir, dist, speedPercent): try: - controller.move_at_speed(axis, dir, dist, speedPercent / 100.0) + stackshot_controller.move_at_speed(axis, dir, dist, speedPercent / 100.0) except: # pass print('move') -def stopAxis(controller, axis): +def stopAxis(stackshot_controller, axis): try: - controller.stop(axis) + stackshot_controller.stop(axis) except: # pass print('stop') @@ -85,12 +86,12 @@ def __init__(self): self.working_thread = None self.stop_flag = Value('i', 0) # 0: false, 1: true - # when start app, connect with StackShot3X - self.controller = StackShotController() + # connect with StackShot3X + self.stackshot_controller = StackShotController() try: print('connecting to Stackshot3X...') - self.controller.open() - self.working_thread = Thread(target=self.controller.get_status, args=(RailAxis.ANY,), daemon=True) + self.stackshot_controller.open() + self.working_thread = Thread(target=self.stackshot_controller.get_status, args=(RailAxis.ANY,), daemon=True) self.working_thread.start() self.working_thread.join(timeout=3) if self.working_thread.is_alive() == True: @@ -98,6 +99,12 @@ def __init__(self): except Exception as excpt: print(excpt) exit() + return + + # connect with RotationTable + self.rotationtable_controller = RotationTableController() + print('connecting to RotationTable...') + self.rotationtable_controller.start() # load config from config.ini def loadConfig(self): @@ -147,7 +154,6 @@ def loadConfig(self): self.config.write(f) f.close() - # select "Image Src Folder" PATH from file dialog and save to config def updateImageSrcFolder(self): file = QtWidgets.QFileDialog.getExistingDirectory() @@ -211,14 +217,15 @@ def moveStackShot(self, dir: RailDir, dist: float): elif self.gui.zRadioButton.isChecked() == True: axis = RailAxis.Z - moveAxis(self.controller, axis, dir, dist, self.gui.speedPercent.value()) + moveAxis(self.stackshot_controller, axis, dir, dist, self.gui.speedPercent.value()) # wait for rail stop - while self.controller.get_status(axis) != RailStatus.IDLE: + while self.stackshot_controller.get_status(axis) != RailStatus.IDLE: time.sleep(0.2) # start action def startAction(self): print('start') + self.saveBracketsAndSpeed() # set stop_flag to 0(false) @@ -226,7 +233,7 @@ def startAction(self): self.stop_flag.value = 0 # false # create and thread to execute actions - self.working_thread = Thread(target=exec_actions, args=(self.stop_flag, self.gui.actionsPanel.toPlainText(), self.controller, self.gui.brackets.value(), self.gui.doFocusStacking.isChecked(), self.gui.doMetashape.isChecked(), self.gui.speedPercent.value(), self.config), daemon=True) + self.working_thread = Thread(target=exec_actions, args=(self.stop_flag, self.gui.actionsPanel.toPlainText(), self.stackshot_controller, self.rotationtable_controller, self.gui.brackets.value(), self.gui.doFocusStacking.isChecked(), self.gui.doMetashape.isChecked(), self.gui.speedPercent.value(), self.config), daemon=True) self.working_thread.start() # stop in progress action @@ -249,10 +256,12 @@ def closeEvent(self, event: QtGui.QCloseEvent): # after stop all axis, disconnect from StackShot3X try: print('discoonnecting from Stackshot3X...') - self.controller.stop(RailAxis.X) - self.controller.stop(RailAxis.Y) - self.controller.stop(RailAxis.Z) - self.controller.close() + self.stackshot_controller.stop(RailAxis.X) + self.stackshot_controller.stop(RailAxis.Y) + self.stackshot_controller.stop(RailAxis.Z) + self.stackshot_controller.close() except Exception as excpt: print(excpt) return + + self.rotationtable_controller.end() From b4a861d0eb6c8c15742253e6165fcc479f097d90 Mon Sep 17 00:00:00 2001 From: Yuki Yabumoto Date: Mon, 16 Dec 2024 11:17:58 +0900 Subject: [PATCH 5/5] update rotation table --- exec_actions.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/exec_actions.py b/exec_actions.py index 73d0885..7064ce4 100644 --- a/exec_actions.py +++ b/exec_actions.py @@ -71,8 +71,16 @@ def exec_actions( # rotate (Rotation Table) elif action[0] == 'rot': + ret = rotationtable_controller.set_speed(500) # TODO とりあえずハードコード。UI追加する? + if ret == False: + print("Error in rotation table.") + while rotationtable_controller.get_status(): + time.sleep(0.1) + angle = int(action[1]) - rotationtable_controller.rotate(angle) + ret = rotationtable_controller.rotate(angle) + if ret == False: + print("Error in rotation table.") while rotationtable_controller.get_status(): time.sleep(0.1)