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— iftrue, theHardwareManagermoves the object to a dedicatedQThread. Threaded objects must not have aQObjectparent at construction; this is also why their childQObjectmembers are constructed ininitialize()rather than in the constructor itself.d_critical— iftrue(the default), ahardwareFailure()emission aborts any active experiment and blocks new ones untilbcTestConnection()succeeds again. Iffalse, the object is simply marked disconnected and the experiment continues. The flag is also user-editable through the hardware settings dialog.d_commType— the activeCommunicationProtocoltype. The set of protocols a driver supports is declared viaREGISTER_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 thehwPrepareForExperiment()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 formd_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_modelis 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, callsinitialize(), and wires uphardwareFailurerouting. Drivers must implementinitialize()(the place to construct child QObjects, since the constructor cannot for threaded drivers) andtestConnection()(a cheap interaction such as an*IDN? query that confirms the expected hardware is responding; store a message ind_errorStringand return false on failure). Per-connection work belongs in testConnection(); one-shot setup belongs in initialize(). Drivers register at static initialization withREGISTER_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 viaREGISTER_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(), andhwReadSettings()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
nullptrif 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
- 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.
See also
- 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:
builds the CommunicationProtocol via buildCommunication() and calls its initialize();
calls the driver’s initialize() override;
wires the hardwareFailure() signal to clear d_isConnected.
Connection testing is dispatched separately via bcTestConnection().
See also
-
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
-
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.
See also
-
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
-
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().
See also
- Parameters:
exp – Experiment 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:
exp – Experiment 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
GpibControllerobject 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
-
void validationDataRead(AuxDataStorage::AuxDataMap, QPrivateSignal)
Signal containing data for experiment validation.
The AuxDataStorage::AuxDataMap payload holds the key/value pairs for experiment validation.
See also
-
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.
See also
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_errorStringand return false. The default implementation returns true.See also
- 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
-
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
- 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
finaland 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