diff --git a/src/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Clusters/DriversTab.razor b/src/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Clusters/DriversTab.razor
index 3bb2631..ba30127 100644
--- a/src/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Clusters/DriversTab.razor
+++ b/src/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Clusters/DriversTab.razor
@@ -1,3 +1,5 @@
+@using System.Text.Json
+@using ZB.MOM.WW.OtOpcUa.Admin.Components.Pages.Modbus
@using ZB.MOM.WW.OtOpcUa.Admin.Services
@using ZB.MOM.WW.OtOpcUa.Configuration.Entities
@inject DriverInstanceService DriverSvc
@@ -50,13 +52,14 @@ else
+
Type string must match the driver's registered factory name; this dropdown wraps the canonical names.
@@ -65,9 +68,19 @@ else
-
-
-
Phase 1: generic JSON editor — per-driver schema validation arrives in each driver's phase (decision #94).
+ @if (string.Equals(_type, "Modbus", StringComparison.OrdinalIgnoreCase))
+ {
+ @* #147 — typed editor for Modbus drivers. The generic textarea is a fall-back
+ for driver types that haven't yet shipped a typed editor. *@
+
+
+ }
+ else
+ {
+
+
+
Phase 1: generic JSON editor — per-driver schema validation arrives in each driver's phase (decision #94).
+ }
@if (_error is not null) { @_error
}
@@ -87,11 +100,17 @@ else
private List? _namespaces;
private bool _showForm;
private string _name = string.Empty;
- private string _type = "ModbusTcp";
+ private string _type = "Modbus";
private string _nsId = string.Empty;
private string _config = "{}";
private string? _error;
+ // #147 — typed editor model for Modbus drivers. Defaults match ModbusDriverOptions
+ // defaults so an unedited form produces config equivalent to the historical
+ // pre-typed-editor wire output. Serialised to _config on Save when type=Modbus.
+ private ModbusOptionsEditor.ModbusOptionsViewModel _modbusOptions = new();
+ private static readonly JsonSerializerOptions ModbusJsonOptions = new() { WriteIndented = true };
+
protected override async Task OnParametersSetAsync() => await ReloadAsync();
private async Task ReloadAsync()
@@ -111,11 +130,57 @@ else
}
try
{
- await DriverSvc.AddAsync(GenerationId, ClusterId, _nsId, _name, _type, _config, CancellationToken.None);
+ // #147 — for Modbus drivers serialize the typed editor model into the DriverConfig
+ // JSON column. Other driver types still use the raw textarea contents until each
+ // ships its own typed editor (decision #94 — per-driver schema validation arrives
+ // per driver phase).
+ var configJson = string.Equals(_type, "Modbus", StringComparison.OrdinalIgnoreCase)
+ ? SerializeModbusOptions(_modbusOptions)
+ : _config;
+
+ await DriverSvc.AddAsync(GenerationId, ClusterId, _nsId, _name, _type, configJson, CancellationToken.None);
_name = string.Empty; _config = "{}";
+ _modbusOptions = new();
_showForm = false;
await ReloadAsync();
}
catch (Exception ex) { _error = ex.Message; }
}
+
+ ///
+ /// Maps the view-model field names onto the JSON shape ModbusDriverFactoryExtensions
+ /// consumes. Hand-rolled because the DTO uses millisecond / byte field flavours that the
+ /// view model exposes as TimeSpan-derived integers; a System.Text.Json round-trip would
+ /// emit the .NET-native names instead.
+ ///
+ private static string SerializeModbusOptions(ModbusOptionsEditor.ModbusOptionsViewModel m) =>
+ JsonSerializer.Serialize(new
+ {
+ host = m.Host,
+ port = m.Port,
+ unitId = m.UnitId,
+ family = m.Family.ToString(),
+ melsecSubFamily = m.MelsecSubFamily.ToString(),
+ keepAlive = new
+ {
+ enabled = m.KeepAliveEnabled,
+ timeMs = m.KeepAliveTimeSec * 1000,
+ intervalMs = m.KeepAliveIntervalSec * 1000,
+ retryCount = m.KeepAliveRetryCount,
+ },
+ reconnect = new
+ {
+ initialDelayMs = m.ReconnectInitialDelayMs,
+ maxDelayMs = m.ReconnectMaxDelayMs,
+ backoffMultiplier = m.ReconnectBackoffMultiplier,
+ },
+ maxRegistersPerRead = m.MaxRegistersPerRead,
+ maxRegistersPerWrite = m.MaxRegistersPerWrite,
+ maxCoilsPerRead = m.MaxCoilsPerRead,
+ maxReadGap = m.MaxReadGap,
+ useFC15ForSingleCoilWrites = m.UseFC15ForSingleCoilWrites,
+ useFC16ForSingleRegisterWrites = m.UseFC16ForSingleRegisterWrites,
+ writeOnChangeOnly = m.WriteOnChangeOnly,
+ tags = Array.Empty