148 lines
5.7 KiB
Python
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
|