using RXResult = mesh::MorseRxResult; static void level(RadioLibWrapper& w, float rssi, uint32_t duration) { w.rssi = rssi; for (uint32_t elapsed = 0; elapsed < duration; elapsed += 2) { advance(2); w.serviceCarrierWave(); } } static void pattern(RadioLibWrapper& w, const char* code, uint32_t unit = 48) { for (size_t symbol = 0; code[symbol]; ++symbol) { level(w, -80, unit * (code[symbol] == '-' ? 3 : 1)); if (code[symbol + 1]) level(w, -110, unit); } } static void receive_e(RadioLibWrapper& w) { level(w, -110, 520); pattern(w, "."); level(w, -110, 336); assert(!strncmp(w.morseCaptureText(), "E", 1)); } static void packet_receive_restored(const RadioLibWrapper& w) { assert(!w._cw_active && !w._morse_rx_owned && !w._cw_stopping && !w.board.awake); assert(!w.chip.carrier && !w.board.before && !w.board.after && !w.chip.starts); assert(w.isInRecvMode() && w._rx_ps_armed && w.chip.callback); assert(w._profile_refresh_required && w.calibrations); } struct FskReceiver : RadioLibWrapper { FskReceiver() { reset_ok=true; } int16_t prepareMorseReceiver(float frequency_mhz) override { _morse_rx_fsk_mode=true; chip.fsk=true; return RadioLibWrapper::prepareMorseReceiver(frequency_mhz); } }; int main() { for (bool gain : {false,true}) { RadioLibWrapper w; w._rx_boosted_gain_valid=true; w._cur_rx_boosted_gain=gain; assert(w.startMorseRx(902.1f,25,60000)==RXResult::Started); assert(w.rx_gain_applies==1 && w.physical_rx_gain==gain && w.rssi_reads==1); assert(w.rx_gain_order && w.rx_gain_order 0 && stats.minimum_dbm == -110 && stats.maximum_dbm == -80); const auto captured = std::string(w.morseCaptureText()); w.clearMorseCapture(); assert(w.morseCaptureText() == captured); assert(w.stopMorseRx() == RXResult::Stopped); packet_receive_restored(w); assert(w.morseCaptureText() == captured && w._profiles.primary == primary && w._profiles.secondary.params == secondary); std::string pages; size_t offset = 0; while (offset < w.morseCaptureLength()) { char chunk[3]; const auto count = w.readMorseCapture(offset, chunk, sizeof(chunk)); assert(count); pages += chunk; offset += count; } assert(pages == captured); assert(w.stopMorseRx() == RXResult::Stopped && w.morseCaptureText() == captured); const auto stopped_generation = w.morseCaptureGeneration(); assert(w.startMorseRx(902.1f, 25, 1000) == RXResult::Started); assert(w.morseCaptureLength() == 0 && w.morseCaptureGeneration() == stopped_generation + 1); assert(w.stopMorseRx() == RXResult::Stopped); const auto cleared_generation = w.morseCaptureGeneration(); w.clearMorseCapture(); assert(!w._morse_rx && w.morseCaptureLength() == 0 && w.morseCaptureGeneration() == cleared_generation + 1); } // RX admission fails before receive/PA mutation and never damages a retained capture. for (int reason = 0; reason < 12; ++reason) { RadioLibWrapper w; assert(w.startMorseRx(902.1f, 25, 60000) == RXResult::Started); receive_e(w); assert(w.stopMorseRx() == RXResult::Stopped); const auto captured = std::string(w.morseCaptureText()); const auto generation = w.morseCaptureGeneration(); const auto raw_starts = w.chip.raw_rx_starts, standbys = w.chip.standby_calls; float frequency = 902.1f; uint8_t speed = 25; uint32_t duration = 60000; RXResult wanted = RXResult::Busy; if (reason == 0) w.state = STATE_TX_WAIT; if (reason == 1) w.events.record(); if (reason == 2) w.busy = true; if (reason == 3) w.packet = true; if (reason == 4) { w.supported = false; wanted = RXResult::Unsupported; } if (reason == 5) { frequency = NAN; wanted = RXResult::InvalidFrequency; } if (reason == 6) { frequency = 99; wanted = RXResult::InvalidFrequency; } if (reason == 7) { speed = 0; wanted = RXResult::InvalidSpeed; } if (reason == 8) { speed = uint8_t(mesh::MorseTx::MaxWpm + 1); wanted = RXResult::InvalidSpeed; } if (reason == 9) { duration = 999; wanted = RXResult::InvalidDuration; } if (reason == 10) { duration = 300001; wanted = RXResult::InvalidDuration; } if (reason == 11) { w.receiver_allocation_ok = false; wanted = RXResult::Failed; } assert(w.startMorseRx(frequency, speed, duration) == wanted); assert(w.morseCaptureText() == captured && w.morseCaptureGeneration() == generation); assert(w.chip.raw_rx_starts == raw_starts && w.chip.standby_calls == standbys && !w.board.awake); } for (uint8_t profile : {0, 1}) { RadioLibWrapper w; const float frequency = w._profiles.params(profile).freq; if (profile) w._profiles.secondary.mode = mesh::RadioProfileMode::Off; assert(w.startMorseRx(frequency, 25, 1000) == RXResult::Started); assert(!w.chip.starts && !w.board.before && w.isMorseRxActive()); char reply[160]; assert(mesh::handleCarrierWaveCommand(&w, "get cw2", reply, sizeof(reply))); assert(!strcmp(reply, "> Morse RX owns radio; use get morse")); assert(w.stopMorseRx() == RXResult::Stopped); packet_receive_restored(w); } for (int fault = 0; fault < 7; ++fault) { RadioLibWrapper w; w.startMorseRx(902.1f, 25, 60000); receive_e(w); w.stopMorseRx(); const auto captured = std::string(w.morseCaptureText()); const auto generation = w.morseCaptureGeneration(); if (fault == 0) w.board.frequency_ok = false; if (fault == 1) w.fail_morse_params = true; if (fault == 2) w.chip.rx_result = -705; if (fault == 3) w.rssi = NAN; if (fault == 4) w.rssi = -181; if (fault == 5) w.chip.frequency_result = -705; if (fault == 6) w.chip.irq_result = -705; assert(w.startMorseRx(902.1f, 25, 60000) == RXResult::Failed); assert(w.morseCaptureText() == captured && w.morseCaptureGeneration() == generation); assert(!w.chip.starts && !w.board.before && !w.board.after); w.board.frequency_ok = true; w.fail_morse_params = false; w.chip.rx_result = w.chip.frequency_result = w.chip.irq_result = 0; w.rssi = -110; if (w._cw_active) { advance(100); w.serviceCarrierWave(); } packet_receive_restored(w); assert(w.morseCaptureText() == captured); } for (int fault : {0, 1, 2, 3}) { RadioLibWrapper w; w.startMorseRx(902.1f, 25, 60000); receive_e(w); const auto captured = std::string(w.morseCaptureText()); if (fault == 0) w.fail_tune = true; if (fault == 1) w.fail_rx_starts = 2; if (fault == 2) w.register_failures = 1; if (fault == 3) w.chip.standby_result = w.chip.stop_result = -705; assert(w.stopMorseRx() == RXResult::Failed); assert(w.isMorseRxActive() && w.morseRxRecovering() && w.board.awake); assert(w.morseRxRemainingMillis() == 0 && !w.chip.starts && !w.board.before); assert(w.morseCaptureText() == captured); w.clearMorseCapture(); assert(w.morseCaptureText() == captured); assert(w.startMorseTx(903, 25, "E") == mesh::MorseTxResult::Busy); assert(w.startMorseRx(903, 25, 1000) == RXResult::Busy); const auto stops = w.chip.stops; advance(99); w.serviceCarrierWave(); assert(w.chip.stops == stops); w.fail_tune = false; w.fail_rx_starts = 0; w.reset_ok = true; w.chip.stop_result = w.chip.standby_result = 0; advance(1); w.serviceCarrierWave(); packet_receive_restored(w); assert(w.morseCaptureText() == captured); } for (uint8_t changed : {0, 1, 2}) { RadioLibWrapper w; w.startMorseRx(902.1f, 25, 60000); receive_e(w); if (changed < 2) ++w._profiles.generation[changed]; else w._profiles.secondary.params.freq = 902.1f; w.serviceCarrierWave(); if (changed == 2) { assert(w.isMorseRxActive()); w.stopMorseRx(); } packet_receive_restored(w); assert(!strncmp(w.morseCaptureText(), "E", 1)); } { RadioLibWrapper w; g_mock_millis = 0xfffffff0; w.startMorseRx(902.1f, 25, 300000); receive_e(w); g_mock_millis = uint32_t(0xfffffff0 + 300000); w.serviceCarrierWave(); packet_receive_restored(w); assert(!strncmp(w.morseCaptureText(), "E", 1)); } { RadioLibWrapper w; w.startMorseRx(902.1f, 25, 60000); receive_e(w); const auto reads = w.rssi_reads; for (int call = 0; call < 100; ++call) w.serviceCarrierWave(); assert(w.rssi_reads == reads); // repeated foreground calls never busy-spin SPI level(w, NAN, 14); assert(w.isMorseRxActive() && w._morse_rx_read_errors == 7); mesh::MorseRxDiagnostics stats; assert(w.morseRxDiagnostics(stats) && stats.read_errors == 7); level(w, -110, 2); assert(w._morse_rx_read_errors == 0); level(w, NAN, 16); packet_receive_restored(w); assert(!strncmp(w.morseCaptureText(), "E", 1)); } }