From 459ce57cd480888e34b090d2e0ef06720775ceb9 Mon Sep 17 00:00:00 2001 From: risingLee <871066422@qq.com> Date: Mon, 19 Jan 2026 16:59:52 +0800 Subject: [PATCH] =?UTF-8?q?=E9=A6=96=E6=AC=A1=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + config.json | 29 +++ work_order.json | 58 +++++ wrench_controller.py | 524 +++++++++++++++++++++++++++++++++++++++++++ wrench_gui.py | 447 ++++++++++++++++++++++++++++++++++++ 文档.txt | 424 ++++++++++++++++++++++++++++++++++ 配置详细说明 | 0 7 files changed, 1483 insertions(+) create mode 100644 .gitignore create mode 100644 config.json create mode 100644 work_order.json create mode 100644 wrench_controller.py create mode 100644 wrench_gui.py create mode 100644 文档.txt create mode 100644 配置详细说明 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c18dd8d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +__pycache__/ diff --git a/config.json b/config.json new file mode 100644 index 0000000..468c424 --- /dev/null +++ b/config.json @@ -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" + } +} diff --git a/work_order.json b/work_order.json new file mode 100644 index 0000000..4df3981 --- /dev/null +++ b/work_order.json @@ -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" + } + ] +} diff --git a/wrench_controller.py b/wrench_controller.py new file mode 100644 index 0000000..646c5f8 --- /dev/null +++ b/wrench_controller.py @@ -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("无法连接到扳手") diff --git a/wrench_gui.py b/wrench_gui.py new file mode 100644 index 0000000..7896fab --- /dev/null +++ b/wrench_gui.py @@ -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() diff --git a/文档.txt b/文档.txt new file mode 100644 index 0000000..84a3c8f --- /dev/null +++ b/文档.txt @@ -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字节 + + diff --git a/配置详细说明 b/配置详细说明 new file mode 100644 index 0000000..e69de29