简介:这份托利多中间件电子秤数据接口C#源码包,面向需要在Windows平台对接托利多电子秤的开发者,重点解决RL00、BCOM等多型号设备的数据读取、指令交互与业务系统集成问题,源码已测试通过并附带厂家SDK。包内以C#源码、项目工程和SDK依赖为核心,配合配置、说明文档及示例程序,可辅助梳理通信协议适配、接口调用与多型号兼容思路。资源共132个文件,涵盖10个cs源码、3个csproj与1个sln工程文件、31个dll(含SDK依赖)、49个xml及6个config配置、5个exe和若干doc、txt说明,压缩包约32.67MB,目录按工程与运行依赖组织,便于检索和复现测试环境。已有1442人学习。对从事工业称重、产线自动化和仓储零售数据采集的读者,可据此研究中间件封装方式、设备接口调试和兼容性处理,也可借鉴现成工程结构快速搭建自己的采集程序,减少从零摸索与反复联调的成本。
1. 电子秤数据接口不是“读串口”这么简单:先看这套 C# 中间件能解决什么
电子秤数据接口 C# 源码、厂家 SDK、多型号兼容,这几个词放在一起时,很多人的第一反应是“不就是读个串口吗”。真到产线上,RL 类秤型和 BCOM 类协议混着用,有的走串口,有的必须调厂家 SDK,业务系统还要求重量、单位、稳定标志、时间戳一次性拿全,事情就没那么轻了。这套源码做的是中间件层:把不同型号的电子秤收敛成统一接口,带厂家 SDK 桥接和测试工程,C# 项目可以直接跑起来联调。适合正在做称重工位、上位机采集、MES 或仓储系统对接的开发者,尤其适合手里有多台不同型号秤、又不想把协议代码散落在业务系统里的场景。
2. 技术选型与工程定位:C#、厂家 SDK、串口/网口三层的边界怎么划
2.1 为什么用 C# 做中间件而不是把协议塞进业务系统
工业现场的称重设备接口有一个很现实的特点:协议差异不在业务侧,而在设备侧。同样叫“读重量”,A 型号可能返回一帧 ASCII,B 型号走二进制帧,C 型号必须加载厂家 SDK 后注册回调。业务系统如果直接写串口读取,后面每加一个型号就要改一次订单、库存或产线逻辑,改到最后没人敢动。C# 在这里的价值不是语言本身多新,而是上位机生态成熟:串口、网口、Windows 服务、WinForms 调试工具、厂家 SDK 的托管封装都能在同一套工程里解决。
我一般会把中间件拆成三层:设备接口层只定义“我要什么数据”,适配层负责“怎么从这台秤拿到数据”,SDK 桥接层负责“厂家动态库怎么初始化、回调怎么转成事件”。业务系统只依赖IWeightDevice,不关心背后是串口还是 SDK。这样做的直接好处是新增型号时改的是适配器,不是业务代码;测试时也可以用回放帧替代真实秤,不用每次把产线停掉。血泪经验是,千万不要让业务层直接引用厂家 SDK 的 DLL,一旦位数不匹配或者 SDK 升级,整个业务程序都要跟着重编译。
从工程定位看,这套源码更像“设备访问层 + 小型驱动框架”,不是完整的上位机业务系统。它把连接、读帧、校验、单位换算、异常重试、日志这些公共动作收在中间件里,业务侧拿到的是统一的数据对象。对熟手来说,重点看它的接口边界和适配器注册方式;对新手来说,先跑测试工程,确认本机环境、串口名、SDK 位数都对,再上真实设备。这个顺序反了,很容易把环境问题误判成协议问题。
2.2 运行环境与依赖清单:.NET、串口、SDK、日志、配置
这套 C# 源码常见做法是兼容 .NET Framework 4.7.2 和 .NET 6/8 两套目标,现场老工控机跑前者,新项目跑后者。串口用System.IO.Ports,网口用TcpClient,厂家 SDK 以托管 DLL 或非托管 DLL + P/Invoke 的方式接入。日志不要用Console.WriteLine硬扛,NLog 或 Serilog 都行,关键是能按设备号、帧内容、异常堆栈落到文件里。配置建议放appsettings.json或 ini,串口号、波特率、超时、重试次数、SDK 路径都不要写死。
| 组件 | 作用 | 现场注意点 |
|---|---|---|
| .NET Framework 4.7.2 / .NET 6+ | 运行宿主 | 老工控机优先确认运行时版本 |
| System.IO.Ports | 串口通信 | 端口名、占用、USB 转串口驱动都要核 |
| 厂家 SDK | 部分型号必需 | x86/x64 必须和主程序一致 |
| NLog / Serilog | 日志与排查 | 记录原始帧,别只记“读取失败” |
| SQLite 或本地文件 | 断网缓存可选 | 不是所有项目都需要,别过度设计 |
| 测试工程 | 离线回放 | 先跑通再上设备 |
环境准备可以按下面几步走。先确认 SDK 和运行时:
dotnet --info这条命令看三件事:当前 SDK 版本、运行时列表、进程位数。如果项目是 x86,而机器上只有 x64 运行时,后面加载厂家 DLL 会直接翻车。第二步把厂家 SDK 的 DLL 放到输出目录下的libs或sdk文件夹,并在项目文件里设置“始终复制”。第三步用虚拟串口或回放文件跑测试工程,不要一上来就接真实秤。第四步打开日志目录,确认每一帧原始数据都能落盘。
项目文件里常见的复制配置如下:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFrameworks>net472;net8.0</TargetFrameworks> <Platforms>x86;x64</Platforms> <LangVersion>latest</LangVersion> </PropertyGroup> <ItemGroup> <!-- 厂家 SDK 按实际目录放,主程序位数必须与 DLL 一致 --> <None Include="libs\**\*.dll" CopyToOutputDirectory="PreserveNewest" /> </ItemGroup> </Project>逻辑说明:TargetFrameworks让同一套代码能覆盖老工控机和新项目;Platforms是为了把 x86/x64 的差异显式暴露出来,避免“开发机 x64 能跑、现场 x86 加载失败”。参数说明:libs\**\*.dll要按实际 SDK 目录改,CopyToOutputDirectory选PreserveNewest可以减少手工拷贝。注意不要把 SDK DLL 提交到公共仓库,现场部署时按授权方式随程序分发。
提示:串口名在 Windows 下可能是
COM3,在 Linux 下可能是/dev/ttyUSB0。中间件配置里不要只写死一种格式,最好留一个设备地址解析器。
2.3 从“能读数”到“能交付”:接口层、适配层、业务层的职责
“能读数”和“能交付”之间差的是边界。接口层定义统一契约,比如连接、断开、读一次重量、订阅连续重量、获取设备状态;适配层实现具体协议,串口适配器只管帧怎么收、怎么校验,SDK 适配器只管回调怎么转事件;业务层只处理重量值、单位、稳定标志和时间戳。这样拆完,新增一个型号就是新增一个适配器类,再在工厂里注册,不需要改业务判断。现场最怕的是if (型号 == A) ... else if (型号 == B) ...散落在十几个文件里,后面加型号就是灾难。
我一般会要求适配层不抛“业务异常”,只抛设备异常或协议异常,并且异常里带设备号、原始帧、重试次数。业务层拿到异常后决定是重试、降级还是报警。测试工程则直接调适配器,用回放帧模拟粘包、半包、校验错、超时。这样的结构对新手也友好:先看接口知道系统要什么,再看某个适配器知道某型号怎么工作,最后看测试知道边界条件覆盖了哪些。中间件不是把代码写复杂,而是把变化点关进小盒子里。
3. 目录结构与通信链路:一个称重请求从串口到业务对象的完整路径
3.1 源码目录怎么分层:Core、Adapters、SdkBridge、Tests、Tools
源码目录一般不会平铺,按职责分比较稳。下面是一个常见的目录骨架,实际项目里名字可能不同,但分层思路基本一致:
ScaleBridge/ src/ ScaleBridge.Core/ # 接口、数据模型、配置、异常 ScaleBridge.Adapters/ # 串口、网口、SDK 适配器 ScaleBridge.SdkBridge/ # 厂家 SDK 封装、回调、生命周期 ScaleBridge.Host/ # 控制台或 WinForms 调试宿主 tests/ ScaleBridge.Tests/ # 回放帧、粘包、校验、重试测试 tools/ FramePlayer/ # 离线帧回放工具 PortMonitor/ # 串口监视与日志查看 config/ devices.sample.json # 设备配置样例Core里不要放任何厂家 SDK 引用,保证业务层和测试工程能独立编译。Adapters按通信方式分,SerialScaleAdapter、TcpScaleAdapter、SdkScaleAdapter各自独立。SdkBridge单独一层是为了把非托管资源、回调线程、释放逻辑关起来,避免适配器里到处写DllImport。Tests不要只测“正常一帧”,要覆盖半包、粘包、校验失败、超时、重复帧。Tools看起来可选,但现场排查时很有用,尤其是帧回放工具,能把日志里的原始字节重新灌进解析器,复现问题比盯着秤按按钮快得多。
配置样例建议带设备 ID、协议类型、地址、超时、单位换算因子。不要把所有型号的字段揉成一个巨型配置,公共字段放外层,协议私有字段放options里。这样新增型号不会影响老配置。目录分层之后,编译产物也要分开,SDK 相关 DLL 放独立目录,部署时不容易漏。
3.2 连接建立与心跳:串口参数、网口连接、SDK 初始化
串口连接的核心参数是波特率、数据位、停止位、校验位、流控和读写超时。称重设备常见 9600、8 数据位、无校验、1 停止位,但这不是万能答案,必须看设备手册。流控如果设错,表现可能是“能打开但收不到完整帧”。读写超时不要设得太短,连续称重时设备响应有间隔;也不要太长,否则业务线程会被拖住。网口设备还要考虑心跳和断线重连,SDK 设备则要先初始化再注册回调。
配置可以长这样:
{ "devices": [ { "id": "scale-01", "protocol": "serial", "address": "COM3", "baudRate": 9600, "dataBits": 8, "stopBits": "One", "parity": "None", "readTimeoutMs": 800, "retry": 2, "options": { "stableOnly": true, "unit": "kg" } }, { "id": "scale-02", "protocol": "sdk", "address": "USB", "sdkPath": "libs/vendor-scale.dll", "retry": 1 } ] }逻辑说明:protocol决定工厂创建哪个适配器,address对串口是端口名,对 SDK 可能是设备句柄或 USB 标识。readTimeoutMs控制单次读取等待,retry控制失败重试次数,stableOnly表示业务是否只接受稳定重量。参数说明:baudRate、dataBits、stopBits、parity必须和设备手册一致;sdkPath要相对于输出目录,不要写开发机绝对路径。注意stableOnly不是所有业务都适合,发货扫描可能要求稳定,过程监控可能允许波动。
连接建立顺序建议固定:先校验配置,再创建设备,再打开连接,最后启动心跳或订阅。SDK 设备还要多一步“加载 DLL 并检查位数”,失败时日志里要写清楚是找不到 DLL、入口点错误还是位数不匹配。心跳不要用业务线程做,单独定时器或后台任务,失败后按退避策略重连。现场网络抖动很常见,重连逻辑要能区分“设备离线”和“协议解析失败”,否则日志会变成一锅粥。
3.3 数据帧解析与统一模型:重量、单位、稳定标志、时间戳
业务层不关心帧长什么样,只关心统一模型。模型里至少要有设备 ID、重量值、单位、是否稳定、原始文本或字节、采集时间。重量值建议用decimal,不要用double做金额或贸易结算相关计算;单位统一转成业务约定的单位,比如 kg,转换因子放配置。稳定标志很关键,很多现场问题不是读不到数,而是把波动值当稳定值用了。
public sealed class WeightSample { public string DeviceId { get; init; } = string.Empty; public decimal Value { get; init; } public string Unit { get; init; } = "kg"; public bool IsStable { get; init; } public DateTimeOffset Timestamp { get; init; } = DateTimeOffset.Now; public string Raw { get; init; } = string.Empty; // 业务侧只拿统一模型,不直接碰协议字节 public decimal ToUnit(string targetUnit) { return Unit == targetUnit ? Value : UnitConverter.Convert(Value, Unit, targetUnit); } }逻辑说明:Raw保留原始帧,排查时能对照协议;Timestamp用DateTimeOffset避免时区问题;ToUnit把单位换算收在模型里,业务层不用到处乘系数。参数说明:DeviceId要和配置里的id一致,IsStable由适配器解析稳定位后填入,Unit不要直接信任设备返回值,最好做一次白名单校验。解析流程一般是:收字节、找帧头帧尾、校验、截取载荷、按型号解析重量和标志、单位归一、生成模型、抛给业务或写入队列。任何一步失败都要记录原始帧,不然就是黑匣子。
4. 核心模块代码拆解:接口抽象、协议适配器、SDK 桥接与异常重试
4.1 统一接口 IWeightDevice 与设备工厂
接口不要设计得太花,够用就行。连接、断开、读一次、连续订阅、状态查询,这五个动作能覆盖大多数称重场景。读一次适合扫描工位,连续订阅适合产线监控。接口里不要暴露串口对象或 SDK 句柄,否则抽象就漏了。工厂根据配置里的protocol创建适配器,新增型号时只加分支或注册表。
public interface IWeightDevice : IDisposable { string DeviceId { get; } bool IsConnected { get; } Task ConnectAsync(CancellationToken ct = default); Task DisconnectAsync(CancellationToken ct = default); Task<WeightSample> ReadOnceAsync(CancellationToken ct = default); // 连续称重用事件或 IAsyncEnumerable,二选一即可 event EventHandler<WeightSample>? WeightChanged; } public static class WeightDeviceFactory { public static IWeightDevice Create(DeviceConfig cfg, ILogger logger) { return cfg.Protocol switch { "serial" => new SerialScaleAdapter(cfg, logger), "tcp" => new TcpScaleAdapter(cfg, logger), "sdk" => new SdkScaleAdapter(cfg, new VendorSdkBridge(cfg, logger), logger), _ => throw new NotSupportedException($"未注册的协议: {cfg.Protocol}") }; } }逻辑说明:ReadOnceAsync给请求式业务用,WeightChanged给连续采集用;工厂集中处理协议到适配器的映射,业务层不写switch。参数说明:DeviceConfig里带设备 ID、地址、超时、重试和协议私有参数;ILogger从宿主注入,方便测试替换。注意Dispose要真的释放串口和 SDK 回调,不要只置空引用。新增型号时优先加注册表,不要在主工厂里堆几十个分支。
4.2 串口协议适配器:读帧、校验、粘包处理
串口适配器最容易翻车的地方不是打开端口,而是粘包和半包。称重设备连续发送时,一次Read可能读到半帧,也可能读到一帧半。稳妥做法是维护一个字节缓冲区:每次读到数据就追加,循环查找帧头帧尾,完整帧取出来解析,剩余字节留在缓冲区。解析失败要丢弃当前帧头之前的数据,避免死循环。校验失败记录原始帧并继续找下一帧,不要直接让整个连接断掉。
private readonly List<byte> _buffer = new(); private async Task<WeightSample?> ReadFrameAsync(CancellationToken ct) { var chunk = new byte[256]; var n = await _serialPort.BaseStream.ReadAsync(chunk, ct); if (n <= 0) return null; _buffer.AddRange(chunk.Take(n)); // 帧头 0x02,帧尾 0x03,长度按实际协议调整 while (true) { var start = _buffer.IndexOf(0x02); var end = _buffer.IndexOf(0x03); if (start < 0 || end < 0 || end <= start) break; var frame = _buffer.Skip(start).Take(end - start + 1).ToArray(); _buffer.RemoveRange(0, end + 1); if (!ChecksumOk(frame)) { _logger.Warn("校验失败,原始帧: {Frame}", BitConverter.ToString(frame)); continue; } return ParseFrame(frame); } // 防止异常数据把缓冲区撑爆 if (_buffer.Count > 4096) _buffer.RemoveRange(0, _buffer.Count - 1024); return null; }逻辑说明:先追加再循环找完整帧,找到后从缓冲区移除;校验失败只记日志并继续,不破坏后续帧。参数说明:0x02、0x03要换成实际帧头帧尾,ChecksumOk按型号实现,4096是缓冲区保护阈值,可按最大帧长调整。注意ReadAsync的取消令牌要传到底,否则断开连接时可能卡住。半包场景下返回null是正常的,调用方继续读即可,不要当成设备故障。
4.3 厂家 SDK 桥接:托管回调、线程安全、资源释放
SDK 桥接层要把非托管边界关住。初始化、注册回调、注销回调、释放句柄都放在这一层,适配器只接收统一事件。SDK 回调线程通常不是 UI 线程,所以事件里不要直接更新界面,入队或切回同步上下文。回调里也不要抛异常,异常跨非托管边界可能直接让进程退出。资源释放要幂等,重复Dispose不能崩。
public sealed class VendorSdkBridge : IDisposable { private readonly object _gate = new(); private readonly ILogger _logger; private IntPtr _handle = IntPtr.Zero; private bool _disposed; public event EventHandler<WeightSample>? WeightChanged; public void Start() { lock (_gate) { if (_disposed) throw new ObjectDisposedException(nameof(VendorSdkBridge)); _handle = VendorScaleSdk.Open(); // 虚构的 SDK 入口 if (_handle == IntPtr.Zero) throw new InvalidOperationException("SDK 初始化失败"); VendorScaleSdk.SetCallback(_handle, OnNativeWeight); } } private void OnNativeWeight(int value, int stable, IntPtr userData) { try { // 回调线程不可靠,先转成统一模型再抛给上层 var sample = new WeightSample { DeviceId = "sdk-scale", Value = value / 1000m, Unit = "kg", IsStable = stable == 1, Raw = $"sdk:{value},{stable}" }; WeightChanged?.Invoke(this, sample); } catch (Exception ex) { _logger.Error(ex, "SDK 回调处理失败"); } } public void Dispose() { lock (_gate) { if (_disposed) return; _disposed = true; if (_handle != IntPtr.Zero) { VendorScaleSdk.SetCallback(_handle, null); VendorScaleSdk.Close(_handle); _handle = IntPtr.Zero; } } } }逻辑说明:_gate保护句柄和释放过程,回调里用try/catch兜底,避免异常穿透非托管边界。参数说明:value / 1000m是按 SDK 返回值缩放,实际项目要按手册确认小数位;stable == 1是稳定标志约定,不同 SDK 可能用位掩码;userData可用于回传上下文。注意回调注册和注销必须成对,退出程序前不注销,可能留下后台线程或句柄泄漏。
4.4 重试、超时与日志:别让一次读失败拖垮整条产线
重试不是万能药,读超时和校验失败要分开处理。串口读超时可能是设备没发,也可能是波特率错;校验失败通常说明帧边界或协议解析有问题。重试次数不要太多,2 到 3 次足够,间隔用短退避。连续订阅场景不要每次失败都重连,先清缓冲区再试。日志要记录设备 ID、操作、耗时、重试次数、原始帧摘要,不然现场只能靠猜。
public async Task<WeightSample> ReadWithRetryAsync(CancellationToken ct) { var lastEx = default(Exception); for (var i = 0; i <= _cfg.Retry; i++) { var sw = Stopwatch.StartNew(); try { var sample = await ReadOnceCoreAsync(ct); _logger.Info("{DeviceId} 读取成功,耗时 {Elapsed}ms", _cfg.Id, sw.ElapsedMilliseconds); return sample; } catch (OperationCanceledException) { throw; } catch (Exception ex) { lastEx = ex; _logger.Warn(ex, "{DeviceId} 第 {Attempt} 次读取失败,耗时 {Elapsed}ms", _cfg.Id, i + 1, sw.ElapsedMilliseconds); if (i < _cfg.Retry) await Task.Delay(100 * (i + 1), ct); } } throw new ScaleReadException($"设备 {_cfg.Id} 读取失败", lastEx); }逻辑说明:取消异常直接抛出,不参与重试;每次失败记录尝试次数和耗时;最后一次失败包装成业务可识别的ScaleReadException。参数说明:_cfg.Retry是额外重试次数,100 * (i + 1)是简单退避,实际可配。注意不要吞掉OperationCanceledException,否则程序退出时线程退不干净。
5. 避坑与排查:串口能打开但读不到数、SDK 位数不匹配这些坑怎么定位
5.1 现象一:端口打开成功,重量一直是 0
现象:串口Open()返回成功,日志也没有异常,但读出来的重量一直是 0,或者只收到空帧。原因通常有三类:波特率、数据位、校验位和设备不一致;设备需要先发命令才返回数据;USB 转串口驱动把端口占住了,程序打开的是另一个虚拟端口。解决时先看原始字节日志,如果一帧都没有,优先核对串口参数和接线;如果有一帧但解析为 0,检查帧头帧尾和重量字段偏移。用串口监视工具抓一次真实数据,和配置逐项对照,比反复重启程序有效。
5.2 现象二:x86/x64 不匹配导致 SDK 初始化失败
现象:串口型号正常,切到 SDK 型号就报“找不到入口点”“无法加载 DLL”或直接BadImageFormatException。原因多半是主程序位数和厂家 SDK 不一致,x64 进程加载了 x86 的 DLL,或者 SDK 依赖的 VC 运行库缺失。解决时先确认宿主进程位数,再把项目平台目标改成对应位数,SDK 依赖的 DLL 一起复制到输出目录。日志里要打印IntPtr.Size和 DLL 路径。注意不要只改项目属性里的“首选 32 位”,发布后的启动方式也可能影响位数。
5.3 现象三:连续称重时粘包/半包,数据错位
现象:单次读取正常,连续称重时偶尔出现重量跳变、单位错乱,或者解析器卡住不再出数。原因是串口是流式传输,一次读取不等于一帧,缓冲区里可能有多帧或半帧。解决时用固定缓冲区循环找帧头帧尾,校验失败只丢弃当前帧,不要清空整个缓冲区;同时加最大缓冲保护,防止异常数据撑爆内存。测试工程里要专门构造“半帧 + 完整帧 + 粘包”的回放数据,别只测一帧正常数据。这个坑很玄学,现场复现难,离线回放最省时间。
5.4 现象四:UI 卡死或退出后串口占用
现象:点“连接”后界面卡住,或者程序退出后端口仍被占用,必须拔插 USB 才能恢复。原因通常是把同步Read放在了 UI 线程,或者Dispose没关串口、没注销 SDK 回调。解决时串口读写全部异步化,UI 只订阅事件;退出时按“停止订阅、注销回调、关闭连接、释放句柄”的顺序清理。Dispose要幂等,异常也要继续释放。注意调试时不要同时开两个程序抢同一个串口,日志里看到“拒绝访问”先查占用,不要先怀疑协议。
5.5 现象五:多型号切换后单位和小数点错乱
现象:A 型号显示 12.34 kg,切到 B 型号变成 1234 g 或 0.012 t,业务侧按 kg 计算就全错。原因是不同型号返回的单位和小数位不同,适配器直接透传了原始值。解决时在适配器里做单位归一,统一转成业务约定单位,并保留原始单位和原始值用于排查。小数点位数不要靠猜,按协议字段或 SDK 返回值缩放。配置里加单位白名单,遇到未知单位直接报错而不是静默转换。测试用例要覆盖 kg、g、t 三种返回,别让单位问题留到现场。
6. 二次开发怎么改:新增一个型号、接一套业务系统、做可验证的回归
二次开发最常做三件事:新增型号、接业务系统、补回归测试。新增型号不要改业务层,按“新建适配器 -> 实现解析 -> 工厂注册 -> 回放测试”的顺序走。比如新增一个走网口的型号,先复制TcpScaleAdapter的骨架,改帧头和校验,再在工厂里加"tcp-v2"分支。接业务系统时只暴露IWeightDevice和WeightSample,不要让业务方引用 SDK 命名空间。回归测试用日志里的原始帧做回放,覆盖正常帧、半包、校验错、超时四类,改完解析器先跑测试再上设备。下面是一个注册示例:
public static IWeightDevice Create(DeviceConfig cfg, ILogger logger) { return cfg.Protocol switch { "serial" => new SerialScaleAdapter(cfg, logger), "tcp" => new TcpScaleAdapter(cfg, logger), "tcp-v2" => new TcpV2ScaleAdapter(cfg, logger), // 新增型号只加这里 "sdk" => new SdkScaleAdapter(cfg, new VendorSdkBridge(cfg, logger), logger), _ => throw new NotSupportedException($"未注册的协议: {cfg.Protocol}") }; }改动点可以按下面这张表核对,避免漏项:
| 改动点 | 要改哪里 | 验证方式 |
|---|---|---|
| 新协议解析 | 新增适配器类 | 回放帧测试 |
| 工厂注册 | WeightDeviceFactory | 配置切换能创建 |
| 单位换算 | 适配器或模型 | kg/g/t 用例 |
| SDK 依赖 | SdkBridge和输出目录 | 位数与路径检查 |
| 日志字段 | 适配器日志 | 原始帧可追溯 |
我一般会先让解析器在离线回放里跑通,再接一台真实秤做短时联调,最后才放进产线灰度。从那以后我每次接新型号都强制走一遍“回放用例 + 位数检查 + 单位用例”,不给自己留后悔药。源码包、测试工程和厂家 SDK 适配层放在同一份资源里,拿到后先跑测试工程,再按现场设备改配置。希望帮到你。
本文还有配套的精品资源,点击获取