Project

General

Profile

Bug #9353 » retevis_rt98_at-779v.py

Jim Unroe, 09/10/2021 09:14 AM

 
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
LIST_VFOMR = ["MR", "VFO"]
88
LIST_SCAN = ["TO", "CO", "SE"]
89

    
90
LIST_PRIORITY_CH = ["Off", "Priority Channel 1", "Priority Channel 2",
91
                    "Priority Channel 1 + Priority Channel 2"]
92

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

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

    
110
LIST_RT98V_MODES = ["FreeNet", "COM", "COMII"]
111
LIST_RT98U_MODES = ["PMR", "COM", "COMII"]
112

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

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

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

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

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

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

    
255
"""
256

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

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

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

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

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

    
476

    
477
# Format for the version messages returned by the radio
478
VER_FORMAT = '''
479
u8 hdr;
480
char model[7];
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 str(radio_type) == "RT98U" or "AT-779U":
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 str(radio_type) == "RT98V" or "AT-779V":
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
    else:
514
        LOG.error('Unknown band limit value 0x%02x')
515
        raise errors.RadioError("Unknown band 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" or "AT-779U":
701
            image_band_limits = LIST_RT98U_FREQS[int(_embedded.mode)]
702
        if str(_embedded.radio_type) == "RT98V" or "AT-779V":
703
            image_band_limits = LIST_RT98V_FREQS[int(_embedded.mode)]
704
        if model == "RT98U" or "AT-779U":
705
            radio_band_limits = LIST_RT98U_FREQS[int(bandlimit)]
706
        if model == "RT98V" or "AT-779V":
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
        #if str(_embedded.radio_type) == "AT-779U" or "AT-779V":
1141
        #    rs = RadioSettingValueList(LIST_VFOMR,
1142
        #                               LIST_VFOMR[_settings.vfomr])
1143
        #    rset = RadioSetting("vfomr", "VFO / MR", rs)
1144
        #    function.append(rset)
1145

    
1146
        rs = RadioSettingValueInteger(1, 199, _settings3.ch_number + 1)
1147
        rset = RadioSetting("settings3.ch_number", "Channel Number", rs)
1148
        function.append(rset)
1149

    
1150
        # DISPLAY SET
1151
        def _filter(name):
1152
            filtered = ""
1153
            for char in str(name):
1154
                if char in chirp_common.CHARSET_ASCII:
1155
                    filtered += char
1156
                else:
1157
                    filtered += " "
1158
            return filtered
1159

    
1160
        val = RadioSettingValueString(0, 6, _filter(_slabel.startname))
1161
        rs = RadioSetting("slabel.startname", "Startup Label", val)
1162
        function.append(rs)
1163

    
1164
        # VOL SET
1165
        rs = RadioSettingValueBoolean(bool(_settings.beep))
1166
        rset = RadioSetting("beep", "Beep Prompt", rs)
1167
        function.append(rset)
1168

    
1169
        rs = RadioSettingValueInteger(1, 30, _settings.volume)
1170
        rset = RadioSetting("volume", "Volume Level", rs)
1171
        function.append(rset)
1172

    
1173
        rs = RadioSettingValueInteger(1, 16, _settings.mic_gain)
1174
        rset = RadioSetting("mic_gain", "Mic Gain", rs)
1175
        function.append(rset)
1176

    
1177
        # ON/OFF SET
1178
        rs = RadioSettingValueList(LIST_APO,
1179
                                   LIST_APO[_settings.auto_power_off])
1180
        rset = RadioSetting("auto_power_off", "Auto Power Off", rs)
1181
        function.append(rset)
1182

    
1183
        rs = RadioSettingValueList(LIST_AOP, LIST_AOP[_settings.auto_power_on])
1184
        rset = RadioSetting("auto_power_on", "Power On Method", rs)
1185
        function.append(rset)
1186

    
1187
        # STE SET
1188
        rs = RadioSettingValueList(LIST_STE_FREQ,
1189
                                   LIST_STE_FREQ[_settings.ste_frequency])
1190
        rset = RadioSetting("ste_frequency", "STE Frequency", rs)
1191
        function.append(rset)
1192

    
1193
        rs = RadioSettingValueList(LIST_STE_TYPE,
1194
                                   LIST_STE_TYPE[_settings.ste_type])
1195
        rset = RadioSetting("ste_type", "STE Type", rs)
1196
        function.append(rset)
1197

    
1198
        # FUNCTION SET
1199
        rs = RadioSettingValueList(LIST_STEP, LIST_STEP[_settings.tuning_step])
1200
        rset = RadioSetting("tuning_step", "Tuning Step", rs)
1201
        function.append(rset)
1202

    
1203
        rs = RadioSettingValueList(LIST_SQUELCH,
1204
                                   LIST_SQUELCH[_settings.squelch])
1205
        rset = RadioSetting("squelch", "Squelch Level", rs)
1206
        function.append(rset)
1207

    
1208
        #if str(_embedded.radio_type) == "AT-779U" or "AT-779V":
1209
        #    rs = RadioSettingValueList(LIST_SCAN,
1210
        #                               LIST_SCAN[_settings.scan])
1211
        #    rset = RadioSetting("scan", "Frequency Scan", rs)
1212
        #    function.append(rset)
1213

    
1214
        rs = RadioSettingValueBoolean(bool(_settings.sql_key_function))
1215
        rset = RadioSetting("sql_key_function", "SQL Key Function", rs)
1216
        function.append(rset)
1217

    
1218
        rs = RadioSettingValueList(LIST_TIMEOUT,
1219
                                   LIST_TIMEOUT[_settings.timeout_timer])
1220
        rset = RadioSetting("timeout_timer", "Timeout Timer", rs)
1221
        function.append(rset)
1222

    
1223
        # uncategorized
1224
        rs = RadioSettingValueBoolean(bool(_settings.save_chan_param))
1225
        rset = RadioSetting("save_chan_param", "Save Channel Parameters", rs)
1226
        function.append(rset)
1227

    
1228
        rs = RadioSettingValueBoolean(bool(_settings.forbid_chan_menu))
1229
        rset = RadioSetting("forbid_chan_menu", "Forbid Channel Menu", rs)
1230
        function.append(rset)
1231

    
1232
        rs = RadioSettingValueBoolean(bool(not _settings.forbid_initialize))
1233
        rset = RadioSetting("forbid_initialize", "Forbid Initialize", rs)
1234
        function.append(rset)
1235

    
1236
        rs = RadioSettingValueBoolean(bool(_settings.forbid_setting))
1237
        rset = RadioSetting("forbid_setting", "Forbid Setting", rs)
1238
        function.append(rset)
1239

    
1240
        # Information Of Scanning Channel
1241
        scanning = RadioSettingGroup("scanning", "Scanning Setup")
1242
        group.append(scanning)
1243

    
1244
        rs = RadioSettingValueBoolean(bool(_settings2.scan_mode))
1245
        rset = RadioSetting("settings2.scan_mode", "Scan Mode", rs)
1246
        scanning.append(rset)
1247

    
1248
        rs = RadioSettingValueList(LIST_PRIORITY_CH,
1249
                                   LIST_PRIORITY_CH[_settings2.priority_ch])
1250
        rset = RadioSetting("settings2.priority_ch", "Priority Channel", rs)
1251
        scanning.append(rset)
1252

    
1253
        rs = RadioSettingValueInteger(1, 199, _settings2.priority_ch1 + 1)
1254
        rset = RadioSetting("settings2.priority_ch1", "Priority Channel 1", rs)
1255
        scanning.append(rset)
1256

    
1257
        rs = RadioSettingValueInteger(1, 199, _settings2.priority_ch2 + 1)
1258
        rset = RadioSetting("settings2.priority_ch2", "Priority Channel 2", rs)
1259
        scanning.append(rset)
1260

    
1261
        rs = RadioSettingValueList(LIST_REVERT_CH,
1262
                                   LIST_REVERT_CH[_settings2.revert_ch])
1263
        rset = RadioSetting("settings2.revert_ch", "Revert Channel", rs)
1264
        scanning.append(rset)
1265

    
1266
        rs = RadioSettingValueList(LIST_TIME46,
1267
                                   LIST_TIME46[_settings2.look_back_time_a])
1268
        rset = RadioSetting("settings2.look_back_time_a",
1269
                            "Look Back Time A", rs)
1270
        scanning.append(rset)
1271

    
1272
        rs = RadioSettingValueList(LIST_TIME46,
1273
                                   LIST_TIME46[_settings2.look_back_time_b])
1274
        rset = RadioSetting("settings2.look_back_time_b",
1275
                            "Look Back Time B", rs)
1276
        scanning.append(rset)
1277

    
1278
        rs = RadioSettingValueList(LIST_TIME50,
1279
                                   LIST_TIME50[_settings2.dropout_delay_time])
1280
        rset = RadioSetting("settings2.dropout_delay_time",
1281
                            "Dropout Delay Time", rs)
1282
        scanning.append(rset)
1283

    
1284
        rs = RadioSettingValueList(LIST_TIME50,
1285
                                   LIST_TIME50[_settings2.dwell_time])
1286
        rset = RadioSetting("settings2.dwell_time", "Dwell Time", rs)
1287
        scanning.append(rset)
1288

    
1289
        # Embedded Message
1290
        embedded = RadioSettingGroup("embedded", "Embedded Message")
1291
        group.append(embedded)
1292

    
1293
        rs = RadioSettingValueString(0, 7, _filter(_embedded.radio_type))
1294
        rs.set_mutable(False)
1295
        rset = RadioSetting("embedded_msg.radio_type", "Radio Type", rs)
1296
        embedded.append(rset)
1297

    
1298
        if str(_embedded.radio_type) == "RT98V" or "AT-779V":
1299
            options = LIST_RT98V_MODES
1300
        else:
1301
            options = LIST_RT98U_MODES
1302
        rs = RadioSettingValueList(options, options[_embedded.mode])
1303
        rs.set_mutable(False)
1304
        rset = RadioSetting("embedded_msg.mode", "Mode", rs)
1305
        embedded.append(rset)
1306

    
1307
        # frequency
1308
        if str(_embedded.radio_type) == "RT98V" or "AT-779V":
1309
            options = LIST_RT98V_FREQS
1310
        else:
1311
            options = LIST_RT98U_FREQS
1312
        rs = RadioSettingValueList(options, options[_settings3.bandlimit])
1313
        rs.set_mutable(False)
1314
        rset = RadioSetting("settings3.bandlimit", "Frequency", rs)
1315
        embedded.append(rset)
1316

    
1317
        rs = RadioSettingValueString(0, 10, _filter(_embedded.date_mfg))
1318
        rs.set_mutable(False)
1319
        rset = RadioSetting("embedded_msg.date_mfg", "Production Date", rs)
1320
        embedded.append(rset)
1321

    
1322
        rs = RadioSettingValueString(0, 4, _filter(_embedded.mcu_version))
1323
        rs.set_mutable(False)
1324
        rset = RadioSetting("embedded_msg.mcu_version", "MCU Version", rs)
1325
        embedded.append(rset)
1326

    
1327
        return group
1328

    
1329
    def get_settings(self):
1330
        try:
1331
            return self._get_settings()
1332
        except:
1333
            import traceback
1334
            LOG.error("failed to parse settings")
1335
            traceback.print_exc()
1336
            return None
1337

    
1338
    def set_settings(self, settings):
1339
        for element in settings:
1340
            if not isinstance(element, RadioSetting):
1341
                self.set_settings(element)
1342
                continue
1343
            else:
1344
                try:
1345
                    if "." in element.get_name():
1346
                        bits = element.get_name().split(".")
1347
                        obj = self._memobj
1348
                        for bit in bits[:-1]:
1349
                            obj = getattr(obj, bit)
1350
                        setting = bits[-1]
1351
                    else:
1352
                        obj = self._memobj.settings
1353
                        setting = element.get_name()
1354

    
1355
                    if element.has_apply_callback():
1356
                        LOG.debug("using apply callback")
1357
                        element.run_apply_callback()
1358
                    elif setting == "ch_number":
1359
                        setattr(obj, setting, int(element.value) - 1)
1360
                    elif setting == "forbid_initialize":
1361
                        setattr(obj, setting, not int(element.value))
1362
                    elif setting == "priority_ch1":
1363
                        setattr(obj, setting, int(element.value) - 1)
1364
                    elif setting == "priority_ch2":
1365
                        setattr(obj, setting, int(element.value) - 1)
1366
                    elif element.value.get_mutable():
1367
                        LOG.debug("Setting %s = %s" % (setting, element.value))
1368
                        setattr(obj, setting, element.value)
1369
                except Exception, e:
1370
                    LOG.debug(element.get_name())
1371
                    raise
1372

    
1373
    @classmethod
1374
    def match_model(cls, filedata, filename):
1375
        # This radio has always been post-metadata, so never do
1376
        # old-school detection
1377
        return False
1378

    
1379

    
1380
@directory.register
1381
class Rt98Radio(Rt98BaseRadio):
1382
    """Retevis RT98"""
1383
    VENDOR = "Retevis"
1384
    MODEL = "RT98"
1385
    # Allowed radio types is a dict keyed by model of a list of version
1386
    # strings
1387
    ALLOWED_RADIO_TYPES = {'RT98V': ['V100'],
1388
                           'RT98U': ['V100'],
1389
                           'AT-779V': ['V100'],
1390
                           'AT-779U': ['V100']}
(14-14/30)