fix(mqtt): Sparkplug array and unsupported-type metrics are not bindable from the browser (R06:M-5)
The ingestor refuses array metrics and unmapped types, yet DescribeMetric still stated their binding tuple, so a browse-commit produced a tag stuck at BadWaitingForInitialData forever. Those leaves now state no address (the commit refuses them) and say why in the descriptor line. Refs #744 Claude-Session: https://claude.ai/code/session_01Ub9hGmYLQvn9XETzw7CMsT
This commit is contained in:
@@ -249,7 +249,10 @@ One subscription (`spBv1.0/{GroupId}/#`) feeds a per-edge-node state machine
|
|||||||
[Structured metrics](#structured-metrics-template--dataset--propertyset).
|
[Structured metrics](#structured-metrics-template--dataset--propertyset).
|
||||||
- **Unsupported datatypes are skipped with a warning, never guessed**: `PropertySetList`, `Unknown`,
|
- **Unsupported datatypes are skipped with a warning, never guessed**: `PropertySetList`, `Unknown`,
|
||||||
extension values, and any `Template`/`DataSet` that cannot be flattened honestly. `Int8`→`Int16` and
|
extension values, and any `Template`/`DataSet` that cannot be flattened honestly. `Int8`→`Int16` and
|
||||||
`UInt8`→`UInt16` widen; `*Array` variants map to the element type with the array bit set.
|
`UInt8`→`UInt16` widen; `*Array` variants map to the element type with the array bit set — but
|
||||||
|
**array metrics are not read in v1** (warned once, never fed), so the browse picker offers them, and any
|
||||||
|
metric whose type is unsupported, **without an address**: the browse-commit refuses them rather than
|
||||||
|
committing a tag that waits for initial data forever (R06:M-5).
|
||||||
- **Ingest state is reset at every session/authoring seam** — a reconnect or a `ReinitializeAsync`
|
- **Ingest state is reset at every session/authoring seam** — a reconnect or a `ReinitializeAsync`
|
||||||
must not carry a previous session's alias table into a new one.
|
must not carry a previous session's alias table into a new one.
|
||||||
|
|
||||||
|
|||||||
@@ -441,16 +441,25 @@ internal sealed class MqttBrowseSession : IRebirthCapableBrowseSession
|
|||||||
var mapped = metric.DataType?.ToDriverDataType();
|
var mapped = metric.DataType?.ToDriverDataType();
|
||||||
var sparkplugName = metric.DataType?.ToString() ?? nameof(SparkplugDataType.Unknown);
|
var sparkplugName = metric.DataType?.ToString() ?? nameof(SparkplugDataType.Unknown);
|
||||||
|
|
||||||
|
var isArray = metric.DataType?.IsSparkplugArray() ?? false;
|
||||||
|
|
||||||
var descriptor = new StringBuilder(metric.BirthType.ToString()).Append(" · ").Append(sparkplugName);
|
var descriptor = new StringBuilder(metric.BirthType.ToString()).Append(" · ").Append(sparkplugName);
|
||||||
if (metric.Alias is { } alias) descriptor.Append(" · alias ").Append(alias);
|
if (metric.Alias is { } alias) descriptor.Append(" · alias ").Append(alias);
|
||||||
if (mapped is null) descriptor.Append(" · unsupported in v1 — not bindable");
|
if (mapped is null) descriptor.Append(" · unsupported in v1 — not bindable");
|
||||||
|
else if (isArray) descriptor.Append(" · array metrics are not read in v1 — not bindable");
|
||||||
|
|
||||||
|
// Unbindable = the ingestor refuses it: an unmapped type, or ANY array variant (SparkplugIngestor
|
||||||
|
// gates on the array bit and never feeds the tag). Such a leaf states NO address, so the
|
||||||
|
// browse-commit refuses it at commit instead of writing a tag that sits at
|
||||||
|
// BadWaitingForInitialData forever (R06:M-5). Labelling it alone was not enough — it still bound.
|
||||||
|
var bindable = mapped is not null && !isArray;
|
||||||
|
|
||||||
return new AttributeInfo(
|
return new AttributeInfo(
|
||||||
metric.Name,
|
metric.Name,
|
||||||
mapped?.ToString() ?? sparkplugName,
|
mapped?.ToString() ?? sparkplugName,
|
||||||
metric.DataType?.IsSparkplugArray() ?? false,
|
isArray,
|
||||||
descriptor.ToString(),
|
descriptor.ToString(),
|
||||||
AddressFields: BuildMetricAddressFields(metric.Name, scope));
|
AddressFields: bindable ? BuildMetricAddressFields(metric.Name, scope) : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -779,6 +779,13 @@ public sealed class MqttBrowseSessionTests
|
|||||||
"Plant1/EdgeA::Profile", TestContext.Current.CancellationToken)).ShouldHaveSingleItem();
|
"Plant1/EdgeA::Profile", TestContext.Current.CancellationToken)).ShouldHaveSingleItem();
|
||||||
a.DriverDataType.ShouldBe(nameof(Core.Abstractions.DriverDataType.Int32)); // element type
|
a.DriverDataType.ShouldBe(nameof(Core.Abstractions.DriverDataType.Int32)); // element type
|
||||||
a.IsArray.ShouldBeTrue();
|
a.IsArray.ShouldBeTrue();
|
||||||
|
|
||||||
|
// R06:M-5: the ingestor refuses array metrics ("not supported in v1"), so the browser must not
|
||||||
|
// offer one as bindable — no stated address means the browse-commit refuses it in words, instead
|
||||||
|
// of committing a tag that sits at BadWaitingForInitialData forever. The scalar control is
|
||||||
|
// SparkplugAttributes_StateTheBindingTuple_SoTheCommitNeverParsesTheNodeId.
|
||||||
|
a.AddressFields.ShouldBeNull();
|
||||||
|
a.SecurityClass.ShouldContain("not bindable");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -796,6 +803,7 @@ public sealed class MqttBrowseSessionTests
|
|||||||
Enum.TryParse<Core.Abstractions.DriverDataType>(a.DriverDataType, ignoreCase: true, out _)
|
Enum.TryParse<Core.Abstractions.DriverDataType>(a.DriverDataType, ignoreCase: true, out _)
|
||||||
.ShouldBeFalse(); // not a guessed type — unmappable, and visibly so
|
.ShouldBeFalse(); // not a guessed type — unmappable, and visibly so
|
||||||
a.SecurityClass.ShouldContain("unsupported");
|
a.SecurityClass.ShouldContain("unsupported");
|
||||||
|
a.AddressFields.ShouldBeNull(); // R06:M-5 — labelled AND unbindable, not just labelled
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|||||||
Reference in New Issue
Block a user