测试了密胺的整个流程、add(点位设置界面增加了 停止功能按钮)、fix bug(修复了点位名称写入数据库不一致的错误)

This commit is contained in:
2025-09-28 17:55:32 +08:00
parent 80272b92b4
commit faea225e24
16 changed files with 10403 additions and 154 deletions

View File

@ -1,3 +1,3 @@
# fluent_widgets_pyside6
机器臂控制系统的 UI界面
法奥机器臂控制系统的 UI界面 以及密胺制品的相关控制代码

View File

@ -1,8 +1,14 @@
from ...view.mi_an_main_window import Window as MainWindow
from ...view.cood_forms_interface import CoordinateFormsWidget
from ...view.cood_forms_interface import CoordinateTableWidget
from typing import TypedDict
from ...service.robot_service import RobotService
# 法奥机器人
from fairino import Robot
# 子界面管理
class SubViewsDict(TypedDict):
@ -20,6 +26,11 @@ class MainController:
# 初始化子控制器
self._initSubControllers()
# 机器人实例
self.robot_client = Robot.RPC("192.168.58.2")
self.robot_service = RobotService(self.robot_client)
self.__connectSignals()
def _initSubViews(self):
@ -46,8 +57,60 @@ class MainController:
self.main_window.show()
def __connectSignals(self):
pass
self.sub_views["position"].form_move_signal.connect(self.handleMove)
self.sub_views["position"].form_update_signal.connect(self.handleGetPosition)
self.sub_views["position"].form_machine_stop_signal.connect(self.handleMachineStop)
# 机器臂停止移动
def handleMachineStop(self):
self.robot_service.stop_moves()
# 设置点位
def handleGetPosition(self, form_obj: CoordinateTableWidget):
result = self.robot_client.GetActualTCPPose() # 获取 工具坐标
if isinstance(result, tuple) and len(result) == 2:
# 成功:解包错误码和位姿
error, tcp_pos = result
# print(f"成功,位姿:{tcp_pos}")
# 保留三位小数
tcp_pos_rounded = [round(pos, 3) for pos in tcp_pos]
# print(f"保留三位小数后:{tcp_pos_rounded}")
else:
# 失败result 即为错误码
print(f"设置位置失败,错误码:{result}")
return
form_obj.update_table_data(tcp_pos_rounded)
# 机器臂移动
# pos_list 中一定是合法坐标
def handleMove(self, pos_list: list, name_list: list):
# print("handleMove 移动 pos_list: ", pos_list)
# print("handleMove 移动 pos_list: ", name_list)
# 后续需要根据状态来设置
tool_id = 1 # 当前 1为吸盘
vel = 20.0
# self.robot_service.start_moves(pos_list, name_list, tool_id=tool_id, vel=vel)
try:
self.robot_service.start_moves(
pos_list=pos_list,
name_list=name_list,
tool_id=tool_id,
vel=vel
)
except ValueError as e:
# 处理“坐标列表与名称列表长度不一致”的错误
print(f"❌ 移动任务提交失败(参数错误):{str(e)}")
# for desc_pos in pos_list:
# print("handleMove 移动 desc_pos: ", pos_list)
# error_code = self.robot_client.MoveCart(desc_pos=desc_pos, tool=tool_id, user=0,vel=vel)
# if error_code == 0:
# print("运动成功")
# else:
# print(f"运动失败,错误码: {error_code}")
def handleMove(self, pos_list: list):
print("handleMove 移动:", pos_list)

87
app/service/EMV.py Normal file
View File

@ -0,0 +1,87 @@
import socket
import binascii
import time
# 网络继电器的 IP 和端口
HOST = "192.168.58.18"
PORT = 50000
# 电磁阀控制报文
valve_commands = {
1: {
"open": "00000000000601050000FF00",
"close": "000000000006010500000000",
},
2: {
"open": "00000000000601050001FF00",
"close": "000000000006010500010000",
},
3: {
"open": "00000000000601050002FF00",
"close": "000000000006010500020000",
},
}
# 将十六进制字符串转换为字节数据并发送
def send_command(command):
byte_data = binascii.unhexlify(command)
# 创建套接字并连接到继电器
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
try:
sock.connect((HOST, PORT))
sock.send(byte_data)
# 接收响应
response = sock.recv(1024)
# print(f"收到响应: {binascii.hexlify(response)}")
# 校验响应
if response == byte_data:
# print("命令成功下发,继电器已执行操作。")
return True
else:
print("命令下发失败,响应与请求不符。")
return False
except Exception as e:
print(f"通信错误: {e}")
return False
# 控制电磁阀打开
def open(grasp, shake, throw):
if grasp:
# print("打开电磁阀 1")
if send_command(valve_commands[1]["open"]):
time.sleep(1)
if shake:
# print("打开电磁阀 2")
if send_command(valve_commands[2]["open"]):
time.sleep(0.05)
if throw:
print("打开电磁阀 3")
if send_command(valve_commands[3]["open"]):
time.sleep(0.5)
# 控制电磁阀关闭
def close(grasp, shake, throw):
if grasp:
# print("关闭电磁阀 1")
if send_command(valve_commands[1]["close"]):
time.sleep(1)
if shake:
# print("关闭电磁阀 2")
if send_command(valve_commands[2]["close"]):
time.sleep(0.05)
if throw:
# print("关闭电磁阀 3")
if send_command(valve_commands[3]["close"]):
time.sleep(0.5)
# 关闭电磁阀
# open(False, False, True) # 参数传True和False
# close(True,False,True)
# for i in range(10):
# open(False,True,True)
# close(True,True,True)

58
app/service/catch.py Normal file
View File

@ -0,0 +1,58 @@
#!/usr/bin/python3
import time
from enum import Enum
# import Constant
# from COM.COM_Robot import RobotClient
# from Util.util_time import CClockPulse, CTon
from .EMV import *
class CatchStatus(Enum):
CNone = 0
CTake = 1 # 抓取
CRelease = 2 # 放开
CDrop = 3
CShake = 4
COk = 5
class CatchTool:
def __init__(self):
self.catch_status = CatchStatus.CNone
# =========================================
# 密胺餐盘
# 电磁阀1 需要在 吸盘吸的时候 关闭
# 电磁阀1 需要在 夹爪工作的时候打开
# 电磁阀2 开 -> 吸盘吸
# 电磁阀2 关 -> 吸盘放开
# 电磁阀3 开-> 打开夹爪
# 电磁阀3 关 -> 闭合夹爪
# =======================================
# 夹爪 抓取
def gripper_grasp(self):
close(0, 0, 1) # 电磁阀3关
open(1, 0, 0) # 电磁阀1开
time.sleep(1) # 间隔1秒
# 夹爪 放开
def gripper_release(self):
open(1, 0, 1) # 电磁阀1开电磁阀3开
time.sleep(1) # 间隔1秒
# 吸盘 吸取
def suction_pick(self):
close(1, 0, 0) # 电磁阀1关
open(0, 1, 0) # 电磁阀2开
time.sleep(1) # 间隔1秒
# 吸盘 释放(松开)
def suction_release(self):
close(0, 1, 0) # 电磁阀2关
time.sleep(1) # 间隔1秒
# catch_tool = CatchTool()
# catch_tool.gripper_grasp()

146
app/service/drop.py Normal file
View File

@ -0,0 +1,146 @@
import time
# 倒料函数
# 工具坐标tool 修改为 夹具
# 倒料时的旋转角度rot_angle 默认是 180度可以是负的, 注意限位问题
def start_drop(robot_client, rot_angle=180, tool=1, user=0, drop_vel=100):
# 1、让机器臂第六轴旋转180° (倒料)
# 获取倒料点 关节坐标,准备倒料
result = robot_client.GetActualJointPosDegree()
# result = robot.GetActualJointPosRadian()
if isinstance(result, tuple) and len(result) == 2:
# 成功:错误码和关节坐标(度数)
_, joint_pos = result
print(f"倒料点关节坐标:{joint_pos}")
else:
# 失败result 为错误码
print(f"start_drop错误: 获取倒料点关节坐标失败,错误码:{result}")
return False
# 修改第六轴关节坐标角度,准备旋转
new_joint_pos = joint_pos.copy()
# 修改第六轴角度, 加上旋转角度
new_joint_pos[-1] = new_joint_pos[-1] + rot_angle
# print("旋转前关节坐标:", joint_pos)
# print("确认旋转后关节坐标:", new_joint_pos)
# 旋转第六轴,倒料
errno = robot_client.MoveJ(
new_joint_pos, tool=tool, user=user, vel=drop_vel
)
if errno == 0:
print(f"执行坐标: {new_joint_pos} 成功")
print("倒料成功!!!!!")
else:
print(f"start_drop错误: 旋转第六轴失败,错误码: {errno}")
return False
# 确认倒料完成
time.sleep(2)
# 这里需要抖动?
# 第六轴复位
errno = robot_client.MoveJ(joint_pos, tool=tool, user=user, vel=drop_vel)
if errno == 0:
print(f"执行坐标: {joint_pos} 成功")
print("第六轴复位成功!!!!!")
else:
print(f"start_drop错误: 第六轴复位失败,错误码: {errno}")
return False
return True # 倒料成功,包括 旋转 和 复原
# 倒料旋转函数(只旋转不复原)
# 工具坐标tool 修改为 夹具
# 倒料时的旋转角度rot_angle 默认是 180度可以是负的, 注意限位问题
def start_drop_rotate(robot_client, rot_angle=180, tool=1, user=0, drop_vel=100, global_vel = 40):
# 0. 设置全局速度为 100%, 达到最快的倒料旋转速度
robot_client.SetSpeed(100)
# 1、让机器臂第六轴旋转180° (倒料)
# 获取倒料点 关节坐标,准备倒料
result = robot_client.GetActualJointPosDegree()
# result = robot.GetActualJointPosRadian()
if isinstance(result, tuple) and len(result) == 2:
# 成功:错误码和关节坐标(度数)
_, joint_pos = result
print(f"倒料旋转点关节坐标:{joint_pos}")
else:
# 失败result 为错误码
print(f"start_drop_rotate错误: 获取倒料点关节坐标失败,错误码:{result}")
return False
# 修改第六轴关节坐标角度,准备旋转
new_joint_pos = joint_pos.copy()
# 修改第六轴角度, 加上旋转角度
new_joint_pos[-1] = new_joint_pos[-1] + rot_angle
# print("旋转前关节坐标:", joint_pos)
# print("确认旋转后关节坐标:", new_joint_pos)
# 旋转第六轴,倒料
errno = robot_client.MoveJ(
new_joint_pos, tool=tool, user=user, vel=drop_vel
)
# 2. 设置回原来的 全局速度
robot_client.SetSpeed(global_vel)
# 旋转倒料状态
if errno == 0:
print(f"执行坐标: {new_joint_pos} 成功")
print("倒料旋转成功!!!!!")
else:
print(f"start_drop_rotate错误: 旋转第六轴失败,错误码: {errno}")
return False
# 确认倒料完成
time.sleep(1)
# 这里需要抖动?
return True # 倒料旋转成功
# 倒料复原函数(复原, 第六轴旋转回去)
# 工具坐标tool 修改为 夹具
# 复原的旋转角度rot_angle 默认是 180度可以是负的, 注意限位问题
def start_drop_reset(robot_client, rot_angle=180, tool=1, user=0, drop_vel=100, global_vel = 40):
# 0. 设置全局速度为 100%, 达到最快的倒料旋转速度
robot_client.SetSpeed(100)
# 1、让机器臂第六轴旋转180° (复原)
# 获取复原点 关节坐标,准备复原
result = robot_client.GetActualJointPosDegree()
# result = robot.GetActualJointPosRadian()
if isinstance(result, tuple) and len(result) == 2:
# 成功:错误码和关节坐标(度数)
_, joint_pos = result
print(f"倒料复原点关节坐标:{joint_pos}")
else:
# 失败result 为错误码
print(f"start_drop_reset错误: 获取倒料点关节坐标失败,错误码:{result}")
return False
# 修改第六轴关节坐标角度,准备旋转
new_joint_pos = joint_pos.copy()
# 修改第六轴角度, 减去旋转角度复原
new_joint_pos[-1] = new_joint_pos[-1] - rot_angle
# print("旋转前关节坐标:", joint_pos)
# print("确认旋转后关节坐标:", new_joint_pos)
# 旋转第六轴,复原
errno = robot_client.MoveJ(
new_joint_pos, tool=tool, user=user, vel=drop_vel
)
# 2. 设置回原来的 全局速度
robot_client.SetSpeed(global_vel)
if errno == 0:
print(f"执行坐标: {new_joint_pos} 成功")
print("倒料复原成功!!!!!")
else:
print(f"start_drop_reset错误: 复原第六轴失败,错误码: {errno}")
return False
return True # 倒料复原成功

198
app/service/high_machine.py Normal file
View File

@ -0,0 +1,198 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
'''
# @Time : 2025/6/19 11:20
# @Author : reenrr
# @File : EMV-test1.py
# 功能描述 :通过网络控制继电器模块,实现对高周波开启和停止按钮的操作
'''
import socket
import binascii
import time
# 网络继电器的 IP 和端口
# 高周波 19
HOST = '192.168.58.19'
PORT = 50000
# 开启按钮
START_BUTTON = 'start_button'
# 紧急停止按钮
STOP_BUTTON = 'stop_button'
'''
控件控制报文,存储各按钮的开关命令
'''
valve_commands = {
START_BUTTON: {
'open': '00000000000601050000FF00',
'close': '000000000006010500000000',
},
STOP_BUTTON: {
'open': '00000000000601050001FF00',
'close': '000000000006010500010000',
},
}
'''
读取状态命令,获取设备当前状态的指令
'''
read_status_command = {
'button':'000000000006010100000008',
}
# 控件对应 DO 位(从低到高)
button_bit_map = {
START_BUTTON: 0,
STOP_BUTTON: 1,
}
# 控件名称映射表
button_name_map = {
START_BUTTON: "开启按钮",
STOP_BUTTON: "紧急停止按钮",
}
def send_command(command):
'''
发送控制命令到网络继电器,
参数:
command: 十六进制字符串,控制指令
返回:
设备响应数据(字节类型),失败返回 False
'''
# 将十六进制字符串转换为字节数据并发送
byte_data = binascii.unhexlify(command)
# 创建套接字并连接到继电器
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
try:
sock.connect((HOST, PORT))
sock.send(byte_data)
# 接收响应
response = sock.recv(1024)
print(f"收到响应: {binascii.hexlify(response)}")
# 校验响应
return response
except Exception as e:
print(f"通信错误: {e}")
return False
def get_all_button_status(command_type='button'):
'''
获取所有按钮的当前状态
参数:
command_type: 读取类型,默认为 'button'
返回:
字典,键为按钮名称,值为 True/False失败返回空字典
'''
# 获取对应的读取命令
command = read_status_command.get(command_type)
if not command:
print(f"未知的读取类型: {command_type}")
return {}
response = send_command(command)
status_dict = {}
# 校验响应是否有效至少10字节
if response and len(response) >= 10:
# 状态信息存储在响应的第10个字节索引9
status_byte = response[9] # 状态在第10字节
# 将字节转换为8位二进制字符串并反转使低位在前
status_bin = f"{status_byte:08b}"[::-1]
if command_type == 'button':
bit_map = button_bit_map
name_map = button_name_map
else:
print("不支持的映射类型")
return{}
# 解析每个按钮的状态
for key, bit_index in bit_map.items():
# 检查对应位是否为11表示开启0表示关闭
state = status_bin[bit_index] == '1'
status_dict[key] = state
else:
print("读取状态失败或响应无效")
return status_dict
def get_button_status(button_name, command_type='button'):
'''
获取单个控件的当前状态
参数:
device_name: 控件名称,如 RESIN_MOLDING_BUTTON、FINSHING_AGENT_BUTTON、STOP_BUTTON
command_type: 读取类型,默认为 'button'
返回:
True: 开启, False: 关闭, None: 无法读取
'''
status = get_all_button_status(command_type)
return status.get(button_name, None)
def open(start_button=False, stop_button=False):
'''
打开指定的按钮(仅在按钮当前处于关闭状态时才执行)
参数:
resin_molding_button: 素面操作按钮,默认为 False
finishing_agent_button: 烫金操作按钮,默认为 False
stop_button: 紧急停止按钮,默认为 False
'''
status = get_all_button_status()
if start_button and not status.get(START_BUTTON, False):
print("打开开启按钮")
send_command(valve_commands[START_BUTTON]['open'])
time.sleep(0.05)
if stop_button and not status.get(STOP_BUTTON, False):
print("打开紧急停止按钮")
send_command(valve_commands[STOP_BUTTON]['open'])
time.sleep(0.05)
def close(start_button=False, stop_button=False):
'''
关闭指定的按钮(仅在按钮当前处于开启状态时才执行)
参数:
resin_molding_button: 素面操作按钮,默认为 False
finishing_agent_button: 烫金操作按钮,默认为 False
stop_button: 紧急停止按钮,默认为 False
'''
status = get_all_button_status()
if start_button and status.get(START_BUTTON, True):
print("关闭开启按钮")
send_command(valve_commands[START_BUTTON]['close'])
time.sleep(0.05)
if stop_button and status.get(STOP_BUTTON, True):
print("关闭紧急停止按钮")
send_command(valve_commands[STOP_BUTTON]['close'])
time.sleep(0.05)
def start_high(h_time = 80):
# 操作按钮需要先打开后立即关闭
open(start_button=True)
time.sleep(0.5)
close(start_button=True)
print(f"等待高周波完成,等待时间为{h_time}秒......")
time.sleep(h_time)
print("高周波已经完成...")
if __name__ == '__main__':
# 操作按钮需要先打开后立即关闭
# open(start_button=True)
# time.sleep(0.5)
# close(start_button=True)
start_high()

View File

@ -0,0 +1,213 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
'''
# @Time : 2025/6/19 11:20
# @Author : reenrr
# @File : EMV-test1.py
# 功能描述 :通过网络控制继电器模块,实现对冲压机素面操作按钮、烫金操作按钮和紧急停止按钮的远程控制
'''
import socket
import binascii
import time
# 网络继电器的 IP 和端口
# 冲压机20
HOST = '192.168.58.20'
PORT = 50000
# 素面操作按钮(冲压机)或高压启动按钮(高周波)
RESIN_MOLDING_BUTTON ='resin_molding_button'
# 烫金操作按钮
FINSHING_AGENT_BUTTON = 'finishing_agent_button'
# 紧急停止按钮
STOP_BUTTON = 'stop_button'
'''
控件控制报文,存储各按钮的开关命令
'''
valve_commands = {
RESIN_MOLDING_BUTTON: {
'open': '00000000000601050000FF00',
'close': '000000000006010500000000',
},
FINSHING_AGENT_BUTTON: {
'open': '00000000000601050001FF00',
'close': '000000000006010500010000',
},
STOP_BUTTON: {
'open': '00000000000601050002FF00',
'close': '000000000006010500020000',
}
}
'''
读取状态命令,获取设备当前状态的指令
'''
read_status_command = {
'button':'000000000006010100000008',
}
# 控件对应 DO 位(从低到高)
button_bit_map = {
RESIN_MOLDING_BUTTON: 0,
FINSHING_AGENT_BUTTON: 1,
STOP_BUTTON: 2,
}
# 控件名称映射表
button_name_map = {
RESIN_MOLDING_BUTTON: "素面操作按钮",
FINSHING_AGENT_BUTTON: "烫金操作按钮",
STOP_BUTTON: "紧急停止按钮",
}
def send_command(command):
'''
发送控制命令到网络继电器,
参数:
command: 十六进制字符串,控制指令
返回:
设备响应数据(字节类型),失败返回 False
'''
# 将十六进制字符串转换为字节数据并发送
byte_data = binascii.unhexlify(command)
# 创建套接字并连接到继电器
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
try:
sock.connect((HOST, PORT))
sock.send(byte_data)
# 接收响应
response = sock.recv(1024)
print(f"收到响应: {binascii.hexlify(response)}")
# 校验响应
return response
except Exception as e:
print(f"通信错误: {e}")
return False
def get_all_button_status(command_type='button'):
'''
获取所有按钮的当前状态
参数:
command_type: 读取类型,默认为 'button'
返回:
字典,键为按钮名称,值为 True/False失败返回空字典
'''
# 获取对应的读取命令
command = read_status_command.get(command_type)
if not command:
print(f"未知的读取类型: {command_type}")
return {}
response = send_command(command)
status_dict = {}
# 校验响应是否有效至少10字节
if response and len(response) >= 10:
# 状态信息存储在响应的第10个字节索引9
status_byte = response[9] # 状态在第10字节
# 将字节转换为8位二进制字符串并反转使低位在前
status_bin = f"{status_byte:08b}"[::-1]
if command_type == 'button':
bit_map = button_bit_map
name_map = button_name_map
else:
print("不支持的映射类型")
return{}
# 解析每个按钮的状态
for key, bit_index in bit_map.items():
# 检查对应位是否为11表示开启0表示关闭
state = status_bin[bit_index] == '1'
status_dict[key] = state
else:
print("读取状态失败或响应无效")
return status_dict
def get_button_status(button_name, command_type='button'):
'''
获取单个控件的当前状态
参数:
device_name: 控件名称,如 RESIN_MOLDING_BUTTON、FINSHING_AGENT_BUTTON、STOP_BUTTON
command_type: 读取类型,默认为 'button'
返回:
True: 开启, False: 关闭, None: 无法读取
'''
status = get_all_button_status(command_type)
return status.get(button_name, None)
def open(resin_molding_button=False, finishing_agent_button=False, stop_button=False):
'''
打开指定的按钮(仅在按钮当前处于关闭状态时才执行)
参数:
resin_molding_button: 素面操作按钮,默认为 False
finishing_agent_button: 烫金操作按钮,默认为 False
stop_button: 紧急停止按钮,默认为 False
'''
status = get_all_button_status()
if resin_molding_button and not status.get(RESIN_MOLDING_BUTTON, False):
print("打开素面操作按钮")
send_command(valve_commands[RESIN_MOLDING_BUTTON]['open'])
time.sleep(0.05)
if finishing_agent_button and not status.get(FINSHING_AGENT_BUTTON, False):
print("打开烫金操作按钮")
send_command(valve_commands[FINSHING_AGENT_BUTTON]['open'])
time.sleep(0.05)
if stop_button and not status.get(STOP_BUTTON, False):
print("打开紧急操作按钮")
send_command(valve_commands[STOP_BUTTON]['open'])
time.sleep(0.05)
def close(resin_molding_button=False, finishing_agent_button=False, stop_button=False):
'''
关闭指定的按钮(仅在按钮当前处于开启状态时才执行)
参数:
resin_molding_button: 素面操作按钮,默认为 False
finishing_agent_button: 烫金操作按钮,默认为 False
stop_button: 紧急停止按钮,默认为 False
'''
status = get_all_button_status()
if resin_molding_button and status.get(RESIN_MOLDING_BUTTON, True):
print("关闭素面操作按钮")
send_command(valve_commands[RESIN_MOLDING_BUTTON]['close'])
time.sleep(0.05)
if finishing_agent_button and status.get(FINSHING_AGENT_BUTTON, True):
print("关闭烫金操作按钮")
send_command(valve_commands[FINSHING_AGENT_BUTTON]['close'])
time.sleep(0.05)
if stop_button and status.get(STOP_BUTTON, True):
print("关闭紧急操作按钮")
send_command(valve_commands[STOP_BUTTON]['close'])
time.sleep(0.05)
def start_press():
# 操作按钮需要先打开后立即关闭
open(resin_molding_button=True)
time.sleep(0.5)
close(resin_molding_button=True)
# if __name__ == '__main__':
# # 操作按钮需要先打开后立即关闭
# open(resin_molding_button=True)
# time.sleep(0.5)
# close(resin_molding_button=True)

View File

@ -0,0 +1,394 @@
import threading
from typing import List
# # 法奥机器人
from fairino import Robot
# 夹具和吸盘控制
from .catch import CatchTool
# 震动函数
from .vibrate import start_vibrate
# 称重函数
from .weight import start_weight
# 倒料函数
from .drop import start_drop, start_drop_rotate, start_drop_reset
# 高周波
from .high_machine import start_high
# 冲压机
from .press_machine import start_press
import time
class RobotService:
def __init__(self, robot_client: Robot.RPC):
self.robot_client = robot_client # 机器臂实例
self.move_thread = None # 移动任务线程对象
self.moving_running = False # 移动线程运行状态标记
# 跟踪线程相关
# 跟踪线程的机器臂调用
self.robot_tracking_client = Robot.RPC(robot_client.ip_address)
self.point_sequence = [] # 点位序列元数据 保存点位状态status和索引index
self.current_target_index = -1 # 当前正在朝向(移动)的点位索引(初始为-1
self.completed_indices = set() # 已运动过的点位索引集合
self.tracking_lock = threading.Lock()
# 末端工具对象
self.catch_tool = CatchTool()
def start_moves(
self,
pos_list: List[list],
name_list: List[str],
tool_id: int = 1,
vel: float = 5.0,
):
"""启动移动任务(对外接口)"""
# 校验参数:坐标列表和名称列表长度必须一致
if len(pos_list) != len(name_list):
raise ValueError("pos_list与name_list长度不一致")
# 如果已有移动任务线程在运行,先停止
if self.move_thread and self.move_thread.is_alive():
self.stop_moves()
# 保存任务参数
self.pos_list = pos_list
self.name_list = name_list
self.tool_id = tool_id
self.vel = vel
self.moving_running = True
# 初始化点位序列
self.init_point_sequence()
# 启动移动点位跟踪
self.start_tracking()
# 启动移动任务线程, 执行移动任务
self.move_thread = threading.Thread(target=self._process_moves)
self.move_thread.daemon = True
self.move_thread.start()
def stop_moves(self):
"""停止移动任务"""
self.moving_running = False
if self.stop_motion() == 0:
print("stop_moves: 机器臂停止移动成功...")
# 打印 停止时,正在朝哪个点移动
point_dict = self.get_current_target_point()
if point_dict:
print("停止前, 正在朝向移动的目标点位为:")
print(
f"所选中的所有点中的第{point_dict['index']+1} 个点",
"pos:",
point_dict["pos"],
)
else:
print("所有的点位已经移动完成, 无朝向移动的目标点!!!!")
# 停止跟踪
self.stop_tracking()
def stop_motion(self):
error = self.robot_client.send_message("/f/bIII4III102III4IIISTOPIII/b/f")
return error
def _process_moves(self):
"""线程内执行的移动任务逻辑"""
for i in range(len(self.pos_list)):
if not self.moving_running:
break # 若已触发停止,则退出循环
current_pos = self.pos_list[i]
current_name = self.name_list[i]
print(f"坐标名: {current_name},坐标:{current_pos}")
if current_name == "normal":
# 中间的普通点加入平滑时间
error_code = self.robot_client.MoveCart(
desc_pos=current_pos,
tool=self.tool_id,
user=0,
vel=self.vel,
blendT=200, # 平滑, 200毫秒
)
# 加了平滑会出现一个问题,那就是该函数不会阻塞,就直接返回了
else:
# 执行机器臂移动
error_code = self.robot_client.MoveCart(
desc_pos=current_pos, tool=self.tool_id, user=0, vel=self.vel
)
# 根据移动结果处理
# 必须要根据函数返回来决定
if self.moving_running and error_code == 0:
print(f" {current_name} 移动成功 或者 移动指令发送成功!!! ")
# 根据点位名称调用对应函数(核心判断逻辑)
# 功能点会调用相应的函数来进行相应的操作
# 注意: 功能点不能设置为 平滑点!!!!! 否则不能根据函数返回判断是否已经运动到该点位
self._call_action_by_name(current_name) # 可以优化
else:
# 1、该点位移动失败....
# 2、机器臂停止运动还会进入到这里....
print(
f"移动到 {current_name} 失败,错误码:{error_code}, 运行状态: {self.moving_running}"
)
# 可选:是否继续执行下一个点?
self.moving_running = False
# 所有移动任务完成
self.moving_running = False
print("================所有点位任务已完成===============")
def _call_action_by_name(self, point_name: str):
"""根据点位名称调用对应动作(核心判断逻辑)"""
# 1. 夹取相关点位
if point_name in ["抓取点", "夹取点"]:
self.catch_tool.gripper_grasp()
# 2. 放开相关点位
elif point_name in ["放开点"]:
self.catch_tool.gripper_release()
# 3. 称重相关点位
elif point_name in ["称重点"]:
start_weight()
# 4. 震频点相关点位
elif point_name in ["震频点", "震动点"]:
start_vibrate()
# 5. 倒料相关点位
elif point_name in ["倒料点"]:
start_drop(self.robot_client)
# start_high
elif point_name in ["高周波点"]:
start_high()
# start_press
elif point_name in ["冲压点", "冲压机点"]:
start_press()
elif point_name in ["冲压机等待点"]:
# 冲压机花费60秒这里等待一段时间
print("等待冲压机冲压完成, 等待时间为40秒......")
# 因为高周波点等待了80秒所以这里只需要等待40秒一共120秒
# 这里会一起计算高周波等待点的80秒
time.sleep(40)
print("冲压已经完成......")
# start_drop_rotate
# 旋转180°倒料
elif point_name in ["倒料旋转点", "旋转点"]:
start_drop_rotate(self.robot_client)
# 旋转复原点
elif point_name in ["倒料复原点", "旋转复原点"]:
start_drop_reset(self.robot_client)
# 吸取点
# 吸盘吸取
elif point_name in ["吸取点"]:
self.catch_tool.suction_pick()
# 释放点
# 吸盘释放
elif point_name in ["释放点"]:
self.catch_tool.suction_release()
elif point_name in ["冲压机等待点2"]:
# 冲压机花费90秒这里等待一段时间
# 这里是专门等待冲压机的....,不会一起计算高周波等待点的时间...
print("等待冲压机冲压完成, 等待时间为90秒......")
time.sleep(90)
print("冲压已经完成......")
# 4. 可扩展其他功能点位类型
# elif point_name in ["XXX点"]:
# self._xxx_action()
# else:
# print(f"点位 {point_name} 无对应动作,不执行额外操作")
def init_point_sequence(self):
"""初始化点位序列,添加元数据"""
self.point_sequence.clear() # 清空
for idx, point in enumerate(self.pos_list):
self.point_sequence.append(
{
"index": idx,
"pos": point, # 坐标 [x,y,z,rx,ry,rz]
"status": "pending", # 状态pending(未发送)/sent(已发送)/completed(已经过)/current(当前目标)
}
)
self.current_target_index = (
0 if self.point_sequence else -1
) # 初始目标为第一个点
def get_robot_status(self):
"""获取机器人实时状态:是否在运动 + 当前位置"""
try:
with self.tracking_lock:
# 1. 判断是否正在运动
is_moving = self.is_moving()
# 2. 获取当前TCP位置
# [x,y,z,rx,ry,rz]
result = self.robot_tracking_client.GetActualTCPPose()
# 解析返回值
if isinstance(result, tuple) and len(result) >= 2 and result[0] == 0:
# 成功:提取坐标列表(第二个元素)
current_pos = result[1]
# 校验坐标格式6个数字
if not (
isinstance(current_pos, list)
and len(current_pos) == 6
and all(isinstance(p, (int, float)) for p in current_pos)
):
print(f"坐标格式错误:{current_pos}, 需为6个数字的列表")
return None
else:
# 失败:打印错误码
error_code = result[0] if isinstance(result, tuple) else result
print(f"get_robot_status: 获取TCP位置失败, 错误码:{error_code}")
return None
return {
"is_moving": is_moving,
"current_pos": current_pos,
"timestamp": time.time(),
}
except Exception as e:
print(f"get_robot_status: 获取状态失败:{e}")
return None
def is_moving(self) -> bool:
"""
判断机器臂是否正在运动
返回:
bool: True表示正在运动robot_state=2False表示停止/暂停/拖动/异常
"""
# 1. 检查状态包是否已初始化
if self.robot_client.robot_state_pkg is None:
print("is_moving: 警告:机器人状态包未初始化,无法判断运动状态")
return False
try:
# 2. 从状态包中获取robot_state取值1-4
time.sleep(0.05) # 等待状态刷新
robot_state = self.robot_client.robot_state_pkg.robot_state
# 3. 校验robot_state是否在合法范围内
if robot_state not in (1, 2, 3, 4):
print(
f"is_moving: 警告: 无效的robot_state值 {robot_state},按'停止'处理"
)
return False
# 4. 仅robot_state=2表示正在运动
return robot_state == 2
except Exception as e:
print(f"is_moving: 获取运动状态失败:{str(e)}")
return False
def update_point_status(self):
"""更新点位状态:哪些已完成,当前移动的目标点位是哪个"""
status = self.get_robot_status()
if not status:
return
current_pos = status["current_pos"]
is_moving = status["is_moving"]
threshold = 6.0 # 判定“经过该点”的距离阈值mm根据精度调整
# 遍历所有点位,判断是否已经过
for point in self.point_sequence:
idx = point["index"]
# 跳过已标记为“已完成”的点
if point["status"] == "completed":
continue
# 计算当前位置与点位的三维距离
dx = current_pos[0] - point["pos"][0]
dy = current_pos[1] - point["pos"][1]
dz = current_pos[2] - point["pos"][2]
distance = (dx**2 + dy**2 + dz**2) ** 0.5
# 如果距离小于阈值,标记为“已完成”
if distance < threshold:
self.completed_indices.add(idx)
point["status"] = "completed"
max_completed = (
max(self.completed_indices) if self.completed_indices else -1
)
self.current_target_index = max_completed + 1
if 0 <= self.current_target_index < len(self.point_sequence):
self.point_sequence[self.current_target_index]["status"] = "current"
# 停止移动时判断当前目标点
if not is_moving and 0 <= self.current_target_index < len(self.point_sequence):
target_point = self.point_sequence[self.current_target_index]
if target_point["status"] != "completed":
dx = current_pos[0] - target_point["pos"][0]
dy = current_pos[1] - target_point["pos"][1]
dz = current_pos[2] - target_point["pos"][2]
distance = (dx**2 + dy**2 + dz**2) ** 0.5
if distance < threshold:
self.completed_indices.add(self.current_target_index)
target_point["status"] = "completed"
if self.current_target_index + 1 < len(self.point_sequence):
# 将下一个点设置为目前正在朝向移动的点
self.current_target_index += 1
self.point_sequence[self.current_target_index][
"status"
] = "current"
def start_tracking(self):
"""清空已经移动的点位的集合, 开启下一轮跟踪"""
self.completed_indices.clear()
"""启动实时跟踪线程"""
self.tracking_running = True
self.tracking_thread = threading.Thread(target=self._tracking_loop, daemon=True)
self.tracking_thread.start()
def _tracking_loop(self):
"""跟踪循环:持续更新点位状态"""
while self.tracking_running:
self.update_point_status()
time.sleep(0.2) # 5Hz更新频率
def stop_tracking(self):
"""停止跟踪线程"""
self.tracking_running = False
if self.tracking_thread.is_alive():
self.tracking_thread.join()
def get_completed_points(self):
"""返回已运动过的点(索引和坐标)"""
return [
{"index": point["index"], "pos": point["pos"]}
for point in self.point_sequence
if point["status"] == "completed"
]
def get_current_target_point(self):
"""返回当前正在朝向移动的点(索引和坐标)"""
if 0 <= self.current_target_index < len(self.point_sequence):
point = self.point_sequence[self.current_target_index]
return {"index": point["index"], "pos": point["pos"]}
return None # 无当前朝向移动的目标点位(已完成所有点位的移动)

52
app/service/vibrate.py Normal file
View File

@ -0,0 +1,52 @@
import socket
import json
import time
set_vibrate_time = 5 # 震动时间 单位/秒
cmd_set_vibrate = { # 振动控制
"command": "set_vibrate",
"payload": {"time": set_vibrate_time}, # 单位S
}
# 使用 with 语句确保 socket 在使用完毕后正确关闭
def start_vibrate(host="192.168.58.25", port=5000):
try:
with socket.socket() as s:
s.connect((host, port))
s.sendall(json.dumps(cmd_set_vibrate).encode())
# print("已发送振动控制指令")
# 记录震动开始时间
recv_start_time = time.time()
# 接收响应, 检查 震动是否完成
data = s.recv(1024)
if data:
# 计算震动耗时(秒)
recv_elapsed_time = time.time() - recv_start_time
print("震动秒数:", recv_elapsed_time)
# print("收到响应:", data.decode())
response = json.loads(data.decode())
if response.get("vibrate_isok", False):
print("振动执行完成")
return True # 震动完成
else:
print("start_vibrate错误: 振动执行失败")
return False
else:
print("start_vibrate错误: 服务器无响应,连接已关闭")
return False
except (socket.error, json.JSONDecodeError, UnicodeDecodeError) as e:
print(f"start_vibrate错误: {e}")
return False
if __name__ == "__main__":
if start_vibrate():
print("震频完毕")
else:
print("震频发生错误")
exit(-1)

109
app/service/weight.py Normal file
View File

@ -0,0 +1,109 @@
import socket
import json
import time
target_weight = 660 # 目标重量
# 称重指令
cmd_set_target = {
# 称量
"command": "set_target",
"payload": {
"target_weight": target_weight,
"algorithm": "pid",
"direction_control": False,
},
}
# 清零
cmd_set_zero = {
# 去皮
"command": "set_zero"
}
cmd_get_weight = {
# 获取重量
"command": "get_weight"
}
def start_weight(host="192.168.58.25", port=5000):
"""
连接称重设备,发送目标重量并等待达到指定值
Args:
target_weight: 目标重量值
cmd_set_zero: 去皮
cmd_set_target: 称重
cmd_get_weight: 获取重量
host: 服务器IP地址
port: 服务器端口
Returns:
成功时返回达到的目标重量, 失败时返回None
"""
try:
with socket.socket() as s:
s.connect((host, port))
# 去皮
s.sendall(json.dumps(cmd_set_zero).encode())
time.sleep(1) # 等待去皮完成
# 检查去皮是否完成
s.sendall(json.dumps(cmd_get_weight).encode())
weight_data = s.recv(1024)
response = json.loads(weight_data.decode())
zero_weight = response.get("current_weight")
print("清零后的重量:", zero_weight)
except (socket.error, json.JSONDecodeError, UnicodeDecodeError) as e:
print(f"start_weight错误: {e}")
return None
try:
with socket.socket() as s:
if isinstance(zero_weight, (int, float)) and abs(zero_weight) < 0.01:
s.connect((host, port))
# 称重
s.sendall(json.dumps(cmd_set_target).encode())
while True:
data = s.recv(1024)
if not data:
print("start_weight错误: 服务器连接已关闭")
return None
try:
decoded = data.decode()
response = json.loads(decoded)
current_weight = response.get("current_weight")
if current_weight is not None:
print(f"当前重量:{current_weight}")
if current_weight >= target_weight:
return current_weight # 达到目标重量后返回
else:
print(f"获取的重量为None: {current_weight}")
return None
except json.JSONDecodeError:
print(f"start_weight错误: 无效的JSON格式: {decoded}")
return None
except UnicodeDecodeError:
print(
f"start_weight错误: 字节解码错误, 无法解析字节流data为字符串"
)
return None
else:
print("start_weight错误: 去皮失败, 中止称量流程, 请检查设备")
return None
except socket.error as e:
print(f"start_weight错误: 通信错误: {e}")
return None
if __name__ == "__main__":
if start_weight():
print("称重完毕")
else:
print("称重发生错误")
exit(-1)

View File

@ -324,7 +324,7 @@ class FormDatabase:
row[4], # 6. rx
row[5], # 7. ry
row[6], # 8. rz
pos_name, # 9. name # 以点位状态中的 pos_name 为准
row[7], # 9. name # 以表格中的 name 为准
speed, # 10. speed
tool_id, # 11. tool_id
workpiece_id, # 12. workpiece_id
@ -606,8 +606,12 @@ class CoordinateTableWidget(QWidget):
# 选中行更新信号
update_table_signal = Signal()
# 移动到选中行的 坐标的信号
move_to_coodinate_signal = Signal(list)
# 移动到选中行的 坐标的信号 (保证了发送的一定是合法坐标)
# 坐标列表 和 相应的点位名字列表
move_to_coodinate_signal = Signal(list, list)
# 机器停止信号,机器臂停止移动
machine_stop_signal = Signal()
def __init__(
self, parent=None, form_name="default", hideVHeader=False, initRowCount=True
@ -639,6 +643,7 @@ class CoordinateTableWidget(QWidget):
# 状态标签
self.status_label = QLabel("未选中任何行")
self.status_label.setWordWrap(True) # 启用自动换行
self.mainLayout.addWidget(self.status_label)
# 应用主题样式
@ -695,6 +700,7 @@ class CoordinateTableWidget(QWidget):
self.mainLayout.addWidget(self.table, stretch=1)
def createButtons(self):
# =========第一行按钮布局===========
btnLayout = QHBoxLayout()
btnLayout.setSpacing(15)
@ -720,32 +726,45 @@ class CoordinateTableWidget(QWidget):
self.readBtn.clicked.connect(self.moveToSelectedCoodinate)
btnLayout.addWidget(self.readBtn)
# 保存按钮
self.saveBtn = PushButton("保存数据")
self.saveBtn.setIcon(FIF.SAVE.icon()) # 使用保存图标
self.saveBtn.clicked.connect(self.saveToDatabase) # 关联保存函数
btnLayout.addWidget(self.saveBtn)
self.machineStopBtn = PushButton("机器停止")
self.machineStopBtn.setIcon(FIF.PAUSE.icon())
self.machineStopBtn.clicked.connect(self.machine_stop_signal)
btnLayout.addWidget(self.machineStopBtn)
btnLayout2 = QHBoxLayout()
btnLayout2.setSpacing(15)
# ==============第二行按钮布局==================
bottomBtnLayout = QHBoxLayout()
bottomBtnLayout.setSpacing(15)
# 上移按钮
# 1. 上移 + 下移
moveComboLayout = QHBoxLayout()
moveComboLayout.setSpacing(15) # 组合内按钮间距
self.moveUpBtn = PushButton("上移")
self.moveUpBtn.clicked.connect(self.moveRowUp)
btnLayout2.addWidget(self.moveUpBtn)
# 下移按钮
moveComboLayout.addWidget(self.moveUpBtn)
self.moveDownBtn = PushButton("下移")
self.moveDownBtn.clicked.connect(self.moveRowDown)
btnLayout2.addWidget(self.moveDownBtn)
moveComboLayout.addWidget(self.moveDownBtn)
bottomBtnLayout.addLayout(moveComboLayout)
# 状态编辑按钮
# 2. 状态编辑 + 默认状态
stateComboLayout = QHBoxLayout()
stateComboLayout.setSpacing(15)
self.stateEditBtn = PushButton("状态编辑")
self.stateEditBtn.clicked.connect(self.onStateEdit)
btnLayout2.addWidget(self.stateEditBtn)
stateComboLayout.addWidget(self.stateEditBtn)
self.defaultStateBtn = PushButton("默认状态")
# self.defaultStateBtn.clicked.connect(self.onDefaultState) # 需实现默认状态逻辑
stateComboLayout.addWidget(self.defaultStateBtn)
bottomBtnLayout.addLayout(stateComboLayout)
# 3. 保存数据 按钮
self.saveDataBtn = PushButton("保存数据")
self.saveDataBtn.setIcon(FIF.SAVE.icon())
self.saveDataBtn.clicked.connect(self.saveToDatabase)
bottomBtnLayout.addWidget(self.saveDataBtn)
self.mainLayout.addLayout(btnLayout) # 添加 按钮布局一
self.mainLayout.addLayout(btnLayout2) # 添加 按钮布局二
self.mainLayout.addLayout(bottomBtnLayout) # 添加 按钮布局二
# 数据保存到数据库,获取数据时调用
def get_ui_data(self):
@ -1011,9 +1030,14 @@ class CoordinateTableWidget(QWidget):
# 得到选中行的 点位(坐标) 列表,然后发送
sortedRowIdList = sorted(selectedRows) # 将选中行的行索引进行排序
coordinate_rows = list()
coordinate_rows = list() # 多行的坐标
name_rows = list() # 多行的名字
for row_idx in sortedRowIdList:
row_coordinate = list() # 保存这一行的坐标
for col_idx in range(6): # 0-5 ,这些列为坐标 x, y, z, rx, ry, rz
item = self.table.item(row_idx, col_idx)
@ -1041,8 +1065,15 @@ class CoordinateTableWidget(QWidget):
# 最终结果:[[x1, y1, z1, rx1, ry1, rz1], ......]
# x1等都为浮点类型
coordinate_rows.append(row_coordinate)
# 发送移动到这些坐标的信号
self.move_to_coodinate_signal.emit(coordinate_rows)
# ========== 9/17 新增: 点位的名字 移动时携带点位名=====
name_col_idx = 6 # 名字的列索引为6
name_item = self.table.item(row_idx, name_col_idx)
cood_name = name_item.text().strip() # 坐标/点位 的名字
name_rows.append(cood_name)
# 发送移动到这些 坐标 和 名字 的信号
self.move_to_coodinate_signal.emit(coordinate_rows, name_rows)
# 保存数据
def saveToDatabase(self):
@ -1330,7 +1361,11 @@ class CoordinateFormsWidget(QWidget):
form_update_signal = Signal(CoordinateTableWidget)
# 表单中 移动到坐标的信号 (用于机器臂的移动)
form_move_signal = Signal(list)
# 点位列表 和 名字列表
form_move_signal = Signal(list, list)
# 表单中 机器臂停止移动信号
form_machine_stop_signal = Signal()
def __init__(self, parent=None):
super().__init__(parent)
@ -1440,9 +1475,7 @@ class CoordinateFormsWidget(QWidget):
formKey = f"form_{self.formTotalCnt}" # 唯一标识, form_1
# 创建表单实例时传入表单名
newForm = CoordinateTableWidget(form_name=firstFormName, parent=self)
newForm.update_table_signal.connect(self.handleFormUpdate)
newForm.move_to_coodinate_signal.connect(self.form_move_signal)
newForm = self.createNewFormInstance(firstFormName)
self.tabBar.addTab(routeKey=formKey, text=firstFormName, icon=FIF.TILES.icon())
self.formStack.addWidget(newForm)
@ -1474,9 +1507,7 @@ class CoordinateFormsWidget(QWidget):
formKey = f"form_{self.formTotalCnt}" # 唯一标识
# 创建表单实例时传入表单名
newForm = CoordinateTableWidget(form_name=formName, parent=self)
newForm.update_table_signal.connect(self.handleFormUpdate)
newForm.move_to_coodinate_signal.connect(self.form_move_signal)
newForm = self.createNewFormInstance(formName)
self.tabBar.addTab(routeKey=formKey, text=formName, icon=FIF.TILES.icon())
self.formStack.addWidget(newForm)
@ -1502,6 +1533,15 @@ class CoordinateFormsWidget(QWidget):
self.formStack.removeWidget(formToRemove)
formToRemove.deleteLater()
# 创建并返回一个表单实例
def createNewFormInstance(self, formName:str, hideVHeader=False, initRowCount=True):
newForm = CoordinateTableWidget(form_name=formName, parent=self, hideVHeader=hideVHeader, initRowCount=initRowCount)
newForm.update_table_signal.connect(self.handleFormUpdate)
newForm.move_to_coodinate_signal.connect(self.form_move_signal)
newForm.machine_stop_signal.connect(self.form_machine_stop_signal)
return newForm
def switchForm(self, index):
if 0 <= index < self.formStack.count():
self.formStack.setCurrentIndex(index)
@ -1737,11 +1777,7 @@ class CoordinateFormsWidget(QWidget):
for form_name, data_rows in form_data.items():
# 创建新表单
self.formTotalCnt += 1 # 创建的总的表单数加一
new_form = CoordinateTableWidget(
form_name=f"{form_name}", parent=self, initRowCount=False
)
new_form.update_table_signal.connect(self.handleFormUpdate)
new_form.move_to_coodinate_signal.connect(self.form_move_signal)
new_form = self.createNewFormInstance(formName=form_name, initRowCount=False)
# 填充数据到新表单(包含点位名)
for row_idx, x, y, z, rx, ry, rz, name, pos_state_dict in data_rows:

View File

@ -64,7 +64,7 @@ class Window(FramelessWindow):
self.setTitleBar(StandardTitleBar(self))
# use dark theme mode
# setTheme(Theme.DARK)
setTheme(Theme.DARK)
# change the theme color
# setThemeColor('#0078d4')
@ -226,13 +226,8 @@ class Window(FramelessWindow):
QDesktopServices.openUrl(QUrl("https://afdian.net/a/zhiyiYo"))
def move_test(pos_list: list):
print("移动:", pos_list)
# if __name__ == "__main__":
# app = QApplication([])
# w = Window()
# w.position.formsWidget.form_move_signal.connect(move_test)
# w.show()
# app.exec()
if __name__ == "__main__":
app = QApplication([])
w = Window()
w.show()
app.exec()

View File

@ -1,48 +1,77 @@
# coding:utf-8
from PySide6.QtCore import Qt, QEasingCurve, QTimer
from PySide6.QtGui import QColor, QImage, QPixmap
from PySide6.QtWidgets import QWidget, QStackedWidget, QVBoxLayout, QLabel, QHBoxLayout, QFrame, QSizePolicy
from qfluentwidgets import (Pivot, qrouter, SegmentedWidget, TabBar, CheckBox, ComboBox,
TabCloseButtonDisplayMode, BodyLabel, SpinBox, BreadcrumbBar,
SegmentedToggleToolWidget, FluentIcon, TransparentPushButton, EditableComboBox, PrimaryPushButton, Slider, DisplayLabel, TextBrowser, SwitchButton, PillPushButton, ToggleButton)
from PySide6.QtWidgets import (
QWidget,
QStackedWidget,
QVBoxLayout,
QLabel,
QHBoxLayout,
QFrame,
QSizePolicy,
)
from qfluentwidgets import (
Pivot,
qrouter,
SegmentedWidget,
TabBar,
CheckBox,
ComboBox,
TabCloseButtonDisplayMode,
BodyLabel,
SpinBox,
BreadcrumbBar,
SegmentedToggleToolWidget,
FluentIcon,
TransparentPushButton,
EditableComboBox,
PrimaryPushButton,
Slider,
DisplayLabel,
TextBrowser,
SwitchButton,
PillPushButton,
ToggleButton,
)
from .gallery_interface import GalleryInterface
from ..common.translator import Translator
from ..common.style_sheet import StyleSheet
import cv2
class SystemInterface(GalleryInterface):
""" Navigation view interface """
"""Navigation view interface"""
def __init__(self, parent=None):
t = Translator()
super().__init__(
title=t.navigation,
subtitle="qfluentwidgets.components.navigation",
parent=parent
parent=parent,
)
self.setObjectName('systemInterface')
self.setObjectName("systemInterface")
# breadcrumb bar
card = self.addExampleCard(
title=self.tr(''),
title=self.tr(""),
widget=TabInterface(self),
sourcePath='https://github.com/zhiyiYo/PyQt-Fluent-Widgets/blob/PySide6/examples/navigation/tab_view/demo.py',
stretch=1
sourcePath="https://github.com/zhiyiYo/PyQt-Fluent-Widgets/blob/PySide6/examples/navigation/tab_view/demo.py",
stretch=1,
)
card.topLayout.setContentsMargins(12, 0, 0, 0)
def createToggleToolWidget(self):
w = SegmentedToggleToolWidget(self)
w.addItem('k1', FluentIcon.TRANSPARENT)
w.addItem('k2', FluentIcon.CHECKBOX)
w.addItem('k3', FluentIcon.CONSTRACT)
w.setCurrentItem('k1')
w.addItem("k1", FluentIcon.TRANSPARENT)
w.addItem("k2", FluentIcon.CHECKBOX)
w.addItem("k3", FluentIcon.CONSTRACT)
w.setCurrentItem("k1")
return w
class TabInterface(QWidget):
""" Tab interface """
"""Tab interface"""
def __init__(self, parent=None):
super().__init__(parent=parent)
@ -53,12 +82,14 @@ class TabInterface(QWidget):
self.tabView = QWidget(self)
self.controlPanel = QFrame(self)
#clock
self.clock=TransparentPushButton("2025-08-04 16:55", self, FluentIcon.DATE_TIME)
# clock
self.clock = TransparentPushButton(
"2025-08-04 16:55", self, FluentIcon.DATE_TIME
)
# combo box
self.comboBox1 = ComboBox()
self.comboBox1.addItems(['反应釜1', '反应釜2'])
self.comboBox1.addItems(["反应釜1", "反应釜2"])
self.comboBox1.setCurrentIndex(0)
self.comboBox1.setMinimumWidth(180)
@ -67,59 +98,61 @@ class TabInterface(QWidget):
# editable combo box
self.comboBox = EditableComboBox()
self.comboBox.addItems([
"10",
"20",
"30",
"40",
"50",
"60",
"70",
"80",
])
self.comboBox.addItems(
[
"10",
"20",
"30",
"40",
"50",
"60",
"70",
"80",
]
)
self.comboBox.setPlaceholderText("自定义数量")
self.comboBox.setMinimumWidth(90)
#hBoxLayout
# hBoxLayout
self.container1 = QWidget()
self.hBoxLayout1 = QHBoxLayout(self.container1)
self.hBoxLayout1.addWidget(self.label)
self.hBoxLayout1.addWidget(self.comboBox)
#button
self.primaryButton1 = PrimaryPushButton(FluentIcon.PLAY, '启动', self)
self.primaryButton2 = PrimaryPushButton(FluentIcon.PAUSE, '暂停', self)
self.primaryButton3 = PrimaryPushButton(FluentIcon.POWER_BUTTON, '停止', self)
self.primaryButton4 = PrimaryPushButton(FluentIcon.ROTATE, '复位', self)
self.primaryButton5 = PrimaryPushButton(FluentIcon.CLOSE, '急停', self)
self.primaryButton6 = PrimaryPushButton(FluentIcon.SYNC, '清除', self)
# button
self.primaryButton1 = PrimaryPushButton(FluentIcon.PLAY, "启动", self)
self.primaryButton2 = PrimaryPushButton(FluentIcon.PAUSE, "暂停", self)
self.primaryButton3 = PrimaryPushButton(FluentIcon.POWER_BUTTON, "停止", self)
self.primaryButton4 = PrimaryPushButton(FluentIcon.ROTATE, "复位", self)
self.primaryButton5 = PrimaryPushButton(FluentIcon.CLOSE, "急停", self)
self.primaryButton6 = PrimaryPushButton(FluentIcon.SYNC, "清除", self)
self.primaryButton1.setObjectName('primaryButton1')
self.primaryButton2.setObjectName('primaryButton2')
self.primaryButton3.setObjectName('primaryButton3')
self.primaryButton4.setObjectName('primaryButton4')
self.primaryButton5.setObjectName('primaryButton5')
self.primaryButton6.setObjectName('primaryButton6')
self.primaryButton1.setObjectName("primaryButton1")
self.primaryButton2.setObjectName("primaryButton2")
self.primaryButton3.setObjectName("primaryButton3")
self.primaryButton4.setObjectName("primaryButton4")
self.primaryButton5.setObjectName("primaryButton5")
self.primaryButton6.setObjectName("primaryButton6")
#hBoxLayout2
# hBoxLayout2
self.container2 = QWidget()
self.hBoxLayout2 = QHBoxLayout(self.container2)
self.hBoxLayout2.addWidget(self.primaryButton1)
self.hBoxLayout2.addWidget(self.primaryButton2)
#hBoxLayout3
# hBoxLayout3
self.container3 = QWidget()
self.hBoxLayout3 = QHBoxLayout(self.container3)
self.hBoxLayout3.addWidget(self.primaryButton3)
self.hBoxLayout3.addWidget(self.primaryButton4)
#hBoxLayout4
# hBoxLayout4
self.container4 = QWidget()
self.hBoxLayout4 = QHBoxLayout(self.container4)
self.hBoxLayout4.addWidget(self.primaryButton5)
self.hBoxLayout4.addWidget(self.primaryButton6)
#滑动条
# 滑动条
self.slider = Slider(Qt.Horizontal)
self.slider.setFixedWidth(200)
@ -130,62 +163,66 @@ class TabInterface(QWidget):
# Displaylabel
self.label2 = DisplayLabel("目标袋数")
self.label3 = DisplayLabel("0")
self.label3.setObjectName('label3')
self.label3.setObjectName("label3")
self.label3.setStyleSheet("color: red;")
self.label4 = DisplayLabel("剩余袋数")
self.label5 = DisplayLabel("0")
self.label5.setObjectName('label5')
self.label5.setObjectName("label5")
self.label5.setStyleSheet("color: green;")
#hBoxLayout
# hBoxLayout
self.container5 = QWidget()
self.hBoxLayout5 = QHBoxLayout(self.container5)
self.hBoxLayout5.addWidget(self.label2)
self.hBoxLayout5.addWidget(self.label3)
#hBoxLayout
# hBoxLayout
self.container6 = QWidget()
self.hBoxLayout6 = QHBoxLayout(self.container6)
self.hBoxLayout6.addWidget(self.label4)
self.hBoxLayout6.addWidget(self.label5)
#self.movableCheckBox = CheckBox(self.tr('IsTabMovable'), self)
#self.scrollableCheckBox = CheckBox(self.tr('IsTabScrollable'), self)
#self.shadowEnabledCheckBox = CheckBox(self.tr('IsTabShadowEnabled'), self)
#self.tabMaxWidthLabel = BodyLabel(self.tr('TabMaximumWidth'), self)
# self.movableCheckBox = CheckBox(self.tr('IsTabMovable'), self)
# self.scrollableCheckBox = CheckBox(self.tr('IsTabScrollable'), self)
# self.shadowEnabledCheckBox = CheckBox(self.tr('IsTabShadowEnabled'), self)
# self.tabMaxWidthLabel = BodyLabel(self.tr('TabMaximumWidth'), self)
# self.tabMaxWidthSpinBox = SpinBox(self)
#self.closeDisplayModeLabel = BodyLabel(self.tr('TabCloseButtonDisplayMode'), self)
#self.closeDisplayModeComboBox = ComboBox(self)
# self.closeDisplayModeLabel = BodyLabel(self.tr('TabCloseButtonDisplayMode'), self)
# self.closeDisplayModeComboBox = ComboBox(self)
self.hBoxLayout = QHBoxLayout(self)
self.vBoxLayout = QVBoxLayout(self.tabView)
self.panelLayout = QVBoxLayout(self.controlPanel)
#富文本编辑栏用来显示日志
# 富文本编辑栏用来显示日志
self.textBrowser = TextBrowser()
#self.textBrowser.setMarkdown("## Steel Ball Run \n * Johnny Joestar 🦄 \n * Gyro Zeppeli 🐴 aaa\n * aaa\n * aaa\n * aaa\n * aaa\n *")
self.textBrowser.setMarkdown("## 日志\n * 2025-08-06 09:54:24 - 🦄进入系统\n * 2025-08-06 09:54:24 - 🐴无回复\n * 2025-08-06 09:54:24 - 🦄进入系统\n * 2025-08-06 09:54:24 - 🐴无回复\n * 2025-08-06 09:54:24 - 🦄进入系统\n * 2025-08-06 09:54:24 - 🐴无回复\n * 2025-08-06 09:54:24 - 🦄进入系统\n * 2025-08-06 09:54:24 - 🐴无回复\n *")
# self.textBrowser.setMarkdown("## Steel Ball Run \n * Johnny Joestar 🦄 \n * Gyro Zeppeli 🐴 aaa\n * aaa\n * aaa\n * aaa\n * aaa\n *")
self.textBrowser.setMarkdown(
"## 日志\n * 2025-08-06 09:54:24 - 🦄进入系统\n * 2025-08-06 09:54:24 - 🐴无回复\n * 2025-08-06 09:54:24 - 🦄进入系统\n * 2025-08-06 09:54:24 - 🐴无回复\n * 2025-08-06 09:54:24 - 🦄进入系统\n * 2025-08-06 09:54:24 - 🐴无回复\n * 2025-08-06 09:54:24 - 🦄进入系统\n * 2025-08-06 09:54:24 - 🐴无回复\n *"
)
#日志切换按钮
# 日志切换按钮
self.button = SwitchButton()
self.button.checkedChanged.connect(lambda checked: print("是否选中按钮:", checked))
# self.button.checkedChanged.connect(
# lambda checked: print("是否选中按钮:", checked)
# )
# 更改按钮状态
self.button.setChecked(True)
# 获取按钮是否选中
self.button.setOffText("报警")
self.button.setOnText("日志")
#状态按钮
#PillPushButton(self.tr('Tag'), self, FluentIcon.TAG),
# 状态按钮
# PillPushButton(self.tr('Tag'), self, FluentIcon.TAG),
self.state_button1 = PillPushButton()
self.state_button1.setFixedHeight(15)
self.state_button1.setFixedWidth(100)
self.state_button2 = PillPushButton("1",self,"")
self.state_button2 = PillPushButton("1", self, "")
self.state_button2.setFixedHeight(15)
self.state_button2.setFixedWidth(100)
self.state_button3 = PillPushButton("0",self,"")
self.state_button3 = PillPushButton("0", self, "")
self.state_button3.setFixedHeight(15)
self.state_button3.setFixedWidth(100)
@ -193,11 +230,11 @@ class TabInterface(QWidget):
self.state_button4.setFixedHeight(15)
self.state_button4.setFixedWidth(100)
self.state_button5 = PillPushButton("0",self,"")
self.state_button5 = PillPushButton("0", self, "")
self.state_button5.setFixedHeight(15)
self.state_button5.setFixedWidth(100)
self.state_button6 = PillPushButton("0",self,"")
self.state_button6 = PillPushButton("0", self, "")
self.state_button6.setFixedHeight(15)
self.state_button6.setFixedWidth(100)
@ -208,7 +245,7 @@ class TabInterface(QWidget):
self.state5 = DisplayLabel("当前工具号:")
self.state6 = DisplayLabel("报警代码:")
#状态hBoxLayout
# 状态hBoxLayout
self.state_container1 = QWidget()
self.state_hBoxLayout1 = QHBoxLayout(self.state_container1)
self.state_hBoxLayout1.addWidget(self.state1)
@ -239,13 +276,13 @@ class TabInterface(QWidget):
self.state_hBoxLayout6.addWidget(self.state6)
self.state_hBoxLayout6.addWidget(self.state_button6)
#日志vboxlayout
# 日志vboxlayout
self.container7 = QWidget()
self.vBoxLayout7 = QVBoxLayout(self.container7)
self.vBoxLayout7.addWidget(self.button)
self.vBoxLayout7.addWidget(self.textBrowser)
#状态vboxlayout
# 状态vboxlayout
self.container9 = QWidget()
self.vBoxLayout9 = QVBoxLayout(self.container9)
self.vBoxLayout9.addWidget(self.state_container1)
@ -255,13 +292,13 @@ class TabInterface(QWidget):
self.vBoxLayout9.addWidget(self.state_container5)
self.vBoxLayout9.addWidget(self.state_container6)
#日志+状态vboxlayout
# 日志+状态vboxlayout
self.container8 = QWidget()
self.hBoxLayout8 = QHBoxLayout(self.container8)
self.hBoxLayout8.addWidget(self.container7)
self.hBoxLayout8.addWidget(self.container9)
#self.songInterface = QLabel('Song Interface', self)
# self.songInterface = QLabel('Song Interface', self)
# self.albumInterface = QLabel('Album Interface', self)
# self.artistInterface = QLabel('Artist Interface', self)
@ -274,7 +311,7 @@ class TabInterface(QWidget):
# self.shadowEnabledCheckBox.setChecked(True)
# self.tabMaxWidthSpinBox.setRange(60, 400)
#self.tabMaxWidthSpinBox.setValue(self.tabBar.tabMaximumWidth())
# self.tabMaxWidthSpinBox.setValue(self.tabBar.tabMaximumWidth())
# self.closeDisplayModeComboBox.addItem(self.tr('Always'), userData=TabCloseButtonDisplayMode.ALWAYS)
# self.closeDisplayModeComboBox.addItem(self.tr('OnHover'), userData=TabCloseButtonDisplayMode.ON_HOVER)
@ -288,33 +325,33 @@ class TabInterface(QWidget):
# self.addSubInterface(self.artistInterface,
# 'tabArtistInterface', self.tr('Artist'), ':/gallery/images/Singer.png')
self.controlPanel.setObjectName('controlPanel')
self.controlPanel.setObjectName("controlPanel")
StyleSheet.SYSTEM_INTERFACE.apply(self)
#self.connectSignalToSlot()
# self.connectSignalToSlot()
# qrouter.setDefaultRouteKey(
# self.stackedWidget, self.songInterface.objectName())
# def connectSignalToSlot(self):
# self.movableCheckBox.stateChanged.connect(
# lambda: self.tabBar.setMovable(self.movableCheckBox.isChecked()))
# self.scrollableCheckBox.stateChanged.connect(
# lambda: self.tabBar.setScrollable(self.scrollableCheckBox.isChecked()))
# self.shadowEnabledCheckBox.stateChanged.connect(
# lambda: self.tabBar.setTabShadowEnabled(self.shadowEnabledCheckBox.isChecked()))
# self.movableCheckBox.stateChanged.connect(
# lambda: self.tabBar.setMovable(self.movableCheckBox.isChecked()))
# self.scrollableCheckBox.stateChanged.connect(
# lambda: self.tabBar.setScrollable(self.scrollableCheckBox.isChecked()))
# self.shadowEnabledCheckBox.stateChanged.connect(
# lambda: self.tabBar.setTabShadowEnabled(self.shadowEnabledCheckBox.isChecked()))
#self.tabMaxWidthSpinBox.valueChanged.connect(self.tabBar.setTabMaximumWidth)
# self.tabMaxWidthSpinBox.valueChanged.connect(self.tabBar.setTabMaximumWidth)
# self.tabBar.tabAddRequested.connect(self.addTab)
# self.tabBar.tabCloseRequested.connect(self.removeTab)
# self.tabBar.tabAddRequested.connect(self.addTab)
# self.tabBar.tabCloseRequested.connect(self.removeTab)
# self.stackedWidget.currentChanged.connect(self.onCurrentIndexChanged)
# self.stackedWidget.currentChanged.connect(self.onCurrentIndexChanged)
def initLayout(self):
# self.tabBar.setTabMaximumWidth(200)
#self.setFixedHeight(450)
# self.setFixedHeight(450)
self.setMaximumSize(787, 800)
self.setMinimumSize(450, 450)
self.controlPanel.setFixedWidth(220)
@ -322,7 +359,7 @@ class TabInterface(QWidget):
self.hBoxLayout.addWidget(self.controlPanel, 0, Qt.AlignRight)
self.hBoxLayout.setContentsMargins(0, 0, 0, 0)
#self.vBoxLayout.addWidget(self.tabBar)
# self.vBoxLayout.addWidget(self.tabBar)
# self.vBoxLayout.addWidget(self.stackedWidget)
self.vBoxLayout.setContentsMargins(0, 0, 0, 0)
@ -361,13 +398,13 @@ class TabInterface(QWidget):
# self.panelLayout.addWidget(self.closeDisplayModeLabel)
# self.panelLayout.addWidget(self.closeDisplayModeComboBox)
#左边窗口
# 左边窗口
# 创建加载框
#self.loading_button1=ToggleButton(self.tr('Start practicing'), self, FluentIcon.BASKETBALL)
self.loading_button1=ToggleButton()
self.loading_button2=ToggleButton()
self.loading_button3=ToggleButton()
self.loading_button4=ToggleButton()
# self.loading_button1=ToggleButton(self.tr('Start practicing'), self, FluentIcon.BASKETBALL)
self.loading_button1 = ToggleButton()
self.loading_button2 = ToggleButton()
self.loading_button3 = ToggleButton()
self.loading_button4 = ToggleButton()
self.loading1 = DisplayLabel("取料中...")
self.loading2 = DisplayLabel("拍照中...")
self.loading3 = DisplayLabel("抓料中...")
@ -390,7 +427,9 @@ class TabInterface(QWidget):
self.vBoxLayout.addWidget(self.video_label)
# 使用 OpenCV 读取视频
self.cap = cv2.VideoCapture("./app/resource/video/test.mp4") # 替换为你的视频路径
self.cap = cv2.VideoCapture(
"./app/resource/video/test.mp4"
) # 替换为你的视频路径
if not self.cap.isOpened():
print("无法打开视频文件!")
return
@ -399,12 +438,11 @@ class TabInterface(QWidget):
self.timer = QTimer()
self.timer.timeout.connect(self.update_frame)
self.timer.start(30) # 30ms 更新一帧(约 33 FPS
#self.vBoxLayout.addWidget()
# self.vBoxLayout.addWidget()
#左边窗口下面的日志栏位和状态栏
# 左边窗口下面的日志栏位和状态栏
self.vBoxLayout.addWidget(self.container8)
def addSubInterface(self, widget: QLabel, objectName, text, icon):
widget.setObjectName(objectName)
widget.setAlignment(Qt.AlignTop | Qt.AlignLeft)
@ -421,12 +459,12 @@ class TabInterface(QWidget):
# self.tabBar.setCloseButtonDisplayMode(mode)
# def onCurrentIndexChanged(self, index):
# widget = self.stackedWidget.widget(index)
# if not widget:
# return
# widget = self.stackedWidget.widget(index)
# if not widget:
# return
# self.tabBar.setCurrentTab(widget.objectName())
# qrouter.push(self.stackedWidget, widget.objectName())
# self.tabBar.setCurrentTab(widget.objectName())
# qrouter.push(self.stackedWidget, widget.objectName())
def update_frame(self):
ret, frame = self.cap.read()
@ -441,4 +479,6 @@ class TabInterface(QWidget):
bytes_per_line = ch * w
q_img = QImage(frame.data, w, h, bytes_per_line, QImage.Format_RGB888)
pixmap = QPixmap.fromImage(q_img)
self.video_label.setPixmap(pixmap.scaled(self.video_label.size())) # 自适应窗口大小
self.video_label.setPixmap(
pixmap.scaled(self.video_label.size())
) # 自适应窗口大小

8851
fairino/Robot.py Normal file

File diff suppressed because it is too large Load Diff

6
fairino/setup.py Normal file
View File

@ -0,0 +1,6 @@
# setup.py
# python3 setup.py build_ext --inplace
# python setup.py build_ext --inplace
from distutils.core import setup
from Cython.Build import cythonize
setup(name='Robot', ext_modules=cythonize('Robot.py'))

View File

@ -5,3 +5,4 @@ colorthief
scipy
pillow
opencv-python
cython