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:
pythonScriptPathAbsolute path to the
.pyscript that implements the driver. Persisted underpythonScriptPathin QSettings. Read by the Python hardware trampoline when constructing the object.pythonClassNameName 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.pythonEnvPathPath to the virtual environment or conda environment directory (
<env>/bin/python3). Persisted underpythonEnvPath. An empty string means the systempython3executable 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
-
enumerator NoCollision
-
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)
-
enumerator Valid
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 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)
-
QString implementation
-
enum CollisionAction
-
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
-
HardwareProfileData() = default