Project

General

Profile

New Model #1647 » id5100.py

9a12033f - Dan Smith, 11/20/2022 08:31 PM

 
1
# Copyright 2022 Dan Smith <chirp@f.danplanet.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

    
16
import logging
17
from textwrap import dedent
18

    
19
from chirp import chirp_common
20
from chirp import directory
21
from chirp.drivers import icf
22
from chirp import bitwise
23

    
24
LOG = logging.getLogger(__name__)
25

    
26

    
27
MEM_FORMAT = """
28
struct {
29
  u24  mult1:3,
30
       mult2:3,
31
       freq:18;
32
  u16  offset;
33
  u16  rtone:6,
34
       ctone:6,
35
       unknown1:1,
36
       mode:3;
37
  u8   dtcs;
38
  u8   tuning_step:4,
39
       unknown2:4;
40
  u8   unknown3;
41
  u8   tmode:4,
42
       duplex:2,
43
       dtcs_polarity:2;
44
  char name[16];
45
  u8   dv_code;
46
  u8   urcall[7];
47
  u8   rpt1call[7];
48
  u8   rpt2call[7];
49
} memory[1004];
50

    
51
#seekto 0xC040;
52
u8 usedflags[125];
53
#seekto 0xC0BE;
54
u8 skipflags[125];
55
#seekto 0xC13B;
56
u8 pskipflags[125];
57

    
58
#seekto 0xC1C0;
59
struct {
60
  u8 bank;
61
  u8 index;
62
} bank_info[1000];
63

    
64
#seekto 0xC9C0;
65
char ICFComment[16];
66
struct {
67
  char name[16];
68
} bank_names[26];
69
"""
70

    
71
TUNE_STEPS = [5.0, 6.25, 8.33, 5.0, 10.0, 12.5, 15.0, 20.0, 25.0, 30.0, 50.0,
72
              5.0, 5.0, 5.0, 5.0]
73
MODES = ['FM', 'NFM', '2??', 'AM', 'NAM', 'DV', '6??', '7??']
74
TMODES = ['', 'Tone', '2??', 'TSQL', '4??', 'DTCS', 'TSQL-R', 'DTCS-R',
75
          'DTCS-T', 'Tone->DTCS', 'DTCS->Tone', 'Tone->Tone']
76
DUPLEX = ['', '-', '+']
77
DTCS_POL = ['NN', 'NR', 'RN', 'RR']
78
SPECIALS = ['144-C0', '144-C1', '430-C0', '430-C1']
79
MULTS = [5000, 6250, 25000 / 3.0]
80

    
81

    
82
def warp_byte_size(inbytes, obw=8, ibw=8):
83
    """Convert between "byte sizes".
84

    
85
    This will pack N-bit characters into a sequence of 8-bit bytes,
86
    and perform the opposite.
87

    
88
    ibw (input bit width) is the width of the storage
89
    obw (output bit width) is the width of the characters to extract
90

    
91
    ibw=8,obw=7 will pull seven-bit characters from a sequence of bytes
92
    ibw=7,obw=8 will pack seven-bit characters into a sequence of bytes
93
    """
94
    if isinstance(inbytes, str):
95
        inbytes = [ord(x) for x in inbytes]
96
    outbit = 0
97
    tmp = 0
98
    stripmask = 1 << (ibw - 1)
99
    for byte in inbytes:
100
        inbit = 0
101
        for i in range(0, max(obw, ibw - inbit)):
102
            if inbit == ibw:
103
                # Move to next char
104
                inbit = 0
105
                break
106
            tmp = (tmp << 1) | ((byte & stripmask) and 1 or 0)
107
            byte = (byte << 1) & 0xFF
108
            inbit += 1
109
            outbit += 1
110
            if outbit == obw:
111
                yield tmp
112
                tmp = 0
113
                outbit = 0
114

    
115

    
116
class ID5100Bank(icf.IcomNamedBank):
117
    def get_name(self):
118
        _banks = self._model._radio._memobj.bank_names
119
        return str(_banks[self.index].name).rstrip()
120

    
121
    def set_name(self, name):
122
        _banks = self._model._radio._memobj.bank_names
123
        _banks[self.index].name = name.ljust(16)[:16]
124

    
125

    
126
@directory.register
127
class ID5100Radio(icf.IcomRawCloneModeRadio,
128
                  chirp_common.IcomDstarSupport):
129
    VENDOR = "Icom"
130
    MODEL = "ID-5100"
131
    NEEDS_COMPAT_SERIAL = False
132

    
133
    _model = b'\x34\x84\x00\x01'
134
    _endframe = b"Icom Inc\x2E\xEE\xEE"
135

    
136
    # MapRev=1 Size is 260928 0x3FB40
137
    # MapRev=2 Size is 148160 0x242C0
138
    # MapRev=3 Size is 172928 0x2A380
139
    _memsize = 0x3FB40
140

    
141
    _ranges = [(0, _memsize, 64)]
142

    
143
    _num_banks = 26
144
    _bank_class = ID5100Bank
145

    
146
    _can_hispeed = True
147
    _icf_data = {
148
        'MapRev': 1,
149
        'EtcData': 404010,
150
    }
151

    
152
    def process_mmap(self):
153
        was_memsize = self._memsize
154

    
155
        # Apparently the 5100 has different reported memory sizes
156
        # depending on firmware version. When we're loading an image,
157
        # we should adjust our MapRev and _ranges to be correct for
158
        # saving ICF files and uploading. This also means we should
159
        # try to detect which version we are talking to and refuse to
160
        # upload an image to a radio with a mismatch.
161

    
162
        self._memsize = len(self._mmap)
163
        self._ranges = [(0, self._memsize, 64)]
164

    
165
        maprevs = {
166
            0x3FB40: 1,
167
            0x242C0: 2,
168
            0x2A380: 3,
169
        }
170

    
171
        if self._memsize != was_memsize:
172
            self._icf_data['MapRev'] = maprevs.get(self._memsize, 0)
173
            if self._icf_data['MapRev'] == 0:
174
                LOG.error('Unknown memsize %06X!', self._memsize)
175
                raise errors.InvalidDataError('Unsupported memory format!')
176
            LOG.info('Memory length changed from %06X to %06X; new MapRev=%i',
177
                     was_memsize, self._memsize, self._icf_data['MapRev'])
178

    
179
        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
180

    
181
    @classmethod
182
    def get_prompts(cls):
183
        rp = chirp_common.RadioPrompts()
184
        rp.info = dedent('This driver is NOT COMPLETE and does not '
185
                         'even support cloning with a cable. It is, '
186
                         'as yet, untested with real hardware and '
187
                         'should be considered basically radioactive.')
188
        rp.pre_download = rp.pre_upload = rp.info
189
        return rp
190

    
191
    def get_features(self):
192
        rf = chirp_common.RadioFeatures()
193
        rf.has_ctone = True
194
        rf.has_dtcs = True
195
        rf.has_dtcs_polarity = True
196
        rf.has_bank = True
197
        rf.has_bank_index = True
198
        rf.has_bank_names = True
199
        rf.requires_call_lists = False
200
        rf.valid_modes = [x for x in MODES
201
                          if '?' not in x]
202
        rf.valid_tmodes = [x for x in TMODES
203
                           if '-' not in x or '?' not in x]
204
        rf.memory_bounds = (0, 999)
205
        rf.valid_bands = [(118000000, 174000000),
206
                          (375000000, 550000000)]
207
        rf.valid_skips = ['', 'S', 'P']
208
        rf.valid_characters = chirp_common.CHARSET_ASCII
209
        rf.valid_name_length = 16
210
        rf.valid_tuning_steps = list(sorted(set(TUNE_STEPS)))
211
        rf.valid_special_chans = list(SPECIALS)
212
        return rf
213

    
214
    def _get_bank(self, loc):
215
        _bank = self._memobj.bank_info[loc]
216
        _bank.bank &= 0x1F
217
        if _bank.bank == 0x1F:
218
            return None
219
        else:
220
            return _bank.bank
221

    
222
    def _set_bank(self, loc, bank):
223
        _bank = self._memobj.bank_info[loc]
224
        if bank is None:
225
            _bank.bank = 0x1F
226
        else:
227
            _bank.bank = bank
228

    
229
    def _get_bank_index(self, loc):
230
        _bank = self._memobj.bank_info[loc]
231
        return _bank.index
232

    
233
    def _set_bank_index(self, loc, index):
234
        _bank = self._memobj.bank_info[loc]
235
        _bank.index = index
236

    
237
    def _get_raw_memory(self, number):
238
        if isinstance(number, str):
239
            number = 1000 + SPECIALS.index(number)
240
        return number, self._memobj.memory[number]
241

    
242
    def get_raw_memory(self, number):
243
        num, mem = self._get_raw_memory(number)
244
        return repr(mem)
245

    
246
    def get_memory(self, number):
247
        num, _mem = self._get_raw_memory(number)
248
        m = chirp_common.DVMemory()
249
        m.number = num
250
        if isinstance(number, str):
251
            m.extd_number = number
252
        else:
253
            _flg = self._memobj.usedflags[num // 8]
254
            _skp = self._memobj.skipflags[num // 8]
255
            _pskp = self._memobj.pskipflags[num // 8]
256
            m.empty = bool(_flg & (1 << num % 8))
257
            if _pskp & 1 << (num % 8):
258
                m.skip = 'P'
259
            elif _skp & 1 << (num % 8):
260
                m.skip = 'S'
261
            else:
262
                m.skip = ''
263

    
264
        mult = MULTS[_mem.mult1]
265
        m.freq = int(_mem.freq * mult)
266
        m.offset = int(_mem.offset * mult)
267
        m.tuning_step = TUNE_STEPS[_mem.tuning_step]
268
        m.name = str(_mem.name).rstrip()
269
        m.mode = MODES[_mem.mode]
270
        m.rtone = chirp_common.TONES[_mem.rtone]
271
        m.ctone = chirp_common.TONES[_mem.ctone]
272
        m.dtcs = chirp_common.DTCS_CODES[_mem.dtcs]
273
        m.dtcs_polarity = DTCS_POL[_mem.dtcs_polarity]
274
        tmode = TMODES[_mem.tmode]
275
        if '->' in tmode:
276
            m.tmode = 'Cross'
277
            m.cross_mode = tmode
278
        elif '-' in tmode and 0:
279
            # FIXME
280
            m.tmode, extra = tmode.split('-')
281
        else:
282
            m.tmode = tmode
283
        m.duplex = DUPLEX[_mem.duplex]
284

    
285
        m.dv_code = _mem.dv_code
286
        m.dv_urcall = ''.join(chr(x)
287
                              for x in warp_byte_size(_mem.urcall, 7, 8))
288
        m.dv_rpt1call = ''.join(chr(x)
289
                                for x in warp_byte_size(_mem.rpt1call, 7, 8))
290
        m.dv_rpt2call = ''.join(chr(x)
291
                                for x in warp_byte_size(_mem.rpt2call, 7, 8))
292

    
293
        return m
294

    
295
    def set_memory(self, mem):
296
        num, _mem = self._get_raw_memory(mem.number)
297
        if num < 1000:
298
            _flg = self._memobj.usedflags[num // 8]
299
            _skp = self._memobj.skipflags[num // 8]
300
            _pskp = self._memobj.pskipflags[num // 8]
301
            mybit = 1 << (num % 8)
302
            _flg |= mybit
303
            _skp &= ~mybit
304
            _pskp &= ~mybit
305

    
306
            if mem.empty:
307
                return
308

    
309
            _flg &= ~mybit
310
            if mem.skip == 'S':
311
                _skp |= mybit
312
            elif mem.skip == 'P':
313
                _pskp |= mybit
314

    
315
        if chirp_common.is_6_25(mem.freq):
316
            mult = MULTS[1]
317
        elif chirp_common.is_8_33(mem.freq):
318
            mult = MULTS[2]
319
        else:
320
            mult = MULTS[0]
321
        _mem.mult1 = _mem.mult2 = MULTS.index(mult)
322
        _mem.freq = mem.freq / mult
323
        _mem.offset = mem.offset / mult
324
        _mem.tuning_step = TUNE_STEPS.index(mem.tuning_step)
325
        _mem.name = mem.name.ljust(16)
326
        _mem.mode = MODES.index(mem.mode)
327
        _mem.rtone = chirp_common.TONES.index(mem.rtone)
328
        _mem.ctone = chirp_common.TONES.index(mem.ctone)
329
        _mem.dtcs = chirp_common.DTCS_CODES.index(mem.dtcs)
330
        _mem.dtcs_polarity = DTCS_POL.index(mem.dtcs_polarity)
331
        _mem.tmode = TMODES.index(mem.tmode)
332
        _mem.duplex = DUPLEX.index(mem.duplex)
333

    
334
        _mem.unknown1 = 0
335
        _mem.unknown2 = 0
336
        _mem.unknown3 = 0
337

    
338
        if isinstance(mem, chirp_common.DVMemory):
339
            _mem.dv_code = mem.dv_code
340
            _mem.urcall = list(
341
                warp_byte_size(mem.dv_urcall.ljust(8), 8, 7))
342
            _mem.rpt1call = list(
343
                warp_byte_size(mem.dv_rpt1call.ljust(8), 8, 7))
344
            _mem.rpt2call = list(
345
                warp_byte_size(mem.dv_rpt2call.ljust(8), 8, 7))
(11-11/38)