Capabilities JSON Schema

Capabilities JSON Schema

The CONFigure:CAPabilities:JSON? SCPI query returns a single-shot, machine-readable description of what the connected device can do — channels, ranges, streaming limits, transports, power, storage. It is intended for client tools that need to discover device capabilities at connect time without a long sequence of individual SCPI queries.

The full schema is JSON-only and is exposed only over SCPI (USB CDC or WiFi TCP). There is no protobuf equivalent today.

  • Command: CONFigure:CAPabilities:JSON?
  • Short form: CONF:CAP:JSON?
  • Response: single-line JSON document (~5 KB on NQ1, scales with channel count)
  • Schema version: 2
  • Schema URI: https://daqifi.com/schemas/capability/v1

The schema is versioned via two top-level fields the client should always read first:

Field Type Meaning
schema_version uint Integer revision. Bumped on any breaking change. Currently 2.
schema_uri string URI for the published schema. Stable identifier, may be used for offline schema lookup.
extensions object Reserved for non-standard top-level fields. Always present, often empty.

Top-level structure

{
  "schema_version": 2,
  "schema_uri": "https://daqifi.com/schemas/capability/v1",
  "extensions": {},
  "identity":  { ... },
  "channels":  [ ... ],
  "streaming": { ... },
  "storage":   { ... },
  "power":     { ... },
  "transports":{ ... },
  "triggers":  { ... }
}

Every section also has its own extensions: {} object — clients should ignore unknown keys inside extensions rather than fail.

identity

Static device identification. Read once at connect; cache.

Field Type Notes
vendor string Always "DAQiFi".
model string Always "Nyquist" for this firmware family.
variant string Hardware variant: "NQ1", "NQ2", or "NQ3".
serial string 16-character hex serial. Stable across firmware updates.
firmware_rev string Semantic version of running firmware (e.g. "3.4.6b1").
hardware_rev string Board hardware revision (e.g. "2.0.0").
usb.vid uint USB Vendor ID, decimal.
usb.pid uint USB Product ID, decimal.
usb.class string "CDC" (only USB device class supported).

channels

Array. Each element is either an analog input or a digital I/O channel. The element shape depends on kind. id is scoped per-kind, not global — there is an id:0 analog-input AND an id:0 digital-io. Clients should index on (kind, id), not id alone.

Analog input (kind: "analog-input")

{
  "id": 0,
  "kind": "analog-input",
  "signal_type": "voltage",
  "unit": "V",
  "resolution_bits": 12,
  "simultaneous": false,
  "differential": false,
  "ranges": [{ "min": 0.000, "max": 5.000 }],
  "calibration": {
    "model": "linear",
    "user_override_supported": true,
    "slope": 1.000000,
    "intercept": 0.000000
  },
  "extensions": {}
}
Field Type Notes
id uint Channel index within the analog-input kind (0..N-1).
signal_type string "voltage" for normal channels; "temperature" for the on-die monitor channel.
unit string "V" for voltage, "Cel" for temperature.
resolution_bits uint ADC resolution — 12 on NQ1 (MC12b), 18 on NQ2/NQ3 (AD7609).
simultaneous bool true = channel has its own dedicated SAR ADC and converts in lockstep with other simultaneous channels (zero inter-channel skew). false = channel is on the shared mux / scan ADC.
differential bool true = channel terminal supports true differential measurement (single hardware pair → one reading). On NQ1 hardware revision 2.0.0 this is false for every channel — the board routing does not present a working differential path. AD7609-based variants (NQ2/NQ3) currently also report false; populating this correctly per board wiring is a known follow-up (the AD7609 silicon is true-differential, but the board may wire it single-ended, so per-channel validation is required). Clients should always read this flag rather than infer it from the variant name.
ranges[] array of {min, max} Voltage range at the input terminal in volts. NQ1 (MC12b): single-ended unipolar [0, 5]. AD7609 variants: bipolar [-5, 5] or [-10, 10] per AD7609.Range10V.
calibration.model string Always "linear" today.
calibration.user_override_supported bool Whether CONFigure:ADC:chanCALM / chanCALB can override factory cal.
calibration.slope float m in volts = m·counts + b.
calibration.intercept float b in volts = m·counts + b.

The pair (slope, intercept) is the authoritative raw→volts conversion; ranges is informational. If a client computes voltages itself, it should use slope/intercept and treat ranges as a hint for plotting axes.

Digital I/O (kind: "digital-io")

{
  "id": 0,
  "kind": "digital-io",
  "features": {
    "input": true,
    "output": true,
    "pwm": { "min_freq_hz": 1, "max_freq_hz": 50000, "resolution_bits": 16 }
  },
  "extensions": {}
}
Field Type Notes
id uint Channel index within the digital-io kind. The DAQiFi firmware invariant is that DIO array index == user-facing channel number.
features.input bool Channel can be configured as input.
features.output bool Channel can be configured as output.
features.pwm object | absent Present only on PWM-capable channels. On NQ1: channels 0, 3, 4, 5, 6, 7.
features.pwm.min_freq_hz uint Lowest PWM frequency the channel can produce.
features.pwm.max_freq_hz uint Highest PWM frequency.
features.pwm.resolution_bits uint Duty-cycle resolution.

streaming

Describes the streaming-data subsystem (the protobuf / CSV / JSON sample stream invoked via SYSTem:StartStreamData).

{
  "encodings": ["pb", "csv", "json"],
  "transports": ["usb", "wifi", "sd"],
  "sample_rate_range_hz": { "min": 1, "max": 13000 },
  "conservative_envelope_hz": 500,
  "current_max_rate_hz": 0,
  "rate_model": {
    "formula": "min(absolute_max_hz, type1_aggregate_max_hz/simultaneous_count, per_tick_budget_hz/(per_tick_overhead+total_count))",
    "absolute_max_hz": 13000,
    "type1_aggregate_max_hz": 55000,
    "per_tick_budget_hz": 110000,
    "per_tick_overhead": 6
  },
  "rate_validation": "silent_cap",
  "buffer_ranges_bytes": {
    "usb":         { "min": 2048, "max": 65536, "default": 16384 },
    "wifi":        { "min": 1400, "max": 65536, "default": 14000 },
    "sd":          { "min": 4096, "max": 65536, "default": 32768 },
    "encoder":     { "min": 1024, "max": 65536, "default":  8192 },
    "sample_pool": { "min":  100, "max":  2000, "default":  1100 }
  },
  "test_patterns": [0, 1, 2, 3, 4, 5, 6],
  "extensions": {}
}
Field Type Notes
encodings[] string array Which sample-frame encodings the device can emit. Selected via SYSTem:STReam:FORmat.
transports[] string array Which output transports are wired up on this hardware (USB CDC, WiFi TCP, SD card).
sample_rate_range_hz.min uint Lowest accepted streaming frequency.
sample_rate_range_hz.max uint Highest accepted frequency before rate-model capping kicks in.
conservative_envelope_hz uint A frequency the client can request without checking the rate model and expect zero drops across all variants and channel configs. NQ1: 500 Hz. Treat as the “always works” floor for product UI.
current_max_rate_hz uint Live cap given the currently enabled channel set. 0 if nothing enabled (idle). Recompute by re-querying capabilities after changing the channel mask.
rate_model object Closed-form formula and constants the client can use to predict the cap without round-tripping a live query for every UI scrub. See “rate_model” section below.
rate_validation string "silent_cap" — if the client requests above the model’s predicted cap, the firmware silently caps to a safe value rather than rejecting the request. The value the firmware actually adopted is observable via current_max_rate_hz after starting.
buffer_ranges_bytes object Allowed [min, max, default] for each tunable buffer. Used by the SYSTem:MEMory:*:BUFfer knobs.
test_patterns[] uint array Pattern IDs accepted by SYSTem:STReam:TEST:PATtern. 0 = real ADC data, 16 = synthetic patterns for regression testing.

rate_model

The firmware caps the requested streaming frequency using a closed-form formula. The client can predict the cap without asking the device by evaluating:

max_hz = min(
    absolute_max_hz,
    type1_aggregate_max_hz / simultaneous_count,
    per_tick_budget_hz / (per_tick_overhead + total_count)
)

where:

  • simultaneous_count = number of enabled channels with simultaneous: true
  • total_count = number of all enabled channels (analog + digital input streams the user has armed)

The constants (absolute_max_hz, type1_aggregate_max_hz, per_tick_budget_hz, per_tick_overhead) are emitted alongside the formula so the client doesn’t have to hardcode device-specific values. They were measured against real hardware (per-board characterization sweeps in firmware/src/HAL/) and may be retuned in future firmware revisions.

If the client wants the truthful current cap rather than recomputing it, read current_max_rate_hz (live, no recompute needed).

storage

{
  "sd_supported": true,
  "filesystems": ["FAT32"],
  "max_file_size_bytes": 4190109695,
  "extensions": {}
}
Field Type Notes
sd_supported bool Whether the SD card slot is wired up and usable.
filesystems[] string array FAT32 only today.
max_file_size_bytes uint64 Effective per-file maximum. The streaming engine auto-rolls over after this size.

power

{
  "sources": ["usb", "external", "battery"],
  "battery_present": true,
  "external_power_supported": true,
  "otg_output_supported": true,
  "extensions": {}
}
Field Type Notes
sources[] string array Power sources the device CAN run from. Not a live status — for the live source, query the BQ24297 status registers.
battery_present bool True if a battery is wired (NQ1 has it; some test fixtures don’t).
external_power_supported bool True if the board accepts external 5V via the auxiliary jack.
otg_output_supported bool True if the board can act as a USB OTG host (provide 5V on the data port).

transports

Per-transport availability and configuration constants.

{
  "usb":   { "supported": true, "extensions": {} },
  "wifi": {
    "supported": true,
    "bands": ["2.4GHz"],
    "modes": ["sta", "ap"],
    "security": ["open", "wpa", "wpa2"],
    "tcp_command_port": 9760,
    "udp_announce_port": 30303,
    "extensions": {}
  },
  "ethernet":     { "supported": false, "extensions": {} },
  "serial_debug": { "supported": true, "baud": 921600, "extensions": {} }
}
Field Type Notes
usb.supported bool Always true on the current product family.
wifi.supported bool Whether the WINC1500 module is populated and functioning.
wifi.bands[] string array ["2.4GHz"] only — WINC1500 doesn’t do 5 GHz.
wifi.modes[] string array Supported WiFi modes. "sta" = station, "ap" = soft-AP.
wifi.security[] string array WPA-Enterprise is not supported.
wifi.tcp_command_port uint Default port the device listens on for SCPI-over-TCP when associated.
wifi.udp_announce_port uint Port the discovery announcement is sent to.
ethernet.supported bool Always false today; reserved for future variants.
serial_debug.supported bool Whether the ICSP UART debug port is exposed.
serial_debug.baud uint UART baud rate when enabled.

triggers

{
  "hardware_inputs": [],
  "software": true,
  "extensions": {}
}
Field Type Notes
hardware_inputs[] array Hardware trigger pin descriptors. Empty [] on NQ1 (no dedicated trigger pin). NQ3 may populate this in future revisions.
software bool Whether software-triggered streaming start (SYSTem:STReam:START) is supported. Always true today.

Versioning

schema_version will be bumped on any breaking change — for example, removing a field, renaming a field, changing a field’s semantic meaning, or changing a value type. New fields can appear without bumping the version; clients must tolerate unknown fields and must not error on them.

The extensions: {} object at every level is reserved for non-standard fields. Clients should treat the contents as opaque and forward them through unchanged when re-serializing.

If a future firmware version needs to ship two schema variants in parallel (e.g. for backwards compatibility during a migration), the URI in schema_uri will distinguish them.

Client integration patterns

Discovery on connect. The recommended pattern is to issue CONFigure:CAPabilities:JSON? once on first connect, parse the result, and cache the relevant pieces. Re-issue only after a firmware update or after the application detects the device serial number has changed.

Live values that change. Two fields explicitly track current state and should be re-queried on demand rather than cached:

  • streaming.current_max_rate_hz — changes whenever channels are enabled/disabled.
  • calibration.slope / calibration.intercept — change when user calibration is updated via CONFigure:ADC:chanCALM / chanCALB.

For everything else (identity, channel topology, transports, capabilities), the values are stable for the firmware revision and can be cached for the lifetime of the connection.

(kind, id) keying. Always key lookups on the tuple (kind, id), not id alone. Both analog-input and digital-io start counting from 0.

Forward compatibility. Treat unknown fields as opaque, unknown keys in extensions as opaque, and never error on them. Clients that need strict schema enforcement should pin to a specific schema_version and reject anything else.

Range vs calibration. When converting raw ADC counts to volts, use calibration.slope and calibration.intercept. The ranges[] field is a hint about the terminal voltage range for UI plotting; it does not encode the raw→volts transform on its own.

Differential availability. differential: true means the hardware path is wired and validated for differential measurement on that channel. On NQ1 hardware revision 2.0.0 this is false for every analog channel. AD7609-based variants (NQ2/NQ3) report true on differential-wired channels. Clients should always read this flag rather than infer it from the variant name.

Wire format

The response is a single line of JSON terminated by \r\n. There are no leading or trailing whitespace characters and no pretty-printing. Pretty-print after parsing if needed for human display.

The full response on NQ1 is approximately 5 KB. WiFi TCP transport is fine; USB CDC is fine. The firmware emits the JSON in chunks via scpi_printf with a 192-byte buffer per chunk, but the wire response is a single concatenated line.

Example response

NQ1 hardware revision 2.0.0 with default channel configuration:

{"schema_version":2,"schema_uri":"https://daqifi.com/schemas/capability/v1","extensions":{},"identity":{"vendor":"DAQiFi","model":"Nyquist","variant":"NQ1","serial":"7E2898F46200E8A7","firmware_rev":"3.4.6b1","hardware_rev":"2.0.0","usb":{"vid":1240,"pid":63380,"class":"CDC"}},"channels":[{"id":0,"kind":"analog-input","signal_type":"voltage","unit":"V","resolution_bits":12,"simultaneous":false,"differential":false,"ranges":[{"min":0.000,"max":5.000}],"calibration":{"model":"linear","user_override_supported":true,"slope":1.000000,"intercept":0.000000},"extensions":{}}, ... ]}

(Truncated — the full response is ~5 KB and contains 16 analog-input + 16 digital-io entries plus the streaming/storage/power/transports/triggers sections.)

Related SCPI commands

  • *IDN? — short-form identity (less detailed than identity{} here).
  • SYSTem:INFo? — verbose human-readable device info dump.
  • CONFigure:ADC:CHANnel — enable/disable a channel; affects streaming.current_max_rate_hz.
  • CONFigure:ADC:chanCALM / chanCALB — change per-channel calibration; affects calibration.slope/intercept.
  • SYSTem:STReam:STATS? — runtime streaming statistics.

Implementation references

  • Firmware module: firmware/src/services/Capabilities.c / .h
  • SCPI handler: SCPI_CapabilitiesJsonGet in firmware/src/services/SCPI/SCPIInterface.c
  • Schema design history: GitHub PR #343, issue #327, issue #344 (rate_model phase 1+2)

Source of truth: daqifi/daqifi-nyquist-firmware wiki → 03-Capabilities-JSON-Schema · Edit on GitHub · this page is auto-synced; edit upstream to update.