feat(mtconnect): one agent client per driver — a second /raw Device is a deploy error (#628)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
@@ -41,6 +41,7 @@ public static class DraftValidator
|
||||
ValidateEquipReferenceResolution(draft, errors);
|
||||
ValidateCalculationTags(draft, errors);
|
||||
ValidateSqlConnectionStringNotPersisted(draft, errors);
|
||||
ValidateMTConnectSingleDevice(draft, errors);
|
||||
ValidateNodeAclScopes(draft, errors);
|
||||
return errors;
|
||||
}
|
||||
@@ -343,6 +344,42 @@ public static class DraftValidator
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Error code for an MTConnect driver instance that has more than one <c>/raw</c> Device (#628).</summary>
|
||||
public const string MTConnectMultipleDevicesCode = "MTConnectMultipleDevices";
|
||||
|
||||
/// <summary>
|
||||
/// The one message both the deploy gate and the authoring-time guard (<c>RawTreeService.CreateDeviceAsync</c>)
|
||||
/// use, so the two can never describe different rules.
|
||||
/// </summary>
|
||||
/// <param name="driverLabel">The driver instance id or name to cite.</param>
|
||||
public static string MTConnectMultipleDevicesMessage(string driverLabel) =>
|
||||
$"MTConnect driver '{driverLabel}' has more than one Device. MTConnect binds all tags through one agent " +
|
||||
"client per driver instance and does not route by /raw Device, and with several Devices the Device's " +
|
||||
"DeviceConfig (agent URI / device name) is no longer merged into the driver config, so which Device would " +
|
||||
"supply the endpoint is ambiguous. Author one MTConnect driver per agent device, each with a single Device.";
|
||||
|
||||
/// <summary>
|
||||
/// #628 (decision (b)): an MTConnect driver instance owns exactly one agent client, so a second <c>/raw</c>
|
||||
/// Device under it is a genuine ambiguity — the driver ignores <c>RawTagEntry.DeviceName</c>, and
|
||||
/// <c>DriverDeviceConfigMerger</c> only merges a Device's <c>DeviceConfig</c> up to the driver config when
|
||||
/// there is exactly ONE Device. The normal single-Device layout (every driver gets a default
|
||||
/// <c>Device1</c>) stays valid.
|
||||
/// </summary>
|
||||
private static void ValidateMTConnectSingleDevice(DraftSnapshot draft, List<ValidationError> errors)
|
||||
{
|
||||
foreach (var driver in draft.DriverInstances)
|
||||
{
|
||||
if (!string.Equals(driver.DriverType, Core.Abstractions.DriverTypeNames.MTConnect, StringComparison.Ordinal))
|
||||
continue;
|
||||
var count = draft.Devices.Count(d =>
|
||||
string.Equals(d.DriverInstanceId, driver.DriverInstanceId, StringComparison.Ordinal));
|
||||
if (count <= 1) continue;
|
||||
errors.Add(new(MTConnectMultipleDevicesCode,
|
||||
MTConnectMultipleDevicesMessage(driver.Name) + $" ({count} Devices found.)",
|
||||
driver.DriverInstanceId));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>WP7 Calculation-driver deploy gates. For every tag bound to a <c>Calculation</c> driver:
|
||||
/// <list type="number">
|
||||
/// <item><b>scriptId existence</b> — the tag's <c>TagConfig.scriptId</c> must be present and resolve to
|
||||
|
||||
@@ -5,6 +5,7 @@ using ZB.MOM.WW.OtOpcUa.AdminUI.Uns.TagEditors;
|
||||
using ZB.MOM.WW.OtOpcUa.Commons.Types;
|
||||
using ZB.MOM.WW.OtOpcUa.Configuration;
|
||||
using ZB.MOM.WW.OtOpcUa.Configuration.Entities;
|
||||
using ZB.MOM.WW.OtOpcUa.Configuration.Validation;
|
||||
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.AdminUI.Uns;
|
||||
@@ -615,9 +616,16 @@ public sealed class RawTreeService(IDbContextFactory<OtOpcUaConfigDbContext> dbF
|
||||
|
||||
await using var db = await dbFactory.CreateDbContextAsync(ct);
|
||||
|
||||
if (!await db.DriverInstances.AnyAsync(d => d.DriverInstanceId == driverInstanceId, ct))
|
||||
var owner = await db.DriverInstances.FirstOrDefaultAsync(d => d.DriverInstanceId == driverInstanceId, ct);
|
||||
if (owner is null)
|
||||
return new UnsMutationResult(false, $"Driver '{driverInstanceId}' not found.");
|
||||
|
||||
// #628: MTConnect owns one agent client per driver — mirror the deploy gate
|
||||
// (DraftValidator.ValidateMTConnectSingleDevice) so the second Device is refused where it is authored.
|
||||
if (string.Equals(owner.DriverType, DriverTypeNames.MTConnect, StringComparison.Ordinal)
|
||||
&& await db.Devices.AnyAsync(dev => dev.DriverInstanceId == driverInstanceId, ct))
|
||||
return new UnsMutationResult(false, DraftValidator.MTConnectMultipleDevicesMessage(owner.Name));
|
||||
|
||||
if (await db.Devices.AnyAsync(dev => dev.DriverInstanceId == driverInstanceId && dev.Name == name, ct))
|
||||
return new UnsMutationResult(false, $"A device named '{name}' already exists on this driver.");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user