using WIDESEAWCS_S7Simulator.Core.Interfaces;
|
using WIDESEAWCS_S7Simulator.Core.Protocol;
|
|
namespace WIDESEAWCS_S7Simulator.Application.Protocol;
|
|
/// <summary>
|
/// 通用 ACK 镜像处理器。
|
/// 处理行为由 <see cref="MirrorAckRuleOptions"/> 和协议模板字段映射共同决定。
|
/// </summary>
|
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<string, ProtocolFieldMapping> 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<string, ProtocolFieldMapping> fields,
|
IEnumerable<string> 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<string, ProtocolFieldMapping> 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<byte>(BuildAddress(field));
|
|
private static short ReadInt(IMemoryStore memoryStore, ProtocolFieldMapping field) =>
|
memoryStore.Read<short>(BuildAddress(field));
|
|
private static int ReadDInt(IMemoryStore memoryStore, ProtocolFieldMapping field) =>
|
memoryStore.Read<int>(BuildAddress(field));
|
|
private static bool ReadBool(IMemoryStore memoryStore, ProtocolFieldMapping field) =>
|
memoryStore.Read<bool>(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);
|
}
|
}
|