forked from bemasher/rtlamr
/
config.go
356 lines (290 loc) · 10.8 KB
/
config.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
package main
import (
"flag"
"fmt"
"log"
"math"
"net"
"os"
"strconv"
"strings"
"time"
"github.com/bemasher/rtlamr/csv"
"encoding/gob"
"encoding/json"
"encoding/xml"
)
type Config struct {
logFilename string
sampleFilename string
format string
ServerAddr *net.TCPAddr
TimeLimit time.Duration
MeterID uint
MeterType uint
SymbolLength int
BlockSize uint
SampleRate uint
PreambleLength uint
PacketLength uint
Log *log.Logger
LogFile *os.File
GobUnsafe bool
Encoder Encoder
SampleFile *os.File
Quiet bool
Single bool
ShortHelp bool
LongHelp bool
}
func (c *Config) Parse() (err error) {
longHelp := map[string]string{
// short help
"h": `Print short help.`,
// long help
"help": `Print long help.`,
// duration
"duration": `Sets time to receive for, 0 for infinite. Defaults to infinite.
If the time limit expires during processing of a block (which is quite
likely) it will exit on the next pass through the receive loop. Exiting
after an expired duration will print the total runtime to the log file.`,
// filterid
"filterid": `Sets a meter id to filter by, 0 for no filtering. Defaults to no filtering.
Any received messages not matching the given id will be silently ignored.`,
// filtertype
"filtertype": `Sets an ert type to filter by, 0 for no filtering. Defaults to no filtering.
Any received messages not matching the given type will be silently ignored.`,
// format
"format": `Sets the log output format. Defaults to plain.
Plain text is formatted using the following format string:
{Time:%s Offset:%d Length:%d SCM:{ID:%8d Type:%2d Tamper:%+v Consumption:%8d Checksum:0x%04X}}
No fields are omitted for csv, json, xml or gob output. Plain text conditionally
omits offset and length fields if not dumping samples to file via -samplefile.
For json and xml output each line is an element, there is no root node.`,
"gobunsafe": `Must be true to allow writing gob encoded output to stdout. Defaults to false.
Doing so would normally break a terminal, so we disable it unless
explicitly enabled.`,
// logfile
"logfile": `Sets file to dump log messages to. Defaults to os.DevNull and prints to stderr.
Log messages have the following structure:
type Message struct {
Time time.Time
Offset int64
Length int
SCM SCM
}
type SCM struct {
ID uint32
Type uint8
Tamper Tamper
Consumption uint32
Checksum uint16
}
type Tamper struct {
Phy uint8
Enc uint8
}
Messages are encoded one per line for all encoding formats except gob.`,
// quiet
"quiet": `Omits state information logged on startup. Defaults to false.
Below is sample output:
2014/07/01 02:45:42.416406 Server: 127.0.0.1:1234
2014/07/01 02:45:42.417406 BlockSize: 4096
2014/07/01 02:45:42.417406 SampleRate: 2392064
2014/07/01 02:45:42.417406 DataRate: 32768
2014/07/01 02:45:42.417406 SymbolLength: 73
2014/07/01 02:45:42.417406 PreambleSymbols: 42
2014/07/01 02:45:42.417406 PreambleLength: 3066
2014/07/01 02:45:42.417406 PacketSymbols: 192
2014/07/01 02:45:42.417406 PacketLength: 14016
2014/07/01 02:45:42.417406 CenterFreq: 920299072
2014/07/01 02:45:42.417406 TimeLimit: 0
2014/07/01 02:45:42.417406 Format: plain
2014/07/01 02:45:42.417406 LogFile: /dev/stdout
2014/07/01 02:45:42.417406 SampleFile: NUL
2014/07/01 02:45:43.050442 BCH: {GenPoly:16F63 PolyLen:16}
2014/07/01 02:45:43.050442 GainCount: 29
2014/07/01 02:45:43.051442 Running...`,
// samplefile
"samplefile": `Sets file to dump samples for decoded packets to. Defaults to os.DevNull.
Output file format are interleaved in-phase and quadrature samples. Each
are unsigned bytes. These are unmodified output from the dongle. This flag
enables offset and length fields in plain text log messages. Only samples
for correctly received messages are dumped.`,
// single
"single": `Provides one shot execution. Defaults to false.
Receiver listens until exactly one message is received before exiting.`,
// symbollength
"symbollength": `Sets the desired symbol length. Defaults to 73.
Sample rate is determined from this value as follows:
DataRate = 32768
SampleRate = SymbolLength * DataRate
The symbol length also determines the size of the convolution used for the preamble search:
PreambleSymbols = 42
BlockSize = 1 << uint(math.Ceil(math.Log2(float64(PreambleSymbols * SymbolLength))))
Valid symbol lengths are given below (symbol length: bandwidth):
BlockSize: 512 (fast)
7: 229.376 kHz, 8: 262.144 kHz, 9: 294.912 kHz
BlockSize: 2048 (medium)
28: 917.504 kHz, 29: 950.272 kHz, 30: 983.040 kHz
31: 1.015808 MHz, 32: 1.048576 MHz, 33: 1.081344 MHz,
34: 1.114112 MHz, 35: 1.146880 MHz, 36: 1.179648 MHz,
37: 1.212416 MHz, 38: 1.245184 MHz, 39: 1.277952 MHz,
40: 1.310720 MHz, 41: 1.343488 MHz, 42: 1.376256 MHz,
43: 1.409024 MHz, 44: 1.441792 MHz, 45: 1.474560 MHz,
46: 1.507328 MHz, 47: 1.540096 MHz, 48: 1.572864 MHz
BlockSize: 4096 (slow)
49: 1.605632 MHz, 50: 1.638400 MHz, 51: 1.671168 MHz,
52: 1.703936 MHz, 53: 1.736704 MHz, 54: 1.769472 MHz,
55: 1.802240 MHz, 56: 1.835008 MHz, 57: 1.867776 MHz,
58: 1.900544 MHz, 59: 1.933312 MHz, 60: 1.966080 MHz,
61: 1.998848 MHz, 62: 2.031616 MHz, 63: 2.064384 MHz,
64: 2.097152 MHz, 65: 2.129920 MHz, 66: 2.162688 MHz,
67: 2.195456 MHz, 68: 2.228224 MHz, 69: 2.260992 MHz,
70: 2.293760 MHz, 71: 2.326528 MHz, 72: 2.359296 MHz,
73: 2.392064 MHz
BlockSize: 4096 (slow, untested)
74: 2.424832 MHz, 75: 2.457600 MHz, 76: 2.490368 MHz,
77: 2.523136 MHz, 78: 2.555904 MHz, 79: 2.588672 MHz,
80: 2.621440 MHz, 81: 2.654208 MHz, 82: 2.686976 MHz,
83: 2.719744 MHz, 84: 2.752512 MHz, 85: 2.785280 MHz,
86: 2.818048 MHz, 87: 2.850816 MHz, 88: 2.883584 MHz,
89: 2.916352 MHz, 90: 2.949120 MHz, 91: 2.981888 MHz,
92: 3.014656 MHz, 93: 3.047424 MHz, 94: 3.080192 MHz,
95: 3.112960 MHz, 96: 3.145728 MHz, 97: 3.178496 MHz`,
}
flag.StringVar(&c.logFilename, "logfile", "/dev/stdout", "log statement dump file")
flag.StringVar(&c.sampleFilename, "samplefile", os.DevNull, "raw signal dump file")
flag.IntVar(&c.SymbolLength, "symbollength", 73, `symbol length in samples, see -help for valid lengths`)
flag.DurationVar(&c.TimeLimit, "duration", 0, "time to run for, 0 for infinite")
flag.UintVar(&c.MeterID, "filterid", 0, "display only messages matching given id")
flag.UintVar(&c.MeterType, "filtertype", 0, "display only messages matching given type")
flag.StringVar(&c.format, "format", "plain", "format to write log messages in: plain, csv, json, xml or gob")
flag.BoolVar(&c.GobUnsafe, "gobunsafe", false, "allow gob output to stdout")
flag.BoolVar(&c.Quiet, "quiet", false, "suppress printing state information at startup")
flag.BoolVar(&c.Single, "single", false, "one shot execution")
flag.BoolVar(&c.ShortHelp, "h", false, "print short help")
flag.BoolVar(&c.LongHelp, "help", false, "print long help")
// Override default center frequency.
centerFreqFlag := flag.CommandLine.Lookup("centerfreq")
centerFreqString := strconv.FormatUint(CenterFreq, 10)
centerFreqFlag.DefValue = centerFreqString
centerFreqFlag.Value.Set(centerFreqString)
flag.Parse()
rtlamrFlags := map[string]bool{
"logfile": true,
"samplefile": true,
"symbollength": true,
"duration": true,
"filterid": true,
"filtertype": true,
"format": true,
"gobunsafe": true,
"quiet": true,
"single": true,
"h": true,
"help": true,
}
printDefaults := func(validFlags map[string]bool, inclusion bool) {
flag.CommandLine.VisitAll(func(f *flag.Flag) {
if validFlags[f.Name] != inclusion {
return
}
format := " -%s=%s: %s\n"
fmt.Fprintf(os.Stderr, format, f.Name, f.DefValue, f.Usage)
})
}
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
printDefaults(rtlamrFlags, true)
fmt.Fprintln(os.Stderr)
fmt.Fprintln(os.Stderr, "rtltcp specific:")
printDefaults(rtlamrFlags, false)
}
if c.ShortHelp {
flag.Usage()
os.Exit(2)
}
if c.LongHelp {
flag.VisitAll(func(f *flag.Flag) {
if help, exists := longHelp[f.Name]; exists {
f.Usage = help + "\n"
}
})
flag.Usage()
os.Exit(2)
}
// Open or create the log file.
if c.logFilename == "/dev/stdout" {
c.LogFile = os.Stdout
} else {
c.LogFile, err = os.Create(c.logFilename)
}
// Create a new logger with the log file as output.
c.Log = log.New(c.LogFile, "", log.Ldate|log.Lmicroseconds)
if err != nil {
return
}
// Create the sample file.
c.SampleFile, err = os.Create(c.sampleFilename)
if err != nil {
return
}
validSymbolLengths := map[int]bool{
7: true, 8: true, 9: true, 28: true, 29: true, 30: true, 31: true,
32: true, 33: true, 34: true, 35: true, 36: true, 37: true, 38: true,
39: true, 40: true, 41: true, 42: true, 43: true, 44: true, 45: true,
46: true, 47: true, 48: true, 49: true, 50: true, 51: true, 52: true,
53: true, 54: true, 55: true, 56: true, 57: true, 58: true, 59: true,
60: true, 61: true, 62: true, 63: true, 64: true, 65: true, 66: true,
67: true, 68: true, 69: true, 70: true, 71: true, 72: true, 73: true,
74: true, 75: true, 76: true, 77: true, 78: true, 79: true, 80: true,
81: true, 82: true, 83: true, 84: true, 85: true, 86: true, 87: true,
88: true, 89: true, 90: true, 91: true, 92: true, 93: true, 94: true,
95: true, 96: true, 97: true,
}
if !validSymbolLengths[c.SymbolLength] {
log.Printf("warning: invalid symbol length, probably won't receive anything")
}
c.SampleRate = DataRate * uint(c.SymbolLength)
c.PreambleLength = PreambleSymbols * uint(c.SymbolLength)
c.PacketLength = PacketSymbols * uint(c.SymbolLength)
// Power of two sized DFT requires BlockSize to also be power of two.
// BlockSize must be greater than the preamble length, so calculate next
// largest power of two from preamble length.
c.BlockSize = NextPowerOf2(c.PreambleLength)
// Create encoder for specified logging format.
switch strings.ToLower(c.format) {
case "plain":
break
case "csv":
c.Encoder = csv.NewEncoder(c.LogFile)
case "json":
c.Encoder = json.NewEncoder(c.LogFile)
case "xml":
c.Encoder = xml.NewEncoder(c.LogFile)
case "gob":
c.Encoder = gob.NewEncoder(c.LogFile)
// Don't let the user output gob to stdout unless they really want to.
if !c.GobUnsafe && c.logFilename == "/dev/stdout" {
fmt.Println("Gob encoded messages are not stdout safe, specify logfile or use gobunsafe flag.")
os.Exit(1)
}
default:
// We didn't get a valid encoder, exit and say so.
log.Fatal("Invalid log format:", c.format)
}
return
}
func (c Config) Close() {
c.LogFile.Close()
c.SampleFile.Close()
}
// JSON, XML and GOB all implement this interface so we can simplify log
// output formatting.
type Encoder interface {
Encode(interface{}) error
}
func NextPowerOf2(v uint) uint {
return 1 << uint(math.Ceil(math.Log2(float64(v))))
}