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

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("设备信息设置成功")