Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
cb6cd96
modem: port v1's 32-MFSK (cl_mfsk) from C++ to C + MFSK-vs-FSK compar…
rafael2k Jul 15, 2026
ebf9465
docs: MFSK-PORT — add honest acquisition analysis
rafael2k Jul 15, 2026
3a1fdbe
modem: port v1 OFDM framing subset for the MFSK mode (mfsk_ofdm.{c,h})
rafael2k Jul 15, 2026
dca49c2
modem: port v1 MFSK preamble acquisition (time_sync_mfsk_corr) to C
rafael2k Jul 15, 2026
f2b3f4f
docs: MFSK-PORT — end-to-end acquire+decode floor through ch (resolve…
rafael2k Jul 15, 2026
17b6fec
modem: wire rate-1/16 LDPC into the MFSK mode (encode/decode + coded …
rafael2k Jul 15, 2026
3b3e4ec
modem: add MFSK postamble (dual-ended acquisition) + full mode compar…
rafael2k Jul 15, 2026
9894c95
modem: MFSK LDPC rate ladder (1/16..8/16) — rate is nearly free, 1/16…
rafael2k Jul 16, 2026
32a350e
mfsk: port v1 detect_ack_pattern (control-plane pattern-ACK)
rafael2k Jul 16, 2026
87f7cba
modem: introduce modem_backend vtable; FreeDV as backend 1
rafael2k Jul 16, 2026
96931b0
modem: MFSK weak-signal backend (backend 2)
rafael2k Jul 16, 2026
f18871e
arq: register MFSK as the OLLA bottom rung (below DATAC15)
rafael2k Jul 16, 2026
25b7cef
arq: rethink data plane — delivery-driven stop-and-wait + pattern ACK
rafael2k Jul 16, 2026
2de7a03
arq/modem: fix connect turnaround + MFSK data RX in the live path
rafael2k Jul 18, 2026
ce33622
tests: build libfreedvdata.a on demand so `make test` works from a cl…
rafael2k Jul 18, 2026
68f46fa
tests: 2-arg notify fakes in test_arq_ladder
rafael2k Jul 20, 2026
231875b
tests: wire test_freedv_harq into the suite after rebase onto trunk
rafael2k Jul 22, 2026
7732b02
modem/mfsk: postamble-anchored sync fallback for clipped-head bursts
rafael2k Jul 22, 2026
6796065
arq/fsm: IRS mirrors the ISS delivery-driven ladder
rafael2k Jul 22, 2026
c159feb
arq: port LISTEN OFF as a radio release into the rewritten FSM
rafael2k Jul 30, 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
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/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 \
Expand Down
49 changes: 34 additions & 15 deletions datalink_arq/arq.c
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,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 @@ -837,22 +853,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 @@ -865,6 +870,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 @@ -904,6 +919,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 @@ -1068,8 +1084,11 @@ 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);
snapshot->expect_pattern_ack = (g_sess.conn_state == ARQ_CONN_ACCEPTING ||
g_sess.conn_state == ARQ_CONN_CONNECTED);
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
30 changes: 29 additions & 1 deletion 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
Loading
Loading