wanshenmean
2026-03-19 cde6ad77663a80d78d77568428a6287b53347716
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
using Microsoft.Extensions.Options;
using WIDESEAWCS_S7Simulator.Core.Interfaces;
using WIDESEAWCS_S7Simulator.Core.Protocol;
 
namespace WIDESEAWCS_S7Simulator.Application.Protocol;
 
/// <summary>
/// 堆垛机交互设备协议处理器。
/// 当前协议以位交互为主,具体镜像字段由配置驱动,避免写死在代码中。
/// </summary>
public class StackerInteractionProtocolHandler : IDeviceProtocolHandler
{
    private readonly MirrorAckProtocolHandler _mirrorAckHandler;
    private readonly ProtocolMonitoringOptions _options;
 
    public StackerInteractionProtocolHandler(
        MirrorAckProtocolHandler mirrorAckHandler,
        IOptions<ProtocolMonitoringOptions> 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;
    }
 
    /// <summary>
    /// 按堆垛机交互协议对指定偏移进行读写。
    /// 规则:
    /// 1) 初始化:offset2=5, offset4=1, offset96=1
    /// 2) 当 offset192==1:offset24=1, offset4=2
    /// 3) 当 offset192==2:offset24=0, offset4=1
    /// </summary>
    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";
 
    /// <summary>
    /// 优先从模板里找 offset192 对应 DB,找不到时退化为模板第一个字段 DB。
    /// </summary>
    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<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;
            }
        }
    }
}