# Writing parameter files for the SKAARHOJ TCP Device Core You are producing **importable parameter files** for the SKAARHOJ TCP device core (`core-protocol-tcp`, and its identical twin `core-protocol-udp`). Each file describes **one command** the core can send to a device over a plain TCP socket, and how the value for that command is read back out of whatever the device sends. Read this whole document, then read the device's protocol documentation the user gives you, and produce **one `.json` file per action**. --- ## 1. The file format A parameter file is a single JSON object with exactly two keys: ```json { "Info": { }, "Variables": [ ] } ``` `Variables` may be `null` or omitted when the command has no `{tag}` placeholders. **The file name becomes the parameter's label.** `Recall Preset.json` imports as a parameter called *Recall Preset*, regardless of what `Info.Label` says. Name files after what an operator would call the action. ### `Info` fields | Field | Type | Values | Notes | |---|---|---|---| | `Label` | string | | Display name. Overwritten by the file name on import — keep them the same anyway. | | `Description` | string | | Shown in Reactor. One short sentence. | | `Type` | string | `Trigger`, `Toggle`, `String`, `Integer`, `Float` | See §3. | | `Command` | string | | The bytes written to the socket. See §2. | | `StatusRegex` | string | | Regular expression matched against incoming data. Only used with `FeedbackType: "Regex match value"`. | | `FeedbackType` | string | `None`, `Confirm on send`, `Regex match value` | See §5. | | `Min`, `Max` | number | | Range for `Integer` and `Float`. Omit for other types. | | `OnVal`, `OffVal` | string | | `Toggle` only — what `{value}` becomes in each direction. See §3. | | `ValueFormat` | string | `Text`, `Raw byte`, `Hex byte`, `Decimal byte`, `Scaled` | How the parameter's own `{value}` is written into the command. See §4. | | `ValueScale` | number | | Divisor for `ValueFormat: "Scaled"`. | Omit fields that do not apply rather than setting them to empty strings or zeros. ### `Variables` entries One entry per `{tag}` used in `Command`, in the order they appear. | Field | Type | Values | Notes | |---|---|---|---| | `Tag` | string | letters and numbers only | The placeholder name, written **without** braces. `"slot"` matches `{slot}` in the command. | | `Label` | string | | Display name. This is what the extra input is called in Reactor. | | `Description` | string | | Shown in Reactor. | | `Type` | string | `String`, `Int`, `Float`, `Binary` | | | `Format` | string | same set as `ValueFormat` | How this variable's value is written into the command. See §4. | | `Scale` | number | | Divisor for `Format: "Scaled"`. | | `Min`, `Max` | number | | Range for `Int` and `Float`. | --- ## 2. The `Command` field, and how to escape it The command is built in two passes, always in this order: 1. **Placeholders.** Every `{tag}` is replaced with its variable's value, and `{value}` with the parameter's own value. 2. **Escapes.** Backslash sequences in the result are decoded into raw bytes. Supported escapes: | In the Command field | Byte produced | |---|---| | `\r` | `0x0D` carriage return | | `\n` | `0x0A` newline | | `\xHH` | one raw byte, hex — e.g. `\x02` → `0x02` | | `\{` `\}` | a literal `{` or `}` | > **The most common mistake.** These are escapes *in the Command field*, so in **JSON** they must be > written with a doubled backslash. A command that ends in CR LF is `"Command": "PWR on\\r\\n"` — > **not** `"PWR on\r\n"`, which would put real control characters in the JSON string and reach the > device as something else entirely. Values substituted into a command are protected automatically: a variable holding `C:\new` sends those six characters and does not turn into a newline. You never need to escape a *value*. --- ## 3. Parameter types - **`Trigger`** — a button. Fires the command once. It has no value, so `{value}` is not available. - **`Toggle`** — an on/off switch. The parameter sends **one command in both directions**; `{value}` carries `OnVal` or `OffVal` to tell them apart. - Commands that share text: `"Command": "PWR {value}\\r\\n"`, `OnVal: "on"`, `OffVal: "off"` → sends `PWR on␍␊` and `PWR off␍␊`. - Commands that share nothing: put the whole word in the values — `"Command": "{value}\\r\\n"`, `OnVal: "SHUT ON"`, `OffVal: "SHUT OFF"`. - **`String`** — a text field. The text is `{value}`. - **`Integer`** / **`Float`** — a number field with `Min`/`Max`. The number is `{value}`. --- ## 4. Formats — sending a value as bytes rather than as text By default a value is written into the command as its plain text. `Format` (per variable) and `ValueFormat` (for `{value}`) change that. For a value of `15`: | Format | Substituted as | On the wire | |---|---|---| | `Text` (default) | `15` | `31 35` | | `Raw byte` | one byte | `0F` | | `Hex byte` | `0f` | `30 66` | | `Decimal byte` | `15` (clamped to 0–255) | `31 35` | | `Scaled` | value ÷ `Scale` — `1500` with `Scale: 1000` → `1.500` | ASCII digits | Use `Raw byte` for binary protocols that carry a level or an index as one byte. Use `Scaled` for protocols that want `1.500` where the operator thinks in thousandths. --- ## 5. Feedback — reading a value back A TCP device sends what it likes, when it likes; nothing ties a reply to the command that caused it. Feedback therefore watches the **whole incoming stream**, not a response. - **`None`** — fire and forget. The parameter never reports a value. - **`Confirm on send`** — the value is echoed back once the bytes leave the socket. Use it for a device that is silent but reliable. Has no effect on a `Trigger`. - **`Regex match value`** — `StatusRegex` is matched against every incoming message. The **last capture group** (the innermost parentheses) becomes the value. Rules for writing `StatusRegex`: - Anchor with `^` and `$` so unrelated device chatter cannot match. - Escape backslashes for JSON, as in §2: `"StatusRegex": "^LAMP:([0-9]+)$"` needs no doubling, but `"^VAL:(\\d+)$"` does. - For a `Toggle`, capture the device's own words and set `OnVal`/`OffVal` to them — the capture is compared against those, case-insensitively. - For `Integer`/`Float`, the capture must parse as a number. - On a `Trigger`, a capture arrives as text. --- ## 6. Worked examples **A toggle with matching feedback** — `Power.json`: ```json { "Info": { "Label": "Power", "Description": "Switch the projector on and off", "Type": "Toggle", "Command": "PWR {value}\\r\\n", "StatusRegex": "^PWR:(on|off)$", "FeedbackType": "Regex match value", "OffVal": "off", "OnVal": "on", "ValueFormat": "Text" }, "Variables": null } ``` **A trigger with a variable** — `Recall Preset.json`: ```json { "Info": { "Label": "Recall Preset", "Description": "Recall one of the projector's stored picture presets", "Type": "Trigger", "Command": "PRE {slot}\\r\\n", "FeedbackType": "None" }, "Variables": [ { "Label": "Preset Slot", "Description": "Which stored preset to recall", "Tag": "slot", "Type": "Int", "Format": "Text", "Min": 1, "Max": 16 } ] } ``` **A read-back value** — `Lamp Hours.json`: ```json { "Info": { "Label": "Lamp Hours", "Description": "Hours on the current lamp, as reported by the projector", "Type": "Integer", "Command": "LAMP?\\r\\n", "StatusRegex": "^LAMP:([0-9]+)$", "FeedbackType": "Regex match value", "Max": 9999, "ValueFormat": "Text" }, "Variables": null } ``` **A binary command** — `Volume.json`. The level is one raw byte inside an STX/ETX frame: ```json { "Info": { "Label": "Volume", "Description": "Built-in speaker level, sent as one raw byte inside an STX/ETX frame", "Type": "Integer", "Command": "\\x02VOL{value}\\x03", "FeedbackType": "Confirm on send", "Max": 255, "ValueFormat": "Raw byte" }, "Variables": null } ``` --- ## 7. Checklist before you hand the files over - One file per action, named after the action. - Every `{tag}` in `Command` has a matching entry in `Variables`, and every entry is used. - Every escape is doubled for JSON (`\\r`, `\\n`, `\\x02`). - The command ends with whatever terminator the device's documentation specifies — a missing `\\r` is the single most common reason a device ignores a command. - `FeedbackType` is `None` unless the protocol documentation shows what the device actually replies. - `Min`/`Max` reflect the protocol's real range, not a guess. - The JSON parses. Tell the user which of your choices came from their documentation and which you inferred, so they know what to verify with the Test button.