feat(modbus-rtu): bind Transport mode on options/DTO/factory (string-enum guard)
Claude-Session: https://claude.ai/code/session_01GASWkNEi68FSCtvr6rLoEW
This commit is contained in:
@@ -130,6 +130,14 @@ public sealed class ModbusDriverOptions
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public MelsecFamily MelsecSubFamily { get; init; } = MelsecFamily.Q_L_iQR;
|
public MelsecFamily MelsecSubFamily { get; init; } = MelsecFamily.Q_L_iQR;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Wire transport selector. <see cref="ModbusTransportMode.Tcp"/> (default) is the
|
||||||
|
/// historical Modbus TCP/MBAP framing. <see cref="ModbusTransportMode.RtuOverTcp"/>
|
||||||
|
/// frames Modbus RTU PDUs (CRC16, no MBAP header) over the same TCP socket — for
|
||||||
|
/// serial-to-Ethernet gateways that bridge RS-485 without re-encapsulating to MBAP.
|
||||||
|
/// </summary>
|
||||||
|
public ModbusTransportMode Transport { get; init; } = ModbusTransportMode.Tcp;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// When <c>true</c>, the driver suppresses redundant writes: if the most recent
|
/// When <c>true</c>, the driver suppresses redundant writes: if the most recent
|
||||||
/// successful write to a tag carried value V and a new write of V arrives, the second
|
/// successful write to a tag carried value V and a new write of V arrives, the second
|
||||||
|
|||||||
@@ -74,6 +74,8 @@ public static class ModbusDriverFactoryExtensions
|
|||||||
: ParseEnum<ModbusFamily>(dto.Family, "<driver-level>", driverInstanceId, "Family"),
|
: ParseEnum<ModbusFamily>(dto.Family, "<driver-level>", driverInstanceId, "Family"),
|
||||||
MelsecSubFamily = dto.MelsecSubFamily is null ? MelsecFamily.Q_L_iQR
|
MelsecSubFamily = dto.MelsecSubFamily is null ? MelsecFamily.Q_L_iQR
|
||||||
: ParseEnum<MelsecFamily>(dto.MelsecSubFamily, "<driver-level>", driverInstanceId, "MelsecSubFamily"),
|
: ParseEnum<MelsecFamily>(dto.MelsecSubFamily, "<driver-level>", driverInstanceId, "MelsecSubFamily"),
|
||||||
|
Transport = dto.Transport is null ? ModbusTransportMode.Tcp
|
||||||
|
: ParseEnum<ModbusTransportMode>(dto.Transport, "<driver-level>", driverInstanceId, "Transport"),
|
||||||
AutoProhibitReprobeInterval = dto.AutoProhibitReprobeMs is { } reprobeMs ? TimeSpan.FromMilliseconds(reprobeMs) : null,
|
AutoProhibitReprobeInterval = dto.AutoProhibitReprobeMs is { } reprobeMs ? TimeSpan.FromMilliseconds(reprobeMs) : null,
|
||||||
AutoReconnect = dto.AutoReconnect ?? true,
|
AutoReconnect = dto.AutoReconnect ?? true,
|
||||||
// v3: the deploy artifact (Wave C) populates rawTags with the cluster's authored raw Modbus
|
// v3: the deploy artifact (Wave C) populates rawTags with the cluster's authored raw Modbus
|
||||||
@@ -159,6 +161,8 @@ public static class ModbusDriverFactoryExtensions
|
|||||||
public string? Family { get; init; }
|
public string? Family { get; init; }
|
||||||
/// <summary>Gets or sets the Melsec subfamily.</summary>
|
/// <summary>Gets or sets the Melsec subfamily.</summary>
|
||||||
public string? MelsecSubFamily { get; init; }
|
public string? MelsecSubFamily { get; init; }
|
||||||
|
/// <summary>Gets or sets the wire transport mode (Tcp / RtuOverTcp). Null defaults to Tcp.</summary>
|
||||||
|
public string? Transport { get; init; }
|
||||||
/// <summary>Gets or sets the automatic prohibition reprobei interval in milliseconds.</summary>
|
/// <summary>Gets or sets the automatic prohibition reprobei interval in milliseconds.</summary>
|
||||||
public int? AutoProhibitReprobeMs { get; init; }
|
public int? AutoProhibitReprobeMs { get; init; }
|
||||||
/// <summary>Gets or sets a value indicating whether automatic reconnection is enabled.</summary>
|
/// <summary>Gets or sets a value indicating whether automatic reconnection is enabled.</summary>
|
||||||
|
|||||||
+47
@@ -0,0 +1,47 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using Shouldly;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace ZB.MOM.WW.OtOpcUa.Driver.Modbus.Tests;
|
||||||
|
|
||||||
|
public sealed class ModbusTransportConfigRoundTripTests
|
||||||
|
{
|
||||||
|
// Mirrors the AdminUI ModbusDriverForm serializer: camelCase + string enums.
|
||||||
|
private static readonly JsonSerializerOptions _adminOpts = new()
|
||||||
|
{
|
||||||
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||||
|
Converters = { new JsonStringEnumConverter() },
|
||||||
|
};
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Transport_serializes_as_a_string_not_a_number()
|
||||||
|
{
|
||||||
|
var opts = new ModbusDriverOptions { Host = "10.20.0.50", Port = 4001, Transport = ModbusTransportMode.RtuOverTcp };
|
||||||
|
var json = JsonSerializer.Serialize(opts, _adminOpts);
|
||||||
|
json.ShouldContain("\"transport\":\"RtuOverTcp\"");
|
||||||
|
json.ShouldNotContain("\"transport\":1", Case.Sensitive);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Factory_binds_RtuOverTcp_from_string_config()
|
||||||
|
{
|
||||||
|
const string json = """{ "host": "10.20.0.50", "port": 4001, "transport": "RtuOverTcp" }""";
|
||||||
|
using var driver = ModbusDriverFactoryExtensions.CreateInstance("mb-rtu", json);
|
||||||
|
var opts = (ModbusDriverOptions)typeof(ModbusDriver)
|
||||||
|
.GetField("_options", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)!
|
||||||
|
.GetValue(driver)!;
|
||||||
|
opts.Transport.ShouldBe(ModbusTransportMode.RtuOverTcp);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Factory_defaults_Transport_to_Tcp_when_omitted()
|
||||||
|
{
|
||||||
|
const string json = """{ "host": "10.0.0.10" }""";
|
||||||
|
using var driver = ModbusDriverFactoryExtensions.CreateInstance("mb-tcp", json);
|
||||||
|
var opts = (ModbusDriverOptions)typeof(ModbusDriver)
|
||||||
|
.GetField("_options", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)!
|
||||||
|
.GetValue(driver)!;
|
||||||
|
opts.Transport.ShouldBe(ModbusTransportMode.Tcp);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user