Merge b626-ablegacy-focas (batch #626+)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
@@ -68,8 +68,11 @@ public sealed class AbLegacyTagConfigModel
|
||||
if (TagConfigJson.DescribeInvalidEnum<AbLegacyDataType>(_bag, "dataType") is { } dtError)
|
||||
return dtError;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(Address))
|
||||
return "Address is required.";
|
||||
// The editor cannot see the device's PLC family, so it checks only what no family accepts (the same
|
||||
// AbLegacyAddress authority the deploy gate and runtime use) — never stricter than the gate, which adds the
|
||||
// family-specific verdict (PLC-5 octal I/O, MicroLogix-only function files).
|
||||
if (AbLegacyAddress.Validate(Address, family: null) is { } addressError)
|
||||
return addressError;
|
||||
|
||||
// DELIBERATELY STRICTER THAN THE RUNTIME, and re-confirmed as such by the Gitea #554 survey — do not
|
||||
// "align" it. PCCC data-file elements are limited to AbLegacyArray.MaxElements (256). The reader
|
||||
|
||||
@@ -262,12 +262,16 @@ public sealed class AdminOperationsActor : ReceiveActor
|
||||
var driverByDevice = draft.Devices
|
||||
.GroupBy(d => d.DeviceId, StringComparer.Ordinal)
|
||||
.ToDictionary(g => g.Key, g => g.First().DriverInstanceId, StringComparer.Ordinal);
|
||||
var deviceConfigById = draft.Devices
|
||||
.GroupBy(d => d.DeviceId, StringComparer.Ordinal)
|
||||
.ToDictionary(g => g.Key, g => g.First().DeviceConfig, StringComparer.Ordinal);
|
||||
var tagConfigWarnings = new List<(string TagId, string Text)>();
|
||||
foreach (var t in draft.Tags)
|
||||
{
|
||||
if (!driverByDevice.TryGetValue(t.DeviceId, out var driverInstanceId)) continue;
|
||||
if (!driverTypeById.TryGetValue(driverInstanceId, out var driverType)) continue;
|
||||
foreach (var w in EquipmentTagConfigInspector.Inspect(driverType, t.TagConfig))
|
||||
foreach (var w in EquipmentTagConfigInspector.Inspect(
|
||||
driverType, t.TagConfig, deviceConfigById.GetValueOrDefault(t.DeviceId)))
|
||||
tagConfigWarnings.Add((t.TagId, $"tag '{t.TagId}': {w}"));
|
||||
}
|
||||
if (tagConfigWarnings.Count > 0)
|
||||
|
||||
+42
@@ -1,6 +1,7 @@
|
||||
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
||||
using ZB.MOM.WW.OtOpcUa.Driver.AbCip;
|
||||
using ZB.MOM.WW.OtOpcUa.Driver.AbLegacy;
|
||||
using ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.PlcFamilies;
|
||||
using ZB.MOM.WW.OtOpcUa.Driver.Calculation;
|
||||
using ZB.MOM.WW.OtOpcUa.Driver.FOCAS;
|
||||
using ZB.MOM.WW.OtOpcUa.Driver.Modbus;
|
||||
@@ -78,6 +79,47 @@ public static class EquipmentTagConfigInspector
|
||||
return Inspectors.TryGetValue(driverType, out var inspect) ? inspect(tagConfig) : Array.Empty<string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="Inspect(string?, string?)"/> with the owning device's <c>DeviceConfig</c>, so a driver whose
|
||||
/// address grammar depends on the device (AbLegacy: the <c>PlcFamily</c>) is checked against the family the
|
||||
/// runtime will use. Other drivers ignore the device config. A missing family key is the runtime default
|
||||
/// (SLC 500); an unrecognised value is "unknown" and only the family-independent check applies — the gate
|
||||
/// is never stricter than the runtime.
|
||||
/// </summary>
|
||||
/// <param name="driverType">The bound driver's <c>DriverType</c>.</param>
|
||||
/// <param name="tagConfig">The equipment tag's <c>TagConfig</c> JSON.</param>
|
||||
/// <param name="deviceConfig">The owning device's <c>DeviceConfig</c> JSON (may be null).</param>
|
||||
/// <returns>The warnings; empty when clean.</returns>
|
||||
public static IReadOnlyList<string> Inspect(string? driverType, string? tagConfig, string? deviceConfig)
|
||||
{
|
||||
if (string.IsNullOrEmpty(driverType) || tagConfig is null) return Array.Empty<string>();
|
||||
if (string.Equals(driverType, DriverTypeNames.AbLegacy, StringComparison.OrdinalIgnoreCase))
|
||||
return AbLegacyTagDefinitionFactory.Inspect(tagConfig, ReadAbLegacyFamily(deviceConfig));
|
||||
return Inspect(driverType, tagConfig);
|
||||
}
|
||||
|
||||
private static AbLegacyPlcFamily? ReadAbLegacyFamily(string? deviceConfig)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(deviceConfig)) return AbLegacyPlcFamily.Slc500;
|
||||
try
|
||||
{
|
||||
using var doc = System.Text.Json.JsonDocument.Parse(deviceConfig);
|
||||
if (doc.RootElement.ValueKind != System.Text.Json.JsonValueKind.Object) return AbLegacyPlcFamily.Slc500;
|
||||
foreach (var prop in doc.RootElement.EnumerateObject())
|
||||
{
|
||||
if (!string.Equals(prop.Name, "PlcFamily", StringComparison.OrdinalIgnoreCase)) continue;
|
||||
var raw = prop.Value.ValueKind == System.Text.Json.JsonValueKind.String ? prop.Value.GetString() : null;
|
||||
if (string.IsNullOrWhiteSpace(raw)) return AbLegacyPlcFamily.Slc500;
|
||||
return Enum.TryParse<AbLegacyPlcFamily>(raw, ignoreCase: true, out var f) && Enum.IsDefined(f) ? f : null;
|
||||
}
|
||||
return AbLegacyPlcFamily.Slc500;
|
||||
}
|
||||
catch (System.Text.Json.JsonException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Whether the given driver type has a mapped inspector.</summary>
|
||||
/// <param name="driverType">The bound driver's <c>DriverType</c>.</param>
|
||||
/// <returns><see langword="true"/> when an inspector is registered for the driver type.</returns>
|
||||
|
||||
Reference in New Issue
Block a user