Parent: #209. Adds the server-side wiring so a Config DB `DriverType='Modbus'`
row actually boots a Modbus driver instance + publishes its tags under OPC UA
NodeIds, instead of being silently skipped by DriverInstanceBootstrapper.
Changes:
- `ModbusDriverFactoryExtensions` (new) — mirrors
`GalaxyProxyDriverFactoryExtensions` + `FocasDriverFactoryExtensions`.
`DriverTypeName="Modbus"`, `CreateInstance` deserialises
`ModbusDriverConfigDto` (Host/Port/UnitId/TimeoutMs/Probe/Tags) to a full
`ModbusDriverOptions` and hands back a `ModbusDriver`. Strict enum parsing
(Region / DataType / ByteOrder / StringByteOrder) — unknown values fail
fast with an explicit "expected one of" error rather than at first read.
- `Program.cs` — register the factory after Galaxy + FOCAS.
- `Driver.Modbus.csproj` — add `Core` project reference (the DI-free factory
needs `DriverFactoryRegistry` from `Core.Hosting`). Matches the FOCAS
driver's reference shape.
- `Server.csproj` — add the `Driver.Modbus` ProjectReference so the
Program.cs registration compiles against the same assembly the server
loads at runtime.
- `scripts/smoke/seed-modbus-smoke.sql` (new) — one-cluster smoke seed
modelled on `seed-phase-7-smoke.sql`. Creates a `modbus-smoke` cluster +
`modbus-smoke-node` + Draft generation + Namespace + UnsArea/UnsLine/
Equipment + one Modbus `DriverInstance` pointing at the pymodbus standard
fixture (`127.0.0.1:5020`) + one Tag at `HR[200]:UInt16`, ending in
`EXEC sp_PublishGeneration`. HR[100] is deliberately *not* used because
pymodbus `standard.json` runs an auto-increment action on that register.
Full-solution build: 0 errors, only the pre-existing xUnit1051 warnings.
AB CIP / S7 / AB Legacy factories follow in their own PRs per #211 / #212 /
#213. Live boot verification happens in the exit-gate PR once all four
factories are in place.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>