这次我们来看一套基于 C# 的移动跨平台工业监控应用设计方案,项目代号 B1151。这个方案的定位很明确:把传统 C/S 架构下 C# 上位机的能力延伸到移动端,让 Android 手机、平板、Windows 触摸屏都能通过一套代码访问设备状态、读取实时数据、接收告警信息。对于本来就使用 .NET 技术栈的工控团队来说,它的价值在于不需要重新学一套语言,直接把 C# 里积累的通信逻辑、数据解析、设备驱动代码迁移到移动端。
方案的核心卖点可以归纳为四点。第一,跨平台,一套 C# 代码同时输出 Android、iOS、Windows 三端;第二,通信层独立,Modbus TCP、TCP Socket、串口、MQTT、FINS UDP 这类工控协议可以在一个统一接口下管理;第三,实时监控体验完整,设备状态、产量数据、告警事件都能在移动端实时刷新;第四,支持离线缓存和服务端 API 上报,断网时数据不丢,恢复网络后自动补传。
这篇文章会把 B1151 的设计思路和实现过程完整拆开:先看系统架构和技术选型,再讲环境准备、通信模块设计、监控界面实现、数据存储,然后是接口 API、功能测试、性能观察和常见问题排查。如果你正准备做 C# 上位机移动化改造,或者毕业设计选了工业监控方向,这篇文章可以直接当作设计参考。
1. 核心能力速览
先给一张规格表,方便快速判断这个方案适不适合你。
| 能力项 | 说明 |
|---|---|
| 项目类型 | 移动跨平台工业监控应用设计(项目代号 B1151) |
| 技术栈 | C# / .NET MAUI,MVVM 分层架构 |
| 目标平台 | Android、iOS、Windows,可扩展 macOS |
| 通信协议 | Modbus TCP、TCP/UDP Socket、串口、MQTT、FINS UDP(按现场设备选型) |
| 核心功能 | 设备状态监控、实时数据采集、告警推送、历史数据查询、本地缓存补传 |
| 数据存储 | SQLite 本地存储,服务端 API 同步 |
| 启动方式 | Visual Studio 编译打包,Android 生成 APK,Windows 生成 MSIX/EXE |
| API 能力 | REST API 数据上报、SignalR 实时推送(需配套服务端) |
| 批量任务 | 支持多设备并发采集,按设备组轮询 |
| 适合场景 | 工厂产线监控、设备巡检、能源数据采集、上位机移动端扩展 |
说明:移动端能否稳定支撑大规模设备,取决于通信层轮询策略和服务端压力,实际项目建议先做小规模压测,再决定单机接入设备数。
2. 适用场景与使用边界
B1151 适合三类人。第一类是工控软件工程师,手里的 WinForm/WPF 上位机需要移动端补充,这套方案能把串口、Socket、Modbus 那套代码逻辑复用过去;第二类是系统集成商,需要给现场交付一套可演示的产线监控 App,要求快速出原型;第三类是高校学生做工业物联网、智能工厂方向的课题,需要一个结构完整、能落地的设计与实现案例。
从问题域来看,它解决的是“设备数据从现场到手机”的链路问题:PLC、传感器、仪表的数据通过网关或直连进入移动端,经过解析、清洗后在界面上展示,同时写入本地库并通过 API 上报到服务端。这个链路在小型产线、设备柜、实验室环境下都可以跑通。
边界也要说清楚。它不适合做重型 SCADA 的全部功能,比如复杂配方管理、多级权限审批、数百点位的趋势分析,这类场景更适合用专业组态软件。实时性要求极高的运动控制、安全联锁也不建议走移动端,手机网络延迟和 App 生命周期都会影响可靠性。另外,涉及设备远程控制时必须加二次确认、操作审计和设备侧安全策略,不能只依赖 App 端做保护。
3. 系统总体架构设计
B1151 采用经典的四层结构:设备层、通信层、服务层、表现层。
设备层对应现场 PLC、传感器、智能仪表、工业相机等。通信层负责协议解析和数据采集,常见做法是“设备驱动 + 协议适配器”,每一种设备写一个驱动类,内部封装协议细节,对外暴露统一的数据接口。服务层处理业务逻辑:数据缓存、告警判定、统计计算、与远端 API 同步。表现层就是移动端页面,承担实时监控、告警列表、趋势图、参数设置等功能。
数据流向分两条。上行链路:设备 -> 通信层轮询 -> 服务层解析 -> 本地存储 -> UI 绑定刷新 -> API 上报服务端。下行链路:移动端下发指令 -> 服务层校验 -> 通信层写入设备。下行操作在工业现场风险较高,必须做权限校验和操作确认。
模块划分上,建议至少拆出 DeviceManager(设备管理器)、ProtocolAdapter(协议适配)、DataService(数据服务)、AlarmService(告警服务)、SyncService(同步服务)、MainViewModel(主监控视图模型)六个模块。DeviceManager 用单例模式管理设备连接池,避免页面切换导致连接状态丢失;告警和同步服务通过事件或委托把状态变化推给 UI 层。
4. 移动跨平台技术选型:为什么是 C# 方案
在移动跨平台这个方向上,可选方案不少:Flutter、React Native、原生 Android/iOS,还有 .NET MAUI。B1151 选择 C# 方案的核心理由是代码复用率和工控生态。
做上位机的人最熟悉的是 WinForm、WPF、控制台服务这类 .NET 技术。用 .NET MAUI 之后,C# 类库、协议解析代码、数据模型几乎可以原样迁移。比如你之前用 SerialPort 写串口采集,用 Socket 写 TCP 客户端,这些代码在 .NET MAUI 里稍作调整就能继续用;数据模型类、校验逻辑基本不动。如果选 Flutter 或 RN,这些逻辑全部要用 Dart 或 TypeScript 重写一遍。
从生态来看,C# 在工控领域的基础很好:OPC UA 有成熟的开源库,Modbus 有 NModbus,MQTT 有 MQTTnet,机器视觉场景里 Halcon 提供了 .NET 接口,可以和移动端通过服务方式集成。Web 后端用 ASP.NET Core 的团队,还能把认证、API、SignalR 拉通,技术栈完全统一。
当然也要承认,.NET MAUI 的生态相比 Flutter 还在追赶阶段,第三方图表、扫码、蓝牙组件的选择没有那么多。方案里给了一个折中:核心逻辑用 C# 共享代码,涉及特殊硬件能力(扫码枪、蓝牙打印机)时,通过平台特定实现或封装原生库来补,这也是 MAUI 官方推荐的做法。
5. 环境准备与前置条件
开发环境建议按以下清单准备:
- Windows 10/11 64 位系统;
- Visual Studio 2022,安装时勾选“.NET 跨平台开发”工作负载;
- .NET SDK(建议 .NET 8 或更高版本,MAUI 支持有对应版本要求);
- Android SDK、JDK,用于编译 Android 包;
- 测试设备:Android 手机或平板,开启开发者模式和 USB 调试;Windows 触摸屏可直连调试;
- 现场测试条件:至少一台支持 Modbus TCP 的 PLC 或 Modbus 仿真器,一台局域网交换机;没有实体设备时,可以用 Modbus Slave 模拟器验证通信。
磁盘方面,Visual Studio 加 Android 工作负载大概需要 20GB 到 30GB 空间;NuGet 还原会拉取 MAUI 相关包,首次编译较慢,建议保持网络畅通。正式开发前先在 Visual Studio 里运行“新建 .NET MAUI 应用”模板,编译到 Android 模拟器验证环境可用,再开始移植业务代码。
环境检查清单很简单:能否新建 MAUI 项目;能否部署到 Android 模拟器或真机;调试时能否在输出窗口看到日志;真机需要保证与开发机同一局域网,便于调试通信模块。
6. 数据采集与通信模块设计
通信层是整个 B1151 的根基。方案里不直接让 UI 调协议,而是抽象出一组接口,不同协议实现同一个接口。
public interface IDeviceDriver : IDisposable { Task<bool> ConnectAsync(CancellationToken ct); Task<DeviceDataFrame> ReadAsync(string pointAddress, CancellationToken ct); Task<bool> WriteAsync(string pointAddress, object value, CancellationToken ct); bool IsConnected { get; } event EventHandler<DeviceDataFrame> DataReceived; }以一个常见的 Modbus TCP 场景为例,读取多台 PLC 的寄存器数据,可以用 NModbus 库,也可以自己实现报文。现场设备不支持标准协议时,直接用 Socket 收发原始报文,按设备手册解析字节。解析时推荐把每一帧数据定义成结构体或只读记录类型,避免在 UI 线程里逐字节处理。
public class ModbusTcpDriver : IDeviceDriver { private TcpClient _client; private readonly string _ip; private readonly int _port; private readonly byte _unitId; public ModbusTcpDriver(string ip, int port, byte unitId) { _ip = ip; _port = port; _unitId = unitId; } public async Task<bool> ConnectAsync(CancellationToken ct) { _client = new TcpClient(); await _client.ConnectAsync(_ip, _port, ct); return _client.Connected; } public async Task<DeviceDataFrame> ReadAsync(string pointAddress, CancellationToken ct) { // 构造 Modbus TCP 请求帧,读取保持寄存器 // 实际报文按功能码、起始地址、寄存器数量组织 byte[] request = BuildReadRequest(_unitId, pointAddress); await _client.GetStream().WriteAsync(request, ct); byte[] response = await ReadFullFrameAsync(_client.GetStream(), ct); return ParseFrame(response); } public bool IsConnected => _client?.Connected ?? false; // WriteAsync、DataReceived 等成员按实际需求补齐 }这段代码展示了通信层的基本骨架:连接、读、写、状态判断。实际项目中还要补超时控制、断线重连、线程安全和对象池。高频采集场景下,每台设备一个驱动实例,由 DeviceManager 统一管理,避免页面切换时重复创建连接。串口场景的 SerialPort 用法类似,只是把 TcpClient 换成 SerialPort,并用委托或事件把解析后的数据抛给上层。
这里要特别提醒:工业现场的数据帧解析一定要按设备手册逐字节核对,包括字节序、数据类型、校验方式。C# 里 BitConverter 的默认字节序是主机序,解析大端数据时要用 BinaryPrimitives.ReverseEndianness 或自定义解析方法,这是新手最容易踩的坑。
7. 实时监控界面与数据绑定
移动端监控界面采用 MVVM 模式。ViewModel 暴露可观察属性,View 通过数据绑定刷新。核心做法是:通信层收到一帧数据后,更新 ViewModel 中的属性或 ObservableCollection,UI 自动刷新。
public class DeviceStatusViewModel : BindableObject { private string _deviceName; private double _temperature; private bool _isRunning; public string DeviceName { get => _deviceName; set { _deviceName = value; OnPropertyChanged(); } } public double Temperature { get => _temperature; set { _temperature = value; OnPropertyChanged(); } } public bool IsRunning { get => _isRunning; set { _isRunning = value; OnPropertyChanged(); } } public ObservableCollection<PointData> TrendData { get; } = new(); }界面布局上,工业监控 App 的高频页面是设备列表页、实时数据详情页、告警列表页、趋势图页。设备列表页用 CollectionView 展示多台设备,实时数据页用 Grid 布局仪表盘卡片,趋势图用第三方图表控件。用 .NET MAUI 时,页面热重载能在开发时快速调整布局,现场演示前建议先锁死页面方向并做字号适配,保证在平板和手机上都不变形。
实际开发中要注意一个细节:ObservableCollection 的更新必须在 UI 线程。数据采集线程是后台线程,直接往集合里 Add 会抛异常。方案里通过主线程调度器(MainThread.BeginInvokeOnMainThread)或者 AsyncObservableCollection 解决。高频刷新场景下,每秒几十帧的集合更新会造成界面卡顿,常见做法是节流:数据先攒到一个缓冲区,按 1 到 2 秒的间隔统一更新一次界面。告警列表这类低频数据可以每帧都刷新,高频模拟量必须节流。
8. 数据存储与离线缓存设计
移动端无法保证网络始终在线,B1151 在本地使用 SQLite 做历史数据和告警记录的缓存。SQLite 在 .NET MAUI 里有 sqlite-net-pcl 这个轻量级库,直接映射 C# 类到表结构。
public class HistoryRecord { [PrimaryKey, AutoIncrement] public int Id { get; set; } public string DeviceCode { get; set; } public string PointCode { get; set; } public double Value { get; set; } public DateTime CollectTime { get; set; } public bool Synced { get; set; } } public class AlarmRecord { [PrimaryKey, AutoIncrement] public int Id { get; set; } public string DeviceCode { get; set; } public string AlarmType { get; set; } public string Message { get; set; } public DateTime OccurTime { get; set; } public bool Synced { get; set; } }存储策略上,历史数据表要控制增长:按天分表或定期清理,避免 App 体积无线膨胀。告警记录建议保留一段时间后再归档。每次写入时给记录标记 Synced 字段,同步服务在 WiFi 环境下把未同步数据批量上传,成功后更新标记。本地库连接使用单例,避免多线程同时打开数据库造成锁冲突。
离线缓存设计要考虑设备点位的台账信息。很多移动端监控方案只存实时值,断网重连后点位名称、地址映射、报警阈值全部要重新拉取,体验很差。B1151 的做法是:设备配置信息首次登录后同步到本地,后续启动优先读取本地配置,在线时再检查服务端版本并增量更新。这样离线时 App 还能完整展示现场设备和点位信息,操作人员可以安全巡检。
9. 接口 API 与远程推送设计
B1151 的服务端接口采用 ASP.NET Core Web API,移动端通过 REST API 上报数据、拉取配置。实时推送场景使用 SignalR,比如告警发生时要让多个终端同时弹窗,轮询接口效率太低。
服务端告警推送接口示意:
[ApiController] [Route("api/alarm")] public class AlarmController : ControllerBase { private readonly IHubContext<AlarmHub> _hubContext; public AlarmController(IHubContext<AlarmHub> hubContext) { _hubContext = hubContext; } [HttpPost] public async Task<IActionResult> Report(AlarmNotifyDto dto) { // 校验签名、写入数据库 await _hubContext.Clients.Group(dto.DeviceCode) .SendAsync("OnAlarm", dto); return Ok(new { success = true }); } }移动端调用 API 时,建议把 HttpClient 封装成独立服务,统一处理 BaseUrl、Token 刷新、超时和重试。不要在页面代码里直接 new HttpClient,那样会导致端口耗尽和连接复用失效。批量上报时使用数组形式的请求体,一次请求携带数十条记录,减少交互次数。
SignalR 客户端在移动端的生命周期要特别处理:App 退到后台、锁屏、网络切换时,连接会断开,恢复后要自动重连。方案里在 App 级别的生命周期事件中统一管理 SignalR 连接,页面只订阅数据变化,不负责建立连接。告警推送需要后台响应的场景,可以结合系统推送通道,但要注意不同手机厂商对后台进程的限制,纯靠前台连接不能保证 100% 送达,关键告警要同时落库,打开 App 时再补一次全量同步。
10. 功能测试与效果验证
B1151 建议按下面六个维度做功能验证,测试顺序从简单到复杂。
第一,连接测试。用 Modbus 仿真器启动一个从站,在 App 里配置 IP、端口、单元号,点击连接,观察日志是否出现连接成功。判断标准:设备列表状态从离线变成在线,返回值与仿真器设置一致。
第二,实时数据测试。连续读取多个寄存器点位,确认数据刷新间隔、单位换算、字节序是否正确。判断标准:温度、压力、流量等模拟量与仿真器一致;发现数值乱跳时优先检查字节序和数据类型解析。
第三,写操作测试。通过 App 下发启动、停止、设定值修改指令。判断标准:仿真器收到指令,返回值正确;同时验证写操作是否有二次确认、权限校验和操作日志。
第四,告警测试。设置一个模拟量超过阈值,确认告警记录生成、界面弹窗、SignalR 推送三个环节都完成。判断标准:告警消息携带设备号、时间、类型,历史记录可查询。
第五,离线与断网测试。断网后继续采集数据,确认本地 SQLite 记录持续写入;恢复网络后,确认未同步记录自动补齐。判断标准:服务端记录数与本地一致,无重复、无丢失。
第六,稳定性测试。连续运行 24 到 48 小时,观察内存曲线、连接数、CPU 占用,检查断线重连是否正常。判断标准:无内存持续增长、无连接泄漏、断网重连后 10 秒内恢复数据采集。
11. 资源占用与性能观察
移动端的资源占用需要实测,本文不给拍脑袋数字,但给一套观察方法。调试阶段用 Visual Studio 的诊断工具或 Android Profiler 观察内存、CPU、网络流量;正式部署前在真机上连续运行,记录关键指标:App 启动时间、页面切换耗时、内存曲线是否平稳、后台运行是否被系统回收。
影响资源占用的因素主要有四个:轮询频率、界面刷新节流、日志量和图表数据量。轮询频率越高,CPU 和网络开销越大;界面刷新越频繁,渲染线程压力越大;日志写到文件但不轮转,磁盘会慢慢占满;趋势图加载几千个点会让 Canvas 渲染明显变慢。这些点按“能降则降,够用就行”的原则调,比如模拟量从 500ms 轮询改成 2s 轮询,生产上完全够用且省电。
降低占用的通用做法包括:高频率数据只保留最近 N 条在内存,历史数据落库;图表在无操作时暂停刷新;WiFi 下才开启批量同步;使用对象池复用字节缓冲区和数据帧对象,减少 GC 压力。工业监控 App 对实时性有要求,但移动端电源和 CPU 有限,合理降频比单方面追求最高刷新率更符合实际。
12. 常见问题与排查方法
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 编译 MAUI 工程失败 | 缺少工作负载或 Android SDK 版本不匹配 | 查看错误输出,确认安装的 SDK 平台 | 安装 .NET 跨平台工作负载,对齐 SDK 版本 |
| 真机部署失败 | 设备未开启开发者模式或驱动问题 | 检查 USB 调试、设备管理器驱动 | 开启开发者模式,重新安装设备驱动 |
| 连接设备超时 | 设备 IP/端口配置错误或防火墙拦截 | 先 PC 端用工具测试设备连通性 | 核对设备配置,开放防火墙端口 |
| 数据解析乱码或数值错误 | 字节序、数据类型、寄存器地址错误 | 对照设备手册逐字节核对 | 修正解析方法,用固定报文做单元测试 |
| UI 卡顿、列表闪烁 | 后台线程直接改集合、刷新频率过高 | 检查是否有跨线程更新、增加节流 | 用主线程调度器更新集合,降刷新频率 |
| 断网重连失败 | 未处理 Socket 异常或重连策略缺失 | 看日志确认异常类型 | 补超时和重连机制,增加指数退避 |
| SignalR 推送收不到 | 连接未建立或生命周期事件未处理 | 打印连接状态日志 | 在 App 生命周期中统一管理连接和重连 |
| 本地库写入缓慢 | 多处同时写库或数据量过大 | 检查是否使用单例数据库连接 | 统一数据库连接,批量插入,定期清理 |
排查的第一原则是先看日志。B1151 在通信层和服务层都留了日志入口,日志至少要包含时间、设备号、操作类型、异常堆栈。现场问题排查大多数情况下都是靠日志定位的,临时加断点调试不如直接看日志信息直观。
13. 最佳实践与合规建议
工程化落地上,第一点建议是从小规模开始。第一次联调只接一台 PLC、配三个点位,跑通全链路再逐步扩展设备和点位。一次性接几十台设备容易把问题混在一起,分不清是通信问题还是业务问题。
第二点是配置分离。设备 IP、端口、点位映射、告警阈值全部放在配置文件里,不要写死在代码中。现场设备经常更换,写死的配置会变成运维负担。
第三点是安全和合规。工业监控涉及设备数据和生产信息,要重视几个方面:移动端与设备、服务端之间的通信必须加认证和权限控制;远程写操作必须二次确认并审计;涉及员工信息、产线录像、设备参数的数据要按企业安全策略管理。如果方案里接入人脸识别、声音采集、工业相机图像等功能,必须确认获得明确授权,符合隐私和版权规范,并在测试环境验证后再部署到正式现场。
第四点是版本管理。移动端、服务端、设备固件三个版本要对应,接口变更要通过版本号控制。现场出现问题时,能快速定位是哪个组件版本不匹配。
第五点是备份。SQLite 数据库文件要定期备份,特别是在离线采集模式下,本地数据是唯一的记录,一旦丢失无法找回。
14. 总结与下一步
B1151 这套方案最值得尝试的点,是用 C# 把传统上位机能力搬到了移动端,通信层、数据模型、服务逻辑都能复用,团队不需要重新学一门跨平台语言。最先应该验证的功能是 Modbus TCP 数据采集链路,只要这个链路通了,后续的界面、存储、告警、API 都只是往上叠加。
最容易踩的坑有三个:字节序解析错误、后台线程更新 UI 导致崩溃、断网重连没有重试机制。这三个问题几乎每个做工业通信的移动项目都会遇到,建议在编码阶段就把超时、重连、线程调度写成公共基础组件,而不是每个页面各自处理。
接下来可以扩展的方向包括:接入 OPC UA 减少设备协议适配工作量;增加趋势分析和报表导出;把相机和扫码模块集成到巡检闭环流程;服务端增加设备分组、权限细粒度管理和操作审计报表。整个架构已经把通信、存储、接口分开了,新增设备协议和新增页面都不会伤筋动骨。建议收藏备用,做 C# 上位机移动化的项目时直接参考这个框架起步。