diff --git a/decoder.go b/decoder.go index 6ada51699..e8e425a7c 100644 --- a/decoder.go +++ b/decoder.go @@ -22,6 +22,7 @@ import ( "math" ) +// PacketConfig specifies packet-specific radio configuration. type PacketConfig struct { DataRate int BlockSize, BlockSize2 int @@ -46,6 +47,7 @@ func (cfg PacketConfig) Log() { log.Println("Preamble:", cfg.Preamble) } +// Decoder contains buffers and radio configuration. type Decoder struct { cfg PacketConfig @@ -62,21 +64,25 @@ type Decoder struct { pkt []byte } +// Create a new decoder with the given packet configuration. func NewDecoder(cfg PacketConfig) (d Decoder) { d.cfg = cfg + // Allocate necessary buffers. d.iq = make([]byte, d.cfg.BufferLength<<1) d.signal = make([]float64, d.cfg.BufferLength) d.quantized = make([]byte, d.cfg.BufferLength) d.csum = make([]float64, d.cfg.BlockSize+d.cfg.SymbolLength2+1) + // Calculate magnitude lookup table specified by -fastmag flag. if *fastMag { d.lut = NewAlphaMaxBetaMinLUT() } else { d.lut = NewSqrtMagLUT() } + // Pre-calculate a byte-slice version of the preamble for searching. d.preamble = make([]byte, len(d.cfg.Preamble)) for idx := range d.cfg.Preamble { if d.cfg.Preamble[idx] == '1' { @@ -84,6 +90,9 @@ func NewDecoder(cfg PacketConfig) (d Decoder) { } } + // Slice quantized sample buffer to make searching for the preamble more + // memory local. Pre-allocate a flat buffer so memory is contiguous and + // assign slices to the buffer. d.slices = make([][]byte, d.cfg.SymbolLength2) flat := make([]byte, d.cfg.BlockSize2-(d.cfg.BlockSize2%d.cfg.SymbolLength2)) @@ -93,13 +102,16 @@ func NewDecoder(cfg PacketConfig) (d Decoder) { d.slices[symbolOffset] = flat[lower:upper] } + // Signal up to the final stage is 1-bit per byte. Allocate a buffer to + // store packed version 8-bits per byte. d.pkt = make([]byte, d.cfg.PacketSymbols>>3) return } +// Decode accepts a sample block and performs various DSP techniques to extract a packet. func (d Decoder) Decode(input []byte) (pkts [][]byte) { - // Shift new block into buffers. + // Shift buffers to append new block. copy(d.iq, d.iq[d.cfg.BlockSize<<1:]) copy(d.signal, d.signal[d.cfg.BlockSize:]) copy(d.quantized, d.quantized[d.cfg.BlockSize:]) @@ -107,19 +119,33 @@ func (d Decoder) Decode(input []byte) (pkts [][]byte) { iqBlock := d.iq[d.cfg.PacketLength<<1:] signalBlock := d.signal[d.cfg.PacketLength:] + + // Compute the magnitude of the new block. d.lut.Execute(iqBlock, signalBlock) signalBlock = d.signal[d.cfg.PacketLength-d.cfg.SymbolLength2:] + + // Perform matched filter on new block. d.Filter(signalBlock) signalBlock = d.signal[d.cfg.PacketLength-d.cfg.SymbolLength2:] + + // Perform bit-decision on new block. Quantize(signalBlock, d.quantized[d.cfg.PacketLength-d.cfg.SymbolLength2:]) + + // Pack the quantized signal into slices for searching. d.Pack(d.quantized[:d.cfg.BlockSize2], d.slices) + // Get a list of indexes the preamble exists at. indexes := d.Search(d.slices, d.preamble) + // We will likely find multiple instances of the message so only keep + // track of unique instances. seen := make(map[string]bool) + // For each of the indexes the preamble exists at. for _, qIdx := range indexes { + // Check that we're still within the first sample block. We'll catch + // the message on the next sample block otherwise. if qIdx > d.cfg.BlockSize { continue } @@ -130,6 +156,7 @@ func (d Decoder) Decode(input []byte) (pkts [][]byte) { d.pkt[pIdx>>3] |= d.quantized[qIdx+(pIdx*d.cfg.SymbolLength2)] } + // Store the packet in the seen map and append to the packet list. pktStr := fmt.Sprintf("%02X", d.pkt) if !seen[pktStr] { seen[pktStr] = true @@ -140,12 +167,15 @@ func (d Decoder) Decode(input []byte) (pkts [][]byte) { return } +// A MagnitudeLUT knows how to perform complex magnitude on a slice of IQ samples. type MagnitudeLUT interface { Execute([]byte, []float64) } +// Default Magnitude Lookup Table type MagLUT []float64 +// Pre-computes normalized squares with most common DC offset for rtl-sdr dongles. func NewSqrtMagLUT() (lut MagLUT) { lut = make([]float64, 0x100) for idx := range lut { @@ -155,6 +185,7 @@ func NewSqrtMagLUT() (lut MagLUT) { return } +// Calculates complex magnitude on given IQ stream writing result to output. func (lut MagLUT) Execute(input []byte, output []float64) { for idx := range output { lutIdx := idx << 1 @@ -162,8 +193,10 @@ func (lut MagLUT) Execute(input []byte, output []float64) { } } +// Alpha*Max + Beta*Min Magnitude Approximation Lookup Table. type AlphaMaxBetaMinLUT []float64 +// Pre-computes absolute values with most common DC offset for rtl-sdr dongles. func NewAlphaMaxBetaMinLUT() (lut AlphaMaxBetaMinLUT) { lut = make([]float64, 0x100) for idx := range lut { @@ -172,6 +205,7 @@ func NewAlphaMaxBetaMinLUT() (lut AlphaMaxBetaMinLUT) { return } +// Calculates complex magnitude on given IQ stream writing result to output. func (lut AlphaMaxBetaMinLUT) Execute(input []byte, output []float64) { const ( α = 0.948059448969 @@ -190,13 +224,18 @@ func (lut AlphaMaxBetaMinLUT) Execute(input []byte, output []float64) { } } +// Matched filter for Manchester coded signals. Output signal's sign at each +// sample determines the bit-value since Manchester symbols have odd symmetry. func (d Decoder) Filter(input []float64) { + // Computing the cumulative summation over the signal simplifies + // filtering to the difference of a pair of subtractions. var sum float64 for idx, v := range input { sum += v d.csum[idx+1] = sum } + // Filter result is difference of summation of lower and upper symbols. lower := d.csum[d.cfg.SymbolLength:] upper := d.csum[d.cfg.SymbolLength2:] for idx := range input[:len(input)-d.cfg.SymbolLength2] { @@ -206,6 +245,7 @@ func (d Decoder) Filter(input []float64) { return } +// Bit-value is determined by the sign of each sample after filtering. func Quantize(input []float64, output []byte) { for idx, val := range input { output[idx] = byte(math.Float64bits(val)>>63) ^ 0x01 @@ -214,6 +254,9 @@ func Quantize(input []float64, output []byte) { return } +// Packs quantized signal into slices such that the first rank represents +// sample offsets and the second represents the value of each symbol from the +// given offset. func (d Decoder) Pack(input []byte, slices [][]byte) { for symbolOffset, slice := range slices { for symbolIdx := range slice { @@ -224,6 +267,9 @@ func (d Decoder) Pack(input []byte, slices [][]byte) { return } +// For each sample offset look for the preamble. Return a list of indexes the +// preamble is found at. Indexes are absolute in the unsliced quantized +// buffer. func (d Decoder) Search(slices [][]byte, preamble []byte) (indexes []int) { for symbolOffset, slice := range slices { for symbolIdx := range slice[:len(slice)-len(preamble)] {