Project

General

Profile

Bug #11326 » tdh8.py

Detect-o-matic version (fixed) - Dan Smith, 05/01/2024 06:01 PM

 
1
# Copyright 2012 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 2 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 struct
17
import time
18
import logging
19

    
20

    
21
from chirp import chirp_common, errors, util, directory, memmap
22
from chirp import bitwise
23
from chirp.settings import InvalidValueError, RadioSetting, \
24
    RadioSettingGroup, RadioSettingValueFloat, \
25
    RadioSettingValueList, RadioSettingValueBoolean, \
26
    RadioSettingValueString, RadioSettings
27
from textwrap import dedent
28
from chirp import bandplan_na
29

    
30
LOG = logging.getLogger(__name__)
31

    
32
AIRBAND = (108000000, 135999999)
33
MEM_FORMAT = """
34
#seekto 0x0008;
35
struct {
36
  lbcd rxfreq[4];
37
  lbcd txfreq[4];
38
  lbcd rxtone[2];
39
  lbcd txtone[2];
40
  u8 unused1;
41
  u8 pttid:2,
42
     freqhop:1,
43
     unused3:1,
44
     unused4:1,
45
     bcl:1,
46
     unused5:1,
47
     unused2:1;
48
  u8 unused6:1,
49
     unused7:1,
50
     lowpower:2,
51
     wide:1,
52
     unused8:1,
53
     offset:2;
54
  u8 unused10;
55
} memory[200];
56

    
57
#seekto 0x0CA8;
58
struct {
59
  u8 txled:1,
60
     rxled:1,
61
     unused11:1,
62
     ham:1,
63
     gmrs:1,
64
     unused14:1,
65
     unused15:1,
66
     pritx:1;
67
  u8 scanmode:2,
68
     unused16:1,
69
     keyautolock:1,
70
     unused17:1,
71
     btnvoice:1,
72
     unknown18:1,
73
     voiceprompt:1;
74
  u8 fmworkmode:1,
75
     sync:1,
76
     tonevoice:2,
77
     fmrec:1,
78
     mdfa:1,
79
     aworkmode:2;
80
  u8 ponmsg:2,
81
     unused19:1,
82
     mdfb:1,
83
     unused20:1,
84
     dbrx:1,
85
     bworkmode:2;
86
  u8 ablock;
87
  u8 bblock;
88
  u8 fmroad;
89
  u8 unused21:1,
90
     tailclean:1,
91
     rogerprompt:1,
92
     unused23:1,
93
     unused24:1,
94
     voxgain:3;
95
  u8 astep:4,
96
     bstep:4;
97
  u8 squelch;
98
  u8 tot;
99
  u8 lang;
100
  u8 save;
101
  u8 ligcon;
102
  u8 voxdelay;
103
  u8 onlychmode:1,
104
     unused:6,
105
     alarm:1;
106
} settings;
107

    
108
//#seekto 0x0CB8;
109
struct {
110
    u8 ofseta[4];
111
} aoffset;
112

    
113
//#seekto 0x0CBC;
114
struct {
115
    u8 ofsetb[4];
116
} boffset;
117

    
118
#seekto 0x0CD8;
119
struct{
120
    lbcd fmblock[4];
121
}fmmode[25];
122

    
123
#seekto 0x0D48;
124
struct {
125
  char name[8];
126
  u8 unknown2[8];
127
} names[200];
128

    
129
#seekto 0x1A08;
130
lbit usedflags[200];
131

    
132
#seekto 0x1a28;
133
lbit scanadd[200];
134

    
135
#seekto 0x1B38;
136
lbcd fmvfo[4];
137

    
138
#seekto 0x1B58;
139
struct {
140
  lbcd rxfreqa[4];
141
  lbcd txfreq[4];
142
  u8 rxtone[2];
143
  u8 txtone[2];
144
  u8 unused1;
145
  u8 pttid:2,
146
     specialqta:1,
147
     unused3:1,
148
     unused4:1,
149
     bcl:1,
150
     unused5:1,
151
     unused2:1;
152
  u8 unused6:1,
153
     unused7:1,
154
     lowpower:2,
155
     wide:1,
156
     unused8:1,
157
     offset:2;
158
  u8 unused10;
159
} vfoa;
160

    
161
//#seekto 0x1B68;
162
struct {
163
  lbcd rxfreqb[4];
164
  lbcd txfreq[4];
165
  u8 rxtoneb[2];
166
  u8 txtone[2];
167
  u8 unused1;
168
  u8 pttid:2,
169
     specialqtb:1,
170
     unused3:1,
171
     unused4:1,
172
     bclb:1,
173
     unused5:1,
174
     unused2:1;
175
  u8 unused6:1,
176
     unused7:1,
177
     lowpowerb:2,
178
     wideb:1,
179
     unused8:1,
180
     offsetb:2;
181
  u8 unused10;
182
} vfob;
183

    
184
//#seekto 0x1B78;
185
lbit fmusedflags[32];
186

    
187
#seekto 0x1c08;
188
struct {
189
  char msg1[16];
190
  char msg2[16];
191
  char msg3[16];
192
} poweron_msg;
193

    
194

    
195
#seekto 0x1CC8;
196
struct{
197
  u8 stopkey1;
198
  u8 ssidekey1;
199
  u8 ssidekey2;
200
  u8 ltopkey2;
201
  u8 lsidekey3;
202
  u8 lsidekey4;
203
  u8 unused25[10];
204
} press;
205

    
206
#seekto 0x1E28;
207
struct{
208
    u8 idcode[3];
209
}icode;
210

    
211
#seekto 0x1E31;
212
struct{
213
    u8 gcode;
214
}groupcode;
215

    
216
#seekto 0x1E38;
217
struct{
218
    u8 group1[7];
219
}group1;
220

    
221
#seekto 0x1E48;
222
struct{
223
    u8 group2[7];
224
}group2;
225

    
226
#seekto 0x1E58;
227
struct{
228
    u8 group3[7];
229
}group3;
230

    
231
#seekto 0x1E68;
232
struct{
233
    u8 group4[7];
234
}group4;
235

    
236
#seekto 0x1E78;
237
struct{
238
    u8 group5[7];
239
}group5;
240

    
241
#seekto 0x1E88;
242
struct{
243
    u8 group6[7];
244
}group6;
245

    
246
#seekto 0x1E98;
247
struct{
248
    u8 group7[7];
249
}group7;
250

    
251
#seekto 0x1EA8;
252
struct{
253
    u8 group8[7];
254
}group8;
255

    
256
#seekto 0x1EC8;
257
struct{
258
    u8 scode[7];
259
}startcode;
260

    
261
#seekto 0x1ED8;
262
struct{
263
    u8 ecode[7];
264
}endcode;
265
"""
266

    
267
MEM_FORMAT_H3 = """
268
#seekto 0x0008;
269
struct {
270
  lbcd rxfreq[4];
271
  lbcd txfreq[4];
272
  lbcd rxtone[2];
273
  lbcd txtone[2];
274
  u8 unused1;
275
  u8 pttid:2,
276
     freqhop:1,
277
     unused3:1,
278
     unused4:1,
279
     bcl:1,
280
     unused5:1,
281
     unused2:1;
282
  u8 unused6:1,
283
     scramble:1,
284
     lowpower:2,
285
     wide:1,
286
     unused8:1,
287
     offset:2;
288
  u8 unused10;
289
} memory[200];
290

    
291
#seekto 0x0C98;
292
struct{
293
  u8 stopkey1;
294
  u8 ssidekey1;
295
  u8 ssidekey2;
296
  u8 ltopkey2;
297
  u8 lsidekey3;
298
  u8 lsidekey4;
299
} press;
300

    
301
#seekto 0x0CA8;
302
struct {
303
  u8 txled:1,
304
     rxled:1,
305
     unused11:1,
306
     ham:1,
307
     gmrs:1,
308
     unused14:1,
309
     unused15:1,
310
     pritx:1;
311
  u8 scanmode:2,
312
     unused16:1,
313
     keyautolock:1,
314
     unused17:1,
315
     btnvoice:1,
316
     unknown18:1,
317
     voiceprompt:1;
318
  u8 fmworkmode:1,
319
     sync:1,
320
     tonevoice:2,
321
     fmrec:1,
322
     mdfa:1,
323
     aworkmode:2;
324
  u8 ponmsg:2,
325
     unused19:1,
326
     mdfb:1,
327
     unused20:1,
328
     dbrx:1,
329
     bworkmode:2;
330
  u8 ablock;
331
  u8 bblock;
332
  u8 fmroad;
333
  u8 unused21:1,
334
     tailclean:1,
335
     rogerprompt_:1,
336
     kill:1,
337
     stun:1,
338
     voxgain:3;
339
  u8 astep:4,
340
     bstep:4;
341
  u8 squelch;
342
  u8 tot;
343
  u8 rogerprompt:2,
344
     unused11_4:4,
345
     lang:1,
346
     unused11_1:1;
347
  u8 save;
348
  u8 ligcon;
349
  u8 voxdelay;
350
  u8 onlychmode:1,
351
     breathled:3,
352
     unused:3,
353
     alarm:1;
354
} settings;
355

    
356
//#seekto 0x0CB8;
357
struct {
358
    u8 ofseta[4];
359
} aoffset;
360

    
361
//#seekto 0x0CBC;
362
struct {
363
    u8 ofsetb[4];
364
} boffset;
365

    
366
#seekto 0x0CD8;
367
struct{
368
    lbcd fmblock[4];
369
}fmmode[25];
370

    
371
#seekto 0x0D48;
372
struct {
373
  char name[8];
374
} names[200];
375

    
376
#seekto 0x1808;
377
struct{
378
    u8 stuncode[16];
379
    u8 killcode[16];
380
}skcode;
381

    
382
//#seekto 0x1828;
383
struct{
384
    u8 idcode[3];
385
}icode;
386

    
387
#seekto 0x1837;
388
struct{
389
    u8 gcode;
390
}groupcode;
391

    
392
//#seekto 0x1838;
393
struct{
394
    u8 group1[7];
395
}group1;
396

    
397
#seekto 0x1848;
398
struct{
399
    u8 group2[7];
400
}group2;
401

    
402
#seekto 0x1858;
403
struct{
404
    u8 group3[7];
405
}group3;
406

    
407
#seekto 0x1868;
408
struct{
409
    u8 group4[7];
410
}group4;
411

    
412
#seekto 0x1878;
413
struct{
414
    u8 group5[7];
415
}group5;
416

    
417
#seekto 0x1888;
418
struct{
419
    u8 group6[7];
420
}group6;
421

    
422
#seekto 0x1898;
423
struct{
424
    u8 group7[7];
425
}group7;
426

    
427
#seekto 0x18A8;
428
struct{
429
    u8 group8[7];
430
}group8;
431

    
432
#seekto 0x18C8;
433
struct{
434
    u8 scode[7];
435
}startcode;
436

    
437
#seekto 0x18D8;
438
struct{
439
    u8 ecode[7];
440
}endcode;
441

    
442
#seekto 0x1908;
443
lbit usedflags[200];
444

    
445
#seekto 0x1928;
446
lbit scanadd[200];
447

    
448
#seekto 0x1948;
449
lbit fmusedflags[32];
450

    
451
#seekto 0x1958;
452
struct {
453
  lbcd rxfreqa[4];
454
  lbcd txfreq[4];
455
  u8 rxtone[2];
456
  u8 txtone[2];
457
  u8 unused1;
458
  u8 pttid:2,
459
     specialqta:1,
460
     unused3:1,
461
     unused4:1,
462
     bcl:1,
463
     unused5:1,
464
     unused2:1;
465
  u8 unused6:1,
466
     unused7:1,
467
     lowpower:2,
468
     wide:1,
469
     unused8:1,
470
     offset:2;
471
  u8 unused10;
472
} vfoa;
473

    
474
//#seekto 0x1968;
475
struct {
476
  lbcd rxfreqb[4];
477
  lbcd txfreq[4];
478
  u8 rxtoneb[2];
479
  u8 txtone[2];
480
  u8 unused1;
481
  u8 pttid:2,
482
     specialqtb:1,
483
     unused3:1,
484
     unused4:1,
485
     bclb:1,
486
     unused5:1,
487
     unused2:1;
488
  u8 unused6:1,
489
     unused7:1,
490
     lowpowerb:2,
491
     wideb:1,
492
     unused8:1,
493
     offsetb:2;
494
  u8 unused10;
495
} vfob;
496

    
497
//#seekto 0x1978;
498
lbcd fmvfo[4];
499

    
500
#seekto 0x1c08;
501
struct {
502
  char msg1[16];
503
  char msg2[16];
504
  char msg3[16];
505
} poweron_msg;
506

    
507
#seekto 0x1f28;
508
struct{
509
  u8 micgain;
510
} mic;
511

    
512
"""
513

    
514
# basic settings
515
SQUELCH = ['%s' % x for x in range(0, 10)]
516
LIGHT_LIST = ["CONT", "5s", "10s", "15s", "30s"]
517
VOICE_PRMPT_LIST = ["OFF", "ON"]
518
AUTOLOCK_LIST = ["OFF", "ON"]
519
TIME_OUT_LIST = ["OFF", "60s", "120s", "180s"]
520
MDFA_LIST = ["Frequency", "Name"]
521
MDFB_LIST = ["Frequency", "Name"]
522
SYNC_LIST = ["ON", "OFF"]
523
LANG_LIST = ["Chinese", "English"]
524
BTV_SAVER_LIST = ["OFF", "1:1", "1:2", "1:3", "1:4"]
525
DBRX_LIST = ["OFF", "ON"]
526
ASTEP_LIST = ["2.50K", "5.00K", "6.25K",
527
              "10.00K", "12.00K", "25.00K", "50.00K"]
528
BSTEP_LIST = ["2.50K", "5.00K", "6.25K",
529
              "10.00K", "12.00K", "25.00K", "50.00K"]
530
SCAN_MODE_LIST = ["TO", "CO", "SE"]
531
PRIO_LIST = ["Edit", "Busy"]
532
SHORT_KEY_LIST = ["None", "FM Radio", "Lamp", "Monitor",
533
                  "TONE", "Alarm", "Weather"]
534
LONG_KEY_LIST = ["None", "FM Radio", "Lamp",
535
                 "Monitor", "TONE", "Alarm", "Weather"]
536
BUSYLOCK_LIST = ["Off", "On"]
537
PRESS_NAME = ["stopkey1", "ssidekey1", "ssidekey2",
538
              "ltopkey2", "lsidekey3", "lsidekey4"]
539

    
540
VFOA_NAME = ["rxfreqa",
541
             "txfreq",
542
             "rxtone",
543
             "txtone",
544
             "pttid",
545
             "specialqta",
546
             "bcl",
547
             "lowpower",
548
             "wide",
549
             "offset"]
550

    
551
VFOB_NAME = ["rxfreqb",
552
             "txfreq",
553
             "rxtoneb",
554
             "txtone",
555
             "pttid",
556
             "specialqtb",
557
             "bclb",
558
             "lowpowerb",
559
             "wideb",
560
             "offsetb"]
561

    
562
# KEY
563
VOX_GAIN = ["OFF", "1", "2", "3", "4", "5"]
564
VOX_DELAY = ["1.05s", "2.0s", "3.0s"]
565
PTTID_VALUES = ["Off", "BOT", "EOT", "BOTH"]
566
BCLOCK_VALUES = ["Off", "On"]
567
FREQHOP_VALUES = ["Off", "On"]
568
SCAN_VALUES = ["Del", "Add"]
569

    
570
# AB CHANNEL
571
A_OFFSET = ["Off", "-", "+"]
572
A_TX_POWER = ["Low", "Mid", "High"]
573
A_BAND = ["Wide", "Narrow"]
574
A_BUSYLOCK = ["Off", "On"]
575
A_SPEC_QTDQT = ["Off", "On"]
576
A_WORKMODE = ["VFO", "VFO+CH", "CH Mode"]
577

    
578
B_OFFSET = ["Off", "-", "+"]
579
B_TX_POWER = ["Low", "Mid", "High"]
580
B_BAND = ["Wide", "Narrow"]
581
B_BUSYLOCK = ["Off", "On"]
582
B_SPEC_QTDQT = ["Off", "On"]
583
B_WORKMODE = ["VFO", "VFO+CH", "CH Mode"]
584

    
585
# FM
586
FM_WORKMODE = ["CH", "VFO"]
587
FM_CHANNEL = ['%s' % x for x in range(0, 26)]
588

    
589
# DTMF
590
GROUPCODE = ["", "Off", "*", "#", "A", "B", "C", "D"]
591

    
592
AB_LIST = ["A", "B"]
593
ALMOD_LIST = ["Site", "Tone", "Code"]
594
BANDWIDTH_LIST = ["Wide", "Narrow"]
595
COLOR_LIST = ["Off", "Blue", "Orange", "Purple"]
596
DTMFSPEED_LIST = ["%s ms" % x for x in range(50, 2010, 10)]
597
DTMFST_LIST = ["OFF", "DT-ST", "ANI-ST", "DT+ANI"]
598
MODE_LIST = ["Channel", "Name", "Frequency"]
599
PONMSG_LIST = ["Full", "Message", "Icon"]
600
PTTID_LIST = ["Off", "BOT", "EOT", "Both"]
601
PTTIDCODE_LIST = ["%s" % x for x in range(1, 16)]
602
RTONE_LIST = ["1000 Hz", "1450 Hz", "1750 Hz", "2100 Hz"]
603
RESUME_LIST = ["TO", "CO", "SE"]
604
ROGERRX_LIST = ["Off"] + AB_LIST
605
RPSTE_LIST = ["OFF"] + ["%s" % x for x in range(1, 11)]
606
SAVE_LIST = ["Off", "1:1", "1:2", "1:3", "1:4"]
607
SCODE_LIST = ["%s" % x for x in range(1, 16)]
608
SHIFTD_LIST = ["Off", "+", "-"]
609
STEDELAY_LIST = ["OFF"] + ["%s ms" % x for x in range(100, 1100, 100)]
610
STEPS = [2.5, 5.0, 6.25, 10.0, 12.5, 25.0]
611
STEP_LIST = [str(x) for x in STEPS]
612
STEPS = [2.5, 5.0, 6.25, 10.0, 12.5, 20.0, 25.0, 50.0]
613
STEP291_LIST = [str(x) for x in STEPS]
614
TDRAB_LIST = ["Off"] + AB_LIST
615
TDRCH_LIST = ["CH%s" % x for x in range(1, 129)]
616
TIMEOUT_LIST = ["%s sec" % x for x in range(15, 615, 15)] + \
617
    ["Off (if supported by radio)"]
618
VOICE_LIST = ["Off", "English", "Chinese"]
619
VOX_LIST = ["OFF"] + ["%s" % x for x in range(1, 11)]
620
WORKMODE_LIST = ["Frequency", "Channel"]
621
# mic
622
MIC_GAIN_LIST = ['%s' % x for x in range(0, 10)]
623
MIC_GAIN_LIST_H8 = ['%s' % x for x in range(0, 33)]
624
H8_LIST = ["TD-H8", "TD-H8-HAM", "TD-H8-GMRS"]
625

    
626
GMRS_FREQS = bandplan_na.GMRS_HIRPT
627

    
628
NOAA_FREQS = [162550000, 162400000, 162475000, 162425000, 162450000,
629
              162500000, 162525000, 161650000, 161775000, 161750000,
630
              162000000]
631

    
632
HAM_GMRS_NAME = ["NOAA 1", "NOAA 2", "NOAA 3", "NOAA 4", "NOAA 5", "NOAA 6",
633
                 "NOAA 7", "NOAA 8", "NOAA 9", "NOAA 10", "NOAA 11"]
634

    
635
ALL_MODEL = ["TD-H8", "TD-H8-HAM", "TD-H8-GMRS", "TD-H3",
636
             "TD-H3-HAM", "TD-H3-GMRS"]
637

    
638
TD_H8 = b"\x50\x56\x4F\x4A\x48\x1C\x14"
639
TD_H3 = b"\x50\x56\x4F\x4A\x48\x5C\x14"
640

    
641

    
642
def in_range(freq, ranges):
643
    for lo, hi in ranges:
644
        if lo <= freq <= hi:
645
            return True
646
    return False
647

    
648

    
649
def _do_status(radio, block):
650
    status = chirp_common.Status()
651
    status.msg = "Cloning"
652
    status.cur = block
653
    status.max = radio._memsize
654
    radio.status_fn(status)
655

    
656

    
657
def _upper_band_from_data(data):
658
    return data[0x03:0x04]
659

    
660

    
661
def _upper_band_from_image(radio):
662
    return _upper_band_from_data(radio.get_mmap())
663

    
664

    
665
def _firmware_version_from_data(data, version_start, version_stop):
666
    version_tag = data[version_start:version_stop]
667
    return version_tag
668

    
669

    
670
def _firmware_version_from_image(radio):
671
    version = _firmware_version_from_data(radio.get_mmap(),
672
                                          radio._fw_ver_file_start,
673
                                          radio._fw_ver_file_stop)
674
    # LOG.debug("_firmware_version_from_image: " + util.hexprint(version))
675
    return version
676

    
677

    
678
def _do_ident(serial, magic, secondack=True):
679
    serial.timeout = 1
680

    
681
    LOG.info("Sending Magic: %s" % util.hexprint(magic))
682
    serial.write(magic)
683
    ack = serial.read(1)
684

    
685
    if ack != b"\x06":
686
        if ack:
687
            # LOG.debug(repr(ack))
688
            pass
689
        raise errors.RadioError("Radio did not respond")
690

    
691
    serial.write(b"\x02")
692

    
693
    response = b""
694
    for i in range(1, 9):
695
        byte = serial.read(1)
696
        response += byte
697
        if byte == b"\xDD":
698
            break
699

    
700
    if len(response) in [8, 12]:
701
        # DEBUG
702
        LOG.info("Valid response, got this:")
703
        LOG.info(util.hexprint(response))
704
        if len(response) == 12:
705
            ident = response[0] + response[3] + response[5] + response[7:]
706
        else:
707
            ident = response
708
    else:
709
        # bad response
710
        msg = "Unexpected response, got this:"
711
        msg += util.hexprint(response)
712
        LOG.debug(msg)
713
        raise errors.RadioError("Unexpected response from radio.")
714

    
715
    if secondack:
716
        serial.write(b"\x06")
717
        ack = serial.read(1)
718
        if ack != b"\x06":
719
            raise errors.RadioError("Radio refused clone")
720

    
721
    return ident
722

    
723

    
724
def response_mode(mode):
725
    data = mode
726
    return data
727

    
728

    
729
def _read_block(radio, start, size):
730
    serial = radio.pipe
731

    
732
    cmd = struct.pack(">cHb", b'R', start, size)
733
    expectedresponse = b"W" + cmd[1:]
734

    
735
    try:
736
        serial.write(cmd)
737
        response = serial.read(5 + size)
738
        if response[:4] != expectedresponse:
739
            raise errors.RadioError("Error reading block %04x." % (start))
740
        block_data = response[4:-1]
741

    
742
    except Exception:
743
        raise errors.RadioError("Failed to read block at %04x" % start)
744

    
745
    return block_data
746

    
747

    
748
def _get_radio_firmware_version(radio):
749
    if radio.MODEL in ALL_MODEL:
750
        block = _read_block(radio, 0x1B40, 0x20)
751
        version = block[0:6]
752
    return version
753

    
754

    
755
IDENT_BLACKLIST = {
756
    b"\x50\x56\x4F\x4A\x48\x1C\x14": "Radio identifies as TIDRADIO TD-H8",
757
}
758

    
759

    
760
def _do_download(radio):
761
    # Radio must have already been ident'd by detect_from_serial()
762
    data = radio.ident_mode
763
    # Main block
764
    LOG.info("Downloading...")
765

    
766
    for i in range(0, radio._memsize, 0x20):
767
        block = _read_block(radio, i, 0x20)
768
        data += block
769
        _do_status(radio, i)
770
    _do_status(radio, radio._memsize)
771
    LOG.info("done.")
772

    
773
    return memmap.MemoryMapBytes(data)
774

    
775

    
776
def _exit_write_block(radio):
777
    serial = radio.pipe
778
    try:
779
        serial.write(b"E")
780

    
781
    except Exception:
782
        raise errors.RadioError("Radio refused to exit programming mode")
783

    
784

    
785
def _write_block(radio, addr, data):
786
    serial = radio.pipe
787
    cmd = struct.pack(">cHb", b'W', addr, 0x20)
788
    data = radio.get_mmap()[addr + 8: addr + 40]
789
    # The checksum needs to be in the last
790
    check_sum = bytes([sum(data) & 0xFF])
791
    data += check_sum
792
    used_data = cmd + data
793
    serial.write(used_data)
794

    
795
    ack = radio.pipe.read(1)
796
    if ack != b"\x06":
797
        raise errors.RadioError("Radio refused to accept block 0x%04x" % addr)
798

    
799

    
800
def _do_upload(radio):
801
    data = _do_ident(radio.pipe, radio._idents[0])
802
    radio_version = _get_radio_firmware_version(radio)
803
    LOG.info("Radio Version is %s" % repr(radio_version))
804

    
805
    if radio.ident_mode == data:
806
        LOG.info("Successful match.")
807
    else:
808
        msg = ("Model mismatch!")
809
        raise errors.RadioError(msg)
810

    
811
    # Main block
812
    LOG.debug("Uploading...")
813

    
814
    for start_addr, end_addr in radio._ranges_main:
815
        for addr in range(start_addr, end_addr, 0x20):
816
            _write_block(radio, addr, 0x20)
817
            _do_status(radio, addr)
818
    _exit_write_block(radio)
819
    LOG.debug("Upload all done.")
820

    
821

    
822
TDH8_CHARSET = chirp_common.CHARSET_ALPHANUMERIC + \
823
    "!@#$%^&*()+-=[]:\";'<>?,./"
824

    
825

    
826
@directory.register
827
class TDH8(chirp_common.CloneModeRadio):
828
    """TIDRADIO TD-H8"""
829
    VENDOR = "TIDRADIO"
830
    MODEL = "TD-H8"
831
    ident_mode = b'P31183\xff\xff'
832
    BAUD_RATE = 38400
833
    NEEDS_COMPAT_SERIAL = False
834
    _memsize = 0x1eef
835
    _ranges_main = [(0x0000, 0x1eef)]
836
    _idents = [TD_H8]
837
    _txbands = [(136000000, 175000000), (400000000, 521000000)]
838
    _rxbands = []
839
    _aux_block = True
840
    _tri_power = True
841
    _gmrs = False
842
    _ham = False
843
    _mem_params = (0x1F2F)
844

    
845
    # offset of fw version in image file
846
    _fw_ver_file_start = 0x1838
847
    _fw_ver_file_stop = 0x1846
848
    _valid_chars = TDH8_CHARSET
849
    _tx_power = [chirp_common.PowerLevel("Low",  watts=1.00),
850
                 chirp_common.PowerLevel("Mid",  watts=4.00),
851
                 chirp_common.PowerLevel("High", watts=8.00)]
852

    
853
    @classmethod
854
    def detect_from_serial(cls, pipe):
855
        ident = _do_ident(pipe, cls._idents[0])
856
        for rclass in [cls] + cls.detected_models():
857
            if (rclass.ident_mode == ident and
858
                    rclass.MODEL.startswith(cls.MODEL)):
859
                return rclass
860
        LOG.error('No model match found for %r', ident)
861
        raise errors.RadioError('Unsupported model')
862

    
863
    @classmethod
864
    def get_prompts(cls):
865
        rp = chirp_common.RadioPrompts()
866
        rp.pre_download = (dedent("""\
867
            1. Turn radio off.
868
            2. Connect cable to mic/spkr connector.
869
            3. Make sure connector is firmly connected.
870
            4. Turn radio on (volume may need to be set at 100%).
871
            5. Ensure that the radio is tuned to channel with no activity.
872
            6. Click OK to download image from device."""))
873
        rp.pre_upload = (dedent("""\
874
            1. Turn radio off.
875
            2. Connect cable to mic/spkr connector.
876
            3. Make sure connector is firmly connected.
877
            4. Turn radio on (volume may need to be set at 100%).
878
            5. Ensure that the radio is tuned to channel with no activity.
879
            6. Click OK to upload image to device."""))
880
        return rp
881

    
882
    def get_features(self):
883
        rf = chirp_common.RadioFeatures()
884
        rf.has_settings = True
885
        rf.has_bank = False
886
        rf.has_cross = True
887
        rf.has_ctone = True
888
        rf.has_rx_dtcs = True
889
        rf.has_tuning_step = False
890
        rf.has_ctone = True
891
        rf.can_odd_split = True
892
        rf.valid_name_length = 8
893
        rf.valid_characters = self._valid_chars
894
        rf.valid_skips = ["", "S"]
895
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
896
        rf.valid_cross_modes = [
897
            "Tone->Tone",
898
            "DTCS->",
899
            "->DTCS",
900
            "Tone->DTCS",
901
            "DTCS->Tone",
902
            "->Tone",
903
            "DTCS->DTCS"]
904
        rf.valid_power_levels = [x for x in self._tx_power if x]
905
        rf.valid_duplexes = ["", "-", "+", "split", "off"]
906
        rf.valid_modes = ["FM", "NFM"]
907
        rf.valid_tuning_steps = STEPS
908

    
909
        rf.valid_bands = self._txbands + self._rxbands
910
        rf.valid_bands.sort()
911
        rf.memory_bounds = (1, 199)
912
        return rf
913

    
914
    def process_mmap(self):
915
        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
916

    
917
    def sync_in(self):
918
        try:
919
            self._mmap = _do_download(self)
920
            self.process_mmap()
921
        except Exception as e:
922
            raise errors.RadioError("Failed to communicate with radio: %s" % e)
923

    
924
    def sync_out(self):
925
        try:
926
            _do_upload(self)
927
        except errors.RadioError:
928
            raise
929
        except Exception as e:
930
            raise errors.RadioError("Failed to communicate with radio: %s" % e)
931

    
932
    def get_raw_memory(self, number):
933
        return repr(self._memobj.memory[number])
934

    
935
    # Encoding processing
936
    def _decode_tone(self, val):
937
        if val == 16665 or val == 0:
938
            return '', None, None
939
        elif val >= 12000:
940
            return 'DTCS', val - 12000, 'R'
941
        elif val >= 8000:
942
            return 'DTCS', val - 8000, 'N'
943
        else:
944
            return 'Tone', val / 10.0, None
945

    
946
    # Decoding processing
947
    def _encode_tone(self, memval, mode, value, pol):
948
        if mode == "":
949
            memval[0].set_raw(0xFF)
950
            memval[1].set_raw(0xFF)
951
        elif mode == 'Tone':
952
            memval.set_value(int(value * 10))
953

    
954
        elif mode == 'DTCS':
955
            flag = 0x80 if pol == 'N' else 0xC0
956
            memval.set_value(value)
957
            memval[1].set_bits(flag)
958
        else:
959
            raise Exception("Internal error: invalid mode `%s'" % mode)
960

    
961
    def _get_mem(self, number):
962
        return self._memobj.memory[number]
963

    
964
    def _get_nam(self, number):
965
        return self._memobj.names[number - 1]
966

    
967
    def _get_fm(self, number):
968
        return self._memobj.fmmode[number]
969

    
970
    def _get_get_scanvfo(self, number):
971
        return self._memobj.fmvfo[number]
972

    
973
    def get_memory(self, number):
974
        _mem = self._get_mem(number)
975
        _nam = self._get_nam(number)
976
        mem = chirp_common.Memory()
977
        mem.number = number
978

    
979
        if _mem.get_raw()[0] == 0xff:
980
            mem.empty = True
981
            return mem
982

    
983
        # narrow and wide
984
        mem.mode = _mem.wide and "NFM" or "FM"
985

    
986
        # power
987
        try:
988
            mem.power = self._tx_power[_mem.lowpower]
989
            if mem.power is None:
990
                # Gaps are basically missing power levels
991
                raise IndexError()
992
        except IndexError:
993
            LOG.error("Radio reported invalid power level %s (in %s)" %
994
                      (_mem.lowpower, self._tx_power))
995
            mem.power = self._tx_power[0]
996

    
997
        # Channel name
998
        for char in _nam.name:
999
            if "\x00" in str(char) or "\xFF" in str(char):
1000
                char = ""
1001
            mem.name += str(char)
1002

    
1003
        mem.name = mem.name.rstrip()
1004
        if self.ident_mode != b'P31183\xff\xff' and \
1005
                (mem.number >= 189 and mem.number <= 199):
1006
            mem.name = HAM_GMRS_NAME[mem.number - 200]
1007

    
1008
        # tmode
1009
        lin2 = int(_mem.rxtone)
1010
        rxtone = self._decode_tone(lin2)
1011

    
1012
        lin = int(_mem.txtone)
1013
        txtone = self._decode_tone(lin)
1014

    
1015
        if txtone[0] == "Tone" and not rxtone[0]:
1016
            mem.tmode = "Tone"
1017
        elif txtone[0] == rxtone[0] and txtone[0] == "Tone" \
1018
                and mem.rtone == mem.ctone:
1019
            mem.tmode = "TSQL"
1020
        elif txtone[0] == rxtone[0] and txtone[0] == "DTCS" \
1021
                and mem.dtcs == mem.rx_dtcs:
1022
            mem.tmode = "DTCS"
1023
        elif rxtone[0] or txtone[0]:
1024
            mem.tmode = "Cross"
1025
            mem.cross_mode = "%s->%s" % (txtone[0], rxtone[0])
1026

    
1027
        chirp_common.split_tone_decode(mem, txtone, rxtone)
1028

    
1029
        mem.skip = '' if self._memobj.scanadd[mem.number - 1] else 'S'
1030

    
1031
        mem.freq = int(_mem.rxfreq) * 10
1032
        if _mem.txfreq.get_raw() == b'\xff\xff\xff\xff':
1033
            mem.offset = 0
1034
            mem.duplex = 'off'
1035
        else:
1036
            chirp_common.split_to_offset(mem,
1037
                                         int(_mem.rxfreq) * 10,
1038
                                         int(_mem.txfreq) * 10)
1039

    
1040
        if self._gmrs:
1041
            # mem.duplex = ""
1042
            # mem.offset = 0
1043
            if mem.number >= 1 and mem.number <= 30:
1044
                mem.immutable.append('freq')
1045
                if mem.number >= 8 and mem.number <= 14:
1046
                    mem.mode = 'NFM'
1047
                    mem.power = self._tx_power[0]
1048
                    mem.immutable = ['freq', 'mode', 'power',
1049
                                     'duplex', 'offset']
1050
            elif mem.number >= 31 and mem.number <= 54:
1051
                # mem.immutable = ['duplex', 'offset']
1052
                mem.duplex = '+'
1053
                mem.offset = 5000000
1054
            elif mem.number >= 189 and mem.number <= 199:
1055
                ham_freqs = NOAA_FREQS[mem.number - 189]
1056
                mem.freq = ham_freqs
1057
                mem.immutable = ['name', 'power', 'duplex', 'freq',
1058
                                 'rx_dtcs', 'vfo', 'tmode', 'empty',
1059
                                 'offset', 'rtone', 'ctone', 'dtcs',
1060
                                 'dtcs_polarity', 'cross_mode']
1061
        elif self._ham:
1062
            if mem.number >= 189 and mem.number <= 199:
1063
                ham_freqs = NOAA_FREQS[mem.number - 189]
1064
                mem.freq = ham_freqs
1065
                mem.immutable = ['name', 'power', 'freq', 'rx_dtcs', 'vfo',
1066
                                 'tmode', 'empty', 'offset', 'rtone', 'ctone',
1067
                                 'dtcs', 'dtcs_polarity', 'cross_mode']
1068

    
1069
        # other function
1070
        # pttid
1071
        mem.extra = RadioSettingGroup("Extra", "extra")
1072

    
1073
        rs = RadioSetting("pttid", "PTT ID",
1074
                          RadioSettingValueList(PTTID_VALUES,
1075
                                                PTTID_VALUES[_mem.pttid]))
1076
        mem.extra.append(rs)
1077

    
1078
        # Busylock
1079
        rs = RadioSetting("bcl", "Busy Lock",
1080
                          RadioSettingValueList(BCLOCK_VALUES,
1081
                                                BCLOCK_VALUES[_mem.bcl]))
1082
        mem.extra.append(rs)
1083

    
1084
        rs = RadioSetting(
1085
            "freqhop", "Frequency Hop", RadioSettingValueList(
1086
                FREQHOP_VALUES, FREQHOP_VALUES[_mem.freqhop]))
1087
        mem.extra.append(rs)
1088

    
1089
        if in_range(mem.freq, self._rxbands):
1090
            mem.duplex = 'off'
1091
            mem.immutable.append('duplex')
1092
        if in_range(mem.freq, [AIRBAND]):
1093
            mem.mode = 'AM'
1094
            mem.immutable.append('mode')
1095

    
1096
        return mem
1097

    
1098
    def _set_mem(self, number):
1099
        return self._memobj.memory[number]
1100

    
1101
    def _set_nam(self, number):
1102
        return self._memobj.names[number - 1]
1103

    
1104
    def _get_scan_list(self, scan_data):
1105
        # scan_val_list - Get all scans Add data 1-200 digits
1106
        scan_val_list = []
1107
        for x in range(25):
1108
            a = self._get_scan(x)
1109
            for i in range(0, 8):
1110
                scan_val = (getattr(a, 'scan%i' % (i+1)))
1111
                used_scan_val = str(scan_val)[3]
1112
                scan_val_list.append(used_scan_val)
1113

    
1114
        # used_scan_list - 25 structures, split the scan added
1115
        # data into 25 groups of 8 bits each
1116
        used_scan_list = []
1117
        count_num = 1
1118
        for i in range(0, len(scan_val_list), 8):
1119
            used_scan_list.append(scan_val_list[i:i + 8])
1120
            count_num += 1
1121
        # Determine whether it is a standard number that can be divisible
1122
        # Which group is the scan addition located in the modified channel
1123
        if scan_data % 8 != 0:
1124
            x_list = scan_data / 8
1125
            y_list = scan_data % 8
1126

    
1127
        else:
1128
            x_list = (scan_data / 8) - 1
1129
            y_list = 8
1130

    
1131
        return ([x_list, y_list])
1132

    
1133
    def set_memory(self, mem):
1134
        _mem = self._get_mem(mem.number)
1135
        _nam = self._get_nam(mem.number)
1136

    
1137
        # When the channel is empty, you need to set "usedflags" to 0,
1138
        # When the channel is used , you need to set "usedflags" to 1.
1139
        self._memobj.usedflags[mem.number - 1] = int(not mem.empty)
1140

    
1141
        if mem.empty:
1142
            _mem.fill_raw(b'\xFF')
1143
            return
1144

    
1145
        _mem.fill_raw(b'\x00')
1146

    
1147
        if mem.duplex == "":
1148
            _mem.rxfreq = _mem.txfreq = mem.freq / 10
1149
        elif mem.duplex == "split":
1150
            _mem.txfreq = mem.offset / 10
1151
        elif mem.duplex == "+":
1152
            _mem.txfreq = (mem.freq + mem.offset) / 10
1153
        elif mem.duplex == "-":
1154
            _mem.txfreq = (mem.freq - mem.offset) / 10
1155
        elif mem.duplex == 'off':
1156
            _mem.txfreq.fill_raw(b'\xFF')
1157
        else:
1158
            _mem.txfreq = mem.freq / 10
1159

    
1160
        if in_range(mem.freq, self._rxbands):
1161
            _mem.txfreq.fill_raw(b'\xFF')
1162

    
1163
        _mem.rxfreq = mem.freq / 10
1164
        _namelength = self.get_features().valid_name_length
1165

    
1166
        for i in range(_namelength):
1167
            try:
1168
                _nam.name[i] = mem.name[i]
1169
            except IndexError:
1170
                _nam.name[i] = "\xFF"
1171

    
1172
        txtone, rxtone = chirp_common.split_tone_encode(mem)
1173

    
1174
        self._encode_tone(_mem.txtone, *txtone)
1175
        self._encode_tone(_mem.rxtone, *rxtone)
1176

    
1177
        if mem.mode == "FM":
1178
            _mem.wide = 0
1179
        else:
1180
            _mem.wide = 1
1181

    
1182
        try:
1183
            _mem.lowpower = self._tx_power.index(mem.power or
1184
                                                 self._tx_power[-1])
1185
        except ValueError:
1186
            _mem.lowpower = 0
1187
            LOG.warning('Unsupported power %r', mem.power)
1188

    
1189
        # Skip/Scanadd Setting
1190
        self._memobj.scanadd[mem.number - 1] = mem.skip != 'S'
1191

    
1192
        for setting in mem.extra:
1193
            if (self.ident_mode == b'P31185\xff\xff' or
1194
                self.ident_mode == b'P31184\xff\xff') and \
1195
                    mem.number >= 189 and mem.number <= 199:
1196
                if setting.get_name() == 'pttid':
1197
                    setting.value = 'Off'
1198
                    setattr(_mem, setting.get_name(), setting.value)
1199
                elif setting.get_name() == 'bcl':
1200
                    setting.value = 'Off'
1201
                    setattr(_mem, setting.get_name(), setting.value)
1202
                elif setting.get_name() == 'freqhop':
1203
                    setting.value = 'Off'
1204
                    setattr(_mem, setting.get_name(), setting.value)
1205
            else:
1206
                setattr(_mem, setting.get_name(), setting.value)
1207

    
1208
    def _is_orig(self):
1209
        version_tag = _firmware_version_from_image(self)
1210
        try:
1211
            if b'BFB' in version_tag:
1212
                idx = version_tag.index(b"BFB") + 3
1213
                version = int(version_tag[idx:idx + 3])
1214
                return version < 291
1215
            return False
1216
        except Exception:
1217
            pass
1218
        raise errors.RadioError("Unable to parse version string %s" %
1219
                                version_tag)
1220

    
1221
    def _my_upper_band(self):
1222
        band_tag = _upper_band_from_image(self)
1223
        return band_tag
1224

    
1225
    def _get_settings(self):
1226
        _settings = self._memobj.settings
1227
        _press = self._memobj.press
1228
        _aoffset = self._memobj.aoffset
1229
        _boffset = self._memobj.boffset
1230
        _vfoa = self._memobj.vfoa
1231
        _vfob = self._memobj.vfob
1232
        _gcode = self._memobj.groupcode
1233
        _msg = self._memobj.poweron_msg
1234
        basic = RadioSettingGroup("basic", "Basic Settings")
1235
        abblock = RadioSettingGroup("abblock", "A/B Channel")
1236
        fmmode = RadioSettingGroup("fmmode", "FM")
1237
        dtmf = RadioSettingGroup("dtmf", "DTMF")
1238

    
1239
        # group = RadioSettings(fmmode, dtmf)
1240
        group = RadioSettings(basic, abblock, fmmode, dtmf)
1241

    
1242
        rs = RadioSetting("squelch", "Squelch Level",
1243
                          RadioSettingValueList(
1244
                              SQUELCH, SQUELCH[_settings.squelch]))
1245
        basic.append(rs)
1246

    
1247
        rs = RadioSetting("ligcon", "Light Control",
1248
                          RadioSettingValueList(
1249
                              LIGHT_LIST, LIGHT_LIST[_settings.ligcon]))
1250
        basic.append(rs)
1251

    
1252
        rs = RadioSetting("voiceprompt", "Voice Prompt",
1253
                          RadioSettingValueList(
1254
                              VOICE_PRMPT_LIST, VOICE_PRMPT_LIST[
1255
                                  _settings.voiceprompt]))
1256
        basic.append(rs)
1257

    
1258
        rs = RadioSetting("keyautolock", "Auto Lock",
1259
                          RadioSettingValueList(
1260
                              AUTOLOCK_LIST, AUTOLOCK_LIST[
1261
                                  _settings.keyautolock]))
1262
        basic.append(rs)
1263

    
1264
        rs = RadioSetting("mdfa", "MDF-A",
1265
                          RadioSettingValueList(
1266
                              MDFA_LIST, MDFA_LIST[_settings.mdfa]))
1267
        basic.append(rs)
1268

    
1269
        rs = RadioSetting("mdfb", "MDF-B",
1270
                          RadioSettingValueList(
1271
                              MDFB_LIST, MDFB_LIST[_settings.mdfb]))
1272
        basic.append(rs)
1273

    
1274
        rs = RadioSetting("sync", "SYNC",
1275
                          RadioSettingValueList(
1276
                              SYNC_LIST, SYNC_LIST[_settings.sync]))
1277
        basic.append(rs)
1278

    
1279
        rs = RadioSetting("save", "Battery Save",
1280
                          RadioSettingValueList(
1281
                              BTV_SAVER_LIST, BTV_SAVER_LIST[_settings.save]))
1282
        basic.append(rs)
1283

    
1284
        rs = RadioSetting("dbrx", "Double Rx",
1285
                          RadioSettingValueList(
1286
                              DBRX_LIST, DBRX_LIST[_settings.dbrx]))
1287
        basic.append(rs)
1288

    
1289
        rs = RadioSetting("astep", "A Step",
1290
                          RadioSettingValueList(
1291
                              ASTEP_LIST, ASTEP_LIST[_settings.astep]))
1292
        basic.append(rs)
1293

    
1294
        rs = RadioSetting("bstep", "B Step",
1295
                          RadioSettingValueList(
1296
                              BSTEP_LIST, BSTEP_LIST[_settings.bstep]))
1297
        basic.append(rs)
1298

    
1299
        rs = RadioSetting("scanmode", "Scan Mode",
1300
                          RadioSettingValueList(
1301
                              SCAN_MODE_LIST, SCAN_MODE_LIST[
1302
                                  _settings.scanmode]))
1303
        basic.append(rs)
1304

    
1305
        rs = RadioSetting("pritx", "Priority TX",
1306
                          RadioSettingValueList(
1307
                              PRIO_LIST, PRIO_LIST[_settings.pritx]))
1308
        basic.append(rs)
1309

    
1310
        rs = RadioSetting("btnvoice", "Beep",
1311
                          RadioSettingValueBoolean(_settings.btnvoice))
1312
        basic.append(rs)
1313

    
1314
        rs = RadioSetting("rogerprompt", "Roger",
1315
                          RadioSettingValueBoolean(_settings.rogerprompt))
1316
        basic.append(rs)
1317

    
1318
        rs = RadioSetting("txled", "Disp Lcd(TX)",
1319

    
1320
                          RadioSettingValueBoolean(_settings.txled))
1321
        basic.append(rs)
1322

    
1323
        rs = RadioSetting("rxled", "Disp Lcd(RX)",
1324
                          RadioSettingValueBoolean(_settings.rxled))
1325
        basic.append(rs)
1326

    
1327
        rs = RadioSetting("onlychmode", "Only CH Mode",
1328
                          RadioSettingValueBoolean(_settings.onlychmode))
1329
        basic.append(rs)
1330
        rs = RadioSetting("ssidekey1", "SHORT_KEY_PF1",
1331
                          RadioSettingValueList(
1332
                              SHORT_KEY_LIST, SHORT_KEY_LIST[
1333
                                  _press.ssidekey1]))
1334
        basic.append(rs)
1335
        rs = RadioSetting("lsidekey3", "LONG_KEY_PF1",
1336
                          RadioSettingValueList(
1337
                              LONG_KEY_LIST,
1338
                              LONG_KEY_LIST[_press.lsidekey3]))
1339
        basic.append(rs)
1340
        if self.MODEL in H8_LIST:
1341
            rs = RadioSetting("stopkey1", "SHORT_KEY_TOP",
1342
                              RadioSettingValueList(SHORT_KEY_LIST,
1343
                                                    SHORT_KEY_LIST[0]))
1344
            basic.append(rs)
1345

    
1346
            rs = RadioSetting("ltopkey2", "LONG_KEY_TOP",
1347
                              RadioSettingValueList(
1348
                                  LONG_KEY_LIST,
1349
                                  LONG_KEY_LIST[_press.ltopkey2]))
1350
            basic.append(rs)
1351

    
1352
            rs = RadioSetting("ssidekey2", "SHORT_KEY_PF2",
1353
                              RadioSettingValueList(
1354
                                  SHORT_KEY_LIST,
1355
                                  SHORT_KEY_LIST[_press.ssidekey2]))
1356
            basic.append(rs)
1357

    
1358
            rs = RadioSetting("lsidekey4", "LONG_KEY_PF2",
1359
                              RadioSettingValueList(
1360
                                LONG_KEY_LIST,
1361
                                LONG_KEY_LIST[_press.lsidekey4]))
1362
            basic.append(rs)
1363

    
1364
        rs = RadioSetting("voxgain", "VOX Gain",
1365
                          RadioSettingValueList(
1366
                              VOX_GAIN, VOX_GAIN[_settings.voxgain]))
1367
        basic.append(rs)
1368

    
1369
        rs = RadioSetting("voxdelay", "VOX Delay",
1370
                          RadioSettingValueList(
1371
                              VOX_DELAY, VOX_DELAY[_settings.voxdelay]))
1372
        basic.append(rs)
1373

    
1374
        rs = RadioSetting("ponmsg", "Power-On Message",
1375
                          RadioSettingValueList(
1376
                              PONMSG_LIST, PONMSG_LIST[_settings.ponmsg]))
1377
        basic.append(rs)
1378

    
1379
        # mic gain
1380
        if self.MODEL not in H8_LIST:
1381
            _mic = self._memobj.mic
1382
            rs = RadioSetting("micgain", "MIC GAIN",
1383
                              RadioSettingValueList(
1384
                                  MIC_GAIN_LIST, current_index=_mic.micgain))
1385
            basic.append(rs)
1386

    
1387
        if self.MODEL not in H8_LIST:
1388
            rs = RadioSetting("kill", "Kill",
1389
                              RadioSettingValueBoolean(_settings.kill))
1390
            basic.append(rs)
1391
            rs = RadioSetting("stun", "Stun",
1392
                              RadioSettingValueBoolean(_settings.stun))
1393
            basic.append(rs)
1394

    
1395
        def _filter(name):
1396
            filtered = ""
1397
            for char in str(name):
1398
                if char in chirp_common.CHARSET_ASCII:
1399
                    filtered += char
1400
                else:
1401
                    filtered += " "
1402
            return filtered
1403

    
1404
        rs = RadioSetting("poweron_msg.msg1", "Power-On Message 1",
1405
                          RadioSettingValueString(0, 16, _filter(_msg.msg1)))
1406
        basic.append(rs)
1407
        rs = RadioSetting("poweron_msg.msg2", "Power-On Message 2",
1408
                          RadioSettingValueString(0, 16, _filter(_msg.msg2)))
1409
        basic.append(rs)
1410
        rs = RadioSetting("poweron_msg.msg3", "Power-On Message 3",
1411
                          RadioSettingValueString(0, 16, _filter(_msg.msg3)))
1412
        basic.append(rs)
1413

    
1414
        # A channel
1415
        a_freq = int(_vfoa.rxfreqa)
1416
        freqa = "%i.%05i" % (a_freq / 100000, a_freq % 100000)
1417
        if freqa == "0.00000":
1418
            val1a = RadioSettingValueString(0, 7, '0.00000')
1419
        else:
1420
            val1a = RadioSettingValueFloat(
1421
                136, 520, float(freqa), 0.00001, 5)
1422
        rs = RadioSetting("rxfreqa", "A Channel - Frequency", val1a)
1423
        abblock.append(rs)
1424

    
1425
        # Offset
1426
        # If the offset is 12.345
1427
        # Then the data obtained is [0x45, 0x23, 0x01, 0x00]
1428
        a_set_val = _aoffset.ofseta
1429
        a_set_list = len(_aoffset.ofseta) - 1
1430
        real_val = ''
1431
        for i in range(a_set_list, -1, -1):
1432
            real_val += str(a_set_val[i])[2:]
1433
        if real_val == "FFFFFFFF":
1434
            rs = RadioSetting("ofseta", "A Offset Frequency",
1435
                              RadioSettingValueString(0, 7, ""))
1436

    
1437
        else:
1438
            real_val = int(real_val)
1439
            real_val = "%i.%05i" % (real_val / 100000, real_val % 100000)
1440
            rs = RadioSetting("ofseta", "A Offset Frequency",
1441
                              RadioSettingValueFloat(
1442
                                  0.00000, 59.99750, real_val, 0.00001, 5))
1443
        abblock.append(rs)
1444

    
1445
        rs = RadioSetting("offset", "A Offset",
1446
                          RadioSettingValueList(
1447
                              A_OFFSET, A_OFFSET[_vfoa.offset]))
1448
        abblock.append(rs)
1449

    
1450
        rs = RadioSetting("lowpower", "A TX Power",
1451
                          RadioSettingValueList(
1452
                              A_TX_POWER, A_TX_POWER[_vfoa.lowpower]))
1453
        abblock.append(rs)
1454

    
1455
        rs = RadioSetting("wide", "A Band",
1456
                          RadioSettingValueList(
1457
                              A_BAND, A_BAND[_vfoa.wide]))
1458
        abblock.append(rs)
1459

    
1460
        rs = RadioSetting("bcl", "A Busy Lock",
1461
                          RadioSettingValueList(
1462
                              A_BUSYLOCK, A_BUSYLOCK[_vfoa.bcl]))
1463
        abblock.append(rs)
1464

    
1465
        rs = RadioSetting("specialqta", "A Special QT/DQT",
1466
                          RadioSettingValueList(
1467
                              A_SPEC_QTDQT, A_SPEC_QTDQT[_vfoa.specialqta]))
1468
        abblock.append(rs)
1469

    
1470
        rs = RadioSetting("aworkmode", "A Work Mode",
1471
                          RadioSettingValueList(
1472
                              A_WORKMODE, A_WORKMODE[_settings.aworkmode]))
1473
        abblock.append(rs)
1474

    
1475
        # B channel
1476
        b_freq = int(str(int(_vfob.rxfreqb)).ljust(8, '0'))
1477
        freqb = "%i.%05i" % (b_freq / 100000, b_freq % 100000)
1478
        if freqb == "0.00000":
1479
            val1a = RadioSettingValueString(0, 7, '0.00000')
1480
        else:
1481
            val1a = RadioSettingValueFloat(
1482
                136, 520, float(freqb), 0.00001, 5)
1483
        rs = RadioSetting("rxfreqb", "B Channel - Frequency", val1a)
1484
        abblock.append(rs)
1485

    
1486
        # Offset frequency
1487
        # If the offset is 12.345
1488
        # Then the data obtained is [0x45, 0x23, 0x01, 0x00]
1489
        # Need to use the following anonymous function to process data
1490
        b_set_val = _boffset.ofsetb
1491
        b_set_list = len(_boffset.ofsetb) - 1
1492
        real_val = ''
1493
        for i in range(b_set_list, -1, -1):
1494
            real_val += str(b_set_val[i])[2:]
1495
        if real_val == "FFFFFFFF":
1496
            rs = RadioSetting("ofsetb", "B Offset Frequency",
1497
                              RadioSettingValueString(0, 7, " "))
1498
        else:
1499
            real_val = int(real_val)
1500
            real_val = "%i.%05i" % (real_val / 100000, real_val % 100000)
1501
            rs = RadioSetting("ofsetb", "B Offset Frequency",
1502
                              RadioSettingValueFloat(
1503
                                  0.00000, 59.99750, real_val, 0.00001, 5))
1504
        abblock.append(rs)
1505

    
1506
        rs = RadioSetting("offsetb", "B Offset",
1507
                          RadioSettingValueList(
1508
                              B_OFFSET, B_OFFSET[_vfob.offsetb]))
1509
        abblock.append(rs)
1510

    
1511
        rs = RadioSetting("lowpowerb", "B TX Power",
1512
                          RadioSettingValueList(
1513
                              B_TX_POWER, B_TX_POWER[_vfob.lowpowerb]))
1514
        abblock.append(rs)
1515

    
1516
        rs = RadioSetting("wideb", "B Band",
1517
                          RadioSettingValueList(
1518
                              B_BAND, B_BAND[_vfob.wideb]))
1519
        abblock.append(rs)
1520

    
1521
        rs = RadioSetting("bclb", "B Busy Lock",
1522
                          RadioSettingValueList(
1523
                              B_BUSYLOCK, B_BUSYLOCK[_vfob.bclb]))
1524
        abblock.append(rs)
1525

    
1526
        rs = RadioSetting("specialqtb", "B Special QT/DQT",
1527
                          RadioSettingValueList(
1528
                              B_SPEC_QTDQT, B_SPEC_QTDQT[_vfob.specialqtb]))
1529
        abblock.append(rs)
1530

    
1531
        rs = RadioSetting("bworkmode", "B Work Mode",
1532
                          RadioSettingValueList(
1533
                              B_WORKMODE, B_WORKMODE[_settings.bworkmode]))
1534
        abblock.append(rs)
1535

    
1536
        rs = RadioSetting("fmworkmode", "Work Mode",
1537
                          RadioSettingValueList(
1538
                              FM_WORKMODE, FM_WORKMODE[_settings.fmworkmode]))
1539
        fmmode.append(rs)
1540

    
1541
        rs = RadioSetting("fmroad", "Channel",
1542
                          RadioSettingValueList(
1543
                              FM_CHANNEL, FM_CHANNEL[_settings.fmroad]))
1544
        fmmode.append(rs)
1545

    
1546
        rs = RadioSetting("fmrec", "Forbid Receive",
1547
                          RadioSettingValueBoolean(_settings.fmrec))
1548
        fmmode.append(rs)
1549

    
1550
        # FM
1551
        numeric = '0123456789.'
1552
        for i in range(25):
1553
            if self._memobj.fmusedflags[i]:
1554
                _fm = self._get_fm(i).fmblock
1555
                try:
1556
                    if not (760 < int(_fm) < 1080):
1557
                        raise ValueError()
1558
                    val = '%.1f' % (int(_fm) / 10)
1559
                except ValueError:
1560
                    LOG.warning('FM channel index %i is invalid', i)
1561
                    val = ''
1562
            else:
1563
                val = ''
1564
            rs = RadioSetting('block%02i' % i, "Channel %i" % (i + 1),
1565
                              RadioSettingValueString(0, 5,
1566
                                                      val,
1567
                                                      False, charset=numeric))
1568
            fmmode.append(rs)
1569

    
1570
        try:
1571
            _fmv = int(self._memobj.fmvfo) / 10
1572
        except ValueError:
1573
            LOG.warning('FM VFO is invalid')
1574
            _fmv = 0
1575

    
1576
        rs = RadioSetting(
1577
            "fmvfo", "VFO", RadioSettingValueFloat(
1578
                76.0, 108.0, _fmv, 0.1, 1))
1579
        fmmode.append(rs)
1580

    
1581
        # DTMF
1582
        gcode_val = str(_gcode.gcode)[2:]
1583
        if gcode_val == "FF":
1584
            gcode_val = "Off"
1585
        elif gcode_val == "0F":
1586
            gcode_val = "#"
1587
        elif gcode_val == "0E":
1588
            gcode_val = "*"
1589
        elif gcode_val == '00':
1590
            gcode_val = ""
1591
        else:
1592
            gcode_val = gcode_val[1]
1593
        rs = RadioSetting("gcode", "Group Code",
1594
                          RadioSettingValueList(GROUPCODE,
1595
                                                gcode_val))
1596
        dtmf.append(rs)
1597

    
1598
        icode_list = self._memobj.icode.idcode
1599
        used_icode = ''
1600
        for i in icode_list:
1601
            if i == 0xFF:
1602
                continue
1603
            used_icode += str(i)[3]
1604
        dtmfcharsani = "0123456789ABCD "
1605
        i_val = RadioSettingValueString(0, 3, used_icode)
1606
        rs = RadioSetting("icode", "ID Code", i_val)
1607
        i_val.set_charset(dtmfcharsani)
1608
        dtmf.append(rs)
1609

    
1610
        gcode_list_1 = self._memobj.group1.group1
1611
        used_group1 = ''
1612
        for i in gcode_list_1:
1613
            if i == 0xFF:
1614
                continue
1615
            used_group1 += str(i)[3]
1616
        group1_val = RadioSettingValueString(0, 7, used_group1)
1617
        rs = RadioSetting("group1", "1", group1_val)
1618
        group1_val.set_charset(dtmfcharsani)
1619
        dtmf.append(rs)
1620

    
1621
        gcode_list_2 = self._memobj.group2.group2
1622
        used_group2 = ''
1623
        for i in gcode_list_2:
1624
            if i == 0xFF:
1625
                continue
1626
            used_group2 += str(i)[3]
1627
        group2_val = RadioSettingValueString(0, 7, used_group2)
1628
        rs = RadioSetting("group2", "2", group2_val)
1629
        group2_val.set_charset(dtmfcharsani)
1630
        dtmf.append(rs)
1631

    
1632
        gcode_list_3 = self._memobj.group3.group3
1633
        used_group3 = ''
1634
        for i in gcode_list_3:
1635
            if i == 0xFF:
1636
                continue
1637
            used_group3 += str(i)[3]
1638
        group3_val = RadioSettingValueString(0, 7, used_group3)
1639
        rs = RadioSetting("group3", "3", group3_val)
1640
        group3_val.set_charset(dtmfcharsani)
1641
        dtmf.append(rs)
1642

    
1643
        gcode_list_4 = self._memobj.group4.group4
1644
        used_group4 = ''
1645
        for i in gcode_list_4:
1646
            if i == 0xFF:
1647
                continue
1648
            used_group4 += str(i)[3]
1649
        group4_val = RadioSettingValueString(0, 7, used_group4)
1650
        rs = RadioSetting("group4", "4", group4_val)
1651
        group4_val.set_charset(dtmfcharsani)
1652
        dtmf.append(rs)
1653

    
1654
        gcode_list_5 = self._memobj.group5.group5
1655
        used_group5 = ''
1656
        for i in gcode_list_5:
1657
            if i == 0xFF:
1658
                continue
1659
            used_group5 += str(i)[3]
1660
        group5_val = RadioSettingValueString(0, 7, used_group5)
1661
        rs = RadioSetting("group5", "5", group5_val)
1662
        group5_val.set_charset(dtmfcharsani)
1663
        dtmf.append(rs)
1664

    
1665
        gcode_list_6 = self._memobj.group6.group6
1666
        used_group6 = ''
1667
        for i in gcode_list_6:
1668
            if i == 0xFF:
1669
                continue
1670
            used_group6 += str(i)[3]
1671
        group6_val = RadioSettingValueString(0, 7, used_group6)
1672
        rs = RadioSetting("group6", "6", group6_val)
1673
        group6_val.set_charset(dtmfcharsani)
1674
        dtmf.append(rs)
1675

    
1676
        gcode_list_7 = self._memobj.group7.group7
1677
        used_group7 = ''
1678
        for i in gcode_list_7:
1679
            if i == 0xFF:
1680
                continue
1681
            used_group7 += str(i)[3]
1682
        group7_val = RadioSettingValueString(0, 7, used_group7)
1683
        rs = RadioSetting("group7", "7", group7_val)
1684
        group7_val.set_charset(dtmfcharsani)
1685
        dtmf.append(rs)
1686

    
1687
        gcode_list_8 = self._memobj.group8.group8
1688
        used_group8 = ''
1689
        for i in gcode_list_8:
1690
            if i == 0xFF:
1691
                continue
1692
            used_group8 += str(i)[3]
1693
        group8_val = RadioSettingValueString(0, 7, used_group8)
1694
        rs = RadioSetting("group8", "8", group7_val)
1695
        group8_val.set_charset(dtmfcharsani)
1696
        dtmf.append(rs)
1697

    
1698
        scode_list = self._memobj.startcode.scode
1699
        used_scode = ''
1700
        for i in scode_list:
1701
            if i == 0xFF:
1702
                continue
1703
            used_scode += str(i)[3]
1704
        scode_val = RadioSettingValueString(0, 7, used_scode)
1705
        rs = RadioSetting("scode", "PTT ID Starting(BOT)", scode_val)
1706
        scode_val.set_charset(dtmfcharsani)
1707
        dtmf.append(rs)
1708

    
1709
        ecode_list = self._memobj.endcode.ecode
1710
        used_ecode = ''
1711
        for i in ecode_list:
1712
            if i == 0xFF:
1713
                continue
1714
            used_ecode += str(i)[3]
1715
        ecode_val = RadioSettingValueString(0, 7, used_ecode)
1716
        rs = RadioSetting("ecode", "PTT ID Ending(BOT)", ecode_val)
1717
        dtmf.append(rs)
1718
        # H3
1719
        if self.MODEL not in H8_LIST:
1720
            # stuncode
1721
            ecode_list = self._memobj.skcode.stuncode
1722
            used_ecode = ''
1723
            for i in ecode_list:
1724
                if i == 0xFF:
1725
                    continue
1726
                used_ecode += str(i)[3]
1727
            ecode_val = RadioSettingValueString(0, 16, used_ecode)
1728
            rs = RadioSetting("stuncode", "Stun Code", ecode_val)
1729
            dtmf.append(rs)
1730
            # killcode
1731
            ecode_list = self._memobj.skcode.killcode
1732
            used_ecode = ''
1733
            for i in ecode_list:
1734
                if i == 0xFF:
1735
                    continue
1736
                used_ecode += str(i)[3]
1737
            ecode_val = RadioSettingValueString(0, 16, used_ecode)
1738
            rs = RadioSetting("killcode", "Kill Code", ecode_val)
1739
            dtmf.append(rs)
1740

    
1741
        return group
1742

    
1743
    def get_settings(self):
1744
        try:
1745
            return self._get_settings()
1746
        except Exception:
1747
            raise InvalidValueError("Setting Failed!")
1748

    
1749
    def set_settings(self, settings):
1750

    
1751
        def fm_validate(value):
1752
            if 760 > value or value > 1080:
1753
                msg = ("FM Channel must be between 76.0-108.0")
1754
                raise InvalidValueError(msg)
1755

    
1756
        _settings = self._memobj.settings
1757
        _press = self._memobj.press
1758
        _aoffset = self._memobj.aoffset
1759
        _boffset = self._memobj.boffset
1760
        _vfoa = self._memobj.vfoa
1761
        _vfob = self._memobj.vfob
1762
        _fmmode = self._memobj.fmmode
1763

    
1764
        for element in settings:
1765
            if not isinstance(element, RadioSetting):
1766
                if element.get_name() == "fm_preset":
1767
                    self._set_fm_preset(element)
1768
                else:
1769
                    self.set_settings(element)
1770
                    continue
1771
            else:
1772
                try:
1773
                    name = element.get_name()
1774
                    if "." in name:
1775
                        bits = name.split(".")
1776
                        obj = self._memobj
1777
                        for bit in bits[:-1]:
1778
                            if "/" in bit:
1779
                                bit, index = bit.split("/", 1)
1780
                                index = int(index)
1781
                                obj = getattr(obj, bit)[index]
1782
                            else:
1783
                                obj = getattr(obj, bit)
1784
                        setting = bits[-1]
1785
                    elif name in PRESS_NAME:
1786
                        obj = _press
1787
                        setting = element.get_name()
1788

    
1789
                    elif name in VFOA_NAME:
1790
                        obj = _vfoa
1791
                        setting = element.get_name()
1792
                    elif name == "ofseta":
1793
                        obj = _aoffset
1794
                        setting = element.get_name()
1795
                    elif name in VFOB_NAME:
1796
                        obj = _vfob
1797
                        setting = element.get_name()
1798
                    elif name == "ofsetb":
1799
                        obj = _boffset
1800
                        setting = element.get_name()
1801
                    elif "block" in name:
1802
                        obj = _fmmode
1803
                        setting = element.get_name()
1804
                    elif "fmvfo" in name:
1805
                        obj = self._memobj.fmvfo
1806
                        setting = element.get_name()
1807
                    elif "gcode" in name:
1808
                        obj = self._memobj.groupcode.gcode
1809
                        setting = element.get_name()
1810
                    elif "idcode" in name:
1811
                        obj = self._memobj.icode.idcode
1812
                        setting = element.get_name()
1813
                    elif "scode" in name:
1814
                        obj = self._memobj.startcode.scode
1815
                        setting = element.get_name()
1816
                    elif "ecode" in name:
1817
                        obj = self._memobj.endcode.ecode
1818
                        setting = element.get_name()
1819
                    elif "group1" in name:
1820
                        obj = self._memobj.group1.group1
1821
                        setting = element.get_name()
1822
                    elif "group2" in name:
1823
                        obj = self._memobj.group2.group2
1824
                        setting = element.get_name()
1825
                    elif "group3" in name:
1826
                        obj = self._memobj.group3.group3
1827
                        setting = element.get_name()
1828
                    elif "group4" in name:
1829
                        obj = self._memobj.group4.group4
1830
                        setting = element.get_name()
1831
                    elif "group5" in name:
1832
                        obj = self._memobj.group5.group5
1833
                        setting = element.get_name()
1834
                    elif "group6" in name:
1835
                        obj = self._memobj.group6.group6
1836
                        setting = element.get_name()
1837
                    elif "group7" in name:
1838
                        obj = self._memobj.group7.group7
1839
                        setting = element.get_name()
1840
                    elif "group8" in name:
1841
                        obj = self._memobj.group8.group8
1842
                        setting = element.get_name()
1843
                    elif "micgain" in name:
1844
                        obj = self._memobj.mic.micgain
1845
                        setting = element.get_name()
1846
                    elif "killcode" in name:
1847
                        obj = self._memobj.skcode.killcode
1848
                        setting = element.get_name()
1849
                    elif "stuncode" in name:
1850
                        obj = self._memobj.skcode.stuncode
1851
                        setting = element.get_name()
1852
                    else:
1853
                        obj = _settings
1854
                        setting = element.get_name()
1855
                    if element.has_apply_callback():
1856
                        LOG.debug("Using apply callback")
1857
                        element.run_apply_callback()
1858

    
1859
                    # Channel A
1860
                    elif setting == "rxfreqa" and element.value.get_mutable():
1861
                        val = int(str(element.value).replace(
1862
                            '.', '').ljust(8, '0'))
1863
                        if (val >= 13600000 and val <= 17400000) or \
1864
                                (val >= 40000000 and val <= 52000000):
1865
                            setattr(obj, setting, val)
1866
                        else:
1867
                            msg = (
1868
                                "Frequency must be between "
1869
                                "136.00000-174.00000 or 400.00000-520.00000")
1870
                            raise InvalidValueError(msg)
1871

    
1872
                    elif setting == "ofseta" and element.value.get_mutable():
1873
                        if '.' in str(element.value):
1874
                            val = str(element.value).replace(' ', '')
1875
                            if len(
1876
                                val[val.index(".") + 1:]
1877
                                ) >= 1 and int(val[val.index(".") + 1:]
1878
                                               ) != 0:
1879
                                val = '00' + val.replace('.', '')
1880
                            else:
1881
                                val = '0' + val.replace('.', '')
1882
                            val = val.ljust(8, '0')
1883
                            lenth_val = 0
1884
                            list_val = []
1885
                        else:
1886
                            val = '0' + str(element.value).replace(' ', '')
1887
                            val = val.ljust(8, '0')
1888
                            lenth_val = 0
1889
                            list_val = []
1890
                        if (int(val) >= 0 and int(val) <= 5999750):
1891
                            if int(val) == 0:
1892
                                _aoffset.ofseta = [0xFF, 0xFF, 0xFF, 0xFF]
1893
                            else:
1894
                                while lenth_val < (len(val)):
1895
                                    list_val.insert(
1896
                                        0, val[lenth_val:lenth_val + 2])
1897
                                    lenth_val += 2
1898
                                for i in range(len(list_val)):
1899
                                    list_val[i] = int(list_val[i], 16)
1900
                                _aoffset.ofseta = list_val
1901
                        else:
1902
                            msg = ("Offset must be between 0.00000-59.99750")
1903
                            raise InvalidValueError(msg)
1904

    
1905
                    # B channel
1906
                    elif setting == "rxfreqb" and element.value.get_mutable():
1907
                        val = 0
1908
                        val = int(str(element.value).replace(
1909
                            '.', '').ljust(8, '0'))
1910
                        if (val >= 13600000 and val <= 17400000) or \
1911
                                (val >= 40000000 and val <= 52000000):
1912
                            setattr(obj, setting, val)
1913
                        else:
1914
                            msg = (
1915
                                "Frequency must be between "
1916
                                "136.00000-174.00000 or 400.00000-520.00000")
1917
                            raise InvalidValueError(msg)
1918
                        # setattr(obj, setting, val)
1919

    
1920
                    elif setting == "ofsetb" and element.value.get_mutable():
1921
                        if '.' in str(element.value):
1922
                            val = str(element.value).replace(' ', '')
1923
                            if len(val[val.index(".") + 1:]
1924
                                   ) >= 1 and int(val[val.index(".") + 1:]
1925
                                                  ) != 0:
1926
                                val = '00' + \
1927
                                    str(element.value).replace('.', '')
1928
                            else:
1929
                                val = '0' + str(element.value).replace('.', '')
1930
                            val = val.ljust(8, '0')
1931
                            lenth_val = 0
1932
                            list_val = []
1933
                        else:
1934
                            val = '0' + str(element.value).replace(' ', '')
1935
                            val = val.ljust(8, '0')
1936
                            lenth_val = 0
1937
                            list_val = []
1938
                        if (int(val) >= 0 and int(val) <= 5999750):
1939
                            if int(val) == 0:
1940
                                _boffset.ofsetb = [0xFF, 0xFF, 0xFF, 0xFF]
1941
                            else:
1942
                                while lenth_val < (len(val)):
1943
                                    list_val.insert(
1944
                                        0, val[lenth_val:lenth_val + 2])
1945
                                    lenth_val += 2
1946
                                for i in range(len(list_val)):
1947
                                    list_val[i] = int(list_val[i], 16)
1948
                                _boffset.ofsetb = list_val
1949
                        else:
1950
                            msg = ("Offset must be between 0.00000-59.99750")
1951
                            raise InvalidValueError(msg)
1952

    
1953
                    # FM
1954
                    elif "block" in name:
1955
                        num = int(name[-2:], 10)
1956
                        val = str(element.value)
1957
                        if val.strip():
1958
                            try:
1959
                                val = int(float(val) * 10)
1960
                            except ValueError:
1961
                                raise InvalidValueError(
1962
                                    'Value must be between 76.0-108.0')
1963
                            fm_validate(val)
1964
                        else:
1965
                            val = 0
1966
                        self._memobj.fmmode[num].fmblock = val
1967
                        self._memobj.fmusedflags[num] = bool(val)
1968

    
1969
                    elif setting == 'fmvfo' and element.value.get_mutable():
1970
                        self._memobj.fmvfo = int(element.value * 10)
1971

    
1972
                    elif setting == 'gcode' and element.value.get_mutable():
1973
                        val = str(element.value)
1974
                        if val == 'Off':
1975
                            gcode_used = 0xFF
1976
                        elif val == 'A':
1977
                            gcode_used = 0x0A
1978
                        elif val == 'B':
1979
                            gcode_used = 0x0B
1980
                        elif val == 'C':
1981
                            gcode_used = 0x0C
1982
                        elif val == 'D':
1983
                            gcode_used = 0x0D
1984
                        elif val == '#':
1985
                            gcode_used = 0x0F
1986
                        elif val == '*':
1987
                            gcode_used = 0x0E
1988
                        elif val == '':
1989
                            gcode_used = 0x00
1990
                        self._memobj.groupcode.gcode = gcode_used
1991

    
1992
                    elif setting == 'icode' and element.value.get_mutable():
1993
                        val = str(element.value)
1994
                        list_val = []
1995
                        lenth_val = 0
1996
                        while lenth_val < (len(val)):
1997
                            if val[lenth_val] != ' ':
1998
                                list_val.append(int(val[lenth_val], 16))
1999
                                lenth_val += 1
2000
                            else:
2001
                                list_val.append(0xFF)
2002
                                lenth_val += 1
2003
                        self._memobj.icode.idcode = list_val
2004

    
2005
                    elif setting == 'scode' and element.value.get_mutable():
2006
                        val = str(element.value)
2007
                        list_val = []
2008
                        lenth_val = 0
2009
                        while lenth_val < (len(val)):
2010
                            if val[lenth_val] != ' ':
2011
                                list_val.append(int(val[lenth_val], 16))
2012
                                lenth_val += 1
2013
                            else:
2014
                                list_val.append(0xFF)
2015
                                lenth_val += 1
2016
                        self._memobj.startcode.scode = list_val
2017

    
2018
                    elif setting == 'ecode' and element.value.get_mutable():
2019
                        val = str(element.value)
2020
                        list_val = []
2021
                        lenth_val = 0
2022
                        while lenth_val < (len(val)):
2023
                            if val[lenth_val] != ' ':
2024
                                list_val.append(int(val[lenth_val], 16))
2025
                                lenth_val += 1
2026
                            else:
2027
                                list_val.append(0xFF)
2028
                                lenth_val += 1
2029
                        self._memobj.endcode.ecode = list_val
2030

    
2031
                    elif setting == 'group1' and element.value.get_mutable():
2032
                        val = str(element.value)
2033
                        list_val = []
2034
                        lenth_val = 0
2035
                        while lenth_val < (len(val)):
2036
                            if val[lenth_val] != ' ':
2037
                                list_val.append(int(val[lenth_val], 16))
2038
                                lenth_val += 1
2039
                            else:
2040
                                list_val.append(0xFF)
2041
                                lenth_val += 1
2042
                        self._memobj.group1.group1 = list_val
2043

    
2044
                    elif setting == 'group2' and element.value.get_mutable():
2045
                        val = str(element.value)
2046
                        list_val = []
2047
                        lenth_val = 0
2048
                        while lenth_val < (len(val)):
2049
                            if val[lenth_val] != ' ':
2050
                                list_val.append(int(val[lenth_val], 16))
2051
                                lenth_val += 1
2052
                            else:
2053
                                list_val.append(0xFF)
2054
                                lenth_val += 1
2055
                        self._memobj.group2.group2 = list_val
2056

    
2057
                    elif setting == 'group3' and element.value.get_mutable():
2058
                        val = str(element.value)
2059
                        list_val = []
2060
                        lenth_val = 0
2061
                        while lenth_val < (len(val)):
2062
                            if val[lenth_val] != ' ':
2063
                                list_val.append(int(val[lenth_val], 16))
2064
                                lenth_val += 1
2065
                            else:
2066
                                list_val.append(0xFF)
2067
                                lenth_val += 1
2068
                        self._memobj.group3.group3 = list_val
2069

    
2070
                    elif setting == 'group4' and element.value.get_mutable():
2071
                        val = str(element.value)
2072
                        list_val = []
2073
                        lenth_val = 0
2074
                        while lenth_val < (len(val)):
2075
                            if val[lenth_val] != ' ':
2076
                                list_val.append(int(val[lenth_val], 16))
2077
                                lenth_val += 1
2078
                            else:
2079
                                list_val.append(0xFF)
2080
                                lenth_val += 1
2081
                        self._memobj.group4.group4 = list_val
2082

    
2083
                    elif setting == 'group5' and element.value.get_mutable():
2084
                        val = str(element.value)
2085
                        list_val = []
2086
                        lenth_val = 0
2087
                        while lenth_val < (len(val)):
2088
                            if val[lenth_val] != ' ':
2089
                                list_val.append(int(val[lenth_val], 16))
2090
                                lenth_val += 1
2091
                            else:
2092
                                list_val.append(0xFF)
2093
                                lenth_val += 1
2094
                        self._memobj.group5.group5 = list_val
2095

    
2096
                    elif setting == 'group6' and element.value.get_mutable():
2097
                        val = str(element.value)
2098
                        list_val = []
2099
                        lenth_val = 0
2100
                        while lenth_val < (len(val)):
2101
                            if val[lenth_val] != ' ':
2102
                                list_val.append(int(val[lenth_val], 16))
2103
                                lenth_val += 1
2104
                            else:
2105
                                list_val.append(0xFF)
2106
                                lenth_val += 1
2107
                        self._memobj.group6.group6 = list_val
2108

    
2109
                    elif setting == 'group7' and element.value.get_mutable():
2110
                        val = str(element.value)
2111
                        list_val = []
2112
                        lenth_val = 0
2113
                        while lenth_val < (len(val)):
2114
                            if val[lenth_val] != ' ':
2115
                                list_val.append(int(val[lenth_val], 16))
2116
                                lenth_val += 1
2117
                            else:
2118
                                list_val.append(0xFF)
2119
                                lenth_val += 1
2120
                        self._memobj.group7.group7 = list_val
2121

    
2122
                    elif setting == 'group8' and element.value.get_mutable():
2123
                        val = str(element.value)
2124
                        list_val = []
2125
                        lenth_val = 0
2126
                        while lenth_val < (len(val)):
2127
                            if val[lenth_val] != ' ':
2128
                                list_val.append(int(val[lenth_val], 16))
2129
                                lenth_val += 1
2130
                            else:
2131
                                list_val.append(0xFF)
2132
                                lenth_val += 1
2133
                        self._memobj.group8.group8 = list_val
2134
                    elif setting == 'micgain':
2135
                        self._memobj.mic.micgain = (
2136
                            str(element.value))
2137
                    elif setting == 'stuncode' and element.value.get_mutable():
2138
                        val = str(element.value)
2139
                        list_val = []
2140
                        lenth_val = 0
2141
                        while lenth_val < (len(val)):
2142
                            if val[lenth_val] != ' ':
2143
                                list_val.append(int(val[lenth_val], 16))
2144
                                lenth_val += 1
2145
                            else:
2146
                                list_val.append(0xFF)
2147
                                lenth_val += 1
2148
                        self._memobj.skcode.stuncode = list_val
2149
                    elif setting == 'killcode' and element.value.get_mutable():
2150
                        val = str(element.value)
2151
                        list_val = []
2152
                        lenth_val = 0
2153
                        while lenth_val < (len(val)):
2154
                            if val[lenth_val] != ' ':
2155
                                list_val.append(int(val[lenth_val], 16))
2156
                                lenth_val += 1
2157
                            else:
2158
                                list_val.append(0xFF)
2159
                                lenth_val += 1
2160
                        self._memobj.skcode.killcode = list_val
2161
                    elif element.value.get_mutable():
2162
                        setattr(obj, setting, element.value)
2163
                except Exception:
2164
                    LOG.debug(element.get_name())
2165
                    raise
2166

    
2167
    def _set_fm_preset(self, settings):
2168
        for element in settings:
2169
            try:
2170
                val = element.value
2171
                if self._memobj.fm_presets <= 108.0 * 10 - 650:
2172
                    value = int(val.get_value() * 10 - 650)
2173
                else:
2174
                    value = int(val.get_value() * 10)
2175
                LOG.debug("Setting fm_presets = %s" % (value))
2176
                self._memobj.fm_presets = value
2177
            except Exception:
2178
                LOG.debug(element.get_name())
2179
                raise
2180

    
2181

    
2182
@directory.register
2183
@directory.detected_by(TDH8)
2184
class TDH8_HAM(TDH8):
2185
    VENDOR = "TIDRADIO"
2186
    MODEL = "TD-H8-HAM"
2187
    ident_mode = b'P31185\xff\xff'
2188
    _ham = True
2189
    _txbands = [(144000000, 149000000), (420000000, 451000000)]
2190

    
2191

    
2192
@directory.register
2193
@directory.detected_by(TDH8)
2194
class TDH8_GMRS(TDH8):
2195
    VENDOR = "TIDRADIO"
2196
    MODEL = "TD-H8-GMRS"
2197
    ident_mode = b'P31184\xff\xff'
2198
    _gmrs = True
2199
    _txbands = [(136000000, 175000000), (400000000, 521000000)]
2200
    _tx_power = [chirp_common.PowerLevel("Low", watts=1.00),
2201
                 chirp_common.PowerLevel("High", watts=8.00)]
2202

    
2203
    def validate_memory(self, mem):
2204
        msgs = super().validate_memory(mem)
2205
        if 31 <= mem.number <= 54 and mem.freq not in GMRS_FREQS:
2206
            msgs.append(chirp_common.ValidationError(
2207
                "The frequency in channels 31-54 must be between"
2208
                "462.55000-462.72500 in 0.025 increments."))
2209
        return msgs
2210

    
2211

    
2212
@directory.register
2213
class UV68(TDH8):
2214
    VENDOR = "TID"
2215
    MODEL = "TD-UV68"
2216

    
2217

    
2218
@directory.register
2219
class TDH3(TDH8):
2220
    VENDOR = "TIDRADIO"
2221
    MODEL = "TD-H3"
2222
    _memsize = 0x1fef
2223
    _ranges_main = [(0x0000, 0x1fef)]
2224
    _idents = [TD_H3]
2225
    _txbands = [(136000000, 600000000)]
2226
    _rxbands = [(50000000, 107999000), (108000000, 136000000)]
2227
    _aux_block = True
2228
    _tri_power = True
2229
    _gmrs = False
2230
    _ham = False
2231
    _mem_params = (0x1F2F)
2232
    _tx_power = [chirp_common.PowerLevel("Low",  watts=1.00),
2233
                 chirp_common.PowerLevel("High",  watts=4.00)]
2234

    
2235
    def process_mmap(self):
2236
        self._memobj = bitwise.parse(MEM_FORMAT_H3, self._mmap)
2237

    
2238

    
2239
@directory.register
2240
@directory.detected_by(TDH3)
2241
class TDH3_HAM(TDH3):
2242
    VENDOR = "TIDRADIO"
2243
    MODEL = "TD-H3-HAM"
2244
    ident_mode = b'P31185\xff\xff'
2245
    _ham = True
2246
    _txbands = [(144000000, 149000000), (420000000, 451000000)]
2247

    
2248

    
2249
@directory.register
2250
@directory.detected_by(TDH3)
2251
class TDH3_GMRS(TDH3):
2252
    VENDOR = "TIDRADIO"
2253
    MODEL = "TD-H3-GMRS"
2254
    ident_mode = b'P31184\xff\xff'
2255
    _gmrs = True
2256
    _txbands = [(136000000, 175000000), (400000000, 521000000)]
2257

    
2258
    def validate_memory(self, mem):
2259
        msgs = super().validate_memory(mem)
2260
        if 31 <= mem.number <= 54 and mem.freq not in GMRS_FREQS:
2261
            msgs.append(chirp_common.ValidationError(
2262
                "The frequency in channels 31-54 must be between"
2263
                "462.55000-462.72500 in 0.025 increments."))
2264
        return msgs
(3-3/3)