Changelog
Unreleased
Added
- README packet spectrogram —
scripts/plot_pktA_spectrogram.pyrenders the PU-3 packet from the packet-A sample recording as a single labelled spectrogram strip (preamble, sync, SFD down-chirps, header + payload + CRC), used as README Figure 1. gr-lora-sdrstreaming —decode_stream/flushnow work withsync_algorithm='gr-lora-sdr': a new_GrFrameStreamscanner runs the native-rate dechirp run detection incrementally over arbitrary-sized chunks (bounded memory) and decodes each candidate as soon as its window is buffered.decode_file/decode_iqreuse the same scanner, so both sync algorithms now stream.
Changed
save_packetsprettier output — besidespacket_XXXX.binpayloads and the append-onlymetadata.jsonl,save_packetsnow writes a readablepackets.txtsummary with each packet'sPacket.__str__detail table.
Removed
- Chase diagnosis fields dropped from
Packet—chase_used,chase_attemptsandchase_stageare no longer exposed on the result type; Chase remains a decoder mode and its outcome is read fromok/crc_validas before. Thechase_flip_frac/chase_attempts_avgmetrics were removed fromperf/awgn_chase_vs_hard.pyaccordingly.
v0.3.0 (2026-08-23)
Added
- Automatic carrier offset estimation —
decode_file/decode_iq/decodeacceptcarrier_offset_hz='auto', which scans the recording for preamble runs and resolves the offset from the preamble upchirp and the first SFD downchirp (the timing term cancels, so the estimate is unambiguous over±fs/2). SeeLoRaDecoder.estimate_carrier_offset. DecoderSettings.sfo_ppm— optional clock-offset override for thegr-lora-sdrsync: a fixed ppm value,None(derive from residual CFO), or'auto'(default) to sweepsfo_ppm_searchand accept the first frame whose CRC validates.DecoderSettings.max_os_factor— thegr-lora-sdrsync band-limits and decimates high-oversampled input down to at most thisfs/bwbefore running the native-rateframe_syncport.- Header checksum verification — explicit headers are now validated with
the LoRa 5-bit checksum (
verify_header_checksum) before their fields are trusted. - Sub-sample timing refinement — when the hard decode of a fully buffered
packet fails, the xhonneux sync probes
timing_searchsub-sample alignments and the surrounding symbol positions before giving up.
Changed
sync.pynow implements Algorithm 1 of Xhonneux et al. exactly:- Fractional CFO from the last two upchirp pairs (Eq. 14) instead of an average over all preamble pairs.
- Stage 2 uses the first post-detection window for
s_tilde_upand the paper'sM_tilde = N - s_tilde_up. - Stage 3 demodulates the single final upchirp (U7) and single first SFD downchirp (D1) instead of averaged windows.
- Integer offsets via Eq. 17/18, with the integer STO unwrapped into
[-N/2, N/2)so a slightly negative STO no longer pushes the payload a full symbol early. gr-lora-sdrsync no longer returns failed candidates —decode_file/decode_iqwithsync_algorithm='gr-lora-sdr'now drop runs whose frame never synchronized (false-positive preamble detections on noise), matching thexhonneuxpath which already filtered them.Packet.__str__renders a detail table —print(packet)shows an aligned key/value table with the full payload hex (wrapped at 32 bytes per line), full payload text, positions, SNR, frequency offset and CRC status.__repr__keeps the compact one-line summary for lists and logs.calc_payload_sym_numnow uses the SF-dependent explicit-header spare nibbles (sf - 7) instead of a fixed 3, fixing symbol-count undercounts for SF < 10 (gr-lora_sdr packets at SF 7-9 previously dewhitened one nibble short).apply_time_shiftfractional delays are zero-padded so the FFT wrap cannot alias into the signal.- Short preambles — stage 3 now demodulates the final preamble upchirp
from
l + min(4, N_preamble_up - N_detect)instead of the paper's fixed U7 (l+4), so preambles with only 6 upchirps (6 up + 2 sync + 2.25 SFD) decode correctly when configured withN_preamble_up=6. - Header checksum fix — the
c0checksum bit usedbit0(n1)where gr-lora_sdr's algorithm usesbit3(n2); valid headers were being rejected. decode_filesmall-chunk performance — the sub-sample timing probe is only run once a packet is fully buffered, removing the per-chunk repeat cost while a packet is still streaming in.- Packaging — license and authorship follow the LibreCube convention
(MIT,
CONTRIBUTORS.txt); wheel and source distribution rebuilt for 0.3.0.
Fixed
- Explicit-header checksum validation (see Changed).
Tests
- Reference OTA packet-start positions updated for the unwrapped-STO sync.
tests/test_sync_paper.py— estimator identities (Eq. 19 = Eq. 20 atM_hat=0), Eq. 17/18 round-trips, noiseless fractional CFO/STO recovery, and end-to-end decode with injected integer/fractional CFO and STO.scripts/gr_ref.py,scripts/gr_packet_test.py— generate packets with gr-lora_sdr's TX (SF 7-10, CR 1/2/4, with/without CRC, with/without offsets) and verify the payload against the transmitted ground truth and gr-lora_sdr's RX chain.scripts/ota_test.py— decode the OTA capturedataset/lora_capture2.dat(4× "Hi from Shayan") at the reference positions and cross-check the payload with gr-lora_sdr's RX.docs/guide/sync-algorithm.md— equation-by-equation mapping of the algorithm, the preamble-length generalization, and theN/2disambiguation.
v0.2.0 (2026-08-06)
Breaking: the decoder now returns Packet objects on every path, and the
streaming API replaces the old detect_packets.
Added
Packetresult type — stable schema withok/error/crc_valid,snr_est,freq_offset_hz, sample positions and diagnostics;to_dict()for JSON serialization.LoRaDecoder.decode_stream/flush/reset— live chunked decoding; packets may straddle chunk boundaries and memory stays bounded.decode_filestreams from disk — newiter_iq_chunksreader (raw float32/int16 + stereo WAV) keeps memory bounded for multi-GB files.estimate_snr— preamble-based SNR estimator (previously documented but never implemented).load_iq/save_packets/sample_rateI/O helpers.- Stateful linear-phase FIR decimator, preamble gate, and pending/truncation handling in the streaming scan.
Removed
detect_packets(replaced bydecode_iq) and themulti_packetmodule.
Changed
full_decodereturns aPacketinstead of a dict.- Constructor gains implicit-header, chase and streaming tuning parameters.
decode_file/decode_iqalways returnlist[Packet].
Tests
- Streaming equivalence across chunk sizes, packet splits across chunk boundaries, synthetic implicit-header round-trips at SF 7–12, SNR estimator, and I/O helpers (46 tests).
v0.1.0 (initial)
- Pure-Python LoRa receiver with the 3-stage Xhonneux et al. synchronizer.
- GNU Radio TX/RX flowgraphs and AWGN performance sweeps.