SPOT Tools JSON File Spec
This document defines the JSON format used by SPOT Tools to load/save radio and channel state.
Audience: developers integrating tooling, converters, or automated pipelines.
Format Overview
SPOT Tools stores:
- Radio-wide settings (
radio) - Channel list (
channels)
Current exported schema:
schema_version: 9radio_model: "uv-k5-telemetry"
Canonical Top-Level Shape
{
"schema_version": 8,
"radio_model": "uv-k5-telemetry",
"radio": { "...": "see Radio section" },
"channel_count": 200,
"channels": [ { "...": "see Channel section" } ]
}Notes:
- On import, only
channelsis strictly required. channel_countis informational on import (not authoritative).schema_versionandradio_modelare currently not enforced by the parser.
Radio Object
radio has two sub-objects:
active_options: primary telemetry-relevant settingslegacy_values: retained compatibility/settings state
radio.active_options
(canonical)
{
"beep_control": true,
"bpm_control": true,
"battery_type": 0,
"scan": {
"active_list_mask": 1,
"list_names": ["L1", "...", "L16"],
"resume_mode": "stop",
"dwell_time_seconds": 5
},
"telemetry": {
"peak_time": 22
},
"receiver": {
"squelch_level": 0,
"rf_gain": 12,
"tag_mode": "channel"
},
"display": {
"power_on_display_mode": "full_screen",
"backlight_time": 4
},
"boot": {
"line1": "SPOT",
"line2": "Telemetry RX"
},
"keys": {
"flashlight_enabled": true,
"arrow_orientation": "up_down"
},
"meter_calibration": {
"s0_level": 130,
"s9_level": 76
},
"developer": {
"pulse_threshold_dbm": -95,
"full_scale_dbm": -31,
"battery_calibration": 2300
}
}Allowed values and constraints
beep_control: booleanbpm_control: booleanbattery_type: integerscan.active_list_mask: integer mask, effective visible range0..65535(& 0xffffon read/write today)scan.list_names: array (up to first 16 used), each entry:- string
- printable ASCII only (
0x20..0x7E) - max 10 chars
- fallback to
L<n>if cleaned result is empty
scan.resume_mode:"stop"|"dwell"scan.dwell_time_seconds: integer, sanitized to nearest of1, 2, 3, 5, 10, 15, 20, 25, 30telemetry.peak_time: integer, sanitized to nearest of5, 10, 20, 22, 24, 29, 39(encoded values for0.5, 1, 2, 3, 5, 10, 20seconds)receiver.squelch_level: integerreceiver.rf_gain: integer, clamped to0..24receiver.tag_mode:"channel"|"tag"display.power_on_display_mode:"full_screen"|"message"|"voltage"|"none"display.backlight_time: integerboot.line1,boot.line2: string, truncated to 15 charskeys.flashlight_enabled: booleankeys.arrow_orientation:"up_down"|"left_right"meter_calibration.s0_level,meter_calibration.s9_level: integerdeveloper.pulse_threshold_dbm: integer, optionaldeveloper.full_scale_dbm: integer, optionaldeveloper.battery_calibration: integer, optional
radio.legacy_values
(canonical)
{
"tx_timeout_timer": 1,
"mic_sensitivity": 4,
"roger_mode": "off",
"repeater_tail_tone_elimination": 0,
"tx_vfo_index": 0,
"auto_keypad_lock": false,
"cross_band_rx_tx": "off",
"dual_watch": "off",
"vfo_open": true,
"raw": {}
}Allowed enum values:
roger_mode:"off"|"roger"|"mdc"cross_band_rx_tx:"off"|"chan_a"|"chan_b"dual_watch:"off"|"chan_a"|"chan_b"
legacy_values.raw is preserved as a JSON object and
round-tripped.
Channel Object
Canonical per-channel shape:
{
"index": 0,
"active": true,
"name": "664 R L",
"rx_frequency": 16840830,
"step_setting": 9,
"attributes": {
"band": 2,
"scanlists": [1, 2, 3]
},
"legacy": {
"bandwidth": "narrow"
}
}Required fields:
index(integer)active(boolean)name(string)rx_frequency(number)step_setting(number)attributes(object) with:band(integer)
Optional:
legacy.bandwidth("wide"|"narrow")
Scan List Encoding (Channel Attributes)
Canonical export uses:
attributes.scanlists: array of integers1..16
Import supports three forms (priority order):
attributes.scanlists(preferred)attributes.scanlist_mask(0..65535for the currently visible 16-list model)- legacy booleans:
attributes.scanlist1attributes.scanlist2
Step Setting Encoding
Canonical export uses:
step_setting(top-level per channel)
Import accepts:
step_setting(preferred)- legacy fallback:
legacy.step_setting
Compatibility and Fallback Rules
Radio compatibility
beep_controlmay be provided in either:radio.active_options.beep_controlradio.legacy_values.beep_control(fallback)
bpm_controlsame fallback behavior asbeep_control.battery_typemay be provided in:radio.active_options.battery_typeradio.legacy_values.battery_type(fallback)
Legacy scan default mapping
If radio.active_options.scan.active_list_mask is absent,
parser accepts legacy:
radio.active_options.scan.default_listwith values:"list1".."list5"-> maps to single-bitactive_list_mask"all"-> falls back to default mode/mask
If legacy default_list is used and not
"all", target_mode is inferred as
"lists".
Legacy
scan.target_mode handling
- Import accepts
radio.active_options.scan.target_modevalues"lists"and"all"for backward compatibility. - Canonical export does not require this field; mode is derived from
active_list_maskby current firmware-facing logic.
Normalization (Sanitization) Rules
After parsing/import, SPOT Tools sanitizes channels
(sanitizeChannels):
name: trim, printable ASCII only, max 10 charsscanlistMask: forced to the currently visible 16-bit range (& 0xffff)stepSetting: if unsupported, replaced with default telemetry step (STEP_INDEX_0_5_KHZ)bandwidth: forced to"narrow"activechannels are validated against telemetry constraints:- band must be
0..6 - frequency range must be:
- min
14_400_000 - max exclusive
17_400_000
- min
- band must be
- invalid active channels are coerced/inactivated per sanitizer logic
Important:
- Save and write paths also run sanitization, so file values may be corrected on output.
Export Behavior (Canonicalization)
When SPOT Tools saves JSON:
- emits
schema_version: 9 - emits canonical
radio.active_options+radio.legacy_values - emits channels with:
index,active,name,rx_frequency,step_settingattributes.bandattributes.scanlists(notscanlist_mask,scanlist1,scanlist2)legacy.bandwidth
- emits
radio.active_options.receiver.tag_mode - emits
radio.active_options.developeronly when one or more optional developer-backed values are actually present
Strictness and Unknown Fields
- Known fields are type-checked strictly.
- Missing optional fields fall back to defaults.
- Unknown fields are generally ignored by parser.
legacy_values.rawis the designated container for opaque vendor/compat data.
Minimal Valid Input Example
{
"channels": [
{
"index": 0,
"active": true,
"name": "CH001",
"rx_frequency": 16840000,
"attributes": {
"band": 2,
"scanlists": [1]
}
}
]
}This is accepted; omitted radio fields are filled from defaults.