Project

General

Profile

New Model #8803 » kg935g_935gplus_uv8H beta 1.py

Wouxun KG-UV8H driver beta 1 - Mel Terechenok, 03/10/2023 08:51 PM

 
1
# Wouxun KG-935G Driver
2
# Updated to support the KG-935G Plus
3
# Updated to support the KG-UV8H
4
# melvin.terechenok@gmail.com
5

    
6
# Copyright 2019 Pavel Milanes CO7WT <pavelmc@gmail.com>
7
#
8
# Based on the work of Krystian Struzik <toner_82@tlen.pl>
9
# who figured out the crypt used and made possible the
10
# Wuoxun KG-UV8D Plus driver, in which this work is based.
11
#
12
# This program is free software: you can redistribute it and/or modify
13
# it under the terms of the GNU General Public License as published by
14
# the Free Software Foundation, either version 3 of the License, or
15
# (at your option) any later version.
16
#
17
# This program is distributed in the hope that it will be useful,
18
# but WITHOUT ANY WARRANTY; without even the implied warranty of
19
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20
# GNU General Public License for more details.
21
#
22
# You should have received a copy of the GNU General Public License
23
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
24

    
25
"""Wouxun KG-935G radio management module"""
26

    
27
import struct
28
import time
29
import logging
30

    
31
from chirp import util, chirp_common, bitwise, memmap, errors, directory
32
from chirp.settings import RadioSetting, RadioSettingGroup, \
33
    RadioSettingValueBoolean, RadioSettingValueList, \
34
    RadioSettingValueInteger, RadioSettingValueString, \
35
    RadioSettingValueFloat, RadioSettingValueMap, RadioSettings, \
36
    InvalidValueError
37

    
38

    
39
LOG = logging.getLogger(__name__)
40

    
41
CMD_ID = 128    # \x80
42
CMD_END = 129   # \x81
43
CMD_RD = 130    # \82
44
CMD_WR = 131    # \83
45

    
46
MEM_VALID = 158
47

    
48
# set up map of addresses to upload to known memory locations only
49
# Add new regions as new settings are discovered
50
# 1st entry is start address in hex
51
# 2nd entry is write size (number of bytes to transfer in decimal (64-MAX))
52
# 3rd entry is write count
53
# so 0x00, 64, 512 =  start at 0x00 and write 64*512 bytes = 0 - 32768
54

    
55
# NOTE :   It looks like the radio MUST have write
56
#          sizes in values of 1,2,4,8,16,32 or 64 bytes if using
57
#          a write count value of > 1.
58
#          OTHERWISE - it appears the radio gets out of sync
59
#          and the memory write is messed up
60
#          for that section
61

    
62
config_map_935G = (     # map address, write size, write count
63
    # (0x00,   64, 512),  #- Use for full upload testing
64
    (0x44,   32, 1),    # Freq Limits
65
    (0x440,  8,  1),     # Area Message
66
    (0x480,  8, 5),    # Scan Groups
67
    (0x500,  8, 15),    # Call Codes
68
    (0x580,  8, 15),    # Call Names
69
    (0x600,  8, 5),    # FM Presets
70
    (0x800,  64, 2),    # settings
71
    (0x880,  16, 1),    # VFO A
72
    (0x8C0,  16, 1),    # VFO B
73
    (0x900,  64, 250),  # Channel Memory 0-999
74
    (0x4780, 32, 375),  # Memory Names 0-999
75
    (0x7670, 8, 125),   # Ch Valid bytes 0-999
76
    )
77

    
78
AB_LIST = ["A", "B"]
79
STEPS = [2.5, 5.0, 6.25, 10.0, 12.5, 25.0, 50.0, 100.0]
80
STEP_LIST = [str(x) for x in STEPS]
81
ROGER_LIST = ["Off", "Begin", "End", "Both"]
82
TIMEOUT_LIST = ["Off"] + [str(x) + "s" for x in range(15, 901, 15)]
83
VOX_LIST = ["Off"] + ["%s" % x for x in range(1, 10)]
84
BANDWIDTH_LIST = ["Narrow", "Wide"]
85
VOICE_LIST = ["Off", "On"]
86
LANGUAGE_LIST = ["CN", "English", "TC"]
87
SCANMODE_LIST = ["TO", "CO", "SE"]
88
PFKEYLONG_LIST = ["undef", "FRQ2-PTT", "Selec Call", "Scan", "Flashlight",
89
                  "Alarm", "SOS", "FM Radio", "Moni", "Strobe", "Weather",
90
                  "Tlk A", "Reverse", "CTC Scan", "DCS Scan", "BRT"]
91
PFKEYSHORT_LIST = ["undef", "Scan", "Flashlight", "Alarm", "SOS", "FM Radio",
92
                   "Moni", "Strobe", "Weather", "Tlk A", "Reverse",
93
                   "CTC Scan", "DCS Scan", "BRT"]
94
PFKEYLONG_LIST_UV8H = ["Selec Call", "Vice-Fre Tx"]
95
PFKEYSHORT_LIST_UV8H = ["undef", "Scan", "Lamp", "Alarm", "SOS", "Radio"]
96

    
97
PFKEYLONG_LIST_935GPLUS = ["undef", "FRQ2-PTT", "Selec Call", "Favorite",
98
                           "Bright+", "Scan", "Flashlight", "Alarm", "SOS",
99
                           "FM Radio", "Moni", "Strobe", "Weather", "Tlk A",
100
                           "Reverse", "CTC Scan", "DCS Scan", "Backlight"]
101
PFKEYSHORT_LIST_935GPLUS = ["undef", "Favorite", "Bright+", "Scan",
102
                            "Flashlight", "Alarm", "SOS", "FM Radio",
103
                            "Moni", "Strobe", "Weather", "Tlk A", "Reverse",
104
                            "CTC Scan", "DCS Scan", "Backlight"]
105
WORKMODE_LIST = ["VFO", "Ch.Number.", "Ch.Freq.", "Ch.Name"]
106
WORKMODE_LIST_935GPLUS = ["FREQ", "Ch.Number.", "Ch.Freq.", "Ch.Name"]
107
BACKLIGHT_LIST = ["Always On"] + [str(x) + "s" for x in range(1, 21)] + \
108
    ["Always Off"]
109
OFFSET_LIST = ["Off", "Plus Shift", "Minus Shift"]
110
PONMSG_LIST = ["MSG - Bitmap", "Battery Volts"]
111
SPMUTE_LIST = ["QT", "QT+DTMF", "QT*DTMF"]
112
DTMFST_LIST = ["OFF", "DTMF", "ANI", "DTMF+ANI"]
113
DTMF_TIMES = [('%dms' % dtmf, (dtmf // 10)) for dtmf in range(50, 501, 10)]
114
ALERTS = [1750, 2100, 1000, 1450]
115
ALERTS_LIST = [str(x) for x in ALERTS]
116
PTTID_LIST = ["BOT", "EOT", "Both"]
117
LIST_10 = ["Off"] + ["%s" % x for x in range(1, 11)]
118
SCANGRP_LIST = ["All"] + ["%s" % x for x in range(1, 11)]
119
SCQT_LIST = ["Decoder", "Encoder", "All"]
120
SMUTESET_LIST = ["Off", "Tx", "Rx", "Tx+Rx"]
121
POWER_LIST = ["Lo", "Mid", "Hi"]
122
HOLD_TIMES = ["Off"] + ["%s" % x for x in range(100, 5001, 100)]
123
RPTMODE_LIST = ["Radio", "Repeater"]
124
RPTTYPE_MAP = [("X-DIRPT", 1), ("X-TWRPT", 2)]
125
CALLGROUP_LIST = [str(x) for x in range(1, 21)]
126
THEME_LIST = ["White-1", "White-2", "Black-1", "Black-2"]
127
THEME_LIST_935GPLUS = ["White-1", "White-2", "Black-1", "Black-2",
128
                       "Cool", "Rain", "NotARubi", "Sky", "BTWR", "Candy"]
129
DSPBRTSBY_LIST = ["OFF", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]
130
DSPBRTACT_MAP = [("1", 1), ("2", 2), ("3", 3), ("4", 4), ("5", 5),
131
                 ("6", 6), ("7", 7), ("8", 8), ("9", 9), ("10", 10)]
132
TONESCANSAVELIST = ["Rx", "Tx", "Tx/Rx"]
133
PTTDELAY_TIMES = [('%dms' % pttdelay,
134
                  (pttdelay // 100)) for pttdelay in range(100, 3001, 100)]
135
SCRAMBLE_LIST = ["OFF"] + [str(x) for x in range(1, 9)]
136
ONOFF_LIST = ["OFF", "ON"]
137
# MRT - Map CTCSS Tones -  Value in mem is hex value of
138
# (ctcss tone * 10) + 0x8000
139
# MRT - 0x8000 is for CTCSS tones
140
# MRT - Map DCS Tones -  Value in mem is hex representation of
141
# DCS Tone# in Octal + either 0x4000 or 0x6000 for polarity
142
# MRT - 0x4000 is for DCS n tones
143
# MRT - 0x6000 is for DCS i tones
144
TONE_MAP = [('Off', 0x0000)] + \
145
           [('%.1f' % tone,
146
            int(0x8000 + tone * 10)) for tone in chirp_common.TONES] + \
147
           [('D%03dn' % tone, int(0x4000 + int(str(tone), 8)))
148
               for tone in chirp_common.DTCS_CODES] + \
149
           [('D%03di' % tone, int(0x6000 + int(str(tone), 8)))
150
               for tone in chirp_common.DTCS_CODES]
151
BATT_DISP_LIST = ["Icon", "Voltage", "Percent"]
152
WX_TYPE = ["Weather", "Icon-Only", "Tone", "Flash", "Tone-Flash"]
153
# memory slot 0 is not used, start at 1 (so need 1000 slots, not 999)
154
# structure elements whose name starts with x are currently unidentified
155

    
156
_MEM_FORMAT_935G = """
157
    #seekto 0x0044;
158
    struct {
159
        u32    rx_start;
160
        u32    rx_stop;
161
        u32    tx_start;
162
        u32    tx_stop;
163
    } uhf_limits;
164

    
165
    #seekto 0x0054;
166
    struct {
167
        u32    rx_start;
168
        u32    rx_stop;
169
        u32    tx_start;
170
        u32    tx_stop;
171
    } vhf_limits;
172

    
173
    #seekto 0x0400;
174
    struct {
175
        char     oem1[8];
176
        char     unknown[2];
177
        u8     unknown2[10];
178
        u8     unknown3[10];
179
        u8     unknown4[8];
180
        char     oem2[10];
181
        u8     version[6];
182
        char     date[8];
183
        u8     unknown5[2];
184
        char     model[8];
185
    } oem_info;
186

    
187
    #seekto 0x0480;
188
    struct {
189
        u16    Group_lower1;
190
        u16    Group_upper1;
191
        u16    Group_lower2;
192
        u16    Group_upper2;
193
        u16    Group_lower3;
194
        u16    Group_upper3;
195
        u16    Group_lower4;
196
        u16    Group_upper4;
197
        u16    Group_lower5;
198
        u16    Group_upper5;
199
        u16    Group_lower6;
200
        u16    Group_upper6;
201
        u16    Group_lower7;
202
        u16    Group_upper7;
203
        u16    Group_lower8;
204
        u16    Group_upper8;
205
        u16    Group_lower9;
206
        u16    Group_upper9;
207
        u16    Group_lower10;
208
        u16    Group_upper10;
209
    } scan_groups;
210

    
211
    #seekto 0x0500;
212
    struct {
213
        u8 cid[6];
214
    } call_ids[20];
215

    
216
    #seekto 0x0580;
217
    struct {
218
        char    call_name1[6];
219
        char    call_name2[6];
220
        char    call_name3[6];
221
        char    call_name4[6];
222
        char    call_name5[6];
223
        char    call_name6[6];
224
        char    call_name7[6];
225
        char    call_name8[6];
226
        char    call_name9[6];
227
        char    call_name10[6];
228
        char    call_name11[6];
229
        char    call_name12[6];
230
        char    call_name13[6];
231
        char    call_name14[6];
232
        char    call_name15[6];
233
        char    call_name16[6];
234
        char    call_name17[6];
235
        char    call_name18[6];
236
        char    call_name19[6];
237
        char    call_name20[6];
238
    } call_names;
239

    
240

    
241
    #seekto 0x0600;
242
    struct {
243
        u16    FM_radio1;
244
        u16    FM_radio2;
245
        u16    FM_radio3;
246
        u16    FM_radio4;
247
        u16    FM_radio5;
248
        u16    FM_radio6;
249
        u16    FM_radio7;
250
        u16    FM_radio8;
251
        u16    FM_radio9;
252
        u16    FM_radio10;
253
        u16    FM_radio11;
254
        u16    FM_radio12;
255
        u16    FM_radio13;
256
        u16    FM_radio14;
257
        u16    FM_radio15;
258
        u16    FM_radio16;
259
        u16    FM_radio17;
260
        u16    FM_radio18;
261
        u16    FM_radio19;
262
        u16    FM_radio20;
263
        u16 unknown_pad_x0640[235];
264
        u8 unknown07fe;
265
        u8 unknown07ff;
266
        u8      ponmsg;
267
        char    dispstr[15];
268
        u8 unknown0810;
269
        u8 unknown0811;
270
        u8 unknown0812;
271
        u8 unknown0813;
272
        u8 unknown0814;
273
        u8      voice;
274
        u8      timeout;
275
        u8      toalarm;
276
        u8      channel_menu;
277
        u8      power_save;
278
        u8      autolock;
279
        u8      keylock;
280
        u8      beep;
281
        u8      stopwatch;
282
        u8      vox;
283
        u8      scan_rev;
284
        u8      backlight;
285
        u8      roger_beep;
286
        char      mode_sw_pwd[6];
287
        char      reset_pwd[6];
288
        u16     pri_ch;
289
        u8      ani_sw;
290
        u8      ptt_delay;
291
        u8      ani_code[6];
292
        u8      dtmf_st;
293
        u8      BCL_A;
294
        u8      BCL_B;
295
        u8      ptt_id;
296
        u8      prich_sw;
297
        u8 unknown083d;
298
        u8 unknown083e;
299
        u8 unknown083f;
300
        u8      alert;
301
        u8      pf1_shrt;
302
        u8      pf1_long;
303
        u8      pf2_shrt;
304
        u8      pf2_long;
305
        u8 unknown0845;
306
        u8      work_mode_a;
307
        u8      work_mode_b;
308
        u8      dtmf_tx_time;
309
        u8      dtmf_interval;
310
        u8      main_band;
311
        u16      work_ch_a;
312
        u16      work_ch_b;
313
        u8 unknown084f;
314
        u8 unknown0850;
315
        u8 unknown0851;
316
        u8 unknown0852;
317
        u8 unknown0853;
318
        u8 unknown0854;
319
        u8 unknown0855;
320
        u8 unknown0856;
321
        u8 unknown0857;
322
        u8 unknown0858;
323
        u8 unknown0859;
324
        u8 unknown085a;
325
        u8 unknown085b;
326
        u8 unknown085c;
327
        u8 unknown085d;
328
        u8 unknown085e;
329
        u8 unknown085f;
330
        u8 unknown0860;
331
        u8      TDR_single_mode;
332
        u8      ring_time;
333
        u8      ScnGrpA_Act;
334
        u8      ScnGrpB_Act;
335
        u8 unknown0865;
336
        u8      rpt_tone;
337
        u8 unknown0867;
338
        u8      scan_det;
339
        u8      ToneScnSave;
340
        u8 unknown086a;
341
        u8      smuteset;
342
        u8      cur_call_grp;
343
        u8      DspBrtAct;
344
        u8      DspBrtSby;
345
        u8 unknown086f;
346
        u8      theme;
347
        u8      wxalert;
348
        u8      VFO_repeater_a;
349
        u8      VFO_repeater_b;
350
        u8 unknown0874;
351
        u8 unknown0875;
352
        u8 unknown0876;
353
        u8 unknown0877;
354
        u8 unknown0878;
355
        u8 unknown0879;
356
        u8 unknown087a;
357
        u8 unknown087b;
358
        u8 unknown087c;
359
        u8 unknown087d;
360
        u8 unknown087e;
361
        u8 unknown087f;
362
    } settings;
363

    
364
    #seekto 0x0880;
365
    struct {
366
        u32     rxfreq;
367
        u32     unknown0;
368
        u16     rxtone;
369
        u16     txtone;
370
        u8      scrambler:4,
371
                power:4;
372
        u8      unknown3:1,
373
                unknown5:2,
374
                unknown4:1,
375
                cmpndr:1,
376
                mute_mode:2,
377
                iswide:1;
378
        u8      step;
379
        u8      squelch;
380
      } vfoa;
381

    
382
    #seekto 0x08c0;
383
    struct {
384
        u32     rxfreq;
385
        u32     unknown0;
386
        u16     rxtone;
387
        u16     txtone;
388
        u8      scrambler:4,
389
                power:4;
390
        u8      unknown3:1,
391
                unknown5:2,
392
                unknown4:1,
393
                cmpndr:1,
394
                mute_mode:2,
395
                iswide:1;
396
        u8      step;
397
        u8      squelch;
398
    } vfob;
399

    
400
    #seekto 0x0900;
401
    struct {
402
        u32     rxfreq;
403
        u32     txfreq;
404
        u16     rxtone;
405
        u16     txtone;
406
        u8      scrambler:4,
407
                power:4;
408
        u8      unknown3:2,
409
                scan_add:1,
410
                unknown4:1,
411
                compander:1,
412
                mute_mode:2,
413
                iswide:1;
414
        u8      unknown5;
415
        u8      unknown6;
416
    } memory[1000];
417

    
418
    #seekto 0x4780;
419
    struct {
420
        u8    name[8];
421
                u8    unknown[4];
422
    } names[1000];
423

    
424
    #seekto 0x7670;
425
    u8          valid[1000];
426
    """
427

    
428
_MEM_FORMAT_935GPLUS = """
429
    #seekto 0x0044;
430
    struct {
431
        u32    rx_start;
432
        u32    rx_stop;
433
        u32    tx_start;
434
        u32    tx_stop;
435
    } uhf_limits;
436

    
437
    #seekto 0x0054;
438
    struct {
439
        u32    rx_start;
440
        u32    rx_stop;
441
        u32    tx_start;
442
        u32    tx_stop;
443
    } vhf_limits;
444

    
445
    #seekto 0x0400;
446
    struct {
447
        char     oem1[8];
448
        char     unknown[2];
449
        u8     unknown2[10];
450
        u8     unknown3[10];
451
        u8     unknown4[8];
452
        char     oem2[10];
453
        u8     version[6];
454
        char     date[8];
455
        u8     unknown5[2];
456
        char     model[8];
457
    } oem_info;
458

    
459
    #seekto 0x0480;
460
    struct {
461
        u16    Group_lower1;
462
        u16    Group_upper1;
463
        u16    Group_lower2;
464
        u16    Group_upper2;
465
        u16    Group_lower3;
466
        u16    Group_upper3;
467
        u16    Group_lower4;
468
        u16    Group_upper4;
469
        u16    Group_lower5;
470
        u16    Group_upper5;
471
        u16    Group_lower6;
472
        u16    Group_upper6;
473
        u16    Group_lower7;
474
        u16    Group_upper7;
475
        u16    Group_lower8;
476
        u16    Group_upper8;
477
        u16    Group_lower9;
478
        u16    Group_upper9;
479
        u16    Group_lower10;
480
        u16    Group_upper10;
481
    } scan_groups;
482

    
483
    #seekto 0x0500;
484
    struct {
485
        u8 cid[6];
486
    } call_ids[20];
487

    
488
    #seekto 0x0580;
489
    struct {
490
        char    call_name1[6];
491
        char    call_name2[6];
492
        char    call_name3[6];
493
        char    call_name4[6];
494
        char    call_name5[6];
495
        char    call_name6[6];
496
        char    call_name7[6];
497
        char    call_name8[6];
498
        char    call_name9[6];
499
        char    call_name10[6];
500
        char    call_name11[6];
501
        char    call_name12[6];
502
        char    call_name13[6];
503
        char    call_name14[6];
504
        char    call_name15[6];
505
        char    call_name16[6];
506
        char    call_name17[6];
507
        char    call_name18[6];
508
        char    call_name19[6];
509
        char    call_name20[6];
510
    } call_names;
511

    
512

    
513
    #seekto 0x0600;
514
    struct {
515
        u16    FM_radio1;
516
        u16    FM_radio2;
517
        u16    FM_radio3;
518
        u16    FM_radio4;
519
        u16    FM_radio5;
520
        u16    FM_radio6;
521
        u16    FM_radio7;
522
        u16    FM_radio8;
523
        u16    FM_radio9;
524
        u16    FM_radio10;
525
        u16    FM_radio11;
526
        u16    FM_radio12;
527
        u16    FM_radio13;
528
        u16    FM_radio14;
529
        u16    FM_radio15;
530
        u16    FM_radio16;
531
        u16    FM_radio17;
532
        u16    FM_radio18;
533
        u16    FM_radio19;
534
        u16    FM_radio20;
535
        u16 unknown_pad_x0640[235];
536
        u8 unknown07fe;
537
        u8 unknown07ff;
538
        u8      ponmsg;
539
        char    dispstr[15];
540
        u8 unknown0810;
541
        u8 unknown0811;
542
        u8 unknown0812;
543
        u8 unknown0813;
544
        u8 unknown0814;
545
        u8      voice;
546
        u8      timeout;
547
        u8      toalarm;
548
        u8      channel_menu;
549
        u8      power_save;
550
        u8      autolock;
551
        u8      keylock;
552
        u8      beep;
553
        u8      stopwatch;
554
        u8      vox;
555
        u8      scan_rev;
556
        u8      backlight;
557
        u8      roger_beep;
558
        char      mode_sw_pwd[6];
559
        char      reset_pwd[6];
560
        u16     pri_ch;
561
        u8      ani_sw;
562
        u8      ptt_delay;
563
        u8      ani_code[6];
564
        u8      dtmf_st;
565
        u8      BCL_A;
566
        u8      BCL_B;
567
        u8      ptt_id;
568
        u8      prich_sw;
569
        u8 unknown083d;
570
        u8 unknown083e;
571
        u8 unknown083f;
572
        u8      alert;
573
        u8      pf1_shrt;
574
        u8      pf1_long;
575
        u8      pf2_shrt;
576
        u8      pf2_long;
577
        u8 unknown0845;
578
        u8      work_mode_a;
579
        u8      work_mode_b;
580
        u8      dtmf_tx_time;
581
        u8      dtmf_interval;
582
        u8      main_band;
583
        u16      work_ch_a;
584
        u16      work_ch_b;
585
        u8 unknown084f;
586
        u8 unknown0850;
587
        u8 unknown0851;
588
        u8 unknown0852;
589
        u8 unknown0853;
590
        u8 unknown0854;
591
        u8 unknown0855;
592
        u8 unknown0856;
593
        u8 unknown0857;
594
        u8 unknown0858;
595
        u8 unknown0859;
596
        u8 unknown085a;
597
        u8 unknown085b;
598
        u8 unknown085c;
599
        u8 unknown085d;
600
        u8 unknown085e;
601
        u8 unknown085f;
602
        u8 unknown0860;
603
        u8      TDR_single_mode;
604
        u8      ring_time;
605
        u8      ScnGrpA_Act;
606
        u8      ScnGrpB_Act;
607
        u8 unknown0865;
608
        u8      rpt_tone;
609
        u8 unknown0867;
610
        u8      scan_det;
611
        u8      ToneScnSave;
612
        u8 unknown086a;
613
        u8      smuteset;
614
        u8      cur_call_grp;
615
        u8      DspBrtAct;
616
        u8      DspBrtSby;
617
        u8 unknown086f;
618
        u8      theme;
619
        u8      batt_ind;
620
        u8      wxalert;
621
        u8      wxalert_type;
622
        u8 VFO_repeater_a;
623
        u8 VFO_repeater_b;
624
        u8 sim_rec;
625
        u8 unknown0877;
626
        u8 unknown0878;
627
        u8 unknown0879;
628
        u8 unknown087a;
629
        u8 unknown087b;
630
        u8 unknown087c;
631
        u8 unknown087d;
632
        u8 unknown087e;
633
        u8 unknown087f;
634
    } settings;
635

    
636
    #seekto 0x0880;
637
    struct {
638
        u32     rxfreq;
639
        u32     unknown0;
640
        u16     rxtone;
641
        u16     txtone;
642
        u8      scrambler:4,
643
                power:4;
644
        u8      unknown3:1,
645
                unknown5:2,
646
                unknown4:1,
647
                cmpndr:1,
648
                mute_mode:2,
649
                iswide:1;
650
        u8      step;
651
        u8      squelch;
652
      } vfoa;
653

    
654
    #seekto 0x08c0;
655
    struct {
656
        u32     rxfreq;
657
        u32     unknown0;
658
        u16     rxtone;
659
        u16     txtone;
660
        u8      scrambler:4,
661
                power:4;
662
        u8      unknown3:1,
663
                unknown5:2,
664
                unknown4:1,
665
                cmpndr:1,
666
                mute_mode:2,
667
                iswide:1;
668
        u8      step;
669
        u8      squelch;
670
    } vfob;
671

    
672
    #seekto 0x0900;
673
    struct {
674
        u32     rxfreq;
675
        u32     txfreq;
676
        u16     rxtone;
677
        u16     txtone;
678
        u8      scrambler:4,
679
                power:4;
680
        u8      unknown3:2,
681
                scan_add:1,
682
                unknown4:1,
683
                compander:1,
684
                mute_mode:2,
685
                iswide:1;
686
        u8      unknown5;
687
        u8      unknown6;
688
    } memory[1000];
689

    
690
    #seekto 0x4780;
691
    struct {
692
        u8    name[8];
693
                u8    unknown[4];
694
    } names[1000];
695

    
696
    #seekto 0x7670;
697
    u8          valid[1000];
698
    """
699

    
700
_MEM_FORMAT_UV8H = """
701
    #seekto 0x0044;
702
    struct {
703
        u32    rx_start;
704
        u32    rx_stop;
705
        u32    tx_start;
706
        u32    tx_stop;
707
    } uhf_limits;
708

    
709
    #seekto 0x0054;
710
    struct {
711
        u32    rx_start;
712
        u32    rx_stop;
713
        u32    tx_start;
714
        u32    tx_stop;
715
    } vhf_limits;
716

    
717
    #seekto 0x0400;
718
    struct {
719
        char     oem1[8];
720
        char     unknown[2];
721
        u8     unknown2[10];
722
        u8     unknown3[10];
723
        u8     unknown4[8];
724
        char     oem2[10];
725
        u8     version[6];
726
        char     date[8];
727
        u8     unknown5[2];
728
        char     model[8];
729
    } oem_info;
730

    
731
    #seekto 0x0480;
732
    struct {
733
        u16    Group_lower1;
734
        u16    Group_upper1;
735
        u16    Group_lower2;
736
        u16    Group_upper2;
737
        u16    Group_lower3;
738
        u16    Group_upper3;
739
        u16    Group_lower4;
740
        u16    Group_upper4;
741
        u16    Group_lower5;
742
        u16    Group_upper5;
743
        u16    Group_lower6;
744
        u16    Group_upper6;
745
        u16    Group_lower7;
746
        u16    Group_upper7;
747
        u16    Group_lower8;
748
        u16    Group_upper8;
749
        u16    Group_lower9;
750
        u16    Group_upper9;
751
        u16    Group_lower10;
752
        u16    Group_upper10;
753
    } scan_groups;
754

    
755
    #seekto 0x0500;
756
    struct {
757
        u8 cid[6];
758
    } call_ids[20];
759

    
760
    #seekto 0x0580;
761
    struct {
762
        char    call_name1[6];
763
        char    call_name2[6];
764
        char    call_name3[6];
765
        char    call_name4[6];
766
        char    call_name5[6];
767
        char    call_name6[6];
768
        char    call_name7[6];
769
        char    call_name8[6];
770
        char    call_name9[6];
771
        char    call_name10[6];
772
        char    call_name11[6];
773
        char    call_name12[6];
774
        char    call_name13[6];
775
        char    call_name14[6];
776
        char    call_name15[6];
777
        char    call_name16[6];
778
        char    call_name17[6];
779
        char    call_name18[6];
780
        char    call_name19[6];
781
        char    call_name20[6];
782
    } call_names;
783

    
784

    
785
    #seekto 0x0600;
786
    struct {
787
        u16    FM_radio1;
788
        u16    FM_radio2;
789
        u16    FM_radio3;
790
        u16    FM_radio4;
791
        u16    FM_radio5;
792
        u16    FM_radio6;
793
        u16    FM_radio7;
794
        u16    FM_radio8;
795
        u16    FM_radio9;
796
        u16    FM_radio10;
797
        u16    FM_radio11;
798
        u16    FM_radio12;
799
        u16    FM_radio13;
800
        u16    FM_radio14;
801
        u16    FM_radio15;
802
        u16    FM_radio16;
803
        u16    FM_radio17;
804
        u16    FM_radio18;
805
        u16    FM_radio19;
806
        u16    FM_radio20;
807
        u16 unknown_pad_x0640[235];
808
        u8 unknown07fe;
809
        u8 unknown07ff;
810
        u8      ponmsg;
811
        char    dispstr[15];
812
        u8 unknown0810;
813
        u8 unknown0811;
814
        u8 unknown0812;
815
        u8 unknown0813;
816
        u8 unknown0814;
817
        u8      voice;
818
        u8      timeout;
819
        u8      toalarm;
820
        u8      channel_menu;
821
        u8      power_save;
822
        u8      autolock;
823
        u8      keylock;
824
        u8      beep;
825
        u8      stopwatch;
826
        u8      vox;
827
        u8      scan_rev;
828
        u8      backlight;
829
        u8      roger_beep;
830
        char      mode_sw_pwd[6];
831
        char      reset_pwd[6];
832
        u16     pri_ch;
833
        u8      ani_sw;
834
        u8      ptt_delay;
835
        u8      ani_code[6];
836
        u8      dtmf_st;
837
        u8      BCL_A; 
838
        u8      BCL_B;
839
        u8      ptt_id;
840
        u8      prich_sw;
841
        u8      rpttype;
842
        u8      rptspk;
843
        u8      rptptt;
844
        u8      alert;
845
        u8      pf1_long;
846
        u8      pf1_shrt;
847
        u8      unk845;
848
        u8      work_mode_a;
849
        u8      work_mode_b;
850
        u8      dtmf_tx_time;
851
        u8      dtmf_interval;
852
        u8  unk4a;
853
        u16      work_ch_a;
854
        u16      work_ch_b;
855
        u8  unk4d;
856
        u8      main_band;
857
        u8 unknown084f;
858
        u8 unknown0850;
859
        u8 unknown0851;
860
        u8 unknown0852;
861
        u8 unknown0853;
862
        u8 unknown0854;
863
        u8      rptmode;
864
        u8      language;
865
        u8 unknown0857;
866
        u8 unknown0858;
867
        u8 unknown0859;
868
        u8 unknown085a;
869
        u8 unknown085b;
870
        u8 unknown085c;
871
        u8 unknown085d;
872
        u8 unknown085e;
873
        u8      TDR_single_mode;
874
        u8      ring_time;
875
        u8      ScnGrpA_Act;
876
        u8      ScnGrpB_Act;
877
        u8 unk863;
878
        u8      rpt_tone;
879
        u8      rpt_hold;
880
        u8      scan_det;
881
        u8      ToneScnSave;
882
        u8 unk868;
883
        u8      smuteset;
884
        u8      cur_call_grp;
885
        u8      DspBrtAct;
886
        u8 unknown086c;
887
        u8      theme;
888
        u8      wxalert;
889
        u8 unknown086f;
890
        u8 unknown0870;
891
        u8      unk871;
892
        u8      unk872;
893
        u8      unk873;
894
        u8 unknown0874;
895
        u8 unknown0875;
896
        u8 unknown0876;
897
        u8 unknown0877;
898
        u8 unknown0878;
899
        u8 unknown0879;
900
        u8 unknown087a;
901
        u8 unknown087b;
902
        u8 unknown087c;
903
        u8 unknown087d;
904
        u8 unknown087e;
905
        u8 unknown087f;
906
    } settings;
907

    
908
    #seekto 0x0880;
909
    struct {
910
        u32     rxfreq;
911
        u32     offset;
912
        u16     rxtone;
913
        u16     txtone;
914
        u8      scrambler:4,
915
                power:4;
916
        u8      unknown3:1,
917
                ofst_dir:2,
918
                unknown4:1,
919
                cmpndr:1,
920
                mute_mode:2,
921
                iswide:1;
922
        u8      step;
923
        u8      squelch;
924
      } vfoa;
925

    
926
    #seekto 0x08c0;
927
    struct {
928
        u32     rxfreq;
929
        u32     offset;
930
        u16     rxtone;
931
        u16     txtone;
932
        u8      scrambler:4,
933
                power:4;
934
        u8      unknown3:1,
935
                ofst_dir:2,
936
                unknown4:1,
937
                cmpndr:1,
938
                mute_mode:2,
939
                iswide:1;
940
        u8      step;
941
        u8      squelch;
942
    } vfob;
943

    
944
    #seekto 0x0900;
945
    struct {
946
        u32     rxfreq;
947
        u32     txfreq;
948
        u16     rxtone;
949
        u16     txtone;
950
        u8      scrambler:4,
951
                power:4;
952
        u8      unknown3:2,
953
                scan_add:1,
954
                unknown4:1,
955
                compander:1,
956
                mute_mode:2,
957
                iswide:1;
958
        u8      unknown5;
959
        u8      unknown6;
960
    } memory[1000];
961

    
962
    #seekto 0x4780;
963
    struct {
964
        u8    name[8];
965
                u8    unknown[4];
966
    } names[1000];
967

    
968
    #seekto 0x7670;
969
    u8          valid[1000];
970
    """
971

    
972

    
973
# Support for the Wouxun KG-935G radio
974
# Serial coms are at 19200 baud
975
# The data is passed in variable length records
976
# Record structure:
977
#  Offset   Usage
978
#    0      start of record (\x7c)
979
#    1      Command (\x80 Identify \x81 End/Reboot \x82 Read \x83 Write)
980
#    2      direction (\xff PC-> Radio, \x00 Radio -> PC)
981
#    3      length of payload (excluding header/checksum) (n)
982
#    4      payload (n bytes)
983
#    4+n+1  checksum - byte sum (% 256) of bytes 1 -> 4+n
984
#
985
# Memory Read Records:
986
# the payload is 3 bytes, first 2 are offset (big endian),
987
# 3rd is number of bytes to read
988
# Memory Write Records:
989
# the maximum payload size (from the Wouxun software) seems to be 66 bytes
990
#  (2 bytes location + 64 bytes data).
991

    
992
# MRT 1.2 correct spelling of Wouxon
993

    
994

    
995
def str2callid(val):
996
    """ Convert caller id strings from callid2str.
997
    """
998
    ascii2bin = "0123456789"
999
    s = str(val).strip()
1000
    LOG.debug("val = %s" % val)
1001
    LOG.debug("s = %s" % s)
1002
    if len(s) < 3 or len(s) > 6:
1003
        raise InvalidValueError(
1004
            "Caller ID must be at least 3 and no more than 6 digits")
1005
    if s[0] == '0':
1006
        raise InvalidValueError(
1007
            "First digit of a Caller ID cannot be a zero '0'")
1008
    blk = bytearray()
1009
    for c in s:
1010
        if c not in ascii2bin:
1011
            raise InvalidValueError(
1012
                "Caller ID must be all digits 0x%x" % c)
1013
        b = ascii2bin.index(c)
1014
        blk.append(b)
1015
    if len(blk) < 6:
1016
        blk.append(0x0F)  # EOL a short ID
1017
    if len(blk) < 6:
1018
        for i in range(0, (6 - len(blk))):
1019
            blk.append(0xf0)
1020
    LOG.debug("blk = %s" % blk)
1021
    return blk
1022

    
1023

    
1024
@directory.register
1025
class KG935GRadio(chirp_common.CloneModeRadio,
1026
                  chirp_common.ExperimentalRadio):
1027

    
1028
    """Wouxun KG-935G"""
1029
    VENDOR = "Wouxun"
1030
    MODEL = "KG-935G"
1031
    _model = b"KG-UV8D-B"
1032
    BAUD_RATE = 19200
1033
    # MRT - Added Medium Power level for 935G support
1034
    POWER_LEVELS = [chirp_common.PowerLevel("L", watts=0.5),
1035
                    chirp_common.PowerLevel("M", watts=4.5),
1036
                    chirp_common.PowerLevel("H", watts=5.5)]
1037
    NEEDS_COMPAT_SERIAL = False
1038
    _record_start = 0x7C
1039

    
1040
    def _checksum(self, data):
1041
        cs = 0
1042
        for byte in data:
1043
            cs += byte
1044
        return cs % 256
1045

    
1046
    def _write_record(self, cmd, payload=b''):
1047
        _packet = struct.pack('BBBB', self._record_start, cmd, 0xFF,
1048
                              len(payload))
1049
        checksum = bytes([self._checksum(_packet[1:] + payload)])
1050
        _packet += self.encrypt(payload + checksum)
1051
        LOG.debug("Sent:\n%s" % util.hexprint(_packet))
1052
        self.pipe.write(_packet)
1053

    
1054
    def _read_record(self):
1055
        # read 4 chars for the header
1056
        _header = self.pipe.read(4)
1057
        if len(_header) != 4:
1058
            raise errors.RadioError('Radio did not respond')
1059
        _length = struct.unpack('xxxB', _header)[0]
1060
        _packet = self.pipe.read(_length)
1061
        _rcs_xor = _packet[-1]
1062
        _packet = self.decrypt(_packet)
1063
        _cs = self._checksum(_header[1:])
1064
        _cs += self._checksum(_packet)
1065
        _cs %= 256
1066
        _rcs = self.strxor(self.pipe.read(1)[0], _rcs_xor)[0]
1067
        LOG.debug("_cs =%x", _cs)
1068
        LOG.debug("_rcs=%x", _rcs)
1069
        return (_rcs != _cs, _packet)
1070

    
1071
    def decrypt(self, data):
1072
        result = b''
1073
        for i in range(len(data)-1, 0, -1):
1074
            result += self.strxor(data[i], data[i - 1])
1075
        result += self.strxor(data[0], 0x57)
1076
        return result[::-1]
1077

    
1078
    def encrypt(self, data):
1079
        result = self.strxor(0x57, data[0])
1080
        for i in range(1, len(data), 1):
1081
            result += self.strxor(result[i - 1], data[i])
1082
        return result
1083

    
1084
    def strxor(self, xora, xorb):
1085
        return bytes([xora ^ xorb])
1086

    
1087
    # Identify the radio
1088
    #
1089
    # A Gotcha: the first identify packet returns a bad checksum, subsequent
1090
    # attempts return the correct checksum... (well it does on my radio!)
1091
    #
1092
    # The ID record returned by the radio also includes the
1093
    # current frequency range
1094
    # as 4 bytes big-endian in 10Hz increments
1095
    #
1096
    # Offset
1097
    #  0:10     Model, zero padded (Looks for 'KG-UV8D-B')
1098

    
1099
    def _identify(self):
1100
        """Do the identification dance"""
1101
        for _i in range(0, 10):
1102
            self._write_record(CMD_ID)
1103
            _chksum_err, _resp = self._read_record()
1104
            LOG.debug("Got:\n%s" % util.hexprint(_resp))
1105
            if _chksum_err:
1106
                LOG.error("Checksum error: retrying ident...")
1107
                time.sleep(0.100)
1108
                continue
1109
            LOG.debug("Model %s" % util.hexprint(_resp[0:9]))
1110
            if _resp[0:9] == self._model:
1111
                return
1112
            if len(_resp) == 0:
1113
                raise Exception("Radio not responding")
1114
            else:
1115
                raise Exception("Unable to identify radio")
1116

    
1117
    def _finish(self):
1118
        self._write_record(CMD_END)
1119

    
1120
    def process_mmap(self):
1121
        self._memobj = bitwise.parse(_MEM_FORMAT_935G, self._mmap)
1122

    
1123
    def sync_in(self):
1124
        try:
1125
            self._mmap = self._download()
1126
        except errors.RadioError:
1127
            raise
1128
        except Exception as e:
1129
            raise errors.RadioError("Failed to communicate with radio: %s" % e)
1130
        self.process_mmap()
1131

    
1132
    def sync_out(self):
1133
        self._upload()
1134

    
1135
    # TODO: Load all memory.
1136
    # It would be smarter to only load the active areas and none of
1137
    # the padding/unused areas. Padding still need to be investigated.
1138
    def _download(self):
1139
        """Talk to a wouxun KG-935G and do a download"""
1140
        try:
1141
            self._identify()
1142
            return self._do_download(0, 32768, 64)
1143
        except errors.RadioError:
1144
            raise
1145
        except Exception as e:
1146
            LOG.exception('Unknown error during download process')
1147
            raise errors.RadioError("Failed to communicate with radio: %s" % e)
1148

    
1149
    def _do_download(self, start, end, blocksize):
1150
        # allocate & fill memory
1151
        image = b""
1152
        for i in range(start, end, blocksize):
1153
            req = struct.pack('>HB', i, blocksize)
1154
            self._write_record(CMD_RD, req)
1155
            cs_error, resp = self._read_record()
1156
            if cs_error:
1157
                LOG.debug(util.hexprint(resp))
1158
                raise Exception("Checksum error on read")
1159
            # LOG.debug("Got:\n%s" % util.hexprint(resp))
1160
            image += resp[2:]
1161
            if self.status_fn:
1162
                status = chirp_common.Status()
1163
                status.cur = i
1164
                status.max = end
1165
                status.msg = "Cloning from radio"
1166
                self.status_fn(status)
1167
        self._finish()
1168
        return memmap.MemoryMapBytes(image)
1169

    
1170
    def _upload(self):
1171
        """Talk to a wouxun KG-935G and do a upload"""
1172
        try:
1173
            self._identify()
1174
            self._do_upload()
1175
        except errors.RadioError:
1176
            raise
1177
        except Exception as e:
1178
            raise errors.RadioError("Failed to communicate with radio: %s" % e)
1179
        return
1180

    
1181
    def _do_upload(self):
1182
        cfgmap = config_map_935G
1183

    
1184
        for start, blocksize, count in cfgmap:
1185
            end = start + (blocksize * count)
1186
            LOG.debug("start = " + str(start))
1187
            LOG.debug("end = " + str(end))
1188
            LOG.debug("blksize = " + str(blocksize))
1189

    
1190
            for addr in range(start, end, blocksize):
1191
                ptr = addr
1192
                LOG.debug("ptr = " + str(ptr))
1193
                req = struct.pack('>H', addr)
1194
                chunk = self.get_mmap()[ptr:ptr + blocksize]
1195
                self._write_record(CMD_WR, req + chunk)
1196
                LOG.debug(util.hexprint(req + chunk))
1197
                cserr, ack = self._read_record()
1198
                LOG.debug(util.hexprint(ack))
1199
                j = struct.unpack('>H', ack)[0]
1200
                if cserr or j != ptr:
1201
                    raise Exception("Radio did not ack block %i" % ptr)
1202
                ptr += blocksize
1203
                if self.status_fn:
1204
                    status = chirp_common.Status()
1205
                    status.cur = ptr
1206
                    status.max = 0x8000
1207
                    status.msg = "Cloning to radio"
1208
                    self.status_fn(status)
1209

    
1210
        self._finish()
1211

    
1212
    def get_features(self):
1213
        rf = chirp_common.RadioFeatures()
1214
        rf.has_settings = True
1215
        rf.has_ctone = True
1216
        rf.has_rx_dtcs = True
1217
        rf.has_cross = True
1218
        rf.has_tuning_step = False
1219
        rf.has_bank = False
1220
        rf.can_odd_split = True
1221
        rf.valid_skips = ["", "S"]
1222
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
1223
        rf.valid_cross_modes = [
1224
            "Tone->Tone",
1225
            "Tone->DTCS",
1226
            "DTCS->Tone",
1227
            "DTCS->",
1228
            "->Tone",
1229
            "->DTCS",
1230
            "DTCS->DTCS",
1231
        ]
1232
        rf.valid_modes = ["FM", "NFM"]
1233
        rf.valid_power_levels = self.POWER_LEVELS
1234
        rf.valid_name_length = 8
1235
        rf.valid_duplexes = ["", "-", "+", "split", "off"]
1236
        #   MRT - Open up channel memory freq range to support
1237
        #   RxFreq limit expansion
1238
        rf.valid_bands = [(30000000, 299999990),  # supports 2m
1239
                          (300000000, 999999990)]  # supports 70cm
1240

    
1241
        rf.valid_characters = chirp_common.CHARSET_ASCII
1242
        rf.memory_bounds = (1, 999)  # 999 memories
1243
        rf.valid_tuning_steps = STEPS
1244
        return rf
1245

    
1246
    @classmethod
1247
    def get_prompts(cls):
1248
        rp = chirp_common.RadioPrompts()
1249
        rp.experimental = \
1250
            ('This driver is experimental.  USE AT YOUR OWN RISK\n'
1251
             '\n'
1252
             'Please save a copy of the image from your radio with Chirp '
1253
             'before modifying any values.\n'
1254
             '\n'
1255
             'Please keep a copy of your memories with the original Wouxon'
1256
             'CPS software if you treasure them, this driver is new and'
1257
             'may contain bugs.\n'
1258
             )
1259
        return rp
1260

    
1261
    def get_raw_memory(self, number):
1262
        return repr(self._memobj.memory[number])
1263

    
1264
    def _get_tone(self, _mem, mem):
1265
        # MRT - corrected the Polarity decoding to match 935G implementation
1266
        # use 0x2000 bit mask for R
1267
        # MRT - 0x2000 appears to be the bit mask for Inverted DCS tones
1268
        # MRT - n DCS Tone will be 0x4xxx values - i DCS Tones will
1269
        # be 0x6xxx values.
1270
        # MRT - Chirp Uses N for n DCS Tones and R for i DCS Tones
1271
        def _get_dcs(val):
1272
            code = int("%03o" % (val & 0x07FF))
1273
            pol = (val & 0x2000) and "R" or "N"
1274
            return code, pol
1275
        # MRT - Modified the function below to bitwise AND with 0x4000
1276
        # to check for 935G DCS Tone decoding
1277
        # MRT 0x4000 appears to be the bit mask for DCS tones
1278
        tpol = False
1279
        # MRT Beta 1.1 - Fix the txtone compare to 0x4000 - was rxtone.
1280
        if _mem.txtone != 0xFFFF and (_mem.txtone & 0x4000) == 0x4000:
1281
            tcode, tpol = _get_dcs(_mem.txtone)
1282
            mem.dtcs = tcode
1283
            txmode = "DTCS"
1284
        elif _mem.txtone != 0xFFFF and _mem.txtone != 0x0:
1285
            mem.rtone = (_mem.txtone & 0x7fff) / 10.0
1286
            txmode = "Tone"
1287
        else:
1288
            txmode = ""
1289
        # MRT - Modified the function below to bitwise AND with 0x4000
1290
        # to check for 935G DCS Tone decoding
1291
        rpol = False
1292
        if _mem.rxtone != 0xFFFF and (_mem.rxtone & 0x4000) == 0x4000:
1293
            rcode, rpol = _get_dcs(_mem.rxtone)
1294
            mem.rx_dtcs = rcode
1295
            rxmode = "DTCS"
1296
        elif _mem.rxtone != 0xFFFF and _mem.rxtone != 0x0:
1297
            mem.ctone = (_mem.rxtone & 0x7fff) / 10.0
1298
            rxmode = "Tone"
1299
        else:
1300
            rxmode = ""
1301

    
1302
        if txmode == "Tone" and not rxmode:
1303
            mem.tmode = "Tone"
1304
        elif txmode == rxmode and txmode == "Tone" and mem.rtone == mem.ctone:
1305
            mem.tmode = "TSQL"
1306
        elif txmode == rxmode and txmode == "DTCS" and mem.dtcs == mem.rx_dtcs:
1307
            mem.tmode = "DTCS"
1308
        elif rxmode or txmode:
1309
            mem.tmode = "Cross"
1310
            mem.cross_mode = "%s->%s" % (txmode, rxmode)
1311

    
1312
        # always set it even if no dtcs is used
1313
        mem.dtcs_polarity = "%s%s" % (tpol or "N", rpol or "N")
1314

    
1315
        LOG.debug("Got TX %s (%i) RX %s (%i)" %
1316
                  (txmode, _mem.txtone, rxmode, _mem.rxtone))
1317

    
1318
    def get_memory(self, number):
1319
        _mem = self._memobj.memory[number]
1320
        _nam = self._memobj.names[number]
1321

    
1322
        mem = chirp_common.Memory()
1323
        mem.number = number
1324
        _valid = self._memobj.valid[mem.number]
1325
        LOG.debug("%d %s", number, _valid == MEM_VALID)
1326
        if _valid != MEM_VALID:
1327
            mem.empty = True
1328
            return mem
1329
        else:
1330
            mem.empty = False
1331

    
1332
        mem.freq = int(_mem.rxfreq) * 10
1333

    
1334
        if _mem.txfreq == 0xFFFFFFFF:
1335
            # TX freq not set
1336
            mem.duplex = "off"
1337
            mem.offset = 0
1338
        elif int(_mem.rxfreq) == int(_mem.txfreq):
1339
            mem.duplex = ""
1340
            mem.offset = 0
1341
        elif abs(int(_mem.rxfreq) * 10 - int(_mem.txfreq) * 10) > 70000000:
1342
            mem.duplex = "split"
1343
            mem.offset = int(_mem.txfreq) * 10
1344
        else:
1345
            mem.duplex = int(_mem.rxfreq) > int(_mem.txfreq) and "-" or "+"
1346
            mem.offset = abs(int(_mem.rxfreq) - int(_mem.txfreq)) * 10
1347

    
1348
        for char in _nam.name:
1349
            if char != 0:
1350
                mem.name += chr(char)
1351
        mem.name = mem.name.rstrip()
1352

    
1353
        self._get_tone(_mem, mem)
1354

    
1355
        mem.skip = "" if bool(_mem.scan_add) else "S"
1356
        _mem.power = _mem.power & 0x3
1357
        if _mem.power > 2:
1358
            _mem.power = 2
1359
        mem.power = self.POWER_LEVELS[_mem.power]
1360
        mem.mode = _mem.iswide and "FM" or "NFM"
1361
        return mem
1362

    
1363
    def _set_tone(self, mem, _mem):
1364
        def _set_dcs(code, pol):
1365
            # MRT Change from + 0x2800 to bitwise OR with 0x4000 to
1366
            # set the bit for DCS
1367
            val = int("%i" % code, 8) | 0x4000
1368
            if pol == "R":
1369
                # MRT Change to 0x2000 from 0x8000 to set the bit for
1370
                # i/R polarity
1371
                val += 0x2000
1372
            return val
1373

    
1374
        rx_mode = tx_mode = None
1375
        rxtone = txtone = 0x0000
1376

    
1377
        if mem.tmode == "Tone":
1378
            tx_mode = "Tone"
1379
            rx_mode = None
1380
            txtone = int(mem.rtone * 10) + 0x8000
1381
        elif mem.tmode == "TSQL":
1382
            rx_mode = tx_mode = "Tone"
1383
            rxtone = txtone = int(mem.ctone * 10) + 0x8000
1384
        elif mem.tmode == "DTCS":
1385
            tx_mode = rx_mode = "DTCS"
1386
            txtone = _set_dcs(mem.dtcs, mem.dtcs_polarity[0])
1387
            rxtone = _set_dcs(mem.dtcs, mem.dtcs_polarity[1])
1388
        elif mem.tmode == "Cross":
1389
            tx_mode, rx_mode = mem.cross_mode.split("->")
1390
            if tx_mode == "DTCS":
1391
                txtone = _set_dcs(mem.dtcs, mem.dtcs_polarity[0])
1392
            elif tx_mode == "Tone":
1393
                txtone = int(mem.rtone * 10) + 0x8000
1394
            if rx_mode == "DTCS":
1395
                rxtone = _set_dcs(mem.rx_dtcs, mem.dtcs_polarity[1])
1396
            elif rx_mode == "Tone":
1397
                rxtone = int(mem.ctone * 10) + 0x8000
1398

    
1399
        _mem.rxtone = rxtone
1400
        _mem.txtone = txtone
1401

    
1402
        LOG.debug("Set TX %s (%i) RX %s (%i)" %
1403
                  (tx_mode, _mem.txtone, rx_mode, _mem.rxtone))
1404

    
1405
    def set_memory(self, mem):
1406
        number = mem.number
1407

    
1408
        _mem = self._memobj.memory[number]
1409
        _nam = self._memobj.names[number]
1410

    
1411
        if mem.empty:
1412
            _mem.set_raw("\x00" * (_mem.size() // 8))
1413
            self._memobj.valid[number] = 0
1414
            self._memobj.names[number].set_raw("\x00" * (_nam.size() // 8))
1415
            return
1416

    
1417
        _mem.rxfreq = int(mem.freq / 10)
1418
        if mem.duplex == "off":
1419
            _mem.txfreq = 0xFFFFFFFF
1420
        elif mem.duplex == "split":
1421
            _mem.txfreq = int(mem.offset / 10)
1422
        elif mem.duplex == "off":
1423
            for i in range(0, 4):
1424
                _mem.txfreq[i].set_raw("\xFF")
1425
        elif mem.duplex == "+":
1426
            _mem.txfreq = int(mem.freq / 10) + int(mem.offset / 10)
1427
        elif mem.duplex == "-":
1428
            _mem.txfreq = int(mem.freq / 10) - int(mem.offset / 10)
1429
        else:
1430
            _mem.txfreq = int(mem.freq / 10)
1431
        _mem.scan_add = int(mem.skip != "S")
1432
        _mem.iswide = int(mem.mode == "FM")
1433
        # set the tone
1434
        self._set_tone(mem, _mem)
1435
        # MRT set the scrambler and compander to off by default
1436
        # MRT This changes them in the channel memory
1437
        _mem.scrambler = 0
1438
        _mem.compander = 0
1439
        # set the power
1440
        _mem.power = _mem.power & 0x3
1441
        if mem.power:
1442
            if _mem.power > 2:
1443
                _mem.power = 2
1444
            _mem.power = self.POWER_LEVELS.index(mem.power)
1445
        else:
1446
            _mem.power = True
1447
        # MRT set to mute mode to QT (not QT+DTMF or QT*DTMF) by default
1448
        # MRT This changes them in the channel memory
1449
        _mem.mute_mode = 0
1450

    
1451
        # MRT it is unknown what impact these values have
1452
        # MRT This changes them in the channel memory to match what
1453
        # Wouxun CPS shows when creating a channel
1454
        # MRT It is likely that these are just left as is and not
1455
        # written to by CPS - bit remnants of 0xFF in the unused memory
1456
        # _mem.unknown1 = 0
1457
        # MRT Set to 3 to TO MATCH CPS VALUES
1458
        _mem.unknown3 = 3
1459
        # MRT Set to 1 to TO MATCH CPS VALUES
1460
        _mem.unknown4 = 1
1461
        # MRT set unknown5 to 1 and unknown6 to 0
1462
        _mem.unknown5 = 1
1463
        _mem.unknown6 = 255
1464

    
1465
        for i in range(0, len(_nam.name)):
1466
            if i < len(mem.name) and mem.name[i]:
1467
                _nam.name[i] = ord(mem.name[i])
1468
            else:
1469
                _nam.name[i] = 0x0
1470
        self._memobj.valid[mem.number] = MEM_VALID
1471

    
1472
    def _get_settings(self):
1473
        _settings = self._memobj.settings
1474
        _vfoa = self._memobj.vfoa
1475
        _vfob = self._memobj.vfob
1476
        _scan = self._memobj.scan_groups
1477
        _callname = self._memobj.call_names
1478

    
1479
        if self.MODEL == "KG-935G":
1480
            themelist = THEME_LIST
1481
            vfoa_grp_label = "VFO A Settings"
1482
            vfob_grp_label = "VFO B Settings"
1483
            workmodelist = WORKMODE_LIST
1484
            pfkeyshort = PFKEYSHORT_LIST
1485
            pfkeylong = PFKEYLONG_LIST
1486
            dispmesg = "Display Message - Interface Display Edit"
1487
            areamsglabel = "Model / Bottom Banner"
1488
            vfo_area = "VFO "
1489
            ani_msg = "ANI-ID Switch (ANI-SW)"
1490
            pttdly_msg = "PTT-DLY"
1491
            idtx_msg = "PTT-ID"
1492
        elif self.MODEL == "KG-UV8H":
1493
            themelist = THEME_LIST
1494
            vfoa_grp_label = "VFO A Settings"
1495
            vfob_grp_label = "VFO B Settings"
1496
            workmodelist = WORKMODE_LIST
1497
            pfkeyshort = PFKEYSHORT_LIST_UV8H
1498
            pfkeylong = PFKEYLONG_LIST_UV8H
1499
            dispmesg = "Display Message - Interface Display Edit"
1500
            areamsglabel = "Model / Bottom Banner"
1501
            vfo_area = "VFO "
1502
            ani_msg = "ANI-ID Switch (ANI-SW)"
1503
            pttdly_msg = "PTT-DLY"
1504
            idtx_msg = "PTT-ID"
1505
        elif self.MODEL == "KG-935G Plus":
1506
            themelist = THEME_LIST_935GPLUS
1507
            vfoa_grp_label = "Freq Mode A Settings"
1508
            vfob_grp_label = "Freq Mode B Settings"
1509
            workmodelist = WORKMODE_LIST_935GPLUS
1510
            pfkeyshort = PFKEYSHORT_LIST_935GPLUS
1511
            pfkeylong = PFKEYLONG_LIST_935GPLUS
1512
            dispmesg = "Top Message"
1513
            areamsglabel = "Area Message"
1514
            vfo_area = "Area "
1515
            ani_msg = "Send ANI-ID on Tx(ANI-SW)"
1516
            pttdly_msg = "ID Delay"
1517
            idtx_msg = "ID Transmit Setting"
1518

    
1519
        cfg_grp = RadioSettingGroup("cfg_grp", "Config Settings")
1520
        vfoa_grp = RadioSettingGroup("vfoa_grp", vfoa_grp_label)
1521
        vfob_grp = RadioSettingGroup("vfob_grp", vfob_grp_label)
1522
        key_grp = RadioSettingGroup("key_grp", "Key Settings")
1523
        fmradio_grp = RadioSettingGroup("fmradio_grp", "FM Broadcast Memory")
1524
        lmt_grp = RadioSettingGroup("lmt_grp", "Frequency Limits")
1525
        oem_grp = RadioSettingGroup("oem_grp", "OEM Info")
1526
        scan_grp = RadioSettingGroup("scan_grp", "Scan Group")
1527
        call_grp = RadioSettingGroup("call_grp", "Call Settings")
1528
        group = RadioSettings(cfg_grp, vfoa_grp, vfob_grp,
1529
                              fmradio_grp, key_grp, scan_grp,
1530
                              call_grp, lmt_grp, oem_grp)
1531

    
1532
        # Call Settings
1533
        rs = RadioSetting("cur_call_grp", "Current Call Group",
1534
                          RadioSettingValueList(CALLGROUP_LIST,
1535
                                                CALLGROUP_LIST[_settings.
1536
                                                               cur_call_grp]))
1537
        call_grp.append(rs)
1538

    
1539
        def apply_callid(setting, obj):
1540
            c = str2callid(setting.value)
1541
            obj.cid = c
1542

    
1543
        callchars = "0123456789"
1544
        for i in range(1, 21):
1545
            callnum = str(i)
1546
            _msg = ""
1547
            _msg1 = str(eval("_callname.call_name"+callnum)).split("\0")[0]
1548
            # MRT - Handle default factory values of 0xFF or
1549
            # non-ascii values in Call Name memory
1550
            for char in _msg1:
1551
                if char < chr(0x20) or char > chr(0x7E):
1552
                    _msg += ""
1553
                else:
1554
                    _msg += str(char)
1555
            val = RadioSettingValueString(0, 6, _msg)
1556
            val.set_mutable(True)
1557
            rs = RadioSetting("call_names.call_name"+callnum,
1558
                              "Call Name "+callnum, val)
1559
            call_grp.append(rs)
1560

    
1561
            x = i - 1
1562
            callid = self._memobj.call_ids[x]
1563
            c_id = RadioSettingValueString(0, 6,
1564
                                           self.callid2str(callid.cid),
1565
                                           False)
1566
            rs = RadioSetting("call_ids[%i].cid" % x,
1567
                              "Call Code %s" % str(i), c_id)
1568
            rs.set_apply_callback(apply_callid, callid)
1569
            call_grp.append(rs)
1570

    
1571
        # Configuration Settings
1572
        #
1573
        rs = RadioSetting("DspBrtAct", "Display Brightness ACTIVE",
1574
                          RadioSettingValueMap(DSPBRTACT_MAP,
1575
                                               _settings.DspBrtAct))
1576
        cfg_grp.append(rs)
1577
        if self.MODEL != "KG-UV8H":
1578
            rs = RadioSetting("DspBrtSby", "Display Brightness STANDBY",
1579
                            RadioSettingValueList(DSPBRTSBY_LIST,
1580
                                                    DSPBRTSBY_LIST[_settings.
1581
                                                                DspBrtSby]))
1582
            cfg_grp.append(rs)
1583
        rs = RadioSetting("wxalert", "Weather Alert",
1584
                          RadioSettingValueBoolean(_settings.wxalert))
1585
        cfg_grp.append(rs)
1586

    
1587
        if self.MODEL == "KG-935G Plus":
1588
            rs = RadioSetting("wxalert_type", "Weather Alert Notification",
1589
                              RadioSettingValueList(WX_TYPE,
1590
                                                    WX_TYPE[_settings.
1591
                                                            wxalert_type]))
1592
            cfg_grp.append(rs)
1593

    
1594
        rs = RadioSetting("power_save", "Battery Saver",
1595
                          RadioSettingValueBoolean(_settings.power_save))
1596
        cfg_grp.append(rs)
1597
        rs = RadioSetting("theme", "Theme",
1598
                          RadioSettingValueList(
1599
                              themelist, themelist[_settings.theme]))
1600
        cfg_grp.append(rs)
1601
        rs = RadioSetting("backlight", "Backlight Active Time",
1602
                          RadioSettingValueList(BACKLIGHT_LIST,
1603
                                                BACKLIGHT_LIST[_settings.
1604
                                                               backlight]))
1605
        cfg_grp.append(rs)
1606
        rs = RadioSetting("scan_rev", "Scan Mode",
1607
                          RadioSettingValueList(SCANMODE_LIST,
1608
                                                SCANMODE_LIST[_settings.
1609
                                                              scan_rev]))
1610
        cfg_grp.append(rs)
1611
        rs = RadioSetting("prich_sw", "Priority Channel Scan",
1612
                          RadioSettingValueBoolean(_settings.prich_sw))
1613
        cfg_grp.append(rs)
1614
        rs = RadioSetting("pri_ch",
1615
                          "Priority Channel - Can not be empty Channel",
1616
                          RadioSettingValueInteger(1, 999, _settings.pri_ch))
1617
        cfg_grp.append(rs)
1618
        rs = RadioSetting("scan_det", "Scan Mode Tone Detect",
1619
                          RadioSettingValueBoolean(_settings.scan_det))
1620
        cfg_grp.append(rs)
1621
        rs = RadioSetting("ToneScnSave", "Tone Scan Save",
1622
                          RadioSettingValueList(TONESCANSAVELIST,
1623
                                                TONESCANSAVELIST[_settings.
1624
                                                                 ToneScnSave]))
1625
        cfg_grp.append(rs)
1626
        rs = RadioSetting("roger_beep", "Roger Beep",
1627
                          RadioSettingValueList(ROGER_LIST,
1628
                                                ROGER_LIST[_settings.
1629
                                                           roger_beep]))
1630
        cfg_grp.append(rs)
1631
        rs = RadioSetting("timeout", "Timeout Timer (TOT)",
1632
                          RadioSettingValueList(
1633
                              TIMEOUT_LIST, TIMEOUT_LIST[_settings.timeout]))
1634
        cfg_grp.append(rs)
1635
        rs = RadioSetting("toalarm", "Timeout Alarm (TOA)",
1636
                          RadioSettingValueInteger(0, 10, _settings.toalarm))
1637
        cfg_grp.append(rs)
1638
        rs = RadioSetting("vox", "VOX",
1639
                          RadioSettingValueList(LIST_10,
1640
                                                LIST_10[_settings.vox]))
1641
        cfg_grp.append(rs)
1642
        rs = RadioSetting("voice", "Voice Guide",
1643
                          RadioSettingValueBoolean(_settings.voice))
1644
        cfg_grp.append(rs)
1645
        rs = RadioSetting("beep", "Keypad Beep",
1646
                          RadioSettingValueBoolean(_settings.beep))
1647
        cfg_grp.append(rs)
1648
        rs = RadioSetting("BCL_A", "Busy Channel Lock-out A",
1649
                          RadioSettingValueBoolean(_settings.BCL_A))
1650
        cfg_grp.append(rs)
1651
        rs = RadioSetting("BCL_B", "Busy Channel Lock-out B",
1652
                          RadioSettingValueBoolean(_settings.BCL_B))
1653
        cfg_grp.append(rs)
1654
        rs = RadioSetting("smuteset", "Secondary Area Mute (SMUTESET)",
1655
                          RadioSettingValueList(SMUTESET_LIST,
1656
                                                SMUTESET_LIST[_settings.
1657
                                                              smuteset]))
1658
        cfg_grp.append(rs)
1659
        rs = RadioSetting("ani_sw", ani_msg,
1660
                          RadioSettingValueBoolean(_settings.ani_sw))
1661
        cfg_grp.append(rs)
1662
        rs = RadioSetting("dtmf_st", "DTMF Sidetone (SIDETONE)",
1663
                          RadioSettingValueList(DTMFST_LIST,
1664
                                                DTMFST_LIST[_settings.
1665
                                                            dtmf_st]))
1666
        cfg_grp.append(rs)
1667
        rs = RadioSetting("alert", "Alert Tone",
1668
                          RadioSettingValueList(ALERTS_LIST,
1669
                                                ALERTS_LIST[_settings.alert]))
1670
        cfg_grp.append(rs)
1671
        rs = RadioSetting("ptt_delay", pttdly_msg,
1672
                          RadioSettingValueMap(PTTDELAY_TIMES,
1673
                                               _settings.ptt_delay))
1674
        cfg_grp.append(rs)
1675
        rs = RadioSetting("ptt_id", idtx_msg,
1676
                          RadioSettingValueList(PTTID_LIST,
1677
                                                PTTID_LIST[_settings.ptt_id]))
1678
        cfg_grp.append(rs)
1679

    
1680
        rs = RadioSetting("ring_time", "Ring Time",
1681
                          RadioSettingValueList(LIST_10,
1682
                                                LIST_10[_settings.ring_time]))
1683
        cfg_grp.append(rs)
1684

    
1685
        if self.MODEL == "KG-UV8H":
1686
            rs = RadioSetting("language", "Language",
1687
                                RadioSettingValueList(
1688
                                    LANGUAGE_LIST,
1689
                                    LANGUAGE_LIST[_settings.language]))
1690
            cfg_grp.append(rs)
1691
            rs = RadioSetting("rptmode", "Radio Work Mode",
1692
                                RadioSettingValueList(
1693
                                    RPTMODE_LIST,
1694
                                    RPTMODE_LIST[_settings.rptmode]))
1695
            cfg_grp.append(rs)
1696
            rs = RadioSetting("rpttype", "Repeater Type",
1697
                                RadioSettingValueMap(RPTTYPE_MAP,
1698
                                    _settings.rpttype))
1699
            cfg_grp.append(rs)
1700
            rs = RadioSetting("rptspk", "Repeater Speaker",
1701
                            RadioSettingValueBoolean(_settings.rptspk))
1702
            cfg_grp.append(rs)
1703
            rs = RadioSetting("rptptt", "Repeater PTT",
1704
                            RadioSettingValueBoolean(_settings.rptptt))
1705
            cfg_grp.append(rs)
1706
            rs = RadioSetting("rpt_hold", "RPT Hold Time",
1707
                            RadioSettingValueList(HOLD_TIMES,
1708
                                                HOLD_TIMES[_settings.rpt_hold]))
1709
            cfg_grp.append(rs)
1710

    
1711
        rs = RadioSetting("rpt_tone", "Repeater Tone",
1712
                          RadioSettingValueBoolean(_settings.rpt_tone))
1713
        cfg_grp.append(rs)
1714
        rs = RadioSetting("stopwatch", "Timer / Stopwatch",
1715
                          RadioSettingValueBoolean(_settings.stopwatch))
1716
        cfg_grp.append(rs)
1717
        rs = RadioSetting("autolock", "Autolock",
1718
                          RadioSettingValueBoolean(_settings.autolock))
1719
        cfg_grp.append(rs)
1720
        rs = RadioSetting("keylock", "Keypad Lock",
1721
                          RadioSettingValueBoolean(_settings.keylock))
1722
        cfg_grp.append(rs)
1723
        rs = RadioSetting("ponmsg", "Poweron message",
1724
                          RadioSettingValueList(
1725
                               PONMSG_LIST, PONMSG_LIST[_settings.ponmsg]))
1726
        cfg_grp.append(rs)
1727
        rs = RadioSetting("dtmf_tx_time", "DTMF Transmit Time",
1728
                          RadioSettingValueMap(DTMF_TIMES,
1729
                                               _settings.dtmf_tx_time))
1730
        cfg_grp.append(rs)
1731
        rs = RadioSetting("dtmf_interval", "DTMF Interval Time",
1732
                          RadioSettingValueMap(DTMF_TIMES,
1733
                                               _settings.dtmf_interval))
1734
        cfg_grp.append(rs)
1735

    
1736
        if self.MODEL == "KG-935G Plus":
1737
            rs = RadioSetting("batt_ind", "Battery Indicator",
1738
                              RadioSettingValueList(
1739
                                  BATT_DISP_LIST,
1740
                                  BATT_DISP_LIST[_settings.batt_ind]))
1741
            cfg_grp.append(rs)
1742

    
1743
            rs = RadioSetting("sim_rec", "Simultaneous Receive",
1744
                              RadioSettingValueBoolean(_settings.sim_rec))
1745
            cfg_grp.append(rs)
1746

    
1747
        rs = RadioSetting("channel_menu", "Menu available in channel mode",
1748
                          RadioSettingValueBoolean(_settings.channel_menu))
1749
        cfg_grp.append(rs)
1750

    
1751
        pswdchars = "0123456789"
1752
        _msg = str(_settings.mode_sw_pwd).split("\0")[0]
1753
        val = RadioSettingValueString(0, 6, _msg, False)
1754
        val.set_mutable(True)
1755
        val.set_charset(pswdchars)
1756
        rs = RadioSetting("mode_sw_pwd", "Mode SW Pwd", val)
1757
        cfg_grp.append(rs)
1758

    
1759
        _msg = str(_settings.reset_pwd).split("\0")[0]
1760
        val = RadioSettingValueString(0, 6, _msg, False)
1761
        val.set_charset(pswdchars)
1762
        val.set_mutable(True)
1763
        rs = RadioSetting("reset_pwd", "Reset Pwd", val)
1764
        cfg_grp.append(rs)
1765

    
1766
        # Key Settings
1767
        #
1768
        _msg = str(_settings.dispstr).split("\0")[0]
1769
        val = RadioSettingValueString(0, 15, _msg)
1770
        val.set_mutable(True)
1771
        rs = RadioSetting("dispstr",
1772
                          dispmesg, val)
1773
        key_grp.append(rs)
1774

    
1775
        def apply_ani_id(setting, obj):
1776
            c = str2callid(setting.value)
1777
            obj.ani_code = c
1778

    
1779
        cid = self._memobj.settings
1780
        my_callid = RadioSettingValueString(3, 6,
1781
                                            self.callid2str(cid.ani_code),
1782
                                            False)
1783
        rs = RadioSetting("ani_code", "ANI Edit", my_callid)
1784
        rs.set_apply_callback(apply_ani_id, cid)
1785
        key_grp.append(rs)
1786

    
1787
        rs = RadioSetting("pf1_shrt", "PF1 SHORT Key function",
1788
                          RadioSettingValueList(
1789
                              pfkeyshort,
1790
                              pfkeyshort[_settings.pf1_shrt]))
1791
        key_grp.append(rs)
1792
        rs = RadioSetting("pf1_long", "PF1 LONG Key function",
1793
                          RadioSettingValueList(
1794
                              pfkeylong,
1795
                              pfkeylong[_settings.pf1_long]))
1796
        key_grp.append(rs)
1797
        if self.MODEL != "KG-UV8H":
1798
            rs = RadioSetting("pf2_shrt", "PF2 SHORT Key function",
1799
                            RadioSettingValueList(
1800
                                pfkeyshort,
1801
                                pfkeyshort[_settings.pf2_shrt]))
1802
            key_grp.append(rs)
1803
            rs = RadioSetting("pf2_long", "PF2 LONG Key function",
1804
                            RadioSettingValueList(
1805
                                pfkeylong,
1806
                                pfkeylong[_settings.pf2_long]))
1807
            key_grp.append(rs)
1808

    
1809
#       SCAN GROUP settings
1810
        rs = RadioSetting("ScnGrpA_Act", "Scan Group A Active",
1811
                          RadioSettingValueList(SCANGRP_LIST,
1812
                                                SCANGRP_LIST[_settings.
1813
                                                             ScnGrpA_Act]))
1814
        scan_grp.append(rs)
1815
        rs = RadioSetting("ScnGrpB_Act", "Scan Group B Active",
1816
                          RadioSettingValueList(SCANGRP_LIST,
1817
                                                SCANGRP_LIST[_settings.
1818
                                                             ScnGrpB_Act]))
1819
        scan_grp.append(rs)
1820

    
1821
        for i in range(1, 11):
1822
            scgroup = str(i)
1823

    
1824
            rs = RadioSetting("scan_groups.Group_lower"+scgroup,
1825
                              "Scan Group "+scgroup+" Lower",
1826
                              RadioSettingValueInteger(1, 999,
1827
                                                       eval("self._memobj. \
1828
                                                            scan_groups. \
1829
                                                            Group_lower" +
1830
                                                            scgroup)))
1831
            scan_grp.append(rs)
1832

    
1833
            rs = RadioSetting("scan_groups.Group_upper"+scgroup,
1834
                              "Scan Group "+scgroup+" Upper",
1835
                              RadioSettingValueInteger(1, 999,
1836
                                                       eval("self._memobj. \
1837
                                                            scan_groups. \
1838
                                                            Group_upper" +
1839
                                                            scgroup)))
1840
            scan_grp.append(rs)
1841

    
1842
        # VFO A Settings
1843
        #
1844
        rs = RadioSetting("work_mode_a", vfo_area + "A Workmode",
1845
                          RadioSettingValueList(workmodelist,
1846
                                                workmodelist[_settings.
1847
                                                             work_mode_a]))
1848
        vfoa_grp.append(rs)
1849
        rs = RadioSetting("work_ch_a", vfo_area + "A Work Channel",
1850
                          RadioSettingValueInteger(1, 999,
1851
                                                   _settings.work_ch_a))
1852
        vfoa_grp.append(rs)
1853
        rs = RadioSetting("vfoa.rxfreq", vfo_area + "A Rx Frequency (MHz)",
1854
                          RadioSettingValueFloat(
1855
                              30.00000, 999.999999,
1856
                              (_vfoa.rxfreq / 100000.0), 0.000001, 6))
1857
        vfoa_grp.append(rs)
1858
        if self.MODEL == "KG-UV8H":
1859
            rs = RadioSetting("vfoa.offset", vfo_area + "A Offset (MHz)",
1860
                            RadioSettingValueFloat(
1861
                                0.00000, 599.999999,
1862
                                (_vfoa.offset / 100000.0), 0.000001, 6))
1863
            vfoa_grp.append(rs)
1864

    
1865
        rs = RadioSetting("vfoa.rxtone", vfo_area + "A Rx tone",
1866
                          RadioSettingValueMap(
1867
                            TONE_MAP, _vfoa.rxtone))
1868
        vfoa_grp.append(rs)
1869
        rs = RadioSetting("vfoa.txtone", vfo_area + "A Tx tone",
1870
                          RadioSettingValueMap(
1871
                            TONE_MAP, _vfoa.txtone))
1872
        vfoa_grp.append(rs)
1873

    
1874
        # MRT - AND power with 0x03 to display only the lower 2 bits for
1875
        # power level and to clear the upper bits
1876
        # MRT - any bits set in the upper 2 bits will cause radio to show
1877
        # invalid values for power level and a display glitch
1878
        # MRT - when PTT is pushed
1879
        _vfoa.power = _vfoa.power & 0x3
1880
        if _vfoa.power > 2:
1881
            _vfoa.power = 2
1882
        rs = RadioSetting("vfoa.power", vfo_area + "A Power",
1883
                          RadioSettingValueList(
1884
                              POWER_LIST, POWER_LIST[_vfoa.power]))
1885
        vfoa_grp.append(rs)
1886
        rs = RadioSetting("vfoa.iswide", vfo_area + "A Wide/Narrow",
1887
                          RadioSettingValueList(
1888
                              BANDWIDTH_LIST, BANDWIDTH_LIST[_vfoa.iswide]))
1889
        vfoa_grp.append(rs)
1890
        rs = RadioSetting("vfoa.mute_mode", vfo_area + "A Mute (SP Mute)",
1891
                          RadioSettingValueList(
1892
                              SPMUTE_LIST, SPMUTE_LIST[_vfoa.mute_mode]))
1893
        vfoa_grp.append(rs)
1894
        if self.MODEL == "KG-UV8H":
1895
            rs = RadioSetting("vfoa.ofst_dir", vfo_area + "A Shift Dir",
1896
                            RadioSettingValueList(
1897
                                OFFSET_LIST, OFFSET_LIST[_vfoa.ofst_dir]))
1898
            vfoa_grp.append(rs)
1899
        else:
1900
            rs = RadioSetting("VFO_repeater_a", vfo_area + "A Repeater",
1901
                            RadioSettingValueBoolean(_settings.VFO_repeater_a))
1902
            vfoa_grp.append(rs)
1903

    
1904
        rs = RadioSetting("vfoa.scrambler", vfo_area + "A Descramble",
1905
                          RadioSettingValueList(
1906
                              SCRAMBLE_LIST, SCRAMBLE_LIST[_vfoa.scrambler]))
1907
        vfoa_grp.append(rs)
1908

    
1909
        rs = RadioSetting("vfoa.cmpndr", vfo_area + "A Compander",
1910
                          RadioSettingValueList(
1911
                             ONOFF_LIST, ONOFF_LIST[_vfoa.cmpndr]))
1912
        vfoa_grp.append(rs)
1913

    
1914
        rs = RadioSetting("vfoa.step", vfo_area + "A Step (kHz)",
1915
                          RadioSettingValueList(
1916
                              STEP_LIST, STEP_LIST[_vfoa.step]))
1917
        vfoa_grp.append(rs)
1918
        rs = RadioSetting("vfoa.squelch", vfo_area + "A Squelch",
1919
                          RadioSettingValueList(
1920
                              LIST_10, LIST_10[_vfoa.squelch]))
1921
        vfoa_grp.append(rs)
1922

    
1923
        # VFO B Settings
1924
        rs = RadioSetting("work_mode_b", vfo_area + "B Workmode",
1925
                          RadioSettingValueList(workmodelist,
1926
                                                workmodelist[_settings.
1927
                                                             work_mode_b]))
1928
        vfob_grp.append(rs)
1929
        rs = RadioSetting("work_ch_b", vfo_area + "B Work Channel",
1930
                          RadioSettingValueInteger(1, 999,
1931
                                                   _settings.work_ch_b))
1932
        vfob_grp.append(rs)
1933
        rs = RadioSetting("vfob.rxfreq", vfo_area + "B Rx Frequency (MHz)",
1934
                          RadioSettingValueFloat(
1935
                              30.000000, 999.999999,
1936
                              (_vfob.rxfreq / 100000.0), 0.000001, 6))
1937
        vfob_grp.append(rs)
1938

    
1939
        if self.MODEL == "KG-UV8H":
1940
            rs = RadioSetting("vfob.offset", vfo_area + "B Offset (MHz)",
1941
                            RadioSettingValueFloat(
1942
                                0.00000, 599.999999,
1943
                                (_vfob.offset / 100000.0), 0.000001, 6))
1944
            vfob_grp.append(rs)
1945

    
1946
        rs = RadioSetting("vfob.rxtone", vfo_area + "B Rx tone",
1947
                          RadioSettingValueMap(
1948
                            TONE_MAP, _vfob.rxtone))
1949
        vfob_grp.append(rs)
1950
        rs = RadioSetting("vfob.txtone", vfo_area + "B Tx tone",
1951
                          RadioSettingValueMap(
1952
                            TONE_MAP, _vfob.txtone))
1953
        vfob_grp.append(rs)
1954

    
1955
        # MRT - AND power with 0x03 to display only the lower 2 bits for
1956
        # power level and to clear the upper bits
1957
        # MRT - any bits set in the upper 2 bits will cause radio to show
1958
        # invalid values for power level and a display glitch
1959
        # MRT - when PTT is pushed
1960
        _vfob.power = _vfob.power & 0x3
1961
        if _vfob.power > 2:
1962
            _vfob.power = 2
1963
        rs = RadioSetting("vfob.power", vfo_area + "B Power",
1964
                          RadioSettingValueList(
1965
                              POWER_LIST, POWER_LIST[_vfob.power]))
1966
        vfob_grp.append(rs)
1967
        rs = RadioSetting("vfob.iswide", vfo_area + "B Wide/Narrow",
1968
                          RadioSettingValueList(
1969
                              BANDWIDTH_LIST, BANDWIDTH_LIST[_vfob.iswide]))
1970
        vfob_grp.append(rs)
1971
        rs = RadioSetting("vfob.mute_mode", vfo_area + "B Mute (SP Mute)",
1972
                          RadioSettingValueList(
1973
                              SPMUTE_LIST, SPMUTE_LIST[_vfob.mute_mode]))
1974
        vfob_grp.append(rs)
1975
        if self.MODEL == "KG-UV8H":
1976
            rs = RadioSetting("vfob.ofst_dir", vfo_area + "B Shift Dir",
1977
                            RadioSettingValueList(
1978
                                OFFSET_LIST, OFFSET_LIST[_vfob.ofst_dir]))
1979
            vfob_grp.append(rs)
1980
        else:    
1981
            rs = RadioSetting("VFO_repeater_b", vfo_area + "B Repeater",
1982
                            RadioSettingValueBoolean(_settings.VFO_repeater_b))
1983
            vfob_grp.append(rs)
1984

    
1985
        rs = RadioSetting("vfob.scrambler", vfo_area + "B Descramble",
1986
                          RadioSettingValueList(
1987
                              SCRAMBLE_LIST, SCRAMBLE_LIST[_vfob.scrambler]))
1988
        vfob_grp.append(rs)
1989

    
1990
        rs = RadioSetting("vfob.cmpndr", vfo_area + "B Compander",
1991
                          RadioSettingValueList(
1992
                              ONOFF_LIST, ONOFF_LIST[_vfob.cmpndr]))
1993
        vfob_grp.append(rs)
1994

    
1995
        rs = RadioSetting("vfob.step", vfo_area + "B Step (kHz)",
1996
                          RadioSettingValueList(
1997
                              STEP_LIST, STEP_LIST[_vfob.step]))
1998
        vfob_grp.append(rs)
1999
        rs = RadioSetting("vfob.squelch", vfo_area + "B Squelch",
2000
                          RadioSettingValueList(
2001
                              LIST_10, LIST_10[_vfob.squelch]))
2002
        vfob_grp.append(rs)
2003

    
2004
        # FM RADIO PRESETS
2005

    
2006
        # memory stores raw integer value like 760
2007
        # radio will divide 760 by 10 and interpret correctly at 76.0Mhz
2008
        for i in range(1, 21):
2009
            chan = str(i)
2010
            rs = RadioSetting("FM_radio" + chan, "FM Preset" + chan,
2011
                              RadioSettingValueFloat(76.0, 108.0,
2012
                                                     eval("_settings. \
2013
                                                          FM_radio" +
2014
                                                          chan)/10.0,
2015
                                                     0.1, 1))
2016
            fmradio_grp.append(rs)
2017

    
2018
        # Freq Limits settings
2019
        rs = RadioSetting("vhf_limits.rx_start", "VHF RX Lower Limit (MHz)",
2020
                          RadioSettingValueFloat(
2021
                              30.000000, 299.999999,
2022
                              (self._memobj.vhf_limits.rx_start / 100000.0),
2023
                              0.000001, 6))
2024

    
2025
        lmt_grp.append(rs)
2026
        rs = RadioSetting("vhf_limits.rx_stop", "VHF RX Upper Limit (MHz)",
2027
                          RadioSettingValueFloat(
2028
                              30.000000, 299.999999,
2029
                              (self._memobj.vhf_limits.rx_stop / 100000.0),
2030
                              0.000001, 6))
2031
        lmt_grp.append(rs)
2032

    
2033
        rs = RadioSetting("vhf_limits.tx_start", "VHF TX Lower Limit (MHz)",
2034
                          RadioSettingValueFloat(
2035
                              30.000000, 299.999999,
2036
                              (self._memobj.vhf_limits.tx_start / 100000.0),
2037
                              0.000001, 6))
2038

    
2039
        lmt_grp.append(rs)
2040
        rs = RadioSetting("vhf_limits.tx_stop", "VHF TX Upper Limit (MHz)",
2041
                          RadioSettingValueFloat(
2042
                              30.000000, 299.999999,
2043
                              (self._memobj.vhf_limits.tx_stop / 100000.0),
2044
                              0.000001, 6))
2045
        lmt_grp.append(rs)
2046

    
2047
        # MRT - TX Limits do not appear to change radio's ability
2048
        #  to transmit on other freqs.
2049
        # MRT - Appears that the radio firmware prevent Tx
2050
        # on anything other than a valid GMRS Freq
2051

    
2052
        rs = RadioSetting("uhf_limits.rx_start", "UHF RX Lower Limit (MHz)",
2053
                          RadioSettingValueFloat(
2054
                              300.000000, 999.999999,
2055
                              (self._memobj.uhf_limits.rx_start / 100000.0),
2056
                              0.000001, 6))
2057
        lmt_grp.append(rs)
2058
        rs = RadioSetting("uhf_limits.rx_stop", "UHF RX Upper Limit (MHz)",
2059
                          RadioSettingValueFloat(
2060
                              300.000000, 999.999999,
2061
                              (self._memobj.uhf_limits.rx_stop / 100000.0),
2062
                              0.000001, 6))
2063
        lmt_grp.append(rs)
2064

    
2065
        rs = RadioSetting("uhf_limits.tx_start", "UHF TX Lower Limit (MHz)",
2066
                          RadioSettingValueFloat(
2067
                              300.000000, 999.999999,
2068
                              (self._memobj.uhf_limits.tx_start / 100000.0),
2069
                              0.000001, 6))
2070
        lmt_grp.append(rs)
2071
        rs = RadioSetting("uhf_limits.tx_stop", "UHF TX Upper Limit (MHz)",
2072
                          RadioSettingValueFloat(
2073
                              300.000000, 999.999999,
2074
                              (self._memobj.uhf_limits.tx_stop / 100000.0),
2075
                              0.000001, 6))
2076
        lmt_grp.append(rs)
2077

    
2078
        # OEM info
2079
        def _decode(lst):
2080
            _str = ''.join([chr(int(c)) for c in lst
2081
                            if chr(int(c)) in chirp_common.CHARSET_ASCII])
2082
            return _str
2083

    
2084
        def do_nothing(setting, obj):
2085
            return
2086

    
2087
        _str = _decode(self._memobj.oem_info.model)
2088
        val = RadioSettingValueString(0, 8, _str)
2089
        val.set_mutable(True)
2090
        rs = RadioSetting("oem_info.model", areamsglabel, val)
2091
        if self.MODEL == "KG-935G":
2092
            oem_grp.append(rs)
2093
        else:
2094
            key_grp.append(rs)
2095

    
2096
        _str = _decode(self._memobj.oem_info.oem1)
2097
        val = RadioSettingValueString(0, 15, _str)
2098
        val.set_mutable(False)
2099
        rs = RadioSetting("oem_info.oem1", "OEM String 1", val)
2100
        rs.set_apply_callback(do_nothing, _settings)
2101
        oem_grp.append(rs)
2102
        _str = _decode(self._memobj.oem_info.oem2)
2103
        val = RadioSettingValueString(0, 15, _str)
2104
        val.set_mutable(False)
2105
        rs = RadioSetting("oem_info.oem2", "Firmware Version ??", val)
2106
        rs.set_apply_callback(do_nothing, _settings)
2107
        oem_grp.append(rs)
2108

    
2109
        _str = _decode(self._memobj.oem_info.date)
2110
        val = RadioSettingValueString(0, 15, _str)
2111
        val.set_mutable(False)
2112
        rs = RadioSetting("oem_info.date", "OEM Date", val)
2113
        rs.set_apply_callback(do_nothing, _settings)
2114
        oem_grp.append(rs)
2115

    
2116
        return group
2117

    
2118
    def get_settings(self):
2119
        try:
2120
            return self._get_settings()
2121
        except:
2122
            import traceback
2123
            LOG.error("Failed to parse settings: %s", traceback.format_exc())
2124
            return None
2125

    
2126
    def set_settings(self, settings):
2127
        for element in settings:
2128
            if not isinstance(element, RadioSetting):
2129
                self.set_settings(element)
2130
                continue
2131
            else:
2132
                try:
2133
                    if "." in element.get_name():
2134
                        bits = element.get_name().split(".")
2135
                        obj = self._memobj
2136
                        for bit in bits[:-1]:
2137
                            if "[" in bit and "]" in bit:
2138
                                bit, index = bit.split("[", 1)
2139
                                index, junk = index.split("]", 1)
2140
                                index = int(index)
2141
                                obj = getattr(obj, bit)[index]
2142
                            else:
2143
                                obj = getattr(obj, bit)
2144
                        setting = bits[-1]
2145
                    else:
2146
                        obj = self._memobj.settings
2147
                        setting = element.get_name()
2148

    
2149
                    if element.has_apply_callback():
2150
                        LOG.debug("Using apply callback")
2151
                        element.run_apply_callback()
2152
                    else:
2153
                        LOG.debug("Setting %s = %s" % (setting, element.value))
2154
                        if self._is_freq(element):
2155
                            # setattr(obj, setting, int(element.value / 10))
2156
                            # MRT rescale freq values to match radio
2157
                            # expected values
2158
                            setattr(obj, setting,
2159
                                    int(element.values()[0]._current *
2160
                                        100000.0))
2161
                        elif self._is_fmradio(element):
2162
                            # MRT rescale FM Radio values to match radio
2163
                            # expected values
2164
                            setattr(obj, setting,
2165
                                    int(element.values()[0]._current * 10.0))
2166
                        else:
2167
                            setattr(obj, setting, element.value)
2168
                except Exception as e:
2169
                    LOG.debug(element.get_name())
2170
                    raise
2171

    
2172
    def _is_freq(self, element):
2173
        return ("rxfreq" in element.get_name() or
2174
                "txoffset" in element.get_name() or
2175
                "rx_start" in element.get_name() or
2176
                "rx_stop" in element.get_name() or
2177
                "tx_start" in element.get_name() or
2178
                "tx_stop" in element.get_name())
2179

    
2180
    def _is_fmradio(self, element):
2181
        return "FM_radio" in element.get_name()
2182

    
2183
    def callid2str(self, cid):
2184
        """Caller ID per MDC-1200 spec? Must be 3-6 digits (100 - 999999).
2185
        One digit (binary) per byte, terminated with '0xc'
2186
        """
2187

    
2188
        bin2ascii = "0123456789"
2189
        cidstr = ""
2190
        for i in range(0, 6):
2191
            b = cid[i].get_value()
2192
            # Handle fluky firmware 0x0a is sometimes used instead of 0x00
2193
            if b == 0x0a:
2194
                b = 0x00
2195
            if b == 0xc or b == 0xf:  # the cid EOL
2196
                break
2197
            if b > 0xa:
2198
                raise InvalidValueError(
2199
                    "Caller ID code has illegal byte 0x%x" % b)
2200
            cidstr += bin2ascii[b]
2201
        return cidstr
2202

    
2203

    
2204
@directory.register
2205
class KG935GPlusRadio(KG935GRadio):
2206

    
2207
    """Wouxun KG-935G Plus"""
2208
    VENDOR = "Wouxun"
2209
    MODEL = "KG-935G Plus"
2210
    NEEDS_COMPAT_SERIAL = False
2211

    
2212
    def process_mmap(self):
2213
        self._memobj = bitwise.parse(_MEM_FORMAT_935GPLUS, self._mmap)
2214

    
2215

    
2216
@directory.register
2217
class KGUV8HRadio(KG935GRadio):
2218

    
2219
    """Wouxun KG-UV8H"""
2220
    VENDOR = "Wouxun"
2221
    MODEL = "KG-UV8H"
2222
    NEEDS_COMPAT_SERIAL = False
2223

    
2224
    def process_mmap(self):
2225
        self._memobj = bitwise.parse(_MEM_FORMAT_UV8H, self._mmap)
(37-37/37)