news 2026/10/10 9:17:15

C# 多协议传感器采集平台实战:串口/WiFi/USB全接入,事件驱动架构性能拉满

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C# 多协议传感器采集平台实战:串口/WiFi/USB全接入,事件驱动架构性能拉满

做过工业采集和边缘物联网项目的朋友,应该都懂多传感器接入的糟心。
这边STM32通过串口发温湿度,那边ESP32连WiFi走MQTT传环境数据,还有USB-CDC的ADC设备一路吐高频采样值——接口不统一,协议各玩各的,连数据格式都千差万别。
很多项目写到最后,采集层的代码堆得乱七八糟,接收、解析、存库、刷界面全揉在一块,数据量稍微一大,UI直接卡死,排查问题都不知道从哪下手。更别说还要考虑时序数据持久化、本地备份、跨平台部署这些事。

最近在做边缘计算节点的项目,基于C#搭了一套多协议传感器数据采集平台,把串口、WiFi、USB三类传感器统一接入,用事件驱动模式彻底拆解开数据采集、业务处理和UI展示,再配上InfluxDB做时序存储、LiveCharts跑实时曲线,跑了几个月稳定性和性能都在线。
这篇就把完整的设计思路和核心实现拆出来,都是实际项目踩坑踩出来的干货。

一、技术选型:为什么选这一套组合?

选型的核心思路很简单:够用、稳定、开发效率高,不搞花里胡哨的技术堆砌。

  • C# + .NET:开发效率高,类型安全,支持桌面、控制台、服务多种部署形态。.NET 6+原生跨平台,Windows工业电脑和Linux边缘网关都能跑,一套代码复用。
  • MQTTnet:.NET生态最成熟的MQTT库,轻量高性能,客户端和服务端都支持。本地做Broker或者对接外部MQTT服务器都方便,比自己手写Socket稳定太多。
  • LiveCharts:专为.NET设计的图表库,数据绑定简单,支持实时滚动更新。不用自己啃GDI+画曲线,几行代码就能出专业的实时波形。
  • InfluxDB:时序数据库首选,针对带时间戳的传感器数据做了深度优化,写入和查询性能甩关系型数据库几条街,非常适合海量历史数据存储。
  • SQLite:嵌入式轻量数据库,不用额外装服务,随程序走。作为本地备份兜底,断网或者InfluxDB异常的时候保证数据不丢失。

二、整体架构:事件驱动,四层解耦

整个平台的核心设计思想就是「职责分离,事件驱动」,把原来拧成一团的逻辑拆成四层,层与层之间只通过事件传递数据,不做直接调用。

从上到下分别是:

  1. 采集接入层:对接各类硬件接口,负责原始数据接收和协议解析,统一转换成标准数据模型后抛出事件。
  2. 数据总线层:全局事件中枢,接收所有采集器的数据,分发给下游订阅者,是解耦的核心。
  3. 业务处理层:包含可视化展示和持久化存储,各自订阅数据总线,按需处理数据,互不干扰。
  4. 存储层:时序数据主存InfluxDB + 本地备份SQLite,双写保证数据可靠性。

这种架构最大的好处就是,采集线程永远不会因为刷UI、存数据库被阻塞,UI也不会因为采集量大而卡顿。新增传感器或者新增业务逻辑,只需要加对应的模块,不用动原有代码,扩展性极强。

三、核心模块实现细节

1. 统一数据模型:先把格式对齐

不管什么接口、什么协议的传感器,上来之后先转成统一的数据结构,后面所有层都只认这个模型,彻底屏蔽底层硬件差异。

public class SensorData { /// <summary> /// 设备唯一标识 /// </summary> public string DeviceId { get; set; } /// <summary> /// 测量类型:Temperature/Humidity/Voltage等 /// </summary> public string MeasureType { get; set; } /// <summary> /// 采样数值 /// </summary> public double Value { get; set; } /// <summary> /// 采集时间戳 /// </summary> public DateTime Timestamp { get; set; } = DateTime.Now; }

2. 多协议采集层:三类传感器接入实现

(1)串口采集(STM32温湿度)

串口是最常用也最容易出问题的接口,核心坑点是粘包和分包。这里采用事件驱动接收,按自定义帧协议解析,解析完整帧再抛出数据。

public class SerialPortCollector { private readonly SerialPort _serialPort = new SerialPort(); public event Action<SensorData> OnDataReceived; public void Start(string portName, int baudRate) { _serialPort.PortName = portName; _serialPort.BaudRate = baudRate; _serialPort.DataReceived += SerialPort_DataReceived; _serialPort.Open(); } private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e) { int count = _serialPort.BytesToRead; byte[] buffer = new byte[count]; _serialPort.Read(buffer, 0, count); // 按帧头帧尾协议解析,处理粘包分包 if (!TryParseFrame(buffer, out double temp, out double humi)) return; // 解析完成后抛出事件 OnDataReceived?.Invoke(new SensorData { DeviceId = "STM32_01", MeasureType = "Temperature", Value = temp }); } }
(2)WiFi传感器(ESP32 + MQTT)

WiFi类传感器一般走MQTT协议,用MQTTnet客户端订阅主题,解析JSON报文即可。重点是要做断线自动重连,生产环境网络波动很常见。

public class MqttSensorCollector { private readonly IMqttClient _mqttClient; public event Action<SensorData> OnDataReceived; public async Task Start(string brokerIp, int port, string topic) { var factory = new MqttFactory(); _mqttClient = factory.CreateMqttClient(); // 接收消息处理 _mqttClient.ApplicationMessageReceivedAsync += e => { string payload = Encoding.UTF8.GetString(e.ApplicationMessage.Payload); var data = JsonSerializer.Deserialize<SensorData>(payload); OnDataReceived?.Invoke(data); return Task.CompletedTask; }; // 断线重连 _mqttClient.DisconnectedAsync += async e => { await Task.Delay(3000); await _mqttClient.ReconnectAsync(); }; // 连接并订阅 await _mqttClient.ConnectAsync(new MqttClientOptionsBuilder() .WithTcpServer(brokerIp, port) .WithKeepAlivePeriod(TimeSpan.FromSeconds(30)) .Build()); await _mqttClient.SubscribeAsync(topic); } }
(3)USB-CDC 高速ADC采集

USB-CDC本质是虚拟串口,但采样频率高、数据量大,不适合每条数据都抛事件。一般做法是在内置缓冲区攒一批数据,解析完成后批量抛出,减少事件触发开销。

3. 数据总线:全局事件中枢

用一个静态类做全局事件总线,所有采集器把数据发到这里,所有消费者从这里订阅。采集端和消费端完全不知道对方存在,彻底解耦。

public static class DataBus { /// <summary> /// 数据发布事件 /// </summary> public static event Action<SensorData> DataPublished; /// <summary> /// 发布数据 /// </summary> public static void Publish(SensorData data) { DataPublished?.Invoke(data); } /// <summary> /// 订阅数据 /// </summary> public static void Subscribe(Action<SensorData> handler) { DataPublished += handler; } } // 采集器注册:所有采集器的数据都汇入总线 serialCollector.OnDataReceived += DataBus.Publish; mqttCollector.OnDataReceived += DataBus.Publish; usbCollector.OnDataReceived += DataBus.Publish;

4. 实时可视化:LiveCharts 动态滚动曲线

UI层用LiveCharts做实时曲线,两个核心优化点:一是限制数据点数量实现自动滚动,二是X轴格式化为真实时间。

很多人用LiveCharts越用越卡,大多是因为数据点只加不删,或者每次加数据都强制全量刷新。

// 可观测集合,绑定到曲线控件 public ObservableCollection<MeasurePoint> TemperaturePoints { get; set; } = new(); // 订阅数据总线 DataBus.Subscribe(data => { if (data.MeasureType != "Temperature") return; // 切回UI线程更新 this.BeginInvoke(() => { TemperaturePoints.Add(new MeasurePoint { Time = data.Timestamp, Value = data.Value }); // 保留最近300个点,超出自动移除,实现滚动效果 if (TemperaturePoints.Count > 300) TemperaturePoints.RemoveAt(0); }); }); // X轴配置为时间格式 AxisX = new Axis { LabelFormatter = v => DateTime.FromOADate(v).ToString("HH:mm:ss"), Separator = new Separator { Step = TimeSpan.FromSeconds(10).ToOADate() } };

5. 持久化层:InfluxDB + SQLite 双存储策略

传感器数据是典型的时序数据,InfluxDB写入查询效率最高,但不能只存一份。采用「主存+本地备份」双写策略,保证数据不丢。

InfluxDB 批量写入

绝对不要来一条写一条,单条写入开销极大。用队列攒数据,定时批量写入,性能能提升几十倍。

public class InfluxDbWriter { private readonly Queue<SensorData> _queue = new Queue<SensorData>(); private readonly Timer _flushTimer; public InfluxDbWriter() { // 1秒刷新一次,批量写入 _flushTimer = new Timer(FlushBatch, null, 1000, 1000); } public void Write(SensorData data) { lock (_queue) { _queue.Enqueue(data); } } private void FlushBatch(object state) { SensorData[] batch; lock (_queue) { batch = _queue.ToArray(); _queue.Clear(); } if (batch.Length == 0) return; // 调用InfluxDB.Client批量写入 // 具体写入代码省略 } }
SQLite 本地备份

同时异步写入本地SQLite数据库,作为兜底。SQLite无需额外服务,文件型数据库,非常适合边缘设备本地存储。写入策略同样是批量提交,降低磁盘IO。

四、生产环境踩坑与性能优化

1. UI线程阻塞问题

这是新手最容易踩的坑:直接在采集线程里操作控件,或者在数据接收事件里做耗时操作。
事件驱动架构本身已经把采集和UI分开了,只要记住所有UI更新都通过BeginInvoke切到UI线程,且只做最少的界面操作,基本不会出现界面卡死。

2. 高频采集的内存控制

ADC这类每秒上千条的高频数据,不要每条都触发事件,先在采集层做降采样或者批量打包再上报。
曲线展示不要无限累加数据点,保留最近一段时间的即可,否则内存会持续上涨。

3. MQTT断线重连

MQTTnet默认不会自动重连,网络一波动就永久断开。一定要监听Disconnected事件,做指数退避重连,生产环境必备。

4. InfluxDB写入优化

  • 必须批量写入,建议每批100~1000条
  • Tag只放索引字段(设备ID、测量类型),数值全部放Field
  • 不要频繁建表,相同类型数据共用一个Measurement

5. 跨平台部署注意事项

  • Linux下串口路径是/dev/ttyUSB0这种格式,要做配置化适配
  • Linux串口需要用户权限,把运行用户加到dialout组
  • 边缘网关可以用控制台程序+Systemd托管,开机自启

五、部署方案:适配边缘计算节点

这套平台支持两种部署模式,代码基本不用改:

  1. 本地监控模式:采集、存储、UI都在一台Windows工业电脑上,用WinForms/WPF做界面,适合车间、机房本地监控场景。
  2. 边缘网关模式:采集+本地存储部署在Linux边缘节点,无UI,以后台服务运行,数据定时同步到云端,适合分布式多点采集。

因为基于.NET跨平台框架,两种模式只需要改配置文件,核心代码完全复用,部署非常灵活。

六、总结

这套方案不是什么高大上的架构,但胜在简单、实用、踩过坑。对于绝大多数工业传感器采集、环境监测、设备状态监控的场景,完全够用,开发效率比从零搓接口、画控件高太多。

如果你也在做多协议传感器接入,被UI卡顿、代码耦合、数据存储混乱的问题困扰,非常推荐试试事件驱动+时序数据库的思路。不用搞复杂的中间件,几行代码就能把架构理清楚。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/10 9:15:34

Twenty CRM 5.8万星背后:为AI Agent打造的可编程数据模型与集成实战

1. 从5.8万星说起&#xff1a;这个CRM到底戳中了什么痛点第一次在代码托管平台上刷到Twenty这个项目时&#xff0c;5.8万颗星这个数字确实让我停了一下。做企业软件这么多年&#xff0c;我太清楚CRM这个赛道有多拥挤了——从老牌巨头到各种轻量级SaaS&#xff0c;几乎每个团队都…

作者头像 李华
网站建设 2026/10/10 9:15:29

SAP ABAP CDS Service Consumption 深度解析,从数据模型到 OData、InA 与 SQL Service

在 ABAP 系统里完成一个 CDS View,并不代表这个数据模型已经能够被外部系统使用。 CDS View 更接近 ABAP 应用内部的数据语义模型。它可以被 ABAP SQL 查询,可以成为 RAP Business Object 的组成部分,也可以继续被其他 CDS View 组合。但当消费方离开 Application Server A…

作者头像 李华
网站建设 2026/10/10 9:15:14

小白程序员也能学会的大模型Agent开发入门指南

本文对比了大模型算法专家与Agent开发岗的薪资、学习内容和前景。大模型算法岗薪资高但门槛极高&#xff0c;适合深入研究模型&#xff1b;Agent开发岗则更注重AI应用落地&#xff0c;适合初学者和程序员&#xff0c;薪资稳定且发展前景广阔。建议小白程序员抓住Agent开发黄金期…

作者头像 李华
网站建设 2026/10/10 9:14:41

老鼠乌托邦的深层系统启示:人类复杂度更高,为何文明退行风险远超简单生物系统 【合规免责声明】 本文为社会系统、技术文明、交叉学术思辨类原创分析文章,仅用于理论研究、学术探讨、社会趋势理性观察。

老鼠乌托邦的深层系统启示&#xff1a;人类复杂度更高&#xff0c;为何文明退行风险远超简单生物系统【合规免责声明】本文为社会系统、技术文明、交叉学术思辨类原创分析文章&#xff0c;仅用于理论研究、学术探讨、社会趋势理性观察。全文观点中立客观、基于公开实验数据与前…

作者头像 李华
网站建设 2026/10/10 9:14:40

【大数据毕设项目】基于Hadoop+Spark的城市噪音数据分析与可视化系统\基于Python的城市噪音监测数据大屏设计

文章目录 一、项目开发背景意义 二、项目开发技术 三、项目开发内容 四、项目展示 五、项目相关代码 六、最后 一、项目开发背景意义 随着城市化进程的不断加快&#xff0c;城市噪音污染已成为影响居民生活质量和城市生态环境的重要因素。传统的噪音监测手段往往受限于数…

作者头像 李华