配置系统基础:INI + JSON 怎么管
这是「从零搭建工业控制系统」系列第14篇。前面几篇一直在说"从配置读取",这篇终于讲配置系统本身——INI管全局参数,JSON管命令定义。下一篇讲热加载。
配置文件的三座大山
项目里的配置分三类,各管各的:
| 类型 | 文件 | 管什么 | 格式 |
|---|---|---|---|
| 全局参数 | Config.ini | IP地址、超时、开关 | INI |
| 命令定义 | *.jsonc | 阀门/伺服/设备命令 | JSONC |
| 工艺参数 | *.json | PPS阈值、报警参数 | JSON |
为什么不全用JSON?因为INI对操作员友好。车间的人用记事本打开Config.ini,改个IP改个超时,一目了然。JSON的嵌套结构对非技术人员不友好。
ConfigReader:单例读取器
ConfigReader用ini-parser库封装了INI文件的读写。单例模式,线程安全:
public class ConfigReader { private static readonly object _lockObject = new object(); private static ConfigReader _instance; private readonly FileIniDataParser _parser; private readonly string _configFilePath; public static ConfigReader Instance { get { if (_instance == null) { lock (_lockObject) { if (_instance == null) { _instance = new ConfigReader(Paths.WorkingConfigIni); } } } return _instance; } } private ConfigReader(string configFilePath) { _configFilePath = configFilePath; _parser = new FileIniDataParser(); _parser.Parser.Configuration.CommentString = ";"; } }注释符改成;而不是默认的#,因为Windows用户习惯分号注释。
AppConfig:配置数据模型
AppConfig是一个纯数据类,所有属性都有默认值。配置文件缺失某项时不报错,用默认值:
[Obfuscation(Exclude = true, ApplyToMembers = true)] public class AppConfig { public bool AutoStart { get; set; } public bool ShowUILog { get; set; } = true; public bool ShowCallerInfoInUILog { get; set; } = false; // 设备通信参数 public string DigitalIOIp { get; set; } public int DigitalIOPort { get; set; } public int DigitalIOReadTimeout { get; set; } = 5000; public string ValveModuleIp { get; set; } public int ValveModulePort { get; set; } public int ValveModuleReadTimeout { get; set; } = 5000; public string AnalogIOIp { get; set; } public int AnalogIOPort { get; set; } public int AnalogIOReadTimeout { get; set; } = 5000; // 灯塔配置 public bool TowerLightServiceEnable { get; set; } = true; public int BuzzerDurationSeconds { get; set; } = 5; // Chamber控制 public bool AutoAllowControl { get; set; } = true; public bool ManualAllowControl { get; set; } = true; }Obfuscation(Exclude=true)保护属性名不被混淆——因为这些属性在XAML里通过{x:Static}引用。
读取配置的坑
读取INI配置有个容易忽略的坑:新增配置项后,旧配置文件没有这个key。
// 读取ChamberControl节 if (data.Sections.ContainsSection("ChamberControl")) { var chamberControl = data["ChamberControl"]; config.AutoAllowControl = GetBoolValue(chamberControl, "AutoAllowControl", true); config.ManualAllowControl = GetBoolValue(chamberControl, "ManualAllowControl", true); }GetBoolValue第三个参数是默认值。旧配置文件没有AutoAllowControl这个key时,返回默认值true。
踩过的坑:之前DevicePanelViewModel里初始值设为false,但AppConfig默认true。程序启动后配置文件还没读取就用了ViewModel的初始值,导致UI显示未勾选但实际逻辑按true执行。后来统一了默认值才解决。
JSONC配置:命令定义
命令配置用JSONC格式(带注释的JSON)。CommandRegistry加载这些文件:
Configs/ ├── ValveActions.jsonc # 阀门动作 ├── MotionCommands.jsonc # 运动命令 ├── HardwareCommands.jsonc # 硬件命令 ├── TrackingCommands.jsonc # 追踪命令 ├── RecipeCommands.jsonc # 配方流程命令 └── OnlyDebug/ # 调试覆盖配置 └── *.jsoncOnlyDebug目录下的同名文件会覆盖主配置,方便调试时临时修改命令参数而不影响生产配置。
IO配置的disabled字段
JSON配置支持disabled字段控制IO是否加载:
{ "address": "0xXX", "name": "DI_XX", "disabled": true }不配置disabled就是启用,只有需要禁用才加这个字段。读取时过滤掉:
public List<DeviceIO> GetInputDevicesByJsonOrder() { return _inputDevices .Where(d => !d.Disabled) .ToList(); }比删掉配置项更安全——配置还在,只是不加载。哪天需要启用改回false就行。
配置保存的完整流程
操作员在设置界面修改参数后点保存:
// 1. ViewModel收集UI值 config.AutoAllowControl = AutoAllowControl; config.ManualAllowControl = ManualAllowControl; config.TowerLightServiceEnable = TowerLightServiceEnable; // 2. 调用ConfigReader写入文件 ConfigReader.Instance.WriteConfig(config); // 3. 触发配置变更通知 WeakReferenceMessenger.Default.Send(new ConfigChangedMessage()); // 4. 安全服务收到消息后响应写文件和发消息是两步。写完文件不发消息,其他服务不知道配置变了。发消息但不写文件,重启后配置丢失。两步缺一不可。
配置踩坑清单
| 坑 | 现象 | 解决 |
|---|---|---|
| 默认值不一致 | UI和逻辑用的默认值不同 | 统一在AppConfig里定义 |
| 旧文件缺key | 读取报错 | GetBoolValue带默认值参数 |
| 多次Changed | 热加载触发多次 | 500ms防抖Timer |
| 混淆后属性名变了 | XAML绑定失效 | Obfuscation排除 |
| 配置改了不生效 | 服务不知道配置变了 | 消息机制通知 |
本篇小结
| 知识点 | 关键做法 |
|---|---|
| INI vs JSON | INI给操作员,JSON给开发 |
| 默认值 | AppConfig属性全带默认值 |
| 调试覆盖 | OnlyDebug目录覆盖主配置 |
| IO禁用 | disabled字段过滤,不删配置 |
| 配置变更 | 写文件 + 发消息,缺一不可 |
配置系统的核心:操作员改了能生效,程序重启不丢失,默认值保证安全。
下期预告
第15篇:配置热重载机制
配置基础说完了,下篇讲热加载——FileSystemWatcher监听文件变化、防抖处理、注册表热更新,改配置不用重启程序。