Project

General

Profile

Bug #10433 ยป tmd710.py

Dan Smith, 03/10/2023 05:44 AM

 
1
# Copyright 2011 Dan Smith <dsmith@danplanet.com>
2
# --        2019 Rick DeWitt <aa0rd@yahoo.com>
3
# -- Implementing Kenwood TM-D710G as MCP Clone Mode for Python 2.7
4
# -- Thanks to Herm Halbach, W7HRM, for the 710 model testing.
5
# This program is free software: you can redistribute it and/or modify
6
# it under the terms of the GNU General Public License as published by
7
# the Free Software Foundation, either version 3 of the License, or
8
# (at your option) any later version.
9
#
10
# This program is distributed in the hope that it will be useful,
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
# GNU General Public License for more details.
14
#
15
# You should have received a copy of the GNU General Public License
16
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
17

    
18
import time
19
import struct
20
import logging
21
import re
22
import math
23
from chirp import chirp_common, directory, memmap
24
from chirp import bitwise, errors, util
25
from chirp.settings import RadioSettingGroup, RadioSetting, \
26
    RadioSettingValueBoolean, RadioSettingValueList, \
27
    RadioSettingValueString, RadioSettingValueInteger, \
28
    RadioSettingValueFloat, RadioSettings, InvalidValueError
29
from textwrap import dedent
30
from chirp.drivers import kenwood_live
31

    
32
LOG = logging.getLogger(__name__)
33

    
34
HAS_FUTURE = True
35
try:                         # PY3 compliance
36
    from builtins import bytes
37
except ImportError:
38
    HAS_FUTURE = False
39
    LOG.debug('python-future package is not '
40
              'available; %s requires it' % __name__)
41

    
42
BAUD = 0
43
STIMEOUT = 0.2
44
TERM = b'\x0d'         # Cmd write terminator (CR)
45
ACK = b'\x06'           # Data write acknowledge char
46
W8S = 0.001      # short wait, secs
47
W8L = 0.1       # long wait
48
TMD710_DUPLEX = ["", "+", "-", "n/a", "split"]
49
TMD710_SKIP = ["", "S"]
50
TMD710_MODES = ["FM", "NFM", "AM"]
51
TMD710_BANDS = [(118000000, 135995000),
52
                (136000000, 199995000),
53
                (200000000, 299995000),
54
                (300000000, 399995000),
55
                (400000000, 523995000),
56
                (800000000, 1299995000)]
57
TMD710_STEPS = [5.0, 6.25, 8.33, 10.0, 12.5, 15.0, 20.0, 25.0,
58
                30.0, 50.0, 100.0]
59
# Need string list of those steps for mem.extra value list
60
STEPS_STR = []
61
for val in TMD710_STEPS:
62
    STEPS_STR.append("%3.2f" % val)
63
TMD710_TONE_MODES = ["", "Tone", "TSQL", "DTCS", "Cross"]
64
TMD710_CROSS = ["Tone->Tone", "Tone->DTCS", "DTCS->Tone"]
65
TMD710_DTSC = list(chirp_common.DTCS_CODES)
66
TMD710_TONES = list(chirp_common.TONES)
67
TMD710_TONES.remove(159.8)
68
TMD710_TONES.remove(165.5)
69
TMD710_TONES.remove(171.3)
70
TMD710_TONES.remove(177.3)
71
TMD710_TONES.remove(183.5)
72
TMD710_TONES.remove(189.9)
73
TMD710_TONES.remove(196.6)
74
TMD710_TONES.remove(199.5)
75
TMD710_CHARS = chirp_common.CHARSET_ASCII
76
TMD710_CHARS += chr(34)     # "
77

    
78

    
79
def _command(ser, cmd, rsplen, w8t=0.01):
80
    """Send cmd to radio via ser port
81
    cmd is output string with possible terminator
82
    rsplen is expected response char count, NOT incl prefix and term
83
    If rsplen = 0 then do not0 read after write """
84
    ser.write(cmd)
85
    LOG.debug(" Out %4i ->: %s" % (len(cmd), util.hexprint(cmd[0: 32])))
86
    time.sleep(w8t)
87
    result = b""
88
    if rsplen > 0:  # read response
89
        result = ser.read(rsplen)
90
        LOG.debug(" In %4i <-: %s" % (len(result),
91
                                      util.hexprint(result[0: 32])))
92
    return result
93

    
94

    
95
def _connect_radio(radio):
96
    """Determine baud rate and verify radio on-line"""
97
    global BAUD
98
    xid = "D710" + radio.SHORT
99
    resp = kenwood_live.get_id(radio.pipe)
100
    BAUD = radio.pipe.baudrate      # As detected by kenwood_live
101
    LOG.debug("Got [%s] at %i Baud." % (resp, BAUD))
102
    resp = resp[3:]     # Strip "ID " prefix
103
    if len(resp) > 2:   # Got something from "ID"
104
        if resp == xid:     # Good comms
105
            return
106
        else:
107
            stx = "Radio responded as %s, not %s." % (resp, xid)
108
            raise errors.RadioError(stx)
109
    raise errors.RadioError("No response from radio")
110

    
111

    
112
def _update_status(self, status, step=1):
113
    """ Increment status bar """
114
    status.cur += step
115
    self.status_fn(status)
116
    return
117

    
118

    
119
def _val_list(setting, opts, obj, atrb, fix=0, ndx=-1):
120
    """Callback:from ValueList. Set the integer index.
121
    This function is here to be available to get_mem and get_set
122
    fix is optional additive offset to the list index
123
    ndx is optional obj[ndx] array index """
124
    value = opts.index(str(setting.value))
125
    value += fix
126
    if ndx >= 0:    # indexed obj
127
        setattr(obj[ndx], atrb, value)
128
    else:
129
        setattr(obj, atrb, value)
130
    return
131

    
132

    
133
class KenwoodTMx710Radio(chirp_common.CloneModeRadio):
134
    """ Base class for TMD-710 """
135
    VENDOR = "Kenwood"
136
    MODEL = "TM-x710"
137
    SHORT = "X"       # Short model ID code
138
    NEEDS_COMPAT_SERIAL = False
139

    
140
    _upper = 999         # Number of normal chans
141

    
142
    # Put Special memory channels after normal ones
143
    SPECIAL_MEMORIES = {"Scan-0Lo": 1000, "Scan-0Hi": 1001,
144
                        "Scan-1Lo": 1002, "Scan-1Hi": 1003,
145
                        "Scan-2Lo": 1004, "Scan-2Hi": 1005,
146
                        "Scan-3Lo": 1006, "Scan-3Hi": 1007,
147
                        "Scan-4Lo": 1008, "Scan-4Hi": 1009,
148
                        "Scan-5Lo": 1010, "Scan-5Hi": 1011,
149
                        "Scan-6Lo": 1012, "Scan-6Hi": 1013,
150
                        "Scan-7Lo": 1014, "Scan-7Hi": 1015,
151
                        "Scan-8Lo": 1016, "Scan-8Hi": 1017,
152
                        "Scan-9Lo": 1018, "Scan-9Hi": 1019,
153
                        "WX-1": 1020, "WX-2": 1021,
154
                        "WX-3": 1022, "WX-4": 1023,
155
                        "WX-5": 1024, "WX-6": 1025,
156
                        "WX-7": 1026, "WX-8": 1027,
157
                        "WX-9": 1028, "WX-10": 1029,
158
                        "Call C0": 1030, "Call C1": 1031
159
                        }
160
    # _REV dict is used to retrieve name given number
161
    SPECIAL_MEMORIES_REV = dict(zip(SPECIAL_MEMORIES.values(),
162
                                    SPECIAL_MEMORIES.keys()))
163

    
164
    def get_features(self):
165
        rf = chirp_common.RadioFeatures()
166
        rf.can_odd_split = True
167
        rf.has_dtcs = True
168
        rf.has_dtcs_polarity = False
169
        if self.SHORT == "G":             # NOT for D710
170
            rf.has_rx_dtcs = True       # Enable DTCS Rx Code column
171
            rf.has_cross = True
172
            rf.valid_cross_modes = TMD710_CROSS
173
        rf.has_bank = False
174
        rf.has_settings = True
175
        rf.has_ctone = True
176
        rf.has_mode = True
177
        rf.has_comment = False
178
        rf.valid_tmodes = TMD710_TONE_MODES
179
        rf.valid_modes = TMD710_MODES
180
        rf.valid_duplexes = TMD710_DUPLEX
181
        rf.valid_tuning_steps = TMD710_STEPS
182
        rf.valid_tones = TMD710_TONES
183
        rf.valid_dtcs_codes = TMD710_DTSC
184
        # Supports upper and lower case text
185
        rf.valid_characters = TMD710_CHARS
186
        rf.valid_name_length = 8
187
        rf.valid_skips = TMD710_SKIP
188
        rf.valid_bands = TMD710_BANDS
189
        rf.memory_bounds = (0, 999)        # including special chans 1000-1029
190
        rf.valid_special_chans = sorted(self.SPECIAL_MEMORIES.keys())
191
        return rf
192

    
193
    @classmethod
194
    def get_prompts(cls):
195
        rp = chirp_common.RadioPrompts()
196
        rp.pre_download = _(dedent("""\
197
            Connect your interface cable to the PC Port on the
198
            back of the 'TX/RX' unit. NOT the Com Port on the head.
199
            """))
200
        rp.pre_upload = _(dedent("""\
201
            Connect your interface cable to the PC Port on the
202
            back of the 'TX/RX' unit. NOT the Com Port on the head.
203
            """))
204
        return rp
205

    
206
    def sync_in(self):
207
        """Download from radio"""
208
        try:
209
            _connect_radio(self)
210
            data = bytes(self._read_mem())
211
        except errors.RadioError:
212
            # Pass through any real errors we raise
213
            raise
214
        except Exception:
215
            # If anything unexpected happens, make sure we raise
216
            # a RadioError and log the problem
217
            LOG.exception('Unexpected error during download')
218
            raise errors.RadioError('Unexpected error communicating '
219
                                    'with the radio')
220
        self._mmap = memmap.MemoryMapBytes(data)
221
        self.process_mmap()
222

    
223
    def sync_out(self):
224
        """Upload to radio"""
225
        try:
226
            _connect_radio(self)
227
            self._write_mem()
228
        except Exception:
229
            # If anything unexpected happens, make sure we raise
230
            # a RadioError and log the problem
231
            LOG.exception('Unexpected error during upload')
232
            raise errors.RadioError('Unexpected error communicating '
233
                                    'with the radio')
234

    
235
    def process_mmap(self):
236
        """Process the mem map into the mem object"""
237
        self._memobj = bitwise.parse(self.MEM_FORMAT, self._mmap)
238

    
239
    def get_memory(self, number):
240
        """Convert raw channel data (_mem) into UI columns (mem)"""
241
        mem = chirp_common.Memory()
242
        if self.SHORT == "G":
243
            mem.extra = RadioSettingGroup("extra", "Extra")
244
        # If called from 'Properties', spcl chans number is integer
245
        propflg = False
246
        if isinstance(number, int):
247
            if number > 999:
248
                propflg = True
249
        if isinstance(number, str) or propflg:
250
            if propflg:
251
                mem.number = number
252
                mem.name = self.SPECIAL_MEMORIES_REV[number]
253
                mem.extd_number = mem.name
254
            else:
255
                mem.name = number   # Spcl chns 1st var
256
                mem.number = self.SPECIAL_MEMORIES[number]
257
                mem.extd_number = number    # Uses name as LOC
258
                mem.immutable = ["name"]
259
            if mem.number < 1030:       # Scan edges, WX
260
                _mem = self._memobj.ch_mem[mem.number]
261
                _map = self._memobj.chmap[mem.number]
262
            else:                       # Call chans
263
                _mem = self._memobj.call[mem.number - 1030]
264
        else:       # Normal mem chans
265
            _mem = self._memobj.ch_mem[number]
266
            _nam = self._memobj.ch_nam[number]
267
            _map = self._memobj.chmap[number]
268
            mem.number = number
269
            mnx = ""
270
            for char in _nam.name:
271
                if int(char) < 127:
272
                    mnx += chr(int(char))
273
            mem.name = mnx.rstrip()
274
        if _mem.rxfreq == 0x0ffffffff or _mem.rxfreq == 0:
275
            mem.empty = True
276
            return mem
277
        mem.empty = False
278
        if mem.number < 1030 and _map.skip != 0x0ff:      # empty
279
            mem.skip = TMD710_SKIP[_map.skip]
280
        mem.freq = int(_mem.rxfreq)
281
        mem.duplex = TMD710_DUPLEX[_mem.duplex]
282
        mem.offset = int(_mem.offset)
283
        # Duplex = 4 (split); offset contains the TX freq
284
        mem.mode = TMD710_MODES[_mem.mode]
285
        # _mem.tmode is 4-bit pattern, not number
286
        mx = 0      # No tone
287
        mem.cross_mode = TMD710_CROSS[0]
288
        mem.rx_dtcs = TMD710_DTSC[_mem.dtcs]
289
        mem.dtcs = TMD710_DTSC[_mem.dtcs]
290
        if self.SHORT == "G":
291
            if _mem.tmode & 8:     # Tone
292
                mx = 1
293
            if _mem.tmode & 4:     # Tsql
294
                mx = 2
295
            if _mem.tmode & 2:     # Dtcs
296
                mx = 3
297
            if _mem.tmode & 1:     # Cross
298
                mx = 4
299
                if _mem.cross == 1:     # Tone->DTCS
300
                    mem.cross_mode = TMD710_CROSS[1]
301
                if _mem.cross == 2:     # DTCS->Tone
302
                    mem.cross_mode = TMD710_CROSS[2]
303
        else:           # D710; may have bit 8 set
304
            if _mem.tmode & 4:     # Tone
305
                mx = 1
306
            if _mem.tmode & 2:     # Tsql
307
                mx = 2
308
            if _mem.tmode & 1:     # Dtcs
309
                mx = 3
310
                mem.dtcs = TMD710_DTSC[_mem.dtcs]
311
        mem.tmode = TMD710_TONE_MODES[mx]
312
        mem.ctone = TMD710_TONES[_mem.ctone]
313
        mem.rtone = TMD710_TONES[_mem.rtone]
314
        mem.tuning_step = TMD710_STEPS[_mem.tstep]
315

    
316
        if self.SHORT == "G":         # Only the 710G
317
            rx = RadioSettingValueList(STEPS_STR, STEPS_STR[_mem.splitstep])
318
            sx = "Split TX step (KHz)"
319
            rset = RadioSetting("splitstep", sx, rx)
320
            mem.extra.append(rset)
321

    
322
        return mem
323

    
324
    def set_memory(self, mem):
325
        """Convert UI column data (mem) into MEM_FORMAT memory (_mem)"""
326
        if mem.number > 999:      # Special chans
327
            if mem.number < 1030:        # Scan, Wx
328
                _mem = self._memobj.ch_mem[mem.number]
329
                _map = self._memobj.chmap[mem.number]
330
            else:                        # Call chans
331
                _mem = self._memobj.call[mem.number - 1030]
332
            _nam = None
333
        else:
334
            _mem = self._memobj.ch_mem[mem.number]
335
            _nam = self._memobj.ch_nam[mem.number]
336
            _map = self._memobj.chmap[mem.number]
337
            nx = len(mem.name)
338
            for ix in range(8):
339
                if ix < nx:
340
                    _nam.name[ix] = mem.name[ix]
341
                else:
342
                    _nam.name[ix] = chr(0x0ff)    # needs 8 chrs
343
        if mem.empty:
344
            _mem.rxfreq = 0x0ffffffff
345
            _mem.offset = 0x0ffffff
346
            _mem.duplex = 0x0f
347
            _mem.tstep = 0x0ff
348
            _mem.tmode = 0x0f
349
            _mem.mode = 0x0ff
350
            _mem.rtone = 0x0ff
351
            _mem.ctone = 0x0ff
352
            _mem.dtcs = 0x0ff
353
            _map.skip = 0x0ff
354
            _map.band = 0x0ff
355
            if _nam:
356
                for ix in range(8):
357
                    _nam.name[ix] = chr(0x0ff)
358
            return
359
        if _mem.rxfreq == 0x0ffffffff:    # New Channel needs defaults
360
            _mem.rxfreq = 144000000
361
            _map.band = 5
362
            _map.skip = 0
363
            _mem.mode = 0
364
            _mem.duplex = 0
365
            _mem.offset = 0
366
            _mem.rtone = 8
367
            _mem.ctone = 8
368
            _mem.dtcs = 0
369
            _mem.tstep = 0
370
            _mem.splitstep = 0
371
        # Now use the UI values entered so far
372
        _mem.rxfreq = mem.freq
373
        _mem.mode = TMD710_MODES.index(mem.mode)
374
        try:
375
            _tone = mem.rtone
376
            _mem.rtone = TMD710_TONES.index(mem.rtone)
377
            _tone = mem.ctone
378
            _mem.ctone = TMD710_TONES.index(mem.ctone)
379
        except ValueError:
380
            raise errors.UnsupportedToneError("This radio does not support " +
381
                                              "tone %.1fHz" % _tone)
382
        _mem.dtcs = TMD710_DTSC.index(mem.dtcs)
383
        _mem.tmode = 0      # None
384
        _mem.cross = 0
385
        if self.SHORT == "G":
386
            if mem.tmode == "Tone":
387
                _mem.tmode = 8
388
            if mem.tmode == "TSQL":
389
                _mem.tmode = 4
390
            if mem.tmode == "DTCS":
391
                _mem.tmode = 2
392
            if mem.tmode == "Cross":
393
                _mem.tmode = 1
394
                mx = TMD710_CROSS.index(mem.cross_mode)
395
                _mem.cross = 3          # t -t
396
                if mx == 1:
397
                    _mem.cross = 1      # t-d
398
                    _mem.dtcs = TMD710_DTSC.index(mem.rx_dtcs)
399
                if mx == 2:
400
                    _mem.cross = 2      # d-t
401
                    _mem.dtcs = TMD710_DTSC.index(mem.dtcs)
402
        else:
403
            _mem.tmode = 0x80       # None
404
            if mem.tmode == "Tone":
405
                _mem.tmode = 0x0c
406
            if mem.tmode == "TSQL":
407
                _mem.tmode = 0x0a
408
            if mem.tmode == "DTCS":
409
                _mem.tmode = 0x09
410
        if mem.duplex == "n/a":     # Not valid
411
            mem.duplex = ""
412
        _mem.duplex = TMD710_DUPLEX.index(mem.duplex)
413
        _mem.offset = mem.offset
414
        _mem.tstep = TMD710_STEPS.index(mem.tuning_step)
415
        # Set _map.band for this bank. Not Calls!
416
        if mem.number < 1030:
417
            _map.band = 5
418
            val = mem.freq
419
            for mx in range(6):     # Band codes are 0, 5, 6, 7, 8, 9
420
                if val >= TMD710_BANDS[mx][0] and \
421
                        val <= TMD710_BANDS[mx][1]:
422
                    _map.band = mx
423
                    if mx > 0:
424
                        _map.band = mx + 4
425
            _map.skip = TMD710_SKIP.index(mem.skip)
426
        # Only 1 mem.extra entry now
427
        for ext in mem.extra:
428
            if ext.get_name() == "splitstep":
429
                val = STEPS_STR.index(str(ext.value))
430
                setattr(_mem, "splitstep", val)
431
            else:
432
                setattr(_mem, ext.get_name(), ext.value)
433
        return
434

    
435
    def get_settings(self):
436
        """Translate the MEM_FORMAT structs into settings in the UI"""
437
        # Define mem struct write-back shortcuts
438
        if self.SHORT == "G":
439
            _bmp = self._memobj.bitmap
440
        _blk1 = self._memobj.block1
441
        _blk1a = self._memobj.block1a
442
        _pmg = self._memobj.pmg     # array[6] of settings
443
        _dtmc = self._memobj.dtmc
444
        _dtmn = self._memobj.dtmn
445
        _com = self._memobj.mcpcom
446
        _skyc = self._memobj.skycmd
447
        basic = RadioSettingGroup("basic", "Basic")
448
        disp = RadioSettingGroup("disp", "PM0: Display")    # PM[0] settings
449
        aud = RadioSettingGroup("aud", "PM0: Audio")
450
        aux = RadioSettingGroup("aux", "PM0: Aux")
451
        txrx = RadioSettingGroup("txrc", "PM0: Transmit/Receive")
452
        memz = RadioSettingGroup("memz", "PM0: Memory")
453
        pfk = RadioSettingGroup("pfk", "PM0: PF Keys")
454
        pvfo = RadioSettingGroup("pvfo", "PM0: Programmable VFO")
455
        bmsk = RadioSettingGroup("bmsk", "PM0: Band Masks")    # end PM[0]
456
        rptr = RadioSettingGroup("rptr", "Repeater")
457
        dtmf = RadioSettingGroup("dtmf", "DTMF")
458
        skyk = RadioSettingGroup("skyk", "Sky Command")
459
        pmm = RadioSettingGroup("pmm", "PM Groups 1-5(Partial)")
460
        group = RadioSettings(basic, disp, aud, aux, txrx, memz, pvfo, pfk,
461
                              bmsk, rptr, dtmf, skyk, pmm)
462

    
463
        mhz1 = 1000000.   # Raw freq is stored with 0.1 Htz resolution
464

    
465
        def _adjraw(setting, obj, atrb, fix=0, ndx=-1):
466
            """Callback for Integer add or subtract fix from value."""
467
            vx = int(str(setting.value))
468
            value = vx + int(fix)
469
            if value < 0:
470
                value = 0
471
            if ndx < 0:
472
                setattr(obj, atrb, value)
473
            else:
474
                setattr(obj[ndx], atrb, value)
475
            return
476

    
477
        def _mhz_val(setting, obj, atrb, ndx=-1, ndy=-1):
478
            """ Callback to set freq back to Htz """
479
            vx = float(str(setting.value))
480
            vx = int(vx * mhz1)
481
            if ndx < 0:
482
                setattr(obj, atrb, vx)
483
            else:
484
                if atrb[0:7] == "progvfo":      # 2-deep
485
                    stx = atrb.split(".")
486
                    setattr(obj[ndx].progvfo[ndy], stx[1], vx)
487
                else:
488
                    setattr(obj[ndx], atrb, vx)
489
            return
490

    
491
        def _char_to_str(chrx):
492
            """ Remove ff pads from char array """
493
            #  chrx is char array
494
            str1 = ""
495
            for sx in chrx:
496
                if int(sx) > 31 and int(sx) < 127:
497
                    str1 += chr(int(sx))
498
            return str1
499

    
500
        def _pswd_vfy(setting, obj, atrb):
501
            """ Verify password is 1-6 chars, numbers 1-5 """
502
            str1 = str(setting.value).strip()   # initial
503
            str2 = ''.join(filter(lambda c: c in '12345', str1))  # valid chars
504
            if str1 != str2:
505
                # Two lines due to python 73 char limit
506
                sx = "Bad characters in Password"
507
                raise errors.RadioError(sx)
508
            str2 = str1.ljust(6, chr(255))      # pad to 6 with ff's
509
            setattr(obj, atrb, str2)
510
            return
511

    
512
        def _pad_str(setting, lenstr, padchr, obj, atrb, ndx=-1):
513
            """ pad string to lenstr with padchr  """
514
            str1 = str(setting.value).strip()      # initial string
515
            str2 = str1.ljust(lenstr, padchr)
516
            if ndx < 0:
517
                setattr(obj, atrb, str2)
518
            else:
519
                setattr(obj[ndx], atrb, str2)
520
            return
521

    
522
        # ===== BASIC GROUP =====
523
        sx = _char_to_str(_com.comnt)
524
        rx = RadioSettingValueString(0, 32, sx)
525
        sx = "Comment"
526
        rset = RadioSetting("mcpcom.comnt", sx, rx)
527
        basic.append(rset)
528

    
529
        rx = RadioSettingValueInteger(0, 5, _blk1.pmrecall)
530
        sx = "Current PM Select"
531
        rset = RadioSetting("block1.pmrecall", sx, rx)
532
        basic.append(rset)
533

    
534
        rx = RadioSettingValueBoolean(bool(_blk1.pwdon))
535
        sx = "Password"
536
        rset = RadioSetting("block1.pwdon", sx, rx)
537
        basic.append(rset)
538

    
539
        sx = _char_to_str(_blk1.pswd).strip()
540
        rx = RadioSettingValueString(0, 6, sx)
541
        # rx.set_charset("12345")   # Keeps finding `'
542
        sx = "-   Password (numerals 1-5)"
543
        rset = RadioSetting("block1.pswd", sx, rx)
544
        rset.set_apply_callback(_pswd_vfy, _blk1, "pswd")
545
        basic.append(rset)
546

    
547
        # ===== PM0 (off) DISPLAY GROUP =====
548
        rx = RadioSettingValueString(0, 8, _char_to_str(_pmg[0].pwron))
549
        sx = "Power-On message"
550
        rset = RadioSetting("pmg/0.pwron", sx, rx)
551
        disp.append(rset)
552

    
553
        if self.SHORT == "G":         # TMD-710G
554
            rx = RadioSettingValueBoolean(bool(_bmp.bmpon))
555
            sx = "PM0: Custom display bitmap"
556
            rset = RadioSetting("bitmap.bmpon", sx, rx)
557
            disp.append(rset)
558

    
559
            rx = RadioSettingValueString(0, 64, _char_to_str(_bmp.bmpfyl))
560
            rx.set_mutable(False)
561
            sx = "-   Custom bitmap filename"
562
            rset = RadioSetting("bitmap.bmpfyl", sx, rx)
563
            rset.set_doc("Read-only: To modify, use MCP-6 s/w")
564
            disp.append(rset)
565

    
566
        opts = ["VFO", "Mem Recall"]
567
        rx = RadioSettingValueList(opts, opts[_pmg[0].a_mr])
568
        sx = "A: Left Side VFO/MR"
569
        rset = RadioSetting("pmg/0.a_mr", sx, rx)
570
        rset.set_apply_callback(_val_list, opts, _pmg[0], "a_mr")
571
        disp.append(rset)
572

    
573
        rx = RadioSettingValueInteger(0, 999, _pmg[0].a_chn)
574
        sx = "A: Left Side MR Channel"
575
        rset = RadioSetting("pmg/0.a_chn", sx, rx)
576
        disp.append(rset)
577

    
578
        rx = RadioSettingValueList(opts, opts[_pmg[0].b_mr])
579
        sx = "B: Right Side VFO/MR"
580
        rset = RadioSetting("pmg/0.b_mr", sx, rx)
581
        rset.set_apply_callback(_val_list, opts, _pmg[0], "b_mr")
582
        disp.append(rset)
583

    
584
        rx = RadioSettingValueInteger(0, 999, _pmg[0].b_chn)
585
        sx = "B: Right Side MR Channel"
586
        rset = RadioSetting("pmg/0.b_chn", sx, rx)
587
        disp.append(rset)
588

    
589
        rx = RadioSettingValueInteger(0, 8, _pmg[0].bright)
590
        sx = "Brightness level"
591
        rset = RadioSetting("pmg/0.bright", sx, rx)
592
        disp.append(rset)
593

    
594
        opts = ["Amber", "Green"]
595
        rx = RadioSettingValueList(opts, opts[_pmg[0].bkltclr])
596
        sx = "Backlight color"
597
        rset = RadioSetting("pmg/0.bkltclr", sx, rx)
598
        rset.set_apply_callback(_val_list, opts, _pmg[0], "bkltclr")
599
        disp.append(rset)
600

    
601
        val = _pmg[0].bkltcont + 1
602
        rx = RadioSettingValueInteger(1, 16, val)
603
        sx = "Contrast level"
604
        rset = RadioSetting("pmg/0.bkltcont", sx, rx)
605
        rset.set_apply_callback(_adjraw, _pmg[0], "bkltcont", -1)
606
        disp.append(rset)
607

    
608
        opts = ["Positive", "Negative"]
609
        rx = RadioSettingValueList(opts, opts[_pmg[0].dsprev])
610
        sx = "Color mode"
611
        rset = RadioSetting("pmg/0.dsprev", sx, rx)
612
        rset.set_apply_callback(_val_list, opts, _pmg[0], "dsprev")
613
        disp.append(rset)
614

    
615
        rx = RadioSettingValueBoolean(bool(_pmg[0].autobri))
616
        sx = "Auto brightness"
617
        rset = RadioSetting("pmg/0.autobri", sx, rx)
618
        disp.append(rset)
619

    
620
        rx = RadioSettingValueBoolean(bool(_pmg[0].dispbar))
621
        sx = "Display partition bar"
622
        rset = RadioSetting("pmg/0.dispbar", sx, rx)
623
        disp.append(rset)
624

    
625
        rx = RadioSettingValueBoolean(bool(_pmg[0].single))
626
        sx = "Single band display"
627
        rset = RadioSetting("pmg/0.single", sx, rx)
628
        disp.append(rset)
629

    
630
        rx = RadioSettingValueBoolean(bool(_pmg[0].autopm))
631
        sx = "Auto PM Store"
632
        rset = RadioSetting("pmg/0.autopm", sx, rx)
633
        disp.append(rset)
634

    
635
        # ===== AUDIO GROUP =====
636
        rx = RadioSettingValueBoolean(bool(_pmg[0].beepon))
637
        sx = "Beep On"
638
        rset = RadioSetting("pmg/0.beepon", sx, rx)
639
        aud.append(rset)
640

    
641
        val = _pmg[0].beepvol + 1     # 1-7 downloads as 0-6
642
        rx = RadioSettingValueInteger(1, 7, val)
643
        sx = "Beep volume (1 - 7)"
644
        rset = RadioSetting("pmg/0.beepvol", sx, rx)
645
        rset.set_apply_callback(_adjraw, _pmg[0], "beepvol", -1)
646
        aud.append(rset)
647

    
648
        opts = ["Mode1", "Mode2"]
649
        rx = RadioSettingValueList(opts, opts[_pmg[0].extspkr])
650
        sx = "External Speaker"
651
        rset = RadioSetting("pmg/0.extspkr", sx, rx)
652
        rset.set_apply_callback(_val_list, opts, _pmg[0], "extspkr")
653
        aud.append(rset)
654

    
655
        rx = RadioSettingValueBoolean(bool(_pmg[0].pbkrpt))
656
        sx = "VGS Plugin: Playback repeat"
657
        rset = RadioSetting("pmg/0.pbkrpt", sx, rx)
658
        aud.append(rset)
659

    
660
        rx = RadioSettingValueInteger(0, 60, _pmg[0].pbkint)
661
        sx = "     Playback repeat interval (0 - 60 secs)"
662
        rset = RadioSetting("pmg/0.pbkint", sx, rx)
663
        aud.append(rset)
664

    
665
        rx = RadioSettingValueBoolean(bool(_pmg[0].cntrec))
666
        sx = "     Continuous recording"
667
        rset = RadioSetting("pmg/0.cntrec", sx, rx)
668
        aud.append(rset)
669

    
670
        opts = ["Off", "Auto", "Manual"]
671
        rx = RadioSettingValueList(opts, opts[_pmg[0].ance])
672
        sx = "     Announce mode"
673
        rset = RadioSetting("pmg/0.ance", sx, rx)
674
        rset.set_apply_callback(_val_list, opts, _pmg[0], "ance")
675
        aud.append(rset)
676

    
677
        opts = ["English", "Japanese"]
678
        rx = RadioSettingValueList(opts, opts[_pmg[0].lang])
679
        sx = "     Announce language"
680
        rset = RadioSetting("pmg/0.lang", sx, rx)
681
        rset.set_apply_callback(_val_list, opts, _pmg[0], "lang")
682
        aud.append(rset)
683

    
684
        rx = RadioSettingValueInteger(1, 7, _pmg[0].vcvol + 1)
685
        sx = "     Voice volume (1 - 7)"
686
        rset = RadioSetting("pmg/0.vcvol", sx, rx)
687
        rset.set_apply_callback(_adjraw, _pmg[0], "vcvol", -1)
688
        aud.append(rset)
689

    
690
        rx = RadioSettingValueInteger(0, 4, _pmg[0].vcspd)
691
        sx = "     Voice speed (0 - 4)"
692
        rset = RadioSetting("pmg/0.vcspd", sx, rx)
693
        aud.append(rset)
694

    
695
        # ===== AUX GROUP =====
696
        opts = ["9600", "19200", "38400", "57600"]
697
        rx = RadioSettingValueList(opts, opts[_blk1.pcbaud])
698
        sx = "PC port baud rate"
699
        rset = RadioSetting("block1.pcbaud", sx, rx)
700
        rset.set_apply_callback(_val_list, opts, _blk1, "pcbaud")
701
        aux.append(rset)
702

    
703
        opts = ["A-Band", "B-Band", "TX-A / RX-B", "RX-A / TX-B"]
704
        rx = RadioSettingValueList(opts, opts[_pmg[0].intband])
705
        sx = "Internal TNC band"
706
        rset = RadioSetting("pmg/0.intband", sx, rx)
707
        rset.set_apply_callback(_val_list, opts, _pmg[0], "intband")
708
        aux.append(rset)
709

    
710
        opts = ["A-Band", "B-Band", "TX-A / RX-B", "RX-A / TX-B"]
711
        rx = RadioSettingValueList(opts, opts[_pmg[0].extband])
712
        sx = "External TNC band"
713
        rset = RadioSetting("pmg/0.extband", sx, rx)
714
        rset.set_apply_callback(_val_list, opts, _pmg[0], "extband")
715
        aux.append(rset)
716

    
717
        opts = ["1200", "9600"]
718
        rx = RadioSettingValueList(opts, opts[_pmg[0].extbaud])
719
        sx = "External TNC baud"
720
        rset = RadioSetting("pmg/0.extbaud", sx, rx)
721
        rset.set_apply_callback(_val_list, opts, _pmg[0], "extbaud")
722
        aux.append(rset)
723

    
724
        opts = ["Off", "BUSY", "SQL", "TX", "BUSY/TX", "SQL/TX"]
725
        rx = RadioSettingValueList(opts, opts[_pmg[0].sqcsrc])
726
        sx = "SQC output source"
727
        rset = RadioSetting("pmg/0.sqcsrc", sx, rx)
728
        rset.set_apply_callback(_val_list, opts, _pmg[0], "sqcsrc")
729
        aux.append(rset)
730

    
731
        opts = ["Low", "High"]
732
        rx = RadioSettingValueList(opts, opts[_pmg[0].sqclogic])
733
        sx = "SQC logic"
734
        rset = RadioSetting("pmg/0.sqclogic", sx, rx)
735
        rset.set_apply_callback(_val_list, opts, _pmg[0], "sqclogic")
736
        aux.append(rset)
737

    
738
        opts = ["Off", "30", "60", "90", "120", "180"]
739
        rx = RadioSettingValueList(opts, opts[_pmg[0].apo])
740
        sx = "APO: Auto Power Off (Mins)"
741
        rset = RadioSetting("pmg/0.apo", sx, rx)
742
        rset.set_apply_callback(_val_list, opts, _pmg[0], "apo")
743
        aux.append(rset)
744

    
745
        opts = ["Time Operate (TO)", "Carrier Operate (CO)", "Seek"]
746
        rx = RadioSettingValueList(opts, opts[_pmg[0].scnrsm])
747
        sx = "Scan resume mode"
748
        rset = RadioSetting("pmg/0.scnrsm", sx, rx)
749
        rset.set_apply_callback(_val_list, opts, _pmg[0], "scnrsm")
750
        aux.append(rset)
751

    
752
        rx = RadioSettingValueInteger(1, 10, _pmg[0].scntot + 1)
753
        sx = "   Scan TO delay (Secs)"
754
        rset = RadioSetting("pmg/0.scntot", sx, rx)
755
        rset.set_apply_callback(_adjraw, _pmg[0], "scntot", -1)
756
        aux.append(rset)
757

    
758
        rx = RadioSettingValueInteger(1, 10, _pmg[0].scncot + 1)
759
        sx = "   Scan CO delay (Secs)"
760
        rset = RadioSetting("pmg/0.scncot", sx, rx)
761
        rset.set_apply_callback(_adjraw, _pmg[0], "scncot", -1)
762
        aux.append(rset)
763

    
764
        opts = ["Mode 1: 1ch", "Mode 2: 61ch", "Mode 3: 91ch",
765
                "Mode 4: 181ch"]
766
        rx = RadioSettingValueList(opts, opts[_pmg[0].vsmode])
767
        sx = "Visual scan"
768
        rset = RadioSetting("pmg/0.vsmode", sx, rx)
769
        rset.set_apply_callback(_val_list, opts, _pmg[0], "vsmode")
770
        aux.append(rset)
771

    
772
        rx = RadioSettingValueBoolean(bool(_blk1.m10mz))
773
        sx = "10 Mhz mode"
774
        rset = RadioSetting("block1.m10mz", sx, rx)
775
        aux.append(rset)
776

    
777
        rx = RadioSettingValueBoolean(bool(_blk1.ansbck))
778
        sx = "Remote control answerback"
779
        rset = RadioSetting("block1.ansbck", sx, rx)
780
        aux.append(rset)
781

    
782
        # ===== TX / RX Group =========
783
        opts = ["A: Left", "B: Right"]
784
        rx = RadioSettingValueList(opts, opts[_pmg[0].txband])
785
        sx = "TX Side (PTT)"
786
        rset = RadioSetting("pmg/0.txband", sx, rx)
787
        rset.set_apply_callback(_val_list, opts, _pmg[0], "txband")
788
        txrx.append(rset)
789

    
790
        opts = ["High (50W)", "Medium (10W)", "Low (5W)"]
791
        rx = RadioSettingValueList(opts, opts[_pmg[0].a_pwr])
792
        sx = "A-Band transmit power"
793
        rset = RadioSetting("pmg/0.a_pwr", sx, rx)
794
        rset.set_apply_callback(_val_list, opts, _pmg[0], "a_pwr")
795
        txrx.append(rset)
796

    
797
        rx = RadioSettingValueList(opts, opts[_pmg[0].b_pwr])
798
        sx = "B-Band transmit power"
799
        rset = RadioSetting("pmg/0.b_pwr", sx, rx)
800
        rset.set_apply_callback(_val_list, opts, _pmg[0], "b_pwr")
801
        txrx.append(rset)
802

    
803
        opts = ["Off", "125", "250", "500", "750", "1000"]
804
        rx = RadioSettingValueList(opts, opts[_pmg[0].mutehu])
805
        sx = "Rx Mute hangup time (ms)"
806
        rset = RadioSetting("pmg/0.mutehu", sx, rx)
807
        rset.set_apply_callback(_val_list, opts, _pmg[0], "mutehu")
808
        txrx.append(rset)
809

    
810
        opts = ["Off", "125", "250", "500"]
811
        rx = RadioSettingValueList(opts, opts[_pmg[0].ssqlhu])
812
        sx = "S-meter SQL hangup time (ms)"
813
        rset = RadioSetting("pmg/0.ssqlhu", sx, rx)
814
        rset.set_apply_callback(_val_list, opts, _pmg[0], "ssqlhu")
815
        txrx.append(rset)
816

    
817
        rx = RadioSettingValueBoolean(bool(_pmg[0].beatshft))
818
        sx = "Beat shift"
819
        rset = RadioSetting("pmg/0.beatshft", sx, rx)
820
        txrx.append(rset)
821

    
822
        rx = RadioSettingValueBoolean(bool(_pmg[0].asmsql))
823
        sx = "A-Band S-meter SQL"
824
        rset = RadioSetting("pmg/0.asmsql", sx, rx)
825
        txrx.append(rset)
826

    
827
        rx = RadioSettingValueBoolean(bool(_pmg[0].bsmsql))
828
        sx = "B-Band S-meter SQL"
829
        rset = RadioSetting("pmg/0.bsmsql", sx, rx)
830
        txrx.append(rset)
831

    
832
        rx = RadioSettingValueBoolean(bool(_pmg[0].vhfaip))
833
        sx = "VHF band AIP"
834
        rset = RadioSetting("pmg/0.vhfaip", sx, rx)
835
        txrx.append(rset)
836

    
837
        rx = RadioSettingValueBoolean(bool(_pmg[0].uhfaip))
838
        sx = "UHF band AIP"
839
        rset = RadioSetting("pmg/0.uhfaip", sx, rx)
840
        txrx.append(rset)
841

    
842
        opts = ["High", "Medium", "Low"]
843
        rx = RadioSettingValueList(opts, opts[_blk1.micsens])
844
        sx = "Microphone sensitivity (gain)"
845
        rset = RadioSetting("block1.micsens", sx, rx)
846
        txrx.append(rset)
847

    
848
        opts = ["3", "5", "10"]
849
        rx = RadioSettingValueList(opts, opts[_pmg[0].tot])
850
        sx = "Time-Out timer (Mins)"
851
        rset = RadioSetting("pmg/0.tot", sx, rx)
852
        #  rset.set_apply_callback(_val_list, opts, _pmg[0], "tot")
853
        txrx.append(rset)
854

    
855
        rx = RadioSettingValueBoolean(bool(_pmg[0].wxalerta))
856
        sx = "WX Alert A-band"
857
        rset = RadioSetting("pmg/0.wxalerta", sx, rx)
858
        txrx.append(rset)
859

    
860
        rx = RadioSettingValueBoolean(bool(_pmg[0].wxalertb))
861
        sx = "WX Alert B-band"
862
        rset = RadioSetting("pmg/0.wxalertb", sx, rx)
863
        txrx.append(rset)
864

    
865
        opts = ["Off", "15", "30", "60"]
866
        rx = RadioSettingValueList(opts, opts[_pmg[0].wxscntm])
867
        sx = "WX alert scan memory time (Mins)"
868
        rset = RadioSetting("pmg/0.wxscntm", sx, rx)
869
        rset.set_apply_callback(_val_list, opts, _pmg[0], "wxscntm")
870
        txrx.append(rset)
871

    
872
        # ===== DTMF GROUP =====
873
        rx = RadioSettingValueBoolean(bool(_pmg[0].dtmfhld))
874
        sx = "DTMF hold"
875
        rset = RadioSetting("pmg/0.dtmfhld", sx, rx)
876
        dtmf.append(rset)
877

    
878
        opts = ["100", "250", "500", "750", "1000", "1500", "2000"]
879
        rx = RadioSettingValueList(opts, opts[_pmg[0].dtmfpau])
880
        sx = "DTMF pause duration (mS)"
881
        rset = RadioSetting("pmg/0.dtmfpau", sx, rx)
882
        rset.set_apply_callback(_val_list, opts, _pmg[0], "dtmfpau")
883
        dtmf.append(rset)
884

    
885
        opts = ["Fast", "Slow"]
886
        rx = RadioSettingValueList(opts, opts[_pmg[0].dtmfspd])
887
        sx = "DTMF speed"
888
        rset = RadioSetting("pmg/0.dtmfspd", sx, rx)
889
        rset.set_apply_callback(_val_list, opts, _pmg[0], "dtmfspd")
890
        dtmf.append(rset)
891

    
892
        for mx in range(0, 10):
893
            csx = _char_to_str(_dtmn[mx].id).strip()
894
            rx = RadioSettingValueString(0, 8, csx)
895
            sx = "DTMF %i Name (8 chars)" % mx
896
            rset = RadioSetting("dtmn.id/%d" % mx, sx, rx)
897
            rset.set_apply_callback(_pad_str, 8, chr(255), _dtmn, "id", mx)
898
            dtmf.append(rset)
899

    
900
            csx = _char_to_str(_dtmc[mx].code).strip()
901
            rx = RadioSettingValueString(0, 16, csx)
902
            sx = "    Code %i (16 chars)" % mx
903
            rset = RadioSetting("dtmc.code/%d" % mx, sx, rx)
904
            rset.set_apply_callback(_pad_str, 16, chr(255), _dtmc, "code", mx)
905
            dtmf.append(rset)
906

    
907
        # ===== MEMORY GROUP =====
908
        opts = ["All Bands", "Current Band"]
909
        rx = RadioSettingValueList(opts, opts[_pmg[0].recall])
910
        sx = "Memory recall method"
911
        rset = RadioSetting("pmg/0.recall", sx, rx)
912
        rset.set_apply_callback(_val_list, opts, _pmg[0], "recall")
913
        memz.append(rset)
914

    
915
        rx = RadioSettingValueString(0, 10, _char_to_str(_pmg[0].memgrplk))
916
        sx = "Group link"
917
        rset = RadioSetting("pmg/0.memgrplk", sx, rx)
918
        memz.append(rset)
919

    
920
        opts = ["Fast", "Slow"]
921
        rx = RadioSettingValueList(opts, opts[_pmg[0].eclnkspd])
922
        sx = "Echolink speed"
923
        rset = RadioSetting("pmg/0.eclnkspd", sx, rx)
924
        rset.set_apply_callback(_val_list, opts, _pmg[0], "eclnkspd")
925
        memz.append(rset)
926

    
927
        rx = RadioSettingValueBoolean(bool(_blk1.dspmemch))
928
        sx = "Display memory channel number"
929
        rset = RadioSetting("block1.dspmemch", sx, rx)
930
        memz.append(rset)
931

    
932
        # ===== REPEATER GROUP =====
933
        rx = RadioSettingValueBoolean(bool(_pmg[0].rptr1750))
934
        sx = "1750 Hz transmit hold"
935
        rset = RadioSetting("pmg/0.rptr1750", sx, rx)
936
        rptr.append(rset)
937

    
938
        rx = RadioSettingValueBoolean(bool(_pmg[0].rptrofst))
939
        sx = "Auto repeater offset"
940
        rset = RadioSetting("pmg/0.rptrofst", sx, rx)
941
        rptr.append(rset)
942

    
943
        opts = ["Cross Band", "TX:A-Band / RX:B-Band", "RX:A-Band / TX:B-Band"]
944
        rx = RadioSettingValueList(opts, opts[_blk1.rptrmode])
945
        sx = "Repeater Mode"
946
        rset = RadioSetting("block1.rptrmode", sx, rx)
947
        rset.set_apply_callback(_val_list, opts, _blk1, "rptrmode")
948
        rptr.append(rset)
949

    
950
        opts = ["Off", "Morse", "Voice"]
951
        rx = RadioSettingValueList(opts, opts[_blk1.rptridx])
952
        sx = "Repeater ID transmit"
953
        rset = RadioSetting("block1.rptridx", sx, rx)
954
        rset.set_apply_callback(_val_list, opts, _blk1, "rptridx")
955
        rptr.append(rset)
956

    
957
        rx = RadioSettingValueString(0, 12, _char_to_str(_blk1a.rptrid))
958
        sx = "Repeater ID"
959
        rset = RadioSetting("block1a.rptrid", sx, rx)
960
        rptr.append(rset)
961

    
962
        rx = RadioSettingValueBoolean(bool(_blk1.rptrhold))
963
        sx = "Repeater transmit hold"
964
        rset = RadioSetting("block1.rptrhold", sx, rx)
965
        rptr.append(rset)
966

    
967
        # ===== Prog VFO Group =============
968
        for mx in range(0, 10):
969
            # Raw freq is 0.1 Mhz resolution
970
            vfx = int(_pmg[0].progvfo[mx].blow) / mhz1
971
            if vfx == 0:
972
                vfx = 118
973
            rx = RadioSettingValueFloat(118.0, 1299.9, vfx, 0.005, 3)
974
            sx = "VFO-%i Low Limit (MHz)" % mx
975
            rset = RadioSetting("pmg/0.progvfo/%d.blow" % mx, sx, rx)
976
            rset.set_apply_callback(_mhz_val, _pmg, "progvfo.blow", 0, mx)
977
            pvfo.append(rset)
978

    
979
            vfx = int(_pmg[0].progvfo[mx].bhigh) / mhz1
980
            if vfx == 0:
981
                vfx = 118
982
            rx = RadioSettingValueFloat(118.0, 1300.0, vfx, 0.005, 3)
983
            sx = "   VFO-%i High Limit (MHz)" % mx
984
            rset = RadioSetting("pmg/0.progvfo/%d.bhigh" % mx, sx, rx)
985
            rset.set_apply_callback(_mhz_val, _pmg, "progvfo.bhigh", 0, mx)
986
            pvfo.append(rset)
987

    
988
        # ===== PFK GROUP =====
989
        opts = ["WX CH", "FRQ.BAND", "CTRL", "MONITOR", "VGS", "VOICE",
990
                "GROUP UP", "MENU", "MUTE", "SHIFT", "DUAL", "M>V",
991
                "1750 Tone"]
992
        rx = RadioSettingValueList(opts, opts[_pmg[0].pf1key])
993
        sx = "Front panel PF1 key"
994
        rset = RadioSetting("pmg/0.pf1key", sx, rx)
995
        rset.set_apply_callback(_val_list, opts, _pmg[0], "pf1key")
996
        pfk.append(rset)
997

    
998
        rx = RadioSettingValueList(opts, opts[_pmg[0].pf2key])
999
        sx = "Front panel PF2 key"
1000
        rset = RadioSetting("pmg/0.pf2key", sx, rx)
1001
        rset.set_apply_callback(_val_list, opts, _pmg[0], "pf2key")
1002
        pfk.append(rset)
1003

    
1004
        opts = ["WX CH", "FRQ.BAND", "CTRL", "MONITOR", "VGS", "VOICE",
1005
                "GROUP UP", "MENU", "MUTE", "SHIFT", "DUAL", "M>V",
1006
                "VFO", "MR", "CALL", "MHz", "TONE", "REV", "LOW",
1007
                "LOCK", "A/B", "ENTER", "1750 Tone", "M.LIST",
1008
                "S.LIST", "MSG.NEW", "REPLY", "POS", "P.MONI",
1009
                "BEACON", "DX", "WX"]
1010
        rx = RadioSettingValueList(opts, opts[_pmg[0].micpf1])
1011
        sx = "Microphone PF1 key"
1012
        rset = RadioSetting("pmg/0.micpf1", sx, rx)
1013
        rset.set_apply_callback(_val_list, opts, _pmg[0], "micpf1")
1014
        pfk.append(rset)
1015

    
1016
        rx = RadioSettingValueList(opts, opts[_pmg[0].micpf2])
1017
        sx = "Microphone PF2 key"
1018
        rset = RadioSetting("pmg/0.micpf2", sx, rx)
1019
        rset.set_apply_callback(_val_list, opts, _pmg[0], "micpf2")
1020
        pfk.append(rset)
1021

    
1022
        rx = RadioSettingValueList(opts, opts[_pmg[0].micpf3])
1023
        sx = "Microphone PF3 key"
1024
        rset = RadioSetting("pmg/0.micpf3", sx, rx)
1025
        rset.set_apply_callback(_val_list, opts, _pmg[0], "micpf3")
1026
        pfk.append(rset)
1027

    
1028
        rx = RadioSettingValueList(opts, opts[_pmg[0].micpf4])
1029
        sx = "Microphone PF4 key"
1030
        rset = RadioSetting("pmg/0.micpf4", sx, rx)
1031
        rset.set_apply_callback(_val_list, opts, _pmg[0], "micpf4")
1032
        pfk.append(rset)
1033

    
1034
        # ===== BMSK GROUP =====
1035
        rx = RadioSettingValueBoolean(bool(_pmg[0].abnd118))
1036
        sx = "A/Left: 118Mhz Band"
1037
        rset = RadioSetting("pmg/0.abnd118", sx, rx)
1038
        bmsk.append(rset)
1039

    
1040
        rx = RadioSettingValueBoolean(bool(_pmg[0].abnd144))
1041
        sx = "A/Left: 144Mhz Band"
1042
        rset = RadioSetting("pmg/0.abnd144", sx, rx)
1043
        bmsk.append(rset)
1044

    
1045
        rx = RadioSettingValueBoolean(bool(_pmg[0].abnd220))
1046
        sx = "A/Left: 220Mhz Band"
1047
        rset = RadioSetting("pmg/0.abnd220", sx, rx)
1048
        bmsk.append(rset)
1049

    
1050
        rx = RadioSettingValueBoolean(bool(_pmg[0].abnd300))
1051
        sx = "A/Left: 300Mhz Band"
1052
        rset = RadioSetting("pmg/0.abnd300", sx, rx)
1053
        bmsk.append(rset)
1054

    
1055
        rx = RadioSettingValueBoolean(bool(_pmg[0].abnd430))
1056
        sx = "A/Left: 430Mhz Band"
1057
        rset = RadioSetting("pmg/0.abnd430", sx, rx)
1058
        bmsk.append(rset)
1059

    
1060
        rx = RadioSettingValueBoolean(bool(_pmg[0].bbnd144))
1061
        sx = "B/Right: 144Mhz Band"
1062
        rset = RadioSetting("pmg/0.bbnd144", sx, rx)
1063
        bmsk.append(rset)
1064

    
1065
        rx = RadioSettingValueBoolean(bool(_pmg[0].bbnd220))
1066
        sx = "B/Right: 220Mhz Band"
1067
        rset = RadioSetting("pmg/0.bbnd220", sx, rx)
1068
        bmsk.append(rset)
1069

    
1070
        rx = RadioSettingValueBoolean(bool(_pmg[0].bbnd300))
1071
        sx = "B/Right: 300Mhz Band"
1072
        rset = RadioSetting("pmg/0.bbnd300", sx, rx)
1073
        bmsk.append(rset)
1074

    
1075
        rx = RadioSettingValueBoolean(bool(_pmg[0].bbnd430))
1076
        sx = "B/Right: 430Mhz Band"
1077
        rset = RadioSetting("pmg/0.bbnd430", sx, rx)
1078
        bmsk.append(rset)
1079

    
1080
        rx = RadioSettingValueBoolean(bool(_pmg[0].bbnd800))
1081
        sx = "B/Right: 800Mhz Band"
1082
        rset = RadioSetting("pmg/0.bbnd800", sx, rx)
1083
        bmsk.append(rset)
1084

    
1085
        # ===== Sky command Group =============
1086
        rx = RadioSettingValueString(0, 10, _char_to_str(_skyc.cmdr))
1087
        sx = "Commandr call sign"
1088
        rset = RadioSetting("skycmd.cmdr", sx, rx)
1089
        rset.set_apply_callback(_pad_str, 10, chr(0), _skyc, "cmdr")
1090
        skyk.append(rset)
1091

    
1092
        rx = RadioSettingValueString(0, 10, _char_to_str(_skyc.tptr))
1093
        sx = "Transporter call sign"
1094
        rset = RadioSetting("skycmd.tptr", sx, rx)
1095
        rset.set_apply_callback(_pad_str, 10, chr(0), _skyc, "tptr")
1096
        skyk.append(rset)
1097

    
1098
        opts = []
1099
        for val in TMD710_TONES:
1100
            opts.append(str(val))
1101
        rx = RadioSettingValueList(opts, opts[_skyc.skytone])
1102
        sx = "Tone frequency"
1103
        rset = RadioSetting("skycmd.skytone", sx, rx)
1104
        rset.set_apply_callback(_val_list, opts, _skyc, "skytone")
1105
        skyk.append(rset)
1106

    
1107
        # ===== PM MEMORY GROUP =====
1108
        """ These 5 blocks of 512 bytes are repeats of the major settings """
1109
        # Only showing limited settings for now...
1110
        _pmn = self._memobj.pm_name
1111
        for ix in range(1, 6):
1112
            nx = ix - 1          # Names are [0-4]
1113
            rx = RadioSettingValueString(0, 16, _char_to_str(_pmn[nx].pmname))
1114
            sx = "PM Group %i Name" % ix
1115
            rset = RadioSetting("pm_name/%i.pmname" % nx, sx, rx)
1116
            rset.set_apply_callback(_pad_str, 16, chr(0xff), _pmn,
1117
                                    "pmname", nx)
1118
            pmm.append(rset)
1119

    
1120
            rx = RadioSettingValueString(0, 8, _char_to_str(_pmg[ix].pwron))
1121
            sx = "-   Power-On Message"
1122
            rset = RadioSetting("pmg/%i.pwron" % ix, sx, rx)
1123
            rset.set_apply_callback(_pad_str, 8, chr(0xff), _pmg, "pwron", ix)
1124
            pmm.append(rset)
1125

    
1126
            opts = ["VFO", "Mem Recall"]
1127
            rx = RadioSettingValueList(opts, opts[_pmg[ix].a_mr])
1128
            sx = "-   A: Left Side VFO/MR"
1129
            rset = RadioSetting("pmg/%i.a_mr" % ix, sx, rx)
1130
            rset.set_apply_callback(_val_list, opts, _pmg[ix], "a_mr")
1131
            pmm.append(rset)
1132

    
1133
            rx = RadioSettingValueInteger(0, 999, _pmg[ix].a_chn)
1134
            sx = "-   A: Left Side MR Channel"
1135
            rset = RadioSetting("pmg/%i.a_chn" % ix, sx, rx)
1136
            pmm.append(rset)
1137

    
1138
            rx = RadioSettingValueList(opts, opts[_pmg[ix].b_mr])
1139
            sx = "-   B: Right Side VFO/MR"
1140
            rset = RadioSetting("pmg/%i.b_mr" % ix, sx, rx)
1141
            rset.set_apply_callback(_val_list, opts, _pmg[ix], "b_mr")
1142
            pmm.append(rset)
1143

    
1144
            rx = RadioSettingValueInteger(0, 999, _pmg[ix].b_chn)
1145
            sx = "-   B: Right Side MR Channel"
1146
            rset = RadioSetting("pmg/%i.b_chn" % ix, sx, rx)
1147
            pmm.append(rset)
1148

    
1149
            rx = RadioSettingValueInteger(0, 8, _pmg[ix].bright)
1150
            sx = "-   Brightness level"
1151
            rset = RadioSetting("pmg/%i.bright" % ix, sx, rx)
1152
            pmm.append(rset)
1153

    
1154
            opts = ["Amber", "Green"]
1155
            rx = RadioSettingValueList(opts, opts[_pmg[ix].bkltclr])
1156
            sx = "-   Backlight color"
1157
            rset = RadioSetting("pmg/%i.bkltclr" % ix, sx, rx)
1158
            rset.set_apply_callback(_val_list, opts, _pmg[ix], "bkltclr")
1159
            pmm.append(rset)
1160

    
1161
            val = _pmg[ix].bkltcont + 1
1162
            rx = RadioSettingValueInteger(1, 16, val)
1163
            sx = "-   Contrast level"
1164
            rset = RadioSetting("pmg/%i.bkltcont" % ix, sx, rx)
1165
            rset.set_apply_callback(_adjraw, _pmg[ix], "bkltcont", -1)
1166
            pmm.append(rset)
1167

    
1168
            opts = ["Positive", "Negative"]
1169
            rx = RadioSettingValueList(opts, opts[_pmg[ix].dsprev])
1170
            sx = "-   Color mode"
1171
            rset = RadioSetting("pmg/%i.dsprev" % ix, sx, rx)
1172
            rset.set_apply_callback(_val_list, opts, _pmg[ix], "dsprev")
1173
            pmm.append(rset)
1174

    
1175
            rx = RadioSettingValueBoolean(bool(_pmg[ix].beepon))
1176
            sx = "-   Beep On"
1177
            rset = RadioSetting("pmg/%i.beepon" % ix, sx, rx)
1178
            pmm.append(rset)
1179

    
1180
            val = _pmg[ix].beepvol + 1     # 1-7 downloads as 0-6
1181
            rx = RadioSettingValueInteger(1, 7, val)
1182
            sx = "-   Beep volume (1 - 7)"
1183
            rset = RadioSetting("pmg/%i.beepvol" % ix, sx, rx)
1184
            rset.set_apply_callback(_adjraw, _pmg[ix], "beepvol", -1)
1185
            pmm.append(rset)
1186

    
1187
            rx = RadioSettingValueBoolean(bool(_pmg[ix].autopm))
1188
            sx = "-   Auto PM Store"
1189
            rset = RadioSetting("pmg/%i.autopm" % ix, sx, rx)
1190
            pmm.append(rset)
1191

    
1192
            opts = ["A: Left", "B: Right"]
1193
            rx = RadioSettingValueList(opts, opts[_pmg[ix].txband])
1194
            sx = "-   X Side (PTT)"
1195
            rset = RadioSetting("pmg/%i.txband" % ix, sx, rx)
1196
            rset.set_apply_callback(_val_list, opts, _pmg[ix], "txband")
1197
            pmm.append(rset)
1198

    
1199
            opts = ["High (50W)", "Medium (10W)", "Low (5W)"]
1200
            rx = RadioSettingValueList(opts, opts[_pmg[ix].a_pwr])
1201
            sx = "-   A-Band transmit power"
1202
            rset = RadioSetting("pmg/%i.a_pwr" % ix, sx, rx)
1203
            rset.set_apply_callback(_val_list, opts, _pmg[ix], "a_pwr")
1204
            pmm.append(rset)
1205

    
1206
            rx = RadioSettingValueList(opts, opts[_pmg[ix].b_pwr])
1207
            sx = "-   B-Band transmit power"
1208
            rset = RadioSetting("pmg/%i.b_pwr" % ix, sx, rx)
1209
            rset.set_apply_callback(_val_list, opts, _pmg[ix], "b_pwr")
1210
            pmm.append(rset)
1211

    
1212
        return group       # END get_settings()
1213

    
1214
    def set_settings(self, settings):
1215
        """ Convert UI modified changes into mem_format values """
1216
        blks = (self._memobj.block1, self._memobj.block1a,
1217
                self._memobj.pmg, self._memobj.pm_name)
1218
        for _settings in blks:
1219
            for element in settings:
1220
                if not isinstance(element, RadioSetting):
1221
                    self.set_settings(element)
1222
                    continue
1223
                else:
1224
                    try:
1225
                        name = element.get_name()
1226
                        if "." in name:
1227
                            bits = name.split(".")
1228
                            obj = self._memobj
1229
                            for bit in bits[:-1]:
1230
                                if "/" in bit:
1231
                                    bit, index = bit.split("/", 1)
1232
                                    index = int(index)
1233
                                    obj = getattr(obj, bit)[index]
1234
                                else:
1235
                                    obj = getattr(obj, bit)
1236
                            setting = bits[-1]
1237
                        else:
1238
                            obj = _settings
1239
                            setting = element.get_name()
1240

    
1241
                        if element.has_apply_callback():
1242
                            LOG.debug("Using apply callback")
1243
                            element.run_apply_callback()
1244
                        elif element.value.get_mutable():
1245
                            LOG.debug("Setting %s = %s"
1246
                                      % (setting, element.value))
1247
                            setattr(obj, setting, element.value)
1248
                    except Exception as e:
1249
                        LOG.debug(element.get_name())
1250
                        raise
1251
        return
1252

    
1253
    @classmethod
1254
    def match_model(cls, fdata, fyle):
1255
        """ Included to prevent 'File > New' error """
1256
        return False
1257

    
1258

    
1259
if HAS_FUTURE:   # Only register drivers if environment is PY3 compliant
1260
    @directory.register
1261
    class KenwoodTMD710Radio(KenwoodTMx710Radio):
1262
        """ Kenwood TM-D710 VHF/UHF/APRS Radio model. """
1263
        VENDOR = "Kenwood"
1264
        MODEL = "TM-D710_CloneMode"
1265
        SHORT = ""       # Quick model code
1266

    
1267
        _num_blocks = 3
1268
        _num_packets = [0x9c, 1, 1]
1269

    
1270
        MEM_FORMAT = """
1271
        struct chns {            // 16 bytes channel structure
1272
        ul32  rxfreq;
1273
        u8   tstep;
1274
        u8   mode;
1275
        u8   tmode:4,
1276
            duplex:4;         // 4 = split
1277
        u8   rtone;
1278
        u8   ctone;
1279
        u8   dtcs;
1280
        ul32 offset;          // or Split mode TX freq
1281
        u8   splitstep;
1282
        u8   cross;           // not used
1283
        };
1284

    
1285
        struct pm_grp {         // 512 bytes per group
1286
        u8   unk0200;
1287
        u8   a_mr;
1288
        u8   unk0202;
1289
        u8   unk0203;
1290
        u8   unk0204;
1291
        u8   unk0205;
1292
        u8   unk0206;
1293
        u8   a_pwr;
1294
        u8   wxalerta;
1295
        u8   asmsql;
1296
        u8   a_chn;
1297
        u8   unk020b;
1298
        u8   unk020c;
1299
        u8   b_mr;
1300
        u8   unk020e;
1301
        u8   unk020f;
1302
        u8   unk0210;
1303
        u8   unk0211;
1304
        u8   unk0212;
1305
        u8   b_pwr;
1306
        u8   wxalertb;
1307
        u8   bsmsql;
1308
        u8   b_chn;
1309
        u8   unk0217;
1310
        u8   unk0218;
1311
        u8   unk0219;
1312
        u8   unk021a;
1313
        u8   unk021b;
1314
        u8   unk021c;
1315
        u8   unk021d;
1316
        u8   unk021e;
1317
        u8   unk021f;
1318
        u8   unk0220;
1319
        u8   unk0221;
1320
        u8   unk0222;
1321
        u8   unk0223;
1322
        u8   unk0224;
1323
        u8   unk0225;
1324
        u8   unk0226;
1325
        u8   unk0227;
1326
        u8   unk0228;
1327
        u8   unk0229;
1328
        u8   unk022a;
1329
        u8   unk022b;
1330
        u8   unk022c;
1331
        u8   unk022d;
1332
        u8   unk022e;
1333
        u8   unk022f;
1334
        u8   unk0230;
1335
        u8   unk0231;
1336
        u8   sqclogic;
1337
        u8   txband;
1338
        u8   single;
1339
        u8   unk0235;
1340
        u8   mute;
1341
        u8   unk0237;
1342
        u8   unk0238;
1343
        u8   unk0239;
1344
        u8   unk0237a;
1345
        u8   unk023b;
1346
        u8   unk023c;
1347
        u8   unk023d;
1348
        u8   unk023e;
1349
        u8   unk023f;
1350
        struct chns vfo[10];         // 0x0240 - 0x02df
1351
        char pwron[8];
1352
        u8   unk02e8;
1353
        u8   unk02e9;
1354
        u8   unk02ea;
1355
        u8   unk02eb;
1356
        u8   unk02ec;
1357
        u8   unk02ed;
1358
        u8   unk02ee;
1359
        u8   unk02ef;
1360
        char memgrplk[10];
1361
        u8   unk02fa;
1362
        u8   unk02fb;
1363
        u8   unk02fc;
1364
        u8   unk02fd;
1365
        u8   unk02fe;
1366
        u8   unk02ff;
1367
        struct {
1368
            ul32 blow;
1369
            ul32 bhigh;
1370
        } progvfo[10];
1371
        u8   beepon;
1372
        u8   beepvol;
1373
        u8   extspkr;
1374
        u8   ance;
1375
        u8   lang;
1376
        u8   vcvol;
1377
        u8   vcspd;
1378
        u8   pbkrpt;
1379
        u8   pbkint;
1380
        u8   cntrec;
1381
        u8   vhfaip;
1382
        u8   uhfaip;
1383
        u8   ssqlhu;
1384
        u8   mutehu;
1385
        u8   beatshft;
1386
        u8   tot;
1387
        u8   recall;
1388
        u8   eclnkspd;
1389
        u8   dtmfhld;
1390
        u8   dtmfspd;
1391
        u8   dtmfpau;
1392
        u8   dtmflck;
1393
        u8   rptrofst;
1394
        u8   rptr1750;
1395
        u8   bright;
1396
        u8   autobri;
1397
        u8   bkltclr;
1398
        u8   pf1key;
1399
        u8   pf2key;
1400
        u8   micpf1;
1401
        u8   micpf2;
1402
        u8   micpf3;
1403
        u8   micpf4;
1404
        u8   miclck;
1405
        u8   unk0372;
1406
        u8   scnrsm;
1407
        u8   apo;
1408
        u8   extband;
1409
        u8   extbaud;
1410
        u8   sqcsrc;
1411
        u8   autopm;
1412
        u8   dispbar;
1413
        u8   unk037a;
1414
        u8   bkltcont;
1415
        u8   dsprev;
1416
        u8   vsmode;
1417
        u8   intband;
1418
        u8   wxscntm;
1419
        u8   scntot;
1420
        u8   scncot;
1421
        u8   unk0382;
1422
        u8   unk0383;
1423
        u8   unk0384;
1424
        u8   unk0385;
1425
        u8   unk0386;
1426
        u8   unk0387;
1427
        u8   unk0388;
1428
        u8   unk0389;
1429
        u8   unk038a;
1430
        u8   unk038b;
1431
        u8   unk038c;
1432
        u8   unk038d;
1433
        u8   unk038e;
1434
        u8   unk038f;
1435
        u8   abnd118;
1436
        u8   abnd144;
1437
        u8   abnd220;
1438
        u8   abnd300;
1439
        u8   abnd430;
1440
        u8   bbnd144;
1441
        u8   bbnd220;
1442
        u8   bbnd300;
1443
        u8   bbnd430;
1444
        u8   bbnd800;
1445
        u8   unk039a;
1446
        u8   unk039b;
1447
        u8   unk039c;
1448
        u8   unk039d;
1449
        u8   unk039e;
1450
        u8   unk039f;
1451
        u8   unk03a0[96];       // to 0x03ff
1452
        };                        // end of struct pm
1453

    
1454
        #seekto 0x0000;         // block1: x000 - x023f
1455
        struct {
1456
        u8   unk000[16];
1457
        u8   unk010;
1458
        u8   unk011;
1459
        char unk012[3];
1460
        u8   ansbck;
1461
        u8   pmrecall;            // 0x0016
1462
        u8   pnlklk;
1463
        u8   dspmemch;
1464
        u8   m10mz;
1465
        u8   micsens;
1466
        u8   opband;
1467
        u8   unk01c;
1468
        u8   rptrmode;
1469
        u8   rptrhold;
1470
        u8   rptridx;
1471
        u8   unk020;
1472
        u8   pcbaud;
1473
        u8   unk022;
1474
        u8   pwdon;               //  0x0023
1475
        u8   unk024;
1476
        u8   unk025;
1477
        u8   unk026;
1478
        u8   unk027;
1479
        u8   unk028;
1480
        u8   unk029;
1481
        char pswd[6];             // 0x023a - 23f
1482
        } block1;
1483

    
1484
        #seekto 0x0030;
1485
        struct {
1486
        char code[16];            // @ 0x0030
1487
        } dtmc[10];
1488

    
1489
        struct {
1490
        char id[8];               // 0x00d0 - 0x011f
1491
        } dtmn[10];
1492

    
1493
        struct {                    // block1a: 0x0120 - 0x023f
1494
        u8   unk0120;
1495
        u8   unk0121;
1496
        u8   unk0122[78];
1497
        char rptrid[12];          // 0x0170 - 017b
1498
        u8   unk017c;
1499
        u8   unk017d;
1500
        u8   unk017e;
1501
        u8   unk017f;
1502
        u8   unk0180[128];        // 0x0180 - 0x01ff
1503
        } block1a;
1504

    
1505
        struct pm_grp pmg[6];       // 0x0200 - 0x0dff
1506

    
1507
        #seekto 0x0e00;
1508
        struct {
1509
        u8   band;
1510
        u8   skip;
1511
        } chmap[1030];              // to 0x0160b
1512

    
1513
        #seekto 0x01700;            // 0x01700 - 0x0575f
1514
        struct chns ch_mem[1030];   // 0-999 MR and 1000 -1029 Specials
1515

    
1516
        #seekto 0x05760;
1517
        struct chns call[2];
1518

    
1519
        #seekto 0x05800;
1520
        struct {
1521
        char name[8];
1522
        } ch_nam[1020];         // ends @ 0x07e0
1523

    
1524
        #seekto 0x077e0;        // 0x077e0 - 0x07830
1525
        struct {
1526
        char name[8];
1527
        } wxnam[10];
1528

    
1529
        #seekto 0x07da0;
1530
        struct {
1531
        char pmname[16];
1532
        } pm_name[5];
1533

    
1534
        #seekto 0x07df0;
1535
        struct {
1536
        char comnt[32];
1537
        } mcpcom;
1538

    
1539
        #seekto 0x08660;
1540
        struct {
1541
        char cmdr[10];
1542
        char tptr[10];
1543
        u8  skytone;          // 0x08674
1544
        } skycmd;
1545
                            // data stops at 0x09b98
1546
        """
1547

    
1548
        def _read_mem(radio):
1549
            """ Load the memory map """
1550
            global BAUD
1551
            status = chirp_common.Status()
1552
            status.cur = 0
1553
            val = 0
1554
            for mx in range(0, radio._num_blocks):
1555
                val += radio._num_packets[mx]
1556
            status.max = val
1557
            status.msg = "Reading %i packets" % val
1558
            radio.status_fn(status)
1559

    
1560
            data = ""
1561

    
1562
            radio.pipe.baudrate = BAUD
1563
            cmc = b"0M PROGRAM" + TERM
1564
            resp0 = _command(radio.pipe, cmc, 3, W8S)
1565
            junk = radio.pipe.read(16)       # flushit
1566
            for bkx in range(0, 0x09c):
1567
                if bkx != 0x07f:            # Skip block 7f !!??
1568
                    cmc = struct.pack('>cHB', b'R', bkx << 8, 0)
1569
                    resp0 = _command(radio.pipe, cmc, 260, W8S)
1570
                    junk = _command(radio.pipe, ACK, 1, W8S)
1571
                    if len(resp0) < 260:
1572
                        junk = _command(radio.pipe, "E", 2, W8S)
1573
                        sx = "Block 0x%x read error: " % bkx
1574
                        sx += "Got %i bytes, expected 260." % len(resp0)
1575
                        LOG.error(sx)
1576
                        sx = "Block read error! Check debug.log"
1577
                        raise errors.RadioError(sx)
1578
                    if bkx == 0:   # 1st packet of 1st block
1579
                        mht = resp0[4:7]   # [57 00 00 00] 03 4b 01 ff ff ...
1580
                        data = resp0[5:6]  # 2nd byte (4b) replaces 1st
1581
                        data += resp0[5:]  # then bytes 2 on (4b 4b 01 ff ...)
1582
                    else:
1583
                        data += resp0[4:]       # skip cmd echo
1584
                    _update_status(radio, status)        # UI Update
1585
            cmc = struct.pack('>cHB', b'R', 0xFEF0, 0x10)
1586
            resp0 = _command(radio.pipe, cmc, 0x014, W8S)
1587
            data += resp0[4:]
1588
            junk = _command(radio.pipe, ACK, 1, W8S)
1589
            _update_status(radio, status)
1590
            cmc = struct.pack('>cHB', b'R', 0xFF00, 0x90)
1591
            resp0 = _command(radio.pipe, cmc, 0x094, W8S)
1592
            data += resp0[4:]
1593
            junk = _command(radio.pipe, ACK, 1, W8S)
1594
            _update_status(radio, status)
1595
            # Exit Prog mode, no TERM
1596
            resp = _command(radio.pipe, "E", 2, W8S)     # Rtns 06 0d
1597
            radio.pipe.baudrate = BAUD
1598
            return data
1599

    
1600
        def _write_mem(radio):
1601
            """ PROG MCP Blocks Send """
1602
            global BAUD
1603
            # UI progress
1604
            status = chirp_common.Status()
1605
            status.cur = 0
1606
            val = 0
1607
            for mx in range(0, radio._num_blocks):
1608
                val += radio._num_packets[mx]
1609
            status.max = val
1610
            status.msg = "Writing %i packets" % val
1611
            radio.status_fn(status)
1612

    
1613
            imgadr = 0
1614
            radio.pipe.baudrate = BAUD
1615
            resp0 = _command(radio.pipe, "0M PROGRAM" + TERM, 3, W8S)
1616
            # Read block 0 magic header thingy, save it
1617
            cmc = "R" + chr(0) + chr(0) + chr(4)
1618
            resp0 = _command(radio.pipe, cmc, 8, W8S)
1619
            mht0 = resp0[4:]    # Expecting [57 00 00 04] 03 4b 01 ff
1620
            junk = _command(radio.pipe, ACK, 1, W8S)
1621
            cmc = "W" + chr(0) + chr(0) + chr(1) + chr(0x0ff)
1622
            junk = _command(radio.pipe, cmc, 1, W8S)     # responds ACK
1623
            cmc = "R" + chr(0x080) + chr(0) + chr(3)
1624
            resp = _command(radio.pipe, cmc, 7, W8S)   # [57 80 00 03] 00 33 00
1625
            mht1 = resp[4:]
1626
            junk = _command(radio.pipe, ACK, 1, W8S)
1627
            cmc = "W" + chr(0x080) + chr(0) + chr(1) + chr(0x0ff)
1628
            junk = _command(radio.pipe, cmc, 1, W8S)
1629
            imgadr = 4      # After 03 4b 01 ff
1630
            for bkx in range(0, radio._num_packets[0]):
1631
                cmc = "W" + chr(bkx) + chr(0) + chr(0)
1632
                imgstep = 256
1633
                if bkx == 0:
1634
                    imgstep = 0x0fc
1635
                    cmc = "W" + chr(0) + chr(4) + chr(imgstep)
1636
                    cmc += radio.get_mmap()[imgadr:imgadr + imgstep]
1637
                else:       # after first packet
1638
                    cmc += radio.get_mmap()[imgadr:imgadr + imgstep]
1639
                if bkx != 0x07f:        # don't send 7f !
1640
                    resp0 = _command(radio.pipe, cmc, 1, W8S)
1641
                    if resp0 != ACK:
1642
                        LOG.error("Packet 0x%x Write error, no ACK." % bkx)
1643
                        sx = "Radio failed to acknowledge upload packet!"
1644
                        raise errors.RadioError(sx)
1645
                    imgadr += imgstep
1646
                _update_status(radio, status)        # UI Update
1647
            # write fe and ff blocks
1648
            cmc = "W" + chr(0x0fe) + chr(0x0f0) + chr(16)
1649
            cmc += radio.get_mmap()[imgadr:imgadr + 16]
1650
            resp0 = _command(radio.pipe, cmc, 1, W8S)
1651
            if resp0 != ACK:
1652
                LOG.error("Packet 0xfe Write error, no ACK.")
1653
                sx = "Radio failed to acknowledge upload packet!"
1654
                raise errors.RadioError(sx)
1655
            imgadr += 16
1656
            cmc = "W" + chr(0x0ff) + chr(0) + chr(0x090)
1657
            cmc += radio.get_mmap()[imgadr:imgadr + 0x090]
1658
            resp0 = _command(radio.pipe, cmc, 1, W8S)
1659
            if resp0 != ACK:
1660
                LOG.error("Packet 0xff Write error, no ACK.")
1661
                sx = "Radio failed to acknowledge upload packet!"
1662
                raise errors.RadioError(sx)
1663
            # Write mht1
1664
            cmc = "W" + chr(0x080) + chr(0) + chr(3) + mht1
1665
            resp0 = _command(radio.pipe, cmc, 1, W8S)
1666
            if resp0 != ACK:
1667
                LOG.error("Mht1 Write error at 0x080 00 03 , no ACK.")
1668
                sx = "Radio failed to acknowledge upload packet!"
1669
                raise errors.RadioError(sx)
1670
            # and mht0
1671
            cmc = "W" + chr(0) + chr(0) + chr(4) + mht0
1672
            resp0 = _command(radio.pipe, cmc, 1, W8S)
1673
            if resp0 != ACK:
1674
                LOG.error("Mht0 Write error at 00 00 04 , no ACK.")
1675
                sx = "Radio failed to acknowledge upload packet!"
1676
                raise errors.RadioError(sx)
1677
            # Write E to Exit PROG mode
1678
            resp = _command(radio.pipe, "E", 2, W8S)
1679
            return
1680

    
1681
    @directory.register
1682
    class KenwoodTMD710GRadio(KenwoodTMx710Radio):
1683
        """ Kenwood TM-D710G VHF/UHF/GPS/APRS Radio model. """
1684
        VENDOR = "Kenwood"
1685
        MODEL = "TM-D710G_CloneMode"
1686
        SHORT = "G"       # Quick model code 1 for G
1687

    
1688
        _num_blocks = 2                # Only reading first 2, not GPS logs
1689
        _packet_size = [261, 261, 261]
1690
        _block_addr = [0, 0x100, 0x200]       # starting addr, each block
1691
        _num_packets = [0x7f, 0x0fe, 0x200]   # num packets per block, 0-based
1692

    
1693
        MEM_FORMAT = """
1694
        struct chns {            // 16 bytes channel structure
1695
        ul32  rxfreq;
1696
        u8   tstep;
1697
        u8   mode;
1698
        u8   tmode:4,
1699
            duplex:4;         // 4 = split
1700
        u8   rtone;
1701
        u8   ctone;
1702
        u8   dtcs;
1703
        u8   cross;
1704
        ul32 offset;          // or Split mode TX freq
1705
        u8   splitstep;
1706
        };
1707

    
1708
        struct pm_grp {         // 512 bytes per group
1709
        u8   unk0200;
1710
        u8   a_mr;
1711
        u8   unk0202;
1712
        u8   unk0203;
1713
        u8   unk0204;
1714
        u8   unk0205;
1715
        u8   unk0206;
1716
        u8   a_pwr;
1717
        u8   wxalerta;
1718
        u8   asmsql;
1719
        u8   a_chn;
1720
        u8   unk020b;
1721
        u8   unk020c;
1722
        u8   b_mr;
1723
        u8   unk020e;
1724
        u8   unk020f;
1725
        u8   unk0210;
1726
        u8   unk0211;
1727
        u8   unk0212;
1728
        u8   b_pwr;
1729
        u8   wxalertb;
1730
        u8   bsmsql;
1731
        u8   b_chn;
1732
        u8   unk0217;
1733
        u8   unk0218;
1734
        u8   unk0219;
1735
        u8   unk021a;
1736
        u8   unk021b;
1737
        u8   unk021c;
1738
        u8   unk021d;
1739
        u8   unk021e;
1740
        u8   unk021f;
1741
        u8   unk0220;
1742
        u8   unk0221;
1743
        u8   unk0222;
1744
        u8   unk0223;
1745
        u8   unk0224;
1746
        u8   unk0225;
1747
        u8   unk0226;
1748
        u8   unk0227;
1749
        u8   unk0228;
1750
        u8   unk0229;
1751
        u8   unk022a;
1752
        u8   unk022b;
1753
        u8   unk022c;
1754
        u8   unk022d;
1755
        u8   unk022e;
1756
        u8   unk022f;
1757
        u8   unk0230;
1758
        u8   unk0231;
1759
        u8   sqclogic;
1760
        u8   txband;
1761
        u8   single;
1762
        u8   unk0235;
1763
        u8   mute;
1764
        u8   unk0237;
1765
        u8   unk0238;
1766
        u8   unk0239;
1767
        u8   unk0237a;
1768
        u8   unk023b;
1769
        u8   unk023c;
1770
        u8   unk023d;
1771
        u8   unk023e;
1772
        u8   unk023f;
1773
        struct chns vfo[10];         // 0x0240 - 0x02df
1774
        char pwron[8];
1775
        u8   unk02e8;
1776
        u8   unk02e9;
1777
        u8   unk02ea;
1778
        u8   unk02eb;
1779
        u8   unk02ec;
1780
        u8   unk02ed;
1781
        u8   unk02ee;
1782
        u8   unk02ef;
1783
        char memgrplk[10];
1784
        u8   unk02fa;
1785
        u8   unk02fb;
1786
        u8   unk02fc;
1787
        u8   unk02fd;
1788
        u8   unk02fe;
1789
        u8   unk02ff;
1790
        struct {
1791
            ul32 blow;
1792
            ul32 bhigh;
1793
        } progvfo[10];
1794
        u8   beepon;
1795
        u8   beepvol;
1796
        u8   extspkr;
1797
        u8   ance;
1798
        u8   lang;
1799
        u8   vcvol;
1800
        u8   vcspd;
1801
        u8   pbkrpt;
1802
        u8   pbkint;
1803
        u8   cntrec;
1804
        u8   vhfaip;
1805
        u8   uhfaip;
1806
        u8   ssqlhu;
1807
        u8   mutehu;
1808
        u8   beatshft;
1809
        u8   tot;
1810
        u8   recall;
1811
        u8   eclnkspd;
1812
        u8   dtmfhld;
1813
        u8   dtmfspd;
1814
        u8   dtmfpau;
1815
        u8   dtmflck;
1816
        u8   rptrofst;
1817
        u8   rptr1750;
1818
        u8   bright;
1819
        u8   autobri;
1820
        u8   bkltclr;
1821
        u8   pf1key;
1822
        u8   pf2key;
1823
        u8   micpf1;
1824
        u8   micpf2;
1825
        u8   micpf3;
1826
        u8   micpf4;
1827
        u8   miclck;
1828
        u8   unk0372;
1829
        u8   scnrsm;
1830
        u8   apo;
1831
        u8   extband;
1832
        u8   extbaud;
1833
        u8   sqcsrc;
1834
        u8   autopm;
1835
        u8   dispbar;
1836
        u8   unk037a;
1837
        u8   bkltcont;
1838
        u8   dsprev;
1839
        u8   vsmode;
1840
        u8   intband;
1841
        u8   wxscntm;
1842
        u8   scntot;
1843
        u8   scncot;
1844
        u8   unk0382;
1845
        u8   unk0383;
1846
        u8   unk0384;
1847
        u8   unk0385;
1848
        u8   unk0386;
1849
        u8   unk0387;
1850
        u8   unk0388;
1851
        u8   unk0389;
1852
        u8   unk038a;
1853
        u8   unk038b;
1854
        u8   unk038c;
1855
        u8   unk038d;
1856
        u8   unk038e;
1857
        u8   unk038f;
1858
        u8   abnd118;
1859
        u8   abnd144;
1860
        u8   abnd220;
1861
        u8   abnd300;
1862
        u8   abnd430;
1863
        u8   bbnd144;
1864
        u8   bbnd220;
1865
        u8   bbnd300;
1866
        u8   bbnd430;
1867
        u8   bbnd800;
1868
        u8   unk039a;
1869
        u8   unk039b;
1870
        u8   unk039c;
1871
        u8   unk039d;
1872
        u8   unk039e;
1873
        u8   unk039f;
1874
        u8   unk03a0[96];       // to 0x03ff
1875
        };                        // end of struct pm
1876

    
1877
        #seekto 0x0000;         // block1: x000 - x023f
1878
        struct {
1879
        u8   unk000[16];
1880
        u8   unk010;
1881
        u8   unk011;
1882
        char unk012[3];
1883
        u8   ansbck;
1884
        u8   pmrecall;            // 0x0016
1885
        u8   pnlklk;
1886
        u8   dspmemch;
1887
        u8   m10mz;
1888
        u8   micsens;
1889
        u8   opband;
1890
        u8   unk01c;
1891
        u8   rptrmode;
1892
        u8   rptrhold;
1893
        u8   rptridx;
1894
        u8   unk020;
1895
        u8   pcbaud;
1896
        u8   unk022;
1897
        u8   pwdon;               //  0x0023
1898
        u8   unk024;
1899
        u8   unk025;
1900
        u8   unk026;
1901
        u8   unk027;
1902
        u8   unk028;
1903
        u8   unk029;
1904
        char pswd[6];             // 0x023a - 23f
1905
        } block1;
1906

    
1907
        #seekto 0x0030;
1908
        struct {
1909
        char code[16];            // @ 0x0030
1910
        } dtmc[10];
1911

    
1912
        struct {
1913
        char id[8];               // 0x00d0 - 0x011f
1914
        } dtmn[10];
1915

    
1916
        struct {                    // block1a: 0x0120 - 0x023f
1917
        u8   unk0120;
1918
        u8   unk0121;
1919
        u8   unk0122[78];
1920
        char rptrid[12];          // 0x0170 - 017b
1921
        u8   unk017c;
1922
        u8   unk017d;
1923
        u8   unk017e;
1924
        u8   unk017f;
1925
        u8   unk0180[128];        // 0x0180 - 0x01ff
1926
        } block1a;
1927

    
1928
        struct pm_grp pmg[6];       // 0x0200 - 0x0dff
1929

    
1930
        #seekto 0x0e00;
1931
        struct {
1932
        u8   band;
1933
        u8   skip;
1934
        } chmap[1030];              // to 0x0160b
1935

    
1936
        #seekto 0x01700;            // 0x01700 - 0x0575f
1937
        struct chns ch_mem[1030];   // 0-999 MR and 1000 -1029 Specials
1938

    
1939
        #seekto 0x058a0;
1940
        struct chns call[2];
1941

    
1942
        #seekto 0x05900;
1943
        struct {
1944
        char name[8];
1945
        } ch_nam[1020];         // ends @ 0x07840
1946

    
1947
        #seekto 0x078e0;        // 0x078e0 - 0x0792f
1948
        struct {
1949
        char name[8];
1950
        } wxnam[10];
1951

    
1952
        #seekto 0x07da0;
1953
        struct {
1954
        char pmname[16];
1955
        } pm_name[5];
1956

    
1957
        #seekto 0x07df0;
1958
        struct {
1959
        char comnt[32];
1960
        } mcpcom;
1961
                            // Block 1 ends @ 0x07eff
1962
                            // Block 2 starts @ 0x07f00
1963
        #seekto 0x08660;
1964
        struct {
1965
        char cmdr[10];
1966
        char tptr[10];
1967
        u8  skytone;          // 0x08674
1968
        } skycmd;
1969

    
1970
        #seekto 0x10ef0;
1971
        struct {
1972
        u8   bmp[1896];
1973
        u8   unk11658[8];     // 0x11658
1974
        char bmpfyl[64];      // 0x11660
1975
        u8   unk116a0[95];
1976
        u8   bmpon;           // 0x116ff
1977
        } bitmap;
1978

    
1979
                        // 2nd block ends @ 0x017cff
1980
        """
1981

    
1982
        def _make_command(self, cmd, addr, length, data=b''):
1983
            cmc = struct.pack('>IB', addr, length)
1984
            return cmd.encode() + cmc[1:] + data
1985

    
1986
        def _read_mem(radio):
1987
            """ Load the memory map """
1988
            global BAUD
1989
            status = chirp_common.Status()
1990
            status.cur = 0
1991
            val = 0
1992
            for mx in range(0, radio._num_blocks):
1993
                val += radio._num_packets[mx]
1994
            status.max = val
1995
            status.msg = "Reading %i packets" % val
1996
            radio.status_fn(status)
1997

    
1998
            data = b""
1999

    
2000
            radio.pipe.baudrate = BAUD
2001
            resp0 = radio.pipe.read(16)     # flush
2002
            cmc = b"0M PROGRAM" + TERM
2003
            resp0 = _command(radio.pipe, cmc, 3, W8S)
2004
            if resp0[:1] == "?":        # try once more
2005
                resp0 = _command(radio.pipe, cmc, 3, W8S)
2006
            radio.pipe.baudrate = 57600     # PROG mode is always 57.6
2007
            LOG.debug("Switching to 57600 baud download.")
2008
            junk = radio.pipe.read(1)       # trailing byte
2009
            for blkn in range(0, radio._num_blocks):
2010
                for bkx in range(0, radio._num_packets[blkn]):
2011
                    addr = (radio._block_addr[blkn] << 8) | (bkx << 8)
2012
                    resp0 = _command(radio.pipe,
2013
                                     radio._make_command('R', addr, 0),
2014
                                     radio._packet_size[blkn], W8S)
2015
                    if len(resp0) < radio._packet_size[blkn]:
2016
                        junk = _command(radio.pipe, b"E", 0, W8S)
2017
                        lb = len(resp0)
2018
                        xb = radio._packet_size[blkn]
2019
                        sx = "Block 0x%x, 0x%x read error: " % (blkn, bkx)
2020
                        sx += "Got %i bytes, expected %i." % (lb, xb)
2021
                        LOG.error(sx)
2022
                        sx = "Block read error! Check debug.log"
2023
                        raise errors.RadioError(sx)
2024
                    if blkn == 0 and bkx == 0:   # 1st packet of 1st block
2025
                        mht = resp0[5:9]   # Magic Header Thingy after cmd echo
2026
                        data += mht[0:1]
2027
                        data += b'\xff\xff\xff'
2028
                        data += resp0[9:]
2029
                    else:
2030
                        data += resp0[5:]       # skip cmd echo
2031
                    _update_status(radio, status)        # UI Update
2032
            # Exit Prog mode, no TERM
2033
            resp = _command(radio.pipe, b"E", 0, W8S)
2034
            radio.pipe.baudrate = BAUD
2035
            return data
2036

    
2037
        def _write_mem(radio):
2038
            """ PROG MCP Blocks Send """
2039
            global BAUD
2040
            # UI progress
2041
            status = chirp_common.Status()
2042
            status.cur = 0
2043
            val = 0
2044
            for mx in range(0, radio._num_blocks):
2045
                val += radio._num_packets[mx]
2046
            status.max = val
2047
            status.msg = "Writing %i packets" % val
2048
            radio.status_fn(status)
2049

    
2050
            imgadr = 0
2051
            radio.pipe.baudrate = BAUD
2052
            resp0 = _command(radio.pipe, b"0M PROGRAM" + TERM, 3, W8S)
2053
            radio.pipe.baudrate = 57600
2054
            LOG.debug("Switching to 57600 baud upload.")
2055
            junk = radio.pipe.read(1)
2056
            # Read block 0 magic header thingy, save it
2057
            addr = radio._block_addr[0] << 8
2058
            resp0 = _command(radio.pipe,
2059
                             radio._make_command('R', addr, 4),
2060
                             16, W8S)
2061
            mht0 = resp0[5:]
2062
            # Now get block 1 mht
2063
            addr = radio._block_addr[1] << 8
2064
            resp0 = _command(radio.pipe,
2065
                             radio._make_command('R', addr, 5),
2066
                             16, W8S)
2067
            mht1 = resp0[5:]
2068
            for blkn in range(0, radio._num_blocks):
2069
                for bkx in range(0, radio._num_packets[blkn]):
2070
                    addr = (radio._block_addr[blkn] << 8) | (bkx << 8)
2071

    
2072
                    if bkx == 0:    # First packet of the block includes mht
2073
                        if blkn == 0:
2074
                            data = (b'\xff\x4b\x01\x32' +
2075
                                    radio.get_mmap()[4:imgadr + 256])
2076
                        elif blkn == 1:
2077
                            data = mht1 + radio.get_mmap()[imgadr + 5:imgadr +
2078
                                                           256]
2079
                    else:       # after first packet
2080
                        data = radio.get_mmap()[imgadr:imgadr + 256]
2081
                    cmc = radio._make_command('W', addr, 0, data)
2082

    
2083
                    resp0 = _command(radio.pipe, cmc, 6, W8S)
2084
                    if bkx > 0 and resp0 != ACK:
2085
                        LOG.error("Packet 0x%x Write error, no ACK!" % bkx)
2086
                        sx = "Radio failed to acknowledge upload. "
2087
                        sx += "See debug.log"
2088
                        raise errors.RadioError(sx)
2089
                    imgadr += 256
2090
                    _update_status(radio, status)        # UI Update
2091
            # Re-write magic headers
2092
            cmc = radio._make_command('W', (radio._block_addr[0] << 8) | 1, 3,
2093
                                      mht0[1:3] + b'\x32')
2094
            resp0 = _command(radio.pipe, cmc, 1, W8S)
2095
            cmc = radio._make_command('W', radio._block_addr[1] << 8, 5, mht1)
2096
            resp0 = _command(radio.pipe, cmc, 1, W8S)
2097
            cmc = radio._make_command('Z', radio._block_addr[0], 1, mht0[0:1])
2098
            resp0 = _command(radio.pipe, cmc, 16, W8S)
2099
            # Write E to Exit PROG mode
2100
            resp = _command(radio.pipe, b"E", 0, W8S)
2101
            radio.pipe.baudrate = BAUD
2102
            return
    (1-1/1)