-
Notifications
You must be signed in to change notification settings - Fork 1
/
decoder.py
251 lines (209 loc) · 7.02 KB
/
decoder.py
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
#!/usr/bin/env python3
import struct
import re
mode_map = {
0:"AM",
1:"FM",
# Need to confirm
2: "MO",
3:"ED",
}
inv_mode_map = inv_map = {v: k for k, v in mode_map.items()}
def extract_bits(b, first, last):
"""
###################################################################
#
# Extract a bit field from an 8-bit byte.
#
# Inputs:
# byte -an 8 bit byte
# first -first bit number
# last -last bit number (inclusive)
#
# Returns:
# Integer value 0 - 255
#
# Notes:
# Bits are numbered from left to right, i.e.,
# 01234567 with bit 0 being most significant
#
# first <= last
###################################################################
"""
# last + 1 because it's an inclusive it range and the `range` function
# is exclusive
#
# 7-i because we're numbering the bits from mostsig to leastsig
mask = sum([pow(2,7-i) for i in range(first, last+1)])
# 7 - last because we're numbering from mostisg to leastsig and
# we need to shift the result down into the least sig bits.
return (b & mask) >> (7 - last)
def set_bits(b, first, last, v):
"""
###################################################################
#
# Extract a bit field from an 8-bit byte.
#
# Inputs:
# byte -an 8 bit byte
# first -first bit number
# last -last bit number (inclusive)
#
# Returns:
# Integer value 0 - 255
#
# Notes:
# Bits are numbered from left to right, i.e.,
# 01234567 with bit 0 being most significant
#
# first <= last
###################################################################
"""
# TODO TODO TODO This is wrong. It doesn't set 0 bits
#
# last + 1 because it's an inclusive it range and the `range` function
# is exclusive
#
# 7-i because we're numbering the bits from mostsig to leastsig
mask = sum([pow(2,7-i) for i in range(first, last+1)])
# 7 - last because we're numbering from mostisg to leastsig and
# we need to shift the result down into the least sig bits.
return b | ((v & (mask >> (7-last))) << (7 - last))
def make_has_label_bitfield(text_tags):
bf = bytearray([0x00] * 38)
for i, text_tag in enumerate(text_tags):
byi = i // 8
# lowest chan stored in the LSB
bti = 7 - (i % 8)
# can't set a 0 bit right now
if not text_tag.tag:
bf[byi] = set_bits(bf[byi], bti, bti, 1)
return bf
class channel:
def __init__(self, freq, flags):
self.freq = freq
self.flags = flags
def __repr__(self):
return f"{self.freq} {self.flags}".strip()
def encode(self):
fr = bytearray(self.freq.encode())
fl = bytearray(self.flags.encode())
return fr + fl
@classmethod
def decode(cls, bs):
freq = frequency.decode(bs[0:3])
flags = chan_flags.decode(bytearray([bs[3]]))
return cls(freq=freq, flags=flags)
class chan_flags:
def __init__(self, mode=None, atten=None, delay=None, lockout=None):
self.mode = mode
self.atten = atten
self.delay = delay
self.lockout = lockout
def __repr__(self):
r = ''
if self.mode is not None:
r += mode_map[self.mode] + " "
if self.atten:
r += "A"
if self.delay:
r += "D"
if self.lockout:
r += "L"
return r.strip()
def encode(self):
bs = bytearray([0])
bs[0] = set_bits(bs[0], 6, 7, self.mode)
bs[0] = set_bits(bs[0], 5, 5, 1 if self.atten else 0 )
bs[0] = set_bits(bs[0], 4, 4, 1 if self.delay else 0 )
bs[0] = set_bits(bs[0], 3, 3, 1 if self.lockout else 0)
return bs
@classmethod
def decode(cls, bs):
mode = extract_bits(bs[0], 6, 7)
atten = 1 == extract_bits(bs[0], 5, 5)
delay = 1 == extract_bits(bs[0], 4, 4)
lockout = 1 == extract_bits(bs[0], 3, 3)
return cls(mode=mode, atten=atten, delay=delay, lockout=lockout)
class frequency:
def __init__(self, freq, unused):
self.freq = freq
self.unused = unused
# TODO: validate that it's within the freq ranges an steps allowed
def __repr__(self):
if self.unused:
return "-"
formatted_freq =f"{self.freq/1000000.0:10.05f}"
r = f"{formatted_freq:>10}" + " "
return r.strip()
def encode(self):
f = int(self.freq / 250)
bs = bytearray(struct.pack("<L", f))
del bs[3]
return bs
@classmethod
def decode(cls, bs):
# 3 bytes of a 4 byte Little Endian Integer are the first 3 bytes
ef = bytearray(bs[0:3])
ef.append(0)
ef = struct.unpack("<L", ef)
# Frequencies are stored in 250Hz increments
freq = ef[0] * 250
# Bytes 090909 for the frequency represent an unused frequency.
# 148.034250 isn't a 5KHz, 6.5KHz, or 7.5KHz step and therefore
# isn't valid. (Steps obtained from the User Manual, pg 81.)
#
# 0xffff is used as the unused in the lockouts section. 4THz isn't
# within range of this scanner.
unused = freq in [148034250, 4194303750]
return cls(freq=freq, unused=unused)
class text_tag:
def __init__(self, tag):
self.tag = tag
if self.tag is None or self.tag == '' or self.tag == '~no tag~':
self.tag = None
def __repr__(self):
if self.tag is None:
return '~no tag~'
return self.tag
def encode(self):
if self.tag is None:
return bytes([0xff]) * 12
# TODO check if lower case or other symbols are allowed
disallowed_chars = re.compile("[^A-Z0-9 .\-#_@+*&/,]")
otag = self.tag.upper().strip()
tag = disallowed_chars.sub(' ', otag)
# pad with spaces, encode, and trim, this _must_ always be exactly 12
# bytes of ascii text, unless the first byte is 0ff.
tag = f"{tag:12}"[0:12]
if otag != tag.strip():
print(f"Rewriting tag '{otag}' to '{tag.strip()}'")
tag = tag.encode('ascii')
return tag
@classmethod
def decode(cls, tag):
ttag = None
if tag[0] != 0xff:
ttag = tag.decode('ascii')
return cls(tag=ttag)
class talk_group:
def __init__(self, tgid):
self.tgid = tgid
def __repr__(self):
return f"{self.tgid:05}"
def encode(self):
bs = bytearray(struct.pack("<H", self.tgid))
return bs
@classmethod
def decode(cls, bs):
tgid = struct.unpack("<H", bs)[0]
return cls(tgid=tgid)
def index_to_memory_slot(i, channels_per_bank, bank_radix):
return (bank_radix * (i//channels_per_bank)) + (i%channels_per_bank)
def index_to_tg_memory_slot(i, tg_mem_per_sub_bank, tg_sub_bank_per_bank):
tg_mem_per_bank = tg_mem_per_sub_bank * tg_sub_bank_per_bank
tg_slot_bank = i // tg_mem_per_bank
tg_sub_bank_mem_slot = i % tg_mem_per_bank
tg_slot_sub_bank = tg_sub_bank_mem_slot // tg_mem_per_sub_bank
tg_slot_mem = tg_sub_bank_mem_slot % tg_mem_per_sub_bank
return f"{tg_slot_bank}-{tg_slot_sub_bank}-{tg_slot_mem:02}"