using WIDESEAWCS_S7Simulator.Core.Interfaces; using WIDESEAWCS_S7Simulator.Core.Protocol; namespace WIDESEAWCS_S7Simulator.Application.Protocol; /// /// 通用 ACK 镜像处理器。 /// 处理行为由 和协议模板字段映射共同决定。 /// public class MirrorAckProtocolHandler { public bool Process( IMemoryStore memoryStore, ProtocolTemplate template, ProtocolRuntimeState runtimeState, MirrorAckRuleOptions rule, string? stateKey = null) { if (memoryStore == null) throw new ArgumentNullException(nameof(memoryStore)); if (template == null) throw new ArgumentNullException(nameof(template)); if (runtimeState == null) throw new ArgumentNullException(nameof(runtimeState)); if (rule == null) throw new ArgumentNullException(nameof(rule)); var fields = template.Fields.ToDictionary(f => f.FieldKey, StringComparer.OrdinalIgnoreCase); if (!fields.TryGetValue(rule.WcsAckFieldKey, out var wcsAckField)) { return false; } var key = BuildStateKey(rule, stateKey); var ack = ReadAsInt(memoryStore, wcsAckField); if (TryGetLastAck(runtimeState, key, out var lastAck) && lastAck == ack) { return false; } SetLastAck(runtimeState, key, ack); bool changed = false; // ACK=1:按配置把 WCS 字段镜像回 PLC 字段,并复位 STB。 if (ack == 1) { changed |= TryMirrorField(memoryStore, fields, rule.WcsTaskIdFieldKey, rule.PlcTaskIdFieldKey); changed |= TryMirrorField(memoryStore, fields, rule.WcsTargetIdFieldKey, rule.PlcTargetIdFieldKey); changed |= TryWriteZero(memoryStore, fields, rule.PlcStbFieldKey); } // ACK=2:复位 STB,并清理 ACK=2 对应的配置字段。 else if (ack == 2) { changed |= TryWriteZero(memoryStore, fields, rule.PlcStbFieldKey); changed |= ClearFields(memoryStore, fields, rule.ClearFieldKeysOnAck2); } // ACK=0:清理 ACK=0 对应的配置字段,并复位 STB。 else if (ack == 0) { changed |= ClearFields(memoryStore, fields, rule.ClearFieldKeysOnAck0); changed |= TryWriteZero(memoryStore, fields, rule.PlcStbFieldKey); } return changed; } private static string BuildStateKey(MirrorAckRuleOptions rule, string? stateKey) { if (!string.IsNullOrWhiteSpace(stateKey)) { return stateKey; } if (!string.IsNullOrWhiteSpace(rule.RuleId)) { return rule.RuleId; } return rule.WcsAckFieldKey; } private static bool TryGetLastAck(ProtocolRuntimeState runtimeState, string key, out int ack) { return runtimeState.LastWcsAckByKey.TryGetValue(key, out ack); } private static void SetLastAck(ProtocolRuntimeState runtimeState, string key, int ack) { runtimeState.LastWcsAckByKey[key] = ack; runtimeState.LastWcsAck = ack; } private static bool TryMirrorField( IMemoryStore memoryStore, Dictionary fields, string? fromKey, string? toKey) { if (string.IsNullOrWhiteSpace(fromKey) || string.IsNullOrWhiteSpace(toKey)) { return false; } if (!fields.TryGetValue(fromKey, out var fromField) || !fields.TryGetValue(toKey, out var toField)) { return false; } // 以目标字段类型为准写入,确保与 PLC 地址定义一致。 switch (toField.DataType) { case ProtocolDataType.Bool: WriteBool(memoryStore, toField, ReadAsInt(memoryStore, fromField) != 0); return true; case ProtocolDataType.Int: WriteInt(memoryStore, toField, (short)ReadAsInt(memoryStore, fromField)); return true; case ProtocolDataType.DInt: WriteDInt(memoryStore, toField, ReadAsInt(memoryStore, fromField)); return true; default: WriteByte(memoryStore, toField, (byte)ReadAsInt(memoryStore, fromField)); return true; } } private static bool ClearFields( IMemoryStore memoryStore, Dictionary fields, IEnumerable fieldKeys) { bool changed = false; foreach (var key in fieldKeys) { if (fields.TryGetValue(key, out var field)) { WriteZero(memoryStore, field); changed = true; } } return changed; } private static bool TryWriteZero( IMemoryStore memoryStore, Dictionary fields, string fieldKey) { if (string.IsNullOrWhiteSpace(fieldKey) || !fields.TryGetValue(fieldKey, out var field)) { return false; } WriteZero(memoryStore, field); return true; } private static int ReadAsInt(IMemoryStore memoryStore, ProtocolFieldMapping field) { return field.DataType switch { ProtocolDataType.Bool => ReadBool(memoryStore, field) ? 1 : 0, ProtocolDataType.Int => ReadInt(memoryStore, field), ProtocolDataType.DInt => ReadDInt(memoryStore, field), _ => ReadByte(memoryStore, field) }; } private static void WriteZero(IMemoryStore memoryStore, ProtocolFieldMapping field) { switch (field.DataType) { case ProtocolDataType.Bool: WriteBool(memoryStore, field, false); break; case ProtocolDataType.Int: WriteInt(memoryStore, field, 0); break; case ProtocolDataType.DInt: WriteDInt(memoryStore, field, 0); break; case ProtocolDataType.String: { var len = field.Length > 0 ? field.Length : 32; WriteString(memoryStore, field, new string('\0', len), len); break; } default: WriteByte(memoryStore, field, 0); break; } } 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 byte ReadByte(IMemoryStore memoryStore, ProtocolFieldMapping field) => memoryStore.Read(BuildAddress(field)); private static short ReadInt(IMemoryStore memoryStore, ProtocolFieldMapping field) => memoryStore.Read(BuildAddress(field)); private static int ReadDInt(IMemoryStore memoryStore, ProtocolFieldMapping field) => memoryStore.Read(BuildAddress(field)); private static bool ReadBool(IMemoryStore memoryStore, ProtocolFieldMapping field) => memoryStore.Read(BuildAddress(field)); private static void WriteByte(IMemoryStore memoryStore, ProtocolFieldMapping field, byte value) => memoryStore.Write(BuildAddress(field), value); private static void WriteInt(IMemoryStore memoryStore, ProtocolFieldMapping field, short value) => memoryStore.Write(BuildAddress(field), value); private static void WriteDInt(IMemoryStore memoryStore, ProtocolFieldMapping field, int value) => memoryStore.Write(BuildAddress(field), value); private static void WriteBool(IMemoryStore memoryStore, ProtocolFieldMapping field, bool value) => memoryStore.Write(BuildAddress(field), value); private static void WriteString(IMemoryStore memoryStore, ProtocolFieldMapping field, string value, int length) { var bytes = System.Text.Encoding.ASCII.GetBytes(value); if (bytes.Length > length) { bytes = bytes.Take(length).ToArray(); } var buffer = new byte[length]; Array.Copy(bytes, buffer, bytes.Length); memoryStore.WriteBytes(BuildAddress(field), buffer); } }