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);
}
}