AcquisitionManager

AcquisitionManager is the object that drives a single Experiment from the moment hardware initialization completes through the end of the acquisition loop. It owns the in-progress experiment shared pointer, accumulates FID waveforms and auxiliary sensor readings into the per-experiment storage tree, and emits the lifecycle signals that coordinate the main window, the hardware layer, and the batch layer. All acquisitions in Blackchirp — single-experiment runs as well as multi-experiment batch sequences — pass through AcquisitionManager. The user-facing acquisition workflow is described in Experiment Setup.

The manager lives on a dedicated QThread ("AcquisitionManagerThread") created in MainWindow. Its slots and signal emissions execute on that thread. The GUI thread uses QMetaObject::invokeMethod to cross the thread boundary when calling AcquisitionManager::beginExperiment(). Waveform processing is further dispatched to the Qt thread pool via QtConcurrent::run; the processing result is handed back to the AM thread through a QFutureWatcher<FtmwProcessingResult> so that state mutations remain thread-confined.

The immediate collaborators are:

  • Experiment — the in-progress experiment shared pointer; the AM writes FID data and aux readings into its storage objects and queries it for completion status.

  • HardwareManager (cross-thread) — receives beginAcquisition and endAcquisition to start and stop hardware triggers; provides aux data via its auxData signal; applies clock settings in response to newClockSettings.

  • FidStorageBase — the FID storage layer embedded in the experiment; waveform data accumulate here.

  • AuxDataStorage — the auxiliary sensor storage embedded in the experiment; each timed aux-data point is committed here.

  • BatchManager (GUI thread, queued connection) — receives experimentComplete after every acquisition loop ends and decides whether to start the next experiment.

  • LifStorage (LIF mode only) — the LIF storage object embedded in the experiment when a LIF scan is configured.

State machine

AcquisitionManager tracks its phase through the AcquisitionState enum.

State

Meaning

Idle

No experiment is running. The manager is ready for a new beginExperiment call.

Acquiring

An experiment is active. Waveform data, aux readings, and validation checks are processed as they arrive.

Paused

Acquisition is suspended. Incoming data are dropped until resume() is called.

The typical lifecycle of a single experiment is:

  1. MainWindow::experimentInitialized receives HardwareManager::experimentInitialized, calls Experiment::initialize(), and invokes beginExperiment(exp) on the AM thread.

  2. beginExperiment transitions to Acquiring, emits newClockSettings (FTMW experiments), emits beginAcquisition, starts the aux-data interval timer, and starts the FTMW drain timer for buffered-waveform modes.

  3. During the loop, the aux-data timer fires periodically; each firing calls auxDataTick, which collects FTMW shot counts (and optional phase/chirp metrics), calls processAuxData, and emits auxDataSignal so HardwareManager can read sensor values.

  4. processAuxData stores the readings in AuxDataStorage and re-emits them as auxData(map, timestamp) for plot widgets.

  5. processValidationData checks each incoming sensor value against its configured limit; a violation triggers abort.

  6. FTMW waveforms arrive through a WaveformBuffer polled by the drain timer. Batches of entries are dispatched to a worker via QtConcurrent::run; the QFutureWatcher<FtmwProcessingResult> delivers the result back to the AM thread. A processing failure calls abort.

  7. checkComplete is called after each processing batch. If Experiment::canBackup is true it launches a concurrent backup via QtConcurrent::run (the QFutureWatcher<void> emits backupComplete when done). If Experiment::isComplete is true it calls finishAcquisition.

  8. pause and resume toggle between Acquiring and Paused without stopping hardware. While Paused the drain timer still fires but skips processing.

  9. abort marks the experiment as aborted and calls finishAcquisition.

  10. finishAcquisition stops the drain timer, signals the worker to exit, waits for the worker to finish, emits endAcquisition, transitions to Idle, calls Experiment::finalSave, and emits experimentComplete.

Note

AcquisitionManager::experimentComplete is a signal; the identically named BatchManager::experimentComplete is a slot. The two are connected via a queued connection that crosses the AM thread into the GUI thread, and this signal-to-slot handoff is what advances a batch from one experiment to the next.

Backups

The acquisition loop launches periodic backups of the in-progress experiment on a worker thread via QtConcurrent::run. QFutureWatcher raises the backupComplete signal on the AM thread when each backup finishes, and FtmwViewWidget connects to it to refresh its list of available backups.

API Reference

struct FtmwProcessingResult

Result of a worker-thread waveform-processing batch.

Returned by the QtConcurrent worker to the AcquisitionManager event loop so that side-effect operations (advance, signals, abort) remain on the AM thread. The struct is the sole cross-thread data contract between the waveform-processing worker and the manager.

Public Members

int entriesProcessed = {0}

Number of waveform entries processed in this batch.

bool success = {true}

false if a fatal processing error occurred.

QString errorString

Non-empty when success is false; describes the error.

QString warningString

Non-empty when a non-fatal anomaly was detected.

class AcquisitionManager : public QObject

Drives a single Experiment from hardware initialization through acquisition-loop completion.

AcquisitionManager owns the in-progress Experiment shared pointer, accumulates FID data and auxiliary sensor readings into the per-experiment storage tree, and emits the signals that notify the main window, HardwareManager, and BatchManager of lifecycle events.

The manager lives on a dedicated QThread (“AcquisitionManagerThread”) created in MainWindow. All public slots and signal emissions execute on that thread. Callers on other threads must use QMetaObject::invokeMethod or queued signal/slot connections — direct calls from the GUI thread are not safe.

Waveform processing is dispatched to the Qt thread pool via QtConcurrent::run; the result is returned to the AM thread through a QFutureWatcher so that all state mutations remain thread-confined.

Public Types

enum AcquisitionState

Describes the current phase of the acquisition loop.

Values:

enumerator Idle

No experiment is running; the manager accepts a new beginExperiment call.

enumerator Acquiring

An experiment is active and data are being collected.

enumerator Paused

Acquisition is temporarily suspended; no new data are processed.

Public Functions

explicit AcquisitionManager(QObject *parent = nullptr)

Constructs an AcquisitionManager with the given parent.

~AcquisitionManager()

Destroys the manager, waiting for any in-flight waveform worker to finish.

Public Slots

void beginExperiment(std::shared_ptr<Experiment> exp)

Begins acquiring data for the given experiment.

Called by MainWindow::experimentInitialized() via QMetaObject::invokeMethod after hardware initialization succeeds. Sets the state to Acquiring, emits beginAcquisition(), starts the aux-data timer if configured, and starts the FTMW drain timer for buffered waveform modes.

Parameters:

exp – Shared pointer to the fully initialized experiment. The manager holds this pointer for the duration of the acquisition loop.

void processAuxData(AuxDataStorage::AuxDataMap m)

Incorporates a map of aux-sensor readings into the current experiment.

Stores the readings in the experiment’s AuxDataStorage and forwards the data map via the auxData() signal. If storage fails, abort() is called. No-op when the state is not Acquiring.

Parameters:

m – Map of sensor key to measured value returned by HardwareManager.

void processValidationData(AuxDataStorage::AuxDataMap m)

Validates a map of sensor readings against the experiment’s limit set.

Calls Experiment::validateItem() for each entry. If any value exceeds its configured limit, abort() is called. No-op when the state is not Acquiring.

Parameters:

m – Map of sensor key to measured value returned by HardwareManager.

void clockSettingsComplete(const QHash<RfConfig::ClockType, RfConfig::ClockFreq> clocks)

Records the actual clock frequencies applied by hardware.

Called when HardwareManager emits allClocksReady. Stores the confirmed frequencies in the FTMW configuration and marks hardware as ready so waveform acquisition can proceed.

Parameters:

clocks – Map from clock type to the frequency actually programmed by hardware.

void pause()

Suspends data processing without stopping hardware triggers.

Transitions from Acquiring to Paused. Incoming waveform data and aux readings are dropped until resume() is called. No-op if the state is not Acquiring.

void resume()

Resumes data processing after a pause.

Transitions from Paused back to Acquiring. No-op if the state is not Paused.

void abort()

Aborts the current experiment and ends the acquisition loop.

Marks the experiment as aborted, then calls finishAcquisition(). Safe to call from Acquiring or Paused. No-op from Idle.

void processLifDigitizerShot(const QVector<qint8> b)

Accumulates a raw LIF waveform shot into the current LIF scan point.

Adds the waveform to the LIF configuration, emits lifPointUpdate(), and advances to the next scan point if the current point is complete. Calls checkComplete() to detect overall experiment completion.

Parameters:

b – Raw 8-bit waveform bytes from the LIF digitizer.

void lifHardwareReady(bool success)

Notifies the manager of the outcome of a LIF hardware configuration step.

If success is false, the experiment is aborted. If true, the LIF configuration is marked hardware-ready so shot acquisition can proceed.

Parameters:

successtrue if the LIF delay and frequency were set successfully.

void requestBackup()

Requests an immediate backup of the current FID list.

Connected to FtmwViewWidget’s manual backup toolbar action via a queued signal/slot wire. The request is silently ignored if no experiment is active, if the FTMW mode does not support backups (only Target_Shots, Target_Duration, and Forever do), or if the experiment is in the Idle state. If an automatic backup is already in flight, the request is folded into that backup — the in-flight write becomes the user’s manual snapshot for reporting purposes.

Signals

void logMessage(QString msg, LogHandler::MessageCode code = LogHandler::Normal)

Requests that a message be appended to the application log.

Parameters:
  • msg – The text to log.

  • code – Severity classification; defaults to LogHandler::Normal.

void statusMessage(QString msg, int timeout = 0)

Requests that a transient status string be displayed in the status bar.

Parameters:
  • msg – The status text.

  • timeout – Display duration in milliseconds; 0 means indefinite.

void experimentComplete()

Emitted when the acquisition loop for the current experiment has ended.

Emitted after endAcquisition(), after the experiment is saved to disk, and before the experiment shared pointer is released. BatchManager::experimentComplete() is connected to this signal so the batch can decide whether to advance.

void ftmwUpdateProgress(int perMil)

Reports FTMW acquisition progress to the progress bar.

Parameters:

perMil – Progress in units of 1/1000 of completion (0–1000).

void newClockSettings(QHash<RfConfig::ClockType, RfConfig::ClockFreq> clocks)

Notifies HardwareManager that clock frequencies should be applied.

Emitted at experiment start and whenever an FTMW segment boundary is crossed.

Parameters:

clocks – Map from clock type to target frequency.

void beginAcquisition()

Notifies HardwareManager that data collection should begin.

Connected to HardwareManager::beginAcquisition. Emitted after clock settings have been dispatched at the start of beginExperiment().

void endAcquisition()

Notifies HardwareManager that data collection has ended.

Emitted inside finishAcquisition() before the state transitions to Idle. HardwareManager uses this to stop hardware triggers.

void auxDataSignal()

Requests that HardwareManager read and return aux sensor data.

Emitted on each aux-data timer tick. HardwareManager responds by emitting its auxData signal, which is connected to processAuxData().

void auxData(AuxDataStorage::AuxDataMap data, QDateTime timestamp)

Delivers a completed aux-data point to observers such as the plot widget.

Parameters:
  • data – Map of sensor key to measured value.

  • timestamp – Acquisition time of the point.

void backupComplete()

Emitted when a concurrent backup operation launched via QtConcurrent has finished.

Connected to the FtmwViewWidget so that the view can refresh its backup list.

void lifPointUpdate()

Notifies the LIF display that the current LIF point has new data.

void nextLifPoint(double delay, double frequency)

Requests that HardwareManager configure LIF hardware for the next scan point.

Parameters:
  • delay – Delay value in seconds for the next LIF point.

  • frequency – Laser frequency in wavenumbers for the next LIF point.

void lifShotAcquired(int perMil)

Reports LIF acquisition progress to the LIF progress bar.

Parameters:

perMil – Progress in units of 1/1000 of completion (0–1000).

Protected Functions

void timerEvent(QTimerEvent *event) override

Handles the periodic aux-data timer tick.

On each firing of the aux-data interval timer, reads FTMW shot counts and optional phase/chirp metrics, then calls processAuxData() and emits auxDataSignal().

Parameters:

event – The timer event; accepted if it matches the aux-data timer ID.

Private Functions

void auxDataTick()
void checkComplete()
void finishAcquisition()
void drainFtmwBuffer()
void onProcessingComplete()
void dispatchBackup()
void onBackupFinished()
void temporaryStatusMessage(const QString &msg, int msecs)

Emits a timed status message and queues the persistent “Acquiring”/”Paused” label to be restored when it expires.

Use for transient notifications (e.g. backup completions) that should briefly replace the acquisition-state label without leaving the bar blank afterward.

bool ftmwModeSupportsBackup() const

Private Members

std::unique_ptr<QFutureWatcher<void>> pu_fw
std::unique_ptr<QFutureWatcher<FtmwProcessingResult>> pu_processingWatcher
std::shared_ptr<Experiment> ps_currentExperiment
AcquisitionState d_state
int d_auxTimerId
QTimer *p_drainTimer = {nullptr}
std::atomic<bool> d_abortProcessing = {false}
bool d_lastBackupWasManual = {false}

Set by requestBackup() / dispatchBackup() so onBackupFinished() can pick the right log/status message.