C# + ScottPlot 做一套工业振动监控系统

0 阅读5分钟

前言

工厂里的风机转了好几年没出过毛病,结果某个周五下午轴承突然异响,一停就是大半天。事后大家都在想同一个问题:要是能提前知道就好了。

这就是振动监控的意义。最近用 C# + WinForms + ScottPlot 5.x 写了一套多设备振动监控的小系统,从数据采集、实时绘图到报警和历史回放都走了一遍。过程中踩了一些坑,也摸到了一些门道,记录一下。

运行效果

实时监控界面展示多通道振动曲线:

报警日志和历史回放界面:

多设备数据叠加对比:

为什么选生产者-消费者模式

最开始的想法很简单:定时器读数据,画图,循环。结果压力测试下 UI 卡得没法看。问题在于数据是源源不断进来的,而界面刷新是有上限的,混在一起肯定出问题。

后来改成这套结构:

传感器数据 → 生产者线程 → Channel 缓冲区 → 消费者线程 → 内存缓存
                                                          ↓
                                               UI 定时器刷新图表

System.Threading.Channels 里的 Channel<T> 做缓冲,比 ConcurrentQueue 顺手得多,支持异步等待,不用空转 CPU。

_dataChannel = Channel.CreateBounded<VibrationPacket>(new BoundedChannelOptions(10000)
{
    FullMode = BoundedChannelFullMode.DropOldest,
    SingleWriter = false,
    SingleReader = true
});

DropOldest 这个设置是故意选的。工业场景下数据的新鲜度比完整性更重要,缓冲区满了就丢掉最旧的数据,不能让内存撑爆。

数据包结构

public class VibrationPacket
{
    public DateTime Timestamp { get; set; }
    public string DeviceId { get; set; }
    public string Channel { get; set; }
    public double Value { get; set; }
}

看着简单,但时间戳有个坑。演示程序用 DateTime.Now 没问题,真要接硬件传感器,必须用设备自带的硬件时间戳。否则多通道数据对齐会出现毫秒级偏差,高频场景下频谱图直接废掉。

信号仿真要真实一点

很多例子的信号仿真就是一个正弦波,太假了。真实旋转机械的振动是基频加多倍频 harmonics 再加随机噪声,偶尔还有冲击。

double fundamental = 5.0 * Math.Sin(2 * Math.PI * baseFreq * t);
double harmonic2x  = 2.0 * Math.Sin(2 * Math.PI * baseFreq * 2 * t);
double harmonic3x  = 0.8 * Math.Sin(2 * Math.PI * baseFreq * 3 * t);
double noise       = (rng.NextDouble() - 0.5) * 1.5;

double impulse = rng.NextDouble() < 0.005
    ? (rng.NextDouble() > 0.5 ? 1 : -1) * (8 + rng.NextDouble() * 4)
    : 0;

0.5% 概率触发冲击,模拟轴承剥落那种突发信号。这样跑起来报警偶尔会触发,演示效果比一条平滑曲线真实得多。

ScottPlot 5.x 的更新方式

ScottPlot 从 4.x 升到 5.x,API 变化不小。最大的坑是 SignalXY 的更新方式——不能像 4.x 那样直接改数组内容了,必须移除旧的 plot 再添加新的:

fpRealtime.Plot.Remove(signalPlot);
var newPlot = fpRealtime.Plot.Add.SignalXY(xs, ys);
newPlot.Color = signalPlot.Color;
newPlot.LineWidth = 1.5f;
newPlot.LegendText = signalPlot.LegendText;
_signalPlots[key] = newPlot;

实测 20Hz 刷新率、3 个通道、3000 个点以内没压力。

滑动窗口用队列实现:

buffer.Enqueue((relTime, packet.Value));
double cutoff = relTime - DISPLAY_TIME_WINDOW;
while (buffer.Count > 0 && buffer.Peek().time < cutoff)
    buffer.Dequeue();

30 秒窗口,最多 3000 个点,内存占用可控。

报警用边沿检测

值超阈值就弹窗是最蠢的做法。超标持续一分钟,能弹出一千个窗口。正确做法是只在状态切换的时候触发:

bool wasAlarm = _alarmState.ContainsKey(key) && _alarmState[key];
bool isAlarm  = Math.Abs(packet.Value) >= ALARM_THRESHOLD;
_alarmState[key] = isAlarm;

if (isAlarm && !wasAlarm)  // 上升沿触发
{
    string level = Math.Abs(packet.Value) >= ALARM_THRESHOLD * 1.5
        ? "紧急" : "报警";
    // 记录日志
}

写到 UI 控件时用 BeginInvoke 而不是 Invoke,前者不阻塞调用线程,高频场景下吞吐量差别明显。

BeginInvoke((Action)(() =>
{
    AppendAlarmRow(packet, level);
}));

历史回放

实时监控是看当下,回放是复盘。异常发生后,工程师需要倒回去看事发前后的曲线变化。

回放定时器每 50ms 推进一次,支持 0.5x 到 8x 倍速:

double stepPerTick = 0.5 * speedMultiplier;
_playbackTimer.Interval = 50;
_playbackTimer.Tick += (s, e) =>
{
    _playbackCursor += stepPerTick;
    // 从历史数据中截取窗口段并绘制
};

切换倍速时停掉旧定时器用新步长重启,比改 Interval 更干净。

回放期间实时图不刷新,用 _isPlayback 标志控制,避免两张图同时更新造成混乱。

暗色主题

工厂控制室光线通常偏暗,暗色主题不是花活,是刚需。切换逻辑很简单:

bool isDark = fpRealtime.Plot.FigureBackground.Color.R < 100;
var bg = isDark ? new ScottPlot.Color(245, 245, 245) : new ScottPlot.Color(30, 30, 30);
fpRealtime.Plot.FigureBackground.Color = bg;
fpRealtime.Plot.DataBackground.Color = isDark ? new ScottPlot.Color(255,255,255) : new ScottPlot.Color(45,45,48);

判断当前背景色亮度,切到另一套配色。

CSV 导出

导出功能看似简单,但有个编码问题:

File.WriteAllText(dlg.FileName, sb.ToString(), Encoding.UTF8);

如果设备名或通道名里有中文,不指定 UTF-8 的话 Excel 打开大概率乱码。

还可以往哪延伸

目前这套是单机仿真,真要上产线还有几块要补:

数据源对接:Modbus TCP、OPC-UA 或串口协议,替换掉仿真生成器

频域分析:加 FFT 频谱图,通过频率成分诊断不平衡、不对中、轴承磨损等具体故障

报警持久化:报警记录写 SQLite,程序重启不丢

总结

做这套东西最大的收获不是写了多少行代码,而是想明白了数据从哪来、以什么节奏来、UI 能扛多大压力、报警怎么不把人逼疯。这几个问题想清楚了,代码实现反而不是最难的部分。

生产者-消费者解耦、边沿检测、滑动窗口、回放隔离——每个设计背后都有具体的工程场景在驱动,不是为了用某个技术而用。

如果大家也在做类似的数据采集和监控项目,希望这些细节能帮你少踩几个坑。

关键词

振动监控、C#、WinForms、ScottPlot、生产者消费者、实时数据、报警检测、历史回放、工业传感器、Channel、边沿检测、数据可视化、多设备监控

最后

如果你觉得这篇文章对你有帮助,不妨点个赞支持一下!你的支持是我继续分享知识的动力。如果有任何疑问或需要进一步的帮助,欢迎随时留言。

也可以加入微信公众号 [DotNet技术匠] 社区,与其他热爱技术的同行一起交流心得,共同成长!

优秀是一种习惯,欢迎大家留言学习!

作者:技术老小子

出处:mp.weixin.qq.com/s/8S6eYw06z7xJo_ESRyUymg

声明:网络内容,仅供学习,尊重版权,侵权速删,歉意致谢!