HardwareProfileManager

HardwareProfileManager is the singleton that persists and manages hardware profiles. A profile ties a user-chosen label (e.g., "frontPanel") to a hardware-type/driver pair and stores whether it is active, along with timestamps and an optional description. Profiles use human-readable keys of the form <Type>.<label> ("FlowController.frontPanel", "FlowController.backup") so that hardware identity survives reconfiguration.

Profiles are stored under the HardwareProfiles QSettings group, using the path <Type>/<label>/<subkey>. Every mutating operation is thread-safe via an internal QReadWriteLock. Profile data is loaded from QSettings during construction and flushed on destruction; saveProfiles() and loadProfiles() allow explicit control when needed.

Labels must be non-empty, start with a letter, contain only letters, digits, and hyphens, and not exceed 64 characters. validateLabel() and isValidLabel() enforce these rules; generateDefaultLabel() produces a collision-free label drawn from the candidate list Default, Main, Primary, Secondary, Backup (falling back to <Type>1, <Type>2, … if all are taken). Mutating operations return explicit success/failure values rather than substituting defaults, so callers must handle errors.

Storage layout and usage

Profiles live under the HardwareProfiles QSettings group, with the path <Type>/<label>/<subkey>:

[HardwareProfiles]
FlowController/frontPanel/implementation=mks647c
FlowController/frontPanel/active=true
FlowController/frontPanel/created=2024-01-15T10:30:00
FlowController/frontPanel/description=Main flow controller
FlowController/backup/implementation=virtual
FlowController/backup/active=false

Typical mutation/query sequence:

HardwareProfileManager manager;

// Create profiles with meaningful labels.
QString label1 = manager.createHardwareProfile("FlowController", "mks647c", "frontPanel");
QString label2 = manager.createHardwareProfile("FlowController", "virtual", "backup");

// Query.
QStringList active = manager.getActiveProfiles("FlowController");
QString impl       = manager.getImplementation("FlowController", "frontPanel");

// Manage state.
manager.deactivateHardwareProfile("FlowController", "backup");
manager.deleteHardwareProfile("FlowController", "backup");

Per-profile Python fields

Three fields stored per profile support Python-backed hardware drivers:

pythonScriptPath

Absolute path to the .py script that implements the driver. Persisted under pythonScriptPath in QSettings. Read by the Python hardware trampoline when constructing the object.

pythonClassName

Name of the Python class inside the script. Persisted under pythonClassName. Must be a valid Python identifier that inherits from the appropriate Blackchirp Python base class.

pythonEnvPath

Path to the virtual environment or conda environment directory (<env>/bin/python3). Persisted under pythonEnvPath. An empty string means the system python3 executable is used.

These fields are only meaningful for Python driver keys; non-Python drivers leave them empty. Python hardware is described in Python Hardware.

System profiles

Some hardware types are required for Blackchirp to operate (e.g., the FTMW clock and digitizer). For each required type, HardwareProfileManager guarantees a system profile — a profile with the label virtual backed by the corresponding virtual driver. ensureSystemProfiles() creates these on startup if they are missing, and isSystemProfile() flags them so the UI can prevent deletion or relabeling.

Relationship to RuntimeHardwareConfig

HardwareProfileManager owns all profile metadata. RuntimeHardwareConfig tracks which profiles are active — i.e., participating in the current hardware configuration — and is refreshed to match the manager when profiles are activated or deactivated. The profile creation and management UI is described in Hardware and Library Configuration.

API Reference

class HardwareProfileManager : public SettingsStorage

Singleton manager for hardware profiles.

A profile is a (hardware type, label, implementation) triple — fixed at creation — together with its persisted settings. The hardware type and label form the profile’s identity (e.g. "FlowController.frontPanel"); the implementation key is stored as a profile field but is immutable, so changing the implementation requires creating a new profile under a new label. Profile identities survive hardware reconfiguration and are the canonical reference for a hardware object’s settings group.

Labels are unique within a hardware type and reusable across types. Persistence rides on SettingsStorage; all operations are thread-safe via an internal QReadWriteLock (multiple readers, exclusive writer). Mutating operations return explicit success/failure values rather than substituting defaults, so callers must handle errors.

Public Types

enum CollisionAction

Collision resolution strategies when labels conflict.

Values:

enumerator NoCollision

No collision detected

enumerator Rename

Automatically rename the new profile

enumerator Replace

Replace the existing profile

enumerator Restore

Keep existing profile, discard new

enumerator Cancel

Cancel the operation

enum LabelValidationError

Label validation error types.

Values:

enumerator Valid

Label is valid

enumerator Empty

Label is empty or whitespace only

enumerator TooLong

Label exceeds maximum length

enumerator InvalidCharacters

Label contains invalid characters

enumerator StartsWithNumber

Label starts with a number

enumerator StartsWithUnderscore

Label starts with underscore

enumerator ContainsDots

Label contains dots (conflicts with key format)

Public Functions

virtual ~HardwareProfileManager()

Destructor - saves profiles to persistent storage.

QString createHardwareProfile(const QString &type, const QString &implementation, const QString &requestedLabel = QString(), CollisionAction collisionAction = Rename)

Create a new hardware profile.

Creates a new profile with the specified implementation and label. If requestedLabel is empty, a default label will be auto-generated. If requestedLabel conflicts with existing profile, collision resolution depends on the collisionAction parameter.

Parameters:
  • type – Hardware type (e.g., “FlowController”, “FtmwDigitizer”)

  • implementation – Implementation key (e.g., “mks647c”, “virtual”)

  • requestedLabel – Desired label for the profile (auto-generated if empty)

  • collisionAction – How to handle label collisions (default: Rename)

Returns:

Actual label used for the profile, or empty string if creation failed

bool deleteHardwareProfile(const QString &type, const QString &label)

Delete a hardware profile.

Parameters:
  • type – Hardware type

  • label – Profile label to delete

Returns:

True if profile was deleted successfully

bool activateHardwareProfile(const QString &type, const QString &label)

Activate a hardware profile.

Parameters:
  • type – Hardware type

  • label – Profile label to activate

Returns:

True if profile was activated successfully

bool deactivateHardwareProfile(const QString &type, const QString &label)

Deactivate a hardware profile.

Parameters:
  • type – Hardware type

  • label – Profile label to deactivate

Returns:

True if profile was deactivated successfully

bool profileExists(const QString &type, const QString &label) const

Check if a profile exists.

Parameters:
  • type – Hardware type

  • label – Profile label

Returns:

True if profile exists

bool isProfileActive(const QString &type, const QString &label) const

Check if a profile is active.

Parameters:
  • type – Hardware type

  • label – Profile label

Returns:

True if profile exists and is active

bool isLabelAvailable(const QString &type, const QString &label) const

Check if a label is available for use within a hardware type.

Parameters:
  • type – Hardware type

  • label – Label to check

Returns:

True if label is available (not already used in this type)

QString generateDefaultLabel(const QString &type) const

Generate a default label for a hardware type.

Picks the first available label from a fixed candidate list: “Default”, “Main”, “Primary”, “Secondary”, “Backup”. If all of those are taken, falls back to “{type}1”, “{type}2”, etc.

Parameters:

type – Hardware type

Returns:

Generated default label that is available

QStringList getExistingLabels(const QString &type) const

Get all existing labels for a hardware type.

Parameters:

type – Hardware type

Returns:

List of labels currently used by this hardware type

LabelValidationError validateLabel(const QString &label) const

Validate a label according to naming rules.

Parameters:

label – Label to validate

Returns:

Validation result indicating if label is valid or why it’s invalid

bool isValidLabel(const QString &label) const

Check if a label is valid (convenience function)

Parameters:

label – Label to check

Returns:

True if label passes all validation rules

inline int getMaxLabelLength() const

Get maximum allowed label length.

Returns:

Maximum number of characters allowed in a label

QStringList getActiveProfiles(const QString &type) const

Get all active profiles for a hardware type.

Parameters:

type – Hardware type

Returns:

List of labels for active profiles

QStringList getInactiveProfiles(const QString &type) const

Get all inactive profiles for a hardware type.

Parameters:

type – Hardware type

Returns:

List of labels for inactive profiles

QStringList getAllProfiles(const QString &type) const

Get all profiles (active and inactive) for a hardware type.

Parameters:

type – Hardware type

Returns:

List of all labels for this hardware type

QString getImplementation(const QString &type, const QString &label) const

Get implementation for a specific profile.

Parameters:
  • type – Hardware type

  • label – Profile label

Returns:

Implementation key, or empty string if profile doesn’t exist

std::optional<bool> getThreaded(const QString &type, const QString &label) const

Get threading override for a specific profile.

Parameters:
  • type – Hardware type

  • label – Profile label

Returns:

Threading override, or nullopt if no override is stored (use type-level default)

bool setThreaded(const QString &type, const QString &label, bool threaded)

Set threading override for a specific profile.

Parameters:
  • type – Hardware type

  • label – Profile label

  • threaded – Threading override value

Returns:

True if successfully set

QString getPythonScriptPath(const QString &type, const QString &label) const

Get Python script path for a specific profile.

Parameters:
  • type – Hardware type

  • label – Profile label

Returns:

Python script path, or empty string if not set or profile doesn’t exist

bool setPythonScriptPath(const QString &type, const QString &label, const QString &path)

Set Python script path for a specific profile.

Parameters:
  • type – Hardware type

  • label – Profile label

  • path – Python script path

Returns:

True if successfully set

QString getPythonClassName(const QString &type, const QString &label) const

Get Python class name for a specific profile.

Parameters:
  • type – Hardware type

  • label – Profile label

Returns:

Python class name, or empty string if not set or profile doesn’t exist

bool setPythonClassName(const QString &type, const QString &label, const QString &name)

Set Python class name for a specific profile.

Parameters:
  • type – Hardware type

  • label – Profile label

  • name – Python class name

Returns:

True if successfully set

QString getPythonEnvPath(const QString &type, const QString &label) const

Get Python environment path for a specific profile.

Parameters:
  • type – Hardware type

  • label – Profile label

Returns:

Path to venv/conda environment directory, or empty string if not set

bool setPythonEnvPath(const QString &type, const QString &label, const QString &path)

Set Python environment path for a specific profile.

Parameters:
  • type – Hardware type

  • label – Profile label

  • path – Path to venv/conda environment directory (empty = system python3)

Returns:

True if successfully set

QStringList getConfiguredHardwareTypes() const

Get all hardware types that have profiles.

Returns:

List of hardware types with at least one profile

QDateTime getProfileCreationTime(const QString &type, const QString &label) const

Get profile creation timestamp.

Parameters:
  • type – Hardware type

  • label – Profile label

Returns:

Creation timestamp, or invalid QDateTime if profile doesn’t exist

QDateTime getProfileLastModified(const QString &type, const QString &label) const

Get profile last modified timestamp.

Parameters:
  • type – Hardware type

  • label – Profile label

Returns:

Last modified timestamp, or invalid QDateTime if profile doesn’t exist

bool setProfileDescription(const QString &type, const QString &label, const QString &description)

Set profile description.

Parameters:
  • type – Hardware type

  • label – Profile label

  • description – User description of the profile

Returns:

True if description was set successfully

QString getProfileDescription(const QString &type, const QString &label) const

Get profile description.

Parameters:
  • type – Hardware type

  • label – Profile label

Returns:

Profile description, or empty string if not set or profile doesn’t exist

CollisionAction detectCollision(const QString &type, const QString &label, const QString &implementation) const

Detect if creating a profile would cause a collision.

Parameters:
  • type – Hardware type

  • label – Requested label

  • implementation – Implementation key

Returns:

Collision type detected

QString resolveCollisionByRename(const QString &type, const QString &baseLabel) const

Resolve label collision by generating alternative label.

Parameters:
  • type – Hardware type

  • baseLabel – Base label that caused collision

Returns:

Alternative label that is available

bool activateAllProfiles(const QString &type)

Activate all profiles for a hardware type.

Parameters:

type – Hardware type

Returns:

True if all profiles were activated successfully

bool deactivateAllProfiles(const QString &type)

Deactivate all profiles for a hardware type.

Parameters:

type – Hardware type

Returns:

True if all profiles were deactivated successfully

bool deleteAllProfiles(const QString &type)

Delete all profiles for a hardware type.

Parameters:

type – Hardware type

Returns:

True if all profiles were deleted successfully

void clearAllProfiles()

Clear all profiles from all hardware types.

QByteArray exportProfiles() const

Export all profiles to binary data.

Returns:

Serialized profile data suitable for storage or transfer

QByteArray exportProfiles(const QString &type) const

Export profiles for specific hardware type.

Parameters:

type – Hardware type to export

Returns:

Serialized profile data for the specified type

bool importProfiles(const QByteArray &data, CollisionAction collisionAction = Rename)

Import profiles from binary data.

Parameters:
  • data – Serialized profile data (from exportProfiles)

  • collisionAction – How to handle label collisions during import

Returns:

True if import was successful

bool importProfile(const HardwareProfileData &profileData, CollisionAction collisionAction = Rename)

Import a single profile.

Parameters:
  • profileData – Profile data to import

  • collisionAction – How to handle label collisions

Returns:

True if profile was imported successfully

HardwareProfileData getProfileData(const QString &type, const QString &label) const

Get profile data for export/backup purposes.

Parameters:
  • type – Hardware type

  • label – Profile label

Returns:

Profile data structure, or invalid data if profile doesn’t exist

void saveProfiles()

Force save all profiles to persistent storage.

Profiles are automatically saved when the manager is destroyed, but this method allows explicit saving.

void loadProfiles()

Load profiles from persistent storage.

Called automatically during construction, but can be used to reload from storage if external changes occurred.

bool hasUnsavedChanges() const

Check if profiles have been modified since last save.

Returns:

True if there are unsaved changes

void ensureSystemProfiles()

Ensure system profiles exist for all required hardware types.

Called at application startup and when the hardware config dialog opens. Creates a “virtual” profile with the appropriate virtual implementation for each required hardware type that does not already have one.

Required type -> virtual implementation mapping: FtmwDigitizer -> VirtualFtmwDigitizer Clock -> FixedClock LifDigitizer -> VirtualLifDigitizer (when LIF enabled) LifLaser -> VirtualLifLaser (when LIF enabled)

Public Static Functions

static HardwareProfileManager &instance()

Get singleton instance.

All operations are thread-safe due to internal QReadWriteLock usage.

Returns:

Reference to the singleton instance

static bool isSystemProfile(const QString &hwType, const QString &label)

Check if a profile is a system-protected profile that cannot be removed.

Parameters:
  • hwType – Hardware type string

  • label – Profile label

Returns:

True if this is a system profile (label == “virtual” for a required hardware type)

Private Functions

void loadProfilesFromSettings()

Load profiles from SettingsStorage into memory structures.

void saveProfilesToSettings()

Save profiles from memory structures to SettingsStorage.

void setModified()

Mark profiles as modified (thread-safe)

LabelValidationError validateLabelInternal(const QString &label) const

Internal label validation implementation.

Parameters:

label – Label to validate

Returns:

Validation error or Valid

CollisionAction detectCollisionInternal(const QString &type, const QString &label, const QString &implementation) const

Internal collision detection (assumes lock is held)

Parameters:
  • type – Hardware type

  • label – Requested label

  • implementation – Implementation key

Returns:

Collision type

bool createProfileInternal(const QString &type, const QString &implementation, const QString &label)

Internal profile creation (assumes lock is held)

Parameters:
  • type – Hardware type

  • implementation – Implementation key

  • label – Profile label (must be validated and available)

Returns:

True if profile was created

QString generateDefaultLabelInternal(const QString &type) const

Internal default label generation (assumes lock is held)

Parameters:

type – Hardware type

Returns:

Generated label

void updateModificationTime(const QString &type, const QString &label)

Update profile modification timestamp (assumes lock is held)

Parameters:
  • type – Hardware type

  • label – Profile label

QString resolveCollisionByRenameInternal(const QString &type, const QString &baseLabel) const

Internal collision resolution (assumes caller holds lock)

Parameters:
  • type – Hardware type

  • baseLabel – Base label that caused collision

Returns:

Alternative label that is available

HardwareProfileManager()

Private constructor for singleton pattern.

HardwareProfileManager(const QString &orgName, const QString &appName)

Private constructor with custom organization and application names.

Parameters:
  • orgName – Organization name for QSettings

  • appName – Application name for QSettings

HardwareProfileManager(const HardwareProfileManager&) = delete
HardwareProfileManager &operator=(const HardwareProfileManager&) = delete

Private Members

mutable QReadWriteLock d_profilesLock
QHash<QString, QHash<QString, ProfileInfo>> d_profiles
mutable QMutex d_modifiedFlagLock
bool d_modified = false

Private Static Attributes

static HardwareProfileManager *s_instance = nullptr

Singleton instance

Friends

friend class HardwareProfileManagerTest
struct ProfileInfo

Internal profile data structure.

Public Functions

inline ProfileInfo()
inline ProfileInfo(const QString &impl, bool act = true)

Public Members

QString implementation

Implementation key

bool active = true

Whether profile is active

QDateTime created

Creation timestamp

QDateTime modified

Last modified timestamp

QString description

User description

std::optional<bool> threaded

Threading override (nullopt = use type-level default)

QString pythonScriptPath

Python script path (only used by Python hardware types)

QString pythonClassName

Python class name (only used by Python hardware types)

QString pythonEnvPath

Python environment directory (venv/conda; empty = system python3)

struct HardwareProfileData

Portable snapshot of one hardware profile for import and export operations.

Carries the type, label, implementation, active state, timestamps, and description that together identify and describe a single hardware profile. Used by HardwareProfileManager::exportProfiles() and importProfiles().

Public Functions

HardwareProfileData() = default
inline HardwareProfileData(const QString &t, const QString &l, const QString &i, bool a = true)

Public Members

QString type

Hardware type (e.g., “FlowController”)

QString label

User-defined label (e.g., “mainDevice”)

QString implementation

Implementation key (e.g., “mks647c”)

bool active = true

Whether profile is active

QDateTime created

Profile creation timestamp

QDateTime modified

Profile last modified timestamp

QString description

User description of the profile