首次提交
commit
459ce57cd4
|
|
@ -0,0 +1 @@
|
|||
__pycache__/
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"wrench": {
|
||||
"host": "192.168.110.122",
|
||||
"port": 7888,
|
||||
"timeout": 30,
|
||||
"address": 1,
|
||||
"auto_reconnect": true,
|
||||
"max_reconnect_attempts": 3,
|
||||
"description": "扳手连接配置,auto_reconnect=自动重连,max_reconnect_attempts=最大重连次数"
|
||||
},
|
||||
"test_mode": {
|
||||
"enabled": true,
|
||||
"description": "测试模式:失败也算成功,用于无钉子测试"
|
||||
},
|
||||
"bolt_default_config": {
|
||||
"mode": 1,
|
||||
"torque_tolerance": 0.10,
|
||||
"angle_min": 1,
|
||||
"angle_max": 360,
|
||||
"description": "螺栓默认配置:mode=1(M1扭矩模式), torque_tolerance=10%, angle_min=1度, angle_max=360度"
|
||||
},
|
||||
"default_parameters": {
|
||||
"product_id": "0000000000",
|
||||
"station_name": "11111111111111111111",
|
||||
"employee_id": "2222222222",
|
||||
"tool_sn": "0000000000",
|
||||
"controller_sn": "3333333333"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
{
|
||||
"order_id": "WO20260119001",
|
||||
"product_name": "汽车底盘组件",
|
||||
"station": "装配工位A1",
|
||||
"operator": "张三",
|
||||
"bolts": [
|
||||
{
|
||||
"bolt_id": 1,
|
||||
"name": "前轮螺栓1",
|
||||
"target_torque": 280,
|
||||
"mode": 1,
|
||||
"torque_tolerance": 0.10,
|
||||
"angle_min": 1,
|
||||
"angle_max": 360,
|
||||
"status": "pending"
|
||||
},
|
||||
{
|
||||
"bolt_id": 2,
|
||||
"name": "前轮螺栓2",
|
||||
"target_torque": 300,
|
||||
"mode": 1,
|
||||
"torque_tolerance": 0.10,
|
||||
"angle_min": 1,
|
||||
"angle_max": 360,
|
||||
"status": "pending"
|
||||
},
|
||||
{
|
||||
"bolt_id": 3,
|
||||
"name": "后轮螺栓1",
|
||||
"target_torque": 320,
|
||||
"mode": 1,
|
||||
"torque_tolerance": 0.10,
|
||||
"angle_min": 1,
|
||||
"angle_max": 360,
|
||||
"status": "pending"
|
||||
},
|
||||
{
|
||||
"bolt_id": 4,
|
||||
"name": "后轮螺栓2",
|
||||
"target_torque": 310,
|
||||
"mode": 1,
|
||||
"torque_tolerance": 0.10,
|
||||
"angle_min": 1,
|
||||
"angle_max": 360,
|
||||
"status": "pending"
|
||||
},
|
||||
{
|
||||
"bolt_id": 5,
|
||||
"name": "后轮螺栓3",
|
||||
"target_torque": 310,
|
||||
"mode": 1,
|
||||
"torque_tolerance": 0.10,
|
||||
"angle_min": 1,
|
||||
"angle_max": 360,
|
||||
"status": "pending"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,524 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
电动扳手通讯控制模块
|
||||
支持扭矩设定和结果解析
|
||||
"""
|
||||
|
||||
import socket
|
||||
import struct
|
||||
import json
|
||||
from datetime import datetime
|
||||
from typing import Tuple, Optional
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class WrenchController:
|
||||
"""电动扳手控制器"""
|
||||
|
||||
def __init__(self, config_file: str = "config.json"):
|
||||
"""
|
||||
初始化扳手控制器
|
||||
:param config_file: 配置文件路径
|
||||
"""
|
||||
self.config = self._load_config(config_file)
|
||||
self.host = self.config["wrench"]["host"]
|
||||
self.port = self.config["wrench"]["port"]
|
||||
self.timeout = self.config["wrench"].get("timeout", 30)
|
||||
self.address = self.config["wrench"].get("address", 0x01)
|
||||
self.test_mode = self.config.get("test_mode", {}).get("enabled", False)
|
||||
self.auto_reconnect = self.config["wrench"].get("auto_reconnect", True)
|
||||
self.max_reconnect_attempts = self.config["wrench"].get("max_reconnect_attempts", 3)
|
||||
self.sock = None
|
||||
self.is_connected = False
|
||||
|
||||
print(f"加载配置: {self.host}:{self.port}")
|
||||
if self.test_mode:
|
||||
print("⚠️ 测试模式已启用:失败也算成功")
|
||||
if self.auto_reconnect:
|
||||
print(f"✅ 自动重连已启用:最多尝试{self.max_reconnect_attempts}次")
|
||||
|
||||
def _load_config(self, config_file: str) -> dict:
|
||||
"""加载配置文件"""
|
||||
try:
|
||||
config_path = Path(config_file)
|
||||
if not config_path.exists():
|
||||
print(f"配置文件不存在: {config_file},使用默认配置")
|
||||
return self._get_default_config()
|
||||
|
||||
with open(config_path, 'r', encoding='utf-8') as f:
|
||||
config = json.load(f)
|
||||
print(f"成功加载配置文件: {config_file}")
|
||||
return config
|
||||
except Exception as e:
|
||||
print(f"加载配置文件失败: {e},使用默认配置")
|
||||
return self._get_default_config()
|
||||
|
||||
def _get_default_config(self) -> dict:
|
||||
"""获取默认配置"""
|
||||
return {
|
||||
"wrench": {
|
||||
"host": "192.168.110.122",
|
||||
"port": 7888,
|
||||
"timeout": 30,
|
||||
"address": 1,
|
||||
"auto_reconnect": True,
|
||||
"max_reconnect_attempts": 3
|
||||
},
|
||||
"test_mode": {
|
||||
"enabled": False,
|
||||
"description": "测试模式:失败也算成功,用于无钉子测试"
|
||||
},
|
||||
"default_parameters": {
|
||||
"product_id": "0000000000",
|
||||
"station_name": "11111111111111111111",
|
||||
"employee_id": "2222222222",
|
||||
"tool_sn": "0000000000",
|
||||
"controller_sn": "3333333333"
|
||||
}
|
||||
}
|
||||
|
||||
def connect(self, timeout: float = 5.0):
|
||||
"""连接到扳手"""
|
||||
try:
|
||||
print(f"正在连接到 {self.host}:{self.port} ...")
|
||||
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.sock.settimeout(timeout)
|
||||
self.sock.connect((self.host, self.port))
|
||||
self.is_connected = True
|
||||
print(f"✅ 已连接到扳手: {self.host}:{self.port}")
|
||||
return True
|
||||
except socket.timeout:
|
||||
print(f"❌ 连接超时: {self.host}:{self.port}")
|
||||
self.is_connected = False
|
||||
return False
|
||||
except ConnectionRefusedError:
|
||||
print(f"❌ 连接被拒绝: {self.host}:{self.port} (扳手可能未开机或端口错误)")
|
||||
self.is_connected = False
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"❌ 连接失败: {e}")
|
||||
self.is_connected = False
|
||||
return False
|
||||
|
||||
def reconnect(self):
|
||||
"""尝试重新连接"""
|
||||
if not self.auto_reconnect:
|
||||
return False
|
||||
|
||||
print("🔄 尝试重新连接...")
|
||||
self.disconnect()
|
||||
|
||||
for attempt in range(1, self.max_reconnect_attempts + 1):
|
||||
print(f"重连尝试 {attempt}/{self.max_reconnect_attempts}")
|
||||
if self.connect():
|
||||
print("✅ 重连成功!")
|
||||
return True
|
||||
if attempt < self.max_reconnect_attempts:
|
||||
import time
|
||||
time.sleep(2) # 等待2秒后重试
|
||||
|
||||
print("❌ 重连失败,已达到最大尝试次数")
|
||||
return False
|
||||
|
||||
def disconnect(self):
|
||||
"""断开连接"""
|
||||
if self.sock:
|
||||
try:
|
||||
self.sock.close()
|
||||
except:
|
||||
pass
|
||||
self.sock = None
|
||||
self.is_connected = False
|
||||
print("已断开连接")
|
||||
|
||||
def _calculate_checksum(self, data: bytes) -> int:
|
||||
"""计算校验和(累加和)"""
|
||||
return sum(data) & 0xFF
|
||||
|
||||
def _send_command(self, command: bytes) -> bool:
|
||||
"""发送命令"""
|
||||
try:
|
||||
if not self.sock or not self.is_connected:
|
||||
print("❌ Socket未连接")
|
||||
if self.auto_reconnect:
|
||||
if self.reconnect():
|
||||
# 重连成功,重新发送
|
||||
return self._send_command(command)
|
||||
return False
|
||||
|
||||
self.sock.sendall(command)
|
||||
print(f"✅ 已发送命令: {command.hex(' ').upper()}")
|
||||
return True
|
||||
except BrokenPipeError:
|
||||
print("❌ 连接已断开")
|
||||
self.is_connected = False
|
||||
if self.auto_reconnect:
|
||||
if self.reconnect():
|
||||
return self._send_command(command)
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"❌ 发送命令失败: {e}")
|
||||
self.is_connected = False
|
||||
if self.auto_reconnect:
|
||||
if self.reconnect():
|
||||
return self._send_command(command)
|
||||
return False
|
||||
|
||||
def _receive_response(self, timeout: float = 2.0) -> Optional[bytes]:
|
||||
"""接收响应"""
|
||||
try:
|
||||
if not self.sock or not self.is_connected:
|
||||
print("❌ Socket未连接")
|
||||
return None
|
||||
|
||||
self.sock.settimeout(timeout)
|
||||
response = self.sock.recv(1024)
|
||||
|
||||
if response:
|
||||
print(f"✅ 收到响应: {response.hex(' ').upper()}")
|
||||
return response
|
||||
else:
|
||||
print("❌ 收到空响应(连接可能已关闭)")
|
||||
self.is_connected = False
|
||||
return None
|
||||
|
||||
except socket.timeout:
|
||||
print("⏱️ 接收响应超时")
|
||||
return None
|
||||
except ConnectionResetError:
|
||||
print("❌ 连接被重置")
|
||||
self.is_connected = False
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"❌ 接收响应失败: {e}")
|
||||
self.is_connected = False
|
||||
return None
|
||||
|
||||
def enable_remote_control(self, enable: bool = True) -> bool:
|
||||
"""
|
||||
启用/停止远程控制
|
||||
:param enable: True启用,False停止
|
||||
:return: 是否成功
|
||||
"""
|
||||
data = bytearray([
|
||||
0xC5, 0xC5, # 报文头
|
||||
self.address, # 地址码
|
||||
0x11, # 功能码
|
||||
0xFF, 0xFF, # 保留
|
||||
0x01, # 数据长度
|
||||
0x01 if enable else 0x00 # 数据内容
|
||||
])
|
||||
data.append(self._calculate_checksum(data))
|
||||
|
||||
print(f"{'启用' if enable else '停止'}远程控制...")
|
||||
return self._send_command(bytes(data))
|
||||
|
||||
def set_torque_parameters(self,
|
||||
target_torque: int,
|
||||
mode: int = 1,
|
||||
torque_tolerance: float = 0.10,
|
||||
target_angle: int = 0,
|
||||
angle_min: int = 1,
|
||||
angle_max: int = 360,
|
||||
product_id: str = None,
|
||||
station_name: str = None,
|
||||
employee_id: str = None,
|
||||
tool_sn: str = None,
|
||||
controller_sn: str = None) -> bytes:
|
||||
"""
|
||||
设定扭矩参数(功能码0x10)
|
||||
:param target_torque: 目标扭矩(Nm)
|
||||
:param mode: 模式 1=M1模式(扭矩模式), 2=M2模式(角度模式)
|
||||
:param torque_tolerance: 扭矩偏差百分比(仅M1模式),如0.10表示±10%
|
||||
:param target_angle: 目标角度(度),M1模式填0
|
||||
:param angle_min: 角度下限(度)
|
||||
:param angle_max: 角度上限(度)
|
||||
:param product_id: 产品ID(10字节)
|
||||
:param station_name: 工位名称(20字节)
|
||||
:param employee_id: 员工号(10字节)
|
||||
:param tool_sn: 工具系列号(10字节)
|
||||
:param controller_sn: 控制器系列号(10字节)
|
||||
:return: 生成的报文
|
||||
"""
|
||||
# 从配置文件获取默认参数
|
||||
defaults = self.config.get("default_parameters", {})
|
||||
product_id = product_id or defaults.get("product_id", "0000000000")
|
||||
station_name = station_name or defaults.get("station_name", "11111111111111111111")
|
||||
employee_id = employee_id or defaults.get("employee_id", "2222222222")
|
||||
tool_sn = tool_sn or defaults.get("tool_sn", "0000000000")
|
||||
controller_sn = controller_sn or defaults.get("controller_sn", "3333333333")
|
||||
|
||||
# 获取当前时间
|
||||
now = datetime.now()
|
||||
|
||||
# 计算扭矩上下限
|
||||
if mode == 1: # M1模式
|
||||
torque_min = int(target_torque * (1 - torque_tolerance))
|
||||
torque_max = int(target_torque * (1 + torque_tolerance))
|
||||
else: # M2模式
|
||||
torque_min = int(target_torque * 0.8) # 默认下限
|
||||
torque_max = int(target_torque * 1.2) # 默认上限
|
||||
|
||||
# 角度需要乘10
|
||||
angle_value = target_angle * 10
|
||||
angle_min_value = angle_min * 10
|
||||
angle_max_value = angle_max * 10
|
||||
|
||||
# 构建报文
|
||||
data = bytearray([
|
||||
0xC5, 0xC5, # 报文头
|
||||
self.address, # 地址码
|
||||
0x10, # 功能码
|
||||
0xFF, 0xFF, # 反退角(保留)
|
||||
0x50, # 数据长度
|
||||
])
|
||||
|
||||
# 产品ID (10字节)
|
||||
data.extend(product_id.encode('ascii')[:10].ljust(10, b'\x00'))
|
||||
|
||||
# 工位名称 (20字节)
|
||||
data.extend(station_name.encode('utf-8')[:20].ljust(20, b'\x00'))
|
||||
|
||||
# 员工号 (10字节)
|
||||
data.extend(employee_id.encode('ascii')[:10].ljust(10, b'\x00'))
|
||||
|
||||
# 工具系列号 (10字节)
|
||||
data.extend(tool_sn.encode('ascii')[:10].ljust(10, b'\x00'))
|
||||
|
||||
# 控制器系列号 (10字节)
|
||||
data.extend(controller_sn.encode('ascii')[:10].ljust(10, b'\x00'))
|
||||
|
||||
# 时间
|
||||
data.extend(struct.pack('>H', now.year)) # 年(2字节)
|
||||
data.append(now.month) # 月
|
||||
data.append(now.day) # 日
|
||||
data.append(now.hour) # 时
|
||||
data.append(now.minute) # 分
|
||||
data.append(now.second) # 秒
|
||||
|
||||
# 参数模式
|
||||
data.append(mode)
|
||||
|
||||
# 目标扭矩 (2字节)
|
||||
data.extend(struct.pack('>H', target_torque))
|
||||
|
||||
# 扭矩下限 (2字节)
|
||||
data.extend(struct.pack('>H', torque_min))
|
||||
|
||||
# 扭矩上限 (2字节)
|
||||
data.extend(struct.pack('>H', torque_max))
|
||||
|
||||
# 目标角度 (2字节)
|
||||
data.extend(struct.pack('>H', angle_value))
|
||||
|
||||
# 角度上限 (2字节)
|
||||
data.extend(struct.pack('>H', angle_max_value))
|
||||
|
||||
# 角度下限 (2字节)
|
||||
data.extend(struct.pack('>H', angle_min_value))
|
||||
|
||||
# 计算并添加校验码
|
||||
checksum = self._calculate_checksum(data)
|
||||
data.append(checksum)
|
||||
|
||||
# 打印报文信息
|
||||
mode_str = "M1(扭矩模式)" if mode == 1 else "M2(角度模式)"
|
||||
print(f"\n设定扭矩参数:")
|
||||
print(f" 模式: {mode_str}")
|
||||
print(f" 目标扭矩: {target_torque} Nm")
|
||||
print(f" 扭矩范围: {torque_min}-{torque_max} Nm")
|
||||
print(f" 目标角度: {target_angle}°")
|
||||
print(f" 角度范围: {angle_min}-{angle_max}°")
|
||||
print(f" 报文: {data.hex(' ').upper()}")
|
||||
|
||||
# 发送命令
|
||||
self._send_command(bytes(data))
|
||||
|
||||
return bytes(data)
|
||||
|
||||
def start_wrench(self, direction: int = 1) -> bool:
|
||||
"""
|
||||
启动扳手
|
||||
:param direction: 0=停止, 1=正转, 2=反转
|
||||
:return: 是否成功
|
||||
"""
|
||||
data = bytearray([
|
||||
0xC5, 0xC5, # 报文头
|
||||
self.address, # 地址码
|
||||
0x01, # 功能码
|
||||
0xFF, 0xFF, # 保留
|
||||
0x01, # 数据长度
|
||||
direction # 数据内容
|
||||
])
|
||||
data.append(self._calculate_checksum(data))
|
||||
|
||||
action = {0: "停止", 1: "正转启动", 2: "反转启动"}
|
||||
print(f"\n{action.get(direction, '未知操作')}扳手...")
|
||||
return self._send_command(bytes(data))
|
||||
|
||||
def parse_result(self, response: bytes) -> dict:
|
||||
"""
|
||||
解析执行结果反馈(功能码0x15)
|
||||
:param response: 接收到的响应报文
|
||||
:return: 解析后的结果字典
|
||||
"""
|
||||
if not response or len(response) < 44:
|
||||
return {"error": "响应数据不完整"}
|
||||
|
||||
# 验证报文头和功能码
|
||||
if response[0:2] != b'\xC5\xC5':
|
||||
return {"error": "报文头错误"}
|
||||
|
||||
if response[3] != 0x15:
|
||||
return {"error": f"功能码错误,期望0x15,实际0x{response[3]:02X}"}
|
||||
|
||||
# 解析结果状态
|
||||
status_code = response[4]
|
||||
status_map = {
|
||||
0x00: "成功-OK",
|
||||
0x01: "成功-扭矩到达",
|
||||
0x02: "成功-位置到达",
|
||||
0x10: "失败-NG",
|
||||
0x11: "失败-小于扭矩下限",
|
||||
0x12: "失败-大于扭矩上限",
|
||||
0x13: "失败-打滑",
|
||||
0x14: "失败-小于角度下限",
|
||||
0x15: "失败-大于角度上限",
|
||||
0x16: "失败-2次拧紧"
|
||||
}
|
||||
|
||||
status = status_map.get(status_code, f"未知状态(0x{status_code:02X})")
|
||||
is_success = status_code in [0x00, 0x01, 0x02]
|
||||
|
||||
# 测试模式:失败也算成功
|
||||
if self.test_mode and not is_success:
|
||||
print(f"⚠️ 测试模式:将失败状态({status})转换为成功")
|
||||
is_success = True
|
||||
status = f"测试模式-{status}"
|
||||
|
||||
# 解析其他数据
|
||||
employee_id = response[7:17].decode('ascii', errors='ignore').rstrip('\x00')
|
||||
bolt_no = struct.unpack('>H', response[17:19])[0]
|
||||
|
||||
year = struct.unpack('>H', response[19:21])[0]
|
||||
month = response[21]
|
||||
day = response[22]
|
||||
hour = response[23]
|
||||
minute = response[24]
|
||||
second = response[25]
|
||||
timestamp = f"{year}-{month:02d}-{day:02d} {hour:02d}:{minute:02d}:{second:02d}"
|
||||
|
||||
mode = "M1(扭矩模式)" if response[26] == 0x01 else "M2(角度模式)"
|
||||
|
||||
target_torque = struct.unpack('>H', response[27:29])[0]
|
||||
actual_torque = struct.unpack('>H', response[29:31])[0]
|
||||
target_angle = struct.unpack('>H', response[31:33])[0] / 10
|
||||
actual_angle = struct.unpack('>H', response[33:35])[0] / 10
|
||||
torque_max = struct.unpack('>H', response[35:37])[0]
|
||||
torque_min = struct.unpack('>H', response[37:39])[0]
|
||||
angle_max = struct.unpack('>H', response[39:41])[0] / 10
|
||||
angle_min = struct.unpack('>H', response[41:43])[0] / 10
|
||||
|
||||
result = {
|
||||
"success": is_success,
|
||||
"status": status,
|
||||
"status_code": f"0x{status_code:02X}",
|
||||
"employee_id": employee_id,
|
||||
"bolt_no": bolt_no,
|
||||
"timestamp": timestamp,
|
||||
"mode": mode,
|
||||
"target_torque": target_torque,
|
||||
"actual_torque": actual_torque,
|
||||
"target_angle": target_angle,
|
||||
"actual_angle": actual_angle,
|
||||
"torque_range": f"{torque_min}-{torque_max} Nm",
|
||||
"angle_range": f"{angle_min}-{angle_max}°"
|
||||
}
|
||||
|
||||
return result
|
||||
|
||||
def print_result(self, result: dict):
|
||||
"""打印结果"""
|
||||
if "error" in result:
|
||||
print(f"\n❌ 错误: {result['error']}")
|
||||
return
|
||||
|
||||
print("\n" + "="*50)
|
||||
if result["success"]:
|
||||
print("✅ 执行成功!")
|
||||
else:
|
||||
print("❌ 执行失败!")
|
||||
print("="*50)
|
||||
print(f"状态: {result['status']} ({result['status_code']})")
|
||||
print(f"时间: {result['timestamp']}")
|
||||
print(f"员工号: {result['employee_id']}")
|
||||
print(f"螺栓号: {result['bolt_no']}")
|
||||
print(f"模式: {result['mode']}")
|
||||
print(f"目标扭矩: {result['target_torque']} Nm")
|
||||
print(f"实际扭矩: {result['actual_torque']} Nm")
|
||||
print(f"扭矩范围: {result['torque_range']}")
|
||||
print(f"目标角度: {result['target_angle']}°")
|
||||
print(f"实际角度: {result['actual_angle']}°")
|
||||
print(f"角度范围: {result['angle_range']}")
|
||||
print("="*50)
|
||||
|
||||
def wait_for_result(self, timeout: float = None) -> Optional[dict]:
|
||||
"""
|
||||
等待并解析执行结果
|
||||
:param timeout: 超时时间(秒),默认使用配置文件中的值
|
||||
:return: 解析后的结果字典
|
||||
"""
|
||||
if timeout is None:
|
||||
timeout = self.timeout
|
||||
|
||||
print(f"\n等待扳手执行结果(超时{timeout}秒)...")
|
||||
response = self._receive_response(timeout)
|
||||
|
||||
if response:
|
||||
print(f"收到响应: {response.hex(' ').upper()}")
|
||||
result = self.parse_result(response)
|
||||
self.print_result(result)
|
||||
return result
|
||||
else:
|
||||
print("未收到响应")
|
||||
return None
|
||||
|
||||
|
||||
# 使用示例
|
||||
if __name__ == "__main__":
|
||||
# 创建控制器实例(自动从config.json加载配置)
|
||||
wrench = WrenchController()
|
||||
|
||||
# 连接到扳手
|
||||
if wrench.connect():
|
||||
try:
|
||||
# 1. 启用远程控制
|
||||
wrench.enable_remote_control(True)
|
||||
|
||||
# 2. 设定扭矩参数 - M1模式,目标扭矩300Nm
|
||||
wrench.set_torque_parameters(
|
||||
target_torque=300,
|
||||
mode=1, # M1模式
|
||||
torque_tolerance=0.10, # ±10%
|
||||
angle_max=360,
|
||||
angle_min=1
|
||||
)
|
||||
|
||||
# 3. 启动扳手
|
||||
wrench.start_wrench(direction=1)
|
||||
|
||||
# 4. 等待并获取执行结果(使用配置文件中的超时时间)
|
||||
result = wrench.wait_for_result()
|
||||
|
||||
# 5. 根据结果进行后续处理
|
||||
if result and result.get("success"):
|
||||
print("\n✅ 扳手操作成功完成!")
|
||||
else:
|
||||
print("\n❌ 扳手操作失败!")
|
||||
|
||||
finally:
|
||||
# 断开连接
|
||||
wrench.disconnect()
|
||||
else:
|
||||
print("无法连接到扳手")
|
||||
|
|
@ -0,0 +1,447 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
电动扳手自动拧紧界面程序
|
||||
支持多螺栓自动拧紧流程
|
||||
"""
|
||||
|
||||
import tkinter as tk
|
||||
from tkinter import ttk, messagebox
|
||||
import json
|
||||
import threading
|
||||
import time
|
||||
from datetime import datetime
|
||||
from wrench_controller import WrenchController
|
||||
|
||||
|
||||
class WrenchGUI:
|
||||
"""电动扳手图形界面"""
|
||||
|
||||
def __init__(self, root):
|
||||
self.root = root
|
||||
self.root.title("电动扳手自动拧紧系统")
|
||||
self.root.geometry("900x750")
|
||||
|
||||
# 数据
|
||||
self.work_order = None
|
||||
self.current_bolt_index = 0
|
||||
self.wrench = None
|
||||
self.is_running = False
|
||||
self.thread = None
|
||||
self.test_mode = False
|
||||
|
||||
# 创建界面
|
||||
self._create_widgets()
|
||||
|
||||
# 加载工单数据
|
||||
self.load_work_order()
|
||||
|
||||
# 检查测试模式
|
||||
self.check_test_mode()
|
||||
|
||||
def _create_widgets(self):
|
||||
"""创建界面组件"""
|
||||
# 标题
|
||||
title_frame = tk.Frame(self.root, bg="#2c3e50", height=60)
|
||||
title_frame.pack(fill=tk.X)
|
||||
title_frame.pack_propagate(False)
|
||||
|
||||
title_label = tk.Label(
|
||||
title_frame,
|
||||
text="电动扳手自动拧紧系统",
|
||||
font=("微软雅黑", 20, "bold"),
|
||||
fg="white",
|
||||
bg="#2c3e50"
|
||||
)
|
||||
title_label.pack(pady=15)
|
||||
|
||||
# 测试模式指示器
|
||||
self.test_mode_label = tk.Label(
|
||||
title_frame,
|
||||
text="",
|
||||
font=("微软雅黑", 10, "bold"),
|
||||
fg="#f39c12",
|
||||
bg="#2c3e50"
|
||||
)
|
||||
self.test_mode_label.place(relx=1.0, rely=0.5, anchor=tk.E, x=-20)
|
||||
|
||||
# 工单信息区域
|
||||
info_frame = tk.LabelFrame(self.root, text="工单信息", font=("微软雅黑", 12), padx=10, pady=10)
|
||||
info_frame.pack(fill=tk.X, padx=10, pady=10)
|
||||
|
||||
self.order_id_label = tk.Label(info_frame, text="工单号: --", font=("微软雅黑", 10))
|
||||
self.order_id_label.grid(row=0, column=0, sticky=tk.W, padx=5, pady=2)
|
||||
|
||||
self.product_label = tk.Label(info_frame, text="产品: --", font=("微软雅黑", 10))
|
||||
self.product_label.grid(row=0, column=1, sticky=tk.W, padx=5, pady=2)
|
||||
|
||||
self.station_label = tk.Label(info_frame, text="工位: --", font=("微软雅黑", 10))
|
||||
self.station_label.grid(row=1, column=0, sticky=tk.W, padx=5, pady=2)
|
||||
|
||||
self.operator_label = tk.Label(info_frame, text="操作员: --", font=("微软雅黑", 10))
|
||||
self.operator_label.grid(row=1, column=1, sticky=tk.W, padx=5, pady=2)
|
||||
|
||||
# 当前螺栓信息
|
||||
current_frame = tk.LabelFrame(self.root, text="当前螺栓", font=("微软雅黑", 12), padx=10, pady=10)
|
||||
current_frame.pack(fill=tk.X, padx=10, pady=10)
|
||||
|
||||
self.current_bolt_label = tk.Label(
|
||||
current_frame,
|
||||
text="等待开始...",
|
||||
font=("微软雅黑", 16, "bold"),
|
||||
fg="#2c3e50"
|
||||
)
|
||||
self.current_bolt_label.pack(pady=5)
|
||||
|
||||
self.current_torque_label = tk.Label(
|
||||
current_frame,
|
||||
text="目标扭矩: -- Nm",
|
||||
font=("微软雅黑", 12)
|
||||
)
|
||||
self.current_torque_label.pack(pady=2)
|
||||
|
||||
self.current_status_label = tk.Label(
|
||||
current_frame,
|
||||
text="状态: 待机",
|
||||
font=("微软雅黑", 12),
|
||||
fg="#7f8c8d"
|
||||
)
|
||||
self.current_status_label.pack(pady=2)
|
||||
|
||||
# 进度条
|
||||
progress_frame = tk.Frame(self.root, padx=10, pady=5)
|
||||
progress_frame.pack(fill=tk.X, padx=10)
|
||||
|
||||
self.progress_label = tk.Label(progress_frame, text="总进度: 0/0", font=("微软雅黑", 10))
|
||||
self.progress_label.pack(anchor=tk.W)
|
||||
|
||||
self.progress_bar = ttk.Progressbar(progress_frame, mode='determinate', length=400)
|
||||
self.progress_bar.pack(fill=tk.X, pady=5)
|
||||
|
||||
# 螺栓列表
|
||||
list_frame = tk.LabelFrame(self.root, text="螺栓列表", font=("微软雅黑", 12), padx=10, pady=10)
|
||||
list_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
|
||||
|
||||
# 创建表格
|
||||
columns = ("序号", "名称", "扭矩(Nm)", "状态", "实际扭矩", "时间")
|
||||
self.tree = ttk.Treeview(list_frame, columns=columns, show="headings", height=6)
|
||||
|
||||
# 设置列
|
||||
self.tree.heading("序号", text="序号")
|
||||
self.tree.heading("名称", text="名称")
|
||||
self.tree.heading("扭矩(Nm)", text="扭矩(Nm)")
|
||||
self.tree.heading("状态", text="状态")
|
||||
self.tree.heading("实际扭矩", text="实际扭矩(Nm)")
|
||||
self.tree.heading("时间", text="完成时间")
|
||||
|
||||
self.tree.column("序号", width=60, anchor=tk.CENTER)
|
||||
self.tree.column("名称", width=150, anchor=tk.W)
|
||||
self.tree.column("扭矩(Nm)", width=100, anchor=tk.CENTER)
|
||||
self.tree.column("状态", width=100, anchor=tk.CENTER)
|
||||
self.tree.column("实际扭矩", width=120, anchor=tk.CENTER)
|
||||
self.tree.column("时间", width=150, anchor=tk.CENTER)
|
||||
|
||||
# 滚动条
|
||||
scrollbar = ttk.Scrollbar(list_frame, orient=tk.VERTICAL, command=self.tree.yview)
|
||||
self.tree.configure(yscrollcommand=scrollbar.set)
|
||||
|
||||
self.tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
|
||||
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
|
||||
|
||||
# 配置标签颜色
|
||||
self.tree.tag_configure("pending", foreground="#7f8c8d")
|
||||
self.tree.tag_configure("running", foreground="#3498db", font=("微软雅黑", 10, "bold"))
|
||||
self.tree.tag_configure("success", foreground="#27ae60", font=("微软雅黑", 10, "bold"))
|
||||
self.tree.tag_configure("failed", foreground="#e74c3c")
|
||||
|
||||
# 控制按钮
|
||||
button_frame = tk.Frame(self.root, padx=10, pady=10)
|
||||
button_frame.pack(fill=tk.X)
|
||||
|
||||
self.start_button = tk.Button(
|
||||
button_frame,
|
||||
text="开始拧紧",
|
||||
font=("微软雅黑", 12, "bold"),
|
||||
bg="#27ae60",
|
||||
fg="white",
|
||||
width=15,
|
||||
height=1,
|
||||
cursor="hand2",
|
||||
command=self.start_process
|
||||
)
|
||||
self.start_button.pack(side=tk.LEFT, padx=5)
|
||||
|
||||
self.stop_button = tk.Button(
|
||||
button_frame,
|
||||
text="停止",
|
||||
font=("微软雅黑", 12, "bold"),
|
||||
bg="#e74c3c",
|
||||
fg="white",
|
||||
width=15,
|
||||
height=1,
|
||||
cursor="hand2",
|
||||
state=tk.DISABLED,
|
||||
command=self.stop_process
|
||||
)
|
||||
self.stop_button.pack(side=tk.LEFT, padx=5)
|
||||
|
||||
self.reload_button = tk.Button(
|
||||
button_frame,
|
||||
text="重新加载工单",
|
||||
font=("微软雅黑", 12),
|
||||
bg="#3498db",
|
||||
fg="white",
|
||||
width=15,
|
||||
height=1,
|
||||
cursor="hand2",
|
||||
command=self.load_work_order
|
||||
)
|
||||
self.reload_button.pack(side=tk.LEFT, padx=5)
|
||||
|
||||
# 日志区域
|
||||
log_frame = tk.LabelFrame(self.root, text="操作日志", font=("微软雅黑", 10), padx=5, pady=5)
|
||||
log_frame.pack(fill=tk.X, padx=10, pady=(0, 10))
|
||||
|
||||
self.log_text = tk.Text(log_frame, height=5, font=("Consolas", 9))
|
||||
self.log_text.pack(fill=tk.BOTH, expand=True)
|
||||
|
||||
log_scrollbar = ttk.Scrollbar(log_frame, orient=tk.VERTICAL, command=self.log_text.yview)
|
||||
self.log_text.configure(yscrollcommand=log_scrollbar.set)
|
||||
log_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
|
||||
|
||||
def check_test_mode(self):
|
||||
"""检查测试模式"""
|
||||
try:
|
||||
with open("config.json", "r", encoding="utf-8") as f:
|
||||
config = json.load(f)
|
||||
self.test_mode = config.get("test_mode", {}).get("enabled", False)
|
||||
|
||||
if self.test_mode:
|
||||
self.test_mode_label.config(text="⚠️ 测试模式")
|
||||
self.log("⚠️ 测试模式已启用:失败也算成功", "WARN")
|
||||
else:
|
||||
self.test_mode_label.config(text="")
|
||||
except Exception as e:
|
||||
self.log(f"读取配置失败: {e}", "ERROR")
|
||||
|
||||
def log(self, message, level="INFO"):
|
||||
"""添加日志"""
|
||||
timestamp = datetime.now().strftime("%H:%M:%S")
|
||||
log_message = f"[{timestamp}] [{level}] {message}\n"
|
||||
self.log_text.insert(tk.END, log_message)
|
||||
self.log_text.see(tk.END)
|
||||
print(log_message.strip())
|
||||
|
||||
def load_work_order(self):
|
||||
"""加载工单数据"""
|
||||
try:
|
||||
with open("work_order.json", "r", encoding="utf-8") as f:
|
||||
self.work_order = json.load(f)
|
||||
|
||||
# 更新界面
|
||||
self.order_id_label.config(text=f"工单号: {self.work_order['order_id']}")
|
||||
self.product_label.config(text=f"产品: {self.work_order['product_name']}")
|
||||
self.station_label.config(text=f"工位: {self.work_order['station']}")
|
||||
self.operator_label.config(text=f"操作员: {self.work_order['operator']}")
|
||||
|
||||
# 更新螺栓列表
|
||||
self.tree.delete(*self.tree.get_children())
|
||||
for bolt in self.work_order['bolts']:
|
||||
self.tree.insert("", tk.END, values=(
|
||||
bolt['bolt_id'],
|
||||
bolt['name'],
|
||||
bolt['target_torque'],
|
||||
"待拧紧",
|
||||
"--",
|
||||
"--"
|
||||
), tags=("pending",))
|
||||
|
||||
# 更新进度
|
||||
total = len(self.work_order['bolts'])
|
||||
self.progress_label.config(text=f"总进度: 0/{total}")
|
||||
self.progress_bar['maximum'] = total
|
||||
self.progress_bar['value'] = 0
|
||||
|
||||
self.current_bolt_index = 0
|
||||
self.log(f"成功加载工单: {self.work_order['order_id']}, 共{total}个螺栓")
|
||||
|
||||
except Exception as e:
|
||||
messagebox.showerror("错误", f"加载工单失败: {e}")
|
||||
self.log(f"加载工单失败: {e}", "ERROR")
|
||||
|
||||
def start_process(self):
|
||||
"""开始拧紧流程"""
|
||||
if not self.work_order:
|
||||
messagebox.showwarning("警告", "请先加载工单数据")
|
||||
return
|
||||
|
||||
if self.is_running:
|
||||
return
|
||||
|
||||
self.is_running = True
|
||||
self.start_button.config(state=tk.DISABLED)
|
||||
self.stop_button.config(state=tk.NORMAL)
|
||||
self.reload_button.config(state=tk.DISABLED)
|
||||
|
||||
# 启动工作线程
|
||||
self.thread = threading.Thread(target=self.work_thread, daemon=True)
|
||||
self.thread.start()
|
||||
|
||||
self.log("开始自动拧紧流程")
|
||||
|
||||
def stop_process(self):
|
||||
"""停止拧紧流程"""
|
||||
self.is_running = False
|
||||
self.start_button.config(state=tk.NORMAL)
|
||||
self.stop_button.config(state=tk.DISABLED)
|
||||
self.reload_button.config(state=tk.NORMAL)
|
||||
self.log("用户停止流程")
|
||||
|
||||
def work_thread(self):
|
||||
"""工作线程"""
|
||||
try:
|
||||
# 连接扳手
|
||||
self.log("正在连接扳手...")
|
||||
self.wrench = WrenchController()
|
||||
|
||||
if not self.wrench.connect():
|
||||
self.log("❌ 连接扳手失败,请检查IP和端口配置", "ERROR")
|
||||
self.root.after(0, lambda: messagebox.showerror(
|
||||
"连接失败",
|
||||
f"无法连接到扳手\nIP: {self.wrench.host}\n端口: {self.wrench.port}\n\n请检查:\n1. 扳手是否开机\n2. 网络连接是否正常\n3. IP和端口配置是否正确"
|
||||
))
|
||||
self.stop_process()
|
||||
return
|
||||
|
||||
self.log(f"✅ 已连接到扳手: {self.wrench.host}:{self.wrench.port}", "SUCCESS")
|
||||
|
||||
# 启用远程控制
|
||||
self.log("正在启用远程控制...")
|
||||
if not self.wrench.enable_remote_control(True):
|
||||
self.log("❌ 启用远程控制失败", "ERROR")
|
||||
self.root.after(0, lambda: messagebox.showerror("错误", "启用远程控制失败"))
|
||||
self.stop_process()
|
||||
return
|
||||
|
||||
self.log("✅ 已启用远程控制", "SUCCESS")
|
||||
|
||||
# 遍历所有螺栓
|
||||
bolts = self.work_order['bolts']
|
||||
for index, bolt in enumerate(bolts):
|
||||
if not self.is_running:
|
||||
break
|
||||
|
||||
self.current_bolt_index = index
|
||||
self.process_bolt(bolt, index)
|
||||
|
||||
# 完成
|
||||
if self.is_running:
|
||||
self.log("所有螺栓拧紧完成!", "SUCCESS")
|
||||
self.root.after(0, lambda: messagebox.showinfo("完成", "所有螺栓已成功拧紧!"))
|
||||
|
||||
except Exception as e:
|
||||
self.log(f"流程异常: {e}", "ERROR")
|
||||
finally:
|
||||
if self.wrench:
|
||||
self.wrench.disconnect()
|
||||
self.root.after(0, self.stop_process)
|
||||
|
||||
def process_bolt(self, bolt, index):
|
||||
"""处理单个螺栓"""
|
||||
bolt_id = bolt['bolt_id']
|
||||
bolt_name = bolt['name']
|
||||
target_torque = bolt['target_torque']
|
||||
|
||||
self.log(f"开始拧紧: [{bolt_id}] {bolt_name}, 目标扭矩: {target_torque} Nm")
|
||||
|
||||
# 更新界面
|
||||
self.root.after(0, lambda: self.update_current_bolt(bolt, "拧紧中..."))
|
||||
self.root.after(0, lambda: self.update_tree_item(index, "拧紧中", "running"))
|
||||
|
||||
# 设定参数
|
||||
self.log(f"设定参数: 扭矩={target_torque}Nm, 模式=M{bolt.get('mode', 1)}")
|
||||
self.wrench.set_torque_parameters(
|
||||
target_torque=target_torque,
|
||||
mode=bolt.get('mode', 1),
|
||||
torque_tolerance=bolt.get('torque_tolerance', 0.10),
|
||||
angle_min=bolt.get('angle_min', 1),
|
||||
angle_max=bolt.get('angle_max', 360)
|
||||
)
|
||||
|
||||
# 循环尝试,直到成功
|
||||
attempt = 0
|
||||
while self.is_running:
|
||||
attempt += 1
|
||||
self.log(f"第 {attempt} 次尝试拧紧螺栓 {bolt_id}")
|
||||
|
||||
# 启动扳手
|
||||
self.log("发送启动命令...")
|
||||
if not self.wrench.start_wrench(direction=1):
|
||||
self.log("❌ 发送启动命令失败", "ERROR")
|
||||
time.sleep(1)
|
||||
continue
|
||||
|
||||
# 等待结果
|
||||
self.log("等待扳手响应...")
|
||||
result = self.wrench.wait_for_result()
|
||||
|
||||
if result and result.get("success"):
|
||||
# 成功
|
||||
actual_torque = result.get("actual_torque", 0)
|
||||
timestamp = datetime.now().strftime("%H:%M:%S")
|
||||
|
||||
self.log(f"✅ 螺栓 {bolt_id} 拧紧成功! 实际扭矩: {actual_torque} Nm", "SUCCESS")
|
||||
|
||||
# 更新界面
|
||||
self.root.after(0, lambda at=actual_torque, ts=timestamp: self.update_tree_item(
|
||||
index, "成功", "success", at, ts
|
||||
))
|
||||
self.root.after(0, lambda: self.update_progress(index + 1))
|
||||
|
||||
break
|
||||
else:
|
||||
# 失败,继续尝试
|
||||
if result:
|
||||
self.log(f"❌ 螺栓 {bolt_id} 拧紧失败: {result.get('status', '未知错误')}, 继续重试...", "WARN")
|
||||
else:
|
||||
self.log(f"❌ 螺栓 {bolt_id} 无响应, 继续重试...", "WARN")
|
||||
|
||||
time.sleep(1) # 等待1秒后重试
|
||||
|
||||
# 完成后等待一下
|
||||
time.sleep(0.5)
|
||||
|
||||
def update_current_bolt(self, bolt, status):
|
||||
"""更新当前螺栓显示"""
|
||||
self.current_bolt_label.config(text=f"[{bolt['bolt_id']}] {bolt['name']}")
|
||||
self.current_torque_label.config(text=f"目标扭矩: {bolt['target_torque']} Nm")
|
||||
self.current_status_label.config(text=f"状态: {status}")
|
||||
|
||||
def update_tree_item(self, index, status, tag, actual_torque="--", timestamp="--"):
|
||||
"""更新表格项"""
|
||||
items = self.tree.get_children()
|
||||
if index < len(items):
|
||||
item = items[index]
|
||||
values = list(self.tree.item(item)['values'])
|
||||
values[3] = status
|
||||
if actual_torque != "--":
|
||||
values[4] = actual_torque
|
||||
if timestamp != "--":
|
||||
values[5] = timestamp
|
||||
self.tree.item(item, values=values, tags=(tag,))
|
||||
|
||||
def update_progress(self, completed):
|
||||
"""更新进度"""
|
||||
total = len(self.work_order['bolts'])
|
||||
self.progress_label.config(text=f"总进度: {completed}/{total}")
|
||||
self.progress_bar['value'] = completed
|
||||
|
||||
|
||||
def main():
|
||||
root = tk.Tk()
|
||||
app = WrenchGUI(root)
|
||||
root.mainloop()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -0,0 +1,424 @@
|
|||
App3定扭矩扳手通讯协议(C协议)
|
||||
|
||||
一、通讯参数说明
|
||||
1、装置端与上位端以TCP/IP连接传输报文,装置端可以是Server端,也可以是Client端
|
||||
|
||||
二、报文格式说明
|
||||
报文格式
|
||||
报文头
|
||||
2字节 地址码
|
||||
1字节 功能码
|
||||
1字节 保留
|
||||
2字节 数据长度
|
||||
1字节 数据内容
|
||||
校验码(累加和)
|
||||
1字节
|
||||
0xC5 0xC5 0x01 0x01 0xFF 0xFF XX XX… XX XX
|
||||
|
||||
报文头: 0xC5 0xC5
|
||||
地址码: 表示每台装置的地址
|
||||
功能码:表示该帧报文的功能类型定义
|
||||
保留:保留2字节今后扩展使用
|
||||
数据长度:表示后面数据内容的长度,不包含报文头、地址码、功能码、保留字节和校验码
|
||||
数据内容:根据功能码有不同数据定义
|
||||
校验码:从报文头开始到数据内容结束的单字节累加和
|
||||
|
||||
三、功能码说明
|
||||
1、功能吗0x11:启停远程控制,启用远程控制后,锂电扳手才会接受远程启停控制(App->扳手)
|
||||
2、功能码0x01:扳手启停控制 (App->扳手) 此指令在启动远程控制0x11帧后生效
|
||||
3、功能码0x06:作为接收方应答发送方使用
|
||||
4、功能码0x10 参数设置 (App -> 扳手)
|
||||
5、功能码0x12: 每秒钟扭矩角度数据(扳手->App)
|
||||
6、功能吗0x15: 运行结果反馈,在锂电扳手每次运行结束后发出该报文(扳手->App)
|
||||
7、功能码0x21: 扳手请求对时(扳手->App)
|
||||
8、功能码0x22: App发送对时报文(App -> 扳手)
|
||||
9、功能码 0x33: 设备延时自动关机心跳报文(App -> 扳手), 每分钟向设备发送,保证设备不自动断电
|
||||
10、功能码:0x44:设备GPS定位,周期发送(上电后一直发送)包含信息:是否有效、经度、纬度、高度
|
||||
11、功能码 0x04:电池电量百分比数据 (此帧可作为心跳包定时发送)
|
||||
12、功能码:0x50: 脉冲、离合、冲击扳手参数设置
|
||||
13、功能码:0x55: 脉冲、离合、冲击扳手执行结果反馈
|
||||
脉冲、离合、冲击扳手运行过程扭矩数据同0x12帧
|
||||
14、功能码:0x23:扳手发送复位螺母个数选择项
|
||||
15、功能码:0x25查询扳手SN码
|
||||
16、功能码:0x26 应答扳手SN码
|
||||
17、功能码:0x17: 发送结果反馈帧0x15确认(App -> 扳手)
|
||||
18、功能码:0x05: 发状态到APP
|
||||
|
||||
四、报文内容
|
||||
1、功能码0x11:启动/停止远程控制
|
||||
字节序号
|
||||
1-2 报文头 0xC5 0xC5
|
||||
3 地址码 0x01
|
||||
4 功能码 0x11
|
||||
5-6 保留 0xFF 0xFF
|
||||
7 数据长度 0x01
|
||||
8 数据内容 0xXX 0x00:停止远程控制
|
||||
0x01:启用远程控制
|
||||
9 校验码(累加和) 0xXX
|
||||
报文示例:
|
||||
C5 C5 01 11 FF FF 01 01 9C
|
||||
|
||||
2、功能码0x01:启停控制
|
||||
字节序号
|
||||
1-2 报文头 0xC5 0xC5
|
||||
3 地址码 0x01
|
||||
4 功能码 0x01
|
||||
5-6 保留 0xFF 0xFF
|
||||
7 数据长度 0x01
|
||||
8 数据内容 0xXX 0x00:停止STOP
|
||||
0x01:正转启动RUN
|
||||
0x02:反转启动RUN
|
||||
9 校验码(累加和) 0xXX
|
||||
报文示例:
|
||||
C5 C5 01 01 FF FF 01 02 8D
|
||||
|
||||
3、功能码0x06: 应答控制
|
||||
字节序号
|
||||
1-2 报文头 0xC5 0xC5
|
||||
3 地址码 0x01
|
||||
4 功能码 0x06
|
||||
5-6 保留 0xFF 0xFF
|
||||
7 数据长度 0x01
|
||||
8 数据内容 0xXX 0x00:应答ACK
|
||||
0x01:应答NAK
|
||||
9 校验码(累加和) 1字节
|
||||
报文示例:
|
||||
C5 C5 01 06 FF FF 01 00 90
|
||||
|
||||
4、 功能码0x10: 参数设置(App -> 扳手)
|
||||
字节序号
|
||||
1-2 报文头 0xC5 0xC5
|
||||
3 地址码 0x01
|
||||
4 功能码 0x10
|
||||
5-6 反退角 0xFF 0xFF
|
||||
7 数据长度 0x50
|
||||
8-17 产品ID 10字节 ASCII码,不足补0
|
||||
18-37 工位名称 20字节 中英字符,不足补0
|
||||
38-47 员工号 10字节 ASCII码,不足补0
|
||||
48-57 工具系列号 10字节 ASCII码, 不足补0
|
||||
58-67 控制器系列号 10字节 ASCII码, 不足补0
|
||||
68-69 时间-年 2字节
|
||||
70 时间-月 1字节
|
||||
71 时间-日 1字节
|
||||
72 时间-小时 1字节
|
||||
73 时间-分钟 1字节
|
||||
74 时间-秒 1字节
|
||||
75 参数模式 1字节 M1模式:0x01 M2模式:0x02
|
||||
76-77 目标扭矩高位
|
||||
目标扭矩低位 2字节 此参数为目标扭矩 此参数为预设扭矩
|
||||
78-79 扭矩下限高位
|
||||
扭矩下限低位 2字节 目标扭矩下限值
|
||||
次值为=目标扭矩*(1-A)
|
||||
A为扭矩偏差百分比 扭矩下限值
|
||||
目标角度执行过程中超出扭矩上下限值即停机;如填0,不受上下限扭矩控制
|
||||
80-81 扭矩上限高位
|
||||
扭矩上限低位 2字节 目标扭矩上限值
|
||||
次值为=目标扭矩*(1+A)
|
||||
A为扭矩偏差百分比 扭矩上限值
|
||||
目标角度执行过程中超出扭矩上下限值即停机;如填0,不受上下限扭矩控制
|
||||
82-83 目标角度高位
|
||||
目标角度低位 2字节
|
||||
0x00
|
||||
0x00 目标角度值
|
||||
84-85 角度上限高位
|
||||
角度上限低位 2字节
|
||||
角度上限值
|
||||
实际转角超出角度上、下限值即停机;如填0,不受角度上下限控制 目标角度上限值
|
||||
86-87 角度下限高位
|
||||
角度下限低位 2字节
|
||||
角度下限值
|
||||
实际转角超出角度上、下限值即停机;如填0,不受角度上下限控制 目标角度下限值
|
||||
88 校验码(累加和) 1字节
|
||||
注:此帧中所有角度设定值需要乘10后填入协议, 例如需要设置目标角度20°, 协议中应填写目标角度 00 C8
|
||||
报文示例1:
|
||||
时间:2024/9/30 10:01:08 M1模式 目标扭矩500,扭矩偏差A=10%(450Nm-550Nm),角度上限360,角度下限1
|
||||
C5 C5 01 10 FF FF 50 30 30 30 30 30 30 30 30 30 30 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 32 32 32 32 32 32 32 32 32 32 30 30 30 30 30 30 30 30 30 30 33 33 33 33 33 33 33 33 33 33 07 E8 09 1E 0A 01 08 01 01 F4 01 C2 02 26 00 00 0E 10 00 0A A1
|
||||
|
||||
报文示例2:
|
||||
时间:2024/9/30 10:01:08 M2模式 目标扭矩180,扭矩下限200Nm,扭矩上限400NM,目标角度值90,目标角度上限360,目标角度下限1
|
||||
C5 C5 01 10 FF FF 50 30 30 30 30 30 30 30 30 30 30 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 32 32 32 32 32 32 32 32 32 32 30 30 30 30 30 30 30 30 30 30 33 33 33 33 33 33 33 33 33 33 07 E8 09 1E 0A 01 08 02 00 B4 00 C8 01 F4 03 84 0E 10 00 0A BA
|
||||
|
||||
|
||||
5、功能码0x12: 每秒钟扭矩角度数据
|
||||
字节序号
|
||||
1-2 报文头 0xC5 0xC5
|
||||
3 地址码 0x01
|
||||
4 功能码 0x12
|
||||
5-6 保留 0xFF 0xFF
|
||||
7 数据长度 12+n*4
|
||||
8-17 员工号 10字节 ASCII码
|
||||
18-19 螺栓号 2字节
|
||||
20-21 第1个点扭矩值高位
|
||||
第1个点扭矩值低位
|
||||
22-23 第1个点角度值高位
|
||||
第1个点角度值低位
|
||||
24-25 第2个点扭矩值高位
|
||||
第2个点扭矩值低位
|
||||
26-27 第2个点角度值高位
|
||||
第2个点角度值低位
|
||||
……
|
||||
第n个点扭矩值高位
|
||||
第n个点扭矩值低位
|
||||
第n个点角度值高位
|
||||
第n个点角度值低位
|
||||
校验码(累加和) 1字节
|
||||
报文示例:
|
||||
螺栓号:0001,第1点扭矩值0,角度值0,第2点扭矩值16,角度值2,第3点扭矩值49,角度值6,第4点扭矩值66,角度值10,第5点扭矩值85,角度值17
|
||||
C5 C5 01 12 FF FF 20 30 30 30 30 30 30 30 30 30 30 00 01 00 00 00 00 00 10 00 02 00 31 00 06 00 42 00 0A 00 55 00 11 97
|
||||
|
||||
|
||||
6、功能码0x15: 执行结果反馈
|
||||
字节序号
|
||||
1-2 报文头 0xC5 0xC5
|
||||
3 地址码 0x01
|
||||
4 功能码 0x15
|
||||
5 结果状态 0xFF 0x00 : 成功OK
|
||||
0x01: OK-扭矩到达
|
||||
0x02: OK-位置到达
|
||||
|
||||
0x10: 失败NG
|
||||
0x11: NG-小于扭矩下限
|
||||
0x12: NG-大于扭矩上限
|
||||
0x13: NG-打滑
|
||||
0x14: NG-小于角度下限
|
||||
0x15: NG-大于角度上限
|
||||
0x16: NG-2次拧紧
|
||||
6 保留 0xFF
|
||||
7 数据长度 0x24
|
||||
8-17 员工号 10字节 ASCII码
|
||||
18-19 螺栓号 2字节
|
||||
20-21 时间-年 2字节
|
||||
22 时间-月 1字节
|
||||
23 时间-日 1字节
|
||||
24 时间-小时 1字节
|
||||
25 时间-分钟 1字节
|
||||
26 时间-秒 1字节
|
||||
27 参数模式 1字节 M1模式:0x01 M2模式:0x02
|
||||
28-29 目标扭矩高位
|
||||
目标扭矩低位 2字节 目标扭矩值 预设扭矩实际值
|
||||
30-31 实际扭矩高位
|
||||
实际扭矩低位 2字节 目标扭矩实际值 最终扭矩实际值
|
||||
32-33 目标角度高位
|
||||
目标角度低位 2字节
|
||||
0x00
|
||||
0x00 目标角度设置值
|
||||
34-35 实际角度高位
|
||||
实际角度低位 2字节 角度实际值 目标角度实际值
|
||||
36-37 扭矩上限高位
|
||||
扭矩上限低位 2字节
|
||||
目标扭矩上限值
|
||||
最终扭矩上限值
|
||||
38-39 扭矩下限高位
|
||||
扭矩下限低位 2字节
|
||||
目标扭矩下限值
|
||||
最终扭矩下限值
|
||||
40-41 角度上限高位
|
||||
角度上限低位 2字节 角度上限值 目标角度上限值
|
||||
42-43 角度下限高位
|
||||
角度下限低位 2字节 角度下限值 目标角度下限值
|
||||
44 校验码(累加和) 1字节
|
||||
报文示例:
|
||||
时间:2024/9/30 10:01:08 M2模式 预设扭矩实际值181,最终扭矩实际值485,目标角度设置值45,目标角度实际值44,最终扭矩上限值500,最终扭矩下限值400, 目标角度上限 100,目标角度下限1.
|
||||
C5 C5 01 15 FF FF 24 30 30 30 30 30 30 30 30 30 30 00 01 07 E8 09 1E 0A 01 08 02 00 B5 01 E5 00 2D 00 2C 01 F4 01 90 00 64 00 01 AD
|
||||
|
||||
|
||||
7、功能码0x21: 扳手请求对时(扳手->App)
|
||||
字节序号
|
||||
1-2 报文头 0xC5 0xC5
|
||||
3 地址码 0x01
|
||||
4 功能码 0x21
|
||||
5-6 保留 0xFF 0xFF
|
||||
7 数据长度 0x01
|
||||
8 数据内容 0x01 0x01:请求对时
|
||||
9 校验码(累加和) 1字节
|
||||
报文示例:
|
||||
C5 C5 01 21 FF FF 01 01 AC
|
||||
|
||||
|
||||
8、 功能码0x22: App发送对时报文(App -> 扳手)
|
||||
字节序号
|
||||
1-2 报文头 0xC5 0xC5
|
||||
3 地址码 0x01
|
||||
4 功能码 0x22
|
||||
5-6 保留 0xFF 0xFF
|
||||
7 数据长度 0x07
|
||||
8-9 时间-年 2字节
|
||||
10 时间-月 1字节
|
||||
11 时间-日 1字节
|
||||
12 时间-小时 1字节
|
||||
13 时间-分钟 1字节
|
||||
14 时间-秒 1字节
|
||||
15 校验码(累加和) 1字节
|
||||
报文示例:
|
||||
C5 C5 01 22 FF FF 07 07 E8 09 1E 0A 01 08 DB
|
||||
|
||||
|
||||
9、功能码0x33: 设备延时自动关机心跳报文
|
||||
字节序号
|
||||
1-2 报文头 0xC5 0xC5
|
||||
3 地址码 0x01
|
||||
4 功能码 0x33
|
||||
5-6 保留 0xFF 0xFF
|
||||
7 数据长度 0x01
|
||||
8 数据内容 0x00
|
||||
9 校验码(累加和) 1字节
|
||||
报文示例:
|
||||
C5 C5 01 33 FF FF 01 00 BD
|
||||
|
||||
10、功能码0x44: GPS信息报文
|
||||
字节序号
|
||||
1-2 报文头 0xC5 0xC5
|
||||
3 地址码 0x01
|
||||
4 功能码 0x44
|
||||
5-6 保留 0xFF 0xFF
|
||||
7 数据长度
|
||||
(包含”是否有效”字节+”数据内容”)
|
||||
8 是否有效 0x01:有效
|
||||
0x00:无效
|
||||
数据内容(纬度,N/S,精度,E/W,高度,)
|
||||
3225.62461,N,11923.81795,E,14, ASCII格式,以‘,‘分隔
|
||||
N 校验码(累加和) 1字节
|
||||
|
||||
|
||||
|
||||
11、功能码 0x04:电池电量百分比数据
|
||||
报文头 地址码 功能码 保留 数据长度 数据内容 校验码(累加和)
|
||||
0xC5 0xC5 0x01 0x04 0xFF 0xFF 0x01 1字节 XX
|
||||
电池电量范围 0-100, 表示电量0 -100%
|
||||
|
||||
12、 功能码0x50: 脉冲、离合、冲击扳手参数设置(App -> 扳手)
|
||||
字节序号
|
||||
1-2 报文头 0xC5 0xC5
|
||||
3 地址码 0x01 该字节用于区分同一组中不同的扳手
|
||||
4 功能码 0x50
|
||||
5-6 保留 0xFF 0xFF
|
||||
7 数据长度 0x50
|
||||
8-17 产品ID 10字节 ASCII码, 不足补0
|
||||
18-37 工位名称 20字节 中英字符,不足补0
|
||||
38-47 员工号 10字节 ASCII码, 不足补0
|
||||
48-57 工具系列号 10字节 ASCII码, 不足补0
|
||||
58-67 螺栓型号 10字节 ASCII码, 不足补0
|
||||
68-69 时间-年 2字节
|
||||
70 时间-月 1字节
|
||||
71 时间-日 1字节
|
||||
72 时间-小时 1字节
|
||||
73 时间-分钟 1字节
|
||||
74 时间-秒 1字节
|
||||
75 参数模式 1字节 扭矩模式:0x00
|
||||
定点硬连接(H):0x01
|
||||
定点软连接(S):0x02
|
||||
76-77 目标扭矩高位
|
||||
目标扭矩低位 2字节 0-65535表示0-6553.5NM
|
||||
78-79 K1值高位
|
||||
K1值低位 2字节 数值范围
|
||||
50-350 K1,K2,K3值只在定点硬连接和软连接模式下有效,扭矩模式下填0x00
|
||||
|
||||
80-81 K2值高位
|
||||
K2值低位 2字节 数值范围
|
||||
1-80
|
||||
82-83 K3值高位
|
||||
K3值低位 2字节 数值范围
|
||||
1-3000
|
||||
84 螺栓状态 1字节 0%-100%
|
||||
85 误差精度 1字节
|
||||
86-87 螺栓个数高位
|
||||
螺栓个数低位 2字节
|
||||
|
||||
88 校验码(累加和) 1字节
|
||||
|
||||
|
||||
13、功能码0x55 : 脉冲、离合、冲击扳手执行结果反馈
|
||||
字节序号
|
||||
1-2 报文头 0xC5 0xC5
|
||||
3 地址码 0x01
|
||||
4 功能码 0x55
|
||||
5 结果状态 0xXX 0x00 : 成功OK
|
||||
0x10: 失败NG
|
||||
6 保留 0xFF
|
||||
7 数据长度 0x1C
|
||||
8-17 员工号 10字节 ASCII码
|
||||
18-19 螺栓号 2字节
|
||||
20-21 时间-年 2字节
|
||||
22 时间-月 1字节
|
||||
23 时间-日 1字节
|
||||
24 时间-小时 1字节
|
||||
25 时间-分钟 1字节
|
||||
26 时间-秒 1字节
|
||||
27 参数模式 1字节 扭矩模式:0x00
|
||||
定点硬连接(H):0x01
|
||||
定点软连接(S):0x02
|
||||
28-29 目标扭矩高位
|
||||
目标扭矩低位 2字节 0-65535表示0-6553.5NM
|
||||
30-31 实际扭矩高位
|
||||
实际扭矩低位 2字节 0-65535表示0-6553.5NM
|
||||
32-33 实际角度高位
|
||||
实际角度低位 2字节
|
||||
|
||||
34-35 保留 2字节
|
||||
36 校验码(累加和) 1字节
|
||||
|
||||
|
||||
|
||||
14、功能码0x23: 扳手发送复位螺母个数选择项报文
|
||||
字节序号
|
||||
1-2 报文头 0xC5 0xC5
|
||||
3 地址码 0x01
|
||||
4 功能码 0x23
|
||||
5-6 保留 0xFF 0xFF
|
||||
7 数据长度 0x01
|
||||
8 数据内容 1字节 0x00: 退出螺母计数模式
|
||||
0x01: 按原螺母数重新开始计数运行
|
||||
9 校验码(累加和) 1字节
|
||||
|
||||
|
||||
15、功能码0x25: 查询设备SN码
|
||||
字节序号
|
||||
1-2 报文头 0xC5 0xC5
|
||||
3 地址码 0x01
|
||||
4 功能码 0x25
|
||||
5-6 保留 0xFF 0xFF
|
||||
7 数据长度 0x05
|
||||
8-12 数据内容 00 00 00 00 00
|
||||
13 校验码(累加和) 0xXX
|
||||
报文举例:查询SN码:C5 C5 01 25 FF FF 05 00 00 00 00 00 B3
|
||||
|
||||
16、功能码0x26: 应答设备SN码
|
||||
字节序号
|
||||
1-2 报文头 0xC5 0xC5
|
||||
3 地址码 0x01
|
||||
4 功能码 0x26
|
||||
5-6 保留 0xFF 0xFF
|
||||
7 数据长度 0x05
|
||||
8-12 数据内容 设备SN码10位0-9数值 以压缩BCD码表示
|
||||
13 校验码(累加和) 0xXX
|
||||
报文举例:应答SN码:C5 C5 01 26 FF FF 05 12 34 56 78 90 58
|
||||
设备SN码: 1234567890
|
||||
|
||||
17、功能码0x17: 发送结果反馈帧0x15报文的确认帧,扳手在发送0x15结果帧后,未在0.5S内收到该0x17帧,结果帧将保存到扳手;反之扳手收到0x17帧则扳手不保存结果帧。
|
||||
字节序号
|
||||
1-2 报文头 0xC5 0xC5
|
||||
3 地址码 0x01
|
||||
4 功能码 0x17
|
||||
5-6 保留 0xFF 0xFF
|
||||
7 数据长度 0x01
|
||||
8 数据内容 0x00
|
||||
9 校验码(累加和) 1字节
|
||||
|
||||
|
||||
8、功能码0x05 : (发状态:控制器→显示屏)
|
||||
字节序号
|
||||
1-2 报文头 0xC5 0xC5
|
||||
3 地址码 0x01
|
||||
4 功能码 0x05 发状态参数:控制器→显示屏
|
||||
5-6 保留 0xFF 0xFF
|
||||
7 数据长度 0x04
|
||||
8-9 错误码 2字节 0 表示无错误,可以不显示;其余数值为错误码,
|
||||
10-11 控制位 2字节 Bit1: 正向0
|
||||
反向1 Bit2: 预留
|
||||
Bit3: 预留 Bit4: 预留
|
||||
Bit5: 预留 Bit6: 预留
|
||||
Bit7: 预留 Bit8: 预留
|
||||
Bit9---Bit16:预留
|
||||
12 校验码(累加和) 1字节
|
||||
|
||||
|
||||
Loading…
Reference in New Issue