Merge branch 'worktree-agent-a6ace38a85443d6d6' into heidenhain-pylsv2-parity
This commit is contained in:
@@ -102,6 +102,8 @@ public static class HeidenhainCatalog
|
||||
}
|
||||
|
||||
Add("Controller", DriverDataType.String, false, HeidenhainPollGroup.Slow, IdentityFeature, "Model", "NcVersion", "PlcVersion", "Options");
|
||||
// Derived locally from the same R_VR reply (HeidenhainControlFamilies.Classify) — no extra wire request.
|
||||
Add("Controller", DriverDataType.String, false, HeidenhainPollGroup.Slow, IdentityFeature, "Family");
|
||||
Add("Status", DriverDataType.Int32, false, HeidenhainPollGroup.Status, "Execution", "ExecutionState");
|
||||
Add("Status", DriverDataType.Int32, false, HeidenhainPollGroup.Status, "ProgramState", "ProgramState");
|
||||
Add("Program", DriverDataType.String, false, HeidenhainPollGroup.Status, "Program", "Main", "Current");
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
namespace ZB.MOM.WW.OtOpcUa.Driver.Heidenhain;
|
||||
|
||||
/// <summary>
|
||||
/// The HEIDENHAIN control generation, classified from the <c>R_VR</c> model field. The published value of the
|
||||
/// <c>Controller/Family</c> leaf is the member name. Only <see cref="ITnc530"/> is hardware-proven; every other
|
||||
/// member follows pyLSV2 (MIT) and is hardware-unproven here (<c>docs/drivers/Heidenhain.md</c> §Control families).
|
||||
/// </summary>
|
||||
public enum HeidenhainControlFamily
|
||||
{
|
||||
/// <summary>A model string no rule recognises. Treated like a non-iTNC: no iTNC-only feature is attempted.</summary>
|
||||
Unknown,
|
||||
|
||||
/// <summary>iTNC 530 (pyLSV2 <c>ControlType.MILL_OLD</c>) — the only family proven on hardware.</summary>
|
||||
ITnc530,
|
||||
|
||||
/// <summary>TNC 640 / 620 / 320 / 128 (pyLSV2 <c>ControlType.MILL_NEW</c>).</summary>
|
||||
Tnc,
|
||||
|
||||
/// <summary>TNC7 (pyLSV2 <c>ControlType.TNC7</c>: "behaves just a bit different again").</summary>
|
||||
Tnc7,
|
||||
|
||||
/// <summary>CNC PILOT 640 / MANUALplus 620 lathe controls (pyLSV2 <c>ControlType.LATHE_NEW</c>).</summary>
|
||||
Lathe,
|
||||
|
||||
/// <summary>MillPlus (IT V600) — pyLSV2 <c>ControlType.MILLPLUS</c>, "not officially supported by any other Heidenhain tool".</summary>
|
||||
MillPlus,
|
||||
|
||||
/// <summary>
|
||||
/// Legacy TNC 426 / 430. NOT a pyLSV2 class (pyLSV2 reports these as <c>UNKNOWN</c>); named here only so the
|
||||
/// leaf is informative. It carries no gate of its own beyond "not an iTNC".
|
||||
/// </summary>
|
||||
LegacyTnc,
|
||||
}
|
||||
|
||||
/// <summary>The single authority that classifies a control and states which reference-gated features it has.</summary>
|
||||
public static class HeidenhainControlFamilies
|
||||
{
|
||||
// per pyLSV2 (MIT) dat_cls.py:VersionInfo.control setter — strip every space, upper-case, first matching
|
||||
// prefix wins. ITNC530 is tested before any TNC prefix; TNC7 cannot collide with TNC640/620/320/128.
|
||||
private static readonly (string Prefix, HeidenhainControlFamily Family)[] Rules =
|
||||
[
|
||||
("ITNC530", HeidenhainControlFamily.ITnc530),
|
||||
("TNC640", HeidenhainControlFamily.Tnc),
|
||||
("TNC620", HeidenhainControlFamily.Tnc),
|
||||
("TNC320", HeidenhainControlFamily.Tnc),
|
||||
("TNC128", HeidenhainControlFamily.Tnc),
|
||||
("MANUALPLUS", HeidenhainControlFamily.Lathe),
|
||||
("CNCPILOT640", HeidenhainControlFamily.Lathe),
|
||||
("TNC7", HeidenhainControlFamily.Tnc7),
|
||||
("MILLPLUS", HeidenhainControlFamily.MillPlus),
|
||||
// Not from pyLSV2 (it has no rule for these): legacy pre-iTNC milling controls.
|
||||
("TNC426", HeidenhainControlFamily.LegacyTnc),
|
||||
("TNC430", HeidenhainControlFamily.LegacyTnc),
|
||||
];
|
||||
|
||||
/// <summary>Classifies the <c>R_VR</c> model field (e.g. <c>iTNC530</c>, <c>TNC 640</c>, <c>CNCpilot640</c>).</summary>
|
||||
/// <param name="model">The model string as the control reported it; null or blank is <see cref="HeidenhainControlFamily.Unknown"/>.</param>
|
||||
/// <returns>The family.</returns>
|
||||
public static HeidenhainControlFamily Classify(string? model)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(model)) return HeidenhainControlFamily.Unknown;
|
||||
var normalized = model.Replace(" ", "", StringComparison.Ordinal).Trim().ToUpperInvariant();
|
||||
foreach (var (prefix, family) in Rules)
|
||||
if (normalized.StartsWith(prefix, StringComparison.Ordinal)) return family;
|
||||
return HeidenhainControlFamily.Unknown;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// True when <c>R_DP</c> data-path reads (and therefore the DATA login) may be attempted. Per pyLSV2 (MIT)
|
||||
/// client.py:read_data_path — "does not work on non iTNC controls" — this is the iTNC 530 alone; an
|
||||
/// <see cref="HeidenhainControlFamily.Unknown"/> control is refused too, so no unrecognised control ever
|
||||
/// receives a DATA login.
|
||||
/// </summary>
|
||||
/// <param name="family">The family.</param>
|
||||
/// <returns>Whether DATA is supported.</returns>
|
||||
public static bool SupportsDataPath(HeidenhainControlFamily family) => family == HeidenhainControlFamily.ITnc530;
|
||||
}
|
||||
@@ -151,6 +151,9 @@ internal sealed class HeidenhainDeviceRuntime : IAsyncDisposable
|
||||
/// <summary>The identity of the current session's control, when connected.</summary>
|
||||
public string? ControllerModel { get { lock (_publication) return _session?.Identity?.Model; } }
|
||||
|
||||
/// <summary>The control family classified from the current session's identity, or null.</summary>
|
||||
public HeidenhainControlFamily? ControllerFamily { get { lock (_publication) return _session?.Identity?.Family; } }
|
||||
|
||||
/// <summary>Starts the acquisition loop (idempotent).</summary>
|
||||
public void Start()
|
||||
{
|
||||
|
||||
@@ -417,6 +417,7 @@ public sealed class HeidenhainDriver : IDriver, IReadable, ISubscribable, ITagDi
|
||||
bag[$"{d.HostName}.state"] = d.State.ToString();
|
||||
bag[$"{d.HostName}.data"] = d.DataHealth;
|
||||
if (d.ControllerModel is { } m) bag[$"{d.HostName}.model"] = m;
|
||||
if (d.ControllerFamily is { } f) bag[$"{d.HostName}.family"] = f.ToString();
|
||||
}
|
||||
|
||||
// lmxopcua#839: controller alarms no ALARM: selector claims (shown by no condition) — current + since start.
|
||||
|
||||
@@ -73,7 +73,7 @@ public sealed class HeidenhainDriverProbe : IDriverProbe
|
||||
await client.LoginAsync("INSPECT", password, cts.Token).ConfigureAwait(false);
|
||||
var versions = Lsv2Data.Versions(await client.ReadVersionAsync(cts.Token).ConfigureAwait(false));
|
||||
sw.Stop();
|
||||
return new(true, $"LSV2 INSPECT session OK at {target}: {versions.Model} NC {versions.Nc}", sw.Elapsed);
|
||||
return new(true, $"LSV2 INSPECT session OK at {target}: {versions.Model} ({versions.Family}) NC {versions.Nc}", sw.Elapsed);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
|
||||
@@ -78,8 +78,9 @@ internal sealed class HeidenhainSession : IAsyncDisposable
|
||||
if (_settings.TelemetryEnabled && _settings.DncLoginEnabled) _dncStatus = await OptionalLoginAsync("DNC", ct).ConfigureAwait(false);
|
||||
if (_settings.DataEnabled && _settings.HasDataBindings)
|
||||
{
|
||||
// Reference-derived DATA support is iTNC only.
|
||||
_dataStatus = _identity.Model.Contains("itnc", StringComparison.OrdinalIgnoreCase)
|
||||
// DATA (R_DP) is iTNC 530 only — per pyLSV2 (MIT) client.py:read_data_path. Any other family, and an
|
||||
// unrecognised one, is Unsupported WITHOUT sending the DATA login.
|
||||
_dataStatus = HeidenhainControlFamilies.SupportsDataPath(_identity.Family)
|
||||
? await OptionalLoginAsync("DATA", ct).ConfigureAwait(false)
|
||||
: HeidenhainStatus.BadNotSupported;
|
||||
}
|
||||
@@ -439,7 +440,7 @@ internal sealed class HeidenhainSession : IAsyncDisposable
|
||||
switch (value)
|
||||
{
|
||||
case Lsv2Versions v:
|
||||
return name switch { "Model" => v.Model, "NcVersion" => v.Nc, "PlcVersion" => v.Plc, _ => v.Options };
|
||||
return name switch { "Model" => v.Model, "NcVersion" => v.Nc, "PlcVersion" => v.Plc, "Family" => v.Family.ToString(), _ => v.Options };
|
||||
case Lsv2Program p:
|
||||
return name switch { "Main" => p.Main, "Current" => p.Current, _ => (object)p.Line };
|
||||
case double[] o when address.StartsWith("Overrides/", StringComparison.Ordinal):
|
||||
|
||||
@@ -5,7 +5,11 @@ using System.Text;
|
||||
namespace ZB.MOM.WW.OtOpcUa.Driver.Heidenhain.Lsv2;
|
||||
|
||||
/// <summary>Controller identity from <c>R_VR</c>.</summary>
|
||||
public sealed record Lsv2Versions(string Model, string Nc, string Plc, string Options);
|
||||
public sealed record Lsv2Versions(string Model, string Nc, string Plc, string Options)
|
||||
{
|
||||
/// <summary>The control family derived from <see cref="Model"/> (computed, no backing field: equality and the #831 identity-change signal are unchanged).</summary>
|
||||
public HeidenhainControlFamily Family => HeidenhainControlFamilies.Classify(Model);
|
||||
}
|
||||
|
||||
/// <summary>Program stack from <c>R_RI 24</c>.</summary>
|
||||
public sealed record Lsv2Program(long Line, string Main, string Current);
|
||||
|
||||
Reference in New Issue
Block a user