using Microsoft.Extensions.Options;
using WIDESEAWCS_S7Simulator.Core.Interfaces;
using WIDESEAWCS_S7Simulator.Core.Protocol;
namespace WIDESEAWCS_S7Simulator.Application.Protocol;
///
/// PlcLink 堆垛机设备协议处理器。
///
public class PlcLinkStackerProtocolHandler : IDeviceProtocolHandler
{
private readonly MirrorAckProtocolHandler _mirrorAckHandler;
private readonly ProtocolMonitoringOptions _options;
private const string ProtocolNameStatic = "PlcLinkStackerProtocol";
public PlcLinkStackerProtocolHandler(
MirrorAckProtocolHandler mirrorAckHandler,
IOptions options)
{
_mirrorAckHandler = mirrorAckHandler;
_options = options.Value;
}
public string ProtocolName => "PlcLinkStackerProtocol";
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 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;
}
///
/// 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
///
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";
}
///
/// 优先按「DB号+Offset」匹配模板字段;找不到则按 Offset 回退;仍找不到则使用默认 Byte 映射。
///
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(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;
}
}
}
}