ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

固定式扫码系统实战:C#上位机与DevExpress v23.1开发指南

固定式扫码系统实战:C#上位机与DevExpress v23.1开发指南 简介DevExpress v23.1 固定式扫码系统 C# 源码面向工业扫码与桌面端信息管理开发者适合需要实现固定工位连续读码、异常声音/界面报警、扫码记录持久化查询的 WinForms 项目。压缩包共 572 个文件大小约 120.67MB主要包含 dll、xml、cs、txt 等类型dll 承载 DevExpress 控件及第三方运行库cs 存放扫码逻辑、串口封装与数据库访问代码xml 与 txt 提供配置和说明另含 nupkg、pdb 等依赖与调试符号。已有 175 人学习/下载。源码在基础扫码功能上重点更新了扫描报警和数据库查询模块内置 SerialPortUitls 串口工具类以事件回调方式持续读取串口缓冲字节便于实时处理条码数据同时附带解决方案、工程文件、SQL 脚本及界面资源可快速恢复开发环境。读者可直接复用串口通信与报警查询架构按自身业务调整报警触发条件、扩展多数据库查询或改造 DevExpress 前端展示。1. 固定式扫码系统为什么绕不开 DevExpress v23.1先看它解决了产线上的什么痛点产线上的扫码场景跟手持枪最大的区别是“不能等人”。固定式读码器装在流水线上几十毫秒内就要把条码读完并回传上位机要用同样的节奏把数据接住、解析、落库、上屏。这个活儿看着简单真做起来一堆事串口数据断包、重复读码、无码空跑、批量换型。如果界面层再用原生 WinForms 从零堆光报表和流水表就够熬几周。DevExpress v23.1 的 GridView、LayoutControl 和编辑控件正好把这一层补上配合 C# 源码可以在两三天内搭出一套能上线的固定式扫码系统。这篇文章按我实际做产线项目的顺序把硬件选型、通信、界面、源码和坑一次讲透。适合正在做上位机开发、或者被产线扫码需求找上门的 C# 工程师。2. 固定式扫码的硬件与通信选型串口、TCP/IP 和触发模式怎么定固定式扫码系统决定命运的不是代码而是读码器的触发方式和回传链路。这两个选错后面代码写得再漂亮也白搭。我见过不少项目上位机代码写完了到现场才发现读码器根本没配成想要的工作模式或者串口线太长导致数据丢得厉害。所以这一章先把硬件侧的三个关键决策讲清楚再落一张可以直接抄的参数表。2.1 触发模式外触发、内触发和连续触发选错直接导致重复读码触发模式决定了读码器什么时候开始扫描。固定式读码器在这一点上和手持枪完全不一样——手持枪靠人扣扳机固定式读码器必须由外部条件或内部逻辑决定“什么时候扫一帧”。外触发是最严谨的模式也是产线标准做法。读码器接光电传感器或 PLC 的 IO 信号物体挡住光电开关的那一瞬间信号送给读码器读码器才开始读一帧。好处是和产线节拍严格同步一个物体只触发一次极少出现重复读码。缺点是必须有外部信号源如果现场没有预留传感器这个方案就做不了只能往内触发或连续触发上靠。内触发是读码器根据内部条件自己触发。常见两种定时触发每 N 毫秒自动读一帧适合物体基本匀速运动、不方便接传感器的场合运动检测触发读码器发现画面内容变化了才读一帧适合物体静止后才扫码的工位。运动检测模式对静止物体无效画面不动读码器会认为没有新物体进来。连续触发则是一刻不停地读码把读到的条码全部往上传。这个模式最容易配置成功但数据量最大、重复码最多上位机必须做去重。我一般只在联调阶段用连续触发正式上线能不选就不选。选错触发模式之后最典型的后果就是流水表里出现大量重复条码库存数据涨得一塌糊涂操作员还说不清怎么回事。2.2 通信方式串口和 TCP/IP 的取舍没有你想的那么简单读码器读到了条码得有通道把结果送回上位机。固定式读码器常见的输出接口就两种串口和以太网老一点还有并口和 USB 仿真串口但新项目基本用不上。串口RS232/RS485是工业现场的老兵。RS232 点对点接线简单三根线就能通RS485 支持一总线挂多台设备适合几十米长的产线。串口最大的坑是现场干扰和电平不匹配线稍微长一点或者和变频器、电机电缆走同一个线槽就会出现乱码、断包。另外电脑上的串口往往是 USB 转出来的驱动不稳定也会让数据时断时续这些问题在固定式扫码系统里都遇到过。TCP/IP 是现在新项目的默认选择。读码器通过网线连交换机上位机可以同时监听多台读码器的数据每条数据里通常带设备 ID区分是哪台设备扫的很容易。更实在的好处是以太网断线后读码器内部有缓冲区网络恢复后会把断线期间的数据补传回来串口没有这个概念线一断数据就丢了。我一般这样选型如果客户的 PLC 或老产线控制器只支持串口那就用串口新上线的工位一律 TCP/IP。串口也不是不能选但要做三层防护线用屏蔽双绞线、走线避开动力电缆、波特率不要拉满9600 或 19200 就好。115200 看着爽现场干扰一来就知道难受了。2.3 固定式读码器通信参数速查以串口为例的对照表拿到一台新读码器最尴尬的就是不知道出厂是什么参数。大部分固定式读码器的出厂串口配置是 9600/8/N/1也就是波特率 9600、数据位 8、无校验、停止位 1。但不同品牌差异很大基恩士有些型号默认 115200康耐视默认 9600 也很常见。所以第一步永远是查手册别靠猜。下面这张表是我每次调试都会对着核的参数参数常见出厂值现场建议说明波特率9600 / 1152009600 或 19200数据量大再上 115200注意线材质量数据位88几乎不会改动停止位11保持与读码器手册一致校验NoneNone 或 Even干扰严重时用 Even 校验对端必须同步改流控NoneNone固定式读码器极少需要硬件流控开了反而卡提示改了读码器通信参数后一定要保存到读码器的永久存储区。很多读码器重启后会退回出厂参数不改的话现场一断电就回到默认值你会怀疑人生。除了串口参数TCP/IP 模式下还有两个必须确认的参数端口号和工作模式。读码器默认监听端口五花八门基恩士常用 9000康耐视常用 23Telnet上位机监听哪个端口读码器就要连哪个端口。工作模式更关键——上位机写的是监听端口读码器必须配置成 TCP 客户端模式主动连过来两边对不上数据永远收不到。联调第一步永远是先拿一个 TCP 调试助手连上去看有没有心跳返回再谈写代码。2.4 多台读码器混接时端口映射和线程模型怎么设计产线上经常不只有一台读码器。一条组装线可能有四五个工位每个工位都要扫码上位机就要同时接待四五个数据源。串口方案下每个串口对应一台设备上位机要开多个 SerialPort、多个 DataReceived 事件。比较麻烦的是 USB 转串口设备会随机分配 COM 口今天 COM3、明天 COM5。所以串口方案里最好按读码器序列号固定 COM 口或者在代码里按 VID/PID 识别设备否则换一个 USB 口代码就要改一遍。TCP/IP 方案下更简单上位机开一个监听端口读码器全部以客户端模式往这个端口连数据里带设备 ID 就能区分。我一般用一个 ConcurrentDictionarystring, TcpClient 维护在线设备列表每台设备一个接收线程数据进来后按设备 ID 分流到不同处理队列再统一汇总到看板和数据库。这个模型在固定式扫码项目里基本够用不用引入重量级框架。3. 用 DevExpress v23.1 搭扫码上位机界面GridView 与 LayoutControl 的实战组合界面不是写好看了就完而是要“刷得快、看得清、能回溯”。产线上操作员八小时盯着这块屏幕界面卡不卡、信息清不清楚直接影响效率和误操作率。这一章讲两个核心控件怎么组合以及怎么避免数据量大时界面发飘。3.1 为什么界面层选 DevExpress v23.1 而不是原生 WinForms 控件先把一个常见的误区说破扫码上位机的界面随便写写就行反正没人看。事实是产线操作员一天八小时盯着屏幕界面卡顿比功能缺失更容易引发投诉。原生 WinForms 的 DataGridView 在小数据量下表现还行但固定式扫码系统的流水是只增不减的一天几万行DataGridView 一过万滚动就发飘。DevExpress v23.1 在三个地方更适合这个场景。第一是 GridView 的渲染效率开启虚拟模式后即使绑几万行滚动手感依然稳。第二是 LayoutControl 的布局能力产线看板需要“大字、多区域、固定比例”LayoutControl 拖拽几下就能做出专业看板原生控件要写一堆锚点代码。第三是生态网上可以搜到大量 devexpress source code 相关的自定义控件示例遇到不熟悉的功能基本不用从零研究。对比项原生 DataGridViewDevExpress GridViewv23.1大数据量虚拟模式卡顿明显滚动流畅布局工具无靠代码锚点LayoutControl 可视化拖拽外观定制需要大量 GDI 代码内置皮肤和样式库常见案例参考少多需要提醒的是DevExpress 是商业控件v23.1 是官方发布的正式版本。网上确实有大量破解流传但产线上出了问题没人管法律风险也犯不上。买一套授权的成本相比停产一天造成的损失几乎可以忽略。3.2 用 LayoutControl 搭产线看板大字号结果、状态灯和工单信息产线看板的核心诉求是操作员抬头瞄一眼就知道刚才这件扫上了没有、扫的是不是对的、今天一共扫了多少件。所以布局要做成“上大下小、左状态右流水”。我一般用一个根分组把屏幕分成上下两块上面是大字号的当前扫码结果和状态灯下面是 GridView 流水表。layoutControl1.BeginUpdate(); var root new DevExpress.XtraLayout.LayoutControlGroup(); root.Text 扫码工位 01; var groupCurrent new DevExpress.XtraLayout.LayoutControlGroup(); groupCurrent.Text 当前扫码; var itemCurrent layoutControl1.AddItem(条码, labelCurrent); labelCurrent.Font new Font(Microsoft YaHei, 36, FontStyle.Bold); itemCurrent.TextVisible true; groupCurrent.AddItem(itemCurrent); layoutControl1.Root.AddGroup(groupCurrent); layoutControl1.EndUpdate();逻辑说明AddItem 是 LayoutControl 把任意控件包装成布局项的标准入口。要显示“条码内容”这种带标题的形式就把 TextVisible 设为 true如果只想要内容大字可以设为 false。BeginUpdate/EndUpdate 的作用是暂停布局重排所有控件加完后再一次性刷新避免界面上看到控件一个个蹦出来。状态灯我用一个 PanelControl背景色随时间变化——正常绿色、离线灰色、校验失败红色。这个颜色在后台线程里改时必须用 BeginInvoke 回到 UI 线程再赋值否则会抛跨线程访问异常。DevExpress 控件和原生控件一样都受这个限制别以为皮肤控件就自动帮你做了线程封送。3.3 GridView 做扫码流水表批量绑定、定时刷新的卡顿解法流水表是整个系统里最容易卡的地方。每秒钟进来几条记录每来一条就 AddNewRow、Refresh、MoveLast这样操作 UI 线程根本忙不过来。更合理的设计是数据先落内存 DataTableGridView 每 500ms 或 1s 刷新一次把一秒内几十次刷新合并成两次。private readonly DataTable _scanTable CreateSchema(); private readonly object _lock new object(); private void AppendToTable(BarcodeResult result) { lock (_lock) { var row _scanTable.NewRow(); row[device_id] result.DeviceId; row[barcode] result.Barcode; row[scan_time] result.Time; row[is_ok] result.IsOk; _scanTable.Rows.Add(row); } } private void timer_Tick(object sender, EventArgs e) { lock (_lock) { gridControl1.DataSource null; gridControl1.DataSource _scanTable; } gridView1.MoveLast(); }逻辑说明AppendToTable 在扫码线程里被高频率调用用 lock 保证与 UI 线程刷新时不会同时读写 DataTable。定时器刷新时先把 DataSource 置空再重新绑定是为了让 GridView 完全重建视图避免增量刷新残留状态。MoveLast 让视图跳到最新一行操作员永远看到的是最新记录。如果行数超过两三万记得给 GridView 开启只读模式把 OptionsBehavior.Editable 设为 falseAllowAddRows、AllowDeleteRows 都设为 false。否则操作员鼠标一晃就把记录改了扫码数据就对不上。还有一个细节OptionsBehavior.EditorShowMode 建议设为 Click避免鼠标悬停就触发单元格编辑。3.4 DevExpress v23.1 里必调的三个界面参数字体、行高和显示格式拖动控件只是第一步有三个参数直接影响操作员体验。第一个是字体产线屏幕通常离人一米以上原生控件默认字号根本看不清我一般把 GridView 字体统一设为 12~14 号行高用 RowHeight 固定避免不同 DPI 下看板走样。第二个是时间格式扫码时间要显示到秒列属性里设 DisplayFormat 为yyyy-MM-dd HH:mm:ss不要默认带毫秒看板太花。第三个是行号列GridView 的 Indicator 默认是灰色小三角用 CustomDrawRowIndicator 可以显示行号但 v23.1 对这个事件的触发时机有调整具体见第 5 章避坑。还有一个偷懒技巧整个工位只有一块屏幕时把 GridView 的 Dock 设为 Fill再打开 LayoutControl 的自适应比例窗口大小变化时界面不会错位。产线现场经常有人误触分辨率这个设置能让看板不至于瞬间乱掉。4. 核心 C# 源码实现串口读取、条码解析和数据库落地的完整链路前面两章把硬件和界面定下来了这一章是真正的核心链路从读码器把数据送进上位机到内存里解析、去重、落库最后推到界面。这条链路要处理三个关键问题断包、重复、批量写库。我按实际代码组织的顺序一步步拆。4.1 SerialPort 接收缓冲区与按帧切包不要用 ReadLine固定式读码器的串口输出以换行结束一条完整数据但 Windows 的 SerialPort 控件是按字节到达回调的不是按行回调。直接用 ReadLine() 按固定字节长度去读都会出现半包甚至多包粘在一起。正确做法是维护一个全局缓冲区把所有收到的字符先追加进去再按结束符把完整的一行切出来。这里有个细节读码器的换行符可能是\r\n、\n或只有\r品牌各不相同。基恩士和老设备用\r\n多一些康耐视经常只发\r。所以要做一个兼容三种结束符的切分函数不要写死一种。还有一个容易被忽视的点DataReceived 事件在后台线程触发所有在事件里调用的东西都不能碰 UI 控件。很多人第一次写直接在 DataReceived 里往 ListBox 添加数据然后就跨线程异常了。更合理的方法是 DataReceived 只负责把原始数据丢进缓冲区然后触发一个自定义事件由外层决定数据去向。private readonly StringBuilder _buffer new StringBuilder(); private void OnDataReceived(object sender, SerialDataReceivedEventArgs e) { string incoming _port.ReadExisting(); _buffer.Append(incoming); while (TryExtractFrame(_buffer, out string frame)) { BarcodeScanned?.Invoke(this, new BarcodeScannedEventArgs(frame.Trim())); } } private static bool TryExtractFrame(StringBuilder buffer, out string frame) { frame null; // 兼容 \r\n、\r、\n 三种结束符 var text buffer.ToString(); int idx text.IndexOf(\n); int endIdx idx 0 ? idx : text.IndexOf(\r); if (endIdx 0) return false; frame text.Substring(0, endIdx).TrimEnd(\r); buffer.Remove(0, endIdx 1); return true; }逻辑说明TryExtractFrame 先找\n再找\r是因为\r\n里先出现的是\n优先按\n切再把行尾残留的\r去掉。如果某帧结束符恰好是\r且后面没有\n这套逻辑也能兼容。buffer.Remove 会把切走的字符从缓冲区头部清掉剩余的半包继续等下一批到达。这个写法不依赖任何第三方包任何 SerialPort 场景都能直接用。参数说明如果一台工位接多台读码器每台读码器的 SerialPort 必须有自己的独立 StringBuilder千万不要共用一个缓冲区否则两台设备的数据会互相穿插切出来的行会出现 A 设备包一半、B 设备包一半的乱象。4.2 条码解析与校验去前缀、去重窗口和工单规则匹配读码器返回的原始字符串很少是干干净净的条码内容。基恩士会在前面加 “OK” 或 “IMAGE:”康耐视会加条码类型标记比如]C01代表 Code128。这些头信息不能直接进数据库必须先剥掉。但剥的时候要小心有些条码内容本身可能包含冒号所以“剥前缀”不能无脑切第一个冒号要看读码器手册里前缀的固定格式。重复数据比前缀更头疼。在连续触发或内触发模式下同一个码会被读到很多次。如果不去重流水表里全是重复值。我通常按“设备 ID 条码内容 时间窗口”去重窗口时长由产线节拍决定。public class BarcodeParser { private readonly ConcurrentDictionarystring, DateTime _lastSeen new(); private readonly TimeSpan _dedupWindow; public BarcodeParser(TimeSpan dedupWindow) { _dedupWindow dedupWindow; } public bool TryParse(string raw, string deviceId, out BarcodeResult result) { result null; // 剥掉读码器前缀 string body raw; if (body.StartsWith(IMAGE:)) body body.Substring(6); if (body.StartsWith(OK:)) body body.Substring(3); body body.Trim(); if (body.Length 0) return false; // 工单规则校验按前缀和长度过滤 if (!ValidateByOrder(body)) return false; // 去重 string key deviceId | body; if (_lastSeen.TryGetValue(key, out DateTime last)) { if (DateTime.Now - last _dedupWindow) return false; } _lastSeen[key] DateTime.Now; result new BarcodeResult(deviceId, body, DateTime.Now, true); return true; } }参数说明去重窗口不是固定值。节拍 10 秒一件的产线窗口设 5 秒足够节拍 1 秒一件的高速线窗口只能设 300ms。窗口设太小会放过重复码设太大会把相邻的同一型号真实条码也吞掉。这个参数上线第一周大概率要调几次最好放到配置文件里不要写死在代码中。ValidateByOrder 是工单规则校验常见规则有前缀匹配和长度匹配。产线上经常出现“条码读到了但根本不是这个工单的料”的情况这个校验能在源头拦住比后面人工看流水表高效得多。4.3 数据落地为什么扫码结果先进内存队列而不是直接写库很多刚上手的同学会把扫码和写库写在一起扫到一个码立刻 insert 一条。这在扫码频率低时没问题但频率一上来数据库连接反复开关、事务反复提交系统吞吐量立刻被 IO 拖死。更严重的是数据库瞬间卡顿会让扫码线程阻塞最新的条码反而接不住。所以我一般把扫码和落库解耦成两个环节扫码线程只负责解析和入队落库线程独立从队列取数据批量写库。这样扫码的实时性完全不依赖数据库的响应时间。private readonly ChannelBarcodeResult _queue Channel.CreateBoundedBarcodeResult( new BoundedChannelOptions(5000) { FullMode BoundedChannelFullMode.DropWrite }); // 扫码线程 private void OnBarcodeScanned(object sender, BarcodeScannedEventArgs e) { if (_parser.TryParse(e.Frame, e.DeviceId, out var result)) _queue.Writer.TryWrite(result); } // 落库线程 private async Task DbWriterLoopAsync() { var batch new ListBarcodeResult(100); while (await _queue.Reader.WaitToReadAsync()) { batch.Clear(); while (batch.Count 100 _queue.Reader.TryRead(out var item)) batch.Add(item); await BulkInsertAsync(batch); } }逻辑说明Channel 是 .NET 内置的线程安全队列比手动 ConcurrentQueue 更简洁支持异步读写。BoundedChannelOptions 限制队列最多攒 5000 条数据FullMode 设为 DropWrite意思是队列满了就丢弃新数据避免扫码线程被队列堵塞。批量写的触发条件是攒够 100 条或者由外部 Timer 每 1 秒唤醒一次 DbWriterLoopAsync。参数说明DropWrite 看起来是丢数据但总比扫码线程堵塞、产线停线好得多。真实产线宁可先丢几条数据、后面从日志补录也不能让扫码系统因为数据库抖动而停止工作。如果你不能接受丢数据就把 FullMode 改成 Wait然后接受扫码线程偶尔被阻塞的现实。这两种策略要按产线要求权衡没有绝对正确的答案。4.4 用 DevExpress 的数据源绑定把内存数据送到界面上数据落到数据库了但界面该显示什么正确答案是显示内存 DataTable而不是直接查数据库。数据库查询的刷新周期是秒级产线看板需要亚秒级更新直接查库会平添大量压力。做法是扫码线程解析成功后同时把一个副本写入内存 DataTableGridView 绑定的就是这个 DataTable。这样看板刷新只是内存操作完全不碰数据库。数据库写入成功后再把对应行标记为“已落库”即使数据库暂时故障操作员也能从界面上看出哪些数据还没存进去。private void OnBarcodeScanned(object sender, BarcodeScannedEventArgs e) { if (_parser.TryParse(e.Frame, e.DeviceId, out var result)) { AppendToTable(result); _queue.Writer.TryWrite(result); } } private void AppendToTable(BarcodeResult result) { lock (_lock) { var row _scanTable.NewRow(); row[device_id] result.DeviceId; row[barcode] result.Barcode; row[scan_time] result.Time; row[is_ok] result.IsOk; _scanTable.Rows.Add(row); } }逻辑说明扫码线程干两件事把结果放进内存表给界面把结果放进 Channel 给落库。这两个动作必须在同一条解析成功路径上不能让其中一个异常影响另一个。AppendToTable 抛异常不能影响 Channel 写入反过来也一样。lock 的对象 _lock 必须在构造函数里初始化多个方法使用同一个锁对象否则锁不住。参数说明如果后续要升级为“落库失败自动补录”只需要在 BarcodeResult 里加一个 PersistState 字段。每批写库失败时把对应行标记为“未落库”等数据库恢复后按标记补录。这样功能边界清楚改动面也小。5. 固定式扫码系统避坑指南产线现场的 5 个高频坑代码能跑只是第一步真正磨人的是现场不可控因素。这五个坑是我在推进多个固定式扫码项目过程中真实遇到过的有些甚至让产线停过线。每条按现象、原因、解决讲清楚希望能帮你省下几天的排查时间。5.1 串口断包条码被截成两半还以为是读码器坏了现象读码器明明扫到了完整条码上位机收到的却是半截比如PRD202和40101分两次到。更迷惑的是有时候又是完整的纯看运气。原因Windows 的 SerialPort 是按字节到达回调的USB 转串口驱动尤其喜欢把一帧数据拆成好几段到达。直接用 ReadLine() 去读自然就撞上了断包。而且断包并不是“读码器坏了”换一台设备也一样。解决按 4.1 的缓冲区加结束符切帧方案先攒数据再切行。排查时先拿串口调试助手给读码器发触发指令看返回是否完整。如果不完整先把波特率降下来降下来后依然断说明是线材或现场干扰问题而不只是代码问题。这一步能帮你分清是上位机没写好还是产线物理环境太差。5.2 重复读码库存数量莫名虚高现象操作员没扫几件流水表里却出现了几十条相同条码数量看板一路涨但实际物料根本没那么多。原因连续触发模式下读码器每秒读几十帧只要物件还在视野里每一帧都能读出同一个码并按上报频率发出来。没有去重机制上位机就把每一次上报都当成一次真实扫码。解决第一选择是在读码器上把触发模式改成外触发或单次触发从源头掐断重复。第二是在上位机做去重窗口按设备 ID 加条码加时间窗口过滤。两个都做更稳。去重窗口参数必须按节拍调节拍快则窗口短节拍慢则窗口长上线前拿真实工件测一小时确认没有漏掉相邻工件。5.3 GridView 刷新卡死UI 线程被扫码线程拖死现象扫码速度一快整个窗口卡住不动操作员拍屏幕也没反应速度降下来又慢慢恢复。原因扫码线程每解析一条就往 GridView 里 AddNewRow或者在扫码线程里直接操作了 UI 控件。UI 线程被高频刷新请求淹没连鼠标消息都来不及处理表现就是卡死。DevExpress 控件和原生控件一样不允许在非 UI 线程直接操作虽然有时不立刻报错但状态会越来越乱。解决所有 UI 刷新统一放定时器批量处理按 3.3 的做法500ms 或 1s 刷新一次。也可以用 BeginInvoke 把更新动作排到 UI 线程但批量合并仍然更省。这样既规避了跨线程问题又把一秒内几十次刷新合并成两次界面自然就顺了。5.4 读码器连不上IP、端口和协议三层都要对齐现象程序启动后界面一直显示设备离线TCP 调试助手手动却能连上。原因读码器的网络设置和上位机不在同一网段或者端口写错或者读码器的工作模式没配对。最常见的是子网掩码不对读码器是 192.168.0.10/24上位机是 192.168.1.100/24两个网段根本不通。解决先在本机 ping 读码器 IPping 不通就是网络问题调 IP 或网段ping 得通但连不上用 TCP 调试助手按目标端口试。注意读码器可能同时有 TCP 服务端和客户端两种模式上位机写监听端口时读码器必须配置为 TCP 客户端模式主动连过来。还有读码器开了多个端口时数据从哪个端口出上位机就监听哪个端口别只盯着默认端口。5.5 数据库故障连带扫码停摆落库失败让整个产线停下来现象数据库磁盘满了或文件损坏上位机报了一堆数据库异常紧接着扫码系统整个无响应产线被迫停线。原因把扫码和落库耦合在一起每扫一个码都同步写库数据库一出问题扫码线程也被异常打崩溃。这种情况在夜班最危险因为没人第一时间发现数据库故障。解决按 4.3 的 Channel 解耦方案扫码线程只入队落库线程独立处理。落库失败时把数据写入本地 CSV 或 SQLite 备份文件上位机屏幕显示数据积压警告。我还会在数据库断开时把内存表数据标记为未落库等到数据库恢复后自动补录。这个策略保证数据库故障期间扫码系统依然能正常扫最多是数据延后落库。6. 从能跑到跑稳看门狗、重连机制和日志追溯的最后一公里固定式扫码系统跑在产线上最怕的不是功能缺而是无声无息地死掉。操作员不会看你代码只会说“不知道什么时候开始扫不上了”。所以最后一公里我一般做三件事。第一给读码器连接加心跳和自动重连。不管串口还是 TCP都定时丢一个查询指令基恩士用SR指令、康耐视用!指令读码器有回应才算活着。连续三次没回应判定掉线上位机每 3 秒尝试重连一次同时把“设备离线”的状态大字显示在看板上。TCP 模式下读码器自带断线重连缓冲上位机只要把监听端口挂住数据会自动补传。第二看门狗线程。每 5 秒检查一次 UI 线程响应时间用 BeginInvoke 一个空方法看多久被执行。如果超过 2 秒没响应就重启整个主窗体。这个方法很粗暴但在无人值守的夜班产线上救过我几次。重启前先把内存里的扫码流水全部落库别丢数据。第三日志按天分文件。不止记异常还要记正常扫码记录、重连事件、异常内容。产线出了问题第一件事就是打开当天日志看读码器几点几分掉线、几点几分恢复。没有日志的系统出了问题只能靠猜等于没出问题也要提心吊胆。另外给扫码结果加个比对规则是进阶玩法。很多产线不只要求读到码还要求码的前缀、长度符合当前工单预期。不匹配的码直接标红并在看板弹警告操作员能当场拦住不良品。这个功能在 DevExpress 的 GridView 里做起来很简单在 RowCellStyle 事件里判断即可。private void gridView1_RowCellStyle(object sender, RowCellStyleEventArgs e) { var barcode gridView1.GetRowCellValue(e.RowHandle, barcode)?.ToString(); if (!string.IsNullOrEmpty(barcode) barcode.Length ! expectedLength) { e.Appearance.BackColor Color.FromArgb(255, 230, 230); e.Appearance.ForeColor Color.DarkRed; } }逻辑说明RowCellStyle 是 GridView 的单元格样式事件e.RowHandle 是当前行索引GetRowCellValue 取出该行条码列的值。expectedLength 是工单预期长度每次切换工单时从配置重新赋值。v23.1 里只要设置 e.Appearance 就能生效不需要额外调用 Refresh事件触发是自动的。这是我在固定式扫码上位机项目里最常用的收尾手段功能能跑只是及格跑得稳、出问题能查、操作员看得懂才是上线的标准。希望帮到你。本文还有配套的精品资源点击获取
返回列表