Skip to content
Testing
Skill

/scopy-api-patterns

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.

BOOST
From plugin
scopy
50215 skills13 commands1 MCP
Install
$ npx -y skills add analogdevicesinc/scopy --skill scopy-api-patterns --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/scopy-api-patterns

Context preview

The summary Claude sees to decide when to auto-load this skill.

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.

SKILL.md

scopy-api-patterns.SKILL.md
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;  // 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>

Friend Class Access Pattern

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.

Plugin Integration

initApi() implementation

void <Plugin>Plugin::initApi()
{
    m_api = new <Plugin>_API(this);
    m_api->setObjectName("<plugin>");  // lowercase, short
    ScopyJS::GetInstance()->registerApi(m_api);
}

Lifecycle placement

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;
}

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):**

// 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 /
Read more
Ships withscopy

Scopy is a software oscilloscope and signal analysis toolset.

Get the whole plugin

Other skills on scopy.