#!/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