Files
HW-Debug-Tools/SerialMate/src/config.py
T
hulk c26d2cdbaa feat: 新增 SerialMate 串口调试助手 v0.1.0
- 协议帧构成令牌序列可定制(包头/板ID/LEN口径/5种CRC x 3种覆盖), 默认兼容 DLPilot 光机协议
2026-09-27 18:52:54 +08:00

148 lines
5.7 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
运行目录定位与设置持久化(BASE_DIR 语义与 DLPilot config.py 一致)
==================================================================
frozen(PyInstaller onefile)时 BASE_DIR=exe 所在目录(sys.executable),
源码运行时=config.py 所在目录(即 src/)。设置写/读自 BASE_DIR 平铺的
serial_mate.json, 不建子目录。
读容错: 文件缺失/损坏/缺键一律回落默认值, 并保证返回的 dict 键全齐,
GUI 无需判空。
"""
import json
import os
import shutil
import sys
if getattr(sys, 'frozen', False):
BASE_DIR = os.path.dirname(os.path.abspath(sys.executable))
else:
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
CONFIG_FILE = os.path.join(BASE_DIR, 'serial_mate.json')
LEGACY_CONFIG = os.path.join(BASE_DIR, 'serial_assistant.json') # 更名前的配置
PROFILES_FILE = os.path.join(BASE_DIR, 'board_profiles.json')
def _migrate_legacy_config():
"""SerialAssistant -> SerialMate 更名迁移: 旧配置搬为新名(一次性, 已存在则跳过)"""
if os.path.exists(CONFIG_FILE) or not os.path.exists(LEGACY_CONFIG):
return
try:
shutil.copy2(LEGACY_CONFIG, CONFIG_FILE)
except OSError:
pass
DEFAULTS = {
'serial': {'port': '', 'baud': 115200, 'bytesize': 8, 'parity': 'N', 'stopbits': 1},
'proto': {'tx_hdr': 'D5', 'rx_hdr': 'B5', 'board': '01',
'crc_algo': 'SUM8', 'crc_scope': 'no_header', 'parse': True,
'layout': 'HDR ID LEN PAYLOAD CRC', 'len_sem': 'payload'},
'tx': {'mode': 'wrap', 'periodic': False, 'interval_ms': 1000, 'profile': ''},
'rx': {'display': 'text', 'show_raw': True},
'channels': [
{'enabled': False, 'name': '通道1', 'mode': 'num', 'offset': 0,
'fmt': 'u16be', 'index': 1, 'pattern': '(?<=:)\\d+', 'scale': 1.0},
{'enabled': False, 'name': '通道2', 'mode': 'bin', 'offset': 0,
'fmt': 's16be', 'index': 1, 'pattern': '(?<=:)\\d+', 'scale': 1.0},
{'enabled': False, 'name': '通道3', 'mode': 'bin', 'offset': 0,
'fmt': 'u16be', 'index': 1, 'pattern': '(?<=:)\\d+', 'scale': 1.0},
{'enabled': False, 'name': '通道4', 'mode': 'bin', 'offset': 0,
'fmt': 'u16be', 'index': 1, 'pattern': '(?<=:)\\d+', 'scale': 1.0},
],
}
# 板型档案的帧协议字段(不含 parse —— 解析开关是查看偏好, 不随板型走)
_PROFILE_PROTO_KEYS = ('tx_hdr', 'rx_hdr', 'board', 'layout', 'len_sem',
'crc_algo', 'crc_scope')
# 首个板型的命令预设(DLPilot 光机命令集, 全表见上游 protocols.md)
DEFAULT_COMMANDS = (
('握手', 'M1'), ('查版本', 'M999'),
('开光机', 'M730S0'), ('关光机', 'M730S1'),
('设光强', 'M731S100'), ('查电流', 'M732'),
('查DMD温度', 'M201'), ('查LAMP温度', 'M202'),
('TEC自动控温开', 'M7S0'), ('TEC自动控温关', 'M7S1'),
('凝露点33.0°C', 'M8S330'), ('软件重启', 'M888'),
)
def default_profiles():
"""首个板型档案: DLPilot 光机(协议字段取默认值)"""
return [{'name': 'DLPilot光机',
'proto': {k: DEFAULTS['proto'][k] for k in _PROFILE_PROTO_KEYS},
'commands': [list(c) for c in DEFAULT_COMMANDS]}]
def _merge(base, over):
"""递归覆盖: over 缺键用 base 补齐, over 多余键保留(向前兼容旧配置)"""
out = dict(base)
for k, v in over.items():
if k in base and isinstance(base[k], dict) and isinstance(v, dict):
out[k] = _merge(base[k], v)
else:
out[k] = v
return out
def load():
_migrate_legacy_config()
"""读设置; 任何异常回落默认(键全齐), 不抛出"""
try:
with open(CONFIG_FILE, encoding='utf-8') as f:
data = json.load(f)
if not isinstance(data, dict):
raise ValueError('配置根不是对象')
except Exception:
return json.loads(json.dumps(DEFAULTS)) # 深拷贝默认值
return _merge(DEFAULTS, data)
def save(cfg):
"""写设置; 失败静默(只读目录等场景不阻塞退出)"""
try:
with open(CONFIG_FILE, 'w', encoding='utf-8') as f:
json.dump(cfg, f, ensure_ascii=False, indent=1)
except Exception:
pass
def load_profiles():
"""读板型档案(board_profiles.json); 缺失/损坏回落默认并落盘。
档案结构: {'profiles': [{'name', 'proto'{7字段}, 'commands'[[名,命令]..]}..]}"""
try:
with open(PROFILES_FILE, encoding='utf-8') as f:
data = json.load(f)
profiles = data.get('profiles') if isinstance(data, dict) else data
if isinstance(profiles, list) and profiles:
out = []
for p in profiles:
if not isinstance(p, dict) or not p.get('name'):
continue
out.append({'name': str(p['name']),
'proto': {k: p.get('proto', {}).get(k,
DEFAULTS['proto'][k])
for k in _PROFILE_PROTO_KEYS},
'commands': [[str(n), str(c)]
for n, c in p.get('commands', [])
if isinstance(n, str) and isinstance(c, str)]})
if out:
return out
except Exception:
pass
profiles = default_profiles()
save_profiles(profiles)
return profiles
def save_profiles(profiles):
"""写板型档案; 失败静默并返回 False"""
try:
with open(PROFILES_FILE, 'w', encoding='utf-8') as f:
json.dump({'profiles': profiles}, f, ensure_ascii=False, indent=1)
return True
except Exception:
return False