PythonHardwareBase

PythonHardwareBase turns a HardwareObject subclass into a Python-backed trampoline: a C++ shim whose virtuals forward to a child Python interpreter over JSON IPC. A concrete trampoline inherits both its hardware base class — AWG, Clock, FtmwDigitizer, and so on — and this mixin via multiple inheritance. The hardware base supplies the Qt slot/signal API the rest of Blackchirp uses; the mixin owns the subprocess and the IPC plumbing.

A subclass routes its hardware-base lifecycle hooks through the mixin: initialize calls initPythonProcess() to bind the comm pointer and the settings get/set callbacks; testConnection calls testPythonConnection(), which lazily starts the subprocess on the first invocation and dispatches the script’s test_connection method on every call; sleep and readSettings delegate to pythonSleep() and pythonReadSettings(). Hardware-specific virtuals translate to JSON dispatches through pu_process->sendRequest(). Push-style hardware additionally calls pu_process->setEnabledProxies() and connects waveformReceived to a shot handler.

The script path, class name, and Python environment directory are read on demand from HardwareProfileManager. startPythonProcess() refuses to start the subprocess if either the script path or the class name is empty rather than substituting a default. resolvePythonExecutable() probes the configured environment for the standard venv and conda layouts and falls back to the literal "python3" (resolved through PATH) when no environment is set; findHostScript() locates the IPC host script python_hw_host.py in the application directory or the share/blackchirp/ install location. The profile workflow and the user-side script API are documented in Selecting a Python Driver and Writing a Python Driver; the contributor- level architecture of the trampoline mixin and the IPC host is covered in Python Hardware.

API Reference

class PythonHardwareBase

Mixin that turns a HardwareObject subclass into a Python-backed trampoline by handing off lifecycle work to a child Python subprocess.

A trampoline is a C++ class that inherits from both a hardware base (e.g. AWG, Clock, FlowController, FtmwDigitizer) and PythonHardwareBase via multiple inheritance. The hardware base supplies the Qt slot/signal API; this mixin owns the subprocess and IPC plumbing. Subclasses initialize the mixin with d_key and d_model, call initPythonProcess() from their hardware-base initialize hook, call testPythonConnection() from the test-connection hook, delegate sleep and the read-settings hook (hwReadSettings or the per-base variant: fcReadSettings, pcReadSettings, tcReadSettings, pgReadSettings, awgReadSettings, clockReadSettings, ftmwReadSettings, gpibReadSettings, ioReadSettings, lifLaserReadSettings, lifDigitizerReadSettings) to pythonSleep() and pythonReadSettings(), and translate hardware-specific virtuals into pu_process->sendRequest() dispatches with snake_case method names matching the user script. Push-style hardware also calls setEnabledProxies() and connects pu_process->waveformReceived to a shot handler.

Script path, class name, and Python environment are resolved per- profile from HardwareProfileManager. startPythonProcess() refuses to start the subprocess if either the script path or the class name is empty rather than substituting a default.

Subclassed by PythonAwg, PythonClock, PythonFlowController, PythonFtmwDigitizer, PythonGpibController, PythonIOBoard, PythonLifDigitizer, PythonLifLaser, PythonPressureController, PythonPulseGenerator, PythonTemperatureController

Public Functions

explicit PythonHardwareBase(const QString &key, const QString &model)

Construct the mixin with the owning HardwareObject’s identity.

Parameters:
  • key – Hardware key in the form "<Type>.<label>" (e.g., "PythonAwg.Default"). Used to look up the profile’s script path, class name, and environment directory in HardwareProfileManager.

  • model – Driver class name (e.g., "PythonAwg") forwarded to the Python script as self.settings.model.

virtual ~PythonHardwareBase()

Stops the subprocess if it is running.

void stopProcess()

Stop the Python subprocess if it is running. Idempotent; safe to call from the destructor of a subclass.

inline QString pythonErrorString() const

Returns the human-readable error message produced by the most recent failed startPythonProcess() or testPythonConnection() call. Empty when the last operation succeeded.

Public Static Functions

static QString resolvePythonExecutable(const QString &envPath)

Resolve the Python interpreter path from a profile’s environment directory.

Probes envPath for the standard venv and conda layouts (bin/python3, bin/python, Scripts/python.exe). Falls back to the literal string "python3" — which the operating system resolves through PATH — if envPath is empty or no interpreter is found inside it.

Parameters:

envPath – Path to a venv or conda environment directory, or an empty string for the system Python.

Returns:

Absolute path to a Python executable, or "python3".

static QString findHostScript()

Locate the IPC host script (python_hw_host.py) shipped with Blackchirp.

Searches the application directory and the standard share/blackchirp/ install location.

Returns:

Absolute path to the host script, or an empty string if it is not found in any of the search paths.

Protected Functions

void initPythonProcess(CommunicationProtocol *comm, PythonProcess::SettingsGetter getter, PythonProcess::SettingsSetter setter)

Construct pu_process and bind the comm pointer and settings callbacks.

Call from the hardware base class’s initialize hook. Does not start the subprocess; that happens lazily on the first testPythonConnection() so the registry-driven default-settings pass on profile creation does not spawn a Python interpreter.

Parameters:
  • comm – Pointer to the trampoline’s CommunicationProtocol (p_comm). The pointer is forwarded into the PythonProcess and refreshed on every testPythonConnection(); ownership stays with the HardwareObject.

  • getter – Callback used to service self.settings.get relay requests. Typically a lambda capturing the trampoline’s SettingsStorage::get.

  • setter – Callback used to service self.settings.set relay requests. Typically a lambda capturing the trampoline’s SettingsStorage::set (which is protected, so the lambda is the bridge that lets the script update persistent settings).

bool testPythonConnection(CommunicationProtocol *comm)

Start the subprocess if needed, refresh the comm pointer, and dispatch test_connection.

Call from the hardware base class’s test-connection hook (testConnection, testClockConnection, fcTestConnection, …). On the first call, lazily starts the subprocess via startPythonProcess(); subsequent calls reuse the running process. Updates the bound comm pointer in case the protocol has been swapped since the last call.

Parameters:

comm – Pointer to the trampoline’s CommunicationProtocol (p_comm).

Returns:

true on success; false if the subprocess could not be started or the script reported a failure (pythonErrorString() then carries the diagnostic).

bool startPythonProcess()

Launch the Python subprocess for this trampoline.

Reads pythonScriptPath, pythonClassName, and pythonEnvPath for the profile keyed by the constructor’s key argument from HardwareProfileManager. Resolves the interpreter via resolvePythonExecutable() and the host script via findHostScript(), then delegates to PythonProcess::start. Refuses to start (and sets pythonErrorString()) if the script path or class name is empty rather than substituting a placeholder.

Returns:

true if the process started and completed its _init / initialize handshake.

void pythonSleep(bool b)

Forward sleep(b) to the Python script.

Trampolines that override the HardwareObject sleep() hook delegate to this helper.

void pythonReadSettings()

Forward read_settings to the running Python script.

Used by the trampoline’s per-base read-settings hook override to push post-edit settings back into the script without restarting the subprocess (a restart would re-run initialize() and disrupt any connected state).

Protected Attributes

std::unique_ptr<PythonProcess> pu_process

The owned PythonProcess. Non-null after initPythonProcess() returns; the subprocess inside it is started lazily on first testPythonConnection().

Private Members

const QString d_pyKey
const QString d_pyModel
QString d_pythonErrorString