Skip to content

Commit fb86bec

Browse files
committed
Add peer fading reporting and fix CONNECT_ACK callsign display
1 parent 406b912 commit fb86bec

File tree

11 files changed

+162
-63
lines changed

11 files changed

+162
-63
lines changed

src/gui/app.cpp

Lines changed: 45 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -349,29 +349,43 @@ App::App(const Options& opts) : options_(opts), sim_ui_visible_(opts.enable_sim)
349349
protocol_.onPingReceived();
350350
});
351351

352-
protocol_.setDataModeChangedCallback([this](Modulation mod, CodeRate rate, float snr_db) {
352+
protocol_.setDataModeChangedCallback([this](Modulation mod, CodeRate rate, float snr_db, float peer_fading) {
353353
// Update modem engine with new data mode
354354
modem_.setDataMode(mod, rate);
355355

356-
// Get waveform mode and fading index from modem
356+
// Local estimate for operator visibility/debugging.
357357
auto waveform = modem_.getWaveformMode();
358-
float fading = modem_.getFadingIndex();
359-
360-
const char* quality = fadingToQuality(fading);
358+
float local_fading = modem_.getFadingIndex();
359+
360+
const char* local_quality = fadingToQuality(local_fading);
361+
bool peer_fading_valid = (peer_fading >= 0.0f);
362+
const char* peer_quality = peer_fading_valid ? fadingToQuality(peer_fading) : "n/a";
363+
char peer_fading_text[32];
364+
if (peer_fading_valid) {
365+
snprintf(peer_fading_text, sizeof(peer_fading_text), "%.2f %s", peer_fading, peer_quality);
366+
} else {
367+
snprintf(peer_fading_text, sizeof(peer_fading_text), "n/a");
368+
}
361369
const char* wf_name = waveformDisplayName(waveform);
362-
guiLog("MODE_CHANGE: %s %s %s (peer_snr=%.1f dB, local_fading=%.2f %s)",
370+
guiLog("MODE_CHANGE: %s %s %s (peer_snr=%.1f dB, peer_fading=%s, local_fading=%.2f %s)",
363371
wf_name, modulationToString(mod), codeRateToString(rate),
364-
snr_db, fading, quality);
372+
snr_db, peer_fading_text,
373+
local_fading, local_quality);
365374

366375
// Format display with waveform info and channel quality
367-
char buf[120];
376+
char buf[200];
368377
if (waveform == protocol::WaveformMode::MC_DPSK) {
369-
snprintf(buf, sizeof(buf), "[MODE] MC-DPSK 8 carriers %s (peer SNR=%d dB, local fading=%.2f %s)",
370-
codeRateToString(rate), static_cast<int>(snr_db), fading, quality);
378+
snprintf(buf, sizeof(buf),
379+
"[MODE] MC-DPSK 8 carriers %s (peer SNR=%d dB, peer fading=%s, local fading=%.2f %s)",
380+
codeRateToString(rate), static_cast<int>(snr_db),
381+
peer_fading_text,
382+
local_fading, local_quality);
371383
} else {
372-
snprintf(buf, sizeof(buf), "[MODE] %s %s %s (peer SNR=%d dB, local fading=%.2f %s)",
384+
snprintf(buf, sizeof(buf),
385+
"[MODE] %s %s %s (peer SNR=%d dB, peer fading=%s, local fading=%.2f %s)",
373386
wf_name, modulationToString(mod), codeRateToString(rate),
374-
static_cast<int>(snr_db), fading, quality);
387+
static_cast<int>(snr_db), peer_fading_text,
388+
local_fading, local_quality);
375389
}
376390
rx_log_.push_back(buf);
377391
if (rx_log_.size() > MAX_RX_LOG) {
@@ -713,24 +727,35 @@ void App::initVirtualStation() {
713727
}
714728
});
715729

716-
virtual_protocol_.setDataModeChangedCallback([this](Modulation mod, CodeRate rate, float snr_db) {
730+
virtual_protocol_.setDataModeChangedCallback([this](Modulation mod, CodeRate rate, float snr_db, float peer_fading) {
717731
// Update virtual modem engine with new data mode
718732
virtual_modem_->setDataMode(mod, rate);
719733

720734
// Show [SIM-MODE] line so user can see what the responder actually measured
721735
auto waveform = virtual_modem_->getWaveformMode();
722-
float fading = virtual_modem_->getFadingIndex();
723-
const char* quality = fadingToQuality(fading);
736+
float local_fading = virtual_modem_->getFadingIndex();
737+
const char* local_quality = fadingToQuality(local_fading);
738+
bool peer_fading_valid = (peer_fading >= 0.0f);
739+
const char* peer_quality = peer_fading_valid ? fadingToQuality(peer_fading) : "n/a";
740+
char peer_fading_text[32];
741+
if (peer_fading_valid) {
742+
snprintf(peer_fading_text, sizeof(peer_fading_text), "%.2f %s", peer_fading, peer_quality);
743+
} else {
744+
snprintf(peer_fading_text, sizeof(peer_fading_text), "n/a");
745+
}
724746
const char* wf_name = waveformDisplayName(waveform);
725747

726-
guiLog("SIM: Virtual MODE_CHANGE: %s %s %s (peer_snr=%.1f dB, local_fading=%.2f %s)",
748+
guiLog("SIM: Virtual MODE_CHANGE: %s %s %s (peer_snr=%.1f dB, peer_fading=%s, local_fading=%.2f %s)",
727749
wf_name, modulationToString(mod), codeRateToString(rate),
728-
snr_db, fading, quality);
750+
snr_db, peer_fading_text,
751+
local_fading, local_quality);
729752

730-
char buf[120];
731-
snprintf(buf, sizeof(buf), "[SIM-MODE] %s %s %s (peer SNR=%d dB, local fading=%.2f %s)",
753+
char buf[200];
754+
snprintf(buf, sizeof(buf),
755+
"[SIM-MODE] %s %s %s (peer SNR=%d dB, peer fading=%s, local fading=%.2f %s)",
732756
wf_name, modulationToString(mod), codeRateToString(rate),
733-
static_cast<int>(snr_db), fading, quality);
757+
static_cast<int>(snr_db), peer_fading_text,
758+
local_fading, local_quality);
734759
rx_log_.push_back(buf);
735760
if (rx_log_.size() > MAX_RX_LOG) rx_log_.pop_front();
736761
});

src/gui/modem/modem_rx_decode.cpp

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,23 @@ void ModemEngine::notifyFrameParsed(const Bytes& frame_data, protocol::v2::Frame
5555
// Try parsing for more details
5656
auto connect = v2::ConnectFrame::deserialize(frame_data);
5757
if (connect) {
58-
status_callback_("[" + type_str + "] " + connect->getSrcCallsign() +
59-
" -> " + connect->getDstCallsign());
58+
std::string src = connect->getSrcCallsign();
59+
std::string dst = connect->getDstCallsign();
60+
61+
if (src.empty()) {
62+
src = "UNKNOWN";
63+
}
64+
if (dst.empty()) {
65+
// Hash-addressed CONNECT_ACK/NAK may not carry dst callsign.
66+
// Show local station prefix instead of a blank target in UI.
67+
if (!log_prefix_.empty() && log_prefix_ != "MODEM") {
68+
dst = log_prefix_;
69+
} else {
70+
dst = "UNKNOWN";
71+
}
72+
}
73+
74+
status_callback_("[" + type_str + "] " + src + " -> " + dst);
6075
return;
6176
}
6277

src/protocol/connection.cpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,7 @@ void Connection::acceptCall() {
275275

276276
auto ack = v2::ConnectFrame::makeConnectAck(local_call_, remote_call_,
277277
static_cast<uint8_t>(negotiated_mode_),
278-
rec_mod, rec_rate, measured_snr_db_);
278+
rec_mod, rec_rate, measured_snr_db_, fading_index_);
279279
Bytes ack_data = ack.serialize();
280280

281281
LOG_MODEM(INFO, "Connection: Sending CONNECT_ACK (%zu bytes)", ack_data.size());
@@ -290,7 +290,7 @@ void Connection::acceptCall() {
290290

291291
// Notify application of initial data mode
292292
if (on_data_mode_changed_) {
293-
on_data_mode_changed_(data_modulation_, data_code_rate_, measured_snr_db_);
293+
on_data_mode_changed_(data_modulation_, data_code_rate_, measured_snr_db_, fading_index_);
294294
}
295295
}
296296

@@ -667,7 +667,8 @@ void Connection::onFrameReceived(const Bytes& frame_data) {
667667

668668
// Notify application of mode change
669669
if (on_data_mode_changed_) {
670-
on_data_mode_changed_(data_modulation_, data_code_rate_, pending_snr_db_);
670+
on_data_mode_changed_(data_modulation_, data_code_rate_,
671+
pending_snr_db_, pending_fading_index_);
671672
}
672673
} else {
673674
// Regular data ACK
@@ -803,6 +804,7 @@ void Connection::tick(uint32_t elapsed_ms) {
803804
auto frame = v2::ControlFrame::makeModeChange(local_call_, remote_call_,
804805
mode_change_seq_, pending_modulation_,
805806
pending_code_rate_, pending_snr_db_,
807+
pending_fading_index_,
806808
pending_reason_);
807809
transmitFrame(frame.serialize());
808810
mode_change_timeout_ms_ = MODE_CHANGE_TIMEOUT_MS;

src/protocol/connection.hpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ class Connection {
202202
bool isFading() const { return fading_index_ > 0.65f; }
203203

204204
// Callback when remote station requests mode change
205-
using DataModeChangedCallback = std::function<void(Modulation mod, CodeRate rate, float snr_db)>;
205+
using DataModeChangedCallback = std::function<void(Modulation mod, CodeRate rate, float snr_db, float peer_fading_index)>;
206206
void setDataModeChangedCallback(DataModeChangedCallback cb) { on_data_mode_changed_ = cb; }
207207

208208
// Request mode change to remote station
@@ -246,6 +246,7 @@ class Connection {
246246
Modulation pending_modulation_ = Modulation::DQPSK;
247247
CodeRate pending_code_rate_ = CodeRate::R1_4;
248248
float pending_snr_db_ = 15.0f;
249+
float pending_fading_index_ = 0.0f;
249250
uint8_t pending_reason_ = 0;
250251
static constexpr uint32_t MODE_CHANGE_TIMEOUT_MS = 45000; // 45s for DPSK round trip
251252
static constexpr int MODE_CHANGE_MAX_RETRIES = 2;

src/protocol/connection_handlers.cpp

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -177,18 +177,27 @@ void Connection::handleConnect(const v2::ConnectFrame& frame, const std::string&
177177
data_code_rate_ = rec_rate;
178178
arq_.setCodeRate(data_code_rate_); // Update ARQ for correct total_cw calculation
179179

180-
// Use hash-based method since we may not know the actual callsign
181-
// CONNECT_ACK now carries the initial data mode - no separate MODE_CHANGE needed!
182-
auto ack = v2::ConnectFrame::makeConnectAckByHash(local_call_, frame.src_hash,
183-
static_cast<uint8_t>(negotiated_mode_),
184-
rec_mod, rec_rate, snr_db);
185-
transmitFrame(ack.serialize());
180+
// Prefer full-callsign CONNECT_ACK when the initiator callsign is known,
181+
// fallback to hash-only ACK when we only have src_hash.
182+
Bytes ack_data;
183+
if (!src_call.empty()) {
184+
auto ack = v2::ConnectFrame::makeConnectAck(local_call_, src_call,
185+
static_cast<uint8_t>(negotiated_mode_),
186+
rec_mod, rec_rate, snr_db, fading_index_);
187+
ack_data = ack.serialize();
188+
} else {
189+
auto ack = v2::ConnectFrame::makeConnectAckByHash(local_call_, frame.src_hash,
190+
static_cast<uint8_t>(negotiated_mode_),
191+
rec_mod, rec_rate, snr_db, fading_index_);
192+
ack_data = ack.serialize();
193+
}
194+
transmitFrame(ack_data);
186195
enterConnected();
187196
// NOTE: Don't call on_handshake_confirmed_ yet - wait for first frame from initiator
188197

189198
// Notify application of initial data mode
190199
if (on_data_mode_changed_) {
191-
on_data_mode_changed_(data_modulation_, data_code_rate_, snr_db);
200+
on_data_mode_changed_(data_modulation_, data_code_rate_, snr_db, fading_index_);
192201
}
193202
} else {
194203
pending_remote_call_ = src_call.empty() ? "REMOTE" : src_call;
@@ -217,6 +226,7 @@ void Connection::handleConnectAck(const v2::ConnectFrame& frame, const std::stri
217226
Modulation init_mod = static_cast<Modulation>(frame.initial_modulation);
218227
CodeRate init_rate = static_cast<CodeRate>(frame.initial_code_rate);
219228
float snr_db = v2::decodeSNR(frame.measured_snr);
229+
float peer_fading = v2::decodeFadingIndex(frame.mode_capabilities);
220230

221231
// Apply the initial data mode immediately
222232
data_modulation_ = init_mod;
@@ -228,9 +238,9 @@ void Connection::handleConnectAck(const v2::ConnectFrame& frame, const std::stri
228238
remote_call_ = src_call;
229239
}
230240

231-
LOG_MODEM(INFO, "Connection: Connected to %s (waveform=%s, data=%s %s, SNR=%.1f dB)",
241+
LOG_MODEM(INFO, "Connection: Connected to %s (waveform=%s, data=%s %s, SNR=%.1f dB, peer_fading=%.2f)",
232242
remote_call_.c_str(), waveformModeToString(negotiated_mode_),
233-
modulationToString(data_modulation_), codeRateToString(data_code_rate_), snr_db);
243+
modulationToString(data_modulation_), codeRateToString(data_code_rate_), snr_db, peer_fading);
234244

235245
// We are the initiator - we sent CONNECT and received CONNECT_ACK
236246
is_initiator_ = true;
@@ -246,7 +256,7 @@ void Connection::handleConnectAck(const v2::ConnectFrame& frame, const std::stri
246256

247257
// Notify application of initial data mode
248258
if (on_data_mode_changed_) {
249-
on_data_mode_changed_(data_modulation_, data_code_rate_, snr_db);
259+
on_data_mode_changed_(data_modulation_, data_code_rate_, snr_db, peer_fading);
250260
}
251261
}
252262

@@ -342,11 +352,12 @@ void Connection::handleModeChange(const v2::ControlFrame& frame, const std::stri
342352
case v2::ModeChangeReason::INITIAL_SETUP: reason_str = "initial setup"; break;
343353
}
344354

345-
LOG_MODEM(INFO, "Connection: MODE_CHANGE from %s: %s %s (SNR=%.1f dB, reason=%s)",
355+
LOG_MODEM(INFO, "Connection: MODE_CHANGE from %s: %s %s (SNR=%.1f dB, fading=%.2f, reason=%s)",
346356
remote_call_.c_str(),
347357
modulationToString(info.modulation),
348358
codeRateToString(info.code_rate),
349359
info.snr_db,
360+
info.fading_index,
350361
reason_str);
351362

352363
// Update local state
@@ -359,7 +370,7 @@ void Connection::handleModeChange(const v2::ControlFrame& frame, const std::stri
359370

360371
// Notify application of mode change
361372
if (on_data_mode_changed_) {
362-
on_data_mode_changed_(info.modulation, info.code_rate, info.snr_db);
373+
on_data_mode_changed_(info.modulation, info.code_rate, info.snr_db, info.fading_index);
363374
}
364375
}
365376

@@ -383,6 +394,7 @@ void Connection::requestModeChange(Modulation new_mod, CodeRate new_rate,
383394
pending_modulation_ = new_mod;
384395
pending_code_rate_ = new_rate;
385396
pending_snr_db_ = measured_snr;
397+
pending_fading_index_ = fading_index_;
386398
pending_reason_ = reason;
387399
mode_change_pending_ = true;
388400
mode_change_retry_count_ = 0;
@@ -391,7 +403,7 @@ void Connection::requestModeChange(Modulation new_mod, CodeRate new_rate,
391403
mode_change_seq_++;
392404
auto frame = v2::ControlFrame::makeModeChange(local_call_, remote_call_,
393405
mode_change_seq_, new_mod, new_rate,
394-
measured_snr, reason);
406+
measured_snr, fading_index_, reason);
395407
transmitFrame(frame.serialize());
396408

397409
// NOTE: Don't update local mode until ACK is received

src/protocol/frame_v2.cpp

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ ControlFrame ControlFrame::makeKeepalive(const std::string& src, const std::stri
208208

209209
ControlFrame ControlFrame::makeModeChange(const std::string& src, const std::string& dst,
210210
uint16_t seq, Modulation new_mod, CodeRate new_rate,
211-
float snr_db, uint8_t reason) {
211+
float snr_db, float fading_index, uint8_t reason) {
212212
ControlFrame f;
213213
f.type = FrameType::MODE_CHANGE;
214214
f.flags = Flags::VERSION_V2;
@@ -219,14 +219,14 @@ ControlFrame ControlFrame::makeModeChange(const std::string& src, const std::str
219219
f.payload[1] = static_cast<uint8_t>(new_rate);
220220
f.payload[2] = encodeSNR(snr_db);
221221
f.payload[3] = reason;
222-
f.payload[4] = 0; // Reserved
222+
f.payload[4] = encodeFadingIndex(fading_index);
223223
f.payload[5] = 0; // Reserved
224224
return f;
225225
}
226226

227227
ControlFrame ControlFrame::makeModeChangeByHash(const std::string& src, uint32_t dst_hash,
228228
uint16_t seq, Modulation new_mod, CodeRate new_rate,
229-
float snr_db, uint8_t reason) {
229+
float snr_db, float fading_index, uint8_t reason) {
230230
ControlFrame f;
231231
f.type = FrameType::MODE_CHANGE;
232232
f.flags = Flags::VERSION_V2;
@@ -237,7 +237,7 @@ ControlFrame ControlFrame::makeModeChangeByHash(const std::string& src, uint32_t
237237
f.payload[1] = static_cast<uint8_t>(new_rate);
238238
f.payload[2] = encodeSNR(snr_db);
239239
f.payload[3] = reason;
240-
f.payload[4] = 0; // Reserved
240+
f.payload[4] = encodeFadingIndex(fading_index);
241241
f.payload[5] = 0; // Reserved
242242
return f;
243243
}
@@ -628,7 +628,7 @@ ConnectFrame ConnectFrame::makeConnect(const std::string& src, const std::string
628628

629629
ConnectFrame ConnectFrame::makeConnectAck(const std::string& src, const std::string& dst,
630630
uint8_t neg_mode, Modulation init_mod, CodeRate init_rate,
631-
float snr_db) {
631+
float snr_db, float fading_index) {
632632
ConnectFrame f;
633633
f.type = FrameType::CONNECT_ACK;
634634
f.flags = Flags::VERSION_V2;
@@ -641,7 +641,8 @@ ConnectFrame ConnectFrame::makeConnectAck(const std::string& src, const std::str
641641
std::strncpy(f.dst_callsign, dst.c_str(), MAX_CALLSIGN_LEN - 1);
642642
f.dst_callsign[MAX_CALLSIGN_LEN - 1] = '\0';
643643

644-
f.mode_capabilities = 0; // Not used in ACK
644+
// CONNECT_ACK reuses this byte to carry responder fading index.
645+
f.mode_capabilities = encodeFadingIndex(fading_index);
645646
f.negotiated_mode = neg_mode;
646647

647648
// Initial data mode - eliminates separate MODE_CHANGE after connect
@@ -689,7 +690,7 @@ ConnectFrame ConnectFrame::makeDisconnect(const std::string& src, const std::str
689690

690691
ConnectFrame ConnectFrame::makeConnectAckByHash(const std::string& src, uint32_t dst_hash,
691692
uint8_t neg_mode, Modulation init_mod, CodeRate init_rate,
692-
float snr_db) {
693+
float snr_db, float fading_index) {
693694
ConnectFrame f;
694695
f.type = FrameType::CONNECT_ACK;
695696
f.flags = Flags::VERSION_V2;
@@ -702,7 +703,7 @@ ConnectFrame ConnectFrame::makeConnectAckByHash(const std::string& src, uint32_t
702703
// dst_callsign unknown - leave empty (will be filled from received CONNECT)
703704
f.dst_callsign[0] = '\0';
704705

705-
f.mode_capabilities = 0;
706+
f.mode_capabilities = encodeFadingIndex(fading_index);
706707
f.negotiated_mode = neg_mode;
707708

708709
// Initial data mode - eliminates separate MODE_CHANGE after connect

0 commit comments

Comments
 (0)