hfmodem 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hfmodem/__init__.py +12 -0
- hfmodem/__main__.py +12 -0
- hfmodem/besra/__init__.py +12 -0
- hfmodem/besra/arq/__init__.py +5 -0
- hfmodem/besra/arq/modem.py +342 -0
- hfmodem/besra/arq/session.py +1265 -0
- hfmodem/besra/crc.py +80 -0
- hfmodem/besra/dsp/__init__.py +5 -0
- hfmodem/besra/dsp/detect.py +301 -0
- hfmodem/besra/dsp/templates.py +132 -0
- hfmodem/besra/fec/__init__.py +8 -0
- hfmodem/besra/fec/rs.py +299 -0
- hfmodem/besra/frame/__init__.py +6 -0
- hfmodem/besra/frame/callsign.py +145 -0
- hfmodem/besra/frame/frame.py +199 -0
- hfmodem/besra/host/__init__.py +10 -0
- hfmodem/besra/host/modem_core.py +329 -0
- hfmodem/besra/host/protocol.py +68 -0
- hfmodem/besra/host/run_server.py +453 -0
- hfmodem/besra/host/server.py +633 -0
- hfmodem/besra/monitor.py +312 -0
- hfmodem/besra/phy/__init__.py +5 -0
- hfmodem/besra/phy/demodulator.py +1418 -0
- hfmodem/besra/phy/memory.py +301 -0
- hfmodem/besra/phy/modulator.py +343 -0
- hfmodem/besra/phy/quality.py +136 -0
- hfmodem/besra/radio.py +828 -0
- hfmodem/besra/sim/__init__.py +5 -0
- hfmodem/besra/sim/air.py +92 -0
- hfmodem/besra/sim/channel.py +80 -0
- hfmodem/besra/sim/echo.py +60 -0
- hfmodem/cli.py +222 -0
- hfmodem/core/__init__.py +2 -0
- hfmodem/core/audio.py +964 -0
- hfmodem/core/band.py +52 -0
- hfmodem/core/busy.py +488 -0
- hfmodem/core/cbtrace.py +183 -0
- hfmodem/core/config.py +441 -0
- hfmodem/core/cwid.py +124 -0
- hfmodem/core/devices.py +88 -0
- hfmodem/core/gil.py +120 -0
- hfmodem/core/levels.py +242 -0
- hfmodem/core/occupied.py +138 -0
- hfmodem/core/ptt.py +745 -0
- hfmodem/core/rates.py +213 -0
- hfmodem/core/regulatory/__init__.py +164 -0
- hfmodem/core/regulatory/part97.py +300 -0
- hfmodem/core/resample.py +55 -0
- hfmodem/core/rig.py +912 -0
- hfmodem/core/rigs.py +46 -0
- hfmodem/core/rxreadiness.py +238 -0
- hfmodem/core/wav.py +246 -0
- hfmodem/kestrel/__init__.py +2 -0
- hfmodem/kestrel/arq/__init__.py +2 -0
- hfmodem/kestrel/arq/frames.py +187 -0
- hfmodem/kestrel/arq/fsm.py +608 -0
- hfmodem/kestrel/arq/modem.py +247 -0
- hfmodem/kestrel/arq/phy.py +421 -0
- hfmodem/kestrel/arq/run_pair.py +62 -0
- hfmodem/kestrel/assets.py +26 -0
- hfmodem/kestrel/coding/__init__.py +2 -0
- hfmodem/kestrel/coding/crc.py +40 -0
- hfmodem/kestrel/coding/turbo.py +260 -0
- hfmodem/kestrel/host/__init__.py +19 -0
- hfmodem/kestrel/host/modem_core.py +329 -0
- hfmodem/kestrel/host/protocol.py +30 -0
- hfmodem/kestrel/host/run_server.py +54 -0
- hfmodem/kestrel/host/server.py +399 -0
- hfmodem/kestrel/rx/__init__.py +2 -0
- hfmodem/kestrel/rx/bw2750_allocations.py +68 -0
- hfmodem/kestrel/rx/tablegen.py +772 -0
- hfmodem/kestrel/rx/varahf2300.py +844 -0
- hfmodem/kestrel/rx/varahf500.py +787 -0
- hfmodem/kestrel/tx/__init__.py +4 -0
- hfmodem/kestrel/tx/assets/prototype_pulse.npz +0 -0
- hfmodem/kestrel/tx/bw500_pulse.py +101 -0
- hfmodem/kestrel/tx/varahf2300_tx.py +249 -0
- hfmodem/kestrel/tx/varahf500_tx.py +258 -0
- hfmodem/kestrel/vara/__init__.py +11 -0
- hfmodem/kestrel/vara/vara_arq.py +7361 -0
- hfmodem/kestrel/vara/vara_control.py +167 -0
- hfmodem/kestrel/vara/vara_frames.py +1599 -0
- hfmodem/kestrel/vara/vara_mfsk.py +322 -0
- hfmodem/kestrel/vara/vara_ofdm.py +146 -0
- hfmodem/sabir/__init__.py +15 -0
- hfmodem/sabir/arq/__init__.py +9 -0
- hfmodem/sabir/arq/fastctl.py +92 -0
- hfmodem/sabir/arq/fsm.py +1085 -0
- hfmodem/sabir/arq/layout.py +119 -0
- hfmodem/sabir/arq/modem.py +383 -0
- hfmodem/sabir/arq/profiles.py +79 -0
- hfmodem/sabir/arq/wire.py +396 -0
- hfmodem/sabir/compress/__init__.py +83 -0
- hfmodem/sabir/dsp/__init__.py +8 -0
- hfmodem/sabir/dsp/channel.py +88 -0
- hfmodem/sabir/dsp/constellation.py +157 -0
- hfmodem/sabir/dsp/fading.py +215 -0
- hfmodem/sabir/dsp/sync.py +24 -0
- hfmodem/sabir/fec/__init__.py +7 -0
- hfmodem/sabir/fec/ldpc.py +190 -0
- hfmodem/sabir/fec/tbcc.py +207 -0
- hfmodem/sabir/floor/__init__.py +11 -0
- hfmodem/sabir/floor/beacon.py +352 -0
- hfmodem/sabir/floor/mfsk.py +282 -0
- hfmodem/sabir/frame/__init__.py +6 -0
- hfmodem/sabir/frame/codec.py +116 -0
- hfmodem/sabir/frame/datagram.py +158 -0
- hfmodem/sabir/frame/reporting.py +57 -0
- hfmodem/sabir/host/__init__.py +9 -0
- hfmodem/sabir/host/cbor.py +126 -0
- hfmodem/sabir/host/client.py +114 -0
- hfmodem/sabir/host/hostlink.py +382 -0
- hfmodem/sabir/host/messages.py +115 -0
- hfmodem/sabir/host/modem_core.py +425 -0
- hfmodem/sabir/host/run_server.py +52 -0
- hfmodem/sabir/monitor.py +365 -0
- hfmodem/sabir/offair.py +211 -0
- hfmodem/sabir/onair.py +456 -0
- hfmodem/sabir/phy/__init__.py +6 -0
- hfmodem/sabir/phy/modem.py +579 -0
- hfmodem/sabir/phy/preamble.py +125 -0
- hfmodem/sabir/phy/rate.py +7 -0
- hfmodem/sabir/proof.py +261 -0
- hfmodem/sabir/radio.py +352 -0
- hfmodem/sabir/sim/__init__.py +2 -0
- hfmodem/sabir/sim/air.py +159 -0
- hfmodem/sabir/sim/design.py +151 -0
- hfmodem/sabir/sim/impulse.py +37 -0
- hfmodem/sabir/sim/m1.py +239 -0
- hfmodem/sabir/sim/m2.py +250 -0
- hfmodem/sabir/sim/m2b.py +219 -0
- hfmodem/sabir/sim/m3.py +360 -0
- hfmodem/sabir/sim/m4.py +191 -0
- hfmodem/sabir/sim/m6a.py +160 -0
- hfmodem/sabir/sim/m6b.py +195 -0
- hfmodem/sabir/sim/m6c.py +260 -0
- hfmodem/sabir/sim/negotiation_campaign.py +81 -0
- hfmodem/sabir/sim/release.py +91 -0
- hfmodem/sabir/sim/watterson.py +119 -0
- hfmodem/shrike/__init__.py +15 -0
- hfmodem/shrike/arq.py +4208 -0
- hfmodem/shrike/beacon.py +102 -0
- hfmodem/shrike/coding.py +490 -0
- hfmodem/shrike/compress.py +1075 -0
- hfmodem/shrike/frame.py +130 -0
- hfmodem/shrike/hostmode.py +345 -0
- hfmodem/shrike/live.py +298 -0
- hfmodem/shrike/modem.py +377 -0
- hfmodem/shrike/monitor.py +174 -0
- hfmodem/shrike/onair.py +14490 -0
- hfmodem/shrike/ota.py +798 -0
- hfmodem/shrike/p1rx.py +1989 -0
- hfmodem/shrike/p2rx.py +973 -0
- hfmodem/shrike/p3acquire.py +301 -0
- hfmodem/shrike/p3frame.py +393 -0
- hfmodem/shrike/p3rx.py +1355 -0
- hfmodem/shrike/p3trial.py +253 -0
- hfmodem/shrike/p4chirp.py +160 -0
- hfmodem/shrike/p4coding.py +128 -0
- hfmodem/shrike/p4decode.py +143 -0
- hfmodem/shrike/p4normal.py +186 -0
- hfmodem/shrike/p4probe.py +47 -0
- hfmodem/shrike/p4rx.py +165 -0
- hfmodem/shrike/p4sig.py +196 -0
- hfmodem/shrike/pactor1.py +801 -0
- hfmodem/shrike/pactor2.py +699 -0
- hfmodem/shrike/placement.py +1053 -0
- hfmodem/shrike/ptc.py +2776 -0
- hfmodem/shrike/rx.py +632 -0
- hfmodem/shrike/rxfront.py +3011 -0
- hfmodem/shrike/session.py +146 -0
- hfmodem/shrike/spec.py +519 -0
- hfmodem/shrike/tablegen.py +200 -0
- hfmodem/shrike/traffic.py +163 -0
- hfmodem/shrike/unknowns.py +774 -0
- hfmodem/station/__init__.py +2 -0
- hfmodem/station/air.py +196 -0
- hfmodem/station/arbiter.py +282 -0
- hfmodem/station/hosts.py +157 -0
- hfmodem/station/link.py +271 -0
- hfmodem/station/mail.py +681 -0
- hfmodem/station/narrate.py +506 -0
- hfmodem/station/preflight.py +603 -0
- hfmodem/station/process.py +308 -0
- hfmodem/tests/__init__.py +4 -0
- hfmodem/tests/besra/__init__.py +2 -0
- hfmodem/tests/besra/groundtruth.py +151 -0
- hfmodem/tests/besra/test_ardopcf_interop.py +91 -0
- hfmodem/tests/besra/test_arq_ladder_descent.py +158 -0
- hfmodem/tests/besra/test_arq_robustness.py +909 -0
- hfmodem/tests/besra/test_arq_session.py +1158 -0
- hfmodem/tests/besra/test_arq_unreadable_budget.py +384 -0
- hfmodem/tests/besra/test_callsign.py +126 -0
- hfmodem/tests/besra/test_cli.py +384 -0
- hfmodem/tests/besra/test_demod_robustness.py +814 -0
- hfmodem/tests/besra/test_demodulator.py +247 -0
- hfmodem/tests/besra/test_host_besra.py +114 -0
- hfmodem/tests/besra/test_host_loopback.py +275 -0
- hfmodem/tests/besra/test_integration.py +167 -0
- hfmodem/tests/besra/test_lint.py +40 -0
- hfmodem/tests/besra/test_mail.py +264 -0
- hfmodem/tests/besra/test_memory_arq.py +464 -0
- hfmodem/tests/besra/test_modulator.py +106 -0
- hfmodem/tests/besra/test_monitor.py +321 -0
- hfmodem/tests/besra/test_offair_conack.py +71 -0
- hfmodem/tests/besra/test_offair_ke8lva_greeting.py +396 -0
- hfmodem/tests/besra/test_primitives.py +41 -0
- hfmodem/tests/besra/test_publication.py +61 -0
- hfmodem/tests/besra/test_quality.py +793 -0
- hfmodem/tests/besra/test_radio.py +30 -0
- hfmodem/tests/besra/test_radio_audio_path.py +1076 -0
- hfmodem/tests/besra/test_radio_line_ptt.py +285 -0
- hfmodem/tests/besra/test_rf_corpus.py +138 -0
- hfmodem/tests/besra/test_rig.py +321 -0
- hfmodem/tests/besra/test_rs.py +73 -0
- hfmodem/tests/besra/test_rx_funnel.py +265 -0
- hfmodem/tests/besra/test_rx_local.py +144 -0
- hfmodem/tests/besra/test_tx_exact.py +130 -0
- hfmodem/tests/conftest.py +48 -0
- hfmodem/tests/core/__init__.py +2 -0
- hfmodem/tests/core/fakerig.py +279 -0
- hfmodem/tests/core/test_air.py +169 -0
- hfmodem/tests/core/test_arbiter.py +224 -0
- hfmodem/tests/core/test_audio.py +661 -0
- hfmodem/tests/core/test_band.py +29 -0
- hfmodem/tests/core/test_busy_evidence.py +254 -0
- hfmodem/tests/core/test_call_bandwidth.py +84 -0
- hfmodem/tests/core/test_capture_loss.py +321 -0
- hfmodem/tests/core/test_cbtrace.py +89 -0
- hfmodem/tests/core/test_client_sid.py +171 -0
- hfmodem/tests/core/test_config.py +228 -0
- hfmodem/tests/core/test_devices.py +90 -0
- hfmodem/tests/core/test_drive_sweep.py +492 -0
- hfmodem/tests/core/test_gil.py +116 -0
- hfmodem/tests/core/test_gwsurvey.py +293 -0
- hfmodem/tests/core/test_id_reminder.py +147 -0
- hfmodem/tests/core/test_levels.py +158 -0
- hfmodem/tests/core/test_link.py +376 -0
- hfmodem/tests/core/test_narrate.py +464 -0
- hfmodem/tests/core/test_occupant_identity.py +158 -0
- hfmodem/tests/core/test_occupied.py +286 -0
- hfmodem/tests/core/test_one_ladder.py +327 -0
- hfmodem/tests/core/test_preflight.py +524 -0
- hfmodem/tests/core/test_ptt.py +220 -0
- hfmodem/tests/core/test_ptt_last_resort.py +907 -0
- hfmodem/tests/core/test_rates.py +278 -0
- hfmodem/tests/core/test_regulatory.py +317 -0
- hfmodem/tests/core/test_rehear.py +462 -0
- hfmodem/tests/core/test_rehear_shrike_verdicts.py +324 -0
- hfmodem/tests/core/test_replay.py +97 -0
- hfmodem/tests/core/test_resample_wav_cwid.py +148 -0
- hfmodem/tests/core/test_rig.py +583 -0
- hfmodem/tests/core/test_rig_panic.py +167 -0
- hfmodem/tests/core/test_rigs.py +32 -0
- hfmodem/tests/core/test_signal_teardown.py +713 -0
- hfmodem/tests/core/test_station_capture_loss.py +165 -0
- hfmodem/tests/core/test_station_rehearsal.py +537 -0
- hfmodem/tests/core/test_tx_drive.py +277 -0
- hfmodem/tests/core/test_witness.py +457 -0
- hfmodem/tests/evidence.py +63 -0
- hfmodem/tests/gates/__init__.py +2 -0
- hfmodem/tests/gates/test_corpus_present.py +238 -0
- hfmodem/tests/gates/test_entry_points.py +118 -0
- hfmodem/tests/gates/test_figures.py +84 -0
- hfmodem/tests/gates/test_gates_script.py +194 -0
- hfmodem/tests/gates/test_import_direction.py +223 -0
- hfmodem/tests/gates/test_no_secret_in_tracked_files.py +151 -0
- hfmodem/tests/gates/test_one_gateway_list.py +198 -0
- hfmodem/tests/gates/test_packaging.py +212 -0
- hfmodem/tests/gates/test_singleton_facts.py +192 -0
- hfmodem/tests/gates/test_stale_negatives.py +1543 -0
- hfmodem/tests/gates/test_tablegen.py +328 -0
- hfmodem/tests/gates/test_unproven_failsafe.py +160 -0
- hfmodem/tests/kestrel/__init__.py +2 -0
- hfmodem/tests/kestrel/corpora.py +1298 -0
- hfmodem/tests/kestrel/fake_rigctld.py +239 -0
- hfmodem/tests/kestrel/test_ack_downshift.py +101 -0
- hfmodem/tests/kestrel/test_arq_bw2300.py +91 -0
- hfmodem/tests/kestrel/test_arq_loopback.py +105 -0
- hfmodem/tests/kestrel/test_arq_teardown.py +226 -0
- hfmodem/tests/kestrel/test_arq_vara_tokens.py +185 -0
- hfmodem/tests/kestrel/test_burst_naming.py +144 -0
- hfmodem/tests/kestrel/test_bw2300_cut_head.py +94 -0
- hfmodem/tests/kestrel/test_bw2300_preamble.py +104 -0
- hfmodem/tests/kestrel/test_bw2300_real_audio.py +57 -0
- hfmodem/tests/kestrel/test_bw2300_rec1_rec0.py +130 -0
- hfmodem/tests/kestrel/test_bw2300_rec2.py +557 -0
- hfmodem/tests/kestrel/test_bw2300_roundtrip.py +63 -0
- hfmodem/tests/kestrel/test_bw2750.py +450 -0
- hfmodem/tests/kestrel/test_bw2750_below_offer.py +89 -0
- hfmodem/tests/kestrel/test_bw2750_control_tails.py +76 -0
- hfmodem/tests/kestrel/test_bw2750_floor.py +91 -0
- hfmodem/tests/kestrel/test_bw2750_low_levels.py +101 -0
- hfmodem/tests/kestrel/test_bw2750_nak.py +324 -0
- hfmodem/tests/kestrel/test_bw2750_peer_answer.py +171 -0
- hfmodem/tests/kestrel/test_bw2750_responder_nak.py +316 -0
- hfmodem/tests/kestrel/test_bw2750_tx_levels.py +303 -0
- hfmodem/tests/kestrel/test_bw500_answer.py +207 -0
- hfmodem/tests/kestrel/test_bw500_connect_request.py +206 -0
- hfmodem/tests/kestrel/test_bw500_continue_table.py +231 -0
- hfmodem/tests/kestrel/test_bw500_cut_head.py +71 -0
- hfmodem/tests/kestrel/test_bw500_data_over.py +170 -0
- hfmodem/tests/kestrel/test_bw500_handshake.py +89 -0
- hfmodem/tests/kestrel/test_bw500_low_levels.py +163 -0
- hfmodem/tests/kestrel/test_bw500_peer_answer.py +164 -0
- hfmodem/tests/kestrel/test_bw500_rec2.py +281 -0
- hfmodem/tests/kestrel/test_bw500_recovery.py +285 -0
- hfmodem/tests/kestrel/test_bw500_session.py +226 -0
- hfmodem/tests/kestrel/test_bw500_session_frames.py +137 -0
- hfmodem/tests/kestrel/test_bw500_stream_low_levels.py +75 -0
- hfmodem/tests/kestrel/test_bw500_terminal_window.py +117 -0
- hfmodem/tests/kestrel/test_bw500_tx_native.py +93 -0
- hfmodem/tests/kestrel/test_caller_in_peer_turn.py +397 -0
- hfmodem/tests/kestrel/test_capture_clock.py +158 -0
- hfmodem/tests/kestrel/test_channel_busy.py +608 -0
- hfmodem/tests/kestrel/test_connect_ask_stream.py +125 -0
- hfmodem/tests/kestrel/test_connect_confirm_stream.py +289 -0
- hfmodem/tests/kestrel/test_connect_retry.py +443 -0
- hfmodem/tests/kestrel/test_connect_rx_priority.py +106 -0
- hfmodem/tests/kestrel/test_connect_teardown.py +164 -0
- hfmodem/tests/kestrel/test_connected_ack.py +92 -0
- hfmodem/tests/kestrel/test_connected_ack_channel.py +351 -0
- hfmodem/tests/kestrel/test_connected_ack_stream.py +626 -0
- hfmodem/tests/kestrel/test_connected_turn_requests.py +140 -0
- hfmodem/tests/kestrel/test_crc.py +73 -0
- hfmodem/tests/kestrel/test_data_ack_pairs.py +45 -0
- hfmodem/tests/kestrel/test_data_ack_refusal.py +26 -0
- hfmodem/tests/kestrel/test_data_nak_pairs.py +148 -0
- hfmodem/tests/kestrel/test_data_naks_0919.py +143 -0
- hfmodem/tests/kestrel/test_data_over_gate.py +742 -0
- hfmodem/tests/kestrel/test_data_replies_0919.py +139 -0
- hfmodem/tests/kestrel/test_data_retry_clock.py +89 -0
- hfmodem/tests/kestrel/test_drained_frame.py +65 -0
- hfmodem/tests/kestrel/test_drained_handover.py +233 -0
- hfmodem/tests/kestrel/test_drained_handover_2300.py +140 -0
- hfmodem/tests/kestrel/test_early_idle_release.py +61 -0
- hfmodem/tests/kestrel/test_final_ack_recovery.py +203 -0
- hfmodem/tests/kestrel/test_fractional_continue.py +120 -0
- hfmodem/tests/kestrel/test_greeting_by_stream.py +469 -0
- hfmodem/tests/kestrel/test_greeting_spans_overs.py +195 -0
- hfmodem/tests/kestrel/test_handshake_channel.py +95 -0
- hfmodem/tests/kestrel/test_handshake_offair.py +140 -0
- hfmodem/tests/kestrel/test_handshake_replay.py +103 -0
- hfmodem/tests/kestrel/test_head_cut_continue.py +166 -0
- hfmodem/tests/kestrel/test_hfcapture_segments.py +211 -0
- hfmodem/tests/kestrel/test_host_loopback.py +316 -0
- hfmodem/tests/kestrel/test_idle_recovery.py +144 -0
- hfmodem/tests/kestrel/test_intermediate_over_answer.py +413 -0
- hfmodem/tests/kestrel/test_intermediate_query_answer.py +235 -0
- hfmodem/tests/kestrel/test_intermediate_query_probe.py +150 -0
- hfmodem/tests/kestrel/test_k0si_data_ack_0920.py +93 -0
- hfmodem/tests/kestrel/test_kb5_partial_idle.py +69 -0
- hfmodem/tests/kestrel/test_kd7uhr_response_zero.py +46 -0
- hfmodem/tests/kestrel/test_key_refusal.py +404 -0
- hfmodem/tests/kestrel/test_link_frequency.py +243 -0
- hfmodem/tests/kestrel/test_linksetup_frame.py +167 -0
- hfmodem/tests/kestrel/test_loopback_honesty.py +133 -0
- hfmodem/tests/kestrel/test_lower_answer_recovery.py +252 -0
- hfmodem/tests/kestrel/test_mail.py +645 -0
- hfmodem/tests/kestrel/test_mfsk_timing.py +136 -0
- hfmodem/tests/kestrel/test_monitor_traffic.py +580 -0
- hfmodem/tests/kestrel/test_nak_repacketization.py +118 -0
- hfmodem/tests/kestrel/test_nak_scan_deadline.py +47 -0
- hfmodem/tests/kestrel/test_negative_corpus.py +138 -0
- hfmodem/tests/kestrel/test_negotiated_setup.py +89 -0
- hfmodem/tests/kestrel/test_no_data_retries.py +121 -0
- hfmodem/tests/kestrel/test_onair_connect_chain.py +479 -0
- hfmodem/tests/kestrel/test_onair_dress_rehearsal.py +1204 -0
- hfmodem/tests/kestrel/test_onair_frequency.py +112 -0
- hfmodem/tests/kestrel/test_onair_mail_path.py +379 -0
- hfmodem/tests/kestrel/test_over_buffer_phase.py +61 -0
- hfmodem/tests/kestrel/test_over_carry_bw2750.py +95 -0
- hfmodem/tests/kestrel/test_over_continue_answer.py +315 -0
- hfmodem/tests/kestrel/test_over_splice.py +479 -0
- hfmodem/tests/kestrel/test_over_stream_cut_head.py +99 -0
- hfmodem/tests/kestrel/test_owed_ask.py +140 -0
- hfmodem/tests/kestrel/test_peer_answer.py +229 -0
- hfmodem/tests/kestrel/test_peer_gap_idle_clock.py +117 -0
- hfmodem/tests/kestrel/test_preamble_lock.py +75 -0
- hfmodem/tests/kestrel/test_ptt_release.py +118 -0
- hfmodem/tests/kestrel/test_ptt_tail_check.py +558 -0
- hfmodem/tests/kestrel/test_reack_budget.py +99 -0
- hfmodem/tests/kestrel/test_reack_over.py +268 -0
- hfmodem/tests/kestrel/test_reack_release.py +127 -0
- hfmodem/tests/kestrel/test_ready_pounce.py +194 -0
- hfmodem/tests/kestrel/test_real_audio.py +124 -0
- hfmodem/tests/kestrel/test_recovery_diagnostics.py +59 -0
- hfmodem/tests/kestrel/test_release_reoffer.py +138 -0
- hfmodem/tests/kestrel/test_repeated_intermediate_queries.py +121 -0
- hfmodem/tests/kestrel/test_response_by_payload.py +311 -0
- hfmodem/tests/kestrel/test_response_by_stream.py +1018 -0
- hfmodem/tests/kestrel/test_rig_unkey_safety.py +348 -0
- hfmodem/tests/kestrel/test_rig_wire_evidence.py +201 -0
- hfmodem/tests/kestrel/test_rx_levels.py +331 -0
- hfmodem/tests/kestrel/test_rx_segmenter.py +1023 -0
- hfmodem/tests/kestrel/test_session_bursts.py +201 -0
- hfmodem/tests/kestrel/test_session_replay.py +205 -0
- hfmodem/tests/kestrel/test_static.py +194 -0
- hfmodem/tests/kestrel/test_station_id.py +100 -0
- hfmodem/tests/kestrel/test_tone_band.py +151 -0
- hfmodem/tests/kestrel/test_tools_cli.py +353 -0
- hfmodem/tests/kestrel/test_trace_timing.py +302 -0
- hfmodem/tests/kestrel/test_transmitted_bursts.py +167 -0
- hfmodem/tests/kestrel/test_turbo_exactness.py +89 -0
- hfmodem/tests/kestrel/test_turn_law.py +1455 -0
- hfmodem/tests/kestrel/test_turn_request_confirms_connect.py +115 -0
- hfmodem/tests/kestrel/test_two_over_window.py +436 -0
- hfmodem/tests/kestrel/test_tx_dc_tail.py +61 -0
- hfmodem/tests/kestrel/test_tx_selfloop.py +76 -0
- hfmodem/tests/kestrel/test_txwitness.py +768 -0
- hfmodem/tests/kestrel/test_unasked_handover.py +421 -0
- hfmodem/tests/kestrel/test_unattributed_answer.py +213 -0
- hfmodem/tests/kestrel/test_unknown_data_reply.py +125 -0
- hfmodem/tests/kestrel/test_vara_connect.py +58 -0
- hfmodem/tests/kestrel/test_vara_control.py +207 -0
- hfmodem/tests/kestrel/test_vara_data_framing.py +190 -0
- hfmodem/tests/kestrel/test_vara_giveup.py +237 -0
- hfmodem/tests/kestrel/test_vara_interop.py +233 -0
- hfmodem/tests/kestrel/test_vara_launcher_cleanup.py +49 -0
- hfmodem/tests/kestrel/test_vara_mail_rx.py +322 -0
- hfmodem/tests/kestrel/test_vara_nak.py +74 -0
- hfmodem/tests/kestrel/test_vara_tx_recovery.py +160 -0
- hfmodem/tests/kestrel/test_w0lon_idle_train.py +230 -0
- hfmodem/tests/kestrel/test_weak_data_0920.py +84 -0
- hfmodem/tests/kestrel/test_weak_known_continue.py +73 -0
- hfmodem/tests/kestrel/test_whole_burst.py +121 -0
- hfmodem/tests/sabir/__init__.py +2 -0
- hfmodem/tests/sabir/clips.py +41 -0
- hfmodem/tests/sabir/test_harq_buffer.py +140 -0
- hfmodem/tests/sabir/test_hostapi.py +577 -0
- hfmodem/tests/sabir/test_kat.py +153 -0
- hfmodem/tests/sabir/test_m1.py +125 -0
- hfmodem/tests/sabir/test_m2.py +214 -0
- hfmodem/tests/sabir/test_m2b.py +144 -0
- hfmodem/tests/sabir/test_m3.py +207 -0
- hfmodem/tests/sabir/test_m4.py +285 -0
- hfmodem/tests/sabir/test_m6a.py +193 -0
- hfmodem/tests/sabir/test_m6b.py +143 -0
- hfmodem/tests/sabir/test_m6c.py +91 -0
- hfmodem/tests/sabir/test_monitor.py +153 -0
- hfmodem/tests/sabir/test_negotiation.py +331 -0
- hfmodem/tests/sabir/test_offair.py +242 -0
- hfmodem/tests/sabir/test_onair.py +655 -0
- hfmodem/tests/sabir/test_preroll_bound.py +54 -0
- hfmodem/tests/sabir/test_proof.py +155 -0
- hfmodem/tests/sabir/test_release_profiles.py +251 -0
- hfmodem/tests/sabir/test_release_safety.py +312 -0
- hfmodem/tests/sabir/test_static.py +127 -0
- hfmodem/tests/sabir/test_waveform.py +88 -0
- hfmodem/tests/shrike/__init__.py +2 -0
- hfmodem/tests/shrike/archive.py +116 -0
- hfmodem/tests/shrike/ptyrig.py +192 -0
- hfmodem/tests/shrike/recorded_pcm.py +19 -0
- hfmodem/tests/shrike/test_ackfloor.py +261 -0
- hfmodem/tests/shrike/test_ackoff.py +148 -0
- hfmodem/tests/shrike/test_ackplace.py +458 -0
- hfmodem/tests/shrike/test_alc_knee.py +252 -0
- hfmodem/tests/shrike/test_announcement.py +27 -0
- hfmodem/tests/shrike/test_answer_band.py +499 -0
- hfmodem/tests/shrike/test_answer_band_validity.py +62 -0
- hfmodem/tests/shrike/test_answer_instruments_0915.py +209 -0
- hfmodem/tests/shrike/test_armdefaults.py +352 -0
- hfmodem/tests/shrike/test_arq.py +135 -0
- hfmodem/tests/shrike/test_arq_refusal_budget.py +160 -0
- hfmodem/tests/shrike/test_assessed_radio_startup.py +89 -0
- hfmodem/tests/shrike/test_b2f_loopback_send_ws8eoc.py +417 -0
- hfmodem/tests/shrike/test_b2f_loopback_ws8eoc.py +877 -0
- hfmodem/tests/shrike/test_beacon.py +233 -0
- hfmodem/tests/shrike/test_breakin.py +1362 -0
- hfmodem/tests/shrike/test_breakin_prompt_placement.py +211 -0
- hfmodem/tests/shrike/test_burstlock.py +1130 -0
- hfmodem/tests/shrike/test_bytestream.py +198 -0
- hfmodem/tests/shrike/test_callb.py +226 -0
- hfmodem/tests/shrike/test_capture_drain.py +178 -0
- hfmodem/tests/shrike/test_changeover_clock.py +89 -0
- hfmodem/tests/shrike/test_changeover_cs5.py +72 -0
- hfmodem/tests/shrike/test_changeover_p1_cs.py +146 -0
- hfmodem/tests/shrike/test_changeover_progression.py +145 -0
- hfmodem/tests/shrike/test_changeover_request.py +236 -0
- hfmodem/tests/shrike/test_clockfit.py +197 -0
- hfmodem/tests/shrike/test_codeword_reach.py +297 -0
- hfmodem/tests/shrike/test_coding.py +193 -0
- hfmodem/tests/shrike/test_compress.py +346 -0
- hfmodem/tests/shrike/test_connect_evidence_0916.py +355 -0
- hfmodem/tests/shrike/test_connect_gate.py +747 -0
- hfmodem/tests/shrike/test_connect_prefix.py +215 -0
- hfmodem/tests/shrike/test_connect_tail.py +149 -0
- hfmodem/tests/shrike/test_connected_hush.py +117 -0
- hfmodem/tests/shrike/test_control_offset_follow.py +361 -0
- hfmodem/tests/shrike/test_control_order.py +64 -0
- hfmodem/tests/shrike/test_control_profiles.py +261 -0
- hfmodem/tests/shrike/test_corpus.py +169 -0
- hfmodem/tests/shrike/test_cs6_arq.py +404 -0
- hfmodem/tests/shrike/test_cs6_driver.py +361 -0
- hfmodem/tests/shrike/test_cs6_rx.py +205 -0
- hfmodem/tests/shrike/test_cs6_stalls.py +287 -0
- hfmodem/tests/shrike/test_cs6_waveform.py +108 -0
- hfmodem/tests/shrike/test_cs_evidence.py +196 -0
- hfmodem/tests/shrike/test_csalias.py +397 -0
- hfmodem/tests/shrike/test_cssearch.py +205 -0
- hfmodem/tests/shrike/test_data_pulse_flush.py +309 -0
- hfmodem/tests/shrike/test_duplex.py +567 -0
- hfmodem/tests/shrike/test_emptybk.py +197 -0
- hfmodem/tests/shrike/test_entry_answer.py +490 -0
- hfmodem/tests/shrike/test_entry_candidate_body.py +208 -0
- hfmodem/tests/shrike/test_entry_counter_law.py +294 -0
- hfmodem/tests/shrike/test_entry_counter_slots.py +232 -0
- hfmodem/tests/shrike/test_entry_delay.py +118 -0
- hfmodem/tests/shrike/test_entry_slots_kept.py +480 -0
- hfmodem/tests/shrike/test_first_loaded_packet.py +114 -0
- hfmodem/tests/shrike/test_fixed_clock_peer.py +91 -0
- hfmodem/tests/shrike/test_flushdebt.py +114 -0
- hfmodem/tests/shrike/test_follow_p1_cross_check.py +148 -0
- hfmodem/tests/shrike/test_free_signal_event.py +110 -0
- hfmodem/tests/shrike/test_gear_gate_speed_up.py +194 -0
- hfmodem/tests/shrike/test_grant_credit_bound.py +107 -0
- hfmodem/tests/shrike/test_grant_reconnect.py +24 -0
- hfmodem/tests/shrike/test_granted_entry_retry.py +238 -0
- hfmodem/tests/shrike/test_granted_entry_slots.py +374 -0
- hfmodem/tests/shrike/test_granted_progress.py +270 -0
- hfmodem/tests/shrike/test_grantslot.py +372 -0
- hfmodem/tests/shrike/test_grid.py +2865 -0
- hfmodem/tests/shrike/test_grid_phase.py +145 -0
- hfmodem/tests/shrike/test_holdbudget.py +451 -0
- hfmodem/tests/shrike/test_honest_account.py +393 -0
- hfmodem/tests/shrike/test_honest_lines.py +214 -0
- hfmodem/tests/shrike/test_hostmode.py +15 -0
- hfmodem/tests/shrike/test_hostmode_manual_0916.py +453 -0
- hfmodem/tests/shrike/test_identify.py +153 -0
- hfmodem/tests/shrike/test_inlink_anchored_read_0916.py +291 -0
- hfmodem/tests/shrike/test_inlink_grid_read_0917.py +207 -0
- hfmodem/tests/shrike/test_iss_answer_placement.py +187 -0
- hfmodem/tests/shrike/test_iss_login_advance.py +212 -0
- hfmodem/tests/shrike/test_iss_turn_changeover.py +549 -0
- hfmodem/tests/shrike/test_k0nts_sl3_0915.py +161 -0
- hfmodem/tests/shrike/test_kb5lzk_answer_radius_0915.py +148 -0
- hfmodem/tests/shrike/test_keyed_truth.py +620 -0
- hfmodem/tests/shrike/test_keyingguard.py +171 -0
- hfmodem/tests/shrike/test_late_changeover.py +118 -0
- hfmodem/tests/shrike/test_late_entry_loop.py +248 -0
- hfmodem/tests/shrike/test_late_recorded_entry.py +74 -0
- hfmodem/tests/shrike/test_listen_cycle.py +184 -0
- hfmodem/tests/shrike/test_long_sl1.py +192 -0
- hfmodem/tests/shrike/test_longcycle.py +505 -0
- hfmodem/tests/shrike/test_longcycle_cadence.py +205 -0
- hfmodem/tests/shrike/test_mail.py +317 -0
- hfmodem/tests/shrike/test_mail_wait_greeting.py +155 -0
- hfmodem/tests/shrike/test_modem.py +319 -0
- hfmodem/tests/shrike/test_no_p3_fallback.py +280 -0
- hfmodem/tests/shrike/test_offair.py +78 -0
- hfmodem/tests/shrike/test_offset_fine_estimate.py +237 -0
- hfmodem/tests/shrike/test_over_grant_hold.py +198 -0
- hfmodem/tests/shrike/test_p1.py +142 -0
- hfmodem/tests/shrike/test_p1_6a9.py +77 -0
- hfmodem/tests/shrike/test_p1_late_render.py +162 -0
- hfmodem/tests/shrike/test_p1_oracle.py +679 -0
- hfmodem/tests/shrike/test_p1_setup_phase.py +164 -0
- hfmodem/tests/shrike/test_p1_speed_down.py +195 -0
- hfmodem/tests/shrike/test_p1_speedup_seam.py +264 -0
- hfmodem/tests/shrike/test_p1_stream_epoch_floor.py +88 -0
- hfmodem/tests/shrike/test_p1_stream_protocol_scope.py +122 -0
- hfmodem/tests/shrike/test_p1ack.py +237 -0
- hfmodem/tests/shrike/test_p1counter.py +282 -0
- hfmodem/tests/shrike/test_p1cs.py +1118 -0
- hfmodem/tests/shrike/test_p1data.py +343 -0
- hfmodem/tests/shrike/test_p1drive.py +66 -0
- hfmodem/tests/shrike/test_p1memory.py +160 -0
- hfmodem/tests/shrike/test_p1memory_session.py +188 -0
- hfmodem/tests/shrike/test_p1peer.py +624 -0
- hfmodem/tests/shrike/test_p1rx.py +281 -0
- hfmodem/tests/shrike/test_p1rx_heads.py +315 -0
- hfmodem/tests/shrike/test_p1status.py +184 -0
- hfmodem/tests/shrike/test_p1transparent.py +164 -0
- hfmodem/tests/shrike/test_p2.py +785 -0
- hfmodem/tests/shrike/test_p2_entry_deadline.py +40 -0
- hfmodem/tests/shrike/test_p2_oracle.py +602 -0
- hfmodem/tests/shrike/test_p2_render_equivalence.py +38 -0
- hfmodem/tests/shrike/test_p2entry.py +154 -0
- hfmodem/tests/shrike/test_p2link.py +133 -0
- hfmodem/tests/shrike/test_p2memory.py +105 -0
- hfmodem/tests/shrike/test_p2qso.py +475 -0
- hfmodem/tests/shrike/test_p2rx.py +345 -0
- hfmodem/tests/shrike/test_p2session.py +491 -0
- hfmodem/tests/shrike/test_p3_acquisition_budget.py +147 -0
- hfmodem/tests/shrike/test_p3_answer_acquire_budget.py +166 -0
- hfmodem/tests/shrike/test_p3_breakin_carriers.py +214 -0
- hfmodem/tests/shrike/test_p3_breakin_timing.py +813 -0
- hfmodem/tests/shrike/test_p3_budget_goodbye_0914.py +81 -0
- hfmodem/tests/shrike/test_p3_callback_budget.py +117 -0
- hfmodem/tests/shrike/test_p3_carrier_frequency.py +94 -0
- hfmodem/tests/shrike/test_p3_changeover_order.py +62 -0
- hfmodem/tests/shrike/test_p3_changeover_refined_body.py +96 -0
- hfmodem/tests/shrike/test_p3_cold_acquisition.py +66 -0
- hfmodem/tests/shrike/test_p3_cold_entry_2323.py +124 -0
- hfmodem/tests/shrike/test_p3_comb.py +185 -0
- hfmodem/tests/shrike/test_p3_comb_offset.py +196 -0
- hfmodem/tests/shrike/test_p3_control_edge.py +146 -0
- hfmodem/tests/shrike/test_p3_critic_risks.py +360 -0
- hfmodem/tests/shrike/test_p3_current_packet_loop.py +188 -0
- hfmodem/tests/shrike/test_p3_current_sl1_0919.py +119 -0
- hfmodem/tests/shrike/test_p3_emitted_turn_retry.py +151 -0
- hfmodem/tests/shrike/test_p3_entry_controls.py +231 -0
- hfmodem/tests/shrike/test_p3_entry_iss_phase.py +162 -0
- hfmodem/tests/shrike/test_p3_entry_timing_trial.py +113 -0
- hfmodem/tests/shrike/test_p3_failed_turn_clock.py +333 -0
- hfmodem/tests/shrike/test_p3_first_changeover_ack.py +208 -0
- hfmodem/tests/shrike/test_p3_flow_phase.py +185 -0
- hfmodem/tests/shrike/test_p3_gateway_acquisition.py +126 -0
- hfmodem/tests/shrike/test_p3_late_turn_answer_2300.py +85 -0
- hfmodem/tests/shrike/test_p3_late_turn_regrid_integration.py +112 -0
- hfmodem/tests/shrike/test_p3_mail_changeover_phase.py +214 -0
- hfmodem/tests/shrike/test_p3_mail_clock.py +122 -0
- hfmodem/tests/shrike/test_p3_mail_turn_0919.py +189 -0
- hfmodem/tests/shrike/test_p3_max_down.py +149 -0
- hfmodem/tests/shrike/test_p3_morning_loop.py +177 -0
- hfmodem/tests/shrike/test_p3_morning_timing.py +175 -0
- hfmodem/tests/shrike/test_p3_offer.py +258 -0
- hfmodem/tests/shrike/test_p3_oracle.py +592 -0
- hfmodem/tests/shrike/test_p3_pending_turn_clock_0914.py +173 -0
- hfmodem/tests/shrike/test_p3_pending_turn_regrid.py +88 -0
- hfmodem/tests/shrike/test_p3_protocol_geometry.py +139 -0
- hfmodem/tests/shrike/test_p3_reply_delay.py +83 -0
- hfmodem/tests/shrike/test_p3_reply_placement.py +181 -0
- hfmodem/tests/shrike/test_p3_reply_timing.py +294 -0
- hfmodem/tests/shrike/test_p3_retry_downshift.py +194 -0
- hfmodem/tests/shrike/test_p3_rx_recovery.py +323 -0
- hfmodem/tests/shrike/test_p3_stale_answer.py +134 -0
- hfmodem/tests/shrike/test_p3_standing_request_2325.py +64 -0
- hfmodem/tests/shrike/test_p3_terminal_confirm_arq.py +203 -0
- hfmodem/tests/shrike/test_p3_terminal_marker.py +85 -0
- hfmodem/tests/shrike/test_p3_terminal_radio.py +69 -0
- hfmodem/tests/shrike/test_p3_timing_trial.py +225 -0
- hfmodem/tests/shrike/test_p3_timing_unbounded.py +102 -0
- hfmodem/tests/shrike/test_p3_tracked_crop_0914.py +38 -0
- hfmodem/tests/shrike/test_p3_turn_budget_0914.py +339 -0
- hfmodem/tests/shrike/test_p3_turn_recovery.py +293 -0
- hfmodem/tests/shrike/test_p3_upgrade.py +1684 -0
- hfmodem/tests/shrike/test_p3counter.py +181 -0
- hfmodem/tests/shrike/test_p3level_session.py +256 -0
- hfmodem/tests/shrike/test_p3memory.py +190 -0
- hfmodem/tests/shrike/test_p3rx.py +728 -0
- hfmodem/tests/shrike/test_p4chirp.py +248 -0
- hfmodem/tests/shrike/test_p4coding.py +123 -0
- hfmodem/tests/shrike/test_p4decode.py +108 -0
- hfmodem/tests/shrike/test_p4normal.py +119 -0
- hfmodem/tests/shrike/test_p4probe.py +112 -0
- hfmodem/tests/shrike/test_p4rx.py +130 -0
- hfmodem/tests/shrike/test_p4sig.py +163 -0
- hfmodem/tests/shrike/test_phase_power_cat.py +447 -0
- hfmodem/tests/shrike/test_placement.py +178 -0
- hfmodem/tests/shrike/test_prekey_read_0913.py +296 -0
- hfmodem/tests/shrike/test_ptc.py +1153 -0
- hfmodem/tests/shrike/test_ptc_transport.py +85 -0
- hfmodem/tests/shrike/test_qrmguard.py +342 -0
- hfmodem/tests/shrike/test_qrtack.py +470 -0
- hfmodem/tests/shrike/test_qso.py +490 -0
- hfmodem/tests/shrike/test_radio_mode_readback.py +121 -0
- hfmodem/tests/shrike/test_radio_startup_flow.py +130 -0
- hfmodem/tests/shrike/test_raster.py +219 -0
- hfmodem/tests/shrike/test_reclaim.py +121 -0
- hfmodem/tests/shrike/test_reclaim_geometry.py +140 -0
- hfmodem/tests/shrike/test_refused_breakin_ack.py +247 -0
- hfmodem/tests/shrike/test_repeat_gear_stall.py +253 -0
- hfmodem/tests/shrike/test_replay_keying.py +42 -0
- hfmodem/tests/shrike/test_replay_recut.py +286 -0
- hfmodem/tests/shrike/test_rise.py +104 -0
- hfmodem/tests/shrike/test_rx.py +267 -0
- hfmodem/tests/shrike/test_rx_anchor_pin.py +106 -0
- hfmodem/tests/shrike/test_rx_control_phase.py +107 -0
- hfmodem/tests/shrike/test_rxcost.py +280 -0
- hfmodem/tests/shrike/test_second_session_readback.py +32 -0
- hfmodem/tests/shrike/test_session.py +237 -0
- hfmodem/tests/shrike/test_session_clock.py +177 -0
- hfmodem/tests/shrike/test_shared_memory_field.py +166 -0
- hfmodem/tests/shrike/test_shortfall.py +426 -0
- hfmodem/tests/shrike/test_signoff.py +247 -0
- hfmodem/tests/shrike/test_silence.py +665 -0
- hfmodem/tests/shrike/test_sl2_gear_prekey.py +162 -0
- hfmodem/tests/shrike/test_sl2_rotation_field.py +230 -0
- hfmodem/tests/shrike/test_sl2_rx_tracked.py +239 -0
- hfmodem/tests/shrike/test_slot_deadline.py +763 -0
- hfmodem/tests/shrike/test_spec.py +183 -0
- hfmodem/tests/shrike/test_static.py +264 -0
- hfmodem/tests/shrike/test_stinttail.py +183 -0
- hfmodem/tests/shrike/test_stream_recording.py +376 -0
- hfmodem/tests/shrike/test_teardown_grant.py +116 -0
- hfmodem/tests/shrike/test_terminal_ack.py +158 -0
- hfmodem/tests/shrike/test_timing_recovery.py +100 -0
- hfmodem/tests/shrike/test_traffic_logging.py +142 -0
- hfmodem/tests/shrike/test_traffic_sl_hold.py +119 -0
- hfmodem/tests/shrike/test_tx_gear_hold.py +135 -0
- hfmodem/tests/shrike/test_tx_latency_correction.py +156 -0
- hfmodem/tests/shrike/test_txhead.py +637 -0
- hfmodem/tests/shrike/test_unexpected_long_window_0914.py +232 -0
- hfmodem/tests/shrike/test_uninvited_entry.py +180 -0
- hfmodem/tests/shrike/test_unkey_on_signal.py +98 -0
- hfmodem/tests/shrike/test_v27_arq_emission_opportunity.py +267 -0
- hfmodem/tests/shrike/test_v28_prekey_recovery.py +153 -0
- hfmodem/tests/shrike/test_v29_native_short_fallback.py +61 -0
- hfmodem/tests/shrike/test_v29_p3_control_collect.py +85 -0
- hfmodem/tests/shrike/test_v29_short_fallback_qualification.py +91 -0
- hfmodem/tests/shrike/test_ve3kpg_cs6_0920.py +59 -0
- hfmodem/tests/shrike/test_wideband_header_0914.py +115 -0
- hfmodem/tests/shrike/test_wideband_prekey_0914.py +262 -0
- hfmodem/tests/shrike/test_ws8eoc_long_sl1_0920.py +92 -0
- hfmodem/tests/test_pending_terminal_grant.py +184 -0
- hfmodem/tests/test_v31_changeover_frequency_range.py +23 -0
- hfmodem/tests/winlink/__init__.py +2 -0
- hfmodem/tests/winlink/audio_rehearsal.py +129 -0
- hfmodem/tests/winlink/corpora.py +52 -0
- hfmodem/tests/winlink/test_audio_rehearsal.py +110 -0
- hfmodem/tests/winlink/test_client.py +406 -0
- hfmodem/tests/winlink/test_lzhuf.py +114 -0
- hfmodem/tests/winlink/test_mail_command.py +142 -0
- hfmodem/tests/winlink/test_message.py +176 -0
- hfmodem/tests/winlink/test_session.py +1175 -0
- hfmodem/tests/winlink/test_ww2mi_login.py +248 -0
- hfmodem/winlink/__init__.py +28 -0
- hfmodem/winlink/client.py +311 -0
- hfmodem/winlink/lzhuf.py +444 -0
- hfmodem/winlink/message.py +186 -0
- hfmodem/winlink/session.py +871 -0
- hfmodem-0.1.0.dist-info/METADATA +36 -0
- hfmodem-0.1.0.dist-info/RECORD +729 -0
- hfmodem-0.1.0.dist-info/WHEEL +5 -0
- hfmodem-0.1.0.dist-info/entry_points.txt +2 -0
- hfmodem-0.1.0.dist-info/licenses/LICENSE +661 -0
- hfmodem-0.1.0.dist-info/licenses/NOTICE +126 -0
- hfmodem-0.1.0.dist-info/top_level.txt +1 -0
hfmodem/__init__.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# SPDX-License-Identifier: AGPL-3.0-only
|
|
2
|
+
# SPDX-FileCopyrightText: 2026 Saul St John (W9SSJ)
|
|
3
|
+
|
|
4
|
+
"""hfmodem — four HF softmodems and the station that runs them.
|
|
5
|
+
|
|
6
|
+
Each protocol is a subpackage: `shrike` (PACTOR-1/2/3), `kestrel` (VARA),
|
|
7
|
+
`besra` (ARDOP) and `sabir`. They share `core` for everything that touches the
|
|
8
|
+
radio rather than the waveform, and `host` for the dialects an application
|
|
9
|
+
speaks to a modem.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
__version__ = "0.1.0"
|
hfmodem/__main__.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# SPDX-License-Identifier: AGPL-3.0-only
|
|
2
|
+
# SPDX-FileCopyrightText: 2026 Saul St John (W9SSJ)
|
|
3
|
+
|
|
4
|
+
"""`python -m hfmodem`, for when the console script is not on PATH.
|
|
5
|
+
|
|
6
|
+
At the radio the venv is usually addressed by path rather than activated, and
|
|
7
|
+
`.venv/bin/hfmodem` and `.venv/bin/python -m hfmodem` should not disagree about
|
|
8
|
+
whether the station will start.
|
|
9
|
+
"""
|
|
10
|
+
from hfmodem.cli import main
|
|
11
|
+
|
|
12
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# SPDX-License-Identifier: AGPL-3.0-only
|
|
2
|
+
# SPDX-FileCopyrightText: 2026 Saul St John (W9SSJ)
|
|
3
|
+
|
|
4
|
+
"""besra — an open, ARDOP-compatible HF modem.
|
|
5
|
+
|
|
6
|
+
The ARDOP-compatible member of the hfmodem flock (`compat/ardop`). ARDOP is an
|
|
7
|
+
open, published amateur mode; besra implements it from that published
|
|
8
|
+
specification, and from ardopcf where the wire demands exactness (`NOTICE` names
|
|
9
|
+
the parts). See `docs/protocols/ardop/` for the functional record.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
# SPDX-License-Identifier: AGPL-3.0-only
|
|
2
|
+
# SPDX-FileCopyrightText: 2026 Saul St John (W9SSJ)
|
|
3
|
+
|
|
4
|
+
"""`BesraModem` — the real ARDOP modem: PHY + ARQ behind the host `ModemCore` seam.
|
|
5
|
+
|
|
6
|
+
This binds the three validated layers into one modem:
|
|
7
|
+
|
|
8
|
+
* the ARQ session (`besra.arq.session`) drives the NEWSTATE protocol,
|
|
9
|
+
* the modulator (`besra.phy.modulator`) renders each frame the session sends
|
|
10
|
+
to 12 kHz audio, and
|
|
11
|
+
* the demodulator (`besra.phy.demodulator`) turns received audio back into the
|
|
12
|
+
decoded frames the session consumes.
|
|
13
|
+
|
|
14
|
+
The modem renders into and decodes out of an injected audio link (`audio_out`
|
|
15
|
+
for transmit, `receive_audio` for receive). Time and I/O are driven one of two
|
|
16
|
+
ways over the same core:
|
|
17
|
+
|
|
18
|
+
* **passive** — a synchronous pump (`besra.sim.air.StepAir`) calls
|
|
19
|
+
`receive_audio`/`tick` directly, for deterministic radioless tests; or
|
|
20
|
+
* **threaded** — `start(observer, threaded=True)` runs a background pump that
|
|
21
|
+
drains delivered audio and advances timers off the wall clock, so the host
|
|
22
|
+
server can drive it live. A single lock serialises the ARQ session across the
|
|
23
|
+
host thread and the pump thread.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
import logging
|
|
29
|
+
import queue
|
|
30
|
+
import threading
|
|
31
|
+
import time
|
|
32
|
+
|
|
33
|
+
import numpy as np
|
|
34
|
+
|
|
35
|
+
from ..frame import callsign, frame as F
|
|
36
|
+
from ..host import protocol as P
|
|
37
|
+
from ..host.modem_core import ModemCore, ModemObserver
|
|
38
|
+
from ..phy import modulator
|
|
39
|
+
from ..phy.demodulator import SAMPLE_RATE, DecodedFrame, Demodulator
|
|
40
|
+
from .session import ArqSession, peer_quality
|
|
41
|
+
|
|
42
|
+
log = logging.getLogger(__name__)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class _ObserverAdapter:
|
|
46
|
+
"""Maps the ARQ session's `Observer` calls onto the host `ModemObserver`.
|
|
47
|
+
|
|
48
|
+
The two vocabularies differ only in spelling (`newstate` vs `modem_newstate`);
|
|
49
|
+
this is the thin seam that keeps the session ignorant of the host dialect."""
|
|
50
|
+
|
|
51
|
+
def __init__(self, obs: ModemObserver) -> None:
|
|
52
|
+
self._obs = obs
|
|
53
|
+
|
|
54
|
+
def newstate(self, state: str) -> None:
|
|
55
|
+
self._obs.modem_newstate(state)
|
|
56
|
+
|
|
57
|
+
def connected(self, remote: str, bw: int) -> None:
|
|
58
|
+
self._obs.modem_connected(remote, bw)
|
|
59
|
+
|
|
60
|
+
def disconnected(self) -> None:
|
|
61
|
+
self._obs.modem_disconnected()
|
|
62
|
+
|
|
63
|
+
def data_received(self, kind: str, blob: bytes) -> None:
|
|
64
|
+
self._obs.modem_data_received(kind, blob)
|
|
65
|
+
|
|
66
|
+
def buffer(self, nbytes: int) -> None:
|
|
67
|
+
self._obs.modem_buffer(nbytes)
|
|
68
|
+
|
|
69
|
+
def pending(self, cancel: bool = False) -> None:
|
|
70
|
+
self._obs.modem_pending(cancel)
|
|
71
|
+
|
|
72
|
+
def target(self, call: str) -> None:
|
|
73
|
+
self._obs.modem_target(call)
|
|
74
|
+
|
|
75
|
+
def status(self, text: str) -> None:
|
|
76
|
+
self._obs.modem_status(text)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class BesraModem(ModemCore):
|
|
80
|
+
"""A radio-capable ARDOP modem. Transmits by rendering frames into its audio
|
|
81
|
+
link; receives by decoding audio the link delivers via `receive_audio`."""
|
|
82
|
+
|
|
83
|
+
#: silence padded around a delivered burst so the leader detector has room to
|
|
84
|
+
#: acquire and the decoder to flush (the demod's own convention).
|
|
85
|
+
_PAD_LEAD = 2400
|
|
86
|
+
_PAD_TAIL = 4800
|
|
87
|
+
|
|
88
|
+
def __init__(self, *, bandwidth: int = 500, timeout_s: int = 90,
|
|
89
|
+
leader_ms: int = modulator.DEFAULT_LEADER_MS,
|
|
90
|
+
threaded: bool = False) -> None:
|
|
91
|
+
self._start_threaded = threaded # host server drives live; sim drives passively
|
|
92
|
+
self._mycall = ""
|
|
93
|
+
self._gridsquare = ""
|
|
94
|
+
self._bandwidth = bandwidth
|
|
95
|
+
self._bw_forced = False
|
|
96
|
+
self._listen = False
|
|
97
|
+
self._protocolmode = "ARQ"
|
|
98
|
+
self._timeout_s = timeout_s
|
|
99
|
+
self._leader_ms = leader_ms
|
|
100
|
+
|
|
101
|
+
self._observer: ModemObserver | None = None
|
|
102
|
+
self._session: ArqSession | None = None
|
|
103
|
+
self._demod = Demodulator(expect_session=self.expected_session,
|
|
104
|
+
rx_epoch=self.rx_epoch)
|
|
105
|
+
self._clock = 0.0
|
|
106
|
+
self.audio_out = None # set by the air / device: called with int16 samples
|
|
107
|
+
|
|
108
|
+
self._lock = threading.RLock()
|
|
109
|
+
self._rx_q: "queue.Queue[np.ndarray]" = queue.Queue()
|
|
110
|
+
self._pump: threading.Thread | None = None
|
|
111
|
+
self._running = False
|
|
112
|
+
|
|
113
|
+
# -- ModemCore lifecycle ----------------------------------------------
|
|
114
|
+
|
|
115
|
+
def start(self, observer: ModemObserver, *, threaded: bool | None = None) -> None:
|
|
116
|
+
self._observer = observer
|
|
117
|
+
self._session = ArqSession(
|
|
118
|
+
self._mycall, transport=self, observer=_ObserverAdapter(observer),
|
|
119
|
+
bandwidth=self._bandwidth, timeout_s=self._timeout_s,
|
|
120
|
+
listen=self._listen)
|
|
121
|
+
self._session.tick(self._clock) # publishes the initial NEWSTATE DISC
|
|
122
|
+
if self._start_threaded if threaded is None else threaded:
|
|
123
|
+
self._running = True
|
|
124
|
+
self._pump = threading.Thread(target=self._run, name="besra-pump",
|
|
125
|
+
daemon=True)
|
|
126
|
+
self._pump.start()
|
|
127
|
+
|
|
128
|
+
def stop(self) -> None:
|
|
129
|
+
self._running = False
|
|
130
|
+
with self._lock:
|
|
131
|
+
self._session = None
|
|
132
|
+
|
|
133
|
+
def status(self, text: str) -> None:
|
|
134
|
+
"""Say something to the host on the STATUS line, from outside the session —
|
|
135
|
+
the radio backend's receive funnel is the one thing here the ARQ layer
|
|
136
|
+
cannot see. Silent before `start`, when there is no host to say it to."""
|
|
137
|
+
if self._observer is not None:
|
|
138
|
+
self._observer.modem_status(text)
|
|
139
|
+
|
|
140
|
+
@property
|
|
141
|
+
def state(self) -> str:
|
|
142
|
+
return self._session.state if self._session else P.ArdopState.OFFLINE
|
|
143
|
+
|
|
144
|
+
@property
|
|
145
|
+
def queued(self) -> int:
|
|
146
|
+
with self._lock:
|
|
147
|
+
return self._session.queued if self._session else 0
|
|
148
|
+
|
|
149
|
+
def expected_session(self) -> int | None:
|
|
150
|
+
"""The ARQ session id the demodulator should treat as corroborating a bare
|
|
151
|
+
control frame right now (`ArqSession.expected_session`), or None. A method
|
|
152
|
+
rather than a property so it can be handed to a `Demodulator` as its
|
|
153
|
+
`expect_session` callable — the radio backend's decoder polls it live."""
|
|
154
|
+
s = self._session
|
|
155
|
+
return s.expected_session if s is not None else None
|
|
156
|
+
|
|
157
|
+
def rx_epoch(self) -> int:
|
|
158
|
+
"""`ArqSession.rx_epoch` for the demodulator's memory ARQ, polled the same
|
|
159
|
+
way and for the same reason as `expected_session`."""
|
|
160
|
+
s = self._session
|
|
161
|
+
return s.rx_epoch if s is not None else 0
|
|
162
|
+
|
|
163
|
+
# -- config (the host server pushes these before start) ----------------
|
|
164
|
+
|
|
165
|
+
def set_mycall(self, call: str) -> None:
|
|
166
|
+
# The host sets MYCALL after start(), so push it into the live session.
|
|
167
|
+
self._mycall = call
|
|
168
|
+
with self._lock:
|
|
169
|
+
if self._session is not None:
|
|
170
|
+
self._session.mycall = call
|
|
171
|
+
|
|
172
|
+
def set_listen(self, on: bool) -> None:
|
|
173
|
+
self._listen = on
|
|
174
|
+
with self._lock:
|
|
175
|
+
if self._session is not None:
|
|
176
|
+
self._session.listen = on
|
|
177
|
+
|
|
178
|
+
@property
|
|
179
|
+
def bandwidth(self) -> int:
|
|
180
|
+
"""The session bandwidth in force, which is also the passband a burst
|
|
181
|
+
occupies — `radio.RadioLink` states the emission from it, and a host
|
|
182
|
+
`ARQBW` moves both at once."""
|
|
183
|
+
return self._bandwidth
|
|
184
|
+
|
|
185
|
+
def set_bandwidth(self, hz: int, forced: bool) -> None:
|
|
186
|
+
# ARQBW arrives after start() too, so push it into the live session —
|
|
187
|
+
# otherwise the next ConReq goes out at whatever the modem was built
|
|
188
|
+
# with, and the host's setting is a stored number that governs nothing.
|
|
189
|
+
self._bandwidth = hz
|
|
190
|
+
self._bw_forced = forced
|
|
191
|
+
with self._lock:
|
|
192
|
+
if self._session is not None:
|
|
193
|
+
self._session.bandwidth = hz
|
|
194
|
+
|
|
195
|
+
# -- ModemCore session verbs ------------------------------------------
|
|
196
|
+
|
|
197
|
+
def connect(self, target: str, repeats: int = 5) -> None:
|
|
198
|
+
with self._lock:
|
|
199
|
+
if self._session is None:
|
|
200
|
+
return
|
|
201
|
+
self._session.connect(target, repeats)
|
|
202
|
+
|
|
203
|
+
def transmit(self, blob: bytes) -> None:
|
|
204
|
+
with self._lock:
|
|
205
|
+
if self._session is None:
|
|
206
|
+
return
|
|
207
|
+
self._session.queue_data(blob)
|
|
208
|
+
|
|
209
|
+
def purge_buffer(self) -> None:
|
|
210
|
+
with self._lock:
|
|
211
|
+
if self._session is None:
|
|
212
|
+
return
|
|
213
|
+
self._session.purge()
|
|
214
|
+
|
|
215
|
+
def disconnect(self) -> None:
|
|
216
|
+
with self._lock:
|
|
217
|
+
if self._session is None:
|
|
218
|
+
return
|
|
219
|
+
self._session.disconnect()
|
|
220
|
+
|
|
221
|
+
def abort(self) -> None:
|
|
222
|
+
with self._lock:
|
|
223
|
+
if self._session is None:
|
|
224
|
+
return
|
|
225
|
+
self._session.abort()
|
|
226
|
+
|
|
227
|
+
# -- the ARQ session's Transport: render a frame into the audio link ---
|
|
228
|
+
|
|
229
|
+
def send(self, frame_type: int, payload: bytes, session_id: int) -> float:
|
|
230
|
+
# ConReq/Ping/ID (unconnected frames) go out with Session ID 0xFF; both
|
|
231
|
+
# ends re-derive the real session from the callsigns (spec §2.1).
|
|
232
|
+
wire_session = 0xFF if F.FRAMES[frame_type].forces_session else session_id
|
|
233
|
+
samples = modulator.render_frame(frame_type, payload=payload,
|
|
234
|
+
session_id=wire_session,
|
|
235
|
+
leader_ms=self._leader_ms)
|
|
236
|
+
log.info("TX %s %.2fs", F.FRAMES[frame_type].name, len(samples) / SAMPLE_RATE)
|
|
237
|
+
obs = self._observer
|
|
238
|
+
if obs is not None:
|
|
239
|
+
obs.modem_ptt(True)
|
|
240
|
+
if self.audio_out is not None:
|
|
241
|
+
self.audio_out(samples)
|
|
242
|
+
if obs is not None:
|
|
243
|
+
obs.modem_ptt(False)
|
|
244
|
+
# The transmit-frame duration, so the session can schedule its repeat from
|
|
245
|
+
# when the frame *ends* (the reference anchors dttNextPlay after playback),
|
|
246
|
+
# not from when it starts — otherwise a 1.75 s ConReq eats the listen window.
|
|
247
|
+
return len(samples) / SAMPLE_RATE
|
|
248
|
+
|
|
249
|
+
# -- receive / timing --------------------------------------------------
|
|
250
|
+
|
|
251
|
+
def deliver_rx(self, samples: np.ndarray) -> None:
|
|
252
|
+
"""Hand received audio to the modem. In threaded mode it is queued for
|
|
253
|
+
the pump; passively it decodes inline."""
|
|
254
|
+
if self._running:
|
|
255
|
+
self._rx_q.put(samples)
|
|
256
|
+
else:
|
|
257
|
+
self.receive_audio(samples)
|
|
258
|
+
|
|
259
|
+
def receive_audio(self, samples: np.ndarray) -> None:
|
|
260
|
+
padded = np.concatenate([
|
|
261
|
+
np.zeros(self._PAD_LEAD, dtype="<i2"), samples,
|
|
262
|
+
np.zeros(self._PAD_TAIL, dtype="<i2")])
|
|
263
|
+
self.receive_frames(self._demod.decode(padded))
|
|
264
|
+
|
|
265
|
+
def receive_frames(self, frames: list[DecodedFrame]) -> None:
|
|
266
|
+
"""Feed already-decoded frames to the session. The radio backend decodes a
|
|
267
|
+
rolling window on its own thread and reports each frame once it has decoded,
|
|
268
|
+
so it enters here rather than through `receive_audio` — its windows overlap,
|
|
269
|
+
and the same frame decoded a second time would replay its DATAACK and clear a
|
|
270
|
+
data frame the peer never acknowledged. The one repeat it does hand up is a
|
|
271
|
+
position it had only failed at before (`RollingDecoder.DEDUP_S`), which
|
|
272
|
+
arrives as a NAK this end now has the payload to correct."""
|
|
273
|
+
for frame in frames:
|
|
274
|
+
fd = F.FRAMES.get(frame.type)
|
|
275
|
+
# A DATAACK/DATANAK's own type carries the peer's grade of the frame it
|
|
276
|
+
# answers — the whole of our transmit-rate feedback, and invisible while
|
|
277
|
+
# all 32 codes logged under the one frame name.
|
|
278
|
+
graded = peer_quality(frame.type)
|
|
279
|
+
log.info("RX %s sess=%#04x ok=%s%s%s%s%s%s", fd.name if fd else hex(frame.type),
|
|
280
|
+
frame.session_id,
|
|
281
|
+
"UNVERIFIED" if frame.unverified else frame.ok,
|
|
282
|
+
" HEADER-ONLY" if frame.header_only else "",
|
|
283
|
+
"" if graded is None else f" theirq={graded}",
|
|
284
|
+
"" if frame.quality is None else f" q={frame.quality}",
|
|
285
|
+
# q=0 is a legal grade and also what is left when the body ran
|
|
286
|
+
# off the end of the capture (`DecodedFrame.truncated`), and
|
|
287
|
+
# five of the second kind were carried for days as the first.
|
|
288
|
+
" TRUNCATED" if frame.truncated else "",
|
|
289
|
+
f" {frame.caller}>{frame.target}" if frame.caller else "")
|
|
290
|
+
with self._lock:
|
|
291
|
+
if self._session is None:
|
|
292
|
+
return
|
|
293
|
+
for frame in frames:
|
|
294
|
+
self._session.on_receive(frame.type, self._frame_payload(frame),
|
|
295
|
+
frame.session_id, frame.ok, frame.quality,
|
|
296
|
+
header_only=frame.header_only)
|
|
297
|
+
|
|
298
|
+
def tick(self, now: float) -> None:
|
|
299
|
+
self._clock = now
|
|
300
|
+
with self._lock:
|
|
301
|
+
if self._session is not None:
|
|
302
|
+
self._session.tick(now)
|
|
303
|
+
|
|
304
|
+
def _run(self) -> None:
|
|
305
|
+
"""Threaded pump: decode delivered audio and advance timers off the wall
|
|
306
|
+
clock until stopped."""
|
|
307
|
+
start = time.monotonic()
|
|
308
|
+
while self._running:
|
|
309
|
+
try:
|
|
310
|
+
samples = self._rx_q.get(timeout=0.1)
|
|
311
|
+
except queue.Empty:
|
|
312
|
+
samples = None
|
|
313
|
+
if samples is not None:
|
|
314
|
+
try:
|
|
315
|
+
self.receive_audio(samples)
|
|
316
|
+
except Exception as exc: # a malformed burst must not kill the pump
|
|
317
|
+
obs = self._observer
|
|
318
|
+
if obs is not None:
|
|
319
|
+
obs.modem_fault(f"receive error: {exc}")
|
|
320
|
+
self.tick(time.monotonic() - start)
|
|
321
|
+
|
|
322
|
+
@staticmethod
|
|
323
|
+
def _frame_payload(frame: DecodedFrame) -> bytes:
|
|
324
|
+
"""Reconstruct the byte payload the ARQ session expects, since the demod
|
|
325
|
+
surfaces some frames as parsed fields rather than raw bytes: the packed
|
|
326
|
+
caller‖target for ConReq/Ping, the packed callsign‖grid for an ID frame,
|
|
327
|
+
the timing triple for a ConAck. Everything else carries its bytes."""
|
|
328
|
+
fd = F.FRAMES.get(frame.type)
|
|
329
|
+
name = fd.name if fd else ""
|
|
330
|
+
if (name.startswith("ConReq") or name == "Ping") and frame.caller and frame.target:
|
|
331
|
+
return callsign.pack_callsign(frame.caller) + callsign.pack_callsign(frame.target)
|
|
332
|
+
if name == "IDFrame" and frame.caller:
|
|
333
|
+
# The ARQ session never consumes an ID's grid, so a missing/short one
|
|
334
|
+
# is zero-filled rather than raising out of the pump (pack_grid is
|
|
335
|
+
# strict about 2/4/6/8-char grids).
|
|
336
|
+
grid = frame.grid or ""
|
|
337
|
+
packed_grid = callsign.pack_grid(grid) if len(grid) in (2, 4, 6, 8) else b"\x00" * 6
|
|
338
|
+
return callsign.pack_callsign(frame.caller) + packed_grid
|
|
339
|
+
if name.startswith("ConAck"):
|
|
340
|
+
t = min(255, (frame.conack_timing_ms or 0) // 10)
|
|
341
|
+
return bytes([t, t, t])
|
|
342
|
+
return frame.payload or b""
|