完善rokae自动化测试脚本工具,增加readme文件等

This commit is contained in:
2025-08-30 15:21:01 +08:00
parent 71046300fe
commit 15009924b3
9 changed files with 573 additions and 28 deletions

220
rokae/README.md Normal file
View File

@@ -0,0 +1,220 @@
## 自动化测试工具脚本
### 0. 用前须知
1. 调用必须在rokae目录下执行
2. python3.13开发使用时需版本匹配并安装requirements.txt中的依赖库
### 1. socket通信支持xService
> #### **A. 调用方法示例**
```python
from codes import openapi
import time
hr = openapi.HmiRequest()
time.sleep(2)
hr.close()
msg_id = hr.exec_cmd("controller.get_params")
record = hr.get_from_id(msg_id)
print(f"version = {eval(record)['data']['version']}")
```
> #### **B. 目前支持(后续可按需增加)**
```
collision.get_params
collision.set_params
controller.get_params
controller.heart
controller.reboot
controller.set_params
device.get_params
diagnosis.get_params
diagnosis.open
diagnosis.save
diagnosis.set_params
drag.get_params
drag.set_params
fieldbus_device.get_params
fieldbus_device.load_cfg
fieldbus_device.set_params
io_device.load_cfg
jog.get_params
jog.set_params
jog.start
log_code.data.code_list
log_code.data
modbus.get_params
modbus.get_values
modbus.load_cfg
modbus.set_params
move.get_joint_pos
move.get_monitor_cfg
move.get_params
move.get_pos
move.get_quickstop_distance
move.get_quickturn_pos
move.quick_turn
move.set_monitor_cfg
move.set_params
move.set_quickstop_distance
move.set_quickturn_pos
move.stop
overview.get_autoload
overview.get_cur_prj
overview.reload
overview.set_autoload
register.set_value
rl_task.pp_to_main
rl_task.run
rl_task.set_run_params
rl_task.stop
safety.safety_area.overall_enable
safety.safety_area.safety_area_enable
safety.safety_area.set_param
safety.safety_area.signal_enable
safety_area_data
servo.clear_alarm
socket.get_params
socket.set_params
soft_limit.get_params
soft_limit.set_params
state.get_state
state.get_tp_mode
state.set_tp_mode
state.switch_auto
state.switch_manual
state.switch_motor_off
state.switch_motor_on
system_io.query_configuration
system_io.query_event_cfg
system_io.update_configuration
```
### 2. modbus通信
> #### **A. 调用方法示例**
```python
from codes import openapi
md = openapi.ModbusRequest()
md.r_switch_auto()
_ = md.w_sta_tcp_vel
print(_)
```
> #### **B. 目前支持**
自定义的寄存器读写操作以及98%以上的预置modbus功能码。
### 3. 外部通信
> #### **A. 调用方法示例**
```python
from codes import openapi
ec = openapi.EcRequest()
_ = ec.get_cart_pos
ec.close_reduced_mode()
```
> #### **B. 目前支持**
包括状态和控制操作覆盖98%以上的外部通信功能。
### 4. 文件传输
> #### **A. 调用方法示例**
```python
from codes import openapi
pd = openapi.PreDos()
downloads = "C:/Users/Administrator/Downloads"
pd.pull_prj_from_server("aio", downloads)
local_prj = downloads + "/aio.zip"
pd.push_prj_to_server(local_prj)
local_file = downloads + "/aio.txt"
server_file = "/tmp/aio.txt"
pd.push_file_to_server(local_file, server_file)
pd.pull_file_from_server(server_file, local_file)
```
> #### **B. 目前支持除了prj_name为工程名其他均为对应系统下的绝对路径**
- push_prj_to_server(prj_file)
- pull_prj_from_server(prj_name, local_prj_path)
- push_file_to_server(local_file, server_file)
- pull_file_from_server(server_file, local_file)
### 5. 初始化以及固件更新
> #### **A. 调用方法示例**
```python
from codes import openapi
ri = openapi.RobotInit()
ri.robot_init()
ri.fw_updater("C:/Users/Administrator/Downloads/FW/v3.1.1.rpa")
ri.fw_updater("C:/Users/Administrator/Downloads/XMS3-R580-W4G3C2_ME_AE_SS_xC.v3.1.0.R0.rpa")
```
> #### **B. 目前支持**
- robot_init() # 初始化机器人新建modbus设备并配置寄存器文件打开IO设备中的modbus设备
- fw_updater(fw_file) # 目前仅测试支持机型文件和控制器
### 6. 升级协议
> #### **A. 调用方法示例**
```python
from codes import openapi
ur = openapi.UpgradeRequest()
ur.version_query()
ur.robot_reboot()
```
> #### **B. 目前支持**
- erase_cfg
- clear_rubbish
- soft_reboot
- version_query
- robot_reboot
- backup_origin
- origin_recovery
### 7. 生成运动轨迹
> #### **A. 调用方法示例**
```python
from codes import openapi
hr = openapi.HmiRequest()
pd = openapi.PreDos() # 如下三行首次运行时,必须打开
ri = openapi.RobotInit(hr, pd) # 非首次重复运行时,可以注释
ri.robot_init() # 注释后可以节省运行时间
md = openapi.ModbusRequest()
pt = openapi.PlotTrajectory(hr, md, prj_name="arc_test_M", tasks=["task0", ], curve_name="hw_cart_pos_feedback_tcp_in_world")
pt.draw_traj()
```
> #### **B. 目前支持**
自动保存3D空间运动曲线图到本地的trajectory.html文件
- hw_cart_pos_feedback_motorside
- hw_cart_pos_feedback_linkside
- hw_cart_pos_feedback_encoder_tcp_in_world
- hw_cart_pos_feedback_flan_in_world
- hw_cart_pos_feedback_tcp_in_world

View File

@@ -1,5 +1,5 @@
{
"ip_addr": "192.168.40.130",
"ip_addr": "192.168.0.160",
"ssh_port": "22",
"socket_port": 5050,
"xService_port": 6666,

Binary file not shown.

View File

@@ -11,13 +11,18 @@ from typing import Callable, Dict
from loguru import logger
from pymodbus.client.tcp import ModbusTcpClient
from paramiko import SSHClient, AutoAddPolicy
import plotly.graph_objects as go
from codes import clibs
class HmiRequest:
def __init__(self):
self.c = self.c_xs = self.t_rx_pkg = self.t_rx_pkg_xs = self.t_s_heart = None
self.c: socket.socket
self.c_xs: socket.socket
self.t_rx_pkg: threading.Thread
self.t_rx_pkg_xs: threading.Thread
self.t_s_heart: threading.Thread
self.data = self.data_xs = b""
self.is_connected = True
self.sock_conn()
@@ -170,8 +175,8 @@ class HmiRequest:
cmd, ts = msg_id.split("@")
t = ts.replace("T", " ")
t_ts = datetime.timestamp(datetime.strptime(t, "%Y-%m-%d %H:%M:%S.%f"))
t_send = datetime.strftime(datetime.fromtimestamp(t_ts-time_delay), '%Y-%m-%d %H:%M:%S.%f')
t_receive = datetime.strftime(datetime.fromtimestamp(t_ts+time_delay), '%Y-%m-%d %H:%M:%S.%f')
t_send = datetime.strftime(datetime.fromtimestamp(t_ts-time_delay), "%Y-%m-%d %H:%M:%S.%f")
t_receive = datetime.strftime(datetime.fromtimestamp(t_ts+time_delay), "%Y-%m-%d %H:%M:%S.%f")
for idx in range(time_delay):
time.sleep(clibs.interval)
try:
@@ -187,7 +192,7 @@ class HmiRequest:
logger.error(f"hr: {time_delay}s内无法找到请求 {msg_id} 的响应!")
# raise Exception()
def exec_cmd(self, cmd, **kwargs):
def exec_cmd(self, cmd: str, **kwargs):
try:
with open(f"{clibs.base_path}/assets/protocols/{cmd}.json", mode="r", encoding="utf-8") as f_cmd:
req = json.load(f_cmd)
@@ -199,15 +204,14 @@ class HmiRequest:
logger.error(f"hr: 暂不支持 {cmd} 功能或确认该功能存在err = {err}")
raise Exception()
match cmd:
case "state.set_tp_mode" | "overview.set_autoload" | "overview.reload" | "rl_task.pp_to_main" | "rl_task.run" | "rl_task.stop" | "rl_task.set_run_params" | "diagnosis.set_params" | "diagnosis.open" | "drag.set_params" | "controller.set_params" | "collision.set_params" | "move.set_quickstop_distance" | "move.set_params" | "move.set_monitor_cfg" | "modbus.get_values" | "modbus.set_params" | "system_io.update_configuration" | "diagnosis.get_params" | "jog.set_params" | "jog.start" | "move.stop" | "move.quick_turn" | "move.set_quickturn_pos" | "soft_limit.set_params" | "fieldbus_device.set_params" | "socket.set_params" | "diagnosis.save" | "register.set_value":
req["data"].update(kwargs)
case "safety.safety_area.signal_enable" | "safety.safety_area.overall_enable" | "safety.safety_area.safety_area_enable" | "safety.safety_area.set_param":
req["c"].update(kwargs)
case "log_code.data.code_list":
req["s"]["log_code.data"]["code_list"] = kwargs["code_list"]
case _:
pass
if cmd in ["state.set_tp_mode", "overview.set_autoload", "overview.reload", "rl_task.pp_to_main", "rl_task.run", "rl_task.stop", "rl_task.set_run_params", "diagnosis.set_params", "diagnosis.open", "drag.set_params", "controller.set_params", "collision.set_params", "move.set_quickstop_distance", "move.set_params", "move.set_monitor_cfg", "modbus.get_values", "modbus.set_params", "system_io.update_configuration", "diagnosis.get_params", "jog.set_params", "jog.start", "move.stop", "move.quick_turn", "move.set_quickturn_pos", "soft_limit.set_params", "fieldbus_device.set_params", "socket.set_params", "diagnosis.save", "register.set_value"]:
req["data"].update(kwargs)
elif cmd in ["safety.safety_area.signal_enable", "safety.safety_area.overall_enable", "safety.safety_area.safety_area_enable", "safety.safety_area.set_param"]:
req["c"].update(kwargs)
elif cmd in ["log_code.data.code_list"]:
req["s"]["log_code.data"]["code_list"] = kwargs["code_list"]
else:
pass
cmd = json.dumps(req, separators=(",", ":"))
if p_type == 0:
@@ -217,12 +221,13 @@ class HmiRequest:
else:
raise Exception("Unexpected CMD Input")
time.sleep(clibs.interval/4)
return req["id"]
class ModbusRequest:
def __init__(self):
self.c = None
self.c: ModbusTcpClient
self.sock_conn()
def sock_conn(self):
@@ -250,10 +255,10 @@ class ModbusRequest:
def r_reset_estop_clear_alarm(self):
self.__reg_high_pulse(40002, "复位外部急停状态,并清除告警信息")
def r_motor_off(self):
def r_motor_on(self):
self.__reg_high_pulse(40004, "上电")
def r_motor_on(self):
def r_motor_off(self):
self.__reg_high_pulse(40005, "下电")
def r_motor_on_off(self, action: bool):
@@ -333,7 +338,7 @@ class ModbusRequest:
def __get_state(self, addr: int, count: int = 1, **kwargs):
res = self.c.read_holding_registers(addr, count=count).registers[0]
logger.info(f"md: {addr}-{kwargs['func']},结果为{res} {kwargs['desc']}")
logger.info(f'md: {addr}-{kwargs["func"]},结果为{res} {kwargs["desc"]}')
return res
@property
@@ -501,7 +506,7 @@ class ModbusRequest:
class EcRequest:
def __init__(self):
self.c = None
self.c: socket.socket
self.exec_desc = " :--: true表示执行成功false表示执行失败"
self.sock_conn()
@@ -783,7 +788,7 @@ class PreDos(object):
logger.error(f"pd: SSH无法连接需检查网络连通性或者登录信息是否正确 -- {e}")
raise Exception()
def push_prj_to_server(self, prj_file):
def push_prj_to_server(self, prj_file: str):
# prj_file工程的本地完整路径
self.__ssh2server()
prj_name, postfix = os.path.basename(prj_file).split(".")
@@ -797,7 +802,7 @@ class PreDos(object):
_ = stderr.read().decode()
self.__ssh.close()
def pull_prj_from_server(self, prj_name, local_prj_path):
def pull_prj_from_server(self, prj_name: str, local_prj_path: str):
# prj_name要拉取的服务端工程名
# local_prj_path存放工程文件的本地路径
self.__ssh2server()
@@ -817,7 +822,7 @@ class PreDos(object):
self.__ssh.close()
def push_file_to_server(self, local_file, server_file):
def push_file_to_server(self, local_file: str, server_file: str):
# local_file本地文件完整路径
# server_file服务端文件完整路径
self.__ssh2server()
@@ -830,7 +835,7 @@ class PreDos(object):
_ = stderr.read().decode()
self.__ssh.close()
def pull_file_from_server(self, server_file, local_file):
def pull_file_from_server(self, server_file: str, local_file: str):
# local_file本地文件完整路径
# server_file服务端文件完整路径
self.__ssh2server()
@@ -851,9 +856,9 @@ class PreDos(object):
class RobotInit(object):
def __init__(self):
self.pd = PreDos()
self.hr = HmiRequest()
def __init__(self, hr: HmiRequest, pd: PreDos):
self.hr: HmiRequest = hr
self.pd: PreDos = pd
def robot_init(self):
# 推送配置文件
@@ -903,7 +908,7 @@ class RobotInit(object):
self.hr.exec_cmd("io_device.load_cfg")
self.hr.exec_cmd("modbus.load_cfg")
def fw_updater(self, fw_file):
def fw_updater(self, fw_file: str):
"""
:param fw_file: xCore升级文件/机型文件的绝对路径
:return: None
@@ -984,7 +989,7 @@ class UpgradeRequest:
http://confluence.i.rokae.com/pages/viewpage.action?pageId=21531754
"""
def __init__(self):
self.c = None
self.c: socket.socket
self.sock_conn()
def sock_conn(self):
@@ -1039,3 +1044,88 @@ class UpgradeRequest:
def origin_recovery(self): # OK 恢复备份,恢复后手动设置,并重启生效
self.sr_pkg({"cmd": "recover"})
class PlotTrajectory:
# hw_cart_pos_feedback_motorside笛卡尔空间下电机侧位置反馈单位m,rad
# hw_cart_pos_feedback_linkside笛卡尔空间下连杆侧位置反馈单位m,rad
# hw_cart_pos_feedback_encoder_tcp_in_world笛卡尔位置反馈关节端编码器数据单位m,rad
# hw_cart_pos_feedback_flan_in_world笛卡尔位置反馈法兰相对于世界坐标系
# hw_cart_pos_feedback_tcp_in_world笛卡尔位置反馈tcp相对于世界坐标系
def __init__(self, hr: HmiRequest, md: ModbusRequest, prj_name: str, tasks: list, curve_name: str = "hw_cart_pos_feedback_tcp_in_world"):
self.hr: HmiRequest = hr
self.md: ModbusRequest = md
self.curve_name: str = curve_name
self.prj_name: str = prj_name
self.tasks: list = tasks
def __change_curve_state(self, stat):
if not stat:
display_pdo_params = [{"name": "count", "channel": 0}]
else:
display_pdo_params = [{"name": self.curve_name, "channel": chl} for chl in range(3)]
self.hr.exec_cmd("diagnosis.set_params", display_pdo_params=display_pdo_params)
self.hr.exec_cmd("diagnosis.open", open=stat, display_open=stat)
def __plot_trajectory(self, start_time, end_time):
x, y, z = [], [], []
try:
clibs.lock.acquire(True)
clibs.cursor.execute(f"SELECT content FROM logs WHERE timestamp BETWEEN '{start_time}' AND '{end_time}' AND content LIKE '%diagnosis.result%' ORDER BY id ASC")
records = clibs.cursor.fetchall()
finally:
clibs.lock.release()
for record in records:
data = eval(record[0])["data"]
for item in data:
item_rm = [x * 1000 for x in reversed(item["value"])]
# item_rm = item["value"]
if item.get("channel", None) == 0 and item.get("name", None) == self.curve_name:
x.extend(item_rm)
elif item.get("channel", None) == 1 and item.get("name", None) == self.curve_name:
y.extend(item_rm)
elif item.get("channel", None) == 2 and item.get("name", None) == self.curve_name:
z.extend(item_rm)
# pyplot
trace_path = go.Scatter3d(x=x, y=y, z=z, mode="lines", line=dict(color="blue", width=4), name="运动轨迹")
trace_start = go.Scatter3d(x=[x[0]], y=[y[0]], z=[z[0]], mode="markers+text", marker=dict(color="green", size=8), text=["起点"], textposition="top center", name="起点")
trace_end = go.Scatter3d(x=[x[-1]], y=[y[-1]], z=[z[-1]], mode="markers+text", marker=dict(color="red", size=8), text=["终点"], textposition="bottom center", name="终点")
fig = go.Figure(data=[trace_path, trace_start, trace_end])
fig.write_html("trajectory.html", auto_open=False)
fig.update_layout(
scene=dict(
xaxis_title="X",
yaxis_title="Y",
zaxis_title="Z"
),
margin=dict(l=0, r=0, b=0, t=30),
legend=dict(x=0, y=1),
dragmode="orbit"
)
fig.show(config={"scrollZoom": True})
def draw_traj(self):
self.md.r_soft_estop(True)
self.md.r_reset_estop_clear_alarm()
self.md.r_switch_auto()
self.md.r_motor_on()
self.__change_curve_state(True)
self.hr.exec_cmd("overview.reload", prj_path=f"{self.prj_name}/_build/{self.prj_name}.prj", tasks=self.tasks)
self.hr.exec_cmd("rl_task.set_run_params", loop_mode=False, override=1.0)
self.hr.exec_cmd("rl_task.pp_to_main", tasks=self.tasks)
self.hr.exec_cmd("rl_task.run", tasks=self.tasks)
t_start = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time()))
time.sleep(clibs.interval)
while self.md.w_sta_robot_is_moving:
time.sleep(clibs.interval * 10)
time.sleep(clibs.interval * 5)
t_end = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time()))
self.__change_curve_state(False)
self.__plot_trajectory(t_start, t_end)

229
rokae/codes/openapi.pyi Normal file
View File

@@ -0,0 +1,229 @@
import socket
import threading
from pymodbus.client.tcp import ModbusTcpClient
class HmiRequest:
is_connected: bool
def __init__(self) -> None: ...
def close(self) -> None: ...
@staticmethod
def get_from_id(msg_id: str) -> str: ...
def exec_cmd(self, cmd: str, **kwargs): ...
class ModbusRequest:
def __init__(self) -> None: ...
def r_clear_alarm(self) -> None: ...
def r_reset_estop(self) -> None: ...
def r_reset_estop_clear_alarm(self) -> None: ...
def r_motor_on(self) -> None: ...
def r_motor_off(self) -> None: ...
def r_motor_on_off(self, action: bool): ...
def r_motoron_pp2main_start(self) -> None: ...
def r_motoron_start(self) -> None: ...
def r_pulse_motoroff(self) -> None: ...
def r_pp2main(self) -> None: ...
def r_program_start(self) -> None: ...
def r_program_stop(self) -> None: ...
def r_program_start_stop(self, action: bool): ...
def r_set_program_speed(self, speed: int): ...
def r_soft_estop(self, action: bool): ...
def r_switch_auto_motoron(self) -> None: ...
def r_switch_auto(self) -> None: ...
def r_switch_manual(self) -> None: ...
def r_switch_auto_manual(self, action: bool): ...
def r_reduced_mode(self, action: bool): ...
def r_switch_safe_region(self, idx: int, enable: bool): ...
@property
def w_alarm_state(self): ...
@property
def w_sta_collision(self): ...
@property
def w_sta_collision_alarm(self): ...
@property
def w_sta_collision_open(self): ...
@property
def w_controller_is_running(self): ...
@property
def w_encoder_low_battery(self): ...
@property
def w_sta_error_code(self): ...
@property
def w_sta_estop(self): ...
@property
def w_sta_heartbeat(self): ...
@property
def w_sta_home(self): ...
@property
def w_sta_motor(self): ...
@property
def w_sta_operation_mode(self): ...
@property
def w_sta_program(self): ...
@property
def w_sta_program_full(self): ...
@property
def w_sta_program_not_run(self): ...
@property
def w_sta_program_reset(self): ...
@property
def w_sta_program_speed(self): ...
@property
def w_sta_reduce_mode(self): ...
@property
def w_sta_robot_is_busy(self): ...
@property
def w_sta_robot_is_moving(self): ...
@property
def w_safe_door_state(self): ...
@property
def w_sta_safe_jnt_pos(self): ...
@property
def w_sta_soft_estop(self): ...
@property
def w_sta_safe_region(self): ...
@property
def w_sta_on_path(self): ...
@property
def w_sta_near_path(self): ...
@property
def w_sta_cart_pose(self) -> list: ...
@property
def w_sta_cart_vel(self) -> list: ...
@property
def w_sta_jnt_pose(self) -> list: ...
@property
def w_sta_jnt_vel(self) -> list: ...
@property
def w_sta_tcp_pose(self) -> list: ...
@property
def w_sta_tcp_vel(self) -> list: ...
@property
def w_sta_tcp_vel_mag(self): ...
def io_write_coils(self, addr, value: bool): ...
def io_read_coils(self): ...
def io_read_discretes(self): ...
class EcRequest:
def __init__(self) -> None: ...
def motor_on(self): ...
def motor_off(self): ...
def pp_to_main(self): ...
def program_start(self): ...
def program_stop(self): ...
def clear_alarm(self): ...
def switch_operation_auto(self): ...
def switch_operation_manual(self): ...
def open_drag_mode(self): ...
def close_drag_mode(self): ...
def get_project_list(self): ...
def get_current_project(self): ...
def load_project(self, project_name): ...
def estop_reset(self): ...
def estopreset_and_clearalarm(self): ...
def motoron_pp2main_start(self): ...
def motoron_start(self): ...
def pause_motoroff(self): ...
def set_program_speed(self, speed: int = 20): ...
def set_soft_estop(self, enable: str): ...
def switch_auto_motoron(self): ...
def open_safe_region(self, number: int): ...
def close_safe_region(self, number: int): ...
def open_reduced_mode(self): ...
def close_reduced_mode(self): ...
def setdo_value(self, do_name: str, do_value: str): ...
def set_robot_time(self, robot_time: str = ...): ...
@property
def motor_on_state(self): ...
@property
def robot_running_state(self): ...
@property
def estop_state(self): ...
@property
def operation_mode(self): ...
@property
def home_state(self): ...
@property
def fault_state(self): ...
@property
def collision_state(self): ...
@property
def task_state(self): ...
@property
def get_cart_pos(self): ...
@property
def get_joint_pos(self): ...
@property
def get_joint_vel(self): ...
@property
def get_joint_trq(self): ...
@property
def reduced_mode_state(self): ...
def get_io_state(self, io_list: str): ...
@property
def alarm_state(self): ...
@property
def collision_alarm_state(self): ...
@property
def collision_open_state(self): ...
@property
def controller_is_running(self): ...
@property
def encoder_low_battery_state(self): ...
@property
def robot_error_code(self): ...
@property
def rl_pause_state(self): ...
@property
def program_reset_state(self): ...
@property
def program_speed_value(self): ...
@property
def robot_is_busy(self): ...
@property
def robot_is_moving(self): ...
@property
def safe_door_state(self): ...
@property
def soft_estop_state(self): ...
@property
def get_cart_vel(self): ...
@property
def get_tcp_pos(self): ...
@property
def get_tcp_vel(self): ...
@property
def get_tcp_vel_mag(self): ...
@property
def ext_estop_state(self): ...
@property
def hand_estop_state(self): ...
@property
def sta_on_path(self): ...
@property
def sta_near_path(self): ...
class PreDos:
def __init__(self) -> None: ...
def push_prj_to_server(self, prj_file: str): ...
def pull_prj_from_server(self, prj_name: str, local_prj_path: str): ...
def push_file_to_server(self, local_file: str, server_file: str): ...
def pull_file_from_server(self, server_file: str, local_file: str): ...
class RobotInit:
def __init__(self, hr: HmiRequest, pd: PreDos) -> None: ...
def robot_init(self) -> None: ...
def fw_updater(self, fw_file: str): ...
class UpgradeRequest:
def __init__(self) -> None: ...
def erase_cfg(self) -> None: ...
def clear_rubbish(self) -> None: ...
def soft_reboot(self) -> None: ...
def version_query(self) -> None: ...
def robot_reboot(self) -> None: ...
def backup_origin(self) -> None: ...
def origin_recovery(self) -> None: ...
class PlotTrajectory:
def __init__(self, hr: HmiRequest, md: ModbusRequest, prj_name: str, tasks: list, curve_name: str = 'hw_cart_pos_feedback_tcp_in_world') -> None: ...
def draw_traj(self) -> None: ...

4
rokae/requirements.txt Normal file
View File

@@ -0,0 +1,4 @@
loguru==0.7.3
paramiko==3.5.1
plotly==6.3.0
pymodbus==3.9.2

Binary file not shown.