Project

General

Profile

Bug #9353 » retevis_rt98_at-779v.py

Jim Unroe, 09/10/2021 05:06 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
LIST_VFOMR = ["MR", "VFO"]
88
LIST_SCAN = ["TO", "CO", "SE"]
89

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

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

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

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

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

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

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

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

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

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

    
257
"""
258

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

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

    
397
MEM_FORMAT = MEM_FORMAT + """
398
#seekto 0x3340;
399
struct {
400
  u8 unknown_3340:4,
401
     tuning_step:4;
402
  u8 unknown_3341:7,
403
     beep:1;
404
  u8 unknown_3342:3,
405
     timeout_timer:5;
406
  u8 unknown_3343:6,
407
     auto_power_off:2;
408
  u8 unknown_3344:4,
409
     squelch:4;
410
  u8 unknown_3345:3,
411
     volume:5;
412
  u8 unknown_3346:6,
413
     scan_resume:2;
414
  u8 unknown_3347;
415
  u8 unknown_3348:6,
416
     display_mode:2;
417
  u8 unknown_3349:7,
418
     auto_power_on:1;
419
  u8 unknown_334A:3,
420
     mic_gain:5;
421
  u8 unknown_334B;
422
  u8 unknown_334C:5,
423
     ste_type:3;
424
  u8 unknown_334D:6,
425
     ste_frequency:2;
426
  u8 unknown_334E:1,
427
     forbid_setting:1,
428
     unknown1:1,
429
     forbid_initialize:1,
430
     save_chan_param:1,
431
     forbid_chan_menu:1,
432
     sql_key_function:1,
433
     unknown2:1;
434
} settings;
435
"""
436

    
437
MEM_FORMAT = MEM_FORMAT + """
438
#seekto 0x3380;
439
struct {
440
  u8 unknown_3380:7,
441
     scan_mode:1;
442
  u8 unknown_3381:6,
443
     priority_ch:2;
444
  u8 priority_ch1;
445
  u8 priority_ch2;
446
  u8 unknown_3384:4,
447
     revert_ch:4;
448
  u8 look_back_time_a;
449
  u8 look_back_time_b;
450
  u8 dropout_delay_time;
451
  u8 dwell_time;
452
} settings2;
453
"""
454

    
455
#  RT98  memory map
456
#  section: 8  Embedded Messages
457
#
458
#  bytes:bit  type                 description
459
#  ---------------------------------------------------------------------------
460
#  6          char radio_type[5]   radio type, vhf=rt98v, uhf=rt98u
461
#  2          u8 unknown1[2]
462
#  4          char mcu_version[4]  mcu version, [x.xx]
463
#  2          u8 unknown2[2]
464
#  1          u8 mode              rt98u mode: 0=pmr, 1=com, 2=comii
465
#                                  rt98v mode: 0=freenet, 1=com, 2=comii
466
#  1          u8 unknown3
467
#  10         u8 unused1[10]
468
#  4          u8 unknown4[4]
469
#  3          u8 unused2[3]
470
#  16         u8 unknown5[16]
471
#  10         char date_mfg[16]    date manufactured, [yyyy-mm-dd]
472
#
473
MEM_FORMAT = MEM_FORMAT + """
474
#seekto 0x3D00;
475
struct {
476
char radio_type[7];
477
char mcu_version[4];
478
u8 unknown2[2];
479
u8 mode;
480
u8 unknown3;
481
u8 unused1[10];
482
u8 unknown4[4];
483
u8 unused2[3];
484
u8 unknown5[16];
485
char date_mfg[10];
486
} embedded_msg;
487
"""
488

    
489

    
490
# Format for the version messages returned by the radio
491
VER_FORMAT = '''
492
u8 hdr;
493
char model[7];
494
u8 bandlimit;
495
char version[6];
496
u8 ack;
497
'''
498

    
499

    
500
# Radio supports upper case and symbols
501
CHARSET_ASCII_PLUS = chirp_common.CHARSET_UPPER_NUMERIC + '- '
502

    
503
# Band limits as defined by the band byte in ver_response, defined in Hz, for
504
# VHF and UHF, used for RX and TX.
505
RT98V_BAND_LIMITS = {0x00: [(149000000, 149200000)],
506
                     0x01: [(136000000, 174000000)],
507
                     0x02: [(147000000, 174000000)]}
508

    
509
RT98U_BAND_LIMITS = {0x00: [(446000000, 446200000)],
510
                     0x01: [(400000000, 470000000)],
511
                     0x02: [(450000000, 470000000)]}
512

    
513

    
514
# Get band limits from a band limit value
515
def get_band_limits_Hz(radio_type, limit_value):
516
    if str(radio_type) == "RT98U" or "AT-779U":
517
        if limit_value not in RT98U_BAND_LIMITS:
518
            limit_value = 0x01
519
            LOG.warning('Unknown band limit value 0x%02x, default to 0x01')
520
        bandlimitfrequencies = RT98U_BAND_LIMITS[limit_value]
521
    elif str(radio_type) == "RT98V" or "AT-779V":
522
        if limit_value not in RT98V_BAND_LIMITS:
523
            limit_value = 0x01
524
            LOG.warning('Unknown band limit value 0x%02x, default to 0x01')
525
        bandlimitfrequencies = RT98V_BAND_LIMITS[limit_value]
526
    else:
527
        LOG.error('Unknown band limit value 0x%02x')
528
        raise errors.RadioError("Unknown band limit value")
529
    return bandlimitfrequencies
530

    
531

    
532
def _echo_write(radio, data):
533
    try:
534
        radio.pipe.write(data)
535
        radio.pipe.read(len(data))
536
    except Exception, e:
537
        LOG.error("Error writing to radio: %s" % e)
538
        raise errors.RadioError("Unable to write to radio")
539

    
540

    
541
def _checksum(data):
542
    cs = 0
543
    for byte in data:
544
        cs += ord(byte)
545
    return cs % 256
546

    
547

    
548
def _read(radio, length):
549
    try:
550
        data = radio.pipe.read(length)
551
    except Exception, e:
552
        _finish(radio)
553
        LOG.error("Error reading from radio: %s" % e)
554
        raise errors.RadioError("Unable to read from radio")
555

    
556
    if len(data) != length:
557
        _finish(radio)
558
        LOG.error("Short read from radio (%i, expected %i)" %
559
                  (len(data), length))
560
        LOG.debug(util.hexprint(data))
561
        raise errors.RadioError("Short read from radio")
562
    return data
563

    
564

    
565
# strip trailing 0x00 to convert a string returned by bitwise.parse into a
566
# python string
567
def cstring_to_py_string(cstring):
568
    return "".join(c for c in cstring if c != '\x00')
569

    
570

    
571
# Check the radio version reported to see if it's one we support,
572
# returns bool version supported, and the band index
573
def check_ver(ver_response, allowed_types):
574
    ''' Check the returned radio version is one we approve of '''
575

    
576
    LOG.debug('ver_response = ')
577
    LOG.debug(util.hexprint(ver_response))
578

    
579
    resp = bitwise.parse(VER_FORMAT, ver_response)
580
    verok = False
581

    
582
    if resp.hdr == 0x49 and resp.ack == 0x06:
583
        model, version = [cstring_to_py_string(bitwise.get_string(s)).strip()
584
                          for s in (resp.model, resp.version)]
585
        LOG.debug('radio model: \'%s\' version: \'%s\'' %
586
                  (model, version))
587
        LOG.debug('allowed_types = %s' % allowed_types)
588

    
589
        if model in allowed_types:
590
            LOG.debug('model in allowed_types')
591

    
592
            if version in allowed_types[model]:
593
                LOG.debug('version in allowed_types[model]')
594
                verok = True
595
    else:
596
        _finish(radio)
597
        raise errors.RadioError('Failed to parse version response')
598

    
599
    return verok, str(resp.model), int(resp.bandlimit)
600

    
601

    
602
def _ident(radio):
603
    radio.pipe.timeout = 1
604
    _echo_write(radio, "PROGRAM")
605
    response = radio.pipe.read(3)
606
    if response != "QX\06":
607
        _finish(radio)
608
        LOG.debug("Response was :\n%s" % util.hexprint(response))
609
        raise errors.RadioError("Radio did not respond. Check connection.")
610
    _echo_write(radio, "\x02")
611
    ver_response = radio.pipe.read(16)
612
    LOG.debug(util.hexprint(ver_response))
613

    
614
    verok, model, bandlimit = check_ver(ver_response,
615
                                        radio.ALLOWED_RADIO_TYPES)
616
    if not verok:
617
        _finish(radio)
618
        raise errors.RadioError(
619
            'Radio version not in allowed list for %s-%s: %s' %
620
            (radio.VENDOR, radio.MODEL, util.hexprint(ver_response)))
621

    
622
    return model, bandlimit
623

    
624

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

    
665

    
666
def _finish(radio):
667
    endframe = "\x45\x4E\x44"
668
    _echo_write(radio, endframe)
669
    result = radio.pipe.read(1)
670
    if result != "\x06":
671
        LOG.error("Got:\n%s" % util.hexprint(result))
672
        raise errors.RadioError("Radio did not finish cleanly")
673

    
674

    
675
def do_download(radio):
676

    
677
    _ident(radio)
678

    
679
    _memobj = None
680
    data = ""
681

    
682
    for addr in range(0, radio._memsize, 0x10):
683
        block = _send(radio, 'R', addr, 0x10)
684
        data += block
685
        status = chirp_common.Status()
686
        status.cur = len(data)
687
        status.max = radio._memsize
688
        status.msg = "Downloading from radio"
689
        radio.status_fn(status)
690

    
691
    _finish(radio)
692

    
693
    return memmap.MemoryMap(data)
694

    
695

    
696
def do_upload(radio):
697
    model, bandlimit = _ident(radio)
698
    _embedded = radio._memobj.embedded_msg
699

    
700
    if model != str(_embedded.radio_type):
701
        LOG.warning('radio and image model types differ')
702
        LOG.warning('model type (radio): %s' % str(model))
703
        LOG.warning('model type (image): %s' % str(_embedded.radio_type))
704

    
705
        _finish(radio)
706

    
707
        msg = ("The upload was stopped because the radio type "
708
               "of the image (%s) does not match that "
709
               "of the radio (%s).")
710
        raise errors.RadioError(msg % (str(_embedded.radio_type), str(model)))
711

    
712
    if bandlimit != int(_embedded.mode):
713
        if str(_embedded.radio_type) == "RT98U" or "AT-779U":
714
            image_band_limits = LIST_RT98U_FREQS[int(_embedded.mode)]
715
        if str(_embedded.radio_type) == "RT98V" or "AT-779V":
716
            image_band_limits = LIST_RT98V_FREQS[int(_embedded.mode)]
717
        if model == "RT98U" or "AT-779U":
718
            radio_band_limits = LIST_RT98U_FREQS[int(bandlimit)]
719
        if model == "RT98V" or "AT-779V":
720
            radio_band_limits = LIST_RT98V_FREQS[int(bandlimit)]
721

    
722
        LOG.warning('radio and image band limits differ')
723
        LOG.warning('image band limits: %s' % image_band_limits)
724
        LOG.warning('radio band limits: %s' % radio_band_limits)
725

    
726
        _finish(radio)
727

    
728
        msg = ("The upload was stopped because the band limits "
729
               "of the image (%s) does not match that "
730
               "of the radio (%s).")
731
        raise errors.RadioError(msg % (image_band_limits, radio_band_limits))
732

    
733
    try:
734
        for start, end in radio._ranges:
735
            for addr in range(start, end, 0x10):
736
                block = radio._mmap[addr:addr+0x10]
737
                _send(radio, 'W', addr, len(block), block)
738
                status = chirp_common.Status()
739
                status.cur = addr
740
                status.max = end
741
                status.msg = "Uploading to Radio"
742
                radio.status_fn(status)
743
        _finish(radio)
744
    except errors.RadioError:
745
        raise
746
    except Exception as e:
747
        _finish(radio)
748
        raise errors.RadioError('Failed to upload to radio: %s' % e)
749

    
750

    
751
#
752
# The base class, extended for use with other models
753
#
754
class Rt98BaseRadio(chirp_common.CloneModeRadio,
755
                    chirp_common.ExperimentalRadio):
756
    """Retevis RT98 Base"""
757
    VENDOR = "Retevis"
758
    MODEL = "RT98 Base"
759
    BAUD_RATE = 9600
760

    
761
    _memsize = 0x3E00
762
    _ranges = [(0x0000, 0x3310),
763
               (0x3320, 0x3390)]
764

    
765
    @classmethod
766
    def get_prompts(cls):
767
        rp = chirp_common.RadioPrompts()
768
        rp.experimental = ("The Retevis RT98 driver is an beta version."
769
                           "Proceed with Caution and backup your data")
770
        return rp
771

    
772
    def get_features(self):
773
        class FakeEmbedded(object):
774
            mode = 0
775
            radio_type = 'RT98U'
776

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

    
821
        try:
822
            rf.valid_bands = get_band_limits_Hz(
823
                str(_embedded.radio_type),
824
                int(_embedded.mode))
825
        except TypeError as e:
826
            # If we're asked without memory loaded, assume the most permissive
827
            rf.valid_bands = get_band_limits_Hz(str(_embedded.radio_type), 1)
828
        except Exception as e:
829
            LOG.error('Failed to get band limits for RT98: %s' % e)
830
            rf.valid_bands = get_band_limits_Hz(str(_embedded.radio_type), 1)
831

    
832
        rf.valid_tuning_steps = TUNING_STEPS
833
        return rf
834

    
835
    def validate_memory(self, mem):
836
        _embedded = self._memobj.embedded_msg
837
        msgs = ""
838
        msgs = chirp_common.CloneModeRadio.validate_memory(self, mem)
839

    
840
        # FreeNet and PMR radio types
841
        if _embedded.mode == 0:  # PMR or FreeNet
842
            freq = float(mem.freq) / 1000000
843

    
844
            # FreeNet
845
            if str(_embedded.radio_type) == "RT98V":
846
                if freq not in FREENET_FREQS:
847
                    _msg_freq = 'Memory location not a valid FreeNet frequency'
848
                    # warn user invalid frequency
849
                    msgs.append(chirp_common.ValidationError(_msg_freq))
850

    
851
            # PMR
852
            if str(_embedded.radio_type) == "RT98U":
853
                if freq not in PMR_FREQS:
854
                    _msg_freq = 'Memory location not a valid PMR frequency'
855
                    # warn user invalid frequency
856
                    msgs.append(chirp_common.ValidationError(_msg_freq))
857

    
858
        return msgs
859

    
860
    # Do a download of the radio from the serial port
861
    def sync_in(self):
862
        self._mmap = do_download(self)
863
        self.process_mmap()
864

    
865
    # Do an upload of the radio to the serial port
866
    def sync_out(self):
867
        do_upload(self)
868

    
869
    def process_mmap(self):
870
        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
871

    
872
    # Return a raw representation of the memory object, which
873
    # is very helpful for development
874
    def get_raw_memory(self, number):
875
        return repr(self._memobj.memory[number - 1])
876

    
877
    def _get_dcs_index(self, _mem, which):
878
        base = getattr(_mem, '%scode' % which)
879
        extra = getattr(_mem, '%sdcsextra' % which)
880
        return (int(extra) << 8) | int(base)
881

    
882
    def _set_dcs_index(self, _mem, which, index):
883
        base = getattr(_mem, '%scode' % which)
884
        extra = getattr(_mem, '%sdcsextra' % which)
885
        base.set_value(index & 0xFF)
886
        extra.set_value(index >> 8)
887

    
888
    # Extract a high-level memory object from the low-level memory map
889
    # This is called to populate a memory in the UI
890
    def get_memory(self, number):
891
        _embedded = self._memobj.embedded_msg
892
        # Get a low-level memory object mapped to the image
893
        _mem = self._memobj.memory[number - 1]
894

    
895
        # get flag info
896
        cbyte = (number - 1) / 8
897
        cbit = 7 - ((number - 1) % 8)
898
        setflag = self._memobj.csetflag[cbyte].c[cbit]
899
        skipflag = self._memobj.cskipflag[cbyte].c[cbit]
900

    
901
        mem = chirp_common.Memory()
902

    
903
        mem.number = number  # Set the memory number
904

    
905
        # We'll consider any blank (i.e. 0MHz frequency) to be empty
906
        if _mem.freq == 0:
907
            mem.empty = True
908
            return mem
909

    
910
        if setflag == 0:
911
            mem.empty = True
912
            return mem
913

    
914
        if _mem.get_raw()[0] == "\xFF":
915
            mem.empty = True
916
            return mem
917

    
918
        # set the name
919
        mem.name = str(_mem.name).rstrip()  # Set the alpha tag
920

    
921
        # Convert your low-level frequency and offset to Hertz
922
        mem.freq = int(_mem.freq) * 10
923
        mem.offset = int(_mem.offset) * 10
924

    
925
        # Set the duplex flags
926
        if _mem.duplex == DUPLEX_POSSPLIT:
927
            mem.duplex = '+'
928
        elif _mem.duplex == DUPLEX_NEGSPLIT:
929
            mem.duplex = '-'
930
        elif _mem.duplex == DUPLEX_NOSPLIT:
931
            mem.duplex = ''
932
        elif _mem.duplex == DUPLEX_ODDSPLIT:
933
            mem.duplex = 'split'
934
        else:
935
            LOG.error('%s: get_mem: unhandled duplex: %02x' %
936
                      (mem.name, _mem.duplex))
937

    
938
        # handle tx off
939
        if _mem.tx_off:
940
            mem.duplex = 'off'
941

    
942
        # Set the channel width
943
        if _mem.channel_width == CHANNEL_WIDTH_12d5kHz:
944
            mem.mode = 'NFM'
945
        elif _embedded.mode == 0:  # PMR or FreeNet
946
            LOG.info('PMR and FreeNet channels must be Channel Width 12.5kHz')
947
            mem.mode = 'NFM'
948
        elif _mem.channel_width == CHANNEL_WIDTH_25kHz:
949
            mem.mode = 'FM'
950
        elif _mem.channel_width == CHANNEL_WIDTH_20kHz:
951
            LOG.info(
952
                '%s: get_mem: promoting 20kHz channel width to 25kHz' %
953
                mem.name)
954
            mem.mode = 'FM'
955
        else:
956
            LOG.error('%s: get_mem: unhandled channel width: 0x%02x' %
957
                      (mem.name, _mem.channel_width))
958

    
959
        # set the power level
960
        if _embedded.mode == 0:  # PMR or FreeNet
961
            if str(_embedded.radio_type) == "RT98U":
962
                LOG.info('using PMR power levels')
963
                _levels = PMR_POWER_LEVELS
964
            if str(_embedded.radio_type) == "RT98V":
965
                LOG.info('using FreeNet power levels')
966
                _levels = FREENET_POWER_LEVELS
967
        else:  # COM or COMII
968
            LOG.info('using general power levels')
969
            _levels = POWER_LEVELS
970

    
971
        if _mem.txpower == TXPOWER_LOW:
972
            mem.power = _levels[0]
973
        elif _embedded.mode == 0:  # PMR or FreeNet
974
            LOG.info('FreeNet or PMR channel is not set to TX Power Low')
975
            LOG.info('Setting channel to TX Power Low')
976
            mem.power = _levels[0]
977
        elif _mem.txpower == TXPOWER_MED:
978
            mem.power = _levels[1]
979
        elif _mem.txpower == TXPOWER_HIGH:
980
            mem.power = _levels[2]
981
        else:
982
            LOG.error('%s: get_mem: unhandled power level: 0x%02x' %
983
                      (mem.name, _mem.txpower))
984

    
985
        # CTCSS Tones and DTCS Codes
986
        rxtone = txtone = None
987

    
988
        rxmode = TMODES[_mem.rxtmode]
989
        txmode = TMODES[_mem.txtmode]
990

    
991
        if rxmode == "Tone":
992
            rxtone = TONES[_mem.rxtone]
993
        elif rxmode == "DTCS":
994
            rxtone = chirp_common.ALL_DTCS_CODES[self._get_dcs_index(
995
                                                 _mem, 'rx')]
996

    
997
        if txmode == "Tone":
998
            txtone = TONES[_mem.txtone]
999
        elif txmode == "DTCS":
1000
            txtone = chirp_common.ALL_DTCS_CODES[self._get_dcs_index(
1001
                                                 _mem, 'tx')]
1002

    
1003
        rxpol = _mem.rxinv and "R" or "N"
1004
        txpol = _mem.txinv and "R" or "N"
1005

    
1006
        chirp_common.split_tone_decode(mem,
1007
                                       (txmode, txtone, txpol),
1008
                                       (rxmode, rxtone, rxpol))
1009

    
1010
        # Check if this memory is in the scan enabled list
1011
        mem.skip = "S" if skipflag == 0 else ""
1012

    
1013
        # Extra
1014
        mem.extra = RadioSettingGroup("extra", "Extra")
1015

    
1016
        rs = RadioSettingValueBoolean(bool(_mem.busychannellockout))
1017
        rset = RadioSetting("busychannellockout", "Busy channel lockout", rs)
1018
        mem.extra.append(rset)
1019

    
1020
        rs = RadioSettingValueBoolean(bool(_mem.reverse))
1021
        rset = RadioSetting("reverse", "Reverse", rs)
1022
        mem.extra.append(rset)
1023

    
1024
        rs = RadioSettingValueBoolean(bool(_mem.talkaround))
1025
        rset = RadioSetting("talkaround", "Talk around", rs)
1026
        mem.extra.append(rset)
1027

    
1028
        rs = RadioSettingValueBoolean(bool(_mem.squelch_mode))
1029
        rset = RadioSetting("squelch_mode", "Squelch mode", rs)
1030
        mem.extra.append(rset)
1031

    
1032
        return mem
1033

    
1034
    # Store details about a high-level memory to the memory map
1035
    # This is called when a user edits a memory in the UI
1036
    def set_memory(self, mem):
1037
        _embedded = self._memobj.embedded_msg
1038
        # Get a low-level memory object mapped to the image
1039

    
1040
        _mem = self._memobj.memory[mem.number - 1]
1041

    
1042
        cbyte = (mem.number - 1) / 8
1043
        cbit = 7 - ((mem.number - 1) % 8)
1044

    
1045
        if mem.empty:
1046
            self._memobj.csetflag[cbyte].c[cbit] = 0
1047
            self._memobj.cskipflag[cbyte].c[cbit] = 0
1048
            _mem.set_raw('\xff' * (_mem.size() / 8))
1049
            return
1050

    
1051
        _mem.set_raw('\x00' * (_mem.size() / 8))
1052

    
1053
        # set the occupied bitfield
1054
        self._memobj.csetflag[cbyte].c[cbit] = 1
1055
        # set the scan add bitfield
1056
        self._memobj.cskipflag[cbyte].c[cbit] = 0 if (mem.skip == "S") else 1
1057

    
1058
        _mem.freq = mem.freq / 10             # Convert to low-level frequency
1059
        _mem.offset = mem.offset / 10         # Convert to low-level frequency
1060

    
1061
        # Store the alpha tag
1062
        _mem.name = mem.name.ljust(6)[:6]  # Store the alpha tag
1063

    
1064
        # Set duplex bitfields
1065
        if mem.duplex == '+':
1066
            _mem.duplex = DUPLEX_POSSPLIT
1067
        elif mem.duplex == '-':
1068
            _mem.duplex = DUPLEX_NEGSPLIT
1069
        elif mem.duplex == '':
1070
            _mem.duplex = DUPLEX_NOSPLIT
1071
        elif mem.duplex == 'split':
1072
            diff = mem.offset - mem.freq
1073
            _mem.duplex = DUPLEXES.index("-") \
1074
                if diff < 0 else DUPLEXES.index("+")
1075
            _mem.offset = abs(diff) / 10
1076
        else:
1077
            LOG.error('%s: set_mem: unhandled duplex: %s' %
1078
                      (mem.name, mem.duplex))
1079

    
1080
        # handle tx off
1081
        _mem.tx_off = 0
1082
        if mem.duplex == 'off':
1083
            _mem.tx_off = 1
1084

    
1085
        # Set the channel width - remember we promote 20kHz channels to FM
1086
        # on import, so don't handle them here
1087
        if mem.mode == 'FM':
1088
            _mem.channel_width = CHANNEL_WIDTH_25kHz
1089
        elif mem.mode == 'NFM':
1090
            _mem.channel_width = CHANNEL_WIDTH_12d5kHz
1091
        else:
1092
            LOG.error('%s: set_mem: unhandled mode: %s' % (
1093
                mem.name, mem.mode))
1094

    
1095
        # CTCSS Tones and DTCS Codes
1096
        ((txmode, txtone, txpol),
1097
         (rxmode, rxtone, rxpol)) = chirp_common.split_tone_encode(mem)
1098

    
1099
        _mem.txtmode = TMODES.index(txmode)
1100

    
1101
        _mem.rxtmode = TMODES.index(rxmode)
1102

    
1103
        if txmode == "Tone":
1104
            _mem.txtone = TONES.index(txtone)
1105
        elif txmode == "DTCS":
1106
            self._set_dcs_index(_mem, 'tx',
1107
                                chirp_common.ALL_DTCS_CODES.index(txtone))
1108

    
1109
        _mem.squelch_mode = False
1110
        if rxmode == "Tone":
1111
            _mem.rxtone = TONES.index(rxtone)
1112
            _mem.squelch_mode = True
1113
        elif rxmode == "DTCS":
1114
            self._set_dcs_index(_mem, 'rx',
1115
                                chirp_common.ALL_DTCS_CODES.index(rxtone))
1116
            _mem.squelch_mode = True
1117

    
1118
        _mem.txinv = txpol == "R"
1119
        _mem.rxinv = rxpol == "R"
1120

    
1121
        # set the power level
1122
        if mem.power == POWER_LEVELS[0]:
1123
            _mem.txpower = TXPOWER_LOW
1124
        elif mem.power == POWER_LEVELS[1]:
1125
            _mem.txpower = TXPOWER_MED
1126
        elif mem.power == POWER_LEVELS[2]:
1127
            _mem.txpower = TXPOWER_HIGH
1128
        else:
1129
            LOG.error('%s: set_mem: unhandled power level: %s' %
1130
                      (mem.name, mem.power))
1131

    
1132
        # extra settings
1133
        for setting in mem.extra:
1134
            setattr(_mem, setting.get_name(), setting.value)
1135

    
1136
    def _get_settings(self):
1137
        _embedded = self._memobj.embedded_msg
1138
        _settings = self._memobj.settings
1139
        _settings2 = self._memobj.settings2
1140
        _settings3 = self._memobj.settings3
1141
        _slabel = self._memobj.slabel
1142

    
1143
        function = RadioSettingGroup("function", "Function Setup")
1144
        group = RadioSettings(function)
1145

    
1146
        # Function Setup
1147
        # MODE SET
1148
        rs = RadioSettingValueList(LIST_DISPLAY_MODE,
1149
                                   LIST_DISPLAY_MODE[_settings.display_mode])
1150
        rset = RadioSetting("display_mode", "Display Mode", rs)
1151
        function.append(rset)
1152

    
1153
        if "AT-779" in str(_embedded.radio_type):
1154
            rs = RadioSettingValueList(LIST_VFOMR,
1155
                                       LIST_VFOMR[_settings3.vfomr])
1156
            rset = RadioSetting("settings3.vfomr", "VFO/MR", rs)
1157
            function.append(rset)
1158

    
1159
        rs = RadioSettingValueInteger(1, 199, _settings3.ch_number + 1)
1160
        rset = RadioSetting("settings3.ch_number", "Channel Number", rs)
1161
        function.append(rset)
1162

    
1163
        # DISPLAY SET
1164
        def _filter(name):
1165
            filtered = ""
1166
            for char in str(name):
1167
                if char in chirp_common.CHARSET_ASCII:
1168
                    filtered += char
1169
                else:
1170
                    filtered += " "
1171
            return filtered
1172

    
1173
        val = RadioSettingValueString(0, 6, _filter(_slabel.startname))
1174
        rs = RadioSetting("slabel.startname", "Startup Label", val)
1175
        function.append(rs)
1176

    
1177
        # VOL SET
1178
        rs = RadioSettingValueBoolean(bool(_settings.beep))
1179
        rset = RadioSetting("beep", "Beep Prompt", rs)
1180
        function.append(rset)
1181

    
1182
        rs = RadioSettingValueInteger(1, 30, _settings.volume)
1183
        rset = RadioSetting("volume", "Volume Level", rs)
1184
        function.append(rset)
1185

    
1186
        rs = RadioSettingValueInteger(1, 16, _settings.mic_gain)
1187
        rset = RadioSetting("mic_gain", "Mic Gain", rs)
1188
        function.append(rset)
1189

    
1190
        # ON/OFF SET
1191
        rs = RadioSettingValueList(LIST_APO,
1192
                                   LIST_APO[_settings.auto_power_off])
1193
        rset = RadioSetting("auto_power_off", "Auto Power Off", rs)
1194
        function.append(rset)
1195

    
1196
        rs = RadioSettingValueList(LIST_AOP, LIST_AOP[_settings.auto_power_on])
1197
        rset = RadioSetting("auto_power_on", "Power On Method", rs)
1198
        function.append(rset)
1199

    
1200
        # STE SET
1201
        rs = RadioSettingValueList(LIST_STE_FREQ,
1202
                                   LIST_STE_FREQ[_settings.ste_frequency])
1203
        rset = RadioSetting("ste_frequency", "STE Frequency", rs)
1204
        function.append(rset)
1205

    
1206
        rs = RadioSettingValueList(LIST_STE_TYPE,
1207
                                   LIST_STE_TYPE[_settings.ste_type])
1208
        rset = RadioSetting("ste_type", "STE Type", rs)
1209
        function.append(rset)
1210

    
1211
        # FUNCTION SET
1212
        rs = RadioSettingValueList(LIST_STEP, LIST_STEP[_settings.tuning_step])
1213
        rset = RadioSetting("tuning_step", "Tuning Step", rs)
1214
        function.append(rset)
1215

    
1216
        rs = RadioSettingValueList(LIST_SQUELCH,
1217
                                   LIST_SQUELCH[_settings.squelch])
1218
        rset = RadioSetting("squelch", "Squelch Level", rs)
1219
        function.append(rset)
1220

    
1221
        if "AT-779" in str(_embedded.radio_type):
1222
            rs = RadioSettingValueList(LIST_SCAN,
1223
                                       LIST_SCAN[_settings.scan_resume])
1224
            rset = RadioSetting("scan_resume", "Frequency Scan", rs)
1225
            function.append(rset)
1226

    
1227
        rs = RadioSettingValueBoolean(bool(_settings.sql_key_function))
1228
        rset = RadioSetting("sql_key_function", "SQL Key Function", rs)
1229
        function.append(rset)
1230

    
1231
        rs = RadioSettingValueList(LIST_TIMEOUT,
1232
                                   LIST_TIMEOUT[_settings.timeout_timer])
1233
        rset = RadioSetting("timeout_timer", "Timeout Timer", rs)
1234
        function.append(rset)
1235

    
1236
        # uncategorized
1237
        rs = RadioSettingValueBoolean(bool(_settings.save_chan_param))
1238
        rset = RadioSetting("save_chan_param", "Save Channel Parameters", rs)
1239
        function.append(rset)
1240

    
1241
        rs = RadioSettingValueBoolean(bool(_settings.forbid_chan_menu))
1242
        rset = RadioSetting("forbid_chan_menu", "Forbid Channel Menu", rs)
1243
        function.append(rset)
1244

    
1245
        rs = RadioSettingValueBoolean(bool(not _settings.forbid_initialize))
1246
        rset = RadioSetting("forbid_initialize", "Forbid Initialize", rs)
1247
        function.append(rset)
1248

    
1249
        rs = RadioSettingValueBoolean(bool(_settings.forbid_setting))
1250
        rset = RadioSetting("forbid_setting", "Forbid Setting", rs)
1251
        function.append(rset)
1252

    
1253
        # Information Of Scanning Channel
1254
        scanning = RadioSettingGroup("scanning", "Scanning Setup")
1255
        group.append(scanning)
1256

    
1257
        rs = RadioSettingValueBoolean(bool(_settings2.scan_mode))
1258
        rset = RadioSetting("settings2.scan_mode", "Scan Mode", rs)
1259
        scanning.append(rset)
1260

    
1261
        rs = RadioSettingValueList(LIST_PRIORITY_CH,
1262
                                   LIST_PRIORITY_CH[_settings2.priority_ch])
1263
        rset = RadioSetting("settings2.priority_ch", "Priority Channel", rs)
1264
        scanning.append(rset)
1265

    
1266
        rs = RadioSettingValueInteger(1, 199, _settings2.priority_ch1 + 1)
1267
        rset = RadioSetting("settings2.priority_ch1", "Priority Channel 1", rs)
1268
        scanning.append(rset)
1269

    
1270
        rs = RadioSettingValueInteger(1, 199, _settings2.priority_ch2 + 1)
1271
        rset = RadioSetting("settings2.priority_ch2", "Priority Channel 2", rs)
1272
        scanning.append(rset)
1273

    
1274
        rs = RadioSettingValueList(LIST_REVERT_CH,
1275
                                   LIST_REVERT_CH[_settings2.revert_ch])
1276
        rset = RadioSetting("settings2.revert_ch", "Revert Channel", rs)
1277
        scanning.append(rset)
1278

    
1279
        rs = RadioSettingValueList(LIST_TIME46,
1280
                                   LIST_TIME46[_settings2.look_back_time_a])
1281
        rset = RadioSetting("settings2.look_back_time_a",
1282
                            "Look Back Time A", rs)
1283
        scanning.append(rset)
1284

    
1285
        rs = RadioSettingValueList(LIST_TIME46,
1286
                                   LIST_TIME46[_settings2.look_back_time_b])
1287
        rset = RadioSetting("settings2.look_back_time_b",
1288
                            "Look Back Time B", rs)
1289
        scanning.append(rset)
1290

    
1291
        rs = RadioSettingValueList(LIST_TIME50,
1292
                                   LIST_TIME50[_settings2.dropout_delay_time])
1293
        rset = RadioSetting("settings2.dropout_delay_time",
1294
                            "Dropout Delay Time", rs)
1295
        scanning.append(rset)
1296

    
1297
        rs = RadioSettingValueList(LIST_TIME50,
1298
                                   LIST_TIME50[_settings2.dwell_time])
1299
        rset = RadioSetting("settings2.dwell_time", "Dwell Time", rs)
1300
        scanning.append(rset)
1301

    
1302
        # Embedded Message
1303
        embedded = RadioSettingGroup("embedded", "Embedded Message")
1304
        group.append(embedded)
1305

    
1306
        rs = RadioSettingValueString(0, 7, _filter(_embedded.radio_type))
1307
        rs.set_mutable(False)
1308
        rset = RadioSetting("embedded_msg.radio_type", "Radio Type", rs)
1309
        embedded.append(rset)
1310

    
1311
        if str(_embedded.radio_type) == "RT98V" or "AT-779V":
1312
            options = LIST_RT98V_MODES
1313
        else:
1314
            options = LIST_RT98U_MODES
1315
        rs = RadioSettingValueList(options, options[_embedded.mode])
1316
        rs.set_mutable(False)
1317
        rset = RadioSetting("embedded_msg.mode", "Mode", rs)
1318
        embedded.append(rset)
1319

    
1320
        # frequency
1321
        if str(_embedded.radio_type) == "RT98V" or "AT-779V":
1322
            options = LIST_RT98V_FREQS
1323
        else:
1324
            options = LIST_RT98U_FREQS
1325
        rs = RadioSettingValueList(options, options[_settings3.bandlimit])
1326
        rs.set_mutable(False)
1327
        rset = RadioSetting("settings3.bandlimit", "Frequency", rs)
1328
        embedded.append(rset)
1329

    
1330
        rs = RadioSettingValueString(0, 10, _filter(_embedded.date_mfg))
1331
        rs.set_mutable(False)
1332
        rset = RadioSetting("embedded_msg.date_mfg", "Production Date", rs)
1333
        embedded.append(rset)
1334

    
1335
        rs = RadioSettingValueString(0, 4, _filter(_embedded.mcu_version))
1336
        rs.set_mutable(False)
1337
        rset = RadioSetting("embedded_msg.mcu_version", "MCU Version", rs)
1338
        embedded.append(rset)
1339

    
1340
        return group
1341

    
1342
    def get_settings(self):
1343
        try:
1344
            return self._get_settings()
1345
        except:
1346
            import traceback
1347
            LOG.error("failed to parse settings")
1348
            traceback.print_exc()
1349
            return None
1350

    
1351
    def set_settings(self, settings):
1352
        for element in settings:
1353
            if not isinstance(element, RadioSetting):
1354
                self.set_settings(element)
1355
                continue
1356
            else:
1357
                try:
1358
                    if "." in element.get_name():
1359
                        bits = element.get_name().split(".")
1360
                        obj = self._memobj
1361
                        for bit in bits[:-1]:
1362
                            obj = getattr(obj, bit)
1363
                        setting = bits[-1]
1364
                    else:
1365
                        obj = self._memobj.settings
1366
                        setting = element.get_name()
1367

    
1368
                    if element.has_apply_callback():
1369
                        LOG.debug("using apply callback")
1370
                        element.run_apply_callback()
1371
                    elif setting == "ch_number":
1372
                        setattr(obj, setting, int(element.value) - 1)
1373
                    elif setting == "forbid_initialize":
1374
                        setattr(obj, setting, not int(element.value))
1375
                    elif setting == "priority_ch1":
1376
                        setattr(obj, setting, int(element.value) - 1)
1377
                    elif setting == "priority_ch2":
1378
                        setattr(obj, setting, int(element.value) - 1)
1379
                    elif element.value.get_mutable():
1380
                        LOG.debug("Setting %s = %s" % (setting, element.value))
1381
                        setattr(obj, setting, element.value)
1382
                except Exception, e:
1383
                    LOG.debug(element.get_name())
1384
                    raise
1385

    
1386
    @classmethod
1387
    def match_model(cls, filedata, filename):
1388
        # This radio has always been post-metadata, so never do
1389
        # old-school detection
1390
        return False
1391

    
1392

    
1393
@directory.register
1394
class Rt98Radio(Rt98BaseRadio):
1395
    """Retevis RT98"""
1396
    VENDOR = "Retevis"
1397
    MODEL = "RT98"
1398
    # Allowed radio types is a dict keyed by model of a list of version
1399
    # strings
1400
    ALLOWED_RADIO_TYPES = {'RT98V': ['V100'],
1401
                           'RT98U': ['V100'],
1402
                           'AT-779V': ['V100'],
1403
                           'AT-779U': ['V100']}
(26-26/30)