kenwood_live.py

Jens Jensen, 05/16/2014 11:41 am

Download (39.7 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

    
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
class KenwoodOldLiveRadio(KenwoodLiveRadio):
272
    _kenwood_valid_tones = list(chirp_common.OLD_TONES)
273

    
274
    def set_memory(self, memory):
275
        supported_tones = list(chirp_common.OLD_TONES)
276
        supported_tones.remove(69.3)
277
        if memory.rtone not in supported_tones:
278
            raise errors.UnsupportedToneError("This radio does not support " +
279
                                              "tone %.1fHz" % memory.rtone)
280
        if memory.ctone not in supported_tones:
281
            raise errors.UnsupportedToneError("This radio does not support " +
282
                                              "tone %.1fHz" % memory.ctone)
283

    
284
        return KenwoodLiveRadio.set_memory(self, memory)
285

    
286
@directory.register
287
class THD7Radio(KenwoodOldLiveRadio):
288
    """Kenwood TH-D7"""
289
    MODEL = "TH-D7"
290

    
291
    _kenwood_split = True
292

    
293
    def get_features(self):
294
        rf = chirp_common.RadioFeatures()
295
        rf.has_settings = True
296
        rf.has_dtcs = False
297
        rf.has_dtcs_polarity = False
298
        rf.has_bank = False
299
        rf.has_mode = True
300
        rf.has_tuning_step = False
301
        rf.can_odd_split = True
302
        rf.valid_duplexes = ["", "-", "+", "split"]
303
        rf.valid_modes = MODES.values()
304
        rf.valid_tmodes = ["", "Tone", "TSQL"]
305
        rf.valid_characters = chirp_common.CHARSET_ALPHANUMERIC
306
        rf.valid_name_length = 7
307
        rf.memory_bounds = (1, self._upper)
308
        return rf
309

    
310
    def _make_mem_spec(self, mem):
311
        if mem.duplex in " -+":
312
            duplex = util.get_dict_rev(DUPLEX, mem.duplex)
313
            offset = mem.offset
314
        else:
315
            duplex = 0
316
            offset = 0
317
        
318
        spec = ( \
319
            "%011i" % mem.freq,
320
            "%X" % STEPS.index(mem.tuning_step),
321
            "%i" % duplex,
322
            "0",
323
            "%i" % (mem.tmode == "Tone"),
324
            "%i" % (mem.tmode == "TSQL"),
325
            "", # DCS Flag
326
            "%02i" % (self._kenwood_valid_tones.index(mem.rtone) + 1),
327
            "", # DCS Code
328
            "%02i" % (self._kenwood_valid_tones.index(mem.ctone) + 1),
329
            "%09i" % offset,
330
            "%i" % util.get_dict_rev(MODES, mem.mode),
331
            "%i" % ((mem.skip == "S") and 1 or 0))
332

    
333
        return spec
334

    
335
    def _parse_mem_spec(self, spec):
336
        mem = chirp_common.Memory()
337

    
338
        mem.number = int(spec[2])
339
        mem.freq = int(spec[3], 10)
340
        mem.tuning_step = STEPS[int(spec[4], 16)]
341
        mem.duplex = DUPLEX[int(spec[5])]
342
        mem.tmode = get_tmode(spec[7], spec[8], spec[9])
343
        mem.rtone = self._kenwood_valid_tones[int(spec[10]) - 1]
344
        mem.ctone = self._kenwood_valid_tones[int(spec[12]) - 1]
345
        if spec[11] and spec[11].isdigit():
346
            mem.dtcs = chirp_common.DTCS_CODES[int(spec[11][:-1]) - 1]
347
        else:
348
            print "Unknown or invalid DCS: %s" % spec[11]
349
        if spec[13]:
350
            mem.offset = int(spec[13])
351
        else:
352
            mem.offset = 0
353
        mem.mode = MODES[int(spec[14])]
354
        mem.skip = int(spec[15]) and "S" or ""
355

    
356
        return mem
357

    
358
    def _kenwood_get(self, cmd):
359
        resp = command(self.pipe, cmd)
360
        if " " in resp:
361
            return resp.split(" ", 1)
362
        else:
363
            raise errors.RadioError("Radio refused to return %s" % cmd)
364

    
365
    def _kenwood_set(self, cmd, value):
366
        resp = command(self.pipe, cmd, value)
367
        if " " in resp:
368
            return
369
        raise errors.RadioError("Radio refused to set %s" % cmd)
370

    
371
    def _kenwood_get_bool(self, cmd):
372
        _cmd, result = self._kenwood_get(cmd)
373
        return result == "1"
374

    
375
    def _kenwood_set_bool(self, cmd, value):
376
        return self._kenwood_set(cmd, str(int(value)))
377

    
378
    def _kenwood_get_int(self, cmd):
379
        _cmd, result = self._kenwood_get(cmd)
380
        return int(result)
381

    
382
    def _kenwood_set_int(self, cmd, value, digits=1):
383
        return self._kenwood_set(cmd, ("%%0%ii" % digits) % value)
384
    
385
    def get_settings(self):
386
        aux = RadioSettingGroup("aux", "Aux")
387
        tnc = RadioSettingGroup("tnc", "TNC")
388
        save = RadioSettingGroup("save", "Save")
389
        display = RadioSettingGroup("display", "Display")
390
        dtmf = RadioSettingGroup("dtmf", "DTMF")
391
        radio = RadioSettingGroup("radio", "Radio",
392
                                  aux, tnc, save, display, dtmf)
393
        sky = RadioSettingGroup("sky", "SkyCommand")
394
        aprs = RadioSettingGroup("aprs", "APRS")
395
        top = RadioSettingGroup("top", "All Settings", radio, aprs, sky)
396

    
397
        bools = [("AMR", aprs, "APRS Message Auto-Reply"),
398
                 ("AIP", aux, "Advanced Intercept Point"),
399
                 ("ARO", aux, "Automatic Repeater Offset"),
400
                 ("BCN", aprs, "Beacon"),
401
                 ("CH", radio, "Channel Mode Display"),
402
                 #("DIG", aprs, "APRS Digipeater"),
403
                 ("DL", all, "Dual"),
404
                 ("LK", all, "Lock"),
405
                 ("LMP", all, "Lamp"),
406
                 ("TSP", dtmf, "DTMF Fast Transmission"),
407
                 ("TXH", dtmf, "TX Hold"),
408
                 ]
409

    
410
        for setting, group, name in bools:
411
            value = self._kenwood_get_bool(setting)
412
            rs = RadioSetting(setting, name,
413
                              RadioSettingValueBoolean(value))
414
            group.append(rs)
415

    
416
        lists = [("BAL", all, "Balance"),
417
                 ("BEP", aux, "Beep"),
418
                 ("BEPT", aprs, "APRS Beep"),
419
                 ("DS", tnc, "Data Sense"),
420
                 ("DTB", tnc, "Data Band"),
421
                 ("DTBA", aprs, "APRS Data Band"),
422
                 ("DTX", aprs, "APRS Data TX"),
423
                 #("ICO", aprs, "APRS Icon"),
424
                 ("MNF", all, "Memory Display Mode"),
425
                 ("PKSA", aprs, "APRS Packet Speed"),
426
                 ("POSC", aprs, "APRS Position Comment"),
427
                 ("PT", dtmf, "DTMF Speed"),
428
                 ("SV", save, "Battery Save"),
429
                 ("TEMP", aprs, "APRS Temperature Units"),
430
                 ("TXI", aprs, "APRS Transmit Interval"),
431
                 #("UNIT", aprs, "APRS Display Units"),
432
                 ("WAY", aprs, "Waypoint Mode"),
433
                 ]
434

    
435
        for setting, group, name in lists:
436
            value = self._kenwood_get_int(setting)
437
            options = TH_D7_SETTINGS[setting]
438
            rs = RadioSetting(setting, name,
439
                              RadioSettingValueList(options,
440
                                                    options[value]))
441
            group.append(rs)
442

    
443
        ints = [("CNT", display, "Contrast", 1, 16),
444
                ]
445
        for setting, group, name, minv, maxv in ints:
446
            value = self._kenwood_get_int(setting)
447
            rs = RadioSetting(setting, name,
448
                              RadioSettingValueInteger(minv, maxv, value))
449
            group.append(rs)
450

    
451
        strings = [("MES", display, "Power-on Message", 8),
452
                   ("MYC", aprs, "APRS Callsign", 8),
453
                   ("PP", aprs, "APRS Path", 32),
454
                   ("SCC", sky, "SkyCommand Callsign", 8),
455
                   ("SCT", sky, "SkyCommand To Callsign", 8),
456
                   #("STAT", aprs, "APRS Status Text", 32),
457
                   ]
458
        for setting, group, name, length in strings:
459
            _cmd, value = self._kenwood_get(setting)
460
            rs = RadioSetting(setting, name,
461
                              RadioSettingValueString(0, length, value))
462
            group.append(rs)
463

    
464
        return top
465

    
466
    def set_settings(self, settings):
467
        for element in settings:
468
            if not element.changed():
469
                continue
470
            if isinstance(element.value, RadioSettingValueBoolean):
471
                self._kenwood_set_bool(element.get_name(), element.value)
472
            elif isinstance(element.value, RadioSettingValueList):
473
                options = TH_D7_SETTINGS[element.get_name()]
474
                self._kenwood_set_int(element.get_name(),
475
                                      options.index(str(element.value)))
476
            elif isinstance(element.value, RadioSettingValueInteger):
477
                if element.value.get_max() > 9:
478
                    digits = 2
479
                else:
480
                    digits = 1
481
                self._kenwood_set_int(element.get_name(), element.value, digits)
482
            elif isinstance(element.value, RadioSettingValueString):
483
                self._kenwood_set(element.get_name(), str(element.value))
484
            else:
485
                print "Unknown type %s" % element.value
486

    
487
@directory.register
488
class THD7GRadio(THD7Radio):
489
    """Kenwood TH-D7G"""
490
    MODEL = "TH-D7G"
491

    
492
@directory.register
493
class TMD700Radio(KenwoodOldLiveRadio):
494
    """Kenwood TH-D700"""
495
    MODEL = "TM-D700"
496

    
497
    _kenwood_split = True
498

    
499
    def get_features(self):
500
        rf = chirp_common.RadioFeatures()
501
        rf.has_dtcs = True
502
        rf.has_dtcs_polarity = False
503
        rf.has_bank = False
504
        rf.has_mode = False
505
        rf.has_tuning_step = False
506
        rf.can_odd_split = True
507
        rf.valid_duplexes = ["", "-", "+", "split"]
508
        rf.valid_modes = ["FM"]
509
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS"]
510
        rf.valid_characters = chirp_common.CHARSET_ALPHANUMERIC
511
        rf.valid_name_length = 8
512
        rf.memory_bounds = (1, self._upper)
513
        return rf
514

    
515
    def _make_mem_spec(self, mem):
516
        if mem.duplex in " -+":
517
            duplex = util.get_dict_rev(DUPLEX, mem.duplex)
518
        else:
519
            duplex = 0
520
        spec = ( \
521
            "%011i" % mem.freq,
522
            "%X" % STEPS.index(mem.tuning_step),
523
            "%i" % duplex,
524
            "0",
525
            "%i" % (mem.tmode == "Tone"),
526
            "%i" % (mem.tmode == "TSQL"),
527
            "%i" % (mem.tmode == "DTCS"),
528
            "%02i" % (self._kenwood_valid_tones.index(mem.rtone) + 1),
529
            "%03i0" % (chirp_common.DTCS_CODES.index(mem.dtcs) + 1),
530
            "%02i" % (self._kenwood_valid_tones.index(mem.ctone) + 1),
531
            "%09i" % mem.offset,
532
            "%i" % util.get_dict_rev(MODES, mem.mode),
533
            "%i" % ((mem.skip == "S") and 1 or 0))
534

    
535
        return spec
536

    
537
    def _parse_mem_spec(self, spec):
538
        mem = chirp_common.Memory()
539

    
540
        mem.number = int(spec[2])
541
        mem.freq = int(spec[3])
542
        mem.tuning_step = STEPS[int(spec[4], 16)]
543
        mem.duplex = DUPLEX[int(spec[5])]
544
        mem.tmode = get_tmode(spec[7], spec[8], spec[9])
545
        mem.rtone = self._kenwood_valid_tones[int(spec[10]) - 1]
546
        mem.ctone = self._kenwood_valid_tones[int(spec[12]) - 1]
547
        if spec[11] and spec[11].isdigit():
548
            mem.dtcs = chirp_common.DTCS_CODES[int(spec[11][:-1]) - 1]
549
        else:
550
            print "Unknown or invalid DCS: %s" % spec[11]
551
        if spec[13]:
552
            mem.offset = int(spec[13])
553
        else:
554
            mem.offset = 0
555
        mem.mode = MODES[int(spec[14])]
556
        mem.skip = int(spec[15]) and "S" or ""
557

    
558
        return mem
559

    
560
@directory.register
561
class TMV7Radio(KenwoodOldLiveRadio):
562
    """Kenwood TM-V7"""
563
    MODEL = "TM-V7"
564

    
565
    mem_upper_limit = 200 # Will be updated
566

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

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

    
597
        return spec
598

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

    
612
        return mem
613

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

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

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

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

    
641
        return True
642

    
643
    def _detect_split(self):
644
        return 50
645

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

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

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

    
662
@directory.register
663
class TMG707Radio(TMV7Radio):
664
    """Kenwood TM-G707"""
665
    MODEL = "TM-G707"
666
    
667
    def get_features(self):
668
        rf = TMV7Radio.get_features(self)
669
        rf.has_sub_devices = False
670
        rf.memory_bounds = (1, 180)
671
        rf.valid_bands = [(118000000, 174000000),
672
                          (300000000, 520000000),
673
                          (800000000, 999000000)]
674
        return rf
675

    
676
THG71_STEPS = [ 5, 6.25, 10, 12.5, 15, 20, 25, 30, 50, 100 ]
677
@directory.register
678
class THG71Radio(TMV7Radio):
679
    """Kenwood TH-G71"""
680
    MODEL = "TH-G71"
681

    
682
    def get_features(self):
683
        rf = TMV7Radio.get_features(self)
684
        rf.has_tuning_step = True
685
        rf.valid_tuning_steps = list(THG71_STEPS)
686
        rf.valid_name_length = 6
687
        rf.has_sub_devices = False
688
        rf.valid_bands = [(118000000, 174000000),
689
                          (320000000, 470000000),
690
                                                  (800000000, 945000000)]                
691
        return rf
692

    
693
    def _make_mem_spec(self, mem):
694
        spec = ( \
695
            "%011i" % mem.freq,
696
            "%X" % THG71_STEPS.index(mem.tuning_step),
697
            "%i" % util.get_dict_rev(DUPLEX, mem.duplex),
698
            "0",
699
            "%i" % (mem.tmode == "Tone"),
700
            "%i" % (mem.tmode == "TSQL"),
701
            "0",
702
            "%02i" % (self._kenwood_valid_tones.index(mem.rtone) + 1),
703
            "000",
704
            "%02i" % (self._kenwood_valid_tones.index(mem.ctone) + 1),
705
            "%09i" % mem.offset,
706
            "%i" % ((mem.skip == "S") and 1 or 0))
707
        return spec
708

    
709
    def _parse_mem_spec(self, spec):
710
        mem = chirp_common.Memory()
711
        mem.number = int(spec[2])
712
        mem.freq = int(spec[3])
713
        mem.tuning_step = THG71_STEPS[int(spec[4], 16)]
714
        mem.duplex = DUPLEX[int(spec[5])]
715
        if int(spec[7]):
716
            mem.tmode = "Tone"
717
        elif int(spec[8]):
718
            mem.tmode = "TSQL"
719
        mem.rtone = self._kenwood_valid_tones[int(spec[10]) - 1]
720
        mem.ctone = self._kenwood_valid_tones[int(spec[12]) - 1]
721
        if spec[13]:
722
            mem.offset = int(spec[13])
723
        else:
724
            mem.offset = 0
725
        return mem
726

    
727
                
728
THF6A_STEPS = [5.0, 6.25, 8.33, 9.0, 10.0, 12.5, 15.0, 20.0, 25.0, 30.0, 50.0,
729
               100.0]
730

    
731
THF6A_DUPLEX = dict(DUPLEX)
732
THF6A_DUPLEX[3] = "split"
733

    
734
@directory.register
735
class THF6ARadio(KenwoodLiveRadio):
736
    """Kenwood TH-F6"""
737
    MODEL = "TH-F6"
738

    
739
    _upper = 399
740
    _kenwood_split = True
741

    
742
    def get_features(self):
743
        rf = chirp_common.RadioFeatures()
744
        rf.has_dtcs_polarity = False
745
        rf.has_bank = False
746
        rf.can_odd_split = True
747
        rf.valid_modes = list(THF6_MODES)
748
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS"]
749
        rf.valid_tuning_steps = list(THF6A_STEPS)
750
        rf.valid_bands = [(1000, 1300000000)]
751
        rf.valid_skips = ["", "S"]
752
        rf.valid_duplexes = THF6A_DUPLEX.values()
753
        rf.valid_characters = chirp_common.CHARSET_ASCII
754
        rf.valid_name_length = 8
755
        rf.memory_bounds = (0, self._upper)
756
        return rf
757

    
758
    def _cmd_set_memory(self, number, spec):
759
        if spec:
760
            spec = "," + spec
761
        return "MW", "0,%03i%s" % (number, spec)
762

    
763
    def _cmd_get_memory(self, number):
764
        return "MR", "0,%03i" % number
765

    
766
    def _cmd_get_memory_name(self, number):
767
        return "MNA", "%03i" % number
768

    
769
    def _cmd_set_memory_name(self, number, name):
770
        return "MNA", "%03i,%s" % (number, name)
771

    
772
    def _cmd_get_split(self, number):
773
        return "MR", "1,%03i" % number
774

    
775
    def _cmd_set_split(self, number, spec):
776
        return "MW", "1,%03i,%s" % (number, spec)
777

    
778
    def _parse_mem_spec(self, spec):
779
        mem = chirp_common.Memory()
780

    
781
        mem.number = int(spec[1])
782
        mem.freq = int(spec[2])
783
        mem.tuning_step = THF6A_STEPS[int(spec[3], 16)]
784
        mem.duplex = THF6A_DUPLEX[int(spec[4])]
785
        mem.tmode = get_tmode(spec[6], spec[7], spec[8])
786
        mem.rtone = self._kenwood_valid_tones[int(spec[9])]
787
        mem.ctone = self._kenwood_valid_tones[int(spec[10])]
788
        if spec[11] and spec[11].isdigit():
789
            mem.dtcs = chirp_common.DTCS_CODES[int(spec[11])]
790
        else:
791
            print "Unknown or invalid DCS: %s" % spec[11]
792
        if spec[12]:
793
            mem.offset = int(spec[12])
794
        else:
795
            mem.offset = 0
796
        mem.mode = THF6_MODES[int(spec[13])]
797
        if spec[14] == "1":
798
            mem.skip = "S"
799

    
800
        return mem
801

    
802
    def _make_mem_spec(self, mem):
803
        if mem.duplex in " +-":
804
            duplex = util.get_dict_rev(THF6A_DUPLEX, mem.duplex)
805
            offset = mem.offset
806
        elif mem.duplex == "split":
807
            duplex = 0
808
            offset = 0
809
        else:
810
            print "Bug: unsupported duplex `%s'" % mem.duplex
811
        spec = ( \
812
            "%011i" % mem.freq,
813
            "%X" % THF6A_STEPS.index(mem.tuning_step),
814
            "%i" % duplex,
815
            "0",
816
            "%i" % (mem.tmode == "Tone"),
817
            "%i" % (mem.tmode == "TSQL"),
818
            "%i" % (mem.tmode == "DTCS"),
819
            "%02i" % (self._kenwood_valid_tones.index(mem.rtone)),
820
            "%02i" % (self._kenwood_valid_tones.index(mem.ctone)),
821
            "%03i" % (chirp_common.DTCS_CODES.index(mem.dtcs)),
822
            "%09i" % offset,
823
            "%i" % (THF6_MODES.index(mem.mode)),
824
            "%i" % (mem.skip == "S"))
825

    
826
        return spec
827

    
828
@directory.register
829
class THF7ERadio(THF6ARadio):
830
    """Kenwood TH-F7"""
831
    MODEL = "TH-F7"
832

    
833
D710_DUPLEX = ["", "+", "-", "split"]
834
D710_MODES = ["FM", "NFM", "AM"]
835
D710_SKIP = ["", "S"]
836
D710_STEPS = [5.0, 6.25, 8.33, 10.0, 12.5, 15.0, 20.0, 25.0, 30.0, 50.0, 100.0]
837
D710_TONES = list(chirp_common.TONES)
838
D710_TONES.remove(159.8)
839
D710_TONES.remove(165.5)
840
D710_TONES.remove(171.3)
841
D710_TONES.remove(177.3)
842
D710_TONES.remove(183.5)
843
D710_TONES.remove(189.9)
844
D710_TONES.remove(196.6)
845
D710_TONES.remove(199.5)
846

    
847
@directory.register
848
class TMD710Radio(KenwoodLiveRadio):
849
    """Kenwood TM-D710"""
850
    MODEL = "TM-D710"
851
    
852
    _upper = 999
853
    _kenwood_valid_tones = list(D710_TONES)
854

    
855
    def get_features(self):
856
        rf = chirp_common.RadioFeatures()
857
        rf.can_odd_split = True
858
        rf.has_dtcs_polarity = False
859
        rf.has_bank = False
860
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS"]
861
        rf.valid_modes = D710_MODES
862
        rf.valid_duplexes = D710_DUPLEX
863
        rf.valid_tuning_steps = D710_STEPS
864
        rf.valid_characters = chirp_common.CHARSET_ASCII.replace(',','')
865
        rf.valid_name_length = 8
866
        rf.valid_skips = D710_SKIP
867
        rf.memory_bounds = (0, 999)
868
        return rf
869

    
870
    def _cmd_get_memory(self, number):
871
        return "ME", "%03i" % number
872

    
873
    def _cmd_get_memory_name(self, number):
874
        return "MN", "%03i" % number
875

    
876
    def _cmd_set_memory(self, number, spec):
877
        return "ME", "%03i,%s" % (number, spec)
878

    
879
    def _cmd_set_memory_name(self, number, name):
880
        return "MN", "%03i,%s" % (number, name)
881

    
882
    def _parse_mem_spec(self, spec):
883
        mem = chirp_common.Memory()
884

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

    
908
        return mem
909

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

    
930
        return spec
931

    
932
@directory.register
933
class THD72Radio(TMD710Radio):
934
    """Kenwood TH-D72"""
935
    MODEL = "TH-D72 (live mode)"
936
    HARDWARE_FLOW = sys.platform == "darwin" # only OS X driver needs hw flow
937

    
938
    def _parse_mem_spec(self, spec):
939
        mem = chirp_common.Memory()
940

    
941
        mem.number = int(spec[0])
942
        mem.freq = int(spec[1])
943
        mem.tuning_step = D710_STEPS[int(spec[2], 16)]
944
        mem.duplex = D710_DUPLEX[int(spec[3])]
945
        # Reverse
946
        if int(spec[5]):
947
            mem.tmode = "Tone"
948
        elif int(spec[6]):
949
            mem.tmode = "TSQL"
950
        elif int(spec[7]):
951
            mem.tmode = "DTCS"
952
        mem.rtone = self._kenwood_valid_tones[int(spec[9])]
953
        mem.ctone = self._kenwood_valid_tones[int(spec[10])]
954
        mem.dtcs = chirp_common.DTCS_CODES[int(spec[11])]
955
        mem.offset = int(spec[13])
956
        mem.mode = D710_MODES[int(spec[14])]
957
        # TX Frequency
958
        if int(spec[15]):
959
            mem.duplex = "split"
960
            mem.offset = int(spec[15])
961
        # Lockout
962
        mem.skip = D710_SKIP[int(spec[17])] # Memory Lockout
963

    
964
        return mem
965

    
966
    def _make_mem_spec(self, mem):
967
        spec = ( \
968
            "%010i" % mem.freq,
969
            "%X" % D710_STEPS.index(mem.tuning_step),
970
            "%i" % (0 if mem.duplex == "split" else \
971
                        D710_DUPLEX.index(mem.duplex)),
972
            "0", # Reverse
973
            "%i" % (mem.tmode == "Tone" and 1 or 0),
974
            "%i" % (mem.tmode == "TSQL" and 1 or 0),
975
            "%i" % (mem.tmode == "DTCS" and 1 or 0),
976
            "0",
977
            "%02i" % (self._kenwood_valid_tones.index(mem.rtone)),
978
            "%02i" % (self._kenwood_valid_tones.index(mem.ctone)),
979
            "%03i" % (chirp_common.DTCS_CODES.index(mem.dtcs)),
980
            "0",
981
            "%08i" % (0 if mem.duplex == "split" else mem.offset), # Offset
982
            "%i" % D710_MODES.index(mem.mode),
983
            "%010i" % (mem.offset if mem.duplex == "split" else 0), # TX Freq
984
            "0", # Unknown
985
            "%i" % D710_SKIP.index(mem.skip), # Memory Lockout
986
            )
987

    
988
        return spec
989

    
990
@directory.register
991
class TMV71Radio(TMD710Radio):
992
    """Kenwood TM-V71"""
993
    MODEL = "TM-V71"
994

    
995
THK2_DUPLEX = ["", "+", "-"]
996
THK2_MODES = ["FM", "NFM"]
997
THK2_TONES = list(chirp_common.TONES)
998
THK2_TONES.remove(159.8) # ??
999
THK2_TONES.remove(165.5) # ??
1000
THK2_TONES.remove(171.3) # ??
1001
THK2_TONES.remove(177.3) # ??
1002
THK2_TONES.remove(183.5) # ??
1003
THK2_TONES.remove(189.9) # ??
1004
THK2_TONES.remove(196.6) # ??
1005
THK2_TONES.remove(199.5) # ??
1006

    
1007
THK2_CHARS = chirp_common.CHARSET_UPPER_NUMERIC + "-/"
1008

    
1009
@directory.register
1010
class THK2Radio(KenwoodLiveRadio):
1011
    """Kenwood TH-K2"""
1012
    MODEL = "TH-K2"
1013

    
1014
    _kenwood_valid_tones = list(THK2_TONES)
1015

    
1016
    def get_features(self):
1017
        rf = chirp_common.RadioFeatures()
1018
        rf.can_odd_split = False
1019
        rf.has_dtcs_polarity = False
1020
        rf.has_bank = False
1021
        rf.has_tuning_step = False
1022
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS"]
1023
        rf.valid_modes = THK2_MODES
1024
        rf.valid_duplexes = THK2_DUPLEX
1025
        rf.valid_characters = THK2_CHARS
1026
        rf.valid_name_length = 6
1027
        rf.valid_bands = [(136000000, 173990000)]
1028
        rf.valid_skips = ["", "S"]
1029
        rf.valid_tuning_steps = [5.0]
1030
        rf.memory_bounds = (1, 50)
1031
        return rf
1032

    
1033
    def _cmd_get_memory(self, number):
1034
        return "ME", "%02i" % number
1035

    
1036
    def _cmd_get_memory_name(self, number):
1037
        return "MN", "%02i" % number
1038

    
1039
    def _cmd_set_memory(self, number, spec):
1040
        return "ME", "%02i,%s" % (number, spec)
1041

    
1042
    def _cmd_set_memory_name(self, number, name):
1043
        return "MN", "%02i,%s" % (number, name)
1044

    
1045
    def _parse_mem_spec(self, spec):
1046
        mem = chirp_common.Memory()
1047

    
1048
        mem.number = int(spec[0])
1049
        mem.freq = int(spec[1])
1050
        #mem.tuning_step = 
1051
        mem.duplex = THK2_DUPLEX[int(spec[3])]
1052
        if int(spec[5]):
1053
            mem.tmode = "Tone"
1054
        elif int(spec[6]):
1055
            mem.tmode = "TSQL"
1056
        elif int(spec[7]):
1057
            mem.tmode = "DTCS"
1058
        mem.rtone = self._kenwood_valid_tones[int(spec[8])]
1059
        mem.ctone = self._kenwood_valid_tones[int(spec[9])]
1060
        mem.dtcs = chirp_common.DTCS_CODES[int(spec[10])]
1061
        mem.offset = int(spec[11])
1062
        mem.mode = THK2_MODES[int(spec[12])]
1063
        mem.skip = int(spec[16]) and "S" or ""
1064
        return mem
1065

    
1066
    def _make_mem_spec(self, mem):
1067
        try:
1068
            rti = self._kenwood_valid_tones.index(mem.rtone)
1069
            cti = self._kenwood_valid_tones.index(mem.ctone)
1070
        except ValueError:
1071
            raise errors.UnsupportedToneError()
1072

    
1073
        spec = ( \
1074
            "%010i" % mem.freq,
1075
            "0",
1076
            "%i"    % THK2_DUPLEX.index(mem.duplex),
1077
            "0",
1078
            "%i"    % int(mem.tmode == "Tone"),
1079
            "%i"    % int(mem.tmode == "TSQL"),
1080
            "%i"    % int(mem.tmode == "DTCS"),
1081
            "%02i"  % rti,
1082
            "%02i"  % cti,
1083
            "%03i"  % chirp_common.DTCS_CODES.index(mem.dtcs),
1084
            "%08i"  % mem.offset,
1085
            "%i"    % THK2_MODES.index(mem.mode),
1086
            "0",
1087
            "%010i" % 0,
1088
            "0",
1089
            "%i"    % int(mem.skip == "S")
1090
            )
1091
        return spec
1092
            
1093
TM271_STEPS = [2.5, 5.0, 6.25, 10.0, 12.5, 15.0, 20.0, 25.0, 30.0, 50.0, 100.0]
1094

    
1095
@directory.register
1096
class TM271Radio(THK2Radio):
1097
    """Kenwood TM-271"""
1098
    MODEL = "TM-271"
1099
    
1100
    def get_features(self):
1101
        rf = chirp_common.RadioFeatures()
1102
        rf.can_odd_split = False
1103
        rf.has_dtcs_polarity = False
1104
        rf.has_bank = False
1105
        rf.has_tuning_step = False
1106
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS"]
1107
        rf.valid_modes = THK2_MODES
1108
        rf.valid_duplexes = THK2_DUPLEX
1109
        rf.valid_characters = THK2_CHARS
1110
        rf.valid_name_length = 6
1111
        rf.valid_bands = [(137000000, 173990000)]
1112
        rf.valid_skips = ["", "S"]
1113
        rf.valid_tuning_steps = list(TM271_STEPS)
1114
        rf.memory_bounds = (0, 99)
1115
        return rf
1116

    
1117
    def _cmd_get_memory(self, number):
1118
        return "ME", "%03i" % number
1119

    
1120
    def _cmd_get_memory_name(self, number):
1121
        return "MN", "%03i" % number
1122

    
1123
    def _cmd_set_memory(self, number, spec):
1124
        return "ME", "%03i,%s" % (number, spec)
1125

    
1126
    def _cmd_set_memory_name(self, number, name):
1127
        return "MN", "%03i,%s" % (number, name)
1128

    
1129
@directory.register
1130
class TM281Radio(TM271Radio):
1131
    """Kenwood TM-281"""
1132
    MODEL = "TM-281"
1133
    # seems that this is a perfect clone of TM271 with just a different model
1134

    
1135
def do_test():
1136
    """Dev test"""
1137
    mem = chirp_common.Memory()
1138
    mem.number = 1
1139
    mem.freq = 144000000
1140
    mem.duplex = "split"
1141
    mem.offset = 146000000
1142

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

    
1162
    radio = tc(FakeSerial())
1163
    radio.set_memory(mem)
1164

    
1165
@directory.register
1166
class TM471Radio(THK2Radio):
1167
    """Kenwood TM-471"""
1168
    MODEL = "TM-471"
1169
    
1170
    def get_features(self):
1171
        rf = chirp_common.RadioFeatures()
1172
        rf.can_odd_split = False
1173
        rf.has_dtcs_polarity = False
1174
        rf.has_bank = False
1175
        rf.has_tuning_step = False
1176
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS"]
1177
        rf.valid_modes = THK2_MODES
1178
        rf.valid_duplexes = THK2_DUPLEX
1179
        rf.valid_characters = THK2_CHARS
1180
        rf.valid_name_length = 6
1181
        rf.valid_bands = [(444000000, 479990000)]
1182
        rf.valid_skips = ["", "S"]
1183
        rf.valid_tuning_steps = [5.0]
1184
        rf.memory_bounds = (0, 99)
1185
        return rf
1186

    
1187
    def _cmd_get_memory(self, number):
1188
        return "ME", "%03i" % number
1189

    
1190
    def _cmd_get_memory_name(self, number):
1191
        return "MN", "%03i" % number
1192

    
1193
    def _cmd_set_memory(self, number, spec):
1194
        return "ME", "%03i,%s" % (number, spec)
1195

    
1196
    def _cmd_set_memory_name(self, number, name):
1197
        return "MN", "%03i,%s" % (number, name)
1198

    
1199
def do_test():
1200
    """Dev test"""
1201
    mem = chirp_common.Memory()
1202
    mem.number = 1
1203
    mem.freq = 440000000
1204
    mem.duplex = "split"
1205
    mem.offset = 442000000
1206

    
1207
    tc = THF6ARadio
1208
    class FakeSerial:
1209
        """Faked serial line"""
1210
        buf = ""
1211
        def write(self, buf):
1212
            """Write"""
1213
            self.buf = buf
1214
        def read(self, count):
1215
            """Read"""
1216
            if self.buf[:2] == "ID":
1217
                return "ID %s\r" % tc.MODEL
1218
            return self.buf
1219
        def setTimeout(self, foo):
1220
            """Set Timeout"""
1221
            pass
1222
        def setBaudrate(self, foo):
1223
            """Set Baudrate"""
1224
            pass
1225

    
1226
    radio = tc(FakeSerial())
1227
    radio.set_memory(mem)
1228

    
1229
if __name__ == "__main__":
1230
    do_test()
1231