Skip to content

Commands Module

Low-level CI-V command encoding and decoding. Most users should use the high-level Radio API (via create_radio).

cmd_map is required

Every command builder takes cmd_map as a required, keyword-only parameter — there is no hardcoded fallback path. Checked directly against every non-underscore function defined in src/rigplane/commands/*.py (excluding _frame.py): 244 distinct builder functions require cmd_map (256 counting names re-exported twice for backward compatibility, e.g. antenna.py: get_antenna aliasing get_antenna_1); the remaining 33 exported functions are parse_* response decoders, which take no cmd_map because they decode a frame the radio already sent, with nothing left to look up.

Calling a builder without a real map — cmd_map omitted, or passed explicitly as None — raises TypeError rather than silently building a wrong frame. Both shapes are handled by commands/_frame.py: require_cmd_map, applied to every migrated builder: a call missing cmd_map entirely gets Python's own missing-argument TypeError with an explanation appended, and a call passing cmd_map=None gets a dedicated TypeError before the builder body ever runs.

from rigplane.commands import get_af_level

get_af_level(to_addr=0x98)  # TypeError: missing keyword-only argument 'cmd_map'
get_af_level(to_addr=0x98, cmd_map=None)  # TypeError: cmd_map is None -- ...

Calling a builder directly

from pathlib import Path
from rigplane.rig_loader import load_rig
from rigplane.commands import get_af_level

cfg = load_rig(Path("rigs/ic7300.toml"))
cmd_map = cfg.to_command_map()

frame = get_af_level(to_addr=0x94, cmd_map=cmd_map)

commands/bound.py: BoundCommands binds a radio's CommandMap once, at construction, so call sites never pass cmd_map themselves:

from rigplane.commands.bound import BoundCommands

bound = BoundCommands(cmd_map)
frame = bound.get_af_level(to_addr=0x94)

runtime/radio.py: CoreRadio constructs one BoundCommands per radio and uses it for every migrated builder.

The undeclared-command policy (three states, not two)

A command a profile's CommandMap does not declare is not silently ignored. BoundCommands classifies every miss into one of three states:

  1. Declared — the profile has an entry; the builder sends its bytes.
  2. Declared absent — the profile records the radio as confirmed not to have this command, naming a source; calling it raises core.exceptions.CommandError quoting that source.
  3. Unknown — neither declared nor recorded absent. This state is not expected to exist in a released profile (a coverage test enumerates every builder against every profile), but if reached, BoundCommands refuses the same way as state 2 and, once, invokes the optional on_undeclared hook before raising.

Neither state 2 nor state 3 logs and continues: both raise CommandError, so a caller cannot observe a command that silently did nothing.

See docs/api/rig-loader.md for the CommandMap class reference.

rigplane.commands

CI-V command encoding and decoding for Icom transceivers.

CI-V frame format::

FE FE <to> <from> <cmd> [<sub>] [<data>...] FD

For dual-receiver radios (IC-7610), commands marked Command29=true use::

FE FE <to> <from> 29 <receiver> <cmd> [<sub>] [<data>...] FD

where receiver = 0x00 (MAIN) or 0x01 (SUB).

Reference: wfview icomcommander.cpp, IC-7610.rig

bcd_decode(data)

Decode Icom BCD-encoded frequency bytes to Hz.

Parameters:

Name Type Description Default
data bytes

5 bytes of BCD-encoded frequency.

required

Returns:

Type Description
int

Frequency in Hz.

Raises:

Type Description
ValueError

If data is not exactly 5 bytes or contains invalid BCD.

Examples:

>>> bcd_decode(bytes.fromhex('0040071400'))
14074000

bcd_encode_value(value, *, byte_count)

Encode an integer as packed BCD using a fixed byte width.

build_civ_frame(to_addr, from_addr, command, sub=None, data=None)

Build a CI-V frame.

Parameters:

Name Type Description Default
to_addr int

Destination CI-V address.

required
from_addr int

Source CI-V address.

required
command int

CI-V command byte.

required
sub int | None

Optional sub-command byte.

None
data bytes | None

Optional payload data.

None

Returns:

Type Description
bytes

Complete CI-V frame bytes.

build_cmd29_frame(to_addr, from_addr, command, sub=None, data=None, receiver=RECEIVER_MAIN)

Build a Command29-wrapped CI-V frame for dual-receiver radios.

For commands marked Command29=true in IC-7610.rig, the frame format is::

FE FE <to> <from> 29 <receiver> <cmd> [<sub>] [<data>...] FD

The 0x29 prefix tells the radio which receiver (MAIN/SUB) the command targets, without requiring a VFO select first.

Parameters:

Name Type Description Default
to_addr int

Destination CI-V address.

required
from_addr int

Source CI-V address.

required
command int

Original CI-V command byte (e.g. 0x11 for ATT, 0x16 for PREAMP).

required
sub int | None

Optional sub-command byte.

None
data bytes | None

Optional payload data.

None
receiver int

RECEIVER_MAIN (0x00) or RECEIVER_SUB (0x01).

RECEIVER_MAIN

Returns:

Type Description
bytes

Complete CI-V frame bytes with Command29 prefix.

build_memory_clear(channel, to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build CI-V frame to clear memory channel (0x0B).

Note

The 2-byte BCD channel payload appended here is contradicted by all four Icom manuals, which document 0x0B as a bare command with no data field -- see MOR-2055 for the payload question. This migration re-plumbs the command-map lookup only and does not change these wire bytes.

build_memory_contents_get(channel, to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build CI-V frame to get memory contents (0x1A 0x00).

build_memory_contents_set(mem, to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build CI-V frame to set memory contents (0x1A 0x00).

build_memory_mode_get(to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build CI-V frame to get current memory mode (0x08).

build_memory_mode_set(channel, to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build CI-V frame to set memory mode (0x08).

build_memory_to_vfo(channel, to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build CI-V frame to load memory to VFO (0x0A).

Note

The 2-byte BCD channel payload appended here is contradicted by all four Icom manuals, which document 0x0A as a bare command with no data field -- see MOR-2055 for the payload question. This migration re-plumbs the command-map lookup only and does not change these wire bytes.

build_memory_write(to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build CI-V frame to write VFO to memory (0x09).

command_carries_sub(command)

Whether command's wire bytes include a CI-V sub-command byte.

The one place this question is answered: :func:decode_wire_tuple (splitting a declared [commands] tuple) and :func:parse_civ_frame (splitting a received frame's payload) both call this, so a tuple split for a request and a frame split for its reply agree on where the sub-command byte is.

filter_hz_to_index(hz, *, segments)

Convert a filter width in Hz to a CI-V index using profile segments.

filter_index_to_hz(index, *, segments)

Convert a CI-V filter-width index to Hz using profile segments.

get_af_level(to_addr, from_addr=CONTROLLER_ADDR, receiver=RECEIVER_MAIN, *, command29=True, cmd_map)

Build a 'read AF output level' CI-V command (0x14 0x01).

get_af_mute(to_addr, from_addr=CONTROLLER_ADDR, receiver=RECEIVER_MAIN, *, command29=True, cmd_map)

Build a read AF Mute command.

get_agc(to_addr, from_addr=CONTROLLER_ADDR, receiver=RECEIVER_MAIN, *, command29=True, cmd_map)

Build a read AGC mode command.

get_agc_time_constant(to_addr, from_addr=CONTROLLER_ADDR, receiver=RECEIVER_MAIN, *, command29=True, cmd_map)

Build a read AGC time constant command.

get_alc(to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build a 'read ALC meter' CI-V command.

get_antenna_1(to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build ANT1 select/read command (0x12 0x00) WITHOUT data byte.

get_antenna_2(to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build ANT2 select/read command (0x12 0x01) WITHOUT data byte.

get_anti_vox_gain(to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build a read Anti-Vox Gain command.

get_apf_type_level(to_addr, from_addr=CONTROLLER_ADDR, receiver=RECEIVER_MAIN, *, command29=True, cmd_map)

Build a read APF Type Level command.

get_attenuator(to_addr, from_addr=CONTROLLER_ADDR, receiver=RECEIVER_MAIN, *, command29=True, cmd_map)

Build CI-V command to read attenuator level (Command29-aware).

get_audio_peak_filter(to_addr, from_addr=CONTROLLER_ADDR, receiver=RECEIVER_MAIN, *, command29=True, cmd_map)

Build a read audio peak filter mode command.

get_auto_notch(to_addr, from_addr=CONTROLLER_ADDR, receiver=RECEIVER_MAIN, *, command29=True, cmd_map)

Build a read auto-notch status command.

get_band_edge_freq(to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build a read band-edge frequency command (0x02).

get_break_in_delay(to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build a read Break-In Delay command.

get_bsr(band, register, to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build CI-V frame to get band stacking register (0x1A 0x01).

get_comp_meter(to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build a read compressor meter command (0x15 0x14).

get_compressor_level(to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build a read Compressor Level command.

get_cw_pitch(to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build a read CW Pitch command.

get_dash_ratio(to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build a read Dash Ratio command.

get_data_mode(to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build a 'get DATA mode' CI-V command (0x1A 0x06).

get_digisel(to_addr, from_addr=CONTROLLER_ADDR, receiver=RECEIVER_MAIN, *, command29=True, cmd_map)

Build CI-V command to read DIGI-SEL status (Command29-aware).

get_digisel_shift(to_addr, from_addr=CONTROLLER_ADDR, receiver=RECEIVER_MAIN, *, command29=True, cmd_map)

Build a read DIGI-SEL Shift command.

get_drive_gain(to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build a read Drive Gain command.

get_dual_watch(to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build CI-V command to query dual watch status.

Unlike the setter, the getter needs no key split: every profile that declares dual watch reads it back through one unified entry (IC-7610's [0x07, 0xC2], IC-9700's [0x16, 0x59]), since only the write side needs a separate address per direction.

get_filter_shape(to_addr, from_addr=CONTROLLER_ADDR, receiver=RECEIVER_MAIN, *, command29=True, cmd_map)

Build a read DSP IF filter shape command.

get_filter_width(to_addr, from_addr=CONTROLLER_ADDR, receiver=RECEIVER_MAIN, *, cmd_map)

Build a 'get DSP IF filter width' CI-V command (0x1A 0x03, cmd29).

get_freq(to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build a 'get frequency' CI-V command.

get_id_meter(to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build a read Id (drain current) meter command (0x15 0x16).

get_ip_plus(to_addr, from_addr=CONTROLLER_ADDR, *, command29=True, cmd_map)

Build CI-V command to read IP+ status.

get_key_speed(to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build a read Key Speed command.

get_main_sub_band(to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build a 'get main/sub band' CI-V command (0x07 0xD2).

get_main_sub_tracking(to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build a read Main/Sub Tracking status command (0x16 0x5E).

get_manual_notch_width(to_addr, from_addr=CONTROLLER_ADDR, receiver=RECEIVER_MAIN, *, command29=True, cmd_map)

Build a 'get manual notch width' CI-V command (0x16 0x57).

get_mic_gain(to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build a read Mic Gain command.

get_mode(to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build a 'get mode' CI-V command.

get_monitor_gain(to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build a read Monitor Gain command.

get_nb(to_addr, from_addr=CONTROLLER_ADDR, *, command29=True, cmd_map)

Build CI-V command to read NB status.

get_nb_depth(to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build a read NB Depth command.

get_nb_level(to_addr, from_addr=CONTROLLER_ADDR, receiver=RECEIVER_MAIN, *, command29=True, cmd_map)

Build a read NB Level command.

get_nb_width(to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build a read NB Width command.

get_notch_filter(to_addr, from_addr=CONTROLLER_ADDR, receiver=RECEIVER_MAIN, *, command29=True, cmd_map)

Build a read Notch Filter level command.

get_nr(to_addr, from_addr=CONTROLLER_ADDR, *, command29=True, cmd_map)

Build CI-V command to read NR status.

get_nr_level(to_addr, from_addr=CONTROLLER_ADDR, receiver=RECEIVER_MAIN, *, command29=True, cmd_map)

Build a read NR Level command.

get_overflow_status(to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build a read overflow status command.

get_pbt_inner(to_addr, from_addr=CONTROLLER_ADDR, receiver=RECEIVER_MAIN, *, command29=True, cmd_map)

Build a read PBT Inner command.

get_pbt_outer(to_addr, from_addr=CONTROLLER_ADDR, receiver=RECEIVER_MAIN, *, command29=True, cmd_map)

Build a read PBT Outer command.

get_power_meter(to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build a read RF power meter command (0x15 0x11).

get_powerstat(to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build CI-V frame to query radio power status (0x18 GET).

get_preamp(to_addr, from_addr=CONTROLLER_ADDR, receiver=RECEIVER_MAIN, *, command29=True, cmd_map)

Build CI-V command to read preamp status (Command29-aware).

get_quick_dual_watch(to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Read the persistent Quick Dual Watch menu toggle.

See get_quick_split -- same ruling, same shape.

get_quick_split(to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Read the persistent Quick Split menu toggle (0x1A 0x05 ).

MOR-2007 ruling 2: bench-confirmed on the live IC-7300, this menu item is readable, writable and persistent (restored across power cycles) -- not the one-shot trigger the pre-migration quick_split() name implied. That builder always sent this same bare-GET frame (the menu address alone, no data byte) and its only caller (runtime/radio.py) never read the reply, so it fired nothing; deleted along with quick_dual_watch() below.

get_ref_adjust(to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build a read REF Adjust command.

get_repeater_tone(to_addr, from_addr=CONTROLLER_ADDR, receiver=RECEIVER_MAIN, *, command29=True, cmd_map)

Build CI-V command to get repeater tone status (0x16 0x42).

get_repeater_tsql(to_addr, from_addr=CONTROLLER_ADDR, receiver=RECEIVER_MAIN, *, command29=True, cmd_map)

Build CI-V command to get repeater TSQL status (0x16 0x43).

get_rf_gain(to_addr, from_addr=CONTROLLER_ADDR, receiver=RECEIVER_MAIN, *, command29=True, cmd_map)

Build a 'read RF gain' CI-V command (0x14 0x02).

get_rf_power(to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build a 'get RF power' CI-V command.

get_rit_frequency(to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build a read RIT frequency offset command (0x21 0x00).

get_rx_antenna_ant1(to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build read RX ANT state for ANT1 (0x12 0x00). Warning: also selects ANT1.

get_rx_antenna_ant2(to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build read RX ANT state for ANT2 (0x12 0x01). Warning: also selects ANT2.

get_s_meter(to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build a 'read S-meter' CI-V command.

get_s_meter_sql_status(to_addr, from_addr=CONTROLLER_ADDR, receiver=RECEIVER_MAIN, *, command29=True, cmd_map)

Build a read S-meter squelch status command.

get_scope_center_type(to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build a bare 'get scope center type' CI-V command (0x27 0x1C).

Takes no receiver argument (MOR-1981, MOR-2002 step 2b-vfo-scope): 0x1C is outside SCOPE_RECEIVER_SELECTOR_SUBS, so a receiver byte on this read is a SET, not a selector -- see _scope_selector_data.

get_scope_data_output_enabled(to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Query whether CI-V scope waveform output is currently enabled.

get_scope_enabled(to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Query whether the radio's panel scope is currently enabled.

get_selected_freq(to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build a 'get selected receiver frequency' CI-V command (0x25 0x00).

get_selected_mode(to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build a 'get selected receiver mode' CI-V command (0x26 0x00).

get_speech(what=0, *, to_addr, from_addr=CONTROLLER_ADDR, cmd_map)

Build a speech announcement CI-V command (0x13).

Fire-and-forget. Triggers the IC-7610 voice synthesizer.

Parameters:

Name Type Description Default
what int

0 = all (S-meter, frequency, mode), 1 = frequency + S-meter, 2 = mode.

0

get_split(to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build CI-V command to read split state (0x0F).

get_squelch(to_addr, from_addr=CONTROLLER_ADDR, receiver=RECEIVER_MAIN, *, command29=True, cmd_map)

Build a 'get squelch level' CI-V command (0x14 0x03).

get_ssb_tx_bandwidth(to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build a read SSB TX bandwidth preset command.

get_swr(to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build a 'read SWR meter' CI-V command.

get_tone_freq(to_addr, from_addr=CONTROLLER_ADDR, receiver=RECEIVER_MAIN, *, command29=True, cmd_map, ctcss_tones_centihz)

Build CI-V command to get tone frequency (0x1B 0x00).

get_transceiver_id(to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build a read transceiver ID command (0x19 0x00).

get_tsql_freq(to_addr, from_addr=CONTROLLER_ADDR, receiver=RECEIVER_MAIN, *, command29=True, cmd_map, ctcss_tones_centihz)

Build CI-V command to get TSQL frequency (0x1B 0x01).

get_tuner_status(to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build a read tuner/ATU status command (0x1C 0x01).

get_tuning_step(to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build CI-V command to get tuning step (0x10).

get_tx_band_count(to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build CI-V frame to query number of TX bands (0x1E 0x00).

get_tx_band_edge(band, to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build CI-V frame to query TX band N edge frequencies (0x1E 0x01).

Parameters:

Name Type Description Default
band int

Band number (0-99, encoded as BCD byte).

required
to_addr int

Radio CI-V address.

required
from_addr int

Controller CI-V address.

CONTROLLER_ADDR
cmd_map CommandMap

The radio's bound command map.

required

Returns:

Type Description
bytes

Complete CI-V frame bytes.

get_unselected_freq(to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build a 'get unselected receiver frequency' CI-V command (0x25 0x01).

get_unselected_mode(to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build a 'get unselected receiver mode' CI-V command (0x26 0x01).

get_various_squelch(to_addr, from_addr=CONTROLLER_ADDR, receiver=RECEIVER_MAIN, *, command29=True, cmd_map)

Build a read various-squelch status command (0x15 0x05, Command29).

get_vd_meter(to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build a read Vd (supply voltage) meter command (0x15 0x15).

get_vfo(to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build a 'get VFO' CI-V command (0x07 read back current VFO).

get_vox_delay(to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build a read VOX Delay command (0x1A 0x05 0x02 0x92).

get_vox_gain(to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build a read Vox Gain command.

get_xfc_status(to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build a read XFC status command (0x1C 0x02).

hz_to_table_index(hz, *, table)

Convert Hz to the closest table-based filter-width index.

Returns the index whose table entry is closest to hz.

parse_ack_nak(frame)

Check if frame is ACK (0xFB) or NAK (0xFA).

Parameters:

Name Type Description Default
frame CivFrame

Parsed CivFrame.

required

Returns:

Type Description
bool | None

True for ACK, False for NAK, None if neither.

parse_band_stack_response(frame)

Parse band stacking register response (0x1A 0x01).

parse_bool_response(frame, *, command, sub=None, prefix=b'')

Parse a boolean CI-V response payload.

parse_civ_frame(data)

Parse a CI-V frame into a CivFrame.

Parameters:

Name Type Description Default
data bytes

Raw CI-V frame bytes (including FE FE preamble and FD terminator).

required

Returns:

Type Description
CivFrame

Parsed CivFrame.

Raises:

Type Description
ValueError

If frame is malformed.

parse_data_mode_response(frame)

Parse a DATA mode response frame.

Returns:

Type Description
bool

True if DATA mode is active (data[0] != 0x00), False otherwise.

parse_frequency_response(frame)

Parse a frequency response frame.

Parameters:

Name Type Description Default
frame CivFrame

Parsed CivFrame (command 0x02/0x03/0x00 with 5-byte BCD data).

required

Returns:

Type Description
int

Frequency in Hz.

Raises:

Type Description
ValueError

If frame is not a frequency response.

parse_level_response(frame, *, command=_CMD_LEVEL, sub=None, prefix=b'', bcd_bytes=2)

Parse a BCD-encoded level/config response.

parse_memory_contents_response(frame)

Parse memory contents response (0x1A 0x00).

parse_memory_mode_response(frame)

Parse memory mode response (0x08).

parse_meter_response(frame)

Parse a meter response frame.

Returns:

Type Description
int

Meter value 0-255.

parse_mode_response(frame)

Parse a mode response frame.

Returns:

Type Description
tuple[Mode, int | None]

Tuple of (mode, filter_width or None).

parse_powerstat(frame)

Parse power status response (0x18 GET).

Returns:

Type Description
bool

True if powered on, False if powered off.

parse_rit_frequency_response(data)

Parse RIT frequency response data (2-byte BCD + sign byte).

parse_scope_data_output_enabled_response(frame, *, command=_CMD_SCOPE, sub=_SUB_SCOPE_DATA_OUTPUT)

Parse a 0x27 0x11 waveform-output state response.

parse_scope_enabled_response(frame, *, command=_CMD_SCOPE, sub=_SUB_SCOPE_ON)

Parse a 0x27 0x10 panel-scope state response.

command/sub are the map-derived shape the caller's request used (runtime/_scope_runtime.py: ScopeRuntimeMixin.get_scope_session_state via runtime/radio.py: CoreRadio._expect_shape); they default to the shared hardcoded constants only so a caller that never passes one -- runtime/_civ_rx.py's unsolicited-frame decoding, and every pre-migration direct test call -- keeps working unchanged. Every other parse_scope_*_response function below takes the same two keywords for the same reason.

parse_scope_ref_response(frame, *, command=_CMD_SCOPE, sub=_SUB_SCOPE_REF)

Decode scope REF level from CI-V response.

Wire format (IC-7610 CI-V Reference p.15): byte 0: high nibble = 10 dB digit, low nibble = 1 dB digit byte 1: high nibble = 0.1 dB digit, low nibble = 0 byte 2: sign (0x00 = +, 0x01 = -)

parse_scope_span_response(frame, presets, *, command=_CMD_SCOPE, sub=_SUB_SCOPE_SPAN)

Decode a 0x27 0x15 span reply into (receiver, span index).

presets is the radio's declared span table -- see scope_set_span above for why it is a parameter and not a module constant. Both reply shapes are bounded by it: the one-byte form is already an index and is range-checked against len(presets), and the five-byte BCD form is matched exactly against the table by _span_index_for_hz. No match raises ValueError, unchanged -- a profile that declares no presets therefore decodes no span at all.

parse_selected_freq_response(frame)

Parse a 0x25 selected/unselected frequency response.

Returns:

Type Description
tuple[int, int]

Tuple of (receiver_byte, frequency_hz).

parse_selected_mode_response(frame)

Parse a 0x26 selected/unselected mode response.

Returns:

Type Description
tuple[int, Mode, int | None, int | None]

Tuple of (receiver_byte, mode, data_mode_or_None, filter_or_None).

parse_system_date_response(frame, *, prefix=_CTL_MEM_SYSTEM_DATE)

Parse a system-date reply.

prefix is the map-derived extended-address bytes the caller's request used (runtime/radio.py: CoreRadio._expect_shape); it defaults to the shared IC-7610-shaped constant only so tests built before this migration, which never passed one, keep working unchanged.

parse_system_time_response(frame, *, prefix=_CTL_MEM_SYSTEM_TIME)

Parse a system-time reply. See parse_system_date_response for prefix.

parse_tone_freq_response(frame, *, ctcss_tones_centihz)

Parse tone frequency response (0x1B 0x00).

parse_tsql_freq_response(frame, *, ctcss_tones_centihz)

Parse TSQL frequency response (0x1B 0x01).

parse_tx_band_count_response(data)

Parse 0x1E 0x00 response data into band count (BCD decoded).

Parameters:

Name Type Description Default
data bytes

Response payload (single BCD byte, or empty).

required

Returns:

Type Description
int

Number of TX bands.

parse_tx_band_edge_response(data)

Parse 0x1E 0x01 response data into (start_hz, end_hz).

Data format: 5-byte BCD start frequency + 5-byte BCD end frequency.

Parameters:

Name Type Description Default
data bytes

10 bytes of BCD-encoded frequency pair.

required

Returns:

Type Description
tuple[int, int]

Tuple of (start_hz, end_hz).

Raises:

Type Description
ValueError

If data is shorter than 10 bytes.

parse_utc_offset_response(frame, *, prefix=_CTL_MEM_UTC_OFFSET)

Parse a UTC-offset reply. See parse_system_date_response for prefix.

power_off(to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build CI-V frame to power off the radio.

power_on(to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build CI-V frame to power on the radio.

ptt_off(to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build a PTT-off CI-V command. See ptt_on.

ptt_on(to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build a PTT-on CI-V command.

The payload byte (0x01) is constant, so under the tuple contract (Q7, docs/plans/2026-08-29-profile-driven-command-bytes.md §8.1) every profile's ptt_on tuple already carries it; this builder passes no data of its own.

scan_set_df_span(df_span, to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build CI-V command to set DeltaF scan span (0x0E 0xA1-0xA7).

scan_set_resume(resume_mode, to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build CI-V command to set scan resume mode (0x0E sub).

Which sub-bytes are legal is a per-radio domain (MOR-2007 ruling 4): every documented CI-V guide (IC-7300/IC-7610/IC-9700/IC-705) lists only 0xD0 (resume OFF) and 0xD3 ("Close&Delay") -- not the 0xD1/0xD2 5s/10s states the deleted code-level VALID_SCAN_RESUME frozenset accepted on every radio regardless. This builder only encodes the byte the caller supplies; the profile-aware caller (runtime/radio.py: CoreRadio.scan_set_resume) validates it against RadioProfile.scan_resume_values first, same shape as scan_start_type above.

scan_start(to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build CI-V command to start scanning (0x0E 0x01).

scan_start_type(scan_type, to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build CI-V command to start scan with specific type (0x0E sub).

Which sub-bytes are legal is a per-radio domain (MOR-2007 ruling 4): the deleted code-level VALID_SCAN_TYPES frozenset omitted 0x13 (fine ΔF scan, documented on IC-7300/IC-7610/IC-9700) and IC-705's 0x24 (mode-select scan). This builder only encodes the byte the caller supplies; the profile-aware caller (runtime/radio.py: CoreRadio.scan_start) validates it against RadioProfile.scan_type_values first -- the same shape commands/dsp.py: set_agc uses for AGC-mode domain validation (MOR-1522).

scan_stop(to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build CI-V command to stop scanning (0x0E 0x00).

scope_set_center_type(center_type, to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build a 'set scope center type' CI-V command (0x27 0x1C).

Takes no receiver argument (MOR-2007, the latent setter-side twin of MOR-1981/#2821 -- see the module docstring): 0x27 0x1C takes exactly one data byte, no selector, so a receiver byte here would build a genuinely different, wrong frame rather than address a Main/Sub choice.

scope_set_span(span, to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map, presets, receiver=None)

Build the 0x27 0x15 SET frame for span-preset index span.

presets is the radio's declared span table (profiles.RadioProfile.scope_span_presets_hz, from rigs/*.toml [scope].span_presets_hz), supplied by the caller because commands/ may not import profiles/ (.importlinter). It is the only source of both the legal index range and the Hz value put on the wire, so a profile that declares no presets rejects every index through the same _validate_scope_range error every other out-of-range span raises.

send_cw(text, to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build CI-V frames to send CW text.

CW text is sent in chunks of up to 30 characters per frame. Each character is sent as ASCII byte in the data field.

Parameters:

Name Type Description Default
text str

CW text to send (A-Z, 0-9, and common prosigns).

required
to_addr int

Radio CI-V address.

required
from_addr int

Controller CI-V address.

CONTROLLER_ADDR

Returns:

Type Description
list[bytes]

List of CI-V frame bytes (one per chunk).

set_af_level(level, to_addr, from_addr=CONTROLLER_ADDR, receiver=RECEIVER_MAIN, *, command29=True, cmd_map)

Build a 'set AF output level' CI-V command.

set_af_mute(on, to_addr, from_addr=CONTROLLER_ADDR, receiver=RECEIVER_MAIN, *, command29=True, cmd_map)

Build a set AF Mute command.

set_agc(mode, to_addr, from_addr=CONTROLLER_ADDR, receiver=RECEIVER_MAIN, *, command29=True, cmd_map)

Build a set AGC mode command.

Encodes the raw single-BCD-byte AGC mode value. Which mode values are legal for a given radio (IC-7300's FAST/MID/SLOW vs. the X6200's OFF/FAST/SLOW/AUTO) is a per-profile domain, not a universal one — this builder only enforces the wire-format's single-BCD-byte range and leaves domain validation to the profile-aware caller (MOR-1522).

set_agc_time_constant(value, to_addr, from_addr=CONTROLLER_ADDR, receiver=RECEIVER_MAIN, *, command29=True, cmd_map)

Build a set AGC time constant command.

set_antenna_1(enabled, to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build ANT1 select command (0x12 0x00 <00|01>).

set_antenna_2(enabled, to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build ANT2 select command (0x12 0x01 <00|01>).

set_anti_vox_gain(level, to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build a set Anti-Vox Gain command.

set_apf_type_level(level, to_addr, from_addr=CONTROLLER_ADDR, receiver=RECEIVER_MAIN, *, command29=True, cmd_map)

Build a set APF Type Level command.

set_attenuator_level(db, to_addr, from_addr=CONTROLLER_ADDR, receiver=RECEIVER_MAIN, *, command29=True, cmd_map)

Set attenuator level in dB (IC-7610 supports 0..45 in 3 dB steps).

set_audio_peak_filter(mode, to_addr, from_addr=CONTROLLER_ADDR, receiver=RECEIVER_MAIN, *, command29=True, cmd_map)

Build a set audio peak filter mode command.

set_auto_notch(on, to_addr, from_addr=CONTROLLER_ADDR, receiver=RECEIVER_MAIN, *, command29=True, cmd_map)

Build a set auto-notch status command.

set_break_in(mode, to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build a set break-in mode command.

Encodes the raw single-BCD-byte break-in value. Which values are legal for a given radio (the documented OFF/SEMI/FULL domain on IC-705/ IC-7300/IC-9700/IC-7610 vs. the X6100/X6200, which have no confirmed domain at all) is a per-profile question, not a universal one — this builder only enforces the wire-format's single-BCD-byte range and leaves domain validation to the profile-aware caller (MOR-1534, mirrors MOR-1522's set_agc fix).

set_break_in_delay(level, to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build a set Break-In Delay command.

set_bsr(bsr, to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build CI-V frame to set band stacking register (0x1A 0x01).

set_compressor_level(level, to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build a set Compressor Level command.

set_cw_pitch(level, to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build a set CW Pitch command.

Parameters:

Name Type Description Default
level int

Raw sidetone level 0-255. Hz-to-level conversion is the caller's profile domain (profiles/control_domain.py: encode_legacy_control).

required

set_dash_ratio(value, to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build a set Dash Ratio command.

set_data_mode(on, to_addr, from_addr=CONTROLLER_ADDR, receiver=RECEIVER_MAIN, *, cmd_map)

Build a 'set DATA mode' CI-V command (0x1A 0x06 <0x00-0x03>).

Parameters:

Name Type Description Default
on int | bool

False/0 to disable, True/1 to enable DATA1, or an explicit DATA mode 0-3.

required

set_digisel(on, to_addr, from_addr=CONTROLLER_ADDR, receiver=RECEIVER_MAIN, *, command29=True, cmd_map)

Set DIGI-SEL status (Command29-aware).

set_digisel_shift(level, to_addr, from_addr=CONTROLLER_ADDR, receiver=RECEIVER_MAIN, *, command29=True, cmd_map)

Build a set DIGI-SEL Shift command.

set_drive_gain(level, to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build a set Drive Gain command.

set_dual_watch(on, to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build CI-V command to enable or disable dual watch.

Dispatches to set_dual_watch_on/set_dual_watch_off (MOR-2007 ruling 1's split keys) rather than resolving a key of its own -- tests/test_profile_command_coverage.py resolves this delegate by walking both branches by name; it deliberately carries no @expose_command_key (its own key is a function of on, not of cmd_map alone).

set_dual_watch_off(to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build CI-V command to turn off dual watch.

Resolves the split key set_dual_watch_off (MOR-2007 ruling 1), not the bare set_dual_watch the hardcoded fallback used to resolve -- see the module docstring.

set_dual_watch_on(to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build CI-V command to turn on dual watch. See set_dual_watch_off.

set_filter_shape(shape, to_addr, from_addr=CONTROLLER_ADDR, receiver=RECEIVER_MAIN, *, command29=True, cmd_map)

Build a set DSP IF filter shape command.

Encodes the raw single-BCD-byte filter-shape value. Which values are legal is a per-profile [filter_shape] values domain, not a universal enum -- this builder only enforces the wire-format's single-BCD-byte range and leaves domain validation to the profile-aware caller (MOR-1534, mirrors MOR-1522's set_agc fix).

set_filter_width(filter_index, to_addr, from_addr=CONTROLLER_ADDR, receiver=RECEIVER_MAIN, *, cmd_map)

Build a 'set DSP IF filter width' CI-V command (0x1A 0x03, cmd29).

Parameters:

Name Type Description Default
filter_index int

Filter width index encoded by the active radio profile.

required
receiver int

RECEIVER_MAIN (0x00) or RECEIVER_SUB (0x01).

RECEIVER_MAIN

set_freq(freq_hz, to_addr, from_addr=CONTROLLER_ADDR, receiver=RECEIVER_MAIN, *, cmd_map)

Build a 'set frequency' CI-V command.

Note

BCD encoding uses 5 bytes (10 decimal digits), so the maximum representable frequency is 9,999,999,999 Hz (~10 GHz). Frequencies outside this range will raise ValueError from :func:bcd_encode.

Parameters:

Name Type Description Default
freq_hz int

Frequency in Hz (0 - 9,999,999,999).

required
to_addr int

Radio CI-V address.

required
from_addr int

Controller CI-V address.

CONTROLLER_ADDR
receiver int

RECEIVER_MAIN (0x00) or RECEIVER_SUB (0x01).

RECEIVER_MAIN

Returns:

Type Description
bytes

CI-V frame bytes.

set_ip_plus(on, to_addr, from_addr=CONTROLLER_ADDR, receiver=RECEIVER_MAIN, *, command29=True, cmd_map)

Set IP+ on/off.

set_key_speed(level, to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build a set Key Speed command.

Parameters:

Name Type Description Default
level int

Raw key-speed level 0-255. WPM-to-level conversion is the caller's profile domain (profiles/control_domain.py: encode_legacy_control).

required

set_main_sub_tracking(on, to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build a set Main/Sub Tracking status command (0x16 0x5E).

set_manual_notch_width(width, to_addr, from_addr=CONTROLLER_ADDR, receiver=RECEIVER_MAIN, *, command29=True, cmd_map)

Build a 'set manual notch width' CI-V command (0x16 0x57).

Encodes the raw single-BCD-byte notch-width value. Which values are legal is a per-profile [notch] width_values domain, not a fixed 0/1/2 (WIDE/MID/NAR) enum on every radio — this builder only enforces the wire-format's single-BCD-byte range and leaves domain validation to the profile-aware caller (MOR-1542, mirrors set_break_in/ set_filter_shape/set_ssb_tx_bandwidth's MOR-1534 fix; CoreRadio keeps the domain-legality seat).

set_mic_gain(level, to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build a set Mic Gain command.

set_mode(mode, filter_width=None, *, to_addr, from_addr=CONTROLLER_ADDR, receiver=RECEIVER_MAIN, cmd_map)

Build a 'set mode' CI-V command.

Parameters:

Name Type Description Default
mode Mode

Operating mode.

required
filter_width int | None

Optional filter number (1-3).

None
to_addr int

Radio CI-V address.

required
from_addr int

Controller CI-V address.

CONTROLLER_ADDR
receiver int

RECEIVER_MAIN (0x00) or RECEIVER_SUB (0x01).

RECEIVER_MAIN

Returns:

Type Description
bytes

CI-V frame bytes.

set_monitor_gain(level, to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build a set Monitor Gain command.

set_nb(on, to_addr, from_addr=CONTROLLER_ADDR, receiver=RECEIVER_MAIN, *, command29=True, cmd_map)

Set Noise Blanker on/off.

set_nb_depth(value, to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build a set NB Depth command.

set_nb_level(level, to_addr, from_addr=CONTROLLER_ADDR, receiver=RECEIVER_MAIN, *, command29=True, cmd_map)

Build a set NB Level command.

set_nb_width(value, to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build a set NB Width command.

set_notch_filter(level, to_addr, from_addr=CONTROLLER_ADDR, receiver=RECEIVER_MAIN, *, command29=True, cmd_map)

Build a set Notch Filter level command.

set_nr(on, to_addr, from_addr=CONTROLLER_ADDR, receiver=RECEIVER_MAIN, *, command29=True, cmd_map)

Set Noise Reduction on/off.

set_nr_level(level, to_addr, from_addr=CONTROLLER_ADDR, receiver=RECEIVER_MAIN, *, command29=True, cmd_map)

Build a set NR Level command.

set_pbt_inner(level, to_addr, from_addr=CONTROLLER_ADDR, receiver=RECEIVER_MAIN, *, command29=True, cmd_map)

Build a set PBT Inner command.

set_pbt_outer(level, to_addr, from_addr=CONTROLLER_ADDR, receiver=RECEIVER_MAIN, *, command29=True, cmd_map)

Build a set PBT Outer command.

set_preamp(level=1, *, to_addr, from_addr=CONTROLLER_ADDR, receiver=RECEIVER_MAIN, command29=True, cmd_map)

Set preamp level (0=off, 1=PREAMP1, 2=PREAMP2).

set_quick_dual_watch(enabled, to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Write the persistent Quick Dual Watch menu toggle.

See get_quick_split.

set_quick_split(enabled, to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Write the persistent Quick Split menu toggle. See get_quick_split.

set_ref_adjust(value, to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build a set REF Adjust command.

set_repeater_tone(on, to_addr, from_addr=CONTROLLER_ADDR, receiver=RECEIVER_MAIN, *, command29=True, cmd_map)

Build CI-V command to set repeater tone (0x16 0x42).

set_repeater_tsql(on, to_addr, from_addr=CONTROLLER_ADDR, receiver=RECEIVER_MAIN, *, command29=True, cmd_map)

Build CI-V command to set repeater TSQL (0x16 0x43).

set_rf_gain(level, to_addr, from_addr=CONTROLLER_ADDR, receiver=RECEIVER_MAIN, *, command29=True, cmd_map)

Build a 'set RF gain' CI-V command.

set_rf_power(level, to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build a 'set RF power' CI-V command.

Parameters:

Name Type Description Default
level int

Power level 0-255 (radio maps to actual watts).

required

set_rit_frequency(offset_hz, to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build a set RIT frequency offset command (0x21 0x00).

set_rx_antenna_ant1(enabled, to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build set RX ANT state for ANT1 (0x12 0x00 <00|01>). Warning: also selects ANT1.

set_rx_antenna_ant2(enabled, to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build set RX ANT state for ANT2 (0x12 0x01 <00|01>). Warning: also selects ANT2.

set_selected_mode(mode, data_mode, filter_index, to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build a "set selected receiver mode" CI-V command (0x26 0x00).

Frame layout: FE FE <to> <from> 26 00 <mode> <data_mode> <filter> FD. The leading 0x00 data byte selects the active receiver (mirroring :func:get_selected_mode); mode / data_mode / filter set the full tuple so a mode-only change preserves the radio's current data-mode and filter.

set_split(on, to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Enable or disable split mode.

set_squelch(level, to_addr, from_addr=CONTROLLER_ADDR, receiver=RECEIVER_MAIN, *, command29=True, cmd_map)

Build a 'set squelch level' CI-V command.

set_ssb_tx_bandwidth(bandwidth, to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build a set SSB TX bandwidth preset command.

Encodes the raw single-BCD-byte bandwidth value. Which values are legal is a per-profile [ssb_tx_bw] values domain, not a universal enum -- this builder only enforces the wire-format's single-BCD-byte range and leaves domain validation to the profile-aware caller (MOR-1534, mirrors MOR-1522's set_agc fix).

set_tone_freq(freq_centihz, to_addr, from_addr=CONTROLLER_ADDR, receiver=RECEIVER_MAIN, *, command29=True, cmd_map, ctcss_tones_centihz)

Build CI-V command to set tone frequency (0x1B 0x00).

set_tsql_freq(freq_centihz, to_addr, from_addr=CONTROLLER_ADDR, receiver=RECEIVER_MAIN, *, command29=True, cmd_map, ctcss_tones_centihz)

Build CI-V command to set TSQL frequency (0x1B 0x01).

set_tuner_status(value, to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build a set tuner/ATU status command (0x1C 0x01). 0=off, 1=on, 2=tune.

set_tuning_step(step, to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build CI-V command to set tuning step (0x10).

step (0-8, the BCD-encoded index) is a wire-format bound uniform across every profile that declares tuning steps, not a per-radio domain -- unlike scan type/resume below, this check stays here (unaffected by MOR-2007 ruling 4).

set_vfo(code, *, to_addr, from_addr=CONTROLLER_ADDR, cmd_map)

Select a VFO or receiver by its wire code.

Parameters:

Name Type Description Default
code int

The selector byte to send, taken from the caller's profile.

required

Which byte names which VFO is a property of the radio, declared per profile as [vfo] main_select / sub_select. commands may not import profiles, so the resolution belongs one layer up -- runtime/radio.py: CoreRadio._set_vfo_wire does it.

set_vox_delay(value, to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build a set VOX Delay command (0x1A 0x05 0x02 0x92).

set_vox_gain(level, to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build a set Vox Gain command.

set_xfc_status(on, to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build a set XFC status command (0x1C 0x02).

stop_cw(to_addr, from_addr=CONTROLLER_ADDR, *, cmd_map)

Build CI-V frame to stop CW sending.

table_index_to_hz(index, *, table)

Convert a table-based filter-width index to Hz.

Used by rigs like FTX-1 where a 2-digit code maps directly to a position in a mode-dependent lookup table.

CI-V Frame Format

FE FE <to> <from> <cmd> [<sub>] [<data>...] FD
  • FE FE — preamble (2 bytes)
  • <to> — destination CI-V address (1 byte)
  • <from> — source CI-V address (1 byte)
  • <cmd> — command byte (1 byte)
  • <sub> — optional sub-command (1 byte)
  • <data> — optional payload (variable length)
  • FD — terminator (1 byte)

Constants

from rigplane import IC_7610_ADDR, CONTROLLER_ADDR

IC_7610_ADDR   # 0x98 — IC-7610's default CI-V address
CONTROLLER_ADDR  # 0xE0 — Controller address (us)

Frame Building

build_civ_frame()

def build_civ_frame(
    to_addr: int,
    from_addr: int,
    command: int,
    sub: int | None = None,
    data: bytes | None = None,
) -> bytes

Build a raw CI-V frame.

parse_civ_frame()

def parse_civ_frame(data: bytes) -> CivFrame

Parse raw bytes into a CivFrame dataclass.

Command Builders

Each function returns raw CI-V frame bytes ready to send. cmd_map is required and keyword-only on every one of these; omitted here for brevity — see the sections above for the full contract.

Frequency

get_frequency(to_addr=0x98, cmd_map=cmd_map) -> bytes
set_frequency(freq_hz: int, to_addr=0x98, cmd_map=cmd_map) -> bytes

Mode

get_mode(to_addr=0x98, cmd_map=cmd_map) -> bytes
set_mode(mode: Mode, filter_width: int | None = None, *, to_addr=0x98, cmd_map=cmd_map) -> bytes

RF Power

get_rf_power(to_addr=0x98, cmd_map=cmd_map) -> bytes
set_rf_power(level: int, to_addr=0x98, cmd_map=cmd_map) -> bytes

Meters

get_s_meter(to_addr=0x98, cmd_map=cmd_map) -> bytes
get_swr(to_addr=0x98, cmd_map=cmd_map) -> bytes
get_alc(to_addr=0x98, cmd_map=cmd_map) -> bytes

PTT

ptt_on(to_addr=0x98, cmd_map=cmd_map) -> bytes
ptt_off(to_addr=0x98, cmd_map=cmd_map) -> bytes

VFO

# ``code`` is the rig's selector byte, from the profile's
# ``[vfo] main_select`` / ``sub_select`` — this builder holds no
# name-to-byte table. ``radio.py: CoreRadio._set_vfo_wire`` resolves it.
select_vfo(code: int, *, to_addr=0x98, cmd_map=cmd_map) -> bytes
set_split(on: bool, to_addr=0x98, cmd_map=cmd_map) -> bytes

RF Controls (Command29-aware)

All RF control commands use build_cmd29_frame() for dual-receiver compatibility.

# Frame builder for Command29-wrapped commands
build_cmd29_frame(to_addr, from_addr, command, sub=None, data=None, receiver=RECEIVER_MAIN) -> bytes

# Attenuator
get_attenuator(to_addr=0x98, receiver=RECEIVER_MAIN, cmd_map=cmd_map) -> bytes
set_attenuator_level(db: int, to_addr=0x98, receiver=RECEIVER_MAIN, cmd_map=cmd_map) -> bytes
# No set_attenuator(bool) builder at this layer (MOR-2086): a command
# builder cannot see the profile, so it cannot resolve on/off to a valid
# dB value. Use runtime/radio.py: CoreRadio.set_attenuator instead, which
# resolves against the connected profile's declared values.

# Preamp
get_preamp(to_addr=0x98, receiver=RECEIVER_MAIN, cmd_map=cmd_map) -> bytes
set_preamp(level: int = 1, *, to_addr=0x98, receiver=RECEIVER_MAIN, cmd_map=cmd_map) -> bytes

# DIGI-SEL
get_digisel(to_addr=0x98, receiver=RECEIVER_MAIN, cmd_map=cmd_map) -> bytes
set_digisel(on: bool, to_addr=0x98, receiver=RECEIVER_MAIN, cmd_map=cmd_map) -> bytes

Constants: RECEIVER_MAIN = 0x00, RECEIVER_SUB = 0x01

CW

send_cw(text: str, to_addr=0x98, cmd_map=cmd_map) -> list[bytes]  # Returns multiple frames
stop_cw(to_addr=0x98, cmd_map=cmd_map) -> bytes

Power Control

power_on(to_addr=0x98, cmd_map=cmd_map) -> bytes
power_off(to_addr=0x98, cmd_map=cmd_map) -> bytes

Response Parsers

parse_frequency_response()

def parse_frequency_response(frame: CivFrame) -> int

Parse a frequency response to Hz. Raises ValueError if not a frequency response.

parse_mode_response()

def parse_mode_response(frame: CivFrame) -> tuple[Mode, int | None]

Parse a mode response. Returns (mode, filter_width).

parse_meter_response()

def parse_meter_response(frame: CivFrame) -> int

Parse a meter response to 0–255 int.

parse_ack_nak()

def parse_ack_nak(frame: CivFrame) -> bool | None

Check if frame is ACK (True), NAK (False), or neither (None).

CI-V Command Codes

Values below are confirmed either as surviving constants in commands/_frame.py (frequency, mode, levels, meters, PTT, attenuator, preamp, power, scope, ACK/NAK) or, for the three now resolved entirely from a profile's CommandMap (VFO select, split, CW keying), as the value rigs/ic7300.toml declares — a profile is free to declare a different byte for its own radio.

Code Command
0x03 Read frequency
0x04 Read mode
0x05 Set frequency
0x06 Set mode
0x07 VFO select / equalize / swap
0x0F Split on/off
0x11 Attenuator
0x14 Levels (RF power, etc.)
0x15 Meter readings
0x16 Preamp
0x17 CW keying
0x18 Power on/off
0x27 Scope/waterfall
0x1C PTT / transceiver status
0xFB ACK (command accepted)
0xFA NAK (command rejected)