wanshenmean
2026-03-17 94ad631d316da04c46266ddb1fc6e63e6f8f2fae
Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Application/Protocol/Devices/PlcLinkStackerProtocolHandler.cs
@@ -1,16 +1,17 @@
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Options;
using WIDESEAWCS_S7Simulator.Core.Interfaces;
using WIDESEAWCS_S7Simulator.Core.Protocol;
namespace WIDESEAWCS_S7Simulator.Application.Protocol;
/// <summary>
/// 化成堆垛机设备协议处理器。
/// PlcLink 堆垛机设备协议处理器。
/// </summary>
public class PlcLinkStackerProtocolHandler : IDeviceProtocolHandler
{
    private readonly MirrorAckProtocolHandler _mirrorAckHandler;
    private readonly ProtocolMonitoringOptions _options;
    private const string ProtocolNameStatic = "PlcLinkStackerProtocol";
    public PlcLinkStackerProtocolHandler(
        MirrorAckProtocolHandler mirrorAckHandler,
@@ -24,16 +25,18 @@
    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 false;
            return changed;
        }
        bool changed = false;
        foreach (var ruleId in ruleIds)
        {
            var rule = _options.MirrorAckRules
@@ -43,9 +46,174 @@
                continue;
            }
            changed |= _mirrorAckHandler.Process(memoryStore, template, runtimeState, rule, $"{ProtocolName}:{ruleId}");
            var stateKey = $"{ProtocolName}:{ruleId}";
            changed |= _mirrorAckHandler.Process(memoryStore, template, runtimeState, rule, stateKey);
        }
        return changed;
    }
    /// <summary>
    /// 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
    /// </summary>
    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";
    }
    /// <summary>
    /// 优先按「DB号+Offset」匹配模板字段;找不到则按 Offset 回退;仍找不到则使用默认 Byte 映射。
    /// </summary>
    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<bool>(address) ? 1 : 0,
            ProtocolDataType.Int => memoryStore.Read<short>(address),
            ProtocolDataType.DInt => memoryStore.Read<int>(address),
            ProtocolDataType.String => TryReadStringNumeric(memoryStore, field),
            _ => memoryStore.Read<byte>(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<bool>(address);
                if (current == next) return false;
                memoryStore.Write(address, next);
                return true;
            }
            case ProtocolDataType.Int:
            {
                var next = (short)targetValue;
                var current = memoryStore.Read<short>(address);
                if (current == next) return false;
                memoryStore.Write(address, next);
                return true;
            }
            case ProtocolDataType.DInt:
            {
                var current = memoryStore.Read<int>(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<byte>(address);
                if (current == next) return false;
                memoryStore.Write(address, next);
                return true;
            }
        }
    }
}