335 lines
10 KiB
Go
335 lines
10 KiB
Go
// RTLAMR - An rtl-sdr receiver for smart meters operating in the 900MHz ISM band.
|
|
// Copyright (C) 2015 Douglas Hall
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Affero General Public License as published
|
|
// by the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Affero General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
package decode
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"log"
|
|
"math"
|
|
)
|
|
|
|
// PacketConfig specifies packet-specific radio configuration.
|
|
type PacketConfig struct {
|
|
DataRate int
|
|
|
|
BlockSize, BlockSize2 int
|
|
SymbolLength, SymbolLength2 int
|
|
SampleRate int
|
|
|
|
PreambleSymbols, PacketSymbols int
|
|
PreambleLength, PacketLength int
|
|
Preamble string
|
|
|
|
BufferLength int
|
|
|
|
CenterFreq uint32
|
|
}
|
|
|
|
func (cfg PacketConfig) Decimate(decimation int) PacketConfig {
|
|
cfg.BlockSize /= decimation
|
|
cfg.BlockSize2 /= decimation
|
|
cfg.SymbolLength /= decimation
|
|
cfg.SymbolLength2 /= decimation
|
|
cfg.SampleRate /= decimation
|
|
cfg.DataRate /= decimation
|
|
|
|
cfg.PreambleLength /= decimation
|
|
cfg.PacketLength /= decimation
|
|
|
|
cfg.BufferLength /= decimation
|
|
|
|
return cfg
|
|
}
|
|
|
|
func (d Decoder) Log() {
|
|
if d.Decimation != 1 {
|
|
log.Printf("BlockSize: %d|%d\n", d.Cfg.BlockSize, d.DecCfg.BlockSize)
|
|
log.Println("CenterFreq:", d.Cfg.CenterFreq)
|
|
log.Printf("SampleRate: %d|%d\n", d.Cfg.SampleRate, d.DecCfg.SampleRate)
|
|
log.Printf("DataRate: %d|%d\n", d.Cfg.DataRate, d.DecCfg.DataRate)
|
|
log.Printf("SymbolLength: %d|%d\n", d.Cfg.SymbolLength, d.DecCfg.SymbolLength)
|
|
log.Println("PreambleSymbols:", d.Cfg.PreambleSymbols)
|
|
log.Printf("PreambleLength: %d|%d\n", d.Cfg.PreambleLength, d.DecCfg.PreambleLength)
|
|
log.Println("PacketSymbols:", d.Cfg.PacketSymbols)
|
|
log.Printf("PacketLength: %d|%d\n", d.Cfg.PacketLength, d.DecCfg.PacketLength)
|
|
log.Println("Preamble:", d.Cfg.Preamble)
|
|
|
|
if d.Cfg.SymbolLength%d.Decimation != 0 {
|
|
log.Println("Warning: decimated symbol length is non-integral, sensitivity may be poor")
|
|
}
|
|
|
|
if d.DecCfg.SymbolLength < 3 {
|
|
log.Fatal("Error: illegal decimation factor, choose a smaller factor")
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
log.Println("CenterFreq:", d.Cfg.CenterFreq)
|
|
log.Println("SampleRate:", d.Cfg.SampleRate)
|
|
log.Println("DataRate:", d.Cfg.DataRate)
|
|
log.Println("SymbolLength:", d.Cfg.SymbolLength)
|
|
log.Println("PreambleSymbols:", d.Cfg.PreambleSymbols)
|
|
log.Println("PreambleLength:", d.Cfg.PreambleLength)
|
|
log.Println("PacketSymbols:", d.Cfg.PacketSymbols)
|
|
log.Println("PacketLength:", d.Cfg.PacketLength)
|
|
log.Println("Preamble:", d.Cfg.Preamble)
|
|
}
|
|
|
|
// Decoder contains buffers and radio configuration.
|
|
type Decoder struct {
|
|
Cfg PacketConfig
|
|
|
|
Decimation int
|
|
DecCfg PacketConfig
|
|
|
|
IQ []byte
|
|
Signal []float64
|
|
Filtered []float64
|
|
Quantized []byte
|
|
|
|
csum []float64
|
|
demod Demodulator
|
|
|
|
preamble []byte
|
|
slices [][]byte
|
|
|
|
pkt []byte
|
|
}
|
|
|
|
// Create a new decoder with the given packet configuration.
|
|
func NewDecoder(cfg PacketConfig, decimation int) (d Decoder) {
|
|
d.Cfg = cfg
|
|
|
|
d.Cfg.SymbolLength2 = d.Cfg.SymbolLength << 1
|
|
d.Cfg.SampleRate = d.Cfg.DataRate * d.Cfg.SymbolLength
|
|
|
|
d.Cfg.PreambleLength = d.Cfg.PreambleSymbols * d.Cfg.SymbolLength2
|
|
d.Cfg.PacketLength = d.Cfg.PacketSymbols * d.Cfg.SymbolLength2
|
|
|
|
d.Cfg.BlockSize = NextPowerOf2(d.Cfg.PreambleLength)
|
|
d.Cfg.BlockSize2 = d.Cfg.BlockSize << 1
|
|
|
|
d.Cfg.BufferLength = d.Cfg.PacketLength + d.Cfg.BlockSize
|
|
|
|
d.Decimation = decimation
|
|
d.DecCfg = d.Cfg.Decimate(d.Decimation)
|
|
|
|
// Allocate necessary buffers.
|
|
d.IQ = make([]byte, d.Cfg.BufferLength<<1)
|
|
d.Signal = make([]float64, d.DecCfg.BufferLength)
|
|
d.Filtered = make([]float64, d.DecCfg.BufferLength)
|
|
d.Quantized = make([]byte, d.DecCfg.BufferLength)
|
|
|
|
d.csum = make([]float64, (d.DecCfg.PacketLength - d.DecCfg.SymbolLength2 + 1))
|
|
|
|
// Calculate magnitude lookup table specified by -fastmag flag.
|
|
d.demod = 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.DecCfg.SymbolLength2)
|
|
flat := make([]byte, d.DecCfg.BlockSize2-(d.DecCfg.BlockSize2%d.DecCfg.SymbolLength2))
|
|
|
|
symbolsPerBlock := d.DecCfg.BlockSize2 / d.DecCfg.SymbolLength2
|
|
for symbolOffset := range d.slices {
|
|
lower := symbolOffset * symbolsPerBlock
|
|
upper := (symbolOffset + 1) * symbolsPerBlock
|
|
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.DecCfg.PacketSymbols+7)>>3)
|
|
|
|
return
|
|
}
|
|
|
|
// Decode accepts a sample block and performs various DSP techniques to extract a packet.
|
|
func (d Decoder) Decode(input []byte) []int {
|
|
// Shift buffers to append new block.
|
|
copy(d.IQ, d.IQ[d.Cfg.BlockSize<<1:])
|
|
copy(d.Signal, d.Signal[d.DecCfg.BlockSize:])
|
|
copy(d.Filtered, d.Filtered[d.DecCfg.BlockSize:])
|
|
copy(d.Quantized, d.Quantized[d.DecCfg.BlockSize:])
|
|
copy(d.IQ[d.Cfg.PacketLength<<1:], input[:])
|
|
|
|
iqBlock := d.IQ[d.Cfg.PacketLength<<1:]
|
|
signalBlock := d.Signal[d.DecCfg.PacketLength:]
|
|
|
|
// Compute the magnitude of the new block.
|
|
d.demod.Execute(iqBlock, signalBlock)
|
|
|
|
signalBlock = d.Signal[d.DecCfg.PacketLength-d.DecCfg.SymbolLength2:]
|
|
filterBlock := d.Filtered[d.DecCfg.PacketLength-d.DecCfg.SymbolLength2:]
|
|
|
|
// Perform matched filter on new block.
|
|
d.Filter(signalBlock, filterBlock)
|
|
|
|
// Perform bit-decision on new block.
|
|
Quantize(filterBlock, d.Quantized[d.DecCfg.PacketLength-d.DecCfg.SymbolLength2:])
|
|
|
|
// Pack the quantized signal into slices for searching.
|
|
d.Pack(d.Quantized[:d.DecCfg.BlockSize2], d.slices)
|
|
|
|
// Return a list of indexes the preamble exists at.
|
|
return d.Search(d.slices, d.preamble)
|
|
}
|
|
|
|
// A Demodulator knows how to demodulate an array of uint8 IQ samples into an
|
|
// array of float64 samples.
|
|
type Demodulator 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 {
|
|
lut[idx] = 127.4 - float64(idx)
|
|
lut[idx] *= lut[idx]
|
|
}
|
|
return
|
|
}
|
|
|
|
// Calculates complex magnitude on given IQ stream writing result to output.
|
|
func (lut MagLUT) Execute(input []byte, output []float64) {
|
|
decIdx := 0
|
|
dec := (len(input) / len(output))
|
|
|
|
for idx := 0; decIdx < len(output); idx += dec {
|
|
output[decIdx] = math.Sqrt(lut[input[idx]] + lut[input[idx+1]])
|
|
decIdx++
|
|
}
|
|
}
|
|
|
|
// 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, output []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.DecCfg.SymbolLength:]
|
|
upper := d.csum[d.DecCfg.SymbolLength2:]
|
|
n := len(input) - d.DecCfg.SymbolLength2
|
|
for idx := 0; idx < n; idx++ {
|
|
output[idx] = (lower[idx] - d.csum[idx]) - (upper[idx] - lower[idx])
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
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.
|
|
//
|
|
// Transforms:
|
|
// <--Sym1--><--Sym2--><--Sym3--><--Sym4--><--Sym5--><--Sym6--><--Sym7--><--Sym8-->
|
|
// <12345678><12345678><12345678><12345678><12345678><12345678><12345678><12345678>
|
|
// to:
|
|
// <11111111><22222222><33333333><44444444><55555555><66666666><77777777><88888888>
|
|
func (d Decoder) Pack(input []byte, slices [][]byte) {
|
|
for symbolOffset, slice := range slices {
|
|
for symbolIdx := range slice {
|
|
slice[symbolIdx] = input[symbolIdx*d.DecCfg.SymbolLength2+symbolOffset]
|
|
}
|
|
}
|
|
|
|
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) {
|
|
preambleLength := len(preamble)
|
|
for symbolOffset, slice := range slices {
|
|
for symbolIdx := range slice[:len(slice)-preambleLength] {
|
|
if bytes.Equal(preamble, slice[symbolIdx:][:preambleLength]) {
|
|
indexes = append(indexes, symbolIdx*d.DecCfg.SymbolLength2+symbolOffset)
|
|
}
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// Given a list of indeces the preamble exists at, sample the appropriate bits
|
|
// of the signal's bit-decision. Pack bits of each index into an array of byte
|
|
// arrays and return.
|
|
func (d Decoder) Slice(indices []int) (pkts [][]byte) {
|
|
// 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 indices the preamble exists at.
|
|
for _, qIdx := range indices {
|
|
// Check that we're still within the first sample block. We'll catch
|
|
// the message on the next sample block otherwise.
|
|
if qIdx > d.DecCfg.BlockSize {
|
|
continue
|
|
}
|
|
|
|
// Packet is 1 bit per byte, pack to 8-bits per byte.
|
|
for pIdx := 0; pIdx < d.DecCfg.PacketSymbols; pIdx++ {
|
|
d.pkt[pIdx>>3] <<= 1
|
|
d.pkt[pIdx>>3] |= d.Quantized[qIdx+(pIdx*d.DecCfg.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
|
|
pkts = append(pkts, make([]byte, len(d.pkt)))
|
|
copy(pkts[len(pkts)-1], d.pkt)
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func NextPowerOf2(v int) int {
|
|
return 1 << uint(math.Ceil(math.Log2(float64(v))))
|
|
}
|