Project

General

Profile

Bug #10833 » retevis_rt98.py

Jim Unroe, 09/09/2023 12:33 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 struct
17
import logging
18

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

    
29
LOG = logging.getLogger(__name__)
30

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

    
43
TXPOWER_LOW = 0x00
44
TXPOWER_MED = 0x01
45
TXPOWER_HIGH = 0x02
46

    
47
DUPLEX_NOSPLIT = 0x00
48
DUPLEX_POSSPLIT = 0x01
49
DUPLEX_NEGSPLIT = 0x02
50

    
51
CHANNEL_WIDTH_12d5kHz = 0x00
52
CHANNEL_WIDTH_20kHz = 0x01
53
CHANNEL_WIDTH_25kHz = 0x02
54

    
55
TUNING_STEPS = [2.5, 5.0, 6.25, 10.0, 12.5, 20.0, 25.0, 30.0, 50.0]
56

    
57
POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=5),
58
                chirp_common.PowerLevel("Mid", watts=10),
59
                chirp_common.PowerLevel("High", watts=15)]
60

    
61
PMR_POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=0.5), ]
62

    
63
FREENET_POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=1), ]
64

    
65
PMR_FREQS = [446006250, 446018750, 446031250, 446043750,
66
             446056250, 446068750, 446081250, 446093750,
67
             446106250, 446118750, 446131250, 446143750,
68
             446156250, 446168750, 446181250, 446193750]
69

    
70
FREENET_FREQS = [149025000, 149037500, 149050000,
71
                 149087500, 149100000, 149112500]
72

    
73
CROSS_MODES = ["Tone->Tone", "DTCS->", "->DTCS", "Tone->DTCS", "DTCS->Tone",
74
               "->Tone", "DTCS->DTCS"]
75

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

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

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

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

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

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

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

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

    
205
#  RT98  memory map
206
#  section: 2 and 3  Channel Set/Skip Flags
207
#
208
#    Channel Set (starts 0x3240) : Channel Set  bit is value 0 if a memory
209
#                                  location in the channel bank is active.
210
#    Channel Skip (starts 0x3260): Channel Skip bit is value 0 if a memory
211
#                                  location in the channel bank is active.
212
#
213
#    Both flag maps are a total 24 bytes in length, aligned on 32 byte records.
214
#    bit = 0 channel not set/skip,  1 is channel set/no skip
215
#
216
#    to index a channel:
217
#        cbyte = channel / 8 ;
218
#        cbit  = channel % 8 ;
219
#        setflag  = csetflag[cbyte].c[cbit] ;
220
#        skipflag = cskipflag[cbyte].c[cbit] ;
221
#
222
#    channel range is 1-199, range is 32 bytes (last 7 unknown)
223
#
224
MEM_FORMAT = MEM_FORMAT + """
225
#seekto 0x3240;
226
struct {
227
   bit c[8];
228
} csetflag[32];
229

    
230
#seekto 0x3260;
231
struct {
232
   bit c[8];
233
} cskipflag[32];
234

    
235
"""
236

    
237
#  RT98  memory map
238
#  section: 4  Startup Label
239
#
240
#  bytes:bit  type                 description
241
#  ---------------------------------------------------------------------------
242
#  6          char start_label[6]  label displayed at startup (usually
243
#                                  your call sign)
244
#
245
MEM_FORMAT = MEM_FORMAT + """
246
#seekto 0x3300;
247
struct {
248
    char startname[6];
249
} slabel;
250
"""
251

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

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

    
415
MEM_FORMAT = MEM_FORMAT + """
416
#seekto 0x3380;
417
struct {
418
  u8 unknown_3380:7,
419
     scan_mode:1;
420
  u8 unknown_3381:6,
421
     priority_ch:2;
422
  u8 priority_ch1;
423
  u8 priority_ch2;
424
  u8 unknown_3384:4,
425
     revert_ch:4;
426
  u8 look_back_time_a;
427
  u8 look_back_time_b;
428
  u8 dropout_delay_time;
429
  u8 dwell_time;
430
} settings2;
431
"""
432

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

    
467

    
468
# Format for the version messages returned by the radio
469
VER_FORMAT = '''
470
u8 hdr;
471
char model[7];
472
u8 bandlimit;
473
char version[6];
474
u8 ack;
475
'''
476

    
477

    
478
# Radio supports upper case and symbols
479
CHARSET_ASCII_PLUS = chirp_common.CHARSET_UPPER_NUMERIC + '- '
480

    
481
# Band limits as defined by the band byte in ver_response, defined in Hz, for
482
# VHF and UHF, used for RX and TX.
483
RT98V_BAND_LIMITS = {0x00: [(149000000, 149200000)],
484
                     0x01: [(136000000, 174000000)],
485
                     0x02: [(147000000, 174000000)]}
486

    
487
RT98U_BAND_LIMITS = {0x00: [(446000000, 446200000)],
488
                     0x01: [(400000000, 470000000)],
489
                     0x02: [(450000000, 470000000)]}
490

    
491

    
492
# Get band limits from a band limit value
493
def get_band_limits_Hz(radio_type, limit_value):
494
    if str(radio_type).rstrip("\00") in ["RT98U", "AT-779U"]:
495
        if limit_value not in RT98U_BAND_LIMITS:
496
            limit_value = 0x01
497
            LOG.warning('Unknown band limit value 0x%02x, default to 0x01')
498
        bandlimitfrequencies = RT98U_BAND_LIMITS[limit_value]
499
    elif str(radio_type).rstrip("\00") in ["RT98V", "AT-779V"]:
500
        if limit_value not in RT98V_BAND_LIMITS:
501
            limit_value = 0x01
502
            LOG.warning('Unknown band limit value 0x%02x, default to 0x01')
503
        bandlimitfrequencies = RT98V_BAND_LIMITS[limit_value]
504
    return bandlimitfrequencies
505

    
506

    
507
def _echo_write(radio, data):
508
    try:
509
        radio.pipe.write(data)
510
        radio.pipe.read(len(data))
511
    except Exception as e:
512
        LOG.error("Error writing to radio: %s" % e)
513
        raise errors.RadioError("Unable to write to radio")
514

    
515

    
516
def _checksum(data):
517
    cs = 0
518
    for byte in data:
519
        cs += byte
520
    return cs % 256
521

    
522

    
523
def _read(radio, length):
524
    try:
525
        data = radio.pipe.read(length)
526
    except Exception as e:
527
        _finish(radio)
528
        LOG.error("Error reading from radio: %s" % e)
529
        raise errors.RadioError("Unable to read from radio")
530

    
531
    if len(data) != length:
532
        _finish(radio)
533
        LOG.error("Short read from radio (%i, expected %i)" %
534
                  (len(data), length))
535
        LOG.debug(util.hexprint(data))
536
        raise errors.RadioError("Short read from radio")
537
    return data
538

    
539

    
540
# strip trailing 0x00 to convert a string returned by bitwise.parse into a
541
# python string
542
def cstring_to_py_string(cstring):
543
    return "".join(c for c in cstring if c != '\x00')
544

    
545

    
546
# Check the radio version reported to see if it's one we support,
547
# returns bool version supported, and the band index
548
def check_ver(ver_response, allowed_types):
549
    ''' Check the returned radio version is one we approve of '''
550

    
551
    LOG.debug('ver_response = ')
552
    LOG.debug(util.hexprint(ver_response))
553

    
554
    resp = bitwise.parse(VER_FORMAT, ver_response)
555
    verok = False
556

    
557
    if resp.hdr == 0x49 and resp.ack == 0x06:
558
        model, version = [cstring_to_py_string(bitwise.get_string(s)).strip()
559
                          for s in (resp.model, resp.version)]
560
        LOG.debug('radio model: \'%s\' version: \'%s\'' %
561
                  (model, version))
562
        LOG.debug('allowed_types = %s' % allowed_types)
563

    
564
        if model in allowed_types:
565
            LOG.debug('model in allowed_types')
566

    
567
            if version in allowed_types[model]:
568
                LOG.debug('version in allowed_types[model]')
569
                verok = True
570
    else:
571
        _finish(radio)
572
        raise errors.RadioError('Failed to parse version response')
573

    
574
    return verok, str(resp.model), int(resp.bandlimit)
575

    
576

    
577
def _ident(radio):
578
    radio.pipe.timeout = 1
579
    _echo_write(radio, b"PROGRAM")
580
    response = radio.pipe.read(3)
581
    if response != b"QX\06":
582
        _finish(radio)
583
        LOG.debug("Response was :\n%s" % util.hexprint(response))
584
        raise errors.RadioError("Radio did not respond. Check connection.")
585
    _echo_write(radio, b"\x02")
586
    ver_response = radio.pipe.read(16)
587
    LOG.debug(util.hexprint(ver_response))
588

    
589
    verok, model, bandlimit = check_ver(ver_response,
590
                                        radio.ALLOWED_RADIO_TYPES)
591
    if not verok:
592
        _finish(radio)
593
        raise errors.RadioError(
594
            'Radio version not in allowed list for %s-%s: %s' %
595
            (radio.VENDOR, radio.MODEL, util.hexprint(ver_response)))
596

    
597
    return model, bandlimit
598

    
599

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

    
640

    
641
def _finish(radio):
642
    endframe = b"\x45\x4E\x44"
643
    _echo_write(radio, endframe)
644
    result = radio.pipe.read(1)
645
    if result != b"\x06":
646
        LOG.error("Got:\n%s" % util.hexprint(result))
647
        raise errors.RadioError("Radio did not finish cleanly")
648

    
649

    
650
def do_download(radio):
651

    
652
    _ident(radio)
653

    
654
    _memobj = None
655
    data = b""
656

    
657
    for addr in range(0, radio._memsize, 0x10):
658
        block = _send(radio, b'R', addr, 0x10)
659
        data += block
660
        status = chirp_common.Status()
661
        status.cur = len(data)
662
        status.max = radio._memsize
663
        status.msg = "Downloading from radio"
664
        radio.status_fn(status)
665

    
666
    _finish(radio)
667

    
668
    return memmap.MemoryMapBytes(data)
669

    
670

    
671
def do_upload(radio):
672
    model, bandlimit = _ident(radio)
673
    _embedded = radio._memobj.embedded_msg
674

    
675
    if model != str(_embedded.radio_type):
676
        LOG.warning('radio and image model types differ')
677
        LOG.warning('model type (radio): %s' % str(model))
678
        LOG.warning('model type (image): %s' % str(_embedded.radio_type))
679

    
680
        _finish(radio)
681

    
682
        msg = ("The upload was stopped because the radio type "
683
               "of the image (%s) does not match that "
684
               "of the radio (%s).")
685
        raise errors.RadioError(msg % (str(_embedded.radio_type), str(model)))
686

    
687
    if bandlimit != int(_embedded.mode):
688
        if str(_embedded.radio_type).rstrip("\00") in ["RT98U", "AT-779U"]:
689
            image_band_limits = LIST_RT98U_FREQS[int(_embedded.mode)]
690
        if str(_embedded.radio_type).rstrip("\00") in ["RT98V", "AT-779V"]:
691
            image_band_limits = LIST_RT98V_FREQS[int(_embedded.mode)]
692
        if str(model).rstrip("\00") in ["RT98U", "AT-779U"]:
693
            radio_band_limits = LIST_RT98U_FREQS[int(bandlimit)]
694
        if str(model).rstrip("\00") in ["RT98V", "AT-779V"]:
695
            radio_band_limits = LIST_RT98V_FREQS[int(bandlimit)]
696

    
697
        LOG.warning('radio and image band limits differ')
698
        LOG.warning('image band limits: %s' % image_band_limits)
699
        LOG.warning('radio band limits: %s' % radio_band_limits)
700

    
701
        _finish(radio)
702

    
703
        msg = ("The upload was stopped because the band limits "
704
               "of the image (%s) does not match that "
705
               "of the radio (%s).")
706
        raise errors.RadioError(msg % (image_band_limits, radio_band_limits))
707

    
708
    try:
709
        for start, end in radio._ranges:
710
            for addr in range(start, end, 0x10):
711
                block = radio._mmap[addr:addr+0x10]
712
                _send(radio, b'W', addr, len(block), block)
713
                status = chirp_common.Status()
714
                status.cur = addr
715
                status.max = end
716
                status.msg = "Uploading to Radio"
717
                radio.status_fn(status)
718
        _finish(radio)
719
    except errors.RadioError:
720
        raise
721
    except Exception as e:
722
        _finish(radio)
723
        raise errors.RadioError('Failed to upload to radio: %s' % e)
724

    
725

    
726
#
727
# The base class, extended for use with other models
728
#
729
class Rt98BaseRadio(chirp_common.CloneModeRadio,
730
                    chirp_common.ExperimentalRadio):
731
    """Retevis RT98 Base"""
732
    VENDOR = "Retevis"
733
    MODEL = "RT98 Base"
734
    BAUD_RATE = 9600
735
    NEEDS_COMPAT_SERIAL = False
736

    
737
    _memsize = 0x3E00
738
    _ranges = [(0x0000, 0x3310),
739
               (0x3320, 0x3390)]
740

    
741
    @classmethod
742
    def get_prompts(cls):
743
        rp = chirp_common.RadioPrompts()
744
        rp.experimental = ("The Retevis RT98 driver is an beta version."
745
                           "Proceed with Caution and backup your data")
746
        return rp
747

    
748
    def get_features(self):
749
        class FakeEmbedded(object):
750
            mode = 0
751
            radio_type = 'RT98U'
752

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

    
795
        try:
796
            rf.valid_bands = get_band_limits_Hz(
797
                str(_embedded.radio_type),
798
                int(_embedded.mode))
799
        except TypeError:
800
            # If we're asked without memory loaded, assume the most permissive
801
            rf.valid_bands = get_band_limits_Hz(str(_embedded.radio_type), 1)
802
        except Exception as e:
803
            LOG.error('Failed to get band limits for RT98: %s' % e)
804
            rf.valid_bands = get_band_limits_Hz(str(_embedded.radio_type), 1)
805

    
806
        rf.valid_tuning_steps = TUNING_STEPS
807
        return rf
808

    
809
    # Do a download of the radio from the serial port
810
    def sync_in(self):
811
        self._mmap = do_download(self)
812
        self.process_mmap()
813

    
814
    # Do an upload of the radio to the serial port
815
    def sync_out(self):
816
        do_upload(self)
817

    
818
    def process_mmap(self):
819
        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
820

    
821
    # Return a raw representation of the memory object, which
822
    # is very helpful for development
823
    def get_raw_memory(self, number):
824
        return repr(self._memobj.memory[number - 1])
825

    
826
    def _get_dcs_index(self, _mem, which):
827
        base = getattr(_mem, '%scode' % which)
828
        extra = getattr(_mem, '%sdcsextra' % which)
829
        return (int(extra) << 8) | int(base)
830

    
831
    def _set_dcs_index(self, _mem, which, index):
832
        base = getattr(_mem, '%scode' % which)
833
        extra = getattr(_mem, '%sdcsextra' % which)
834
        base.set_value(index & 0xFF)
835
        extra.set_value(index >> 8)
836

    
837
    # Extract a high-level memory object from the low-level memory map
838
    # This is called to populate a memory in the UI
839
    def get_memory(self, number):
840
        _embedded = self._memobj.embedded_msg
841
        # Get a low-level memory object mapped to the image
842
        _mem = self._memobj.memory[number - 1]
843

    
844
        # get flag info
845
        cbyte = (number - 1) / 8
846
        cbit = 7 - ((number - 1) % 8)
847
        setflag = self._memobj.csetflag[cbyte].c[cbit]
848
        skipflag = self._memobj.cskipflag[cbyte].c[cbit]
849

    
850
        mem = chirp_common.Memory()
851

    
852
        mem.number = number  # Set the memory number
853

    
854
        # We'll consider any blank (i.e. 0 MHz frequency) to be empty
855
        if _mem.freq == 0:
856
            mem.empty = True
857
            return mem
858

    
859
        if setflag == 0:
860
            mem.empty = True
861
            return mem
862

    
863
        if _mem.get_raw()[0] == "\xFF":
864
            mem.empty = True
865
            return mem
866

    
867
        # set the name
868
        mem.name = str(_mem.name).rstrip()  # Set the alpha tag
869

    
870
        # Convert your low-level frequency and offset to Hertz
871
        mem.freq = int(_mem.freq) * 10
872
        mem.offset = int(_mem.offset) * 10
873

    
874
        # Set the duplex flags
875
        if _mem.tx_off:  # handle tx off
876
            mem.duplex = 'off'
877
        elif _mem.duplex == DUPLEX_POSSPLIT:
878
            mem.duplex = '+'
879
        elif _mem.duplex == DUPLEX_NEGSPLIT:
880
            mem.duplex = '-'
881
        elif _mem.duplex == DUPLEX_NOSPLIT:
882
            mem.duplex = ''
883
        elif _mem.duplex == DUPLEX_ODDSPLIT:
884
            mem.duplex = 'split'
885
        else:
886
            LOG.error('%s: get_mem: unhandled duplex: %02x' %
887
                      (mem.name, _mem.duplex))
888

    
889
        # Set the channel width
890
        if _mem.channel_width == CHANNEL_WIDTH_12d5kHz:
891
            mem.mode = 'NFM'
892
        elif _embedded.mode == 0:  # PMR or FreeNet
893
            LOG.info('PMR and FreeNet channels must be Channel Width 12.5 kHz')
894
            mem.mode = 'NFM'
895
        elif _mem.channel_width == CHANNEL_WIDTH_25kHz:
896
            mem.mode = 'FM'
897
        elif _mem.channel_width == CHANNEL_WIDTH_20kHz:
898
            LOG.info(
899
                '%s: get_mem: promoting 20 kHz channel width to 25 kHz' %
900
                mem.name)
901
            mem.mode = 'FM'
902
        else:
903
            LOG.error('%s: get_mem: unhandled channel width: 0x%02x' %
904
                      (mem.name, _mem.channel_width))
905

    
906
        # set the power level
907
        if _embedded.mode == 0:  # PMR or FreeNet
908
            if str(_embedded.radio_type).rstrip("\00") == "RT98U":
909
                LOG.info('using PMR power levels')
910
                _levels = PMR_POWER_LEVELS
911
            if str(_embedded.radio_type).rstrip("\00") == "RT98V":
912
                LOG.info('using FreeNet power levels')
913
                _levels = FREENET_POWER_LEVELS
914
        else:  # COM or COMII
915
            LOG.info('using general power levels')
916
            _levels = POWER_LEVELS
917

    
918
        if _mem.txpower == TXPOWER_LOW:
919
            mem.power = _levels[0]
920
        elif _embedded.mode == 0:  # PMR or FreeNet
921
            LOG.info('FreeNet or PMR channel is not set to TX Power Low')
922
            LOG.info('Setting channel to TX Power Low')
923
            mem.power = _levels[0]
924
        elif _mem.txpower == TXPOWER_MED:
925
            mem.power = _levels[1]
926
        elif _mem.txpower == TXPOWER_HIGH:
927
            mem.power = _levels[2]
928
        else:
929
            LOG.error('%s: get_mem: unhandled power level: 0x%02x' %
930
                      (mem.name, _mem.txpower))
931

    
932
        # CTCSS Tones and DTCS Codes
933
        rxtone = txtone = None
934

    
935
        rxmode = TMODES[_mem.rxtmode]
936
        txmode = TMODES[_mem.txtmode]
937

    
938
        if rxmode == "Tone":
939
            rxtone = TONES[_mem.rxtone]
940
        elif rxmode == "DTCS":
941
            rxtone = chirp_common.ALL_DTCS_CODES[self._get_dcs_index(
942
                                                 _mem, 'rx')]
943

    
944
        if txmode == "Tone":
945
            txtone = TONES[_mem.txtone]
946
        elif txmode == "DTCS":
947
            txtone = chirp_common.ALL_DTCS_CODES[self._get_dcs_index(
948
                                                 _mem, 'tx')]
949

    
950
        rxpol = _mem.rxinv and "R" or "N"
951
        txpol = _mem.txinv and "R" or "N"
952

    
953
        chirp_common.split_tone_decode(mem,
954
                                       (txmode, txtone, txpol),
955
                                       (rxmode, rxtone, rxpol))
956

    
957
        # Check if this memory is in the scan enabled list
958
        mem.skip = "S" if skipflag == 0 else ""
959

    
960
        # Extra
961
        mem.extra = RadioSettingGroup("extra", "Extra")
962

    
963
        rs = RadioSettingValueBoolean(bool(_mem.busychannellockout))
964
        rset = RadioSetting("busychannellockout", "Busy channel lockout", rs)
965
        mem.extra.append(rset)
966

    
967
        rs = RadioSettingValueBoolean(bool(_mem.reverse))
968
        rset = RadioSetting("reverse", "Reverse", rs)
969
        mem.extra.append(rset)
970

    
971
        rs = RadioSettingValueBoolean(bool(_mem.talkaround))
972
        rset = RadioSetting("talkaround", "Talk around", rs)
973
        mem.extra.append(rset)
974

    
975
        rs = RadioSettingValueBoolean(bool(_mem.squelch_mode))
976
        rset = RadioSetting("squelch_mode", "Squelch mode", rs)
977
        mem.extra.append(rset)
978

    
979
        return mem
980

    
981
    # Store details about a high-level memory to the memory map
982
    # This is called when a user edits a memory in the UI
983
    def set_memory(self, mem):
984
        _embedded = self._memobj.embedded_msg
985
        # Get a low-level memory object mapped to the image
986

    
987
        _mem = self._memobj.memory[mem.number - 1]
988

    
989
        cbyte = (mem.number - 1) / 8
990
        cbit = 7 - ((mem.number - 1) % 8)
991

    
992
        if mem.empty:
993
            self._memobj.csetflag[cbyte].c[cbit] = 0
994
            self._memobj.cskipflag[cbyte].c[cbit] = 0
995
            _mem.set_raw("\xff" * 32)
996
            return
997

    
998
        _mem.set_raw("\x00" * 32)
999

    
1000
        # FreeNet and PMR radio types
1001
        if _embedded.mode == 0:  # PMR or FreeNet
1002

    
1003
            mem.mode = 'NFM'
1004
            mem.offset = 0
1005

    
1006
            # FreeNet
1007
            if str(_embedded.radio_type).rstrip("\00") == "RT98V":
1008
                if mem.number >= 1 and mem.number <= 6:
1009
                    FREENET_FREQ = FREENET_FREQS[mem.number - 1]
1010
                    mem.freq = FREENET_FREQ
1011
                else:
1012
                    _mem.tx_off = 1
1013
                    mem.duplex = 'off'
1014

    
1015
            # PMR
1016
            if str(_embedded.radio_type).rstrip("\00") == "RT98U":
1017
                if mem.number >= 1 and mem.number <= 16:
1018
                    PMR_FREQ = PMR_FREQS[mem.number - 1]
1019
                    mem.freq = PMR_FREQ
1020
                else:
1021
                    _mem.tx_off = 1
1022
                    mem.duplex = 'off'
1023

    
1024
        # set the occupied bitfield
1025
        self._memobj.csetflag[cbyte].c[cbit] = 1
1026
        # set the scan add bitfield
1027
        self._memobj.cskipflag[cbyte].c[cbit] = 0 if (mem.skip == "S") else 1
1028

    
1029
        _mem.freq = mem.freq / 10             # Convert to low-level frequency
1030
        _mem.offset = mem.offset / 10         # Convert to low-level frequency
1031

    
1032
        # Store the alpha tag
1033
        _mem.name = mem.name.ljust(6)[:6]  # Store the alpha tag
1034

    
1035
        # Set duplex bitfields
1036
        _mem.tx_off = 0
1037
        if mem.duplex == 'off':  # handle tx off
1038
            _mem.tx_off = 1
1039
        elif mem.duplex == '+':
1040
            _mem.duplex = DUPLEX_POSSPLIT
1041
        elif mem.duplex == '-':
1042
            _mem.duplex = DUPLEX_NEGSPLIT
1043
        elif mem.duplex == '':
1044
            _mem.duplex = DUPLEX_NOSPLIT
1045
        elif mem.duplex == 'split':
1046
            diff = mem.offset - mem.freq
1047
            _mem.duplex = DUPLEXES.index("-") \
1048
                if diff < 0 else DUPLEXES.index("+")
1049
            _mem.offset = abs(diff) / 10
1050
        else:
1051
            LOG.error('%s: set_mem: unhandled duplex: %s' %
1052
                      (mem.name, mem.duplex))
1053

    
1054
        # Set the channel width - remember we promote 20 kHz channels to FM
1055
        # on import, so don't handle them here
1056
        if mem.mode == 'FM':
1057
            _mem.channel_width = CHANNEL_WIDTH_25kHz
1058
        elif mem.mode == 'NFM':
1059
            _mem.channel_width = CHANNEL_WIDTH_12d5kHz
1060
        else:
1061
            LOG.error('%s: set_mem: unhandled mode: %s' % (
1062
                mem.name, mem.mode))
1063

    
1064
        # CTCSS Tones and DTCS Codes
1065
        ((txmode, txtone, txpol),
1066
         (rxmode, rxtone, rxpol)) = chirp_common.split_tone_encode(mem)
1067

    
1068
        _mem.txtmode = TMODES.index(txmode)
1069

    
1070
        _mem.rxtmode = TMODES.index(rxmode)
1071

    
1072
        if txmode == "Tone":
1073
            _mem.txtone = TONES.index(txtone)
1074
        elif txmode == "DTCS":
1075
            self._set_dcs_index(_mem, 'tx',
1076
                                chirp_common.ALL_DTCS_CODES.index(txtone))
1077

    
1078
        _mem.squelch_mode = False
1079
        if rxmode == "Tone":
1080
            _mem.rxtone = TONES.index(rxtone)
1081
            _mem.squelch_mode = True
1082
        elif rxmode == "DTCS":
1083
            self._set_dcs_index(_mem, 'rx',
1084
                                chirp_common.ALL_DTCS_CODES.index(rxtone))
1085
            _mem.squelch_mode = True
1086

    
1087
        _mem.txinv = txpol == "R"
1088
        _mem.rxinv = rxpol == "R"
1089

    
1090
        # set the power level
1091
        if _embedded.mode == 0:  # PMR or FreeNet
1092
            if str(_embedded.radio_type).rstrip("\00") == "RT98U":
1093
                LOG.info('using PMR power levels')
1094
                _levels = PMR_POWER_LEVELS
1095
            if str(_embedded.radio_type).rstrip("\00") == "RT98V":
1096
                LOG.info('using FreeNet power levels')
1097
                _levels = FREENET_POWER_LEVELS
1098
        else:  # COM or COMII
1099
            LOG.info('using general power levels')
1100
            _levels = POWER_LEVELS
1101

    
1102
        if mem.power is None:
1103
            _mem.txpower = TXPOWER_HIGH
1104
        elif mem.power == _levels[0]:
1105
            _mem.txpower = TXPOWER_LOW
1106
        elif _embedded.mode == 0:  # PMR or FreeNet
1107
            LOG.info('FreeNet or PMR channel is not set to TX Power Low')
1108
            LOG.info('Setting channel to TX Power Low')
1109
            _mem.txpower = TXPOWER_LOW
1110
        elif mem.power == _levels[1]:
1111
            _mem.txpower = TXPOWER_MED
1112
        elif mem.power == _levels[2]:
1113
            _mem.txpower = TXPOWER_HIGH
1114
        else:
1115
            LOG.error('%s: set_mem: unhandled power level: %s' %
1116
                      (mem.name, mem.power))
1117

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

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

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

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

    
1139
        if "AT-779" in str(_embedded.radio_type):
1140
            rs = RadioSettingValueList(LIST_VFOMR,
1141
                                       LIST_VFOMR[_settings3.vfomr])
1142
            rset = RadioSetting("settings3.vfomr", "VFO/MR", rs)
1143
            function.append(rset)
1144

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

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

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

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

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

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

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

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

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

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

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

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

    
1207
        if "AT-779" in str(_embedded.radio_type):
1208
            rs = RadioSettingValueList(LIST_SCAN,
1209
                                       LIST_SCAN[_settings.scan_resume])
1210
            rset = RadioSetting("scan_resume", "Frequency Scan", rs)
1211
            function.append(rset)
1212

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1326
        return group
1327

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

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

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

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

    
1378

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