HardwareObject

HardwareObject is the abstract base class for every device Blackchirp talks to: digitizers, AWGs, clocks, pulse generators, flow controllers, pressure controllers, temperature sensors, and so on. It provides the common identity, communication, settings, and lifecycle machinery that the rest of the program relies on; concrete drivers add the per-vendor command translation and any device-specific behavior.

Hardware profiles, loadouts, and the registry that supplies setting defaults are described in Hardware and Library Configuration; the runtime Hardware menu and connection workflow are covered in Hardware Menu and Hardware Dialog. The REGISTER_HARDWARE_META, REGISTER_HARDWARE_PROTOCOLS, and REGISTER_HARDWARE_SETTINGS macros that every driver uses to register itself with the framework are documented on HardwareRegistry.

Identity

Each instance has a hardware type (e.g. "AWG", "FtmwDigitizer", "PulseGenerator") naming the abstract role and a label — a free-form, user-supplied string distinguishing multiple instances of the same type (e.g. "main", "secondary"). The hardware type matches the interface class’s metaobject name by convention.

Type and label combine to form d_key (e.g. "PulseGenerator.main"), which uniquely identifies the instance. d_key is also the SettingsStorage group that holds this instance’s persistent settings, which is why the same key cannot be reused for two different drivers: an instance’s driver is fixed at profile creation, and changing it requires deleting the profile and rebuilding it under the same (or a new) label.

d_model is a separate string carrying just the driver class name (e.g. "AWG70002a", "PythonAwg"); it is recorded inside the settings group and used for display purposes — for example, to show the user which driver backs a profile in the hardware settings dialog. d_model is not part of d_key and is not used as a dispatch identifier.

Constructing a driver

Two-layer inheritance is the convention: an interface class (AWG, FtmwDigitizer, PulseGenerator, …) declares the slot and signal API the rest of Blackchirp consumes, and one or more driver classes (AWG70002a, VirtualAwg, …) translate that API into vendor commands. A typical pair:

class Analyzer : public HardwareObject
{
    Q_OBJECT
public:
    Analyzer(const QString& impl, const QString& label, QObject *parent = nullptr)
        : HardwareObject(QString(Analyzer::staticMetaObject.className()),
                         impl, label, parent) {}

public slots:
    bool setFoo(double f) {
        auto ok = hwSetFoo(f);
        if(!ok) emit hardwareFailure();
        else    readFoo();
        return ok;
    }
    double readFoo() {
        d_foo = hwReadFoo();
        emit fooUpdated(d_foo, QPrivateSignal());
        return d_foo;
    }

signals:
    void fooUpdated(double, QPrivateSignal);

private:
    double d_foo;
    virtual bool   hwSetFoo(double f) = 0;
    virtual double hwReadFoo()        = 0;
};

class VendorAnalyzer : public Analyzer
{
public:
    VendorAnalyzer(const QString& label, QObject *parent = nullptr)
        : Analyzer(QString(VendorAnalyzer::staticMetaObject.className()),
                   label, parent) {}
private:
    bool   hwSetFoo(double f) override;
    double hwReadFoo()        override;
};

Each driver is also registered at static initialization with REGISTER_HARDWARE_META, REGISTER_HARDWARE_PROTOCOLS, and (where the driver introduces settings) REGISTER_HARDWARE_SETTINGS; see hardwareregistration.h and HardwareRegistry.

Configuration flags

Drivers may set the following flags in their constructors:

  • d_threaded — if true, the HardwareManager moves the object to a dedicated QThread. Threaded objects must not have a QObject parent at construction; this is also why their child QObject members are constructed in initialize() rather than in the constructor itself.

  • d_critical — if true (the default), a hardwareFailure() emission aborts any active experiment and blocks new ones until bcTestConnection() succeeds again. If false, the object is simply marked disconnected and the experiment continues. The flag is also user-editable through the hardware settings dialog.

  • d_commType — the active CommunicationProtocol type. The set of protocols a driver supports is declared via REGISTER_HARDWARE_PROTOCOLS; the user picks one at profile creation time and the persisted value is loaded by the base constructor.

Drivers whose CommunicationProtocol type is CommunicationProtocol::Custom additionally declare any user-supplied connection parameters (device path, serial number, etc.) following the convention documented in CustomInstrument.

Lifecycle

HardwareObject instances are not bound to program lifetime. The HardwareManager creates and destroys them in response to loadout activation, profile edits, and protocol changes. After construction the manager calls bcInitInstrument(), which builds the CommunicationProtocol object, calls initialize(), and wires up hardwareFailure routing. Drivers must implement initialize(): it is the place to construct child QObject s (the constructor cannot, for threaded drivers) and perform any one-shot setup. Per-connection work belongs in testConnection() instead, since connection attempts happen many times over the lifetime of an instance.

Connection testing

Drivers must implement testConnection(): it should attempt some cheap interaction with the device (typically an *IDN? query) to confirm both that something is responding and that it is the expected hardware. On failure, store a descriptive message in d_errorString and return false. testConnection() is called from the bcTestConnection() wrapper, which first reloads settings from SettingsStorage; the override therefore sees up-to-date settings and may read them freely.

Optional virtual hooks

Drivers may override these hooks to participate in the experiment lifecycle and the auxiliary-data system:

  • validationKeys() — keys whose values appear on the experiment setup dialog’s Validation page.

  • sleep() — switch the device to/from a low-power state.

  • beginAcquisition() / endAcquisition() — start- and end-of-experiment hooks.

  • prepareForExperiment() — validate and stage per-experiment settings; called via the hwPrepareForExperiment() wrapper.

  • readAuxData() — values to plot on the Aux/Rolling tabs and to persist as auxiliary data.

  • readValidationData() — values to verify post-acquisition, separate from the plotted aux data.

  • readSettings() — refresh cached state when the user accepts the hardware settings dialog.

Each is documented at the per-method level below.

Threading

Functions intended to be invoked from outside the object — for example by HardwareManager during an acquisition — must be declared as Qt slots so they reach the object via queued connection when the driver is threaded.

API Reference

class HardwareObject : public QObject, public SettingsStorage

Abstract base class for all hardware connected to the instrument.

Identity: an instance carries a hardware type (e.g. "AWG", "FtmwDigitizer") and a label distinguishing multiple instances of the same type. They combine to form d_key (e.g. "PulseGenerator.main"), which is also the SettingsStorage group holding the instance’s persistent settings. The key cannot be reused across drivers; changing a driver requires deleting and rebuilding the profile. d_model is a separate display-only field carrying the driver class name, persisted inside the settings group.

Lifecycle: instances are not bound to program lifetime. The HardwareManager creates and destroys them in response to loadout activation, profile edits, and protocol changes. After construction the manager calls bcInitInstrument(), which builds the CommunicationProtocol, calls initialize(), and wires up hardwareFailure routing. Drivers must implement initialize() (the place to construct child QObjects, since the constructor cannot for threaded drivers) and testConnection() (a cheap interaction such as an *IDN? query that confirms the expected hardware is responding; store a message in d_errorString and return false on failure). Per-connection work belongs in testConnection(); one-shot setup belongs in initialize(). Drivers register at static initialization with REGISTER_HARDWARE_META, REGISTER_HARDWARE_PROTOCOLS, and (where they introduce settings) REGISTER_HARDWARE_SETTINGS; see hardwareregistration.h.

Configuration flags settable in the constructor:

  • d_threaded — if true, the HardwareManager moves the object to a dedicated QThread. Threaded objects must not have a QObject parent and must not create child QObjects in their constructors; construct children inside initialize() instead.

  • d_critical — if true (the default), a hardwareFailure() emission aborts any active experiment and blocks new ones until bcTestConnection() succeeds again. If false, the object is simply marked disconnected and the experiment continues. Also user-editable through the hardware settings dialog.

  • d_commType — the active CommunicationProtocol type. The set of protocols a driver supports is declared via REGISTER_HARDWARE_PROTOCOLS; the user picks one at profile creation time and the persisted value is loaded by the base constructor. Drivers whose protocol is CommunicationProtocol::Custom declare additional user-supplied connection parameters following the convention documented in CustomInstrument.

Optional virtual hooks: drivers may override validationKeys(), sleep(), beginAcquisition() / endAcquisition(), prepareForExperiment(), readAuxData(), readValidationData(), and hwReadSettings() to participate in the experiment lifecycle and the auxiliary-data system. Each is documented at the per-method level below.

Threading: functions intended to be invoked from outside the object — for example by HardwareManager during an acquisition — must be declared as Qt slots so they reach the object via queued connection when the driver is threaded.

Subclassed by AWG, Clock, FlowController, FtmwDigitizer, GpibController, IOBoard, LifDigitizer, LifLaser, PressureController, PulseGenerator, TemperatureController

Public Functions

explicit HardwareObject(const QString &hwType, const QString &hwImpl, const QString &label, QObject *parent = nullptr)

Constructor.

Combines hwType and label into d_key (the unique identifier and SettingsStorage group for this instance), stores hwImpl in d_model for display, loads the persisted critical/protocol values, and applies registry-supplied setting defaults.

Parameters:
  • hwType – Hardware type, by convention the interface class’s metaobject name (e.g., "FtmwDigitizer").

  • hwImpl – Driver class name, stored in d_model for display (e.g., "VirtualFtmwDigitizer").

  • label – User-supplied label distinguishing instances of the same type.

  • parent – QObject parent. Must be nullptr if the driver sets d_threaded to true in its constructor.

virtual ~HardwareObject()
QString errorString()

Returns most recent error message.

Returns:

Error message

inline bool isConnected() const

Returns whether the instrument is currently connected.

Does not attempt to communicate with the device; returns the cached d_isConnected value, which is set to true when testConnection() returns true and reset to false when hardwareFailure() is emitted.

See also

bcTestConnection

Returns:

Whether the device is connected.

inline virtual QStringList validationKeys() const

Returns list of validation keys.

Validation keys indicate settings which can be checked on the Validation page of the experiment setup dialog. This function should be overriden to return keys that will be used with the readValidationData() function.

The default implementation returns an empty list.

Returns:

List of validation key strings.

inline void purgeSettings()

Removes all persisted settings for this hardware profile.

Calls SettingsStorage::purge() to delete the entire QSettings group for this hardware object. Should be called before deleteLater() when permanently removing a hardware profile.

QVector<CommunicationProtocol::CommType> supportedProtocols() const

Returns the list of supported communication protocols.

Looks the protocol set up in HardwareRegistry by this instance’s hardware type and implementation key. The set is populated at static-initialization time by REGISTER_HARDWARE_PROTOCOLS; change a driver’s supported protocols by editing that macro invocation in the driver’s .cpp file, not by overriding this function. Returns {CommunicationProtocol::Virtual} when no protocols are registered for the implementation.

Returns:

Vector of supported CommunicationProtocol::CommType values.

Public Members

const QString d_key

Unique identifier and SettingsStorage group: “hwType.label”. Fixed at profile creation.

const QString d_model

Driver class name, used for display only (e.g., “AWG70002a”).

bool d_critical

Whether a communication error should abort an experiment.

bool d_threaded

Whether the object should have its own thread of execution.

CommunicationProtocol::CommType d_commType

Type of communication

Public Slots

void bcInitInstrument()

Wrapper for one-shot initialization after construction.

Called by the HardwareManager after the object has been moved to its thread (if d_threaded is true) and the thread has started. It:

  1. builds the CommunicationProtocol via buildCommunication() and calls its initialize();

  2. calls the driver’s initialize() override;

  3. wires the hardwareFailure() signal to clear d_isConnected.

Connection testing is dispatched separately via bcTestConnection().

See also

initialize

void bcTestConnection()

Wrapper for testing hardware communication.

Called from the HardwareManager when the user requests a connection test from the UI, and from the connection-testing pass that follows hardware creation. Reloads settings from disk via bcReadSettings(), tests the CommunicationProtocol’s underlying QIODevice, then calls the driver’s testConnection() override. The result is stored in d_isConnected and reported via the connected() signal.

See also

testConnection

void bcReadAuxData()

Wrapper for reading aux and validation data.

Calls readAuxData() and readValidationData(), emitting auxDataRead() and validationDataRead() with whatever each returns. No-op if the device is not connected.

void bcReadSettings()

Wrapper for reloading settings from disk.

Called whenever the user closes the hardware settings dialog or a connection test is initiated. Refreshes the in-memory SettingsStorage cache, updates d_critical and d_commType, restarts the rolling-data timer, and then dispatches to hwReadSettings() so the driver (or its intermediate base) can refresh its own cached state.

See also

hwReadSettings

virtual void sleep(bool b)

Puts device into a standby mode.

The default implementation does nothing.

Parameters:

b – If true, go into standby mode. Else, active mode.

virtual bool hwPrepareForExperiment(Experiment &exp)

Per-experiment preparation wrapper.

If the device is currently disconnected, reattempts the connection; if that still fails and d_critical is true, marks the experiment with an error and returns false. Otherwise delegates to prepareForExperiment(). Interface classes override this when they need to perform setup that runs even for non-critical disconnected devices (registering keys, writing derived settings, etc.).

Todo:

Split into two virtuals so interface classes do not have to override both this and prepareForExperiment().

Parameters:

expExperiment carrying the requested per-device settings; may be mutated to record actual settings or an error string.

Returns:

Whether preparation succeeded.

inline virtual void beginAcquisition()

Hook called when an experiment begins.

Drivers may override to start acquisition (for example, an AWG starts playing waveforms; an FtmwDigitizer arms for trigger events). The default implementation does nothing.

inline virtual void endAcquisition()

Hook called when an experiment ends.

Called whether the experiment completed normally or was aborted. Drivers may override to stop acquisition (for example, halting AWG playback). The default implementation does nothing.

inline virtual void storeOptHwConfig(Experiment *exp)

Append this device’s configuration to an experiment.

Called by HardwareManager::storeAllOptHw for each optional-hardware instance whose loadout flag is set. The default implementation does nothing; optional-hardware base classes (PulseGenerator, FlowController, IOBoard, PressureController, TemperatureController) override it to call exp->addOptHwConfig() with their typed config object.

Declared as a slot so HardwareManager can invoke it via a blocking queued connection when the driver runs on its own thread.

Parameters:

expExperiment to populate.

inline virtual void prepareForShutdown()

Quiesce a threaded driver before its thread is torn down.

Called by HardwareManager via Qt::BlockingQueuedConnection immediately before thread->quit() during destruction or runtime removal. The default implementation does nothing; drivers that own recurring poll timers should override to stop them so no further slots fire on the worker thread once shutdown is in progress. This matters for drivers whose polled slots block on a nested QEventLoop (e.g. PythonProcess::sendRequest) — without quiescence, a fresh request can be initiated while the destruction sequence is already running, leading to use-after-free when the loop returns.

Declared as a slot so HardwareManager can invoke it via a blocking queued connection when the driver runs on its own thread.

bool setCommProtocol(CommunicationProtocol::CommType commType, QObject *gc = nullptr)

Sets communication protocol at runtime.

Allows changing the communication protocol after construction. The protocol must be supported as declared by supportedProtocols(). This will rebuild the communication object with the new protocol.

Parameters:
  • commType – New communication protocol type

  • gc – Pointer to the GpibController object for GPIB devices

Returns:

True if protocol change was successful

void buildCommunication(QObject *gc = nullptr, CommunicationProtocol::CommType commType = CommunicationProtocol::None)

(Re)build the CommunicationProtocol object.

Constructs the CommunicationProtocol subclass that matches commType (or d_commType if commType is CommunicationProtocol::None) into p_comm, replacing any prior instance, and forwards p_comm’s hardwareFailure() to this object’s hardwareFailure().

Parameters:
  • gc – Pointer to the GpibController, used only when the selected protocol is GPIB.

  • commType – New protocol type, or CommunicationProtocol::None to use the persisted d_commType.

Signals

void connected(bool success, QString msg, QPrivateSignal)

Indicates whether a connection is successful.

Parameters:
  • success – True if connection is successful

  • msg – If unsuccessful, an optional message to display on the log tab along with the failure message.

void hardwareFailure()

Signal emitted when communication with the device fails.

Emitting this signal sets d_isConnected to false. If d_critical is true the active experiment is aborted and new experiments are blocked until bcTestConnection() succeeds.

void auxDataRead(AuxDataStorage::AuxDataMap, QPrivateSignal)

Signal containing data for Aux Data plots.

The AuxDataStorage::AuxDataMap payload holds the key/value pairs for Aux Data.

See also

readAuxData

void validationDataRead(AuxDataStorage::AuxDataMap, QPrivateSignal)

Signal containing data for experiment validation.

The AuxDataStorage::AuxDataMap payload holds the key/value pairs for experiment validation.

void rollingDataRead(AuxDataStorage::AuxDataMap, QPrivateSignal)

Signal containing data for Rolling Data plots.

This signal is emitted on a timer whose timeout interval is controlled by the user. When the timer fires, readAuxData() is called and the results are emitted in this signal as an AuxDataStorage::AuxDataMap of key/value pairs.

Protected Functions

inline void hwLog(QAnyStringView text)

Log a message to the global LogHandler with this device’s d_key prepended.

inline void hwWarn(QAnyStringView text)

Log a warning to the global LogHandler with this device’s d_key prepended.

inline void hwError(QAnyStringView text)

Log an error to the global LogHandler with this device’s d_key prepended.

inline void hwDebug(QAnyStringView text)

Log a debug message to the global LogHandler with this device’s d_key prepended.

inline virtual bool prepareForExperiment(Experiment &exp)

Validate and stage per-experiment settings.

Drivers override this to read the requested per-device settings out of exp, push them to the device, and write back the settings actually applied (which may differ from what was requested). On failure, store a descriptive message in exp.d_errorString and return false. The default implementation returns true.

Parameters:

exp – Mutable Experiment carrying per-device settings.

Returns:

Whether preparation succeeded.

virtual void initialize() = 0

One-shot initialization run after construction.

Drivers must implement this even if there is nothing to do. It is the place to construct child QObjects (the constructor cannot, for threaded drivers) and perform any setup that should run once per instance lifetime. Per-connection work belongs in testConnection() instead.

See also

bcInitInstrument

virtual bool testConnection() = 0

Attempt to communicate with the device.

Drivers must implement this. Send a cheap test interaction (typically an *IDN? query) to confirm the device is responsive and is the expected hardware. On failure, store a descriptive message in d_errorString.

See also

bcTestConnection

Returns:

Whether the connection attempt succeeded.

void timerEvent(QTimerEvent *event) override

Rolling-data timer handler.

On each tick of the rolling-data timer (configured by the user via BC::Key::HW::rInterval), calls readAuxData() and emits rollingDataRead() with the result.

Parameters:

event – Timer event that triggered the call.

Protected Attributes

QString d_errorString

Last error message; consumed by errorString().

bool d_enabledForExperiment

Whether the device is active in the current experiment.

CommunicationProtocol *p_comm

Active communication protocol; built by buildCommunication().

Private Functions

void applyRegisteredSettings(const QString &hwType)

Apply registered setting defaults from HardwareRegistry.

Called from the base constructor to apply any settings registered via REGISTER_HARDWARE_SETTINGS and REGISTER_HARDWARE_ARRAY macros. Uses setDefault() for scalar settings (preserves existing values) and setArray() for array settings (only if array doesn’t exist yet).

Parameters:

hwType – Hardware type key (e.g., “FtmwDigitizer”, “AWG”)

virtual AuxDataStorage::AuxDataMap readAuxData()

Perform read of auxiliary data.

Derived classes may override this function to return data to be displayed on the aux and/or rolling data plots.

Returns:

AuxDataStorage::AuxDataMap (an alias for std::map<QString,QVariant,std::less<>>) containing key-value pairs.

virtual AuxDataStorage::AuxDataMap readValidationData()

Perform read of validation data.

Derived classes may override this function to return any additional data that can be used for experiment validation which is not already returned in readAuxData(). This may, for example, consist of digital data that is not appropriate for graphical presentation.

Returns:

AuxDataStorage::AuxDataMap (an alias for std::map<QString,QVariant,std::less<>>) containing key-value pairs.

inline virtual void hwReadSettings()

Driver hook for refreshing values from SettingsStorage.

Called from bcReadSettings() after the in-memory SettingsStorage cache has been refreshed from disk and the global per-instance fields (d_critical, d_commType, rolling-data timer) have been updated. Drivers and intermediate hardware bases override this to pick up any user-edited values relevant to their state — for example a poll-interval, a channel-count, or a per-driver Python-script hook.

Intermediate bases (FlowController, PressureController, TemperatureController, PulseGenerator) override this as final and dispatch to a per-base hook (fcReadSettings, pcReadSettings, tcReadSettings, pgReadSettings) so derived drivers cannot accidentally bypass the base-class work by forgetting to chain.

Private Members

bool d_isConnected

Contains whether device’s last communication was successful.

int d_rollingDataTimerId = {-1}

ID for rolling data timerEvent

Friends

friend class HardwareCommunicationTest