Project

General

Profile

New Model #9665 » rt490-py3.py

RT490 module, updated for Python3 - Darryl Pogue, 04/29/2023 12:43 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 CONTRIBUTORS
22
#  BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
23
#  OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24
#  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25
#  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26
#  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27
#  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
28
#  THE POSSIBILITY OF SUCH DAMAGE.
29

    
30
import logging
31
import struct
32

    
33
from chirp import (
34
    bitwise,
35
    chirp_common,
36
    directory,
37
    errors,
38
    memmap,
39
    util,
40
)
41

    
42
from chirp.settings import (
43
    RadioSetting,
44
    RadioSettingGroup,
45
    RadioSettings,
46
    RadioSettingValueBoolean,
47
    RadioSettingValueInteger,
48
    RadioSettingValueList,
49
    RadioSettingValueString,
50
    RadioSettingValueMap,
51
    RadioSettingValueFloat,
52
    InvalidValueError
53
)
54

    
55
LOG = logging.getLogger(__name__)
56

    
57
import time
58
import os
59
import unittest
60
from textwrap import dedent
61
try:
62
    from builtins import bytes
63
    has_future = True
64
except ImportError:
65
    has_future = False
66
    LOG.debug('python-future package is not available; '
67
              '%s requires it' % __name__)
68

    
69
# 'True' or 'False'
70
# True unlocks:
71
# - FM preset
72
# - Channel memory # preset
73
# - Killswitch (with revive killed radios)
74
# - Bands settings
75
# - Disable radio identification string verification
76
#   (good to recover from a bad state)
77
RT490_EXPERIMENTAL = True
78

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

    
265
CMD_ACK = b"\x06"
266

    
267
DTCS_CODES = tuple(sorted(chirp_common.DTCS_CODES + (645,)))
268

    
269
DTMFCHARS = '0123456789ABCD*#'
270

    
271
def _enter_programming_mode(radio):
272
    serial = radio.pipe
273

    
274
    exito = False
275
    for i in range(0, 5):
276
        serial.write(radio._magic)
277
        ack = serial.read(1)
278

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

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

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

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

    
303
def _exit_programming_mode(radio):
304
    serial = radio.pipe
305
    try:
306
        serial.write(b"E")
307
    except Exception:
308
        raise errors.RadioError("Radio refused to exit programming mode")
309

    
310
def _read_block(radio, block_addr, block_size):
311
    serial = radio.pipe
312

    
313
    cmd = struct.pack(">cHb", b'R', block_addr, block_size)
314
    expectedresponse = b"R" + cmd[1:]
315
    LOG.debug("Reading block %04x..." % (block_addr))
316

    
317
    try:
318
        serial.write(cmd)
319
        response = serial.read(4 + block_size)
320
        if response[:4] != expectedresponse:
321
            raise Exception("Error reading block %04x." % (block_addr))
322

    
323
        block_data = response[4:]
324
    except Exception:
325
        raise errors.RadioError("Failed to read block at %04x" % block_addr)
326

    
327
    return block_data
328

    
329
def _write_block(radio, block_addr, block_size):
330
    serial = radio.pipe
331

    
332
    cmd = struct.pack(">cHb", b'W', block_addr, block_size)
333
    data = radio.get_mmap()[block_addr:block_addr + block_size]
334

    
335
    LOG.debug("Writing Data:")
336
    LOG.debug(util.hexprint(cmd + data))
337

    
338
    try:
339
        serial.write(cmd + data)
340
        if serial.read(1) != CMD_ACK:
341
            raise Exception("No ACK")
342
    except Exception:
343
        raise errors.RadioError("Failed to send block "
344
                                "to radio at %04x" % block_addr)
345

    
346
def do_download(radio):
347
    LOG.debug("download")
348
    _enter_programming_mode(radio)
349

    
350
    data = b""
351

    
352
    status = chirp_common.Status()
353
    status.msg = "Cloning from radio"
354

    
355
    status.cur = 0
356
    status.max = radio._memsize
357

    
358
    for addr in range(0, radio._memsize, radio.BLOCK_SIZE):
359
        status.cur = addr + radio.BLOCK_SIZE
360
        radio.status_fn(status)
361

    
362
        block = _read_block(radio, addr, radio.BLOCK_SIZE)
363
        data += block
364

    
365
        LOG.debug("Address: %04x" % addr)
366
        LOG.debug(util.hexprint(block))
367

    
368
    _exit_programming_mode(radio)
369

    
370
    return memmap.MemoryMapBytes(data)
371

    
372
def do_upload(radio):
373
    status = chirp_common.Status()
374
    status.msg = "Uploading to radio"
375

    
376
    _enter_programming_mode(radio)
377

    
378
    status.cur = 0
379
    status.max = radio._memsize
380

    
381
    for start_addr, end_addr in radio._ranges:
382
        for addr in range(start_addr, end_addr, radio.BLOCK_SIZE):
383
            status.cur = addr + radio.BLOCK_SIZE
384
            radio.status_fn(status)
385
            _write_block(radio, addr, radio.BLOCK_SIZE)
386

    
387
    _exit_programming_mode(radio)
388

    
389
class RT490Radio(chirp_common.CloneModeRadio):
390
    """RADTEL RT-490"""
391
    VENDOR = "Radtel"
392
    MODEL = "RT-490"
393
    BLOCK_SIZE = 0x40 # 64 bytes
394
    BAUD_RATE = 9600
395
    NEEDS_COMPAT_SERIAL = False
396

    
397
    POWER_LEVELS = [chirp_common.PowerLevel("H", watts=5.00),
398
                    chirp_common.PowerLevel("L", watts=3.00)]
399

    
400
    # magic = progmode + modelType + garbage (works with any last char)
401
    _magic = b"PROGROMJJCCU"
402

    
403
    # fingerprint is default band ranges of the radio
404
    # the driver can change band ranges and fingerprint will
405
    # change accordingly, so it is not used to verify radio id.
406
    _fingerprint = b"\x01\x36\x01\x80\x04\x00\x05\x20"
407

    
408
    # Ranges of memory used when uploading data to radio
409
    # same as official software
410
    _ranges = [
411
               (0x0000, 0x2400),
412
               (0x3400, 0x3C40),
413
               (0x3FC0, 0x4000)
414
              ]
415

    
416
    if RT490_EXPERIMENTAL:
417
        # Experimental driver (already heavily tested)
418
        _ranges = [ (0x0000, 0x2400), (0x3400, 0x3C40), (0x3F80, 0x4000) ]
419

    
420
    # Danger zone
421
    #_ranges = [ (0x0000, 0x2500), (0x3400, 0x3C40), (0x3E00, 0x4000) ]
422

    
423
    # 16KB of memory, download read everything
424
    # same as official software (remark: loops if overread :))
425
    _memsize = 16384
426

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

    
467
    def set_settings(self, settings):
468
        for element in settings:
469
            if not isinstance(element, RadioSetting):
470
                self.set_settings(element)
471
                continue
472
            else:
473
                self._set_setting(element)
474

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

    
560
    def _get_settings_bands(self):
561
        ret = RadioSettingGroup('bands', 'Bands')
562
        bands = [ ('136', self._memobj.settings_bands.band136), ('200', self._memobj.settings_bands.band200),
563
                  ('350', self._memobj.settings_bands.band350), ('400', self._memobj.settings_bands.band400) ]
564
        for label, band in bands:
565
            rs = RadioSetting('settings_bands.band%s.enable' % label, 'Enable Band %s' % label,
566
                              RadioSettingValueBoolean(band.enable))
567
            ret.append(rs)
568
            rsi = RadioSettingValueInteger(1, 1000, band.freq_low)
569
            #if label == '136' or label == '400':
570
            #    rsi.set_mutable(False)
571
            rs = RadioSetting("settings_bands.band%s.freq_low" % label,
572
                              "Band %s Lower Limit (MHz) (EXPERIMENTAL)" % label,
573
                              rsi)
574
            ret.append(rs)
575
            rsi = RadioSettingValueInteger(1, 1000, band.freq_high)
576
            #if label == '350':
577
            #    rsi.set_mutable(False)
578
            rs = RadioSetting("settings_bands.band%s.freq_high" % label,
579
                              "Band %s Upper Limit (MHz) (EXPERIMENTAL)" % label,
580
                              rsi)
581
            ret.append(rs)
582
        return ret
583

    
584
    def _get_settings_ks(self):
585
        ret = RadioSettingGroup('killswitch', 'Killswitch')
586
        # Kill Enable/Disable enable_killsw
587
        ret.append(RadioSetting('settings.enable_killsw', 'Enable Killswitch',
588
                    RadioSettingValueBoolean(self._memobj.settings.enable_killsw)))
589
        # Kill DTMF
590
        cur = ''.join(
591
            DTMFCHARS[i]
592
            for i in self._memobj.settings_killswitch.kill_dtmf if int(i) < 0xF)
593
        ret.append(
594
            RadioSetting( 'settings_killswitch.kill_dtmf', 'DTMF Kill',
595
                            RadioSettingValueString(6, 6, cur,
596
                                                autopad=False,
597
                                                charset=DTMFCHARS)))
598
        # Revive DTMF
599
        cur = ''.join(
600
            DTMFCHARS[i]
601
            for i in self._memobj.settings_killswitch.revive_dtmf if int(i) < 0xF)
602
        ret.append(
603
            RadioSetting( 'settings_killswitch.revive_dtmf', 'DTMF Revive',
604
                            RadioSettingValueString(6, 6, cur,
605
                                                autopad=False,
606
                                                charset=DTMFCHARS)))
607
        # Enable/Disable entire radio
608
        rs = RadioSettingValueString(0, 255, "Can be used to revive radio")
609
        rs.set_mutable(False)
610
        ret.append(RadioSetting('dummy', 'Factory reserved', rs))
611
        tmp = 1 if int(self._memobj.management_settings.active) > 0 else 0
612
        ret.append(RadioSetting('management_settings.active', 'Radio Status',
613
                                RadioSettingValueList(self.ENABLERADIO,
614
                                                      self.ENABLERADIO[tmp])))
615
        return ret
616

    
617
    def _get_settings_dtmf(self):
618
        dtmf = RadioSettingGroup('dtmf', 'DTMF')
619
        # DTMF Group
620
        msgs = [ "Allowed chars (%s)" % DTMFCHARS,
621
                "Input from 0 to 5 characters."
622
                ]
623
        for msg in msgs:
624
            rsvs = RadioSettingValueString(0, 255, msg, autopad=False)
625
            rsvs.set_mutable(False)
626
            rs = RadioSetting('dummy_dtmf_msg_%i' % msgs.index(msg), 'Input rule %i' % int(msgs.index(msg)+1), rsvs)
627
            dtmf.append(rs)
628
        for i in range(1, 16):
629
            cur = ''.join(
630
                DTMFCHARS[i]
631
                for i in self._memobj.settings_dtmfgroup[i - 1].code if int(i) < 0xF)
632
            dtmf.append(
633
                RadioSetting(
634
                    'settings_dtmfgroup.code@%i' % i, 'PTT ID Code %i' % i,
635
                    RadioSettingValueString(0, 5, cur,
636
                                            autopad=False,
637
                                            charset=DTMFCHARS)))
638
        # DTMF Speed (on time in ms)
639
        dtmf_speed_on = int(self._memobj.settings_dtmf.dtmf_speed_on)
640
        if dtmf_speed_on > len(self.DTMF_SPEEDS)-1:
641
            self._memobj.settings_dtmf.dtmf_speed_on = 0
642
            LOG.debug('DTMF Speed On overflow')
643
        cur = self.DTMF_SPEEDS[dtmf_speed_on]
644
        dtmf.append(
645
            RadioSetting(
646
                'settings_dtmf.dtmf_speed_on', 'DTMF Speed (on time in ms)',
647
                RadioSettingValueList(self.DTMF_SPEEDS, cur)))
648
        # DTMF Speed (on time in ms)
649
        dtmf_speed_off = int(self._memobj.settings_dtmf.dtmf_speed_off)
650
        if dtmf_speed_off > len(self.DTMF_SPEEDS)-1:
651
            self._memobj.settings_dtmf.dtmf_speed_off = 0
652
            LOG.debug('DTMF Speed Off overflow')
653
        cur = self.DTMF_SPEEDS[dtmf_speed_off]
654
        dtmf.append(
655
            RadioSetting(
656
                'settings_dtmf.dtmf_speed_off', 'DTMF Speed (off time in ms)',
657
                RadioSettingValueList(self.DTMF_SPEEDS, cur)))
658
        # PTT ID
659
        pttid = int(self._memobj.settings_dtmf.pttid)
660
        if pttid > len(self.PTTID)-1:
661
            self._memobj.settings_dtmf.pttid = 0
662
            LOG.debug('PTT ID overflow')
663
        cur = self.PTTID[pttid]
664
        dtmf.append(
665
            RadioSetting(
666
                'settings_dtmf.pttid', 'Send DTMF Code (PTT ID)',
667
                RadioSettingValueList(self.PTTID, cur)))
668
        # PTT ID Delay
669
        pttiddelay = int(self._memobj.settings.pttiddelay)
670
        if pttiddelay > len(self.PTTIDDELAYS)-1:
671
            self._memobj.settings.pttiddelay = 0
672
            LOG.debug('PTT ID  Delay overflow')
673
        cur = self.PTTIDDELAYS[pttiddelay]
674
        dtmf.append(
675
            RadioSetting(
676
                'settings.pttiddelay', 'PTT ID Delay',
677
                RadioSettingValueList(self.PTTIDDELAYS, cur)))
678
        dtmf.append(
679
            RadioSetting(
680
                'settings.dtmfst', 'DTMF Side Tone (Required for GPS ID)',
681
                RadioSettingValueList(self.DTMFSTLIST, self.DTMFSTLIST[self._memobj.settings.dtmfst])))
682
        return dtmf
683

    
684
    def _get_settings_sidekeys(self):
685
        ret = RadioSettingGroup('sidekeys', 'Side Keys')
686
        ret.append(RadioSetting( 'settings_sidekeys.pf2_short', 'Side key 1 (PTT2) Short press',
687
                      RadioSettingValueMap( self.SIDEKEY_VALUEMAP, self._memobj.settings_sidekeys.pf2_short)))
688
        ret.append(RadioSetting( 'settings_sidekeys.pf2_long', 'Side key 1 (PTT2) Long press',
689
                      RadioSettingValueMap( self.SIDEKEY_VALUEMAP[:-1], self._memobj.settings_sidekeys.pf2_long)))
690
        ret.append(RadioSetting( 'settings_sidekeys.pf3_short', 'Side key 2 (PTT3) Short press',
691
                      RadioSettingValueMap( self.SIDEKEY_VALUEMAP[:-1], self._memobj.settings_sidekeys.pf3_short)))
692
        rs = RadioSettingValueString(0, 255, "MONI")
693
        rs.set_mutable(False)
694
        ret.append(RadioSetting( 'dummy', 'Side key 2 (PTT3) Long press', rs))
695
        return ret
696

    
697
    def _get_settings_vfo(self, vfo, chan): # WIP TODO Rx/Tx tones
698
        ret = RadioSettingGroup('settings_vfo@%s' % chan.lower(), 'VFO %s Settings' % chan)
699
        ret.append(RadioSetting('settings.workmode%s' % chan.lower(), 'VFO %s Workmode' % chan,
700
                                RadioSettingValueList(
701
                                self.WORKMODES,
702
                                self.WORKMODES[self._memobj.settings['workmode'+chan.lower()]])))
703
        tmp = ''.join(DTMFCHARS[i] for i in self._memobj.settings_vfo['vfo_'+chan.lower()].rxfreq if i < 0xFF)
704
        ret.append(RadioSetting('settings_vfo.vfo_%s.rxfreq' % chan.lower(), 'Rx Frequency',
705
                          RadioSettingValueFloat(
706
                              66, 550, chirp_common.format_freq(int(tmp) * 10), resolution=0.00001, precision=5 )))
707
        # TODO Rx/Tx tones
708
        ret.append(RadioSetting('settings_vfo.vfo_%s.sftd' % chan.lower(), 'Freq offset direction',
709
                                RadioSettingValueList(
710
                                self.VFO_SFTD,
711
                                self.VFO_SFTD[self._memobj.settings_vfo['vfo_'+chan.lower()].sftd])))
712
        tmp = ''.join(DTMFCHARS[i] for i in self._memobj.settings_vfo['vfo_'+chan.lower()].offset if i < 0xFF)
713
        ret.append(RadioSetting('settings_vfo.vfo_%s.offset' % chan.lower(), 'Tx Offset',
714
                          RadioSettingValueFloat(
715
                              0, 99.9999, float(tmp) / 10000) ))
716
        ret.append(RadioSetting('settings_vfo.vfo_%s.signal' % chan.lower(), 'PTT ID Code (S-Code)',
717
                                RadioSettingValueList(self.SIGNAL,
718
                                                      self.SIGNAL[self._memobj.settings_vfo['vfo_'+chan.lower()].signal])))
719
        ret.append(RadioSetting('settings_vfo.vfo_%s.power' % chan.lower(), 'Tx Power',
720
                                RadioSettingValueList(self.POWER_LEVELS_LIST,
721
                                                      self.POWER_LEVELS_LIST[self._memobj.settings_vfo['vfo_'+chan.lower()].power])))
722
        ret.append(RadioSetting('settings_vfo.vfo_%s.fhss' % chan.lower(), 'FHSS (Encryption)',
723
                                RadioSettingValueList(self.FHSS_LIST,
724
                                                      self.FHSS_LIST[self._memobj.settings_vfo['vfo_'+chan.lower()].fhss])))
725
        ret.append(RadioSetting('settings_vfo.vfo_%s.narrow' % chan.lower(), 'Wide / Narrow',
726
                                RadioSettingValueList(['Wide', 'Narrow'],
727
                                                      ['Wide', 'Narrow'][self._memobj.settings_vfo['vfo_'+chan.lower()].narrow])))
728
        ret.append(RadioSetting('settings_vfo.vfo_%s.freqstep' % chan.lower(), 'Tuning Step',
729
                                RadioSettingValueList(self.TUNING_STEPS_LIST,
730
                                                      self.TUNING_STEPS_LIST[self._memobj.settings_vfo['vfo_'+chan.lower()].freqstep])))
731
        return ret
732

    
733
    def _get_custom_channel_names(self):
734
        ret = RadioSettingGroup('ccn', 'Custom Channel Names')
735
        msgs = [ "Add custom chan names to radio",
736
                "-> Menu 09 CH-NAME",
737
                "Allowed chars (ASCII only)",
738
                "Input from 0 to 10 characters."
739
                ]
740
        for msg in msgs:
741
            rsvs = RadioSettingValueString(0, 255, msg, autopad=False)
742
            rsvs.set_mutable(False)
743
            rs = RadioSetting('dummy_cnames_msg_%i' % msgs.index(msg), 'Input rule %i' % int(msgs.index(msg)+1), rsvs)
744
            ret.append(rs)
745
        for i in range(0, len(self._memobj.custom_channel_names)):
746
            tmp = ''.join([ str(j) for j in self._memobj.custom_channel_names[i].name
747
                                   if ord(str(j)) < 0xFF and ord(str(j)) > 0x00])
748
            ret.append( RadioSetting( 'custom_channel_names@name@%i' % i,
749
                                      'Custom Channel Name (%i)' % i,
750
                                      RadioSettingValueString(0, 10, tmp, autopad=True,
751
                                                              charset=self.FULL_CHARSET_ASCII)))
752
        return ret
753

    
754
    def _get_custom_ani_names(self):
755
        ret = RadioSettingGroup('can', 'Custom ANI Names')
756
        msgs = [ "Can be used as radio id in GPS.",
757
                "Allowed chars (ASCII only)",
758
                "Input from 0 to 10 characters."
759
                ]
760
        for msg in msgs:
761
            rsvs = RadioSettingValueString(0, 255, msg, autopad=False)
762
            rsvs.set_mutable(False)
763
            rs = RadioSetting('dummy_caninames_msg_%i' % msgs.index(msg), 'Input rule %i' % int(msgs.index(msg)+1), rsvs)
764
            ret.append(rs)
765
        for i in range(0, len(self._memobj.custom_ani_names)):
766
            tmp = ''.join([ str(j) for j in self._memobj.custom_ani_names[i].name
767
                                   if ord(str(j)) < 0xFF and ord(str(j)) > 0x00])
768
            ret.append( RadioSetting( 'custom_ani_names@name@%i' % i,
769
                                      'Custom ANI Name (%i)' % (i+51),
770
                                      RadioSettingValueString(0, 10, tmp, autopad=True,
771
                                                              charset=self.FULL_CHARSET_ASCII)))
772
        return ret
773

    
774
    def _get_anicodes(self):
775
        ret = RadioSettingGroup('ani', 'ANI Codes')
776
        split = len(self._memobj.anicodes) - len(self._memobj.custom_ani_names)
777
        msgs = [ "Allowed chars (%s)" % DTMFCHARS,
778
                "Input from 0 to 6 characters."
779
                ]
780
        for msg in msgs:
781
            rsvs = RadioSettingValueString(0, 255, msg, autopad=False)
782
            rsvs.set_mutable(False)
783
            rs = RadioSetting('dummy_canic_msg_%i' % msgs.index(msg), 'Input rule %i' % int(msgs.index(msg)+1), rsvs)
784
            ret.append(rs)
785
        
786
        for i in range(0, split):
787
            tmp = ''.join([ DTMFCHARS[int(j)] for j in self._memobj.anicodes[i].anicode if int(j) < 0xFF ])
788
            #LOG.debug("ANI Code (%i) '%s'" % (i, tmp) )
789
            ret.append( RadioSetting( 'anicodes@anicode@%i' % i,
790
                                      'ANI-ID (%i) Code' % (i+1),
791
                                      RadioSettingValueString(0, 6, tmp, autopad=False,
792
                                                              charset=DTMFCHARS)))
793
        for i in range(split, len(self._memobj.anicodes)):
794
            tmp = ''.join([ DTMFCHARS[int(j)] for j in self._memobj.anicodes[i].anicode if int(j) < 0xFF ])
795
            tmp2 = ''.join([ str(j) for j in self._memobj.custom_ani_names[i-split].name
796
                                   if ord(str(j)) < 0xFF and ord(str(j)) > 0x00])
797
            #LOG.debug("ANI Code (%s) (%i) '%s'" % (tmp2, i, tmp) )
798
            ret.append( RadioSetting( 'anicodes@anicode@%i' % i,
799
                                      'ANI-ID (%s) (%i) Code' % (tmp2, i+1),
800
                                      RadioSettingValueString(0, 6, tmp, autopad=False,
801
                                                              charset=DTMFCHARS)))
802
        return ret
803

    
804
    def _get_settings_adv(self):
805
        ret = RadioSettingGroup('advanced', 'Advanced')
806
        if RT490_EXPERIMENTAL:
807
            ret.append(RadioSetting("settings.cha_memidx", "Channel A Memory index",
808
                                    RadioSettingValueInteger(1, self._memory_size, int(self._memobj.settings.cha_memidx)+1)))
809
            ret.append(RadioSetting("settings.chb_memidx", "Channel B Memory index",
810
                                    RadioSettingValueInteger(1, self._memory_size, int(self._memobj.settings.chb_memidx)+1)))
811
        ret.append(RadioSetting('settings.vox', 'VOX Sensitivity',
812
                                RadioSettingValueList(self.VOX_LIST,
813
                                                      self.VOX_LIST[self._memobj.settings.vox])))
814
        ret.append(RadioSetting('settings.vox_delay', 'VOX Delay',
815
                                RadioSettingValueList(self.VOXDELAYLIST,
816
                                                      self.VOXDELAYLIST[self._memobj.settings.vox_delay])))
817
        ret.append(RadioSetting('settings.tdr', 'Dual Receive (TDR)',
818
                                RadioSettingValueBoolean(self._memobj.settings.tdr)))
819
        ret.append(RadioSetting('settings.txundertdr', 'Tx under TDR',
820
                                RadioSettingValueList(self.TDRTXMODES,
821
                                                      self.TDRTXMODES[self._memobj.settings.txundertdr])))
822
        ret.append(RadioSetting('settings.voice', 'Menu Voice Prompts',
823
                                RadioSettingValueBoolean(self._memobj.settings.voice)))
824
        ret.append(RadioSetting('settings.scanmode', 'Scan Mode',
825
                                RadioSettingValueList(self.SCANMODES,
826
                                                      self.SCANMODES[self._memobj.settings.scanmode])))
827
        ret.append(RadioSetting('settings.bcl', 'Busy Channel Lockout',
828
                                RadioSettingValueBoolean(self._memobj.settings.bcl)))
829
        ret.append(RadioSetting('settings.display_ani', 'Display ANI ID',
830
                                RadioSettingValueBoolean(self._memobj.settings.display_ani)))
831
        ret.append(RadioSetting('settings.ani_id', 'ANI ID',
832
                                RadioSettingValueList(self.ANI_IDS,
833
                                                      self.ANI_IDS[self._memobj.settings.ani_id])))
834
        ret.append(RadioSetting('settings.alarm_mode', 'Alarm Mode',
835
                                RadioSettingValueList(self.ALARMMODES,
836
                                                      self.ALARMMODES[self._memobj.settings.alarm_mode])))
837
        ret.append(RadioSetting('settings.alarmsound', 'Alarm Sound',
838
                                RadioSettingValueBoolean(self._memobj.settings.alarmsound)))
839
        ret.append(RadioSetting('settings.fmradio', 'Enable FM Radio',
840
                                RadioSettingValueList(self.FMRADIO,
841
                                                      self.FMRADIO[self._memobj.settings.fmradio])))
842
        if RT490_EXPERIMENTAL:
843
            tmp = self._memobj.settings.fmpreset / 10.0
844
            if tmp < 65.0 or tmp > 108.0:
845
                tmp = 80.0
846
            ret.append(RadioSetting("settings.fmpreset", "FM Radio Freq",
847
                                    RadioSettingValueFloat(65, 108, tmp, resolution=0.1, precision=1)))
848
        ret.append(RadioSetting('settings.kblock', 'Enable Keyboard Lock',
849
                                RadioSettingValueBoolean(self._memobj.settings.kblock)))
850
        ret.append(RadioSetting('settings.autolock', 'Autolock Keyboard',
851
                                RadioSettingValueList(self.AUTOLOCK_TO,
852
                                                      self.AUTOLOCK_TO[self._memobj.settings.autolock])))
853
        ret.append(RadioSetting('settings.timer_menu_quit', 'Menu Exit Time',
854
                                RadioSettingValueList(self.MENUEXIT_TO,
855
                                                      self.MENUEXIT_TO[self._memobj.settings.timer_menu_quit])))
856
        ret.append(RadioSetting('settings.enable_gps', 'Enable GPS',
857
                                RadioSettingValueBoolean(self._memobj.settings.enable_gps)))
858
        ret.append(RadioSetting('settings.scan_dcs', 'CDCSS Save Modes',
859
                                RadioSettingValueList(self.SCANDCSMODES,
860
                                                      self.SCANDCSMODES[self._memobj.settings.scan_dcs])))
861
        ret.append(RadioSetting('settings.tailnoiseclear', 'Tail Noise Clear',
862
                                RadioSettingValueBoolean(self._memobj.settings.tailnoiseclear)))
863
        ret.append(RadioSetting('settings.rptnoiseclear', 'Rpt Noise Clear',
864
                                RadioSettingValueList(self.RPTNOISE,
865
                                                      self.RPTNOISE[self._memobj.settings.rptnoiseclear])))
866
        ret.append(RadioSetting('settings.rptnoisedelay', 'Rpt Noise Delay',
867
                                RadioSettingValueList(self.RPTNOISE,
868
                                                      self.RPTNOISE[self._memobj.settings.rptnoisedelay])))
869
        ret.append(RadioSetting('settings.rpttone', 'Rpt Tone',
870
                                RadioSettingValueList(self.RPTTONES,
871
                                                      self.RPTTONES[self._memobj.settings.rpttone])))
872
        return ret
873

    
874
    def _get_settings_basic(self):
875
        ret = RadioSettingGroup('basic', 'Basic')
876
        ret.append(RadioSetting('settings.squelch', 'Carrier Squelch Level',
877
                                RadioSettingValueList(self.SQUELCHLVLS,
878
                                                      self.SQUELCHLVLS[self._memobj.settings.squelch])))
879
        ret.append(RadioSetting('settings.savemode', 'Battery Savemode',
880
                                RadioSettingValueList(self.SAVEMODES,
881
                                                      self.SAVEMODES[self._memobj.settings.savemode])))
882
        ret.append(RadioSetting('settings.backlight', 'Backlight Timeout',
883
                                RadioSettingValueList(self.BACKLIGHT_TO,
884
                                                      self.BACKLIGHT_TO[self._memobj.settings.backlight])))
885
        ret.append(RadioSetting('settings.timeout', 'Timeout Timer (TOT)',
886
                                RadioSettingValueList(self.TOT_LIST,
887
                                                      self.TOT_LIST[self._memobj.settings.timeout])))
888
        ret.append(RadioSetting('settings.beep', 'Beep',
889
                    RadioSettingValueBoolean(self._memobj.settings.beep)))
890
        ret.append(RadioSetting('settings.active_channel', 'Active Channel',
891
                                RadioSettingValueList(self.CHANNELS,
892
                                                      self.CHANNELS[self._memobj.settings.active_channel])))
893
        ret.append(RadioSetting('settings.cha_disp', 'Channel A Display Mode',
894
                                RadioSettingValueList(self.DISPLAYMODES,
895
                                                      self.DISPLAYMODES[self._memobj.settings.cha_disp])))
896
        ret.append(RadioSetting('settings.chb_disp', 'Channel B Display Mode',
897
                                RadioSettingValueList(self.DISPLAYMODES,
898
                                                      self.DISPLAYMODES[self._memobj.settings.chb_disp])))
899
        ret.append(RadioSetting('settings.roger', 'Roger Beep',
900
                    RadioSettingValueBoolean(self._memobj.settings.roger)))
901
        ret.append(RadioSetting('settings.powermsg', 'Power Message',
902
                                RadioSettingValueList(self.POWERMESSAGES,
903
                                                      self.POWERMESSAGES[self._memobj.settings.powermsg])))
904
        ret.append(RadioSetting('settings.rx_time', 'Show RX Time',
905
                    RadioSettingValueBoolean(self._memobj.settings.rx_time)))
906
        return ret
907

    
908
    def _get_memcodes(self):
909
        ret = RadioSettingGroup('mc', 'Memory Channel Privacy Codes')
910
        msgs = [ "Only hexadecimal chars accepted.",
911
                "Allowed chars (%s)" % self.KEY_CHARS,
912
                "Input from 0 to 6 characters. If code",
913
                "length is less than 6 chars it will be",
914
                "padded with leading zeros.",
915
                "Ex: 1D32EB or 0F12 or AB521, etc...",
916
                "Enable Code for the Location on the",
917
                "'Other' tab in 'Memory Properties'."
918
                ]
919
        for msg in msgs:
920
            rsvs = RadioSettingValueString(0, 255, msg, autopad=False)
921
            rsvs.set_mutable(False)
922
            rs = RadioSetting('dummy_memcodes_msg_%i' % msgs.index(msg), 'Input rule %i' % int(msgs.index(msg)+1), rsvs)
923
            ret.append(rs)
924
        for i in range(self._memory_size):
925
            code = ""
926
            if self._memobj.memcode[i].code[3] < 0xFF:
927
                code = self._decode_key(self._memobj.memcode[i].code)
928
                code = code.zfill(6)
929
            rsvs = RadioSettingValueString(0, 6, code, autopad=False,
930
                                        charset=self.KEY_CHARS)
931
            rs = RadioSetting('memcode@code@%i' % i,
932
                              'Memory Location (%i) Privacy Code' % int(i+1), rsvs)
933
            ret.append(rs)
934
        return ret
935

    
936
    def get_settings(self):
937
        radio_settings = []
938
        basic = self._get_settings_basic()
939
        radio_settings.append(basic)
940
        adv = self._get_settings_adv()
941
        radio_settings.append(adv)
942
        vfoa = self._get_settings_vfo(self._memobj.settings_vfo.vfo_a, 'A')
943
        radio_settings.append(vfoa)
944
        vfob = self._get_settings_vfo(self._memobj.settings_vfo.vfo_b, 'B')
945
        radio_settings.append(vfob)
946
        sk =  self._get_settings_sidekeys()
947
        radio_settings.append(sk)
948
        dtmf = self._get_settings_dtmf()
949
        radio_settings.append(dtmf)
950
        ccn = self._get_custom_channel_names()
951
        radio_settings.append(ccn)
952
        can = self._get_custom_ani_names()
953
        radio_settings.append(can)
954
        ani = self._get_anicodes()
955
        radio_settings.append(ani)
956
        mcodes = self._get_memcodes()
957
        radio_settings.append(mcodes)
958
        if RT490_EXPERIMENTAL:
959
            ks = self._get_settings_ks()
960
            radio_settings.append(ks)
961
            bands = self._get_settings_bands()
962
            radio_settings.append(bands)
963
        top = RadioSettings(*radio_settings)
964
        return top
965

    
966
    def get_raw_memory(self, number):
967
        return repr(self._memobj.memory[number])
968

    
969
    # TODO Add Code when RadioSettingValueString is fixed
970
    def _get_extra(self, _mem, num):
971
        group = RadioSettingGroup('extra', 'Extra')
972
        #LOG.debug("Get extra %i" % num)
973

    
974
        s = RadioSetting('bcl', 'Busy Channel Lockout',
975
                         RadioSettingValueBoolean(_mem.bcl))
976
        group.append(s)
977

    
978
        dcp = int(_mem.dcp)
979
        if dcp > len(self.FHSS_LIST)-1:
980
            _mem.dcp = cur = 0
981
            LOG.debug('DCP ID / FHSS overflow for channel %d' % num)
982
        cur = self.FHSS_LIST[dcp]
983
        s = RadioSetting('dcp', 'FHSS (Encryption)',
984
                         RadioSettingValueList(self.FHSS_LIST, cur))
985
        group.append(s)
986

    
987
        # Does not work, no error, why ??? TODO
988
        """
989
        code = ""
990
        if self._memobj.memcode[num-1].code[3] < 0xFF:
991
            code = self._decode_key(self._memobj.memcode[num-1].code)
992
            code = code.zfill(6)
993
        LOG.debug('CODE "%s"' % code)
994
        s = RadioSetting('dcp_code', 'DCP code',
995
                         RadioSettingValueString(0, 6, code,
996
                                                autopad=False,
997
                                                charset=self.KEY_CHARS))
998
        group.append(s) """
999

    
1000
        pttid = int(_mem.pttid)
1001
        if pttid > len(self.PTTID)-1:
1002
            _mem.pttid = cur = 0
1003
            LOG.debug('PTTID overflow for channel %d' % num)
1004
        cur = self.PTTID[pttid]
1005
        s = RadioSetting('pttid', 'Send DTMF Code (PTT ID)',
1006
                         RadioSettingValueList(self.PTTID, cur))
1007
        group.append(s)
1008

    
1009
        cur = self.SIGNAL[int(_mem.signal)]
1010
        s = RadioSetting('signal', 'PTT ID Code (S-Code)',
1011
                         RadioSettingValueList(self.SIGNAL, cur))
1012
        group.append(s)
1013

    
1014
        s = RadioSetting('learn', 'Use Memory Privacy Code as Tx/Rx DCS (Learn)',
1015
                         RadioSettingValueBoolean(_mem.learn))
1016
        group.append(s)
1017

    
1018
        return group
1019

    
1020
    # TODO Add Code when RadioSettingValueString is fixed
1021
    def _set_extra(self, _mem, mem):
1022
        memidx = mem.number - 1
1023
        _mem.bcl = int(mem.extra['bcl'].value)
1024
        _mem.dcp = int(mem.extra['dcp'].value)
1025
        _mem.pttid = int(mem.extra['pttid'].value)
1026
        _mem.signal = int(mem.extra['signal'].value)
1027
        #self._memobj.memcode[mem.number].code = self._encode_key(mem.extra['dcp_code'].value)
1028
        if (int(mem.extra['learn'].value) > 0) and (self._memobj.memcode[mem.number-1].code[3] == 0xA0):
1029
            _mem.learn = 1
1030
        elif (int(mem.extra['learn'].value) > 0) and (self._memobj.memcode[mem.number-1].code[3] != 0xA0):
1031
            _mem.learn = 0
1032
            raise InvalidValueError(dedent("""\
1033
            >>Use Memory Privacy Code as Tx/Rx DCS (Learn)<< requires
1034
            that a memory code has been previously set for this memory.
1035
            
1036
            Go in 'Settings' -> 'Memory Channel Privacy Codes' and set
1037
            a code for the current memory before enabling 'Learn'.
1038
            """))
1039
        else:
1040
            _mem.learn = 0
1041

    
1042
    def _is_txinh(self, _mem):
1043
        raw_tx = ""
1044
        for i in range(0, 4):
1045
            raw_tx += _mem.txfreq[i].get_raw()
1046
        return raw_tx == "\xFF\xFF\xFF\xFF"
1047

    
1048
    def get_memory(self, num):
1049
        memidx = num - 1
1050
        _mem = self._memobj.memory[memidx]
1051
        _nam = self._memobj.memname[memidx]
1052

    
1053
        mem = chirp_common.Memory()
1054
        mem.number = num
1055
        if int(_mem.rxfreq) == 166666665:
1056
            mem.empty = True
1057
            return mem
1058

    
1059
        mem.name = ''.join([str(c) for c in _nam.name
1060
                            if ord(str(c)) < 127]).rstrip()
1061
        mem.freq = int(_mem.rxfreq) * 10
1062
        offset = (int(_mem.txfreq) - int(_mem.rxfreq)) * 10
1063
        if self._is_txinh(_mem) or _mem.tx_enable == 0:
1064
           mem.duplex = 'off'
1065
           #_mem.txfreq = _mem.rxfreq # TODO REMOVE (force fix broken saves)
1066
        elif offset == 0:
1067
           mem.duplex = ''
1068
           mem.offset = mem.freq
1069
        elif abs(offset) < 100000000:
1070
            mem.duplex = offset < 0 and '-' or '+'
1071
            mem.offset = abs(offset)
1072
        else:
1073
           mem.duplex = 'split'
1074
           mem.offset = int(_mem.txfreq) * 10
1075

    
1076
        mem.power = self.POWER_LEVELS[_mem.power]
1077

    
1078
        if _mem.unknown3_0 and _mem.narrow:
1079
            mem.mode = 'NAM'
1080
        elif _mem.unknown3_0 and not _mem.narrow:
1081
            mem.mode = 'AM'
1082
        elif not _mem.unknown3_0 and _mem.narrow:
1083
            mem.mode = 'NFM'
1084
        elif not _mem.unknown3_0 and not _mem.narrow:
1085
            mem.mode = 'FM'
1086
        else:
1087
            LOG.exception('Failed to get mode for %i' % num)
1088

    
1089
        mem.skip = '' if _mem.scan else 'S'
1090

    
1091
        #LOG.warning('got txtone: %s' % repr(self._decode_tone(_mem.txtone)))
1092
        #LOG.warning('got rxtone: %s' % repr(self._decode_tone(_mem.rxtone)))
1093
        txtone = self._decode_tone(_mem.txtone)
1094
        rxtone = self._decode_tone(_mem.rxtone)
1095
        chirp_common.split_tone_decode(mem, txtone, rxtone)
1096
        try:
1097
            mem.extra = self._get_extra(_mem, num)
1098
        except Exception:
1099
            LOG.exception('Failed to get extra for %i' % num)
1100
        return mem
1101

    
1102
    def set_memory(self, mem):
1103
        memidx = mem.number - 1
1104
        _mem = self._memobj.memory[memidx]
1105
        _nam = self._memobj.memname[memidx]
1106

    
1107
        if mem.empty:
1108
            _mem.set_raw(b'\xff' * 16)
1109
            _nam.set_raw(b'\xff' * 16)
1110
            return
1111

    
1112
        if int(_mem.rxfreq) == 166666665:
1113
            LOG.debug('Initializing new memory %i' % memidx)
1114
            _mem.set_raw(b'\x00' * 16)
1115

    
1116
        _nam.name = mem.name.ljust(12, chr(255)) # with xFF pad (mimic factory behavior)
1117

    
1118
        _mem.rxfreq = mem.freq // 10
1119
        _mem.tx_enable = 1
1120
        if mem.duplex == '':
1121
            _mem.txfreq = mem.freq // 10
1122
        elif mem.duplex == 'split':
1123
            _mem.txfreq = mem.offset // 10
1124
        elif mem.duplex == 'off':
1125
            _mem.tx_enable = 0
1126
            _mem.txfreq = mem.freq // 10 # Optional but keeps compat with vendor software
1127
        elif mem.duplex == '-':
1128
            _mem.txfreq = (mem.freq - mem.offset) // 10
1129
        elif mem.duplex == '+':
1130
            _mem.txfreq = (mem.freq + mem.offset) // 10
1131
        else:
1132
            raise errors.RadioError('Unsupported duplex mode %r' % mem.duplex)
1133

    
1134
        txtone, rxtone = chirp_common.split_tone_encode(mem)
1135
        #LOG.warning('tx tone is %s' % repr(txtone))
1136
        #LOG.warning('rx tone is %s' % repr(rxtone))
1137
        _mem.txtone = self._encode_tone(*txtone)
1138
        _mem.rxtone = self._encode_tone(*rxtone)
1139

    
1140
        try:
1141
            _mem.power = self.POWER_LEVELS.index(mem.power)
1142
        except ValueError:
1143
            _mem.power = 0
1144

    
1145
        if int(_mem.rxfreq) < 30000000:
1146
            _mem.unknown3_0 = mem.mode in [ 'AM', 'NAM' ]
1147
        else:
1148
            _mem.unknown3_0 = 0
1149
        _mem.narrow = mem.mode[0] == 'N'
1150

    
1151
        _mem.scan = mem.skip != 'S'
1152

    
1153
        if mem.extra:
1154
            self._set_extra(_mem, mem)
1155

    
1156
    def sync_out(self):
1157
        try:
1158
            do_upload(self)
1159
        except errors.RadioError:
1160
            # Pass through any real errors we raise
1161
            raise
1162
        except Exception as e:
1163
            # If anything unexpected happens, make sure we raise
1164
            # a RadioError and log the problem
1165
            LOG.exception('Unexpected error during upload')
1166
            raise errors.RadioError('Unexpected error communicating '
1167
                                    'with the radio')
1168

    
1169
    def sync_in(self):
1170
        """Download from radio"""
1171
        try:
1172
            data = do_download(self)
1173
        except errors.RadioError:
1174
            # Pass through any real errors we raise
1175
            raise
1176
        except Exception:
1177
            # If anything unexpected happens, make sure we raise
1178
            # a RadioError and log the problem
1179
            LOG.exception('Unexpected error during download')
1180
            raise errors.RadioError('Unexpected error communicating '
1181
                                    'with the radio')
1182
        self._mmap = data
1183
        self.process_mmap()
1184

    
1185

    
1186
    def process_mmap(self):
1187
        self._memobj = bitwise.parse(MEM_FORMAT_RT490 %
1188
                                     { "memsize": self._memory_size }, self._mmap)
1189

    
1190
    def get_features(self): # GOOD ?
1191
        rf = chirp_common.RadioFeatures()
1192
        rf.has_settings = True
1193
        rf.has_bank = False
1194
        rf.has_cross = True
1195
        rf.has_rx_dtcs = True
1196
        rf.has_dtcs_polarity = True
1197
        rf.has_tuning_step = True
1198
        rf.can_odd_split = True
1199
        rf.has_name = True
1200
        rf.valid_name_length = 12
1201
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
1202
        rf.valid_cross_modes = ["Tone->Tone", "DTCS->", "->DTCS", "Tone->DTCS",
1203
                                "DTCS->Tone", "->Tone", "DTCS->DTCS"]
1204
        rf.valid_power_levels = self.POWER_LEVELS
1205
        rf.valid_duplexes = ["", "+", "-", "split", "off"]
1206
        rf.valid_modes = ["FM", "NFM", "AM", "NAM"]
1207
        rf.memory_bounds = (1, 256)
1208
        rf.valid_tuning_steps = self.TUNING_STEPS
1209
        rf.valid_bands = [(108000000, 136000000),
1210
                          (136000000, 180000000),
1211
                          (200000000, 260000000),
1212
                          (350000000, 400000000),
1213
                          (400000000, 520000000)]
1214
        return rf
1215

    
1216
    @classmethod
1217
    def get_prompts(cls):
1218
        rp = chirp_common.RadioPrompts()
1219
        rp.pre_upload = _(dedent("""\
1220
            This driver is in development and should be considered
1221
            as experimental.
1222
            """))
1223
        rp.experimental = _(dedent("""\
1224
            This driver is in development and should be considered
1225
            as experimental.
1226
            """))
1227
        rp.info = _(dedent("""\
1228
            This driver is in development and should be considered
1229
            as experimental.
1230
            """))
1231
        return rp
1232

    
1233
    def _encode_key(self, key):
1234
        arr = bytearray(4)
1235
        arr[3] = 160
1236
        arr[2] = self.KEY_CHARS.index(key[0]) #<< 4
1237
        arr[2] = arr[2]<<4
1238
        arr[2] |= self.KEY_CHARS.index(key[1])
1239
        arr[1] = self.KEY_CHARS.index(key[2]) #<< 4
1240
        arr[1] = arr[1]<<4
1241
        arr[1] |= self.KEY_CHARS.index(key[3])
1242
        arr[0] = self.KEY_CHARS.index(key[4]) #<< 4
1243
        arr[0] = arr[0]<<4
1244
        arr[0] |= self.KEY_CHARS.index(key[5])
1245
        return arr
1246

    
1247
    def _decode_key(self, key):
1248
        ret = ""
1249
        if key[3] == 0xA0:
1250
            ret += self.KEY_CHARS[key[2] >> 4]
1251
            ret += self.KEY_CHARS[key[2] & 0xF]
1252
            ret += self.KEY_CHARS[key[1] >> 4]
1253
            ret += self.KEY_CHARS[key[1] & 0xF]
1254
            ret += self.KEY_CHARS[key[0] >> 4]
1255
            ret += self.KEY_CHARS[key[0] & 0xF]
1256
            LOG.debug('DCP Code: "%s"' % ret)
1257
        return ret
1258

    
1259
    def _decode_tone(self, toneval):
1260
        if toneval in (0, 0xFFFF):
1261
            #LOG.debug('no tone value: %s' % toneval)
1262
            return None, None, None
1263
        elif toneval < 670:
1264
            toneval = toneval - 1
1265
            index = toneval % len(DTCS_CODES)
1266
            if index != int(toneval):
1267
                pol = 'R'
1268
                # index -= 1
1269
            else:
1270
                pol = 'N'
1271
            return 'DTCS', DTCS_CODES[index], pol
1272
        else:
1273
            return 'Tone', toneval / 10.0, 'N'
1274

    
1275
    def _encode_tone(self, mode, val, pol):
1276
        if not mode:
1277
            return 0x0000
1278
        elif mode == 'Tone':
1279
            return int(val * 10)
1280
        elif mode == 'DTCS':
1281
            index = DTCS_CODES.index(val)
1282
            if pol == 'R':
1283
                index += len(DTCS_CODES)
1284
            index += 1
1285
            #LOG.debug('Encoded dtcs %s/%s to %04x' % (val, pol, index))
1286
            return index
1287
        else:
1288
            raise errors.RadioError('Unsupported tone mode %r' % mode)
1289

    
1290
class MML8629Alias(chirp_common.Alias):
1291
    VENDOR = "MMLradio"
1292
    MODEL = "JC-8629"
1293

    
1294
class JJCC8629Alias(chirp_common.Alias):
1295
    VENDOR = "JJCC"
1296
    MODEL = "JC-8629"
1297

    
1298
class SocotranJC8629Alias(chirp_common.Alias):
1299
    VENDOR = "Socotran"
1300
    MODEL = "JC-8629"
1301

    
1302
class SocotranFB8629Alias(chirp_common.Alias):
1303
    VENDOR = "Socotran"
1304
    MODEL = "FB-8629"
1305

    
1306
class Jianpai8629Alias(chirp_common.Alias):
1307
    VENDOR = "Jianpai"
1308
    MODEL = "8800 Plus"
1309

    
1310
class Boristone8RSAlias(chirp_common.Alias):
1311
    VENDOR = "Boristone"
1312
    MODEL = "8RS"
1313

    
1314
class AbbreeAR869Alias(chirp_common.Alias):
1315
    VENDOR = "Abbree"
1316
    MODEL = "AR-869"
1317

    
1318
class HamGeekHG590Alias(chirp_common.Alias):
1319
    VENDOR = "HamGeek"
1320
    MODEL = "HG-590"
1321

    
1322
@directory.register
1323
class RT490RadioGeneric(RT490Radio):
1324
    ALIASES = [ MML8629Alias, JJCC8629Alias, SocotranJC8629Alias,
1325
               SocotranFB8629Alias, Jianpai8629Alias, Boristone8RSAlias,
1326
               AbbreeAR869Alias, HamGeekHG590Alias ]
1327

    
1328

    
1329
IMHEX_DESC = """
1330
// Perfect tool for binary reverse engineering
1331
// https://github.com/WerWolv/ImHex
1332
// Below is the pattern
1333
"""
1334
IMHEX_PATTERN = """
1335
struct memory {                    // Memory settings
1336
  u8 rxfreq[4];
1337
  u8 txfreq[4];
1338
  u16 rxtone;
1339
  u16 txtone;
1340
  u8 signal;            // int 0->14, Signal 1->15
1341
  u8 pttid;             // [ 'OFF', 'BOT', 'EOT', 'Both']
1342
  u8 dcp_power;           // POWER_LEVELS
1343
  u8 unknown3_0_narrow_unknown3_1_bcl_scan_tx_enable_learn;           // bool ??? TODO
1344
};
1345
struct memname {
1346
  char name[12];
1347
  padding[4];
1348
};
1349
struct memcode {
1350
  u8 code[4];
1351
};
1352
struct custom_ani_names {
1353
  char name[12];
1354
  padding[4];
1355
};
1356
struct anicodes {
1357
  u8 anicode[6];
1358
  padding[10];
1359
};
1360
struct custom_channel_names {
1361
  char name[12];
1362
  padding[4];
1363
};
1364
bitfield workmode {
1365
  b : 4;
1366
  a : 4;
1367
};
1368
struct settings {
1369
  u8 squelch;           // 0: int 0 -> 9
1370
  u8 savemode;          // 1: [ 'OFF', 'Normal', 'Super', 'Deep' ]
1371
  u8 vox;               // 2: off=0, 1 -> 9
1372
  u8 backlight;         // 3: [ 'OFF', '5s', '15s', '20s', '30s', '1m', '2m', '3m' ]
1373
  u8 tdr;               // 4: bool
1374
  u8 timeout;           // 5: n*30seconds, 0-240s
1375
  u8 beep;              // 6: bool
1376
  u8 voice;             // 7: bool
1377
  u8 byte_not_used_10;  // 8: Allways 1
1378
  u8 dtmfst;            // 9: [ 'OFF', 'KB Side Tone', 'ANI Side Tone', 'KB ST + ANI ST' ]
1379
  u8 scanmode;          // 10: [ 'TO', 'CO', 'SE' ]
1380
  u8 pttid;             // 11: [ 'OFF', 'BOT', 'EOT', 'Both']
1381
  u8 pttiddelay;        // 12: [ '0', '100ms', '200ms', '400ms', '600ms', '800ms', '1000ms' ]
1382
  u8 cha_disp;          // 13: [ 'Name', 'Freq', 'Channel ID' ]
1383
  u8 chb_disp;          // 14: [ 'Name', 'Freq', 'Channel ID' ]
1384
  u8 bcl;               // 15: bool
1385
  u8 autolock;          // 0: [ 'OFF', '5s', '10s', 15s' ]
1386
  u8 alarm_mode;        // 1: [ 'Site', 'Tone', 'Code' ]
1387
  u8 alarmsound;        // 2: bool
1388
  u8 txundertdr;        // 3: [ 'OFF', 'A', 'B' ]
1389
  u8 tailnoiseclear;    // 4: [off, on]
1390
  u8 rptnoiseclear;     // 5: n*100ms, 0-1000
1391
  u8 rptnoisedelay;     // 6: n*100ms, 0-1000
1392
  u8 roger;             // 7: bool
1393
  u8 active_channel;    // 8: 0 or 1
1394
  u8 fmradio;           // 9: boolean, inverted
1395
  workmode _workmode;    // 10:up    [ 'VFO', 'CH Mode' ]
1396
  u8 kblock;            // 11: bool                                     // TODO TEST WITH autolock
1397
  u8 powermsg;          // 12: 0=Image / 1=Voltage
1398
  u8 byte_not_used_21;  // 13: Allways 0
1399
  u8 rpttone;           // 14: [ '1000Hz', '1450Hz', '1750Hz', '2100Hz' ]
1400
  u8 byte_not_used_22;  // 15: pad with xFF
1401
  u8 vox_delay;         // 0: [str(float(a)/10)+'s' for a in range(5,21)] '0.5s' to '2.0s'
1402
  u8 timer_menu_quit;   // 1: [ '5s', '10s', '15s', '20s', '25s', '30s', '35s', '40s', '45s', '50s', '60s' ]
1403
  u8 byte_not_used_30;  // 2: pad with xFF
1404
  u8 byte_not_used_31;  // 3: pad with xFF
1405
  u8 enable_killsw;     // 4: bool
1406
  u8 display_ani;       // 5: bool
1407
  u8 byte_not_used_32;  // 6: pad with xFF
1408
  u8 enable_gps;        // 7: bool
1409
  u8 scan_dcs;          // 8: [ 'All', 'Receive', 'Transmit' ]
1410
  u8 ani_id;            // 9: int 0-59 (ANI 1-60)
1411
  u8 rx_time;           // 10: bool
1412
  padding[5];          // 11: Pad xFF
1413
  u8 cha_memidx;        // 0: Memory index when channel A use memories
1414
  u8 byte_not_used_40;
1415
  u8 chb_memidx;        // 2: Memory index when channel B use memories
1416
  u8 byte_not_used_41;
1417
  padding[10];
1418
  u16 fmpreset;
1419
};
1420
struct settings_vfo_chan {
1421
  u8   rxfreq[8];       // 0
1422
  u16 rxtone;          // 8
1423
  u16 txtone;          // 10
1424
  u16 byte_not_used0;  // 12 Pad xFF
1425
  u8   sftd_signal;        // 14 int 0->14, Signal 1->15
1426
  u8   byte_not_used1;  // 15 Pad xFF
1427
  u8   power;           // 16:0 POWER_LEVELS
1428
  u8   fhss_narrow;        // 17 bool true=NFM false=FM
1429
  u8   byte_not_used2;  // 18 Pad xFF but received 0x00 ???
1430
  u8   freqstep;        // 19:3 [ '2.5 KHz', '5.0 KHz', '6.25 KHz', '10.0 KHz', '12.5 KHz', '20.0 KHz', '25.0 KHz', '50.0 KHz' ]
1431
  u8   byte_not_used3;  // 20:4 Pad xFF but received 0x00 ??? TODO
1432
  u8   offset[6];       // 21:5 Freq NN.NNNN (without the dot) TEST TEST
1433
  u8   byte_not_used4;  // 27:11   Pad xFF
1434
  u8   byte_not_used5;  // 28      Pad xFF
1435
  u8   byte_not_used6;  // 29      Pad xFF
1436
  u8   byte_not_used7;  // 30      Pad xFF
1437
  u8   byte_not_used8;  // 31:15   Pad xFF
1438
};
1439
struct settings_vfo {
1440
  settings_vfo_chan vfo_a;
1441
  settings_vfo_chan vfo_b;
1442
};
1443
struct settings_sidekeys {                    // Values from Radio
1444
  u8 pf2_short;         // { '7': 'FM', '10': 'Tx Power', '28': 'Scan', '29': 'Search, '1': 'PPT B' }
1445
  u8 pf2_long;          // { '7': 'FM', '10': 'Tx Power', '28': 'Scan', '29': 'Search' }
1446
  u8 pf3_short;         // { '7': 'FM', '10': 'Tx Power', '28': 'Scan', '29': 'Search' }
1447
  u8 ffpad;             // Pad xFF
1448
};
1449
struct dtmfcode {
1450
  u8 code[5];           // 5 digits DTMF
1451
  padding[11];         // Pad xFF
1452
};
1453
struct settings_dtmf {                // @15296+3x16
1454
  u8 byte_not_used1;    // 0: Pad xFF
1455
  u8 byte_not_used2;    // 1: Pad xFF
1456
  u8 byte_not_used3;    // 2: Pad xFF
1457
  u8 byte_not_used4;    // 3: Pad xFF
1458
  u8 byte_not_used5;    // 4: Pad xFF
1459
  u8 unknown_dtmf;      // 5: 0 TODO ???? wtf is alarmcode/alarmcall TODO
1460
  u8 pttid;             // 6: [off, BOT, EOT, Both]
1461
  u8 dtmf_speed_on;     // 7: ['50ms', '100ms', '200ms', '300ms', '500ms']
1462
  u8 dtmf_speed_off;    // 8:0 ['50ms', '100ms', '200ms', '300ms', '500ms']
1463
};
1464
struct settings_dtmf_global {
1465
  dtmfcode settings_dtmfgroup[15];
1466
  settings_dtmf _settings_dtmf;
1467
};
1468
struct settings_killswitch {
1469
  u8 kill_dtmf[6];      // 0: Kill DTMF
1470
  padding[2];         // Pad xFF
1471
  u8 revive_dtmf[6];    // 8: Revive DTMF
1472
  padding[2];         // Pad xFF
1473
};
1474
struct management_settings {
1475
  u8 unknown_data_0[16];
1476
  u8 unknown_data_1;
1477
  u8 active;            // Bool radio killed (killed=0, active=1)
1478
  padding[46];
1479
};
1480
struct band {
1481
  u8 enable;            // 0 bool / enable-disable Tx on band
1482
  u8 freq_low[2];       // 1 lowest band frequency
1483
  u8 freq_high[2];      // 3 highest band frequency
1484
};
1485
struct settings_bands {
1486
  band band136;  // 0  Settings for 136MHz band
1487
  band band400;  // 5  Settings for 400MHz band
1488
  band band200;  // 10 Settings for 200MHz band
1489
  padding[1];    // 15
1490
  band band350;  // 0  Settings for 350MHz band
1491
  padding[43];// 5
1492
};
1493

    
1494
memory mem[256] @ 0x0000;
1495
memname mname[256] @ 0x1000;
1496
memcode mcode[256] @ 0x2000;
1497
custom_ani_names caninames[10] @ 0x3400;
1498
anicodes anic[60] @ 0x3500;
1499
custom_channel_names ccnames[10] @ 0x3900;
1500
settings _settings @ 0x3A00;
1501
settings_vfo _settings_vfo @ 0x3A40;
1502
settings_sidekeys _settings_sidekeys @ 0x3A80;
1503
settings_dtmf_global _settings_dtmf_global @ 0x3B00;
1504
settings_killswitch _settings_killswitch @ 0x3C00;
1505
management_settings _management_settings @ 0x3F80;
1506
settings_bands _settings_bands @ 0x3FC0;
1507
"""
(22-22/33)