# 智能设备生产监控系统 **Repository Path**: Yubin1874/SmartDeviceMonitor ## Basic Information - **Project Name**: 智能设备生产监控系统 - **Description**: 项目描述:面向工业罐装场景,基于WPF自主开发的PLC实时监控上位机。 实现对Modbus RTU设备的200ms轮询采集、温度趋势可视化、生产数据记录、报警管理及设备启停控制。 技术栈:.NET 8、WPF、MVVM(CommunityToolkit.Mvvm)、DI、Modbus RTU(NModbus4)、SQLite(FreeSql)、LiveCharts.Wpf、 - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 4 - **Forks**: 3 - **Created**: 2026-06-25 - **Last Updated**: 2026-10-10 ## Categories & Tags **Categories**: Uncategorized **Tags**: WPF, modbus, Net8, Sqlite ## README # SmartDeviceMonitor — 智能设备生产监控系统 > 一个本科阶段的 WPF 工业上位机学习项目。基于 MVVM + Modbus RTU 实现,面向智能罐装生产线的设备监控与数据采集。 > 当前状态:核心功能可运行,代码仍在持续优化中,欢迎交流讨论。 ![.NET 8](https://img.shields.io/badge/.NET-8.0-blue) ![WPF](https://img.shields.io/badge/WPF-Desktop-blue) ![Modbus RTU](https://img.shields.io/badge/Modbus-RTU-orange) ![SQLite](https://img.shields.io/badge/SQLite-3-green) --- ## 项目简介 这是我在学习 C# 上位机开发过程中,为理解 **WPF / MVVM / 工业通信 / 数据可视化** 而做的一个完整练手项目。项目模拟(未来可接入真实 PLC)一条罐装生产线的监控场景,实现: - 通过 **Modbus RTU** 从 PLC 读取温度、产量、液位、阀门状态等实时数据 - 使用 **WPF + MVVM**(CommunityToolkit.Mvvm)构建 UI,降低界面与业务逻辑的耦合 - 使用 **SQLite** 持久化生产记录,集成 Serilog 做多目标日志(文件 + 数据库 + 控制台) - 使用 **LiveCharts.Wpf** 绘制实时温度趋势曲线 **做这个项目的初衷**:之前用 WinForms 写过一个类似项目,但代码耦合严重(视图直接调串口、全局变量到处飞),修改功能时牵一发动全身。这次想用 WPF 的 MVVM + DI 重新梳理一遍,让自己在入职实习前对工业软件的架构有基本认知。 --- ## 技术栈 | 技术 | 说明 | 为什么选这个 | |------|------|-------------| | **.NET 8** | 运行时 | 学校还在教 .NET Framework,但企业新项目基本转向 .NET 6+,提前适应 | | **WPF** | 桌面 UI | 比 WinForms 更适合复杂工业界面,支持数据绑定、样式、自定义控件 | | **CommunityToolkit.Mvvm** | MVVM 框架 | 不用手写 `INotifyPropertyChanged`,自动代码生成,减少样板代码 | | **Microsoft.Extensions.DI** | 依赖注入 | 解耦 ViewModel 与 Service,比直接 `new` 更符合企业规范 | | **NModbus4** | Modbus RTU 通信 | 工业设备最常用协议,串口 + Modbus 是上位机基本功 | | **FreeSql + SQLite** | ORM + 本地数据库 | 轻量级,不需要单独装数据库,方便部署到车间工控机 | | **LiveCharts.Wpf** | 数据可视化 | 工业场景需要实时曲线,先学会这个,以后有机会再学 OxyPlot | | **Serilog** | 结构化日志 | 工业软件日志很重要,需要同时写文件、数据库和界面 RichTextBox | --- ## 功能清单 ### 已完成功能 - [x] 登录认证(SHA256 加盐哈希,支持管理员/工程师/操作员角色) - [x] Modbus RTU 串口连接与轮询(200ms 周期) - [x] 实时数据显示:产量、温度、运行时间、节拍、液位、阀门状态 - [x] 温度趋势柱状图(40 点滚动窗口) - [x] 设备控制:启动 / 停止 / 复位(写 Modbus 线圈) - [x] 生产数据记录(SQLite 存储批次、温度、产量、节拍) - [x] 报警管理与最近报警列表展示 - [x] 自定义深色主题(Theme.xaml) - [x] 自定义用户控件:StatusLight(三色状态灯)、StatusIndicator(PLC在线指示) - [x] 全局异常捕获(UI线程 + 非UI线程 + Task) ### 待完善 / 已知问题 - [ ] **StatusLight 的 GradientStop 绑定**:`RelativeSource={RelativeSource AncestorType=UserControl}` 在 `Freezable` 上查找不到祖先,需要改为 `ElementName` 绑定(已定位,待修复) - [ ] **PLC 数据异常处理**:`ReadStateAsync` 抛出 `OperationCanceledException` 时会直接终止轮询循环,应改为 `InvalidOperationException` 走重试逻辑 - [ ] **DashBoardViewModel 中 `Task.Run` 属性更新**:虽然 `[ObservableProperty]` 会自动调度,但异常 Task 被丢弃后可能静默失败 - [ ] **缺少用户管理界面**:当前仅有默认的 Admin/Engineer/Operator 账户,无法增删改查 - [ ] **缺少配置界面**:串口号、波特率等参数目前通过 JSON 文件读取,缺少可视化配置页 - [ ] **无单元测试**:课程项目赶进度,测试全靠手点,这是最大短板 --- ## 项目结构 ``` SmartDeviceMonitor/ ├── App.xaml.cs # 程序入口,DI 容器注册,全局异常处理 ├── MainWindow.xaml/.cs # 主窗口,顶部导航栏,ContentControl 承载当前页面 ├── MainWindowViewModel.cs # 主窗口 VM,管理导航、PLC连接状态、当前用户 ├── Assests/Styles/ │ └── Theme.xaml # 深色主题资源字典 ├── Converters/ │ ├── LightStateToBrushConverter.cs # 三色灯枚举 → 画刷转换 │ └── ZeroToVisibilityConverter.cs # 0 → Visible ├── GlobalEnum/ │ ├── LightStateEnum.cs # 灯色:Off / Green / Yellow / Red │ ├── NavigationEnum.cs # 导航页面枚举 │ └── RoleEnum.cs # 用户角色:Admin / Engineer / Operator ├── Models/ │ ├── User.cs # 用户实体(FreeSql 特性标注) │ ├── DeviceState.cs # PLC 读取的实时状态实体 │ ├── DeviceSettings.cs # 串口/PLC 配置参数 │ ├── ProductionRecord.cs # 生产记录实体 │ ├── AlarmRecord.cs # 报警记录实体 │ ├── SystemLog.cs # 系统日志实体 │ └── ModbusConfigHelper.cs # 寄存器地址常量(0x00~0x08) ├── Services/ │ ├── PLCServices.cs # Modbus RTU 通信、轮询循环、读写线圈 │ ├── UserServices.cs # 用户认证、密码哈希(SHA256+盐) │ ├── ConfigServices.cs # JSON 配置文件读写 │ ├── DBProvider.cs # FreeSql 初始化 │ ├── AlarmServices.cs # 报警触发与分发 │ ├── DataServices.cs # 数据查询服务(预留) │ └── Logs/ │ └── LogServices.cs # Serilog 封装 ├── UserControls/ │ ├── StatusLight.xaml/.cs # 三色状态灯 │ ├── StatusIndicator.xaml/.cs # 状态指示器 │ └── NavigationMenu.xaml/.cs # 左侧导航菜单 ├── ViewModels/ │ ├── DashBoardViewModel.cs # 主页监控 │ ├── AlarmsViewModel.cs # 报警管理 │ ├── LogsViewModel.cs # 运行日志 │ ├── DashQueryViewModel.cs # 数据查询 │ └── SettingViewModel.cs # 系统设置(预留) └── Views/ ├── LoginWindow.xaml/.cs # 登录窗 ├── DashBoardView.xaml/.cs # 主页监控 ├── AlarmsView.xaml/.cs # 报警视图 ├── LogsView.xaml/.cs # 日志视图 ├── DashQueryView.xaml/.cs # 数据查询视图 └── SettingView.xaml/.cs # 设置视图(预留) ``` > `App.xaml` 中通过 `DataTemplate` 将 `ViewModel` 映射到 `View`,实现导航切换。 --- ## 我的踩坑记录(持续更新) 这些问题都是写这个项目时真实遇到的,记录在这里方便自己复盘。 ### 1. `RoutedEventArgs` 缺少 `new` **现象**:编译报错"不可调用的成员'RoutedEventArgs'不能像方法一样使用"。 **原因**:`LoginWindow_KeyDown` 里写了 `RoutedEventArgs()` 而不是 `new RoutedEventArgs()`。更简单的做法:直接传现有的 `e`(`KeyEventArgs` 本身就是 `RoutedEventArgs` 的子类)。 **结论**:WPF 事件参数直接复用,不需要重复 `new`。 ### 2. `ComboBox` 的 `DisplayMemberPath` 大小写错误 **现象**:`UserNameCombo` 绑定后显示空白。 **原因**:`DisplayMemberPath="Username"` 但实体属性是 `UserName`(大写 N)。虽然 .NET 反射不严格区分大小写,但 WPF 的 `PropertyPath` 在部分版本下是敏感的。 **结论**:XAML 的 `Path` 和实体属性名保持完全一致。 ### 3. `StatusLight` 内部 `GradientStop` 绑定失效 **现象**:三色灯永远是灰色,Converter 从未被调用。 **原因**:`GradientStop` 继承自 `Freezable`,**不在视觉树中**,`RelativeSource={RelativeSource AncestorType=UserControl}` 查找不到祖先。 **修复**:给 `UserControl` 加 `x:Name="Root"`,绑定改为 `ElementName=Root, Path=State`。 **教训**:`Freezable` 对象绑定不要用 `RelativeSource`,用 `ElementName`。 ### 4. `MainWindowViewModel` 中 `isPLCConnected` 小写赋值 **现象**:PLC 连接成功后,顶部"PLC在线"圆点不显示。 **原因**:`PLCServices.ConnectionChanged += (x, c) => isPLCConnected = c;` 写的是字段而不是属性。`[ObservableProperty]` 生成的是大写 `IsPLCConnected` 属性,字段直接赋值**绕过 `SetProperty`,不触发 `PropertyChanged`**。 **结论**:手写事件回调时,注意属性名的大小写,必须走 setter。 ### 5. `PasswordBox` 文字居中 **现象**:密码输入框文字偏左。 **原因**:`PasswordBox` 没有 `TextAlignment` 属性,必须重写 `ControlTemplate`,把 `PART_ContentHost` 的 `HorizontalContentAlignment` 设为 `Center`。 **教训**:`PART_ContentHost` 是命名约定,WPF 会识别它作为内容容器。 ### 6. `App.xaml.cs` 中 `Current.MainWindow = this.MainWindow` **现象**:登录后主窗口显示,但 `Application.Current.MainWindow` 为 `null`。 **原因**:`this` 是 `App` 类,此时 `App.MainWindow` 还没有赋值。 **修复**:改为 `Current.MainWindow = mainWindow`(用前面 `new` 出来的实例)。 ### 7. `Task.Run` 包裹 `async` lambda 的异常被吞 **现象**:`DashBoardViewModel` 的 `OnDataReceived` 中,数据库插入失败但界面无报错。 **原因**:`_ = Task.Run(async () => { ... await InsertAsync(); });` 返回值被丢弃,异常被 `TaskScheduler.UnobservedTaskException` 捕获。 **当前做法**:在 `App.xaml.cs` 中注册了 `UnobservedTaskException` 处理器并 `SetObserved()`,至少不会崩溃。但更好的做法是把数据更新逻辑移到 `Dispatcher.Invoke` 中同步执行。 --- ## 运行环境 - **操作系统**:Windows 10/11(.NET 8 WPF 仅支持 Windows) - **开发环境**:Visual Studio 2022(需要 .NET 8 桌面开发工作负载) - **运行时**:.NET 8.0 SDK - **硬件需求**:有可用串口(或虚拟串口如 `com0com`)用于模拟 Modbus 通信;若无串口,程序会提示连接失败,但其他 UI 功能仍可运行 ### 快速启动 ```bash # 1. 克隆仓库 git clone https://gitee.com/yourname/SmartDeviceMonitor.git # 2. 用 VS 2022 打开 SmartDeviceMonitor.sln # 3. 还原 NuGet 包 # 4. F5 运行(默认启动后弹出 LoginWindow,默认账户:Admin / Admin) ``` > 首次运行会自动创建 `SmartDeviceMonitor.db` 数据库和 `Logs/` 目录。 --- ## 关于作者 - 自学 C# 上位机开发,从 WinForms 到 WPF,从单文件到 MVVM + DI - 2027 届本科在读 - 长期目标:制造业工业软件岗位 **这个项目不是完美的,但它是我从 "能跑就行" 到 "想写得像企业代码" 的第一步。** 如果你也在学上位机开发,欢迎交流,一起踩坑一起进步。 --- ## License MIT — 允许自由使用,但代码按原样提供,不保证生产环境稳定性。 > **免责声明**:本项目为学习用途,Modbus 通信逻辑基于模拟环境,直接接入真实 PLC 前请根据实际情况调整寄存器地址和异常处理逻辑。