Project

General

Profile

New Model #9665 » radtel_rt490_passes_automated_tests.py

Jim Unroe, 07/05/2023 04:57 PM

 
1
#  Copyright (c) 2022 <angelof9@protonmail.com>
2
#
3
#  BSD 2-Clause "Simplified" License
4
#  https://opensource.org/licenses/BSD-2-Clause
5
#
6
#  Redistribution and use in source and binary forms,
7
#  with or without modification, are permitted provided
8
#  that the following conditions are met:
9
#
10
#  1. Redistributions of source code must retain the above
11
#     copyright notice, this list of conditions and
12
#     the following disclaimer.
13
#  2. Redistributions in binary form must reproduce the
14
#     above copyright notice, this list of conditions and
15
#     the following disclaimer in the documentation
16
#     and/or other materials provided with the distribution.
17
#
18
#  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19
#  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
20
#  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21
#  PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
22
#  CONTRIBUTORS
23
#  BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
24
#  OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25
#  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26
#  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27
#  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28
#  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
29
#  THE POSSIBILITY OF SUCH DAMAGE.
30

    
31
#  Python3 byte clean by Darryl Pogue
32
#  Pep8 style copliant by Jim Unroe <rock.unroe@gmail.com>
33

    
34
import logging
35
import struct
36

    
37
from chirp import (
38
    bitwise,
39
    chirp_common,
40
    directory,
41
    errors,
42
    memmap,
43
    util,
44
)
45

    
46
from chirp.settings import (
47
    RadioSetting,
48
    RadioSettingGroup,
49
    RadioSettings,
50
    RadioSettingValueBoolean,
51
    RadioSettingValueInteger,
52
    RadioSettingValueList,
53
    RadioSettingValueString,
54
    RadioSettingValueMap,
55
    RadioSettingValueFloat,
56
    InvalidValueError
57
)
58

    
59
from textwrap import dedent
60

    
61
LOG = logging.getLogger(__name__)
62

    
63
# 'True' or 'False'
64
# True unlocks:
65
# - FM preset
66
# - Channel memory  # preset
67
# - Killswitch (with revive killed radios)
68
# - Bands settings
69
# - Disable radio identification string verification
70
#   (good to recover from a bad state)
71
RT490_EXPERIMENTAL = True
72

    
73
MEM_FORMAT_RT490 = """
74
struct {                    // Memory settings
75
  lbcd rxfreq[4];
76
  lbcd txfreq[4];
77
  ul16 rxtone;
78
  ul16 txtone;
79
  u8 signal;            // int 0->14, Signal 1->15
80
  u8 pttid;             // ['OFF', 'BOT', 'EOT', 'Both']
81
  u8 dcp:4,             // What is DCP ? FHSS ? DC-FHSS ??? TODO
82
     power:4;           // POWER_LEVELS
83
  u8 unknown3_0:1,      // Used by the driver to store AM/NAM flag
84
                        // (thank you Radtel for free space)
85
     narrow:1,          // bool true=NFM false=FM (col[7] a)
86
     unknown3_1:1,
87
     unknown3_2:1,
88
     bcl:1,             // bool (col[9] a2)
89
     scan:1,            // bool (col[10] a3)
90
     tx_enable:1,       // bool (col[1] a4)
91
     learn:1;           // bool ??? TODO (col[14] a5)
92
} memory[%(memsize)d];
93
#seekto 0x1000;             // Memory names (@4096)
94
struct {
95
  char name[12];
96
  u8 ffpad[4];
97
} memname[%(memsize)d];
98
#seekto 0x2000;             // GOOD DCP keys ??? TODO ?? (@8192)
99
struct {
100
  u8 code[4];
101
} memcode[%(memsize)d];     // up to @x2400
102
                        // Filled with xFF during download (=> no need to fill
103
                        // with xFF), ready to upload
104
#seekto 0x3400;             // Custom ANI Names (@13312)
105
struct {
106
  char name[10];
107
  u8 ffpad[6];
108
} custom_ani_names[10];
109
                        // Filled with xFF during download (=> no need to fill
110
                        // with xFF), ready to upload
111
#seekto 0x3500;             // ANI Codes (@13568)
112
struct {
113
  u8 anicode[6];
114
  u8 ffpad[10];
115
} anicodes[60];
116
                        // Filled with xFF during download (=> no need to fill
117
                        // with xFF), ready to upload
118
#seekto 0x3900;             // Custom channel names (@14592)
119
struct {
120
  char name[10];
121
  u8 ffpad[6];
122
} custom_channel_names[10];
123
                        // Filled with xFF during download (=> no need to fill
124
                        // with xFF), ready to upload
125
#seekto 0x3A00;             // Settings (@14848)
126
struct {
127
  u8 squelch;           // 0: int 0 -> 9
128
  u8 savemode;          // 1: ['OFF', 'Normal', 'Super', 'Deep']
129
  u8 vox;               // 2: off=0, 1 -> 9
130
  u8 backlight;         // 3: ['OFF', '5s', '15s', '20s', '30s', '1m', '2m',
131
                        //     '3m']
132
  u8 tdr;               // 4: bool
133
  u8 timeout;           // 5: n*30seconds, 0-240s
134
  u8 beep;              // 6: bool
135
  u8 voice;             // 7: bool
136
  u8 byte_not_used_10;  // 8: Allways 1
137
  u8 dtmfst;            // 9: ['OFF', 'KB Side Tone', 'ANI Side Tone',
138
                        //     'KB ST + ANI ST']
139
  u8 scanmode;          // 10: ['TO', 'CO', 'SE']
140
  u8 pttid;             // 11: ['OFF', 'BOT', 'EOT', 'Both']
141
  u8 pttiddelay;        // 12: ['0', '100ms', '200ms', '400ms', '600ms',
142
                        //      '800ms', '1000ms']
143
  u8 cha_disp;          // 13: ['Name', 'Freq', 'Channel ID']
144
  u8 chb_disp;          // 14: ['Name', 'Freq', 'Channel ID']
145
  u8 bcl;               // 15: bool
146
  u8 autolock;          // 0: ['OFF', '5s', '10s', 15s']
147
  u8 alarm_mode;        // 1: ['Site', 'Tone', 'Code']
148
  u8 alarmsound;        // 2: bool
149
  u8 txundertdr;        // 3: ['OFF', 'A', 'B']
150
  u8 tailnoiseclear;    // 4: [off, on]
151
  u8 rptnoiseclear;     // 5: n*100ms, 0-1000
152
  u8 rptnoisedelay;     // 6: n*100ms, 0-1000
153
  u8 roger;             // 7: bool
154
  u8 active_channel;    // 8: 0 or 1
155
  u8 fmradio;           // 9: boolean, inverted
156
  u8 workmodeb:4,       // 10:up    ['VFO', 'CH Mode']
157
     workmodea:4;       // 10:down  ['VFO', 'CH Mode']
158
  u8 kblock;            // 11: bool                  // TODO TEST WITH autolock
159
  u8 powermsg;          // 12: 0=Image / 1=Voltage
160
  u8 byte_not_used_21;  // 13: Allways 0
161
  u8 rpttone;           // 14: ['1000Hz', '1450Hz', '1750Hz', '2100Hz']
162
  u8 byte_not_used_22;  // 15: pad with xFF
163
  u8 vox_delay;         // 0: [str(float(a)/10)+'s' for a in range(5,21)]
164
                        //     '0.5s' to '2.0s'
165
  u8 timer_menu_quit;   // 1: ['5s', '10s', '15s', '20s', '25s', '30s', '35s',
166
                        //     '40s', '45s', '50s', '60s']
167
  u8 byte_not_used_30;  // 2: pad with xFF
168
  u8 byte_not_used_31;  // 3: pad with xFF
169
  u8 enable_killsw;     // 4: bool
170
  u8 display_ani;       // 5: bool
171
  u8 byte_not_used_32;  // 6: pad with xFF
172
  u8 enable_gps;        // 7: bool
173
  u8 scan_dcs;          // 8: ['All', 'Receive', 'Transmit']
174
  u8 ani_id;            // 9: int 0-59 (ANI 1-60)
175
  u8 rx_time;           // 10: bool
176
  u8 ffpad0[5];          // 11: Pad xFF
177
  u8 cha_memidx;        // 0: Memory index when channel A use memories
178
  u8 byte_not_used_40;
179
  u8 chb_memidx;        // 2: Memory index when channel B use memories
180
  u8 byte_not_used_41;
181
  u8 ffpad1[10];
182
  ul16 fmpreset;
183
} settings;
184
                        // Filled with xFF during download (=> no need to fill
185
                        // with xFF), ready to upload
186
struct settings_vfo_chan {
187
  u8   rxfreq[8];       // 0
188
  ul16 rxtone;          // 8
189
  ul16 txtone;          // 10
190
  ul16 byte_not_used0;  // 12 Pad xFF
191
  u8   sftd:4,          // 14 Shift dir ['OFF', '+', '-']
192
       signal:4;        // 14 int 0->14, Signal 1->15
193
  u8   byte_not_used1;  // 15 Pad xFF
194
  u8   power;           // 16:0 POWER_LEVELS
195
  u8   fhss:4,          // 17 ['OFF', 'FHSS 1', 'FHSS 2', 'FHSS 3', 'FHSS 4']
196
       narrow:4;        // 17 bool true=NFM false=FM
197
  u8   byte_not_used2;  // 18 Pad xFF but received 0x00 ???
198
  u8   freqstep;        // 19:3 ['2.5 KHz', '5.0 KHz', '6.25 KHz',
199
                        //       '10.0 KHz', '12.5 KHz', '20.0 KHz',
200
                        //       '25.0 KHz', '50.0 KHz']
201
  u8   byte_not_used3;  // 20:4 Pad xFF but received 0x00 ??? TODO
202
  u8   offset[6];       // 21:5 Freq NN.NNNN (without the dot) TEST TEST
203
  u8   byte_not_used4;  // 27:11   Pad xFF
204
  u8   byte_not_used5;  // 28      Pad xFF
205
  u8   byte_not_used6;  // 29      Pad xFF
206
  u8   byte_not_used7;  // 30      Pad xFF
207
  u8   byte_not_used8;  // 31:15   Pad xFF
208
};
209
#seekto 0x3A40;             // VFO A/B (@14912)
210
struct {
211
  struct settings_vfo_chan vfo_a;
212
  struct settings_vfo_chan vfo_b;
213
} settings_vfo;
214
#seekto 0x3A80;             // Side keys settings (@14976)
215
struct {                    // Values from Radio
216
  u8 pf2_short;         // { '7': 'FM', '10': 'Tx Power', '28': 'Scan',
217
                        //  '29': 'Search, '1': 'PPT B' }
218
  u8 pf2_long;          // { '7': 'FM', '10': 'Tx Power', '28': 'Scan',
219
                        //  '29': 'Search' }
220
  u8 pf3_short;         // {'7': 'FM', '10': 'Tx Power', '28': 'Scan',
221
                        //  '29': 'Search'}
222
  u8 ffpad;             // Pad xFF
223
} settings_sidekeys;
224
struct dtmfcode {
225
  u8 code[5];           // 5 digits DTMF
226
  u8 ffpad[11];         // Pad xFF
227
};
228
                        // Filled with xFF during download (=> no need to fill
229
                        // with xFF), ready to upload
230
#seekto 0x3B00;             // DTMF (@15104)
231
struct dtmfcode settings_dtmfgroup[15];
232
struct {                // @15296+3x16
233
  u8 byte_not_used1;    // 0: Pad xFF something here
234
  u8 byte_not_used2;    // 1: Pad xFF something here
235
  u8 byte_not_used3;    // 2: Pad xFF something here
236
  u8 byte_not_used4;    // 3: Pad xFF
237
  u8 byte_not_used5;    // 4: Pad xFF
238
  u8 unknown_dtmf;      // 5: 0 TODO ???? wtf is alarmcode/alarmcall TODO
239
  u8 pttid;             // 6: [off, BOT, EOT, Both]
240
  u8 dtmf_speed_on;     // 7: ['50ms', '100ms', '200ms', '300ms', '500ms']
241
  u8 dtmf_speed_off;    // 8:0 ['50ms', '100ms', '200ms', '300ms', '500ms']
242
} settings_dtmf;
243
                        // Filled with xFF during download (=> no need to fill
244
                        // with xFF), ready to upload
245
#seekto 0x3C00;             // DTMF Kill/ReLive Codes (@15360)
246
struct {
247
  u8 kill_dtmf[6];      // 0: Kill DTMF
248
  u8 ffpad1[2];         // Pad xFF
249
  u8 revive_dtmf[6];    // 8: Revive DTMF
250
  u8 ffpad2[2];         // Pad xFF
251
} settings_killswitch;
252
                            // Some unknown data between 0x3E00 and 0x3F00
253
#seekto 0x3F80;             // Hmm hmm
254
struct {
255
  u8 unknown_data_0[16];
256
  u8 unknown_data_1;
257
  u8 active;            // Bool radio killed (killed=0, active=1)
258
  u8 unknown_data_2[46];
259
} management_settings;
260
struct band {
261
  u8 enable;            // 0 bool / enable-disable Tx on band
262
  bbcd freq_low[2];       // 1 lowest band frequency
263
  bbcd freq_high[2];      // 3 highest band frequency
264
};
265
#seekto 0x3FC0;             // Bands settings (@16320)
266
struct {
267
  struct band band136;  // 0  Settings for 136MHz band
268
  struct band band400;  // 5  Settings for 400MHz band
269
  struct band band200;  // 10 Settings for 200MHz band
270
  u8 byte_not_used1;    // 15
271
  struct band band350;  // 0  Settings for 350MHz band
272
  u8 byte_not_used2[43];// 5
273
} settings_bands;
274
"""
275

    
276
CMD_ACK = b"\x06"
277

    
278
DTCS_CODES = tuple(sorted(chirp_common.DTCS_CODES + (645,)))
279

    
280
DTMFCHARS = '0123456789ABCD*#'
281

    
282

    
283
def _enter_programming_mode(radio):
284
    serial = radio.pipe
285

    
286
    exito = False
287
    for i in range(0, 5):
288
        serial.write(radio._magic)
289
        ack = serial.read(1)
290

    
291
        try:
292
            if ack == CMD_ACK:
293
                exito = True
294
                break
295
        except Exception:
296
            LOG.debug("Attempt #%s, failed, trying again" % i)
297
            pass
298

    
299
    # check if we had EXITO
300
    if exito is False:
301
        msg = "The radio did not accept program mode after five tries.\n"
302
        msg += "Check you interface cable and power cycle your radio."
303
        raise errors.RadioError(msg)
304

    
305
    try:
306
        serial.write(b"F")
307
        ident = serial.read(8)
308
    except Exception:
309
        raise errors.RadioError("Error communicating with radio")
310

    
311
    if not ident.startswith(radio._fingerprint) and not RT490_EXPERIMENTAL:
312
        LOG.debug(util.hexprint(ident))
313
        raise errors.RadioError("Radio returned unknown identification string")
314

    
315

    
316
def _exit_programming_mode(radio):
317
    serial = radio.pipe
318
    try:
319
        serial.write(b"E")
320
    except Exception:
321
        raise errors.RadioError("Radio refused to exit programming mode")
322

    
323

    
324
def _read_block(radio, block_addr, block_size):
325
    serial = radio.pipe
326

    
327
    cmd = struct.pack(">cHb", b'R', block_addr, block_size)
328
    expectedresponse = b"R" + cmd[1:]
329
    LOG.debug("Reading block %04x..." % (block_addr))
330

    
331
    try:
332
        serial.write(cmd)
333
        response = serial.read(4 + block_size)
334
        if response[:4] != expectedresponse:
335
            raise Exception("Error reading block %04x." % (block_addr))
336

    
337
        block_data = response[4:]
338
    except Exception:
339
        raise errors.RadioError("Failed to read block at %04x" % block_addr)
340

    
341
    return block_data
342

    
343

    
344
def _write_block(radio, block_addr, block_size):
345
    serial = radio.pipe
346

    
347
    cmd = struct.pack(">cHb", b'W', block_addr, block_size)
348
    data = radio.get_mmap()[block_addr:block_addr + block_size]
349

    
350
    LOG.debug("Writing Data:")
351
    LOG.debug(util.hexprint(cmd + data))
352

    
353
    try:
354
        serial.write(cmd + data)
355
        if serial.read(1) != CMD_ACK:
356
            raise Exception("No ACK")
357
    except Exception:
358
        raise errors.RadioError("Failed to send block "
359
                                "to radio at %04x" % block_addr)
360

    
361

    
362
def do_download(radio):
363
    LOG.debug("download")
364
    _enter_programming_mode(radio)
365

    
366
    data = b""
367

    
368
    status = chirp_common.Status()
369
    status.msg = "Cloning from radio"
370

    
371
    status.cur = 0
372
    status.max = radio._memsize
373

    
374
    for addr in range(0, radio._memsize, radio.BLOCK_SIZE):
375
        status.cur = addr + radio.BLOCK_SIZE
376
        radio.status_fn(status)
377

    
378
        block = _read_block(radio, addr, radio.BLOCK_SIZE)
379
        data += block
380

    
381
        LOG.debug("Address: %04x" % addr)
382
        LOG.debug(util.hexprint(block))
383

    
384
    _exit_programming_mode(radio)
385

    
386
    return memmap.MemoryMapBytes(data)
387

    
388

    
389
def do_upload(radio):
390
    status = chirp_common.Status()
391
    status.msg = "Uploading to radio"
392

    
393
    _enter_programming_mode(radio)
394

    
395
    status.cur = 0
396
    status.max = radio._memsize
397

    
398
    for start_addr, end_addr in radio._ranges:
399
        for addr in range(start_addr, end_addr, radio.BLOCK_SIZE):
400
            status.cur = addr + radio.BLOCK_SIZE
401
            radio.status_fn(status)
402
            _write_block(radio, addr, radio.BLOCK_SIZE)
403

    
404
    _exit_programming_mode(radio)
405

    
406

    
407
class RT490Radio(chirp_common.CloneModeRadio):
408
    """RADTEL RT-490"""
409
    VENDOR = "Radtel"
410
    MODEL = "RT-490"
411
    BLOCK_SIZE = 0x40  # 64 bytes
412
    BAUD_RATE = 9600
413
    NEEDS_COMPAT_SERIAL = False
414

    
415
    POWER_LEVELS = [chirp_common.PowerLevel("H", watts=5.00),
416
                    chirp_common.PowerLevel("L", watts=3.00)]
417

    
418
    # magic = progmode + modelType + garbage (works with any last char)
419
    _magic = b"PROGROMJJCCU"
420

    
421
    # fingerprint is default band ranges of the radio
422
    # the driver can change band ranges and fingerprint will
423
    # change accordingly, so it is not used to verify radio id.
424
    _fingerprint = b"\x01\x36\x01\x80\x04\x00\x05\x20"
425

    
426
    # Ranges of memory used when uploading data to radio
427
    # same as official software
428
    _ranges = [
429
               (0x0000, 0x2400),
430
               (0x3400, 0x3C40),
431
               (0x3FC0, 0x4000)
432
              ]
433

    
434
    if RT490_EXPERIMENTAL:
435
        # Experimental driver (already heavily tested)
436
        _ranges = [(0x0000, 0x2400), (0x3400, 0x3C40), (0x3F80, 0x4000)]
437

    
438
    # Danger zone
439
    # _ranges = [(0x0000, 0x2500), (0x3400, 0x3C40), (0x3E00, 0x4000)]
440

    
441
    # 16KB of memory, download read everything
442
    # same as official software (remark: loops if overread :))
443
    _memsize = 16384
444

    
445
    POWER_LEVELS_LIST = [str(i) for i in POWER_LEVELS]
446
    FHSS_LIST = ['OFF', 'ENCRYPT 1', 'ENCRYPT 2', 'ENCRYPT 3', 'ENCRYPT 4']
447
    DCP_LIST = ['OFF', 'DCP1', 'DCP2', 'DCP3', 'DCP4']  # Same as FHSS ?
448
    #                                                   # Seems yes
449
    TUNING_STEPS = [2.5, 5.0, 6.25, 10.0, 12.5, 20.0, 25.0, 50.0]
450
    TUNING_STEPS_LIST = [str(i)+' KHz' for i in TUNING_STEPS]
451
    SIGNAL = [str(i) for i in range(1, 16)]
452
    PTTID = ['OFF', 'BOT', 'EOT', 'Both']
453
    PTTIDDELAYS = ['0', '100ms', '200ms', '400ms', '600ms', '800ms', '1000ms']
454
    DTMF_SPEEDS = ['50ms', '100ms', '200ms', '300ms', '500ms']
455
    SIDEKEY_VALUEMAP = [('FM', 7), ('Tx Power', 10), ('Scan', 28),
456
                        ('Search', 29), ('PTT B', 1)]
457
    KEY_CHARS = '0123456789ABCDEF'
458
    FULL_CHARSET_ASCII = ("".join([chr(x) for x in range(ord(" "),
459
                          ord("~") + 1)] + [chr(x) for x in range(128, 255)] +
460
                              [chr(0)]))
461
    VFO_SFTD = ['OFF', '+', '-']
462
    WORKMODES = ['VFO', 'Memory Mode']
463
    SAVEMODES = ['OFF', 'Normal', 'Super', 'Deep']
464
    DISPLAYMODES = ['Name', 'Freq', 'Memory ID']
465
    SCANMODES = ['TO', 'CO', 'SE']
466
    ALARMMODES = ['On Site', 'Send Sound', 'Send Code']
467
    TDRTXMODES = ['OFF', 'A', 'B']
468
    SCANDCSMODES = ['All', 'Receive', 'Transmit']
469
    POWERMESSAGES = ['Image', 'Voltage']
470
    FMRADIO = ['ON', 'OFF']
471
    ENABLERADIO = ['Killed', 'Active']
472
    CHANNELS = ['A', 'B']
473
    TOT_LIST = ['OFF'] + [str(i*30) + "s" for i in range(1, 9)]
474
    VOX_LIST = ['OFF'] + [str(i) for i in range(1, 9)]
475
    BACKLIGHT_TO = ['OFF', '5s', '10s', '15s', '20s', '30s', '1m', '2m', '3m']
476
    AUTOLOCK_TO = ['OFF', '5s', '10s', '15s']
477
    MENUEXIT_TO = ['5s', '10s', '15s', '20s', '25s', '30s', '35s', '40s',
478
                   '45s', '50s', '60s']
479
    SQUELCHLVLS = [str(i) for i in range(10)]
480
    ANI_IDS = [str(i+1) for i in range(60)]
481
    VOXDELAYLIST = [str(float(a)/10)+'s' for a in range(5, 21)]
482
    DTMFSTLIST = ['OFF', 'DT Side Tone', 'ANI Side Tone', 'DT ST + ANI ST']
483
    RPTTONES = ['1000Hz', '1450Hz', '1750Hz', '2100Hz']
484
    RPTNOISE = [str(a)+'s' for a in range(11)]
485
    _memory_size = _upper = 256  # Number of memory slots
486
    _mem_params = (_upper-1)
487
    _frs = _murs = _pmr = _gmrs = True
488

    
489
    def set_settings(self, settings):
490
        for element in settings:
491
            if not isinstance(element, RadioSetting):
492
                self.set_settings(element)
493
                continue
494
            else:
495
                self._set_setting(element)
496

    
497
    def _set_setting(self, setting):  # WIP
498
        _mem = self._memobj
499
        key = setting.get_name()
500
        val = setting.value
501
        if key.startswith('dummy'):
502
            return
503
        elif key.startswith('settings_dtmfgroup.'):
504
            if str(val) == "":
505
                setattr(_mem.settings_dtmfgroup[int(key.split('@')[1])-1],
506
                        'code', [0xFF]*5)
507
            else:
508
                tmp = [DTMFCHARS.index(c) for c in str(val)]
509
                tmp += [0xFF] * (5 - len(tmp))
510
                setattr(_mem.settings_dtmfgroup[int(key.split('@')[1])-1],
511
                        'code', tmp)
512
        elif key.startswith('settings.'):
513
            if key.endswith('_memidx'):
514
                val = int(val) - 1
515
            if key.endswith('fmpreset'):
516
                tmp = val.get_value() * 10
517
                setattr(_mem.settings, key.split('.')[1], tmp)
518
            else:
519
                setattr(_mem.settings, key.split('.')[1], int(val))
520
        elif key.startswith('settings_dtmf.'):
521
            attr = key.split('.')[1]
522
            setattr(_mem.settings_dtmf, attr, int(val))
523
            if attr.startswith('pttid'):
524
                setattr(_mem.settings, attr, int(val))
525
        elif key.startswith('settings_sidekeys.'):
526
            setattr(_mem.settings_sidekeys, key.split('.')[1], int(val))
527
        elif key.startswith('settings_vfo.'):  # TODO rx/tx tones
528
            tmp = key.split('.')
529
            attr = tmp[2]
530
            vfo = tmp[1]
531
            # LOG.debug(">>> PRE key '%s'" % key)
532
            # LOG.debug(">>> PRE val '%s'" % val)
533
            if attr.startswith('rxfreq'):
534
                value = chirp_common.parse_freq(str(val)) / 10
535
                for i in range(7, -1, -1):
536
                    _mem.settings_vfo[vfo].rxfreq[i] = value % 10
537
                    value /= 10
538
            elif attr.startswith('offset'):
539
                value = int(float(str(val)) * 10000)
540
                for i in range(5, -1, -1):
541
                    _mem.settings_vfo[vfo].offset[i] = value % 10
542
                    value /= 10
543
            else:
544
                setattr(_mem.settings_vfo[vfo], attr, int(val))
545
        elif key.startswith('settings_bands.'):
546
            tmp = key.split('.')
547
            attr = tmp[2]
548
            band = tmp[1]
549
            setattr(_mem.settings_bands[band], attr, int(val))
550
        elif key.startswith('settings_killswitch.'):
551
            attr = key.split('.')[1]
552
            if attr.endswith('dtmf'):
553
                if str(val) == "":
554
                    setattr(_mem.settings_killswitch, attr,
555
                            [0x01, 0x02, 0x01, 0x03, 0x01, 0x04])
556
                else:
557
                    setattr(_mem.settings_killswitch, attr,
558
                            [DTMFCHARS.index(c) for c in str(val)])
559
            elif attr.startswith('enable'):
560
                setattr(_mem.settings_killswitch, attr, int(val))
561
            else:
562
                LOG.debug(">>> TODO key '%s'" % key)
563
                LOG.debug(">>> TODO val '%s'" % val)
564
        elif key.startswith('custom_') or key.startswith('anicode') \
565
                or key.startswith('memcode'):
566
            tmp = key.split('@')
567
            if key.startswith('anicode'):
568
                val = [DTMFCHARS.index(c) for c in str(val)]
569
                val += [0xFF] * (6 - len(val))
570
                for i in range(10):
571
                    _mem[str(tmp[0])][int(tmp[2])]["ffpad"][i] = 0xFF
572
            elif key.startswith('memcode'):
573
                if len(str(val)) > 0:
574
                    tmpp = str(val).zfill(6)
575
                    val = self._encode_key(tmpp)
576
                else:
577
                    val = (0xFF, 0xFF, 0xFF, 0xFF)
578
            if key.startswith('custom_'):
579
                for i in range(6):
580
                    _mem[str(tmp[0])][int(tmp[2])]["ffpad"][i] = 0xFF
581
            setattr(_mem[str(tmp[0])][int(tmp[2])], str(tmp[1]), val)
582
        elif key.startswith('management_settings'):
583
            setattr(_mem.management_settings, key.split('.')[1], int(val))
584
        else:
585
            LOG.debug(">>> TODO _set_setting key '%s'" % key)
586
            LOG.debug(">>> TODO _set_setting val '%s'" % val)
587

    
588
    def _get_settings_bands(self):
589
        _mem = self._memobj
590
        ret = RadioSettingGroup('bands', 'Bands')
591
        bands = [('136', _mem.settings_bands.band136),
592
                 ('200', _mem.settings_bands.band200),
593
                 ('350', _mem.settings_bands.band350),
594
                 ('400', _mem.settings_bands.band400)]
595
        for label, band in bands:
596
            rs = RadioSetting('settings_bands.band%s.enable' % label,
597
                              'Enable Band %s' % label,
598
                              RadioSettingValueBoolean(band.enable))
599
            ret.append(rs)
600
            rsi = RadioSettingValueInteger(1, 1000, band.freq_low)
601
            # if label == '136' or label == '400':
602
            #     rsi.set_mutable(False)
603
            rs = RadioSetting("settings_bands.band%s.freq_low" % label,
604
                              "Band %s Lower Limit (MHz) (EXPERIMENTAL)"
605
                              % label, rsi)
606
            ret.append(rs)
607
            rsi = RadioSettingValueInteger(1, 1000, band.freq_high)
608
            # if label == '350':
609
            #     rsi.set_mutable(False)
610
            rs = RadioSetting("settings_bands.band%s.freq_high" % label,
611
                              "Band %s Upper Limit (MHz) (EXPERIMENTAL)"
612
                              % label, rsi)
613
            ret.append(rs)
614
        return ret
615

    
616
    def _get_settings_ks(self):
617
        _mem = self._memobj
618
        ret = RadioSettingGroup('killswitch', 'Killswitch')
619
        # Kill Enable/Disable enable_killsw
620
        rsvb = RadioSettingValueBoolean(_mem.settings.enable_killsw)
621
        ret.append(RadioSetting('settings.enable_killsw',
622
                                'Enable Killswitch', rsvb))
623
        # Kill DTMF
624
        cur = ''.join(
625
            DTMFCHARS[i]
626
            for i in _mem.settings_killswitch.kill_dtmf if int(i) < 0xF)
627
        ret.append(
628
            RadioSetting('settings_killswitch.kill_dtmf', 'DTMF Kill',
629
                         RadioSettingValueString(6, 6, cur,
630
                                                 autopad=False,
631
                                                 charset=DTMFCHARS)))
632
        # Revive DTMF
633
        cur = ''.join(
634
            DTMFCHARS[i]
635
            for i in _mem.settings_killswitch.revive_dtmf if int(i) < 0xF)
636
        ret.append(
637
            RadioSetting('settings_killswitch.revive_dtmf', 'DTMF Revive',
638
                         RadioSettingValueString(6, 6, cur,
639
                                                 autopad=False,
640
                                                 charset=DTMFCHARS)))
641
        # Enable/Disable entire radio
642
        rs = RadioSettingValueString(0, 255, "Can be used to revive radio")
643
        rs.set_mutable(False)
644
        ret.append(RadioSetting('dummy', 'Factory reserved', rs))
645
        tmp = 1 if int(_mem.management_settings.active) > 0 else 0
646
        ret.append(RadioSetting('management_settings.active', 'Radio Status',
647
                                RadioSettingValueList(self.ENABLERADIO,
648
                                                      self.ENABLERADIO[tmp])))
649
        return ret
650

    
651
    def _get_settings_dtmf(self):
652
        _mem = self._memobj
653
        dtmf = RadioSettingGroup('dtmf', 'DTMF')
654
        # DTMF Group
655
        msgs = ["Allowed chars (%s)" % DTMFCHARS,
656
                "Input from 0 to 5 characters."
657
                ]
658
        for msg in msgs:
659
            rsvs = RadioSettingValueString(0, 255, msg, autopad=False)
660
            rsvs.set_mutable(False)
661
            rs = RadioSetting('dummy_dtmf_msg_%i' % msgs.index(msg),
662
                              'Input rule %i' % int(msgs.index(msg)+1), rsvs)
663
            dtmf.append(rs)
664
        for i in range(1, 16):
665
            cur = ''.join(
666
                DTMFCHARS[i]
667
                for i in _mem.settings_dtmfgroup[i - 1].code if int(i) < 0xF)
668
            dtmf.append(
669
                RadioSetting(
670
                    'settings_dtmfgroup.code@%i' % i, 'PTT ID Code %i' % i,
671
                    RadioSettingValueString(0, 5, cur,
672
                                            autopad=False,
673
                                            charset=DTMFCHARS)))
674
        # DTMF Speed (on time in ms)
675
        dtmf_speed_on = int(_mem.settings_dtmf.dtmf_speed_on)
676
        if dtmf_speed_on > len(self.DTMF_SPEEDS)-1:
677
            _mem.settings_dtmf.dtmf_speed_on = 0
678
            LOG.debug('DTMF Speed On overflow')
679
        cur = self.DTMF_SPEEDS[dtmf_speed_on]
680
        dtmf.append(
681
            RadioSetting(
682
                'settings_dtmf.dtmf_speed_on', 'DTMF Speed (on time in ms)',
683
                RadioSettingValueList(self.DTMF_SPEEDS, cur)))
684
        # DTMF Speed (on time in ms)
685
        dtmf_speed_off = int(_mem.settings_dtmf.dtmf_speed_off)
686
        if dtmf_speed_off > len(self.DTMF_SPEEDS)-1:
687
            _mem.settings_dtmf.dtmf_speed_off = 0
688
            LOG.debug('DTMF Speed Off overflow')
689
        cur = self.DTMF_SPEEDS[dtmf_speed_off]
690
        dtmf.append(
691
            RadioSetting(
692
                'settings_dtmf.dtmf_speed_off', 'DTMF Speed (off time in ms)',
693
                RadioSettingValueList(self.DTMF_SPEEDS, cur)))
694
        # PTT ID
695
        pttid = int(_mem.settings_dtmf.pttid)
696
        if pttid > len(self.PTTID)-1:
697
            _mem.settings_dtmf.pttid = 0
698
            LOG.debug('PTT ID overflow')
699
        cur = self.PTTID[pttid]
700
        dtmf.append(
701
            RadioSetting(
702
                'settings_dtmf.pttid', 'Send DTMF Code (PTT ID)',
703
                RadioSettingValueList(self.PTTID, cur)))
704
        # PTT ID Delay
705
        pttiddelay = int(_mem.settings.pttiddelay)
706
        if pttiddelay > len(self.PTTIDDELAYS)-1:
707
            _mem.settings.pttiddelay = 0
708
            LOG.debug('PTT ID  Delay overflow')
709
        cur = self.PTTIDDELAYS[pttiddelay]
710
        rsvl = RadioSettingValueList(self.PTTIDDELAYS, cur)
711
        dtmf.append(RadioSetting('settings.pttiddelay',
712
                    'PTT ID Delay', rsvl))
713
        rsvl = RadioSettingValueList(self.DTMFSTLIST,
714
                                     self.DTMFSTLIST[_mem.settings.dtmfst])
715
        dtmf.append(RadioSetting('settings.dtmfst',
716
                                 'DTMF Side Tone (Required for GPS ID)', rsvl))
717
        return dtmf
718

    
719
    def _get_settings_sidekeys(self):
720
        _mem = self._memobj
721
        ret = RadioSettingGroup('sidekeys', 'Side Keys')
722
        rsvm = RadioSettingValueMap(self.SIDEKEY_VALUEMAP,
723
                                    _mem.settings_sidekeys.pf2_short)
724
        ret.append(RadioSetting('settings_sidekeys.pf2_short',
725
                                'Side key 1 (PTT2) Short press', rsvm))
726
        rsvm = RadioSettingValueMap(self.SIDEKEY_VALUEMAP[:-1],
727
                                    _mem.settings_sidekeys.pf2_long)
728
        ret.append(RadioSetting('settings_sidekeys.pf2_long',
729
                                'Side key 1 (PTT2) Long press', rsvm))
730
        rsvm = RadioSettingValueMap(self.SIDEKEY_VALUEMAP[:-1],
731
                                    _mem.settings_sidekeys.pf3_short)
732
        ret.append(RadioSetting('settings_sidekeys.pf3_short',
733
                                'Side key 2 (PTT3) Short press', rsvm))
734
        rs = RadioSettingValueString(0, 255, "MONI")
735
        rs.set_mutable(False)
736
        ret.append(RadioSetting('dummy', 'Side key 2 (PTT3) Long press', rs))
737
        return ret
738

    
739
    def _get_settings_vfo(self, vfo, chan):  # WIP TODO Rx/Tx tones
740
        _mem = self._memobj
741
        ret = RadioSettingGroup('settings_vfo@%s' % chan.lower(),
742
                                'VFO %s Settings' % chan)
743
        rsvl = RadioSettingValueList(self.WORKMODES,
744
                                     self.WORKMODES[_mem.settings[
745
                                         'workmode'+chan.lower()]])
746
        ret.append(RadioSetting('settings.workmode%s' % chan.lower(),
747
                                'VFO %s Workmode' % chan, rsvl))
748
        tmp = ''.join(DTMFCHARS[i] for i in _mem.settings_vfo[
749
                      'vfo_'+chan.lower()].rxfreq if i < 0xFF)
750
        rsvf = RadioSettingValueFloat(66, 550, chirp_common.format_freq(
751
                                      int(tmp) * 10), resolution=0.00001,
752
                                      precision=5)
753
        ret.append(RadioSetting('settings_vfo.vfo_%s.rxfreq' % chan.lower(),
754
                                'Rx Frequency', rsvf))
755
        # TODO Rx/Tx tones
756
        rsvl = RadioSettingValueList(self.VFO_SFTD,
757
                                     self.VFO_SFTD[_mem.settings_vfo[
758
                                         'vfo_'+chan.lower()].sftd])
759
        ret.append(RadioSetting('settings_vfo.vfo_%s.sftd' % chan.lower(),
760
                                'Freq offset direction', rsvl))
761
        tmp = ''.join(DTMFCHARS[i] for i in _mem.settings_vfo[
762
                      'vfo_'+chan.lower()].offset if i < 0xFF)
763
        rsvf = RadioSettingValueFloat(0, 99.9999, float(tmp) / 10000)
764
        ret.append(RadioSetting('settings_vfo.vfo_%s.offset' % chan.lower(),
765
                                'Tx Offset', rsvf))
766
        rsvl = RadioSettingValueList(self.SIGNAL,
767
                                     self.SIGNAL[
768
                                         _mem.settings_vfo[
769
                                             'vfo_'+chan.lower()].signal])
770
        ret.append(RadioSetting('settings_vfo.vfo_%s.signal' % chan.lower(),
771
                                'PTT ID Code (S-Code)', rsvl))
772
        rsvl = RadioSettingValueList(self.POWER_LEVELS_LIST,
773
                                     self.POWER_LEVELS_LIST[
774
                                         _mem.settings_vfo[
775
                                             'vfo_'+chan.lower()].power])
776
        ret.append(RadioSetting('settings_vfo.vfo_%s.power' % chan.lower(),
777
                                'Tx Power', rsvl))
778
        rsvl = RadioSettingValueList(self.FHSS_LIST,
779
                                     self.FHSS_LIST[
780
                                         _mem.settings_vfo[
781
                                             'vfo_'+chan.lower()].fhss])
782
        ret.append(RadioSetting('settings_vfo.vfo_%s.fhss' % chan.lower(),
783
                                'FHSS (Encryption)', rsvl))
784
        rsvl = RadioSettingValueList(['Wide', 'Narrow'],
785
                                     ['Wide', 'Narrow'][
786
                                         _mem.settings_vfo[
787
                                             'vfo_'+chan.lower()].narrow])
788
        ret.append(RadioSetting('settings_vfo.vfo_%s.narrow' % chan.lower(),
789
                                'Wide / Narrow', rsvl))
790
        rsvl = RadioSettingValueList(self.TUNING_STEPS_LIST,
791
                                     self.TUNING_STEPS_LIST[
792
                                         _mem.settings_vfo[
793
                                             'vfo_'+chan.lower()].freqstep])
794
        ret.append(RadioSetting('settings_vfo.vfo_%s.freqstep' % chan.lower(),
795
                                'Tuning Step', rsvl))
796
        return ret
797

    
798
    def _get_custom_channel_names(self):
799
        _mem = self._memobj
800
        ret = RadioSettingGroup('ccn', 'Custom Channel Names')
801
        msgs = ["Add custom chan names to radio",
802
                "-> Menu 09 CH-NAME",
803
                "Allowed chars (ASCII only)",
804
                "Input from 0 to 10 characters."
805
                ]
806
        for msg in msgs:
807
            rsvs = RadioSettingValueString(0, 255, msg, autopad=False)
808
            rsvs.set_mutable(False)
809
            rs = RadioSetting('dummy_cnames_msg_%i' % msgs.index(msg),
810
                              'Input rule %i' % int(msgs.index(msg)+1), rsvs)
811
            ret.append(rs)
812
        for i in range(0, len(_mem.custom_channel_names)):
813
            tmp = ''.join([str(j) for j in _mem.custom_channel_names[i].name
814
                           if ord(str(j)) < 0xFF and ord(str(j)) > 0x00])
815
            rsvs = RadioSettingValueString(0, 10, tmp, autopad=True,
816
                                           charset=self.FULL_CHARSET_ASCII)
817
            ret.append(RadioSetting('custom_channel_names@name@%i' % i,
818
                                    'Custom Channel Name (%i)' % i, rsvs))
819
        return ret
820

    
821
    def _get_custom_ani_names(self):
822
        _mem = self._memobj
823
        ret = RadioSettingGroup('can', 'Custom ANI Names')
824
        msgs = ["Can be used as radio id in GPS.",
825
                "Allowed chars (ASCII only)",
826
                "Input from 0 to 10 characters."
827
                ]
828
        for msg in msgs:
829
            rsvs = RadioSettingValueString(0, 255, msg, autopad=False)
830
            rsvs.set_mutable(False)
831
            rs = RadioSetting('dummy_caninames_msg_%i' % msgs.index(msg),
832
                              'Input rule %i' % int(msgs.index(msg)+1), rsvs)
833
            ret.append(rs)
834
        for i in range(0, len(_mem.custom_ani_names)):
835
            tmp = ''.join([str(j) for j in
836
                           _mem.custom_ani_names[i].name
837
                           if ord(str(j)) < 0xFF and ord(str(j)) > 0x00])
838
            rsvs = RadioSettingValueString(0, 10, tmp, autopad=True,
839
                                           charset=self.FULL_CHARSET_ASCII)
840
            ret.append(RadioSetting('custom_ani_names@name@%i' % i,
841
                                    'Custom ANI Name (%i)' % (i+51), rsvs))
842
        return ret
843

    
844
    def _get_anicodes(self):
845
        _mem = self._memobj
846
        ret = RadioSettingGroup('ani', 'ANI Codes')
847
        split = len(_mem.anicodes) - len(_mem.custom_ani_names)
848
        msgs = ["Allowed chars (%s)" % DTMFCHARS,
849
                "Input from 0 to 6 characters."
850
                ]
851
        for msg in msgs:
852
            rsvs = RadioSettingValueString(0, 255, msg, autopad=False)
853
            rsvs.set_mutable(False)
854
            rs = RadioSetting('dummy_canic_msg_%i' % msgs.index(msg),
855
                              'Input rule %i' % int(msgs.index(msg)+1), rsvs)
856
            ret.append(rs)
857

    
858
        for i in range(0, split):
859
            tmp = ''.join([DTMFCHARS[int(j)] for j in
860
                           _mem.anicodes[i].anicode if int(j) < 0xFF])
861
            # LOG.debug("ANI Code (%i) '%s'" % (i, tmp))
862
            rsvs = RadioSettingValueString(0, 6, tmp, autopad=False,
863
                                           charset=DTMFCHARS)
864
            ret.append(RadioSetting('anicodes@anicode@%i' % i,
865
                                    'ANI-ID (%i) Code' % (i+1), rsvs))
866
        for i in range(split, len(_mem.anicodes)):
867
            tmp = ''.join([DTMFCHARS[int(j)] for j in
868
                          _mem.anicodes[i].anicode if int(j) < 0xFF])
869
            tmp2 = ''.join([str(j) for j in
870
                            _mem.custom_ani_names[i-split].name
871
                            if ord(str(j)) < 0xFF and ord(str(j)) > 0x00])
872
            # LOG.debug("ANI Code (%s) (%i) '%s'" % (tmp2, i, tmp))
873
            rsvs = RadioSettingValueString(0, 6, tmp, autopad=False,
874
                                           charset=DTMFCHARS)
875
            ret.append(RadioSetting('anicodes@anicode@%i' % i,
876
                       'ANI-ID (%s) (%i) Code' % (tmp2, i+1), rsvs))
877
        return ret
878

    
879
    def _get_settings_adv(self):
880
        _mem = self._memobj
881
        ret = RadioSettingGroup('advanced', 'Advanced')
882
        if RT490_EXPERIMENTAL:
883
            rsvi = RadioSettingValueInteger(
884
                1, self._memory_size, int(_mem.settings.cha_memidx)+1)
885
            ret.append(RadioSetting("settings.cha_memidx",
886
                                    "Channel A Memory index", rsvi))
887
            rsvi = RadioSettingValueInteger(1, self._memory_size,
888
                                            int(_mem.settings.chb_memidx)+1)
889
            ret.append(RadioSetting("settings.chb_memidx",
890
                       "Channel B Memory index", rsvi))
891
        ret.append(RadioSetting('settings.vox', 'VOX Sensitivity',
892
                   RadioSettingValueList(self.VOX_LIST,
893
                                         self.VOX_LIST[_mem.settings.vox])))
894
        ret.append(RadioSetting('settings.vox_delay', 'VOX Delay',
895
                   RadioSettingValueList(self.VOXDELAYLIST,
896
                                         self.VOXDELAYLIST[
897
                                             _mem.settings.vox_delay])))
898
        ret.append(RadioSetting('settings.tdr', 'Dual Receive (TDR)',
899
                   RadioSettingValueBoolean(_mem.settings.tdr)))
900
        ret.append(RadioSetting('settings.txundertdr', 'Tx under TDR',
901
                   RadioSettingValueList(self.TDRTXMODES,
902
                                         self.TDRTXMODES[
903
                                             _mem.settings.txundertdr])))
904
        ret.append(RadioSetting('settings.voice', 'Menu Voice Prompts',
905
                                RadioSettingValueBoolean(_mem.settings.voice)))
906
        ret.append(RadioSetting('settings.scanmode', 'Scan Mode',
907
                   RadioSettingValueList(self.SCANMODES,
908
                                         self.SCANMODES[
909
                                             _mem.settings.scanmode])))
910
        ret.append(RadioSetting('settings.bcl', 'Busy Channel Lockout',
911
                   RadioSettingValueBoolean(_mem.settings.bcl)))
912
        ret.append(RadioSetting('settings.display_ani', 'Display ANI ID',
913
                   RadioSettingValueBoolean(_mem.settings.display_ani)))
914
        ret.append(RadioSetting('settings.ani_id', 'ANI ID',
915
                   RadioSettingValueList(self.ANI_IDS,
916
                                         self.ANI_IDS[_mem.settings.ani_id])))
917
        ret.append(RadioSetting('settings.alarm_mode', 'Alarm Mode',
918
                   RadioSettingValueList(self.ALARMMODES,
919
                                         self.ALARMMODES[
920
                                             _mem.settings.alarm_mode])))
921
        ret.append(RadioSetting('settings.alarmsound', 'Alarm Sound',
922
                   RadioSettingValueBoolean(_mem.settings.alarmsound)))
923
        ret.append(RadioSetting('settings.fmradio', 'Enable FM Radio',
924
                   RadioSettingValueList(self.FMRADIO,
925
                                         self.FMRADIO[_mem.settings.fmradio])))
926
        if RT490_EXPERIMENTAL:
927
            tmp = _mem.settings.fmpreset / 10.0
928
            if tmp < 65.0 or tmp > 108.0:
929
                tmp = 80.0
930
            ret.append(RadioSetting("settings.fmpreset", "FM Radio Freq",
931
                       RadioSettingValueFloat(65, 108, tmp, resolution=0.1,
932
                                              precision=1)))
933
        ret.append(RadioSetting('settings.kblock', 'Enable Keyboard Lock',
934
                   RadioSettingValueBoolean(_mem.settings.kblock)))
935
        ret.append(RadioSetting('settings.autolock', 'Autolock Keyboard',
936
                   RadioSettingValueList(self.AUTOLOCK_TO,
937
                                         self.AUTOLOCK_TO[
938
                                             _mem.settings.autolock])))
939
        ret.append(RadioSetting('settings.timer_menu_quit', 'Menu Exit Time',
940
                   RadioSettingValueList(self.MENUEXIT_TO,
941
                                         self.MENUEXIT_TO[
942
                                             _mem.settings.timer_menu_quit])))
943
        ret.append(RadioSetting('settings.enable_gps', 'Enable GPS',
944
                   RadioSettingValueBoolean(_mem.settings.enable_gps)))
945
        ret.append(RadioSetting('settings.scan_dcs', 'CDCSS Save Modes',
946
                   RadioSettingValueList(self.SCANDCSMODES,
947
                                         self.SCANDCSMODES[
948
                                             _mem.settings.scan_dcs])))
949
        ret.append(RadioSetting('settings.tailnoiseclear', 'Tail Noise Clear',
950
                   RadioSettingValueBoolean(_mem.settings.tailnoiseclear)))
951
        ret.append(RadioSetting('settings.rptnoiseclear', 'Rpt Noise Clear',
952
                   RadioSettingValueList(self.RPTNOISE,
953
                                         self.RPTNOISE[
954
                                             _mem.settings.rptnoiseclear])))
955
        ret.append(RadioSetting('settings.rptnoisedelay', 'Rpt Noise Delay',
956
                   RadioSettingValueList(self.RPTNOISE,
957
                                         self.RPTNOISE[
958
                                             _mem.settings.rptnoisedelay])))
959
        ret.append(RadioSetting('settings.rpttone', 'Rpt Tone',
960
                   RadioSettingValueList(self.RPTTONES,
961
                                         self.RPTTONES[
962
                                             _mem.settings.rpttone])))
963
        return ret
964

    
965
    def _get_settings_basic(self):
966
        _mem = self._memobj
967
        ret = RadioSettingGroup('basic', 'Basic')
968
        ret.append(RadioSetting('settings.squelch', 'Carrier Squelch Level',
969
                   RadioSettingValueList(self.SQUELCHLVLS,
970
                                         self.SQUELCHLVLS[
971
                                             _mem.settings.squelch])))
972
        ret.append(RadioSetting('settings.savemode', 'Battery Savemode',
973
                   RadioSettingValueList(self.SAVEMODES,
974
                                         self.SAVEMODES[
975
                                             _mem.settings.savemode])))
976
        ret.append(RadioSetting('settings.backlight', 'Backlight Timeout',
977
                   RadioSettingValueList(self.BACKLIGHT_TO,
978
                                         self.BACKLIGHT_TO[
979
                                             _mem.settings.backlight])))
980
        ret.append(RadioSetting('settings.timeout', 'Timeout Timer (TOT)',
981
                   RadioSettingValueList(self.TOT_LIST,
982
                                         self.TOT_LIST[
983
                                             _mem.settings.timeout])))
984
        ret.append(RadioSetting('settings.beep', 'Beep',
985
                   RadioSettingValueBoolean(_mem.settings.beep)))
986
        ret.append(RadioSetting('settings.active_channel', 'Active Channel',
987
                   RadioSettingValueList(self.CHANNELS,
988
                                         self.CHANNELS[
989
                                             _mem.settings.active_channel])))
990
        ret.append(RadioSetting('settings.cha_disp', 'Channel A Display Mode',
991
                   RadioSettingValueList(self.DISPLAYMODES,
992
                                         self.DISPLAYMODES[
993
                                             _mem.settings.cha_disp])))
994
        ret.append(RadioSetting('settings.chb_disp', 'Channel B Display Mode',
995
                   RadioSettingValueList(self.DISPLAYMODES,
996
                                         self.DISPLAYMODES[
997
                                             _mem.settings.chb_disp])))
998
        ret.append(RadioSetting('settings.roger', 'Roger Beep',
999
                   RadioSettingValueBoolean(_mem.settings.roger)))
1000
        ret.append(RadioSetting('settings.powermsg', 'Power Message',
1001
                   RadioSettingValueList(self.POWERMESSAGES,
1002
                                         self.POWERMESSAGES[
1003
                                             _mem.settings.powermsg])))
1004
        ret.append(RadioSetting('settings.rx_time', 'Show RX Time',
1005
                   RadioSettingValueBoolean(_mem.settings.rx_time)))
1006
        return ret
1007

    
1008
    def _get_memcodes(self):
1009
        ret = RadioSettingGroup('mc', 'Memory Channel Privacy Codes')
1010
        msgs = ["Only hexadecimal chars accepted.",
1011
                "Allowed chars (%s)" % self.KEY_CHARS,
1012
                "Input from 0 to 6 characters. If code",
1013
                "length is less than 6 chars it will be",
1014
                "padded with leading zeros.",
1015
                "Ex: 1D32EB or 0F12 or AB521, etc...",
1016
                "Enable Code for the Location on the",
1017
                "'Other' tab in 'Memory Properties'."
1018
                ]
1019
        for msg in msgs:
1020
            rsvs = RadioSettingValueString(0, 255, msg, autopad=False)
1021
            rsvs.set_mutable(False)
1022
            rs = RadioSetting('dummy_memcodes_msg_%i' % msgs.index(msg),
1023
                              'Input rule %i' % int(msgs.index(msg)+1), rsvs)
1024
            ret.append(rs)
1025
        for i in range(self._memory_size):
1026
            code = ""
1027
            if self._memobj.memcode[i].code[3] < 0xFF:
1028
                code = self._decode_key(self._memobj.memcode[i].code)
1029
                code = code.zfill(6)
1030
            rsvs = RadioSettingValueString(0, 6, code, autopad=False,
1031
                                           charset=self.KEY_CHARS)
1032
            rs = RadioSetting('memcode@code@%i' % i,
1033
                              'Memory Location (%i) Privacy Code' %
1034
                              int(i+1), rsvs)
1035
            ret.append(rs)
1036
        return ret
1037

    
1038
    def get_settings(self):
1039
        radio_settings = []
1040
        basic = self._get_settings_basic()
1041
        radio_settings.append(basic)
1042
        adv = self._get_settings_adv()
1043
        radio_settings.append(adv)
1044
        vfoa = self._get_settings_vfo(self._memobj.settings_vfo.vfo_a, 'A')
1045
        radio_settings.append(vfoa)
1046
        vfob = self._get_settings_vfo(self._memobj.settings_vfo.vfo_b, 'B')
1047
        radio_settings.append(vfob)
1048
        sk = self._get_settings_sidekeys()
1049
        radio_settings.append(sk)
1050
        dtmf = self._get_settings_dtmf()
1051
        radio_settings.append(dtmf)
1052
        ccn = self._get_custom_channel_names()
1053
        radio_settings.append(ccn)
1054
        can = self._get_custom_ani_names()
1055
        radio_settings.append(can)
1056
        ani = self._get_anicodes()
1057
        radio_settings.append(ani)
1058
        mcodes = self._get_memcodes()
1059
        radio_settings.append(mcodes)
1060
        if RT490_EXPERIMENTAL:
1061
            ks = self._get_settings_ks()
1062
            radio_settings.append(ks)
1063
            bands = self._get_settings_bands()
1064
            radio_settings.append(bands)
1065
        top = RadioSettings(*radio_settings)
1066
        return top
1067

    
1068
    def get_raw_memory(self, number):
1069
        return repr(self._memobj.memory[number])
1070

    
1071
    # TODO Add Code when RadioSettingValueString is fixed
1072
    def _get_extra(self, _mem, num):
1073
        group = RadioSettingGroup('extra', 'Extra')
1074
        # LOG.debug("Get extra %i" % num)
1075

    
1076
        s = RadioSetting('bcl', 'Busy Channel Lockout',
1077
                         RadioSettingValueBoolean(_mem.bcl))
1078
        group.append(s)
1079

    
1080
        dcp = int(_mem.dcp)
1081
        if dcp > len(self.FHSS_LIST)-1:
1082
            _mem.dcp = cur = 0
1083
            LOG.debug('DCP ID / FHSS overflow for channel %d' % num)
1084
        cur = self.FHSS_LIST[dcp]
1085
        s = RadioSetting('dcp', 'FHSS (Encryption)',
1086
                         RadioSettingValueList(self.FHSS_LIST, cur))
1087
        group.append(s)
1088

    
1089
        # Does not work, no error, why ??? TODO
1090
        """
1091
        code = ""
1092
        if self._memobj.memcode[num-1].code[3] < 0xFF:
1093
            code = self._decode_key(self._memobj.memcode[num-1].code)
1094
            code = code.zfill(6)
1095
        LOG.debug('CODE "%s"' % code)
1096
        s = RadioSetting('dcp_code', 'DCP code',
1097
                         RadioSettingValueString(0, 6, code,
1098
                                                autopad=False,
1099
                                                charset=self.KEY_CHARS))
1100
        group.append(s) """
1101

    
1102
        pttid = int(_mem.pttid)
1103
        if pttid > len(self.PTTID)-1:
1104
            _mem.pttid = cur = 0
1105
            LOG.debug('PTTID overflow for channel %d' % num)
1106
        cur = self.PTTID[pttid]
1107
        s = RadioSetting('pttid', 'Send DTMF Code (PTT ID)',
1108
                         RadioSettingValueList(self.PTTID, cur))
1109
        group.append(s)
1110

    
1111
        cur = self.SIGNAL[int(_mem.signal)]
1112
        s = RadioSetting('signal', 'PTT ID Code (S-Code)',
1113
                         RadioSettingValueList(self.SIGNAL, cur))
1114
        group.append(s)
1115

    
1116
        s = RadioSetting('learn',
1117
                         'Use Memory Privacy Code as Tx/Rx DCS (Learn)',
1118
                         RadioSettingValueBoolean(_mem.learn))
1119
        group.append(s)
1120

    
1121
        return group
1122

    
1123
    # TODO Add Code when RadioSettingValueString is fixed
1124
    def _set_extra(self, _mem, mem):
1125
        # memidx = mem.number - 1  # commented because not used
1126
        _mem.bcl = int(mem.extra['bcl'].value)
1127
        _mem.dcp = int(mem.extra['dcp'].value)
1128
        _mem.pttid = int(mem.extra['pttid'].value)
1129
        _mem.signal = int(mem.extra['signal'].value)
1130
        # self._memobj.memcode[mem.number].code = \
1131
        #     self._encode_key(mem.extra['dcp_code'].value)
1132
        if (int(mem.extra['learn'].value) > 0) and \
1133
                (self._memobj.memcode[mem.number-1].code[3] == 0xA0):
1134
            _mem.learn = 1
1135
        elif (int(mem.extra['learn'].value) > 0) and \
1136
                (self._memobj.memcode[mem.number-1].code[3] != 0xA0):
1137
            _mem.learn = 0
1138
            raise InvalidValueError(dedent("""\
1139
            >>Use Memory Privacy Code as Tx/Rx DCS (Learn)<< requires
1140
            that a memory code has been previously set for this memory.
1141

    
1142
            Go in 'Settings' -> 'Memory Channel Privacy Codes' and set
1143
            a code for the current memory before enabling 'Learn'.
1144
            """))
1145
        else:
1146
            _mem.learn = 0
1147

    
1148
    def _is_txinh(self, _mem):
1149
        raw_tx = ""
1150
        for i in range(0, 4):
1151
            raw_tx += _mem.txfreq[i].get_raw()
1152
        return raw_tx == "\xFF\xFF\xFF\xFF"
1153

    
1154
    def get_memory(self, num):
1155
        memidx = num - 1
1156
        _mem = self._memobj.memory[memidx]
1157
        _nam = self._memobj.memname[memidx]
1158

    
1159
        mem = chirp_common.Memory()
1160
        mem.number = num
1161
        if int(_mem.rxfreq) == 166666665:
1162
            mem.empty = True
1163
            return mem
1164

    
1165
        mem.name = ''.join([str(c) for c in _nam.name
1166
                            if ord(str(c)) < 127]).rstrip()
1167
        mem.freq = int(_mem.rxfreq) * 10
1168
        offset = (int(_mem.txfreq) - int(_mem.rxfreq)) * 10
1169
        if self._is_txinh(_mem) or _mem.tx_enable == 0:
1170
            mem.duplex = 'off'
1171
            # _mem.txfreq = _mem.rxfreq # TODO REMOVE (force fix broken saves)
1172
        elif offset == 0:
1173
            mem.duplex = ''
1174
            mem.offset = 0
1175
        elif abs(offset) < 100000000:
1176
            mem.duplex = offset < 0 and '-' or '+'
1177
            mem.offset = abs(offset)
1178
        else:
1179
            mem.duplex = 'split'
1180
            mem.offset = int(_mem.txfreq) * 10
1181

    
1182
        mem.power = self.POWER_LEVELS[_mem.power]
1183

    
1184
        if _mem.unknown3_0 and _mem.narrow:
1185
            mem.mode = 'NAM'
1186
        elif _mem.unknown3_0 and not _mem.narrow:
1187
            mem.mode = 'AM'
1188
        elif not _mem.unknown3_0 and _mem.narrow:
1189
            mem.mode = 'NFM'
1190
        elif not _mem.unknown3_0 and not _mem.narrow:
1191
            mem.mode = 'FM'
1192
        else:
1193
            LOG.exception('Failed to get mode for %i' % num)
1194

    
1195
        mem.skip = '' if _mem.scan else 'S'
1196

    
1197
        # LOG.warning('got txtone: %s' % repr(self._decode_tone(_mem.txtone)))
1198
        # LOG.warning('got rxtone: %s' % repr(self._decode_tone(_mem.rxtone)))
1199
        txtone = self._decode_tone(_mem.txtone)
1200
        rxtone = self._decode_tone(_mem.rxtone)
1201
        chirp_common.split_tone_decode(mem, txtone, rxtone)
1202
        try:
1203
            mem.extra = self._get_extra(_mem, num)
1204
        except Exception:
1205
            LOG.exception('Failed to get extra for %i' % num)
1206
        return mem
1207

    
1208
    def set_memory(self, mem):
1209
        memidx = mem.number - 1
1210
        _mem = self._memobj.memory[memidx]
1211
        _nam = self._memobj.memname[memidx]
1212

    
1213
        if mem.empty:
1214
            _mem.set_raw(b'\xff' * 16)
1215
            _nam.set_raw(b'\xff' * 16)
1216
            return
1217

    
1218
        if int(_mem.rxfreq) == 166666665:
1219
            LOG.debug('Initializing new memory %i' % memidx)
1220
            _mem.set_raw(b'\x00' * 16)
1221

    
1222
        _nam.name = mem.name.ljust(12, chr(255))  # with xFF pad (mimic factory
1223
        #                                           behavior)
1224

    
1225
        _mem.rxfreq = mem.freq / 10
1226
        _mem.tx_enable = 1
1227
        if mem.duplex == '':
1228
            _mem.txfreq = mem.freq / 10
1229
        elif mem.duplex == 'split':
1230
            _mem.txfreq = mem.offset / 10
1231
        elif mem.duplex == 'off':
1232
            _mem.tx_enable = 0
1233
            _mem.txfreq = mem.freq / 10  # Optional but keeps compat with
1234
            #                              vendor software
1235
        elif mem.duplex == '-':
1236
            _mem.txfreq = (mem.freq - mem.offset) / 10
1237
        elif mem.duplex == '+':
1238
            _mem.txfreq = (mem.freq + mem.offset) / 10
1239
        else:
1240
            raise errors.RadioError('Unsupported duplex mode %r' % mem.duplex)
1241

    
1242
        txtone, rxtone = chirp_common.split_tone_encode(mem)
1243
        # LOG.warning('tx tone is %s' % repr(txtone))
1244
        # LOG.warning('rx tone is %s' % repr(rxtone))
1245
        _mem.txtone = self._encode_tone(*txtone)
1246
        _mem.rxtone = self._encode_tone(*rxtone)
1247

    
1248
        try:
1249
            _mem.power = self.POWER_LEVELS.index(mem.power)
1250
        except ValueError:
1251
            _mem.power = 0
1252

    
1253
        if int(_mem.rxfreq) < 30000000:
1254
            _mem.unknown3_0 = mem.mode in ['AM', 'NAM']
1255
        else:
1256
            _mem.unknown3_0 = 0
1257
        _mem.narrow = mem.mode[0] == 'N'
1258

    
1259
        _mem.scan = mem.skip != 'S'
1260

    
1261
        if mem.extra:
1262
            self._set_extra(_mem, mem)
1263

    
1264
    def sync_out(self):
1265
        try:
1266
            do_upload(self)
1267
        except errors.RadioError:
1268
            # Pass through any real errors we raise
1269
            raise
1270
        except Exception:
1271
            # If anything unexpected happens, make sure we raise
1272
            # a RadioError and log the problem
1273
            LOG.exception('Unexpected error during upload')
1274
            raise errors.RadioError('Unexpected error communicating '
1275
                                    'with the radio')
1276

    
1277
    def sync_in(self):
1278
        """Download from radio"""
1279
        try:
1280
            data = do_download(self)
1281
        except errors.RadioError:
1282
            # Pass through any real errors we raise
1283
            raise
1284
        except Exception:
1285
            # If anything unexpected happens, make sure we raise
1286
            # a RadioError and log the problem
1287
            LOG.exception('Unexpected error during download')
1288
            raise errors.RadioError('Unexpected error communicating '
1289
                                    'with the radio')
1290
        self._mmap = data
1291
        self.process_mmap()
1292

    
1293
    def process_mmap(self):
1294
        self._memobj = bitwise.parse(MEM_FORMAT_RT490 %
1295
                                     {"memsize": self._memory_size},
1296
                                     self._mmap)
1297

    
1298
    def get_features(self):  # GOOD ?
1299
        rf = chirp_common.RadioFeatures()
1300
        rf.has_settings = True
1301
        rf.has_bank = False
1302
        rf.has_cross = True
1303
        rf.has_rx_dtcs = True
1304
        rf.has_dtcs_polarity = True
1305
        rf.has_tuning_step = False
1306
        rf.can_odd_split = True
1307
        rf.has_name = True
1308
        rf.valid_name_length = 12
1309
        rf.valid_skips = ["", "S"]
1310
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
1311
        rf.valid_cross_modes = ["Tone->Tone", "DTCS->", "->DTCS", "Tone->DTCS",
1312
                                "DTCS->Tone", "->Tone", "DTCS->DTCS"]
1313
        rf.valid_power_levels = self.POWER_LEVELS
1314
        rf.valid_duplexes = ["", "+", "-", "split", "off"]
1315
        rf.valid_modes = ["FM", "NFM", "AM", "NAM"]
1316
        rf.memory_bounds = (1, 256)
1317
        rf.valid_tuning_steps = self.TUNING_STEPS
1318
        rf.valid_bands = [(108000000, 136000000),
1319
                          (136000000, 180000000),
1320
                          (200000000, 260000000),
1321
                          (350000000, 400000000),
1322
                          (400000000, 520000000)]
1323
        return rf
1324

    
1325
    @classmethod
1326
    def get_prompts(cls):
1327
        rp = chirp_common.RadioPrompts()
1328
        rp.pre_upload = _(dedent("""\
1329
            This driver is in development and should be considered
1330
            as experimental.
1331
            """))
1332
        rp.experimental = _(dedent("""\
1333
            This driver is in development and should be considered
1334
            as experimental.
1335
            """))
1336
        rp.info = _(dedent("""\
1337
            This driver is in development and should be considered
1338
            as experimental.
1339
            """))
1340
        return rp
1341

    
1342
    def _encode_key(self, key):
1343
        arr = bytearray(4)
1344
        arr[3] = 160
1345
        arr[2] = self.KEY_CHARS.index(key[0])  # << 4
1346
        arr[2] = arr[2] << 4
1347
        arr[2] |= self.KEY_CHARS.index(key[1])
1348
        arr[1] = self.KEY_CHARS.index(key[2])  # << 4
1349
        arr[1] = arr[1] << 4
1350
        arr[1] |= self.KEY_CHARS.index(key[3])
1351
        arr[0] = self.KEY_CHARS.index(key[4])  # << 4
1352
        arr[0] = arr[0] << 4
1353
        arr[0] |= self.KEY_CHARS.index(key[5])
1354
        return arr
1355

    
1356
    def _decode_key(self, key):
1357
        ret = ""
1358
        if key[3] == 0xA0:
1359
            ret += self.KEY_CHARS[key[2] >> 4]
1360
            ret += self.KEY_CHARS[key[2] & 0xF]
1361
            ret += self.KEY_CHARS[key[1] >> 4]
1362
            ret += self.KEY_CHARS[key[1] & 0xF]
1363
            ret += self.KEY_CHARS[key[0] >> 4]
1364
            ret += self.KEY_CHARS[key[0] & 0xF]
1365
            LOG.debug('DCP Code: "%s"' % ret)
1366
        return ret
1367

    
1368
    def _decode_tone(self, toneval):
1369
        if toneval in (0, 0xFFFF):
1370
            # LOG.debug('no tone value: %s' % toneval)
1371
            return None, None, None
1372
        elif toneval < 670:
1373
            toneval = toneval - 1
1374
            index = toneval % len(DTCS_CODES)
1375
            if index != int(toneval):
1376
                pol = 'R'
1377
                # index -= 1
1378
            else:
1379
                pol = 'N'
1380
            return 'DTCS', DTCS_CODES[index], pol
1381
        else:
1382
            return 'Tone', toneval / 10.0, 'N'
1383

    
1384
    def _encode_tone(self, mode, val, pol):
1385
        if not mode:
1386
            return 0x0000
1387
        elif mode == 'Tone':
1388
            return int(val * 10)
1389
        elif mode == 'DTCS':
1390
            index = DTCS_CODES.index(val)
1391
            if pol == 'R':
1392
                index += len(DTCS_CODES)
1393
            index += 1
1394
            # LOG.debug('Encoded dtcs %s/%s to %04x' % (val, pol, index))
1395
            return index
1396
        else:
1397
            raise errors.RadioError('Unsupported tone mode %r' % mode)
1398

    
1399
    @classmethod
1400
    def match_model(cls, filedata, filename):
1401
        # This radio has always been post-metadata, so never do
1402
        # old-school detection
1403
        return False
1404

    
1405

    
1406
class MML8629Alias(chirp_common.Alias):
1407
    VENDOR = "MMLradio"
1408
    MODEL = "JC-8629"
1409

    
1410

    
1411
class JJCC8629Alias(chirp_common.Alias):
1412
    VENDOR = "JJCC"
1413
    MODEL = "JC-8629"
1414

    
1415

    
1416
class SocotranJC8629Alias(chirp_common.Alias):
1417
    VENDOR = "Socotran"
1418
    MODEL = "JC-8629"
1419

    
1420

    
1421
class SocotranFB8629Alias(chirp_common.Alias):
1422
    VENDOR = "Socotran"
1423
    MODEL = "FB-8629"
1424

    
1425

    
1426
class Jianpai8629Alias(chirp_common.Alias):
1427
    VENDOR = "Jianpai"
1428
    MODEL = "8800 Plus"
1429

    
1430

    
1431
class Boristone8RSAlias(chirp_common.Alias):
1432
    VENDOR = "Boristone"
1433
    MODEL = "8RS"
1434

    
1435

    
1436
class AbbreeAR869Alias(chirp_common.Alias):
1437
    VENDOR = "Abbree"
1438
    MODEL = "AR-869"
1439

    
1440

    
1441
class HamGeekHG590Alias(chirp_common.Alias):
1442
    VENDOR = "HamGeek"
1443
    MODEL = "HG-590"
1444

    
1445

    
1446
@directory.register
1447
class RT490RadioGeneric(RT490Radio):
1448
    ALIASES = [MML8629Alias, JJCC8629Alias, SocotranJC8629Alias,
1449
               SocotranFB8629Alias, Jianpai8629Alias, Boristone8RSAlias,
1450
               AbbreeAR869Alias, HamGeekHG590Alias]
1451

    
1452

    
1453
IMHEX_DESC = """
1454
// Perfect tool for binary reverse engineering
1455
// https://github.com/WerWolv/ImHex
1456
// Below is the pattern
1457
"""
1458
IMHEX_PATTERN = """
1459
struct memory {                    // Memory settings
1460
  u8 rxfreq[4];
1461
  u8 txfreq[4];
1462
  u16 rxtone;
1463
  u16 txtone;
1464
  u8 signal;            // int 0->14, Signal 1->15
1465
  u8 pttid;             // ['OFF', 'BOT', 'EOT', 'Both']
1466
  u8 dcp_power;           // POWER_LEVELS
1467
  u8 unknown3_0_narrow_unknown3_1_bcl_scan_tx_enable_learn;    // bool ??? TODO
1468
};
1469
struct memname {
1470
  char name[12];
1471
  padding[4];
1472
};
1473
struct memcode {
1474
  u8 code[4];
1475
};
1476
struct custom_ani_names {
1477
  char name[12];
1478
  padding[4];
1479
};
1480
struct anicodes {
1481
  u8 anicode[6];
1482
  padding[10];
1483
};
1484
struct custom_channel_names {
1485
  char name[12];
1486
  padding[4];
1487
};
1488
bitfield workmode {
1489
  b : 4;
1490
  a : 4;
1491
};
1492
struct settings {
1493
  u8 squelch;           // 0: int 0 -> 9
1494
  u8 savemode;          // 1: ['OFF', 'Normal', 'Super', 'Deep']
1495
  u8 vox;               // 2: off=0, 1 -> 9
1496
  u8 backlight;         // 3: ['OFF', '5s', '15s', '20s', '30s', '1m', '2m',
1497
                        //      '3m']
1498
  u8 tdr;               // 4: bool
1499
  u8 timeout;           // 5: n*30seconds, 0-240s
1500
  u8 beep;              // 6: bool
1501
  u8 voice;             // 7: bool
1502
  u8 byte_not_used_10;  // 8: Allways 1
1503
  u8 dtmfst;            // 9: ['OFF', 'KB Side Tone', 'ANI Side Tone',
1504
                        //      'KB ST + ANI ST']
1505
  u8 scanmode;          // 10: ['TO', 'CO', 'SE']
1506
  u8 pttid;             // 11: ['OFF', 'BOT', 'EOT', 'Both']
1507
  u8 pttiddelay;        // 12: ['0', '100ms', '200ms', '400ms', '600ms',
1508
                        //      '800ms', '1000ms']
1509
  u8 cha_disp;          // 13: ['Name', 'Freq', 'Channel ID']
1510
  u8 chb_disp;          // 14: ['Name', 'Freq', 'Channel ID']
1511
  u8 bcl;               // 15: bool
1512
  u8 autolock;          // 0: ['OFF', '5s', '10s', 15s']
1513
  u8 alarm_mode;        // 1: ['Site', 'Tone', 'Code']
1514
  u8 alarmsound;        // 2: bool
1515
  u8 txundertdr;        // 3: ['OFF', 'A', 'B']
1516
  u8 tailnoiseclear;    // 4: [off, on]
1517
  u8 rptnoiseclear;     // 5: n*100ms, 0-1000
1518
  u8 rptnoisedelay;     // 6: n*100ms, 0-1000
1519
  u8 roger;             // 7: bool
1520
  u8 active_channel;    // 8: 0 or 1
1521
  u8 fmradio;           // 9: boolean, inverted
1522
  workmode _workmode;   // 10: up    ['VFO', 'CH Mode']
1523
  u8 kblock;            // 11: bool                  // TODO TEST WITH autolock
1524
  u8 powermsg;          // 12: 0=Image / 1=Voltage
1525
  u8 byte_not_used_21;  // 13: Allways 0
1526
  u8 rpttone;           // 14: ['1000Hz', '1450Hz', '1750Hz', '2100Hz']
1527
  u8 byte_not_used_22;  // 15: pad with xFF
1528
  u8 vox_delay;         // 0: [str(float(a)/10)+'s' for a in range(5,21)]
1529
                        //      '0.5s' to '2.0s'
1530
  u8 timer_menu_quit;   // 1: ['5s', '10s', '15s', '20s', '25s', '30s',
1531
                        //      '35s', '40s', '45s', '50s', '60s']
1532
  u8 byte_not_used_30;  // 2: pad with xFF
1533
  u8 byte_not_used_31;  // 3: pad with xFF
1534
  u8 enable_killsw;     // 4: bool
1535
  u8 display_ani;       // 5: bool
1536
  u8 byte_not_used_32;  // 6: pad with xFF
1537
  u8 enable_gps;        // 7: bool
1538
  u8 scan_dcs;          // 8: ['All', 'Receive', 'Transmit']
1539
  u8 ani_id;            // 9: int 0-59 (ANI 1-60)
1540
  u8 rx_time;           // 10: bool
1541
  padding[5];          // 11: Pad xFF
1542
  u8 cha_memidx;        // 0: Memory index when channel A use memories
1543
  u8 byte_not_used_40;
1544
  u8 chb_memidx;        // 2: Memory index when channel B use memories
1545
  u8 byte_not_used_41;
1546
  padding[10];
1547
  u16 fmpreset;
1548
};
1549
struct settings_vfo_chan {
1550
  u8   rxfreq[8];       // 0
1551
  u16 rxtone;          // 8
1552
  u16 txtone;          // 10
1553
  u16 byte_not_used0;  // 12 Pad xFF
1554
  u8   sftd_signal;        // 14 int 0->14, Signal 1->15
1555
  u8   byte_not_used1;  // 15 Pad xFF
1556
  u8   power;           // 16:0 POWER_LEVELS
1557
  u8   fhss_narrow;        // 17 bool true=NFM false=FM
1558
  u8   byte_not_used2;  // 18 Pad xFF but received 0x00 ???
1559
  u8   freqstep;        // 19:3 ['2.5 KHz', '5.0 KHz', '6.25 KHz',
1560
                        //       '10.0 KHz', '12.5 KHz', '20.0 KHz',
1561
                        //       '25.0 KHz', '50.0 KHz']
1562
  u8   byte_not_used3;  // 20:4 Pad xFF but received 0x00 ??? TODO
1563
  u8   offset[6];       // 21:5 Freq NN.NNNN (without the dot) TEST TEST
1564
  u8   byte_not_used4;  // 27:11   Pad xFF
1565
  u8   byte_not_used5;  // 28      Pad xFF
1566
  u8   byte_not_used6;  // 29      Pad xFF
1567
  u8   byte_not_used7;  // 30      Pad xFF
1568
  u8   byte_not_used8;  // 31:15   Pad xFF
1569
};
1570
struct settings_vfo {
1571
  settings_vfo_chan vfo_a;
1572
  settings_vfo_chan vfo_b;
1573
};
1574
struct settings_sidekeys {                    // Values from Radio
1575
  u8 pf2_short;         // { '7': 'FM', '10': 'Tx Power', '28': 'Scan',
1576
                        //   '29': 'Search, '1': 'PPT B' }
1577
  u8 pf2_long;          // { '7': 'FM', '10': 'Tx Power', '28': 'Scan',
1578
                        //  '29': 'Search' }
1579
  u8 pf3_short;         // { '7': 'FM', '10': 'Tx Power', '28': 'Scan',
1580
                        //  '29': 'Search' }
1581
  u8 ffpad;             // Pad xFF
1582
};
1583
struct dtmfcode {
1584
  u8 code[5];           // 5 digits DTMF
1585
  padding[11];         // Pad xFF
1586
};
1587
struct settings_dtmf {                // @15296+3x16
1588
  u8 byte_not_used1;    // 0: Pad xFF
1589
  u8 byte_not_used2;    // 1: Pad xFF
1590
  u8 byte_not_used3;    // 2: Pad xFF
1591
  u8 byte_not_used4;    // 3: Pad xFF
1592
  u8 byte_not_used5;    // 4: Pad xFF
1593
  u8 unknown_dtmf;      // 5: 0 TODO ???? wtf is alarmcode/alarmcall TODO
1594
  u8 pttid;             // 6: [off, BOT, EOT, Both]
1595
  u8 dtmf_speed_on;     // 7: ['50ms', '100ms', '200ms', '300ms', '500ms']
1596
  u8 dtmf_speed_off;    // 8:0 ['50ms', '100ms', '200ms', '300ms', '500ms']
1597
};
1598
struct settings_dtmf_global {
1599
  dtmfcode settings_dtmfgroup[15];
1600
  settings_dtmf _settings_dtmf;
1601
};
1602
struct settings_killswitch {
1603
  u8 kill_dtmf[6];      // 0: Kill DTMF
1604
  padding[2];         // Pad xFF
1605
  u8 revive_dtmf[6];    // 8: Revive DTMF
1606
  padding[2];         // Pad xFF
1607
};
1608
struct management_settings {
1609
  u8 unknown_data_0[16];
1610
  u8 unknown_data_1;
1611
  u8 active;            // Bool radio killed (killed=0, active=1)
1612
  padding[46];
1613
};
1614
struct band {
1615
  u8 enable;            // 0 bool / enable-disable Tx on band
1616
  u8 freq_low[2];       // 1 lowest band frequency
1617
  u8 freq_high[2];      // 3 highest band frequency
1618
};
1619
struct settings_bands {
1620
  band band136;  // 0  Settings for 136MHz band
1621
  band band400;  // 5  Settings for 400MHz band
1622
  band band200;  // 10 Settings for 200MHz band
1623
  padding[1];    // 15
1624
  band band350;  // 0  Settings for 350MHz band
1625
  padding[43];// 5
1626
};
1627

    
1628
memory mem[256] @ 0x0000;
1629
memname mname[256] @ 0x1000;
1630
memcode mcode[256] @ 0x2000;
1631
custom_ani_names caninames[10] @ 0x3400;
1632
anicodes anic[60] @ 0x3500;
1633
custom_channel_names ccnames[10] @ 0x3900;
1634
settings _settings @ 0x3A00;
1635
settings_vfo _settings_vfo @ 0x3A40;
1636
settings_sidekeys _settings_sidekeys @ 0x3A80;
1637
settings_dtmf_global _settings_dtmf_global @ 0x3B00;
1638
settings_killswitch _settings_killswitch @ 0x3C00;
1639
management_settings _management_settings @ 0x3F80;
1640
settings_bands _settings_bands @ 0x3FC0;
1641
"""
(26-26/33)