| name | scopy-api-patterns |
| description | API class conventions for Scopy plugins including ApiObject inheritance, Q_INVOKABLE patterns, and IIOWidgetGroup access. Auto-loads when writing API classes or implementing test automation interfaces. |
Scopy API Class Patterns
API Class Structure
Every plugin API class follows this pattern:
#include "scopy-<plugin>_export.h"
#include <pluginbase/apiobject.h>
namespace scopy::<plugin> {
class <Plugin>Plugin;
class SCOPY_<PLUGIN>_EXPORT <Plugin>_API : public ApiObject
{
Q_OBJECT
public:
explicit <Plugin>_API(<Plugin>Plugin *plugin);
~<Plugin>_API();
Q_INVOKABLE QStringList getTools();
Q_INVOKABLE QString getAttribute();
Q_INVOKABLE void setAttribute(const QString &value);
Q_INVOKABLE QStringList getWidgetKeys();
Q_INVOKABLE QString readWidget(const QString &key);
Q_INVOKABLE void writeWidget(const QString &key, const QString &value);
Q_INVOKABLE void refresh();
private:
QString readFromWidget(const QString &key);
void writeToWidget(const QString &key, const QString &value);
<Plugin>Plugin *m_plugin;
};
}
Friend Class Access Pattern
The API class accesses private plugin members via friend class:
class <Plugin>Plugin : public QObject, PluginBase {
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.
Plugin Integration
initApi() implementation
void <Plugin>Plugin::initApi()
{
m_api = new <Plugin>_API(this);
m_api->setObjectName("<plugin>");
ScopyJS::GetInstance()->registerApi(m_api);
}
Lifecycle placement
bool <Plugin>Plugin::onConnect()
{
initApi();
return true;
}
bool <Plugin>Plugin::onDisconnect()
{
if(m_api) { delete m_api; m_api = nullptr; }
return true;
}
Private Helper Methods
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);
}
Getter/Setter Naming
| 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) |
Widget Key Format
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)
Rules
- Every
Q_INVOKABLE method must null-check m_plugin members before access
.cpp must end with #include "moc_<plugin>_api.cpp"
- Use
Q_LOGGING_CATEGORY(CAT_<PLUGIN>_API, "<Plugin>_API") for warnings
- Return
QString() (empty) on error, never crash
- All getter return types are
QString (JS compatibility)
- Setter parameters use
const QString &
IIOWidget Conversion Rules
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):
QString getXxx() {
QString raw = readFromWidget(key);
if(raw.isEmpty()) return raw;
return QString::number(raw.toDouble() / 1e6, 'f', 3);
}
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):
QString getXxx() {
QString raw = readFromWidget(key);
int idx = raw.indexOf(" dB");
if(idx != -1) raw = raw.left(idx).trimmed();
return raw;
}
void setXxx(const QString &val) { writeToWidget(key, val); }
dBFS ↔ linear scale (e.g. IIO stores 0.5 linear, UI shows 6 dBFS):
#include <cmath>
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));
}
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::log10 or std::pow (dBFS↔linear scale conversions),
add #include <cmath> to the .cpp file.