.. index:: single: adding a driver single: hardware driver; recipe single: REGISTER_HARDWARE_META; driver single: REGISTER_HARDWARE_PROTOCOLS; driver single: REGISTER_HARDWARE_SETTINGS; driver single: REGISTER_CUSTOM_COMM; driver single: REGISTER_LIBRARY; driver single: state-management patterns; C++ drivers single: virtual sibling; driver single: HardwareObject; new driver single: testConnection; new driver single: initialize; new driver Adding a New Hardware Driver ============================ Adding a new C++ driver of an *existing* hardware type — a new AWG model, a new mass flow controller, a new GPIB synthesizer — is the single most common contributor task in Blackchirp. This page is the canonical recipe. It walks through picking the right interface class, the five files a driver consists of, the registration macros, the three state-management patterns the existing drivers fall into (with one worked example each), and the smoke-testing checklist before you declare the driver done. This page assumes the *type* already exists. If no existing interface class matches your hardware — that is, you are adding a new abstract base alongside :cpp:class:`AWG`, :cpp:class:`FlowController`, and friends — see :doc:`/developer_guide/adding_a_hardware_type`. If you want to drive your hardware from a Python script rather than a C++ class, see :doc:`/developer_guide/python_hardware` for the trampoline architecture; this page covers the C++ side that any new Python trampoline still has to coexist with. Picking the right base class ---------------------------- Every driver inherits from a hardware-type interface class. There are eleven of them. Pick the one that matches the role your hardware will play: .. list-table:: :header-rows: 1 :widths: 25 35 40 * - Interface class - Domain - Source directory * - :cpp:class:`AWG` - Chirp generation: arbitrary-waveform generator or DDS-based ramp generator that drives the CP-FTMW excitation. - ``src/hardware/optional/chirpsource/`` * - :cpp:class:`Clock` - RF/microwave synthesizer used as a tunable LO, AWG reference, or DR clock. - ``src/hardware/core/clock/`` * - :cpp:class:`FtmwDigitizer` - FTMW digitizer: the high-bandwidth oscilloscope or transient recorder that captures FIDs. - ``src/hardware/core/ftmwdigitizer/`` * - :cpp:class:`LifDigitizer` - LIF digitizer: the slower oscilloscope that records laser-induced-fluorescence transients. - ``src/hardware/core/lifdigitizer/`` * - :cpp:class:`LifLaser` - Tunable laser source for the LIF module (wavelength setpoint, optional flashlamp control). - ``src/hardware/core/liflaser/`` * - :cpp:class:`FlowController` - Multichannel mass flow controller, optionally with a chamber-pressure setpoint. - ``src/hardware/optional/flowcontroller/`` * - :cpp:class:`GpibController` - GPIB bus bridge (Prologix GPIB-LAN, GPIB-USB) that other :cpp:class:`HardwareObject`\ s talk through. - ``src/hardware/optional/gpibcontroller/`` * - :cpp:class:`IOBoard` - General-purpose analog/digital I/O board for auxiliary readbacks, gate signals, and so on. - ``src/hardware/optional/ioboard/`` * - :cpp:class:`PressureController` - Chamber pressure gauge / pressure controller, optionally with a gate valve and pressure-control mode. - ``src/hardware/optional/pressurecontroller/`` * - :cpp:class:`PulseGenerator` - Multichannel pulse/delay generator that sequences gas, laser, AWG-trigger, and protection pulses. - ``src/hardware/optional/pulsegenerator/`` * - :cpp:class:`TemperatureController` - Multichannel temperature readout (LakeShore-style cryogenic monitor, etc.). - ``src/hardware/optional/tempcontroller/`` The ``core/`` vs ``optional/`` split corresponds to whether the type is required to run an FTMW experiment (``core``) or genuinely optional (``optional``). The split is structural rather than user-facing — :cpp:class:`HardwareManager` ensures every required type has at least a virtual profile so the application always has something to talk to — but it is the directory layout you have to put new files into. The directory layout is described on :doc:`/developer_guide/architecture` (source tree section). If none of these match your hardware, the right move is almost always to add a new abstract interface class first; see :doc:`/developer_guide/adding_a_hardware_type`. Files you will create --------------------- Every driver consists of the same touches: 1. ``src/hardware///.h`` — the class declaration. Inherits from the type's interface class; declares ``Q_OBJECT``; declares the constructor with the ``(label, parent=nullptr)`` signature (see below); overrides the pure virtuals the interface class requires. 2. ``src/hardware///.cpp`` — the implementation. Carries the registration macros at file scope and the constructor and method bodies. 3. ``src/data/settings/hardwarekeys.h`` — only if the driver introduces *new* setting keys beyond what the interface class already declares. Add them to the appropriate ``BC::Key::`` namespace, or declare a per-driver namespace inside the driver's header (see :cpp:any:`BC::Key::AWG::awg70002a` for the convention used by most drivers today). 4. *(Optional)* ``REGISTER_LIBRARY`` invocation pointing at a :cpp:class:`VendorLibrary` subclass, if the driver depends on a closed-source SDK. Authoring the library subclass itself is the topic of :doc:`/developer_guide/vendor_libraries`. 5. *(Optional)* a ``Virtual`` sibling that synthesizes plausible readings without a real instrument. Most existing drivers ship with one; see *Virtual sibling* below. **No CMake edits are required**, in the typical case. The hardware glob in ``cmake/BlackchirpHardware.cmake`` discovers source files by filename pattern: dropping ``vendormodel.cpp`` / ``vendormodel.h`` into the right hardware-type directory under one of the recognized prefixes is enough. The recognized prefixes per directory and the AUTOMOC linkage that keeps the static-initialization registrations from being dropped at link time are documented on :doc:`/developer_guide/build_system` (*Hardware aggregator headers* and *Glob-based source discovery*). If your vendor prefix is not yet in the list, that is the only edit required, and it is one line in each of two parallel globs. After adding files (or a new prefix) you must re-run ``cmake`` so the globs are re-evaluated; ``cmake --build`` alone will not pick up new files. Constructor and registration macros ----------------------------------- Every driver follows the same three-macro registration pattern at file scope in the ``.cpp``, plus a small constructor: .. code-block:: cpp // myawg.h #include namespace BC::Key::AWG { inline constexpr QLatin1StringView myawg{"myawg"}; inline const QString myawgName{"Vendor Model 1234 AWG"}; } class MyAwg : public AWG { Q_OBJECT public: explicit MyAwg(const QString& label, QObject *parent = nullptr); ~MyAwg() override = default; public slots: bool prepareForExperiment(Experiment &exp) override; void beginAcquisition() override; void endAcquisition() override; protected: bool testConnection() override; void initialize() override; }; .. code-block:: cpp // myawg.cpp #include "myawg.h" #include REGISTER_HARDWARE_META(MyAwg, "Vendor Model 1234 high-performance AWG") REGISTER_HARDWARE_PROTOCOLS(MyAwg, CommunicationProtocol::Tcp, CommunicationProtocol::Rs232) REGISTER_HARDWARE_SETTINGS(MyAwg, {BC::Key::AWG::markerCount, "Marker Count", "Number of physical marker output channels", 2, 0, QVariant{}, HwSettingPriority::Required} ) MyAwg::MyAwg(const QString& label, QObject *parent) : AWG(QString(MyAwg::staticMetaObject.className()), label, parent) { setDefault(BC::Key::Comm::timeout, 10000); setDefault(BC::Key::Comm::termChar, QString("\n")); save(); } A few non-obvious points: - **Driver key.** The base class constructor takes an ``impl`` string as its first argument. Pass ``QString(MyAwg::staticMetaObject.className())`` so the driver key tracks the class name automatically — renaming the class renames the registry key for free, with no parallel string table to update. The base class's ``hwType`` is filled in similarly inside the interface-class constructor. The two together combine into the instance's ``d_key`` (``".