Project

General

Profile

New Model #861 ยป kenwood_live.py

Brett Bump, 05/08/2013 04:10 PM

 
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

    
21
NOCACHE = os.environ.has_key("CHIRP_NOCACHE")
22

    
23
if __name__ == "__main__":
24
    import sys
25
    sys.path.insert(0, "..")
26

    
27
from chirp import chirp_common, errors, directory, util
28
from chirp.settings import RadioSetting, RadioSettingGroup, \
29
    RadioSettingValueInteger, RadioSettingValueBoolean, \
30
    RadioSettingValueString, RadioSettingValueList
31

    
32
DEBUG = True
33

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

    
39
THF6_MODES = ["FM", "WFM", "AM", "LSB", "USB", "CW"]
40

    
41
LOCK = threading.Lock()
42

    
43
def command(ser, cmd, *args):
44
    """Send @cmd to radio via @ser"""
45
    global LOCK
46

    
47
    start = time.time()
48

    
49
    LOCK.acquire()
50
    if args:
51
        cmd += " " + " ".join(args)
52
    if DEBUG:
53
        print "PC->RADIO: %s" % cmd
54
    ser.write(cmd + "\r")
55

    
56
    result = ""
57
    while not result.endswith("\r"):
58
        result += ser.read(8)
59
        if (time.time() - start) > 0.5:
60
            print "Timeout waiting for data"
61
            break
62

    
63
    if DEBUG:
64
        print "D7->PC: %s" % result.strip()
65

    
66
    LOCK.release()
67

    
68
    return result.strip()
69

    
70
LAST_BAUD = 9600
71
def get_id(ser):
72
    """Get the ID of the radio attached to @ser"""
73
    global LAST_BAUD
74
    bauds = [9600, 19200, 38400, 57600]
75
    bauds.remove(LAST_BAUD)
76
    bauds.insert(0, LAST_BAUD)
77

    
78
    for i in bauds:
79
        print "Trying ID at baud %i" % i
80
        ser.setBaudrate(i)
81
        ser.write("\r")
82
        ser.read(25)
83
        resp = command(ser, "ID")
84
        if " " in resp:
85
            LAST_BAUD = i
86
            return resp.split(" ")[1]
87

    
88
    raise errors.RadioError("No response from radio")
89

    
90
def get_tmode(tone, ctcss, dcs):
91
    """Get the tone mode based on the values of the tone, ctcss, dcs"""
92
    if dcs and int(dcs) == 1:
93
        return "DTCS"
94
    elif int(ctcss):
95
        return "TSQL"
96
    elif int(tone):
97
        return "Tone"
98
    else:
99
        return ""
100

    
101
def iserr(result):
102
    """Returns True if the @result from a radio is an error"""
103
    return result in ["N", "?"]
104

    
105
class KenwoodLiveRadio(chirp_common.LiveRadio):
106
    """Base class for all live-mode kenwood radios"""
107
    BAUD_RATE = 9600
108
    VENDOR = "Kenwood"
109
    MODEL = ""
110

    
111
    _vfo = 0
112
    _upper = 200
113
    _kenwood_split = False
114
    _kenwood_valid_tones = list(chirp_common.TONES)
115

    
116
    def __init__(self, *args, **kwargs):
117
        chirp_common.LiveRadio.__init__(self, *args, **kwargs)
118

    
119
        self.__memcache = {}
120

    
121
        if self.pipe:
122
            self.pipe.setTimeout(0.1)
123
            radio_id = get_id(self.pipe)
124
            if radio_id != self.MODEL.split(" ")[0]:
125
                raise Exception("Radio reports %s (not %s)" % (radio_id,
126
                                                               self.MODEL))
127

    
128
            command(self.pipe, "AI", "0")
129

    
130
    def _cmd_get_memory(self, number):
131
        return "MR", "%i,0,%03i" % (self._vfo, number)
132

    
133
    def _cmd_get_memory_name(self, number):
134
        return "MNA", "%i,%03i" % (self._vfo, number)
135

    
136
    def _cmd_get_split(self, number):
137
        return "MR", "%i,1,%03i" % (self._vfo, number)
138

    
139
    def _cmd_set_memory(self, number, spec):
140
        if spec:
141
            spec = "," + spec
142
        return "MW", "%i,0,%03i%s" % (self._vfo, number, spec)
143

    
144
    def _cmd_set_memory_name(self, number, name):
145
        return "MNA", "%i,%03i,%s" % (self._vfo, number, name)
146

    
147
    def _cmd_set_split(self, number, spec):
148
        return "MW", "%i,1,%03i,%s" % (self._vfo, number, spec)
149

    
150
    def get_raw_memory(self, number):
151
        return command(self.pipe, *self._cmd_get_memory(number))
152

    
153
    def get_memory(self, number):
154
        if number < 0 or number > self._upper:
155
            raise errors.InvalidMemoryLocation( \
156
                "Number must be between 0 and %i" % self._upper)
157
        if self.__memcache.has_key(number) and not NOCACHE:
158
            return self.__memcache[number]
159

    
160
        result = command(self.pipe, *self._cmd_get_memory(number))
161
        if result == "N" or result == "E":
162
            mem = chirp_common.Memory()
163
            mem.number = number
164
            mem.empty = True
165
            self.__memcache[mem.number] = mem
166
            return mem
167
        elif " " not in result:
168
            print "Not sure what to do with this: `%s'" % result
169
            raise errors.RadioError("Unexpected result returned from radio")
170

    
171
        value = result.split(" ")[1]
172
        spec = value.split(",")
173

    
174
        mem = self._parse_mem_spec(spec)
175
        self.__memcache[mem.number] = mem
176

    
177
        result = command(self.pipe, *self._cmd_get_memory_name(number))
178
        if " " in result:
179
            value = result.split(" ", 1)[1]
180
            if value.count(",") == 2:
181
                _zero, _loc, mem.name = value.split(",")
182
            else:
183
                _loc, mem.name = value.split(",")
184
 
185
        if mem.duplex == "" and self._kenwood_split:
186
            result = command(self.pipe, *self._cmd_get_split(number))
187
            if " " in result:
188
                value = result.split(" ", 1)[1]
189
                self._parse_split_spec(mem, value.split(","))
190

    
191
        return mem
192

    
193
    def _make_mem_spec(self, mem):
194
        pass
195

    
196
    def _parse_mem_spec(self, spec):
197
        pass
198

    
199
    def _parse_split_spec(self, mem, spec):
200
        mem.duplex = "split"
201
        mem.offset = int(spec[2])
202

    
203
    def _make_split_spec(self, mem):
204
        return ("%011i" % mem.offset, "0")
205

    
206
    def set_memory(self, memory):
207
        if memory.number < 0 or memory.number > self._upper:
208
            raise errors.InvalidMemoryLocation( \
209
                "Number must be between 0 and %i" % self._upper)
210

    
211
        spec = self._make_mem_spec(memory)
212
        spec = ",".join(spec)
213
        r1 = command(self.pipe, *self._cmd_set_memory(memory.number, spec))
214
        if not iserr(r1):
215
            time.sleep(0.5)
216
            r2 = command(self.pipe, *self._cmd_set_memory_name(memory.number,
217
                                                               memory.name))
218
            if not iserr(r2):
219
                memory.name = memory.name.rstrip()
220
                self.__memcache[memory.number] = memory
221
            else:
222
                raise errors.InvalidDataError("Radio refused name %i: %s" %\
223
                                                  (memory.number,
224
                                                   repr(memory.name)))
225
        else:
226
            raise errors.InvalidDataError("Radio refused %i" % memory.number)
227

    
228
        if memory.duplex == "split" and self._kenwood_split: 
229
            spec = ",".join(self._make_split_spec(memory))
230
            result = command(self.pipe, *self._cmd_set_split(memory.number,
231
                                                             spec))
232
            if iserr(result):
233
                raise errors.InvalidDataError("Radio refused %i" % \
234
                                                  memory.number)
235

    
236
    def erase_memory(self, number):
237
        if not self.__memcache.has_key(number):
238
            return
239

    
240
        resp = command(self.pipe, *self._cmd_set_memory(number, ""))
241
        if iserr(resp):
242
            raise errors.RadioError("Radio refused delete of %i" % number)
243
        del self.__memcache[number]
244

    
245
TH_D7_SETTINGS = {
246
    "BAL"  : ["4:0", "3:1", "2:2", "1:3", "0:4"],
247
    "BEP"  : ["Off", "Key", "Key+Data", "All"],
248
    "BEPT" : ["Off", "Mine", "All New"], # D700 has fourth "All"
249
    "DS"   : ["Data Band", "Both Bands"],
250
    "DTB"  : ["A", "B"],
251
    "DTBA" : ["A", "B", "A:TX/B:RX"], # D700 has fourth A:RX/B:TX
252
    "DTX"  : ["Manual", "PTT", "Auto"],
253
    "ICO"  : ["Kenwood", "Runner", "House", "Tent", "Boat", "SSTV",
254
              "Plane", "Speedboat", "Car", "Bicycle"],
255
    "MNF"  : ["Name", "Frequency"],
256
    "PKSA" : ["1200", "9600"],
257
    "POSC" : ["Off Duty", "Enroute", "In Service", "Returning",
258
              "Committed", "Special", "Priority", "Emergency"],
259
    "PT"   : ["100ms", "200ms", "500ms", "750ms", "1000ms", "1500ms", "2000ms"],
260
    "SCR"  : ["Time", "Carrier", "Seek"],
261
    "SV"   : ["Off", "0.2s", "0.4s", "0.6s", "0.8s", "1.0s",
262
              "2s", "3s", "4s", "5s"],
263
    "TEMP" : ["F", "C"],
264
    "TXI"  : ["30sec", "1min", "2min", "3min", "4min", "5min",
265
              "10min", "20min", "30min"],
266
    "UNIT" : ["English", "Metric"],
267
    "WAY"  : ["Off", "6 digit NMEA", "7 digit NMEA", "8 digit NMEA",
268
              "9 digit NMEA", "6 digit Magellan", "DGPS"],
269
}
270

    
271
@directory.register
272
class THD7Radio(KenwoodLiveRadio):
273
    """Kenwood TH-D7"""
274
    MODEL = "TH-D7"
275

    
276
    _kenwood_split = True
277

    
278
    def get_features(self):
279
        rf = chirp_common.RadioFeatures()
280
        rf.has_settings = True
281
        rf.has_dtcs = False
282
        rf.has_dtcs_polarity = False
283
        rf.has_bank = False
284
        rf.has_mode = True
285
        rf.has_tuning_step = False
286
        rf.can_odd_split = True
287
        rf.valid_duplexes = ["", "-", "+", "split"]
288
        rf.valid_modes = MODES.values()
289
        rf.valid_tmodes = ["", "Tone", "TSQL"]
290
        rf.valid_characters = chirp_common.CHARSET_ALPHANUMERIC
291
        rf.valid_name_length = 7
292
        rf.memory_bounds = (1, self._upper)
293
        return rf
294

    
295
    def _make_mem_spec(self, mem):
296
        if mem.duplex in " -+":
297
            duplex = util.get_dict_rev(DUPLEX, mem.duplex)
298
            offset = mem.offset
299
        else:
300
            duplex = 0
301
            offset = 0
302
        
303
        spec = ( \
304
            "%011i" % mem.freq,
305
            "%X" % STEPS.index(mem.tuning_step),
306
            "%i" % duplex,
307
            "0",
308
            "%i" % (mem.tmode == "Tone"),
309
            "%i" % (mem.tmode == "TSQL"),
310
            "", # DCS Flag
311
            "%02i" % (self._kenwood_valid_tones.index(mem.rtone) + 1),
312
            "", # DCS Code
313
            "%02i" % (self._kenwood_valid_tones.index(mem.ctone) + 1),
314
            "%09i" % offset,
315
            "%i" % util.get_dict_rev(MODES, mem.mode),
316
            "%i" % ((mem.skip == "S") and 1 or 0))
317

    
318
        return spec
319

    
320
    def _parse_mem_spec(self, spec):
321
        mem = chirp_common.Memory()
322

    
323
        mem.number = int(spec[2])
324
        mem.freq = int(spec[3], 10)
325
        mem.tuning_step = STEPS[int(spec[4], 16)]
326
        mem.duplex = DUPLEX[int(spec[5])]
327
        mem.tmode = get_tmode(spec[7], spec[8], spec[9])
328
        mem.rtone = self._kenwood_valid_tones[int(spec[10]) - 1]
329
        mem.ctone = self._kenwood_valid_tones[int(spec[12]) - 1]
330
        if spec[11] and spec[11].isdigit():
331
            mem.dtcs = chirp_common.DTCS_CODES[int(spec[11][:-1]) - 1]
332
        else:
333
            print "Unknown or invalid DCS: %s" % spec[11]
334
        if spec[13]:
335
            mem.offset = int(spec[13])
336
        else:
337
            mem.offset = 0
338
        mem.mode = MODES[int(spec[14])]
339
        mem.skip = int(spec[15]) and "S" or ""
340

    
341
        return mem
342

    
343
    def _kenwood_get(self, cmd):
344
        resp = command(self.pipe, cmd)
345
        if " " in resp:
346
            return resp.split(" ", 1)
347
        else:
348
            raise errors.RadioError("Radio refused to return %s" % cmd)
349

    
350
    def _kenwood_set(self, cmd, value):
351
        resp = command(self.pipe, cmd, value)
352
        if " " in resp:
353
            return
354
        raise errors.RadioError("Radio refused to set %s" % cmd)
355

    
356
    def _kenwood_get_bool(self, cmd):
357
        _cmd, result = self._kenwood_get(cmd)
358
        return result == "1"
359

    
360
    def _kenwood_set_bool(self, cmd, value):
361
        return self._kenwood_set(cmd, str(int(value)))
362

    
363
    def _kenwood_get_int(self, cmd):
364
        _cmd, result = self._kenwood_get(cmd)
365
        return int(result)
366

    
367
    def _kenwood_set_int(self, cmd, value, digits=1):
368
        return self._kenwood_set(cmd, ("%%0%ii" % digits) % value)
369
    
370
    def get_settings(self):
371
        aux = RadioSettingGroup("aux", "Aux")
372
        tnc = RadioSettingGroup("tnc", "TNC")
373
        save = RadioSettingGroup("save", "Save")
374
        display = RadioSettingGroup("display", "Display")
375
        dtmf = RadioSettingGroup("dtmf", "DTMF")
376
        radio = RadioSettingGroup("radio", "Radio",
377
                                  aux, tnc, save, display, dtmf)
378
        sky = RadioSettingGroup("sky", "SkyCommand")
379
        aprs = RadioSettingGroup("aprs", "APRS")
380
        top = RadioSettingGroup("top", "All Settings", radio, aprs, sky)
381

    
382
        bools = [("AMR", aprs, "APRS Message Auto-Reply"),
383
                 ("AIP", aux, "Advanced Intercept Point"),
384
                 ("ARO", aux, "Automatic Repeater Offset"),
385
                 ("BCN", aprs, "Beacon"),
386
                 ("CH", radio, "Channel Mode Display"),
387
                 #("DIG", aprs, "APRS Digipeater"),
388
                 ("DL", all, "Dual"),
389
                 ("LK", all, "Lock"),
390
                 ("LMP", all, "Lamp"),
391
                 ("TSP", dtmf, "DTMF Fast Transmission"),
392
                 ("TXH", dtmf, "TX Hold"),
393
                 ]
394

    
395
        for setting, group, name in bools:
396
            value = self._kenwood_get_bool(setting)
397
            rs = RadioSetting(setting, name,
398
                              RadioSettingValueBoolean(value))
399
            group.append(rs)
400

    
401
        lists = [("BAL", all, "Balance"),
402
                 ("BEP", aux, "Beep"),
403
                 ("BEPT", aprs, "APRS Beep"),
404
                 ("DS", tnc, "Data Sense"),
405
                 ("DTB", tnc, "Data Band"),
406
                 ("DTBA", aprs, "APRS Data Band"),
407
                 ("DTX", aprs, "APRS Data TX"),
408
                 #("ICO", aprs, "APRS Icon"),
409
                 ("MNF", all, "Memory Display Mode"),
410
                 ("PKSA", aprs, "APRS Packet Speed"),
411
                 ("POSC", aprs, "APRS Position Comment"),
412
                 ("PT", dtmf, "DTMF Speed"),
413
                 ("SV", save, "Battery Save"),
414
                 ("TEMP", aprs, "APRS Temperature Units"),
415
                 ("TXI", aprs, "APRS Transmit Interval"),
416
                 #("UNIT", aprs, "APRS Display Units"),
417
                 ("WAY", aprs, "Waypoint Mode"),
418
                 ]
419

    
420
        for setting, group, name in lists:
421
            value = self._kenwood_get_int(setting)
422
            options = TH_D7_SETTINGS[setting]
423
            rs = RadioSetting(setting, name,
424
                              RadioSettingValueList(options,
425
                                                    options[value]))
426
            group.append(rs)
427

    
428
        ints = [("CNT", display, "Contrast", 1, 16),
429
                ]
430
        for setting, group, name, minv, maxv in ints:
431
            value = self._kenwood_get_int(setting)
432
            rs = RadioSetting(setting, name,
433
                              RadioSettingValueInteger(minv, maxv, value))
434
            group.append(rs)
435

    
436
        strings = [("MES", display, "Power-on Message", 8),
437
                   ("MYC", aprs, "APRS Callsign", 8),
438
                   ("PP", aprs, "APRS Path", 32),
439
                   ("SCC", sky, "SkyCommand Callsign", 8),
440
                   ("SCT", sky, "SkyCommand To Callsign", 8),
441
                   #("STAT", aprs, "APRS Status Text", 32),
442
                   ]
443
        for setting, group, name, length in strings:
444
            _cmd, value = self._kenwood_get(setting)
445
            rs = RadioSetting(setting, name,
446
                              RadioSettingValueString(0, length, value))
447
            group.append(rs)
448

    
449
        return top
450

    
451
    def set_settings(self, settings):
452
        for element in settings:
453
            if not element.changed():
454
                continue
455
            if isinstance(element.value, RadioSettingValueBoolean):
456
                self._kenwood_set_bool(element.get_name(), element.value)
457
            elif isinstance(element.value, RadioSettingValueList):
458
                options = TH_D7_SETTINGS[element.get_name()]
459
                self._kenwood_set_int(element.get_name(),
460
                                      options.index(str(element.value)))
461
            elif isinstance(element.value, RadioSettingValueInteger):
462
                if element.value.get_max() > 9:
463
                    digits = 2
464
                else:
465
                    digits = 1
466
                self._kenwood_set_int(element.get_name(), element.value, digits)
467
            elif isinstance(element.value, RadioSettingValueString):
468
                self._kenwood_set(element.get_name(), str(element.value))
469
            else:
470
                print "Unknown type %s" % element.value
471

    
472
@directory.register
473
class THD7GRadio(THD7Radio):
474
    """Kenwood TH-D7G"""
475
    MODEL = "TH-D7G"
476

    
477
@directory.register
478
class TMD700Radio(KenwoodLiveRadio):
479
    """Kenwood TH-D700"""
480
    MODEL = "TM-D700"
481

    
482
    _kenwood_split = True
483

    
484
    def get_features(self):
485
        rf = chirp_common.RadioFeatures()
486
        rf.has_dtcs = True
487
        rf.has_dtcs_polarity = False
488
        rf.has_bank = False
489
        rf.has_mode = False
490
        rf.has_tuning_step = False
491
        rf.can_odd_split = True
492
        rf.valid_duplexes = ["", "-", "+", "split"]
493
        rf.valid_modes = ["FM"]
494
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS"]
495
        rf.valid_characters = chirp_common.CHARSET_ALPHANUMERIC
496
        rf.valid_name_length = 8
497
        rf.memory_bounds = (1, self._upper)
498
        return rf
499

    
500
    def _make_mem_spec(self, mem):
501
        if mem.duplex in " -+":
502
            duplex = util.get_dict_rev(DUPLEX, mem.duplex)
503
        else:
504
            duplex = 0
505
        spec = ( \
506
            "%011i" % mem.freq,
507
            "%X" % STEPS.index(mem.tuning_step),
508
            "%i" % duplex,
509
            "0",
510
            "%i" % (mem.tmode == "Tone"),
511
            "%i" % (mem.tmode == "TSQL"),
512
            "%i" % (mem.tmode == "DTCS"),
513
            "%02i" % (self._kenwood_valid_tones.index(mem.rtone) + 1),
514
            "%03i0" % (chirp_common.DTCS_CODES.index(mem.dtcs) + 1),
515
            "%02i" % (self._kenwood_valid_tones.index(mem.ctone) + 1),
516
            "%09i" % mem.offset,
517
            "%i" % util.get_dict_rev(MODES, mem.mode),
518
            "%i" % ((mem.skip == "S") and 1 or 0))
519

    
520
        return spec
521

    
522
    def _parse_mem_spec(self, spec):
523
        mem = chirp_common.Memory()
524

    
525
        mem.number = int(spec[2])
526
        mem.freq = int(spec[3])
527
        mem.tuning_step = STEPS[int(spec[4], 16)]
528
        mem.duplex = DUPLEX[int(spec[5])]
529
        mem.tmode = get_tmode(spec[7], spec[8], spec[9])
530
        mem.rtone = self._kenwood_valid_tones[int(spec[10]) - 1]
531
        mem.ctone = self._kenwood_valid_tones[int(spec[12]) - 1]
532
        if spec[11] and spec[11].isdigit():
533
            mem.dtcs = chirp_common.DTCS_CODES[int(spec[11][:-1]) - 1]
534
        else:
535
            print "Unknown or invalid DCS: %s" % spec[11]
536
        if spec[13]:
537
            mem.offset = int(spec[13])
538
        else:
539
            mem.offset = 0
540
        mem.mode = MODES[int(spec[14])]
541
        mem.skip = int(spec[15]) and "S" or ""
542

    
543
        return mem
544

    
545
@directory.register
546
class TMV7Radio(KenwoodLiveRadio):
547
    """Kenwood TM-V7"""
548
    MODEL = "TM-V7"
549

    
550
    mem_upper_limit = 200 # Will be updated
551

    
552
    _kenwood_valid_tones = list(chirp_common.OLD_TONES)
553

    
554
    def set_memory(self, memory):
555
        supported_tones = list(chirp_common.OLD_TONES)
556
        supported_tones.remove(69.3)
557
        if memory.rtone not in supported_tones:
558
            raise errors.UnsupportedToneError("This radio does not support " +
559
                                              "tone %.1fHz" % memory.rtone)
560
        if memory.ctone not in supported_tones:
561
            raise errors.UnsupportedToneError("This radio does not support " +
562
                                              "tone %.1fHz" % memory.ctone)
563

    
564
        return KenwoodLiveRadio.set_memory(self, memory)
565

    
566
    def get_features(self):
567
        rf = chirp_common.RadioFeatures()
568
        rf.has_dtcs = False
569
        rf.has_dtcs_polarity = False
570
        rf.has_bank = False
571
        rf.has_mode = False
572
        rf.has_tuning_step = False
573
        rf.valid_modes = ["FM"]
574
        rf.valid_tmodes = ["", "Tone", "TSQL"]
575
        rf.valid_characters = chirp_common.CHARSET_ALPHANUMERIC
576
        rf.valid_name_length = 7
577
        rf.has_sub_devices = True
578
        rf.memory_bounds = (1, self._upper)
579
        return rf
580

    
581
    def _make_mem_spec(self, mem):
582
        spec = ( \
583
            "%011i" % mem.freq,
584
            "%X" % STEPS.index(mem.tuning_step),
585
            "%i" % util.get_dict_rev(DUPLEX, mem.duplex),
586
            "0",
587
            "%i" % (mem.tmode == "Tone"),
588
            "%i" % (mem.tmode == "TSQL"),
589
            "0",
590
            "%02i" % (self._kenwood_valid_tones.index(mem.rtone) + 1),
591
            "000",
592
            "%02i" % (self._kenwood_valid_tones.index(mem.ctone) + 1),
593
            "",
594
            "0")
595

    
596
        return spec
597

    
598
    def _parse_mem_spec(self, spec):
599
        mem = chirp_common.Memory()
600
        mem.number = int(spec[2])
601
        mem.freq = int(spec[3])
602
        mem.tuning_step = STEPS[int(spec[4], 16)]
603
        mem.duplex = DUPLEX[int(spec[5])]
604
        if int(spec[7]):
605
            mem.tmode = "Tone"
606
        elif int(spec[8]):
607
            mem.tmode = "TSQL"
608
        mem.rtone = self._kenwood_valid_tones[int(spec[10]) - 1]
609
        mem.ctone = self._kenwood_valid_tones[int(spec[12]) - 1]
610

    
611
        return mem
612

    
613
    def get_sub_devices(self):
614
        return [TMV7RadioVHF(self.pipe), TMV7RadioUHF(self.pipe)]
615

    
616
    def __test_location(self, loc):
617
        mem = self.get_memory(loc)
618
        if not mem.empty:
619
            # Memory was not empty, must be valid
620
            return True
621

    
622
        # Mem was empty (or invalid), try to set it
623
        if self._vfo == 0:
624
            mem.freq = 144000000
625
        else:
626
            mem.freq = 440000000
627
        mem.empty = False
628
        try:
629
            self.set_memory(mem)
630
        except Exception:
631
            # Failed, so we're past the limit
632
            return False
633

    
634
        # Erase what we did
635
        try:
636
            self.erase_memory(loc)
637
        except Exception:
638
            pass # V7A Can't delete just yet
639

    
640
        return True
641

    
642
    def _detect_split(self):
643
        return 50
644

    
645
class TMV7RadioSub(TMV7Radio):
646
    """Base class for the TM-V7 sub devices"""
647
    def __init__(self, pipe):
648
        TMV7Radio.__init__(self, pipe)
649
        self._detect_split()
650

    
651
class TMV7RadioVHF(TMV7RadioSub):
652
    """TM-V7 VHF subdevice"""
653
    VARIANT = "VHF"
654
    _vfo = 0
655

    
656
class TMV7RadioUHF(TMV7RadioSub):
657
    """TM-V7 UHF subdevice"""
658
    VARIANT = "UHF"
659
    _vfo = 1
660

    
661
@directory.register
662
class TMG707Radio(TMV7Radio):
663
    """Kenwood TM-G707"""
664
    MODEL = "TM-G707"
665
    
666
    def get_features(self):
667
        rf = TMV7Radio.get_features(self)
668
        rf.has_sub_devices = False
669
        rf.memory_bounds = (1, 180)
670
        rf.valid_bands = [(144000000, 148000000),
671
                          (430000000, 450000000)]
672
        return rf
673

    
674
THF6A_STEPS = [5.0, 6.25, 8.33, 9.0, 10.0, 12.5, 15.0, 20.0, 25.0, 30.0, 50.0,
675
               100.0]
676

    
677
THF6A_DUPLEX = dict(DUPLEX)
678
THF6A_DUPLEX[3] = "split"
679

    
680
@directory.register
681
class THF6ARadio(KenwoodLiveRadio):
682
    """Kenwood TH-F6"""
683
    MODEL = "TH-F6"
684

    
685
    _upper = 399
686
    _kenwood_split = True
687

    
688
    def get_features(self):
689
        rf = chirp_common.RadioFeatures()
690
        rf.has_dtcs_polarity = False
691
        rf.has_bank = False
692
        rf.can_odd_split = True
693
        rf.valid_modes = list(THF6_MODES)
694
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS"]
695
        rf.valid_tuning_steps = list(THF6A_STEPS)
696
        rf.valid_bands = [(1000, 1300000000)]
697
        rf.valid_skips = ["", "S"]
698
        rf.valid_duplexes = THF6A_DUPLEX.values()
699
        rf.valid_characters = chirp_common.CHARSET_ASCII
700
        rf.valid_name_length = 8
701
        rf.memory_bounds = (0, self._upper)
702
        return rf
703

    
704
    def _cmd_set_memory(self, number, spec):
705
        if spec:
706
            spec = "," + spec
707
        return "MW", "0,%03i%s" % (number, spec)
708

    
709
    def _cmd_get_memory(self, number):
710
        return "MR", "0,%03i" % number
711

    
712
    def _cmd_get_memory_name(self, number):
713
        return "MNA", "%03i" % number
714

    
715
    def _cmd_set_memory_name(self, number, name):
716
        return "MNA", "%03i,%s" % (number, name)
717

    
718
    def _cmd_get_split(self, number):
719
        return "MR", "1,%03i" % number
720

    
721
    def _cmd_set_split(self, number, spec):
722
        return "MW", "1,%03i,%s" % (number, spec)
723

    
724
    def _parse_mem_spec(self, spec):
725
        mem = chirp_common.Memory()
726

    
727
        mem.number = int(spec[1])
728
        mem.freq = int(spec[2])
729
        mem.tuning_step = THF6A_STEPS[int(spec[3], 16)]
730
        mem.duplex = THF6A_DUPLEX[int(spec[4])]
731
        mem.tmode = get_tmode(spec[6], spec[7], spec[8])
732
        mem.rtone = self._kenwood_valid_tones[int(spec[9])]
733
        mem.ctone = self._kenwood_valid_tones[int(spec[10])]
734
        if spec[11] and spec[11].isdigit():
735
            mem.dtcs = chirp_common.DTCS_CODES[int(spec[11])]
736
        else:
737
            print "Unknown or invalid DCS: %s" % spec[11]
738
        if spec[12]:
739
            mem.offset = int(spec[12])
740
        else:
741
            mem.offset = 0
742
        mem.mode = THF6_MODES[int(spec[13])]
743
        if spec[14] == "1":
744
            mem.skip = "S"
745

    
746
        return mem
747

    
748
    def _make_mem_spec(self, mem):
749
        if mem.duplex in " +-":
750
            duplex = util.get_dict_rev(THF6A_DUPLEX, mem.duplex)
751
            offset = mem.offset
752
        elif mem.duplex == "split":
753
            duplex = 0
754
            offset = 0
755
        else:
756
            print "Bug: unsupported duplex `%s'" % mem.duplex
757
        spec = ( \
758
            "%011i" % mem.freq,
759
            "%X" % THF6A_STEPS.index(mem.tuning_step),
760
            "%i" % duplex,
761
            "0",
762
            "%i" % (mem.tmode == "Tone"),
763
            "%i" % (mem.tmode == "TSQL"),
764
            "%i" % (mem.tmode == "DTCS"),
765
            "%02i" % (self._kenwood_valid_tones.index(mem.rtone)),
766
            "%02i" % (self._kenwood_valid_tones.index(mem.ctone)),
767
            "%03i" % (chirp_common.DTCS_CODES.index(mem.dtcs)),
768
            "%09i" % offset,
769
            "%i" % (THF6_MODES.index(mem.mode)),
770
            "%i" % (mem.skip == "S"))
771

    
772
        return spec
773

    
774
@directory.register
775
class THF7ERadio(THF6ARadio):
776
    """Kenwood TH-F7"""
777
    MODEL = "TH-F7"
778

    
779
D710_DUPLEX = ["", "+", "-", "split"]
780
D710_MODES = ["FM", "NFM", "AM"]
781
D710_SKIP = ["", "S"]
782
D710_STEPS = [5.0, 6.25, 8.33, 10.0, 12.5, 15.0, 20.0, 25.0, 30.0, 50.0, 100.0]
783
D710_TONES = list(chirp_common.TONES)
784
D710_TONES.remove(159.8)
785
D710_TONES.remove(165.5)
786
D710_TONES.remove(171.3)
787
D710_TONES.remove(177.3)
788
D710_TONES.remove(183.5)
789
D710_TONES.remove(189.9)
790
D710_TONES.remove(196.6)
791
D710_TONES.remove(199.5)
792

    
793
@directory.register
794
class TMD710Radio(KenwoodLiveRadio):
795
    """Kenwood TM-D710"""
796
    MODEL = "TM-D710"
797
    
798
    _upper = 999
799
    _kenwood_valid_tones = list(D710_TONES)
800

    
801
    def get_features(self):
802
        rf = chirp_common.RadioFeatures()
803
        rf.can_odd_split = True
804
        rf.has_dtcs_polarity = False
805
        rf.has_bank = False
806
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS"]
807
        rf.valid_modes = D710_MODES
808
        rf.valid_duplexes = D710_DUPLEX
809
        rf.valid_tuning_steps = D710_STEPS
810
        rf.valid_characters = chirp_common.CHARSET_ASCII.replace(',','')
811
        rf.valid_name_length = 8
812
        rf.valid_skips = D710_SKIP
813
        rf.memory_bounds = (0, 999)
814
        return rf
815

    
816
    def _cmd_get_memory(self, number):
817
        return "ME", "%03i" % number
818

    
819
    def _cmd_get_memory_name(self, number):
820
        return "MN", "%03i" % number
821

    
822
    def _cmd_set_memory(self, number, spec):
823
        return "ME", "%03i,%s" % (number, spec)
824

    
825
    def _cmd_set_memory_name(self, number, name):
826
        return "MN", "%03i,%s" % (number, name)
827

    
828
    def _parse_mem_spec(self, spec):
829
        mem = chirp_common.Memory()
830

    
831
        mem.number = int(spec[0])
832
        mem.freq = int(spec[1])
833
        mem.tuning_step = D710_STEPS[int(spec[2], 16)]
834
        mem.duplex = D710_DUPLEX[int(spec[3])]
835
        # Reverse
836
        if int(spec[5]):
837
            mem.tmode = "Tone"
838
        elif int(spec[6]):
839
            mem.tmode = "TSQL"
840
        elif int(spec[7]):
841
            mem.tmode = "DTCS"
842
        mem.rtone = self._kenwood_valid_tones[int(spec[8])]
843
        mem.ctone = self._kenwood_valid_tones[int(spec[9])]
844
        mem.dtcs = chirp_common.DTCS_CODES[int(spec[10])]
845
        mem.offset = int(spec[11])
846
        mem.mode = D710_MODES[int(spec[12])]
847
        # TX Frequency
848
        if int(spec[13]):
849
            mem.duplex = "split"
850
            mem.offset = int(spec[13])
851
        # Unknown
852
        mem.skip = D710_SKIP[int(spec[15])] # Memory Lockout
853

    
854
        return mem
855

    
856
    def _make_mem_spec(self, mem):
857
        spec = ( \
858
            "%010i" % mem.freq,
859
            "%X" % D710_STEPS.index(mem.tuning_step),
860
            "%i" % (0 if mem.duplex == "split" else \
861
                        D710_DUPLEX.index(mem.duplex)),
862
            "0", # Reverse
863
            "%i" % (mem.tmode == "Tone" and 1 or 0),
864
            "%i" % (mem.tmode == "TSQL" and 1 or 0),
865
            "%i" % (mem.tmode == "DTCS" and 1 or 0),
866
            "%02i" % (self._kenwood_valid_tones.index(mem.rtone)),
867
            "%02i" % (self._kenwood_valid_tones.index(mem.ctone)),
868
            "%03i" % (chirp_common.DTCS_CODES.index(mem.dtcs)),
869
            "%08i" % (0 if mem.duplex == "split" else mem.offset), # Offset
870
            "%i" % D710_MODES.index(mem.mode),
871
            "%010i" % (mem.offset if mem.duplex == "split" else 0), # TX Freq
872
            "0", # Unknown
873
            "%i" % D710_SKIP.index(mem.skip), # Memory Lockout
874
            )
875

    
876
        return spec
877

    
878
@directory.register
879
class THD72Radio(TMD710Radio):
880
    """Kenwood TH-D72"""
881
    MODEL = "TH-D72 (live mode)"
882
    HARDWARE_FLOW = sys.platform == "darwin" # only OS X driver needs hw flow
883

    
884
    def _parse_mem_spec(self, spec):
885
        mem = chirp_common.Memory()
886

    
887
        mem.number = int(spec[0])
888
        mem.freq = int(spec[1])
889
        mem.tuning_step = D710_STEPS[int(spec[2], 16)]
890
        mem.duplex = D710_DUPLEX[int(spec[3])]
891
        # Reverse
892
        if int(spec[5]):
893
            mem.tmode = "Tone"
894
        elif int(spec[6]):
895
            mem.tmode = "TSQL"
896
        elif int(spec[7]):
897
            mem.tmode = "DTCS"
898
        mem.rtone = self._kenwood_valid_tones[int(spec[9])]
899
        mem.ctone = self._kenwood_valid_tones[int(spec[10])]
900
        mem.dtcs = chirp_common.DTCS_CODES[int(spec[11])]
901
        mem.offset = int(spec[13])
902
        mem.mode = D710_MODES[int(spec[14])]
903
        # TX Frequency
904
        if int(spec[15]):
905
            mem.duplex = "split"
906
            mem.offset = int(spec[15])
907
        # Lockout
908
        mem.skip = D710_SKIP[int(spec[17])] # Memory Lockout
909

    
910
        return mem
911

    
912
    def _make_mem_spec(self, mem):
913
        spec = ( \
914
            "%010i" % mem.freq,
915
            "%X" % D710_STEPS.index(mem.tuning_step),
916
            "%i" % (0 if mem.duplex == "split" else \
917
                        D710_DUPLEX.index(mem.duplex)),
918
            "0", # Reverse
919
            "%i" % (mem.tmode == "Tone" and 1 or 0),
920
            "%i" % (mem.tmode == "TSQL" and 1 or 0),
921
            "%i" % (mem.tmode == "DTCS" and 1 or 0),
922
            "0",
923
            "%02i" % (self._kenwood_valid_tones.index(mem.rtone)),
924
            "%02i" % (self._kenwood_valid_tones.index(mem.ctone)),
925
            "%03i" % (chirp_common.DTCS_CODES.index(mem.dtcs)),
926
            "0",
927
            "%08i" % (0 if mem.duplex == "split" else mem.offset), # Offset
928
            "%i" % D710_MODES.index(mem.mode),
929
            "%010i" % (mem.offset if mem.duplex == "split" else 0), # TX Freq
930
            "0", # Unknown
931
            "%i" % D710_SKIP.index(mem.skip), # Memory Lockout
932
            )
933

    
934
        return spec
935

    
936
@directory.register
937
class TMV71Radio(TMD710Radio):
938
    """Kenwood TM-V71"""
939
    MODEL = "TM-V71"
940

    
941
THK2_DUPLEX = ["", "+", "-"]
942
THK2_MODES = ["FM", "NFM"]
943
THK2_TONES = list(chirp_common.TONES)
944
THK2_TONES.remove(159.8) # ??
945
THK2_TONES.remove(165.5) # ??
946
THK2_TONES.remove(171.3) # ??
947
THK2_TONES.remove(177.3) # ??
948
THK2_TONES.remove(183.5) # ??
949
THK2_TONES.remove(189.9) # ??
950
THK2_TONES.remove(196.6) # ??
951
THK2_TONES.remove(199.5) # ??
952

    
953
THK2_CHARS = chirp_common.CHARSET_UPPER_NUMERIC + "-/"
954

    
955
@directory.register
956
class THK2Radio(KenwoodLiveRadio):
957
    """Kenwood TH-K2"""
958
    MODEL = "TH-K2"
959

    
960
    _kenwood_valid_tones = list(THK2_TONES)
961

    
962
    def get_features(self):
963
        rf = chirp_common.RadioFeatures()
964
        rf.can_odd_split = False
965
        rf.has_dtcs_polarity = False
966
        rf.has_bank = False
967
        rf.has_tuning_step = False
968
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS"]
969
        rf.valid_modes = THK2_MODES
970
        rf.valid_duplexes = THK2_DUPLEX
971
        rf.valid_characters = THK2_CHARS
972
        rf.valid_name_length = 6
973
        rf.valid_bands = [(136000000, 173990000)]
974
        rf.valid_skips = ["", "S"]
975
        rf.valid_tuning_steps = [5.0]
976
        rf.memory_bounds = (1, 50)
977
        return rf
978

    
979
    def _cmd_get_memory(self, number):
980
        return "ME", "%02i" % number
981

    
982
    def _cmd_get_memory_name(self, number):
983
        return "MN", "%02i" % number
984

    
985
    def _cmd_set_memory(self, number, spec):
986
        return "ME", "%02i,%s" % (number, spec)
987

    
988
    def _cmd_set_memory_name(self, number, name):
989
        return "MN", "%02i,%s" % (number, name)
990

    
991
    def _parse_mem_spec(self, spec):
992
        mem = chirp_common.Memory()
993

    
994
        mem.number = int(spec[0])
995
        mem.freq = int(spec[1])
996
        #mem.tuning_step = 
997
        mem.duplex = THK2_DUPLEX[int(spec[3])]
998
        if int(spec[5]):
999
            mem.tmode = "Tone"
1000
        elif int(spec[6]):
1001
            mem.tmode = "TSQL"
1002
        elif int(spec[7]):
1003
            mem.tmode = "DTCS"
1004
        mem.rtone = self._kenwood_valid_tones[int(spec[8])]
1005
        mem.ctone = self._kenwood_valid_tones[int(spec[9])]
1006
        mem.dtcs = chirp_common.DTCS_CODES[int(spec[10])]
1007
        mem.offset = int(spec[11])
1008
        mem.mode = THK2_MODES[int(spec[12])]
1009
        mem.skip = int(spec[16]) and "S" or ""
1010
        return mem
1011

    
1012
    def _make_mem_spec(self, mem):
1013
        try:
1014
            rti = self._kenwood_valid_tones.index(mem.rtone)
1015
            cti = self._kenwood_valid_tones.index(mem.ctone)
1016
        except ValueError:
1017
            raise errors.UnsupportedToneError()
1018

    
1019
        spec = ( \
1020
            "%010i" % mem.freq,
1021
            "0",
1022
            "%i"    % THK2_DUPLEX.index(mem.duplex),
1023
            "0",
1024
            "%i"    % int(mem.tmode == "Tone"),
1025
            "%i"    % int(mem.tmode == "TSQL"),
1026
            "%i"    % int(mem.tmode == "DTCS"),
1027
            "%02i"  % rti,
1028
            "%02i"  % cti,
1029
            "%03i"  % chirp_common.DTCS_CODES.index(mem.dtcs),
1030
            "%08i"  % mem.offset,
1031
            "%i"    % THK2_MODES.index(mem.mode),
1032
            "0",
1033
            "%010i" % 0,
1034
            "0",
1035
            "%i"    % int(mem.skip == "S")
1036
            )
1037
        return spec
1038
            
1039

    
1040
@directory.register
1041
class TM271Radio(THK2Radio):
1042
    """Kenwood TM-271"""
1043
    MODEL = "TM-271"
1044
    
1045
    def get_features(self):
1046
        rf = chirp_common.RadioFeatures()
1047
        rf.can_odd_split = False
1048
        rf.has_dtcs_polarity = False
1049
        rf.has_bank = False
1050
        rf.has_tuning_step = False
1051
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS"]
1052
        rf.valid_modes = THK2_MODES
1053
        rf.valid_duplexes = THK2_DUPLEX
1054
        rf.valid_characters = THK2_CHARS
1055
        rf.valid_name_length = 6
1056
        rf.valid_bands = [(137000000, 173990000)]
1057
        rf.valid_skips = ["", "S"]
1058
        rf.valid_tuning_steps = [5.0]
1059
        rf.memory_bounds = (0, 99)
1060
        return rf
1061

    
1062
    def _cmd_get_memory(self, number):
1063
        return "ME", "%03i" % number
1064

    
1065
    def _cmd_get_memory_name(self, number):
1066
        return "MN", "%03i" % number
1067

    
1068
    def _cmd_set_memory(self, number, spec):
1069
        return "ME", "%03i,%s" % (number, spec)
1070

    
1071
    def _cmd_set_memory_name(self, number, name):
1072
        return "MN", "%03i,%s" % (number, name)
1073

    
1074
def do_test():
1075
    """Dev test"""
1076
    mem = chirp_common.Memory()
1077
    mem.number = 1
1078
    mem.freq = 144000000
1079
    mem.duplex = "split"
1080
    mem.offset = 146000000
1081

    
1082
    tc = THF6ARadio
1083
    class FakeSerial:
1084
        """Faked serial line"""
1085
        buf = ""
1086
        def write(self, buf):
1087
            """Write"""
1088
            self.buf = buf
1089
        def read(self, count):
1090
            """Read"""
1091
            if self.buf[:2] == "ID":
1092
                return "ID %s\r" % tc.MODEL
1093
            return self.buf
1094
        def setTimeout(self, foo):
1095
            """Set Timeout"""
1096
            pass
1097
        def setBaudrate(self, foo):
1098
            """Set Baudrate"""
1099
            pass
1100

    
1101
    radio = tc(FakeSerial())
1102
    radio.set_memory(mem)
1103

    
1104
if __name__ == "__main__":
1105
    do_test()
1106

    
1107
@directory.register
1108
class TM471Radio(THK2Radio):
1109
    """Kenwood TM-471"""
1110
    MODEL = "TM-471"
1111
    
1112
    def get_features(self):
1113
        rf = chirp_common.RadioFeatures()
1114
        rf.can_odd_split = False
1115
        rf.has_dtcs_polarity = False
1116
        rf.has_bank = False
1117
        rf.has_tuning_step = False
1118
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS"]
1119
        rf.valid_modes = THK2_MODES
1120
        rf.valid_duplexes = THK2_DUPLEX
1121
        rf.valid_characters = THK2_CHARS
1122
        rf.valid_name_length = 6
1123
        rf.valid_bands = [(444000000, 479990000)]
1124
        rf.valid_skips = ["", "S"]
1125
        rf.valid_tuning_steps = [5.0]
1126
        rf.memory_bounds = (0, 99)
1127
        return rf
1128

    
1129
    def _cmd_get_memory(self, number):
1130
        return "ME", "%03i" % number
1131

    
1132
    def _cmd_get_memory_name(self, number):
1133
        return "MN", "%03i" % number
1134

    
1135
    def _cmd_set_memory(self, number, spec):
1136
        return "ME", "%03i,%s" % (number, spec)
1137

    
1138
    def _cmd_set_memory_name(self, number, name):
1139
        return "MN", "%03i,%s" % (number, name)
1140

    
1141
def do_test():
1142
    """Dev test"""
1143
    mem = chirp_common.Memory()
1144
    mem.number = 1
1145
    mem.freq = 440000000
1146
    mem.duplex = "split"
1147
    mem.offset = 442000000
1148

    
1149
    tc = THF6ARadio
1150
    class FakeSerial:
1151
        """Faked serial line"""
1152
        buf = ""
1153
        def write(self, buf):
1154
            """Write"""
1155
            self.buf = buf
1156
        def read(self, count):
1157
            """Read"""
1158
            if self.buf[:2] == "ID":
1159
                return "ID %s\r" % tc.MODEL
1160
            return self.buf
1161
        def setTimeout(self, foo):
1162
            """Set Timeout"""
1163
            pass
1164
        def setBaudrate(self, foo):
1165
            """Set Baudrate"""
1166
            pass
1167

    
1168
    radio = tc(FakeSerial())
1169
    radio.set_memory(mem)
1170

    
1171
if __name__ == "__main__":
1172
    do_test()
1173

    
    (1-1/1)