From scopy_dev_plugin
Defines C++ patterns for Scopy plugin API classes: ApiObject inheritance, Q_INVOKABLE tool/widget methods, friend access to IIOWidgetGroup, and plugin lifecycle integration. For API and test automation development.
npx claudepluginhub analogdevicesinc/scopy --plugin scopy_dev_pluginThis skill uses the workspace's default tool permissions.
Every plugin API class follows this pattern:
Validates Scopy plugin API classes against 7 rule categories for inheritance, Qt integration, null safety, attribute conversions, coverage, helpers, and naming.
Designs public API surfaces for Python libraries: defines __all__, exception hierarchies, async/sync patterns like httpx _BaseClient, pluggy plugins, progressive disclosure, naming, and backward compatibility.
Generates standalone Markdown reference documentation for Qt/C++ source files including Qt classes, utilities, structs, free functions, headers, and main.cpp.
Share bugs, ideas, or general feedback.
Every plugin API class follows this pattern:
#include "scopy-<plugin>_export.h"
#include <pluginbase/apiobject.h>
namespace scopy::<plugin> {
class <Plugin>Plugin; // Forward declaration
class SCOPY_<PLUGIN>_EXPORT <Plugin>_API : public ApiObject
{
Q_OBJECT
public:
explicit <Plugin>_API(<Plugin>Plugin *plugin);
~<Plugin>_API();
// Tool management — always included
Q_INVOKABLE QStringList getTools();
// Typed getters/setters for key attributes
Q_INVOKABLE QString getAttribute();
Q_INVOKABLE void setAttribute(const QString &value);
// Generic widget access for all registered widgets
Q_INVOKABLE QStringList getWidgetKeys();
Q_INVOKABLE QString readWidget(const QString &key);
Q_INVOKABLE void writeWidget(const QString &key, const QString &value);
// Utility
Q_INVOKABLE void refresh();
private:
QString readFromWidget(const QString &key);
void writeToWidget(const QString &key, const QString &value);
<Plugin>Plugin *m_plugin;
};
} // namespace scopy::<plugin>
The API class accesses private plugin members via friend class:
// In plugin header (<plugin>plugin.h):
class <Plugin>Plugin : public QObject, PluginBase {
// ... existing code ...
friend class <Plugin>_API;
private:
<Plugin>_API *m_api = nullptr;
void initApi();
};
Never add public getters to plugin or instrument classes just for the API. Use friend instead.
void <Plugin>Plugin::initApi()
{
m_api = new <Plugin>_API(this);
m_api->setObjectName("<plugin>"); // lowercase, short
ScopyJS::GetInstance()->registerApi(m_api);
}
bool <Plugin>Plugin::onConnect()
{
// ... existing setup code ...
initApi(); // LAST line before return
return true;
}
bool <Plugin>Plugin::onDisconnect()
{
if(m_api) { delete m_api; m_api = nullptr; } // FIRST line
// ... existing cleanup code ...
return true;
}
QString <Plugin>_API::readFromWidget(const QString &key)
{
if(!m_plugin->m_widgetGroup) {
qWarning(CAT_<PLUGIN>_API) << "Widget manager not available";
return QString();
}
IIOWidget *widget = m_plugin->m_widgetGroup->get(key);
if(!widget) {
qWarning(CAT_<PLUGIN>_API) << "Widget not found for key:" << key;
return QString();
}
QPair<QString, QString> result = widget->read();
return result.first;
}
void <Plugin>_API::writeToWidget(const QString &key, const QString &value)
{
if(!m_plugin->m_widgetGroup) {
qWarning(CAT_<PLUGIN>_API) << "Widget manager not available";
return;
}
IIOWidget *widget = m_plugin->m_widgetGroup->get(key);
if(!widget) {
qWarning(CAT_<PLUGIN>_API) << "Widget not found for key:" << key;
return;
}
widget->writeAsync(value);
}
| Attribute Type | Getter | Setter |
|---|---|---|
| Frequency | getRxLoFrequency() | setRxLoFrequency(value) |
| Mode/Enum | getEnsmMode() | setEnsmMode(mode) |
| Boolean enable | isTrackingEnabled() | setTrackingEnabled(value) |
| Per-channel | getRxGain(int channel) | setRxGain(int channel, value) |
| Read-only | getRxRssi(int channel) | (no setter) |
Keys follow the pattern: <device>/<channel>_<direction>/<attribute>
Examples:
ad9361-phy/ensm_mode (device-level attribute)ad9361-phy/voltage0_in/rf_bandwidth (channel attribute)ad9361-phy/altvoltage0_out/frequency (LO frequency)Q_INVOKABLE method must null-check m_plugin members before access.cpp must end with #include "moc_<plugin>_api.cpp"Q_LOGGING_CATEGORY(CAT_<PLUGIN>_API, "<Plugin>_API") for warningsQString() (empty) on error, never crashQString (JS compatibility)const QString &IIOWidget::writeAsync() writes directly to the IIO data strategy — it does NOT apply
m_UItoDS. Similarly, read().first returns the raw IIO value, not the UI-displayed value.
Getters and setters MUST replicate the conversion logic from setDataToUIConversion /
setUItoDataConversion lambdas found in the tool class. Before writing API methods, read the
tool implementation (<plugin>.cpp) and catalogue every widget's conversion lambdas.
Frequency (Hz ↔ MHz):
// Getter: divide raw Hz string by 1e6
QString getXxx() {
QString raw = readFromWidget(key);
if(raw.isEmpty()) return raw;
return QString::number(raw.toDouble() / 1e6, 'f', 3);
}
// Setter: multiply MHz value by 1e6 before writing
void setXxx(const QString &val) {
writeToWidget(key, QString::number(val.toDouble() * 1e6, 'f', 0));
}
IIO unit suffix (e.g. " dB" appended by IIO driver to hardwaregain):
// Getter: strip the trailing suffix before returning
QString getXxx() {
QString raw = readFromWidget(key);
int idx = raw.indexOf(" dB");
if(idx != -1) raw = raw.left(idx).trimmed();
return raw;
}
// Setter: write numeric string only (IIO accepts without suffix)
void setXxx(const QString &val) { writeToWidget(key, val); }
dBFS ↔ linear scale (e.g. IIO stores 0.5 linear, UI shows 6 dBFS):
#include <cmath>
// Getter: linear → dBFS (round to nearest integer)
QString getXxx() {
QString raw = readFromWidget(key);
if(raw.isEmpty()) return raw;
double linear = raw.toDouble();
if(linear <= 0.0) return QString("0");
return QString::number(static_cast<int>(20.0 * std::log10(1.0 / linear) + 0.5));
}
// Setter: dBFS → linear
void setXxx(const QString &val) {
double linear = std::pow(10.0, -val.toDouble() / 20.0);
writeToWidget(key, QString::number(linear, 'g', 10));
}
Combo/enum widgets — validate before writing:
void setXxx(const QString &val)
{
static const QStringList options = {"opt1", "opt2", "opt3"};
if(!options.contains(val)) {
qWarning(CAT_...) << "Invalid value:" << val << "Valid:" << options;
return;
}
writeToWidget(key, val);
}
Note: If any attribute uses
std::log10orstd::pow(dBFS↔linear scale conversions), add#include <cmath>to the.cppfile.