Merge #867 Heidenhain integer leaves keep their declared type; #868 ConfigPublishCoordinator survives ConfigDb outage at start

Claude-Session: https://claude.ai/code/session_01GT67AD99zQQqpLCVm3p7xk
This commit is contained in:
Joseph Doherty
2026-11-11 12:29:00 -05:00
11 changed files with 720 additions and 41 deletions
@@ -256,14 +256,15 @@ internal sealed class HeidenhainSession : IAsyncDisposable
await PrepareTelemetryAsync(wire, ct).ConfigureAwait(false);
value = feature switch
{
"Execution" => Lsv2Data.U16(await _client.ReadRuntimeAsync(23, ct).ConfigureAwait(false)),
"ProgramState" => Lsv2Data.U16(await _client.ReadRuntimeAsync(26, ct).ConfigureAwait(false)),
// Each arm boxed explicitly so no numeric arm can widen another (lmxopcua#867).
"Execution" => (object)Lsv2Data.U16(await _client.ReadRuntimeAsync(23, ct).ConfigureAwait(false)),
"ProgramState" => (object)Lsv2Data.U16(await _client.ReadRuntimeAsync(26, ct).ConfigureAwait(false)),
"Program" => Lsv2Data.Program(await _client.ReadRuntimeAsync(24, ct).ConfigureAwait(false)),
"Overrides" => Lsv2Data.Overrides(await _client.ReadRuntimeAsync(25, ct).ConfigureAwait(false)),
"Axes" => Lsv2Data.Axes(await _client.ReadRuntimeAsync(22, ct).ConfigureAwait(false)),
"Tool" => Lsv2Data.Tool(await _client.ReadRuntimeAsync(51, ct).ConfigureAwait(false)),
HeidenhainCatalog.AlarmsFeature => await AlarmsAsync(ct).ConfigureAwait(false),
_ => await TimerAsync(feature, ct).ConfigureAwait(false),
_ => (object)await TimerAsync(feature, ct).ConfigureAwait(false),
};
}
@@ -444,24 +445,27 @@ internal sealed class HeidenhainSession : IAsyncDisposable
case double[] o when address.StartsWith("Overrides/", StringComparison.Ordinal):
return o[name switch { "Feed" => 0, "Spindle" => 1, _ => 2 }];
case Lsv2Axes a:
return name == "Names" ? a.Names : a.Positions;
return name == "Names" ? (object)a.Names : (object)a.Positions;
case Lsv2Tool t:
// lmxopcua#867: every arm is cast to object. Without it C# unifies long/int/double? to double?, so
// Tool/Number (Int64) and Tool/Index + Tool/AxisCode (Int32) were published as Double. A null
// geometry still boxes to null (BadNotSupported upstream).
return name switch
{
"Number" => t.Number,
"Index" => t.Index,
"AxisCode" => t.Axis,
"Length" => t.Length,
_ => t.Radius,
"Number" => (object)t.Number,
"Index" => (object)t.Index,
"AxisCode" => (object)t.Axis,
"Length" => (object?)t.Length,
_ => (object?)t.Radius,
};
case List<Lsv2Alarm> list:
return name switch
{
"Count" => list.Count,
"Messages" => list.Select(x => x.Message).ToArray(),
"Classes" => list.Select(x => x.ErrorClass).ToArray(),
"Groups" => list.Select(x => x.Group).ToArray(),
_ => list.Select(x => x.Number).ToArray(),
"Count" => (object)list.Count,
"Messages" => (object)list.Select(x => x.Message).ToArray(),
"Classes" => (object)list.Select(x => x.ErrorClass).ToArray(),
"Groups" => (object)list.Select(x => x.Group).ToArray(),
_ => (object)list.Select(x => x.Number).ToArray(),
};
case int i:
return i;