Skip to content

Commit

Permalink
Add comments on decoding methods.
Browse files Browse the repository at this point in the history
  • Loading branch information
bemasher committed Aug 31, 2014
1 parent df217be commit d0f46b3
Showing 1 changed file with 47 additions and 1 deletion.
48 changes: 47 additions & 1 deletion decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"math"
)

// PacketConfig specifies packet-specific radio configuration.
type PacketConfig struct {
DataRate int
BlockSize, BlockSize2 int
Expand All @@ -46,6 +47,7 @@ func (cfg PacketConfig) Log() {
log.Println("Preamble:", cfg.Preamble)
}

// Decoder contains buffers and radio configuration.
type Decoder struct {
cfg PacketConfig

Expand All @@ -62,28 +64,35 @@ 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' {
d.preamble[idx] = 1
}
}

// 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))

Expand All @@ -93,33 +102,50 @@ 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:])
copy(d.iq[d.cfg.PacketLength<<1:], input[:])

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
}
Expand All @@ -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
Expand All @@ -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 {
Expand All @@ -155,15 +185,18 @@ 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
output[idx] = math.Sqrt(lut[input[lutIdx]] + lut[input[lutIdx+1]])
}
}

// 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 {
Expand All @@ -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
Expand All @@ -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] {
Expand All @@ -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
Expand All @@ -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 {
Expand All @@ -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)] {
Expand Down

0 comments on commit d0f46b3

Please sign in to comment.