kenwood_live.py

Add the d74 in line mode - can't write names - Angus Ainslie, 06/13/2020 10:23 am

Download (62.5 kB)

 
1
# Copyright 2010 Dan Smith <dsmith@danplanet.com>
2
#
3
# This program is free software: you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation, either version 3 of the License, or
6
# (at your option) any later version.
7
#
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
12
#
13
# You should have received a copy of the GNU General Public License
14
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
15

    
16
import threading
17
import os
18
import sys
19
import time
20
import logging
21

    
22
from chirp import chirp_common, errors, directory, util
23
from chirp.settings import RadioSetting, RadioSettingGroup, \
24
    RadioSettingValueInteger, RadioSettingValueBoolean, \
25
    RadioSettingValueString, RadioSettingValueList, RadioSettings
26

    
27
LOG = logging.getLogger(__name__)
28

    
29
NOCACHE = "CHIRP_NOCACHE" in os.environ
30

    
31
DUPLEX = {0: "", 1: "+", 2: "-"}
32
MODES = {0: "FM", 1: "AM"}
33
STEPS = list(chirp_common.TUNING_STEPS)
34
STEPS.append(100.0)
35

    
36
KENWOOD_TONES = list(chirp_common.TONES)
37
KENWOOD_TONES.remove(159.8)
38
KENWOOD_TONES.remove(165.5)
39
KENWOOD_TONES.remove(171.3)
40
KENWOOD_TONES.remove(177.3)
41
KENWOOD_TONES.remove(183.5)
42
KENWOOD_TONES.remove(189.9)
43
KENWOOD_TONES.remove(196.6)
44
KENWOOD_TONES.remove(199.5)
45

    
46
THF6_MODES = ["FM", "WFM", "AM", "LSB", "USB", "CW"]
47

    
48

    
49
RADIO_IDS = {
50
    "ID019": "TS-2000",
51
    "ID009": "TS-850",
52
    "ID020": "TS-480_LiveMode",
53
    "ID021": "TS-590S/SG_LiveMode",         # S-model uses same class
54
    "ID023": "TS-590S/SG_LiveMode"          # as SG
55
}
56

    
57
LOCK = threading.Lock()
58
COMMAND_RESP_BUFSIZE = 8
59
LAST_BAUD = 4800
60
LAST_DELIMITER = ("\r", " ")
61

    
62
# The Kenwood TS-2000, TS-480, TS-590 & TS-850 use ";"
63
# as a CAT command message delimiter, and all others use "\n".
64
# Also, TS-2000 and TS-590 don't space delimite the command
65
# fields, but others do.
66

    
67

    
68
def command(ser, cmd, *args):
69
    """Send @cmd to radio via @ser"""
70
    global LOCK, LAST_DELIMITER, COMMAND_RESP_BUFSIZE
71

    
72
    start = time.time()
73

    
74
    LOCK.acquire()
75

    
76
    # TODO: This global use of LAST_DELIMITER breaks reentrancy
77
    # and needs to be fixed.
78
    if args:
79
        cmd += LAST_DELIMITER[1] + LAST_DELIMITER[1].join(args)
80
    cmd += LAST_DELIMITER[0]
81

    
82
    LOG.debug("PC->RADIO: %s" % cmd.strip())
83
    ser.write(cmd.encode())
84

    
85
    result = ""
86
    while not result.endswith(LAST_DELIMITER[0]):
87
        result += ser.read(COMMAND_RESP_BUFSIZE).decode()
88
        if (time.time() - start) > 0.5:
89
            LOG.error("Timeout waiting for data")
90
            break
91

    
92
    if result.endswith(LAST_DELIMITER[0]):
93
        LOG.debug("RADIO->PC: %r" % result.strip())
94
        result = result[:-1]
95
    else:
96
        LOG.error("Giving up")
97

    
98
    LOCK.release()
99

    
100
    return result.strip()
101

    
102

    
103
def get_id(ser):
104
    """Get the ID of the radio attached to @ser"""
105
    global LAST_BAUD
106
    bauds = [4800, 9600, 19200, 38400, 57600, 115200]
107
    bauds.remove(LAST_BAUD)
108
    # Make sure LAST_BAUD is last so that it is tried first below
109
    bauds.append(LAST_BAUD)
110

    
111
    global LAST_DELIMITER
112
    command_delimiters = [("\r", " "), (";", "")]
113

    
114
    for delimiter in command_delimiters:
115
        # Process the baud options in reverse order so that we try the
116
        # last one first, and then start with the high-speed ones next
117
        for i in reversed(bauds):
118
            LAST_DELIMITER = delimiter
119
            LOG.info("Trying ID at baud %i with delimiter \"%s\"" %
120
                     (i, repr(delimiter)))
121
            ser.baudrate = i
122
            ser.write(LAST_DELIMITER[0].encode())
123
            ser.read(25)
124
            resp = command(ser, "ID")
125

    
126
            # most kenwood radios
127
            if " " in resp:
128
                LAST_BAUD = i
129
                return resp.split(" ")[1]
130

    
131
            # Radio responded in the right baud rate,
132
            # but threw an error because of all the crap
133
            # we have been hurling at it. Retry the ID at this
134
            # baud rate, which will almost definitely work.
135
            if "?" in resp:
136
                resp = command(ser, "ID")
137
                LAST_BAUD = i
138
                if " " in resp:
139
                    return resp.split(" ")[1]
140

    
141
            # Kenwood radios that return ID numbers
142
            if resp in list(RADIO_IDS.keys()):
143
                return RADIO_IDS[resp]
144

    
145
    raise errors.RadioError("No response from radio")
146

    
147

    
148
def get_tmode(tone, ctcss, dcs):
149
    """Get the tone mode based on the values of the tone, ctcss, dcs"""
150
    if dcs and int(dcs) == 1:
151
        return "DTCS"
152
    elif int(ctcss):
153
        return "TSQL"
154
    elif int(tone):
155
        return "Tone"
156
    else:
157
        return ""
158

    
159

    
160
def iserr(result):
161
    """Returns True if the @result from a radio is an error"""
162
    return result in ["N", "?"]
163

    
164

    
165
class KenwoodLiveRadio(chirp_common.LiveRadio):
166
    """Base class for all live-mode kenwood radios"""
167
    BAUD_RATE = 9600
168
    VENDOR = "Kenwood"
169
    MODEL = ""
170
    NEEDS_COMPAT_SERIAL = False
171

    
172
    _vfo = 0
173
    _upper = 200
174
    _kenwood_split = False
175
    _kenwood_valid_tones = list(chirp_common.TONES)
176

    
177
    def __init__(self, *args, **kwargs):
178
        chirp_common.LiveRadio.__init__(self, *args, **kwargs)
179

    
180
        self._memcache = {}
181

    
182
        if self.pipe:
183
            self.pipe.timeout = 0.1
184
            radio_id = get_id(self.pipe)
185
            if radio_id != self.MODEL.split(" ")[0]:
186
                raise Exception("Radio reports %s (not %s)" % (radio_id,
187
                                                               self.MODEL))
188

    
189
            command(self.pipe, "AI", "0")
190

    
191
    def _cmd_get_memory(self, number):
192
        return "MR", "%i,0,%03i" % (self._vfo, number)
193

    
194
    def _cmd_get_memory_name(self, number):
195
        return "MNA", "%i,%03i" % (self._vfo, number)
196

    
197
    def _cmd_get_split(self, number):
198
        return "MR", "%i,1,%03i" % (self._vfo, number)
199

    
200
    def _cmd_set_memory(self, number, spec):
201
        if spec:
202
            spec = "," + spec
203
        return "MW", "%i,0,%03i%s" % (self._vfo, number, spec)
204

    
205
    def _cmd_set_memory_name(self, number, name):
206
        return "MNA", "%i,%03i,%s" % (self._vfo, number, name)
207

    
208
    def _cmd_set_split(self, number, spec):
209
        return "MW", "%i,1,%03i,%s" % (self._vfo, number, spec)
210

    
211
    def get_raw_memory(self, number):
212
        return command(self.pipe, *self._cmd_get_memory(number))
213

    
214
    def get_memory(self, number):
215
        if number < 0 or number > self._upper:
216
            raise errors.InvalidMemoryLocation(
217
                "Number must be between 0 and %i" % self._upper)
218
        if number in self._memcache and not NOCACHE:
219
            return self._memcache[number]
220

    
221
        result = command(self.pipe, *self._cmd_get_memory(number))
222
        if result == "N" or result == "E":
223
            mem = chirp_common.Memory()
224
            mem.number = number
225
            mem.empty = True
226
            self._memcache[mem.number] = mem
227
            return mem
228
        elif " " not in result:
229
            LOG.error("Not sure what to do with this: `%s'" % result)
230
            raise errors.RadioError("Unexpected result returned from radio")
231

    
232
        value = result.split(" ")[1]
233
        spec = value.split(",")
234

    
235
        mem = self._parse_mem_spec(spec)
236
        self._memcache[mem.number] = mem
237

    
238
        result = command(self.pipe, *self._cmd_get_memory_name(number))
239
        if " " in result:
240
            value = result.split(" ", 1)[1]
241
            if value.count(",") == 2:
242
                _zero, _loc, mem.name = value.split(",")
243
            else:
244
                _loc, mem.name = value.split(",")
245

    
246
        if mem.duplex == "" and self._kenwood_split:
247
            result = command(self.pipe, *self._cmd_get_split(number))
248
            if " " in result:
249
                value = result.split(" ", 1)[1]
250
                self._parse_split_spec(mem, value.split(","))
251

    
252
        return mem
253

    
254
    def _make_mem_spec(self, mem):
255
        pass
256

    
257
    def _parse_mem_spec(self, spec):
258
        pass
259

    
260
    def _parse_split_spec(self, mem, spec):
261
        mem.duplex = "split"
262
        mem.offset = int(spec[2])
263

    
264
    def _make_split_spec(self, mem):
265
        return ("%011i" % mem.offset, "0")
266

    
267
    def set_memory(self, memory):
268
        if memory.number < 0 or memory.number > self._upper:
269
            raise errors.InvalidMemoryLocation(
270
                "Number must be between 0 and %i" % self._upper)
271

    
272
        spec = self._make_mem_spec(memory)
273
        spec = ",".join(spec)
274
        r1 = command(self.pipe, *self._cmd_set_memory(memory.number, spec))
275
        if not iserr(r1):
276
            time.sleep(0.5)
277
            r2 = command(self.pipe, *self._cmd_set_memory_name(memory.number,
278
                                                               memory.name))
279
            if not iserr(r2):
280
                memory.name = memory.name.rstrip()
281
                self._memcache[memory.number] = memory
282
            else:
283
                raise errors.InvalidDataError("Radio refused name %i: %s" %
284
                                              (memory.number,
285
                                               repr(memory.name)))
286
        else:
287
            raise errors.InvalidDataError("Radio refused %i" % memory.number)
288

    
289
        if memory.duplex == "split" and self._kenwood_split:
290
            spec = ",".join(self._make_split_spec(memory))
291
            result = command(self.pipe, *self._cmd_set_split(memory.number,
292
                                                             spec))
293
            if iserr(result):
294
                raise errors.InvalidDataError("Radio refused %i" %
295
                                              memory.number)
296

    
297
    def erase_memory(self, number):
298
        if number not in self._memcache:
299
            return
300

    
301
        resp = command(self.pipe, *self._cmd_set_memory(number, ""))
302
        if iserr(resp):
303
            raise errors.RadioError("Radio refused delete of %i" % number)
304
        del self._memcache[number]
305

    
306
    def _kenwood_get(self, cmd):
307
        resp = command(self.pipe, cmd)
308
        if " " in resp:
309
            return resp.split(" ", 1)
310
        else:
311
            if resp == cmd:
312
                return [resp, ""]
313
            else:
314
                raise errors.RadioError("Radio refused to return %s" % cmd)
315

    
316
    def _kenwood_set(self, cmd, value):
317
        resp = command(self.pipe, cmd, value)
318
        if resp[:len(cmd)] == cmd:
319
            return
320
        raise errors.RadioError("Radio refused to set %s" % cmd)
321

    
322
    def _kenwood_get_bool(self, cmd):
323
        _cmd, result = self._kenwood_get(cmd)
324
        return result == "1"
325

    
326
    def _kenwood_set_bool(self, cmd, value):
327
        return self._kenwood_set(cmd, str(int(value)))
328

    
329
    def _kenwood_get_int(self, cmd):
330
        _cmd, result = self._kenwood_get(cmd)
331
        return int(result)
332

    
333
    def _kenwood_set_int(self, cmd, value, digits=1):
334
        return self._kenwood_set(cmd, ("%%0%ii" % digits) % value)
335

    
336
    def set_settings(self, settings):
337
        for element in settings:
338
            if not isinstance(element, RadioSetting):
339
                self.set_settings(element)
340
                continue
341
            if not element.changed():
342
                continue
343
            if isinstance(element.value, RadioSettingValueBoolean):
344
                self._kenwood_set_bool(element.get_name(), element.value)
345
            elif isinstance(element.value, RadioSettingValueList):
346
                options = self._get_setting_options(element.get_name())
347
                if len(options) > 9:
348
                    digits = 2
349
                else:
350
                    digits = 1
351
                self._kenwood_set_int(element.get_name(),
352
                                      options.index(str(element.value)),
353
                                      digits)
354
            elif isinstance(element.value, RadioSettingValueInteger):
355
                if element.value.get_max() > 9:
356
                    digits = 2
357
                else:
358
                    digits = 1
359
                self._kenwood_set_int(element.get_name(),
360
                                      element.value, digits)
361
            elif isinstance(element.value, RadioSettingValueString):
362
                self._kenwood_set(element.get_name(), str(element.value))
363
            else:
364
                LOG.error("Unknown type %s" % element.value)
365

    
366

    
367
class KenwoodOldLiveRadio(KenwoodLiveRadio):
368
    _kenwood_valid_tones = list(chirp_common.OLD_TONES)
369

    
370
    def set_memory(self, memory):
371
        supported_tones = list(chirp_common.OLD_TONES)
372
        supported_tones.remove(69.3)
373
        if memory.rtone not in supported_tones:
374
            raise errors.UnsupportedToneError("This radio does not support " +
375
                                              "tone %.1fHz" % memory.rtone)
376
        if memory.ctone not in supported_tones:
377
            raise errors.UnsupportedToneError("This radio does not support " +
378
                                              "tone %.1fHz" % memory.ctone)
379

    
380
        return KenwoodLiveRadio.set_memory(self, memory)
381

    
382

    
383
@directory.register
384
class THD7Radio(KenwoodOldLiveRadio):
385
    """Kenwood TH-D7"""
386
    MODEL = "TH-D7"
387

    
388
    _kenwood_split = True
389

    
390
    _BEP_OPTIONS = ["Off", "Key", "Key+Data", "All"]
391
    _POSC_OPTIONS = ["Off Duty", "Enroute", "In Service", "Returning",
392
                     "Committed", "Special", "Priority", "Emergency"]
393

    
394
    _SETTINGS_OPTIONS = {
395
        "BAL": ["4:0", "3:1", "2:2", "1:3", "0:4"],
396
        "BEP": None,
397
        "BEPT": ["Off", "Mine", "All New"],  # D700 has fourth "All"
398
        "DS": ["Data Band", "Both Bands"],
399
        "DTB": ["A", "B"],
400
        "DTBA": ["A", "B", "A:TX/B:RX"],  # D700 has fourth A:RX/B:TX
401
        "DTX": ["Manual", "PTT", "Auto"],
402
        "ICO": ["Kenwood", "Runner", "House", "Tent", "Boat", "SSTV",
403
                "Plane", "Speedboat", "Car", "Bicycle"],
404
        "MNF": ["Name", "Frequency"],
405
        "PKSA": ["1200", "9600"],
406
        "POSC": None,
407
        "PT": ["100ms", "200ms", "500ms", "750ms",
408
               "1000ms", "1500ms", "2000ms"],
409
        "SCR": ["Time", "Carrier", "Seek"],
410
        "SV": ["Off", "0.2s", "0.4s", "0.6s", "0.8s", "1.0s",
411
               "2s", "3s", "4s", "5s"],
412
        "TEMP": ["F", "C"],
413
        "TXI": ["30sec", "1min", "2min", "3min", "4min", "5min",
414
                "10min", "20min", "30min"],
415
        "UNIT": ["English", "Metric"],
416
        "WAY": ["Off", "6 digit NMEA", "7 digit NMEA", "8 digit NMEA",
417
                "9 digit NMEA", "6 digit Magellan", "DGPS"],
418
    }
419

    
420
    def get_features(self):
421
        rf = chirp_common.RadioFeatures()
422
        rf.has_settings = True
423
        rf.has_dtcs = False
424
        rf.has_dtcs_polarity = False
425
        rf.has_bank = False
426
        rf.has_mode = True
427
        rf.has_tuning_step = False
428
        rf.can_odd_split = True
429
        rf.valid_duplexes = ["", "-", "+", "split"]
430
        rf.valid_modes = list(MODES.values())
431
        rf.valid_tmodes = ["", "Tone", "TSQL"]
432
        rf.valid_characters = \
433
            chirp_common.CHARSET_ALPHANUMERIC + "/.-+*)('&%$#! ~}|{"
434
        rf.valid_name_length = 7
435
        rf.valid_tuning_steps = STEPS
436
        rf.memory_bounds = (1, self._upper)
437
        return rf
438

    
439
    def _make_mem_spec(self, mem):
440
        if mem.duplex in " -+":
441
            duplex = util.get_dict_rev(DUPLEX, mem.duplex)
442
            offset = mem.offset
443
        else:
444
            duplex = 0
445
            offset = 0
446

    
447
        spec = (
448
            "%011i" % mem.freq,
449
            "%X" % STEPS.index(mem.tuning_step),
450
            "%i" % duplex,
451
            "0",
452
            "%i" % (mem.tmode == "Tone"),
453
            "%i" % (mem.tmode == "TSQL"),
454
            "",  # DCS Flag
455
            "%02i" % (self._kenwood_valid_tones.index(mem.rtone) + 1),
456
            "",  # DCS Code
457
            "%02i" % (self._kenwood_valid_tones.index(mem.ctone) + 1),
458
            "%09i" % offset,
459
            "%i" % util.get_dict_rev(MODES, mem.mode),
460
            "%i" % ((mem.skip == "S") and 1 or 0))
461

    
462
        return spec
463

    
464
    def _parse_mem_spec(self, spec):
465
        mem = chirp_common.Memory()
466

    
467
        mem.number = int(spec[2])
468
        mem.freq = int(spec[3], 10)
469
        mem.tuning_step = STEPS[int(spec[4], 16)]
470
        mem.duplex = DUPLEX[int(spec[5])]
471
        mem.tmode = get_tmode(spec[7], spec[8], spec[9])
472
        mem.rtone = self._kenwood_valid_tones[int(spec[10]) - 1]
473
        mem.ctone = self._kenwood_valid_tones[int(spec[12]) - 1]
474
        if spec[11] and spec[11].isdigit():
475
            mem.dtcs = chirp_common.DTCS_CODES[int(spec[11][:-1]) - 1]
476
        else:
477
            LOG.warn("Unknown or invalid DCS: %s" % spec[11])
478
        if spec[13]:
479
            mem.offset = int(spec[13])
480
        else:
481
            mem.offset = 0
482
        mem.mode = MODES[int(spec[14])]
483
        mem.skip = int(spec[15]) and "S" or ""
484

    
485
        return mem
486

    
487
    EXTRA_BOOL_SETTINGS = {
488
        'main': [("LMP", "Lamp")],
489
        'dtmf': [("TXH", "TX Hold")],
490
    }
491
    EXTRA_LIST_SETTINGS = {
492
        'main': [("BAL", "Balance"),
493
                 ("MNF", "Memory Display Mode")],
494
        'save': [("SV", "Battery Save")],
495
    }
496

    
497
    def _get_setting_options(self, setting):
498
        opts = self._SETTINGS_OPTIONS[setting]
499
        if opts is None:
500
            opts = getattr(self, '_%s_OPTIONS' % setting)
501
        return opts
502

    
503
    def get_settings(self):
504
        main = RadioSettingGroup("main", "Main")
505
        aux = RadioSettingGroup("aux", "Aux")
506
        tnc = RadioSettingGroup("tnc", "TNC")
507
        save = RadioSettingGroup("save", "Save")
508
        display = RadioSettingGroup("display", "Display")
509
        dtmf = RadioSettingGroup("dtmf", "DTMF")
510
        radio = RadioSettingGroup("radio", "Radio",
511
                                  aux, tnc, save, display, dtmf)
512
        sky = RadioSettingGroup("sky", "SkyCommand")
513
        aprs = RadioSettingGroup("aprs", "APRS")
514

    
515
        top = RadioSettings(main, radio, aprs, sky)
516

    
517
        bools = [("AMR", aprs, "APRS Message Auto-Reply"),
518
                 ("AIP", aux, "Advanced Intercept Point"),
519
                 ("ARO", aux, "Automatic Repeater Offset"),
520
                 ("BCN", aprs, "Beacon"),
521
                 ("CH", radio, "Channel Mode Display"),
522
                 # ("DIG", aprs, "APRS Digipeater"),
523
                 ("DL", main, "Dual"),
524
                 ("LK", main, "Lock"),
525
                 ("TSP", dtmf, "DTMF Fast Transmission"),
526
                 ]
527

    
528
        for setting, group, name in bools:
529
            value = self._kenwood_get_bool(setting)
530
            rs = RadioSetting(setting, name,
531
                              RadioSettingValueBoolean(value))
532
            group.append(rs)
533

    
534
        lists = [("BEP", aux, "Beep"),
535
                 ("BEPT", aprs, "APRS Beep"),
536
                 ("DS", tnc, "Data Sense"),
537
                 ("DTB", tnc, "Data Band"),
538
                 ("DTBA", aprs, "APRS Data Band"),
539
                 ("DTX", aprs, "APRS Data TX"),
540
                 # ("ICO", aprs, "APRS Icon"),
541
                 ("PKSA", aprs, "APRS Packet Speed"),
542
                 ("POSC", aprs, "APRS Position Comment"),
543
                 ("PT", dtmf, "DTMF Speed"),
544
                 ("TEMP", aprs, "APRS Temperature Units"),
545
                 ("TXI", aprs, "APRS Transmit Interval"),
546
                 # ("UNIT", aprs, "APRS Display Units"),
547
                 ("WAY", aprs, "Waypoint Mode"),
548
                 ]
549

    
550
        for setting, group, name in lists:
551
            value = self._kenwood_get_int(setting)
552
            options = self._get_setting_options(setting)
553
            rs = RadioSetting(setting, name,
554
                              RadioSettingValueList(options,
555
                                                    options[value]))
556
            group.append(rs)
557

    
558
        for group_name, settings in self.EXTRA_BOOL_SETTINGS.items():
559
            group = locals()[group_name]
560
            for setting, name in settings:
561
                value = self._kenwood_get_bool(setting)
562
                rs = RadioSetting(setting, name,
563
                                  RadioSettingValueBoolean(value))
564
                group.append(rs)
565

    
566
        for group_name, settings in self.EXTRA_LIST_SETTINGS.items():
567
            group = locals()[group_name]
568
            for setting, name in settings:
569
                value = self._kenwood_get_int(setting)
570
                options = self._get_setting_options(setting)
571
                rs = RadioSetting(setting, name,
572
                                  RadioSettingValueBoolean(value))
573
                group.append(rs)
574

    
575
        ints = [("CNT", display, "Contrast", 1, 16),
576
                ]
577
        for setting, group, name, minv, maxv in ints:
578
            value = self._kenwood_get_int(setting)
579
            rs = RadioSetting(setting, name,
580
                              RadioSettingValueInteger(minv, maxv, value))
581
            group.append(rs)
582

    
583
        strings = [("MES", display, "Power-on Message", 8),
584
                   ("MYC", aprs, "APRS Callsign", 8),
585
                   ("PP", aprs, "APRS Path", 32),
586
                   ("SCC", sky, "SkyCommand Callsign", 8),
587
                   ("SCT", sky, "SkyCommand To Callsign", 8),
588
                   # ("STAT", aprs, "APRS Status Text", 32),
589
                   ]
590
        for setting, group, name, length in strings:
591
            _cmd, value = self._kenwood_get(setting)
592
            rs = RadioSetting(setting, name,
593
                              RadioSettingValueString(0, length, value))
594
            group.append(rs)
595

    
596
        return top
597

    
598

    
599
@directory.register
600
class THD7GRadio(THD7Radio):
601
    """Kenwood TH-D7G"""
602
    MODEL = "TH-D7G"
603

    
604
    def get_features(self):
605
        rf = super(THD7GRadio, self).get_features()
606
        rf.valid_name_length = 8
607
        return rf
608

    
609

    
610
@directory.register
611
class TMD700Radio(THD7Radio):
612
    """Kenwood TH-D700"""
613
    MODEL = "TM-D700"
614

    
615
    _kenwood_split = True
616

    
617
    _BEP_OPTIONS = ["Off", "Key"]
618
    _POSC_OPTIONS = ["Off Duty", "Enroute", "In Service", "Returning",
619
                     "Committed", "Special", "Priority", "CUSTOM 0",
620
                     "CUSTOM 1", "CUSTOM 2", "CUSTOM 4", "CUSTOM 5",
621
                     "CUSTOM 6", "Emergency"]
622
    EXTRA_BOOL_SETTINGS = {}
623
    EXTRA_LIST_SETTINGS = {}
624

    
625
    def get_features(self):
626
        rf = chirp_common.RadioFeatures()
627
        rf.has_settings = True
628
        rf.has_dtcs = True
629
        rf.has_dtcs_polarity = False
630
        rf.has_bank = False
631
        rf.has_mode = True
632
        rf.has_tuning_step = False
633
        rf.can_odd_split = True
634
        rf.valid_duplexes = ["", "-", "+", "split"]
635
        rf.valid_modes = ["FM", "AM"]
636
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS"]
637
        rf.valid_characters = chirp_common.CHARSET_ALPHANUMERIC
638
        rf.valid_name_length = 8
639
        rf.valid_tuning_steps = STEPS
640
        rf.memory_bounds = (1, self._upper)
641
        return rf
642

    
643
    def _make_mem_spec(self, mem):
644
        if mem.duplex in " -+":
645
            duplex = util.get_dict_rev(DUPLEX, mem.duplex)
646
        else:
647
            duplex = 0
648
        spec = (
649
            "%011i" % mem.freq,
650
            "%X" % STEPS.index(mem.tuning_step),
651
            "%i" % duplex,
652
            "0",
653
            "%i" % (mem.tmode == "Tone"),
654
            "%i" % (mem.tmode == "TSQL"),
655
            "%i" % (mem.tmode == "DTCS"),
656
            "%02i" % (self._kenwood_valid_tones.index(mem.rtone) + 1),
657
            "%03i0" % (chirp_common.DTCS_CODES.index(mem.dtcs) + 1),
658
            "%02i" % (self._kenwood_valid_tones.index(mem.ctone) + 1),
659
            "%09i" % mem.offset,
660
            "%i" % util.get_dict_rev(MODES, mem.mode),
661
            "%i" % ((mem.skip == "S") and 1 or 0))
662

    
663
        return spec
664

    
665
    def _parse_mem_spec(self, spec):
666
        mem = chirp_common.Memory()
667

    
668
        mem.number = int(spec[2])
669
        mem.freq = int(spec[3])
670
        mem.tuning_step = STEPS[int(spec[4], 16)]
671
        mem.duplex = DUPLEX[int(spec[5])]
672
        mem.tmode = get_tmode(spec[7], spec[8], spec[9])
673
        mem.rtone = self._kenwood_valid_tones[int(spec[10]) - 1]
674
        mem.ctone = self._kenwood_valid_tones[int(spec[12]) - 1]
675
        if spec[11] and spec[11].isdigit():
676
            mem.dtcs = chirp_common.DTCS_CODES[int(spec[11][:-1]) - 1]
677
        else:
678
            LOG.warn("Unknown or invalid DCS: %s" % spec[11])
679
        if spec[13]:
680
            mem.offset = int(spec[13])
681
        else:
682
            mem.offset = 0
683
        mem.mode = MODES[int(spec[14])]
684
        mem.skip = int(spec[15]) and "S" or ""
685

    
686
        return mem
687

    
688

    
689
@directory.register
690
class TMV7Radio(KenwoodOldLiveRadio):
691
    """Kenwood TM-V7"""
692
    MODEL = "TM-V7"
693

    
694
    mem_upper_limit = 200  # Will be updated
695

    
696
    def get_features(self):
697
        rf = chirp_common.RadioFeatures()
698
        rf.has_dtcs = False
699
        rf.has_dtcs_polarity = False
700
        rf.has_bank = False
701
        rf.has_mode = False
702
        rf.has_tuning_step = False
703
        rf.valid_modes = ["FM"]
704
        rf.valid_tmodes = ["", "Tone", "TSQL"]
705
        rf.valid_characters = chirp_common.CHARSET_ALPHANUMERIC
706
        rf.valid_name_length = 7
707
        rf.valid_tuning_steps = STEPS
708
        rf.has_sub_devices = True
709
        rf.memory_bounds = (1, self._upper)
710
        return rf
711

    
712
    def _make_mem_spec(self, mem):
713
        spec = (
714
            "%011i" % mem.freq,
715
            "%X" % STEPS.index(mem.tuning_step),
716
            "%i" % util.get_dict_rev(DUPLEX, mem.duplex),
717
            "0",
718
            "%i" % (mem.tmode == "Tone"),
719
            "%i" % (mem.tmode == "TSQL"),
720
            "0",
721
            "%02i" % (self._kenwood_valid_tones.index(mem.rtone) + 1),
722
            "000",
723
            "%02i" % (self._kenwood_valid_tones.index(mem.ctone) + 1),
724
            "",
725
            "0")
726

    
727
        return spec
728

    
729
    def _parse_mem_spec(self, spec):
730
        mem = chirp_common.Memory()
731
        mem.number = int(spec[2])
732
        mem.freq = int(spec[3])
733
        mem.tuning_step = STEPS[int(spec[4], 16)]
734
        mem.duplex = DUPLEX[int(spec[5])]
735
        if int(spec[7]):
736
            mem.tmode = "Tone"
737
        elif int(spec[8]):
738
            mem.tmode = "TSQL"
739
        mem.rtone = self._kenwood_valid_tones[int(spec[10]) - 1]
740
        mem.ctone = self._kenwood_valid_tones[int(spec[12]) - 1]
741

    
742
        return mem
743

    
744
    def get_sub_devices(self):
745
        return [TMV7RadioVHF(self.pipe), TMV7RadioUHF(self.pipe)]
746

    
747
    def __test_location(self, loc):
748
        mem = self.get_memory(loc)
749
        if not mem.empty:
750
            # Memory was not empty, must be valid
751
            return True
752

    
753
        # Mem was empty (or invalid), try to set it
754
        if self._vfo == 0:
755
            mem.freq = 144000000
756
        else:
757
            mem.freq = 440000000
758
        mem.empty = False
759
        try:
760
            self.set_memory(mem)
761
        except Exception:
762
            # Failed, so we're past the limit
763
            return False
764

    
765
        # Erase what we did
766
        try:
767
            self.erase_memory(loc)
768
        except Exception:
769
            pass  # V7A Can't delete just yet
770

    
771
        return True
772

    
773
    def _detect_split(self):
774
        return 50
775

    
776

    
777
class TMV7RadioSub(TMV7Radio):
778
    """Base class for the TM-V7 sub devices"""
779
    def __init__(self, pipe):
780
        TMV7Radio.__init__(self, pipe)
781
        self._detect_split()
782

    
783

    
784
class TMV7RadioVHF(TMV7RadioSub):
785
    """TM-V7 VHF subdevice"""
786
    VARIANT = "VHF"
787
    _vfo = 0
788

    
789

    
790
class TMV7RadioUHF(TMV7RadioSub):
791
    """TM-V7 UHF subdevice"""
792
    VARIANT = "UHF"
793
    _vfo = 1
794

    
795

    
796
@directory.register
797
class TMG707Radio(TMV7Radio):
798
    """Kenwood TM-G707"""
799
    MODEL = "TM-G707"
800

    
801
    def get_features(self):
802
        rf = TMV7Radio.get_features(self)
803
        rf.has_sub_devices = False
804
        rf.memory_bounds = (1, 180)
805
        rf.valid_bands = [(118000000, 174000000),
806
                          (300000000, 520000000),
807
                          (800000000, 999000000)]
808
        return rf
809

    
810

    
811
THG71_STEPS = [5, 6.25, 10, 12.5, 15, 20, 25, 30, 50, 100]
812

    
813

    
814
@directory.register
815
class THG71Radio(TMV7Radio):
816
    """Kenwood TH-G71"""
817
    MODEL = "TH-G71"
818

    
819
    def get_features(self):
820
        rf = TMV7Radio.get_features(self)
821
        rf.has_tuning_step = True
822
        rf.valid_tuning_steps = list(THG71_STEPS)
823
        rf.valid_name_length = 6
824
        rf.has_sub_devices = False
825
        rf.valid_bands = [(118000000, 174000000),
826
                          (320000000, 470000000),
827
                          (800000000, 945000000)]
828
        return rf
829

    
830
    def _make_mem_spec(self, mem):
831
        spec = (
832
            "%011i" % mem.freq,
833
            "%X" % THG71_STEPS.index(mem.tuning_step),
834
            "%i" % util.get_dict_rev(DUPLEX, mem.duplex),
835
            "0",
836
            "%i" % (mem.tmode == "Tone"),
837
            "%i" % (mem.tmode == "TSQL"),
838
            "0",
839
            "%02i" % (self._kenwood_valid_tones.index(mem.rtone) + 1),
840
            "000",
841
            "%02i" % (self._kenwood_valid_tones.index(mem.ctone) + 1),
842
            "%09i" % mem.offset,
843
            "%i" % ((mem.skip == "S") and 1 or 0))
844
        return spec
845

    
846
    def _parse_mem_spec(self, spec):
847
        mem = chirp_common.Memory()
848
        mem.number = int(spec[2])
849
        mem.freq = int(spec[3])
850
        mem.tuning_step = THG71_STEPS[int(spec[4], 16)]
851
        mem.duplex = DUPLEX[int(spec[5])]
852
        if int(spec[7]):
853
            mem.tmode = "Tone"
854
        elif int(spec[8]):
855
            mem.tmode = "TSQL"
856
        mem.rtone = self._kenwood_valid_tones[int(spec[10]) - 1]
857
        mem.ctone = self._kenwood_valid_tones[int(spec[12]) - 1]
858
        if spec[13]:
859
            mem.offset = int(spec[13])
860
        else:
861
            mem.offset = 0
862
        return mem
863

    
864

    
865
THF6A_STEPS = [5.0, 6.25, 8.33, 9.0, 10.0, 12.5, 15.0, 20.0, 25.0, 30.0, 50.0,
866
               100.0]
867

    
868
THF6A_DUPLEX = dict(DUPLEX)
869
THF6A_DUPLEX[3] = "split"
870

    
871

    
872
@directory.register
873
class THF6ARadio(KenwoodLiveRadio):
874
    """Kenwood TH-F6"""
875
    MODEL = "TH-F6"
876

    
877
    _upper = 399
878
    _kenwood_split = True
879
    _kenwood_valid_tones = list(KENWOOD_TONES)
880

    
881
    def get_features(self):
882
        rf = chirp_common.RadioFeatures()
883
        rf.has_dtcs_polarity = False
884
        rf.has_bank = False
885
        rf.can_odd_split = True
886
        rf.valid_modes = list(THF6_MODES)
887
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS"]
888
        rf.valid_tuning_steps = list(THF6A_STEPS)
889
        rf.valid_bands = [(1000, 1300000000)]
890
        rf.valid_skips = ["", "S"]
891
        rf.valid_duplexes = list(THF6A_DUPLEX.values())
892
        rf.valid_characters = chirp_common.CHARSET_ASCII
893
        rf.valid_name_length = 8
894
        rf.memory_bounds = (0, self._upper)
895
        rf.has_settings = True
896
        return rf
897

    
898
    def _cmd_set_memory(self, number, spec):
899
        if spec:
900
            spec = "," + spec
901
        return "MW", "0,%03i%s" % (number, spec)
902

    
903
    def _cmd_get_memory(self, number):
904
        return "MR", "0,%03i" % number
905

    
906
    def _cmd_get_memory_name(self, number):
907
        return "MNA", "%03i" % number
908

    
909
    def _cmd_set_memory_name(self, number, name):
910
        return "MNA", "%03i,%s" % (number, name)
911

    
912
    def _cmd_get_split(self, number):
913
        return "MR", "1,%03i" % number
914

    
915
    def _cmd_set_split(self, number, spec):
916
        return "MW", "1,%03i,%s" % (number, spec)
917

    
918
    def _parse_mem_spec(self, spec):
919
        mem = chirp_common.Memory()
920

    
921
        mem.number = int(spec[1])
922
        mem.freq = int(spec[2])
923
        mem.tuning_step = THF6A_STEPS[int(spec[3], 16)]
924
        mem.duplex = THF6A_DUPLEX[int(spec[4])]
925
        mem.tmode = get_tmode(spec[6], spec[7], spec[8])
926
        mem.rtone = self._kenwood_valid_tones[int(spec[9])]
927
        mem.ctone = self._kenwood_valid_tones[int(spec[10])]
928
        if spec[11] and spec[11].isdigit():
929
            mem.dtcs = chirp_common.DTCS_CODES[int(spec[11])]
930
        else:
931
            LOG.warn("Unknown or invalid DCS: %s" % spec[11])
932
        if spec[12]:
933
            mem.offset = int(spec[12])
934
        else:
935
            mem.offset = 0
936
        mem.mode = THF6_MODES[int(spec[13])]
937
        if spec[14] == "1":
938
            mem.skip = "S"
939

    
940
        return mem
941

    
942
    def _make_mem_spec(self, mem):
943
        if mem.duplex in " +-":
944
            duplex = util.get_dict_rev(THF6A_DUPLEX, mem.duplex)
945
            offset = mem.offset
946
        elif mem.duplex == "split":
947
            duplex = 0
948
            offset = 0
949
        else:
950
            LOG.warn("Bug: unsupported duplex `%s'" % mem.duplex)
951
        spec = (
952
            "%011i" % mem.freq,
953
            "%X" % THF6A_STEPS.index(mem.tuning_step),
954
            "%i" % duplex,
955
            "0",
956
            "%i" % (mem.tmode == "Tone"),
957
            "%i" % (mem.tmode == "TSQL"),
958
            "%i" % (mem.tmode == "DTCS"),
959
            "%02i" % (self._kenwood_valid_tones.index(mem.rtone)),
960
            "%02i" % (self._kenwood_valid_tones.index(mem.ctone)),
961
            "%03i" % (chirp_common.DTCS_CODES.index(mem.dtcs)),
962
            "%09i" % offset,
963
            "%i" % (THF6_MODES.index(mem.mode)),
964
            "%i" % (mem.skip == "S"))
965

    
966
        return spec
967

    
968
    _SETTINGS_OPTIONS = {
969
        "APO": ["Off", "30min", "60min"],
970
        "BAL": ["100%:0%", "75%:25%", "50%:50%", "25%:75%", "%0:100%"],
971
        "BAT": ["Lithium", "Alkaline"],
972
        "CKEY": ["Call", "1750Hz"],
973
        "DATP": ["1200bps", "9600bps"],
974
        "LAN": ["English", "Japanese"],
975
        "MNF": ["Name", "Frequency"],
976
        "MRM": ["All Band", "Current Band"],
977
        "PT": ["100ms", "250ms", "500ms", "750ms",
978
               "1000ms", "1500ms", "2000ms"],
979
        "SCR": ["Time", "Carrier", "Seek"],
980
        "SV": ["Off", "0.2s", "0.4s", "0.6s", "0.8s", "1.0s",
981
               "2s", "3s", "4s", "5s"],
982
        "VXD": ["250ms", "500ms", "750ms", "1s", "1.5s", "2s", "3s"],
983
    }
984

    
985
    def get_settings(self):
986
        main = RadioSettingGroup("main", "Main")
987
        aux = RadioSettingGroup("aux", "Aux")
988
        save = RadioSettingGroup("save", "Save")
989
        display = RadioSettingGroup("display", "Display")
990
        dtmf = RadioSettingGroup("dtmf", "DTMF")
991
        top = RadioSettings(main, aux, save, display, dtmf)
992

    
993
        lists = [("APO", save, "Automatic Power Off"),
994
                 ("BAL", main, "Balance"),
995
                 ("BAT", save, "Battery Type"),
996
                 ("CKEY", aux, "CALL Key Set Up"),
997
                 ("DATP", aux, "Data Packet Speed"),
998
                 ("LAN", display, "Language"),
999
                 ("MNF", main, "Memory Display Mode"),
1000
                 ("MRM", main, "Memory Recall Method"),
1001
                 ("PT", dtmf, "DTMF Speed"),
1002
                 ("SCR", main, "Scan Resume"),
1003
                 ("SV", save, "Battery Save"),
1004
                 ("VXD", aux, "VOX Drop Delay"),
1005
                 ]
1006

    
1007
        bools = [("ANT", aux, "Bar Antenna"),
1008
                 ("ATT", main, "Attenuator Enabled"),
1009
                 ("ARO", main, "Automatic Repeater Offset"),
1010
                 ("BEP", aux, "Beep for keypad"),
1011
                 ("DL", main, "Dual"),
1012
                 ("DLK", dtmf, "DTMF Lockout On Transmit"),
1013
                 ("ELK", aux, "Enable Locked Tuning"),
1014
                 ("LK", main, "Lock"),
1015
                 ("LMP", display, "Lamp"),
1016
                 ("NSFT", aux, "Noise Shift"),
1017
                 ("TH", aux, "Tx Hold for 1750"),
1018
                 ("TSP", dtmf, "DTMF Fast Transmission"),
1019
                 ("TXH", dtmf, "TX Hold DTMF"),
1020
                 ("TXS", main, "Transmit Inhibit"),
1021
                 ("VOX", aux, "VOX Enable"),
1022
                 ("VXB", aux, "VOX On Busy"),
1023
                 ]
1024

    
1025
        ints = [("CNT", display, "Contrast", 1, 16),
1026
                ("VXG", aux, "VOX Gain", 0, 9),
1027
                ]
1028

    
1029
        strings = [("MES", display, "Power-on Message", 8),
1030
                   ]
1031

    
1032
        for setting, group, name in bools:
1033
            value = self._kenwood_get_bool(setting)
1034
            rs = RadioSetting(setting, name,
1035
                              RadioSettingValueBoolean(value))
1036
            group.append(rs)
1037

    
1038
        for setting, group, name in lists:
1039
            value = self._kenwood_get_int(setting)
1040
            options = self._SETTINGS_OPTIONS[setting]
1041
            rs = RadioSetting(setting, name,
1042
                              RadioSettingValueList(options,
1043
                                                    options[value]))
1044
            group.append(rs)
1045

    
1046
        for setting, group, name, minv, maxv in ints:
1047
            value = self._kenwood_get_int(setting)
1048
            rs = RadioSetting(setting, name,
1049
                              RadioSettingValueInteger(minv, maxv, value))
1050
            group.append(rs)
1051

    
1052
        for setting, group, name, length in strings:
1053
            _cmd, value = self._kenwood_get(setting)
1054
            rs = RadioSetting(setting, name,
1055
                              RadioSettingValueString(0, length, value))
1056
            group.append(rs)
1057

    
1058
        return top
1059

    
1060

    
1061
@directory.register
1062
class THF7ERadio(THF6ARadio):
1063
    """Kenwood TH-F7"""
1064
    MODEL = "TH-F7"
1065

    
1066

    
1067
D710_DUPLEX = ["", "+", "-", "split"]
1068
D710_MODES = ["FM", "NFM", "AM"]
1069
D710_SKIP = ["", "S"]
1070
D710_STEPS = [5.0, 6.25, 8.33, 10.0, 12.5, 15.0, 20.0, 25.0, 30.0, 50.0, 100.0]
1071

    
1072

    
1073
@directory.register
1074
class TMD710Radio(KenwoodLiveRadio):
1075
    """Kenwood TM-D710"""
1076
    MODEL = "TM-D710"
1077

    
1078
    _upper = 999
1079
    _kenwood_valid_tones = list(KENWOOD_TONES)
1080

    
1081
    def get_features(self):
1082
        rf = chirp_common.RadioFeatures()
1083
        rf.can_odd_split = True
1084
        rf.has_dtcs_polarity = False
1085
        rf.has_bank = False
1086
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS"]
1087
        rf.valid_modes = D710_MODES
1088
        rf.valid_duplexes = D710_DUPLEX
1089
        rf.valid_tuning_steps = D710_STEPS
1090
        rf.valid_characters = chirp_common.CHARSET_ASCII.replace(',', '')
1091
        rf.valid_name_length = 8
1092
        rf.valid_skips = D710_SKIP
1093
        rf.memory_bounds = (0, 999)
1094
        return rf
1095

    
1096
    def _cmd_get_memory(self, number):
1097
        return "ME", "%03i" % number
1098

    
1099
    def _cmd_get_memory_name(self, number):
1100
        return "MN", "%03i" % number
1101

    
1102
    def _cmd_set_memory(self, number, spec):
1103
        return "ME", "%03i,%s" % (number, spec)
1104

    
1105
    def _cmd_set_memory_name(self, number, name):
1106
        return "MN", "%03i,%s" % (number, name)
1107

    
1108
    def _parse_mem_spec(self, spec):
1109
        mem = chirp_common.Memory()
1110

    
1111
        mem.number = int(spec[0])
1112
        mem.freq = int(spec[1])
1113
        mem.tuning_step = D710_STEPS[int(spec[2], 16)]
1114
        mem.duplex = D710_DUPLEX[int(spec[3])]
1115
        # Reverse
1116
        if int(spec[5]):
1117
            mem.tmode = "Tone"
1118
        elif int(spec[6]):
1119
            mem.tmode = "TSQL"
1120
        elif int(spec[7]):
1121
            mem.tmode = "DTCS"
1122
        mem.rtone = self._kenwood_valid_tones[int(spec[8])]
1123
        mem.ctone = self._kenwood_valid_tones[int(spec[9])]
1124
        mem.dtcs = chirp_common.DTCS_CODES[int(spec[10])]
1125
        mem.offset = int(spec[11])
1126
        mem.mode = D710_MODES[int(spec[12])]
1127
        # TX Frequency
1128
        if int(spec[13]):
1129
            mem.duplex = "split"
1130
            mem.offset = int(spec[13])
1131
        # Unknown
1132
        mem.skip = D710_SKIP[int(spec[15])]  # Memory Lockout
1133

    
1134
        return mem
1135

    
1136
    def _make_mem_spec(self, mem):
1137
        spec = (
1138
            "%010i" % mem.freq,
1139
            "%X" % D710_STEPS.index(mem.tuning_step),
1140
            "%i" % (0 if mem.duplex == "split"
1141
                    else D710_DUPLEX.index(mem.duplex)),
1142
            "0",  # Reverse
1143
            "%i" % (mem.tmode == "Tone" and 1 or 0),
1144
            "%i" % (mem.tmode == "TSQL" and 1 or 0),
1145
            "%i" % (mem.tmode == "DTCS" and 1 or 0),
1146
            "%02i" % (self._kenwood_valid_tones.index(mem.rtone)),
1147
            "%02i" % (self._kenwood_valid_tones.index(mem.ctone)),
1148
            "%03i" % (chirp_common.DTCS_CODES.index(mem.dtcs)),
1149
            "%08i" % (0 if mem.duplex == "split" else mem.offset),  # Offset
1150
            "%i" % D710_MODES.index(mem.mode),
1151
            "%010i" % (mem.offset if mem.duplex == "split" else 0),  # TX Freq
1152
            "0",  # Unknown
1153
            "%i" % D710_SKIP.index(mem.skip),  # Memory Lockout
1154
            )
1155

    
1156
        return spec
1157

    
1158

    
1159
@directory.register
1160
class THD72Radio(TMD710Radio):
1161
    """Kenwood TH-D72"""
1162
    MODEL = "TH-D72 (live mode)"
1163
    HARDWARE_FLOW = sys.platform == "darwin"  # only OS X driver needs hw flow
1164

    
1165
    def _parse_mem_spec(self, spec):
1166
        mem = chirp_common.Memory()
1167

    
1168
        mem.number = int(spec[0])
1169
        mem.freq = int(spec[1])
1170
        mem.tuning_step = D710_STEPS[int(spec[2], 16)]
1171
        mem.duplex = D710_DUPLEX[int(spec[3])]
1172
        # Reverse
1173
        if int(spec[5]):
1174
            mem.tmode = "Tone"
1175
        elif int(spec[6]):
1176
            mem.tmode = "TSQL"
1177
        elif int(spec[7]):
1178
            mem.tmode = "DTCS"
1179
        mem.rtone = self._kenwood_valid_tones[int(spec[9])]
1180
        mem.ctone = self._kenwood_valid_tones[int(spec[10])]
1181
        mem.dtcs = chirp_common.DTCS_CODES[int(spec[11])]
1182
        mem.offset = int(spec[13])
1183
        mem.mode = D710_MODES[int(spec[14])]
1184
        # TX Frequency
1185
        if int(spec[15]):
1186
            mem.duplex = "split"
1187
            mem.offset = int(spec[15])
1188
        # Lockout
1189
        mem.skip = D710_SKIP[int(spec[17])]  # Memory Lockout
1190

    
1191
        return mem
1192

    
1193
    def _make_mem_spec(self, mem):
1194
        spec = (
1195
            "%010i" % mem.freq,
1196
            "%X" % D710_STEPS.index(mem.tuning_step),
1197
            "%i" % (0 if mem.duplex == "split"
1198
                    else D710_DUPLEX.index(mem.duplex)),
1199
            "0",  # Reverse
1200
            "%i" % (mem.tmode == "Tone" and 1 or 0),
1201
            "%i" % (mem.tmode == "TSQL" and 1 or 0),
1202
            "%i" % (mem.tmode == "DTCS" and 1 or 0),
1203
            "0",
1204
            "%02i" % (self._kenwood_valid_tones.index(mem.rtone)),
1205
            "%02i" % (self._kenwood_valid_tones.index(mem.ctone)),
1206
            "%03i" % (chirp_common.DTCS_CODES.index(mem.dtcs)),
1207
            "0",
1208
            "%08i" % (0 if mem.duplex == "split" else mem.offset),  # Offset
1209
            "%i" % D710_MODES.index(mem.mode),
1210
            "%010i" % (mem.offset if mem.duplex == "split" else 0),  # TX Freq
1211
            "0",  # Unknown
1212
            "%i" % D710_SKIP.index(mem.skip),  # Memory Lockout
1213
            )
1214

    
1215
        return spec
1216

    
1217

    
1218
D74_MODES = ['WFM', 'FM', 'NFM', 'AM', 'NAM', 'DV', 'USB', 'LSB', 'CW', 'RTTY', 'DIG', 'PKT', 'NCW', 'NCWR', 'CWR', 'P25', 'Auto', 'RTTYR', 'FSK', 'FSKR', 'DMR', 'USER-L', 'USER-U', 'LSB+CW', 'USB+CW', 'RTTY-L', 'RTTY-U', 'N/A']
1219
D74_MODES = ['FM', 'DV', 'AM', 'LSB', 'USB', 'CW', 'NFM', 'DV', 'WFM', 'R-CW']
1220

    
1221
@directory.register
1222
class THD74Radio(THD72Radio):
1223
    """Kenwood TH-D74"""
1224
    MODEL = "TH-D74"
1225
    HARDWARE_FLOW = sys.platform == "darwin"  # only OS X driver needs hw flow
1226

    
1227
    _upper = 300
1228

    
1229
    def get_features(self):
1230
        rf = chirp_common.RadioFeatures()
1231
        rf.can_odd_split = True
1232
        rf.has_dtcs_polarity = False
1233
        rf.has_bank = False
1234
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS"]
1235
        rf.valid_modes = D74_MODES
1236
        rf.valid_duplexes = D710_DUPLEX
1237
        rf.valid_tuning_steps = D710_STEPS
1238
        rf.valid_characters = chirp_common.CHARSET_ASCII.replace(',', '')
1239
        #rf.valid_name_length = 8
1240
        rf.valid_skips = D710_SKIP
1241
        rf.memory_bounds = (0, 999)
1242
        return rf
1243

    
1244
    def _parse_mem_spec(self, spec):
1245
        LOG.debug("TH-D74->parse: %s" % spec)
1246
        mem = chirp_common.Memory()
1247

    
1248
        mem.number = int(spec[0])
1249
        mem.freq = int(spec[1])
1250
        mem.offset = int(spec[2])
1251
        mem.tuning_step = D710_STEPS[int(spec[3], 16)]
1252
        mem.duplex = D710_DUPLEX[int(spec[14])]
1253
        # Reverse
1254
        if int(spec[8]):
1255
            mem.tmode = "Tone"
1256
        elif int(spec[9]):
1257
            mem.tmode = "TSQL"
1258
        elif int(spec[10]):
1259
            mem.tmode = "DTCS"
1260
        mem.rtone = self._kenwood_valid_tones[int(spec[15])]
1261
        mem.ctone = self._kenwood_valid_tones[int(spec[16])]
1262
        mem.dtcs = chirp_common.DTCS_CODES[int(spec[17])]
1263
        mem.mode = D74_MODES[int(spec[5])]
1264
        # TX Frequency
1265
        # if int(spec[15]):
1266
        #     mem.duplex = "split"
1267
        #     mem.offset = int(spec[15])
1268
        # Lockout
1269
        # mem.skip = D710_SKIP[int(spec[21])]  # Memory Lockout
1270

    
1271
        return mem
1272

    
1273
    def _make_mem_spec(self, mem):
1274

    
1275
        # 0         5 kHz
1276
        # 1         6.25 kHz
1277
        # 2         8.33 kHz
1278
        # 3         9 kHz
1279
        # 4         10 kHz
1280
        # 5         12.5 kHz
1281
        # 6         15 kHz
1282
        # 7         20 kHz
1283
        # 8         25 kHz
1284
        # 9         30 kHz
1285
        # A         50 kHz
1286
        # B         100 kHz
1287
        spec = ( 
1288
            "%010i" % mem.freq,
1289
            "%010i" % (0 if mem.duplex == "split" else mem.offset),  # Offset
1290
            "%X" % D710_STEPS.index(mem.tuning_step),
1291
            "%i" % (D74_MODES.index(mem.mode) == "NFM" and 8 or 0),
1292
            "%i" % D74_MODES.index(mem.mode),
1293
            "0",  # Fine mode
1294
            "0",  # Fine step mode
1295
            "%i" % (mem.tmode == "Tone" and 1 or 0),
1296
            "%i" % (mem.tmode == "TSQL" and 1 or 0),
1297
            "%i" % (mem.tmode == "DTCS" and 1 or 0),
1298
            "0",
1299
            "0",
1300
            "0",
1301
            "%i" % (0 if mem.duplex == "split" else D710_DUPLEX.index(mem.duplex)),#shift
1302
            "%02i" % (self._kenwood_valid_tones.index(mem.rtone)),
1303
            "%02i" % (self._kenwood_valid_tones.index(mem.ctone)),
1304
            "%03i" % (chirp_common.DTCS_CODES.index(mem.dtcs)),
1305
            "0",
1306
            "CQCQCQ",
1307
            "0",  # Unknown
1308
            "00",  # Unknown
1309
            "0",  # Unknown
1310
            )
1311

    
1312
        return spec
1313

    
1314
    def _cmd_set_memory_name(self, number, name):
1315
        return "MR", "1"
1316
        # return "MNA", "%i,%03i,%s" % (self._vfo, number, name)
1317

    
1318
@directory.register
1319
class TMV71Radio(TMD710Radio):
1320
    """Kenwood TM-V71"""
1321
    MODEL = "TM-V71"
1322

    
1323

    
1324
@directory.register
1325
class TMD710GRadio(TMD710Radio):
1326
    """Kenwood TM-D710G"""
1327
    MODEL = "TM-D710G"
1328

    
1329
    @classmethod
1330
    def get_prompts(cls):
1331
        rp = chirp_common.RadioPrompts()
1332
        rp.experimental = ("This radio driver is currently under development, "
1333
                           "and supports the same features as the TM-D710A/E. "
1334
                           "There are no known issues with it, but you should "
1335
                           "proceed with caution.")
1336
        return rp
1337

    
1338

    
1339
THK2_DUPLEX = ["", "+", "-"]
1340
THK2_MODES = ["FM", "NFM"]
1341

    
1342
THK2_CHARS = chirp_common.CHARSET_UPPER_NUMERIC + "-/"
1343

    
1344

    
1345
@directory.register
1346
class THK2Radio(KenwoodLiveRadio):
1347
    """Kenwood TH-K2"""
1348
    MODEL = "TH-K2"
1349

    
1350
    _kenwood_valid_tones = list(KENWOOD_TONES)
1351

    
1352
    def get_features(self):
1353
        rf = chirp_common.RadioFeatures()
1354
        rf.can_odd_split = False
1355
        rf.has_dtcs_polarity = False
1356
        rf.has_bank = False
1357
        rf.has_tuning_step = False
1358
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS"]
1359
        rf.valid_modes = THK2_MODES
1360
        rf.valid_duplexes = THK2_DUPLEX
1361
        rf.valid_characters = THK2_CHARS
1362
        rf.valid_name_length = 6
1363
        rf.valid_bands = [(136000000, 173990000)]
1364
        rf.valid_skips = ["", "S"]
1365
        rf.valid_tuning_steps = [5.0]
1366
        rf.memory_bounds = (0, 49)
1367
        return rf
1368

    
1369
    def _cmd_get_memory(self, number):
1370
        return "ME", "%02i" % number
1371

    
1372
    def _cmd_get_memory_name(self, number):
1373
        return "MN", "%02i" % number
1374

    
1375
    def _cmd_set_memory(self, number, spec):
1376
        return "ME", "%02i,%s" % (number, spec)
1377

    
1378
    def _cmd_set_memory_name(self, number, name):
1379
        return "MN", "%02i,%s" % (number, name)
1380

    
1381
    def _parse_mem_spec(self, spec):
1382
        mem = chirp_common.Memory()
1383

    
1384
        mem.number = int(spec[0])
1385
        mem.freq = int(spec[1])
1386
        # mem.tuning_step =
1387
        mem.duplex = THK2_DUPLEX[int(spec[3])]
1388
        if int(spec[5]):
1389
            mem.tmode = "Tone"
1390
        elif int(spec[6]):
1391
            mem.tmode = "TSQL"
1392
        elif int(spec[7]):
1393
            mem.tmode = "DTCS"
1394
        mem.rtone = self._kenwood_valid_tones[int(spec[8])]
1395
        mem.ctone = self._kenwood_valid_tones[int(spec[9])]
1396
        mem.dtcs = chirp_common.DTCS_CODES[int(spec[10])]
1397
        mem.offset = int(spec[11])
1398
        mem.mode = THK2_MODES[int(spec[12])]
1399
        mem.skip = int(spec[16]) and "S" or ""
1400
        return mem
1401

    
1402
    def _make_mem_spec(self, mem):
1403
        try:
1404
            rti = self._kenwood_valid_tones.index(mem.rtone)
1405
            cti = self._kenwood_valid_tones.index(mem.ctone)
1406
        except ValueError:
1407
            raise errors.UnsupportedToneError()
1408

    
1409
        spec = (
1410
            "%010i" % mem.freq,
1411
            "0",
1412
            "%i" % THK2_DUPLEX.index(mem.duplex),
1413
            "0",
1414
            "%i" % int(mem.tmode == "Tone"),
1415
            "%i" % int(mem.tmode == "TSQL"),
1416
            "%i" % int(mem.tmode == "DTCS"),
1417
            "%02i" % rti,
1418
            "%02i" % cti,
1419
            "%03i" % chirp_common.DTCS_CODES.index(mem.dtcs),
1420
            "%08i" % mem.offset,
1421
            "%i" % THK2_MODES.index(mem.mode),
1422
            "0",
1423
            "%010i" % 0,
1424
            "0",
1425
            "%i" % int(mem.skip == "S")
1426
            )
1427
        return spec
1428

    
1429

    
1430
TM271_STEPS = [2.5, 5.0, 6.25, 10.0, 12.5, 15.0, 20.0, 25.0, 30.0, 50.0, 100.0]
1431

    
1432

    
1433
@directory.register
1434
class TM271Radio(THK2Radio):
1435
    """Kenwood TM-271"""
1436
    MODEL = "TM-271"
1437

    
1438
    def get_features(self):
1439
        rf = chirp_common.RadioFeatures()
1440
        rf.can_odd_split = False
1441
        rf.has_dtcs_polarity = False
1442
        rf.has_bank = False
1443
        rf.has_tuning_step = False
1444
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS"]
1445
        rf.valid_modes = THK2_MODES
1446
        rf.valid_duplexes = THK2_DUPLEX
1447
        rf.valid_characters = THK2_CHARS
1448
        rf.valid_name_length = 6
1449
        rf.valid_bands = [(137000000, 173990000)]
1450
        rf.valid_skips = ["", "S"]
1451
        rf.valid_tuning_steps = list(TM271_STEPS)
1452
        rf.memory_bounds = (0, 99)
1453
        return rf
1454

    
1455
    def _cmd_get_memory(self, number):
1456
        return "ME", "%03i" % number
1457

    
1458
    def _cmd_get_memory_name(self, number):
1459
        return "MN", "%03i" % number
1460

    
1461
    def _cmd_set_memory(self, number, spec):
1462
        return "ME", "%03i,%s" % (number, spec)
1463

    
1464
    def _cmd_set_memory_name(self, number, name):
1465
        return "MN", "%03i,%s" % (number, name)
1466

    
1467

    
1468
@directory.register
1469
class TM281Radio(TM271Radio):
1470
    """Kenwood TM-281"""
1471
    MODEL = "TM-281"
1472
    # seems that this is a perfect clone of TM271 with just a different model
1473

    
1474

    
1475
@directory.register
1476
class TM471Radio(THK2Radio):
1477
    """Kenwood TM-471"""
1478
    MODEL = "TM-471"
1479

    
1480
    def get_features(self):
1481
        rf = chirp_common.RadioFeatures()
1482
        rf.can_odd_split = False
1483
        rf.has_dtcs_polarity = False
1484
        rf.has_bank = False
1485
        rf.has_tuning_step = False
1486
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS"]
1487
        rf.valid_modes = THK2_MODES
1488
        rf.valid_duplexes = THK2_DUPLEX
1489
        rf.valid_characters = THK2_CHARS
1490
        rf.valid_name_length = 6
1491
        rf.valid_bands = [(444000000, 479990000)]
1492
        rf.valid_skips = ["", "S"]
1493
        rf.valid_tuning_steps = [5.0]
1494
        rf.memory_bounds = (0, 99)
1495
        return rf
1496

    
1497
    def _cmd_get_memory(self, number):
1498
        return "ME", "%03i" % number
1499

    
1500
    def _cmd_get_memory_name(self, number):
1501
        return "MN", "%03i" % number
1502

    
1503
    def _cmd_set_memory(self, number, spec):
1504
        return "ME", "%03i,%s" % (number, spec)
1505

    
1506
    def _cmd_set_memory_name(self, number, name):
1507
        return "MN", "%03i,%s" % (number, name)
1508

    
1509

    
1510
@directory.register
1511
class TS590Radio(KenwoodLiveRadio):
1512
    """Kenwood TS-590S/SG"""
1513
    MODEL = "TS-590S/SG_LiveMode"
1514

    
1515
    _kenwood_valid_tones = list(KENWOOD_TONES)
1516
    _kenwood_valid_tones.append(1750)
1517

    
1518
    _upper = 99
1519
    _duplex = ["", "-", "+"]
1520
    _skip = ["", "S"]
1521
    _modes = ["LSB", "USB", "CW", "FM", "AM", "FSK", "CW-R",
1522
              "FSK-R", "Data+LSB", "Data+USB", "Data+FM"]
1523
    _bands = [(1800000, 2000000),    # 160M Band
1524
              (3500000, 4000000),    # 80M Band
1525
              (5167500, 5450000),    # 60M Band
1526
              (7000000, 7300000),    # 40M Band
1527
              (10100000, 10150000),  # 30M Band
1528
              (14000000, 14350000),  # 20M Band
1529
              (18068000, 18168000),  # 17M Band
1530
              (21000000, 21450000),  # 15M Band
1531
              (24890000, 24990000),  # 12M Band
1532
              (28000000, 29700000),  # 10M Band
1533
              (50000000, 54000000)]   # 6M Band
1534

    
1535
    def get_features(self):
1536
        rf = chirp_common.RadioFeatures()
1537

    
1538
        rf.can_odd_split = False
1539
        rf.has_bank = False
1540
        rf.has_ctone = True
1541
        rf.has_dtcs = False
1542
        rf.has_dtcs_polarity = False
1543
        rf.has_name = True
1544
        rf.has_settings = False
1545
        rf.has_offset = True
1546
        rf.has_mode = True
1547
        rf.has_tuning_step = False
1548
        rf.has_nostep_tuning = True
1549
        rf.has_cross = True
1550
        rf.has_comment = False
1551

    
1552
        rf.memory_bounds = (0, self._upper)
1553

    
1554
        rf.valid_bands = self._bands
1555
        rf.valid_characters = chirp_common.CHARSET_UPPER_NUMERIC + "*+-/"
1556
        rf.valid_duplexes = ["", "-", "+"]
1557
        rf.valid_modes = self._modes
1558
        rf.valid_skips = ["", "S"]
1559
        rf.valid_tmodes = ["", "Tone", "TSQL", "Cross"]
1560
        rf.valid_cross_modes = ["Tone->Tone", "->Tone"]
1561
        rf.valid_name_length = 8    # 8 character channel names
1562

    
1563
        return rf
1564

    
1565
    def _my_val_list(setting, opts, obj, atrb):
1566
        """Callback:from ValueList. Set the integer index."""
1567
        value = opts.index(str(setting.value))
1568
        setattr(obj, atrb, value)
1569
        return
1570

    
1571
    def get_memory(self, number):
1572
        """Convert ascii channel data spec into UI columns (mem)"""
1573
        mem = chirp_common.Memory()
1574
        mem.extra = RadioSettingGroup("extra", "Extra")
1575
        # Read the base and split MR strings
1576
        mem.number = number
1577
        spec0 = command(self.pipe, "MR0 %02i" % mem.number)
1578
        spec1 = command(self.pipe, "MR1 %02i" % mem.number)
1579
        mem.name = spec0[41:49]  # Max 8-Char Name if assigned
1580
        mem.name = mem.name.strip()
1581
        mem.name = mem.name.upper()
1582
        _p4 = int(spec0[6:17])    # Rx Frequency
1583
        _p4s = int(spec1[6:17])   # Offset freq (Tx)
1584
        _p5 = int(spec0[17])      # Mode
1585
        _p6 = int(spec0[18])      # Data Mode
1586
        _p7 = int(spec0[19])      # Tone Mode
1587
        _p8 = int(spec0[20:22])   # Tone Frequency Index
1588
        _p9 = int(spec0[22:24])   # CTCSS Frequency Index
1589
        _p11 = int(spec0[27])     # Filter A/B
1590
        _p14 = int(spec0[38:40])  # FM Mode
1591
        _p15 = int(spec0[40])     # Chan Lockout (Skip)
1592
        if _p4 == 0:
1593
            mem.empty = True
1594
            return mem
1595
        mem.empty = False
1596
        mem.freq = _p4
1597
        mem.duplex = self._duplex[0]    # None by default
1598
        mem.offset = 0
1599
        if _p4 < _p4s:   # + shift
1600
            mem.duplex = self._duplex[2]
1601
            mem.offset = _p4s - _p4
1602
        if _p4 > _p4s:   # - shift
1603
            mem.duplex = self._duplex[1]
1604
            mem.offset = _p4 - _p4s
1605
        mx = _p5 - 1     # CAT modes start at 1
1606
        if _p5 == 9:     # except CAT FSK-R is 9, there is no 8
1607
            mx = 7
1608
        if _p6:       # LSB+Data= 8, USB+Data= 9, FM+Data= 10
1609
            if _p5 == 1:     # CAT LSB
1610
                mx = 8
1611
            elif _p5 == 2:   # CAT USB
1612
                mx = 9
1613
            elif _p5 == 4:   # CAT FM
1614
                mx = 10
1615
        mem.mode = self._modes[mx]
1616
        mem.tmode = ""
1617
        mem.cross_mode = "Tone->Tone"
1618
        mem.ctone = self._kenwood_valid_tones[_p9]
1619
        mem.rtone = self._kenwood_valid_tones[_p8]
1620
        if _p7 == 1:
1621
            mem.tmode = "Tone"
1622
        elif _p7 == 2:
1623
            mem.tmode = "TSQL"
1624
        elif _p7 == 3:
1625
            mem.tmode = "Cross"
1626
        mem.skip = self._skip[_p15]
1627

    
1628
        rx = RadioSettingValueBoolean(bool(_p14))
1629
        rset = RadioSetting("fmnrw", "FM Narrow mode (off = Wide)", rx)
1630
        mem.extra.append(rset)
1631
        return mem
1632

    
1633
    def erase_memory(self, number):
1634
        """ Send the blank string to MW0 """
1635
        mem = chirp_common.Memory()
1636
        mem.empty = True
1637
        mem.freq = 0
1638
        mem.offset = 0
1639
        spx = "MW0%03i00000000000000000000000000000000000" % number
1640
        rx = command(self.pipe, spx)      # Send MW0
1641
        return mem
1642

    
1643
    def set_memory(self, mem):
1644
        """Send UI column data (mem) to radio"""
1645
        pfx = "MW0%03i" % mem.number
1646
        xmode = 0
1647
        xtmode = 0
1648
        xrtone = 8
1649
        xctone = 8
1650
        xdata = 0
1651
        xfltr = 0
1652
        xfm = 0
1653
        xskip = 0
1654
        xfreq = mem.freq
1655
        if xfreq > 0:       # if empty; use those defaults
1656
            ix = self._modes.index(mem.mode)
1657
            xmode = ix + 1     # stored as CAT values, LSB= 1
1658
            if ix == 7:        # FSK-R
1659
                xmode = 9     # There is no CAT 8
1660
            if ix > 7:         # a Data mode
1661
                xdata = 1
1662
                if ix == 8:
1663
                    xmode = 1      # LSB
1664
                elif ix == 9:
1665
                    xmode = 2      # USB
1666
                elif ix == 10:
1667
                    xmode = 4      # FM
1668
            if mem.tmode == "Tone":
1669
                xtmode = 1
1670
                xrtone = self._kenwood_valid_tones.index(mem.rtone)
1671
            if mem.tmode == "TSQL" or mem.tmode == "Cross":
1672
                xtmode = 2
1673
                if mem.tmode == "Cross":
1674
                    xtmode = 3
1675
                xctone = self._kenwood_valid_tones.index(mem.ctone)
1676
            for setting in mem.extra:
1677
                if setting.get_name() == "fmnrw":
1678
                    xfm = setting.value
1679
            if mem.skip == "S":
1680
                xskip = 1
1681
        spx = "%011i%1i%1i%1i%02i%02i000%1i0000000000%02i%1i%s" \
1682
            % (xfreq, xmode, xdata, xtmode, xrtone,
1683
                xctone, xfltr, xfm, xskip, mem.name)
1684
        rx = command(self.pipe, pfx, spx)      # Send MW0
1685
        if mem.offset != 0:
1686
            pfx = "MW1%03i" % mem.number
1687
            xfreq = mem.freq - mem.offset
1688
            if mem.duplex == "+":
1689
                xfreq = mem.freq + mem.offset
1690
            spx = "%011i%1i%1i%1i%02i%02i000%1i0000000000%02i%1i%s" \
1691
                % (xfreq, xmode, xdata, xtmode, xrtone,
1692
                   xctone, xfltr, xfm, xskip, mem.name)
1693
            rx = command(self.pipe, pfx, spx)      # Send MW1
1694

    
1695

    
1696
@directory.register
1697
class TS480Radio(KenwoodLiveRadio):
1698
    """Kenwood TS-480"""
1699
    MODEL = "TS-480_LiveMode"
1700

    
1701
    _kenwood_valid_tones = list(KENWOOD_TONES)
1702
    _kenwood_valid_tones.append(1750)
1703

    
1704
    _upper = 99
1705
    _duplex = ["", "-", "+"]
1706
    _skip = ["", "S"]
1707
    _modes = ["LSB", "USB", "CW", "FM", "AM", "FSK", "CW-R", "N/A",
1708
              "FSK-R"]
1709
    _bands = [(1800000, 2000000),    # 160M Band
1710
              (3500000, 4000000),    # 80M Band
1711
              (5167500, 5450000),    # 60M Band
1712
              (7000000, 7300000),    # 40M Band
1713
              (10100000, 10150000),  # 30M Band
1714
              (14000000, 14350000),  # 20M Band
1715
              (18068000, 18168000),  # 17M Band
1716
              (21000000, 21450000),  # 15M Band
1717
              (24890000, 24990000),  # 12M Band
1718
              (28000000, 29700000),  # 10M Band
1719
              (50000000, 54000000)]   # 6M Band
1720

    
1721
    _tsteps = [0.5, 1.0, 2.5, 5.0, 6.25, 10.0, 12.5,
1722
               15.0, 20.0, 25.0, 30.0, 50.0, 100.0]
1723

    
1724
    def get_features(self):
1725
        rf = chirp_common.RadioFeatures()
1726

    
1727
        rf.can_odd_split = False
1728
        rf.has_bank = False
1729
        rf.has_ctone = True
1730
        rf.has_dtcs = False
1731
        rf.has_dtcs_polarity = False
1732
        rf.has_name = True
1733
        rf.has_settings = False
1734
        rf.has_offset = True
1735
        rf.has_mode = True
1736
        rf.has_tuning_step = True
1737
        rf.has_nostep_tuning = True
1738
        rf.has_cross = True
1739
        rf.has_comment = False
1740

    
1741
        rf.memory_bounds = (0, self._upper)
1742

    
1743
        rf.valid_bands = self._bands
1744
        rf.valid_characters = chirp_common.CHARSET_UPPER_NUMERIC + "*+-/"
1745
        rf.valid_duplexes = ["", "-", "+"]
1746
        rf.valid_modes = self._modes
1747
        rf.valid_skips = ["", "S"]
1748
        rf.valid_tmodes = ["", "Tone", "TSQL", "Cross"]
1749
        rf.valid_cross_modes = ["Tone->Tone", "->Tone"]
1750
        rf.valid_name_length = 8    # 8 character channel names
1751
        rf.valid_tuning_steps = self._tsteps
1752

    
1753
        return rf
1754

    
1755
    def _my_val_list(setting, opts, obj, atrb):
1756
        """Callback:from ValueList. Set the integer index."""
1757
        value = opts.index(str(setting.value))
1758
        setattr(obj, atrb, value)
1759
        return
1760

    
1761
    def get_memory(self, number):
1762
        """Convert ascii channel data spec into UI columns (mem)"""
1763
        mem = chirp_common.Memory()
1764
        # Read the base and split MR strings
1765
        mem.number = number
1766
        spec0 = command(self.pipe, "MR0%03i" % mem.number)
1767
        spec1 = command(self.pipe, "MR1%03i" % mem.number)
1768
        # Add 1 to string idecis if refering to CAT manual
1769
        mem.name = spec0[41:49]  # Max 8-Char Name if assigned
1770
        mem.name = mem.name.strip()
1771
        mem.name = mem.name.upper()
1772
        _p4 = int(spec0[6:17])    # Rx Frequency
1773
        _p4s = int(spec1[6:17])   # Offset freq (Tx)
1774
        _p5 = int(spec0[17])      # Mode
1775
        _p6 = int(spec0[18])      # Chan Lockout (Skip)
1776
        _p7 = int(spec0[19])      # Tone Mode
1777
        _p8 = int(spec0[20:22])   # Tone Frequency Index
1778
        _p9 = int(spec0[22:24])   # CTCSS Frequency Index
1779
        _p14 = int(spec0[38:40])  # Tune Step
1780
        if _p4 == 0:
1781
            mem.empty = True
1782
            return mem
1783
        mem.empty = False
1784
        mem.freq = _p4
1785
        mem.duplex = self._duplex[0]    # None by default
1786
        mem.offset = 0
1787
        if _p4 < _p4s:   # + shift
1788
            mem.duplex = self._duplex[2]
1789
            mem.offset = _p4s - _p4
1790
        if _p4 > _p4s:   # - shift
1791
            mem.duplex = self._duplex[1]
1792
            mem.offset = _p4 - _p4s
1793
        mx = _p5 - 1     # CAT modes start at 1
1794
        mem.mode = self._modes[mx]
1795
        mem.tmode = ""
1796
        mem.cross_mode = "Tone->Tone"
1797
        mem.ctone = self._kenwood_valid_tones[_p9]
1798
        mem.rtone = self._kenwood_valid_tones[_p8]
1799
        if _p7 == 1:
1800
            mem.tmode = "Tone"
1801
        elif _p7 == 2:
1802
            mem.tmode = "TSQL"
1803
        elif _p7 == 3:
1804
            mem.tmode = "Cross"
1805
        mem.skip = self._skip[_p6]
1806
        # Tuning step depends on mode
1807
        options = [0.5, 1.0, 2.5, 5.0, 10.0]    # SSB/CS/FSK
1808
        if _p14 == 4 or _p14 == 5:   # AM/FM
1809
            options = self._tsteps[3:]
1810
        mem.tuning_step = options[_p14]
1811

    
1812
        return mem
1813

    
1814
    def erase_memory(self, number):
1815
        mem = chirp_common.Memory()
1816
        mem.empty = True
1817
        mem.freq = 0
1818
        mem.offset = 0
1819
        spx = "MW0%03i00000000000000000000000000000000000" % number
1820
        rx = command(self.pipe, spx)      # Send MW0
1821
        return mem
1822

    
1823
    def set_memory(self, mem):
1824
        """Send UI column data (mem) to radio"""
1825
        pfx = "MW0%03i" % mem.number
1826
        xtmode = 0
1827
        xdata = 0
1828
        xrtone = 8
1829
        xctone = 8
1830
        xskip = 0
1831
        xstep = 0
1832
        xfreq = mem.freq
1833
        if xfreq > 0:       # if empty, use those defaults
1834
            ix = self._modes.index(mem.mode)
1835
            xmode = ix + 1     # stored as CAT values, LSB= 1
1836
            if ix == 7:        # FSK-R
1837
                xmode = 9     # There is no CAT 8
1838
            if mem.tmode == "Tone":
1839
                xtmode = 1
1840
                xrtone = self._kenwood_valid_tones.index(mem.rtone)
1841
            if mem.tmode == "TSQL" or mem.tmode == "Cross":
1842
                xtmode = 2
1843
                if mem.tmode == "Cross":
1844
                    xtmode = 3
1845
                xctone = self._kenwood_valid_tones.index(mem.ctone)
1846
            if mem.skip == "S":
1847
                xskip = 1
1848
            options = [0.5, 1.0, 2.5, 5.0, 10.0]    # SSB/CS/FSK
1849
            if xmode == 4 or xmode == 5:
1850
                options = self._tsteps[3:]
1851
            xstep = options.index(mem.tuning_step)
1852
        spx = "%011i%1i%1i%1i%02i%02i00000000000000%02i%s" \
1853
            % (xfreq, xmode, xskip, xtmode, xrtone,
1854
                xctone, xstep, mem.name)
1855
        rx = command(self.pipe, pfx, spx)      # Send MW0
1856
        if mem.offset != 0:             # Don't send MW1 if empty
1857
            pfx = "MW1%03i" % mem.number
1858
            xfreq = mem.freq - mem.offset
1859
            if mem.duplex == "+":
1860
                xfreq = mem.freq + mem.offset
1861
            spx = "%011i%1i%1i%1i%02i%02i00000000000000%02i%s" \
1862
                  % (xfreq, xmode, xskip, xtmode, xrtone,
1863
                     xctone, xstep, mem.name)
1864
            rx = command(self.pipe, pfx, spx)      # Send MW1