Skip to content
PreciSim
concepts

Device abstraction

Virtual and real devices implement the same interfaces — that is what makes "prove it on the virtual rig first" a real strategy.

In one line

The layers above do not know whether they are talking to a virtual device or a real one.

That is not an aesthetic choice. It decides three things:

  1. whether someone with no hardware can run the whole procedure;
  2. whether a procedure validated on virtual devices has to be rewritten on site;
  3. whether end-to-end tests can run in CI.

What the interfaces look like

One interface per device class, exposing what the business actually needs — never the vendor SDK's shape:

public interface ICamera : IDevice
{
    CameraInfo Info { get; }                       // resolution, pixel size
    Task<Frame> GrabAsync(CancellationToken ct);
    Task SetExposureAsync(double microseconds);
    Task SetGainAsync(double db);
}
 
public interface IAxis : IDevice
{
    AxisInfo Info { get; }                         // travel, units, positive direction
    double Position { get; }                       // mm or deg
    Task MoveAbsAsync(double target, MoveProfile p, CancellationToken ct);
    Task MoveRelAsync(double delta, MoveProfile p, CancellationToken ct);
    Task HomeAsync(CancellationToken ct);
    Task StopAsync();
}
 
public interface IDigitalIo : IDevice
{
    bool Read(int channel);
    Task WriteAsync(int channel, bool value);
    IObservable<IoChange> Changes { get; }
}

Note what is missing: no GetBaslerHandle(), no SendGCode(string), no escape hatch that exists for exactly one vendor. Open one of those and the layers above start depending on it, and the abstraction is gone.

A machine is assembled from configuration

{
  "machine": "demo-2axis-vision",
  "devices": [
    { "id": "cam0", "type": "camera", "driver": "virtual",
      "params": { "width": 2448, "height": 2048, "pixelUm": 3.45 } },
    { "id": "axisX", "type": "axis", "driver": "virtual",
      "params": { "travelMm": 200, "backlashUm": 2.4, "repeatUm": 3 } }
  ]
}

Swap "driver": "virtual" for "driver": "basler" and nothing above changes. That is the whole trick.

How virtual is "virtual"

These are not stubs returning canned data; they compute from a physical model:

Device What is modelled
Camera Projection, lens distortion, depth of field and defocus, exposure/gain, read and shot noise, bad pixels
Axis Trapezoidal/S-curve motion, backlash, pitch error, repeatability, limits, e-stop
IO Cylinder settle delays, light curtain, switch bounce
Galvo Field distortion, jump delay, mark delay

Every item has a switch and parameters. The most useful teaching move is to exaggerate one — push backlash from 2.4 µm to 50 µm and ask a trainee to do bidirectional positioning. The models are documented in Physics and models.

What does not belong in the device layer

  • Business logic. "Blow air, then grab" is workflow, not camera.
  • Calibration results. Those describe the machine's current state and live in the calibration store, not on a device object.
  • Retry policy. Devices report faults honestly; retry / skip / abort is decided once, in the workflow.

Hold those three lines and swapping hardware really is just a configuration change.

Last updated: Sep 21, 2026
Was this page helpful?