diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.Runtime/Drivers/DriverInstanceActor.cs b/src/Server/ZB.MOM.WW.OtOpcUa.Runtime/Drivers/DriverInstanceActor.cs
index 4469cc7..09ba5f0 100644
--- a/src/Server/ZB.MOM.WW.OtOpcUa.Runtime/Drivers/DriverInstanceActor.cs
+++ b/src/Server/ZB.MOM.WW.OtOpcUa.Runtime/Drivers/DriverInstanceActor.cs
@@ -45,15 +45,48 @@ public sealed class DriverInstanceActor : ReceiveActor, IWithTimers
public ITimerScheduler Timers { get; set; } = null!;
- public static Props Props(IDriver driver, TimeSpan? reconnectInterval = null) =>
- Akka.Actor.Props.Create(() => new DriverInstanceActor(driver, reconnectInterval ?? DefaultReconnectInterval));
+ public static Props Props(IDriver driver, TimeSpan? reconnectInterval = null, bool startStubbed = false) =>
+ Akka.Actor.Props.Create(() => new DriverInstanceActor(driver, reconnectInterval ?? DefaultReconnectInterval, startStubbed));
- public DriverInstanceActor(IDriver driver, TimeSpan reconnectInterval)
+ ///
+ /// Returns true when the driver should boot in DEV-STUB mode based on host platform and
+ /// configured roles. Mirrors plan §Task 55: Windows-only driver types (Galaxy, Wonderware
+ /// Historian) are stubbed when running on non-Windows OR when the host carries the
+ /// dev role.
+ ///
+ public static bool ShouldStub(string driverType, IEnumerable roles)
+ {
+ var isWindowsOnly = driverType is "Galaxy" or "Historian.Wonderware";
+ if (!OperatingSystem.IsWindows() && isWindowsOnly) return true;
+ if (roles.Contains("dev") && isWindowsOnly) return true;
+ return false;
+ }
+
+ public DriverInstanceActor(IDriver driver, TimeSpan reconnectInterval, bool startStubbed = false)
{
_driver = driver;
_driverInstanceId = driver.DriverInstanceId;
_reconnectInterval = reconnectInterval;
- Become(Connecting);
+ if (startStubbed)
+ {
+ Context.GetLogger().Info("[DEV-STUB] driver={Name} type={Type}",
+ _driverInstanceId, driver.DriverType);
+ Become(Stubbed);
+ }
+ else
+ {
+ Become(Connecting);
+ }
+ }
+
+ private void Stubbed()
+ {
+ // Stubbed drivers accept the standard message contracts but return deterministic
+ // success without touching real hardware. Read returns null; Write succeeds.
+ Receive(_ => { /* no-op */ });
+ Receive(msg => Sender.Tell(new ApplyResult(true, "stubbed", msg.Correlation)));
+ Receive(_ => Sender.Tell(new WriteAttributeResult(true, "stubbed")));
+ Receive(_ => { /* stubbed drivers don't disconnect */ });
}
private void Connecting()