Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
d862fdf
modem: port v1's 32-MFSK (cl_mfsk) from C++ to C + MFSK-vs-FSK compar…
rafael2k Jul 15, 2026
ff05932
docs: MFSK-PORT — add honest acquisition analysis
rafael2k Jul 15, 2026
45500a8
modem: port v1 OFDM framing subset for the MFSK mode (mfsk_ofdm.{c,h})
rafael2k Jul 15, 2026
f8e48fc
modem: port v1 MFSK preamble acquisition (time_sync_mfsk_corr) to C
rafael2k Jul 15, 2026
45cafa5
docs: MFSK-PORT — end-to-end acquire+decode floor through ch (resolve…
rafael2k Jul 15, 2026
9e571f7
modem: wire rate-1/16 LDPC into the MFSK mode (encode/decode + coded …
rafael2k Jul 15, 2026
bb295ac
modem: add MFSK postamble (dual-ended acquisition) + full mode compar…
rafael2k Jul 15, 2026
95965bc
modem: MFSK LDPC rate ladder (1/16..8/16) — rate is nearly free, 1/16…
rafael2k Jul 16, 2026
319791e
mfsk: port v1 detect_ack_pattern (control-plane pattern-ACK)
rafael2k Jul 16, 2026
4fd4039
modem: introduce modem_backend vtable; FreeDV as backend 1
rafael2k Jul 16, 2026
a6f0079
modem: MFSK weak-signal backend (backend 2)
rafael2k Jul 16, 2026
235fe5a
arq: register MFSK as the OLLA bottom rung (below DATAC15)
rafael2k Jul 16, 2026
5ef3ef8
arq: rethink data plane — delivery-driven stop-and-wait + pattern ACK
rafael2k Jul 16, 2026
7f7215f
arq/modem: fix connect turnaround + MFSK data RX in the live path
rafael2k Jul 18, 2026
281e858
tests: build libfreedvdata.a on demand so `make test` works from a cl…
rafael2k Jul 18, 2026
f8af577
tests: 2-arg notify fakes in test_arq_ladder
rafael2k Jul 20, 2026
3fef643
tests: wire test_freedv_harq into the suite after rebase onto trunk
rafael2k Jul 22, 2026
8e673bd
modem/mfsk: postamble-anchored sync fallback for clipped-head bursts
rafael2k Jul 22, 2026
642d08d
arq/fsm: IRS mirrors the ISS delivery-driven ladder
rafael2k Jul 22, 2026
7de0c2e
arq: port LISTEN OFF as a radio release into the rewritten FSM
rafael2k Jul 30, 2026
98af07b
diag: MERCURY_PIN_LADDER on the MFSK branch (ladder pin, incl. sessio…
rafael2k Aug 3, 2026
85d0362
tests: feed the MFSK decoder the way the live funnel does, and sweep …
rafael2k Aug 5, 2026
7dab3a8
arq: derive the ACCEPT RX window from the ladder, not from DATAC15
rafael2k Aug 5, 2026
7517d19
mfsk: make the RX downmix incremental instead of recomputing the whol…
rafael2k Aug 5, 2026
cdd2b19
mfsk: stop the modulator clipping its own burst, and size the RX wind…
rafael2k Aug 5, 2026
f25143c
mfsk: stop correlating over audio that cannot decode
rafael2k Aug 5, 2026
ca5c1e3
tests: exercise a burst embedded in continuously flowing audio
rafael2k Aug 5, 2026
4dd6fc7
arq+modem: size in-flight-burst guards from the ladder, never from th…
rafael2k Aug 5, 2026
ff90cd2
arq: run pattern-ACK detection only when a pattern ACK can actually a…
rafael2k Aug 5, 2026
a014d40
mfsk: the fringe floor was the sync accept threshold, not the code
rafael2k Aug 6, 2026
1e54414
mfsk: interleave the codeword across the burst
rafael2k Aug 6, 2026
9768318
mfsk: search frequency as well as time when acquiring
rafael2k Aug 7, 2026
f370fcb
watterson: run the Doppler filter in double, it diverges at high samp…
rafael2k Aug 7, 2026
44041c7
arq/fsm: reconcile trunk's deferred LISTEN OFF with the rewritten FSM
rafael2k Aug 7, 2026
883d80d
docs: record the fringe-mode results, the RFDM comparison, and the 48…
rafael2k Aug 7, 2026
e4e6da5
tests/sim: measure connect latency, not just that a connect happened
rafael2k Aug 7, 2026
f22dee4
arq/fsm: anchor the CALL retry to PTT-OFF, not to enqueue
rafael2k Aug 7, 2026
cfaa935
arq: drop an unreachable pattern correlation; refuse to truncate call…
rafael2k Aug 7, 2026
2f6e62d
arq+docs: delete three inert config knobs and the stale FSM documenta…
rafael2k Aug 7, 2026
35319a8
arq: confirm the ACCEPT with a 0.64 s pattern, not a 3.74 s coded frame
rafael2k Aug 7, 2026
8f3020a
docs+utils: settle the short-control-frame question with an instrument
rafael2k Aug 7, 2026
198902c
utils+docs: measure the acquire/decode split instead of inferring it
rafael2k Aug 7, 2026
65424af
utils+docs: measure the directed-pattern ACCEPT, and find the suffix …
rafael2k Aug 7, 2026
533b4af
mfsk: score pattern tone lists in one pass; size the directed suffix …
rafael2k Aug 7, 2026
3f336d8
utils: put the instruments on one calibrated channel, and fix three w…
rafael2k Aug 8, 2026
e1577c7
utils+docs: the directed-pattern ACCEPT loses under fading; keep DATAC16
rafael2k Aug 8, 2026
58912ab
Merge mercuryv2 into mfsk-margin: vtable over per-instance locks
rafael2k Aug 13, 2026
0549ce0
arq: drop a duplicated comment block in encode_callsign_payload
rafael2k Aug 13, 2026
f68e533
docs(ARQ): document the two diagnostic environment variables
rafael2k Aug 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,16 @@ context-*.txt
copilot-*.txt
screen.jpg

# utils build outputs
# dependency files from -MMD
*.d

# test/bench binaries and their output dirs
utils/watterson_test
tests/connect_bench
/results/

utils/shortframe_sweep
utils/acquire_vs_decode
utils/hail_suffix_sweep
utils/resampler_bench
utils/rxcost_bench
13 changes: 9 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,11 @@ MERCURY_LINK_INPUTS = \
main.o common/cfg_utils.o common/iniparser/iniparser.o common/iniparser/dictionary.o \
datalink_arq/arq.o datalink_arq/arq_tnc.o datalink_arq/arith.o datalink_arq/arq_channels.o \
datalink_arq/arq_fsm.o datalink_arq/arq_protocol.o datalink_arq/arq_timing.o datalink_arq/arq_modem.o \
datalink_broadcast/broadcast.o datalink_broadcast/kiss.o modem/modem.o modem/framer.o modem/channel_busy.o modem/freedv/libfreedvdata.a \
audioio/audioio.a common/os_interop.o common/ring_buffer_posix.o common/shm_posix.o common/crc6.o common/hermes_log.o common/virtual_clock.o \
datalink_broadcast/broadcast.o datalink_broadcast/kiss.o modem/modem.o modem/modem_freedv.o \
modem/modem_mfsk.o modem/mfsk.o modem/mfsk_ofdm.o modem/mfsk_sync.o modem/mfsk_ldpc.o modem/mfsk_ldpc_1_16.o \
modem/mfsk_ldpc_2_16.o modem/mfsk_ldpc_3_16.o modem/mfsk_ldpc_5_16.o modem/mfsk_ldpc_8_16.o modem/framer.o \
modem/channel_busy.o modem/freedv/libfreedvdata.a audioio/audioio.a common/os_interop.o \
common/ring_buffer_posix.o common/shm_posix.o common/crc6.o common/hermes_log.o common/virtual_clock.o \
common/chan.o common/queue.o common/mercury_engine.o common/mercury_cli.o data_interfaces/tcp_interfaces.o data_interfaces/net.o \
gui_interface/ui_communication.o gui_interface/ui_status.o \
gui_interface/websocket/mongoose.o gui_interface/websocket/mercury_websocket.o \
Expand Down Expand Up @@ -196,8 +199,10 @@ MERCURY_CORE_OBJS = \
datalink_arq/arq.o datalink_arq/arq_tnc.o datalink_arq/arith.o datalink_arq/arq_channels.o \
datalink_arq/arq_fsm.o datalink_arq/arq_protocol.o datalink_arq/arq_timing.o datalink_arq/arq_modem.o \
datalink_broadcast/broadcast.o datalink_broadcast/kiss.o \
modem/modem.o modem/framer.o modem/channel_busy.o \
common/os_interop.o common/ring_buffer_posix.o common/shm_posix.o common/crc6.o common/hermes_log.o common/virtual_clock.o \
modem/modem.o modem/modem_freedv.o modem/modem_mfsk.o modem/mfsk.o modem/mfsk_ofdm.o modem/mfsk_sync.o \
modem/mfsk_ldpc.o modem/mfsk_ldpc_1_16.o modem/mfsk_ldpc_2_16.o modem/mfsk_ldpc_3_16.o \
modem/mfsk_ldpc_5_16.o modem/mfsk_ldpc_8_16.o modem/framer.o modem/channel_busy.o common/os_interop.o \
common/ring_buffer_posix.o common/shm_posix.o common/crc6.o common/hermes_log.o common/virtual_clock.o \
common/chan.o common/queue.o common/mercury_engine.o common/mercury_cli.o \
data_interfaces/tcp_interfaces.o data_interfaces/net.o \
gui_interface/ui_communication.o gui_interface/ui_status.o \
Expand Down
1 change: 1 addition & 0 deletions claude_context2.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
claude --resume a74b8149-410c-4d71-bf03-4dd2f3841699
12 changes: 0 additions & 12 deletions common/cfg_utils.c
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,7 @@ void cfg_set_defaults(mercury_config *cfg)
cfg->retry_downgrade_threshold = ARQ_RETRY_DOWNGRADE_THRESHOLD_DEFAULT;
cfg->channel_guard_ms = ARQ_CHANNEL_GUARD_MS_DEFAULT;
cfg->iss_post_ack_guard_ms = ARQ_ISS_POST_ACK_GUARD_MS_DEFAULT;
cfg->keepalive_interval_s = ARQ_KEEPALIVE_INTERVAL_S_DEFAULT;
cfg->keepalive_miss_limit = ARQ_KEEPALIVE_MISS_LIMIT_DEFAULT;
cfg->peer_payload_hold_s = ARQ_PEER_PAYLOAD_HOLD_S_DEFAULT;
cfg->startup_max_s = ARQ_STARTUP_MAX_S_DEFAULT;
cfg->busy_detect = false;
cfg->busy_threshold_db = 10;
cfg->busy_hysteresis_db = 3;
Expand Down Expand Up @@ -211,17 +208,11 @@ bool cfg_read(mercury_config *cfg, const char *ini_path)
i = iniparser_getint(ini, CFG_KEY_ARQ_ISS_POST_ACK_GUARD_MS, cfg->iss_post_ack_guard_ms);
if (i >= 200 && i <= 3000) cfg->iss_post_ack_guard_ms = i;

i = iniparser_getint(ini, CFG_KEY_ARQ_KEEPALIVE_INTERVAL_S, cfg->keepalive_interval_s);
if (i >= 5 && i <= 120) cfg->keepalive_interval_s = i;

i = iniparser_getint(ini, CFG_KEY_ARQ_KEEPALIVE_MISS_LIMIT, cfg->keepalive_miss_limit);
if (i >= 2 && i <= 20) cfg->keepalive_miss_limit = i;

i = iniparser_getint(ini, CFG_KEY_ARQ_PEER_PAYLOAD_HOLD_S, cfg->peer_payload_hold_s);
if (i >= 1 && i <= 120) cfg->peer_payload_hold_s = i;

i = iniparser_getint(ini, CFG_KEY_ARQ_STARTUP_MAX_S, cfg->startup_max_s);
if (i >= 2 && i <= 60) cfg->startup_max_s = i;

cfg->busy_detect = (bool) iniparser_getboolean(ini, CFG_KEY_BUSY_DETECT,
cfg->busy_detect ? 1 : 0);
Expand Down Expand Up @@ -338,10 +329,7 @@ bool cfg_write(const mercury_config *cfg, const char *ini_path)
fprintf(f, "retry_downgrade_threshold = %d\n", cfg->retry_downgrade_threshold);
fprintf(f, "channel_guard_ms = %d\n", cfg->channel_guard_ms);
fprintf(f, "iss_post_ack_guard_ms = %d\n", cfg->iss_post_ack_guard_ms);
fprintf(f, "keepalive_interval_s = %d\n", cfg->keepalive_interval_s);
fprintf(f, "keepalive_miss_limit = %d\n", cfg->keepalive_miss_limit);
fprintf(f, "peer_payload_hold_s = %d\n", cfg->peer_payload_hold_s);
fprintf(f, "startup_max_s = %d\n", cfg->startup_max_s);

fprintf(f, "\n[channel]\n");
fprintf(f, "busy_detect = %s\n", cfg->busy_detect ? "true" : "false");
Expand Down
9 changes: 0 additions & 9 deletions common/cfg_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,7 @@
#define CFG_KEY_ARQ_RETRY_DOWNGRADE_THRESHOLD "arq:retry_downgrade_threshold"
#define CFG_KEY_ARQ_CHANNEL_GUARD_MS "arq:channel_guard_ms"
#define CFG_KEY_ARQ_ISS_POST_ACK_GUARD_MS "arq:iss_post_ack_guard_ms"
#define CFG_KEY_ARQ_KEEPALIVE_INTERVAL_S "arq:keepalive_interval_s"
#define CFG_KEY_ARQ_KEEPALIVE_MISS_LIMIT "arq:keepalive_miss_limit"
#define CFG_KEY_ARQ_PEER_PAYLOAD_HOLD_S "arq:peer_payload_hold_s"
#define CFG_KEY_ARQ_STARTUP_MAX_S "arq:startup_max_s"
#define CFG_KEY_BUSY_DETECT "channel:busy_detect"
#define CFG_KEY_BUSY_THRESHOLD_DB "channel:busy_threshold_db"
#define CFG_KEY_BUSY_HYSTERESIS_DB "channel:busy_hysteresis_db"
Expand Down Expand Up @@ -99,14 +96,8 @@ typedef struct {
* Default 700, clamped 200..3000. */
int iss_post_ack_guard_ms; /* ARQ: ISS guard before resuming DATA
* TX after ACK. Default 900, 200..3000.*/
int keepalive_interval_s; /* ARQ: keepalive TX interval. Default
* 20, clamped 5..120. */
int keepalive_miss_limit; /* ARQ: missed keepalives before drop.
* Default 5, clamped 2..20. */
int peer_payload_hold_s; /* ARQ: hold peer payload mode after
* activity. Default 15, clamped 1..120.*/
int startup_max_s; /* ARQ: control-mode-only startup
* window. Default 10, clamped 2..60. */
float tx_gain_db; /* Linear-equivalent gain on the modulator
* TX samples, in dB. 0.0 = no change.
* Range -20.0 .. +20.0 (clamped). */
Expand Down
3 changes: 0 additions & 3 deletions common/mercury_engine.c
Original file line number Diff line number Diff line change
Expand Up @@ -125,10 +125,7 @@ int mercury_engine_init(const mercury_config *cfg,
arq_set_retry_downgrade_threshold(cfg->retry_downgrade_threshold);
arq_set_channel_guard_ms(cfg->channel_guard_ms);
arq_set_iss_post_ack_guard_ms(cfg->iss_post_ack_guard_ms);
arq_set_keepalive_interval_s(cfg->keepalive_interval_s);
arq_set_keepalive_miss_limit(cfg->keepalive_miss_limit);
arq_set_peer_payload_hold_s(cfg->peer_payload_hold_s);
arq_set_startup_max_s(cfg->startup_max_s);
modem_set_tx_gain(powf(10.0f, cfg->tx_gain_db / 20.0f));
modem_set_tx_delay_ms(cfg->tx_delay_ms);
modem_set_busy_cfg((float)cfg->busy_threshold_db,
Expand Down
98 changes: 59 additions & 39 deletions datalink_arq/arq.c
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,22 @@ static void cb_send_tx_frame(int packet_type, int mode,
arq_modem_enqueue(&action);
}

/* Enqueue a Welch-Costas MFSK pattern ACK for the modem TX worker. Carries no
* coded frame — the modem synthesises the ack/break tone burst directly (see
* send_pattern_ack in modem.c). `mode` is the current payload mode, used only
* so the worker can key at the right passband geometry. */
static void cb_send_pattern_ack(int mode, int pattern_kind)
{
arq_action_t action = {
.type = ARQ_ACTION_TX_PATTERN,
.mode = mode,
.frame_size = 0,
.frame_count = 1,
.pattern_kind = pattern_kind,
};
arq_modem_enqueue(&action);
}

static void cb_notify_connected(const char *remote_call, const char *local_call)
{
pthread_mutex_lock(&g_conn_lock);
Expand Down Expand Up @@ -857,22 +873,11 @@ void arq_handle_incoming_frame(uint8_t *data, size_t frame_size, float rx_snr)
{
switch (hdr.subtype)
{
/* ACK: the coded DATAC16 ACK is used only for the post-ACCEPT connect
* confirmation now; in-session ACKs are the MFSK pattern (synthesised
* in modem.c), not a control frame here. */
case ARQ_SUBTYPE_ACK: ev.id = ARQ_EV_RX_ACK; break;
case ARQ_SUBTYPE_DISCONNECT: ev.id = ARQ_EV_RX_DISCONNECT; break;
case ARQ_SUBTYPE_TURN_REQ: ev.id = ARQ_EV_RX_TURN_REQ; break;
case ARQ_SUBTYPE_TURN_ACK: ev.id = ARQ_EV_RX_TURN_ACK; break;
case ARQ_SUBTYPE_KEEPALIVE: ev.id = ARQ_EV_RX_KEEPALIVE; break;
case ARQ_SUBTYPE_KEEPALIVE_ACK: ev.id = ARQ_EV_RX_KEEPALIVE_ACK; break;
case ARQ_SUBTYPE_MODE_REQ:
ev.id = ARQ_EV_RX_MODE_REQ;
ev.mode = (frame_size > ARQ_FRAME_HDR_SIZE)
? (int)data[ARQ_FRAME_HDR_SIZE] : 0;
break;
case ARQ_SUBTYPE_MODE_ACK:
ev.id = ARQ_EV_RX_MODE_ACK;
ev.mode = (frame_size > ARQ_FRAME_HDR_SIZE)
? (int)data[ARQ_FRAME_HDR_SIZE] : 0;
break;
default:
return;
}
Expand All @@ -885,6 +890,16 @@ void arq_handle_incoming_frame(uint8_t *data, size_t frame_size, float rx_snr)
evq_push(&ev);
}

void arq_post_pattern_ack(bool is_break)
{
arq_event_t ev = {0};
ev.id = ARQ_EV_RX_ACK;
ev.rx_flags = is_break ? ARQ_FLAG_HAS_DATA : 0;
/* session_id left 0: patterns carry none, and the dispatch session-ID
* gate treats 0 as "unknown/accept". */
evq_push(&ev);
}

/* ======================================================================
* Public arq.h API
* ====================================================================== */
Expand Down Expand Up @@ -924,6 +939,7 @@ int arq_init(size_t frame_size, int mode)

static const arq_fsm_callbacks_t cbs = {
.send_tx_frame = cb_send_tx_frame,
.send_pattern_ack = cb_send_pattern_ack,
.notify_connected = cb_notify_connected,
.notify_pending = cb_notify_pending,
.notify_cancelpending = cb_notify_cancelpending,
Expand Down Expand Up @@ -1088,8 +1104,36 @@ bool arq_get_runtime_snapshot(arq_runtime_snapshot_t *snapshot)
pthread_mutex_lock(&g_sess_lock);
snapshot->initialized = true;
snapshot->connected = (g_sess.conn_state == ARQ_CONN_CONNECTED);
/* Only while a pattern ACK could actually arrive.
*
* This gates a correlation that modem.c runs over a ~3-burst window on
* EVERY capture chunk, so leaving it on for all of CONNECTED cost about
* half the RX budget for the whole session: measured 3.5k samp/s consumed
* against 8k arriving, the capture ring growing to ~400 kB, and the backlog
* guard then flushing bursts that were still arriving. Trunk, which has no
* pattern ACK, holds 7.99k samp/s on the same bench.
*
* WAIT_ACK is by definition the only state where a pattern ACK is due
* (see ARQ_DFLOW_WAIT_ACK). Everywhere else -- above all IDLE_IRS, where
* the receiver is busy demodulating a 13.5 s burst -- there is nothing to
* detect and the CPU is needed elsewhere.
*
* ACCEPTING is the one other state where a pattern is due -- the caller's
* connect confirm -- but it is emphatically NOT the whole ~18 s ACCEPT
* window, which is how this was first written and what cost the RX budget
* at exactly the wrong moment. The confirm's arrival time is predictable
* (one ISS post-ACK guard after our ACCEPT leaves the air), so the FSM opens
* a bounded few-second window at ACCEPT PTT-OFF and closes it on any state
* change: see confirm_listen_until_ms / ARQ_CONNECT_CONFIRM_LISTEN_MS. */
snapshot->expect_pattern_ack =
(g_sess.conn_state == ARQ_CONN_CONNECTED &&
g_sess.dflow_state == ARQ_DFLOW_WAIT_ACK) ||
(g_sess.conn_state == ARQ_CONN_ACCEPTING &&
g_sess.confirm_listen_until_ms != 0 &&
time_now_ms() < g_sess.confirm_listen_until_ms);
snapshot->trx = trx;
snapshot->tx_backlog_bytes = backlog + g_sess.tx_inflight_bytes;
snapshot->tx_backlog_bytes = backlog +
(g_sess.tx_frame_present ? g_sess.tx_frame_len : 0);
snapshot->speed_level = g_sess.speed_level;
snapshot->payload_mode = g_sess.payload_mode;
snapshot->peer_tx_mode = g_sess.peer_tx_mode;
Expand Down Expand Up @@ -1197,22 +1241,6 @@ void arq_set_iss_post_ack_guard_ms(int ms)
HLOGI(LOG_COMP, "iss_post_ack_guard_ms = %d", atomic_load(&arq_iss_post_ack_guard_ms));
}

void arq_set_keepalive_interval_s(int s)
{
if (s < 5) s = (s <= 0) ? ARQ_KEEPALIVE_INTERVAL_S_DEFAULT : 5;
if (s > 120) s = 120;
atomic_store(&arq_keepalive_interval_s, s);
HLOGI(LOG_COMP, "keepalive_interval_s = %d", atomic_load(&arq_keepalive_interval_s));
}

void arq_set_keepalive_miss_limit(int n)
{
if (n < 2) n = (n <= 0) ? ARQ_KEEPALIVE_MISS_LIMIT_DEFAULT : 2;
if (n > 20) n = 20;
atomic_store(&arq_keepalive_miss_limit, n);
HLOGI(LOG_COMP, "keepalive_miss_limit = %d", atomic_load(&arq_keepalive_miss_limit));
}

void arq_set_ladder_up_successes(int n)
{
if (n < 1) n = (n <= 0) ? ARQ_LADDER_UP_SUCCESSES_DEFAULT : 1;
Expand Down Expand Up @@ -1245,14 +1273,6 @@ void arq_set_peer_payload_hold_s(int s)
HLOGI(LOG_COMP, "peer_payload_hold_s = %d", atomic_load(&arq_peer_payload_hold_s));
}

void arq_set_startup_max_s(int s)
{
if (s < 2) s = (s <= 0) ? ARQ_STARTUP_MAX_S_DEFAULT : 2;
if (s > 60) s = 60;
atomic_store(&arq_startup_max_s, s);
HLOGI(LOG_COMP, "startup_max_s = %d", atomic_load(&arq_startup_max_s));
}

void reset_arq_info(arq_info *conn)
{
if (!conn) return;
Expand Down
33 changes: 29 additions & 4 deletions datalink_arq/arq.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,19 @@ typedef enum
ARQ_ACTION_NONE = 0,
ARQ_ACTION_TX_CONTROL = 1,
ARQ_ACTION_TX_PAYLOAD = 2,
ARQ_ACTION_MODE_SWITCH = 3
ARQ_ACTION_MODE_SWITCH = 3,
ARQ_ACTION_TX_PATTERN = 4 /* emit a Welch-Costas MFSK pattern ACK — no
* coded frame; pattern_kind selects ACK vs
* ACK+TURN (break). Airtime ~0.64 s. */
} arq_action_type_t;

/** @brief Pattern-ACK symbol kind (ARQ_ACTION_TX_PATTERN). */
typedef enum
{
ARQ_PATTERN_ACK = 0, /* plain ACK: "got your frame, keep the floor" */
ARQ_PATTERN_BREAK = 1 /* ACK+TURN: "got it AND I have data" (HAS_DATA) */
} arq_pattern_kind_t;

/** @brief Single modem action item popped by modem TX worker. */
typedef struct
{
Expand All @@ -81,13 +91,20 @@ typedef struct
int frame_count; /* frames in this PTT burst (>= 1); the modem reads
* frame_count * frame_size bytes and modulates them
* behind a single preamble */
int pattern_kind; /* arq_pattern_kind_t, valid for ARQ_ACTION_TX_PATTERN */
} arq_action_t;

/** @brief Snapshot of current ARQ runtime state for telemetry/decision making. */
typedef struct
{
bool initialized;
bool connected;
/* True when a Welch-Costas pattern ACK could legitimately arrive: the
* answerer awaiting the caller's connect-confirm (ACCEPTING) or an active
* session (CONNECTED). The RX pattern detector runs its per-chunk
* correlation only when this is set — running it during CALLING/LISTENING/
* idle is pure overhead that slows the connect-critical DATAC16 decode. */
bool expect_pattern_ack;
int trx;
int tx_backlog_bytes;
int speed_level;
Expand Down Expand Up @@ -266,6 +283,17 @@ void arq_notify_cq_tx_complete(void);
*/
void arq_handle_incoming_frame(uint8_t *data, size_t frame_size, float rx_snr);

/**
* @brief Post a synthesized pattern-ACK event (from the RX pattern detector).
*
* A Welch-Costas pattern ACK carries no coded header, so the modem RX worker
* synthesizes the FSM event directly. In stop-and-wait only one frame is
* outstanding, so an ACK unambiguously acks it.
*
* @param is_break true = ACK+TURN (HAS_DATA piggyback), false = plain ACK.
*/
void arq_post_pattern_ack(bool is_break);

/**
* @brief Feed decoder/link metrics into ARQ adaptation.
* @param sync Decoder sync flag.
Expand Down Expand Up @@ -342,13 +370,10 @@ void arq_set_data_retry_slots(int slots);
/* Setters for the newly-atomic ARQ timing/ladder tunables. */
void arq_set_channel_guard_ms(int ms);
void arq_set_iss_post_ack_guard_ms(int ms);
void arq_set_keepalive_interval_s(int s);
void arq_set_keepalive_miss_limit(int n);
void arq_set_ladder_up_successes(int n);
void arq_set_retry_downgrade_threshold(int n);
void arq_set_mode_hold_after_downgrade_s(int s);
void arq_set_peer_payload_hold_s(int s);
void arq_set_startup_max_s(int s);

/**
* @brief Trigger outgoing call attempt using current ARQ addresses.
Expand Down
Loading
Loading