Project

General

Profile

Bug #9353 » retevis_rt98_at-779v.py

supports 7 character model names - Jim Unroe, 09/07/2021 05:30 PM

 
1
# Copyright 2021 Jim Unroe <rock.unroe@gmail.com>
2
#
3
# This program is free software: you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation, either version 2 of the License, or
6
# (at your option) any later version.
7
#
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
12
#
13
# You should have received a copy of the GNU General Public License
14
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
15

    
16
import os
17
import struct
18
import time
19
import logging
20

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

    
32
LOG = logging.getLogger(__name__)
33

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
253
"""
254

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

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

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

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

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

    
475

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

    
485

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

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

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

    
499

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

    
514

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

    
523

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

    
530

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

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

    
547

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

    
553

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

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

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

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

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

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

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

    
584

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

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

    
605
    return model, bandlimit
606

    
607

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

    
648

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

    
657

    
658
def do_download(radio):
659

    
660
    _ident(radio)
661

    
662
    _memobj = None
663
    data = ""
664

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

    
674
    _finish(radio)
675

    
676
    return memmap.MemoryMap(data)
677

    
678

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

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

    
688
        _finish(radio)
689

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

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

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

    
709
        _finish(radio)
710

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

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

    
733

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

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

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

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

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

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

    
815
        rf.valid_tuning_steps = TUNING_STEPS
816
        return rf
817

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

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

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

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

    
841
        return msgs
842

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

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

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

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

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

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

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

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

    
884
        mem = chirp_common.Memory()
885

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1015
        return mem
1016

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1311
        return group
1312

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

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

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

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

    
1363

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