Project

General

Profile

Bug #9353 » retevis_rt98_at-779v_blf_hack.py

Jim Unroe, 09/09/2021 02:41 PM

 
1
# Copyright 2021 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 os
17
import struct
18
import time
19
import logging
20

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

    
32
LOG = logging.getLogger(__name__)
33

    
34
#
35
#  Chirp Driver for Retevis RT98 models: RT98V (136-174 Mhz)
36
#                                        RT98U (400-490 Mhz)
37
#
38
#
39
#
40
# Global Parameters
41
#
42
TONES = [62.5] + list(chirp_common.TONES)
43
TMODES = ['', 'Tone', 'DTCS']
44
DUPLEXES = ['', '+', '-']
45

    
46
TXPOWER_LOW = 0x00
47
TXPOWER_MED = 0x01
48
TXPOWER_HIGH = 0x02
49

    
50
DUPLEX_NOSPLIT = 0x00
51
DUPLEX_POSSPLIT = 0x01
52
DUPLEX_NEGSPLIT = 0x02
53

    
54
CHANNEL_WIDTH_12d5kHz = 0x00
55
CHANNEL_WIDTH_20kHz = 0x01
56
CHANNEL_WIDTH_25kHz = 0x02
57

    
58
TUNING_STEPS = [2.5, 5.0, 6.25, 10.0, 12.5, 20.0, 25.0, 30.0, 50.0]
59

    
60
POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=5),
61
                chirp_common.PowerLevel("Mid", watts=10),
62
                chirp_common.PowerLevel("High", watts=15)]
63

    
64
PMR_POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=0.5), ]
65

    
66
FREENET_POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=1), ]
67

    
68
PMR_FREQS = [446.00625, 446.01875, 446.03125, 446.04375,
69
             446.05625, 446.06875, 446.08125, 446.09375,
70
             446.10625, 446.11875, 446.13125, 446.14375,
71
             446.15625, 446.16875, 446.18125, 446.19375]
72

    
73
FREENET_FREQS = [149.02500, 149.03750, 149.05000,
74
                 149.08750, 149.10000, 149.11250]
75

    
76
CROSS_MODES = ["Tone->Tone", "DTCS->", "->DTCS", "Tone->DTCS", "DTCS->Tone",
77
               "->Tone", "DTCS->DTCS"]
78

    
79
LIST_STEP = [str(x) for x in TUNING_STEPS]
80
LIST_TIMEOUT = ["Off"] + ["%s min" % x for x in range(1, 31)]
81
LIST_APO = ["Off", "30 min", "1 hr", "2 hrs"]
82
LIST_SQUELCH = ["Off"] + ["Level %s" % x for x in range(1, 10)]
83
LIST_DISPLAY_MODE = ["Channel", "Frequency", "Name"]
84
LIST_AOP = ["Manual", "Auto"]
85
LIST_STE_TYPE = ["Off", "Silent", "120 Degree", "180 Degree", "240 Degree"]
86
LIST_STE_FREQ = ["Off", "55.2 Hz", "259.2 Hz"]
87

    
88
LIST_PRIORITY_CH = ["Off", "Priority Channel 1", "Priority Channel 2",
89
                    "Priority Channel 1 + Priority Channel 2"]
90

    
91
LIST_REVERT_CH = ["Selected", "Selected + TalkBack", "Priority Channel 1",
92
                  "Priority Channel 2", "Last Called", "Last Used",
93
                  "Priority Channel 1 + TalkBack",
94
                  "Priority Channel 2 + TalkBack"]
95

    
96
LIST_TIME50 = ["0.1", "0.2", "0.3", "0.4", "0.5",
97
               "0.6", "0.7", "0.8", "0.9", "1.0",
98
               "1.1", "1.2", "1.3", "1.4", "1.5",
99
               "1.6", "1.7", "1.8", "1.9", "2.0",
100
               "2.1", "3.2", "2.3", "2.4", "2.5",
101
               "2.6", "2.7", "2.8", "2.9", "3.0",
102
               "3.1", "3.2", "3.3", "3.4", "3.5",
103
               "3.6", "3.7", "3.8", "3.9", "4.0",
104
               "4.1", "4.2", "4.3", "4.4", "4.5",
105
               "4.6", "4.7", "4.8", "4.9", "5.0"]
106
LIST_TIME46 = LIST_TIME50[4:]
107

    
108
LIST_RT98V_MODES = ["FreeNet", "COM", "COMII"]
109
LIST_RT98U_MODES = ["PMR", "COM", "COMII"]
110

    
111
LIST_RT98V_FREQS = ["Rx(149 - 149.2 MHz) Tx(149 - 149.2 MHz)",
112
                    "Rx(136 - 174 MHz) Tx(136 - 174 MHz)",
113
                    "Rx(147 - 174 MHz) Tx(147 - 174 MHz)"]
114

    
115
LIST_RT98U_FREQS = ["Rx(446 - 446.2 MHz) Tx(446 - 446.2 MHz)",
116
                    "Rx(400 - 470 MHz) Tx(400 - 470 MHz)",
117
                    "Rx(450 - 470 MHz) Tx(450 - 470 MHz)"]
118

    
119
SETTING_LISTS = {
120
        "tuning_step": LIST_STEP,
121
        "timeout_timer": LIST_TIMEOUT,
122
        "auto_power_off": LIST_APO,
123
        "squelch": LIST_SQUELCH,
124
        "display_mode": LIST_DISPLAY_MODE,
125
        "auto_power_on": LIST_AOP,
126
        "ste_type": LIST_STE_TYPE,
127
        "ste_frequency": LIST_STE_FREQ,
128
        "priority_ch": LIST_PRIORITY_CH,
129
        "revert_ch": LIST_REVERT_CH,
130
        "settings2.dropout_delay_time": LIST_TIME50,
131
        "settings2.dwell_time": LIST_TIME50,
132
        "settings2.look_back_time_a": LIST_TIME46,
133
        "settings2.look_back_time_b": LIST_TIME46
134
}
135

    
136
#  RT98  memory map
137
#  section: 1  Channel Bank
138
#         description of channel bank (199 channels , range 1-199)
139
#         Each 32 Byte (0x20 hex)  record:
140
#  bytes:bit  type                 description
141
#  ---------------------------------------------------------------------------
142
#  4          bbcd freq[4]         receive frequency in packed binary coded
143
#                                  decimal
144
#  4          bbcd offset[4]       transceive offset in packed binary coded
145
#                                  decimal (note: +/- direction set by
146
#                                  'duplex' field)
147
#  1          u8 unknown0
148
#  1          u8
149
#   :1        reverse:1            reverse flag, 0=off, 1=on (reverses
150
#                                  transmit and receive freqencies)
151
#   :1        txoff:1              transmitt off flag, 0=transmit, 1=do not
152
#                                  transmit
153
#   :2        power:2              transmit power setting, value range 0-2,
154
#                                  0=low, 1=middle, 2=high
155
#   :2        duplex:2             duplex settings, 0=simplex, 1=plus (+)
156
#                                  offset, 2=minus(-) offset (see offset field)
157
#   :2        channel_width:2      channel spacing, 0=12.5kHz, 1=20kHz, 2=25kHz
158
#  1          u8
159
#   :2        unknown1:2
160
#   :1        talkaround:1         talkaround flag, 0=off, 1=on
161
#                                  (bypasses repeater)
162
#   :1        squelch_mode:1       squelch mode flag, 0=carrier, 1=ctcss/dcs
163
#   :1        rxdcsextra:1         use with rxcode for index of rx DCS to use
164
#   :1        rxinv:1              inverse DCS rx polarity flag, 0=N, 1=I
165
#   :1        txdcsextra:1         use with txcode for index of tx DCS to use
166
#   :1        txinv:1              inverse DCS tx polarity flag, 0=N, 1=I
167
#  1          u8
168
#   :4        unknown2:4
169
#   :2        rxtmode:2            rx tone mode, value range 0-2, 0=none,
170
#                                  1=CTCSS, 2=DCS  (ctcss tone in field rxtone)
171
#   :2        txtmode:2            tx tone mode, value range 0-2, 0=none,
172
#                                  1=CTCSS, 3=DCS  (ctcss tone in field txtone)
173
#  1          u8
174
#   :2        unknown3:2
175
#   :6        txtone:6             tx ctcss tone, menu index
176
#  1          u8
177
#   :2        unknown4:2
178
#   :6        rxtone:6             rx ctcss tone, menu index
179
#  1          u8 txcode            ?, not used for ctcss
180
#  1          u8 rxcode            ?, not used for ctcss
181
#  1          u8
182
#   :6        unknown5:6
183
#   :1        busychannellockout:1 busy channel lockout flag, 0=off, 1=enabled
184
#   :1        unknown6:1
185
#  6          char name[6]         6 byte char string for channel name
186
#  9          u8 unknown7[9]
187
#
188
MEM_FORMAT = """
189
#seekto 0x0000;
190
struct {
191
  bbcd freq[4];
192
  bbcd offset[4];
193
  u8 unknown0;
194
  u8 reverse:1,
195
     tx_off:1,
196
     txpower:2,
197
     duplex:2,
198
     channel_width:2;
199
  u8 unknown1:2,
200
     talkaround:1,
201
     squelch_mode:1,
202
     rxdcsextra:1,
203
     rxinv:1,
204
     txdcsextra:1,
205
     txinv:1;
206
  u8 unknown2:4,
207
     rxtmode:2,
208
     txtmode:2;
209
  u8 unknown3:2,
210
     txtone:6;
211
  u8 unknown4:2,
212
     rxtone:6;
213
  u8 txcode;
214
  u8 rxcode;
215
  u8 unknown5:6,
216
     busychannellockout:1,
217
     unknown6:1;
218
  char name[6];
219
  u8 unknown7[9];
220
} memory[199];
221
"""
222

    
223
#  RT98  memory map
224
#  section: 2 and 3  Channel Set/Skip Flags
225
#
226
#    Channel Set (starts 0x3240) : Channel Set  bit is value 0 if a memory
227
#                                  location in the channel bank is active.
228
#    Channel Skip (starts 0x3260): Channel Skip bit is value 0 if a memory
229
#                                  location in the channel bank is active.
230
#
231
#    Both flag maps are a total 24 bytes in length, aligned on 32 byte records.
232
#    bit = 0 channel not set/skip,  1 is channel set/no skip
233
#
234
#    to index a channel:
235
#        cbyte = channel / 8 ;
236
#        cbit  = channel % 8 ;
237
#        setflag  = csetflag[cbyte].c[cbit] ;
238
#        skipflag = cskipflag[cbyte].c[cbit] ;
239
#
240
#    channel range is 1-199, range is 32 bytes (last 7 unknown)
241
#
242
MEM_FORMAT = MEM_FORMAT + """
243
#seekto 0x3240;
244
struct {
245
   bit c[8];
246
} csetflag[32];
247

    
248
#seekto 0x3260;
249
struct {
250
   bit c[8];
251
} cskipflag[32];
252

    
253
"""
254

    
255
#  RT98  memory map
256
#  section: 4  Startup Label
257
#
258
#  bytes:bit  type                 description
259
#  ---------------------------------------------------------------------------
260
#  6          char start_label[6]  label displayed at startup (usually
261
#                                  your call sign)
262
#
263
MEM_FORMAT = MEM_FORMAT + """
264
#seekto 0x3300;
265
struct {
266
    char startname[6];
267
} slabel;
268
"""
269

    
270
#  RT98  memory map
271
#  section: 5, 6 and 7  Radio Options
272
#        used to set a number of radio options
273
#
274
# description of function setup options, starting at 0x3310 (settings3)
275
#
276
#  bytes:bit  type                 description
277
#  ---------------------------------------------------------------------------
278
#  1          u8
279
#   :6        unknown:6
280
#   :2        bandlimit_3310:2     frequency ranges, range 0-2,
281
#                                  0=freenet(vhf) or pmr(uhf), 1=com, 2=comii
282
#                   rt98v - 00 FreeNet Rx(149 - 149.2 MHz) Tx(149 - 149.2 MHz)
283
#                           01 COM     Rx(136 - 174 MHz) Tx(136 - 174 MHz)
284
#                           02 COMII   Rx(147 - 174 MHz) Tx(147 - 174 MHz)
285
#                   rt98u - 00 PMR     Rx(446 - 446.2 MHz) Tx(446 - 446.2 MHz)
286
#                           01 COM     Rx(400 - 470 MHz) Tx(400 - 470 MHz)
287
#                           02 COMII   Rx(450 - 470 MHz) Tx(450 - 470 MHz)
288
#  1          u8 ch_number;        channel number, range 1-199
289
#
290
# description of function setup options, starting at 0x3340 (settings)
291
#
292
#  bytes:bit  type                   description
293
#  ---------------------------------------------------------------------------
294
#  1          u8
295
#   :4        unknown_3340:4
296
#   :4        tuning_step:4          tuning step, menu index value from 0-8
297
#                                    2.5, 5, 6.25, 10, 12.5, 20, 25, 30, 50
298
#  1          u8
299
#   :7        unknown_3341:7
300
#   :1        beep:1                 beep mode, range 0-1, 0=off, 1=on
301
#  1           u8
302
#   :3        unknown_3342:3
303
#   :5        timeout_timer:5        timeout timer, range off (no timeout),
304
#                                    1-30 minutes
305
#  1          u8
306
#   :6        unknown_3343:6
307
#   :2        auto_power_off:2       auto power off, range 0-3, off, 30min,
308
#                                    1hr, 2hr
309
#  1          u8
310
#   :4        unknown_3344:4
311
#   :4        squelch:4              squelch level, range off, 1-9
312
#  1          u8
313
#   :3        unknown_3345:3
314
#   :5        volume:5               volume level, range 1-30 (no zero)
315
#  1          u8 unknown_3346
316
#  1          u8 unknown_3347
317
#  1          u8   0x3348 [12]
318
#   :6        unknown_3348:6
319
#   :2        display_mode           display mode, range 0-2, 0=channel,
320
#                                    1=frequency, 2=name
321
#  1           u8
322
#   :7        unknown_3349:7
323
#   :1        auto_power_on:1        auto power on, range 0-1, 0=manual,
324
#                                    1=auto
325
#  1          u8
326
#   :3        unknown_334A:3
327
#   :5        mic_gain:5             mic gain, range 1-30 (no zero)
328
#  1          u8
329
#   :5        unknown_334C:5
330
#   :3        ste_type:3             ste type, range 0-4, 0=off, 1=silent,
331
#                                    2=120degree, 3=180degree, 4=240degree
332
#  1          u8
333
#   :7        unknown_334D:7
334
#   :1        ste_frequency:1        ste frequency, range 0-2, 0=off,
335
#                                    1=55.2Hz, 2=259.2Hz
336
#  1          u8
337
#   :2        unknown_0x334E:2
338
#   :1        forbid_setting:1       forbid setting(optional function),
339
#                                    range 0-1, 0=disabled, 1=enabled
340
#   :1        forbid_initialize:1    forbid initialize operate, range 0-1,
341
#                                    0=enabled, 1=disabled (inverted)
342
#   :1        save_chan_param:1      save channel parameters, range 0-1,
343
#                                    0=disabled, 1=enabled
344
#   :1        forbid_chan_menu:1     forbid channel menu, range 0-1,
345
#                                    0=disabled, 1=enabled
346
#   :1        sql_key_function:1     sql key function, range 0-1,
347
#                                    0=squelch off momentary, 1=squelch off
348
#   :1        unknown:1
349
#
350
# description of function setup options, starting at 0x3380 (settings2)
351
#
352
#  bytes:bit  type                   description
353
#  ---------------------------------------------------------------------------
354
#  1          u8
355
#   :7        unknown_3380:7
356
#   :1        scan_mode:1            scan mode, range 0-1, 0=off, 1=on
357
#  1          u8
358
#   :6        unknown_3381:6
359
#   :2        priority_ch:2          priority channel, range 0-3, 0=off,
360
#                                    1=priority channel 1,
361
#                                    2=priority channel 2,
362
#                                    3=priority channel 1 + priority channel 2
363
#  1          u8 priority_ch1        priority channel 1 number, range 1-199
364
#  1          u8 priority_ch2        priority channel 2 number, range 1-199
365
#  1          u8
366
#   :4        unknown_3384:4
367
#   :4        revert_ch:4            revert channel, range 0-3, 0=selected,
368
#                                    1=selected + talkback, 2=last called,
369
#                                    3=last used
370
#  1          u8 look_back_time_a    look back time a, range 0-45
371
#  1          u8 look_back_time_b    look back time b, range 0-45
372
#  1          u8 dropout_delay_time  dropout delay time, range 0-49
373
#  1          u8 dwell_time          dwell time, range 0-49
374
#
375
MEM_FORMAT = MEM_FORMAT + """
376
#seekto 0x3310;
377
struct {
378
    u8 bandlimit;
379
    u8 ch_number;
380
} settings3;
381
"""
382

    
383
MEM_FORMAT = MEM_FORMAT + """
384
#seekto 0x3340;
385
struct {
386
  u8 unknown_3340:4,
387
     tuning_step:4;
388
  u8 unknown_3341:7,
389
     beep:1;
390
  u8 unknown_3342:3,
391
     timeout_timer:5;
392
  u8 unknown_3343:6,
393
     auto_power_off:2;
394
  u8 unknown_3344:4,
395
     squelch:4;
396
  u8 unknown_3345:3,
397
     volume:5;
398
  u8 unknown_3346;
399
  u8 unknown_3347;
400
  u8 unknown_3348:6,
401
     display_mode:2;
402
  u8 unknown_3349:7,
403
     auto_power_on:1;
404
  u8 unknown_334A:3,
405
     mic_gain:5;
406
  u8 unknown_334B;
407
  u8 unknown_334C:5,
408
     ste_type:3;
409
  u8 unknown_334D:6,
410
     ste_frequency:2;
411
  u8 unknown_334E:1,
412
     forbid_setting:1,
413
     unknown1:1,
414
     forbid_initialize:1,
415
     save_chan_param:1,
416
     forbid_chan_menu:1,
417
     sql_key_function:1,
418
     unknown2:1;
419
} settings;
420
"""
421

    
422
MEM_FORMAT = MEM_FORMAT + """
423
#seekto 0x3380;
424
struct {
425
  u8 unknown_3380:7,
426
     scan_mode:1;
427
  u8 unknown_3381:6,
428
     priority_ch:2;
429
  u8 priority_ch1;
430
  u8 priority_ch2;
431
  u8 unknown_3384:4,
432
     revert_ch:4;
433
  u8 look_back_time_a;
434
  u8 look_back_time_b;
435
  u8 dropout_delay_time;
436
  u8 dwell_time;
437
} settings2;
438
"""
439

    
440
#  RT98  memory map
441
#  section: 8  Embedded Messages
442
#
443
#  bytes:bit  type                 description
444
#  ---------------------------------------------------------------------------
445
#  6          char radio_type[5]   radio type, vhf=rt98v, uhf=rt98u
446
#  2          u8 unknown1[2]
447
#  4          char mcu_version[4]  mcu version, [x.xx]
448
#  2          u8 unknown2[2]
449
#  1          u8 mode              rt98u mode: 0=pmr, 1=com, 2=comii
450
#                                  rt98v mode: 0=freenet, 1=com, 2=comii
451
#  1          u8 unknown3
452
#  10         u8 unused1[10]
453
#  4          u8 unknown4[4]
454
#  3          u8 unused2[3]
455
#  16         u8 unknown5[16]
456
#  10         char date_mfg[16]    date manufactured, [yyyy-mm-dd]
457
#
458
MEM_FORMAT = MEM_FORMAT + """
459
#seekto 0x3D00;
460
struct {
461
char radio_type[5];
462
u8 unknown1[2];
463
char mcu_version[4];
464
u8 unknown2[2];
465
u8 mode;
466
u8 unknown3;
467
u8 unused1[10];
468
u8 unknown4[4];
469
u8 unused2[3];
470
u8 unknown5[16];
471
char date_mfg[10];
472
} embedded_msg;
473
"""
474

    
475

    
476
# Format for the version messages returned by the radio
477
VER_FORMAT = '''
478
u8 hdr;
479
char model[7];
480
u8 bandlimit;
481
char version[6];
482
u8 ack;
483
'''
484

    
485

    
486
# Radio supports upper case and symbols
487
CHARSET_ASCII_PLUS = chirp_common.CHARSET_UPPER_NUMERIC + '- '
488

    
489
# Band limits as defined by the band byte in ver_response, defined in Hz, for
490
# VHF and UHF, used for RX and TX.
491
RT98V_BAND_LIMITS = {0x00: [(149000000, 149200000)],
492
                     0x01: [(136000000, 174000000)],
493
                     0x02: [(147000000, 174000000)]}
494

    
495
RT98U_BAND_LIMITS = {0x00: [(446000000, 446200000)],
496
                     0x01: [(400000000, 470000000)],
497
                     0x02: [(450000000, 470000000)]}
498

    
499

    
500
# Get band limits from a band limit value
501
def get_band_limits_Hz(radio_type, limit_value):
502
    if radio_type == "RT98U":
503
        if limit_value not in RT98U_BAND_LIMITS:
504
            limit_value = 0x01
505
            LOG.warning('Unknown band limit value 0x%02x, default to 0x01')
506
        bandlimitfrequencies = RT98U_BAND_LIMITS[limit_value]
507
    elif radio_type == "RT98V":
508
        if limit_value not in RT98V_BAND_LIMITS:
509
            limit_value = 0x01
510
            LOG.warning('Unknown band limit value 0x%02x, default to 0x01')
511
        bandlimitfrequencies = RT98V_BAND_LIMITS[limit_value]
512
    else:
513
        limit_value = 0x01
514
        LOG.warning('Unknown band limit value 0x%02x, default to 0x01')
515
        bandlimitfrequencies = RT98V_BAND_LIMITS[limit_value]
516
    return bandlimitfrequencies
517

    
518

    
519
def _echo_write(radio, data):
520
    try:
521
        radio.pipe.write(data)
522
        radio.pipe.read(len(data))
523
    except Exception, e:
524
        LOG.error("Error writing to radio: %s" % e)
525
        raise errors.RadioError("Unable to write to radio")
526

    
527

    
528
def _checksum(data):
529
    cs = 0
530
    for byte in data:
531
        cs += ord(byte)
532
    return cs % 256
533

    
534

    
535
def _read(radio, length):
536
    try:
537
        data = radio.pipe.read(length)
538
    except Exception, e:
539
        _finish(radio)
540
        LOG.error("Error reading from radio: %s" % e)
541
        raise errors.RadioError("Unable to read from radio")
542

    
543
    if len(data) != length:
544
        _finish(radio)
545
        LOG.error("Short read from radio (%i, expected %i)" %
546
                  (len(data), length))
547
        LOG.debug(util.hexprint(data))
548
        raise errors.RadioError("Short read from radio")
549
    return data
550

    
551

    
552
# strip trailing 0x00 to convert a string returned by bitwise.parse into a
553
# python string
554
def cstring_to_py_string(cstring):
555
    return "".join(c for c in cstring if c != '\x00')
556

    
557

    
558
# Check the radio version reported to see if it's one we support,
559
# returns bool version supported, and the band index
560
def check_ver(ver_response, allowed_types):
561
    ''' Check the returned radio version is one we approve of '''
562

    
563
    LOG.debug('ver_response = ')
564
    LOG.debug(util.hexprint(ver_response))
565

    
566
    resp = bitwise.parse(VER_FORMAT, ver_response)
567
    verok = False
568

    
569
    if resp.hdr == 0x49 and resp.ack == 0x06:
570
        model, version = [cstring_to_py_string(bitwise.get_string(s)).strip()
571
                          for s in (resp.model, resp.version)]
572
        LOG.debug('radio model: \'%s\' version: \'%s\'' %
573
                  (model, version))
574
        LOG.debug('allowed_types = %s' % allowed_types)
575

    
576
        if model in allowed_types:
577
            LOG.debug('model in allowed_types')
578

    
579
            if version in allowed_types[model]:
580
                LOG.debug('version in allowed_types[model]')
581
                verok = True
582
    else:
583
        _finish(radio)
584
        raise errors.RadioError('Failed to parse version response')
585

    
586
    return verok, str(resp.model), int(resp.bandlimit)
587

    
588

    
589
def _ident(radio):
590
    radio.pipe.timeout = 1
591
    _echo_write(radio, "PROGRAM")
592
    response = radio.pipe.read(3)
593
    if response != "QX\06":
594
        _finish(radio)
595
        LOG.debug("Response was :\n%s" % util.hexprint(response))
596
        raise errors.RadioError("Radio did not respond. Check connection.")
597
    _echo_write(radio, "\x02")
598
    ver_response = radio.pipe.read(16)
599
    LOG.debug(util.hexprint(ver_response))
600

    
601
    verok, model, bandlimit = check_ver(ver_response,
602
                                        radio.ALLOWED_RADIO_TYPES)
603
    if not verok:
604
        _finish(radio)
605
        raise errors.RadioError(
606
            'Radio version not in allowed list for %s-%s: %s' %
607
            (radio.VENDOR, radio.MODEL, util.hexprint(ver_response)))
608

    
609
    return model, bandlimit
610

    
611

    
612
def _send(radio, cmd, addr, length, data=None):
613
    frame = struct.pack(">cHb", cmd, addr, length)
614
    if data:
615
        frame += data
616
        frame += chr(_checksum(frame[1:]))
617
        frame += "\x06"
618
    _echo_write(radio, frame)
619
    LOG.debug("Sent:\n%s" % util.hexprint(frame))
620
    if data:
621
        result = radio.pipe.read(1)
622
        if result != "\x06":
623
            _finish(radio)
624
            LOG.debug("Ack was: %s" % repr(result))
625
            raise errors.RadioError("Radio did not accept block at %04x"
626
                                    % addr)
627
        return
628
    result = _read(radio, length + 6)
629
    LOG.debug("Got:\n%s" % util.hexprint(result))
630
    header = result[0:4]
631
    data = result[4:-2]
632
    ack = result[-1]
633
    if ack != "\x06":
634
        _finish(radio)
635
        LOG.debug("Ack was: %s" % repr(ack))
636
        raise errors.RadioError("Radio NAK'd block at %04x" % addr)
637
    _cmd, _addr, _length = struct.unpack(">cHb", header)
638
    if _addr != addr or _length != _length:
639
        _finish(radio)
640
        LOG.debug("Expected/Received:")
641
        LOG.debug(" Length: %02x/%02x" % (length, _length))
642
        LOG.debug(" Addr: %04x/%04x" % (addr, _addr))
643
        raise errors.RadioError("Radio send unexpected block")
644
    cs = _checksum(result[1:-2])
645
    if cs != ord(result[-2]):
646
        _finish(radio)
647
        LOG.debug("Calculated: %02x" % cs)
648
        LOG.debug("Actual:     %02x" % ord(result[-2]))
649
        raise errors.RadioError("Block at 0x%04x failed checksum" % addr)
650
    return data
651

    
652

    
653
def _finish(radio):
654
    endframe = "\x45\x4E\x44"
655
    _echo_write(radio, endframe)
656
    result = radio.pipe.read(1)
657
    if result != "\x06":
658
        LOG.error("Got:\n%s" % util.hexprint(result))
659
        raise errors.RadioError("Radio did not finish cleanly")
660

    
661

    
662
def do_download(radio):
663

    
664
    _ident(radio)
665

    
666
    _memobj = None
667
    data = ""
668

    
669
    for addr in range(0, radio._memsize, 0x10):
670
        block = _send(radio, 'R', addr, 0x10)
671
        data += block
672
        status = chirp_common.Status()
673
        status.cur = len(data)
674
        status.max = radio._memsize
675
        status.msg = "Downloading from radio"
676
        radio.status_fn(status)
677

    
678
    _finish(radio)
679

    
680
    return memmap.MemoryMap(data)
681

    
682

    
683
def do_upload(radio):
684
    model, bandlimit = _ident(radio)
685
    _embedded = radio._memobj.embedded_msg
686

    
687
    if model != str(_embedded.radio_type):
688
        LOG.warning('radio and image model types differ')
689
        LOG.warning('model type (radio): %s' % str(model))
690
        LOG.warning('model type (image): %s' % str(_embedded.radio_type))
691

    
692
        _finish(radio)
693

    
694
        msg = ("The upload was stopped because the radio type "
695
               "of the image (%s) does not match that "
696
               "of the radio (%s).")
697
        raise errors.RadioError(msg % (str(_embedded.radio_type), str(model)))
698

    
699
    if bandlimit != int(_embedded.mode):
700
        if str(_embedded.radio_type) == "RT98U":
701
            image_band_limits = LIST_RT98U_FREQS[int(_embedded.mode)]
702
        if str(_embedded.radio_type) == "RT98V":
703
            image_band_limits = LIST_RT98V_FREQS[int(_embedded.mode)]
704
        if model == "RT98U":
705
            radio_band_limits = LIST_RT98U_FREQS[int(bandlimit)]
706
        if model == "RT98V":
707
            radio_band_limits = LIST_RT98V_FREQS[int(bandlimit)]
708

    
709
        LOG.warning('radio and image band limits differ')
710
        LOG.warning('image band limits: %s' % image_band_limits)
711
        LOG.warning('radio band limits: %s' % radio_band_limits)
712

    
713
        _finish(radio)
714

    
715
        msg = ("The upload was stopped because the band limits "
716
               "of the image (%s) does not match that "
717
               "of the radio (%s).")
718
        raise errors.RadioError(msg % (image_band_limits, radio_band_limits))
719

    
720
    try:
721
        for start, end in radio._ranges:
722
            for addr in range(start, end, 0x10):
723
                block = radio._mmap[addr:addr+0x10]
724
                _send(radio, 'W', addr, len(block), block)
725
                status = chirp_common.Status()
726
                status.cur = addr
727
                status.max = end
728
                status.msg = "Uploading to Radio"
729
                radio.status_fn(status)
730
        _finish(radio)
731
    except errors.RadioError:
732
        raise
733
    except Exception as e:
734
        _finish(radio)
735
        raise errors.RadioError('Failed to upload to radio: %s' % e)
736

    
737

    
738
#
739
# The base class, extended for use with other models
740
#
741
class Rt98BaseRadio(chirp_common.CloneModeRadio,
742
                    chirp_common.ExperimentalRadio):
743
    """Retevis RT98 Base"""
744
    VENDOR = "Retevis"
745
    MODEL = "RT98 Base"
746
    BAUD_RATE = 9600
747

    
748
    _memsize = 0x3E00
749
    _ranges = [(0x0000, 0x3310),
750
               (0x3320, 0x3390)]
751

    
752
    @classmethod
753
    def get_prompts(cls):
754
        rp = chirp_common.RadioPrompts()
755
        rp.experimental = ("The Retevis RT98 driver is an beta version."
756
                           "Proceed with Caution and backup your data")
757
        return rp
758

    
759
    def get_features(self):
760
        class FakeEmbedded(object):
761
            mode = 0
762
            radio_type = 'RT98U'
763

    
764
        if self._memobj:
765
            _embedded = self._memobj.embedded_msg
766
        else:
767
            # If we have no memory object, take defaults for unit
768
            # test, make_supported, etc
769
            _embedded = FakeEmbedded()
770
        rf = chirp_common.RadioFeatures()
771
        rf.has_settings = True
772
        rf.has_bank = False
773
        rf.can_odd_split = True
774
        rf.has_name = True
775
        if _embedded.mode == 0:  # PMR or FreeNet
776
            rf.has_offset = False
777
        else:
778
            rf.has_offset = True
779
        rf.has_ctone = True
780
        rf.has_cross = True
781
        rf.has_tuning_step = False
782
        rf.has_dtcs = True
783
        rf.has_rx_dtcs = True
784
        rf.has_dtcs_polarity = True
785
        rf.valid_skips = ["", "S"]
786
        rf.memory_bounds = (1, 199)
787
        rf.valid_name_length = 6
788
        if _embedded.mode == 0:  # PMR or FreeNet
789
            rf.valid_duplexes = ['']
790
        else:
791
            rf.valid_duplexes = DUPLEXES + ['split', 'off']
792
        rf.valid_characters = chirp_common.CHARSET_UPPER_NUMERIC + "- "
793
        if _embedded.mode == 0:  # PMR or FreeNet
794
            rf.valid_modes = ['NFM']
795
        else:
796
            rf.valid_modes = ['FM', 'NFM']
797
        rf.valid_tmodes = ['', 'Tone', 'TSQL', 'DTCS', 'Cross']
798
        rf.valid_cross_modes = CROSS_MODES
799
        if _embedded.mode == 0:  # PMR or FreeNet
800
            if str(_embedded.radio_type) == "RT98U":
801
                rf.valid_power_levels = PMR_POWER_LEVELS
802
            if str(_embedded.radio_type) == "RT98V":
803
                rf.valid_power_levels = FREENET_POWER_LEVELS
804
        else:
805
            rf.valid_power_levels = POWER_LEVELS
806
        rf.valid_dtcs_codes = chirp_common.ALL_DTCS_CODES
807

    
808
        try:
809
            rf.valid_bands = get_band_limits_Hz(
810
                str(_embedded.radio_type),
811
                int(_embedded.mode))
812
        except TypeError as e:
813
            # If we're asked without memory loaded, assume the most permissive
814
            rf.valid_bands = get_band_limits_Hz(str(_embedded.radio_type), 1)
815
        except Exception as e:
816
            LOG.error('Failed to get band limits for RT98: %s' % e)
817
            rf.valid_bands = get_band_limits_Hz(str(_embedded.radio_type), 1)
818

    
819
        rf.valid_tuning_steps = TUNING_STEPS
820
        return rf
821

    
822
    def validate_memory(self, mem):
823
        _embedded = self._memobj.embedded_msg
824
        msgs = ""
825
        msgs = chirp_common.CloneModeRadio.validate_memory(self, mem)
826

    
827
        # FreeNet and PMR radio types
828
        if _embedded.mode == 0:  # PMR or FreeNet
829
            freq = float(mem.freq) / 1000000
830

    
831
            # FreeNet
832
            if str(_embedded.radio_type) == "RT98V":
833
                if freq not in FREENET_FREQS:
834
                    _msg_freq = 'Memory location not a valid FreeNet frequency'
835
                    # warn user invalid frequency
836
                    msgs.append(chirp_common.ValidationError(_msg_freq))
837

    
838
            # PMR
839
            if str(_embedded.radio_type) == "RT98U":
840
                if freq not in PMR_FREQS:
841
                    _msg_freq = 'Memory location not a valid PMR frequency'
842
                    # warn user invalid frequency
843
                    msgs.append(chirp_common.ValidationError(_msg_freq))
844

    
845
        return msgs
846

    
847
    # Do a download of the radio from the serial port
848
    def sync_in(self):
849
        self._mmap = do_download(self)
850
        self.process_mmap()
851

    
852
    # Do an upload of the radio to the serial port
853
    def sync_out(self):
854
        do_upload(self)
855

    
856
    def process_mmap(self):
857
        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
858

    
859
    # Return a raw representation of the memory object, which
860
    # is very helpful for development
861
    def get_raw_memory(self, number):
862
        return repr(self._memobj.memory[number - 1])
863

    
864
    def _get_dcs_index(self, _mem, which):
865
        base = getattr(_mem, '%scode' % which)
866
        extra = getattr(_mem, '%sdcsextra' % which)
867
        return (int(extra) << 8) | int(base)
868

    
869
    def _set_dcs_index(self, _mem, which, index):
870
        base = getattr(_mem, '%scode' % which)
871
        extra = getattr(_mem, '%sdcsextra' % which)
872
        base.set_value(index & 0xFF)
873
        extra.set_value(index >> 8)
874

    
875
    # Extract a high-level memory object from the low-level memory map
876
    # This is called to populate a memory in the UI
877
    def get_memory(self, number):
878
        _embedded = self._memobj.embedded_msg
879
        # Get a low-level memory object mapped to the image
880
        _mem = self._memobj.memory[number - 1]
881

    
882
        # get flag info
883
        cbyte = (number - 1) / 8
884
        cbit = 7 - ((number - 1) % 8)
885
        setflag = self._memobj.csetflag[cbyte].c[cbit]
886
        skipflag = self._memobj.cskipflag[cbyte].c[cbit]
887

    
888
        mem = chirp_common.Memory()
889

    
890
        mem.number = number  # Set the memory number
891

    
892
        # We'll consider any blank (i.e. 0MHz frequency) to be empty
893
        if _mem.freq == 0:
894
            mem.empty = True
895
            return mem
896

    
897
        if setflag == 0:
898
            mem.empty = True
899
            return mem
900

    
901
        if _mem.get_raw()[0] == "\xFF":
902
            mem.empty = True
903
            return mem
904

    
905
        # set the name
906
        mem.name = str(_mem.name).rstrip()  # Set the alpha tag
907

    
908
        # Convert your low-level frequency and offset to Hertz
909
        mem.freq = int(_mem.freq) * 10
910
        mem.offset = int(_mem.offset) * 10
911

    
912
        # Set the duplex flags
913
        if _mem.duplex == DUPLEX_POSSPLIT:
914
            mem.duplex = '+'
915
        elif _mem.duplex == DUPLEX_NEGSPLIT:
916
            mem.duplex = '-'
917
        elif _mem.duplex == DUPLEX_NOSPLIT:
918
            mem.duplex = ''
919
        elif _mem.duplex == DUPLEX_ODDSPLIT:
920
            mem.duplex = 'split'
921
        else:
922
            LOG.error('%s: get_mem: unhandled duplex: %02x' %
923
                      (mem.name, _mem.duplex))
924

    
925
        # handle tx off
926
        if _mem.tx_off:
927
            mem.duplex = 'off'
928

    
929
        # Set the channel width
930
        if _mem.channel_width == CHANNEL_WIDTH_12d5kHz:
931
            mem.mode = 'NFM'
932
        elif _embedded.mode == 0:  # PMR or FreeNet
933
            LOG.info('PMR and FreeNet channels must be Channel Width 12.5kHz')
934
            mem.mode = 'NFM'
935
        elif _mem.channel_width == CHANNEL_WIDTH_25kHz:
936
            mem.mode = 'FM'
937
        elif _mem.channel_width == CHANNEL_WIDTH_20kHz:
938
            LOG.info(
939
                '%s: get_mem: promoting 20kHz channel width to 25kHz' %
940
                mem.name)
941
            mem.mode = 'FM'
942
        else:
943
            LOG.error('%s: get_mem: unhandled channel width: 0x%02x' %
944
                      (mem.name, _mem.channel_width))
945

    
946
        # set the power level
947
        if _embedded.mode == 0:  # PMR or FreeNet
948
            if str(_embedded.radio_type) == "RT98U":
949
                LOG.info('using PMR power levels')
950
                _levels = PMR_POWER_LEVELS
951
            if str(_embedded.radio_type) == "RT98V":
952
                LOG.info('using FreeNet power levels')
953
                _levels = FREENET_POWER_LEVELS
954
        else:  # COM or COMII
955
            LOG.info('using general power levels')
956
            _levels = POWER_LEVELS
957

    
958
        if _mem.txpower == TXPOWER_LOW:
959
            mem.power = _levels[0]
960
        elif _embedded.mode == 0:  # PMR or FreeNet
961
            LOG.info('FreeNet or PMR channel is not set to TX Power Low')
962
            LOG.info('Setting channel to TX Power Low')
963
            mem.power = _levels[0]
964
        elif _mem.txpower == TXPOWER_MED:
965
            mem.power = _levels[1]
966
        elif _mem.txpower == TXPOWER_HIGH:
967
            mem.power = _levels[2]
968
        else:
969
            LOG.error('%s: get_mem: unhandled power level: 0x%02x' %
970
                      (mem.name, _mem.txpower))
971

    
972
        # CTCSS Tones and DTCS Codes
973
        rxtone = txtone = None
974

    
975
        rxmode = TMODES[_mem.rxtmode]
976
        txmode = TMODES[_mem.txtmode]
977

    
978
        if rxmode == "Tone":
979
            rxtone = TONES[_mem.rxtone]
980
        elif rxmode == "DTCS":
981
            rxtone = chirp_common.ALL_DTCS_CODES[self._get_dcs_index(
982
                                                 _mem, 'rx')]
983

    
984
        if txmode == "Tone":
985
            txtone = TONES[_mem.txtone]
986
        elif txmode == "DTCS":
987
            txtone = chirp_common.ALL_DTCS_CODES[self._get_dcs_index(
988
                                                 _mem, 'tx')]
989

    
990
        rxpol = _mem.rxinv and "R" or "N"
991
        txpol = _mem.txinv and "R" or "N"
992

    
993
        chirp_common.split_tone_decode(mem,
994
                                       (txmode, txtone, txpol),
995
                                       (rxmode, rxtone, rxpol))
996

    
997
        # Check if this memory is in the scan enabled list
998
        mem.skip = "S" if skipflag == 0 else ""
999

    
1000
        # Extra
1001
        mem.extra = RadioSettingGroup("extra", "Extra")
1002

    
1003
        rs = RadioSettingValueBoolean(bool(_mem.busychannellockout))
1004
        rset = RadioSetting("busychannellockout", "Busy channel lockout", rs)
1005
        mem.extra.append(rset)
1006

    
1007
        rs = RadioSettingValueBoolean(bool(_mem.reverse))
1008
        rset = RadioSetting("reverse", "Reverse", rs)
1009
        mem.extra.append(rset)
1010

    
1011
        rs = RadioSettingValueBoolean(bool(_mem.talkaround))
1012
        rset = RadioSetting("talkaround", "Talk around", rs)
1013
        mem.extra.append(rset)
1014

    
1015
        rs = RadioSettingValueBoolean(bool(_mem.squelch_mode))
1016
        rset = RadioSetting("squelch_mode", "Squelch mode", rs)
1017
        mem.extra.append(rset)
1018

    
1019
        return mem
1020

    
1021
    # Store details about a high-level memory to the memory map
1022
    # This is called when a user edits a memory in the UI
1023
    def set_memory(self, mem):
1024
        _embedded = self._memobj.embedded_msg
1025
        # Get a low-level memory object mapped to the image
1026

    
1027
        _mem = self._memobj.memory[mem.number - 1]
1028

    
1029
        cbyte = (mem.number - 1) / 8
1030
        cbit = 7 - ((mem.number - 1) % 8)
1031

    
1032
        if mem.empty:
1033
            self._memobj.csetflag[cbyte].c[cbit] = 0
1034
            self._memobj.cskipflag[cbyte].c[cbit] = 0
1035
            _mem.set_raw('\xff' * (_mem.size() / 8))
1036
            return
1037

    
1038
        _mem.set_raw('\x00' * (_mem.size() / 8))
1039

    
1040
        # set the occupied bitfield
1041
        self._memobj.csetflag[cbyte].c[cbit] = 1
1042
        # set the scan add bitfield
1043
        self._memobj.cskipflag[cbyte].c[cbit] = 0 if (mem.skip == "S") else 1
1044

    
1045
        _mem.freq = mem.freq / 10             # Convert to low-level frequency
1046
        _mem.offset = mem.offset / 10         # Convert to low-level frequency
1047

    
1048
        # Store the alpha tag
1049
        _mem.name = mem.name.ljust(6)[:6]  # Store the alpha tag
1050

    
1051
        # Set duplex bitfields
1052
        if mem.duplex == '+':
1053
            _mem.duplex = DUPLEX_POSSPLIT
1054
        elif mem.duplex == '-':
1055
            _mem.duplex = DUPLEX_NEGSPLIT
1056
        elif mem.duplex == '':
1057
            _mem.duplex = DUPLEX_NOSPLIT
1058
        elif mem.duplex == 'split':
1059
            diff = mem.offset - mem.freq
1060
            _mem.duplex = DUPLEXES.index("-") \
1061
                if diff < 0 else DUPLEXES.index("+")
1062
            _mem.offset = abs(diff) / 10
1063
        else:
1064
            LOG.error('%s: set_mem: unhandled duplex: %s' %
1065
                      (mem.name, mem.duplex))
1066

    
1067
        # handle tx off
1068
        _mem.tx_off = 0
1069
        if mem.duplex == 'off':
1070
            _mem.tx_off = 1
1071

    
1072
        # Set the channel width - remember we promote 20kHz channels to FM
1073
        # on import, so don't handle them here
1074
        if mem.mode == 'FM':
1075
            _mem.channel_width = CHANNEL_WIDTH_25kHz
1076
        elif mem.mode == 'NFM':
1077
            _mem.channel_width = CHANNEL_WIDTH_12d5kHz
1078
        else:
1079
            LOG.error('%s: set_mem: unhandled mode: %s' % (
1080
                mem.name, mem.mode))
1081

    
1082
        # CTCSS Tones and DTCS Codes
1083
        ((txmode, txtone, txpol),
1084
         (rxmode, rxtone, rxpol)) = chirp_common.split_tone_encode(mem)
1085

    
1086
        _mem.txtmode = TMODES.index(txmode)
1087

    
1088
        _mem.rxtmode = TMODES.index(rxmode)
1089

    
1090
        if txmode == "Tone":
1091
            _mem.txtone = TONES.index(txtone)
1092
        elif txmode == "DTCS":
1093
            self._set_dcs_index(_mem, 'tx',
1094
                                chirp_common.ALL_DTCS_CODES.index(txtone))
1095

    
1096
        _mem.squelch_mode = False
1097
        if rxmode == "Tone":
1098
            _mem.rxtone = TONES.index(rxtone)
1099
            _mem.squelch_mode = True
1100
        elif rxmode == "DTCS":
1101
            self._set_dcs_index(_mem, 'rx',
1102
                                chirp_common.ALL_DTCS_CODES.index(rxtone))
1103
            _mem.squelch_mode = True
1104

    
1105
        _mem.txinv = txpol == "R"
1106
        _mem.rxinv = rxpol == "R"
1107

    
1108
        # set the power level
1109
        if mem.power == POWER_LEVELS[0]:
1110
            _mem.txpower = TXPOWER_LOW
1111
        elif mem.power == POWER_LEVELS[1]:
1112
            _mem.txpower = TXPOWER_MED
1113
        elif mem.power == POWER_LEVELS[2]:
1114
            _mem.txpower = TXPOWER_HIGH
1115
        else:
1116
            LOG.error('%s: set_mem: unhandled power level: %s' %
1117
                      (mem.name, mem.power))
1118

    
1119
        # extra settings
1120
        for setting in mem.extra:
1121
            setattr(_mem, setting.get_name(), setting.value)
1122

    
1123
    def _get_settings(self):
1124
        _embedded = self._memobj.embedded_msg
1125
        _settings = self._memobj.settings
1126
        _settings2 = self._memobj.settings2
1127
        _settings3 = self._memobj.settings3
1128
        _slabel = self._memobj.slabel
1129

    
1130
        function = RadioSettingGroup("function", "Function Setup")
1131
        group = RadioSettings(function)
1132

    
1133
        # Function Setup
1134
        # MODE SET
1135
        rs = RadioSettingValueList(LIST_DISPLAY_MODE,
1136
                                   LIST_DISPLAY_MODE[_settings.display_mode])
1137
        rset = RadioSetting("display_mode", "Display Mode", rs)
1138
        function.append(rset)
1139

    
1140
        rs = RadioSettingValueInteger(1, 199, _settings3.ch_number + 1)
1141
        rset = RadioSetting("settings3.ch_number", "Channel Number", rs)
1142
        function.append(rset)
1143

    
1144
        # DISPLAY SET
1145
        def _filter(name):
1146
            filtered = ""
1147
            for char in str(name):
1148
                if char in chirp_common.CHARSET_ASCII:
1149
                    filtered += char
1150
                else:
1151
                    filtered += " "
1152
            return filtered
1153

    
1154
        val = RadioSettingValueString(0, 6, _filter(_slabel.startname))
1155
        rs = RadioSetting("slabel.startname", "Startup Label", val)
1156
        function.append(rs)
1157

    
1158
        # VOL SET
1159
        rs = RadioSettingValueBoolean(bool(_settings.beep))
1160
        rset = RadioSetting("beep", "Beep Prompt", rs)
1161
        function.append(rset)
1162

    
1163
        rs = RadioSettingValueInteger(1, 30, _settings.volume)
1164
        rset = RadioSetting("volume", "Volume Level", rs)
1165
        function.append(rset)
1166

    
1167
        rs = RadioSettingValueInteger(1, 16, _settings.mic_gain)
1168
        rset = RadioSetting("mic_gain", "Mic Gain", rs)
1169
        function.append(rset)
1170

    
1171
        # ON/OFF SET
1172
        rs = RadioSettingValueList(LIST_APO,
1173
                                   LIST_APO[_settings.auto_power_off])
1174
        rset = RadioSetting("auto_power_off", "Auto Power Off", rs)
1175
        function.append(rset)
1176

    
1177
        rs = RadioSettingValueList(LIST_AOP, LIST_AOP[_settings.auto_power_on])
1178
        rset = RadioSetting("auto_power_on", "Power On Method", rs)
1179
        function.append(rset)
1180

    
1181
        # STE SET
1182
        rs = RadioSettingValueList(LIST_STE_FREQ,
1183
                                   LIST_STE_FREQ[_settings.ste_frequency])
1184
        rset = RadioSetting("ste_frequency", "STE Frequency", rs)
1185
        function.append(rset)
1186

    
1187
        rs = RadioSettingValueList(LIST_STE_TYPE,
1188
                                   LIST_STE_TYPE[_settings.ste_type])
1189
        rset = RadioSetting("ste_type", "STE Type", rs)
1190
        function.append(rset)
1191

    
1192
        # FUNCTION SET
1193
        rs = RadioSettingValueList(LIST_STEP, LIST_STEP[_settings.tuning_step])
1194
        rset = RadioSetting("tuning_step", "Tuning Step", rs)
1195
        function.append(rset)
1196

    
1197
        rs = RadioSettingValueList(LIST_SQUELCH,
1198
                                   LIST_SQUELCH[_settings.squelch])
1199
        rset = RadioSetting("squelch", "Squelch Level", rs)
1200
        function.append(rset)
1201

    
1202
        rs = RadioSettingValueBoolean(bool(_settings.sql_key_function))
1203
        rset = RadioSetting("sql_key_function", "SQL Key Function", rs)
1204
        function.append(rset)
1205

    
1206
        rs = RadioSettingValueList(LIST_TIMEOUT,
1207
                                   LIST_TIMEOUT[_settings.timeout_timer])
1208
        rset = RadioSetting("timeout_timer", "Timeout Timer", rs)
1209
        function.append(rset)
1210

    
1211
        # uncategorized
1212
        rs = RadioSettingValueBoolean(bool(_settings.save_chan_param))
1213
        rset = RadioSetting("save_chan_param", "Save Channel Parameters", rs)
1214
        function.append(rset)
1215

    
1216
        rs = RadioSettingValueBoolean(bool(_settings.forbid_chan_menu))
1217
        rset = RadioSetting("forbid_chan_menu", "Forbid Channel Menu", rs)
1218
        function.append(rset)
1219

    
1220
        rs = RadioSettingValueBoolean(bool(not _settings.forbid_initialize))
1221
        rset = RadioSetting("forbid_initialize", "Forbid Initialize", rs)
1222
        function.append(rset)
1223

    
1224
        rs = RadioSettingValueBoolean(bool(_settings.forbid_setting))
1225
        rset = RadioSetting("forbid_setting", "Forbid Setting", rs)
1226
        function.append(rset)
1227

    
1228
        # Information Of Scanning Channel
1229
        scanning = RadioSettingGroup("scanning", "Scanning Setup")
1230
        group.append(scanning)
1231

    
1232
        rs = RadioSettingValueBoolean(bool(_settings2.scan_mode))
1233
        rset = RadioSetting("settings2.scan_mode", "Scan Mode", rs)
1234
        scanning.append(rset)
1235

    
1236
        rs = RadioSettingValueList(LIST_PRIORITY_CH,
1237
                                   LIST_PRIORITY_CH[_settings2.priority_ch])
1238
        rset = RadioSetting("settings2.priority_ch", "Priority Channel", rs)
1239
        scanning.append(rset)
1240

    
1241
        rs = RadioSettingValueInteger(1, 199, _settings2.priority_ch1 + 1)
1242
        rset = RadioSetting("settings2.priority_ch1", "Priority Channel 1", rs)
1243
        scanning.append(rset)
1244

    
1245
        rs = RadioSettingValueInteger(1, 199, _settings2.priority_ch2 + 1)
1246
        rset = RadioSetting("settings2.priority_ch2", "Priority Channel 2", rs)
1247
        scanning.append(rset)
1248

    
1249
        rs = RadioSettingValueList(LIST_REVERT_CH,
1250
                                   LIST_REVERT_CH[_settings2.revert_ch])
1251
        rset = RadioSetting("settings2.revert_ch", "Revert Channel", rs)
1252
        scanning.append(rset)
1253

    
1254
        rs = RadioSettingValueList(LIST_TIME46,
1255
                                   LIST_TIME46[_settings2.look_back_time_a])
1256
        rset = RadioSetting("settings2.look_back_time_a",
1257
                            "Look Back Time A", rs)
1258
        scanning.append(rset)
1259

    
1260
        rs = RadioSettingValueList(LIST_TIME46,
1261
                                   LIST_TIME46[_settings2.look_back_time_b])
1262
        rset = RadioSetting("settings2.look_back_time_b",
1263
                            "Look Back Time B", rs)
1264
        scanning.append(rset)
1265

    
1266
        rs = RadioSettingValueList(LIST_TIME50,
1267
                                   LIST_TIME50[_settings2.dropout_delay_time])
1268
        rset = RadioSetting("settings2.dropout_delay_time",
1269
                            "Dropout Delay Time", rs)
1270
        scanning.append(rset)
1271

    
1272
        rs = RadioSettingValueList(LIST_TIME50,
1273
                                   LIST_TIME50[_settings2.dwell_time])
1274
        rset = RadioSetting("settings2.dwell_time", "Dwell Time", rs)
1275
        scanning.append(rset)
1276

    
1277
        # Embedded Message
1278
        embedded = RadioSettingGroup("embedded", "Embedded Message")
1279
        group.append(embedded)
1280

    
1281
        rs = RadioSettingValueString(0, 5, _filter(_embedded.radio_type))
1282
        rs.set_mutable(False)
1283
        rset = RadioSetting("embedded_msg.radio_type", "Radio Type", rs)
1284
        embedded.append(rset)
1285

    
1286
        if str(_embedded.radio_type) == "RT98V":
1287
            options = LIST_RT98V_MODES
1288
        else:
1289
            options = LIST_RT98U_MODES
1290
        rs = RadioSettingValueList(options, options[_embedded.mode])
1291
        rs.set_mutable(False)
1292
        rset = RadioSetting("embedded_msg.mode", "Mode", rs)
1293
        embedded.append(rset)
1294

    
1295
        # frequency
1296
        if str(_embedded.radio_type) == "RT98V":
1297
            options = LIST_RT98V_FREQS
1298
        else:
1299
            options = LIST_RT98U_FREQS
1300
        rs = RadioSettingValueList(options, options[_settings3.bandlimit])
1301
        rs.set_mutable(False)
1302
        rset = RadioSetting("settings3.bandlimit", "Frequency", rs)
1303
        embedded.append(rset)
1304

    
1305
        rs = RadioSettingValueString(0, 10, _filter(_embedded.date_mfg))
1306
        rs.set_mutable(False)
1307
        rset = RadioSetting("embedded_msg.date_mfg", "Production Date", rs)
1308
        embedded.append(rset)
1309

    
1310
        rs = RadioSettingValueString(0, 4, _filter(_embedded.mcu_version))
1311
        rs.set_mutable(False)
1312
        rset = RadioSetting("embedded_msg.mcu_version", "MCU Version", rs)
1313
        embedded.append(rset)
1314

    
1315
        return group
1316

    
1317
    def get_settings(self):
1318
        try:
1319
            return self._get_settings()
1320
        except:
1321
            import traceback
1322
            LOG.error("failed to parse settings")
1323
            traceback.print_exc()
1324
            return None
1325

    
1326
    def set_settings(self, settings):
1327
        for element in settings:
1328
            if not isinstance(element, RadioSetting):
1329
                self.set_settings(element)
1330
                continue
1331
            else:
1332
                try:
1333
                    if "." in element.get_name():
1334
                        bits = element.get_name().split(".")
1335
                        obj = self._memobj
1336
                        for bit in bits[:-1]:
1337
                            obj = getattr(obj, bit)
1338
                        setting = bits[-1]
1339
                    else:
1340
                        obj = self._memobj.settings
1341
                        setting = element.get_name()
1342

    
1343
                    if element.has_apply_callback():
1344
                        LOG.debug("using apply callback")
1345
                        element.run_apply_callback()
1346
                    elif setting == "ch_number":
1347
                        setattr(obj, setting, int(element.value) - 1)
1348
                    elif setting == "forbid_initialize":
1349
                        setattr(obj, setting, not int(element.value))
1350
                    elif setting == "priority_ch1":
1351
                        setattr(obj, setting, int(element.value) - 1)
1352
                    elif setting == "priority_ch2":
1353
                        setattr(obj, setting, int(element.value) - 1)
1354
                    elif element.value.get_mutable():
1355
                        LOG.debug("Setting %s = %s" % (setting, element.value))
1356
                        setattr(obj, setting, element.value)
1357
                except Exception, e:
1358
                    LOG.debug(element.get_name())
1359
                    raise
1360

    
1361
    @classmethod
1362
    def match_model(cls, filedata, filename):
1363
        # This radio has always been post-metadata, so never do
1364
        # old-school detection
1365
        return False
1366

    
1367

    
1368
@directory.register
1369
class Rt98Radio(Rt98BaseRadio):
1370
    """Retevis RT98"""
1371
    VENDOR = "Retevis"
1372
    MODEL = "RT98"
1373
    # Allowed radio types is a dict keyed by model of a list of version
1374
    # strings
1375
    ALLOWED_RADIO_TYPES = {'RT98V': ['V100'],
1376
                           'RT98U': ['V100'],
1377
                           'AT-779V': ['V100'],
1378
                           'AT-779U': ['V100']}
(9-9/30)