Project

General

Profile

Bug #8209 » th9800.py

Jony Forester, 12/03/2020 11:12 PM

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

    
18
from chirp import bitwise, chirp_common, directory, errors, util, memmap
19
import struct
20
from chirp.settings import RadioSetting, RadioSettingGroup, \
21
    RadioSettingValueInteger, RadioSettingValueList, \
22
    RadioSettingValueBoolean, RadioSettingValueString, \
23
    RadioSettingValueFloat, InvalidValueError, RadioSettings
24
from chirp.chirp_common import format_freq
25
import os
26
import time
27
import logging
28
from datetime import date
29

    
30
LOG = logging.getLogger(__name__)
31

    
32
TH9800_MEM_FORMAT = """
33
struct mem {
34
  lbcd rx_freq[4];
35
  lbcd tx_freq[4];
36
  lbcd ctcss[2];
37
  lbcd dtcs[2];
38
  u8 power:2,
39
     BeatShift:1,
40
     unknown0a:2,
41
     display:1,     // freq=0, name=1
42
     scan:2;
43
  u8 fmdev:2,       // wide=00, mid=01, narrow=10
44
     scramb:1,
45
     compand:1,
46
     emphasis:1
47
     unknown1a:2,
48
     sqlmode:1;     // carrier, tone
49
  u8 rptmod:2,      // off, -, +
50
     reverse:1,
51
     talkaround:1,
52
     step:4;
53
  u8 dtcs_pol:2,
54
     bclo:2,
55
     unknown3:2,
56
     tmode:2;
57
  lbcd offset[4];
58
  u8 hsdtype:2,     // off, 2-tone, 5-tone, dtmf
59
     unknown5a:1,
60
     am:1,
61
     unknown5b:4;
62
  u8 unknown6[3];
63
  char name[6];
64
  u8 empty[2];
65
};
66

    
67
#seekto 0x%04X;
68
struct mem memory[800];
69

    
70
#seekto 0x%04X;
71
struct {
72
  struct mem lower;
73
  struct mem upper;
74
} scanlimits[5];
75

    
76
#seekto 0x%04X;
77
struct {
78
    u8  unk0xdc20:5,
79
        left_sql:3;
80
    u8  apo;
81
    u8  unk0xdc22:5,
82
        backlight:3;
83
    u8  unk0xdc23;
84
    u8  beep:1,
85
        keylock:1,
86
        pttlock:2,
87
        unk0xdc24_32:2,
88
        hyper_chan:1,
89
        right_func_key:1;
90
    u8  tbst_freq:2,
91
        ani_display:1,
92
        unk0xdc25_4:1
93
        mute_mode:2,
94
        unk0xdc25_10:2;
95
    u8  auto_xfer:1,
96
        auto_contact:1,
97
        unk0xdc26_54:2,
98
        auto_am:1,
99
        unk0xdc26_210:3;
100
    u8  unk0xdc27_76543:5,
101
        scan_mode:1,
102
        unk0xdc27_1:1,
103
        scan_resume:1;
104
    u16 scramb_freq;
105
    u16 scramb_freq1;
106
    u8  exit_delay;
107
    u8  unk0xdc2d;
108
    u8  unk0xdc2e:5,
109
        right_sql:3;
110
    u8  unk0xdc2f:4,
111
        beep_vol:4;
112
    u8  tot;
113
    u8  tot_alert;
114
    u8  tot_rekey;
115
    u8  tot_reset;
116
    u8  unk0xdc34;
117
    u8  unk0xdc35;
118
    u8  unk0xdc36;
119
    u8  unk0xdc37;
120
    u8  p1;
121
    u8  p2;
122
    u8  p3;
123
    u8  p4;
124
} settings;
125

    
126
#seekto 0x%04X;
127
u8  chan_active[128];
128
u8  scan_enable[128];
129
u8  priority[128];
130

    
131
#seekto 0x%04X;
132
struct {
133
    char sn[8];
134
    char model[8];
135
    char code[16];
136
    u8 empty[8];
137
    lbcd prog_yr[2];
138
    lbcd prog_mon;
139
    lbcd prog_day;
140
    u8 empty_10f2c[4];
141
} info;
142

    
143
struct {
144
  lbcd lorx[4];
145
  lbcd hirx[4];
146
  lbcd lotx[4];
147
  lbcd hitx[4];
148
} bandlimits[9];
149

    
150
"""
151

    
152

    
153
BLANK_MEMORY = "\xFF" * 8 + "\x00\x10\x23\x00\xC0\x08\x06\x00" \
154
               "\x00\x00\x76\x00\x00\x00" + "\xFF" * 10
155
DTCS_POLARITY = ["NN", "RN", "NR", "RR"]
156
SCAN_MODES = ["", "S", "P"]
157
MODES = ["WFM", "FM", "NFM"]
158
TMODES = ["", "Tone", "TSQL", "DTCS"]
159
POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=5.00),
160
                chirp_common.PowerLevel("Mid2", watts=10.00),
161
                chirp_common.PowerLevel("Mid1", watts=20.00),
162
                chirp_common.PowerLevel("High", watts=50.00)]
163
BUSY_LOCK = ["off", "Carrier", "2 tone"]
164
MICKEYFUNC = ["None", "SCAN", "SQL.OFF", "TCALL", "PPTR", "PRI", "LOW", "TONE",
165
              "MHz", "REV", "HOME", "BAND", "VFO/MR"]
166
SQLPRESET = ["Off", "2", "5", "9", "Full"]
167
BANDS = ["30MHz", "50MHz", "60MHz", "108MHz", "150MHz", "250MHz", "350MHz",
168
         "450MHz", "850MHz"]
169
STEPS = [2.5, 5.0, 6.25, 7.5, 8.33, 10.0, 12.5,
170
         15.0, 20.0, 25.0, 30.0, 50.0, 100.0]
171

    
172

    
173
class TYTTH9800Base(chirp_common.Radio):
174
    """Base class for TYT TH-9800"""
175
    VENDOR = "TYT"
176

    
177
    def get_features(self):
178
        rf = chirp_common.RadioFeatures()
179
        rf.memory_bounds = (1, 800)
180
        rf.has_bank = False
181
        rf.has_tuning_step = True
182
        rf.valid_tuning_steps = STEPS
183
        rf.can_odd_split = True
184
        rf.valid_duplexes = ["", "-", "+", "split", "off"]
185
        rf.valid_tmodes = TMODES
186
        rf.has_ctone = False
187
        rf.valid_power_levels = POWER_LEVELS
188
        rf.valid_characters = chirp_common.CHARSET_UPPER_NUMERIC + "#*-+"
189
        rf.valid_bands = [(26000000,  33000000),
190
                          (40000000,  65000000),
191
                          (66000000,  88000000),
192
                          (108000000, 133995000),
193
                          (134000000, 180000000),
194
                          (220000000, 250000000),
195
                          (350000000, 399995000),
196
                          (400000000, 512000000),
197
                          (750000000, 950000000)]
198
        rf.valid_skips = SCAN_MODES
199
        rf.valid_modes = MODES + ["AM"]
200
        rf.valid_name_length = 6
201
        rf.has_settings = True
202
        return rf
203

    
204
    def process_mmap(self):
205
        self._memobj = bitwise.parse(
206
            TH9800_MEM_FORMAT %
207
            (self._mmap_offset, self._scanlimits_offset, self._settings_offset,
208
             self._chan_active_offset, self._info_offset), self._mmap)
209

    
210
    def get_active(self, banktype, num):
211
        """get active flag for channel active,
212
        scan enable, or priority banks"""
213
        bank = getattr(self._memobj, banktype)
214
        index = (num - 1) / 8
215
        bitpos = (num - 1) % 8
216
        mask = 2**bitpos
217
        enabled = bank[index] & mask
218
        if enabled:
219
            return True
220
        else:
221
            return False
222

    
223
    def set_active(self, banktype, num, enable=True):
224
        """set active flag for channel active,
225
        scan enable, or priority banks"""
226
        bank = getattr(self._memobj, banktype)
227
        index = (num - 1) / 8
228
        bitpos = (num - 1) % 8
229
        mask = 2**bitpos
230
        if enable:
231
            bank[index] |= mask
232
        else:
233
            bank[index] &= ~mask
234

    
235
    def get_raw_memory(self, number):
236
        return repr(self._memobj.memory[number - 1])
237

    
238
    def get_memory(self, number):
239
        _mem = self._memobj.memory[number - 1]
240
        mem = chirp_common.Memory()
241
        mem.number = number
242

    
243
        mem.empty = not self.get_active("chan_active", number)
244
        if mem.empty:
245
            return mem
246

    
247
        mem.freq = int(_mem.rx_freq) * 10
248

    
249
        txfreq = int(_mem.tx_freq) * 10
250
        if txfreq == mem.freq:
251
            mem.duplex = ""
252
        elif txfreq == 0:
253
            mem.duplex = "off"
254
            mem.offset = 0
255
        elif abs(txfreq - mem.freq) > 70000000:
256
            mem.duplex = "split"
257
            mem.offset = txfreq
258
        elif txfreq < mem.freq:
259
            mem.duplex = "-"
260
            mem.offset = mem.freq - txfreq
261
        elif txfreq > mem.freq:
262
            mem.duplex = "+"
263
            mem.offset = txfreq - mem.freq
264

    
265
        mem.dtcs_polarity = DTCS_POLARITY[_mem.dtcs_pol]
266

    
267
        mem.tmode = TMODES[int(_mem.tmode)]
268
        mem.ctone = mem.rtone = int(_mem.ctcss) / 10.0
269
        mem.dtcs = int(_mem.dtcs)
270

    
271
        mem.name = str(_mem.name)
272
        mem.name = mem.name.replace("\xFF", " ").rstrip()
273

    
274
        if not self.get_active("scan_enable", number):
275
            mem.skip = "S"
276
        elif self.get_active("priority", number):
277
            mem.skip = "P"
278
        else:
279
            mem.skip = ""
280

    
281
        mem.mode = _mem.am and "AM" or MODES[int(_mem.fmdev)]
282

    
283
        mem.power = POWER_LEVELS[_mem.power]
284
        mem.tuning_step = STEPS[_mem.step]
285

    
286
        mem.extra = RadioSettingGroup("extra", "Extra")
287

    
288
        opts = ["Frequency", "Name"]
289
        display = RadioSetting(
290
                "display", "Display",
291
                RadioSettingValueList(opts, opts[_mem.display]))
292
        mem.extra.append(display)
293

    
294
        bclo = RadioSetting(
295
                "bclo", "Busy Lockout",
296
                RadioSettingValueList(BUSY_LOCK, BUSY_LOCK[_mem.bclo]))
297
        bclo.set_doc("Busy Lockout")
298
        mem.extra.append(bclo)
299

    
300
        emphasis = RadioSetting(
301
                "emphasis", "Emphasis",
302
                RadioSettingValueBoolean(bool(_mem.emphasis)))
303
        emphasis.set_doc("Boosts 300Hz to 2500Hz mic response")
304
        mem.extra.append(emphasis)
305

    
306
        compand = RadioSetting(
307
                "compand", "Compand",
308
                RadioSettingValueBoolean(bool(_mem.compand)))
309
        compand.set_doc("Compress Audio")
310
        mem.extra.append(compand)
311

    
312
        BeatShift = RadioSetting(
313
                "BeatShift", "BeatShift",
314
                RadioSettingValueBoolean(bool(_mem.BeatShift)))
315
        BeatShift.set_doc("Beat Shift")
316
        mem.extra.append(BeatShift)
317

    
318
        TalkAround = RadioSetting(
319
                "talkaround", "Talk Around",
320
                RadioSettingValueBoolean(bool(_mem.talkaround)))
321
        TalkAround.set_doc("Simplex mode when out of range of repeater")
322
        mem.extra.append(TalkAround)
323

    
324
        scramb = RadioSetting(
325
                "scramb", "Scramble",
326
                RadioSettingValueBoolean(bool(_mem.scramb)))
327
        scramb.set_doc("Frequency inversion Scramble")
328
        mem.extra.append(scramb)
329

    
330
        return mem
331

    
332
    def set_memory(self, mem):
333
        _mem = self._memobj.memory[mem.number - 1]
334

    
335
        _prev_active = self.get_active("chan_active", mem.number)
336
        self.set_active("chan_active", mem.number, not mem.empty)
337
        if mem.empty or not _prev_active:
338
            LOG.debug("initializing memory channel %d" % mem.number)
339
            _mem.set_raw(BLANK_MEMORY)
340

    
341
        if mem.empty:
342
            return
343

    
344
        _mem.rx_freq = mem.freq / 10
345
        if mem.duplex == "split":
346
            _mem.tx_freq = mem.offset / 10
347
        elif mem.duplex == "-":
348
            _mem.tx_freq = (mem.freq - mem.offset) / 10
349
        elif mem.duplex == "+":
350
            _mem.tx_freq = (mem.freq + mem.offset) / 10
351
        elif mem.duplex == "off":
352
            _mem.tx_freq = 0
353
            _mem.offset = 0
354
        else:
355
            _mem.tx_freq = mem.freq / 10
356

    
357
        _mem.tmode = TMODES.index(mem.tmode)
358
        if mem.tmode == "TSQL" or mem.tmode == "DTCS":
359
            _mem.sqlmode = 1
360
        else:
361
            _mem.sqlmode = 0
362
        _mem.ctcss = mem.rtone * 10
363
        _mem.dtcs = mem.dtcs
364
        _mem.dtcs_pol = DTCS_POLARITY.index(mem.dtcs_polarity)
365

    
366
        _mem.name = mem.name.ljust(6, "\xFF")
367

    
368
        # autoset display to name if filled, else show frequency
369
        if mem.extra:
370
            # mem.extra only seems to be populated when called from edit panel
371
            display = mem.extra["display"]
372
        else:
373
            display = None
374
        if mem.name:
375
            _mem.display = True
376
            if display and not display.changed():
377
                display.value = "Name"
378
        else:
379
            _mem.display = False
380
            if display and not display.changed():
381
                display.value = "Frequency"
382

    
383
        _mem.scan = SCAN_MODES.index(mem.skip)
384
        if mem.skip == "P":
385
            self.set_active("priority", mem.number, True)
386
            self.set_active("scan_enable", mem.number, True)
387
        elif mem.skip == "S":
388
            self.set_active("priority", mem.number, False)
389
            self.set_active("scan_enable", mem.number, False)
390
        elif mem.skip == "":
391
            self.set_active("priority", mem.number, False)
392
            self.set_active("scan_enable", mem.number, True)
393

    
394
        if mem.mode == "AM":
395
            _mem.am = True
396
            _mem.fmdev = 0
397
        else:
398
            _mem.am = False
399
            _mem.fmdev = MODES.index(mem.mode)
400

    
401
        if mem.power:
402
            _mem.power = POWER_LEVELS.index(mem.power)
403
        else:
404
            _mem.power = 0    # low
405
        _mem.step = STEPS.index(mem.tuning_step)
406

    
407
        for setting in mem.extra:
408
            LOG.debug("@set_mem:", setting.get_name(), setting.value)
409
            setattr(_mem, setting.get_name(), setting.value)
410

    
411
    def get_settings(self):
412
        _settings = self._memobj.settings
413
        _info = self._memobj.info
414
        _bandlimits = self._memobj.bandlimits
415
        basic = RadioSettingGroup("basic", "Basic")
416
        info = RadioSettingGroup("info", "Model Info")
417
        top = RadioSettings(basic, info)
418
        basic.append(RadioSetting(
419
                "beep", "Beep",
420
                RadioSettingValueBoolean(_settings.beep)))
421
        basic.append(RadioSetting(
422
                "beep_vol", "Beep Volume",
423
                RadioSettingValueInteger(0, 15, _settings.beep_vol)))
424
        basic.append(RadioSetting(
425
                "keylock", "Key Lock",
426
                RadioSettingValueBoolean(_settings.keylock)))
427
        basic.append(RadioSetting(
428
                "ani_display", "ANI Display",
429
                RadioSettingValueBoolean(_settings.ani_display)))
430
        basic.append(RadioSetting(
431
                "auto_xfer", "Auto Transfer",
432
                RadioSettingValueBoolean(_settings.auto_xfer)))
433
        basic.append(RadioSetting(
434
                "auto_contact", "Auto Contact Always Remind",
435
                RadioSettingValueBoolean(_settings.auto_contact)))
436
        basic.append(RadioSetting(
437
                "auto_am", "Auto AM",
438
                RadioSettingValueBoolean(_settings.auto_am)))
439
        basic.append(RadioSetting(
440
                "left_sql", "Left Squelch",
441
                RadioSettingValueList(
442
                    SQLPRESET, SQLPRESET[_settings.left_sql])))
443
        basic.append(RadioSetting(
444
                "right_sql", "Right Squelch",
445
                RadioSettingValueList(
446
                    SQLPRESET, SQLPRESET[_settings.right_sql])))
447
#      basic.append(RadioSetting("apo", "Auto Power off (0.1h)",
448
#              RadioSettingValueInteger(0, 20, _settings.apo)))
449
        opts = ["Off"] + ["%0.1f" % (t / 10.0) for t in range(1, 21, 1)]
450
        basic.append(RadioSetting(
451
                "apo", "Auto Power off (Hours)",
452
                RadioSettingValueList(opts, opts[_settings.apo])))
453
        opts = ["Off", "1", "2", "3", "Full"]
454
        basic.append(RadioSetting(
455
                "backlight", "Display Backlight",
456
                RadioSettingValueList(opts, opts[_settings.backlight])))
457
        opts = ["Off", "Right", "Left", "Both"]
458
        basic.append(RadioSetting(
459
                "pttlock", "PTT Lock",
460
                RadioSettingValueList(opts, opts[_settings.pttlock])))
461
        opts = ["Manual", "Auto"]
462
        basic.append(RadioSetting(
463
                "hyper_chan", "Hyper Channel",
464
                RadioSettingValueList(opts, opts[_settings.hyper_chan])))
465
        opts = ["Key 1", "Key 2"]
466
        basic.append(RadioSetting(
467
                "right_func_key", "Right Function Key",
468
                RadioSettingValueList(opts, opts[_settings.right_func_key])))
469
        opts = ["1000Hz", "1450Hz", "1750Hz", "2100Hz"]
470
        basic.append(RadioSetting(
471
                "tbst_freq", "Tone Burst Frequency",
472
                RadioSettingValueList(opts, opts[_settings.tbst_freq])))
473
        opts = ["Off", "TX", "RX", "TX RX"]
474
        basic.append(RadioSetting(
475
                "mute_mode", "Mute Mode",
476
                RadioSettingValueList(opts, opts[_settings.mute_mode])))
477
        opts = ["MEM", "MSM"]
478
        scanmode = RadioSetting(
479
                "scan_mode", "Scan Mode",
480
                RadioSettingValueList(opts, opts[_settings.scan_mode]))
481
        scanmode.set_doc("MEM = Normal scan, bypass channels marked skip. "
482
                         " MSM = Scan only channels marked priority.")
483
        basic.append(scanmode)
484
        opts = ["TO", "CO"]
485
        basic.append(RadioSetting(
486
                "scan_resume", "Scan Resume",
487
                RadioSettingValueList(opts, opts[_settings.scan_resume])))
488
        opts = ["%0.1f" % (t / 10.0) for t in range(0, 51, 1)]
489
        basic.append(RadioSetting(
490
                "exit_delay", "Span Transit Exit Delay",
491
                RadioSettingValueList(opts, opts[_settings.exit_delay])))
492
        basic.append(RadioSetting(
493
                "tot", "Time Out Timer (minutes)",
494
                RadioSettingValueInteger(0, 30, _settings.tot)))
495
        basic.append(RadioSetting(
496
                "tot_alert", "Time Out Timer Pre Alert(seconds)",
497
                RadioSettingValueInteger(0, 15, _settings.tot_alert)))
498
        basic.append(RadioSetting(
499
                "tot_rekey", "Time Out Rekey (seconds)",
500
                RadioSettingValueInteger(0, 15, _settings.tot_rekey)))
501
        basic.append(RadioSetting(
502
                "tot_reset", "Time Out Reset(seconds)",
503
                RadioSettingValueInteger(0, 15, _settings.tot_reset)))
504
        basic.append(RadioSetting(
505
                "p1", "P1 Function",
506
                RadioSettingValueList(MICKEYFUNC, MICKEYFUNC[_settings.p1])))
507
        basic.append(RadioSetting(
508
                "p2", "P2 Function",
509
                RadioSettingValueList(MICKEYFUNC, MICKEYFUNC[_settings.p2])))
510
        basic.append(RadioSetting(
511
                "p3", "P3 Function",
512
                RadioSettingValueList(MICKEYFUNC, MICKEYFUNC[_settings.p3])))
513
        basic.append(RadioSetting(
514
                "p4", "P4 Function",
515
                RadioSettingValueList(MICKEYFUNC, MICKEYFUNC[_settings.p4])))
516
#      opts = ["0", "1"]
517
#      basic.append(RadioSetting("x", "Desc",
518
#            RadioSettingValueList(opts, opts[_settings.x])))
519

    
520
        def _filter(name):
521
            filtered = ""
522
            for char in str(name):
523
                if char in chirp_common.CHARSET_ASCII:
524
                    filtered += char
525
                else:
526
                    filtered += " "
527
            return filtered
528

    
529
        rsvs = RadioSettingValueString(0, 8, _filter(_info.sn))
530
        rsvs.set_mutable(False)
531
        rs = RadioSetting("sn", "Serial Number", rsvs)
532
        info.append(rs)
533

    
534
        rsvs = RadioSettingValueString(0, 8, _filter(_info.model))
535
        rsvs.set_mutable(False)
536
        rs = RadioSetting("model", "Model Name", rsvs)
537
        info.append(rs)
538

    
539
        rsvs = RadioSettingValueString(0, 16, _filter(_info.code))
540
        rsvs.set_mutable(False)
541
        rs = RadioSetting("code", "Model Code", rsvs)
542
        info.append(rs)
543

    
544
        progdate = "%d/%d/%d" % (_info.prog_mon, _info.prog_day,
545
                                 _info.prog_yr)
546
        rsvs = RadioSettingValueString(0, 10, progdate)
547
        rsvs.set_mutable(False)
548
        rs = RadioSetting("progdate", "Last Program Date", rsvs)
549
        info.append(rs)
550

    
551
        # 9 band limits
552
        for i in range(0, 9):
553
            objname = BANDS[i] + "lorx"
554
            objnamepp = BANDS[i] + " Rx Start"
555
            # rsv = RadioSettingValueInteger(0, 100000000,
556
            #              int(_bandlimits[i].lorx))
557
            rsv = RadioSettingValueString(
558
                    0, 10, format_freq(int(_bandlimits[i].lorx)*10))
559
            rsv.set_mutable(False)
560
            rs = RadioSetting(objname, objnamepp, rsv)
561
            info.append(rs)
562
            objname = BANDS[i] + "hirx"
563
            objnamepp = BANDS[i] + " Rx end"
564
            rsv = RadioSettingValueString(
565
                    0, 10, format_freq(int(_bandlimits[i].hirx)*10))
566
            rsv.set_mutable(False)
567
            rs = RadioSetting(objname, objnamepp, rsv)
568
            info.append(rs)
569
            objname = BANDS[i] + "lotx"
570
            objnamepp = BANDS[i] + " Tx Start"
571
            rsv = RadioSettingValueString(
572
                    0, 10, format_freq(int(_bandlimits[i].lotx)*10))
573
            rsv.set_mutable(False)
574
            rs = RadioSetting(objname, objnamepp, rsv)
575
            info.append(rs)
576
            objname = BANDS[i] + "hitx"
577
            objnamepp = BANDS[i] + " Tx end"
578
            rsv = RadioSettingValueString(
579
                    0, 10, format_freq(int(_bandlimits[i].hitx)*10))
580
            rsv.set_mutable(False)
581
            rs = RadioSetting(objname, objnamepp, rsv)
582
            info.append(rs)
583

    
584
        return top
585

    
586
    def set_settings(self, settings):
587
        _settings = self._memobj.settings
588
        _info = self._memobj.info
589
        _bandlimits = self._memobj.bandlimits
590
        for element in settings:
591
            if not isinstance(element, RadioSetting):
592
                self.set_settings(element)
593
                continue
594
            if not element.changed():
595
                continue
596
            try:
597
                setting = element.get_name()
598
                oldval = getattr(_settings, setting)
599
                newval = element.value
600

    
601
                LOG.debug("Setting %s(%s) <= %s" % (setting, oldval, newval))
602
                setattr(_settings, setting, newval)
603
            except Exception, e:
604
                LOG.debug(element.get_name())
605
                raise
606

    
607

    
608
@directory.register
609
class TYTTH9800File(TYTTH9800Base, chirp_common.FileBackedRadio):
610
    """TYT TH-9800 .dat file"""
611
    MODEL = "TH-9800 File"
612

    
613
    FILE_EXTENSION = "dat"
614

    
615
    _memsize = 69632
616
    _mmap_offset = 0x1100
617
    _scanlimits_offset = 0xC800 + _mmap_offset
618
    _settings_offset = 0xCB20 + _mmap_offset
619
    _chan_active_offset = 0xCB80 + _mmap_offset
620
    _info_offset = 0xfe00 + _mmap_offset
621

    
622
    def __init__(self, pipe):
623
        self.errors = []
624
        self._mmap = None
625

    
626
        if isinstance(pipe, str):
627
            self.pipe = None
628
            self.load_mmap(pipe)
629
        else:
630
            chirp_common.FileBackedRadio.__init__(self, pipe)
631

    
632
    @classmethod
633
    def match_model(cls, filedata, filename):
634
        return len(filedata) == cls._memsize and filename.endswith('.dat')
635

    
636

    
637
def _identify(radio):
638
    """Do identify handshake with TYT"""
639
    try:
640
        radio.pipe.write("\x02PROGRA")
641
        ack = radio.pipe.read(1)
642
        if ack != "A":
643
            util.hexprint(ack)
644
            raise errors.RadioError("Radio did not ACK first command: %x"
645
                                    % ord(ack))
646
    except:
647
        LOG.debug(util.hexprint(ack))
648
        raise errors.RadioError("Unable to communicate with the radio")
649

    
650
    radio.pipe.write("M\x02")
651
    ident = radio.pipe.read(16)
652
    radio.pipe.write("A")
653
    r = radio.pipe.read(1)
654
    if r != "A":
655
        raise errors.RadioError("Ack failed")
656
    return ident
657

    
658

    
659
def _download(radio, memsize=0x10000, blocksize=0x80):
660
    """Download from TYT TH-9800"""
661
    data = _identify(radio)
662
    LOG.info("ident:", util.hexprint(data))
663
    offset = 0x100
664
    for addr in range(offset, memsize, blocksize):
665
        msg = struct.pack(">cHB", "R", addr, blocksize)
666
        radio.pipe.write(msg)
667
        block = radio.pipe.read(blocksize + 4)
668
        if len(block) != (blocksize + 4):
669
            LOG.debug(util.hexprint(block))
670
            raise errors.RadioError("Radio sent a short block")
671
        radio.pipe.write("A")
672
        ack = radio.pipe.read(1)
673
        if ack != "A":
674
            LOG.debug(util.hexprint(ack))
675
            raise errors.RadioError("Radio NAKed block")
676
        data += block[4:]
677

    
678
        if radio.status_fn:
679
            status = chirp_common.Status()
680
            status.cur = addr
681
            status.max = memsize
682
            status.msg = "Cloning from radio"
683
            radio.status_fn(status)
684

    
685
    radio.pipe.write("ENDR")
686

    
687
    return memmap.MemoryMap(data)
688

    
689

    
690
def _upload(radio, memsize=0xF400, blocksize=0x80):
691
    """Upload to TYT TH-9800"""
692
    data = _identify(radio)
693

    
694
    radio.pipe.timeout = 1
695

    
696
    if data != radio._mmap[:radio._mmap_offset]:
697
        raise errors.RadioError(
698
            "Model mis-match: \n%s\n%s" %
699
            (util.hexprint(data),
700
             util.hexprint(radio._mmap[:radio._mmap_offset])))
701
    # in the factory software they update the last program date when
702
    # they upload, So let's do the same
703
    today = date.today()
704
    y = today.year
705
    m = today.month
706
    d = today.day
707
    _info = radio._memobj.info
708

    
709
    ly = _info.prog_yr
710
    lm = _info.prog_mon
711
    ld = _info.prog_day
712
    LOG.debug("Updating last program date:%d/%d/%d" % (lm, ld, ly))
713
    LOG.debug("                  to today:%d/%d/%d" % (m, d, y))
714

    
715
    _info.prog_yr = y
716
    _info.prog_mon = m
717
    _info.prog_day = d
718

    
719
    offset = 0x0100
720
    for addr in range(offset, memsize, blocksize):
721
        mapaddr = addr + radio._mmap_offset - offset
722
        LOG.debug("addr: 0x%04X, mmapaddr: 0x%04X" % (addr, mapaddr))
723
        msg = struct.pack(">cHB", "W", addr, blocksize)
724
        msg += radio._mmap[mapaddr:(mapaddr + blocksize)]
725
        LOG.debug(util.hexprint(msg))
726
        radio.pipe.write(msg)
727
        ack = radio.pipe.read(1)
728
        if ack != "A":
729
            LOG.debug(util.hexprint(ack))
730
            raise errors.RadioError("Radio did not ack block 0x%04X" % addr)
731

    
732
        if radio.status_fn:
733
            status = chirp_common.Status()
734
            status.cur = addr
735
            status.max = memsize
736
            status.msg = "Cloning to radio"
737
            radio.status_fn(status)
738

    
739
    # End of clone
740
    radio.pipe.write("ENDW")
741

    
742
    # Checksum?
743
    final_data = radio.pipe.read(3)
744
    LOG.debug("final:", util.hexprint(final_data))
745

    
746

    
747
@directory.register
748
class TYTTH9800Radio(TYTTH9800Base, chirp_common.CloneModeRadio,
749
                     chirp_common.ExperimentalRadio):
750
    VENDOR = "TYT"
751
    MODEL = "TH-9800"
752
    BAUD_RATE = 38400
753

    
754
    _memsize = 65296
755
    _mmap_offset = 0x0010
756
    _scanlimits_offset = 0xC800 + _mmap_offset
757
    _settings_offset = 0xCB20 + _mmap_offset
758
    _chan_active_offset = 0xCB80 + _mmap_offset
759
    _info_offset = 0xfe00 + _mmap_offset
760

    
761
    @classmethod
762
    def match_model(cls, filedata, filename):
763
        if len(filedata) != cls._memsize:
764
            return False
765
        # TYT set this model for TH-7800 _AND_ TH-9800
766
        if not filedata[0xfe18:0xfe1e] == "TH9800":
767
            return False
768
        # TH-9800 bandlimits differ from TH-7800.  First band is used
769
        # (non-zero).
770
        first_bandlimit = struct.unpack("BBBBBBBBBBBBBBBB",
771
                                        filedata[0xfe40:0xfe50])
772
        if all(v == 0 for v in first_bandlimit):
773
            return False
774
        return True
775

    
776
    @classmethod
777
    def get_prompts(cls):
778
        rp = chirp_common.RadioPrompts()
779
        rp.experimental = (
780
         'This is experimental support for TH-9800 '
781
         'which is still under development.\n'
782
         'Please ensure you have a good backup with OEM software.\n'
783
         'Also please send in bug and enhancement requests!\n'
784
         'You have been warned. Proceed at your own risk!')
785
        return rp
786

    
787
    def sync_in(self):
788
        try:
789
            self._mmap = _download(self)
790
        except Exception, e:
791
            raise errors.RadioError(
792
                    "Failed to communicate with the radio: %s" % e)
793
        self.process_mmap()
794

    
795
    def sync_out(self):
796
        try:
797
            _upload(self)
798
        except Exception, e:
799
            raise errors.RadioError(
800
                    "Failed to communicate with the radio: %s" % e)
(3-3/3)