Project

General

Profile

New Model #9665 » radtel_rt490_passes_automated_tests_updated_licence.py

. @angelof9:matrix.org, 07/07/2023 04:01 PM

 
1
# Copyright 2023 angeof9 angelof9@protonmail.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 3 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
# This file is dual-licensed by the copyright holder, under the GPLv3
17
# for inclusion in CHIRP here.
18
# The code is also available under the BSD 2-Clause "Simplified" License
19
# here:
20
# https://chirp.danplanet.com/attachments/7833
21

    
22
#  Python3 byte clean by Darryl Pogue
23
#  Pep8 style copliant by Jim Unroe <rock.unroe@gmail.com>
24

    
25
import logging
26
import struct
27

    
28
from chirp import (
29
    bitwise,
30
    chirp_common,
31
    directory,
32
    errors,
33
    memmap,
34
    util,
35
)
36

    
37
from chirp.settings import (
38
    RadioSetting,
39
    RadioSettingGroup,
40
    RadioSettings,
41
    RadioSettingValueBoolean,
42
    RadioSettingValueInteger,
43
    RadioSettingValueList,
44
    RadioSettingValueString,
45
    RadioSettingValueMap,
46
    RadioSettingValueFloat,
47
    InvalidValueError
48
)
49

    
50
from textwrap import dedent
51

    
52
LOG = logging.getLogger(__name__)
53

    
54
# 'True' or 'False'
55
# True unlocks:
56
# - FM preset
57
# - Channel memory  # preset
58
# - Killswitch (with revive killed radios)
59
# - Bands settings
60
# - Disable radio identification string verification
61
#   (good to recover from a bad state)
62
RT490_EXPERIMENTAL = True
63

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

    
267
CMD_ACK = b"\x06"
268

    
269
DTCS_CODES = tuple(sorted(chirp_common.DTCS_CODES + (645,)))
270

    
271
DTMFCHARS = '0123456789ABCD*#'
272

    
273

    
274
def _enter_programming_mode(radio):
275
    serial = radio.pipe
276

    
277
    exito = False
278
    for i in range(0, 5):
279
        serial.write(radio._magic)
280
        ack = serial.read(1)
281

    
282
        try:
283
            if ack == CMD_ACK:
284
                exito = True
285
                break
286
        except Exception:
287
            LOG.debug("Attempt #%s, failed, trying again" % i)
288
            pass
289

    
290
    # check if we had EXITO
291
    if exito is False:
292
        msg = "The radio did not accept program mode after five tries.\n"
293
        msg += "Check you interface cable and power cycle your radio."
294
        raise errors.RadioError(msg)
295

    
296
    try:
297
        serial.write(b"F")
298
        ident = serial.read(8)
299
    except Exception:
300
        raise errors.RadioError("Error communicating with radio")
301

    
302
    if not ident.startswith(radio._fingerprint) and not RT490_EXPERIMENTAL:
303
        LOG.debug(util.hexprint(ident))
304
        raise errors.RadioError("Radio returned unknown identification string")
305

    
306

    
307
def _exit_programming_mode(radio):
308
    serial = radio.pipe
309
    try:
310
        serial.write(b"E")
311
    except Exception:
312
        raise errors.RadioError("Radio refused to exit programming mode")
313

    
314

    
315
def _read_block(radio, block_addr, block_size):
316
    serial = radio.pipe
317

    
318
    cmd = struct.pack(">cHb", b'R', block_addr, block_size)
319
    expectedresponse = b"R" + cmd[1:]
320
    LOG.debug("Reading block %04x..." % (block_addr))
321

    
322
    try:
323
        serial.write(cmd)
324
        response = serial.read(4 + block_size)
325
        if response[:4] != expectedresponse:
326
            raise Exception("Error reading block %04x." % (block_addr))
327

    
328
        block_data = response[4:]
329
    except Exception:
330
        raise errors.RadioError("Failed to read block at %04x" % block_addr)
331

    
332
    return block_data
333

    
334

    
335
def _write_block(radio, block_addr, block_size):
336
    serial = radio.pipe
337

    
338
    cmd = struct.pack(">cHb", b'W', block_addr, block_size)
339
    data = radio.get_mmap()[block_addr:block_addr + block_size]
340

    
341
    LOG.debug("Writing Data:")
342
    LOG.debug(util.hexprint(cmd + data))
343

    
344
    try:
345
        serial.write(cmd + data)
346
        if serial.read(1) != CMD_ACK:
347
            raise Exception("No ACK")
348
    except Exception:
349
        raise errors.RadioError("Failed to send block "
350
                                "to radio at %04x" % block_addr)
351

    
352

    
353
def do_download(radio):
354
    LOG.debug("download")
355
    _enter_programming_mode(radio)
356

    
357
    data = b""
358

    
359
    status = chirp_common.Status()
360
    status.msg = "Cloning from radio"
361

    
362
    status.cur = 0
363
    status.max = radio._memsize
364

    
365
    for addr in range(0, radio._memsize, radio.BLOCK_SIZE):
366
        status.cur = addr + radio.BLOCK_SIZE
367
        radio.status_fn(status)
368

    
369
        block = _read_block(radio, addr, radio.BLOCK_SIZE)
370
        data += block
371

    
372
        LOG.debug("Address: %04x" % addr)
373
        LOG.debug(util.hexprint(block))
374

    
375
    _exit_programming_mode(radio)
376

    
377
    return memmap.MemoryMapBytes(data)
378

    
379

    
380
def do_upload(radio):
381
    status = chirp_common.Status()
382
    status.msg = "Uploading to radio"
383

    
384
    _enter_programming_mode(radio)
385

    
386
    status.cur = 0
387
    status.max = radio._memsize
388

    
389
    for start_addr, end_addr in radio._ranges:
390
        for addr in range(start_addr, end_addr, radio.BLOCK_SIZE):
391
            status.cur = addr + radio.BLOCK_SIZE
392
            radio.status_fn(status)
393
            _write_block(radio, addr, radio.BLOCK_SIZE)
394

    
395
    _exit_programming_mode(radio)
396

    
397

    
398
class RT490Radio(chirp_common.CloneModeRadio):
399
    """RADTEL RT-490"""
400
    VENDOR = "Radtel"
401
    MODEL = "RT-490"
402
    BLOCK_SIZE = 0x40  # 64 bytes
403
    BAUD_RATE = 9600
404
    NEEDS_COMPAT_SERIAL = False
405

    
406
    POWER_LEVELS = [chirp_common.PowerLevel("H", watts=5.00),
407
                    chirp_common.PowerLevel("L", watts=3.00)]
408

    
409
    # magic = progmode + modelType + garbage (works with any last char)
410
    _magic = b"PROGROMJJCCU"
411

    
412
    # fingerprint is default band ranges of the radio
413
    # the driver can change band ranges and fingerprint will
414
    # change accordingly, so it is not used to verify radio id.
415
    _fingerprint = b"\x01\x36\x01\x80\x04\x00\x05\x20"
416

    
417
    # Ranges of memory used when uploading data to radio
418
    # same as official software
419
    _ranges = [
420
               (0x0000, 0x2400),
421
               (0x3400, 0x3C40),
422
               (0x3FC0, 0x4000)
423
              ]
424

    
425
    if RT490_EXPERIMENTAL:
426
        # Experimental driver (already heavily tested)
427
        _ranges = [(0x0000, 0x2400), (0x3400, 0x3C40), (0x3F80, 0x4000)]
428

    
429
    # Danger zone
430
    # _ranges = [(0x0000, 0x2500), (0x3400, 0x3C40), (0x3E00, 0x4000)]
431

    
432
    # 16KB of memory, download read everything
433
    # same as official software (remark: loops if overread :))
434
    _memsize = 16384
435

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

    
480
    def set_settings(self, settings):
481
        for element in settings:
482
            if not isinstance(element, RadioSetting):
483
                self.set_settings(element)
484
                continue
485
            else:
486
                self._set_setting(element)
487

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

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

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

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

    
710
    def _get_settings_sidekeys(self):
711
        _mem = self._memobj
712
        ret = RadioSettingGroup('sidekeys', 'Side Keys')
713
        rsvm = RadioSettingValueMap(self.SIDEKEY_VALUEMAP,
714
                                    _mem.settings_sidekeys.pf2_short)
715
        ret.append(RadioSetting('settings_sidekeys.pf2_short',
716
                                'Side key 1 (PTT2) Short press', rsvm))
717
        rsvm = RadioSettingValueMap(self.SIDEKEY_VALUEMAP[:-1],
718
                                    _mem.settings_sidekeys.pf2_long)
719
        ret.append(RadioSetting('settings_sidekeys.pf2_long',
720
                                'Side key 1 (PTT2) Long press', rsvm))
721
        rsvm = RadioSettingValueMap(self.SIDEKEY_VALUEMAP[:-1],
722
                                    _mem.settings_sidekeys.pf3_short)
723
        ret.append(RadioSetting('settings_sidekeys.pf3_short',
724
                                'Side key 2 (PTT3) Short press', rsvm))
725
        rs = RadioSettingValueString(0, 255, "MONI")
726
        rs.set_mutable(False)
727
        ret.append(RadioSetting('dummy', 'Side key 2 (PTT3) Long press', rs))
728
        return ret
729

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

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

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

    
835
    def _get_anicodes(self):
836
        _mem = self._memobj
837
        ret = RadioSettingGroup('ani', 'ANI Codes')
838
        split = len(_mem.anicodes) - len(_mem.custom_ani_names)
839
        msgs = ["Allowed chars (%s)" % DTMFCHARS,
840
                "Input from 0 to 6 characters."
841
                ]
842
        for msg in msgs:
843
            rsvs = RadioSettingValueString(0, 255, msg, autopad=False)
844
            rsvs.set_mutable(False)
845
            rs = RadioSetting('dummy_canic_msg_%i' % msgs.index(msg),
846
                              'Input rule %i' % int(msgs.index(msg)+1), rsvs)
847
            ret.append(rs)
848

    
849
        for i in range(0, split):
850
            tmp = ''.join([DTMFCHARS[int(j)] for j in
851
                           _mem.anicodes[i].anicode if int(j) < 0xFF])
852
            # LOG.debug("ANI Code (%i) '%s'" % (i, tmp))
853
            rsvs = RadioSettingValueString(0, 6, tmp, autopad=False,
854
                                           charset=DTMFCHARS)
855
            ret.append(RadioSetting('anicodes@anicode@%i' % i,
856
                                    'ANI-ID (%i) Code' % (i+1), rsvs))
857
        for i in range(split, len(_mem.anicodes)):
858
            tmp = ''.join([DTMFCHARS[int(j)] for j in
859
                          _mem.anicodes[i].anicode if int(j) < 0xFF])
860
            tmp2 = ''.join([str(j) for j in
861
                            _mem.custom_ani_names[i-split].name
862
                            if ord(str(j)) < 0xFF and ord(str(j)) > 0x00])
863
            # LOG.debug("ANI Code (%s) (%i) '%s'" % (tmp2, i, tmp))
864
            rsvs = RadioSettingValueString(0, 6, tmp, autopad=False,
865
                                           charset=DTMFCHARS)
866
            ret.append(RadioSetting('anicodes@anicode@%i' % i,
867
                       'ANI-ID (%s) (%i) Code' % (tmp2, i+1), rsvs))
868
        return ret
869

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

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

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

    
1029
    def get_settings(self):
1030
        radio_settings = []
1031
        basic = self._get_settings_basic()
1032
        radio_settings.append(basic)
1033
        adv = self._get_settings_adv()
1034
        radio_settings.append(adv)
1035
        vfoa = self._get_settings_vfo(self._memobj.settings_vfo.vfo_a, 'A')
1036
        radio_settings.append(vfoa)
1037
        vfob = self._get_settings_vfo(self._memobj.settings_vfo.vfo_b, 'B')
1038
        radio_settings.append(vfob)
1039
        sk = self._get_settings_sidekeys()
1040
        radio_settings.append(sk)
1041
        dtmf = self._get_settings_dtmf()
1042
        radio_settings.append(dtmf)
1043
        ccn = self._get_custom_channel_names()
1044
        radio_settings.append(ccn)
1045
        can = self._get_custom_ani_names()
1046
        radio_settings.append(can)
1047
        ani = self._get_anicodes()
1048
        radio_settings.append(ani)
1049
        mcodes = self._get_memcodes()
1050
        radio_settings.append(mcodes)
1051
        if RT490_EXPERIMENTAL:
1052
            ks = self._get_settings_ks()
1053
            radio_settings.append(ks)
1054
            bands = self._get_settings_bands()
1055
            radio_settings.append(bands)
1056
        top = RadioSettings(*radio_settings)
1057
        return top
1058

    
1059
    def get_raw_memory(self, number):
1060
        return repr(self._memobj.memory[number])
1061

    
1062
    # TODO Add Code when RadioSettingValueString is fixed
1063
    def _get_extra(self, _mem, num):
1064
        group = RadioSettingGroup('extra', 'Extra')
1065
        # LOG.debug("Get extra %i" % num)
1066

    
1067
        s = RadioSetting('bcl', 'Busy Channel Lockout',
1068
                         RadioSettingValueBoolean(_mem.bcl))
1069
        group.append(s)
1070

    
1071
        dcp = int(_mem.dcp)
1072
        if dcp > len(self.FHSS_LIST)-1:
1073
            _mem.dcp = cur = 0
1074
            LOG.debug('DCP ID / FHSS overflow for channel %d' % num)
1075
        cur = self.FHSS_LIST[dcp]
1076
        s = RadioSetting('dcp', 'FHSS (Encryption)',
1077
                         RadioSettingValueList(self.FHSS_LIST, cur))
1078
        group.append(s)
1079

    
1080
        # Does not work, no error, why ??? TODO
1081
        """
1082
        code = ""
1083
        if self._memobj.memcode[num-1].code[3] < 0xFF:
1084
            code = self._decode_key(self._memobj.memcode[num-1].code)
1085
            code = code.zfill(6)
1086
        LOG.debug('CODE "%s"' % code)
1087
        s = RadioSetting('dcp_code', 'DCP code',
1088
                         RadioSettingValueString(0, 6, code,
1089
                                                autopad=False,
1090
                                                charset=self.KEY_CHARS))
1091
        group.append(s) """
1092

    
1093
        pttid = int(_mem.pttid)
1094
        if pttid > len(self.PTTID)-1:
1095
            _mem.pttid = cur = 0
1096
            LOG.debug('PTTID overflow for channel %d' % num)
1097
        cur = self.PTTID[pttid]
1098
        s = RadioSetting('pttid', 'Send DTMF Code (PTT ID)',
1099
                         RadioSettingValueList(self.PTTID, cur))
1100
        group.append(s)
1101

    
1102
        cur = self.SIGNAL[int(_mem.signal)]
1103
        s = RadioSetting('signal', 'PTT ID Code (S-Code)',
1104
                         RadioSettingValueList(self.SIGNAL, cur))
1105
        group.append(s)
1106

    
1107
        s = RadioSetting('learn',
1108
                         'Use Memory Privacy Code as Tx/Rx DCS (Learn)',
1109
                         RadioSettingValueBoolean(_mem.learn))
1110
        group.append(s)
1111

    
1112
        return group
1113

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

    
1133
            Go in 'Settings' -> 'Memory Channel Privacy Codes' and set
1134
            a code for the current memory before enabling 'Learn'.
1135
            """))
1136
        else:
1137
            _mem.learn = 0
1138

    
1139
    def _is_txinh(self, _mem):
1140
        raw_tx = ""
1141
        for i in range(0, 4):
1142
            raw_tx += _mem.txfreq[i].get_raw()
1143
        return raw_tx == "\xFF\xFF\xFF\xFF"
1144

    
1145
    def get_memory(self, num):
1146
        memidx = num - 1
1147
        _mem = self._memobj.memory[memidx]
1148
        _nam = self._memobj.memname[memidx]
1149

    
1150
        mem = chirp_common.Memory()
1151
        mem.number = num
1152
        if int(_mem.rxfreq) == 166666665:
1153
            mem.empty = True
1154
            return mem
1155

    
1156
        mem.name = ''.join([str(c) for c in _nam.name
1157
                            if ord(str(c)) < 127]).rstrip()
1158
        mem.freq = int(_mem.rxfreq) * 10
1159
        offset = (int(_mem.txfreq) - int(_mem.rxfreq)) * 10
1160
        if self._is_txinh(_mem) or _mem.tx_enable == 0:
1161
            mem.duplex = 'off'
1162
            # _mem.txfreq = _mem.rxfreq # TODO REMOVE (force fix broken saves)
1163
        elif offset == 0:
1164
            mem.duplex = ''
1165
            mem.offset = 0
1166
        elif abs(offset) < 100000000:
1167
            mem.duplex = offset < 0 and '-' or '+'
1168
            mem.offset = abs(offset)
1169
        else:
1170
            mem.duplex = 'split'
1171
            mem.offset = int(_mem.txfreq) * 10
1172

    
1173
        mem.power = self.POWER_LEVELS[_mem.power]
1174

    
1175
        if _mem.unknown3_0 and _mem.narrow:
1176
            mem.mode = 'NAM'
1177
        elif _mem.unknown3_0 and not _mem.narrow:
1178
            mem.mode = 'AM'
1179
        elif not _mem.unknown3_0 and _mem.narrow:
1180
            mem.mode = 'NFM'
1181
        elif not _mem.unknown3_0 and not _mem.narrow:
1182
            mem.mode = 'FM'
1183
        else:
1184
            LOG.exception('Failed to get mode for %i' % num)
1185

    
1186
        mem.skip = '' if _mem.scan else 'S'
1187

    
1188
        # LOG.warning('got txtone: %s' % repr(self._decode_tone(_mem.txtone)))
1189
        # LOG.warning('got rxtone: %s' % repr(self._decode_tone(_mem.rxtone)))
1190
        txtone = self._decode_tone(_mem.txtone)
1191
        rxtone = self._decode_tone(_mem.rxtone)
1192
        chirp_common.split_tone_decode(mem, txtone, rxtone)
1193
        try:
1194
            mem.extra = self._get_extra(_mem, num)
1195
        except Exception:
1196
            LOG.exception('Failed to get extra for %i' % num)
1197
        return mem
1198

    
1199
    def set_memory(self, mem):
1200
        memidx = mem.number - 1
1201
        _mem = self._memobj.memory[memidx]
1202
        _nam = self._memobj.memname[memidx]
1203

    
1204
        if mem.empty:
1205
            _mem.set_raw(b'\xff' * 16)
1206
            _nam.set_raw(b'\xff' * 16)
1207
            return
1208

    
1209
        if int(_mem.rxfreq) == 166666665:
1210
            LOG.debug('Initializing new memory %i' % memidx)
1211
            _mem.set_raw(b'\x00' * 16)
1212

    
1213
        _nam.name = mem.name.ljust(12, chr(255))  # with xFF pad (mimic factory
1214
        #                                           behavior)
1215

    
1216
        _mem.rxfreq = mem.freq / 10
1217
        _mem.tx_enable = 1
1218
        if mem.duplex == '':
1219
            _mem.txfreq = mem.freq / 10
1220
        elif mem.duplex == 'split':
1221
            _mem.txfreq = mem.offset / 10
1222
        elif mem.duplex == 'off':
1223
            _mem.tx_enable = 0
1224
            _mem.txfreq = mem.freq / 10  # Optional but keeps compat with
1225
            #                              vendor software
1226
        elif mem.duplex == '-':
1227
            _mem.txfreq = (mem.freq - mem.offset) / 10
1228
        elif mem.duplex == '+':
1229
            _mem.txfreq = (mem.freq + mem.offset) / 10
1230
        else:
1231
            raise errors.RadioError('Unsupported duplex mode %r' % mem.duplex)
1232

    
1233
        txtone, rxtone = chirp_common.split_tone_encode(mem)
1234
        # LOG.warning('tx tone is %s' % repr(txtone))
1235
        # LOG.warning('rx tone is %s' % repr(rxtone))
1236
        _mem.txtone = self._encode_tone(*txtone)
1237
        _mem.rxtone = self._encode_tone(*rxtone)
1238

    
1239
        try:
1240
            _mem.power = self.POWER_LEVELS.index(mem.power)
1241
        except ValueError:
1242
            _mem.power = 0
1243

    
1244
        if int(_mem.rxfreq) < 30000000:
1245
            _mem.unknown3_0 = mem.mode in ['AM', 'NAM']
1246
        else:
1247
            _mem.unknown3_0 = 0
1248
        _mem.narrow = mem.mode[0] == 'N'
1249

    
1250
        _mem.scan = mem.skip != 'S'
1251

    
1252
        if mem.extra:
1253
            self._set_extra(_mem, mem)
1254

    
1255
    def sync_out(self):
1256
        try:
1257
            do_upload(self)
1258
        except errors.RadioError:
1259
            # Pass through any real errors we raise
1260
            raise
1261
        except Exception:
1262
            # If anything unexpected happens, make sure we raise
1263
            # a RadioError and log the problem
1264
            LOG.exception('Unexpected error during upload')
1265
            raise errors.RadioError('Unexpected error communicating '
1266
                                    'with the radio')
1267

    
1268
    def sync_in(self):
1269
        """Download from radio"""
1270
        try:
1271
            data = do_download(self)
1272
        except errors.RadioError:
1273
            # Pass through any real errors we raise
1274
            raise
1275
        except Exception:
1276
            # If anything unexpected happens, make sure we raise
1277
            # a RadioError and log the problem
1278
            LOG.exception('Unexpected error during download')
1279
            raise errors.RadioError('Unexpected error communicating '
1280
                                    'with the radio')
1281
        self._mmap = data
1282
        self.process_mmap()
1283

    
1284
    def process_mmap(self):
1285
        self._memobj = bitwise.parse(MEM_FORMAT_RT490 %
1286
                                     {"memsize": self._memory_size},
1287
                                     self._mmap)
1288

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

    
1316
    @classmethod
1317
    def get_prompts(cls):
1318
        rp = chirp_common.RadioPrompts()
1319
        rp.pre_upload = _(dedent("""\
1320
            This driver is in development and should be considered
1321
            as experimental.
1322
            """))
1323
        rp.experimental = _(dedent("""\
1324
            This driver is in development and should be considered
1325
            as experimental.
1326
            """))
1327
        rp.info = _(dedent("""\
1328
            This driver is in development and should be considered
1329
            as experimental.
1330
            """))
1331
        return rp
1332

    
1333
    def _encode_key(self, key):
1334
        arr = bytearray(4)
1335
        arr[3] = 160
1336
        arr[2] = self.KEY_CHARS.index(key[0])  # << 4
1337
        arr[2] = arr[2] << 4
1338
        arr[2] |= self.KEY_CHARS.index(key[1])
1339
        arr[1] = self.KEY_CHARS.index(key[2])  # << 4
1340
        arr[1] = arr[1] << 4
1341
        arr[1] |= self.KEY_CHARS.index(key[3])
1342
        arr[0] = self.KEY_CHARS.index(key[4])  # << 4
1343
        arr[0] = arr[0] << 4
1344
        arr[0] |= self.KEY_CHARS.index(key[5])
1345
        return arr
1346

    
1347
    def _decode_key(self, key):
1348
        ret = ""
1349
        if key[3] == 0xA0:
1350
            ret += self.KEY_CHARS[key[2] >> 4]
1351
            ret += self.KEY_CHARS[key[2] & 0xF]
1352
            ret += self.KEY_CHARS[key[1] >> 4]
1353
            ret += self.KEY_CHARS[key[1] & 0xF]
1354
            ret += self.KEY_CHARS[key[0] >> 4]
1355
            ret += self.KEY_CHARS[key[0] & 0xF]
1356
            LOG.debug('DCP Code: "%s"' % ret)
1357
        return ret
1358

    
1359
    def _decode_tone(self, toneval):
1360
        if toneval in (0, 0xFFFF):
1361
            # LOG.debug('no tone value: %s' % toneval)
1362
            return None, None, None
1363
        elif toneval < 670:
1364
            toneval = toneval - 1
1365
            index = toneval % len(DTCS_CODES)
1366
            if index != int(toneval):
1367
                pol = 'R'
1368
                # index -= 1
1369
            else:
1370
                pol = 'N'
1371
            return 'DTCS', DTCS_CODES[index], pol
1372
        else:
1373
            return 'Tone', toneval / 10.0, 'N'
1374

    
1375
    def _encode_tone(self, mode, val, pol):
1376
        if not mode:
1377
            return 0x0000
1378
        elif mode == 'Tone':
1379
            return int(val * 10)
1380
        elif mode == 'DTCS':
1381
            index = DTCS_CODES.index(val)
1382
            if pol == 'R':
1383
                index += len(DTCS_CODES)
1384
            index += 1
1385
            # LOG.debug('Encoded dtcs %s/%s to %04x' % (val, pol, index))
1386
            return index
1387
        else:
1388
            raise errors.RadioError('Unsupported tone mode %r' % mode)
1389

    
1390
    @classmethod
1391
    def match_model(cls, filedata, filename):
1392
        # This radio has always been post-metadata, so never do
1393
        # old-school detection
1394
        return False
1395

    
1396

    
1397
class MML8629Alias(chirp_common.Alias):
1398
    VENDOR = "MMLradio"
1399
    MODEL = "JC-8629"
1400

    
1401

    
1402
class JJCC8629Alias(chirp_common.Alias):
1403
    VENDOR = "JJCC"
1404
    MODEL = "JC-8629"
1405

    
1406

    
1407
class SocotranJC8629Alias(chirp_common.Alias):
1408
    VENDOR = "Socotran"
1409
    MODEL = "JC-8629"
1410

    
1411

    
1412
class SocotranFB8629Alias(chirp_common.Alias):
1413
    VENDOR = "Socotran"
1414
    MODEL = "FB-8629"
1415

    
1416

    
1417
class Jianpai8629Alias(chirp_common.Alias):
1418
    VENDOR = "Jianpai"
1419
    MODEL = "8800 Plus"
1420

    
1421

    
1422
class Boristone8RSAlias(chirp_common.Alias):
1423
    VENDOR = "Boristone"
1424
    MODEL = "8RS"
1425

    
1426

    
1427
class AbbreeAR869Alias(chirp_common.Alias):
1428
    VENDOR = "Abbree"
1429
    MODEL = "AR-869"
1430

    
1431

    
1432
class HamGeekHG590Alias(chirp_common.Alias):
1433
    VENDOR = "HamGeek"
1434
    MODEL = "HG-590"
1435

    
1436

    
1437
@directory.register
1438
class RT490RadioGeneric(RT490Radio):
1439
    ALIASES = [MML8629Alias, JJCC8629Alias, SocotranJC8629Alias,
1440
               SocotranFB8629Alias, Jianpai8629Alias, Boristone8RSAlias,
1441
               AbbreeAR869Alias, HamGeekHG590Alias]
1442

    
1443

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

    
1619
memory mem[256] @ 0x0000;
1620
memname mname[256] @ 0x1000;
1621
memcode mcode[256] @ 0x2000;
1622
custom_ani_names caninames[10] @ 0x3400;
1623
anicodes anic[60] @ 0x3500;
1624
custom_channel_names ccnames[10] @ 0x3900;
1625
settings _settings @ 0x3A00;
1626
settings_vfo _settings_vfo @ 0x3A40;
1627
settings_sidekeys _settings_sidekeys @ 0x3A80;
1628
settings_dtmf_global _settings_dtmf_global @ 0x3B00;
1629
settings_killswitch _settings_killswitch @ 0x3C00;
1630
management_settings _management_settings @ 0x3F80;
1631
settings_bands _settings_bands @ 0x3FC0;
1632
"""
(27-27/33)