1
|
# Copyright 2015 Eric Dropps <kc1ckh@kc1ckh.com>
|
2
|
#
|
3
|
# This program is free software: you can redistribute it and/or modify
|
4
|
# it under the terms of the GNU General Public License as published by
|
5
|
# the Free Software Foundation, either version 3 of the License, or
|
6
|
# (at your option) any later version.
|
7
|
#
|
8
|
# This program is distributed in the hope that it will be useful,
|
9
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
10
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
11
|
# GNU General Public License for more details.
|
12
|
#
|
13
|
# You should have received a copy of the GNU General Public License
|
14
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
15
|
import logging
|
16
|
|
17
|
from chirp.drivers import icf, id31
|
18
|
from chirp import directory, bitwise, memmap
|
19
|
|
20
|
LOG = logging.getLogger(__name__)
|
21
|
|
22
|
MEM_FORMAT = """
|
23
|
struct {
|
24
|
u24 freq;
|
25
|
u16 offset;
|
26
|
u16 rtone:6,
|
27
|
ctone:6,
|
28
|
unknown2:1,
|
29
|
mode:3;
|
30
|
u8 dtcs;
|
31
|
u8 tune_step:4,
|
32
|
unknown5:4;
|
33
|
u8 unknown4;
|
34
|
u8 tmode:4,
|
35
|
duplex:2,
|
36
|
dtcs_polarity:2;
|
37
|
char name[16];
|
38
|
u8 unknown13;
|
39
|
u8 urcall[7];
|
40
|
u8 rpt1call[7];
|
41
|
u8 rpt2call[7];
|
42
|
} memory[500];
|
43
|
|
44
|
#seekto 0x6A40;
|
45
|
u8 used_flags[70];
|
46
|
|
47
|
#seekto 0x6A86;
|
48
|
u8 skip_flags[69];
|
49
|
|
50
|
#seekto 0x6ACB;
|
51
|
u8 pskp_flags[69];
|
52
|
|
53
|
#seekto 0x6B40;
|
54
|
struct {
|
55
|
u8 unknown:3,
|
56
|
bank:5;
|
57
|
u8 index;
|
58
|
} banks[500];
|
59
|
|
60
|
#seekto 0x6FD0;
|
61
|
struct {
|
62
|
char name[16];
|
63
|
} bank_names[26];
|
64
|
|
65
|
|
66
|
#seekto 0xA8C0;
|
67
|
struct {
|
68
|
u24 freq;
|
69
|
u16 offset;
|
70
|
u8 unknown1[4];
|
71
|
u8 call[7];
|
72
|
char name[16];
|
73
|
char subname[8];
|
74
|
u8 unknown3[10];
|
75
|
} repeaters[750];
|
76
|
|
77
|
#seekto 0x1384E;
|
78
|
struct {
|
79
|
u8 call[7];
|
80
|
} rptcall[750];
|
81
|
|
82
|
#seekto 0x14FBE;
|
83
|
struct {
|
84
|
char name[16];
|
85
|
} rptgroup_names[30];
|
86
|
|
87
|
#seekto 0x1519E;
|
88
|
struct {
|
89
|
char call[8];
|
90
|
char tag[4];
|
91
|
} mycall[6];
|
92
|
|
93
|
#seekto 0x151E6;
|
94
|
struct {
|
95
|
char call[8];
|
96
|
} urcall[200];
|
97
|
|
98
|
#seekto 0x15826;
|
99
|
struct {
|
100
|
char name[16];
|
101
|
} urcallname[200];
|
102
|
"""
|
103
|
|
104
|
@directory.register
|
105
|
class ID51PLUSRadio(id31.ID31Radio):
|
106
|
"""Icom ID-51 Plus/50th Anniversary"""
|
107
|
MODEL = "ID-51 Plus"
|
108
|
|
109
|
_memsize = 0x1FB40
|
110
|
_model = "\x33\x90\x00\x02"
|
111
|
_endframe = "Icom Inc\x2E\x44\x41"
|
112
|
_bank_class = id31.ID31Bank
|
113
|
_ranges = [(0x00000, 0x1FB40, 32)]
|
114
|
|
115
|
MODES = {0: "FM", 1: "NFM", 3: "AM", 5: "DV"}
|
116
|
|
117
|
@classmethod
|
118
|
def match_model(cls, filedata, filename):
|
119
|
"""Given contents of a stored file (@filedata), return True if
|
120
|
this radio driver handles the represented model"""
|
121
|
|
122
|
# The default check for ICOM is just to check memory size
|
123
|
# Since the ID-51 and ID-51 Plus/Anniversary have exactly
|
124
|
# the same memory size, we need to do a more detailed check.
|
125
|
if len(filedata) == cls._memsize:
|
126
|
LOG.debug('File has correct memory size, '
|
127
|
'checking 20 bytes at offset 0x1AF40')
|
128
|
snip = filedata[0x1AF40:0x1AF60]
|
129
|
if snip != ('\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
130
|
'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
131
|
'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
132
|
'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'):
|
133
|
LOG.debug('bytes matched ID-51 Plus Signature')
|
134
|
return True
|
135
|
else:
|
136
|
LOG.debug('bytes did not match ID-51 Plus Signature')
|
137
|
return False
|
138
|
|
139
|
def _get_bank(self, loc):
|
140
|
_bank = self._memobj.banks[loc]
|
141
|
LOG.debug("Bank Value for location %s is %s" % (loc, _bank.bank))
|
142
|
if _bank.bank == 0x1F:
|
143
|
return None
|
144
|
else:
|
145
|
return _bank.bank
|
146
|
|
147
|
def _set_bank(self, loc, bank):
|
148
|
_bank = self._memobj.banks[loc]
|
149
|
if bank is None:
|
150
|
_bank.bank = 0x1F
|
151
|
else:
|
152
|
_bank.bank = bank
|
153
|
|
154
|
def get_features(self):
|
155
|
rf = super(ID51PLUSRadio, self).get_features()
|
156
|
rf.valid_bands = [(108000000, 174000000), (380000000, 479000000)]
|
157
|
return rf
|
158
|
|
159
|
def get_repeater_call_list(self):
|
160
|
calls = []
|
161
|
# Unlike previous DStar radios, there is not a separate repeater
|
162
|
# callsign list. It's only the DV Memory banks.
|
163
|
for repeater in self._memobj.repeaters:
|
164
|
call = id31._decode_call(repeater.call)
|
165
|
if call == "CALLSIGN":
|
166
|
call = ""
|
167
|
calls.append(call.rstrip())
|
168
|
return calls
|
169
|
|
170
|
def process_mmap(self):
|
171
|
self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
|
172
|
|
173
|
|
174
|
@directory.register
|
175
|
class ID51PLUS2Radio(ID51PLUSRadio):
|
176
|
MODEL = 'ID-51 Plus2'
|
177
|
_model = b'\x33\x90\x00\x03'
|
178
|
|
179
|
_icf_data = {
|
180
|
'MapRev': 1,
|
181
|
'EtcData': 400001,
|
182
|
}
|
183
|
|
184
|
@classmethod
|
185
|
def match_model(cls, filedata, filename):
|
186
|
# This model is always matched with metadata
|
187
|
return False
|
188
|
|
189
|
# This is actually a raw mode radio, but the id31 in its class
|
190
|
# hierarchy is not. So hack in the raw mode things for testing.
|
191
|
|
192
|
def process_frame_payload(self, payload):
|
193
|
"""Payloads from a raw-clone-mode radio are already in raw format."""
|
194
|
return icf.unescape_raw_bytes(payload)
|
195
|
|
196
|
def get_payload(self, data, raw, checksum):
|
197
|
"""Returns the data with optional checksum in raw format."""
|
198
|
if checksum:
|
199
|
cs = chr(icf.compute_checksum(data))
|
200
|
else:
|
201
|
cs = ""
|
202
|
payload = "%s%s" % (data, cs)
|
203
|
# Escape control characters.
|
204
|
escaped_payload = [icf.escape_raw_byte(b) for b in payload]
|
205
|
return "".join(escaped_payload)
|
206
|
|
207
|
def sync_in(self):
|
208
|
# The radio returns all the bytes with the high-order bit flipped.
|
209
|
_mmap = icf.clone_from_radio(self)
|
210
|
_mmap = icf.flip_high_order_bit(_mmap.get_packed())
|
211
|
self._mmap = memmap.MemoryMap(_mmap)
|
212
|
self.process_mmap()
|
213
|
|
214
|
def get_mmap(self):
|
215
|
_data = icf.flip_high_order_bit(self._mmap.get_packed())
|
216
|
return memmap.MemoryMap(_data)
|