Skip to content

Commit ae0364d

Browse files
committed
Merge branch 'decimation'
2 parents 07607af + 7f05214 commit ae0364d

File tree

8 files changed

+237
-200
lines changed

8 files changed

+237
-200
lines changed

README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ Available command-line flags are as follows:
2525
```
2626
Usage of rtlamr:
2727
-cpuprofile=: write cpu profile to this file
28+
-decimation=1: integer decimation factor, keep every nth sample
2829
-duration=0: time to run for, 0 for infinite, ex. 1h5m10s
2930
-fastmag=false: use faster alpha max + beta min magnitude approximation
3031
-filterid=: display only messages matching an id in a comma-separated list of ids.
@@ -36,7 +37,7 @@ Usage of rtlamr:
3637
-quiet=false: suppress printing state information at startup
3738
-samplefile=/dev/null: raw signal dump file
3839
-single=false: one shot execution
39-
-symbollength=73: symbol length in samples, see -help for valid lengths
40+
-symbollength=72: symbol length in samples, see -help for valid lengths
4041
4142
rtltcp specific:
4243
-agcmode=false: enable/disable rtl agc
@@ -67,7 +68,9 @@ $ rtlamr
6768
If you want to run the spectrum server on a different machine than the receiver you'll want to specify an address to listen on that is accessible from the machine `rtlamr` will run on with the `-a` option for `rtl_tcp` with an address accessible by the system running the receiver.
6869

6970
### Messages
70-
Currently both SCM (Standard Consumption Message) and IDM (Interval Data Message) packets can be decoded but are mutually exclusive, you cannot receive both simultaneously. See [Wikipedia: Encoder Receiver Transmitter](http://en.wikipedia.org/wiki/Encoder_receiver_transmitter) for more details on packet structure.
71+
Currently both SCM (Standard Consumption Message) and IDM (Interval Data Message) packets can be decoded but are mutually exclusive, you cannot receive both simultaneously. See [RTLAMR: Protocol](http://bemasher.github.io/rtlamr/protocol.html) for more details on packet structure.
72+
73+
There's now experimental support for meters with R900 transmitters!
7174

7275
### Sensitivity
7376
Using a NooElec NESDR Nano R820T with the provided antenna, I can reliably receive standard consumption messages from ~300 different meters and intermittently from another ~600 meters. These figures are calculated from the number of messages received during a 25 minute window. Reliably in this case means receiving at least 10 of the expected 12 messages and intermittently means 3-9 messages.
@@ -79,8 +82,6 @@ Check out the table of meters I've been compiling from various internet sources:
7982

8083
If you've got a meter not on the list that you've successfully received messages from, you can submit this info via a form available at the link above.
8184

82-
There's now experimental support for meters with R900 transmitters!
83-
8485
### Ethics
8586
_Do not use this for nefarious purposes._ If you do, I don't want to know about it, I am not and will not be responsible for your lack of common decency and/or foresight. However, if you find a clever non-evil use for this, by all means, share.
8687

decode/decode.go

Lines changed: 109 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -25,36 +25,79 @@ import (
2525

2626
// PacketConfig specifies packet-specific radio configuration.
2727
type PacketConfig struct {
28-
DataRate int
28+
DataRate int
29+
2930
BlockSize, BlockSize2 int
3031
SymbolLength, SymbolLength2 int
3132
SampleRate int
3233

3334
PreambleSymbols, PacketSymbols int
3435
PreambleLength, PacketLength int
35-
BufferLength int
3636
Preamble string
3737

38+
BufferLength int
39+
3840
CenterFreq uint32
3941
}
4042

41-
func (cfg PacketConfig) Log() {
42-
log.Println("BlockSize:", cfg.BlockSize)
43-
log.Println("CenterFreq:", cfg.CenterFreq)
44-
log.Println("SampleRate:", cfg.SampleRate)
45-
log.Println("DataRate:", cfg.DataRate)
46-
log.Println("SymbolLength:", cfg.SymbolLength)
47-
log.Println("PreambleSymbols:", cfg.PreambleSymbols)
48-
log.Println("PreambleLength:", cfg.PreambleLength)
49-
log.Println("PacketSymbols:", cfg.PacketSymbols)
50-
log.Println("PacketLength:", cfg.PacketLength)
51-
log.Println("Preamble:", cfg.Preamble)
43+
func (cfg PacketConfig) Decimate(decimation int) PacketConfig {
44+
cfg.BlockSize /= decimation
45+
cfg.BlockSize2 /= decimation
46+
cfg.SymbolLength /= decimation
47+
cfg.SymbolLength2 /= decimation
48+
cfg.SampleRate /= decimation
49+
cfg.DataRate /= decimation
50+
51+
cfg.PreambleLength /= decimation
52+
cfg.PacketLength /= decimation
53+
54+
cfg.BufferLength /= decimation
55+
56+
return cfg
57+
}
58+
59+
func (d Decoder) Log() {
60+
if d.Decimation != 1 {
61+
log.Printf("BlockSize: %d|%d\n", d.Cfg.BlockSize, d.DecCfg.BlockSize)
62+
log.Println("CenterFreq:", d.Cfg.CenterFreq)
63+
log.Printf("SampleRate: %d|%d\n", d.Cfg.SampleRate, d.DecCfg.SampleRate)
64+
log.Printf("DataRate: %d|%d\n", d.Cfg.DataRate, d.DecCfg.DataRate)
65+
log.Printf("SymbolLength: %d|%d\n", d.Cfg.SymbolLength, d.DecCfg.SymbolLength)
66+
log.Println("PreambleSymbols:", d.Cfg.PreambleSymbols)
67+
log.Printf("PreambleLength: %d|%d\n", d.Cfg.PreambleLength, d.DecCfg.PreambleLength)
68+
log.Println("PacketSymbols:", d.Cfg.PacketSymbols)
69+
log.Printf("PacketLength: %d|%d\n", d.Cfg.PacketLength, d.DecCfg.PacketLength)
70+
log.Println("Preamble:", d.Cfg.Preamble)
71+
72+
if d.Cfg.SymbolLength%d.Decimation != 0 {
73+
log.Println("Warning: decimated symbol length is non-integral, sensitivity may be poor")
74+
}
75+
76+
if d.DecCfg.SymbolLength < 3 {
77+
log.Fatal("Error: illegal decimation factor, choose a smaller factor")
78+
}
79+
80+
return
81+
}
82+
83+
log.Println("CenterFreq:", d.Cfg.CenterFreq)
84+
log.Println("SampleRate:", d.Cfg.SampleRate)
85+
log.Println("DataRate:", d.Cfg.DataRate)
86+
log.Println("SymbolLength:", d.Cfg.SymbolLength)
87+
log.Println("PreambleSymbols:", d.Cfg.PreambleSymbols)
88+
log.Println("PreambleLength:", d.Cfg.PreambleLength)
89+
log.Println("PacketSymbols:", d.Cfg.PacketSymbols)
90+
log.Println("PacketLength:", d.Cfg.PacketLength)
91+
log.Println("Preamble:", d.Cfg.Preamble)
5292
}
5393

5494
// Decoder contains buffers and radio configuration.
5595
type Decoder struct {
5696
Cfg PacketConfig
5797

98+
Decimation int
99+
DecCfg PacketConfig
100+
58101
IQ []byte
59102
Signal []float64
60103
Filtered []float64
@@ -70,16 +113,30 @@ type Decoder struct {
70113
}
71114

72115
// Create a new decoder with the given packet configuration.
73-
func NewDecoder(cfg PacketConfig, fastMag bool) (d Decoder) {
116+
func NewDecoder(cfg PacketConfig, decimation int, fastMag bool) (d Decoder) {
74117
d.Cfg = cfg
75118

119+
d.Cfg.SymbolLength2 = d.Cfg.SymbolLength << 1
120+
d.Cfg.SampleRate = d.Cfg.DataRate * d.Cfg.SymbolLength
121+
122+
d.Cfg.PreambleLength = d.Cfg.PreambleSymbols * d.Cfg.SymbolLength2
123+
d.Cfg.PacketLength = d.Cfg.PacketSymbols * d.Cfg.SymbolLength2
124+
125+
d.Cfg.BlockSize = NextPowerOf2(d.Cfg.PreambleLength)
126+
d.Cfg.BlockSize2 = d.Cfg.BlockSize << 1
127+
128+
d.Cfg.BufferLength = d.Cfg.PacketLength + d.Cfg.BlockSize
129+
130+
d.Decimation = decimation
131+
d.DecCfg = d.Cfg.Decimate(d.Decimation)
132+
76133
// Allocate necessary buffers.
77134
d.IQ = make([]byte, d.Cfg.BufferLength<<1)
78-
d.Signal = make([]float64, d.Cfg.BufferLength)
79-
d.Filtered = make([]float64, d.Cfg.BufferLength)
80-
d.Quantized = make([]byte, d.Cfg.BufferLength)
135+
d.Signal = make([]float64, d.DecCfg.BufferLength)
136+
d.Filtered = make([]float64, d.DecCfg.BufferLength)
137+
d.Quantized = make([]byte, d.DecCfg.BufferLength)
81138

82-
d.csum = make([]float64, d.Cfg.BlockSize+d.Cfg.SymbolLength2+1)
139+
d.csum = make([]float64, (d.DecCfg.PacketLength - d.DecCfg.SymbolLength2 + 1))
83140

84141
// Calculate magnitude lookup table specified by -fastmag flag.
85142
if fastMag {
@@ -99,10 +156,10 @@ func NewDecoder(cfg PacketConfig, fastMag bool) (d Decoder) {
99156
// Slice quantized sample buffer to make searching for the preamble more
100157
// memory local. Pre-allocate a flat buffer so memory is contiguous and
101158
// assign slices to the buffer.
102-
d.slices = make([][]byte, d.Cfg.SymbolLength2)
103-
flat := make([]byte, d.Cfg.BlockSize2-(d.Cfg.BlockSize2%d.Cfg.SymbolLength2))
159+
d.slices = make([][]byte, d.DecCfg.SymbolLength2)
160+
flat := make([]byte, d.DecCfg.BlockSize2-(d.DecCfg.BlockSize2%d.DecCfg.SymbolLength2))
104161

105-
symbolsPerBlock := d.Cfg.BlockSize2 / d.Cfg.SymbolLength2
162+
symbolsPerBlock := d.DecCfg.BlockSize2 / d.DecCfg.SymbolLength2
106163
for symbolOffset := range d.slices {
107164
lower := symbolOffset * symbolsPerBlock
108165
upper := (symbolOffset + 1) * symbolsPerBlock
@@ -111,7 +168,7 @@ func NewDecoder(cfg PacketConfig, fastMag bool) (d Decoder) {
111168

112169
// Signal up to the final stage is 1-bit per byte. Allocate a buffer to
113170
// store packed version 8-bits per byte.
114-
d.pkt = make([]byte, (d.Cfg.PacketSymbols+7)>>3)
171+
d.pkt = make([]byte, (d.DecCfg.PacketSymbols+7)>>3)
115172

116173
return
117174
}
@@ -120,28 +177,28 @@ func NewDecoder(cfg PacketConfig, fastMag bool) (d Decoder) {
120177
func (d Decoder) Decode(input []byte) []int {
121178
// Shift buffers to append new block.
122179
copy(d.IQ, d.IQ[d.Cfg.BlockSize<<1:])
123-
copy(d.Signal, d.Signal[d.Cfg.BlockSize:])
124-
copy(d.Filtered, d.Filtered[d.Cfg.BlockSize:])
125-
copy(d.Quantized, d.Quantized[d.Cfg.BlockSize:])
180+
copy(d.Signal, d.Signal[d.DecCfg.BlockSize:])
181+
copy(d.Filtered, d.Filtered[d.DecCfg.BlockSize:])
182+
copy(d.Quantized, d.Quantized[d.DecCfg.BlockSize:])
126183
copy(d.IQ[d.Cfg.PacketLength<<1:], input[:])
127184

128185
iqBlock := d.IQ[d.Cfg.PacketLength<<1:]
129-
signalBlock := d.Signal[d.Cfg.PacketLength:]
186+
signalBlock := d.Signal[d.DecCfg.PacketLength:]
130187

131188
// Compute the magnitude of the new block.
132189
d.demod.Execute(iqBlock, signalBlock)
133190

134-
signalBlock = d.Signal[d.Cfg.PacketLength-d.Cfg.SymbolLength2:]
135-
filterBlock := d.Filtered[d.Cfg.PacketLength-d.Cfg.SymbolLength2:]
191+
signalBlock = d.Signal[d.DecCfg.PacketLength-d.DecCfg.SymbolLength2:]
192+
filterBlock := d.Filtered[d.DecCfg.PacketLength-d.DecCfg.SymbolLength2:]
136193

137194
// Perform matched filter on new block.
138195
d.Filter(signalBlock, filterBlock)
139196

140197
// Perform bit-decision on new block.
141-
Quantize(filterBlock, d.Quantized[d.Cfg.PacketLength-d.Cfg.SymbolLength2:])
198+
Quantize(filterBlock, d.Quantized[d.DecCfg.PacketLength-d.DecCfg.SymbolLength2:])
142199

143200
// Pack the quantized signal into slices for searching.
144-
d.Pack(d.Quantized[:d.Cfg.BlockSize2], d.slices)
201+
d.Pack(d.Quantized[:d.DecCfg.BlockSize2], d.slices)
145202

146203
// Return a list of indexes the preamble exists at.
147204
return d.Search(d.slices, d.preamble)
@@ -168,8 +225,12 @@ func NewSqrtMagLUT() (lut MagLUT) {
168225

169226
// Calculates complex magnitude on given IQ stream writing result to output.
170227
func (lut MagLUT) Execute(input []byte, output []float64) {
171-
for idx := 0; idx < len(input); idx += 2 {
172-
output[idx>>1] = math.Sqrt(lut[input[idx]] + lut[input[idx+1]])
228+
decIdx := 0
229+
dec := (len(input) / len(output))
230+
231+
for idx := 0; decIdx < len(output); idx += dec {
232+
output[decIdx] = math.Sqrt(lut[input[idx]] + lut[input[idx+1]])
233+
decIdx++
173234
}
174235
}
175236

@@ -192,14 +253,18 @@ func (lut AlphaMaxBetaMinLUT) Execute(input []byte, output []float64) {
192253
ß = 0.392699081699
193254
)
194255

195-
for idx := 0; idx < len(input); idx += 2 {
256+
decIdx := 0
257+
dec := (len(input) / len(output))
258+
259+
for idx := 0; decIdx < len(output); idx += dec {
196260
i := lut[input[idx]]
197261
q := lut[input[idx+1]]
198262
if i > q {
199-
output[idx>>1] = α*i + ß*q
263+
output[decIdx] = α*i + ß*q
200264
} else {
201-
output[idx>>1] = α*q + ß*i
265+
output[decIdx] = α*q + ß*i
202266
}
267+
decIdx++
203268
}
204269
}
205270

@@ -215,9 +280,10 @@ func (d Decoder) Filter(input, output []float64) {
215280
}
216281

217282
// Filter result is difference of summation of lower and upper symbols.
218-
lower := d.csum[d.Cfg.SymbolLength:]
219-
upper := d.csum[d.Cfg.SymbolLength2:]
220-
for idx := range input[:len(input)-d.Cfg.SymbolLength2] {
283+
lower := d.csum[d.DecCfg.SymbolLength:]
284+
upper := d.csum[d.DecCfg.SymbolLength2:]
285+
n := len(input) - d.DecCfg.SymbolLength2
286+
for idx := 0; idx < n; idx++ {
221287
output[idx] = (lower[idx] - d.csum[idx]) - (upper[idx] - lower[idx])
222288
}
223289

@@ -239,7 +305,7 @@ func Quantize(input []float64, output []byte) {
239305
func (d Decoder) Pack(input []byte, slices [][]byte) {
240306
for symbolOffset, slice := range slices {
241307
for symbolIdx := range slice {
242-
slice[symbolIdx] = input[symbolIdx*d.Cfg.SymbolLength2+symbolOffset]
308+
slice[symbolIdx] = input[symbolIdx*d.DecCfg.SymbolLength2+symbolOffset]
243309
}
244310
}
245311

@@ -254,7 +320,7 @@ func (d Decoder) Search(slices [][]byte, preamble []byte) (indexes []int) {
254320
for symbolOffset, slice := range slices {
255321
for symbolIdx := range slice[:len(slice)-preambleLength] {
256322
if bytes.Equal(preamble, slice[symbolIdx:][:preambleLength]) {
257-
indexes = append(indexes, symbolIdx*d.Cfg.SymbolLength2+symbolOffset)
323+
indexes = append(indexes, symbolIdx*d.DecCfg.SymbolLength2+symbolOffset)
258324
}
259325
}
260326
}
@@ -274,14 +340,14 @@ func (d Decoder) Slice(indices []int) (pkts [][]byte) {
274340
for _, qIdx := range indices {
275341
// Check that we're still within the first sample block. We'll catch
276342
// the message on the next sample block otherwise.
277-
if qIdx > d.Cfg.BlockSize {
343+
if qIdx > d.DecCfg.BlockSize {
278344
continue
279345
}
280346

281347
// Packet is 1 bit per byte, pack to 8-bits per byte.
282-
for pIdx := 0; pIdx < d.Cfg.PacketSymbols; pIdx++ {
348+
for pIdx := 0; pIdx < d.DecCfg.PacketSymbols; pIdx++ {
283349
d.pkt[pIdx>>3] <<= 1
284-
d.pkt[pIdx>>3] |= d.Quantized[qIdx+(pIdx*d.Cfg.SymbolLength2)]
350+
d.pkt[pIdx>>3] |= d.Quantized[qIdx+(pIdx*d.DecCfg.SymbolLength2)]
285351
}
286352

287353
// Store the packet in the seen map and append to the packet list.

flags.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@ var sampleFile *os.File
3939
var msgType = flag.String("msgtype", "scm", "message type to receive: scm, idm or r900")
4040
var fastMag = flag.Bool("fastmag", false, "use faster alpha max + beta min magnitude approximation")
4141

42-
var symbolLength = flag.Int("symbollength", 73, "symbol length in samples, see -help for valid lengths")
42+
var symbolLength = flag.Int("symbollength", 72, "symbol length in samples, see -help for valid lengths")
43+
44+
var decimation = flag.Int("decimation", 1, "integer decimation factor, keep every nth sample")
4345

4446
var timeLimit = flag.Duration("duration", 0, "time to run for, 0 for infinite, ex. 1h5m10s")
4547
var meterID UintMap
@@ -64,6 +66,7 @@ func RegisterFlags() {
6466
"samplefile": true,
6567
"msgtype": true,
6668
"symbollength": true,
69+
"decimation": true,
6770
"duration": true,
6871
"filterid": true,
6972
"filtertype": true,

idm/idm.go

Lines changed: 4 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -28,27 +28,13 @@ import (
2828
)
2929

3030
func NewPacketConfig(symbolLength int) (cfg decode.PacketConfig) {
31-
cfg.DataRate = 32768
32-
3331
cfg.CenterFreq = 912600155
34-
32+
cfg.DataRate = 32768
3533
cfg.SymbolLength = symbolLength
36-
cfg.SymbolLength2 = cfg.SymbolLength << 1
37-
38-
cfg.SampleRate = cfg.DataRate * cfg.SymbolLength
39-
4034
cfg.PreambleSymbols = 32
4135
cfg.PacketSymbols = 92 * 8
42-
43-
cfg.PreambleLength = cfg.PreambleSymbols * cfg.SymbolLength2
44-
cfg.PacketLength = cfg.PacketSymbols * cfg.SymbolLength2
45-
46-
cfg.BlockSize = decode.NextPowerOf2(cfg.PreambleLength)
47-
cfg.BlockSize2 = cfg.BlockSize << 1
48-
49-
cfg.BufferLength = cfg.PacketLength + cfg.BlockSize
50-
5136
cfg.Preamble = "01010101010101010001011010100011"
37+
5238
return
5339
}
5440

@@ -65,8 +51,8 @@ func (p Parser) Cfg() decode.PacketConfig {
6551
return p.Decoder.Cfg
6652
}
6753

68-
func NewParser(symbolLength int, fastMag bool) (p Parser) {
69-
p.Decoder = decode.NewDecoder(NewPacketConfig(symbolLength), fastMag)
54+
func NewParser(symbolLength, decimation int, fastMag bool) (p Parser) {
55+
p.Decoder = decode.NewDecoder(NewPacketConfig(symbolLength), decimation, fastMag)
7056
p.CRC = crc.NewCRC("CCITT", 0xFFFF, 0x1021, 0x1D0F)
7157
return
7258
}
@@ -158,10 +144,6 @@ func NewIDM(data parse.Data) (idm IDM) {
158144

159145
type Interval [47]uint16
160146

161-
// func (interval Interval) MarshalText() (text []byte, err error) {
162-
// return []byte(fmt.Sprintf("%+v", interval)), nil
163-
// }
164-
165147
func (interval Interval) Record() (r []string) {
166148
for _, val := range interval {
167149
r = append(r, strconv.FormatUint(uint64(val), 10))

0 commit comments

Comments
 (0)