using Microsoft.Extensions.Options; using WIDESEAWCS_S7Simulator.Core.Interfaces; using WIDESEAWCS_S7Simulator.Core.Protocol; namespace WIDESEAWCS_S7Simulator.Application.Protocol; /// /// 堆垛机交互设备协议处理器。 /// 当前协议以位交互为主,具体镜像字段由配置驱动,避免写死在代码中。 /// public class StackerInteractionProtocolHandler : IDeviceProtocolHandler { private readonly MirrorAckProtocolHandler _mirrorAckHandler; private readonly ProtocolMonitoringOptions _options; public StackerInteractionProtocolHandler( MirrorAckProtocolHandler mirrorAckHandler, IOptions options) { _mirrorAckHandler = mirrorAckHandler; _options = options.Value; } public string ProtocolName => "StackerInteractionProtocol"; public bool Process(IMemoryStore memoryStore, ProtocolTemplate template, ProtocolRuntimeState runtimeState) { // 先执行堆垛机固定偏移逻辑(按用户协议要求)。 var changed = ApplyStackerOffsets(memoryStore, template, runtimeState); var ruleIds = _options.StackerInteractionRuleIds .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; } /// /// 按堆垛机交互协议对指定偏移进行读写。 /// 规则: /// 1) 初始化:offset2=5, offset4=1, offset96=1 /// 2) 当 offset192==1:offset24=1, offset4=2 /// 3) 当 offset192==2:offset24=0, offset4=1 /// private static bool ApplyStackerOffsets( IMemoryStore memoryStore, ProtocolTemplate template, ProtocolRuntimeState runtimeState) { var dbNumber = ResolveDbNumber(template); if (dbNumber <= 0) { return false; } bool changed = false; var initKey = BuildInitStateKey(template, dbNumber); var offset2Field = ResolveField(template, dbNumber, 2); var offset4Field = ResolveField(template, dbNumber, 4); var offset24Field = ResolveField(template, dbNumber, 24); var offset96Field = ResolveField(template, dbNumber, 96); var offset192Field = ResolveField(template, dbNumber, 192); var offset194Field = ResolveField(template, dbNumber, 194); var offset28Field = ResolveField(template, dbNumber, 28); // 初始化仅在实例启动后的第一次处理时写入一次,避免覆盖运行中值。 if (!runtimeState.LastWcsAckByKey.ContainsKey(initKey)) { changed |= TryWriteNumeric(memoryStore, offset2Field, 5); changed |= TryWriteNumeric(memoryStore, offset4Field, 1); changed |= TryWriteNumeric(memoryStore, offset96Field, 1); runtimeState.LastWcsAckByKey[initKey] = 1; } var offset192 = TryReadNumeric(memoryStore, offset192Field); if (offset192 == 1) { var offset194 = TryReadNumeric(memoryStore, offset194Field); changed |= TryWriteNumeric(memoryStore, offset24Field, offset194); changed |= TryWriteNumeric(memoryStore, offset4Field, 2); changed |= TryWriteNumeric(memoryStore, offset192Field, 0); } else if (offset192 == 2) { changed |= TryWriteNumeric(memoryStore, offset24Field, 0); changed |= TryWriteNumeric(memoryStore, offset4Field, 1); changed |= TryWriteNumeric(memoryStore, offset192Field, 0); changed |= TryWriteNumeric(memoryStore, offset28Field, 0); } return changed; } private static string BuildInitStateKey(ProtocolTemplate template, int dbNumber) { var templateId = string.IsNullOrWhiteSpace(template.Id) ? "unknown" : template.Id; return $"{ProtocolNameStatic}:Init:{templateId}:DB{dbNumber}"; } private const string ProtocolNameStatic = "StackerInteractionProtocol"; /// /// 优先从模板里找 offset192 对应 DB,找不到时退化为模板第一个字段 DB。 /// private static int ResolveDbNumber(ProtocolTemplate template) { var byOffset192 = template.Fields.FirstOrDefault(x => x.Offset == 192)?.DbNumber; if (byOffset192.HasValue && byOffset192.Value > 0) { return byOffset192.Value; } var firstDbNumber = template.Fields.FirstOrDefault()?.DbNumber; return firstDbNumber ?? 0; } private static ProtocolFieldMapping ResolveField(ProtocolTemplate template, int fallbackDbNumber, int offset) { var byDb = template.Fields.FirstOrDefault(x => x.Offset == offset && x.DbNumber == fallbackDbNumber); if (byDb != null) { return byDb; } var any = template.Fields.FirstOrDefault(x => x.Offset == offset); if (any != null) { return any; } return new ProtocolFieldMapping { DbNumber = fallbackDbNumber, 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; } } } }