139 lines
5.6 KiB
C#
139 lines
5.6 KiB
C#
using libplctag;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Driver.AbCip;
|
|
|
|
/// <summary>
|
|
/// Default libplctag-backed <see cref="IAbCipTagRuntime"/>. Wraps a <see cref="Tag"/>
|
|
/// instance + translates our <see cref="AbCipDataType"/> enum into the
|
|
/// <c>GetInt32</c> / <c>GetFloat32</c> / <c>GetString</c> / <c>GetBit</c> calls libplctag
|
|
/// exposes. One runtime instance per <c>(device, tag path)</c>; lifetime is owned by the
|
|
/// driver's per-device state dict.
|
|
/// </summary>
|
|
internal sealed class LibplctagTagRuntime : IAbCipTagRuntime
|
|
{
|
|
private readonly Tag _tag;
|
|
|
|
public LibplctagTagRuntime(AbCipTagCreateParams p)
|
|
{
|
|
_tag = new Tag
|
|
{
|
|
Gateway = p.Gateway,
|
|
Path = p.CipPath,
|
|
PlcType = MapPlcType(p.LibplctagPlcAttribute),
|
|
Protocol = Protocol.ab_eip,
|
|
Name = p.TagName,
|
|
Timeout = p.Timeout,
|
|
};
|
|
}
|
|
|
|
public Task InitializeAsync(CancellationToken cancellationToken) => _tag.InitializeAsync(cancellationToken);
|
|
public Task ReadAsync(CancellationToken cancellationToken) => _tag.ReadAsync(cancellationToken);
|
|
public Task WriteAsync(CancellationToken cancellationToken) => _tag.WriteAsync(cancellationToken);
|
|
|
|
public int GetStatus() => (int)_tag.GetStatus();
|
|
|
|
public object? DecodeValue(AbCipDataType type, int? bitIndex) => type switch
|
|
{
|
|
AbCipDataType.Bool => bitIndex is int bit
|
|
? _tag.GetBit(bit)
|
|
: _tag.GetInt8(0) != 0,
|
|
AbCipDataType.SInt => (int)(sbyte)_tag.GetInt8(0),
|
|
AbCipDataType.USInt => (int)_tag.GetUInt8(0),
|
|
AbCipDataType.Int => (int)_tag.GetInt16(0),
|
|
AbCipDataType.UInt => (int)_tag.GetUInt16(0),
|
|
AbCipDataType.DInt => _tag.GetInt32(0),
|
|
AbCipDataType.UDInt => (int)_tag.GetUInt32(0),
|
|
AbCipDataType.LInt => _tag.GetInt64(0),
|
|
AbCipDataType.ULInt => (long)_tag.GetUInt64(0),
|
|
AbCipDataType.Real => _tag.GetFloat32(0),
|
|
AbCipDataType.LReal => _tag.GetFloat64(0),
|
|
AbCipDataType.String => _tag.GetString(0),
|
|
AbCipDataType.Dt => _tag.GetInt32(0), // seconds-since-epoch DINT; consumer widens as needed
|
|
AbCipDataType.Structure => null, // UDT whole-tag decode lands in PR 6
|
|
_ => null,
|
|
};
|
|
|
|
public void EncodeValue(AbCipDataType type, int? bitIndex, object? value)
|
|
{
|
|
switch (type)
|
|
{
|
|
case AbCipDataType.Bool:
|
|
if (bitIndex is int)
|
|
{
|
|
// BOOL-within-DINT writes are routed at the driver level (AbCipDriver.
|
|
// WriteBitInDIntAsync) via a parallel parent-DINT runtime so the RMW stays
|
|
// serialised. If one reaches here it means the driver dispatch was bypassed —
|
|
// throw so the error surfaces loudly rather than clobbering the whole DINT.
|
|
throw new NotSupportedException(
|
|
"BOOL-with-bitIndex writes must go through AbCipDriver.WriteBitInDIntAsync, not LibplctagTagRuntime.");
|
|
}
|
|
_tag.SetInt8(0, Convert.ToBoolean(value) ? (sbyte)1 : (sbyte)0);
|
|
break;
|
|
case AbCipDataType.SInt:
|
|
_tag.SetInt8(0, Convert.ToSByte(value));
|
|
break;
|
|
case AbCipDataType.USInt:
|
|
_tag.SetUInt8(0, Convert.ToByte(value));
|
|
break;
|
|
case AbCipDataType.Int:
|
|
_tag.SetInt16(0, Convert.ToInt16(value));
|
|
break;
|
|
case AbCipDataType.UInt:
|
|
_tag.SetUInt16(0, Convert.ToUInt16(value));
|
|
break;
|
|
case AbCipDataType.DInt:
|
|
_tag.SetInt32(0, Convert.ToInt32(value));
|
|
break;
|
|
case AbCipDataType.UDInt:
|
|
_tag.SetUInt32(0, Convert.ToUInt32(value));
|
|
break;
|
|
case AbCipDataType.LInt:
|
|
_tag.SetInt64(0, Convert.ToInt64(value));
|
|
break;
|
|
case AbCipDataType.ULInt:
|
|
_tag.SetUInt64(0, Convert.ToUInt64(value));
|
|
break;
|
|
case AbCipDataType.Real:
|
|
_tag.SetFloat32(0, Convert.ToSingle(value));
|
|
break;
|
|
case AbCipDataType.LReal:
|
|
_tag.SetFloat64(0, Convert.ToDouble(value));
|
|
break;
|
|
case AbCipDataType.String:
|
|
_tag.SetString(0, Convert.ToString(value) ?? string.Empty);
|
|
break;
|
|
case AbCipDataType.Dt:
|
|
_tag.SetInt32(0, Convert.ToInt32(value));
|
|
break;
|
|
case AbCipDataType.Structure:
|
|
throw new NotSupportedException("Whole-UDT writes land in PR 6.");
|
|
default:
|
|
throw new NotSupportedException($"AbCipDataType {type} not writable.");
|
|
}
|
|
}
|
|
|
|
public void Dispose() => _tag.Dispose();
|
|
|
|
private static PlcType MapPlcType(string attribute) => attribute switch
|
|
{
|
|
"controllogix" => PlcType.ControlLogix,
|
|
"compactlogix" => PlcType.ControlLogix, // libplctag treats CompactLogix under ControlLogix family
|
|
"micro800" => PlcType.Micro800,
|
|
"micrologix" => PlcType.MicroLogix,
|
|
"slc500" => PlcType.Slc500,
|
|
"plc5" => PlcType.Plc5,
|
|
"omron-njnx" => PlcType.Omron,
|
|
_ => PlcType.ControlLogix,
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Default <see cref="IAbCipTagFactory"/> — creates a fresh <see cref="LibplctagTagRuntime"/>
|
|
/// per call. Stateless; safe to share across devices.
|
|
/// </summary>
|
|
internal sealed class LibplctagTagFactory : IAbCipTagFactory
|
|
{
|
|
public IAbCipTagRuntime Create(AbCipTagCreateParams createParams) =>
|
|
new LibplctagTagRuntime(createParams);
|
|
}
|