Project

General

Profile

Bug #9353 » retevis_rt98_at-779v.py

Jim Unroe, 09/06/2021 02:34 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[5];
480
u8 unknown[2];
481
u8 bandlimit;
482
char version[6];
483
u8 ack;
484
'''
485

    
486

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

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

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

    
500

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

    
515

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

    
524

    
525
def _checksum(data):
526
    cs = 0
527
    for byte in data:
528
        cs += ord(byte)
529
    return cs % 256
530

    
531

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

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

    
548

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

    
554

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

    
560
    LOG.debug('ver_response = ')
561
    LOG.debug(util.hexprint(ver_response))
562

    
563
    resp = bitwise.parse(VER_FORMAT, ver_response)
564
    verok = False
565

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

    
573
        if model in allowed_types:
574
            LOG.debug('model in allowed_types')
575

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

    
583
    return verok, str(resp.model), int(resp.bandlimit)
584

    
585

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

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

    
606
    return model, bandlimit
607

    
608

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

    
649

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

    
658

    
659
def do_download(radio):
660

    
661
    _ident(radio)
662

    
663
    _memobj = None
664
    data = ""
665

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

    
675
    _finish(radio)
676

    
677
    return memmap.MemoryMap(data)
678

    
679

    
680
def do_upload(radio):
681
    model, bandlimit = _ident(radio)
682
    _embedded = radio._memobj.embedded_msg
683

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

    
689
        _finish(radio)
690

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

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

    
706
        LOG.warning('radio and image band limits differ')
707
        LOG.warning('image band limits: %s' % image_band_limits)
708
        LOG.warning('radio band limits: %s' % radio_band_limits)
709

    
710
        _finish(radio)
711

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

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

    
734

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

    
745
    _memsize = 0x3E00
746
    _ranges = [(0x0000, 0x3310),
747
               (0x3320, 0x3390)]
748

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

    
756
    def get_features(self):
757
        class FakeEmbedded(object):
758
            mode = 0
759
            radio_type = 'RT98U'
760

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

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

    
816
        rf.valid_tuning_steps = TUNING_STEPS
817
        return rf
818

    
819
    def validate_memory(self, mem):
820
        _embedded = self._memobj.embedded_msg
821
        msgs = ""
822
        msgs = chirp_common.CloneModeRadio.validate_memory(self, mem)
823

    
824
        # FreeNet and PMR radio types
825
        if _embedded.mode == 0:  # PMR or FreeNet
826
            freq = float(mem.freq) / 1000000
827

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

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

    
842
        return msgs
843

    
844
    # Do a download of the radio from the serial port
845
    def sync_in(self):
846
        self._mmap = do_download(self)
847
        self.process_mmap()
848

    
849
    # Do an upload of the radio to the serial port
850
    def sync_out(self):
851
        do_upload(self)
852

    
853
    def process_mmap(self):
854
        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
855

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

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

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

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

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

    
885
        mem = chirp_common.Memory()
886

    
887
        mem.number = number  # Set the memory number
888

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

    
894
        if setflag == 0:
895
            mem.empty = True
896
            return mem
897

    
898
        if _mem.get_raw()[0] == "\xFF":
899
            mem.empty = True
900
            return mem
901

    
902
        # set the name
903
        mem.name = str(_mem.name).rstrip()  # Set the alpha tag
904

    
905
        # Convert your low-level frequency and offset to Hertz
906
        mem.freq = int(_mem.freq) * 10
907
        mem.offset = int(_mem.offset) * 10
908

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

    
922
        # handle tx off
923
        if _mem.tx_off:
924
            mem.duplex = 'off'
925

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

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

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

    
969
        # CTCSS Tones and DTCS Codes
970
        rxtone = txtone = None
971

    
972
        rxmode = TMODES[_mem.rxtmode]
973
        txmode = TMODES[_mem.txtmode]
974

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

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

    
987
        rxpol = _mem.rxinv and "R" or "N"
988
        txpol = _mem.txinv and "R" or "N"
989

    
990
        chirp_common.split_tone_decode(mem,
991
                                       (txmode, txtone, txpol),
992
                                       (rxmode, rxtone, rxpol))
993

    
994
        # Check if this memory is in the scan enabled list
995
        mem.skip = "S" if skipflag == 0 else ""
996

    
997
        # Extra
998
        mem.extra = RadioSettingGroup("extra", "Extra")
999

    
1000
        rs = RadioSettingValueBoolean(bool(_mem.busychannellockout))
1001
        rset = RadioSetting("busychannellockout", "Busy channel lockout", rs)
1002
        mem.extra.append(rset)
1003

    
1004
        rs = RadioSettingValueBoolean(bool(_mem.reverse))
1005
        rset = RadioSetting("reverse", "Reverse", rs)
1006
        mem.extra.append(rset)
1007

    
1008
        rs = RadioSettingValueBoolean(bool(_mem.talkaround))
1009
        rset = RadioSetting("talkaround", "Talk around", rs)
1010
        mem.extra.append(rset)
1011

    
1012
        rs = RadioSettingValueBoolean(bool(_mem.squelch_mode))
1013
        rset = RadioSetting("squelch_mode", "Squelch mode", rs)
1014
        mem.extra.append(rset)
1015

    
1016
        return mem
1017

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

    
1024
        _mem = self._memobj.memory[mem.number - 1]
1025

    
1026
        cbyte = (mem.number - 1) / 8
1027
        cbit = 7 - ((mem.number - 1) % 8)
1028

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

    
1035
        _mem.set_raw('\x00' * (_mem.size() / 8))
1036

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

    
1042
        _mem.freq = mem.freq / 10             # Convert to low-level frequency
1043
        _mem.offset = mem.offset / 10         # Convert to low-level frequency
1044

    
1045
        # Store the alpha tag
1046
        _mem.name = mem.name.ljust(6)[:6]  # Store the alpha tag
1047

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

    
1064
        # handle tx off
1065
        _mem.tx_off = 0
1066
        if mem.duplex == 'off':
1067
            _mem.tx_off = 1
1068

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

    
1079
        # CTCSS Tones and DTCS Codes
1080
        ((txmode, txtone, txpol),
1081
         (rxmode, rxtone, rxpol)) = chirp_common.split_tone_encode(mem)
1082

    
1083
        _mem.txtmode = TMODES.index(txmode)
1084

    
1085
        _mem.rxtmode = TMODES.index(rxmode)
1086

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

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

    
1102
        _mem.txinv = txpol == "R"
1103
        _mem.rxinv = rxpol == "R"
1104

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

    
1116
        # extra settings
1117
        for setting in mem.extra:
1118
            setattr(_mem, setting.get_name(), setting.value)
1119

    
1120
    def _get_settings(self):
1121
        _embedded = self._memobj.embedded_msg
1122
        _settings = self._memobj.settings
1123
        _settings2 = self._memobj.settings2
1124
        _settings3 = self._memobj.settings3
1125
        _slabel = self._memobj.slabel
1126

    
1127
        function = RadioSettingGroup("function", "Function Setup")
1128
        group = RadioSettings(function)
1129

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

    
1137
        rs = RadioSettingValueInteger(1, 199, _settings3.ch_number + 1)
1138
        rset = RadioSetting("settings3.ch_number", "Channel Number", rs)
1139
        function.append(rset)
1140

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

    
1151
        val = RadioSettingValueString(0, 6, _filter(_slabel.startname))
1152
        rs = RadioSetting("slabel.startname", "Startup Label", val)
1153
        function.append(rs)
1154

    
1155
        # VOL SET
1156
        rs = RadioSettingValueBoolean(bool(_settings.beep))
1157
        rset = RadioSetting("beep", "Beep Prompt", rs)
1158
        function.append(rset)
1159

    
1160
        rs = RadioSettingValueInteger(1, 30, _settings.volume)
1161
        rset = RadioSetting("volume", "Volume Level", rs)
1162
        function.append(rset)
1163

    
1164
        rs = RadioSettingValueInteger(1, 16, _settings.mic_gain)
1165
        rset = RadioSetting("mic_gain", "Mic Gain", rs)
1166
        function.append(rset)
1167

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

    
1174
        rs = RadioSettingValueList(LIST_AOP, LIST_AOP[_settings.auto_power_on])
1175
        rset = RadioSetting("auto_power_on", "Power On Method", rs)
1176
        function.append(rset)
1177

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

    
1184
        rs = RadioSettingValueList(LIST_STE_TYPE,
1185
                                   LIST_STE_TYPE[_settings.ste_type])
1186
        rset = RadioSetting("ste_type", "STE Type", rs)
1187
        function.append(rset)
1188

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

    
1194
        rs = RadioSettingValueList(LIST_SQUELCH,
1195
                                   LIST_SQUELCH[_settings.squelch])
1196
        rset = RadioSetting("squelch", "Squelch Level", rs)
1197
        function.append(rset)
1198

    
1199
        rs = RadioSettingValueBoolean(bool(_settings.sql_key_function))
1200
        rset = RadioSetting("sql_key_function", "SQL Key Function", rs)
1201
        function.append(rset)
1202

    
1203
        rs = RadioSettingValueList(LIST_TIMEOUT,
1204
                                   LIST_TIMEOUT[_settings.timeout_timer])
1205
        rset = RadioSetting("timeout_timer", "Timeout Timer", rs)
1206
        function.append(rset)
1207

    
1208
        # uncategorized
1209
        rs = RadioSettingValueBoolean(bool(_settings.save_chan_param))
1210
        rset = RadioSetting("save_chan_param", "Save Channel Parameters", rs)
1211
        function.append(rset)
1212

    
1213
        rs = RadioSettingValueBoolean(bool(_settings.forbid_chan_menu))
1214
        rset = RadioSetting("forbid_chan_menu", "Forbid Channel Menu", rs)
1215
        function.append(rset)
1216

    
1217
        rs = RadioSettingValueBoolean(bool(not _settings.forbid_initialize))
1218
        rset = RadioSetting("forbid_initialize", "Forbid Initialize", rs)
1219
        function.append(rset)
1220

    
1221
        rs = RadioSettingValueBoolean(bool(_settings.forbid_setting))
1222
        rset = RadioSetting("forbid_setting", "Forbid Setting", rs)
1223
        function.append(rset)
1224

    
1225
        # Information Of Scanning Channel
1226
        scanning = RadioSettingGroup("scanning", "Scanning Setup")
1227
        group.append(scanning)
1228

    
1229
        rs = RadioSettingValueBoolean(bool(_settings2.scan_mode))
1230
        rset = RadioSetting("settings2.scan_mode", "Scan Mode", rs)
1231
        scanning.append(rset)
1232

    
1233
        rs = RadioSettingValueList(LIST_PRIORITY_CH,
1234
                                   LIST_PRIORITY_CH[_settings2.priority_ch])
1235
        rset = RadioSetting("settings2.priority_ch", "Priority Channel", rs)
1236
        scanning.append(rset)
1237

    
1238
        rs = RadioSettingValueInteger(1, 199, _settings2.priority_ch1 + 1)
1239
        rset = RadioSetting("settings2.priority_ch1", "Priority Channel 1", rs)
1240
        scanning.append(rset)
1241

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

    
1246
        rs = RadioSettingValueList(LIST_REVERT_CH,
1247
                                   LIST_REVERT_CH[_settings2.revert_ch])
1248
        rset = RadioSetting("settings2.revert_ch", "Revert Channel", rs)
1249
        scanning.append(rset)
1250

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

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

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

    
1269
        rs = RadioSettingValueList(LIST_TIME50,
1270
                                   LIST_TIME50[_settings2.dwell_time])
1271
        rset = RadioSetting("settings2.dwell_time", "Dwell Time", rs)
1272
        scanning.append(rset)
1273

    
1274
        # Embedded Message
1275
        embedded = RadioSettingGroup("embedded", "Embedded Message")
1276
        group.append(embedded)
1277

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

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

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

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

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

    
1312
        return group
1313

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

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

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

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

    
1364

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