测试了密胺的整个流程、add(点位设置界面增加了 停止功能按钮)、fix bug(修复了点位名称写入数据库不一致的错误)
This commit is contained in:
@ -1,3 +1,3 @@
|
||||
# fluent_widgets_pyside6
|
||||
|
||||
机器臂控制系统的 UI界面
|
||||
法奥机器臂控制系统的 UI界面 以及密胺制品的相关控制代码
|
||||
@ -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 handleMove(self, pos_list: list):
|
||||
print("handleMove 移动:", pos_list)
|
||||
# 机器臂停止移动
|
||||
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}")
|
||||
|
||||
|
||||
87
app/service/EMV.py
Normal file
87
app/service/EMV.py
Normal 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
58
app/service/catch.py
Normal 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
146
app/service/drop.py
Normal 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
198
app/service/high_machine.py
Normal 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():
|
||||
# 检查对应位是否为1(1表示开启,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()
|
||||
|
||||
|
||||
213
app/service/press_machine.py
Normal file
213
app/service/press_machine.py
Normal 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():
|
||||
# 检查对应位是否为1(1表示开启,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)
|
||||
|
||||
|
||||
|
||||
394
app/service/robot_service.py
Normal file
394
app/service/robot_service.py
Normal 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=2),False表示停止/暂停/拖动/异常
|
||||
"""
|
||||
# 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
52
app/service/vibrate.py
Normal 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
109
app/service/weight.py
Normal 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)
|
||||
@ -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,7 +700,8 @@ class CoordinateTableWidget(QWidget):
|
||||
self.mainLayout.addWidget(self.table, stretch=1)
|
||||
|
||||
def createButtons(self):
|
||||
btnLayout = QHBoxLayout()
|
||||
# =========第一行按钮布局===========
|
||||
btnLayout = QHBoxLayout()
|
||||
btnLayout.setSpacing(15)
|
||||
|
||||
self.addBtn = PushButton("添加行")
|
||||
@ -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:
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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,11 +438,10 @@ 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)
|
||||
@ -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
8851
fairino/Robot.py
Normal file
File diff suppressed because it is too large
Load Diff
6
fairino/setup.py
Normal file
6
fairino/setup.py
Normal 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'))
|
||||
@ -4,4 +4,5 @@ darkdetect
|
||||
colorthief
|
||||
scipy
|
||||
pillow
|
||||
opencv-python
|
||||
opencv-python
|
||||
cython
|
||||
Reference in New Issue
Block a user