简介:本资源是一套完整的智能立体仓库WCS(仓库控制系统)C#源码实现,面向工业自动化软件开发工程师、物流系统集成人员及高校智能制造方向学习者,聚焦于提升机、堆垛机等核心设备的调度控制与实时监控。项目基于VS2022开发,采用MVC架构,集成SQL数据库支持,提供设备增删改查、PLC通信参数配置(如S7系列CPU插槽设定)、任务分发逻辑等关键功能模块,可直接用于中小型立体仓项目二次开发或教学实践。压缩包共2003个文件,含254个C#业务逻辑文件、384个运行依赖DLL、221个前端JS/HTML/CSS界面组件、210个PNG图标及监控视图资源,整体大小为136.51MB,结构清晰,模块解耦度高。目前已有729人学习下载,读者可获得可编译运行的完整工程(含.sln解决方案)、设备调度服务层代码、Web监控界面源码及配套数据库脚本,具备良好的工程参考价值与快速上手能力。 先说一个大家伙都懂的事实:搞仓储物流自动化的项目,业务逻辑往往不复杂,真正磨人的是设备调度、通信稳定性和现场调试。市面上关于WCS的成熟文档很少,很多团队都是边做边踩坑。这个项目标题里提到的几个关键词——C#、VS2022、MVC、提升机、堆垛机、设备监控、SQL,基本就是一套标准WCS系统的全部构成。我把这套系统从零到上线的主要设计思路、模块拆分、编码实现和排障经验整理出来,给正在做同类项目的朋友一个参考。
1. 项目定位与整体架构思路
1.1 WCS在整个立体仓库系统里的位置
先理清WCS的定位。一套完整的自动化立体仓库系统从上层到底层依次是:ERP/WMS(仓库管理系统)→ WCS(仓库控制系统)→ PLC/现场设备(堆垛机、提升机、输送机、RGV等)。WMS管的是“账”,比如货位分配、库存记录、出库单管理;WCS管的是“动作”,比如哪个堆垛机去执行哪个任务、提升机现在该停在哪一层、输送线怎么流转托盘。
项目标题里把“WCS控制系统”和“提升机、堆垛机、设备监控界面”并列,说明这套系统不是只做数据库增删改查,而是要真正能指挥设备干活。在实际项目中,WCS就像一个翻译官:把WMS下发的出库/入库指令翻译成设备能执行的动作序列,同时把设备的反馈状态翻译成可视化的监控数据。
1.2 技术选型:为什么选C# + MVC + SQL这套组合
很多做设备控制的工程师可能会奇怪,设备调度软件为什么不直接用WinForm或者WPF,反而用MVC?这里要区分两个概念:项目标题里说的MVC,既是架构模式也是实际可用的框架。
从架构模式来看,MVC非常适合WCS这种需要“界面、逻辑、数据”分离的系统:
- Model层:承载业务实体(任务、设备、托盘)和数据库访问。
- View层:设备监控界面,包括布局图、状态指示灯、报警窗口。
- Controller层:接收界面操作/上位机指令,协调Model层的业务逻辑,并向设备下发动作。
从实际框架来看,VS2022环境下可以用ASP.NET Core MVC写Web端监控页面,也可以用传统.NET Framework MVC写服务端逻辑。我在实际项目中两种都试过,如果现场对远程监控有需求(比如办公室电脑通过浏览器看设备状态),Web MVC更合适;如果纯粹是操作员站,WinForm + MVC分层思想也完全够用。这套源码走的是Web MVC路线,配合SQL Server存储业务数据,整体上是一个“浏览器打开监控界面 + 服务端调度引擎 + 数据库持久化”的组合。
这套选型的好处非常明显:
- C#是工业软件领域生态最完善的语言之一,和PLC通信(S7协议、Modbus TCP)、串口通信、OPC UA都有成熟类库。
- VS2022的调试体验在同类IDE里属于第一梯队,断点调试设备调度逻辑比写C++不知道省多少时间。
- SQL Server在Windows环境下运维成本低,和C#天然兼容,报表统计能力强。
1.3 系统模块拆解
一个完整的WCS系统在编码前最好先把模块边界划清楚。我这套系统的模块划分如下:
| 模块 | 功能职责 |
|---|---|
| 任务调度中心 | 接收WMS任务,任务拆分,任务优先级管理,任务执行状态机 |
| 堆垛机控制模块 | 堆垛机行走/提升/货叉动作序列生成,位置校验,任务确认 |
| 提升机控制模块 | 楼层停靠控制,升降信号交互,到位确认,安全互锁 |
| 设备通信模块 | 与PLC走TCP/IP,心跳维护,报文解析与组装,断线重连 |
| 监控界面 | 设备实时布局显示、状态刷新、故障报警、远程暂停/恢复 |
| 数据管理模块 | 任务记录、设备日志、报警记录、效率统计,报表输出 |
模块划分明确之后,编码时就能并行推进,调试时也能快速定位是通信问题、调度逻辑问题还是数据库问题。
2. 核心业务模块设计与实现思路
2.1 任务流转的完整生命周期
WCS里的任务,核心是入库和出库,另外还有移库、盘点这类辅助任务。每个任务从创建到完成,背后有一个清晰的状态机。
我设计的任务状态流转是这样的:
已创建(Created)→ 已下发(Dispatched)→ 执行中(Processing)→ 已完成(Completed) ↘ 异常挂起(Suspended)→ 取消(Cancelled)入库任务的完整流程如下:
- WMS下发入库任务,携带托盘号(或条码)、目标巷道、货位地址。
- WCS检查目标堆垛机是否空闲、目标货位是否被占用、输送线上是否有空位。
- 如果条件满足,WCS把任务投递到堆垛机任务队列,并给PLC下发“取货”指令。
- 堆垛机运行到输送机取货位置,伸出货叉、抬起托盘、缩回货叉、确认负载变化。
- 堆垛机运行到目标货位,货叉伸出、放下托盘、缩回、确认货物已入库。
- WCS更新数据库货位状态,反馈WMS任务完成。
在实际编码中,这个状态机不能只用一个枚举字段草草了事。我建议在数据库单独建一张任务生命周期日志表,每个状态变更都记录操作时间、操作人(或系统模块)、变更前后状态。后期排查“这个托盘为什么卡在路上了”的时候,这张表就是破案的关键线索。
出库任务和入库流程几乎是对称的,但多了一个细节:出库时需要考虑先进先出(FIFO)原则。WMS下发任务时会指定托盘号,WCS只需要根据托盘号反查货位地址,生成出库动作序列即可。如果WMS不下发具体托盘号,WCS就需要根据数据库里该任务的物料批次信息,自主选择最早入库的货位出库。
2.2 堆垛机调度核心逻辑
堆垛机是整个立体仓库里最贵的设备,也是调度逻辑最复杂的设备。一台堆垛机有三个运动轴:X(行走)、Y(提升)、Z(货叉伸缩),三个轴可以同时运动。控制系统需要考虑的是如何让三个轴协同,以最短时间完成任务,又不能让设备碰撞。
2.2.1 堆垛机的任务指令格式
和PLC约定好报文格式是第一步。我常用的是报文头+功能码+数据体+校验尾的结构,例如:
帧头 0xAA 0x55 | 功能码 | 任务号 | 目标列 | 目标层 | 动作类型 | 校验和 | 帧尾 0x0D 0x0A“目标列、目标层”就是货位坐标,动作类型分为“取货”“放货”“取+放连续作业”。“任务号”用来关联WCS数据库里的任务记录,设备执行过程中每一步都会把任务号带回来,方便追踪。
2.2.2 路径规划与任务优化
单台堆垛机在执行任务时,最简单的策略是“一个任务跑一趟”,从当前位置→取货位→目标货位。但如果任务繁忙,这种策略效率很低。比较实用的优化手段是出入库复合作业:堆垛机出库完成后,如果已经有入库任务在等待,且入库目标货位和出库当前位置距离合理,就让堆垛机直接带着空托盘或直接在出库位接受入库指令,减少空跑次数。
这部分逻辑在代码实现上就是两个方法:单任务执行和任务合并判断。任务合并判断最简单的方法是计算相邻任务间的“曼哈顿距离”:
int GetManhattanDistance(Location current, Location target) { return Math.Abs(current.Col - target.Col) + Math.Abs(current.Row - target.Row); }如果当前任务(出库)的终点和下一个任务(入库)的起点的曼哈顿距离小于某个阈值(比如5列以内),就把两个任务合并成一个任务序列。阈值要根据堆垛机的实际速度标定,不能拍脑袋。
2.2.3 安全互锁
堆垛机的安全设计是绝对不能省的。常见的安全互锁条件包括:
- 货叉伸出到位之前,禁止堆垛机行走/提升。
- 提升到达目标层且确认到位后,才允许货叉动作。
- 堆垛机区域内有人进入(安全光栅触发)时,立即停止所有动作。
这些互锁在WCS层面也要做一层逻辑校验,不能只依赖PLC。比如WCS下发行走指令前,必须确认当前货叉已缩回到位;下发提升指令前,必须确认当前没有“取货中途”这种悬挂状态。这样即使PLC的互锁没生效,WCS也不会发出危险指令。
2.3 提升机协同控制设计
提升机的作用是连接不同楼层的输送系统。在多楼层立体仓库里,1楼收货,3楼/4楼存储,货物必须通过提升机垂直运输。提升机控制相对堆垛机简单,但协同逻辑是它的核心难点。
提升机的工作流程通常是这样的:
- 一楼输送线上的光电传感器检测到托盘到位。
- 输送机PLC把到位信号反馈给WCS。
- WCS给提升机下发“接货至指定楼层”指令。
- 提升机运行到1楼,接货,提升到目标楼层。
- 到层后,提升机把托盘移交给该楼层的输送线。
- 输送线把托盘送到堆垛机取货口,堆垛机接货入库。
这里有一个很重要的协同点:提升机到某一层时,该楼层的输送线必须已经准备好接收/送出托盘。否则托盘会在提升机和输送线之间卡住。我在设计时增加了一个“交接确认”机制:提升机到位后,WCS先给该楼层输送线PLC发送“准备接货”指令,输送线PLC执行滚道正转并反馈“就绪”,提升机才执行送料动作。每一步都有明确的双向握手信号,最大限度避免交接失误。
3. 关键实现:设备监控界面与通信机制
3.1 监控界面的数据模型
监控界面本质上是“把数据库和内存里的设备状态数据可视化”。不需要特别炫酷的3D效果,关键是信息准确、层次分明。通常包含以下几个区域:
- 仓库俯视图区:用网格表示货位,不同颜色表示不同状态(空闲/占用/锁定/故障)。
- 设备状态栏:每台堆垛机、提升机以卡片形式展示当前状态(待机/运行/故障/急停),当前执行的任务号,当前位置坐标。
- 实时报警区:最新报警信息滚动展示,按等级颜色区分。
- 任务队列区:展示待执行任务、正在执行任务、最近完成任务的列表。
这个界面如果基于ASP.NET Core MVC开发,前端可以用原生JS + 定时刷新或者SignalR推送。考虑到现场网络环境可能不稳定,我建议先用定时刷新(建议2秒一次),稳定可靠,别一上来就上WebSocket。等系统跑稳了再加SignalR,体验会更好。
3.2 与PLC通信的实现思路
WCS和PLC的通信方式是整个项目里最容易出问题的地方,也是必须花大力气打磨的模块。
通信层我推荐单独封装一个类库,不要和业务逻辑混在一起。通信类库需要提供的基础能力包括:
- TCP客户端连接管理(PLC通常作为TCP服务端,WCS作为客户端主动连接)。
- 心跳包定时发送和超时检测(每隔3秒发一次心跳,超过10秒没收到回复就报警)。
- 报文接收队列,防止粘包和半包。
- 断线自动重连,重连次数和间隔可配置。
- 收发日志记录,每帧报文都存档,方便回放。
以下是通信层的一个简化示例,使用.NET的TcpClient实现了基本的连接管理和心跳:
public class PlcTcpClient { private TcpClient _tcpClient; private CancellationTokenSource _cts; private readonly string _plcIp; private readonly int _plcPort; private const int HeartbeatIntervalMs = 3000; private const int HeartbeatTimeoutMs = 10000; public event Action<string> OnDataReceived; public event Action OnDisconnected; public PlcTcpClient(string ip, int port) { _plcIp = ip; _plcPort = port; } public async Task StartAsync() { while (true) { try { _tcpClient = new TcpClient(); await _tcpClient.ConnectAsync(_plcIp, _plcPort); _cts = new CancellationTokenSource(); _ = Task.Run(() => ReceiveLoopAsync(_cts.Token)); _ = Task.Run(() => HeartbeatLoopAsync(_cts.Token)); break; } catch (Exception ex) { Console.WriteLine($"连接PLC失败, 3秒后重试: {ex.Message}"); await Task.Delay(3000); } } } private async Task ReceiveLoopAsync(CancellationToken token) { var buffer = new byte[4096]; var stream = _tcpClient.GetStream(); while (!token.IsCancellationRequested) { var read = await stream.ReadAsync(buffer, 0, buffer.Length, token); if (read == 0) { OnDisconnected?.Invoke(); break; } var message = Encoding.ASCII.GetString(buffer, 0, read); OnDataReceived?.Invoke(message); } } private async Task HeartbeatLoopAsync(CancellationToken token) { var lastReceivedTime = DateTime.Now; OnDataReceived += _ => lastReceivedTime = DateTime.Now; while (!token.IsCancellationRequested) { var sendHeartbeat = Encoding.ASCII.GetBytes("PING\n"); await _tcpClient.GetStream().WriteAsync(sendHeartbeat, token); await Task.Delay(HeartbeatIntervalMs, token); if ((DateTime.Now - lastReceivedTime).TotalMilliseconds > HeartbeatTimeoutMs) { Console.WriteLine("心跳超时,断开连接"); OnDisconnected?.Invoke(); break; } } } }这段代码的核心是两个循环:接收循环处理PLC返回的数据,心跳循环保证链路活跃。有一个细节值得注意,就是断线重连必须放在独立任务里,不能阻塞主业务线程。否则PLC随便一断电,WCS整个调度就停了。
3.3 报文解析与设备状态刷新
PLC返回的报文通常是一串固定格式的ASCII或十六进制字符串,比如:
AA 55 01 0021 05 12 0000 23 0D 0A含义是:帧头AA 55,功能码01(状态上报),任务号0021,当前列05,当前层12,报警码0000,校验23,帧尾。WCS收到之后要做两件事:更新数据库里设备的当前坐标和状态;把状态推送到监控界面。
如果监控界面是定时刷新,这一步很简单,前端每2秒调一次后台接口获取最新设备状态即可。数据库状态更新也不需要做成实时,2秒刷一次数据库完全够用。设备状态跟踪表的SQL设计可以参考第4节的表结构。
需要注意的一点是:数据库里保存的设备状态和内存中的实时状态一定要做好同步。我的做法是数据库表保存最近一次的有效状态,内存中维护实时状态缓存,两者每30秒校验一次。界面优先读内存缓存,只有历史查询才走数据库。这样即使数据库出现短暂卡顿,界面也不会跟着卡。
4. SQL数据库设计与优化实践
4.1 核心表结构设计
WCS的数据库表不在多,而在精。核心表保证以下5张就够了:
任务表(Wcs_Task)
CREATE TABLE Wcs_Task ( TaskId INT IDENTITY(1,1) PRIMARY KEY, TaskNo VARCHAR(32) NOT NULL UNIQUE, -- 任务号 TaskType TINYINT NOT NULL, -- 1入库 2出库 3移库 4盘点 TrayCode VARCHAR(32) NOT NULL, -- 托盘条码 Location VARCHAR(16) NULL, -- 目标货位,如 03-05-12 代表第3排05列12层 Priority TINYINT DEFAULT 5, -- 优先级 1-10 Status TINYINT NOT NULL DEFAULT 0, -- 状态机 CreateTime DATETIME DEFAULT GETDATE(), FinishTime DATETIME NULL, WmsTaskNo VARCHAR(32) NULL -- 上游WMS任务号 );设备状态表(Wcs_Device_Status)
CREATE TABLE Wcs_Device_Status ( DeviceId INT PRIMARY KEY, DeviceName VARCHAR(32) NOT NULL, DeviceType TINYINT NOT NULL, -- 1堆垛机 2提升机 3输送机 Status TINYINT NOT NULL DEFAULT 0, -- 0离线 1待机 2运行 3故障 4急停 CurrentX INT NULL, CurrentY INT NULL, CurrentZ INT NULL, CurrentTaskNo VARCHAR(32) NULL, AlarmCode VARCHAR(8) NULL, LastUpdateTime DATETIME DEFAULT GETDATE() );任务生命周期日志表(Wcs_Task_Log)
CREATE TABLE Wcs_Task_Log ( LogId INT IDENTITY(1,1) PRIMARY KEY, TaskNo VARCHAR(32) NOT NULL, OldStatus TINYINT NULL, NewStatus TINYINT NOT NULL, Operator VARCHAR(32) NOT NULL, -- 操作模块或人工账号 Remark NVARCHAR(255) NULL, LogTime DATETIME DEFAULT GETDATE(), INDEX IX_Task_Log_TaskNo (TaskNo), INDEX IX_Task_Log_Time (LogTime) );报警记录表(Wcs_Alarm)
CREATE TABLE Wcs_Alarm ( AlarmId INT IDENTITY(1,1) PRIMARY KEY, DeviceId INT NOT NULL, AlarmCode VARCHAR(8) NOT NULL, AlarmLevel TINYINT NOT NULL, -- 1提示 2警告 3严重 AlarmText NVARCHAR(255) NOT NULL, IsHandled BIT DEFAULT 0, HandleUser VARCHAR(32) NULL, HandleTime DATETIME NULL, CreateTime DATETIME DEFAULT GETDATE() );货位状态表(Wcs_Location)
CREATE TABLE Wcs_Location ( LocationId INT IDENTITY(1,1) PRIMARY KEY, LocationCode VARCHAR(16) NOT NULL UNIQUE, AreaCode VARCHAR(8) NOT NULL, RowNo INT NOT NULL, ColNo INT NOT NULL, LayerNo INT NOT NULL, Status TINYINT DEFAULT 0, -- 0空闲 1占用 2锁定 3故障 TrayCode VARCHAR(32) NULL, LastChangeTime DATETIME DEFAULT GETDATE() );4.2 关键SQL场景:任务下发与货位锁定
任务下发时,要保证货位和托盘的数据一致性。入库任务创建时,WCS先锁定目标货位,再创建任务记录,这两个操作必须放在同一个事务里。我用C#的TransactionScope来保证事务一致性:
using (var scope = new TransactionScope()) { string updateSql = @" UPDATE Wcs_Location SET Status = 2, TrayCode = @trayCode, LastChangeTime = GETDATE() WHERE LocationCode = @locationCode AND Status = 0"; string insertSql = @" INSERT INTO Wcs_Task (TaskNo, TaskType, TrayCode, Location, Priority, Status, WmsTaskNo) VALUES (@taskNo, @taskType, @trayCode, @locationCode, @priority, 0, @wmsTaskNo)"; using (var conn = new SqlConnection(_connStr)) { conn.Open(); var cmd = new SqlCommand(updateSql, conn); cmd.Parameters.AddWithValue("@locationCode", locationCode); cmd.Parameters.AddWithValue("@trayCode", trayCode); var affectedRows = cmd.ExecuteNonQuery(); if (affectedRows == 0) { throw new Exception($"货位 {locationCode} 已被占用,无法入库"); } // 继续执行插入任务... } scope.Complete(); }这里有一个很实用的经验:事务的隔离级别要设置好。默认的Read Committed在高并发下可能造成两个线程同时读到货位状态为0,然后同时执行更新,产生脏数据。解决办法是把隔离级别提升到Serializable,或者直接利用UPDATE语句本身的行锁特性(上面示例中先UPDATE再判断受影响行数,靠行锁天然解决并发问题)。
4.3 历史数据归档与索引优化
WCS运行久了,任务日志表、报警记录表的数据量会快速增长。尤其是日志表,一天几千条很正常。这类表不能无限堆积,否则索引会变大,查询越来越慢。
我的做法是按月分表或者在日志表中加归档标记:
- 每月1号凌晨自动把前两个月的日志备份到Wcs_Task_Log_Archive表。
- 原表只保留最近60天数据。
- 归档表不做频繁查询,如果用户需要查历史,走专门的归档查询接口。
索引方面,我的建议是:
- 经常作为查询条件的字段(TaskType、Status、CreateTime)建普通索引。
- 需要联合查询的字段(TaskNo + Status)建复合索引。
- 不要盲目建索引,写多读少的表索引越少越好,否则影响插入性能。
5. 常见问题与排查技巧实录
5.1 通信断连:PLC重启导致WCS无法自动恢复
现场遇到最频繁的问题就是PLC断电重启后,WCS的TCP连接没有自动恢复。原因通常有两个:一是重连逻辑没有做成循环尝试;二是断开后没有清理旧连接资源,导致重连时端口被占用。
排查步骤建议:
- 先看WCS日志,确认断连发生时是否有异常记录。
- 检查通信类的连接状态标识,确认是否真的释放了旧TcpClient。
- 在PLC侧确认是否配置了固定端口,且防火墙没有拦截WCS的IP。
解决办法是在通信类里增加一个“连接状态管理”状态机,断开时强制清空所有缓存数据、关闭底层Socket,然后以指数退避的方式重连:
int retryCount = 0; while (true) { try { await ConnectAsync(); retryCount = 0; break; } catch { retryCount++; int delayMs = Math.Min(30000, 1000 * (int)Math.Pow(2, retryCount)); Console.WriteLine($"重连失败,{delayMs}ms后重试"); await Task.Delay(delayMs); } }5.2 任务状态卡住:任务“假死”的定位方法
任务卡在“执行中”超过30分钟没动静,现场调度员最烦躁的就是这个。排查“假死”任务时,我有一套固定的排查路径:
- 先查Wcs_Task_Log,看这个任务最后的状态变更是什么时候、谁触发的。
- 再查设备状态表,看对应设备的当前状态是故障、急停还是空闲。
- 如果是设备故障导致的中途终止,确认设备恢复后,人工在监控界面对该任务执行“原地续跑”或“重新下发”。
- 如果设备空闲但任务卡住,多半是任务队列里该任务的优先级被其他任务挤掉,或者数据库里任务状态没有正确更新。
5.3 堆垛机定位不准:货位坐标偏差的校准经验
堆垛机定位不准是机械+电气+软件综合问题。常见现象是:PLC反馈的X坐标和目标列差一列,或者Y坐标和实际层数不一致。排查过程我一般按三步走:
- 先确认PLC的编码器零点是否偏移,让机械人员校准原点。
- 确认WCS下发的坐标单位是否正确——有的PLC用毫米,有的用货位号,转换关系不能搞错。
- 如果偶尔偏一两列,重点查堆垛机是否有打滑、刹车延迟问题。
从软件角度,WCS能做的是增加坐标校验逻辑:下单前检查目标坐标是否在仓库合法范围内;设备到位反馈后,和目标坐标对比,如果偏差超过允许值,触发报警而不是继续执行下一步。这个校验逻辑放在WCS要比放在PLC多一些容错空间。
5.4 监控界面卡顿:数据库性能瓶颈的优化
监控界面卡顿很多情况下不是界面代码的问题,而是数据库扛不住了。尤其是任务日志表膨胀后,界面每次请求都去查全表或者扫大索引,自然就慢了。
优化方案:
- 界面定时刷新请求只查设备和任务的核心字段,不查大字段(比如备注、报警文本)。
- 任务列表只展示最近100条,历史数据通过分页接口查询。
- 数据库层面给Status字段加索引,因为任务列表最常按状态过滤。
- 避免在界面刷新接口里写join多条大表的复杂SQL。
5.5 提升机和输送线交接失败:货物卡在交接位
这个问题的常见原因不是WCS逻辑,而是输送线的光电传感器灵敏度和PLC的信号处理时间不匹配。通常表现为:托盘已经到达交接位,但PLC的到位信号晚了几百毫秒才反馈,导致提升机提前启动或交接未完成。
处理方式分两层:
- PLC侧:增加信号滤波时间和到位确认延时,减少干扰误判。
- WCS侧:增加交接超时重试机制。如果发送接货指令后超过N秒(根据输送线速度标定)没有收到到位反馈,WCS自动重发一次,或报警提示人工介入。
很多人会觉得这个功能是PLC的事,但经验告诉我,WCS加一层重试逻辑能少跑很多现场。
6. 项目总结与个人经验体会
这套系统从需求分析到上线运行,前前后后迭代了三个版本。第一版能把任务跑通,但监控界面慢、通信不稳定;第二版优化了数据库和通信模块,基本满足现场使用;第三版完善了任务状态机和报警机制,才真正达到“无人值守”的可用状态。
我个人的体会是,WCS系统的开发难点从来不是某个单一技术点,而是整体一致性——数据库状态、内存状态、PLC状态、界面展示,这四者任何时候都必须保持一致,否则就是一个bug温床。所以代码里每到一个关键节点,宁可多写一行日志,也不要省略状态确认。
最后再分享一个小技巧:开发阶段就用一个“模拟PLC”来代替真实设备。模拟PLC把TCP通信、报文解析、坐标返回、异常注入都模拟出来,这样你的调度逻辑和监控界面可以提前很久开始调试,不用等设备进场。我在这个项目里就是靠模拟PLC把监控界面和任务调度的bug在实验室就消灭了大半,现场调试时间压缩了至少一周。
希望这套系统拆解和排障经验对你有用。如果你正在做类似的WCS项目,或者正准备选型,欢迎针对具体模块继续交流。
本文还有配套的精品资源,点击获取