Project

General

Profile

Bug #10953 » tmd710.py

40ff94d5 - Dan Smith, 11/19/2023 05:48 PM

 
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
from chirp import chirp_common, directory, memmap
22
from chirp import bitwise, errors, util
23
from chirp.settings import RadioSettingGroup, RadioSetting, \
24
    RadioSettingValueBoolean, RadioSettingValueList, \
25
    RadioSettingValueString, RadioSettingValueInteger, \
26
    RadioSettingValueFloat, RadioSettings
27
from chirp.drivers import kenwood_live
28

    
29
LOG = logging.getLogger(__name__)
30

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

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

    
75

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

    
91

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

    
108

    
109
def _update_status(self, status, step=1):
110
    """ Increment status bar """
111
    status.cur += step
112
    self.status_fn(status)
113
    return
114

    
115

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

    
129

    
130
def _val_list_off(setting, opts, obj, atrb):
131
    """Like above, but where index 0 on the list is "off" and
132
    translated to 0xFF. The rest are shifted down by 1."""
133
    value = (opts.index(str(setting.value)) - 1) % 256
134
    setattr(obj, atrb, value)
135

    
136

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

    
144
    _upper = 999         # Number of normal chans
145

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

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

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

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

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

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

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

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

    
324
        return mem
325

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

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

    
465
        mhz1 = 1000000.   # Raw freq is stored with 0.1 Hz resolution
466

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
774
        rx = RadioSettingValueBoolean(bool(_blk1.m10mz))
775
        sx = "10 MHz mode"
776
        rset = RadioSetting("block1.m10mz", sx, rx)
777
        aux.append(rset)
778

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
867
        opts = ["Off", "5", "10", "30", "60"]
868
        current = opts[(_pmg[0].wxscntm + 1) % 256]
869
        rx = RadioSettingValueList(opts, current)
870
        sx = "WX alert scan memory time (Mins)"
871
        rset = RadioSetting("pmg/0.wxscntm", sx, rx)
872
        rset.set_apply_callback(_val_list_off, opts, _pmg[0], "wxscntm")
873
        txrx.append(rset)
874

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

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

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

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

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

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

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

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

    
930
        rx = RadioSettingValueBoolean(bool(_blk1.dspmemch))
931
        sx = "Display memory channel number"
932
        rset = RadioSetting("block1.dspmemch", sx, rx)
933
        memz.append(rset)
934

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

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

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

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

    
960
        rx = RadioSettingValueString(0, 12, _char_to_str(_blk1a.rptrid))
961
        sx = "Repeater ID"
962
        rset = RadioSetting("block1a.rptrid", sx, rx)
963
        rptr.append(rset)
964

    
965
        rx = RadioSettingValueBoolean(bool(_blk1.rptrhold))
966
        sx = "Repeater transmit hold"
967
        rset = RadioSetting("block1.rptrhold", sx, rx)
968
        rptr.append(rset)
969

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

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

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

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

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

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

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

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

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

    
1043
        rx = RadioSettingValueBoolean(bool(_pmg[0].abnd144))
1044
        sx = "A/Left: 144 MHz Band"
1045
        rset = RadioSetting("pmg/0.abnd144", sx, rx)
1046
        bmsk.append(rset)
1047

    
1048
        rx = RadioSettingValueBoolean(bool(_pmg[0].abnd220))
1049
        sx = "A/Left: 220 MHz Band"
1050
        rset = RadioSetting("pmg/0.abnd220", sx, rx)
1051
        bmsk.append(rset)
1052

    
1053
        rx = RadioSettingValueBoolean(bool(_pmg[0].abnd300))
1054
        sx = "A/Left: 300 MHz Band"
1055
        rset = RadioSetting("pmg/0.abnd300", sx, rx)
1056
        bmsk.append(rset)
1057

    
1058
        rx = RadioSettingValueBoolean(bool(_pmg[0].abnd430))
1059
        sx = "A/Left: 430 MHz Band"
1060
        rset = RadioSetting("pmg/0.abnd430", sx, rx)
1061
        bmsk.append(rset)
1062

    
1063
        rx = RadioSettingValueBoolean(bool(_pmg[0].bbnd144))
1064
        sx = "B/Right: 144 MHz Band"
1065
        rset = RadioSetting("pmg/0.bbnd144", sx, rx)
1066
        bmsk.append(rset)
1067

    
1068
        rx = RadioSettingValueBoolean(bool(_pmg[0].bbnd220))
1069
        sx = "B/Right: 220 MHz Band"
1070
        rset = RadioSetting("pmg/0.bbnd220", sx, rx)
1071
        bmsk.append(rset)
1072

    
1073
        rx = RadioSettingValueBoolean(bool(_pmg[0].bbnd300))
1074
        sx = "B/Right: 300 MHz Band"
1075
        rset = RadioSetting("pmg/0.bbnd300", sx, rx)
1076
        bmsk.append(rset)
1077

    
1078
        rx = RadioSettingValueBoolean(bool(_pmg[0].bbnd430))
1079
        sx = "B/Right: 430 MHz Band"
1080
        rset = RadioSetting("pmg/0.bbnd430", sx, rx)
1081
        bmsk.append(rset)
1082

    
1083
        rx = RadioSettingValueBoolean(bool(_pmg[0].bbnd800))
1084
        sx = "B/Right: 800 MHz Band"
1085
        rset = RadioSetting("pmg/0.bbnd800", sx, rx)
1086
        bmsk.append(rset)
1087

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1215
        return group       # END get_settings()
1216

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

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

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

    
1261

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

    
1270
        _num_blocks = 3
1271
        _num_packets = [0x9c, 1, 1]
1272

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

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

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

    
1487
        #seekto 0x0030;
1488
        struct {
1489
        char code[16];            // @ 0x0030
1490
        } dtmc[10];
1491

    
1492
        struct {
1493
        char id[8];               // 0x00d0 - 0x011f
1494
        } dtmn[10];
1495

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

    
1508
        struct pm_grp pmg[6];       // 0x0200 - 0x0dff
1509

    
1510
        #seekto 0x0e00;
1511
        struct {
1512
        u8   band;
1513
        u8   skip;
1514
        } chmap[1030];              // to 0x0160b
1515

    
1516
        #seekto 0x01700;            // 0x01700 - 0x0575f
1517
        struct chns ch_mem[1030];   // 0-999 MR and 1000 -1029 Specials
1518

    
1519
        #seekto 0x05760;
1520
        struct chns call[2];
1521

    
1522
        #seekto 0x05800;
1523
        struct {
1524
        char name[8];
1525
        } ch_nam[1020];         // ends @ 0x07e0
1526

    
1527
        #seekto 0x077e0;        // 0x077e0 - 0x07830
1528
        struct {
1529
        char name[8];
1530
        } wxnam[10];
1531

    
1532
        #seekto 0x07da0;
1533
        struct {
1534
        char pmname[16];
1535
        } pm_name[5];
1536

    
1537
        #seekto 0x07df0;
1538
        struct {
1539
        char comnt[32];
1540
        } mcpcom;
1541

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

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

    
1563
            data = ""
1564

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

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

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

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

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

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

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

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

    
1910
        #seekto 0x0030;
1911
        struct {
1912
        char code[16];            // @ 0x0030
1913
        } dtmc[10];
1914

    
1915
        struct {
1916
        char id[8];               // 0x00d0 - 0x011f
1917
        } dtmn[10];
1918

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

    
1931
        struct pm_grp pmg[6];       // 0x0200 - 0x0dff
1932

    
1933
        #seekto 0x0e00;
1934
        struct {
1935
        u8   band;
1936
        u8   skip;
1937
        } chmap[1030];              // to 0x0160b
1938

    
1939
        #seekto 0x01700;            // 0x01700 - 0x0575f
1940
        struct chns ch_mem[1030];   // 0-999 MR and 1000 -1029 Specials
1941

    
1942
        #seekto 0x058a0;
1943
        struct chns call[2];
1944

    
1945
        #seekto 0x05900;
1946
        struct {
1947
        char name[8];
1948
        } ch_nam[1020];         // ends @ 0x07840
1949

    
1950
        #seekto 0x078e0;        // 0x078e0 - 0x0792f
1951
        struct {
1952
        char name[8];
1953
        } wxnam[10];
1954

    
1955
        #seekto 0x07da0;
1956
        struct {
1957
        char pmname[16];
1958
        } pm_name[5];
1959

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

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

    
1982
                        // 2nd block ends @ 0x017cff
1983
        """
1984

    
1985
        def _make_command(self, cmd, addr, length, data=b''):
1986
            cmc = struct.pack('>IB', addr, length)
1987
            return cmd + cmc[1:] + data
1988

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

    
2001
            data = b""
2002

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

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

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

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

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