Xiegu X6200 — CAT (CI-V) reference vs implementation¶
Manual: Radioddity "XIEGU X6200 CI-V implementation V1.0.6" (Firmware V1.0.6, dated 2025-06-20, 13 pp.) — official, tabular, CONFIRMED for its documented subset. Cross-referenced against Hamlib rigs/icom/xiegu.c (x6100_priv_caps, reused by X6200; RIG_MODEL_X6200 = 3091). Sources + verbatim extract: manuals/x6200.txt (+ committed manuals/x6200_civ_v1.0.6.pdf). Confidence: high for documented opcodes; Hamlib X108G_LEVELS/FUNCS over-advertise — the Radioddity table wins.
Driver: no x6200/ dir — X6200 routes through Ic705SerialRadio (src/rigplane/backends/ic705/serial.py) → _IcomSerialRadioBase → CoreRadio (src/rigplane/runtime/radio.py, all get_/set_ live here). Factory: backends/factory.py:85 maps model in ("IC-705","X6200") to Ic705SerialRadio; personality comes from rigs/x6200.toml (model="X6200"), not a distinct class.
Profile: rigs/x6200.toml (CI-V addr 0xA4, 19200 bps SERIAL-B). Validation: src/rigplane/validation/registry/*.
Live run: not run — live serial access unavailable to this audit. All statuses are doc-vs-code; every "live" column is not run.
Protocol: CI-V-derived (FE FE A4 00 .. FD), narrower than Icom. Disambiguated from IC-705 (same 0xA4) by Xiegu-only 0x1D 0x19 model-ID → 6200. CH342 dual-UART. 0x18 power on/off intentionally absent (the documented wedge trigger — MOR-170).
Routing fact that drives the gaps below:
CoreRadiosetters/getters build CI-V frames from hardcoded opcodes incommands/*builders, not from the profile[commands]map (thecmd_map=path is never passed for these). The profile gate is_require_capability(<feature>). So a declared[capabilities] featureactivates a method whose opcode is fixed in code — which may not match a Xiegu-documented opcode. The[commands]opcode table inx6200.tomlis largely descriptive, not wired for the legacy freq/mode/func paths.
Command matrix (operator-facing)¶
Legend — Documented: ✅ in Radioddity V1.0.6 table · ⚠️ narrative-only / uncertain · ❌ absent.
Validation: RMVR = round-trip readback check · presence = <cap>.presence only · — = no check / cap absent · structural = capability="".
| CAT (X6200 opcode) | Documented | Backend method (CoreRadio) | Profile cap / cmd | Validation check_id · live |
|---|---|---|---|---|
0x25 0x00/0x01 selected/unselected freq |
✅ | get_selected_freq/set_selected_freq/_unselected_ (dual-RX mixin) |
get/set_selected_freq, get/set_unselected_freq |
freq.write,freq.reverse_sync (structural) · not run |
0x03/0x05 freq (legacy) |
⚠️ narrative only | get_freq/set_freq → main RX path uses hardcoded 0x03/0x05 |
get_freq=[0x03],set_freq=[0x05] (declared, not wired) |
via freq.* structural · not run |
0x26 0x00/0x01 selected/unselected mode |
✅ | get_selected_mode/set_selected_mode/_unselected_ |
get/set_selected_mode,get/set_unselected_mode |
mode.set (structural) · not run |
0x04/0x06 mode (legacy) |
❌ (not in table) | get_mode/set_mode → main path hardcoded 0x04/0x06 |
get_mode=[0x04],set_mode=[0x06] (declared, not wired) |
mode.set · not run |
0x02 RX freq range |
✅ | get_band_edge_freq |
get_band_edge_freq=[0x02] |
— |
0x07 0x00/0x01 VFO A/B select; 0x07 0xB0 swap |
✅ | get/set_vfo_slot (DualRxRuntimeMixin), swap_vfo_ab |
get/set_vfo=[0x07]; [vfo] scheme="ab" |
vfo_slot.set (structural RMVR) · not run |
0x0F 0x00/0x01 split |
✅ (set only) | set_split/get_split (hardcoded 0x0F) |
split; set_split=[0x0F] |
split.set RMVR · not run |
0x11 attenuator on/off |
✅ | get/set_attenuator |
attenuator; get/set_attenuator=[0x11] |
attenuator.set RMVR · not run |
0x16 0x02 preamp |
✅ | get/set_preamp |
preamp; get/set_preamp=[0x16,0x02] |
preamp.set RMVR · not run |
0x16 0x12 AGC |
✅ | get/set_agc |
agc; get/set_agc=[0x16,0x12] |
agc.set RMVR · not run |
0x16 0x22 noise blanker |
✅ | get/set_nb |
nb; get/set_nb=[0x16,0x22] |
nb.set RMVR · not run |
0x16 0x40 NR on/off |
✅ | get/set_nr (hardcoded 0x16 0x40) |
nr |
nr.set RMVR · not run |
0x16 0x44 COMP on/off |
✅ | get/set_compressor (hardcoded 0x16 0x44) |
compressor |
presence-only · not run |
0x16 0x50 key/dial lock |
✅ | get/set_dial_lock |
dial_lock; get/set_dial_lock=[0x16,0x50] |
dial_lock.set RMVR · not run |
0x14 0x01 AF |
✅ | get/set_af_level |
af_level; [0x14,0x01] |
af_level.set RMVR · not run |
0x14 0x02 RF gain |
✅ | get/set_rf_gain |
rf_gain; [0x14,0x02] |
rf_gain.set RMVR · not run |
0x14 0x03 squelch |
✅ | get/set_squelch |
squelch; [0x14,0x03] |
squelch.set RMVR · not run |
0x14 0x06 NR level |
✅ | get/set_nr_level |
nr; [0x14,0x06] |
— (cap nr covered by nr.set on/off only) |
0x14 0x09 CW pitch |
✅ | get/set_cw_pitch |
cw; [0x14,0x09] |
— |
0x14 0x0A TX power |
✅ | get/set_rf_power |
tx; [0x14,0x0A] |
— (no rf_power RMVR check) |
0x14 0x0B mic gain |
✅ | get/set_mic_gain |
[0x14,0x0B] |
— |
0x14 0x0C keyer speed |
✅ | get/set_key_speed |
cw; [0x14,0x0C] |
key_speed.set RMVR · not run |
0x14 0x0D DNF/notch FC |
✅ | get/set_notch_filter (hardcoded 0x14 0x0D) |
notch (no cmd string) |
(see notch row below) |
0x14 0x0F QSK hang |
✅ | get/set_break_in_delay (Icom uses 0x14 0x10?) |
break_in (no cmd string) |
— |
0x14 0x12 NB level |
✅ | get/set_nb_level |
nb; [0x14,0x12] |
— |
0x14 0x15 MONI level |
✅ | get/set_monitor_gain |
[0x14,0x15] |
— |
0x15 0x02/0x11/0x12/0x15 S/pwr/SWR/Vd meters |
✅ | get_s_meter/get_power_meter/get_swr/get_vd_meter |
meters; [0x15,..] |
meters.read RO · not run |
0x1A 0x01 band/spectrum + band-stacking set |
✅ | get_band_spectrum_display / set_bsr |
get_band_spectrum_display=[0x1A,0x01] |
bsr.select (cap bsr not declared → resolves UNSUPPORTED) · not run |
0x1A 0x03 IF filter width (read) |
✅ | get_filter_width (set_filter_width exists) |
get_filter_width=[0x1A,0x03]; cap filter_width not declared |
— (filter_width.set skipped: cap absent) |
0x1A 0x05 (00 62) LCD/key LOCK |
✅ | (no dedicated method; overlaps dial_lock 0x16 0x50) |
— | — |
0x1C 0x00 PTT |
✅ | set_ptt/get_transceiver_status |
tx; ptt_on/off=[0x1C,0x00] |
tx.ptt (MANUAL) · not run |
0x1C 0x01 ATU (off/on/auto-tune) |
✅ | get/set_tuner_status |
tuner; get/set_tuner_status=[0x1C,0x01] |
tuner.tune (MANUAL) · not run |
0x19 0x00 radio ID |
✅ | get_transceiver_id |
get_transceiver_id=[0x19,0x00] |
discovery.identify (structural) · not run |
0x1D 0x19 Xiegu model ID → 6200 |
✅ | (discovery probe probe_xiegu_model_id) |
get_xiegu_model_id=[0x1D,0x19] |
discovery-only · not run |
DNF on/off 0x16 0x41 |
✅ | no method — notch cap routes to set_manual_notch (0x16 0x48, NOT X6200) |
notch declared |
notch.set RMVR → wrong opcode (D) |
| break-in (QSK) on/off | ✅ only as 0x14 0x0F hang time |
get/set_break_in → hardcoded 0x16 0x47 (NOT X6200) |
break_in declared |
presence-only · not run (D) |
| VOX | ❌ no CI-V | get/set_vox → hardcoded 0x16 0x46 (NOT X6200) |
vox declared |
vox.read/vox.set(TX-blocked) · not run (D) |
| repeater tone / TSQL / tone freq | ❌ no 0x1B/0x16 0x42-43 |
get/set_repeater_tone/_tsql/_tone_freq → 0x16 0x42/43, 0x1B 0x00 (NOT X6200) |
repeater_tone,tsql declared |
repeater_tone.set,tone_freq.set,tsql.set,tsql_freq.set RMVR → all wrong opcode (D) |
RIT / XIT 0x21 |
❌ no CI-V | get/set_rit_frequency/_tx_status → hardcoded 0x21 |
rit,xit; [validation] write_only_controls=["rit","xit","notch"] |
rit.set,xit.set RMVR (write-only) · not run (D) |
scope 0x27 |
❌ no CI-V | scope mixin (gated) | cap scope not declared (intentional) |
scope.* resolve UNSUPPORTED · correct |
pbt (PBT 0x14 0x08/0x09) |
❌ no CI-V | get/set_pbt_inner/outer (hardcoded 0x14 0x08/09) |
pbt declared; [controls.pbt_inner/outer] |
no registry check → presence-only (D) |
data_mode 0x26 data-flag / 0x1A 0x06 |
partial (via 0x26 byte) | get/set_data_mode (hardcoded 0x1A 0x06) |
data_mode declared |
no registry check → presence-only |
scan |
❌ no CI-V | none | scan declared |
no registry check → presence-only |
Intentionally skipped (memory / scan / menu / display — nothing to mirror into the browser UI)¶
- Band-stacking register set (
0x1A 0x01 D0+D1) — band-recall UI, not a control round-trip (registrybsrexists but cap undeclared). - LCD backlight (
0x14 0x19) and0x1A 0x05LOCK — front-panel display state. - No memory family, no scan-table CI-V documented for X6200 — nothing to audit.
Gap lists (priority-ordered)¶
A. UNDER-DECLARED — backend implements + opcode is X6200-documented, but profile/registry can't see it¶
| CAT (X6200) | rigplane symbol | Fix | Ticket |
|---|---|---|---|
0x1A 0x03 filter width |
get/set_filter_width exist + get_filter_width=[0x1A,0x03] in toml, but filter_width is not a declared capability → filter_width.set check skipped entirely |
Declare filter_width cap once the segmented BCD index tables (Table 2-4) are confirmed on hardware; the profile comment already flags this as deferred pending HW. Until then: keep as-is. |
NEW (low) |
0x14 0x0A TX power · 0x14 0x0B mic gain · 0x14 0x09 CW pitch · 0x14 0x15 MONI |
methods exist, opcodes documented & in toml, but no RMVR registry check for rf_power/mic_gain/cw_pitch/monitor |
Optional: add level RMVR checks (these are clean read/set BCD-255 pairs). Low — operator-visible but already wired. | NEW (low) |
0x16 0x40 NR on/off · 0x16 0x44 COMP on/off |
set_nr/set_compressor use exactly the documented opcodes; compressor is presence-only and nr.set covers NR |
Add compressor.set RMVR (get/set_compressor, opcode is correct for X6200). |
NEW (med) |
B. VALIDATION GAPS — implemented + declared + opcode-correct, but presence-only (no round-trip)¶
| CAT (X6200) | rigplane symbol | Why presence-only | Ticket |
|---|---|---|---|
0x16 0x44 COMP |
get/set_compressor |
compressor cap has no registry check → compressor.presence |
NEW (med) — opcode is X6200-correct, safe to RMVR |
0x14 0x08/0x09 PBT |
get/set_pbt_inner/_outer |
pbt cap has no registry check; opcodes are generic-Icom and not in the X6200 table → presence masks a likely-unsupported control |
NEW (med) — verify PBT on HW before promoting; may belong in C/D |
0x1A 0x06 data mode |
get/set_data_mode |
data_mode cap has no registry check → presence |
NEW (low) |
| (n/a) scan | — | scan cap declared, no method, no CI-V |
NEW (low) — drop cap or implement |
C. MISSING BACKEND — X6200-documented operator command, no (correct) backend method¶
| CAT (X6200) | Function | Value | Ticket |
|---|---|---|---|
0x16 0x41 DNF on/off |
Digital notch toggle — the X6200's real notch. No method binds it; notch cap mis-binds to 0x16 0x48 (manual notch, Icom-only). |
Med — primary noise-reduction control | NEW (med) → see D |
0x14 0x0D DNF centre freq |
Notch FC 100-3000 Hz. get/set_notch_filter exists at 0x14 0x0D (correct!) but is not surfaced via the notch cap path. |
Low | NEW (low) |
0x1A 0x05 (00 62) key/LCD LOCK |
Distinct from 0x16 0x50 dial lock; documented separately. |
Low — overlaps dial_lock | NEW (low) |
0x1C 0x01 0x02 ATU auto-tune |
set_tuner_status likely only sends on/off; the 0x02 auto-tune action value may be unreached. |
Med — verify auto-tune value path | NEW (med) |
D. MISMATCH / WRONG — declared/checked but the opcode is NOT in the X6200 documented set¶
These are the high-value finds: a capability is declared, a registry RMVR check is generated (and would run on live HW), but the hardcoded backend opcode is one the X6200 firmware does not document → the check would likely time out / NAK, or worse, silently hit an unrelated register. All need a live capture to confirm true behavior.
| CAT path | rigplane symbol | Issue | Ticket |
|---|---|---|---|
| tone/CTCSS family | repeater_tone.set,tone_freq.set,tsql.set,tsql_freq.set RMVR checks (caps repeater_tone,tsql declared) |
Backend sends 0x16 0x42/0x43 + 0x1B 0x00. None are in the X6200 table — no 0x1B, no tone funcs. 4 RMVR checks point at undocumented opcodes. |
NEW (high) — drop repeater_tone/tsql caps from x6200.toml (or gate behind HW confirm) |
| manual notch | notch.set RMVR (cap notch) |
Routes to set_manual_notch = 0x16 0x48 (Icom manual notch). X6200's notch is DNF 0x16 0x41 + FC 0x14 0x0D. Check targets the wrong register. |
NEW (high) — repoint notch to DNF 0x16 0x41; add get/set DNF methods |
| VOX | vox.read/vox.set/vox_gain.set (cap vox) |
get/set_vox = 0x16 0x46, vox-gain 0x14 0x16 — neither documented for X6200. |
NEW (med) — drop vox cap, or confirm on HW |
| break-in | break_in cap (presence-only) |
get/set_break_in = 0x16 0x47 (Icom CW BK-IN mode). X6200 documents only QSK hang time 0x14 0x0F, not a BK-IN-mode register. |
NEW (med) — remap to QSK-hang or drop cap |
| RIT/XIT | rit.set,xit.set write-only checks (caps rit,xit) |
0x21 RIT/XIT — not in the X6200 table. Already partly hedged via [validation] write_only_controls=["rit","xit","notch"] ("SET accepted but GET times out", MOR-179/207 — those were raw-CIV-confirmed on other Icoms, not X6200). For X6200 the SET itself is undocumented. |
NEW (med) — confirm 0x21 on X6200 HW; if absent, drop rit/xit caps |
legacy mode 0x04/0x06 |
get_mode/set_mode main path (hardcoded 0x04/0x06) |
X6200 table documents mode only via 0x26. Main RX mode read/write may not answer on 0x04/0x06. The dual-RX 0x26 path is correct; the legacy main path is the risk. |
NEW (high) — confirm 0x04 responds on HW; if not, route main mode through 0x26 0x00 |
legacy freq 0x03/0x05 |
get_freq/set_freq main path (hardcoded 0x03/0x05) |
0x03 appears only in the page-4 narrative example, not Table 1. Likely works (example is explicit) but unconfirmed; 0x25 0x00 is the table-blessed path. |
NEW (med) — confirm; prefer 0x25 0x00 for main if 0x03 is flaky |
pbt |
get/set_pbt_inner/_outer (0x14 0x08/0x09) |
Generic-Icom PBT opcodes, absent from X6200 table; cap declared with [controls.pbt_inner/outer] ranges. |
NEW (med) — confirm PBT on HW; drop cap if unsupported |
Correctly handled (no gap)¶
0x18power on/off — intentionally omitted fromx6200.toml(MOR-170 wedge).power_controlcap undeclared → nopowerstatpath. ✅0x27scope —scopecap undeclared; allscope.*checks resolve UNSUPPORTED. Matches doc (no scope CI-V). ✅- NB
0x16 0x22, NR0x16 0x40, AGC0x16 0x12, preamp0x16 0x02, ATT0x11, AF/RF/SQL0x14 0x01/02/03, key-lock0x16 0x50, meters0x15, PTT/ATU0x1C— opcodes match the X6200 table; checks are sound. ✅
Documentation quality note¶
The Radioddity V1.0.6 PDF is the single authoritative source and is high quality for what it covers: every opcode above marked ✅ is verbatim from Table 1. The audit's confidence is therefore high on presence/absence of opcodes but the following need a live X6200 capture before acting on the D-list:
- Legacy
0x03/0x04(main freq/mode):0x03is narrative-only;0x04is fully absent. Does the firmware still answer them (Icom-compat), or only0x25/0x26? This decides whether the legacy main paths are silently broken. 0x16 0x41DNF vs0x16 0x48manual-notch: confirm the X6200 NAKs0x48(so thenotch.setcheck is a real failure, not a coincidental hit).- Tone/CTCSS, VOX, break-in, RIT/XIT (
0x1B,0x16 0x42/43/46/47,0x21): confirmed doc-absent; a live NAK capture would let us safely drop the four caps (repeater_tone,tsql,vox, plusbreak_in/rit/xitreview) fromx6200.toml. - PBT
0x14 0x08/0x09: doc-absent but cap-declared with full control ranges — most likely a copied-from-IC-705 over-declaration. 0x1A 0x03filter width +0x1C 0x01 0x02ATU auto-tune: documented but un-surfaced / partially wired; capture to confirm the segmented BCD index and the auto-tune action value.
Headline: the X6200 profile is over-declared — it inherited several IC-705-class capabilities (repeater_tone, tsql, vox, break_in, rit, xit, notch→manual, pbt) whose hardcoded backend opcodes are not in the X6200 CI-V table. On live hardware these generate RMVR checks that would target undocumented registers. The fix is profile-side (drop/remap caps) once a single live capture confirms the NAKs — no backend changes are needed for the correctly-wired majority.