You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
	
	
		
			231 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Python
		
	
		
		
			
		
	
	
			231 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Python
		
	
|   
											1 month ago
										 | 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("设备信息设置成功") |