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