fix(drivers): R5 minors - MTConnect session publish+generation atomic and superseded pump joined; MQTT birth-typed writes use the birth's own datatype; AbLegacy dead init join removed + Dispose join test; docs (#602)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
@@ -102,6 +102,14 @@ public static class MqttWriteEncoder
|
||||
/// <see cref="MqttTagDefinition.DataType"/>, which for an unauthored tag is only the record's
|
||||
/// default and would command the metric with the wrong wire type. Ignored in Plain mode.
|
||||
/// </param>
|
||||
/// <param name="birthDataType">
|
||||
/// Sparkplug only: the metric's OWN birth-declared <see cref="SparkplugDataType"/>, used when the tag
|
||||
/// authored no type. Preferred over <paramref name="effectiveDataType"/> because the
|
||||
/// <c>ToDriverDataType</c> map is lossy (Int8→Int16, Uint8→Uint16, Text/Uuid/Bytes/File→String,
|
||||
/// <c>*Array</c>→scalar), so commanding through it sends the wrong Tahu datatype. A birth type the
|
||||
/// encoder cannot represent (arrays, DataSet, Template, …) is REFUSED as
|
||||
/// <see cref="MqttWriteEncodeError.UnsupportedDataType"/> — a wrong type is never sent.
|
||||
/// </param>
|
||||
/// <returns>The encode outcome.</returns>
|
||||
/// <remarks>
|
||||
/// <b>Retain <see langword="false"/> always; QoS 1 for Plain, 0 for Sparkplug commands.</b> Retain is the one that matters: a retained
|
||||
@@ -115,13 +123,14 @@ public static class MqttWriteEncoder
|
||||
MqttTagDefinition def,
|
||||
object? value,
|
||||
out MqttEncodedWrite encoded,
|
||||
DriverDataType? effectiveDataType = null)
|
||||
DriverDataType? effectiveDataType = null,
|
||||
SparkplugDataType? birthDataType = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(def);
|
||||
encoded = default;
|
||||
|
||||
return mode == MqttMode.SparkplugB
|
||||
? TryEncodeSparkplug(def, value, effectiveDataType, out encoded)
|
||||
? TryEncodeSparkplug(def, value, effectiveDataType, birthDataType, out encoded)
|
||||
: TryEncodePlain(def, value, out encoded);
|
||||
}
|
||||
|
||||
@@ -195,6 +204,7 @@ public static class MqttWriteEncoder
|
||||
MqttTagDefinition def,
|
||||
object? value,
|
||||
DriverDataType? effectiveDataType,
|
||||
SparkplugDataType? birthDataType,
|
||||
out MqttEncodedWrite encoded)
|
||||
{
|
||||
encoded = default;
|
||||
@@ -226,7 +236,9 @@ public static class MqttWriteEncoder
|
||||
}
|
||||
|
||||
var metric = new Payload.Types.Metric { Name = metricName };
|
||||
var error = ApplyValue(metric, def.DataTypeAuthored ? def.DataType : effectiveDataType ?? def.DataType, value);
|
||||
var error = !def.DataTypeAuthored && birthDataType is { } birth
|
||||
? ApplyBirthValue(metric, birth, value)
|
||||
: ApplyValue(metric, def.DataTypeAuthored ? def.DataType : effectiveDataType ?? def.DataType, value);
|
||||
if (error != MqttWriteEncodeError.None)
|
||||
{
|
||||
return error;
|
||||
@@ -251,6 +263,63 @@ public static class MqttWriteEncoder
|
||||
return MqttWriteEncodeError.None;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the metric's value field and <c>Datatype</c> from the metric's own birth-declared Sparkplug
|
||||
/// type, for the types whose driver-type mapping is lossy. Types with a 1:1 driver mapping delegate
|
||||
/// to <see cref="ApplyValue"/>.
|
||||
/// </summary>
|
||||
/// <param name="metric">The metric being built.</param>
|
||||
/// <param name="birth">The birth certificate's declared datatype.</param>
|
||||
/// <param name="value">The value to convert.</param>
|
||||
/// <returns>The encode outcome.</returns>
|
||||
private static MqttWriteEncodeError ApplyBirthValue(Payload.Types.Metric metric, SparkplugDataType birth, object value)
|
||||
{
|
||||
try
|
||||
{
|
||||
switch (birth)
|
||||
{
|
||||
case TahuDataType.Int8:
|
||||
metric.Datatype = (uint)TahuDataType.Int8;
|
||||
metric.IntValue = unchecked((uint)Convert.ToSByte(value, CultureInfo.InvariantCulture));
|
||||
return MqttWriteEncodeError.None;
|
||||
|
||||
case TahuDataType.Uint8:
|
||||
metric.Datatype = (uint)TahuDataType.Uint8;
|
||||
metric.IntValue = Convert.ToByte(value, CultureInfo.InvariantCulture);
|
||||
return MqttWriteEncodeError.None;
|
||||
|
||||
case TahuDataType.Text:
|
||||
case TahuDataType.Uuid:
|
||||
metric.Datatype = (uint)birth;
|
||||
metric.StringValue = value as string ?? Convert.ToString(value, CultureInfo.InvariantCulture) ?? "";
|
||||
return MqttWriteEncodeError.None;
|
||||
|
||||
case TahuDataType.Bytes:
|
||||
case TahuDataType.File:
|
||||
if (value is not byte[] bytes)
|
||||
{
|
||||
return MqttWriteEncodeError.ValueNotRepresentable;
|
||||
}
|
||||
|
||||
metric.Datatype = (uint)birth;
|
||||
metric.BytesValue = ByteString.CopyFrom(bytes);
|
||||
return MqttWriteEncodeError.None;
|
||||
}
|
||||
}
|
||||
catch (Exception ex) when (ex is InvalidCastException or FormatException or OverflowException)
|
||||
{
|
||||
return MqttWriteEncodeError.ValueNotRepresentable;
|
||||
}
|
||||
|
||||
// Arrays, DataSet, Template, PropertySet, Unknown: refused — never sent as a scalar of another type.
|
||||
if (birth.IsSparkplugArray() || birth.ToDriverDataType() is null)
|
||||
{
|
||||
return MqttWriteEncodeError.UnsupportedDataType;
|
||||
}
|
||||
|
||||
return ApplyValue(metric, birth.ToDriverDataType()!.Value, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the metric's typed value field and its <c>Datatype</c> from the tag's declared type.
|
||||
/// </summary>
|
||||
|
||||
Reference in New Issue
Block a user