WinForms 桌面程序(.NET Framework 4.7.2 / C#):监控 72 台气压表真空压力,控制 72 路真空电磁阀 + 72 路载台上电, 接入冷却送风机与扫码枪,实现老化测试业务闭环 + 报警联动。
| 想知道 | 去这里 |
|---|---|
| 设备通讯协议、寄存器、坑点、排障 | docs/通讯接入.md(唯一协议文档) |
| 各文件职责、代码在哪 | 下文「3. 目录结构与职责」 |
| 配置项含义 | 下文「5. 配置项速查」 |
| 业务流(测试/报警/送风机) | 下文「4. 核心业务流」 |
| 改动历史 | 下文「8. 版本历史」+ CHANGELOG.md |
| 现场调试 | 主程序"关于"菜单:通讯测试 / 送风机测试(技术员及以上;复用主程序共享连接,不自建连接) |
原独立测试工程(
ModbusRtuBarometerTest/ModbusTcpIoControllerTest/ModbusTCPFanControllerTest/SerialScannerTest) 已删除,其测试逻辑已合并进主程序与docs/通讯接入.md。
| 设备 | 数量 | 协议 / 接入 | 关键实现 |
|---|---|---|---|
| 真空负压表(气压表) | 72 | Modbus RTU,RS485→CH340 USB,19200 8N1,从站=设备号 1~72 | ModbusRtuBarometerReader |
| IO 耦合器 | 1 | GX-CL140,Modbus TCP 192.168.1.20:502,80DI/160DO |
ModbusTcpIoController |
| 冷却送风机 | 1 | 厂商控制屏,Modbus TCP 端口 50000,定值启停 + 温湿度监视 | FanControllerClient |
| 扫码枪 | 1 | Honeywell Xenon 1902 虚拟串口 115200,WMI 自动识别串口 | ScannerService |
Mock 与真实实现由 UseMockCommunication 一键切换,接现场硬件只改配置、不改代码。
MainForm / WorkstationGridView(Views 视图层,单窗口自绘大画布)
↓ 弹出
Dialogs(窗体层:CommonParameterForm / SettingsForm / StationSettingsForm /
CommunicationTestForm / FanTestForm / InputLotForm / IdBindingForm ...)
↓ 调用
DeviceManager(服务层核心编排:采集/测试状态机/报警联动/送风机生命周期)
├─ IBarometerReader (MockBarometerReader / ModbusRtuBarometerReader)
├─ IIoController (MockIoController / ModbusTcpIoController + IoMapBuilder)
├─ IFanController (MockFanController / FanControllerClient)
├─ ScannerService(扫码枪,独立) UserManager(用户/权限,Users.json,密码 PBKDF2 哈希)+ PasswordHasher
├─ TestEventLogger(测试事件落盘 Logs\TestLog_yyyyMMdd.csv,供历史记录窗体)
└─ AppLogFileWriter(主窗体 UI 操作日志落盘 Logs\AppLog_yyyyMMdd.log,与文本框逐行一致)
↓
Models(BarometerData / FanData / IoStatus / DeviceConfig / RecipeConfig / StationInfo)
- IO 映射:内部用十进制连续编号(输入 1
80、输出 81240),IoMapBuilder换算成三菱八进制物理地址(X/Y), 每台气压表 = 1 输入(报警触点 DI)+ 2 输出(真空阀 Y + 载台上电 Y)。八进制:X007 后是 X010,Y107 后是 Y110。 - 事件:
DeviceManager一次性触发OnBatchDataUpdated(72 台数据数组,避免逐条 72 次 UI 切换);OnConnectionStatusChanged/OnFanDataUpdated/OnDiagnostic。
| 文件 | 职责 |
|---|---|
Services/DeviceManager.cs |
业务编排核心:Start/Stop、采集定时(1s)、测试状态机(StartTesting/StopTesting/ResetDevices/StopAll)、报警边沿联动(IsAlarm→HandleAlarm 关阀+断电)、送风机生命周期 UpdateFanLifecycle、工位静态信息 StationInfo、共享连接门面(EnsureIoConnected/ReadHoldingRegisters/ReconnectFan/GetFanData/StartFan/StopFan) |
Services/ModbusRtuBarometerReader.cs |
气压表 RTU:读压力(0x04 @0x0001~0002,(short)值/10^小数位)、写设备阈值(0x06 @0x0010)、72 台轮询、_syncRoot 串口锁 |
Services/SerialPortHelper.cs |
CH340 串口自动识别(WMI VID_1A86/PID_7523)+ 端口缓存 BarometerPort.cache |
Services/ModbusTcpIoController.cs |
IO 耦合器:DI 读 0x04 @0x1000、DO 写 0x06 / 读 0x03 @0x2000,读-改-写单点,备用通道映射,共享原始寄存器 API(ReadHoldingRegisters/WriteSingleRegister,供测试窗体复用同一连接) |
Services/IoMapBuilder.cs |
内部编号 ↔ 三菱八进制 X/Y 地址映射 |
Services/FanControllerClient.cs |
送风机 TCP:定值启停、读状态/温湿度,断线重连节流,IP 自动识别 + FanLastIp.cache 工控机记忆 |
Services/ScannerService.cs |
扫码枪:WMI 识别串口、串口读码、断线心跳重连 |
Services/TestEventLogger.cs |
测试事件 CSV 落盘(启动/停止/报警/复位/急停/真空建立) |
Services/AppLogFileWriter.cs |
主窗体 UI 操作日志落盘(Logs\AppLog_yyyyMMdd.log,按日期分文件,与文本框逐行一致,写失败静默) |
Services/UserManager.cs |
用户/登录/权限,Users.json 持久化(密码哈希,V1.58.22) |
Services/PasswordHasher.cs |
密码哈希(PBKDF2-HMAC-SHA256,随机盐 + 10 万次迭代,PBKDF2$迭代$盐$哈希 自描述格式) |
Services/RecipeStorage.cs |
配方列表持久化(Recipes.json,启动加载/操作即写盘;SaveWithDuplicateCheck 同名覆盖保存,V1.25/1.26) |
Services/StationSettingsCache.cs |
工位配置缓存(StationSettings.json,按工位缓存 SN/配方/延时/极限温度,设置窗口下次打开自动回填,V1.26) |
Services/Mock*.cs |
Mock 实现(免接线演示) |
Views/MainForm.cs |
主窗体:面板区(9×8)、菜单下拉、状态栏("在线"全部离线标红,V1.24)、权限控制、扫码事件、操作区按钮 |
Views/WorkstationGridView.cs |
工位网格(自绘大画布,V1.51):1 个 UserControl 画全部面板 + 行全选列,滚动零撕裂;文字绝对坐标绘制无模糊;布局外部化(程序目录 PanelLayout.json 可改坐标/颜色/字号/文字,无需重编译);坐标命中实现长按选中/设置按钮/选中框/行全选/悬停提示 |
Models/PanelLayoutConfig.cs |
工位面板布局配置模型(V1.51):面板网格尺寸/面板内各元素坐标/字体/颜色("R,G,B")/按钮与提示文字;LoadOrDefault 文件缺失或损坏回退内置默认;V1.58.13~1.58.19 起全部元素改为"锚定"解析(右缘/上缘/下缘/对齐/垂直居中,改面板宽高自动联动),字段全表见类头注释 |
Dialogs/CommunicationTestForm.cs |
通讯测试窗体(IO 耦合器 DO 输出测试,负压阀/载台上电两页 9×8 灯按钮 + 一键遍历) |
Dialogs/FanTestForm.cs |
送风机测试窗体(定值启停 + 温湿度显示) |
Dialogs/SettingsForm.cs |
系统设置(管理员,按分类编辑 App.config 全部配置项,写回 exe.config 保存即生效,连接参数自动重连;仅设备数量/布局/模拟开关等结构型配置重启生效) |
Dialogs/HomeLayoutEditorForm.cs |
主页区域调整编辑器(V1.58,管理员):自绘预览 + 拖动四条边缘实时改标题栏/菜单栏/右侧区/状态栏尺寸,保存写 HomeLayout.json 即生效,无需重编译 |
Models/HomeLayoutConfig.cs |
主页布局配置模型(V1.58):标题栏/菜单栏/右侧区/状态栏四个尺寸 + Range 约束,LoadOrDefault 缺文件或损坏回退内置默认;MainForm 启动与保存后据此应用布局 |
Dialogs/StationSettingsForm.cs |
工位设置(SN/配方/延时/启动时间 写入 StationInfo;延时/启动时间三 NumericUpDown 冒号分隔,V1.28;保存=应用+缓存+存配方、加入对列=应用+存配方、下电=关闭载台上电) |
Dialogs/RecipeManagerForm.cs |
配方管理窗口(左侧列表可滚动 + 右侧可编辑输入,延时/启动时间冒号分隔三 NumericUpDown,V1.28;添加/更新/删除操作即自动落盘 Recipes.json,V1.27 起无"保存设置"按钮) |
Dialogs/BatchRecipeForm.cs |
批量设置配方窗口(配方名称/延时时间/启动时间/极限温度;延时/启动时间均三 NumericUpDown 冒号分隔,V1.28 删"延时时间2",两个时间都写入配方:延时→延时开启、启动→延时到达;加入队列=保存配方+应用到选中工位,无选中先保存配方并提示选择) |
Dialogs/IdBindingForm.cs / InputLotForm.cs |
录入批号 + 工位↔SN 绑定(扫码枪自动识别填充,生成 Excel) |
Models/ |
BarometerData / FanData(+FanRunState) / IoStatus / DeviceConfig / RecipeConfig / StationInfo / PanelLayoutConfig / HomeLayoutConfig / 用户模型 |
WinForms 视图均拆
.cs+.Designer.cs两个 partial;所有 .cs 必须 UTF-8 with BOM 编码(否则设计器报"无法设计基类 System.Void")。
[准备] 录入批号 → 绑定工位↔SN → 设配方
[启动] 开真空阀 + 载台上电 + 送风机定值启动 → 真空确认(VacuumConfirmTimeoutMs 默认15s 内压力进入正常区间,
超时→真空建立失败报警:关阀+断电+标故障)
[老化] 真空确认后计时 → 达 MaxTestDurationSeconds 自动停止该台
[监控] 压力越限 / 通讯失联(连续失败≥CommunicationLossAlarmCount) / DI触点(可选) → 报警联动
[停止] 关阀+断电(末台时送风机自动停止)
[复位] 人工报警复位后回空闲,可重测
[急停] 全部停止:全关阀+全断电+停送风机(带防误触确认)
- 压力越限:真空压力 >
AlarmPressureThresholdKPa(默认 -95kPa,即真空变差) - 真空建立超时:开阀后 15s 压力未进正常区间
- 通讯失联:某台连续读取失败 ≥
CommunicationLossAlarmCount次 - DI 报警触点(可选):
UseDiAlarmContact=true时启用,默认关
联动动作:报警进入边沿触发一次(不是周期重复)→ 关该台真空阀 + 断载台上电 + 标故障;结果写 CSV 日志。
气压表是"双信号":压力值走 RTU 供软件判报警(联动只由此触发);报警硬件触点走 DI(X000~X107,NPN)只读进 UI 显示,不参与联动。
- 有任一台在测试 → 保持运行;全部停止 → 才允许停机。
UpdateFanLifecycle里"只下发一次命令"状态记忆防重复写。 - 手动定值启/停按钮在无台测试时仍可用。
- IP 自动识别:连接顺序 = FanLastIp.cache(上次成功)→ FanIpAddress → FanIpCandidates;候选列表配几个识别几个,设备换 IP 自动找到并更新缓存。
开启真空 / 启动运行 / 停止运行 / 报警复位 / 全部停止(急停) / 送风机定值启停 / 面板"设置" / 行"全选"。
面板"设置"按钮(V1.24 智能分流):点击时若按钮所在工位未选中,先将其加入选中集合; 然后只选中 1 个工位 → 弹出该工位的工位设置窗口(
StationSettingsForm); 选中 2 个及以上 → 弹出批量设置配方窗口(BatchRecipeForm)。
面板选中交互(V1.19.5~6 / V1.24):空白处"长按约 0.8s"选中该工位(选中框平时全隐藏,有选中才全部显示); 已有选中时长按空白处 = 取消全部选中并隐藏所有选中框;选中框显示时单击空白处/选中框 = 切换该工位选中状态。
| 配置项 | 默认值 | 说明 |
|---|---|---|
TotalBarometers |
72 | 气压表总数 |
TotalInputs / TotalOutputs |
80 / 160 | IO 总数(业务用 72/144) |
CollectInterval |
1000 | 气压表采集间隔(ms) |
PortName / BaudRate |
COM9 / 19200 | 气压表串口(连接成功缓存 BarometerPort.cache 优先复用) |
UseMockCommunication |
false | true=Mock 免接线 |
InvertInputs / InvertOutputs |
false | 输入/输出逻辑取反(NPN/PNP 现场差异,灯亮软件读 OFF 时试 true) |
IoUnitId |
1 | IO 耦合器从站 |
IoInputRegisterStartAddress / IoOutputRegisterStartAddress |
0x1000 / 0x2000 | DI / DO 起始寄存器 |
IoBackupChannelMappingEnabled / IoBackupChannelMappings |
false / 0x2000@0x00->0x2009@0x10;0x2008@0x00->0x2009@0x11 |
备用通道映射(DQ 通道烧毁时启用,源寄存器@通道->目标,寄存器/通道均十六进制,通道 0x00~0x1F,写/读 DO 自动重定向) |
BarometerPressureRegisterAddress |
0x0001 | 压力寄存器(0x0002 为小数位,实测不可靠不再使用) |
BarometerDefaultDecimalPlaces |
1 | 小数位(压力读取与阈值写入统一用,换气压表改这里) |
BarometerPressureScale |
1 | 压力额外缩放 |
AlarmPressureThresholdKPa / AlarmWhenPressureHigherThanThreshold |
-95 / true | 软件报警阈值(不是设备阈值) |
PlcAddress / PlcPort |
192.168.1.20 / 502 | IO 耦合器 |
FanEnabled / FanIpAddress / FanPort |
true / 192.168.1.220 / 50000 | 送风机(可选设备,连不上不影响启动) |
FanAutoDetectEnabled / FanIpCandidates |
true / .220,.221,.222 | 送风机 IP 自动识别 |
FanUnitId / FanTimeoutMs |
1 / 3000 | 送风机从站/超时 |
VacuumConfirmTimeoutMs |
15000 | 真空建立确认超时(ms) |
CommunicationLossAlarmCount |
3 | 通讯失联报警阈值(连续失败次数) |
MaxTestDurationSeconds |
0 | 老化最大时长(0=不限) |
UseDiAlarmContact |
false | DI 报警触点并入判定(需现场确认电平) |
FanTempAlarmLimitC |
0 | 送风机温度告警上限(0=不启用) |
ScannerEnabled / ScannerPort |
false / 空 | 扫码枪开关 / 固定串口(空=WMI 自动识别) |
ScannerDeviceKeyword / ScannerBaudRate |
Xenon 1902 / 115200 | 扫码枪识别关键词 / 波特率 |
| 按钮 | 下拉项 | 权限 |
|---|---|---|
| 用户权限 | 操作员 / 技术员 / 管理员 / 用户管理* | *仅管理员 |
| 参数设置 | 公共参数(批量写气压表阈值)/ 配方管理 | 技术员+ |
| 日志记录 | 历史记录(读 CSV) | 任意 |
| 关于 | 设置* / 通讯测试** / 送风机测试** / 版本说明 | *仅管理员;**技术员+ |
默认账号(Users.json):operator / technician / admin,密码均 123456(PBKDF2 哈希存储,明文不落盘,见 Services/PasswordHasher.cs)。
- 共享连接(V1.23):通讯测试/送风机测试窗体不自己建 TCP 连接,复用
DeviceManager的共享连接 (EnsureIoConnected/ReadHoldingRegisters/ReconnectFan/GetFanData),连接状态与主界面实时一致。 测试窗体读送风机状态走缓存(零额外报文)。 - IO 写 DO 必须读-改-写:0x2004
0x2008 低/高字节被不同业务共用(如 0x2004 低字节=电磁阀6572、高字节=载台上电1~8), 整字直写会误伤其它通道。载台上电"全关"时也只清高字节、保留低字节。 - BeginInvoke 传数组参数必须
new object[]{arg}包装:BarometerData[]会被协变成 params 展开 → TargetParameterCountException。 - UTF-8 with BOM:所有 .cs 必须带 BOM,否则 WinForms 设计器"无法设计基类 System.Void"。
- 两种阈值别混淆:
AlarmPressureThresholdKPa(软件判报警,kPa,不写设备)≠ 设备阈值(写气压表 0x0010,单位与压力读数一致)。 - 小数位固定 1 位(
BarometerDefaultDecimalPlaces):0x0002 小数位寄存器现场 47/72 台返回 0 不可靠,压力读取与阈值写入统一固定。 - 有符号强转:压力原始值须
(short)强转,否则 0xFFFE 被当 65534,负压反号。 - 批量写某台超时 = 设备问题(掉线/断电/地址错/损坏),不是程序 bug;用批量读取/扫描 CSV(
Logs\BarometerScan_*.csv)定位离线台。 - 扫码枪断连判定用 WMI 动态搜索(设备拔出 PnP 节点消失即判断连),不能用注册表/ReadExisting(都不可靠)。
- 送风机状态枚举要 4 值(0x0000~0x0003)+ UI 显式 switch 中文,不能 ToString(程式模式回 0/1 会显示裸数字)。
- 送风机是可选设备:连接失败不影响整机启动;用独立 2s 定时器轮询,不阻塞气压表采集。
- 事件处理一律 IsDisposed 检查 + BeginInvoke:避免窗体释放后 ObjectDisposedException(历史 H1/H3)。
| 版本 | 要点 |
|---|---|
| V1.30 | IO 触发后气压表压力值快速刷新:写输出成功(开/关阀、上/断电、启动/停止测试)对目标工位启动独立 250ms 高频补读,压力变化 ≤0.5 秒可见,跟踪 12s 后自动退出恢复正常轮询,不影响 72 台全量采集性能 |
| V1.29 | 移除时间字段 [JsonProperty] 兼容(JSON 键名直接用 DelayTime/StartTime,旧 Recipes.json/StationSettings.json 需删除重建)+ 工位面板配色微调:boxPower 下电 / boxVacuumOpen 真空关由红底白字改浅灰(LightGray)底黑字(与行全选按钮同色),红仅保留给工作状态"故障" |
| V1.28 | 时间输入样式统一:配方管理/批量设置/工位设置三窗口延时与启动时间均改三个 NumericUpDown + 冒号分隔(时:分:秒,时0-99/分0-59/秒0-59),命名统一 nudDelay*/nudStart*,字段映射对齐(延时→延时开启 DelayTime、启动→延时到达 StartTime;字段名由 DelayStartTime/DelayArriveTime 统一);批量设置配方窗口删除"延时时间2",且原被丢弃的"启动时间"修复为写入配方;工位设置窗口 txtDelay/txtStart 改 NumericUpDown 并调整读取(GetTimeSpan)/回填(SetTimeInputs 钳制)/提示(GetTimeText)逻辑 |
| V1.27 | 配方管理移除"保存设置"按钮:添加/更新/删除操作即自动落盘 Recipes.json(原仅"保存设置"落盘,现三个操作按钮每次成功后自动持久化,改动重启不丢失) |
| V1.26 | 批量设置配方"加入队列"=保存配方到本地+应用到选中工位(无选中先保存配方并提示选择工位);工位设置"保存"=应用面板+缓存(StationSettings.json自动回填)+存配方、"加入对列"=应用面板+存配方、"下电"=关闭载台上电输出;RecipeStorage 新增 SaveWithDuplicateCheck 同名覆盖保存 |
| V1.25 | 配方管理窗口编辑化(名称输入框、时/分/秒分拆、极限温度)+ 列表选中同步显示 + 添加防重名询问更新 + 删除二次确认 + Recipes.json 持久化启动加载 + 配方列表垂直滚动条;批量设置配方窗口左侧标签与右侧输入框垂直居中对齐 |
| V1.24 | 面板"设置"按钮智能分流:点击工位未选中先加入选中;仅选 1 台弹工位设置窗口,选≥2 台弹批量设置配方窗口(ShowBatchRecipeForm 抽取复用);长按空白处取消全部选中并隐藏选中框;状态栏"在线"全部离线标红、默认"未连接"标红;工位面板配色优化(真空关默认红底、测试中=黄灯色、空闲=LimeGreen、上电灯未上电=红) |
| V1.23 | 通讯测试/送风机测试窗体共享主程序连接(不自建 TCP);DeviceManager/ModbusTcpIoController 新增共享原始寄存器 API |
| V1.22 | 通讯测试窗体:一键遍历通断跑马灯、打开自动连接+心跳断连提醒+遍历后台执行、0x2009 映射读-改-写、非模态映射提示窗 |
| V1.21 | 通讯测试窗体 SunnyUI 重构 + 备用通道映射点击提示 |
| V1.20 | 新增通讯测试窗体(IO DO 输出通道手动测试) |
| V1.19.x | 工位面板选中交互/配色系列优化、工位 SN/配方/延时关联(StationInfo)、真空显示文字化、报警阈值统一 kPa、权限角色着色、菜单"帮助"→"关于" |
| V1.18.x | 新增工位设置窗口 StationSettingsForm、行"全选"按钮、状态中文显示 |
| V1.17 | 系统设置窗口 SettingsForm(按分类编辑 App.config,仅管理员) |
| V1.16.x | 接入真实扫码枪 ScannerService、CH340 串口自动识别+端口缓存、连接心跳静默自愈/按需重连、小数位统一修复、工位面板更名重设计、IO 耦合器断网不影响气压表采集 |
| V1.15 | 老化测试业务闭环 + 送风机接入、真空确认/失联报警/老化计时/送风机生命周期、事件 CSV 落盘、设备阈值写入、送风机 IP 自动识别、备用通道映射 |
| V1.14 | 接入真实通讯链路(RTU+TCP),报警边沿联动 |
| V1.13 | 用户数据持久化 Users.json |
| V1.12 | 配方管理窗口左右分栏 |
| V1.09 | 接入 IO 分配表(IoMapBuilder 八进制映射,1 输入+2 输出) |
| V1.08 | 用户权限系统(登录/用户管理/权限按钮)+ 自适应分辨率滚动条 |
| ≤V1.07 | 早期:下拉菜单、修复记录、初始 Mock 架构 |
| 现象 | 排查方向 |
|---|---|
| 气压表数据不更新 | 串口设置/CH340 驱动/从站地址/波特率 19200 |
| 只有部分台能读 / 批量写某台超时 | 该台掉线或损坏(地址错/断电),先扫描定位 |
| 现场灯亮软件读 OFF / 软件写 ON 灯灭 | InvertInputs / InvertOutputs 置 true |
| IO 通讯失败 | 网线插紧(ST 闪绿=接触不良)、同网段、SP+FP 都要供 24V |
| 面板显示不全 | PanelColumns×PanelRows ≥ 设备总数 |
| 设计器"无法设计基类" | .cs 存 UTF-8 with BOM;删 obj/bin/.vs 重建 |
| 启动弹 TargetParameterCountException | BeginInvoke 数组参数未包 new object[]{...} |
| 送风机连不上 | FanIpAddress/FanIpCandidates 配置、端口 50000、FanEnabled=true |
| 程序卡顿 | 增大 CollectInterval;确认采集/写操作在后台线程 |
- ID 绑定数据持久化、批号关联生产记录
- 工位设置窗口"破空"按钮业务待确认(下电/保存/加入对列已实现,V1.26)
已完成的历史待办:用户密码明文→PBKDF2 哈希(V1.58.22,见
Services/PasswordHasher.cs); 界面 LOG 文本框→落盘 AppLog_yyyyMMdd.log(V1.58.21,见Services/AppLogFileWriter.cs)。