堆垛机.cs 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409
  1. using Newtonsoft.Json;
  2. using PlcSiemens.Core.Extension;
  3. using ServiceCenter.Extensions;
  4. using ServiceCenter.Logs;
  5. using ServiceCenter.SqlSugars;
  6. using System.ComponentModel;
  7. using System.Threading.Tasks;
  8. using WCS.Core;
  9. using WCS.Entity;
  10. using WCS.WorkEngineering.Extensions;
  11. using WCS.WorkEngineering.Protocol.SRM;
  12. using WCS.WorkEngineering.Protocol.Station;
  13. using WCS.WorkEngineering.WebApi.Controllers;
  14. using WCS.WorkEngineering.WebApi.Models.AGV.Response;
  15. using WCS.WorkEngineering.Worlds;
  16. using DeviceFlags = WCS.WorkEngineering.Extensions.DeviceFlags;
  17. using KnownException = ServiceCenter.Logs.KnownException;
  18. using TaskStatus = WCS.Entity.TaskStatus;
  19. namespace WCS.WorkEngineering.Systems
  20. {
  21. /// <summary>
  22. /// 堆垛机
  23. /// </summary>
  24. [BelongTo(typeof(MainWorld))]
  25. [Description("堆垛机")]
  26. public class 堆垛机 : DeviceSystem<SRM>
  27. {
  28. /// <summary>
  29. /// 取货点设备集合
  30. /// </summary>
  31. private Dictionary<string, List<Station>> PickUpDevices = new Dictionary<string, List<Station>>();
  32. /// <summary>
  33. /// 放货设备
  34. /// </summary>
  35. private Dictionary<string, List<Station>> PutDevices = new Dictionary<string, List<Station>>();
  36. public 堆垛机()
  37. {
  38. //获取所有的巷道集合
  39. var devices = Device.All.Where(v => v.HasFlag(DeviceFlags.巷道));
  40. //开始分配
  41. foreach (var item in devices)
  42. {
  43. //取货设备
  44. var srm = item.Targets.Where(v => v.HasFlag(DeviceFlags.堆垛机)).FirstOrDefault();
  45. PickUpDevices.Add(srm.Code, item.Sources.Where(v => v.HasFlag(DeviceFlags.输送机)).Select(v => new Station(v, this.World)).ToList());
  46. //放货设备
  47. srm = item.Sources.Where(v => v.HasFlag(DeviceFlags.堆垛机)).FirstOrDefault();
  48. PutDevices.Add(srm.Code, item.Targets.Where(v => v.HasFlag(DeviceFlags.输送机)).Select(v => new Station(v, this.World)).ToList());
  49. }
  50. }
  51. protected override bool ParallelDo => true;
  52. protected override bool SaveLogsToFile => true;
  53. public override void Do(SRM obj)
  54. {
  55. #region 处理完成任务
  56. //判断DB520 完成任务确认清除信号 是否为1
  57. if (obj.Data.OkAck == 1) throw new KnownException($"WCS任务完成确认信号[DB520.OkAck]未清除,请检查堆垛机处理异常原因", LogLevelEnum.Mid);
  58. if (obj.Data2.TaskFinishiId > 0)
  59. {
  60. //处理完成的任务信息
  61. WCS_TaskInfo taskInfo = null;
  62. //开始处理
  63. SqlSugarHelper.Do(db =>
  64. {
  65. World.Log($"堆垛机任务处理:开始--完成任务{obj.Data2.TaskFinishiId}", LogLevelEnum.Low);
  66. #region 获取完成任务
  67. //根据DB521任务号获取对应任务
  68. var task = db.Default.Queryable<WCS_TaskInfo>().First(v => v.ID == obj.Data2.TaskFinishiId);
  69. if (task == null) throw new KnownException($"堆垛机完成任务号{obj.Data2.TaskFinishiId},在WCS当前任务信息中未找到对应任务。", LogLevelEnum.High);
  70. if (task.Status != Entity.TaskStatus.StackerExecution) throw new KnownException($"任务{task.ID}状态是{task.Status.GetDescription()}.堆垛机完成任务需要对应任务状态处于堆垛机执行中", LogLevelEnum.High);
  71. #endregion 获取完成任务
  72. //根据任务类型做不同的处理
  73. switch (task.Type)
  74. {
  75. case TaskType.EnterDepot:
  76. task.Status = Entity.TaskStatus.Finish;
  77. task.EedTime = DateTime.Now;
  78. db.Default.Updateable(task).ExecuteCommand();
  79. task.AddWCS_TASK_DTL(db, task.AddrTo, "入库任务结束");
  80. //完成任务
  81. break;
  82. case TaskType.OutDepot:
  83. switch (task.OutType)
  84. {
  85. case OutTypeEnum.自动出库任务:
  86. var dev = new Station(Device.All.FirstOrDefault(v => v.Code == task.SrmStation), this.World);
  87. if (task.AddrTo == "8271" || task.AddrTo == "8272" || task.AddrTo == "8273" || task.AddrTo == "8274" || task.AddrTo == "8275")
  88. {
  89. task.Status = Entity.TaskStatus.Finish;
  90. dev.Data.GoodsEnd = 8278;
  91. }
  92. else
  93. {
  94. task.Status = TaskStatus.ConveyorExecution;
  95. dev.Data.GoodsEnd = task.AddrTo.ToShort();
  96. }
  97. dev.Data.TaskNumber = task.ID;
  98. dev.Data.VoucherNo++;
  99. db.Default.Updateable(task).ExecuteCommand();
  100. task.AddWCS_TASK_DTL(db, task.SrmStation, "出库任务到达放货站台");
  101. WmsApi.SrmPickOutCompleted(task.ID);
  102. break;
  103. case OutTypeEnum.全自动手动出库任务:
  104. var dev1 = new Station(Device.All.FirstOrDefault(v => v.Code == task.SrmStation), this.World);
  105. if (task.AddrTo == "8271" || task.AddrTo == "8272" || task.AddrTo == "8273" || task.AddrTo == "8274" || task.AddrTo == "8275")
  106. {
  107. task.Status = Entity.TaskStatus.Finish;
  108. dev1.Data.GoodsEnd = 8278;
  109. }
  110. else
  111. {
  112. task.Status = TaskStatus.ConveyorExecution;
  113. dev1.Data.GoodsEnd = task.AddrTo.ToShort();
  114. }
  115. dev1.Data.TaskNumber = task.ID;
  116. dev1.Data.VoucherNo++;
  117. db.Default.Updateable(task).ExecuteCommand();
  118. task.AddWCS_TASK_DTL(db, task.SrmStation, "手动出库任务到达放货站台");
  119. WmsApi.SrmPickOutCompleted(task.ID);
  120. break;
  121. case OutTypeEnum.半自动手动出库任务:
  122. task.Status = Entity.TaskStatus.Finish;
  123. task.EditTime = DateTime.Now;
  124. db.Default.Updateable(task).ExecuteCommand();
  125. task.AddWCS_TASK_DTL(db, task.SrmStation, "半自动手动出库任务结束");
  126. break;
  127. default:
  128. break;
  129. }
  130. break;
  131. case TaskType.TransferDepot:
  132. task.Status = Entity.TaskStatus.Finish;
  133. task.EedTime = DateTime.Now;
  134. db.Default.Updateable(task).ExecuteCommand();
  135. task.AddWCS_TASK_DTL(db, task.AddrTo, "移库任务结束");
  136. break;
  137. }
  138. taskInfo = task;
  139. if (task.Status >= TaskStatus.Finish) task.CompleteOrCancelTasks(db);
  140. });
  141. if (taskInfo == null) throw new KnownException("数据库提交事务错误", LogLevelEnum.High);
  142. // 写入信号
  143. if (taskInfo.Status == TaskStatus.Finish)
  144. {
  145. WmsApi.CompleteTask(taskInfo.ID);
  146. }
  147. obj.Data.OkAck = 1;
  148. //通知WMS任务完成
  149. World.Log($"堆垛机任务处理:结束--完成任务{obj.Data2.TaskFinishiId}", LogLevelEnum.Mid);
  150. }
  151. #endregion 处理完成任务
  152. //堆垛机是否可以下发任务
  153. if (obj.Data2.VoucherNo != obj.Data.VoucherNo) throw new KnownException($"凭证号不一致,DB520:{obj.Data.VoucherNo},DB521:{obj.Data2.VoucherNo}", LogLevelEnum.Mid);
  154. if (obj.Data2.AutoStatus != SrmAutoStatus.Automatic) throw new KnownException($"堆垛机处于{obj.Data2.AutoStatus.GetDescription()}模式", LogLevelEnum.Low);
  155. if (obj.Data2.RunStatus != SrmRunStatus.Idle) throw new KnownException($"堆垛机处于{obj.Data2.RunStatus.GetDescription()}状态", LogLevelEnum.High);
  156. //默认没有移库任务
  157. bool isTransfer = false;
  158. //出入库优先级任务 1:无优先 2:入库 3:出库
  159. int enterOrOut = 1;
  160. //再检查是否有等待执行的货物
  161. SqlSugarHelper.Do(db =>
  162. {
  163. //获取当前堆垛机的所有未完成任务
  164. var tasks = db.Default.Queryable<WCS_TaskInfo>().Where(v => v.Status < Entity.TaskStatus.Finish && (v.Device == obj.Entity.Code));
  165. //任务集合是否有处于堆垛机执行状态的任务
  166. if (tasks.Any(v => v.Status == Entity.TaskStatus.StackerExecution)) throw new KnownException($"有任务处于堆垛机执行状态", LogLevelEnum.High);
  167. //不存在调整优先级任务,判断是否存在移库任务
  168. isTransfer = tasks.Any(v => v.Type == TaskType.TransferDepot && v.Status == Entity.TaskStatus.NewBuild);
  169. if (!isTransfer) //存在调整优先级任务
  170. {
  171. //获取出库任务中新建状态最大优先级
  172. var outPriorityNewBuild = tasks.Where(v => v.Type == TaskType.OutDepot && v.Status == Entity.TaskStatus.WaitingToExecute).Max(v => v.Priority);
  173. //获取入库任务中最大优先级
  174. var enterPriority = tasks.Where(v => v.Type == TaskType.EnterDepot && v.Status < Entity.TaskStatus.StackerExecution).Max(v => v.Priority);
  175. //出入库最大优先级相加大于零
  176. if (outPriorityNewBuild + enterPriority > 0)
  177. {
  178. //出入库优先级任务 1:无优先 2:入库 3:出库
  179. enterOrOut = enterPriority > outPriorityNewBuild ? 2 : 3;
  180. }
  181. }
  182. });
  183. #region 移库
  184. if (isTransfer)
  185. {
  186. WCS_TaskInfo taskInfo = null;
  187. SqlSugarHelper.Do(db =>
  188. {
  189. //获取一条当前堆垛机优先级最高的新建移库任务
  190. var task = db.Default.Queryable<WCS_TaskInfo>().Where(v => v.Device == obj.Entity.Code && v.Type == TaskType.TransferDepot && v.Status == Entity.TaskStatus.NewBuild)
  191. .OrderByDescending(v => v.Priority)
  192. .First() ?? throw new KnownException("未找到移库任务", LogLevelEnum.High);
  193. //任务状态改为堆垛机执行中
  194. task.Status = Entity.TaskStatus.StackerExecution;
  195. task.StartTime = DateTime.Now;
  196. db.Default.Updateable(task).ExecuteCommand();
  197. task.AddWCS_TASK_DTL(db, task.AddrFrom, task.Device, $"堆垛机{obj.Entity.Code}开始执行任务");
  198. taskInfo = task;
  199. });
  200. if (taskInfo == null) throw new KnownException("数据更新错误", LogLevelEnum.High);
  201. var addrFrom = taskInfo.AddrFrom.Split("-");
  202. var addrTo = taskInfo.AddrTo.Split("-");
  203. World.Log($"堆垛机任务处理:开始--下发移库任务[{obj.Data.TaskNumber}][{obj.Data.SLine}][{obj.Data.SCol}][{obj.Data.SLayer}][{obj.Data.ELine}][{obj.Data.ECol}][{obj.Data.ELayer}][{obj.Data.TaskType}][{obj.Data.VoucherNo}]", LogLevelEnum.Mid);
  204. //下发任务
  205. obj.Data.TaskNumber = taskInfo.ID;
  206. obj.Data.SLine = addrFrom[0].ToShort();
  207. obj.Data.SCol = addrFrom[1].ToShort();
  208. obj.Data.SLayer = addrFrom[2].ToShort();
  209. obj.Data.ELine = addrTo[0].ToShort();
  210. obj.Data.ECol = addrTo[1].ToShort();
  211. obj.Data.ELayer = addrTo[2].ToShort();
  212. obj.Data.TaskType = SrmTaskType.MoveGoods;
  213. obj.Data.VoucherNo++;
  214. World.Log($"堆垛机任务处理:结束---下发移库任务[{obj.Data.TaskNumber}][{obj.Data.SLine}][{obj.Data.SCol}][{obj.Data.SLayer}][{obj.Data.ELine}][{obj.Data.ECol}][{obj.Data.ELayer}][{obj.Data.TaskType}][{obj.Data.VoucherNo}]", LogLevelEnum.Mid);
  215. }
  216. #endregion 移库
  217. #region 出入库
  218. //上一个周期是不是出库任务 第一次获取返回结果会是false
  219. var lastIsOut = obj.Entity.GetFlag<bool>("LastIsOut");
  220. obj.Entity.SetFlag("LastIsOut", !lastIsOut);
  221. //入库任务优先 或 上一个周期是出库任务并且出库任务无优先
  222. if (enterOrOut == 2 || (lastIsOut && enterOrOut == 1)) //入库任务
  223. {
  224. //判断本次优先执行楼层,并设置下次执行时优先楼层
  225. //var floor = obj.Entity.GetFlag<int>("FloorIn");
  226. //floor = floor % 2 + 1;
  227. //obj.Entity.SetFlag("FloorIn", floor);
  228. //获取当前堆垛机所有的取货站台
  229. var arrIn = PickUpDevices.First(v => v.Key == obj.Entity.Code).Value;
  230. if (!arrIn.Any()) throw new KnownException($"堆垛机{obj.Entity.Code}无取货路径点", LogLevelEnum.High);
  231. ////获取有货的设备
  232. arrIn = arrIn.Where(v => v.Data2.TaskNumber > 0 && v.Data3.Status.HasFlag(StationStatus.PH_Status) && !v.Data3.Status.HasFlag(StationStatus.Run)).ToList();
  233. if (!arrIn.Any()) throw new KnownException($"[{obj.Entity.Code}]等待入库任务输送到位", LogLevelEnum.Mid);
  234. WCS_TaskInfo taskInfo = null;
  235. Station station = null;
  236. SqlSugarHelper.Do(db =>
  237. {
  238. //根据有货设备的任务号获取所有类型为入库状态为输送机执行中的任务
  239. var tasks = db.Default.Queryable<WCS_TaskInfo>().Where(v => (v.Type == TaskType.EnterDepot)
  240. && v.Status == TaskStatus.ConveyorExecution
  241. && arrIn.Select(p => p.Data2.TaskNumber).Contains(v.ID)).ToList();
  242. //World.Log("可用任务集合:" + JsonConvert.SerializeObject(tasks));
  243. //按条件先后排序获取一条排序后第一条结果1.优先级2.所在楼层与本次优先执行楼层 TODO:待验证排序结果
  244. var task = tasks.OrderByDescending(v => v.Priority).First() ?? throw new KnownException($"{obj.Entity.Code}未找到入库任务", LogLevelEnum.High);
  245. //获取任务所有设备
  246. station = arrIn.First(v => v.Data2.TaskNumber == task.ID);
  247. if(station == null) throw new KnownException($"{obj.Entity.Code}未找到满足条件的入库任务", LogLevelEnum.High);
  248. //获取当前货物巷道
  249. var res = WmsApi.GetLocalIn(task.ID, task.Device, station.Entity.Code);
  250. var loc = res.ResData.CellNo.Split("-");
  251. task.Status = TaskStatus.StackerExecution;
  252. task.AddrTo = $"{loc[0]}-{loc[1]}-{loc[2]}";
  253. task.LastInteractionPoint = station.Entity.Code;
  254. task.EditWho = "WCS";
  255. if (db.Default.Updateable(task).ExecuteCommand() > 1)
  256. {
  257. World.Log($"更新成功", LogLevelEnum.Mid);
  258. }
  259. else
  260. {
  261. World.Log($"更新失败", LogLevelEnum.Mid);
  262. db.Connect.Updateable<WCS_TaskInfo>().SetColumns(p => new WCS_TaskInfo() { AddrTo = task.AddrTo }).Where(p => p.ID == task.ID);
  263. }
  264. task.AddWCS_TASK_DTL(db, station.Entity.Code, task.AddrTo, "任务下发堆垛机执行");
  265. taskInfo = task;
  266. });
  267. if (taskInfo == null) throw new KnownException("数据更新错误", LogLevelEnum.High);
  268. var addrTo = taskInfo.AddrTo.Split("-");
  269. World.Log($"堆垛机任务处理:开始--下发入库任务[{obj.Data.TaskNumber}][{obj.Data.SLine}][{obj.Data.SCol}][{obj.Data.SLayer}][{obj.Data.ELine}][{obj.Data.ECol}][{obj.Data.ELayer}][{obj.Data.TaskType}][{obj.Data.VoucherNo}]", LogLevelEnum.Mid);
  270. //下发任务
  271. obj.Data.TaskNumber = taskInfo.ID;
  272. obj.Data.TaskType = SrmTaskType.Default;
  273. obj.Data.SLine = station.Entity.Code.ToShort();
  274. obj.Data.SCol = 0;
  275. obj.Data.SLayer = 0;
  276. obj.Data.ELine = addrTo[0].ToShort();
  277. obj.Data.ECol = addrTo[1].ToShort();
  278. obj.Data.ELayer = addrTo[2].ToShort();
  279. obj.Data.VoucherNo++;
  280. World.Log($"堆垛机任务处理:结束---下发入库任务[{obj.Data.TaskNumber}][{obj.Data.SLine}][{obj.Data.SCol}][{obj.Data.SLayer}][{obj.Data.ELine}][{obj.Data.ECol}][{obj.Data.ELayer}][{obj.Data.TaskType}][{obj.Data.VoucherNo}]", LogLevelEnum.Mid);
  281. }
  282. else if (enterOrOut == 3 || !lastIsOut) //出库任务
  283. {
  284. //获取当前堆垛机所有的取货站台
  285. var arrOut = PutDevices.First(v => v.Key == obj.Entity.Code).Value;
  286. if (!arrOut.Any()) throw new KnownException($"堆垛机{obj.Entity.Code}无放货路径点", LogLevelEnum.High);
  287. //获取可以放货的设备集合
  288. arrOut = arrOut.Where(v => !v.Data3.Status.HasFlag(StationStatus.PH_Status) //无光电
  289. && !v.Data3.Status.HasFlag(StationStatus.Run) //未运行
  290. && !v.Data3.Status.HasFlag(StationStatus.OT_Status) //无任务
  291. && !v.Data3.Status.HasFlag(StationStatus.UnassignedTask) //未分配任务
  292. && v.Data3.Status.HasFlag(StationStatus.Auto)).ToList(); //自动
  293. if (!arrOut.Any()) throw new KnownException($"[{obj.Entity.Code}]无可用放货站台", LogLevelEnum.Mid);
  294. WCS_TaskInfo taskInfo = null;
  295. SqlSugarHelper.Do(db =>
  296. {
  297. var tasks = db.Default.Queryable<WCS_TaskInfo>().Where(v => v.Type == TaskType.OutDepot && v.Status == TaskStatus.ConveyorExecution).ToList();
  298. var allOutCode = arrOut.Select(v => v.Entity.Code).ToList();
  299. //var taskIn = db.Default.Queryable<WCS_TaskInfo>().Where(v => v.Type == TaskType.EnterDepot && v.Status > TaskStatus.WaitingToExecute);
  300. //按条件先后排序获取一条排序后第一条结果1.优先级2.所在楼层与本次优先执行楼层
  301. var task = db.Default.Queryable<WCS_TaskInfo>().Where(v => v.Type == TaskType.OutDepot && v.Status == TaskStatus.WaitingToExecute)
  302. .Where(v => allOutCode.Contains(v.SrmStation))
  303. .OrderByDescending(v => v.Priority)
  304. .OrderBy(v => v.AddTime)
  305. .First();
  306. if (task == null) throw new KnownException($"{obj.Entity.Code}未找到出库任务", LogLevelEnum.High);
  307. //控制出货口任务数量
  308. if (task.AddrTo != "8278" && tasks.Count(v => v.AddrTo == task.AddrTo) >= 3) return;
  309. //if (taskIn.Any(v => v.AddrNext == task.SrmStation))
  310. //{
  311. // throw new KnownException($"等待入库任务执行", LogLevelEnum.High);
  312. //}
  313. var taskId = task.ID;
  314. //判断是否是二深位任务
  315. var addrFrom = task.AddrFrom.Split("-");
  316. if (addrFrom[4] == "2")
  317. {
  318. var res = WmsApi.AddWcsMoveTask(task.ID);
  319. switch (res.ResData.ResType)
  320. {
  321. case WmsApiMoveTask.允许2升位执行:
  322. break;
  323. case WmsApiMoveTask.执行移库任务:
  324. throw new KnownException($"堆垛机{obj.Entity.Code}需要先执行移库任务", LogLevelEnum.Mid);
  325. case WmsApiMoveTask.一深位有出库任务:
  326. task = db.Default.Queryable<WCS_TaskInfo>()
  327. .Where(v => v.Type == TaskType.OutDepot && v.Status == TaskStatus.WaitingToExecute)
  328. .Where(v => allOutCode.Contains(v.SrmStation) && v.AddrFrom == res.ResData.CellNo)
  329. .First() ?? throw new KnownException($"一深位有出库任务,等待一深位任务执行完成", LogLevelEnum.Mid);
  330. break;
  331. };
  332. if (res.ResData.ResType == WmsApiMoveTask.执行移库任务) return;
  333. if (res.ResData.ResType == WmsApiMoveTask.一深位有出库任务 && res.ResData.CellNo != task.AddrFrom) return;
  334. }
  335. task.Status = TaskStatus.StackerExecution;
  336. task.LastInteractionPoint = task.Device;
  337. task.EditWho = "WCS";
  338. db.Default.Updateable(task).ExecuteCommand();
  339. task.AddWCS_TASK_DTL(db, task.Device, task.SrmStation, "任务下发堆垛机执行");
  340. taskInfo = task;
  341. });
  342. if (taskInfo == null) throw new KnownException("数据更新错误", LogLevelEnum.High);
  343. var addrFrom = taskInfo.AddrFrom.Split("-");
  344. World.Log($"堆垛机任务处理:开始--下发出库任务[{obj.Data.TaskNumber}][{obj.Data.SLine}][{obj.Data.SCol}][{obj.Data.SLayer}][{obj.Data.ELine}][{obj.Data.ECol}][{obj.Data.ELayer}][{obj.Data.TaskType}][{obj.Data.VoucherNo}]", LogLevelEnum.Mid);
  345. obj.Data.TaskNumber = taskInfo.ID;
  346. obj.Data.SLine = addrFrom[0].ToShort();
  347. obj.Data.SCol = addrFrom[1].ToShort();
  348. obj.Data.SLayer = addrFrom[2].ToShort();
  349. obj.Data.ELine = taskInfo.SrmStation.ToShort();
  350. obj.Data.ECol = 0;
  351. obj.Data.ELayer = 0;
  352. obj.Data.TaskType = SrmTaskType.Default;
  353. obj.Data.VoucherNo++;
  354. World.Log($"堆垛机任务处理:结束---下发出库任务[{obj.Data.TaskNumber}][{obj.Data.SLine}][{obj.Data.SCol}][{obj.Data.SLayer}][{obj.Data.ELine}][{obj.Data.ECol}][{obj.Data.ELayer}][{obj.Data.TaskType}][{obj.Data.VoucherNo}]", LogLevelEnum.Mid);
  355. }
  356. #endregion 出入库
  357. }
  358. public override bool Select(Device dev)
  359. {
  360. return dev.HasProtocol(typeof(ISRM520));
  361. }
  362. }
  363. }