Project

General

Profile

New Model #9903 » retevis_rt21_ar-63_#1.py

Jim Unroe, 10/03/2022 02:05 AM

 
1
# Copyright 2021-2022 Jim Unroe <rock.unroe@gmail.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 time
17
import os
18
import struct
19
import logging
20

    
21
from chirp import (
22
    bitwise,
23
    chirp_common,
24
    directory,
25
    errors,
26
    memmap,
27
    util,
28
)
29
from chirp.settings import (
30
    RadioSetting,
31
    RadioSettingGroup,
32
    RadioSettings,
33
    RadioSettingValueBoolean,
34
    RadioSettingValueInteger,
35
    RadioSettingValueList,
36
    RadioSettingValueString,
37
)
38

    
39
LOG = logging.getLogger(__name__)
40

    
41
MEM_FORMAT = """
42
#seekto 0x0010;
43
struct {
44
  lbcd rxfreq[4];       // RX Frequency           0-3
45
  lbcd txfreq[4];       // TX Frequency           4-7
46
  ul16 rx_tone;         // PL/DPL Decode          8-9
47
  ul16 tx_tone;         // PL/DPL Encode          A-B
48
  u8 unknown1:3,        //                        C
49
     bcl:2,             // Busy Lock
50
     unknown2:3;
51
  u8 unknown3:2,        //                        D
52
     highpower:1,       // Power Level
53
     wide:1,            // Bandwidth
54
     unknown4:4;
55
  u8 unknown7:1,        //                        E
56
     scramble_type:3,   // Scramble Type
57
     unknown5:4;
58
  u8 unknown6:5,
59
     scramble_type2:3;  // Scramble Type 2        F
60
} memory[16];
61

    
62
#seekto 0x011D;
63
struct {
64
  u8 unused:4,
65
     pf1:4;             // Programmable Function Key 1
66
} keys;
67

    
68
#seekto 0x012C;
69
struct {
70
  u8 use_scramble;      // Scramble Enable
71
  u8 unknown1[2];
72
  u8 voice;             // Voice Annunciation
73
  u8 tot;               // Time-out Timer
74
  u8 totalert;          // Time-out Timer Pre-alert
75
  u8 unknown2[2];
76
  u8 squelch;           // Squelch Level
77
  u8 save;              // Battery Saver
78
  u8 unknown3[3];
79
  u8 use_vox;           // VOX Enable
80
  u8 vox;               // VOX Gain
81
} settings;
82

    
83
#seekto 0x017E;
84
u8 skipflags[2];       // SCAN_ADD
85
"""
86

    
87
MEM_FORMAT_RB17A = """
88
struct memory {
89
  lbcd rxfreq[4];      // 0-3
90
  lbcd txfreq[4];      // 4-7
91
  ul16 rx_tone;        // 8-9
92
  ul16 tx_tone;        // A-B
93
  u8 unknown1:1,       // C
94
     compander:1,      // Compand
95
     bcl:2,            // Busy Channel Lock-out
96
     cdcss:1,          // Cdcss Mode
97
     scramble_type:3;  // Scramble Type
98
  u8 unknown2:4,       // D
99
     middlepower:1,    // Power Level-Middle
100
     unknown3:1,       //
101
     highpower:1,      // Power Level-High/Low
102
     wide:1;           // Bandwidth
103
  u8 unknown4;         // E
104
  u8 unknown5;         // F
105
};
106

    
107
#seekto 0x0010;
108
  struct memory lomems[16];
109

    
110
#seekto 0x0200;
111
  struct memory himems[14];
112

    
113
#seekto 0x011D;
114
struct {
115
  u8 pf1;              // 011D PF1 Key
116
  u8 topkey;           // 011E Top Key
117
} keys;
118

    
119
#seekto 0x012C;
120
struct {
121
  u8 use_scramble;     // 012C Scramble Enable
122
  u8 channel;          // 012D Channel Number
123
  u8 alarm;            // 012E Alarm Type
124
  u8 voice;            // 012F Voice Annunciation
125
  u8 tot;              // 0130 Time-out Timer
126
  u8 totalert;         // 0131 Time-out Timer Pre-alert
127
  u8 unknown2[2];
128
  u8 squelch;          // 0134 Squelch Level
129
  u8 save;             // 0135 Battery Saver
130
  u8 unknown3[3];
131
  u8 use_vox;          // 0139 VOX Enable
132
  u8 vox;              // 013A VOX Gain
133
} settings;
134

    
135
#seekto 0x017E;
136
u8 skipflags[4];       // Scan Add
137
"""
138

    
139
MEM_FORMAT_RB26 = """
140
#seekto 0x0000;
141
struct {
142
  lbcd rxfreq[4];      // RX Frequency           0-3
143
  lbcd txfreq[4];      // TX Frequency           4-7
144
  ul16 rx_tone;        // PL/DPL Decode          8-9
145
  ul16 tx_tone;        // PL/DPL Encode          A-B
146
  u8 compander:1,      // Compander              C
147
     unknown1:1,       //
148
     highpower:1,      // Power Level
149
     wide:1,           // Bandwidth
150
     bcl:1,            // Busy Lock  OFF=0 ON=1
151
     unknown2:3;       //
152
  u8 reserved[3];      // Reserved               D-F
153
} memory[30];
154

    
155
#seekto 0x002D;
156
struct {
157
  u8 unknown_1:1,      //                        002D
158
     chnumberd:1,      // Channel Number Disable
159
     gain:1,           // MIC Gain
160
     savem:1,          // Battery Save Mode
161
     save:1,           // Battery Save
162
     beep:1,           // Beep
163
     voice:1,          // Voice Prompts
164
     unknown_2:1;      //
165
  u8 squelch;          // Squelch                002E
166
  u8 tot;              // Time-out Timer         002F
167
  u8 channel_4[13];    //                        0030-003C
168
  u8 unknown_3[3];     //                        003D-003F
169
  u8 channel_5[13];    //                        0040-004C
170
  u8 unknown_4;        //                        004D
171
  u8 unknown_5[2];     //                        004E-004F
172
  u8 channel_6[13];    //                        0050-005C
173
  u8 unknown_6;        //                        005D
174
  u8 unknown_7[2];     //                        005E-005F
175
  u8 channel_7[13];    //                        0060-006C
176
  u8 warn;             // Warn Mode              006D
177
  u8 pf1;              // Key Set PF1            006E
178
  u8 pf2;              // Key Set PF2            006F
179
  u8 channel_8[13];    //                        0070-007C
180
  u8 unknown_8;        //                        007D
181
  u8 tail;             // QT/DQT Tail(inverted)  007E
182
} settings;
183

    
184
#seekto 0x01F0;
185
u8 skipflags[4];       // Scan Add
186

    
187
#seekto 0x029F;
188
struct {
189
  u8 chnumber;         // Channel Number         029F
190
} settings2;
191

    
192
#seekto 0x031D;
193
struct {
194
  u8 unused:7,         //                        031D
195
     vox:1;            // Vox
196
  u8 voxl;             // Vox Level              031E
197
  u8 voxd;             // Vox Delay              031F
198
} settings3;
199
"""
200

    
201
MEM_FORMAT_RT76 = """
202
#seekto 0x0000;
203
struct {
204
  lbcd rxfreq[4];      // RX Frequency           0-3
205
  lbcd txfreq[4];      // TX Frequency           4-7
206
  ul16 rx_tone;        // PL/DPL Decode          8-9
207
  ul16 tx_tone;        // PL/DPL Encode          A-B
208
  u8 compander:1,      // Compander              C
209
     hop:1,            // Frequency Hop
210
     highpower:1,      // Power Level
211
     wide:1,           // Bandwidth
212
     scramble:4;       // Scramble
213
  u8 reserved[3];      // Reserved               D-F
214
} memory[30];
215

    
216
#seekto 0x002D;
217
struct {
218
  u8 unknown_1:1,      //                        002D
219
     chnumberd:1,      // Channel Number Disable
220
     gain:1,           // MIC Gain                                 ---
221
     savem:1,          // Battery Save Mode                        ---
222
     save:1,           // Battery Save                             ---
223
     beep:1,           // Beep                                     ---
224
     voice:2;          // Voice Prompts                            ---
225
  u8 squelch;          // Squelch                002E              ---
226
  u8 tot;              // Time-out Timer         002F              ---
227
  u8 channel_4[13];    //                        0030-003C
228
  u8 unused:7,         //                        003D
229
     vox:1;            // Vox                                      ---
230
  u8 voxl;             // Vox Level              003E              ---
231
  u8 voxd;             // Vox Delay              003F              ---
232
  u8 channel_5[13];    //                        0040-004C
233
  u8 unknown_4;        //                        004D
234
  u8 unknown_5[2];     //                        004E-004F
235
  u8 channel_6[13];    //                        0050-005C
236
  u8 chnumber;         // Channel Number         005D              ---
237
  u8 unknown_7[2];     //                        005E-005F
238
  u8 channel_7[13];    //                        0060-006C
239
  u8 warn;             //                        006D              ---
240
  u8 scan;             //                        006E
241
  u8 unknown_8;        //                        006F
242
  u8 channel_8[13];    //                        0070-007C
243
  u8 unknown_9[3];     //                        007D-007F
244
  u8 channel_9[13];    //                        0080-008C
245
  u8 unknown_a;        //                        008D
246
  u8 tailmode;         // DCS Tail Mode          008E
247
  u8 hop;              // Hop Mode               008F
248
} settings;
249

    
250
#seekto 0x004E;
251
u8 skipflags[2];       // SCAN_ADD
252
"""
253

    
254
MEM_FORMAT_RT29 = """
255
#seekto 0x0010;
256
struct {
257
  lbcd rxfreq[4];       // RX Frequency           0-3
258
  lbcd txfreq[4];       // TX Frequency           4-7
259
  ul16 rx_tone;         // PL/DPL Decode          8-9
260
  ul16 tx_tone;         // PL/DPL Encode          A-B
261
  u8 unknown1:2,        //                        C
262
     compander:1,       // Compander
263
     bcl:2,             // Busy Lock
264
     unknown2:3;
265
  u8 unknown3:1,        //                        D
266
     txpower:2,         // Power Level
267
     wide:1,            // Bandwidth
268
     unknown4:3,
269
     cdcss:1;           // Cdcss Mode
270
  u8 unknown5;          //                        E
271
  u8 unknown6:5,
272
     scramble_type:3;   // Scramble Type          F
273
} memory[16];
274

    
275
#seekto 0x011D;
276
struct {
277
  u8 unused1:4,
278
     pf1:4;             // Programmable Function Key 1
279
  u8 unused2:4,
280
     pf2:4;             // Programmable Function Key 2
281
} keys;
282

    
283
#seekto 0x012C;
284
struct {
285
  u8 use_scramble;      // Scramble Enable
286
  u8 unknown1[2];
287
  u8 voice;             // Voice Annunciation
288
  u8 tot;               // Time-out Timer
289
  u8 totalert;          // Time-out Timer Pre-alert
290
  u8 unknown2[2];
291
  u8 squelch;           // Squelch Level
292
  u8 save;              // Battery Saver
293
  u8 unknown3[3];
294
  u8 use_vox;           // VOX Enable
295
  u8 vox;               // VOX Gain
296
  u8 voxd;              // Vox Delay
297
} settings;
298

    
299
#seekto 0x017E;
300
u8 skipflags[2];       // SCAN_ADD
301

    
302
#seekto 0x01B8;
303
u8 fingerprint[5];     // Fingerprint
304
"""
305

    
306
MEM_FORMAT_RT19 = """
307
#seekto 0x0000;
308
struct {
309
  lbcd rxfreq[4];      // RX Frequency           0-3
310
  lbcd txfreq[4];      // TX Frequency           4-7
311
  ul16 rx_tone;        // PL/DPL Decode          8-9
312
  ul16 tx_tone;        // PL/DPL Encode          A-B
313
  u8 function:2,       // Function               C
314
     highpower:1,      // Power Level
315
     wide:1,           // Bandwidth
316
     unknown_1:1,      //
317
     scramble_type:3;  // Scramble #
318
  u8 reserved[3];      // Reserved               D-F
319
} memory[%d];
320

    
321
#seekto 0x002D;
322
struct {
323
  u8 bootsel:1,        // Boot Select            002D
324
     unknown_1:2,      //
325
     savem:1,          // Battery Save Mode
326
     save:1,           // Battery Save
327
     beep:1,           // Beep
328
     voice:2;          // Voice Prompts
329
  u8 squelch;          // Squelch                002E
330
  u8 tot;              // Time-out Timer         002F
331
  u8 channel_4[13];    //                        0030-003C
332
  u8 unused:7,         //                        003D
333
     vox:1;            // Vox
334
  u8 voxl;             // Vox Level              003E
335
  u8 voxd;             // Vox Delay              003F
336
  u8 channel_5[13];    //                        0040-004C
337
  u8 unknown_4;        //                        004D
338
  u8 unknown_5[2];     //                        004E-004F
339
  u8 channel_6[13];    //                        0050-005C
340
  u8 unknown_6;        //                        005D
341
  u8 unknown_7[2];     //                        005E-005F
342
  u8 channel_7[13];    //                        0060-006C
343
  u8 voicel:4,         // Voice Level            006D
344
     unknown_9:3       //
345
     warn:1;           // Warn Mode
346
} settings;
347

    
348
#seekto 0x%X;
349
struct {
350
  u8 freqhop;          // Frequency Hop
351
} freqhops[%d];
352
"""
353

    
354
CMD_ACK = "\x06"
355

    
356
ALARM_LIST = ["Local Alarm", "Remote Alarm"]
357
BCL_LIST = ["Off", "Carrier", "QT/DQT"]
358
BOOTSEL_LIST = ["Channel Mode", "Voice Mode"]
359
CDCSS_LIST = ["Normal Code", "Special Code 2", "Special Code 1"]
360
CDCSS2_LIST = ["Normal Code", "Special Code"]  # RT29 UHF and RT29 VHF
361
FREQHOP_LIST = ["Off", "Hopping 1", "Hopping 2", "Hopping 3"]
362
FUNCTION_LIST = ["Off", "Scramble", "Compand"]
363
GAIN_LIST = ["Standard", "Enhanced"]
364
HOP_LIST = ["Mode A", "Mode B", "Mode C", "Mode D", "Mode E"]
365
PFKEY_LIST = ["None", "Monitor", "Lamp", "Warn", "VOX", "VOX Delay",
366
              "Key Lock", "Scan"]
367
SAVE_LIST = ["Standard", "Super"]
368
SCRAMBLE_LIST = ["OFF"] + ["%s" % x for x in range(1, 9)]
369
TAIL_LIST = ["134.4 Hz", "55 Hz"]
370
TIMEOUTTIMER_LIST = ["Off"] + ["%s seconds" % x for x in range(15, 615, 15)]
371
TOTALERT_LIST = ["Off"] + ["%s seconds" % x for x in range(1, 11)]
372
VOICE_LIST = ["Off", "Chinese", "English"]
373
VOICE_LIST2 = ["Off", "English"]
374
VOICE_LIST3 = VOICE_LIST2 + ["Chinese"]
375
VOX_LIST = ["OFF"] + ["%s" % x for x in range(1, 17)]
376
VOXD_LIST = ["0.5", "1.0", "1.5", "2.0", "2.5", "3.0"]
377
VOXL_LIST = ["OFF"] + ["%s" % x for x in range(1, 10)]
378
WARN_LIST = ["OFF", "Native Warn", "Remote Warn"]
379
PF1_CHOICES = ["None", "Monitor", "Scan", "Scramble", "Alarm"]
380
PF1_VALUES = [0x0F, 0x04, 0x06, 0x08, 0x0C]
381
PF1_17A_CHOICES = ["None", "Monitor", "Scan", "Scramble"]
382
PF1_17A_VALUES = [0x0F, 0x04, 0x06, 0x08]
383
PFKEY23_CHOICES = ["None", "Monitor", "Warn", "VOX", "VOX Delay", "Scan"]
384
PFKEY23_VALUES = [0x00, 0x01, 0x03, 0x04, 0x05, 0x07]
385
PFKEY_CHOICES = ["None", "Monitor", "Scan", "Scramble", "VOX", "Alarm"]
386
PFKEY_VALUES = [0x0F, 0x04, 0x06, 0x08, 0x09, 0x0A]
387
TOPKEY_CHOICES = ["None", "Alarming"]
388
TOPKEY_VALUES = [0xFF, 0x0C]
389

    
390
SETTING_LISTS = {
391
    "alarm": ALARM_LIST,
392
    "bcl": BCL_LIST,
393
    "bootsel": BOOTSEL_LIST,
394
    "cdcss": CDCSS_LIST,
395
    "cdcss": CDCSS2_LIST,
396
    "freqhop": FREQHOP_LIST,
397
    "function": FUNCTION_LIST,
398
    "gain": GAIN_LIST,
399
    "hop": HOP_LIST,
400
    "pfkey": PFKEY_LIST,
401
    "save": SAVE_LIST,
402
    "scramble": SCRAMBLE_LIST,
403
    "tail": TAIL_LIST,
404
    "tot": TIMEOUTTIMER_LIST,
405
    "totalert": TOTALERT_LIST,
406
    "voice": VOICE_LIST,
407
    "voice": VOICE_LIST2,
408
    "voice": VOICE_LIST3,
409
    "vox": VOX_LIST,
410
    "voxd": VOXD_LIST,
411
    "voxl": VOXL_LIST,
412
    "warn": WARN_LIST,
413
    }
414

    
415
GMRS_FREQS1 = [462.5625, 462.5875, 462.6125, 462.6375, 462.6625,
416
               462.6875, 462.7125]
417
GMRS_FREQS2 = [467.5625, 467.5875, 467.6125, 467.6375, 467.6625,
418
               467.6875, 467.7125]
419
GMRS_FREQS3 = [462.5500, 462.5750, 462.6000, 462.6250, 462.6500,
420
               462.6750, 462.7000, 462.7250]
421
GMRS_FREQS = GMRS_FREQS1 + GMRS_FREQS2 + GMRS_FREQS3 * 2
422

    
423
FRS_FREQS = GMRS_FREQS1 + GMRS_FREQS2 + GMRS_FREQS3
424

    
425
PMR_FREQS1 = [446.00625, 446.01875, 446.03125, 446.04375, 446.05625,
426
              446.06875, 446.08125, 446.09375]
427
PMR_FREQS2 = [446.10625, 446.11875, 446.13125, 446.14375, 446.15625,
428
              446.16875, 446.18125, 446.19375]
429
PMR_FREQS = PMR_FREQS1 + PMR_FREQS2
430

    
431

    
432
def _enter_programming_mode(radio):
433
    serial = radio.pipe
434

    
435
    exito = False
436
    for i in range(0, 5):
437
        serial.write(radio._magic)
438
        ack = serial.read(1)
439
        if ack == "\x00":
440
            ack = serial.read(1)
441

    
442
        try:
443
            if ack == CMD_ACK:
444
                exito = True
445
                break
446
        except:
447
            LOG.debug("Attempt #%s, failed, trying again" % i)
448
            pass
449

    
450
    # check if we had EXITO
451
    if exito is False:
452
        msg = "The radio did not accept program mode after five tries.\n"
453
        msg += "Check you interface cable and power cycle your radio."
454
        raise errors.RadioError(msg)
455

    
456
    try:
457
        serial.write("\x02")
458
        ident = serial.read(8)
459
    except:
460
        raise errors.RadioError("Error communicating with radio")
461

    
462
    if not ident == radio._fingerprint:
463
        LOG.debug(util.hexprint(ident))
464
        raise errors.RadioError("Radio returned unknown identification string")
465

    
466
    try:
467
        serial.write(CMD_ACK)
468
        ack = serial.read(1)
469
    except:
470
        raise errors.RadioError("Error communicating with radio")
471

    
472
    if ack != CMD_ACK:
473
        raise errors.RadioError("Radio refused to enter programming mode")
474

    
475

    
476
def _exit_programming_mode(radio):
477
    serial = radio.pipe
478
    try:
479
        serial.write("E")
480
    except:
481
        raise errors.RadioError("Radio refused to exit programming mode")
482

    
483

    
484
def _read_block(radio, block_addr, block_size):
485
    serial = radio.pipe
486

    
487
    cmd = struct.pack(">cHb", 'R', block_addr, block_size)
488
    expectedresponse = "W" + cmd[1:]
489
    LOG.debug("Reading block %04x..." % (block_addr))
490

    
491
    try:
492
        serial.write(cmd)
493
        response = serial.read(4 + block_size)
494
        if response[:4] != expectedresponse:
495
            raise Exception("Error reading block %04x." % (block_addr))
496

    
497
        block_data = response[4:]
498

    
499
        if block_addr != 0 or radio._ack_1st_block:
500
            serial.write(CMD_ACK)
501
            ack = serial.read(1)
502
    except:
503
        raise errors.RadioError("Failed to read block at %04x" % block_addr)
504

    
505
    if block_addr != 0 or radio._ack_1st_block:
506
        if ack != CMD_ACK:
507
            raise Exception("No ACK reading block %04x." % (block_addr))
508

    
509
    return block_data
510

    
511

    
512
def _write_block(radio, block_addr, block_size):
513
    serial = radio.pipe
514

    
515
    cmd = struct.pack(">cHb", 'W', block_addr, block_size)
516
    data = radio.get_mmap()[block_addr:block_addr + block_size]
517

    
518
    LOG.debug("Writing Data:")
519
    LOG.debug(util.hexprint(cmd + data))
520

    
521
    try:
522
        serial.write(cmd + data)
523
        if serial.read(1) != CMD_ACK:
524
            raise Exception("No ACK")
525
    except:
526
        raise errors.RadioError("Failed to send block "
527
                                "to radio at %04x" % block_addr)
528

    
529

    
530
def do_download(radio):
531
    LOG.debug("download")
532
    _enter_programming_mode(radio)
533

    
534
    data = ""
535

    
536
    status = chirp_common.Status()
537
    status.msg = "Cloning from radio"
538

    
539
    status.cur = 0
540
    status.max = radio._memsize
541

    
542
    for addr in range(0, radio._memsize, radio.BLOCK_SIZE):
543
        status.cur = addr + radio.BLOCK_SIZE
544
        radio.status_fn(status)
545

    
546
        block = _read_block(radio, addr, radio.BLOCK_SIZE)
547
        data += block
548

    
549
        LOG.debug("Address: %04x" % addr)
550
        LOG.debug(util.hexprint(block))
551

    
552
    _exit_programming_mode(radio)
553

    
554
    return memmap.MemoryMap(data)
555

    
556

    
557
def do_upload(radio):
558
    status = chirp_common.Status()
559
    status.msg = "Uploading to radio"
560

    
561
    _enter_programming_mode(radio)
562

    
563
    status.cur = 0
564
    status.max = radio._memsize
565

    
566
    for start_addr, end_addr in radio._ranges:
567
        for addr in range(start_addr, end_addr, radio.BLOCK_SIZE_UP):
568
            status.cur = addr + radio.BLOCK_SIZE_UP
569
            radio.status_fn(status)
570
            _write_block(radio, addr, radio.BLOCK_SIZE_UP)
571

    
572
    _exit_programming_mode(radio)
573

    
574

    
575
def model_match(cls, data):
576
    """Match the opened/downloaded image to the correct version"""
577
    rid = data[0x01B8:0x01BE]
578

    
579
    return rid.startswith("P3207")
580

    
581

    
582
@directory.register
583
class RT21Radio(chirp_common.CloneModeRadio):
584
    """RETEVIS RT21"""
585
    VENDOR = "Retevis"
586
    MODEL = "RT21"
587
    BAUD_RATE = 9600
588
    BLOCK_SIZE = 0x10
589
    BLOCK_SIZE_UP = 0x10
590

    
591
    DTCS_CODES = sorted(chirp_common.DTCS_CODES + [17, 50, 645])
592
    POWER_LEVELS = [chirp_common.PowerLevel("High", watts=2.50),
593
                    chirp_common.PowerLevel("Low", watts=1.00)]
594

    
595
    VALID_BANDS = [(400000000, 480000000)]
596

    
597
    _magic = "PRMZUNE"
598
    _fingerprint = "P3207s\xF8\xFF"
599
    _upper = 16
600
    _ack_1st_block = True
601
    _skipflags = True
602
    _reserved = False
603
    _gmrs = _frs = _pmr = False
604

    
605
    _ranges = [
606
               (0x0000, 0x0400),
607
              ]
608
    _memsize = 0x0400
609

    
610
    def get_features(self):
611
        rf = chirp_common.RadioFeatures()
612
        rf.has_settings = True
613
        rf.has_bank = False
614
        rf.has_ctone = True
615
        rf.has_cross = True
616
        rf.has_rx_dtcs = True
617
        rf.has_tuning_step = False
618
        rf.can_odd_split = True
619
        rf.has_name = False
620
        if self.MODEL == "RT76" or \
621
                self.MODEL == "RT19" or self.MODEL == "RT619":
622
            rf.valid_skips = []
623
        else:
624
            rf.valid_skips = ["", "S"]
625
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
626
        rf.valid_cross_modes = ["Tone->Tone", "Tone->DTCS", "DTCS->Tone",
627
                                "->Tone", "->DTCS", "DTCS->", "DTCS->DTCS"]
628
        rf.valid_power_levels = self.POWER_LEVELS
629
        rf.valid_duplexes = ["", "-", "+", "split", "off"]
630
        rf.valid_modes = ["FM", "NFM"]  # 25 KHz, 12.5 kHz.
631
        rf.valid_dtcs_codes = self.DTCS_CODES
632
        rf.memory_bounds = (1, self._upper)
633
        rf.valid_tuning_steps = [2.5, 5., 6.25, 10., 12.5, 25.]
634
        rf.valid_bands = self.VALID_BANDS
635

    
636
        return rf
637

    
638
    def process_mmap(self):
639
        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
640

    
641
    def sync_in(self):
642
        """Download from radio"""
643
        try:
644
            data = do_download(self)
645
        except errors.RadioError:
646
            # Pass through any real errors we raise
647
            raise
648
        except:
649
            # If anything unexpected happens, make sure we raise
650
            # a RadioError and log the problem
651
            LOG.exception('Unexpected error during download')
652
            raise errors.RadioError('Unexpected error communicating '
653
                                    'with the radio')
654
        self._mmap = data
655
        self.process_mmap()
656

    
657
    def sync_out(self):
658
        """Upload to radio"""
659
        try:
660
            do_upload(self)
661
        except:
662
            # If anything unexpected happens, make sure we raise
663
            # a RadioError and log the problem
664
            LOG.exception('Unexpected error during upload')
665
            raise errors.RadioError('Unexpected error communicating '
666
                                    'with the radio')
667

    
668
    def get_raw_memory(self, number):
669
        return repr(self._memobj.memory[number - 1])
670

    
671
    def _get_tone(self, _mem, mem):
672
        def _get_dcs(val):
673
            code = int("%03o" % (val & 0x07FF))
674
            pol = (val & 0x8000) and "R" or "N"
675
            return code, pol
676

    
677
        tpol = False
678
        if _mem.tx_tone != 0xFFFF and _mem.tx_tone > 0x2000:
679
            tcode, tpol = _get_dcs(_mem.tx_tone)
680
            mem.dtcs = tcode
681
            txmode = "DTCS"
682
        elif _mem.tx_tone != 0xFFFF:
683
            mem.rtone = _mem.tx_tone / 10.0
684
            txmode = "Tone"
685
        else:
686
            txmode = ""
687

    
688
        rpol = False
689
        if _mem.rx_tone != 0xFFFF and _mem.rx_tone > 0x2000:
690
            rcode, rpol = _get_dcs(_mem.rx_tone)
691
            mem.rx_dtcs = rcode
692
            rxmode = "DTCS"
693
        elif _mem.rx_tone != 0xFFFF:
694
            mem.ctone = _mem.rx_tone / 10.0
695
            rxmode = "Tone"
696
        else:
697
            rxmode = ""
698

    
699
        if txmode == "Tone" and not rxmode:
700
            mem.tmode = "Tone"
701
        elif txmode == rxmode and txmode == "Tone" and mem.rtone == mem.ctone:
702
            mem.tmode = "TSQL"
703
        elif txmode == rxmode and txmode == "DTCS" and mem.dtcs == mem.rx_dtcs:
704
            mem.tmode = "DTCS"
705
        elif rxmode or txmode:
706
            mem.tmode = "Cross"
707
            mem.cross_mode = "%s->%s" % (txmode, rxmode)
708

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

    
712
        LOG.debug("Got TX %s (%i) RX %s (%i)" %
713
                  (txmode, _mem.tx_tone, rxmode, _mem.rx_tone))
714

    
715
    def get_memory(self, number):
716
        if self._skipflags:
717
            bitpos = (1 << ((number - 1) % 8))
718
            bytepos = ((number - 1) / 8)
719
            LOG.debug("bitpos %s" % bitpos)
720
            LOG.debug("bytepos %s" % bytepos)
721
            _skp = self._memobj.skipflags[bytepos]
722

    
723
        mem = chirp_common.Memory()
724

    
725
        mem.number = number
726

    
727
        if self.MODEL == "RB17A":
728
            if mem.number < 17:
729
                _mem = self._memobj.lomems[number - 1]
730
            else:
731
                _mem = self._memobj.himems[number - 17]
732
        else:
733
            _mem = self._memobj.memory[number - 1]
734

    
735
        if self._reserved:
736
            _rsvd = _mem.reserved.get_raw()
737

    
738
        mem.freq = int(_mem.rxfreq) * 10
739

    
740
        # We'll consider any blank (i.e. 0MHz frequency) to be empty
741
        if mem.freq == 0:
742
            mem.empty = True
743
            return mem
744

    
745
        if _mem.rxfreq.get_raw() == "\xFF\xFF\xFF\xFF":
746
            mem.freq = 0
747
            mem.empty = True
748
            return mem
749

    
750
        if int(_mem.rxfreq) == int(_mem.txfreq):
751
            mem.duplex = ""
752
            mem.offset = 0
753
        else:
754
            mem.duplex = int(_mem.rxfreq) > int(_mem.txfreq) and "-" or "+"
755
            mem.offset = abs(int(_mem.rxfreq) - int(_mem.txfreq)) * 10
756

    
757
        mem.mode = _mem.wide and "FM" or "NFM"
758

    
759
        self._get_tone(_mem, mem)
760

    
761
        if self.MODEL == "RT29_UHF" or self.MODEL == "RT29_VHF":
762
            # set the power level
763
            if _mem.txpower == self.TXPOWER_LOW:
764
                mem.power = self.POWER_LEVELS[2]
765
            elif _mem.txpower == self.TXPOWER_MED:
766
                mem.power = self.POWER_LEVELS[1]
767
            elif _mem.txpower == self.TXPOWER_HIGH:
768
                mem.power = self.POWER_LEVELS[0]
769
            else:
770
                LOG.error('%s: get_mem: unhandled power level: 0x%02x' %
771
                          (mem.name, _mem.txpower))
772
        else:
773
            mem.power = self.POWER_LEVELS[1 - _mem.highpower]
774

    
775
        if self._skipflags:
776
            mem.skip = "" if (_skp & bitpos) else "S"
777
            LOG.debug("mem.skip %s" % mem.skip)
778

    
779
        mem.extra = RadioSettingGroup("Extra", "extra")
780

    
781
        if self.MODEL == "RT21" or self.MODEL == "RB17A" or \
782
                self.MODEL == "RT29_UHF" or self.MODEL == "RT29_VHF":
783
            rs = RadioSettingValueList(BCL_LIST, BCL_LIST[_mem.bcl])
784
            rset = RadioSetting("bcl", "Busy Channel Lockout", rs)
785
            mem.extra.append(rset)
786

    
787
            rs = RadioSettingValueInteger(1, 8, _mem.scramble_type + 1)
788
            rset = RadioSetting("scramble_type", "Scramble Type", rs)
789
            mem.extra.append(rset)
790

    
791
            if self.MODEL == "RB17A":
792
                rs = RadioSettingValueList(CDCSS_LIST, CDCSS_LIST[_mem.cdcss])
793
                rset = RadioSetting("cdcss", "Cdcss Mode", rs)
794
                mem.extra.append(rset)
795

    
796
            if self.MODEL == "RT29_UHF" or self.MODEL == "RT29_VHF":
797
                rs = RadioSettingValueList(CDCSS2_LIST,
798
                                           CDCSS2_LIST[_mem.cdcss])
799
                rset = RadioSetting("cdcss", "Cdcss Mode", rs)
800
                mem.extra.append(rset)
801

    
802
            if self.MODEL == "RB17A" or self.MODEL == "RT29_UHF" or \
803
                    self.MODEL == "RT29_VHF":
804
                rs = RadioSettingValueBoolean(_mem.compander)
805
                rset = RadioSetting("compander", "Compander", rs)
806
                mem.extra.append(rset)
807

    
808
        if self.MODEL == "RB26" or self.MODEL == "RT76" \
809
                or self.MODEL == "RB23" or self.MODEL == "AR-63":
810
            if self.MODEL == "RB26" or self.MODEL == "RB23":
811
                rs = RadioSettingValueBoolean(_mem.bcl)
812
                rset = RadioSetting("bcl", "Busy Channel Lockout", rs)
813
                mem.extra.append(rset)
814

    
815
            rs = RadioSettingValueBoolean(_mem.compander)
816
            rset = RadioSetting("compander", "Compander", rs)
817
            mem.extra.append(rset)
818

    
819
            if self.MODEL == "AR-63":
820
                rs = RadioSettingValueList(SCRAMBLE_LIST,
821
                                           SCRAMBLE_LIST[_mem.scramble])
822
                rset = RadioSetting("scramble", "Scramble", rs)
823
                mem.extra.append(rset)
824

    
825
                rs = RadioSettingValueBoolean(not _mem.hop)
826
                rset = RadioSetting("hop", "Frequency Hop", rs)
827
                mem.extra.append(rset)
828

    
829
        if self.MODEL == "RT19" or self.MODEL == "RT619":
830
            _freqhops = self._memobj.freqhops[number - 1]
831

    
832
            rs = RadioSettingValueList(FUNCTION_LIST,
833
                                       FUNCTION_LIST[_mem.function])
834
            rset = RadioSetting("function", "Function", rs)
835
            mem.extra.append(rset)
836

    
837
            rs = RadioSettingValueInteger(1, 8, _mem.scramble_type + 1)
838
            rset = RadioSetting("scramble_type", "Scramble Type", rs)
839
            mem.extra.append(rset)
840

    
841
            rs = RadioSettingValueList(FREQHOP_LIST,
842
                                       FREQHOP_LIST[_freqhops.freqhop])
843
            rset = RadioSetting("freqhop", "Frequency Hop", rs)
844
            mem.extra.append(rset)
845

    
846
        return mem
847

    
848
    def _set_tone(self, mem, _mem):
849
        def _set_dcs(code, pol):
850
            val = int("%i" % code, 8) + 0x2000
851
            if pol == "R":
852
                val += 0x8000
853
            return val
854

    
855
        rx_mode = tx_mode = None
856
        rx_tone = tx_tone = 0xFFFF
857

    
858
        if mem.tmode == "Tone":
859
            tx_mode = "Tone"
860
            rx_mode = None
861
            tx_tone = int(mem.rtone * 10)
862
        elif mem.tmode == "TSQL":
863
            rx_mode = tx_mode = "Tone"
864
            rx_tone = tx_tone = int(mem.ctone * 10)
865
        elif mem.tmode == "DTCS":
866
            tx_mode = rx_mode = "DTCS"
867
            tx_tone = _set_dcs(mem.dtcs, mem.dtcs_polarity[0])
868
            rx_tone = _set_dcs(mem.dtcs, mem.dtcs_polarity[1])
869
        elif mem.tmode == "Cross":
870
            tx_mode, rx_mode = mem.cross_mode.split("->")
871
            if tx_mode == "DTCS":
872
                tx_tone = _set_dcs(mem.dtcs, mem.dtcs_polarity[0])
873
            elif tx_mode == "Tone":
874
                tx_tone = int(mem.rtone * 10)
875
            if rx_mode == "DTCS":
876
                rx_tone = _set_dcs(mem.rx_dtcs, mem.dtcs_polarity[1])
877
            elif rx_mode == "Tone":
878
                rx_tone = int(mem.ctone * 10)
879

    
880
        _mem.rx_tone = rx_tone
881
        _mem.tx_tone = tx_tone
882

    
883
        LOG.debug("Set TX %s (%i) RX %s (%i)" %
884
                  (tx_mode, _mem.tx_tone, rx_mode, _mem.rx_tone))
885

    
886
    def set_memory(self, mem):
887
        if self._skipflags:
888
            bitpos = (1 << ((mem.number - 1) % 8))
889
            bytepos = ((mem.number - 1) / 8)
890
            LOG.debug("bitpos %s" % bitpos)
891
            LOG.debug("bytepos %s" % bytepos)
892
            _skp = self._memobj.skipflags[bytepos]
893

    
894
        if self.MODEL == "RB17A":
895
            if mem.number < 17:
896
                _mem = self._memobj.lomems[mem.number - 1]
897
            else:
898
                _mem = self._memobj.himems[mem.number - 17]
899
        elif self.MODEL == "RT19" or self.MODEL == "RT619":
900
            _mem = self._memobj.memory[mem.number - 1]
901
            _freqhops = self._memobj.freqhops[mem.number - 1]
902
        else:
903
            _mem = self._memobj.memory[mem.number - 1]
904

    
905
        if self._reserved:
906
            _rsvd = _mem.reserved.get_raw()
907

    
908
        if mem.empty:
909
            if self.MODEL == "RB26" or self.MODEL == "RT76" \
910
                    or self.MODEL == "RB23":
911
                _mem.set_raw("\xFF" * 13 + _rsvd)
912
            elif self.MODEL == "RT19" or self.MODEL == "RT619":
913
                _mem.set_raw("\xFF" * 13 + _rsvd)
914
                _freqhops.freqhop.set_raw("\x00")
915
            elif self.MODEL == "AR-63":
916
                _mem.set_raw("\xFF" * 13 + _rsvd)
917
            else:
918
                _mem.set_raw("\xFF" * (_mem.size() / 8))
919

    
920
            return
921

    
922
        if self.MODEL == "RB17A":
923
            _mem.set_raw("\x00" * 14 + "\xFF\xFF")
924
        elif self._reserved:
925
            _mem.set_raw("\x00" * 13 + _rsvd)
926
        elif self.MODEL == "AR-63":
927
            _mem.set_raw("\x00" * 13 + _rsvd)
928
        else:
929
            _mem.set_raw("\x00" * 13 + "\x30\x8F\xF8")
930

    
931
        if self._gmrs:
932
            if mem.number >= 1 and mem.number <= 30:
933
                GMRS_FREQ = int(GMRS_FREQS[mem.number - 1] * 1000000)
934
                mem.freq = GMRS_FREQ
935
                if mem.number <= 22:
936
                    mem.duplex = ''
937
                    mem.offset = 0
938
                    if mem.number >= 8 and mem.number <= 14:
939
                        mem.mode = "NFM"
940
                        mem.power = self.POWER_LEVELS[1]
941
                if mem.number > 22:
942
                    mem.duplex = '+'
943
                    mem.offset = 5000000
944
        if self._frs:
945
            if mem.number >= 1 and mem.number <= 22:
946
                FRS_FREQ = int(FRS_FREQS[mem.number - 1] * 1000000)
947
                mem.freq = FRS_FREQ
948
                mem.mode = "NFM"
949
                mem.duplex = ''
950
                mem.offset = 0
951
                if mem.number >= 8 and mem.number <= 14:
952
                    mem.power = self.POWER_LEVELS[1]
953
        if self._pmr:
954
            PMR_FREQ = int(PMR_FREQS[mem.number - 1] * 1000000)
955
            mem.freq = PMR_FREQ
956
            mem.duplex = ''
957
            mem.offset = 0
958
            mem.mode = "NFM"
959
            mem.power = self.POWER_LEVELS[1]
960

    
961
        _mem.rxfreq = mem.freq / 10
962

    
963
        if mem.duplex == "off":
964
            for i in range(0, 4):
965
                _mem.txfreq[i].set_raw("\xFF")
966
        elif mem.duplex == "split":
967
            _mem.txfreq = mem.offset / 10
968
        elif mem.duplex == "+":
969
            _mem.txfreq = (mem.freq + mem.offset) / 10
970
        elif mem.duplex == "-":
971
            _mem.txfreq = (mem.freq - mem.offset) / 10
972
        else:
973
            _mem.txfreq = mem.freq / 10
974

    
975
        _mem.wide = mem.mode == "FM"
976

    
977
        self._set_tone(mem, _mem)
978

    
979
        if self.MODEL == "RT29_UHF" or self.MODEL == "RT29_VHF":
980
            # set the power level
981
            if mem.power == self.POWER_LEVELS[2]:
982
                _mem.txpower = self.TXPOWER_LOW
983
            elif mem.power == self.POWER_LEVELS[1]:
984
                _mem.txpower = self.TXPOWER_MED
985
            elif mem.power == self.POWER_LEVELS[0]:
986
                _mem.txpower = self.TXPOWER_HIGH
987
            else:
988
                LOG.error('%s: set_mem: unhandled power level: %s' %
989
                          (mem.name, mem.power))
990
        else:
991
            _mem.highpower = mem.power == self.POWER_LEVELS[0]
992

    
993
        if self._skipflags:
994
            if mem.skip != "S":
995
                _skp |= bitpos
996
            else:
997
                _skp &= ~bitpos
998
            LOG.debug("_skp %s" % _skp)
999

    
1000
        for setting in mem.extra:
1001
            if setting.get_name() == "scramble_type":
1002
                setattr(_mem, setting.get_name(), int(setting.value) - 1)
1003
                if self.MODEL == "RT21":
1004
                    setattr(_mem, "scramble_type2", int(setting.value) - 1)
1005
            elif setting.get_name() == "freqhop":
1006
                setattr(_freqhops, setting.get_name(), setting.value)
1007
            elif setting.get_name() == "hop":
1008
                setattr(_mem, setting.get_name(), not int(setting.value))
1009
            else:
1010
                setattr(_mem, setting.get_name(), setting.value)
1011

    
1012
    def get_settings(self):
1013
        _settings = self._memobj.settings
1014
        basic = RadioSettingGroup("basic", "Basic Settings")
1015
        top = RadioSettings(basic)
1016

    
1017
        if self.MODEL == "RT21" or self.MODEL == "RB17A" or \
1018
                self.MODEL == "RT29_UHF" or self.MODEL == "RT29_VHF":
1019
            _keys = self._memobj.keys
1020

    
1021
            rs = RadioSettingValueList(TIMEOUTTIMER_LIST,
1022
                                       TIMEOUTTIMER_LIST[_settings.tot])
1023
            rset = RadioSetting("tot", "Time-out timer", rs)
1024
            basic.append(rset)
1025

    
1026
            rs = RadioSettingValueList(TOTALERT_LIST,
1027
                                       TOTALERT_LIST[_settings.totalert])
1028
            rset = RadioSetting("totalert", "TOT Pre-alert", rs)
1029
            basic.append(rset)
1030

    
1031
            rs = RadioSettingValueInteger(0, 9, _settings.squelch)
1032
            rset = RadioSetting("squelch", "Squelch Level", rs)
1033
            basic.append(rset)
1034

    
1035
            rs = RadioSettingValueList(VOICE_LIST, VOICE_LIST[_settings.voice])
1036
            rset = RadioSetting("voice", "Voice Annumciation", rs)
1037
            basic.append(rset)
1038

    
1039
            if self.MODEL == "RB17A":
1040
                rs = RadioSettingValueList(ALARM_LIST,
1041
                                           ALARM_LIST[_settings.alarm])
1042
                rset = RadioSetting("alarm", "Alarm Type", rs)
1043
                basic.append(rset)
1044

    
1045
            rs = RadioSettingValueBoolean(_settings.save)
1046
            rset = RadioSetting("save", "Battery Saver", rs)
1047
            basic.append(rset)
1048

    
1049
            rs = RadioSettingValueBoolean(_settings.use_scramble)
1050
            rset = RadioSetting("use_scramble", "Scramble", rs)
1051
            basic.append(rset)
1052

    
1053
            rs = RadioSettingValueBoolean(_settings.use_vox)
1054
            rset = RadioSetting("use_vox", "VOX", rs)
1055
            basic.append(rset)
1056

    
1057
            rs = RadioSettingValueList(VOX_LIST, VOX_LIST[_settings.vox])
1058
            rset = RadioSetting("vox", "VOX Gain", rs)
1059
            basic.append(rset)
1060

    
1061
            if self.MODEL == "RT29_UHF" or self.MODEL == "RT29_VHF":
1062
                rs = RadioSettingValueList(VOXD_LIST,
1063
                                           VOXD_LIST[_settings.voxd])
1064
                rset = RadioSetting("voxd", "Vox Delay", rs)
1065
                basic.append(rset)
1066

    
1067
            def apply_pf1_listvalue(setting, obj):
1068
                LOG.debug("Setting value: " + str(
1069
                          setting.value) + " from list")
1070
                val = str(setting.value)
1071
                index = PF1_CHOICES.index(val)
1072
                val = PF1_VALUES[index]
1073
                obj.set_value(val)
1074

    
1075
            if self.MODEL == "RT21":
1076
                if _keys.pf1 in PF1_VALUES:
1077
                    idx = PF1_VALUES.index(_keys.pf1)
1078
                else:
1079
                    idx = LIST_DTMF_SPECIAL_VALUES.index(0x04)
1080
                rs = RadioSettingValueList(PF1_CHOICES, PF1_CHOICES[idx])
1081
                rset = RadioSetting("keys.pf1", "PF1 Key Function", rs)
1082
                rset.set_apply_callback(apply_pf1_listvalue, _keys.pf1)
1083
                basic.append(rset)
1084

    
1085
            def apply_pf1_17a_listvalue(setting, obj):
1086
                LOG.debug("Setting value: " + str(
1087
                          setting.value) + " from list")
1088
                val = str(setting.value)
1089
                index = PF1_17A_CHOICES.index(val)
1090
                val = PF1_17A_VALUES[index]
1091
                obj.set_value(val)
1092

    
1093
            if self.MODEL == "RB17A":
1094
                if _keys.pf1 in PF1_17A_VALUES:
1095
                    idx = PF1_17A_VALUES.index(_keys.pf1)
1096
                else:
1097
                    idx = LIST_DTMF_SPECIAL_VALUES.index(0x04)
1098
                rs = RadioSettingValueList(PF1_17A_CHOICES,
1099
                                           PF1_17A_CHOICES[idx])
1100
                rset = RadioSetting("keys.pf1", "PF1 Key Function", rs)
1101
                rset.set_apply_callback(apply_pf1_17a_listvalue, _keys.pf1)
1102
                basic.append(rset)
1103

    
1104
            def apply_topkey_listvalue(setting, obj):
1105
                LOG.debug("Setting value: " + str(setting.value) +
1106
                          " from list")
1107
                val = str(setting.value)
1108
                index = TOPKEY_CHOICES.index(val)
1109
                val = TOPKEY_VALUES[index]
1110
                obj.set_value(val)
1111

    
1112
            if self.MODEL == "RB17A":
1113
                if _keys.topkey in TOPKEY_VALUES:
1114
                    idx = TOPKEY_VALUES.index(_keys.topkey)
1115
                else:
1116
                    idx = TOPKEY_VALUES.index(0x0C)
1117
                rs = RadioSettingValueList(TOPKEY_CHOICES, TOPKEY_CHOICES[idx])
1118
                rset = RadioSetting("keys.topkey", "Top Key Function", rs)
1119
                rset.set_apply_callback(apply_topkey_listvalue, _keys.topkey)
1120
                basic.append(rset)
1121

    
1122
            def apply_pfkey_listvalue(setting, obj):
1123
                LOG.debug("Setting value: " + str(setting.value) +
1124
                          " from list")
1125
                val = str(setting.value)
1126
                index = PFKEY_CHOICES.index(val)
1127
                val = PFKEY_VALUES[index]
1128
                obj.set_value(val)
1129

    
1130
            if self.MODEL == "RT29_UHF" or self.MODEL == "RT29_VHF":
1131
                if _keys.pf1 in PFKEY_VALUES:
1132
                    idx = PFKEY_VALUES.index(_keys.pf1)
1133
                else:
1134
                    idx = PFKEY_VALUES.index(0x04)
1135
                rs = RadioSettingValueList(PFKEY_CHOICES, PFKEY_CHOICES[idx])
1136
                rset = RadioSetting("keys.pf1", "PF1 Key Function", rs)
1137
                rset.set_apply_callback(apply_pfkey_listvalue, _keys.pf1)
1138
                basic.append(rset)
1139

    
1140
                if _keys.pf2 in PFKEY_VALUES:
1141
                    idx = PFKEY_VALUES.index(_keys.pf2)
1142
                else:
1143
                    idx = PFKEY_VALUES.index(0x0A)
1144
                rs = RadioSettingValueList(PFKEY_CHOICES, PFKEY_CHOICES[idx])
1145
                rset = RadioSetting("keys.pf2", "PF2 Key Function", rs)
1146
                rset.set_apply_callback(apply_pfkey_listvalue, _keys.pf2)
1147
                basic.append(rset)
1148

    
1149
        if self.MODEL == "RB26" or self.MODEL == "RT76" \
1150
                or self.MODEL == "RB23" \
1151
                or self.MODEL == "RT19" or self.MODEL == "RT619" \
1152
                or self.MODEL == "AR-63":
1153
            if self.MODEL == "RB26" or self.MODEL == "RB23":
1154
                _settings2 = self._memobj.settings2
1155
                _settings3 = self._memobj.settings3
1156

    
1157
            rs = RadioSettingValueInteger(0, 9, _settings.squelch)
1158
            rset = RadioSetting("squelch", "63 Squelch Level", rs)
1159
            basic.append(rset)
1160

    
1161
            rs = RadioSettingValueList(TIMEOUTTIMER_LIST,
1162
                                       TIMEOUTTIMER_LIST[_settings.tot])
1163
            rset = RadioSetting("tot", "Time-out timer", rs)
1164
            basic.append(rset)
1165

    
1166
            if self.MODEL == "RT19" or self.MODEL == "RT619":
1167
                rs = RadioSettingValueList(VOICE_LIST,
1168
                                           VOICE_LIST[_settings.voice])
1169
                rset = RadioSetting("voice", "Voice Prompts", rs)
1170
                basic.append(rset)
1171

    
1172
                rs = RadioSettingValueList(BOOTSEL_LIST,
1173
                                           BOOTSEL_LIST[_settings.bootsel])
1174
                rset = RadioSetting("bootsel", "Boot Select", rs)
1175
                basic.append(rset)
1176

    
1177
                rs = RadioSettingValueInteger(1, 10, _settings.voicel + 1)
1178
                rset = RadioSetting("voicel", "Voice Level", rs)
1179
                basic.append(rset)
1180

    
1181
                rs = RadioSettingValueBoolean(_settings.vox)
1182
                rset = RadioSetting("vox", "Vox Function", rs)
1183
                basic.append(rset)
1184

    
1185
                rs = RadioSettingValueList(VOXL_LIST,
1186
                                           VOXL_LIST[_settings.voxl])
1187
                rset = RadioSetting("voxl", "Vox Level", rs)
1188
                basic.append(rset)
1189

    
1190
                rs = RadioSettingValueList(VOXD_LIST,
1191
                                           VOXD_LIST[_settings.voxd])
1192
                rset = RadioSetting("voxd", "Vox Delay", rs)
1193
                basic.append(rset)
1194

    
1195
            if self.MODEL == "AR-63":
1196
                rs = RadioSettingValueList(VOICE_LIST,
1197
                                           VOICE_LIST[_settings.voice])
1198
                rset = RadioSetting("voice", "Voice Prompts", rs)
1199
                basic.append(rset)
1200

    
1201
            if self.MODEL == "RT76":
1202
                rs = RadioSettingValueList(VOICE_LIST3,
1203
                                           VOICE_LIST3[_settings.voice])
1204
                rset = RadioSetting("voice", "Voice Annumciation", rs)
1205
                basic.append(rset)
1206

    
1207
            if self.MODEL == "RB26" or self.MODEL == "RB23":
1208
                rs = RadioSettingValueList(VOICE_LIST2,
1209
                                           VOICE_LIST2[_settings.voice])
1210
                rset = RadioSetting("voice", "Voice Annumciation", rs)
1211
                basic.append(rset)
1212

    
1213
            if self.MODEL == "RB26":
1214
                rs = RadioSettingValueBoolean(not _settings.chnumberd)
1215
                rset = RadioSetting("chnumberd", "Channel Number Enable", rs)
1216
                basic.append(rset)
1217

    
1218
            rs = RadioSettingValueBoolean(_settings.save)
1219
            rset = RadioSetting("save", "Battery Save", rs)
1220
            basic.append(rset)
1221

    
1222
            rs = RadioSettingValueBoolean(_settings.beep)
1223
            rset = RadioSetting("beep", "Beep", rs)
1224
            basic.append(rset)
1225

    
1226
            if self.MODEL == "RB26" or self.MODEL == "RB23":
1227
                rs = RadioSettingValueBoolean(not _settings.tail)
1228
                rset = RadioSetting("tail", "QT/DQT Tail", rs)
1229
                basic.append(rset)
1230

    
1231
            if self.MODEL != "AR-63":
1232
                rs = RadioSettingValueList(SAVE_LIST,
1233
                                           SAVE_LIST[_settings.savem])
1234
                rset = RadioSetting("savem", "Battery Save Mode", rs)
1235
                basic.append(rset)
1236

    
1237
            if self.MODEL != "RT19" and self.MODEL != "RT619" and \
1238
                    self.MODEL != "AR-63":
1239
                rs = RadioSettingValueList(GAIN_LIST,
1240
                                           GAIN_LIST[_settings.gain])
1241
                rset = RadioSetting("gain", "MIC Gain", rs)
1242
                basic.append(rset)
1243

    
1244
                rs = RadioSettingValueList(WARN_LIST,
1245
                                           WARN_LIST[_settings.warn])
1246
                rset = RadioSetting("warn", "Warn Mode", rs)
1247
                basic.append(rset)
1248

    
1249
            if self.MODEL == "RB26" or self.MODEL == "RB23":
1250
                rs = RadioSettingValueBoolean(_settings3.vox)
1251
                rset = RadioSetting("settings3.vox", "Vox Function", rs)
1252
                basic.append(rset)
1253

    
1254
                rs = RadioSettingValueList(VOXL_LIST,
1255
                                           VOXL_LIST[_settings3.voxl])
1256
                rset = RadioSetting("settings3.voxl", "Vox Level", rs)
1257
                basic.append(rset)
1258

    
1259
                rs = RadioSettingValueList(VOXD_LIST,
1260
                                           VOXD_LIST[_settings3.voxd])
1261
                rset = RadioSetting("settings3.voxd", "Vox Delay", rs)
1262
                basic.append(rset)
1263

    
1264
                if self.MODEL == "RB26":
1265
                    rs = RadioSettingValueList(PFKEY_LIST,
1266
                                               PFKEY_LIST[_settings.pf1])
1267
                    rset = RadioSetting("pf1", "PF1 Key Set", rs)
1268
                    basic.append(rset)
1269

    
1270
                    rs = RadioSettingValueList(PFKEY_LIST,
1271
                                               PFKEY_LIST[_settings.pf2])
1272
                    rset = RadioSetting("pf2", "PF2 Key Set", rs)
1273
                    basic.append(rset)
1274
                elif self.MODEL == "RB23":
1275
                    def apply_pfkey_listvalue(setting, obj):
1276
                        LOG.debug("Setting value: " + str(setting.value) +
1277
                                  " from list")
1278
                        val = str(setting.value)
1279
                        index = PFKEY23_CHOICES.index(val)
1280
                        val = PFKEY23_VALUES[index]
1281
                        obj.set_value(val)
1282

    
1283
                    if _settings.pf1 in PFKEY23_VALUES:
1284
                        idx = PFKEY23_VALUES.index(_settings.pf1)
1285
                    else:
1286
                        idx = PFKEY23_VALUES.index(0x01)
1287
                    rs = RadioSettingValueList(PFKEY23_CHOICES,
1288
                                               PFKEY23_CHOICES[idx])
1289
                    rset = RadioSetting("settings.pf1", "PF1 Key Function", rs)
1290
                    rset.set_apply_callback(apply_pfkey_listvalue,
1291
                                            _settings.pf1)
1292
                    basic.append(rset)
1293

    
1294
                    if _settings.pf2 in PFKEY23_VALUES:
1295
                        idx = PFKEY23_VALUES.index(_settings.pf2)
1296
                    else:
1297
                        idx = PFKEY23_VALUES.index(0x03)
1298
                    rs = RadioSettingValueList(PFKEY23_CHOICES,
1299
                                               PFKEY23_CHOICES[idx])
1300
                    rset = RadioSetting("settings.pf2", "PF2 Key Function", rs)
1301
                    rset.set_apply_callback(apply_pfkey_listvalue,
1302
                                            _settings.pf2)
1303
                    basic.append(rset)
1304

    
1305
                rs = RadioSettingValueInteger(1, 30, _settings2.chnumber + 1)
1306
                rset = RadioSetting("settings2.chnumber", "Channel Number", rs)
1307
                basic.append(rset)
1308

    
1309
            if self.MODEL == "RT76":
1310
                rs = RadioSettingValueBoolean(_settings.vox)
1311
                rset = RadioSetting("vox", "Vox Function", rs)
1312
                basic.append(rset)
1313

    
1314
                rs = RadioSettingValueList(VOXL_LIST,
1315
                                           VOXL_LIST[_settings.voxl])
1316
                rset = RadioSetting("voxl", "Vox Level", rs)
1317
                basic.append(rset)
1318

    
1319
                rs = RadioSettingValueList(VOXD_LIST,
1320
                                           VOXD_LIST[_settings.voxd])
1321
                rset = RadioSetting("voxd", "Vox Delay", rs)
1322
                basic.append(rset)
1323

    
1324
                rs = RadioSettingValueInteger(1, 30, _settings.chnumber + 1)
1325
                rset = RadioSetting("chnumber", "Channel Number", rs)
1326
                basic.append(rset)
1327

    
1328
            if self.MODEL == "AR-63":
1329
                rs = RadioSettingValueBoolean(_settings.warn)
1330
                rset = RadioSetting("warn", "Warn", rs)
1331
                basic.append(rset)
1332

    
1333
                rs = RadioSettingValueBoolean(_settings.scan)
1334
                rset = RadioSetting("scan", "Scan", rs)
1335
                basic.append(rset)
1336

    
1337
                rs = RadioSettingValueList(HOP_LIST,
1338
                                           HOP_LIST[_settings.hop])
1339
                rset = RadioSetting("hop", "Hop Mode", rs)
1340
                basic.append(rset)
1341

    
1342
                rs = RadioSettingValueList(TAIL_LIST,
1343
                                           TAIL_LIST[_settings.tailmode])
1344
                rset = RadioSetting("tailmode", "DCS Tail Mode", rs)
1345
                basic.append(rset)
1346

    
1347
                rs = RadioSettingValueBoolean(_settings.vox)
1348
                rset = RadioSetting("vox", "Vox Function", rs)
1349
                basic.append(rset)
1350

    
1351
                rs = RadioSettingValueList(VOXL_LIST,
1352
                                           VOXL_LIST[_settings.voxl])
1353
                rset = RadioSetting("voxl", "Vox Level", rs)
1354
                basic.append(rset)
1355

    
1356
                rs = RadioSettingValueList(VOXD_LIST,
1357
                                           VOXD_LIST[_settings.voxd])
1358
                rset = RadioSetting("voxd", "Vox Delay", rs)
1359
                basic.append(rset)
1360

    
1361
        return top
1362

    
1363
    def set_settings(self, settings):
1364
        for element in settings:
1365
            if not isinstance(element, RadioSetting):
1366
                self.set_settings(element)
1367
                continue
1368
            else:
1369
                try:
1370
                    if "." in element.get_name():
1371
                        bits = element.get_name().split(".")
1372
                        obj = self._memobj
1373
                        for bit in bits[:-1]:
1374
                            obj = getattr(obj, bit)
1375
                        setting = bits[-1]
1376
                    else:
1377
                        obj = self._memobj.settings
1378
                        setting = element.get_name()
1379

    
1380
                    if element.has_apply_callback():
1381
                        LOG.debug("Using apply callback")
1382
                        element.run_apply_callback()
1383
                    elif setting == "channel":
1384
                        setattr(obj, setting, int(element.value) - 1)
1385
                    elif setting == "chnumber":
1386
                        setattr(obj, setting, int(element.value) - 1)
1387
                    elif setting == "chnumberd":
1388
                        setattr(obj, setting, not int(element.value))
1389
                    elif setting == "tail":
1390
                        setattr(obj, setting, not int(element.value))
1391
                    elif setting == "voicel":
1392
                        setattr(obj, setting, int(element.value) - 1)
1393
                    elif element.value.get_mutable():
1394
                        LOG.debug("Setting %s = %s" % (setting, element.value))
1395
                        setattr(obj, setting, element.value)
1396
                except Exception as e:
1397
                    LOG.debug(element.get_name())
1398
                    raise
1399

    
1400
    @classmethod
1401
    def match_model(cls, filedata, filename):
1402
        if cls.MODEL == "RT21":
1403
            # The RT21 is pre-metadata, so do old-school detection
1404
            match_size = False
1405
            match_model = False
1406

    
1407
            # testing the file data size
1408
            if len(filedata) in [0x0400, ]:
1409
                match_size = True
1410

    
1411
            # testing the model fingerprint
1412
            match_model = model_match(cls, filedata)
1413

    
1414
            if match_size and match_model:
1415
                return True
1416
            else:
1417
                return False
1418
        else:
1419
            # Radios that have always been post-metadata, so never do
1420
            # old-school detection
1421
            return False
1422

    
1423

    
1424
@directory.register
1425
class RB17ARadio(RT21Radio):
1426
    """RETEVIS RB17A"""
1427
    VENDOR = "Retevis"
1428
    MODEL = "RB17A"
1429
    BAUD_RATE = 9600
1430
    BLOCK_SIZE = 0x40
1431
    BLOCK_SIZE_UP = 0x10
1432

    
1433
    POWER_LEVELS = [chirp_common.PowerLevel("High", watts=5.00),
1434
                    chirp_common.PowerLevel("Low", watts=0.50)]
1435

    
1436
    _magic = "PROA8US"
1437
    _fingerprint = "P3217s\xF8\xFF"
1438
    _upper = 30
1439
    _skipflags = True
1440
    _reserved = False
1441
    _gmrs = True
1442

    
1443
    _ranges = [
1444
               (0x0000, 0x0300),
1445
              ]
1446
    _memsize = 0x0300
1447

    
1448
    def process_mmap(self):
1449
        self._memobj = bitwise.parse(MEM_FORMAT_RB17A, self._mmap)
1450

    
1451

    
1452
@directory.register
1453
class RB26Radio(RT21Radio):
1454
    """RETEVIS RB26"""
1455
    VENDOR = "Retevis"
1456
    MODEL = "RB26"
1457
    BAUD_RATE = 9600
1458
    BLOCK_SIZE = 0x20
1459
    BLOCK_SIZE_UP = 0x10
1460

    
1461
    DTCS_CODES = sorted(chirp_common.DTCS_CODES + [645])
1462
    POWER_LEVELS = [chirp_common.PowerLevel("High", watts=3.00),
1463
                    chirp_common.PowerLevel("Low", watts=0.50)]
1464

    
1465
    _magic = "PHOGR" + "\x01" + "0"
1466
    _fingerprint = "P32073" + "\x02\xFF"
1467
    _upper = 30
1468
    _ack_1st_block = False
1469
    _skipflags = True
1470
    _reserved = True
1471
    _gmrs = True
1472

    
1473
    _ranges = [
1474
               (0x0000, 0x0320),
1475
              ]
1476
    _memsize = 0x0320
1477

    
1478
    def process_mmap(self):
1479
        self._memobj = bitwise.parse(MEM_FORMAT_RB26, self._mmap)
1480

    
1481

    
1482
@directory.register
1483
class RT76Radio(RT21Radio):
1484
    """RETEVIS RT76"""
1485
    VENDOR = "Retevis"
1486
    MODEL = "RT76"
1487
    BAUD_RATE = 9600
1488
    BLOCK_SIZE = 0x20
1489
    BLOCK_SIZE_UP = 0x10
1490

    
1491
    DTCS_CODES = sorted(chirp_common.DTCS_CODES + [645])
1492
    POWER_LEVELS = [chirp_common.PowerLevel("High", watts=5.00),
1493
                    chirp_common.PowerLevel("Low", watts=0.50)]
1494

    
1495
    _magic = "PHOGR\x14\xD4"
1496
    _fingerprint = "P32073" + "\x02\xFF"
1497
    _upper = 30
1498
    _ack_1st_block = False
1499
    _skipflags = False
1500
    _reserved = True
1501
    _gmrs = True
1502

    
1503
    _ranges = [
1504
               (0x0000, 0x01E0),
1505
              ]
1506
    _memsize = 0x01E0
1507

    
1508
    def process_mmap(self):
1509
        self._memobj = bitwise.parse(MEM_FORMAT_RT76, self._mmap)
1510

    
1511

    
1512
@directory.register
1513
class RT29UHFRadio(RT21Radio):
1514
    """RETEVIS RT29UHF"""
1515
    VENDOR = "Retevis"
1516
    MODEL = "RT29_UHF"
1517
    BLOCK_SIZE = 0x40
1518
    BLOCK_SIZE_UP = 0x10
1519

    
1520
    TXPOWER_MED = 0x00
1521
    TXPOWER_HIGH = 0x01
1522
    TXPOWER_LOW = 0x02
1523

    
1524
    POWER_LEVELS = [chirp_common.PowerLevel("High", watts=10.00),
1525
                    chirp_common.PowerLevel("Mid", watts=5.00),
1526
                    chirp_common.PowerLevel("Low", watts=1.00)]
1527

    
1528
    _magic = "PROHRAM"
1529
    _fingerprint = "P3207" + "\x13\xF8\xFF"  # UHF model
1530
    _upper = 16
1531
    _skipflags = True
1532
    _reserved = False
1533

    
1534
    _ranges = [
1535
               (0x0000, 0x0300),
1536
              ]
1537
    _memsize = 0x0400
1538

    
1539
    def process_mmap(self):
1540
        self._memobj = bitwise.parse(MEM_FORMAT_RT29, self._mmap)
1541

    
1542

    
1543
@directory.register
1544
class RT29VHFRadio(RT29UHFRadio):
1545
    """RETEVIS RT29VHF"""
1546
    VENDOR = "Retevis"
1547
    MODEL = "RT29_VHF"
1548

    
1549
    TXPOWER_MED = 0x00
1550
    TXPOWER_HIGH = 0x01
1551
    TXPOWER_LOW = 0x02
1552

    
1553
    POWER_LEVELS = [chirp_common.PowerLevel("High", watts=10.00),
1554
                    chirp_common.PowerLevel("Mid", watts=5.00),
1555
                    chirp_common.PowerLevel("Low", watts=1.00)]
1556

    
1557
    VALID_BANDS = [(136000000, 174000000)]
1558

    
1559
    _magic = "PROHRAM"
1560
    _fingerprint = "P2207" + "\x01\xF8\xFF"  # VHF model
1561

    
1562

    
1563
@directory.register
1564
class RB23Radio(RT21Radio):
1565
    """RETEVIS RB23"""
1566
    VENDOR = "Retevis"
1567
    MODEL = "RB23"
1568
    BLOCK_SIZE = 0x20
1569
    BLOCK_SIZE_UP = 0x10
1570

    
1571
    POWER_LEVELS = [chirp_common.PowerLevel("High", watts=5.00),
1572
                    chirp_common.PowerLevel("Low", watts=0.50)]
1573

    
1574
    _magic = "PHOGR" + "\x01" + "0"
1575
    _fingerprint = "P32073" + "\x02\xFF"
1576
    _upper = 30
1577
    _ack_1st_block = False
1578
    _skipflags = True
1579
    _reserved = True
1580
    _gmrs = True
1581

    
1582
    _ranges = [
1583
               (0x0000, 0x0320),
1584
              ]
1585
    _memsize = 0x0320
1586

    
1587
    def process_mmap(self):
1588
        self._memobj = bitwise.parse(MEM_FORMAT_RB26, self._mmap)
1589

    
1590

    
1591
@directory.register
1592
class RT19Radio(RT21Radio):
1593
    """RETEVIS RT19"""
1594
    VENDOR = "Retevis"
1595
    MODEL = "RT19"
1596
    BLOCK_SIZE = 0x20
1597
    BLOCK_SIZE_UP = 0x10
1598

    
1599
    POWER_LEVELS = [chirp_common.PowerLevel("High", watts=2.00),
1600
                    chirp_common.PowerLevel("Low", watts=0.50)]
1601

    
1602
    _magic = "PHOGRQ^"
1603
    _fingerprint = "P32073" + "\x02\xFF"
1604
    _upper = 22
1605
    _mem_params = (_upper,  # number of channels
1606
                   0x160,   # memory start
1607
                   _upper   # number of freqhops
1608
                   )
1609
    _ack_1st_block = False
1610
    _skipflags = False
1611
    _reserved = True
1612
    _frs = True
1613

    
1614
    _ranges = [
1615
               (0x0000, 0x0180),
1616
              ]
1617
    _memsize = 0x0180
1618

    
1619
    def process_mmap(self):
1620
        self._memobj = bitwise.parse(MEM_FORMAT_RT19 % self._mem_params,
1621
                                     self._mmap)
1622

    
1623

    
1624
@directory.register
1625
class RT619Radio(RT19Radio):
1626
    """RETEVIS RT619"""
1627
    VENDOR = "Retevis"
1628
    MODEL = "RT619"
1629

    
1630
    POWER_LEVELS = [chirp_common.PowerLevel("High", watts=0.50),
1631
                    chirp_common.PowerLevel("Low", watts=0.49)]
1632

    
1633
    _magic = "PHOGRS]"
1634
    _fingerprint = "P32073" + "\x02\xFF"
1635
    _upper = 16
1636
    _mem_params = (_upper,  # number of channels
1637
                   0x100,   # memory start
1638
                   _upper   # number of freqhops
1639
                   )
1640
    _pmr = True
1641

    
1642
    _ranges = [
1643
               (0x0000, 0x0120),
1644
              ]
1645
    _memsize = 0x0120
1646

    
1647

    
1648
@directory.register
1649
class AR63Radio(RT21Radio):
1650
    """ABBREE AR-63"""
1651
    VENDOR = "Abbree"
1652
    MODEL = "AR-63"
1653
    BLOCK_SIZE = 0x20
1654
    BLOCK_SIZE_UP = 0x10
1655

    
1656
    POWER_LEVELS = [chirp_common.PowerLevel("High", watts=3.00),
1657
                    chirp_common.PowerLevel("Low", watts=1.00)]
1658

    
1659
    _magic = "PHOGR\xF5\x9A"
1660
    _fingerprint = "P32073" + "\x02\xFF"
1661
    _upper = 16
1662
    _ack_1st_block = False
1663
    _skipflags = True
1664
    _reserved = True
1665
    _gmrs = False
1666

    
1667
    _ranges = [
1668
               (0x0000, 0x0140),
1669
              ]
1670
    _memsize = 0x0140
1671

    
1672
    def process_mmap(self):
1673
        self._memobj = bitwise.parse(MEM_FORMAT_RT76, self._mmap)
(4-4/8)