先讲个真事。去年给一家建材厂换过磅软件,旧系统还是十几年前用老技术写的,串口一拔就死,数据全靠司磅员手抄。司机在磅前排长队,三个人轮流值班都忙不过来。那套取代它的C#称重系统源码,从串口通信到报表打印全部重写,一辆车从开上地磅到磅单出来平均不到四十秒。这篇文章就围绕这套地磅程序的设计思路展开,把称重系统里最容易翻车的串口读取、重量稳定性、数据存储、现场环境这几个环节一次讲透。想接工厂称重项目的外包开发者、正在学C#上位机的朋友,还有想评估自研地磅软件的设备商,都能在这里找到可以落地的方案。
1. 地磅软件到底在解决什么问题:业务流程先于代码
很多人接手称重系统第一反应是"不就是读个重量存个库吗",真到现场会发现完全不是这样。开发地磅程序之前,如果对接线的业务链路没有概念,写出来的代码基本是空中楼阁。
1.1 一次过磅作业背后的三个关键重量
地磅程序处理的不是单个的重量值,而是毛重、皮重、净重三者之间的关系,这是整个软件逻辑的核心骨架。
- 毛重:车辆满载上磅时的总重量,是车加上货的完整重量。
- 皮重:车辆空载时的重量,也可能来自车辆档案里的参考皮重。
- 净重:毛重减去皮重得到的货物重量,是结算、扣款、统计的直接依据。
关系式非常简单:净重 = 毛重 - 皮重。但正是这个简单的减法里藏着一堆业务分支。比如煤场、废品站、粮库往往要求毛重必须大于皮重,如果不小心出现"毛重小于皮重"这种倒挂数据,程序必须当场拦住,而不是任由它进数据库。再比如有些物料按净重结算,有些按毛重追溯,字段怎么设定,直接影响后续的报表准确性。
1.2 从进厂到出厂:整条过磅业务链路的梳理
地磅软件本质上是车辆计量流程的数字化。一次标准的过磅作业,按时间顺序一般是这样:
- 车辆上磅:司机把车开上磅台,停稳,完全停在磅面范围内,避免半轮压磅。
- 身份识别:通过车牌识别摄像头自动识别,或者司磅员手动录入车牌号;部分系统会结合IC卡、RFID卡做自动关联。
- 称重读取:上位机从仪表连续读取重量数据,经过滤波和稳定判断后,锁定当前重量。
- 数据绑定:把锁定重量与车牌、物料、客户、供应商、当班司磅员、时间戳绑定成一条过磅记录。
- 业务分流:如果是"空车进厂",本次重量作为皮重存入车辆档案;如果是"重车出厂",此时得到毛重,再用车辆档案里的皮重计算净重。
- 保存与打印:记录写入数据库,磅单打印或推送至门岗、大屏显示。
- 回皮复核:重车卸货后再回磅称皮重,与档案皮重比对,偏差过大时提示人工核实。
这里有个关键点:不同行业对"皮重怎么来"的规则不一样。有的厂严格规定每车回皮,有的厂允许一个月内的参考皮重直接使用,还有的厂必须在第一次空车入场时就自动记录。开发时不能写死,要把皮重来源做成可配置的策略,这也是源码设计时最容易忽略、后期最难改的地方。
做这套称重系统源码时,我一开始只把流程画在纸上,没急着写代码。事实证明这个习惯救了我:现场业务永远比想象复杂,先理清流程,后面每一步开发都有了依据。
2. 串口读数的第一道关:协议帧解析与SerialPort封装
地磅软件的上游是称重仪表,仪表通过RS232或RS485串口把重量数据发给上位机。这一层是整个系统最基础、也最容易出问题的地方。串口读不稳,后面滤波、存储做得再好都没用。
2.1 主流地磅仪表的两种数据通信方式
市面上常见的仪表通信协议大体分两类,C#开发时首先要判断现场仪表属于哪一种。
第一类是连续发送模式。仪表上电后按固定周期(通常是每秒5到10次)主动向外发送一帧数据,不需要上位机发指令。典型格式是ASCII字符串,常见于托利多、耀华等国产仪表,帧内容类似:
ST,GS,+012345,kg,STABLE\r\n这种模式下上位机只需要被动接收,难度较低,但要注意一帧可能包含状态位和校验位。
第二类是指令应答模式。上位机必须先发一个读重量指令,仪表收到后才返回一帧数据。典型如Modbus RTU协议,通过功能码03读保持寄存器,从特定地址提取重量值。这种方式通信更可控,但实现时要注意指令超时、从站无响应的处理。
实际项目中,两台不同厂家的仪表协议几乎不可能一样。因此,协议解析必须独立封装成一个可替换的模块,而不是散落在窗口事件里。最好的做法是定义一个统一的解析接口,每种仪表对应一个实现类,切换仪表时只改配置。
2.2 SerialPort封装的三个硬性要求:线程隔离、粘包处理、断线自愈
C#的SerialPort类本身不难用,难的是用对。面向工业现场,封装串口通信层至少要满足三个硬性要求。
第一,线程隔离。SerialPort的DataReceived事件在辅助线程上触发,事件处理里绝对不能直接操作UI控件。处理办法是使用线程安全的队列或ConcurrentQueue接收原始字节,再由UI线程的定时器或异步机制消费。否则界面会随机性崩溃,而且极难复现。
第二,粘包和半包处理。串口数据是按字节到达的,一帧数据可能分成多次触发,也可能一次触发包含多帧。如果每次收到事件就当作一帧解析,结果必乱。正确做法是维护一个字节缓冲区,收到数据后先追加,再按帧头和帧尾从缓冲区里截取完整帧,剩下的继续留在缓冲区等待下一次数据。简单说,就像一条流水线上切香肠,切出完整的才算一根。
第三,断线自愈。工业现场串口线被拉扯、仪表断电重启都很常见。程序要能检测到长时间无数据的情况,自动关闭串口、提示告警,并在仪表恢复后重新打开。同时要区分"没有数据"和"数据全是错误",日志里把这些情况分开记录,排查时能省大量时间。
2.3 重量帧解析的代码实现:从字节流里抠出有效重量
下面这段代码是串口封装的简化核心,重点看缓冲区积累和切帧逻辑。实际源码里通常还会有重连线程、日志输出,这里只保留可读性。
public class SerialPortManager : IDisposable { private SerialPort _port; private List<byte> _buffer = new List<byte>(); private byte[] _frameHead; // 帧头,比如 0x02 private byte[] _frameTail; // 帧尾,比如 0x0D 0x0A public event Action<byte[]> FrameReceived; public void Open(string portName, int baudRate) { _port = new SerialPort(portName, baudRate, Parity.None, 8, StopBits.One); _port.DataReceived += OnDataReceived; _port.Open(); } private void OnDataReceived(object sender, SerialDataReceivedEventArgs e) { int count = _port.BytesToRead; byte[] data = new byte[count]; _port.Read(data, 0, count); _buffer.AddRange(data); // 循环切帧:从缓冲区中找出第一个帧头 while (true) { int headIndex = FindFrameHead(_buffer); if (headIndex < 0) { _buffer.Clear(); return; } if (headIndex > 0) { _buffer.RemoveRange(0, headIndex); // 丢弃帧头前的脏数据 } int tailIndex = FindFrameTail(_buffer, _frameHead.Length); if (tailIndex < 0) { // 数据不完整,等待下一波 return; } byte[] frame = _buffer.GetRange(0, tailIndex + _frameTail.Length).ToArray(); _buffer.RemoveRange(0, tailIndex + _frameTail.Length); FrameReceived?.Invoke(frame); } } }这段代码解决了工业串口开发里最典型的问题:数据来得断断续续、帧头前有脏字节、多帧连在一起。拿到完整帧之后,才交给协议解析器去提取重量字段。
下面的重量解析器接收完整帧,按分隔符拆分并取出重量值。这里一定要做状态位判断:
public class WeightResolver { public WeightData Parse(string frame) { // 示例帧: ST,GS,+012345,kg,STABLE string[] parts = frame.Split(','); WeightData data = new WeightData(); data.RawValue = decimal.Parse(parts[2].Replace("+", "")); data.WeightState = parts[4] == "STABLE" ? StableState : UnstableState; data.IsOverScale = parts[4] == "OVER"; return data; } }注意:重量数据不要直接拿字符串里的数值显示,一定要先判断状态位。很多事故现场是仪表还没稳定,软件就锁了个漂移中的重量。
3. 数字跳到你怀疑人生:重量滤波与稳定锁定
地磅软件在工位开发时一切正常,一上线数字就开始跳,这种情况我见过太多。原因往往是现场干扰和机械振动,不是代码逻辑错了。所以重量稳定这块,表面上是滤波算法问题,实际上是对物理过程的理解问题。
3.1 为什么称重值会跳:干扰源与信号特征
一辆重卡停上磅台,轮胎挤压秤台,传感器信号并不会立刻变成一个稳定数字。车辆停稳瞬间有机械回弹,磅台本身有轻微振荡,加上仪表附近若有变频器、电机等大功率设备,串口和模拟信号都会引入干扰。结果就是,你看到的重量值在目标值附近上下浮动,浮动幅度从几公斤到几十公斤都有。
干扰有几个典型特征:一是低频振荡,频率接近车辆悬架和磅台的自然频率,滤波不能太激进,否则响应变慢,现场等着打磅单的司机会很烦躁;二是随机毛刺,表现为瞬时跳变到明显偏离真实值的数字,这种数据必须直接丢弃,不能参与平均,否则会把真实重量带偏。
3.2 三种实用的滤波算法:滑动平均、中值滤波、一阶低通
重量滤波没有银弹,要按现场表现选。我的默认组合是"中值+平均"或"一阶低通",看实际情况切换。称重系统源码里我预置了三种,配置项里可以切换,这是实战总结出来的稳妥做法。
滑动平均:维护一个固定长度的队列,每次新值入队,计算队列平均值。优点是平滑效果好,缺点是跟随性变差。当过磅节奏快、车辆频繁上下磅时,队列太长会导致重量响应迟钝,严重时LIVE数字跟不上车辆移动节奏。
public class SlidingAverageFilter { private Queue<decimal> _queue; private int _windowSize; public SlidingAverageFilter(int windowSize) { _windowSize = windowSize; _queue = new Queue<decimal>(windowSize); } public decimal Push(decimal newValue) { _queue.Enqueue(newValue); if (_queue.Count > _windowSize) _queue.Dequeue(); return _queue.Average(); } }中值滤波:取最近N个值排序后取中间值,专门对付毛刺。比如突然跳一个离目标值200公斤的异常点,如果走平均值,结果会明显偏移;走中值,只要异常点不超过一半,结果纹丝不动。N通常取5到9,N太大同样拖慢响应。
一阶低通:实现非常简单,新值 = 旧值 + α × (原始值 - 旧值),α在0到1之间,约接近0滤波越强。优点是计算量小、响应可调,缺点是对突变信号反应较慢。
现场调试的实际经验是:先选中值滤波过滤毛刺,再用滑动平均平滑振荡。两级滤波叠加,既不会让响应太慢,数字也稳得住。
3.3 稳定判定、自动锁定与业务防错
滤波只是第一步,软件必须在合适时机"锁定"一个重量值。锁定逻辑如果是"连续N帧数据差值小于阈值",那要小心:一些现场车辆虽然静止,但传感器信号有小幅漂移,连续一秒钟差值小于5公斤不代表真的稳定;反之,车辆停稳后,如果有风或车上有盖布晃动,重量也会小幅波动。
我的做法是比较保守的双重稳定条件:
- 连续一段时间(可配置,默认2秒)内,滤波后的重量最大最小值之差小于设定的允许波动值(默认±10公斤)。
- 这一段时间内仪表返回的状态位必须是稳定状态(如果协议里有)。
两个条件同时满足,才认为重量可锁定。锁定后重量值固定不变,直到业务状态切换(比如点击保存、切换毛重/皮重),或者重量变化超过一个较大的阈值说明车辆已经移动,才解除锁定。
public class WeightStabilizer { private decimal _lastValue; private DateTime _stampStart; private bool _started; public bool TryGetStableValue(decimal current, DateTime now, TimeSpan duration, decimal tolerance, out decimal stableValue) { if (!_started) { _started = true; _stampStart = now; _lastValue = current; } else if (Math.Abs(current - _lastValue) > 2 * tolerance) { // 大幅跳变,说明车辆在动或被干扰,重新计时 _stampStart = now; _lastValue = current; } if (now - _stampStart >= duration) { stableValue = current; return true; } stableValue = 0; return false; } }3.4 业务防错:皮重毛重不能倒挂,异常数据必须拦截
稳定判定只是技术层面,业务层面还要加护栏。称重系统里最常出现的异常有三种:
- 毛重小于皮重:明显错误,程序应弹窗拦截,并阻止保存,防止因选择皮重档案错误而出现负数净重。
- 皮重突变:同车牌车辆,本次皮重与档案皮重偏差超过设定范围(比如500公斤),应提示司磅员核对是否为空车状态,是不是有夹带。
- 重量超量程:锁定重量大于仪表最大量程。这个一般仪表本身会显示超载,但软件侧要再做一道防线,避免异常帧被当成有效数据。
注意区分:业务拦截和滤波是完全两回事。滤波解决"这个数字是不是真实重量"的问题,业务拦截解决"这个真实重量合不合逻辑"的问题。源代码里这两块必须放在不同层,不要揉在一起。
4. 数据落地与业务闭环:表结构、库选型和磅单打印
称重系统跑起来的核心是数据,没有可靠记录的过磅软件等于白做。这一节把数据层最关键的三件事说透:表怎么建、库怎么选、磅单打印怎么做。
4.1 过磅记录、车辆档案、用户权限:核心表结构设计
称重系统的数据库表不需要多,但每张表都要经得起审计。基本必备的是这几张:
过磅记录表(WeighRecord),这是整个系统的核心表,一条记录代表一次完整称重。关键字段如下:
| 字段名 | 类型 | 说明 |
|---|---|---|
| RecordId | 自增主键 | 记录唯一编号 |
| PlateNumber | 字符串 | 车牌号 |
| GrossWeight | decimal(10,3) | 毛重,公斤 |
| TareWeight | decimal(10,3) | 皮重,公斤 |
| NetWeight | decimal(10,3) | 净重,公斤 |
| WeighTime | datetime | 称重时间 |
| OperatorName | 字符串 | 司磅员账号 |
| MaterialName | 字符串 | 物料名称 |
| CustomerName | 字符串 | 客户/供应商 |
| Direction | 字符串 | IN/OUT,进厂还是出厂 |
| RecordStatus | 字符串 | 正常、作废、修改 |
车辆档案表(VehicleInfo):车牌、默认皮重、车型、所属客户、备注。皮重来源策略在这里体现。需要强调的是,VehicleInfo 的参考皮重只是参考,不能直接拿来当每一次的皮重,具体怎么用由业务配置决定。
用户权限表(UserInfo):账号、密码散列值、角色(管理员/司磅员/只读)。称重系统必须做角色区分,原因后面"防作弊"一节细说。
操作日志表(SystemLog):记录每一次修改、删除、导出、登录失败等敏感操作。这张表平时看着没用,一遇到争议就是证据,务必保留。
4.2 Access / SQLite / SQL Server:小称重系统的选型思路
很多初学者上来就选SQL Server,对单机地磅项目来说往往过度了。我的选型思路是这样的:
Access:最传统的选择,和Windows兼容性极好,单文件数据库,备份直接拷贝mdb文件。适合只有一台上位机、数据量一天几百条的小磅房。缺点是多线程写入容易锁库,长时间运行后文件膨胀,需要用工具压缩。用Access时特别注意,连接字符串里要配置Provider=Microsoft.ACE.OLEDB.12.0。
SQLite:单文件免费嵌入式数据库,写入性能比Access强,事务支持好,官方没有服务器版但也不需要。关键是lib不用额外部署,适合写进安装包里一次性发给客户。对单机称重系统来说,SQLite是最省心的选择。唯一的坑是并发写时偶尔会出现数据库锁定,需要做好重试机制。
SQL Server:适合需要多台磅房数据联网汇总的场景,比如一个集团多个磅站数据要汇集到总部。此时才值得引入服务器数据库。相应地,部署复杂度也上来了,需要专门有人维护。
我的建议很直接:单磅房无脑SQLite,总部联网再上SQL Server,Access只适合非常老旧的维护项目。
4.3 磅单打印与日报统计:用模板方案一次性解决
磅单打印是做称重系统绕不开的需求,每张磅单上要有车牌、毛重、皮重、净重、物料、时间、司磅员、单位名称,还得有防伪标记。
技术上有两条路:
一条是报表控件方案,比如Rdlc报告、FastReport这类。优点是设计模板所见即所得,导出PDF方便;缺点是控件库体积大、授权要钱,部署到客户机器时容易缺运行时。
另一条是轻量模板方案,用C#直接生成一份简易格式的文件或绘制磅单图片。对于产量不高的磅房,磅单打印用系统自带打印功能就够了:用一个Windows窗体当作磅单模板,绑定数据显示,再调用PrintDocument打印。这种方案的优点是零依赖,缺点是排版灵活度低。
实用经验:给外部客户做项目时,磅单格式几乎必改,所以打印逻辑必须集中在单独的项目或模块里,不要散在窗体代码里。最好预留一个"磅单模板文件路径"的配置,让客户能自己换logo、改单位名。我用的是简单思路——磅单模板存成一个格式比较规整的文本或图片模板,程序读模板后填入数据再生成最终排版,这样改起来最快。
日报统计相对简单,核心是SQL聚合:
SELECT MaterialName, COUNT(*) AS TruckCount, SUM(GrossWeight) AS TotalGross, SUM(NetWeight) AS TotalNet FROM WeighRecord WHERE Direction = 'OUT' AND WeighTime BETWEEN @start AND @end GROUP BY MaterialName统计模块注意两点:一是时区问题,过磅记录的时间直接用本地时间,但统计时最好以凌晨零点为界,这是行业习惯;二是金额统计有时需要单价字段,不要试图从净重反推,单价必须在过磅时就从物料档案带出来存入记录。
5. 源码的组织方式:一个能稳定运行五年的架构
很多人拿到称重系统源码第一反应是找Form1.cs,然后看到一万行代码直接劝退。如果你写的地磅程序是把所有逻辑堆在窗体代码里,那这个项目一旦遇到需求变更就危险了。真正的工业级源码,组织方式是有讲究的。
5.1 四个核心模块:UI、业务、设备、数据
C#桌面端称重系统,我习惯把源代码分成四个项目或四个清晰的文件目录:
- UI层(WinForms/WPF窗体):只负责展示和交互,不含业务规则。按钮点击之后调用业务层接口,不直接操作数据库,不直接解析串口数据。
- 业务层(Service):承载过磅流程状态机,维护毛重/皮重/净重的业务规则,提供保存、打印、修改记录的业务方法,这是整个软件的心脏。
- 设备层(Device):封装串口管理、协议解析、重量滤波。业务层不关心仪表是什么牌子,只拿到一个"当前稳定重量"的实体对象。
- 数据层(Data):封装数据库连接、增删改查、日志写入。业务层只执行高层的仓储方法,比如
SaveWeighRecord(record),而不是直接ExecuteSql。
这种分层最大的好处是,任何一层被替换,其他层不受影响。比如客户觉得数据库太大要换SQL Server,只需要替换数据层;仪表从托利多换成耀华,只要设备层增加一个解析类。
5.2 关键类设计:SerialPortManager、WeightResolver、WeighBridgeService
源码里最重要的三个类,作用必须单一,职责不能漂移。
SerialPortManager负责原始字节流的读取和切帧,对外只暴露FrameReceived事件。它不知道帧里的重量字段在哪,也不关心重量逻辑。
WeightResolver负责把完整的帧转成WeightData,WeightData包含RawValue、IsStable、IsOverScale等属性。它不做滤波,不做稳定判定。
WeighBridgeService是最核心的业务类,它订阅FrameReceived事件,驱动滤波和稳定判定,维护整个过磅流程状态机。
状态机的状态一般是这样的:
public enum WeighState { Idle, // 空闲,等待车辆上磅 Loaded, // 检测到重量变化,车辆已上磅 Stabilizing, // 正在滤波稳定 Locked, // 重量已锁定 Saved, // 重量已保存 Zeroing // 置零/回零 }状态机的好处是把"什么时候该干什么"约束得清清楚楚。比如只有在Locked状态下才能点保存按钮,否则按钮就是灰的或者提示"重量未稳定";只有保存完成后才能回皮或者重打,避免司磅员倒着操作把流程搞乱。
5.3 用参数配置代替硬编码
称重系统上线的现场千奇百怪,每个项目都要调整串口号、波特率、稳定时间、允许波动范围、零点跟踪范围、磅单标题、单位名称。如果这些全写在代码里,每次换项目都要编译一遍源码,还要带着源码到现场改。
我的做法是做一个统一的AppConfig配置类,配置文件放在程序目录下的config.json里。程序启动时加载配置,运行中修改配置可以实时生效。配置项大致分几组:
- 串口参数:端口号、波特率、数据位、校验位、停止位。
- 仪表协议:协议类型、帧头、帧尾、连续/应答模式。
- 重量规则:稳定时间、允许波动范围、零点跟踪阈值、超载报警阈值。
- 业务参数:皮重来源策略、是否允许毛重小于皮重、是否启用自动打印。
- 打印模板:磅单模板文件路径、打印机名称、份数。
把配置独立出来,等于给自己留了条后路。客户提"首页的单位名改成另一个""稳定时间从2秒改到3秒",你不用再重新编译发布,编辑配置文件重启程序就完事。
6. 上线现场必踩的坑:环境干扰与人为因素
称重系统不在工位跑,而是在磅房跑。把软件部署到现场后才是真正的考验。这一节讲的都是源码之外、实操里躲不开的问题。
6.1 串口线磨损、电磁干扰与数据乱码的排查思路
现场最常见的线上故障是"数据乱码"或"读不到重量"。遇到这类问题,别急着动代码,先按这个顺序排查:
- 检查串口线接头是否松动,屏蔽层是否接地。地磅现场经常有重型车辆压过线缆沟,线皮破了但外观却看不出来,要沿线检查。
- 检查串口线是不是和动力线走同一根线槽。RS232抗干扰能力本来就差,和变频器电缆并行几十厘米,数据就会乱。RS485稍好,但也应避开动力线。
- 在串口助手工具里直接监视原始数据。如果助手收到的数据都是乱码,那基本不是软件问题,是物理链路或仪表输出问题。
- 用软件日志定位。日志要记录每一帧的原始字节流和校验结果,而不是只记"读取失败"。没有原始日志,现场排查时你只能瞎猜。
个人经验是:串口通信层一定要有原始日志开关,平时关闭,出问题时打开。这个开关在排查过程中能救命。
6.2 雷击与浪涌:上位机往往是最先受损的设备
地磅磅台在室外,传感器和仪表之间的线缆是漫长的户外线缆,非常容易感应雷电和浪涌。很多用户第一年雷雨季被劈坏好几台上位机,才意识到需要防护。
软件无法防雷,但源码配套的部署方案里必须包含硬件防护建议:
- 仪表和上位机之间加装串口光电隔离器,既能隔离浪涌,也能解决地电位差造成的乱码。
- 上位机电源用带浪涌保护的插排或UPS。
- 门禁道闸和大屏等外部设备不要和上位机共用一个插座回路。
- 如果雷雨频繁,可以增加"防雷击保护器"串在串口线上,成本不高但能显著降低损坏率。
这些不是软件内容,但作为称重系统的整体交付物,必须写进实施文档。否则雷雨季一过,客户第一个电话打给"做软件那个"。
6.3 防作弊与合规:权限管理、操作日志和记录溯源
地磅软件一旦涉及贸易结算,就必须考虑合规性。称重系统的防作弊,不是简单的密码登录,而是全流程的可追溯。
- 角色权限:管理员才能修改皮重档案、调整系统参数;司磅员只能过磅和打印,不能修改已保存的记录。
- 修改留痕:任何记录被修改或删除,都必须在操作日志表里记录操作人、操作时间、旧值、新值、操作原因。
- 硬件联动:严重依赖人工的步骤如下——如果磅房出口有道闸,软件输出开闸信号前,必须确认重量已经稳定并保存;如果担心司机压边磅,可联动摄像头抓拍车辆上磅位置。
- 数据防篡改:记录表增加一个校验字段,比如对净重、时间、车牌拼接后做哈希。数据库被手工改过也能查出来。
有次给某货场做系统,他们要求所有磅单必须带二维码防伪。我们直接在打印模块里生成二维码,内容包含记录ID和校验码,扫码查询时核对记录是否一致。这个功能看似复杂,实现起来就是十几行代码,但对客户信任度的提升非常明显。
6.4 司磅员误操作的兜底:皮重异常与反向拦截
人为误操作和故意作弊同等危险。司磅员一天过几百辆车,太累的时候会犯低级错误。系统必须把这类错误拦住,而不是把责任推给操作员。
典型场景有三类:
- 重车出厂时手滑选了别的车的皮重档案,净重算错。对策是保存前弹窗展示"车号-毛重-皮重-净重"供二次确认。
- 空车回皮时,司机没下车或者车上还带着人和货。皮重和档案皮重差异超过阈值时,程序要报警并暂停保存,提示司磅员核实。
- 车辆没过完磅就点了保存,或半个车轮还在磅台外。半轮压磅会造成重量缺失,软件可以通过重量变化曲线简单判断:从0到目标重量是一次性连续的跃迁,还是中间有停顿和台阶。连续变台阶就是典型半磅特征。
这些兜底逻辑在需求阶段客户很少提,但做上去之后客户满意度会明显提升。因为没有人愿意天天因为小失误重打一遍单子。
做了这么多年的称重系统,我一直觉得这类工业上位机软件的章法跟互联网应用完全不一样:不追求炫酷,追求的是三个月不重启、一斤不差、出事能查。稳定压倒一切,扛得住现场的地磅程序才是好程序。把这套源码里的串口通信、重量稳定、数据闭环和防作弊思维吃透,你的C#开发能力不会只进步一个档次——你会开始理解,真正能落地的工业软件,是把物理环境的粗糙和人的弱点都考虑进去之后,依然能安静运行的东西。