import inspect import os.path import tkinter as tk from tkinter import ttk from .base_page import BasePage from protocol import DeviceInfo, int_to_ip, ip_to_int, bytes_to_mac from config import Cmd class DeviceInfoPage(BasePage): def __init__(self, parent, tcp_client): super().__init__(parent, tcp_client, "设备信息") # 配置样式 self.configure_styles() tcp_client.register_callback(Cmd.DEVICE_INFO_GET, self.on_data_received) tcp_client.register_callback(Cmd.DEVICE_INFO_SET, self.on_set_response) # self.create_widgets() def configure_styles(self): """配置界面样式""" style = ttk.Style() style.configure('Readonly.TEntry', fieldbackground='#f0f0f0', foreground='#666666') def hex_to_int(self, hex_str): """将16进制字符串转换为整数""" try: hex_str = hex_str.strip().replace('0x', '').replace('0X', '') return int(hex_str, 16) if hex_str else 0 except ValueError: return 0 def int_to_hex(self, value): """将整数转换为16进制字符串""" return f"{value:X}" def format_date(self, date_int): """格式化日期""" if date_int == 0: return "未知" year = (date_int >> 16) & 0xFFFF month = (date_int >> 8) & 0xFF day = date_int & 0xFF return f"{year:04d}-{month:02d}-{day:02d}" def format_runtime(self, seconds): """格式化运行时间""" if seconds == 0: return "0秒" days = seconds // (24 * 3600) hours = (seconds % (24 * 3600)) // 3600 minutes = (seconds % 3600) // 60 secs = seconds % 60 parts = [] if days > 0: parts.append(f"{days}天") if hours > 0: parts.append(f"{hours}时") if minutes > 0: parts.append(f"{minutes}分") if secs > 0 or not parts: parts.append(f"{secs}秒") return "".join(parts) def bytes_to_string(self, byte_data): """字节数据转字符串""" try: return byte_data.decode('utf-8', errors='ignore').split('\x00')[0] except: return str(byte_data) def validate_hex_input(self, new_value): """验证16进制输入""" if not new_value: return True pattern = r'^(0[xX])?[0-9A-Fa-f]*$' return re.match(pattern, new_value) is not None def create_widgets(self): """创建界面控件""" print(f"file:{os.path.basename(__file__)} func:{inspect.currentframe().f_code.co_name}") super().create_widgets() # 创建两列框架 main_frame = ttk.Frame(self) main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5) left_frame = ttk.Frame(main_frame) left_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=(0, 5)) right_frame = ttk.Frame(main_frame) right_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=(5, 0)) # 定义所有字段 self.fields = [ # 左列 - 可编辑字段 {"label": "主设备号", "attr": "type_m", "type": "spin", "args": (0, 255), "readonly": False}, {"label": "次设备号", "attr": "type_s", "type": "spin", "args": (0, 255), "readonly": False}, {"label": "设备ID", "attr": "dev_id", "type": "hex_entry", "readonly": False}, {"label": "设备名", "attr": "hostname", "type": "entry", "readonly": False}, {"label": "IP地址", "attr": "ip", "type": "entry", "readonly": False}, {"label": "子网掩码", "attr": "mask", "type": "entry", "readonly": False}, {"label": "网关", "attr": "gw", "type": "entry", "readonly": False}, {"label": "后台端口", "attr": "csg_port", "type": "spin", "args": (1, 65535), "readonly": False}, {"label": "后台IP", "attr": "csg_ipv4", "type": "entry", "readonly": False}, # 右列 - 只读字段 {"label": "MAC地址", "attr": "mac", "type": "entry", "readonly": True}, {"label": "软件版本", "attr": "app_version", "type": "entry", "readonly": True}, {"label": "编译时间", "attr": "app_compile_time", "type": "entry", "readonly": True}, {"label": "硬件版本", "attr": "hardware_version", "type": "entry", "readonly": True}, {"label": "FPGA版本", "attr": "fpga_version", "type": "entry", "readonly": True}, {"label": "出厂日期", "attr": "factory_date", "type": "entry", "readonly": True}, {"label": "部署日期", "attr": "deployment_date", "type": "entry", "readonly": True}, {"label": "运行时间", "attr": "running_time", "type": "entry", "readonly": True} ] # 创建字段控件 self.create_field_controls(left_frame, [f for f in self.fields if not f['readonly']]) self.create_field_controls(right_frame, [f for f in self.fields if f['readonly']]) # 创建按钮 self.create_buttons() def create_field_controls(self, parent, fields): """创建字段控件""" for i, field in enumerate(fields): ttk.Label(parent, text=field["label"] + ":").grid( row=i, column=0, sticky='e', padx=2, pady=2) if field["type"] == "spin": var = tk.IntVar() spinbox = ttk.Spinbox(parent, from_=field["args"][0], to=field["args"][1], textvariable=var, width=15) spinbox.grid(row=i, column=1, sticky='w', padx=2, pady=2) if field["readonly"]: spinbox.configure(state='readonly', style='Readonly.TEntry') elif field["type"] == "hex_entry": var = tk.StringVar() entry = ttk.Entry(parent, textvariable=var, width=15) entry.grid(row=i, column=1, sticky='w', padx=2, pady=2) if not field["readonly"]: entry.configure(validate="key", validatecommand=(entry.register(self.validate_hex_input), '%P')) else: entry.configure(state='readonly', style='Readonly.TEntry') else: var = tk.StringVar() entry = ttk.Entry(parent, textvariable=var, width=20) entry.grid(row=i, column=1, sticky='w', padx=2, pady=2) if field["readonly"]: entry.configure(state='readonly', style='Readonly.TEntry') setattr(self, f"{field['attr']}_var", var) def create_buttons(self): """创建按钮""" btn_frame = ttk.Frame(self) btn_frame.pack(pady=10) ttk.Button(btn_frame, text="读取", command=self.read_data, width=10).pack(side=tk.LEFT, padx=5) ttk.Button(btn_frame, text="设置", command=self.set_data, width=10).pack(side=tk.LEFT, padx=5) def read_data(self): """读取设备数据""" print(f"发送命令: {Cmd.DEVICE_INFO_GET}") if not self.tcp_client.connected: self.show_error("未连接设备") return self.tcp_client.send_packet(Cmd.DEVICE_INFO_GET) def set_data(self): """设置设备数据""" if not self.tcp_client.connected: self.show_error("未连接设备") return info = DeviceInfo() info.type_m = self.type_m_var.get() info.type_s = self.type_s_var.get() info.dev_id = self.hex_to_int(self.dev_id_var.get()) info.hostname = self.hostname_var.get().encode().ljust(128, b'\x00') info.ip = ip_to_int(self.ip_var.get()) info.mask = ip_to_int(self.mask_var.get()) info.gw = ip_to_int(self.gw_var.get()) info.csg_port = self.csg_port_var.get() info.csg_ipv4 = ip_to_int(self.csg_ipv4_var.get()) self.tcp_client.send_packet(Cmd.DEVICE_INFO_SET, info.to_bytes()) def on_data_received(self, header, body): """处理接收到的数据""" print(f"file:{os.path.basename(__file__)} func:{inspect.currentframe().f_code.co_name}") print(f"收到数据: 命令={header.cmd}, 数据长度={len(body)}") try: info = DeviceInfo.from_bytes(body) print(f"解析成功: dev_id={info.dev_id}, hostname={info.hostname}") self.type_m_var.set(info.type_m) self.type_s_var.set(info.type_s) self.dev_id_var.set(self.int_to_hex(info.dev_id)) self.hostname_var.set(self.bytes_to_string(info.hostname)) self.ip_var.set(int_to_ip(info.ip)) self.mask_var.set(int_to_ip(info.mask)) self.gw_var.set(int_to_ip(info.gw)) self.csg_port_var.set(info.csg_port) self.csg_ipv4_var.set(int_to_ip(info.csg_ipv4)) self.mac_var.set(bytes_to_mac(info.mac)) self.app_version_var.set(self.bytes_to_string(info.app_version)) self.app_compile_time_var.set(self.bytes_to_string(info.app_compile_time)) self.hardware_version_var.set(self.bytes_to_string(info.hardware_version)) self.fpga_version_var.set(self.bytes_to_string(info.fpga_version)) self.factory_date_var.set(self.format_date(info.factory_date)) self.deployment_date_var.set(self.format_date(info.deployment_date)) self.running_time_var.set(self.format_runtime(info.running_time)) except Exception as e: print(f"解析设备信息失败: {e}") self.show_error("解析设备信息失败") def on_set_response(self, header, body): """处理设置响应""" self.show_info("设备信息设置成功")