Switching the schema per firmware generation

A single model often has units in the field on very different firmware. A newer generation may add a DSP block and drop a legacy audio section, so one flat schema either shows fields a device doesn't have or hides fields it does.

Instead of publishing a second model per firmware, keep one model and let the device tell you which shape of configuration it speaks. The device reports a schema_version value via set-config, and the schema swaps whole sections in and out based on it.

How it works

  • Each firmware generation gets its own subschema under definitions (v1, v2, …), holding only the properties that generation actually has.
  • One if / then block per generation lives inside allOf, and then is just a $ref to that generation's definition. Adding a third generation is one new definition plus one new if / then block — nothing existing changes.
  • The trigger value is free-form, so schema_version can be a version number ("2.0.0"), a plain label ("Old" / "New"), or a firmware/model codename ("Gen2"). List every accepted value in that branch's enum and they all select the same subschema.
  • Properties declared at the top level (outside allOf) are shared by every generation. A device reporting an unrecognised schema_version therefore still renders the common fields and simply gets no generation-specific sections.
  • Use allOf with if / then rather than a single if / then / else pair once you have more than two generations, and avoid a top-level oneOf — the editor renders oneOf as an option picker rather than switching sections.
⚠️

Every $id in the schema must be unique

$id values are registered document-wide. If two branches (then and else, or two definitions) declare the same $id — for example both defining "$id": "#/properties/about" — the schema fails to compile and the condition silently evaluates as false, so the form always falls through to the other branch with no error shown. Either give every branch a distinct $id or omit $id inside the branches entirely; it is not required for the form to render.

📘

Draft support

Conditional keywords (if / then / else, allOf, $ref) come from JSON Schema draft-07, which the model's Config Schema editor supports. Drafts above 7 are not supported.

Config Schema

{
  "type": "object",
  "title": "Device Configuration",
  "properties": {
    "schema_version": {
      "type": "string",
      "title": "Config Schema Version",
      "readOnly": true
    },
    "about": {
      "type": "object",
      "title": "About",
      "properties": {
        "model": { "type": "string", "title": "Model", "readOnly": true },
        "firmware": { "type": "string", "title": "Firmware", "readOnly": true }
      }
    }
  },
  "definitions": {
    "v1": {
      "properties": {
        "legacy_audio": {
          "type": "object",
          "title": "Audio (legacy firmware)",
          "properties": {
            "gain": { "type": "integer", "title": "Input Gain (dB)", "minimum": -20, "maximum": 20 },
            "mono_mixdown": { "type": "boolean", "title": "Mono Mixdown" }
          }
        }
      }
    },
    "v2": {
      "properties": {
        "dsp": {
          "type": "object",
          "title": "DSP (2.0 firmware)",
          "properties": {
            "eq_preset": { "type": "string", "title": "EQ Preset", "enum": ["flat", "voice", "music"] },
            "auto_gain": { "type": "boolean", "title": "Auto Gain Control" },
            "delay_ms": { "type": "integer", "title": "Output Delay (ms)", "minimum": 0, "maximum": 500 }
          }
        },
        "network": {
          "type": "object",
          "title": "Network (2.0 firmware)",
          "properties": {
            "dhcp": { "type": "boolean", "title": "DHCP" },
            "hostname": { "type": "string", "title": "Hostname" }
          }
        }
      }
    }
  },
  "allOf": [
    {
      "if": {
        "properties": { "schema_version": { "enum": ["1.0.0", "Old", "Gen1"] } },
        "required": ["schema_version"]
      },
      "then": { "$ref": "#/definitions/v1" }
    },
    {
      "if": {
        "properties": { "schema_version": { "enum": ["2.0.0", "New", "Gen2"] } },
        "required": ["schema_version"]
      },
      "then": { "$ref": "#/definitions/v2" }
    }
  ]
}

What the device reports

The device sets its configuration through the Set Config endpoint, including schema_version alongside the sections that its firmware supports.

{
  "schema_version": "Old",
  "about": {
    "model": "AVX-4200",
    "firmware": "1.1.16"
  },
  "legacy_audio": {
    "gain": 3,
    "mono_mixdown": false
  }
}
{
  "schema_version": "2.0.0",
  "about": {
    "model": "AVX-4200",
    "firmware": "2.4.1"
  },
  "dsp": {
    "eq_preset": "voice",
    "auto_gain": true,
    "delay_ms": 20
  },
  "network": {
    "dhcp": true,
    "hostname": "avx4200-lab"
  }
}

A unit reporting "Old" shows About and Audio (legacy firmware); a unit reporting "2.0.0" shows About, DSP (2.0 firmware) and Network (2.0 firmware) — from the same model and the same schema. A unit reporting "Gen2" resolves to the same sections as "2.0.0", since both values appear in that branch's enum.