配置系统基础: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/
├── ValveCommands.jsonc # 阀门命令
├── ServoCommands.jsonc # 伺服命令
├── DeviceCommands.jsonc # 设备命令
├── ProcessCommands.jsonc # 工艺流程命令
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监听文件变化、防抖处理、注册表热更新,改配置不用重启程序。
:配置系统基础——INI + JSON 怎么管&spm=1001.2101.3001.5002&articleId=164033683&d=1&t=3&u=2ba55b0c239540468b6916a8235115b3)

被折叠的 条评论
为什么被折叠?



