@@ -36,6 +36,10 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery,
|
||||
new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly Dictionary<string, (string Host, string Slot, string Axis)> _offsetNodesByName =
|
||||
new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly Dictionary<string, string> _messagesNodesByName =
|
||||
new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly Dictionary<string, string> _currentBlockNodesByName =
|
||||
new(StringComparer.OrdinalIgnoreCase);
|
||||
private DriverHealth _health = new(DriverState.Unknown, null, null);
|
||||
|
||||
/// <summary>
|
||||
@@ -185,6 +189,14 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery,
|
||||
(device.Options.HostAddress, slot, axis);
|
||||
}
|
||||
}
|
||||
|
||||
// Messages/External/Latest + Program/CurrentBlock — single String nodes per
|
||||
// device backed by cnc_rdopmsg3 + cnc_rdactpt caches refreshed on the probe
|
||||
// tick (issue #261). Permissive across series (no capability gate yet).
|
||||
_messagesNodesByName[MessagesLatestReferenceFor(device.Options.HostAddress)] =
|
||||
device.Options.HostAddress;
|
||||
_currentBlockNodesByName[CurrentBlockReferenceFor(device.Options.HostAddress)] =
|
||||
device.Options.HostAddress;
|
||||
}
|
||||
|
||||
if (_options.Probe.Enabled)
|
||||
@@ -230,6 +242,8 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery,
|
||||
_overrideNodesByName.Clear();
|
||||
_toolingNodesByName.Clear();
|
||||
_offsetNodesByName.Clear();
|
||||
_messagesNodesByName.Clear();
|
||||
_currentBlockNodesByName.Clear();
|
||||
_health = new DriverHealth(DriverState.Unknown, _health.LastSuccessfulRead, null);
|
||||
}
|
||||
|
||||
@@ -299,6 +313,19 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery,
|
||||
continue;
|
||||
}
|
||||
|
||||
// Fixed-tree Messages/External/Latest + Program/CurrentBlock — served from
|
||||
// cnc_rdopmsg3 + cnc_rdactpt caches refreshed on the probe tick (issue #261).
|
||||
if (_messagesNodesByName.TryGetValue(reference, out var messagesHost))
|
||||
{
|
||||
results[i] = ReadMessagesLatestField(messagesHost, now);
|
||||
continue;
|
||||
}
|
||||
if (_currentBlockNodesByName.TryGetValue(reference, out var blockHost))
|
||||
{
|
||||
results[i] = ReadCurrentBlockField(blockHost, now);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!_tagsByName.TryGetValue(reference, out var def))
|
||||
{
|
||||
results[i] = new DataValueSnapshot(null, FocasStatusMapper.BadNodeIdUnknown, null, now);
|
||||
@@ -541,6 +568,37 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fixed-tree Messages/External/Latest — single String node per device backed
|
||||
// by cnc_rdopmsg3 across the four FANUC operator-message classes (issue #261).
|
||||
// The issue body permits this minimal "latest message" surface in the first
|
||||
// cut over a full ring-buffer of all four slots.
|
||||
var messagesFolder = deviceFolder.Folder("Messages", "Messages");
|
||||
var externalFolder = messagesFolder.Folder("External", "External");
|
||||
var messagesRef = MessagesLatestReferenceFor(device.HostAddress);
|
||||
externalFolder.Variable("Latest", "Latest", new DriverAttributeInfo(
|
||||
FullName: messagesRef,
|
||||
DriverDataType: DriverDataType.String,
|
||||
IsArray: false,
|
||||
ArrayDim: null,
|
||||
SecurityClass: SecurityClassification.ViewOnly,
|
||||
IsHistorized: false,
|
||||
IsAlarm: false,
|
||||
WriteIdempotent: false));
|
||||
|
||||
// Fixed-tree Program/CurrentBlock — single String node per device backed by
|
||||
// cnc_rdactpt (issue #261). Trim-stable round-trip per the issue body.
|
||||
var programFolder = deviceFolder.Folder("Program", "Program");
|
||||
var blockRef = CurrentBlockReferenceFor(device.HostAddress);
|
||||
programFolder.Variable("CurrentBlock", "CurrentBlock", new DriverAttributeInfo(
|
||||
FullName: blockRef,
|
||||
DriverDataType: DriverDataType.String,
|
||||
IsArray: false,
|
||||
ArrayDim: null,
|
||||
SecurityClass: SecurityClassification.ViewOnly,
|
||||
IsHistorized: false,
|
||||
IsAlarm: false,
|
||||
WriteIdempotent: false));
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
@@ -563,6 +621,12 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery,
|
||||
private static string OffsetReferenceFor(string hostAddress, string slot, string axis) =>
|
||||
$"{hostAddress}::Offsets/{slot}/{axis}";
|
||||
|
||||
private static string MessagesLatestReferenceFor(string hostAddress) =>
|
||||
$"{hostAddress}::Messages/External/Latest";
|
||||
|
||||
private static string CurrentBlockReferenceFor(string hostAddress) =>
|
||||
$"{hostAddress}::Program/CurrentBlock";
|
||||
|
||||
private static ushort? OverrideParamFor(FocasOverrideParameters p, string field) => field switch
|
||||
{
|
||||
"Feed" => p.FeedParam,
|
||||
@@ -700,6 +764,23 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery,
|
||||
state.LastWorkOffsetsUtc = DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
|
||||
// Operator messages + currently-executing block — same best-effort
|
||||
// policy as the other fixed-tree caches (issue #261). A null result
|
||||
// leaves the previous good snapshot in place so reads keep serving
|
||||
// until the next successful refresh.
|
||||
var messages = await client.GetOperatorMessagesAsync(ct).ConfigureAwait(false);
|
||||
if (messages is not null)
|
||||
{
|
||||
state.LastMessages = messages;
|
||||
state.LastMessagesUtc = DateTime.UtcNow;
|
||||
}
|
||||
var block = await client.GetCurrentBlockAsync(ct).ConfigureAwait(false);
|
||||
if (block is not null)
|
||||
{
|
||||
state.LastCurrentBlock = block;
|
||||
state.LastCurrentBlockUtc = DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException) when (ct.IsCancellationRequested) { break; }
|
||||
@@ -801,6 +882,32 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery,
|
||||
device.LastWorkOffsetsUtc, now);
|
||||
}
|
||||
|
||||
private DataValueSnapshot ReadMessagesLatestField(string hostAddress, DateTime now)
|
||||
{
|
||||
if (!_devices.TryGetValue(hostAddress, out var device))
|
||||
return new DataValueSnapshot(null, FocasStatusMapper.BadNodeIdUnknown, null, now);
|
||||
if (device.LastMessages is not { } snap)
|
||||
return new DataValueSnapshot(null, FocasStatusMapper.BadCommunicationError, null, now);
|
||||
// Snapshot is the trimmed list of active classes. "Latest" surfaces the last
|
||||
// (most-recent) entry — the issue body permits this minimal "latest message"
|
||||
// surface in lieu of a full ring buffer of all 4 classes.
|
||||
var latest = snap.Messages.Count == 0
|
||||
? string.Empty
|
||||
: snap.Messages[snap.Messages.Count - 1].Text;
|
||||
return new DataValueSnapshot(latest, FocasStatusMapper.Good,
|
||||
device.LastMessagesUtc, now);
|
||||
}
|
||||
|
||||
private DataValueSnapshot ReadCurrentBlockField(string hostAddress, DateTime now)
|
||||
{
|
||||
if (!_devices.TryGetValue(hostAddress, out var device))
|
||||
return new DataValueSnapshot(null, FocasStatusMapper.BadNodeIdUnknown, null, now);
|
||||
if (device.LastCurrentBlock is not { } snap)
|
||||
return new DataValueSnapshot(null, FocasStatusMapper.BadCommunicationError, null, now);
|
||||
return new DataValueSnapshot(snap.Text, FocasStatusMapper.Good,
|
||||
device.LastCurrentBlockUtc, now);
|
||||
}
|
||||
|
||||
private void TransitionDeviceState(DeviceState state, HostState newState)
|
||||
{
|
||||
HostState old;
|
||||
@@ -905,6 +1012,22 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery,
|
||||
public FocasWorkOffsetsInfo? LastWorkOffsets { get; set; }
|
||||
public DateTime LastWorkOffsetsUtc { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Cached <c>cnc_rdopmsg3</c> snapshot — active operator messages across
|
||||
/// the four FANUC classes — refreshed on every probe tick. Reads of
|
||||
/// <c>Messages/External/Latest</c> serve from this cache (issue #261).
|
||||
/// </summary>
|
||||
public FocasOperatorMessagesInfo? LastMessages { get; set; }
|
||||
public DateTime LastMessagesUtc { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Cached <c>cnc_rdactpt</c> snapshot — currently-executing block text —
|
||||
/// refreshed on every probe tick. Reads of <c>Program/CurrentBlock</c>
|
||||
/// serve from this cache (issue #261).
|
||||
/// </summary>
|
||||
public FocasCurrentBlockInfo? LastCurrentBlock { get; set; }
|
||||
public DateTime LastCurrentBlockUtc { get; set; }
|
||||
|
||||
public void DisposeClient()
|
||||
{
|
||||
Client?.Dispose();
|
||||
|
||||
Reference in New Issue
Block a user