Project

General

Profile

New Model #9903 » retevis_rt21_ar-63_x03.py

Jim Unroe, 10/18/2022 05:12 PM

 
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
MEM_FORMAT_RT40B = """
355
#seekto 0x0000;
356
struct {
357
  lbcd rxfreq[4];      // RX Frequency           0-3
358
  lbcd txfreq[4];      // TX Frequency           4-7
359
  ul16 rx_tone;        // PL/DPL Decode          8-9
360
  ul16 tx_tone;        // PL/DPL Encode          A-B
361
  u8 compander:1,      // Compander              C
362
     unknown1:1,       //
363
     highpower:1,      // Power Level
364
     wide:1,           // Bandwidth
365
     unknown2:4;       //
366
  u8 reserved[3];      // Reserved               D-F
367
} memory[%d];
368

    
369
#seekto 0x002D;
370
struct {
371
  u8 unknown_1:1,      //                        002D
372
     unknown_2:1,      //
373
     savem:2,          // Battery Save Mode
374
     save:1,           // Battery Save
375
     beep:1,           // Beep
376
     voice:2;          // Voice Prompts
377
  u8 squelch;          // Squelch                002E
378
  u8 tot;              // Time-out Timer         002F
379
  u8 channel_4[13];    //                        0030-003C
380
  u8 unknown_3:7,      //                        003D
381
     vox:1;            // Vox
382
  u8 voxl;             // Vox Level              003E
383
  u8 voxd;             // Vox Delay              003F
384
  u8 channel_5[13];    //                        0040-004C
385
  u8 unknown_4[2];     //                        004D-004F
386
  u8 channel_6[13];    //                        0050-005C
387
  u8 chnumber;         // Channel Number         005D
388
  u8 unknown_5[2];     //                        005E-005F
389
  u8 channel_7[13];    //                        0060-006C
390
  u8 unknown_6:7,      //                        006D
391
     pttstone:1;       // PTT Start Tone
392
  u8 unknown_7:7,      //                        006E
393
     pttetone:1;       // PTT End Tone
394
} settings;
395

    
396
#seekto 0x00AD;
397
u8 skipflags[3];       // SCAN_ADD
398
"""
399

    
400
CMD_ACK = "\x06"
401

    
402
ALARM_LIST = ["Local Alarm", "Remote Alarm"]
403
BCL_LIST = ["Off", "Carrier", "QT/DQT"]
404
BOOTSEL_LIST = ["Channel Mode", "Voice Mode"]
405
CDCSS_LIST = ["Normal Code", "Special Code 2", "Special Code 1"]
406
CDCSS2_LIST = ["Normal Code", "Special Code"]  # RT29 UHF and RT29 VHF
407
FREQHOP_LIST = ["Off", "Hopping 1", "Hopping 2", "Hopping 3"]
408
FUNCTION_LIST = ["Off", "Scramble", "Compand"]
409
GAIN_LIST = ["Standard", "Enhanced"]
410
HOP_LIST = ["Mode A", "Mode B", "Mode C", "Mode D", "Mode E"]
411
PFKEY_LIST = ["None", "Monitor", "Lamp", "Warn", "VOX", "VOX Delay",
412
              "Key Lock", "Scan"]
413
SAVE_LIST = ["Standard", "Super"]
414
SAVEM_LIST = ["1-5", "1-8", "1-10", "1-15"]
415
SCRAMBLE_LIST = ["OFF"] + ["%s" % x for x in range(1, 9)]
416
TAIL_LIST = ["134.4 Hz", "55 Hz"]
417
TIMEOUTTIMER_LIST = ["Off"] + ["%s seconds" % x for x in range(15, 615, 15)]
418
TOTALERT_LIST = ["Off"] + ["%s seconds" % x for x in range(1, 11)]
419
VOICE_LIST = ["Off", "Chinese", "English"]
420
VOICE_LIST2 = ["Off", "English"]
421
VOICE_LIST3 = VOICE_LIST2 + ["Chinese"]
422
VOX_LIST = ["OFF"] + ["%s" % x for x in range(1, 17)]
423
VOXD_LIST = ["0.5", "1.0", "1.5", "2.0", "2.5", "3.0"]
424
VOXL_LIST = ["OFF"] + ["%s" % x for x in range(1, 10)]
425
WARN_LIST = ["OFF", "Native Warn", "Remote Warn"]
426
PF1_CHOICES = ["None", "Monitor", "Scan", "Scramble", "Alarm"]
427
PF1_VALUES = [0x0F, 0x04, 0x06, 0x08, 0x0C]
428
PF1_17A_CHOICES = ["None", "Monitor", "Scan", "Scramble"]
429
PF1_17A_VALUES = [0x0F, 0x04, 0x06, 0x08]
430
PFKEY23_CHOICES = ["None", "Monitor", "Warn", "VOX", "VOX Delay", "Scan"]
431
PFKEY23_VALUES = [0x00, 0x01, 0x03, 0x04, 0x05, 0x07]
432
PFKEY_CHOICES = ["None", "Monitor", "Scan", "Scramble", "VOX", "Alarm"]
433
PFKEY_VALUES = [0x0F, 0x04, 0x06, 0x08, 0x09, 0x0A]
434
TOPKEY_CHOICES = ["None", "Alarming"]
435
TOPKEY_VALUES = [0xFF, 0x0C]
436

    
437
SETTING_LISTS = {
438
    "alarm": ALARM_LIST,
439
    "bcl": BCL_LIST,
440
    "bootsel": BOOTSEL_LIST,
441
    "cdcss": CDCSS_LIST,
442
    "cdcss": CDCSS2_LIST,
443
    "freqhop": FREQHOP_LIST,
444
    "function": FUNCTION_LIST,
445
    "gain": GAIN_LIST,
446
    "hop": HOP_LIST,
447
    "pfkey": PFKEY_LIST,
448
    "save": SAVE_LIST,
449
    "savem": SAVEM_LIST,
450
    "scramble": SCRAMBLE_LIST,
451
    "tail": TAIL_LIST,
452
    "tot": TIMEOUTTIMER_LIST,
453
    "totalert": TOTALERT_LIST,
454
    "voice": VOICE_LIST,
455
    "voice": VOICE_LIST2,
456
    "voice": VOICE_LIST3,
457
    "vox": VOX_LIST,
458
    "voxd": VOXD_LIST,
459
    "voxl": VOXL_LIST,
460
    "warn": WARN_LIST,
461
    }
462

    
463
GMRS_FREQS1 = [462.5625, 462.5875, 462.6125, 462.6375, 462.6625,
464
               462.6875, 462.7125]
465
GMRS_FREQS2 = [467.5625, 467.5875, 467.6125, 467.6375, 467.6625,
466
               467.6875, 467.7125]
467
GMRS_FREQS3 = [462.5500, 462.5750, 462.6000, 462.6250, 462.6500,
468
               462.6750, 462.7000, 462.7250]
469
GMRS_FREQS = GMRS_FREQS1 + GMRS_FREQS2 + GMRS_FREQS3 * 2
470

    
471
FRS_FREQS = GMRS_FREQS1 + GMRS_FREQS2 + GMRS_FREQS3
472

    
473
PMR_FREQS1 = [446.00625, 446.01875, 446.03125, 446.04375, 446.05625,
474
              446.06875, 446.08125, 446.09375]
475
PMR_FREQS2 = [446.10625, 446.11875, 446.13125, 446.14375, 446.15625,
476
              446.16875, 446.18125, 446.19375]
477
PMR_FREQS = PMR_FREQS1 + PMR_FREQS2
478

    
479

    
480
def _enter_programming_mode(radio):
481
    serial = radio.pipe
482

    
483
    _magic = radio._magic
484

    
485
    try:
486
        serial.write(_magic)
487
        if radio._echo:
488
            chew = serial.read(len(_magic))  # Chew the echo
489
        ack = serial.read(1)
490
        if ack == "\x00":
491
            ack = serial.read(1)
492
    except:
493
        raise errors.RadioError("Error communicating with radio")
494

    
495
    if not ack:
496
        raise errors.RadioError("No response from radio")
497
    elif ack != CMD_ACK:
498
        raise errors.RadioError("Radio refused to enter programming mode")
499

    
500
    try:
501
        serial.write("\x02")
502
        if radio._echo:
503
            serial.read(1)  # Chew the echo
504
        ident = serial.read(8)
505
    except:
506
        raise errors.RadioError("Error communicating with radio")
507

    
508
    if not ident == radio._fingerprint:
509
        LOG.debug(util.hexprint(ident))
510
        raise errors.RadioError("Radio returned unknown identification string")
511

    
512
    try:
513
        serial.write(CMD_ACK)
514
        if radio._echo:
515
            serial.read(1)  # Chew the echo
516
        ack = serial.read(1)
517
    except:
518
        raise errors.RadioError("Error communicating with radio")
519

    
520
    if ack != CMD_ACK:
521
        raise errors.RadioError("Radio refused to enter programming mode")
522

    
523

    
524
def _exit_programming_mode(radio):
525
    serial = radio.pipe
526
    try:
527
        serial.write("E")
528
        if radio._echo:
529
            chew = serial.read(1)  # Chew the echo
530
    except:
531
        raise errors.RadioError("Radio refused to exit programming mode")
532

    
533

    
534
def _read_block(radio, block_addr, block_size):
535
    serial = radio.pipe
536

    
537
    cmd = struct.pack(">cHb", 'R', block_addr, block_size)
538
    expectedresponse = "W" + cmd[1:]
539
    LOG.debug("Reading block %04x..." % (block_addr))
540

    
541
    try:
542
        serial.write(cmd)
543
        if radio._echo:
544
            serial.read(4)  # Chew the echo
545
        response = serial.read(4 + block_size)
546
        if response[:4] != expectedresponse:
547
            raise Exception("Error reading block %04x." % (block_addr))
548

    
549
        block_data = response[4:]
550

    
551
        if block_addr != 0 or radio._ack_1st_block:
552
            serial.write(CMD_ACK)
553
            if radio._echo:
554
                serial.read(1)  # Chew the echo
555
            ack = serial.read(1)
556
    except:
557
        raise errors.RadioError("Failed to read block at %04x" % block_addr)
558

    
559
    if block_addr != 0 or radio._ack_1st_block:
560
        if ack != CMD_ACK:
561
            raise Exception("No ACK reading block %04x." % (block_addr))
562

    
563
    return block_data
564

    
565

    
566
def _write_block(radio, block_addr, block_size):
567
    serial = radio.pipe
568

    
569
    cmd = struct.pack(">cHb", 'W', block_addr, block_size)
570
    data = radio.get_mmap()[block_addr:block_addr + block_size]
571

    
572
    LOG.debug("Writing Data:")
573
    LOG.debug(util.hexprint(cmd + data))
574

    
575
    try:
576
        serial.write(cmd + data)
577
        if radio._echo:
578
            serial.read(4 + len(data))  # Chew the echo
579
        if serial.read(1) != CMD_ACK:
580
            raise Exception("No ACK")
581
    except:
582
        raise errors.RadioError("Failed to send block "
583
                                "to radio at %04x" % block_addr)
584

    
585

    
586
def do_download(radio):
587
    LOG.debug("download")
588
    _enter_programming_mode(radio)
589

    
590
    data = ""
591

    
592
    status = chirp_common.Status()
593
    status.msg = "Cloning from radio"
594

    
595
    status.cur = 0
596
    status.max = radio._memsize
597

    
598
    for addr in range(0, radio._memsize, radio.BLOCK_SIZE):
599
        status.cur = addr + radio.BLOCK_SIZE
600
        radio.status_fn(status)
601

    
602
        block = _read_block(radio, addr, radio.BLOCK_SIZE)
603
        data += block
604

    
605
        LOG.debug("Address: %04x" % addr)
606
        LOG.debug(util.hexprint(block))
607

    
608
    _exit_programming_mode(radio)
609

    
610
    return memmap.MemoryMap(data)
611

    
612

    
613
def do_upload(radio):
614
    status = chirp_common.Status()
615
    status.msg = "Uploading to radio"
616

    
617
    _enter_programming_mode(radio)
618

    
619
    status.cur = 0
620
    status.max = radio._memsize
621

    
622
    for start_addr, end_addr in radio._ranges:
623
        for addr in range(start_addr, end_addr, radio.BLOCK_SIZE_UP):
624
            status.cur = addr + radio.BLOCK_SIZE_UP
625
            radio.status_fn(status)
626
            _write_block(radio, addr, radio.BLOCK_SIZE_UP)
627

    
628
    _exit_programming_mode(radio)
629

    
630

    
631
def model_match(cls, data):
632
    """Match the opened/downloaded image to the correct version"""
633
    rid = data[0x01B8:0x01BE]
634

    
635
    return rid.startswith("P3207")
636

    
637

    
638
@directory.register
639
class RT21Radio(chirp_common.CloneModeRadio):
640
    """RETEVIS RT21"""
641
    VENDOR = "Retevis"
642
    MODEL = "RT21"
643
    BAUD_RATE = 9600
644
    BLOCK_SIZE = 0x10
645
    BLOCK_SIZE_UP = 0x10
646

    
647
    DTCS_CODES = sorted(chirp_common.DTCS_CODES + [17, 50, 645])
648
    POWER_LEVELS = [chirp_common.PowerLevel("High", watts=2.50),
649
                    chirp_common.PowerLevel("Low", watts=1.00)]
650

    
651
    VALID_BANDS = [(400000000, 480000000)]
652

    
653
    _magic = "PRMZUNE"
654
    _fingerprint = "P3207s\xF8\xFF"
655
    _upper = 16
656
    _ack_1st_block = True
657
    _skipflags = True
658
    _reserved = False
659
    _gmrs = _frs = _pmr = False
660
    _echo = False
661

    
662
    _ranges = [
663
               (0x0000, 0x0400),
664
              ]
665
    _memsize = 0x0400
666

    
667
    def get_features(self):
668
        rf = chirp_common.RadioFeatures()
669
        rf.has_settings = True
670
        rf.has_bank = False
671
        rf.has_ctone = True
672
        rf.has_cross = True
673
        rf.has_rx_dtcs = True
674
        rf.has_tuning_step = False
675
        rf.can_odd_split = True
676
        rf.has_name = False
677
        if self.MODEL == "RT76" or \
678
                self.MODEL == "RT19" or self.MODEL == "RT619":
679
            rf.valid_skips = []
680
        else:
681
            rf.valid_skips = ["", "S"]
682
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
683
        rf.valid_cross_modes = ["Tone->Tone", "Tone->DTCS", "DTCS->Tone",
684
                                "->Tone", "->DTCS", "DTCS->", "DTCS->DTCS"]
685
        rf.valid_power_levels = self.POWER_LEVELS
686
        rf.valid_duplexes = ["", "-", "+", "split", "off"]
687
        rf.valid_modes = ["FM", "NFM"]  # 25 KHz, 12.5 kHz.
688
        rf.valid_dtcs_codes = self.DTCS_CODES
689
        rf.memory_bounds = (1, self._upper)
690
        rf.valid_tuning_steps = [2.5, 5., 6.25, 10., 12.5, 25.]
691
        rf.valid_bands = self.VALID_BANDS
692

    
693
        return rf
694

    
695
    def process_mmap(self):
696
        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
697

    
698
    def sync_in(self):
699
        """Download from radio"""
700
        try:
701
            data = do_download(self)
702
        except errors.RadioError:
703
            # Pass through any real errors we raise
704
            raise
705
        except:
706
            # If anything unexpected happens, make sure we raise
707
            # a RadioError and log the problem
708
            LOG.exception('Unexpected error during download')
709
            raise errors.RadioError('Unexpected error communicating '
710
                                    'with the radio')
711
        self._mmap = data
712
        self.process_mmap()
713

    
714
    def sync_out(self):
715
        """Upload to radio"""
716
        try:
717
            do_upload(self)
718
        except:
719
            # If anything unexpected happens, make sure we raise
720
            # a RadioError and log the problem
721
            LOG.exception('Unexpected error during upload')
722
            raise errors.RadioError('Unexpected error communicating '
723
                                    'with the radio')
724

    
725
    def get_raw_memory(self, number):
726
        return repr(self._memobj.memory[number - 1])
727

    
728
    def _get_tone(self, _mem, mem):
729
        def _get_dcs(val):
730
            code = int("%03o" % (val & 0x07FF))
731
            pol = (val & 0x8000) and "R" or "N"
732
            return code, pol
733

    
734
        tpol = False
735
        if _mem.tx_tone != 0xFFFF and _mem.tx_tone > 0x2000:
736
            tcode, tpol = _get_dcs(_mem.tx_tone)
737
            mem.dtcs = tcode
738
            txmode = "DTCS"
739
        elif _mem.tx_tone != 0xFFFF:
740
            mem.rtone = _mem.tx_tone / 10.0
741
            txmode = "Tone"
742
        else:
743
            txmode = ""
744

    
745
        rpol = False
746
        if _mem.rx_tone != 0xFFFF and _mem.rx_tone > 0x2000:
747
            rcode, rpol = _get_dcs(_mem.rx_tone)
748
            mem.rx_dtcs = rcode
749
            rxmode = "DTCS"
750
        elif _mem.rx_tone != 0xFFFF:
751
            mem.ctone = _mem.rx_tone / 10.0
752
            rxmode = "Tone"
753
        else:
754
            rxmode = ""
755

    
756
        if txmode == "Tone" and not rxmode:
757
            mem.tmode = "Tone"
758
        elif txmode == rxmode and txmode == "Tone" and mem.rtone == mem.ctone:
759
            mem.tmode = "TSQL"
760
        elif txmode == rxmode and txmode == "DTCS" and mem.dtcs == mem.rx_dtcs:
761
            mem.tmode = "DTCS"
762
        elif rxmode or txmode:
763
            mem.tmode = "Cross"
764
            mem.cross_mode = "%s->%s" % (txmode, rxmode)
765

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

    
769
        LOG.debug("Got TX %s (%i) RX %s (%i)" %
770
                  (txmode, _mem.tx_tone, rxmode, _mem.rx_tone))
771

    
772
    def get_memory(self, number):
773
        if self._skipflags:
774
            bitpos = (1 << ((number - 1) % 8))
775
            bytepos = ((number - 1) / 8)
776
            LOG.debug("bitpos %s" % bitpos)
777
            LOG.debug("bytepos %s" % bytepos)
778
            _skp = self._memobj.skipflags[bytepos]
779

    
780
        mem = chirp_common.Memory()
781

    
782
        mem.number = number
783

    
784
        if self.MODEL == "RB17A":
785
            if mem.number < 17:
786
                _mem = self._memobj.lomems[number - 1]
787
            else:
788
                _mem = self._memobj.himems[number - 17]
789
        else:
790
            _mem = self._memobj.memory[number - 1]
791

    
792
        if self._reserved:
793
            _rsvd = _mem.reserved.get_raw()
794

    
795
        mem.freq = int(_mem.rxfreq) * 10
796

    
797
        # We'll consider any blank (i.e. 0MHz frequency) to be empty
798
        if mem.freq == 0:
799
            mem.empty = True
800
            return mem
801

    
802
        if _mem.rxfreq.get_raw() == "\xFF\xFF\xFF\xFF":
803
            mem.freq = 0
804
            mem.empty = True
805
            return mem
806

    
807
        if int(_mem.rxfreq) == int(_mem.txfreq):
808
            mem.duplex = ""
809
            mem.offset = 0
810
        else:
811
            mem.duplex = int(_mem.rxfreq) > int(_mem.txfreq) and "-" or "+"
812
            mem.offset = abs(int(_mem.rxfreq) - int(_mem.txfreq)) * 10
813

    
814
        mem.mode = _mem.wide and "FM" or "NFM"
815

    
816
        self._get_tone(_mem, mem)
817

    
818
        if self.MODEL == "RT29_UHF" or self.MODEL == "RT29_VHF":
819
            # set the power level
820
            if _mem.txpower == self.TXPOWER_LOW:
821
                mem.power = self.POWER_LEVELS[2]
822
            elif _mem.txpower == self.TXPOWER_MED:
823
                mem.power = self.POWER_LEVELS[1]
824
            elif _mem.txpower == self.TXPOWER_HIGH:
825
                mem.power = self.POWER_LEVELS[0]
826
            else:
827
                LOG.error('%s: get_mem: unhandled power level: 0x%02x' %
828
                          (mem.name, _mem.txpower))
829
        else:
830
            mem.power = self.POWER_LEVELS[1 - _mem.highpower]
831

    
832
        if self._skipflags:
833
            mem.skip = "" if (_skp & bitpos) else "S"
834
            LOG.debug("mem.skip %s" % mem.skip)
835

    
836
        mem.extra = RadioSettingGroup("Extra", "extra")
837

    
838
        if self.MODEL == "RT21" or self.MODEL == "RB17A" or \
839
                self.MODEL == "RT29_UHF" or self.MODEL == "RT29_VHF":
840
            rs = RadioSettingValueList(BCL_LIST, BCL_LIST[_mem.bcl])
841
            rset = RadioSetting("bcl", "Busy Channel Lockout", rs)
842
            mem.extra.append(rset)
843

    
844
            rs = RadioSettingValueInteger(1, 8, _mem.scramble_type + 1)
845
            rset = RadioSetting("scramble_type", "Scramble Type", rs)
846
            mem.extra.append(rset)
847

    
848
            if self.MODEL == "RB17A":
849
                rs = RadioSettingValueList(CDCSS_LIST, CDCSS_LIST[_mem.cdcss])
850
                rset = RadioSetting("cdcss", "Cdcss Mode", rs)
851
                mem.extra.append(rset)
852

    
853
            if self.MODEL == "RT29_UHF" or self.MODEL == "RT29_VHF":
854
                rs = RadioSettingValueList(CDCSS2_LIST,
855
                                           CDCSS2_LIST[_mem.cdcss])
856
                rset = RadioSetting("cdcss", "Cdcss Mode", rs)
857
                mem.extra.append(rset)
858

    
859
            if self.MODEL == "RB17A" or self.MODEL == "RT29_UHF" or \
860
                    self.MODEL == "RT29_VHF":
861
                rs = RadioSettingValueBoolean(_mem.compander)
862
                rset = RadioSetting("compander", "Compander", rs)
863
                mem.extra.append(rset)
864

    
865
        if self.MODEL == "RB26" or self.MODEL == "RT76" \
866
                or self.MODEL == "RB23" or self.MODEL == "AR-63":
867
            if self.MODEL == "RB26" or self.MODEL == "RB23":
868
                rs = RadioSettingValueBoolean(_mem.bcl)
869
                rset = RadioSetting("bcl", "Busy Channel Lockout", rs)
870
                mem.extra.append(rset)
871

    
872
            rs = RadioSettingValueBoolean(_mem.compander)
873
            rset = RadioSetting("compander", "Compander", rs)
874
            mem.extra.append(rset)
875

    
876
            if self.MODEL == "AR-63":
877
                rs = RadioSettingValueList(SCRAMBLE_LIST,
878
                                           SCRAMBLE_LIST[_mem.scramble])
879
                rset = RadioSetting("scramble", "Scramble", rs)
880
                mem.extra.append(rset)
881

    
882
                rs = RadioSettingValueBoolean(not _mem.hop)
883
                rset = RadioSetting("hop", "Frequency Hop", rs)
884
                mem.extra.append(rset)
885

    
886
        if self.MODEL == "RT19" or self.MODEL == "RT619":
887
            _freqhops = self._memobj.freqhops[number - 1]
888

    
889
            rs = RadioSettingValueList(FUNCTION_LIST,
890
                                       FUNCTION_LIST[_mem.function])
891
            rset = RadioSetting("function", "Function", rs)
892
            mem.extra.append(rset)
893

    
894
            rs = RadioSettingValueInteger(1, 8, _mem.scramble_type + 1)
895
            rset = RadioSetting("scramble_type", "Scramble Type", rs)
896
            mem.extra.append(rset)
897

    
898
            rs = RadioSettingValueList(FREQHOP_LIST,
899
                                       FREQHOP_LIST[_freqhops.freqhop])
900
            rset = RadioSetting("freqhop", "Frequency Hop", rs)
901
            mem.extra.append(rset)
902

    
903
        if self.MODEL == "RT40B":
904
            rs = RadioSettingValueBoolean(_mem.compander)
905
            rset = RadioSetting("compander", "Compander", rs)
906
            mem.extra.append(rset)
907

    
908
        return mem
909

    
910
    def _set_tone(self, mem, _mem):
911
        def _set_dcs(code, pol):
912
            val = int("%i" % code, 8) + 0x2000
913
            if pol == "R":
914
                val += 0x8000
915
            return val
916

    
917
        rx_mode = tx_mode = None
918
        rx_tone = tx_tone = 0xFFFF
919

    
920
        if mem.tmode == "Tone":
921
            tx_mode = "Tone"
922
            rx_mode = None
923
            tx_tone = int(mem.rtone * 10)
924
        elif mem.tmode == "TSQL":
925
            rx_mode = tx_mode = "Tone"
926
            rx_tone = tx_tone = int(mem.ctone * 10)
927
        elif mem.tmode == "DTCS":
928
            tx_mode = rx_mode = "DTCS"
929
            tx_tone = _set_dcs(mem.dtcs, mem.dtcs_polarity[0])
930
            rx_tone = _set_dcs(mem.dtcs, mem.dtcs_polarity[1])
931
        elif mem.tmode == "Cross":
932
            tx_mode, rx_mode = mem.cross_mode.split("->")
933
            if tx_mode == "DTCS":
934
                tx_tone = _set_dcs(mem.dtcs, mem.dtcs_polarity[0])
935
            elif tx_mode == "Tone":
936
                tx_tone = int(mem.rtone * 10)
937
            if rx_mode == "DTCS":
938
                rx_tone = _set_dcs(mem.rx_dtcs, mem.dtcs_polarity[1])
939
            elif rx_mode == "Tone":
940
                rx_tone = int(mem.ctone * 10)
941

    
942
        _mem.rx_tone = rx_tone
943
        _mem.tx_tone = tx_tone
944

    
945
        LOG.debug("Set TX %s (%i) RX %s (%i)" %
946
                  (tx_mode, _mem.tx_tone, rx_mode, _mem.rx_tone))
947

    
948
    def set_memory(self, mem):
949
        if self._skipflags:
950
            bitpos = (1 << ((mem.number - 1) % 8))
951
            bytepos = ((mem.number - 1) / 8)
952
            LOG.debug("bitpos %s" % bitpos)
953
            LOG.debug("bytepos %s" % bytepos)
954
            _skp = self._memobj.skipflags[bytepos]
955

    
956
        if self.MODEL == "RB17A":
957
            if mem.number < 17:
958
                _mem = self._memobj.lomems[mem.number - 1]
959
            else:
960
                _mem = self._memobj.himems[mem.number - 17]
961
        elif self.MODEL == "RT19" or self.MODEL == "RT619":
962
            _mem = self._memobj.memory[mem.number - 1]
963
            _freqhops = self._memobj.freqhops[mem.number - 1]
964
        else:
965
            _mem = self._memobj.memory[mem.number - 1]
966

    
967
        if self._reserved:
968
            _rsvd = _mem.reserved.get_raw()
969

    
970
        if mem.empty:
971
            if self.MODEL == "RB26" or self.MODEL == "RT76" \
972
                    or self.MODEL == "RB23" \
973
                    or self.MODEL == "RT40B":
974
                _mem.set_raw("\xFF" * 13 + _rsvd)
975
            elif self.MODEL == "RT19" or self.MODEL == "RT619":
976
                _mem.set_raw("\xFF" * 13 + _rsvd)
977
                _freqhops.freqhop.set_raw("\x00")
978
            elif self.MODEL == "AR-63":
979
                _mem.set_raw("\xFF" * 13 + _rsvd)
980
            else:
981
                _mem.set_raw("\xFF" * (_mem.size() / 8))
982

    
983
            return
984

    
985
        if self.MODEL == "RB17A":
986
            _mem.set_raw("\x00" * 14 + "\xFF\xFF")
987
        elif self._reserved:
988
            _mem.set_raw("\x00" * 13 + _rsvd)
989
        elif self.MODEL == "AR-63":
990
            _mem.set_raw("\x00" * 13 + _rsvd)
991
        else:
992
            _mem.set_raw("\x00" * 13 + "\x30\x8F\xF8")
993

    
994
        if self._gmrs:
995
            if mem.number >= 1 and mem.number <= 30:
996
                GMRS_FREQ = int(GMRS_FREQS[mem.number - 1] * 1000000)
997
                mem.freq = GMRS_FREQ
998
                if mem.number <= 22:
999
                    mem.duplex = ''
1000
                    mem.offset = 0
1001
                    if mem.number >= 8 and mem.number <= 14:
1002
                        mem.mode = "NFM"
1003
                        mem.power = self.POWER_LEVELS[1]
1004
                if mem.number > 22:
1005
                    mem.duplex = '+'
1006
                    mem.offset = 5000000
1007
        if self._frs:
1008
            if mem.number >= 1 and mem.number <= 22:
1009
                FRS_FREQ = int(FRS_FREQS[mem.number - 1] * 1000000)
1010
                mem.freq = FRS_FREQ
1011
                mem.mode = "NFM"
1012
                mem.duplex = ''
1013
                mem.offset = 0
1014
                if mem.number >= 8 and mem.number <= 14:
1015
                    mem.power = self.POWER_LEVELS[1]
1016
        if self._pmr:
1017
            PMR_FREQ = int(PMR_FREQS[mem.number - 1] * 1000000)
1018
            mem.freq = PMR_FREQ
1019
            mem.duplex = ''
1020
            mem.offset = 0
1021
            mem.mode = "NFM"
1022
            mem.power = self.POWER_LEVELS[1]
1023

    
1024
        _mem.rxfreq = mem.freq / 10
1025

    
1026
        if mem.duplex == "off":
1027
            for i in range(0, 4):
1028
                _mem.txfreq[i].set_raw("\xFF")
1029
        elif mem.duplex == "split":
1030
            _mem.txfreq = mem.offset / 10
1031
        elif mem.duplex == "+":
1032
            _mem.txfreq = (mem.freq + mem.offset) / 10
1033
        elif mem.duplex == "-":
1034
            _mem.txfreq = (mem.freq - mem.offset) / 10
1035
        else:
1036
            _mem.txfreq = mem.freq / 10
1037

    
1038
        _mem.wide = mem.mode == "FM"
1039

    
1040
        self._set_tone(mem, _mem)
1041

    
1042
        if self.MODEL == "RT29_UHF" or self.MODEL == "RT29_VHF":
1043
            # set the power level
1044
            if mem.power == self.POWER_LEVELS[2]:
1045
                _mem.txpower = self.TXPOWER_LOW
1046
            elif mem.power == self.POWER_LEVELS[1]:
1047
                _mem.txpower = self.TXPOWER_MED
1048
            elif mem.power == self.POWER_LEVELS[0]:
1049
                _mem.txpower = self.TXPOWER_HIGH
1050
            else:
1051
                LOG.error('%s: set_mem: unhandled power level: %s' %
1052
                          (mem.name, mem.power))
1053
        else:
1054
            _mem.highpower = mem.power == self.POWER_LEVELS[0]
1055

    
1056
        if self._skipflags:
1057
            if mem.skip != "S":
1058
                _skp |= bitpos
1059
            else:
1060
                _skp &= ~bitpos
1061
            LOG.debug("_skp %s" % _skp)
1062

    
1063
        for setting in mem.extra:
1064
            if setting.get_name() == "scramble_type":
1065
                setattr(_mem, setting.get_name(), int(setting.value) - 1)
1066
                if self.MODEL == "RT21":
1067
                    setattr(_mem, "scramble_type2", int(setting.value) - 1)
1068
            elif setting.get_name() == "freqhop":
1069
                setattr(_freqhops, setting.get_name(), setting.value)
1070
            elif setting.get_name() == "hop":
1071
                setattr(_mem, setting.get_name(), not int(setting.value))
1072
            else:
1073
                setattr(_mem, setting.get_name(), setting.value)
1074

    
1075
    def get_settings(self):
1076
        _settings = self._memobj.settings
1077
        basic = RadioSettingGroup("basic", "Basic Settings")
1078
        top = RadioSettings(basic)
1079

    
1080
        if self.MODEL == "RT21" or self.MODEL == "RB17A" or \
1081
                self.MODEL == "RT29_UHF" or self.MODEL == "RT29_VHF":
1082
            _keys = self._memobj.keys
1083

    
1084
            rs = RadioSettingValueList(TIMEOUTTIMER_LIST,
1085
                                       TIMEOUTTIMER_LIST[_settings.tot])
1086
            rset = RadioSetting("tot", "Time-out timer", rs)
1087
            basic.append(rset)
1088

    
1089
            rs = RadioSettingValueList(TOTALERT_LIST,
1090
                                       TOTALERT_LIST[_settings.totalert])
1091
            rset = RadioSetting("totalert", "TOT Pre-alert", rs)
1092
            basic.append(rset)
1093

    
1094
            rs = RadioSettingValueInteger(0, 9, _settings.squelch)
1095
            rset = RadioSetting("squelch", "Squelch Level", rs)
1096
            basic.append(rset)
1097

    
1098
            rs = RadioSettingValueList(VOICE_LIST, VOICE_LIST[_settings.voice])
1099
            rset = RadioSetting("voice", "Voice Annumciation", rs)
1100
            basic.append(rset)
1101

    
1102
            if self.MODEL == "RB17A":
1103
                rs = RadioSettingValueList(ALARM_LIST,
1104
                                           ALARM_LIST[_settings.alarm])
1105
                rset = RadioSetting("alarm", "Alarm Type", rs)
1106
                basic.append(rset)
1107

    
1108
            rs = RadioSettingValueBoolean(_settings.save)
1109
            rset = RadioSetting("save", "Battery Saver", rs)
1110
            basic.append(rset)
1111

    
1112
            rs = RadioSettingValueBoolean(_settings.use_scramble)
1113
            rset = RadioSetting("use_scramble", "Scramble", rs)
1114
            basic.append(rset)
1115

    
1116
            rs = RadioSettingValueBoolean(_settings.use_vox)
1117
            rset = RadioSetting("use_vox", "VOX", rs)
1118
            basic.append(rset)
1119

    
1120
            rs = RadioSettingValueList(VOX_LIST, VOX_LIST[_settings.vox])
1121
            rset = RadioSetting("vox", "VOX Gain", rs)
1122
            basic.append(rset)
1123

    
1124
            if self.MODEL == "RT29_UHF" or self.MODEL == "RT29_VHF":
1125
                rs = RadioSettingValueList(VOXD_LIST,
1126
                                           VOXD_LIST[_settings.voxd])
1127
                rset = RadioSetting("voxd", "Vox Delay", rs)
1128
                basic.append(rset)
1129

    
1130
            def apply_pf1_listvalue(setting, obj):
1131
                LOG.debug("Setting value: " + str(
1132
                          setting.value) + " from list")
1133
                val = str(setting.value)
1134
                index = PF1_CHOICES.index(val)
1135
                val = PF1_VALUES[index]
1136
                obj.set_value(val)
1137

    
1138
            if self.MODEL == "RT21":
1139
                if _keys.pf1 in PF1_VALUES:
1140
                    idx = PF1_VALUES.index(_keys.pf1)
1141
                else:
1142
                    idx = LIST_DTMF_SPECIAL_VALUES.index(0x04)
1143
                rs = RadioSettingValueList(PF1_CHOICES, PF1_CHOICES[idx])
1144
                rset = RadioSetting("keys.pf1", "PF1 Key Function", rs)
1145
                rset.set_apply_callback(apply_pf1_listvalue, _keys.pf1)
1146
                basic.append(rset)
1147

    
1148
            def apply_pf1_17a_listvalue(setting, obj):
1149
                LOG.debug("Setting value: " + str(
1150
                          setting.value) + " from list")
1151
                val = str(setting.value)
1152
                index = PF1_17A_CHOICES.index(val)
1153
                val = PF1_17A_VALUES[index]
1154
                obj.set_value(val)
1155

    
1156
            if self.MODEL == "RB17A":
1157
                if _keys.pf1 in PF1_17A_VALUES:
1158
                    idx = PF1_17A_VALUES.index(_keys.pf1)
1159
                else:
1160
                    idx = LIST_DTMF_SPECIAL_VALUES.index(0x04)
1161
                rs = RadioSettingValueList(PF1_17A_CHOICES,
1162
                                           PF1_17A_CHOICES[idx])
1163
                rset = RadioSetting("keys.pf1", "PF1 Key Function", rs)
1164
                rset.set_apply_callback(apply_pf1_17a_listvalue, _keys.pf1)
1165
                basic.append(rset)
1166

    
1167
            def apply_topkey_listvalue(setting, obj):
1168
                LOG.debug("Setting value: " + str(setting.value) +
1169
                          " from list")
1170
                val = str(setting.value)
1171
                index = TOPKEY_CHOICES.index(val)
1172
                val = TOPKEY_VALUES[index]
1173
                obj.set_value(val)
1174

    
1175
            if self.MODEL == "RB17A":
1176
                if _keys.topkey in TOPKEY_VALUES:
1177
                    idx = TOPKEY_VALUES.index(_keys.topkey)
1178
                else:
1179
                    idx = TOPKEY_VALUES.index(0x0C)
1180
                rs = RadioSettingValueList(TOPKEY_CHOICES, TOPKEY_CHOICES[idx])
1181
                rset = RadioSetting("keys.topkey", "Top Key Function", rs)
1182
                rset.set_apply_callback(apply_topkey_listvalue, _keys.topkey)
1183
                basic.append(rset)
1184

    
1185
            def apply_pfkey_listvalue(setting, obj):
1186
                LOG.debug("Setting value: " + str(setting.value) +
1187
                          " from list")
1188
                val = str(setting.value)
1189
                index = PFKEY_CHOICES.index(val)
1190
                val = PFKEY_VALUES[index]
1191
                obj.set_value(val)
1192

    
1193
            if self.MODEL == "RT29_UHF" or self.MODEL == "RT29_VHF":
1194
                if _keys.pf1 in PFKEY_VALUES:
1195
                    idx = PFKEY_VALUES.index(_keys.pf1)
1196
                else:
1197
                    idx = PFKEY_VALUES.index(0x04)
1198
                rs = RadioSettingValueList(PFKEY_CHOICES, PFKEY_CHOICES[idx])
1199
                rset = RadioSetting("keys.pf1", "PF1 Key Function", rs)
1200
                rset.set_apply_callback(apply_pfkey_listvalue, _keys.pf1)
1201
                basic.append(rset)
1202

    
1203
                if _keys.pf2 in PFKEY_VALUES:
1204
                    idx = PFKEY_VALUES.index(_keys.pf2)
1205
                else:
1206
                    idx = PFKEY_VALUES.index(0x0A)
1207
                rs = RadioSettingValueList(PFKEY_CHOICES, PFKEY_CHOICES[idx])
1208
                rset = RadioSetting("keys.pf2", "PF2 Key Function", rs)
1209
                rset.set_apply_callback(apply_pfkey_listvalue, _keys.pf2)
1210
                basic.append(rset)
1211

    
1212
        if self.MODEL == "RB26" or self.MODEL == "RT76" \
1213
                or self.MODEL == "RB23" \
1214
                or self.MODEL == "RT19" or self.MODEL == "RT619" \
1215
                or self.MODEL == "AR-63" \
1216
                or self.MODEL == "RT40B":
1217
            if self.MODEL == "RB26" or self.MODEL == "RB23":
1218
                _settings2 = self._memobj.settings2
1219
                _settings3 = self._memobj.settings3
1220

    
1221
            rs = RadioSettingValueInteger(0, 9, _settings.squelch)
1222
            rset = RadioSetting("squelch", "Squelch Level", rs)
1223
            basic.append(rset)
1224

    
1225
            rs = RadioSettingValueList(TIMEOUTTIMER_LIST,
1226
                                       TIMEOUTTIMER_LIST[_settings.tot])
1227
            rset = RadioSetting("tot", "Time-out timer", rs)
1228
            basic.append(rset)
1229

    
1230
            if self.MODEL == "RT19" or self.MODEL == "RT619":
1231
                rs = RadioSettingValueList(VOICE_LIST,
1232
                                           VOICE_LIST[_settings.voice])
1233
                rset = RadioSetting("voice", "Voice Prompts", rs)
1234
                basic.append(rset)
1235

    
1236
                rs = RadioSettingValueList(BOOTSEL_LIST,
1237
                                           BOOTSEL_LIST[_settings.bootsel])
1238
                rset = RadioSetting("bootsel", "Boot Select", rs)
1239
                basic.append(rset)
1240

    
1241
                rs = RadioSettingValueInteger(1, 10, _settings.voicel + 1)
1242
                rset = RadioSetting("voicel", "Voice Level", rs)
1243
                basic.append(rset)
1244

    
1245
                rs = RadioSettingValueBoolean(_settings.vox)
1246
                rset = RadioSetting("vox", "Vox Function", rs)
1247
                basic.append(rset)
1248

    
1249
                rs = RadioSettingValueList(VOXL_LIST,
1250
                                           VOXL_LIST[_settings.voxl])
1251
                rset = RadioSetting("voxl", "Vox Level", rs)
1252
                basic.append(rset)
1253

    
1254
                rs = RadioSettingValueList(VOXD_LIST,
1255
                                           VOXD_LIST[_settings.voxd])
1256
                rset = RadioSetting("voxd", "Vox Delay", rs)
1257
                basic.append(rset)
1258

    
1259
            if self.MODEL == "AR-63":
1260
                rs = RadioSettingValueList(VOICE_LIST,
1261
                                           VOICE_LIST[_settings.voice])
1262
                rset = RadioSetting("voice", "Voice Prompts", rs)
1263
                basic.append(rset)
1264

    
1265
            if self.MODEL == "RT76":
1266
                rs = RadioSettingValueList(VOICE_LIST3,
1267
                                           VOICE_LIST3[_settings.voice])
1268
                rset = RadioSetting("voice", "Voice Annumciation", rs)
1269
                basic.append(rset)
1270

    
1271
            if self.MODEL == "RB26" or self.MODEL == "RB23":
1272
                rs = RadioSettingValueList(VOICE_LIST2,
1273
                                           VOICE_LIST2[_settings.voice])
1274
                rset = RadioSetting("voice", "Voice Annumciation", rs)
1275
                basic.append(rset)
1276

    
1277
            if self.MODEL == "RB26":
1278
                rs = RadioSettingValueBoolean(not _settings.chnumberd)
1279
                rset = RadioSetting("chnumberd", "Channel Number Enable", rs)
1280
                basic.append(rset)
1281

    
1282
            rs = RadioSettingValueBoolean(_settings.save)
1283
            rset = RadioSetting("save", "Battery Save", rs)
1284
            basic.append(rset)
1285

    
1286
            rs = RadioSettingValueBoolean(_settings.beep)
1287
            rset = RadioSetting("beep", "Beep", rs)
1288
            basic.append(rset)
1289

    
1290
            if self.MODEL == "RB26" or self.MODEL == "RB23":
1291
                rs = RadioSettingValueBoolean(not _settings.tail)
1292
                rset = RadioSetting("tail", "QT/DQT Tail", rs)
1293
                basic.append(rset)
1294

    
1295
            if self.MODEL != "AR-63" and self.MODEL != "RT40B":
1296
                rs = RadioSettingValueList(SAVE_LIST,
1297
                                           SAVE_LIST[_settings.savem])
1298
                rset = RadioSetting("savem", "Battery Save Mode", rs)
1299
                basic.append(rset)
1300

    
1301
            if self.MODEL != "RT19" and self.MODEL != "RT619" and \
1302
                    self.MODEL != "AR-63" and \
1303
                    self.MODEL != "RT40B":
1304
                rs = RadioSettingValueList(GAIN_LIST,
1305
                                           GAIN_LIST[_settings.gain])
1306
                rset = RadioSetting("gain", "MIC Gain", rs)
1307
                basic.append(rset)
1308

    
1309
                rs = RadioSettingValueList(WARN_LIST,
1310
                                           WARN_LIST[_settings.warn])
1311
                rset = RadioSetting("warn", "Warn Mode", rs)
1312
                basic.append(rset)
1313

    
1314
            if self.MODEL == "RB26" or self.MODEL == "RB23":
1315
                rs = RadioSettingValueBoolean(_settings3.vox)
1316
                rset = RadioSetting("settings3.vox", "Vox Function", rs)
1317
                basic.append(rset)
1318

    
1319
                rs = RadioSettingValueList(VOXL_LIST,
1320
                                           VOXL_LIST[_settings3.voxl])
1321
                rset = RadioSetting("settings3.voxl", "Vox Level", rs)
1322
                basic.append(rset)
1323

    
1324
                rs = RadioSettingValueList(VOXD_LIST,
1325
                                           VOXD_LIST[_settings3.voxd])
1326
                rset = RadioSetting("settings3.voxd", "Vox Delay", rs)
1327
                basic.append(rset)
1328

    
1329
                if self.MODEL == "RB26":
1330
                    rs = RadioSettingValueList(PFKEY_LIST,
1331
                                               PFKEY_LIST[_settings.pf1])
1332
                    rset = RadioSetting("pf1", "PF1 Key Set", rs)
1333
                    basic.append(rset)
1334

    
1335
                    rs = RadioSettingValueList(PFKEY_LIST,
1336
                                               PFKEY_LIST[_settings.pf2])
1337
                    rset = RadioSetting("pf2", "PF2 Key Set", rs)
1338
                    basic.append(rset)
1339
                elif self.MODEL == "RB23":
1340
                    def apply_pfkey_listvalue(setting, obj):
1341
                        LOG.debug("Setting value: " + str(setting.value) +
1342
                                  " from list")
1343
                        val = str(setting.value)
1344
                        index = PFKEY23_CHOICES.index(val)
1345
                        val = PFKEY23_VALUES[index]
1346
                        obj.set_value(val)
1347

    
1348
                    if _settings.pf1 in PFKEY23_VALUES:
1349
                        idx = PFKEY23_VALUES.index(_settings.pf1)
1350
                    else:
1351
                        idx = PFKEY23_VALUES.index(0x01)
1352
                    rs = RadioSettingValueList(PFKEY23_CHOICES,
1353
                                               PFKEY23_CHOICES[idx])
1354
                    rset = RadioSetting("settings.pf1", "PF1 Key Function", rs)
1355
                    rset.set_apply_callback(apply_pfkey_listvalue,
1356
                                            _settings.pf1)
1357
                    basic.append(rset)
1358

    
1359
                    if _settings.pf2 in PFKEY23_VALUES:
1360
                        idx = PFKEY23_VALUES.index(_settings.pf2)
1361
                    else:
1362
                        idx = PFKEY23_VALUES.index(0x03)
1363
                    rs = RadioSettingValueList(PFKEY23_CHOICES,
1364
                                               PFKEY23_CHOICES[idx])
1365
                    rset = RadioSetting("settings.pf2", "PF2 Key Function", rs)
1366
                    rset.set_apply_callback(apply_pfkey_listvalue,
1367
                                            _settings.pf2)
1368
                    basic.append(rset)
1369

    
1370
                rs = RadioSettingValueInteger(1, 30, _settings2.chnumber + 1)
1371
                rset = RadioSetting("settings2.chnumber", "Channel Number", rs)
1372
                basic.append(rset)
1373

    
1374
            if self.MODEL == "RT76":
1375
                rs = RadioSettingValueBoolean(_settings.vox)
1376
                rset = RadioSetting("vox", "Vox Function", rs)
1377
                basic.append(rset)
1378

    
1379
                rs = RadioSettingValueList(VOXL_LIST,
1380
                                           VOXL_LIST[_settings.voxl])
1381
                rset = RadioSetting("voxl", "Vox Level", rs)
1382
                basic.append(rset)
1383

    
1384
                rs = RadioSettingValueList(VOXD_LIST,
1385
                                           VOXD_LIST[_settings.voxd])
1386
                rset = RadioSetting("voxd", "Vox Delay", rs)
1387
                basic.append(rset)
1388

    
1389
                rs = RadioSettingValueInteger(1, 30, _settings.chnumber + 1)
1390
                rset = RadioSetting("chnumber", "Channel Number", rs)
1391
                basic.append(rset)
1392

    
1393
            if self.MODEL == "AR-63":
1394
                rs = RadioSettingValueBoolean(_settings.warn)
1395
                rset = RadioSetting("warn", "Warn", rs)
1396
                basic.append(rset)
1397

    
1398
                rs = RadioSettingValueBoolean(_settings.scan)
1399
                rset = RadioSetting("scan", "Scan", rs)
1400
                basic.append(rset)
1401

    
1402
                rs = RadioSettingValueList(HOP_LIST,
1403
                                           HOP_LIST[_settings.hop])
1404
                rset = RadioSetting("hop", "Hop Mode", rs)
1405
                basic.append(rset)
1406

    
1407
                rs = RadioSettingValueList(TAIL_LIST,
1408
                                           TAIL_LIST[_settings.tailmode])
1409
                rset = RadioSetting("tailmode", "DCS Tail Mode", rs)
1410
                basic.append(rset)
1411

    
1412
                rs = RadioSettingValueBoolean(_settings.vox)
1413
                rset = RadioSetting("vox", "Vox Function", rs)
1414
                basic.append(rset)
1415

    
1416
                rs = RadioSettingValueList(VOXL_LIST,
1417
                                           VOXL_LIST[_settings.voxl])
1418
                rset = RadioSetting("voxl", "Vox Level", rs)
1419
                basic.append(rset)
1420

    
1421
                rs = RadioSettingValueList(VOXD_LIST,
1422
                                           VOXD_LIST[_settings.voxd])
1423
                rset = RadioSetting("voxd", "Vox Delay", rs)
1424
                basic.append(rset)
1425

    
1426
            if self.MODEL == "RT40B":
1427
                rs = RadioSettingValueList(VOICE_LIST,
1428
                                           VOICE_LIST[_settings.voice])
1429
                rset = RadioSetting("voice", "Voice Prompts", rs)
1430
                basic.append(rset)
1431

    
1432
                rs = RadioSettingValueList(SAVEM_LIST,
1433
                                           SAVEM_LIST[_settings.savem])
1434
                rset = RadioSetting("savem", "Battery Save Mode", rs)
1435
                basic.append(rset)
1436

    
1437
                rs = RadioSettingValueBoolean(_settings.pttstone)
1438
                rset = RadioSetting("pttstone", "PTT Start Tone", rs)
1439
                basic.append(rset)
1440

    
1441
                rs = RadioSettingValueBoolean(_settings.pttetone)
1442
                rset = RadioSetting("pttetone", "PTT End Tone", rs)
1443
                basic.append(rset)
1444

    
1445
                rs = RadioSettingValueBoolean(_settings.vox)
1446
                rset = RadioSetting("vox", "Vox Function", rs)
1447
                basic.append(rset)
1448

    
1449
                rs = RadioSettingValueList(VOXL_LIST,
1450
                                           VOXL_LIST[_settings.voxl])
1451
                rset = RadioSetting("voxl", "Vox Level", rs)
1452
                basic.append(rset)
1453

    
1454
                rs = RadioSettingValueList(VOXD_LIST,
1455
                                           VOXD_LIST[_settings.voxd])
1456
                rset = RadioSetting("voxd", "Vox Delay", rs)
1457
                basic.append(rset)
1458

    
1459
        return top
1460

    
1461
    def set_settings(self, settings):
1462
        for element in settings:
1463
            if not isinstance(element, RadioSetting):
1464
                self.set_settings(element)
1465
                continue
1466
            else:
1467
                try:
1468
                    if "." in element.get_name():
1469
                        bits = element.get_name().split(".")
1470
                        obj = self._memobj
1471
                        for bit in bits[:-1]:
1472
                            obj = getattr(obj, bit)
1473
                        setting = bits[-1]
1474
                    else:
1475
                        obj = self._memobj.settings
1476
                        setting = element.get_name()
1477

    
1478
                    if element.has_apply_callback():
1479
                        LOG.debug("Using apply callback")
1480
                        element.run_apply_callback()
1481
                    elif setting == "channel":
1482
                        setattr(obj, setting, int(element.value) - 1)
1483
                    elif setting == "chnumber":
1484
                        setattr(obj, setting, int(element.value) - 1)
1485
                    elif setting == "chnumberd":
1486
                        setattr(obj, setting, not int(element.value))
1487
                    elif setting == "tail":
1488
                        setattr(obj, setting, not int(element.value))
1489
                    elif setting == "voicel":
1490
                        setattr(obj, setting, int(element.value) - 1)
1491
                    elif element.value.get_mutable():
1492
                        LOG.debug("Setting %s = %s" % (setting, element.value))
1493
                        setattr(obj, setting, element.value)
1494
                except Exception as e:
1495
                    LOG.debug(element.get_name())
1496
                    raise
1497

    
1498
    @classmethod
1499
    def match_model(cls, filedata, filename):
1500
        if cls.MODEL == "RT21":
1501
            # The RT21 is pre-metadata, so do old-school detection
1502
            match_size = False
1503
            match_model = False
1504

    
1505
            # testing the file data size
1506
            if len(filedata) in [0x0400, ]:
1507
                match_size = True
1508

    
1509
            # testing the model fingerprint
1510
            match_model = model_match(cls, filedata)
1511

    
1512
            if match_size and match_model:
1513
                return True
1514
            else:
1515
                return False
1516
        else:
1517
            # Radios that have always been post-metadata, so never do
1518
            # old-school detection
1519
            return False
1520

    
1521

    
1522
@directory.register
1523
class RB17ARadio(RT21Radio):
1524
    """RETEVIS RB17A"""
1525
    VENDOR = "Retevis"
1526
    MODEL = "RB17A"
1527
    BAUD_RATE = 9600
1528
    BLOCK_SIZE = 0x40
1529
    BLOCK_SIZE_UP = 0x10
1530

    
1531
    POWER_LEVELS = [chirp_common.PowerLevel("High", watts=5.00),
1532
                    chirp_common.PowerLevel("Low", watts=0.50)]
1533

    
1534
    _magic = "PROA8US"
1535
    _fingerprint = "P3217s\xF8\xFF"
1536
    _upper = 30
1537
    _skipflags = True
1538
    _reserved = False
1539
    _gmrs = True
1540

    
1541
    _ranges = [
1542
               (0x0000, 0x0300),
1543
              ]
1544
    _memsize = 0x0300
1545

    
1546
    def process_mmap(self):
1547
        self._memobj = bitwise.parse(MEM_FORMAT_RB17A, self._mmap)
1548

    
1549

    
1550
@directory.register
1551
class RB26Radio(RT21Radio):
1552
    """RETEVIS RB26"""
1553
    VENDOR = "Retevis"
1554
    MODEL = "RB26"
1555
    BAUD_RATE = 9600
1556
    BLOCK_SIZE = 0x20
1557
    BLOCK_SIZE_UP = 0x10
1558

    
1559
    DTCS_CODES = sorted(chirp_common.DTCS_CODES + [645])
1560
    POWER_LEVELS = [chirp_common.PowerLevel("High", watts=3.00),
1561
                    chirp_common.PowerLevel("Low", watts=0.50)]
1562

    
1563
    _magic = "PHOGR" + "\x01" + "0"
1564
    _fingerprint = "P32073" + "\x02\xFF"
1565
    _upper = 30
1566
    _ack_1st_block = False
1567
    _skipflags = True
1568
    _reserved = True
1569
    _gmrs = True
1570

    
1571
    _ranges = [
1572
               (0x0000, 0x0320),
1573
              ]
1574
    _memsize = 0x0320
1575

    
1576
    def process_mmap(self):
1577
        self._memobj = bitwise.parse(MEM_FORMAT_RB26, self._mmap)
1578

    
1579

    
1580
@directory.register
1581
class RT76Radio(RT21Radio):
1582
    """RETEVIS RT76"""
1583
    VENDOR = "Retevis"
1584
    MODEL = "RT76"
1585
    BAUD_RATE = 9600
1586
    BLOCK_SIZE = 0x20
1587
    BLOCK_SIZE_UP = 0x10
1588

    
1589
    DTCS_CODES = sorted(chirp_common.DTCS_CODES + [645])
1590
    POWER_LEVELS = [chirp_common.PowerLevel("High", watts=5.00),
1591
                    chirp_common.PowerLevel("Low", watts=0.50)]
1592

    
1593
    _magic = "PHOGR\x14\xD4"
1594
    _fingerprint = "P32073" + "\x02\xFF"
1595
    _upper = 30
1596
    _ack_1st_block = False
1597
    _skipflags = False
1598
    _reserved = True
1599
    _gmrs = True
1600

    
1601
    _ranges = [
1602
               (0x0000, 0x01E0),
1603
              ]
1604
    _memsize = 0x01E0
1605

    
1606
    def process_mmap(self):
1607
        self._memobj = bitwise.parse(MEM_FORMAT_RT76, self._mmap)
1608

    
1609

    
1610
@directory.register
1611
class RT29UHFRadio(RT21Radio):
1612
    """RETEVIS RT29UHF"""
1613
    VENDOR = "Retevis"
1614
    MODEL = "RT29_UHF"
1615
    BLOCK_SIZE = 0x40
1616
    BLOCK_SIZE_UP = 0x10
1617

    
1618
    TXPOWER_MED = 0x00
1619
    TXPOWER_HIGH = 0x01
1620
    TXPOWER_LOW = 0x02
1621

    
1622
    POWER_LEVELS = [chirp_common.PowerLevel("High", watts=10.00),
1623
                    chirp_common.PowerLevel("Mid", watts=5.00),
1624
                    chirp_common.PowerLevel("Low", watts=1.00)]
1625

    
1626
    _magic = "PROHRAM"
1627
    _fingerprint = "P3207" + "\x13\xF8\xFF"  # UHF model
1628
    _upper = 16
1629
    _skipflags = True
1630
    _reserved = False
1631

    
1632
    _ranges = [
1633
               (0x0000, 0x0300),
1634
              ]
1635
    _memsize = 0x0400
1636

    
1637
    def process_mmap(self):
1638
        self._memobj = bitwise.parse(MEM_FORMAT_RT29, self._mmap)
1639

    
1640

    
1641
@directory.register
1642
class RT29VHFRadio(RT29UHFRadio):
1643
    """RETEVIS RT29VHF"""
1644
    VENDOR = "Retevis"
1645
    MODEL = "RT29_VHF"
1646

    
1647
    TXPOWER_MED = 0x00
1648
    TXPOWER_HIGH = 0x01
1649
    TXPOWER_LOW = 0x02
1650

    
1651
    POWER_LEVELS = [chirp_common.PowerLevel("High", watts=10.00),
1652
                    chirp_common.PowerLevel("Mid", watts=5.00),
1653
                    chirp_common.PowerLevel("Low", watts=1.00)]
1654

    
1655
    VALID_BANDS = [(136000000, 174000000)]
1656

    
1657
    _magic = "PROHRAM"
1658
    _fingerprint = "P2207" + "\x01\xF8\xFF"  # VHF model
1659

    
1660

    
1661
@directory.register
1662
class RB23Radio(RT21Radio):
1663
    """RETEVIS RB23"""
1664
    VENDOR = "Retevis"
1665
    MODEL = "RB23"
1666
    BLOCK_SIZE = 0x20
1667
    BLOCK_SIZE_UP = 0x10
1668

    
1669
    POWER_LEVELS = [chirp_common.PowerLevel("High", watts=5.00),
1670
                    chirp_common.PowerLevel("Low", watts=0.50)]
1671

    
1672
    _magic = "PHOGR" + "\x01" + "0"
1673
    _fingerprint = "P32073" + "\x02\xFF"
1674
    _upper = 30
1675
    _ack_1st_block = False
1676
    _skipflags = True
1677
    _reserved = True
1678
    _gmrs = True
1679

    
1680
    _ranges = [
1681
               (0x0000, 0x0320),
1682
              ]
1683
    _memsize = 0x0320
1684

    
1685
    def process_mmap(self):
1686
        self._memobj = bitwise.parse(MEM_FORMAT_RB26, self._mmap)
1687

    
1688

    
1689
@directory.register
1690
class RT19Radio(RT21Radio):
1691
    """RETEVIS RT19"""
1692
    VENDOR = "Retevis"
1693
    MODEL = "RT19"
1694
    BLOCK_SIZE = 0x20
1695
    BLOCK_SIZE_UP = 0x10
1696

    
1697
    POWER_LEVELS = [chirp_common.PowerLevel("High", watts=2.00),
1698
                    chirp_common.PowerLevel("Low", watts=0.50)]
1699

    
1700
    _magic = "PHOGRQ^"
1701
    _fingerprint = "P32073" + "\x02\xFF"
1702
    _upper = 22
1703
    _mem_params = (_upper,  # number of channels
1704
                   0x160,   # memory start
1705
                   _upper   # number of freqhops
1706
                   )
1707
    _ack_1st_block = False
1708
    _skipflags = False
1709
    _reserved = True
1710
    _frs = True
1711

    
1712
    _ranges = [
1713
               (0x0000, 0x0180),
1714
              ]
1715
    _memsize = 0x0180
1716

    
1717
    def process_mmap(self):
1718
        self._memobj = bitwise.parse(MEM_FORMAT_RT19 % self._mem_params,
1719
                                     self._mmap)
1720

    
1721

    
1722
@directory.register
1723
class RT619Radio(RT19Radio):
1724
    """RETEVIS RT619"""
1725
    VENDOR = "Retevis"
1726
    MODEL = "RT619"
1727

    
1728
    POWER_LEVELS = [chirp_common.PowerLevel("High", watts=0.50),
1729
                    chirp_common.PowerLevel("Low", watts=0.49)]
1730

    
1731
    _magic = "PHOGRS]"
1732
    _fingerprint = "P32073" + "\x02\xFF"
1733
    _upper = 16
1734
    _mem_params = (_upper,  # number of channels
1735
                   0x100,   # memory start
1736
                   _upper   # number of freqhops
1737
                   )
1738
    _pmr = True
1739

    
1740
    _ranges = [
1741
               (0x0000, 0x0120),
1742
              ]
1743
    _memsize = 0x0120
1744

    
1745

    
1746
@directory.register
1747
class AR63Radio(RT21Radio):
1748
    """ABBREE AR-63"""
1749
    VENDOR = "Abbree"
1750
    MODEL = "AR-63"
1751
    BLOCK_SIZE = 0x20
1752
    BLOCK_SIZE_UP = 0x10
1753

    
1754
    POWER_LEVELS = [chirp_common.PowerLevel("High", watts=3.00),
1755
                    chirp_common.PowerLevel("Low", watts=1.00)]
1756

    
1757
    _magic = "PHOGR\xF5\x9A"
1758
    _fingerprint = "P32073" + "\x03\xFF"
1759
    _upper = 16
1760
    _ack_1st_block = False
1761
    _skipflags = True
1762
    _reserved = True
1763
    _gmrs = False
1764

    
1765
    _ranges = [
1766
               (0x0000, 0x0140),
1767
              ]
1768
    _memsize = 0x0140
1769

    
1770
    def process_mmap(self):
1771
        self._memobj = bitwise.parse(MEM_FORMAT_RT76, self._mmap)
1772

    
1773

    
1774
@directory.register
1775
class RT40BRadio(RT21Radio):
1776
    """RETEVIS RT40B"""
1777
    VENDOR = "Retevis"
1778
    MODEL = "RT40B"
1779
    BLOCK_SIZE = 0x20
1780
    BLOCK_SIZE_UP = 0x10
1781

    
1782
    DTCS_CODES = sorted(chirp_common.DTCS_CODES + [645])
1783
    POWER_LEVELS = [chirp_common.PowerLevel("High", watts=2.00),
1784
                    chirp_common.PowerLevel("Low", watts=0.50)]
1785

    
1786
    VALID_BANDS = [(400000000, 480000000)]
1787

    
1788
    _magic = "PHOGRH" + "\x5C"
1789
    _fingerprint = "P32073" + "\x02\xFF"
1790
    _upper = 22
1791
    _mem_params = (_upper,  # number of channels
1792
                   )
1793
    _ack_1st_block = False
1794
    _skipflags = True
1795
    _reserved = True
1796
    _gmrs = True
1797
    _echo = True
1798

    
1799
    _ranges = [
1800
               (0x0000, 0x0160),
1801
              ]
1802
    _memsize = 0x0160
1803

    
1804
    def process_mmap(self):
1805
        self._memobj = bitwise.parse(MEM_FORMAT_RT40B % self._mem_params,
1806
                                     self._mmap)
(8-8/8)