Project

General

Profile

Bug #10085 » retevis_rt21_multiple_id_string_support.py

Jim Unroe, 10/18/2022 06:10 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
    # check if ident is OK
509
    itis = False
510
    for fp in radio._fingerprint:
511
        if fp in ident:
512
            # got it!
513
            itis = True
514

    
515
            break
516

    
517
    if itis is False:
518
        LOG.debug("Incorrect model ID, got this:\n\n" + util.hexprint(ident))
519
        raise errors.RadioError("Radio identification failed.")
520

    
521
    try:
522
        serial.write(CMD_ACK)
523
        if radio._echo:
524
            serial.read(1)  # Chew the echo
525
        ack = serial.read(1)
526
    except:
527
        raise errors.RadioError("Error communicating with radio")
528

    
529
    if ack != CMD_ACK:
530
        raise errors.RadioError("Radio refused to enter programming mode")
531

    
532

    
533
def _exit_programming_mode(radio):
534
    serial = radio.pipe
535
    try:
536
        serial.write("E")
537
        if radio._echo:
538
            chew = serial.read(1)  # Chew the echo
539
    except:
540
        raise errors.RadioError("Radio refused to exit programming mode")
541

    
542

    
543
def _read_block(radio, block_addr, block_size):
544
    serial = radio.pipe
545

    
546
    cmd = struct.pack(">cHb", 'R', block_addr, block_size)
547
    expectedresponse = "W" + cmd[1:]
548
    LOG.debug("Reading block %04x..." % (block_addr))
549

    
550
    try:
551
        serial.write(cmd)
552
        if radio._echo:
553
            serial.read(4)  # Chew the echo
554
        response = serial.read(4 + block_size)
555
        if response[:4] != expectedresponse:
556
            raise Exception("Error reading block %04x." % (block_addr))
557

    
558
        block_data = response[4:]
559

    
560
        if block_addr != 0 or radio._ack_1st_block:
561
            serial.write(CMD_ACK)
562
            if radio._echo:
563
                serial.read(1)  # Chew the echo
564
            ack = serial.read(1)
565
    except:
566
        raise errors.RadioError("Failed to read block at %04x" % block_addr)
567

    
568
    if block_addr != 0 or radio._ack_1st_block:
569
        if ack != CMD_ACK:
570
            raise Exception("No ACK reading block %04x." % (block_addr))
571

    
572
    return block_data
573

    
574

    
575
def _write_block(radio, block_addr, block_size):
576
    serial = radio.pipe
577

    
578
    cmd = struct.pack(">cHb", 'W', block_addr, block_size)
579
    data = radio.get_mmap()[block_addr:block_addr + block_size]
580

    
581
    LOG.debug("Writing Data:")
582
    LOG.debug(util.hexprint(cmd + data))
583

    
584
    try:
585
        serial.write(cmd + data)
586
        if radio._echo:
587
            serial.read(4 + len(data))  # Chew the echo
588
        if serial.read(1) != CMD_ACK:
589
            raise Exception("No ACK")
590
    except:
591
        raise errors.RadioError("Failed to send block "
592
                                "to radio at %04x" % block_addr)
593

    
594

    
595
def do_download(radio):
596
    LOG.debug("download")
597
    _enter_programming_mode(radio)
598

    
599
    data = ""
600

    
601
    status = chirp_common.Status()
602
    status.msg = "Cloning from radio"
603

    
604
    status.cur = 0
605
    status.max = radio._memsize
606

    
607
    for addr in range(0, radio._memsize, radio.BLOCK_SIZE):
608
        status.cur = addr + radio.BLOCK_SIZE
609
        radio.status_fn(status)
610

    
611
        block = _read_block(radio, addr, radio.BLOCK_SIZE)
612
        data += block
613

    
614
        LOG.debug("Address: %04x" % addr)
615
        LOG.debug(util.hexprint(block))
616

    
617
    _exit_programming_mode(radio)
618

    
619
    return memmap.MemoryMap(data)
620

    
621

    
622
def do_upload(radio):
623
    status = chirp_common.Status()
624
    status.msg = "Uploading to radio"
625

    
626
    _enter_programming_mode(radio)
627

    
628
    status.cur = 0
629
    status.max = radio._memsize
630

    
631
    for start_addr, end_addr in radio._ranges:
632
        for addr in range(start_addr, end_addr, radio.BLOCK_SIZE_UP):
633
            status.cur = addr + radio.BLOCK_SIZE_UP
634
            radio.status_fn(status)
635
            _write_block(radio, addr, radio.BLOCK_SIZE_UP)
636

    
637
    _exit_programming_mode(radio)
638

    
639

    
640
def model_match(cls, data):
641
    """Match the opened/downloaded image to the correct version"""
642
    rid = data[0x01B8:0x01BE]
643

    
644
    return rid.startswith("P3207")
645

    
646

    
647
@directory.register
648
class RT21Radio(chirp_common.CloneModeRadio):
649
    """RETEVIS RT21"""
650
    VENDOR = "Retevis"
651
    MODEL = "RT21"
652
    BAUD_RATE = 9600
653
    BLOCK_SIZE = 0x10
654
    BLOCK_SIZE_UP = 0x10
655

    
656
    DTCS_CODES = sorted(chirp_common.DTCS_CODES + [17, 50, 645])
657
    POWER_LEVELS = [chirp_common.PowerLevel("High", watts=2.50),
658
                    chirp_common.PowerLevel("Low", watts=1.00)]
659

    
660
    VALID_BANDS = [(400000000, 480000000)]
661

    
662
    _magic = "PRMZUNE"
663
    _fingerprint = ["P3207s\xF8\xFF",]
664
    _upper = 16
665
    _ack_1st_block = True
666
    _skipflags = True
667
    _reserved = False
668
    _gmrs = _frs = _pmr = False
669
    _echo = False
670

    
671
    _ranges = [
672
               (0x0000, 0x0400),
673
              ]
674
    _memsize = 0x0400
675

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

    
702
        return rf
703

    
704
    def process_mmap(self):
705
        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
706

    
707
    def sync_in(self):
708
        """Download from radio"""
709
        try:
710
            data = do_download(self)
711
        except errors.RadioError:
712
            # Pass through any real errors we raise
713
            raise
714
        except:
715
            # If anything unexpected happens, make sure we raise
716
            # a RadioError and log the problem
717
            LOG.exception('Unexpected error during download')
718
            raise errors.RadioError('Unexpected error communicating '
719
                                    'with the radio')
720
        self._mmap = data
721
        self.process_mmap()
722

    
723
    def sync_out(self):
724
        """Upload to radio"""
725
        try:
726
            do_upload(self)
727
        except:
728
            # If anything unexpected happens, make sure we raise
729
            # a RadioError and log the problem
730
            LOG.exception('Unexpected error during upload')
731
            raise errors.RadioError('Unexpected error communicating '
732
                                    'with the radio')
733

    
734
    def get_raw_memory(self, number):
735
        return repr(self._memobj.memory[number - 1])
736

    
737
    def _get_tone(self, _mem, mem):
738
        def _get_dcs(val):
739
            code = int("%03o" % (val & 0x07FF))
740
            pol = (val & 0x8000) and "R" or "N"
741
            return code, pol
742

    
743
        tpol = False
744
        if _mem.tx_tone != 0xFFFF and _mem.tx_tone > 0x2000:
745
            tcode, tpol = _get_dcs(_mem.tx_tone)
746
            mem.dtcs = tcode
747
            txmode = "DTCS"
748
        elif _mem.tx_tone != 0xFFFF:
749
            mem.rtone = _mem.tx_tone / 10.0
750
            txmode = "Tone"
751
        else:
752
            txmode = ""
753

    
754
        rpol = False
755
        if _mem.rx_tone != 0xFFFF and _mem.rx_tone > 0x2000:
756
            rcode, rpol = _get_dcs(_mem.rx_tone)
757
            mem.rx_dtcs = rcode
758
            rxmode = "DTCS"
759
        elif _mem.rx_tone != 0xFFFF:
760
            mem.ctone = _mem.rx_tone / 10.0
761
            rxmode = "Tone"
762
        else:
763
            rxmode = ""
764

    
765
        if txmode == "Tone" and not rxmode:
766
            mem.tmode = "Tone"
767
        elif txmode == rxmode and txmode == "Tone" and mem.rtone == mem.ctone:
768
            mem.tmode = "TSQL"
769
        elif txmode == rxmode and txmode == "DTCS" and mem.dtcs == mem.rx_dtcs:
770
            mem.tmode = "DTCS"
771
        elif rxmode or txmode:
772
            mem.tmode = "Cross"
773
            mem.cross_mode = "%s->%s" % (txmode, rxmode)
774

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

    
778
        LOG.debug("Got TX %s (%i) RX %s (%i)" %
779
                  (txmode, _mem.tx_tone, rxmode, _mem.rx_tone))
780

    
781
    def get_memory(self, number):
782
        if self._skipflags:
783
            bitpos = (1 << ((number - 1) % 8))
784
            bytepos = ((number - 1) / 8)
785
            LOG.debug("bitpos %s" % bitpos)
786
            LOG.debug("bytepos %s" % bytepos)
787
            _skp = self._memobj.skipflags[bytepos]
788

    
789
        mem = chirp_common.Memory()
790

    
791
        mem.number = number
792

    
793
        if self.MODEL == "RB17A":
794
            if mem.number < 17:
795
                _mem = self._memobj.lomems[number - 1]
796
            else:
797
                _mem = self._memobj.himems[number - 17]
798
        else:
799
            _mem = self._memobj.memory[number - 1]
800

    
801
        if self._reserved:
802
            _rsvd = _mem.reserved.get_raw()
803

    
804
        mem.freq = int(_mem.rxfreq) * 10
805

    
806
        # We'll consider any blank (i.e. 0MHz frequency) to be empty
807
        if mem.freq == 0:
808
            mem.empty = True
809
            return mem
810

    
811
        if _mem.rxfreq.get_raw() == "\xFF\xFF\xFF\xFF":
812
            mem.freq = 0
813
            mem.empty = True
814
            return mem
815

    
816
        if int(_mem.rxfreq) == int(_mem.txfreq):
817
            mem.duplex = ""
818
            mem.offset = 0
819
        else:
820
            mem.duplex = int(_mem.rxfreq) > int(_mem.txfreq) and "-" or "+"
821
            mem.offset = abs(int(_mem.rxfreq) - int(_mem.txfreq)) * 10
822

    
823
        mem.mode = _mem.wide and "FM" or "NFM"
824

    
825
        self._get_tone(_mem, mem)
826

    
827
        if self.MODEL == "RT29_UHF" or self.MODEL == "RT29_VHF":
828
            # set the power level
829
            if _mem.txpower == self.TXPOWER_LOW:
830
                mem.power = self.POWER_LEVELS[2]
831
            elif _mem.txpower == self.TXPOWER_MED:
832
                mem.power = self.POWER_LEVELS[1]
833
            elif _mem.txpower == self.TXPOWER_HIGH:
834
                mem.power = self.POWER_LEVELS[0]
835
            else:
836
                LOG.error('%s: get_mem: unhandled power level: 0x%02x' %
837
                          (mem.name, _mem.txpower))
838
        else:
839
            mem.power = self.POWER_LEVELS[1 - _mem.highpower]
840

    
841
        if self._skipflags:
842
            mem.skip = "" if (_skp & bitpos) else "S"
843
            LOG.debug("mem.skip %s" % mem.skip)
844

    
845
        mem.extra = RadioSettingGroup("Extra", "extra")
846

    
847
        if self.MODEL == "RT21" or self.MODEL == "RB17A" or \
848
                self.MODEL == "RT29_UHF" or self.MODEL == "RT29_VHF":
849
            rs = RadioSettingValueList(BCL_LIST, BCL_LIST[_mem.bcl])
850
            rset = RadioSetting("bcl", "Busy Channel Lockout", rs)
851
            mem.extra.append(rset)
852

    
853
            rs = RadioSettingValueInteger(1, 8, _mem.scramble_type + 1)
854
            rset = RadioSetting("scramble_type", "Scramble Type", rs)
855
            mem.extra.append(rset)
856

    
857
            if self.MODEL == "RB17A":
858
                rs = RadioSettingValueList(CDCSS_LIST, CDCSS_LIST[_mem.cdcss])
859
                rset = RadioSetting("cdcss", "Cdcss Mode", rs)
860
                mem.extra.append(rset)
861

    
862
            if self.MODEL == "RT29_UHF" or self.MODEL == "RT29_VHF":
863
                rs = RadioSettingValueList(CDCSS2_LIST,
864
                                           CDCSS2_LIST[_mem.cdcss])
865
                rset = RadioSetting("cdcss", "Cdcss Mode", rs)
866
                mem.extra.append(rset)
867

    
868
            if self.MODEL == "RB17A" or self.MODEL == "RT29_UHF" or \
869
                    self.MODEL == "RT29_VHF":
870
                rs = RadioSettingValueBoolean(_mem.compander)
871
                rset = RadioSetting("compander", "Compander", rs)
872
                mem.extra.append(rset)
873

    
874
        if self.MODEL == "RB26" or self.MODEL == "RT76" \
875
                or self.MODEL == "RB23" or self.MODEL == "AR-63":
876
            if self.MODEL == "RB26" or self.MODEL == "RB23":
877
                rs = RadioSettingValueBoolean(_mem.bcl)
878
                rset = RadioSetting("bcl", "Busy Channel Lockout", rs)
879
                mem.extra.append(rset)
880

    
881
            rs = RadioSettingValueBoolean(_mem.compander)
882
            rset = RadioSetting("compander", "Compander", rs)
883
            mem.extra.append(rset)
884

    
885
            if self.MODEL == "AR-63":
886
                rs = RadioSettingValueList(SCRAMBLE_LIST,
887
                                           SCRAMBLE_LIST[_mem.scramble])
888
                rset = RadioSetting("scramble", "Scramble", rs)
889
                mem.extra.append(rset)
890

    
891
                rs = RadioSettingValueBoolean(not _mem.hop)
892
                rset = RadioSetting("hop", "Frequency Hop", rs)
893
                mem.extra.append(rset)
894

    
895
        if self.MODEL == "RT19" or self.MODEL == "RT619":
896
            _freqhops = self._memobj.freqhops[number - 1]
897

    
898
            rs = RadioSettingValueList(FUNCTION_LIST,
899
                                       FUNCTION_LIST[_mem.function])
900
            rset = RadioSetting("function", "Function", rs)
901
            mem.extra.append(rset)
902

    
903
            rs = RadioSettingValueInteger(1, 8, _mem.scramble_type + 1)
904
            rset = RadioSetting("scramble_type", "Scramble Type", rs)
905
            mem.extra.append(rset)
906

    
907
            rs = RadioSettingValueList(FREQHOP_LIST,
908
                                       FREQHOP_LIST[_freqhops.freqhop])
909
            rset = RadioSetting("freqhop", "Frequency Hop", rs)
910
            mem.extra.append(rset)
911

    
912
        if self.MODEL == "RT40B":
913
            rs = RadioSettingValueBoolean(_mem.compander)
914
            rset = RadioSetting("compander", "Compander", rs)
915
            mem.extra.append(rset)
916

    
917
        return mem
918

    
919
    def _set_tone(self, mem, _mem):
920
        def _set_dcs(code, pol):
921
            val = int("%i" % code, 8) + 0x2000
922
            if pol == "R":
923
                val += 0x8000
924
            return val
925

    
926
        rx_mode = tx_mode = None
927
        rx_tone = tx_tone = 0xFFFF
928

    
929
        if mem.tmode == "Tone":
930
            tx_mode = "Tone"
931
            rx_mode = None
932
            tx_tone = int(mem.rtone * 10)
933
        elif mem.tmode == "TSQL":
934
            rx_mode = tx_mode = "Tone"
935
            rx_tone = tx_tone = int(mem.ctone * 10)
936
        elif mem.tmode == "DTCS":
937
            tx_mode = rx_mode = "DTCS"
938
            tx_tone = _set_dcs(mem.dtcs, mem.dtcs_polarity[0])
939
            rx_tone = _set_dcs(mem.dtcs, mem.dtcs_polarity[1])
940
        elif mem.tmode == "Cross":
941
            tx_mode, rx_mode = mem.cross_mode.split("->")
942
            if tx_mode == "DTCS":
943
                tx_tone = _set_dcs(mem.dtcs, mem.dtcs_polarity[0])
944
            elif tx_mode == "Tone":
945
                tx_tone = int(mem.rtone * 10)
946
            if rx_mode == "DTCS":
947
                rx_tone = _set_dcs(mem.rx_dtcs, mem.dtcs_polarity[1])
948
            elif rx_mode == "Tone":
949
                rx_tone = int(mem.ctone * 10)
950

    
951
        _mem.rx_tone = rx_tone
952
        _mem.tx_tone = tx_tone
953

    
954
        LOG.debug("Set TX %s (%i) RX %s (%i)" %
955
                  (tx_mode, _mem.tx_tone, rx_mode, _mem.rx_tone))
956

    
957
    def set_memory(self, mem):
958
        if self._skipflags:
959
            bitpos = (1 << ((mem.number - 1) % 8))
960
            bytepos = ((mem.number - 1) / 8)
961
            LOG.debug("bitpos %s" % bitpos)
962
            LOG.debug("bytepos %s" % bytepos)
963
            _skp = self._memobj.skipflags[bytepos]
964

    
965
        if self.MODEL == "RB17A":
966
            if mem.number < 17:
967
                _mem = self._memobj.lomems[mem.number - 1]
968
            else:
969
                _mem = self._memobj.himems[mem.number - 17]
970
        elif self.MODEL == "RT19" or self.MODEL == "RT619":
971
            _mem = self._memobj.memory[mem.number - 1]
972
            _freqhops = self._memobj.freqhops[mem.number - 1]
973
        else:
974
            _mem = self._memobj.memory[mem.number - 1]
975

    
976
        if self._reserved:
977
            _rsvd = _mem.reserved.get_raw()
978

    
979
        if mem.empty:
980
            if self.MODEL == "RB26" or self.MODEL == "RT76" \
981
                    or self.MODEL == "RB23" \
982
                    or self.MODEL == "RT40B":
983
                _mem.set_raw("\xFF" * 13 + _rsvd)
984
            elif self.MODEL == "RT19" or self.MODEL == "RT619":
985
                _mem.set_raw("\xFF" * 13 + _rsvd)
986
                _freqhops.freqhop.set_raw("\x00")
987
            elif self.MODEL == "AR-63":
988
                _mem.set_raw("\xFF" * 13 + _rsvd)
989
            else:
990
                _mem.set_raw("\xFF" * (_mem.size() / 8))
991

    
992
            return
993

    
994
        if self.MODEL == "RB17A":
995
            _mem.set_raw("\x00" * 14 + "\xFF\xFF")
996
        elif self._reserved:
997
            _mem.set_raw("\x00" * 13 + _rsvd)
998
        elif self.MODEL == "AR-63":
999
            _mem.set_raw("\x00" * 13 + _rsvd)
1000
        else:
1001
            _mem.set_raw("\x00" * 13 + "\x30\x8F\xF8")
1002

    
1003
        if self._gmrs:
1004
            if mem.number >= 1 and mem.number <= 30:
1005
                GMRS_FREQ = int(GMRS_FREQS[mem.number - 1] * 1000000)
1006
                mem.freq = GMRS_FREQ
1007
                if mem.number <= 22:
1008
                    mem.duplex = ''
1009
                    mem.offset = 0
1010
                    if mem.number >= 8 and mem.number <= 14:
1011
                        mem.mode = "NFM"
1012
                        mem.power = self.POWER_LEVELS[1]
1013
                if mem.number > 22:
1014
                    mem.duplex = '+'
1015
                    mem.offset = 5000000
1016
        if self._frs:
1017
            if mem.number >= 1 and mem.number <= 22:
1018
                FRS_FREQ = int(FRS_FREQS[mem.number - 1] * 1000000)
1019
                mem.freq = FRS_FREQ
1020
                mem.mode = "NFM"
1021
                mem.duplex = ''
1022
                mem.offset = 0
1023
                if mem.number >= 8 and mem.number <= 14:
1024
                    mem.power = self.POWER_LEVELS[1]
1025
        if self._pmr:
1026
            PMR_FREQ = int(PMR_FREQS[mem.number - 1] * 1000000)
1027
            mem.freq = PMR_FREQ
1028
            mem.duplex = ''
1029
            mem.offset = 0
1030
            mem.mode = "NFM"
1031
            mem.power = self.POWER_LEVELS[1]
1032

    
1033
        _mem.rxfreq = mem.freq / 10
1034

    
1035
        if mem.duplex == "off":
1036
            for i in range(0, 4):
1037
                _mem.txfreq[i].set_raw("\xFF")
1038
        elif mem.duplex == "split":
1039
            _mem.txfreq = mem.offset / 10
1040
        elif mem.duplex == "+":
1041
            _mem.txfreq = (mem.freq + mem.offset) / 10
1042
        elif mem.duplex == "-":
1043
            _mem.txfreq = (mem.freq - mem.offset) / 10
1044
        else:
1045
            _mem.txfreq = mem.freq / 10
1046

    
1047
        _mem.wide = mem.mode == "FM"
1048

    
1049
        self._set_tone(mem, _mem)
1050

    
1051
        if self.MODEL == "RT29_UHF" or self.MODEL == "RT29_VHF":
1052
            # set the power level
1053
            if mem.power == self.POWER_LEVELS[2]:
1054
                _mem.txpower = self.TXPOWER_LOW
1055
            elif mem.power == self.POWER_LEVELS[1]:
1056
                _mem.txpower = self.TXPOWER_MED
1057
            elif mem.power == self.POWER_LEVELS[0]:
1058
                _mem.txpower = self.TXPOWER_HIGH
1059
            else:
1060
                LOG.error('%s: set_mem: unhandled power level: %s' %
1061
                          (mem.name, mem.power))
1062
        else:
1063
            _mem.highpower = mem.power == self.POWER_LEVELS[0]
1064

    
1065
        if self._skipflags:
1066
            if mem.skip != "S":
1067
                _skp |= bitpos
1068
            else:
1069
                _skp &= ~bitpos
1070
            LOG.debug("_skp %s" % _skp)
1071

    
1072
        for setting in mem.extra:
1073
            if setting.get_name() == "scramble_type":
1074
                setattr(_mem, setting.get_name(), int(setting.value) - 1)
1075
                if self.MODEL == "RT21":
1076
                    setattr(_mem, "scramble_type2", int(setting.value) - 1)
1077
            elif setting.get_name() == "freqhop":
1078
                setattr(_freqhops, setting.get_name(), setting.value)
1079
            elif setting.get_name() == "hop":
1080
                setattr(_mem, setting.get_name(), not int(setting.value))
1081
            else:
1082
                setattr(_mem, setting.get_name(), setting.value)
1083

    
1084
    def get_settings(self):
1085
        _settings = self._memobj.settings
1086
        basic = RadioSettingGroup("basic", "Basic Settings")
1087
        top = RadioSettings(basic)
1088

    
1089
        if self.MODEL == "RT21" or self.MODEL == "RB17A" or \
1090
                self.MODEL == "RT29_UHF" or self.MODEL == "RT29_VHF":
1091
            _keys = self._memobj.keys
1092

    
1093
            rs = RadioSettingValueList(TIMEOUTTIMER_LIST,
1094
                                       TIMEOUTTIMER_LIST[_settings.tot])
1095
            rset = RadioSetting("tot", "Time-out timer", rs)
1096
            basic.append(rset)
1097

    
1098
            rs = RadioSettingValueList(TOTALERT_LIST,
1099
                                       TOTALERT_LIST[_settings.totalert])
1100
            rset = RadioSetting("totalert", "TOT Pre-alert", rs)
1101
            basic.append(rset)
1102

    
1103
            rs = RadioSettingValueInteger(0, 9, _settings.squelch)
1104
            rset = RadioSetting("squelch", "Squelch Level", rs)
1105
            basic.append(rset)
1106

    
1107
            rs = RadioSettingValueList(VOICE_LIST, VOICE_LIST[_settings.voice])
1108
            rset = RadioSetting("voice", "Voice Annumciation", rs)
1109
            basic.append(rset)
1110

    
1111
            if self.MODEL == "RB17A":
1112
                rs = RadioSettingValueList(ALARM_LIST,
1113
                                           ALARM_LIST[_settings.alarm])
1114
                rset = RadioSetting("alarm", "Alarm Type", rs)
1115
                basic.append(rset)
1116

    
1117
            rs = RadioSettingValueBoolean(_settings.save)
1118
            rset = RadioSetting("save", "Battery Saver", rs)
1119
            basic.append(rset)
1120

    
1121
            rs = RadioSettingValueBoolean(_settings.use_scramble)
1122
            rset = RadioSetting("use_scramble", "Scramble", rs)
1123
            basic.append(rset)
1124

    
1125
            rs = RadioSettingValueBoolean(_settings.use_vox)
1126
            rset = RadioSetting("use_vox", "VOX", rs)
1127
            basic.append(rset)
1128

    
1129
            rs = RadioSettingValueList(VOX_LIST, VOX_LIST[_settings.vox])
1130
            rset = RadioSetting("vox", "VOX Gain", rs)
1131
            basic.append(rset)
1132

    
1133
            if self.MODEL == "RT29_UHF" or self.MODEL == "RT29_VHF":
1134
                rs = RadioSettingValueList(VOXD_LIST,
1135
                                           VOXD_LIST[_settings.voxd])
1136
                rset = RadioSetting("voxd", "Vox Delay", rs)
1137
                basic.append(rset)
1138

    
1139
            def apply_pf1_listvalue(setting, obj):
1140
                LOG.debug("Setting value: " + str(
1141
                          setting.value) + " from list")
1142
                val = str(setting.value)
1143
                index = PF1_CHOICES.index(val)
1144
                val = PF1_VALUES[index]
1145
                obj.set_value(val)
1146

    
1147
            if self.MODEL == "RT21":
1148
                if _keys.pf1 in PF1_VALUES:
1149
                    idx = PF1_VALUES.index(_keys.pf1)
1150
                else:
1151
                    idx = LIST_DTMF_SPECIAL_VALUES.index(0x04)
1152
                rs = RadioSettingValueList(PF1_CHOICES, PF1_CHOICES[idx])
1153
                rset = RadioSetting("keys.pf1", "PF1 Key Function", rs)
1154
                rset.set_apply_callback(apply_pf1_listvalue, _keys.pf1)
1155
                basic.append(rset)
1156

    
1157
            def apply_pf1_17a_listvalue(setting, obj):
1158
                LOG.debug("Setting value: " + str(
1159
                          setting.value) + " from list")
1160
                val = str(setting.value)
1161
                index = PF1_17A_CHOICES.index(val)
1162
                val = PF1_17A_VALUES[index]
1163
                obj.set_value(val)
1164

    
1165
            if self.MODEL == "RB17A":
1166
                if _keys.pf1 in PF1_17A_VALUES:
1167
                    idx = PF1_17A_VALUES.index(_keys.pf1)
1168
                else:
1169
                    idx = LIST_DTMF_SPECIAL_VALUES.index(0x04)
1170
                rs = RadioSettingValueList(PF1_17A_CHOICES,
1171
                                           PF1_17A_CHOICES[idx])
1172
                rset = RadioSetting("keys.pf1", "PF1 Key Function", rs)
1173
                rset.set_apply_callback(apply_pf1_17a_listvalue, _keys.pf1)
1174
                basic.append(rset)
1175

    
1176
            def apply_topkey_listvalue(setting, obj):
1177
                LOG.debug("Setting value: " + str(setting.value) +
1178
                          " from list")
1179
                val = str(setting.value)
1180
                index = TOPKEY_CHOICES.index(val)
1181
                val = TOPKEY_VALUES[index]
1182
                obj.set_value(val)
1183

    
1184
            if self.MODEL == "RB17A":
1185
                if _keys.topkey in TOPKEY_VALUES:
1186
                    idx = TOPKEY_VALUES.index(_keys.topkey)
1187
                else:
1188
                    idx = TOPKEY_VALUES.index(0x0C)
1189
                rs = RadioSettingValueList(TOPKEY_CHOICES, TOPKEY_CHOICES[idx])
1190
                rset = RadioSetting("keys.topkey", "Top Key Function", rs)
1191
                rset.set_apply_callback(apply_topkey_listvalue, _keys.topkey)
1192
                basic.append(rset)
1193

    
1194
            def apply_pfkey_listvalue(setting, obj):
1195
                LOG.debug("Setting value: " + str(setting.value) +
1196
                          " from list")
1197
                val = str(setting.value)
1198
                index = PFKEY_CHOICES.index(val)
1199
                val = PFKEY_VALUES[index]
1200
                obj.set_value(val)
1201

    
1202
            if self.MODEL == "RT29_UHF" or self.MODEL == "RT29_VHF":
1203
                if _keys.pf1 in PFKEY_VALUES:
1204
                    idx = PFKEY_VALUES.index(_keys.pf1)
1205
                else:
1206
                    idx = PFKEY_VALUES.index(0x04)
1207
                rs = RadioSettingValueList(PFKEY_CHOICES, PFKEY_CHOICES[idx])
1208
                rset = RadioSetting("keys.pf1", "PF1 Key Function", rs)
1209
                rset.set_apply_callback(apply_pfkey_listvalue, _keys.pf1)
1210
                basic.append(rset)
1211

    
1212
                if _keys.pf2 in PFKEY_VALUES:
1213
                    idx = PFKEY_VALUES.index(_keys.pf2)
1214
                else:
1215
                    idx = PFKEY_VALUES.index(0x0A)
1216
                rs = RadioSettingValueList(PFKEY_CHOICES, PFKEY_CHOICES[idx])
1217
                rset = RadioSetting("keys.pf2", "PF2 Key Function", rs)
1218
                rset.set_apply_callback(apply_pfkey_listvalue, _keys.pf2)
1219
                basic.append(rset)
1220

    
1221
        if self.MODEL == "RB26" or self.MODEL == "RT76" \
1222
                or self.MODEL == "RB23" \
1223
                or self.MODEL == "RT19" or self.MODEL == "RT619" \
1224
                or self.MODEL == "AR-63" \
1225
                or self.MODEL == "RT40B":
1226
            if self.MODEL == "RB26" or self.MODEL == "RB23":
1227
                _settings2 = self._memobj.settings2
1228
                _settings3 = self._memobj.settings3
1229

    
1230
            rs = RadioSettingValueInteger(0, 9, _settings.squelch)
1231
            rset = RadioSetting("squelch", "Squelch Level", rs)
1232
            basic.append(rset)
1233

    
1234
            rs = RadioSettingValueList(TIMEOUTTIMER_LIST,
1235
                                       TIMEOUTTIMER_LIST[_settings.tot])
1236
            rset = RadioSetting("tot", "Time-out timer", rs)
1237
            basic.append(rset)
1238

    
1239
            if self.MODEL == "RT19" or self.MODEL == "RT619":
1240
                rs = RadioSettingValueList(VOICE_LIST,
1241
                                           VOICE_LIST[_settings.voice])
1242
                rset = RadioSetting("voice", "Voice Prompts", rs)
1243
                basic.append(rset)
1244

    
1245
                rs = RadioSettingValueList(BOOTSEL_LIST,
1246
                                           BOOTSEL_LIST[_settings.bootsel])
1247
                rset = RadioSetting("bootsel", "Boot Select", rs)
1248
                basic.append(rset)
1249

    
1250
                rs = RadioSettingValueInteger(1, 10, _settings.voicel + 1)
1251
                rset = RadioSetting("voicel", "Voice Level", rs)
1252
                basic.append(rset)
1253

    
1254
                rs = RadioSettingValueBoolean(_settings.vox)
1255
                rset = RadioSetting("vox", "Vox Function", rs)
1256
                basic.append(rset)
1257

    
1258
                rs = RadioSettingValueList(VOXL_LIST,
1259
                                           VOXL_LIST[_settings.voxl])
1260
                rset = RadioSetting("voxl", "Vox Level", rs)
1261
                basic.append(rset)
1262

    
1263
                rs = RadioSettingValueList(VOXD_LIST,
1264
                                           VOXD_LIST[_settings.voxd])
1265
                rset = RadioSetting("voxd", "Vox Delay", rs)
1266
                basic.append(rset)
1267

    
1268
            if self.MODEL == "AR-63":
1269
                rs = RadioSettingValueList(VOICE_LIST,
1270
                                           VOICE_LIST[_settings.voice])
1271
                rset = RadioSetting("voice", "Voice Prompts", rs)
1272
                basic.append(rset)
1273

    
1274
            if self.MODEL == "RT76":
1275
                rs = RadioSettingValueList(VOICE_LIST3,
1276
                                           VOICE_LIST3[_settings.voice])
1277
                rset = RadioSetting("voice", "Voice Annumciation", rs)
1278
                basic.append(rset)
1279

    
1280
            if self.MODEL == "RB26" or self.MODEL == "RB23":
1281
                rs = RadioSettingValueList(VOICE_LIST2,
1282
                                           VOICE_LIST2[_settings.voice])
1283
                rset = RadioSetting("voice", "Voice Annumciation", rs)
1284
                basic.append(rset)
1285

    
1286
            if self.MODEL == "RB26":
1287
                rs = RadioSettingValueBoolean(not _settings.chnumberd)
1288
                rset = RadioSetting("chnumberd", "Channel Number Enable", rs)
1289
                basic.append(rset)
1290

    
1291
            rs = RadioSettingValueBoolean(_settings.save)
1292
            rset = RadioSetting("save", "Battery Save", rs)
1293
            basic.append(rset)
1294

    
1295
            rs = RadioSettingValueBoolean(_settings.beep)
1296
            rset = RadioSetting("beep", "Beep", rs)
1297
            basic.append(rset)
1298

    
1299
            if self.MODEL == "RB26" or self.MODEL == "RB23":
1300
                rs = RadioSettingValueBoolean(not _settings.tail)
1301
                rset = RadioSetting("tail", "QT/DQT Tail", rs)
1302
                basic.append(rset)
1303

    
1304
            if self.MODEL != "AR-63" and self.MODEL != "RT40B":
1305
                rs = RadioSettingValueList(SAVE_LIST,
1306
                                           SAVE_LIST[_settings.savem])
1307
                rset = RadioSetting("savem", "Battery Save Mode", rs)
1308
                basic.append(rset)
1309

    
1310
            if self.MODEL != "RT19" and self.MODEL != "RT619" and \
1311
                    self.MODEL != "AR-63" and \
1312
                    self.MODEL != "RT40B":
1313
                rs = RadioSettingValueList(GAIN_LIST,
1314
                                           GAIN_LIST[_settings.gain])
1315
                rset = RadioSetting("gain", "MIC Gain", rs)
1316
                basic.append(rset)
1317

    
1318
                rs = RadioSettingValueList(WARN_LIST,
1319
                                           WARN_LIST[_settings.warn])
1320
                rset = RadioSetting("warn", "Warn Mode", rs)
1321
                basic.append(rset)
1322

    
1323
            if self.MODEL == "RB26" or self.MODEL == "RB23":
1324
                rs = RadioSettingValueBoolean(_settings3.vox)
1325
                rset = RadioSetting("settings3.vox", "Vox Function", rs)
1326
                basic.append(rset)
1327

    
1328
                rs = RadioSettingValueList(VOXL_LIST,
1329
                                           VOXL_LIST[_settings3.voxl])
1330
                rset = RadioSetting("settings3.voxl", "Vox Level", rs)
1331
                basic.append(rset)
1332

    
1333
                rs = RadioSettingValueList(VOXD_LIST,
1334
                                           VOXD_LIST[_settings3.voxd])
1335
                rset = RadioSetting("settings3.voxd", "Vox Delay", rs)
1336
                basic.append(rset)
1337

    
1338
                if self.MODEL == "RB26":
1339
                    rs = RadioSettingValueList(PFKEY_LIST,
1340
                                               PFKEY_LIST[_settings.pf1])
1341
                    rset = RadioSetting("pf1", "PF1 Key Set", rs)
1342
                    basic.append(rset)
1343

    
1344
                    rs = RadioSettingValueList(PFKEY_LIST,
1345
                                               PFKEY_LIST[_settings.pf2])
1346
                    rset = RadioSetting("pf2", "PF2 Key Set", rs)
1347
                    basic.append(rset)
1348
                elif self.MODEL == "RB23":
1349
                    def apply_pfkey_listvalue(setting, obj):
1350
                        LOG.debug("Setting value: " + str(setting.value) +
1351
                                  " from list")
1352
                        val = str(setting.value)
1353
                        index = PFKEY23_CHOICES.index(val)
1354
                        val = PFKEY23_VALUES[index]
1355
                        obj.set_value(val)
1356

    
1357
                    if _settings.pf1 in PFKEY23_VALUES:
1358
                        idx = PFKEY23_VALUES.index(_settings.pf1)
1359
                    else:
1360
                        idx = PFKEY23_VALUES.index(0x01)
1361
                    rs = RadioSettingValueList(PFKEY23_CHOICES,
1362
                                               PFKEY23_CHOICES[idx])
1363
                    rset = RadioSetting("settings.pf1", "PF1 Key Function", rs)
1364
                    rset.set_apply_callback(apply_pfkey_listvalue,
1365
                                            _settings.pf1)
1366
                    basic.append(rset)
1367

    
1368
                    if _settings.pf2 in PFKEY23_VALUES:
1369
                        idx = PFKEY23_VALUES.index(_settings.pf2)
1370
                    else:
1371
                        idx = PFKEY23_VALUES.index(0x03)
1372
                    rs = RadioSettingValueList(PFKEY23_CHOICES,
1373
                                               PFKEY23_CHOICES[idx])
1374
                    rset = RadioSetting("settings.pf2", "PF2 Key Function", rs)
1375
                    rset.set_apply_callback(apply_pfkey_listvalue,
1376
                                            _settings.pf2)
1377
                    basic.append(rset)
1378

    
1379
                rs = RadioSettingValueInteger(1, 30, _settings2.chnumber + 1)
1380
                rset = RadioSetting("settings2.chnumber", "Channel Number", rs)
1381
                basic.append(rset)
1382

    
1383
            if self.MODEL == "RT76":
1384
                rs = RadioSettingValueBoolean(_settings.vox)
1385
                rset = RadioSetting("vox", "Vox Function", rs)
1386
                basic.append(rset)
1387

    
1388
                rs = RadioSettingValueList(VOXL_LIST,
1389
                                           VOXL_LIST[_settings.voxl])
1390
                rset = RadioSetting("voxl", "Vox Level", rs)
1391
                basic.append(rset)
1392

    
1393
                rs = RadioSettingValueList(VOXD_LIST,
1394
                                           VOXD_LIST[_settings.voxd])
1395
                rset = RadioSetting("voxd", "Vox Delay", rs)
1396
                basic.append(rset)
1397

    
1398
                rs = RadioSettingValueInteger(1, 30, _settings.chnumber + 1)
1399
                rset = RadioSetting("chnumber", "Channel Number", rs)
1400
                basic.append(rset)
1401

    
1402
            if self.MODEL == "AR-63":
1403
                rs = RadioSettingValueBoolean(_settings.warn)
1404
                rset = RadioSetting("warn", "Warn", rs)
1405
                basic.append(rset)
1406

    
1407
                rs = RadioSettingValueBoolean(_settings.scan)
1408
                rset = RadioSetting("scan", "Scan", rs)
1409
                basic.append(rset)
1410

    
1411
                rs = RadioSettingValueList(HOP_LIST,
1412
                                           HOP_LIST[_settings.hop])
1413
                rset = RadioSetting("hop", "Hop Mode", rs)
1414
                basic.append(rset)
1415

    
1416
                rs = RadioSettingValueList(TAIL_LIST,
1417
                                           TAIL_LIST[_settings.tailmode])
1418
                rset = RadioSetting("tailmode", "DCS Tail Mode", rs)
1419
                basic.append(rset)
1420

    
1421
                rs = RadioSettingValueBoolean(_settings.vox)
1422
                rset = RadioSetting("vox", "Vox Function", rs)
1423
                basic.append(rset)
1424

    
1425
                rs = RadioSettingValueList(VOXL_LIST,
1426
                                           VOXL_LIST[_settings.voxl])
1427
                rset = RadioSetting("voxl", "Vox Level", rs)
1428
                basic.append(rset)
1429

    
1430
                rs = RadioSettingValueList(VOXD_LIST,
1431
                                           VOXD_LIST[_settings.voxd])
1432
                rset = RadioSetting("voxd", "Vox Delay", rs)
1433
                basic.append(rset)
1434

    
1435
            if self.MODEL == "RT40B":
1436
                rs = RadioSettingValueList(VOICE_LIST,
1437
                                           VOICE_LIST[_settings.voice])
1438
                rset = RadioSetting("voice", "Voice Prompts", rs)
1439
                basic.append(rset)
1440

    
1441
                rs = RadioSettingValueList(SAVEM_LIST,
1442
                                           SAVEM_LIST[_settings.savem])
1443
                rset = RadioSetting("savem", "Battery Save Mode", rs)
1444
                basic.append(rset)
1445

    
1446
                rs = RadioSettingValueBoolean(_settings.pttstone)
1447
                rset = RadioSetting("pttstone", "PTT Start Tone", rs)
1448
                basic.append(rset)
1449

    
1450
                rs = RadioSettingValueBoolean(_settings.pttetone)
1451
                rset = RadioSetting("pttetone", "PTT End Tone", rs)
1452
                basic.append(rset)
1453

    
1454
                rs = RadioSettingValueBoolean(_settings.vox)
1455
                rset = RadioSetting("vox", "Vox Function", rs)
1456
                basic.append(rset)
1457

    
1458
                rs = RadioSettingValueList(VOXL_LIST,
1459
                                           VOXL_LIST[_settings.voxl])
1460
                rset = RadioSetting("voxl", "Vox Level", rs)
1461
                basic.append(rset)
1462

    
1463
                rs = RadioSettingValueList(VOXD_LIST,
1464
                                           VOXD_LIST[_settings.voxd])
1465
                rset = RadioSetting("voxd", "Vox Delay", rs)
1466
                basic.append(rset)
1467

    
1468
        return top
1469

    
1470
    def set_settings(self, settings):
1471
        for element in settings:
1472
            if not isinstance(element, RadioSetting):
1473
                self.set_settings(element)
1474
                continue
1475
            else:
1476
                try:
1477
                    if "." in element.get_name():
1478
                        bits = element.get_name().split(".")
1479
                        obj = self._memobj
1480
                        for bit in bits[:-1]:
1481
                            obj = getattr(obj, bit)
1482
                        setting = bits[-1]
1483
                    else:
1484
                        obj = self._memobj.settings
1485
                        setting = element.get_name()
1486

    
1487
                    if element.has_apply_callback():
1488
                        LOG.debug("Using apply callback")
1489
                        element.run_apply_callback()
1490
                    elif setting == "channel":
1491
                        setattr(obj, setting, int(element.value) - 1)
1492
                    elif setting == "chnumber":
1493
                        setattr(obj, setting, int(element.value) - 1)
1494
                    elif setting == "chnumberd":
1495
                        setattr(obj, setting, not int(element.value))
1496
                    elif setting == "tail":
1497
                        setattr(obj, setting, not int(element.value))
1498
                    elif setting == "voicel":
1499
                        setattr(obj, setting, int(element.value) - 1)
1500
                    elif element.value.get_mutable():
1501
                        LOG.debug("Setting %s = %s" % (setting, element.value))
1502
                        setattr(obj, setting, element.value)
1503
                except Exception as e:
1504
                    LOG.debug(element.get_name())
1505
                    raise
1506

    
1507
    @classmethod
1508
    def match_model(cls, filedata, filename):
1509
        if cls.MODEL == "RT21":
1510
            # The RT21 is pre-metadata, so do old-school detection
1511
            match_size = False
1512
            match_model = False
1513

    
1514
            # testing the file data size
1515
            if len(filedata) in [0x0400, ]:
1516
                match_size = True
1517

    
1518
            # testing the model fingerprint
1519
            match_model = model_match(cls, filedata)
1520

    
1521
            if match_size and match_model:
1522
                return True
1523
            else:
1524
                return False
1525
        else:
1526
            # Radios that have always been post-metadata, so never do
1527
            # old-school detection
1528
            return False
1529

    
1530

    
1531
@directory.register
1532
class RB17ARadio(RT21Radio):
1533
    """RETEVIS RB17A"""
1534
    VENDOR = "Retevis"
1535
    MODEL = "RB17A"
1536
    BAUD_RATE = 9600
1537
    BLOCK_SIZE = 0x40
1538
    BLOCK_SIZE_UP = 0x10
1539

    
1540
    POWER_LEVELS = [chirp_common.PowerLevel("High", watts=5.00),
1541
                    chirp_common.PowerLevel("Low", watts=0.50)]
1542

    
1543
    _magic = "PROA8US"
1544
    _fingerprint = ["P3217s\xF8\xFF",]
1545
    _upper = 30
1546
    _skipflags = True
1547
    _reserved = False
1548
    _gmrs = True
1549

    
1550
    _ranges = [
1551
               (0x0000, 0x0300),
1552
              ]
1553
    _memsize = 0x0300
1554

    
1555
    def process_mmap(self):
1556
        self._memobj = bitwise.parse(MEM_FORMAT_RB17A, self._mmap)
1557

    
1558

    
1559
@directory.register
1560
class RB26Radio(RT21Radio):
1561
    """RETEVIS RB26"""
1562
    VENDOR = "Retevis"
1563
    MODEL = "RB26"
1564
    BAUD_RATE = 9600
1565
    BLOCK_SIZE = 0x20
1566
    BLOCK_SIZE_UP = 0x10
1567

    
1568
    DTCS_CODES = sorted(chirp_common.DTCS_CODES + [645])
1569
    POWER_LEVELS = [chirp_common.PowerLevel("High", watts=3.00),
1570
                    chirp_common.PowerLevel("Low", watts=0.50)]
1571

    
1572
    _magic = "PHOGR" + "\x01" + "0"
1573
    _fingerprint = ["P32073" + "\x02\xFF",]
1574
    _upper = 30
1575
    _ack_1st_block = False
1576
    _skipflags = True
1577
    _reserved = True
1578
    _gmrs = True
1579

    
1580
    _ranges = [
1581
               (0x0000, 0x0320),
1582
              ]
1583
    _memsize = 0x0320
1584

    
1585
    def process_mmap(self):
1586
        self._memobj = bitwise.parse(MEM_FORMAT_RB26, self._mmap)
1587

    
1588

    
1589
@directory.register
1590
class RT76Radio(RT21Radio):
1591
    """RETEVIS RT76"""
1592
    VENDOR = "Retevis"
1593
    MODEL = "RT76"
1594
    BAUD_RATE = 9600
1595
    BLOCK_SIZE = 0x20
1596
    BLOCK_SIZE_UP = 0x10
1597

    
1598
    DTCS_CODES = sorted(chirp_common.DTCS_CODES + [645])
1599
    POWER_LEVELS = [chirp_common.PowerLevel("High", watts=5.00),
1600
                    chirp_common.PowerLevel("Low", watts=0.50)]
1601

    
1602
    _magic = "PHOGR\x14\xD4"
1603
    _fingerprint = ["P32073" + "\x02\xFF",]
1604
    _upper = 30
1605
    _ack_1st_block = False
1606
    _skipflags = False
1607
    _reserved = True
1608
    _gmrs = True
1609

    
1610
    _ranges = [
1611
               (0x0000, 0x01E0),
1612
              ]
1613
    _memsize = 0x01E0
1614

    
1615
    def process_mmap(self):
1616
        self._memobj = bitwise.parse(MEM_FORMAT_RT76, self._mmap)
1617

    
1618

    
1619
@directory.register
1620
class RT29UHFRadio(RT21Radio):
1621
    """RETEVIS RT29UHF"""
1622
    VENDOR = "Retevis"
1623
    MODEL = "RT29_UHF"
1624
    BLOCK_SIZE = 0x40
1625
    BLOCK_SIZE_UP = 0x10
1626

    
1627
    TXPOWER_MED = 0x00
1628
    TXPOWER_HIGH = 0x01
1629
    TXPOWER_LOW = 0x02
1630

    
1631
    POWER_LEVELS = [chirp_common.PowerLevel("High", watts=10.00),
1632
                    chirp_common.PowerLevel("Mid", watts=5.00),
1633
                    chirp_common.PowerLevel("Low", watts=1.00)]
1634

    
1635
    _magic = "PROHRAM"
1636
    _fingerprint = ["P3207" + "\x13\xF8\xFF",]  # UHF model
1637
    _upper = 16
1638
    _skipflags = True
1639
    _reserved = False
1640

    
1641
    _ranges = [
1642
               (0x0000, 0x0300),
1643
              ]
1644
    _memsize = 0x0400
1645

    
1646
    def process_mmap(self):
1647
        self._memobj = bitwise.parse(MEM_FORMAT_RT29, self._mmap)
1648

    
1649

    
1650
@directory.register
1651
class RT29VHFRadio(RT29UHFRadio):
1652
    """RETEVIS RT29VHF"""
1653
    VENDOR = "Retevis"
1654
    MODEL = "RT29_VHF"
1655

    
1656
    TXPOWER_MED = 0x00
1657
    TXPOWER_HIGH = 0x01
1658
    TXPOWER_LOW = 0x02
1659

    
1660
    POWER_LEVELS = [chirp_common.PowerLevel("High", watts=10.00),
1661
                    chirp_common.PowerLevel("Mid", watts=5.00),
1662
                    chirp_common.PowerLevel("Low", watts=1.00)]
1663

    
1664
    VALID_BANDS = [(136000000, 174000000)]
1665

    
1666
    _magic = "PROHRAM"
1667
    _fingerprint = ["P2207" + "\x01\xF8\xFF",]  # VHF model
1668

    
1669

    
1670
@directory.register
1671
class RB23Radio(RT21Radio):
1672
    """RETEVIS RB23"""
1673
    VENDOR = "Retevis"
1674
    MODEL = "RB23"
1675
    BLOCK_SIZE = 0x20
1676
    BLOCK_SIZE_UP = 0x10
1677

    
1678
    POWER_LEVELS = [chirp_common.PowerLevel("High", watts=5.00),
1679
                    chirp_common.PowerLevel("Low", watts=0.50)]
1680

    
1681
    _magic = "PHOGR" + "\x01" + "0"
1682
    _fingerprint = ["P32073" + "\x02\xFF",]
1683
    _upper = 30
1684
    _ack_1st_block = False
1685
    _skipflags = True
1686
    _reserved = True
1687
    _gmrs = True
1688

    
1689
    _ranges = [
1690
               (0x0000, 0x0320),
1691
              ]
1692
    _memsize = 0x0320
1693

    
1694
    def process_mmap(self):
1695
        self._memobj = bitwise.parse(MEM_FORMAT_RB26, self._mmap)
1696

    
1697

    
1698
@directory.register
1699
class RT19Radio(RT21Radio):
1700
    """RETEVIS RT19"""
1701
    VENDOR = "Retevis"
1702
    MODEL = "RT19"
1703
    BLOCK_SIZE = 0x20
1704
    BLOCK_SIZE_UP = 0x10
1705

    
1706
    POWER_LEVELS = [chirp_common.PowerLevel("High", watts=2.00),
1707
                    chirp_common.PowerLevel("Low", watts=0.50)]
1708

    
1709
    _magic = "PHOGRQ^"
1710
    _fingerprint = ["P32073" + "\x02\xFF",]
1711
    _upper = 22
1712
    _mem_params = (_upper,  # number of channels
1713
                   0x160,   # memory start
1714
                   _upper   # number of freqhops
1715
                   )
1716
    _ack_1st_block = False
1717
    _skipflags = False
1718
    _reserved = True
1719
    _frs = True
1720

    
1721
    _ranges = [
1722
               (0x0000, 0x0180),
1723
              ]
1724
    _memsize = 0x0180
1725

    
1726
    def process_mmap(self):
1727
        self._memobj = bitwise.parse(MEM_FORMAT_RT19 % self._mem_params,
1728
                                     self._mmap)
1729

    
1730

    
1731
@directory.register
1732
class RT619Radio(RT19Radio):
1733
    """RETEVIS RT619"""
1734
    VENDOR = "Retevis"
1735
    MODEL = "RT619"
1736

    
1737
    POWER_LEVELS = [chirp_common.PowerLevel("High", watts=0.50),
1738
                    chirp_common.PowerLevel("Low", watts=0.49)]
1739

    
1740
    _magic = "PHOGRS]"
1741
    _fingerprint = ["P32073" + "\x02\xFF",]
1742
    _upper = 16
1743
    _mem_params = (_upper,  # number of channels
1744
                   0x100,   # memory start
1745
                   _upper   # number of freqhops
1746
                   )
1747
    _pmr = True
1748

    
1749
    _ranges = [
1750
               (0x0000, 0x0120),
1751
              ]
1752
    _memsize = 0x0120
1753

    
1754

    
1755
@directory.register
1756
class AR63Radio(RT21Radio):
1757
    """ABBREE AR-63"""
1758
    VENDOR = "Abbree"
1759
    MODEL = "AR-63"
1760
    BLOCK_SIZE = 0x20
1761
    BLOCK_SIZE_UP = 0x10
1762

    
1763
    POWER_LEVELS = [chirp_common.PowerLevel("High", watts=3.00),
1764
                    chirp_common.PowerLevel("Low", watts=1.00)]
1765

    
1766
    _magic = "PHOGR\xF5\x9A"
1767
    _fingerprint = ["P32073" + "\x02\xFF",
1768
                    "P32073" + "\x03\xFF",]
1769
    _upper = 16
1770
    _ack_1st_block = False
1771
    _skipflags = True
1772
    _reserved = True
1773
    _gmrs = False
1774

    
1775
    _ranges = [
1776
               (0x0000, 0x0140),
1777
              ]
1778
    _memsize = 0x0140
1779

    
1780
    def process_mmap(self):
1781
        self._memobj = bitwise.parse(MEM_FORMAT_RT76, self._mmap)
1782

    
1783

    
1784
@directory.register
1785
class RT40BRadio(RT21Radio):
1786
    """RETEVIS RT40B"""
1787
    VENDOR = "Retevis"
1788
    MODEL = "RT40B"
1789
    BLOCK_SIZE = 0x20
1790
    BLOCK_SIZE_UP = 0x10
1791

    
1792
    DTCS_CODES = sorted(chirp_common.DTCS_CODES + [645])
1793
    POWER_LEVELS = [chirp_common.PowerLevel("High", watts=2.00),
1794
                    chirp_common.PowerLevel("Low", watts=0.50)]
1795

    
1796
    VALID_BANDS = [(400000000, 480000000)]
1797

    
1798
    _magic = "PHOGRH" + "\x5C"
1799
    _fingerprint = ["P32073" + "\x02\xFF",]
1800
    _upper = 22
1801
    _mem_params = (_upper,  # number of channels
1802
                   )
1803
    _ack_1st_block = False
1804
    _skipflags = True
1805
    _reserved = True
1806
    _gmrs = True
1807
    _echo = True
1808

    
1809
    _ranges = [
1810
               (0x0000, 0x0160),
1811
              ]
1812
    _memsize = 0x0160
1813

    
1814
    def process_mmap(self):
1815
        self._memobj = bitwise.parse(MEM_FORMAT_RT40B % self._mem_params,
1816
                                     self._mmap)
(2-2/2)