using Microsoft.Extensions.Options; using WIDESEAWCS_S7Simulator.Core.Interfaces; using WIDESEAWCS_S7Simulator.Core.Protocol; namespace WIDESEAWCS_S7Simulator.Application.Protocol; /// /// PlcLink 堆垛机设备协议处理器。 /// public class PlcLinkStackerProtocolHandler : IDeviceProtocolHandler { private readonly MirrorAckProtocolHandler _mirrorAckHandler; private readonly ProtocolMonitoringOptions _options; private const string ProtocolNameStatic = "PlcLinkStackerProtocol"; public PlcLinkStackerProtocolHandler( MirrorAckProtocolHandler mirrorAckHandler, IOptions options) { _mirrorAckHandler = mirrorAckHandler; _options = options.Value; } public string ProtocolName => "PlcLinkStackerProtocol"; public bool Process(IMemoryStore memoryStore, ProtocolTemplate template, ProtocolRuntimeState runtimeState) { // 先执行 PlcLink 堆垛机固定 DB 交互逻辑。 var changed = ApplyPlcLinkStackerOffsets(memoryStore, template, runtimeState); var ruleIds = _options.PlcLinkStackerRuleIds .Where(x => !string.IsNullOrWhiteSpace(x)) .Distinct(StringComparer.OrdinalIgnoreCase) .ToArray(); if (ruleIds.Length == 0) { return changed; } foreach (var ruleId in ruleIds) { var rule = _options.MirrorAckRules .FirstOrDefault(x => string.Equals(x.RuleId, ruleId, StringComparison.OrdinalIgnoreCase)); if (rule == null) { continue; } var stateKey = $"{ProtocolName}:{ruleId}"; changed |= _mirrorAckHandler.Process(memoryStore, template, runtimeState, rule, stateKey); } return changed; } /// /// PlcLink 堆垛机偏移交互规则: /// 1) 初始化(仅首次):DB910 offset6=1,offset20=1 /// 2) DB900 offset40=1:DB910 offset28=DB900 offset2,DB910 offset20=2 /// 3) DB900 offset40=2:DB910 offset28=0,DB910 offset20=1 /// private static bool ApplyPlcLinkStackerOffsets( IMemoryStore memoryStore, ProtocolTemplate template, ProtocolRuntimeState runtimeState) { const int db900 = 900; const int db910 = 910; bool changed = false; var db900Offset2 = ResolveField(template, db900, 2); var db900Offset40 = ResolveField(template, db900, 40); var db910Offset6 = ResolveField(template, db910, 6); var db910Offset20 = ResolveField(template, db910, 20); var db910Offset28 = ResolveField(template, db910, 28); var db910Offset42 = ResolveField(template, db910, 42); // 初始化只在实例启动后的第一次轮询写入一次,避免覆盖现场值。 var initKey = BuildInitStateKey(template); if (!runtimeState.LastWcsAckByKey.ContainsKey(initKey)) { changed |= TryWriteNumeric(memoryStore, db910Offset6, 1); changed |= TryWriteNumeric(memoryStore, db910Offset20, 1); runtimeState.LastWcsAckByKey[initKey] = 1; } var offset40 = TryReadNumeric(memoryStore, db900Offset40); if (offset40 == 1) { var offset2 = TryReadNumeric(memoryStore, db900Offset2); changed |= TryWriteNumeric(memoryStore, db910Offset28, offset2); changed |= TryWriteNumeric(memoryStore, db910Offset20, 2); } else if (offset40 == 2) { changed |= TryWriteNumeric(memoryStore, db910Offset28, 0); changed |= TryWriteNumeric(memoryStore, db910Offset20, 1); changed |= TryWriteNumeric(memoryStore, db900Offset40, 0); changed |= TryWriteNumeric(memoryStore, db910Offset42, 0); } return changed; } private static string BuildInitStateKey(ProtocolTemplate template) { var templateId = string.IsNullOrWhiteSpace(template.Id) ? "unknown" : template.Id; return $"{ProtocolNameStatic}:Init:{templateId}:DB910"; } /// /// 优先按「DB号+Offset」匹配模板字段;找不到则按 Offset 回退;仍找不到则使用默认 Byte 映射。 /// private static ProtocolFieldMapping ResolveField(ProtocolTemplate template, int dbNumber, int offset) { var byDb = template.Fields.FirstOrDefault(x => x.DbNumber == dbNumber && x.Offset == offset); if (byDb != null) { return byDb; } var byOffset = template.Fields.FirstOrDefault(x => x.Offset == offset); if (byOffset != null) { return byOffset; } return new ProtocolFieldMapping { DbNumber = dbNumber, Offset = offset, Bit = 0, DataType = ProtocolDataType.Byte }; } private static string BuildAddress(ProtocolFieldMapping field) { return field.DataType switch { ProtocolDataType.Bool => $"DB{field.DbNumber}.DBX{field.Offset}.{field.Bit}", ProtocolDataType.Int => $"DB{field.DbNumber}.DBW{field.Offset}", ProtocolDataType.DInt => $"DB{field.DbNumber}.DBD{field.Offset}", _ => $"DB{field.DbNumber}.DBB{field.Offset}" }; } private static int TryReadNumeric(IMemoryStore memoryStore, ProtocolFieldMapping field) { var address = BuildAddress(field); return field.DataType switch { ProtocolDataType.Bool => memoryStore.Read(address) ? 1 : 0, ProtocolDataType.Int => memoryStore.Read(address), ProtocolDataType.DInt => memoryStore.Read(address), ProtocolDataType.String => TryReadStringNumeric(memoryStore, field), _ => memoryStore.Read(address) }; } private static int TryReadStringNumeric(IMemoryStore memoryStore, ProtocolFieldMapping field) { var len = field.Length > 0 ? field.Length : 16; var bytes = memoryStore.ReadBytes(BuildAddress(field), (ushort)len); var text = System.Text.Encoding.ASCII.GetString(bytes).Trim('\0', ' '); return int.TryParse(text, out var value) ? value : 0; } private static bool TryWriteNumeric(IMemoryStore memoryStore, ProtocolFieldMapping field, int targetValue) { var address = BuildAddress(field); switch (field.DataType) { case ProtocolDataType.Bool: { var next = targetValue != 0; var current = memoryStore.Read(address); if (current == next) return false; memoryStore.Write(address, next); return true; } case ProtocolDataType.Int: { var next = (short)targetValue; var current = memoryStore.Read(address); if (current == next) return false; memoryStore.Write(address, next); return true; } case ProtocolDataType.DInt: { var current = memoryStore.Read(address); if (current == targetValue) return false; memoryStore.Write(address, targetValue); return true; } case ProtocolDataType.String: { var len = field.Length > 0 ? field.Length : 16; var nextText = targetValue.ToString(); var nextBytes = new byte[len]; var raw = System.Text.Encoding.ASCII.GetBytes(nextText); Array.Copy(raw, nextBytes, Math.Min(raw.Length, nextBytes.Length)); var currentBytes = memoryStore.ReadBytes(address, (ushort)len); if (currentBytes.SequenceEqual(nextBytes)) return false; memoryStore.WriteBytes(address, nextBytes); return true; } default: { var next = (byte)targetValue; var current = memoryStore.Read(address); if (current == next) return false; memoryStore.Write(address, next); return true; } } } }