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