Project

General

Profile

New Model #11270 » uvk5_egzumer_f4hwn.py

driver in english - Jocelyn Maheu, 03/29/2024 07:04 PM

 
1
# Quansheng UV-K5 driver (c) 2023 Jacek Lipkowski <sq5bpf@lipkowski.org>
2
# Adapted For UV-K5 EGZUMER custom software By EGZUMER, JOC2
3
# Re-Adapted For UV-K5 EGZUMER/F4HWN custom software By JOC2
4
#
5
# based on template.py Copyright 2012 Dan Smith <dsmith@danplanet.com>
6
#
7
#
8
# This is a preliminary version of a driver for the UV-K5
9
# It is based on my reverse engineering effort described here:
10
# https://github.com/sq5bpf/uvk5-reverse-engineering
11
#
12
# Warning: this driver is experimental, it may brick your radio,
13
# eat your lunch and mess up your configuration.
14
#
15
#
16
# This program is free software: you can redistribute it and/or modify
17
# it under the terms of the GNU General Public License as published by
18
# the Free Software Foundation, either version 2 of the License, or
19
# (at your option) any later version.
20
#
21
# This program is distributed in the hope that it will be useful,
22
# but WITHOUT ANY WARRANTY; without even the implied warranty of
23
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
24
# GNU General Public License for more details.
25
#
26
# You should have received a copy of the GNU General Public License
27
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
28

    
29
# change/ modification
30
#
31
# 2024-02-09 :section password has been commented, because the new chirp give error with int.
32
# 2024-02-18 : a bug fix has been remove, that seem not work under mac os.. under investigation..
33
#              search for (bugfix calibration)
34

    
35
import struct
36
import logging
37
import wx
38

    
39
from chirp import chirp_common, directory, bitwise, memmap, errors, util
40
from chirp.settings import RadioSetting, RadioSettingGroup, \
41
    RadioSettingValueBoolean, RadioSettingValueList, \
42
    RadioSettingValueInteger, RadioSettingValueString, \
43
    RadioSettings, InvalidValueError
44

    
45
LOG = logging.getLogger(__name__)
46

    
47
# Show the obfuscated version of commands. Not needed normally, but
48
# might be useful for someone who is debugging a similar radio
49
DEBUG_SHOW_OBFUSCATED_COMMANDS = False
50

    
51
# Show the memory being written/received. Not needed normally, because
52
# this is the same information as in the packet hexdumps, but
53
# might be useful for someone debugging some obscure memory issue
54
DEBUG_SHOW_MEMORY_ACTIONS = False
55

    
56
# TODO: remove the driver version when it's in mainline chirp 
57
DRIVER_VERSION = "Quansheng UV-K5/K6/5R driver ver: 2024/03/29 (c) EGZUMER + F4HWN v2.4.0"
58
FIRMWARE_DRIVER_VERSION_UPDATE = "https://github.com/armel/uv-k5-firmware-custom-feat-F4HWN"
59
CHIRP_DRIVER_VERSION_UPDATE = "https://github.com/armel/uv-k5-chirp-driver"
60
 
61
VALEUR_COMPILER = "ENABLE"
62

    
63
MEM_FORMAT = """
64
//#seekto 0x0000;
65
struct {
66
  ul32 freq;
67
  ul32 offset;
68

    
69
// 0x08
70
  u8 rxcode;
71
  u8 txcode;
72

    
73
// 0x0A
74
  u8 txcodeflag:4,
75
  rxcodeflag:4;
76

    
77
// 0x0B
78
  u8 modulation:4,
79
  offsetDir:4;
80

    
81
// 0x0C
82
  u8 __UNUSED1:3,
83
  busyChLockout:1,
84
  txpower:2,
85
  bandwidth:1,
86
  freq_reverse:1;
87

    
88
  // 0x0D
89
  u8 __UNUSED2:4,
90
  dtmf_pttid:3,
91
  dtmf_decode:1;
92

    
93
  // 0x0E
94
  u8 step;
95
  u8 scrambler;
96

    
97
} channel[214];
98

    
99
//#seekto 0xd60;
100
struct {
101
u8 is_scanlist1:1,
102
is_scanlist2:1,
103
compander:2,
104
is_free:1,
105
band:3;
106
} ch_attr[207];
107

    
108
#seekto 0xe40;
109
ul16 fmfreq[20];
110

    
111
#seekto 0xe70;
112
u8 call_channel;
113
u8 squelch;
114
u8 max_talk_time;
115
u8 noaa_autoscan;
116
u8 key_lock;
117
u8 vox_switch;
118
u8 vox_level;
119
u8 mic_gain;
120

    
121

    
122
u8 backlight_min:4,
123
backlight_max:4;
124

    
125
u8 channel_display_mode;
126
u8 crossband;
127
u8 battery_save;
128
u8 dual_watch;
129
u8 backlight_time;
130
u8 ste;
131
u8 freq_mode_allowed;
132

    
133
#seekto 0xe80;
134
u8 ScreenChannel_A;
135
u8 MrChannel_A;
136
u8 FreqChannel_A;
137
u8 ScreenChannel_B;
138
u8 MrChannel_B;
139
u8 FreqChannel_B;
140
u8 NoaaChannel_A;
141
u8 NoaaChannel_B;
142

    
143
#seekto 0xe90;
144

    
145
u8 keyM_longpress_action:7,
146
    button_beep:1;
147

    
148
u8 key1_shortpress_action;
149
u8 key1_longpress_action;
150
u8 key2_shortpress_action;
151
u8 key2_longpress_action;
152
u8 scan_resume_mode;
153
u8 auto_keypad_lock;
154
u8 power_on_dispmode;
155
ul32 password;
156

    
157
#seekto 0xea0;
158
u8 voice;
159
u8 s0_level;
160
u8 s9_level;
161

    
162
#seekto 0xea8;
163
u8 alarm_mode;
164
u8 roger_beep;
165
u8 rp_ste;
166
u8 TX_VFO;
167
u8 Battery_type;
168

    
169
#seekto 0xeb0;
170
char logo_line1[16];
171
char logo_line2[16];
172

    
173
//#seekto 0xed0;
174
struct {
175
    u8 side_tone;
176
    char separate_code;
177
    char group_call_code;
178
    u8 decode_response;
179
    u8 auto_reset_time;
180
    u8 preload_time;
181
    u8 first_code_persist_time;
182
    u8 hash_persist_time;
183
    u8 code_persist_time;
184
    u8 code_interval_time;
185
    u8 permit_remote_kill;
186

    
187
    #seekto 0xee0;
188
    char local_code[3];
189
    #seek 5;
190
    char kill_code[5];
191
    #seek 3;
192
    char revive_code[5];
193
    #seek 3;
194
    char up_code[16];
195
    char down_code[16];
196
} dtmf;
197

    
198
//#seekto 0xf18;
199
u8 slDef;
200
u8 sl1PriorEnab;
201
u8 sl1PriorCh1;
202
u8 sl1PriorCh2;
203
u8 sl2PriorEnab;
204
u8 sl2PriorCh1;
205
u8 sl2PriorCh2;
206

    
207
#seekto 0xf40;
208
u8 int_flock;
209
u8 int_350tx;
210
u8 int_KILLED;
211
u8 int_200tx;
212
u8 int_500tx;
213
u8 int_350en;
214
u8 int_scren;
215

    
216

    
217
u8  backlight_on_TX_RX:2,
218
    AM_fix:1,
219
    mic_bar:1,
220
    battery_text:2,
221
    live_DTMF_decoder:1,
222
    unknown:1;
223

    
224

    
225
#seekto 0xf50;
226
struct {
227
char name[16];
228
} channelname[200];
229

    
230
#seekto 0x1c00;
231
struct {
232
char name[8];
233
char number[3];
234
#seek 5;
235
} dtmfcontact[16];
236

    
237
struct {
238
    struct {
239
        #seekto 0x1E00;
240
        u8 openRssiThr[10];
241
        #seekto 0x1E10;
242
        u8 closeRssiThr[10];
243
        #seekto 0x1E20;
244
        u8 openNoiseThr[10];
245
        #seekto 0x1E30;
246
        u8 closeNoiseThr[10];
247
        #seekto 0x1E40;
248
        u8 closeGlitchThr[10];
249
        #seekto 0x1E50;
250
        u8 openGlitchThr[10];
251
    } sqlBand4_7;
252

    
253
    struct {
254
        #seekto 0x1E60;
255
        u8 openRssiThr[10];
256
        #seekto 0x1E70;
257
        u8 closeRssiThr[10];
258
        #seekto 0x1E80;
259
        u8 openNoiseThr[10];
260
        #seekto 0x1E90;
261
        u8 closeNoiseThr[10];
262
        #seekto 0x1EA0;
263
        u8 closeGlitchThr[10];
264
        #seekto 0x1EB0;
265
        u8 openGlitchThr[10];
266
    } sqlBand1_3;
267

    
268
    #seekto 0x1EC0;
269
    struct {
270
        ul16 level1;
271
        ul16 level2;
272
        ul16 level4;
273
        ul16 level6;
274
    } rssiLevelsBands3_7;
275

    
276
    struct {
277
        ul16 level1;
278
        ul16 level2;
279
        ul16 level4;
280
        ul16 level6;
281
    } rssiLevelsBands1_2;
282

    
283
    struct {
284
        struct {
285
            u8 lower;
286
            u8 center;
287
            u8 upper;
288
        } low;
289
        struct {
290
            u8 lower;
291
            u8 center;
292
            u8 upper;
293
        } mid;
294
        struct {
295
            u8 lower;
296
            u8 center;
297
            u8 upper;
298
        } hi;
299
        #seek 7;
300
    } txp[7];
301

    
302
    #seekto 0x1F40;
303
    ul16 batLvl[6];
304

    
305
    #seekto 0x1F50;
306
    ul16 vox1Thr[10];
307

    
308
    #seekto 0x1F68;
309
    ul16 vox0Thr[10];
310

    
311
    #seekto 0x1F80;
312
    u8 micLevel[5];
313

    
314
    #seekto 0x1F88;
315
    il16 xtalFreqLow;
316

    
317
    #seekto 0x1F8E;
318
    u8 volumeGain;
319
    u8 dacGain;
320
} cal;
321

    
322

    
323
#seekto 0x1FF0;
324
struct {
325
u8 ENABLE_DTMF_CALLING:1,
326
   ENABLE_PWRON_PASSWORD:1,
327
   ENABLE_TX1750:1,
328
   ENABLE_ALARM:1,
329
   ENABLE_VOX:1,
330
   ENABLE_VOICE:1,
331
   ENABLE_NOAA:1,
332
   ENABLE_FMRADIO:1;
333
u8 __UNUSED:3,
334
   ENABLE_AM_FIX:1,
335
   ENABLE_BLMIN_TMP_OFF:1,
336
   ENABLE_RAW_DEMODULATORS:1,
337
   ENABLE_WIDE_RX:1,
338
   ENABLE_FLASHLIGHT:1;
339
} BUILD_OPTIONS;
340

    
341
#seekto 0x1FF2;
342
u8 eeprom0x1ff2;
343
u8 eeprom0x1ff3;
344
u8 eeprom0x1ff4;
345

    
346
u8 set_gui:1,
347
set_met:1,
348
set_lck:1,
349
set_inv:1,
350
set_contrast:4;
351

    
352
u8 set_tot:4,
353
set_eot:4;
354

    
355
u8 set_low:4,
356
set_ptt:4;
357

    
358
u8 eeprom0x1ff8;
359
u8 eeprom0x1ff9;
360
u8 eeprom0x1ffa;
361
u8 eeprom0x1ffb;
362
u8 eeprom0x1ffc;
363
u8 eeprom0x1ffd;
364
u8 eeprom0x1ffe;
365
u8 eeprom0x1fff;
366
"""
367

    
368

    
369
# flags1
370
FLAGS1_OFFSET_NONE = 0b00
371
FLAGS1_OFFSET_MINUS = 0b10
372
FLAGS1_OFFSET_PLUS = 0b01
373

    
374
POWER_HIGH = 0b10
375
POWER_MEDIUM = 0b01
376
POWER_LOW = 0b00
377

    
378
# SET_LOW_POWER f4hwn
379
SET_LOW_LIST = [ "< 20mW", "125mW", "250mW", "500mW", "1W"]
380

    
381
# SET_PTT f4hwn
382
SET_PTT_LIST = ["CLASSIC", "ONEPUSH"]
383

    
384
# SET_TOT and SET_EOT f4hwn
385
SET_TOT_EOT_LIST = ["OFF", "SOUND", "VISUAL", "ALL"]
386

    
387
# SET_OFF_ON f4hwn
388
SET_OFF_ON_LIST = ["OFF", "ON"]
389

    
390
# SET_lck f4hwn
391
SET_LCK_LIST = ["KEYS", "KEYS+PTT"]
392

    
393
# SET_MET SET_GUI f4hwn
394
SET_MET_LIST = ["TINY", "CLASSIC"]
395

    
396

    
397
# dtmf_flags
398
PTTID_LIST = ["OFF", "UP CODE", "DOWN CODE", "UP+DOWN CODE", "APOLLO QUINDAR"]
399

    
400
# power
401
UVK5_POWER_LEVELS = [chirp_common.PowerLevel("Low",  watts=1.50),
402
                     chirp_common.PowerLevel("Med",  watts=3.00),
403
                     chirp_common.PowerLevel("High", watts=5.00),
404
                     ]
405

    
406
# scrambler
407
SCRAMBLER_LIST = ["OFF", "2600Hz", "2700Hz", "2800Hz", "2900Hz", "3000Hz",
408
                  "3100Hz", "3200Hz", "3300Hz", "3400Hz", "3500Hz"]
409
# compander
410
COMPANDER_LIST = ["OFF", "TX", "RX", "TX/RX"]
411

    
412
# rx mode
413
RXMODE_LIST = ["MAIN ONLY", "DUAL RX RESPOND", "CROSS BAND", "MAIN TX DUAL RX"]
414

    
415
# channel display mode
416
CHANNELDISP_LIST = ["Frequency (FREQ)", "CHANNEL NUMBER", "NAME", "Name + Frequency (NAME + FREQ)"]
417

    
418
# TalkTime
419
TALK_TIME_LIST = ["30 sec", "1 min", "2 min", "3 min", "4 min", "5 min",
420
                  "6 min", "7 min", "8 min", "9 min", "15 min"]
421
# Auto Keypad Lock
422
AUTO_KEYPAD_LOCK_LIST = ["OFF", "AUTO"]
423

    
424
# battery save
425
BATSAVE_LIST = ["OFF", "1:1", "1:2", "1:3", "1:4"]
426

    
427
# battery type
428
BATTYPE_LIST = ["1600 mAh", "2200 mAh"]
429
# bat txt
430
BAT_TXT_LIST = ["NONE", "VOLTAGE", "PERCENT"]
431
# Backlight auto mode
432
BACKLIGHT_LIST = ["OFF", "5 sec", "10 sec", "20 sec", "1 min", "2 min", "4 min",
433
                  "Always On (ON)"]
434

    
435
# Backlight LVL
436
BACKLIGHT_LVL_LIST = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]
437

    
438
# Backlight _TX_RX_LIST
439
BACKLIGHT_TX_RX_LIST = ["OFF", "TX", "RX", "TX/RX"]
440

    
441
# steps TODO: change order
442
STEPS = [2.5, 5, 6.25, 10, 12.5, 25, 8.33, 0.01, 0.05, 0.1, 0.25, 0.5, 1, 1.25,
443
         9, 15, 20, 30, 50, 100, 125, 200, 250, 500]
444

    
445
# ctcss/dcs codes
446
TMODES = ["", "Tone", "DTCS", "DTCS"]
447
TONE_NONE = 0
448
TONE_CTCSS = 1
449
TONE_DCS = 2
450
TONE_RDCS = 3
451

    
452

    
453
CTCSS_TONES = [
454
    67.0, 69.3, 71.9, 74.4, 77.0, 79.7, 82.5, 85.4,
455
    88.5, 91.5, 94.8, 97.4, 100.0, 103.5, 107.2, 110.9,
456
    114.8, 118.8, 123.0, 127.3, 131.8, 136.5, 141.3, 146.2,
457
    151.4, 156.7, 159.8, 162.2, 165.5, 167.9, 171.3, 173.8,
458
    177.3, 179.9, 183.5, 186.2, 189.9, 192.8, 196.6, 199.5,
459
    203.5, 206.5, 210.7, 218.1, 225.7, 229.1, 233.6, 241.8,
460
    250.3, 254.1
461
]
462

    
463
# lifted from ft4.py
464
DTCS_CODES = [  # TODO: add negative codes
465
    23,  25,  26,  31,  32,  36,  43,  47,  51,  53,  54,
466
    65,  71,  72,  73,  74,  114, 115, 116, 122, 125, 131,
467
    132, 134, 143, 145, 152, 155, 156, 162, 165, 172, 174,
468
    205, 212, 223, 225, 226, 243, 244, 245, 246, 251, 252,
469
    255, 261, 263, 265, 266, 271, 274, 306, 311, 315, 325,
470
    331, 332, 343, 346, 351, 356, 364, 365, 371, 411, 412,
471
    413, 423, 431, 432, 445, 446, 452, 454, 455, 462, 464,
472
    465, 466, 503, 506, 516, 523, 526, 532, 546, 565, 606,
473
    612, 624, 627, 631, 632, 654, 662, 664, 703, 712, 723,
474
    731, 732, 734, 743, 754
475
]
476

    
477
# flock list extended
478
FLOCK_LIST = ["DEFAULT+ (137-174, 400-470 + Tx200, Tx350, Tx500)",
479
              "FCC HAM (144-148, 420-450)",
480
              "CE HAM (144-146, 430-440)",
481
              "GB HAM (144-148, 430-440)",
482
              "137-174, 400-430",
483
              "137-174, 400-438",
484
              "Disable All",
485
              "Unlock All"]
486

    
487
SCANRESUME_LIST = ["Listen 5 seconds and resume (TIMEOUT)",
488
                   "Listen until signal disapears (CARRIER)",
489
                   "Stop scanning after receiving a signal (STOP)"]
490
WELCOME_LIST = ["Message line 1,Voltage,Sound (ALL)", "Make a 2 short sound (SOUND)", "User message line 1 and line 2 (MESSAGE)", "Battery voltage (VOLTAGE)", "NONE"]
491
VOICE_LIST = ["OFF", "Chinese", "English"]
492

    
493
# ACTIVE CHANNEL
494
TX_VFO_LIST = ["A", "B"]
495
ALARMMODE_LIST = ["SITE", "TONE"]
496
ROGER_LIST = ["OFF", "Roger beep (ROGER)", "MDC data burst (MDC)"]
497
RTE_LIST = ["OFF", "100ms", "200ms", "300ms", "400ms",
498
            "500ms", "600ms", "700ms", "800ms", "900ms", "1000ms"]
499
VOX_LIST = ["OFF", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]
500

    
501
MEM_SIZE = 0x2000  # size of all memory
502
PROG_SIZE = 0x1d00  # size of the memory that we will write
503
MEM_BLOCK = 0x80  # largest block of memory that we can reliably write
504
CAL_START = 0x1E00  # calibration memory start address
505
F4HWN_START =0x1FF2 # calibration F4HWN memory start address
506

    
507
# fm radio supported frequencies
508
FMMIN = 76.0
509
FMMAX = 108.0
510

    
511
# bands supported by the UV-K5
512
BANDS_STANDARD = {
513
        0: [50.0, 76.0],
514
        1: [108.0, 136.9999],
515
        2: [137.0, 173.9999],
516
        3: [174.0, 349.9999],
517
        4: [350.0, 399.9999],
518
        5: [400.0, 469.9999],
519
        6: [470.0, 600.0]
520
        }
521

    
522
BANDS_WIDE = {
523
        0: [18.0, 108.0],
524
        1: [108.0, 136.9999],
525
        2: [137.0, 173.9999],
526
        3: [174.0, 349.9999],
527
        4: [350.0, 399.9999],
528
        5: [400.0, 469.9999],
529
        6: [470.0, 1300.0]
530
        }
531

    
532
SCANLIST_LIST = ["None", "List1", "List2", "Both"]
533
SCANLIST_SELECT_LIST = ["LIST 1", "LIST 2", "All Channel (ALL)"]
534

    
535
DTMF_CHARS = "0123456789ABCD*# "
536
DTMF_CHARS_ID = "0123456789ABCDabcd"
537
DTMF_CHARS_KILL = "0123456789ABCDabcd"
538
DTMF_CHARS_UPDOWN = "0123456789ABCDabcd#* "
539
DTMF_CODE_CHARS = "ABCD*# "
540
DTMF_DECODE_RESPONSE_LIST = ["DO NOTHING", "Local ringing (RING)", "Replay response (REPLY)",
541
                             "Local ringing + reply response (BOTH)"]
542

    
543
KEYACTIONS_LIST = ["NONE",
544
                   "FLASHLIGHT",
545
                   "POWER",
546
                   "MONITOR",
547
                   "SCAN",
548
                   "Voice detection (VOX)",
549
                   "ALARM",
550
                   "FM RADIO",
551
                   "1750Hz TONE",
552
                   "LOCK KEYPAD",
553
                   "Switch main VFO (SWITCH VFO)",
554
                   "Switch frequency/memory mode (VFO/MR)",
555
                   "Switch demodulation (SWITCH DEMODUL)",
556
                   "Put the backlight OFF temporarily (BL_MIN_TMP_OFF)",
557
                   "Change RxMode: *Main only,*Dual RX,*Cross Band,*TX Dual RX (SWITCH RX MODE)",
558
                   "Toggle CLASSIC to ONE PUSH ptt (SWITCH PTT)",                   
559
                   "SWITCH WIDE NARROW"
560
                  ]
561

    
562
MIC_GAIN_LIST = ["+1.1dB", "+4.0dB", "+8.0dB", "+12.0dB", "+15.1dB"]
563

    
564

    
565
def xorarr(data: bytes):
566
    """the communication is obfuscated using this fine mechanism"""
567
    tbl = [22, 108, 20, 230, 46, 145, 13, 64, 33, 53, 213, 64, 19, 3, 233, 128]
568
    ret = b""
569
    idx = 0
570
    for byte in data:
571
        ret += bytes([byte ^ tbl[idx]])
572
        idx = (idx+1) % len(tbl)
573
    return ret
574

    
575

    
576
def calculate_crc16_xmodem(data: bytes):
577
    """
578
    if this crc was used for communication to AND from the radio, then it
579
    would be a measure to increase reliability.
580
    but it's only used towards the radio, so it's for further obfuscation
581
    """
582
    poly = 0x1021
583
    crc = 0x0
584
    for byte in data:
585
        crc = crc ^ (byte << 8)
586
        for _ in range(8):
587
            crc = crc << 1
588
            if crc & 0x10000:
589
                crc = (crc ^ poly) & 0xFFFF
590
    return crc & 0xFFFF
591

    
592

    
593
def _send_command(serport, data: bytes):
594
    """Send a command to UV-K5 radio"""
595
    LOG.debug("Sending command (unobfuscated) len=0x%4.4x:\n%s",
596
              len(data), util.hexprint(data))
597

    
598
    crc = calculate_crc16_xmodem(data)
599
    data2 = data + struct.pack("<H", crc)
600

    
601
    command = struct.pack(">HBB", 0xabcd, len(data), 0) + \
602
        xorarr(data2) + \
603
        struct.pack(">H", 0xdcba)
604
    if DEBUG_SHOW_OBFUSCATED_COMMANDS:
605
        LOG.debug("Sending command (obfuscated):\n%s", util.hexprint(command))
606
    try:
607
        result = serport.write(command)
608
    except Exception as e:
609
        raise errors.RadioError("Error writing data to radio") from e
610
    return result
611

    
612

    
613
def _receive_reply(serport):
614
    header = serport.read(4)
615
    if len(header) != 4:
616
        LOG.warning("Header short read: [%s] len=%i",
617
                    util.hexprint(header), len(header))
618
        raise errors.RadioError("Header short read")
619
    if header[0] != 0xAB or header[1] != 0xCD or header[3] != 0x00:
620
        LOG.warning("Bad response header: %s len=%i",
621
                    util.hexprint(header), len(header))
622
        raise errors.RadioError("Bad response header")
623

    
624
    cmd = serport.read(int(header[2]))
625
    if len(cmd) != int(header[2]):
626
        LOG.warning("Body short read: [%s] len=%i",
627
                    util.hexprint(cmd), len(cmd))
628
        raise errors.RadioError("Command body short read")
629

    
630
    footer = serport.read(4)
631

    
632
    if len(footer) != 4:
633
        LOG.warning("Footer short read: [%s] len=%i",
634
                    util.hexprint(footer), len(footer))
635
        raise errors.RadioError("Footer short read")
636

    
637
    if footer[2] != 0xDC or footer[3] != 0xBA:
638
        LOG.debug("Reply before bad response footer (obfuscated)"
639
                  "len=0x%4.4x:\n%s", len(cmd), util.hexprint(cmd))
640
        LOG.warning("Bad response footer: %s len=%i",
641
                    util.hexprint(footer), len(footer))
642
        raise errors.RadioError("Bad response footer")
643

    
644
    if DEBUG_SHOW_OBFUSCATED_COMMANDS:
645
        LOG.debug("Received reply (obfuscated) len=0x%4.4x:\n%s",
646
                  len(cmd), util.hexprint(cmd))
647

    
648
    cmd2 = xorarr(cmd)
649

    
650
    LOG.debug("Received reply (unobfuscated) len=0x%4.4x:\n%s",
651
              len(cmd2), util.hexprint(cmd2))
652

    
653
    return cmd2
654

    
655

    
656
def _getstring(data: bytes, begin, maxlen):
657
    tmplen = min(maxlen+1, len(data))
658
    ss = [data[i] for i in range(begin, tmplen)]
659
    key = 0
660
    for key, val in enumerate(ss):
661
        if val < ord(' ') or val > ord('~'):
662
            return ''.join(chr(x) for x in ss[0:key])
663
    return ''
664

    
665

    
666
def _sayhello(serport):
667
    hellopacket = b"\x14\x05\x04\x00\x6a\x39\x57\x64"
668

    
669
    tries = 5
670
    while True:
671
        LOG.debug("Sending hello packet")
672
        _send_command(serport, hellopacket)
673
        rep = _receive_reply(serport)
674
        if rep:
675
            break
676
        tries -= 1
677
        if tries == 0:
678
            LOG.warning("Failed to initialise radio")
679
            raise errors.RadioError("Failed to initialize radio")
680
    if rep.startswith(b'\x18\x05'):
681
        raise errors.RadioError("Radio is in programming mode, "
682
                                "restart radio into normal mode")
683
    firmware = _getstring(rep, 4, 24)
684

    
685
    LOG.info("Found firmware: %s", firmware)
686
    return firmware
687

    
688

    
689
def _readmem(serport, offset, length):
690
    LOG.debug("Sending readmem offset=0x%4.4x len=0x%4.4x", offset, length)
691

    
692
    readmem = b"\x1b\x05\x08\x00" + \
693
        struct.pack("<HBB", offset, length, 0) + \
694
        b"\x6a\x39\x57\x64"
695
    _send_command(serport, readmem)
696
    rep = _receive_reply(serport)
697
    if DEBUG_SHOW_MEMORY_ACTIONS:
698
        LOG.debug("readmem Received data len=0x%4.4x:\n%s",
699
                  len(rep), util.hexprint(rep))
700
    return rep[8:]
701

    
702

    
703
def _writemem(serport, data, offset):
704
    LOG.debug("Sending writemem offset=0x%4.4x len=0x%4.4x",
705
              offset, len(data))
706

    
707
    if DEBUG_SHOW_MEMORY_ACTIONS:
708
        LOG.debug("writemem sent data offset=0x%4.4x len=0x%4.4x:\n%s",
709
                  offset, len(data), util.hexprint(data))
710

    
711
    dlen = len(data)
712
    writemem = b"\x1d\x05" + \
713
        struct.pack("<BBHBB", dlen+8, 0, offset, dlen, 1) + \
714
        b"\x6a\x39\x57\x64"+data
715

    
716
    _send_command(serport, writemem)
717
    rep = _receive_reply(serport)
718

    
719
    LOG.debug("writemem Received data: %s len=%i",
720
              util.hexprint(rep), len(rep))
721

    
722
    if (rep[0] == 0x1e and
723
       rep[4] == (offset & 0xff) and
724
       rep[5] == (offset >> 8) & 0xff):
725
        return True
726

    
727
    LOG.warning("Bad data from writemem")
728
    raise errors.RadioError("Bad response to writemem")
729

    
730

    
731
def _resetradio(serport):
732
    resetpacket = b"\xdd\x05\x00\x00"
733
    _send_command(serport, resetpacket)
734

    
735

    
736
def do_download(radio):
737
    """download eeprom from radio"""
738
    serport = radio.pipe
739
    serport.timeout = 0.5
740
    status = chirp_common.Status()
741
    status.cur = 0
742
    status.max = MEM_SIZE
743
    status.msg = "Downloading from radio"
744
    radio.status_fn(status)
745

    
746
    eeprom = b""
747
    f = _sayhello(serport)
748
    if f:
749
        radio.FIRMWARE_VERSION = f
750
    else:
751
        raise errors.RadioError("Failed to initialize radio")
752

    
753
    addr = 0
754
    while addr < MEM_SIZE:
755
        data = _readmem(serport, addr, MEM_BLOCK)
756
        status.cur = addr
757
        radio.status_fn(status)
758

    
759
        if data and len(data) == MEM_BLOCK:
760
            eeprom += data
761
            addr += MEM_BLOCK
762
        else:
763
            raise errors.RadioError("Memory download incomplete")
764

    
765
    return memmap.MemoryMapBytes(eeprom)
766

    
767

    
768
def do_upload(radio):
769
    """upload configuration to radio eeprom"""
770
    serport = radio.pipe
771
    serport.timeout = 0.5
772
    status = chirp_common.Status()
773
    status.cur = 0
774
    status.msg = "Uploading to radio"
775

    
776
    if radio.upload_f4hwn:
777
        status.max = MEM_SIZE-F4HWN_START
778
        start_addr = F4HWN_START
779
        stop_addr = MEM_SIZE
780
    
781
    else:    
782
        if radio.upload_calibration:
783
            status.max = F4HWN_START-CAL_START
784
            start_addr = CAL_START
785
            stop_addr = F4HWN_START
786
        
787
        else:
788
            status.max = PROG_SIZE
789
            start_addr = 0
790
            stop_addr = PROG_SIZE
791

    
792
    radio.status_fn(status)
793

    
794
    f = _sayhello(serport)
795
    if f:
796
        radio.FIRMWARE_VERSION = f
797
    else:
798
        return False
799

    
800
    addr = start_addr
801
    while addr < stop_addr:
802
        dat = radio.get_mmap()[addr:addr+MEM_BLOCK]
803
        _writemem(serport, dat, addr)
804
        status.cur = addr - start_addr
805
        radio.status_fn(status)
806
        if dat:
807
            addr += MEM_BLOCK
808
        else:
809
            raise errors.RadioError("Memory upload incomplete")
810
    status.msg = "Uploaded OK"
811

    
812
    _resetradio(serport)
813

    
814
    return True
815

    
816

    
817
def min_max_def(value, min_val, max_val, default):
818
    """returns value if in bounds or default otherwise"""
819
    if min_val is not None and value < min_val:
820
        return default
821
    if max_val is not None and value > max_val:
822
        return default
823
    return value
824

    
825

    
826
def list_def(value, lst, default):
827
    """return value if is in the list, default otherwise"""
828
    if isinstance(default, str):
829
        default = lst.index(default)
830
    if value < 0 or value >= len(lst):
831
        return default
832
    return value
833

    
834

    
835
@directory.register
836
class UVK5RadioEgzumer(chirp_common.CloneModeRadio):
837
    """Quansheng UV-K5 (egzumer + f4hwn)"""
838
    VENDOR = "Quansheng"
839
    MODEL = "UV-K5 (egzumer + f4hwn)"
840
    BAUD_RATE = 38400
841
    NEEDS_COMPAT_SERIAL = False
842
    FIRMWARE_VERSION = ""
843

    
844
# this change to send power level chan in the calibration but under macos it give error
845
# bugfix calibration : put in comment next line: upload_calibration = False
846
    upload_calibration = False
847
    upload_f4hwn = False
848

    
849
    def _get_bands(self):
850
        is_wide = self._memobj.BUILD_OPTIONS.ENABLE_WIDE_RX \
851
            if self._memobj is not None else True
852
        bands = BANDS_WIDE if is_wide else BANDS_STANDARD
853
        return bands
854

    
855
    def _find_band(self, hz):
856
        mhz = hz/1000000.0
857
        bands = self._get_bands()
858
        for bnd, rng in bands.items():
859
            if rng[0] <= mhz <= rng[1]:
860
                return bnd
861
        return False
862

    
863
    def _get_vfo_channel_names(self):
864
        """generates VFO_CHANNEL_NAMES"""
865
        bands = self._get_bands()
866
        names = []
867
        for bnd, rng in bands.items():
868
            name = f"F{bnd + 1}({round(rng[0])}M-{round(rng[1])}M)"
869
            names.append(name + "A")
870
            names.append(name + "B")
871
        return names
872

    
873
    def _get_specials(self):
874
        """generates SPECIALS"""
875
        specials = {}
876
        for idx, name in enumerate(self._get_vfo_channel_names()):
877
            specials[name] = 200 + idx
878
        return specials
879

    
880
    @classmethod
881
    def get_prompts(cls):
882
        rp = chirp_common.RadioPrompts()
883
        rp.experimental = \
884
            'This is an experimental driver for the Quansheng UV-K5. ' \
885
            'It may harm your radio, or worse. Use at your own risk.\n\n' \
886
            'Before attempting to do any changes please download' \
887
            'the memory image from the radio with chirp ' \
888
            'and keep it. This can be later used to recover the ' \
889
            'original settings. \n\n' \
890
            'some details are not yet implemented'
891
        rp.pre_download = \
892
            "1. Turn radio on.\n" \
893
            "2. Connect cable to mic/spkr connector.\n" \
894
            "3. Make sure connector is firmly connected.\n" \
895
            "4. Click OK to download image from device.\n\n" \
896
            "It may not work if you turn on the radio " \
897
            "with the cable already attached\n"
898
        rp.pre_upload = \
899
            "1. Turn radio on.\n" \
900
            "2. Connect cable to mic/spkr connector.\n" \
901
            "3. Make sure connector is firmly connected.\n" \
902
            "4. Click OK to upload the image to device.\n\n" \
903
            "It may not work if you turn on the radio " \
904
            "with the cable already attached"
905
        return rp
906

    
907
    # Return information about this radio's features, including
908
    # how many memories it has, what bands it supports, etc
909
    def get_features(self):
910
        rf = chirp_common.RadioFeatures()
911
        rf.has_bank = False
912
        rf.valid_dtcs_codes = DTCS_CODES
913
        rf.has_rx_dtcs = True
914
        rf.has_ctone = True
915
        rf.has_settings = True
916
        rf.has_comment = False
917
        rf.valid_name_length = 10
918
        rf.valid_power_levels = UVK5_POWER_LEVELS
919
        rf.valid_special_chans = self._get_vfo_channel_names()
920
        rf.valid_duplexes = ["", "-", "+", "off"]
921

    
922
        steps = STEPS.copy()
923
        steps.sort()
924
        rf.valid_tuning_steps = steps
925

    
926
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
927
        rf.valid_cross_modes = ["Tone->Tone", "Tone->DTCS", "DTCS->Tone",
928
                                "->Tone", "->DTCS", "DTCS->", "DTCS->DTCS"]
929

    
930
        rf.valid_characters = chirp_common.CHARSET_ASCII
931
        rf.valid_modes = ["FM", "NFM", "AM", "NAM", "USB"]
932

    
933
        rf.valid_skips = [""]
934

    
935
        # This radio supports memories 1-200, 201-214 are the VFO memories
936
        rf.memory_bounds = (1, 200)
937

    
938
        # This is what the BK4819 chip supports
939
        # Will leave it in a comment, might be useful someday
940
        # rf.valid_bands = [(18000000,  620000000),
941
        #                  (840000000, 1300000000)
942
        #                  ]
943
        rf.valid_bands = []
944
        bands = self._get_bands()
945
        for _, rng in bands.items():
946
            rf.valid_bands.append(
947
                    (int(rng[0]*1000000), int(rng[1]*1000000)))
948
        return rf
949

    
950
    # Do a download of the radio from the serial port
951
    def sync_in(self):
952
        self._mmap = do_download(self)
953
        self.process_mmap()
954

    
955
    # Do an upload of the radio to the serial port
956
    def sync_out(self):
957
        do_upload(self)
958

    
959
    # Convert the raw byte array into a memory object structure
960
    def process_mmap(self):
961
        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
962

    
963
    # Return a raw representation of the memory object, which
964
    # is very helpful for development
965
    def get_raw_memory(self, number):
966
        return repr(self._memobj.channel[number-1])
967

    
968
    def validate_memory(self, mem):
969
        msgs = super().validate_memory(mem)
970

    
971
        if mem.duplex == 'off':
972
            return msgs
973

    
974
        # find tx frequency
975
        if mem.duplex == '-':
976
            txfreq = mem.freq - mem.offset
977
        elif mem.duplex == '+':
978
            txfreq = mem.freq + mem.offset
979
        else:
980
            txfreq = mem.freq
981

    
982
        # find band
983
        band = self._find_band(txfreq)
984
        if band is False:
985
            msg = f"Transmit frequency {txfreq/1000000.0:.4f}MHz " \
986
                   "is not supported by this radio"
987
            msgs.append(chirp_common.ValidationWarning(msg))
988

    
989
        band = self._find_band(mem.freq)
990
        if band is False:
991
            msg = f"The frequency {mem.freq/1000000.0:%.4f}MHz " \
992
                   "is not supported by this radio"
993
            msgs.append(chirp_common.ValidationWarning(msg))
994

    
995
        return msgs
996

    
997
    def _set_tone(self, mem, _mem):
998
        ((txmode, txtone, txpol),
999
         (rxmode, rxtone, rxpol)) = chirp_common.split_tone_encode(mem)
1000

    
1001
        if txmode == "Tone":
1002
            txtoval = CTCSS_TONES.index(txtone)
1003
            txmoval = 0b01
1004
        elif txmode == "DTCS":
1005
            txmoval = txpol == "R" and 0b11 or 0b10
1006
            txtoval = DTCS_CODES.index(txtone)
1007
        else:
1008
            txmoval = 0
1009
            txtoval = 0
1010

    
1011
        if rxmode == "Tone":
1012
            rxtoval = CTCSS_TONES.index(rxtone)
1013
            rxmoval = 0b01
1014
        elif rxmode == "DTCS":
1015
            rxmoval = rxpol == "R" and 0b11 or 0b10
1016
            rxtoval = DTCS_CODES.index(rxtone)
1017
        else:
1018
            rxmoval = 0
1019
            rxtoval = 0
1020

    
1021
        _mem.rxcodeflag = rxmoval
1022
        _mem.txcodeflag = txmoval
1023
        _mem.rxcode = rxtoval
1024
        _mem.txcode = txtoval
1025

    
1026
    def _get_tone(self, mem, _mem):
1027
        rxtype = _mem.rxcodeflag
1028
        txtype = _mem.txcodeflag
1029
        rx_tmode = TMODES[rxtype]
1030
        tx_tmode = TMODES[txtype]
1031

    
1032
        rx_tone = tx_tone = None
1033

    
1034
        if tx_tmode == "Tone":
1035
            if _mem.txcode < len(CTCSS_TONES):
1036
                tx_tone = CTCSS_TONES[_mem.txcode]
1037
            else:
1038
                tx_tone = 0
1039
                tx_tmode = ""
1040
        elif tx_tmode == "DTCS":
1041
            if _mem.txcode < len(DTCS_CODES):
1042
                tx_tone = DTCS_CODES[_mem.txcode]
1043
            else:
1044
                tx_tone = 0
1045
                tx_tmode = ""
1046

    
1047
        if rx_tmode == "Tone":
1048
            if _mem.rxcode < len(CTCSS_TONES):
1049
                rx_tone = CTCSS_TONES[_mem.rxcode]
1050
            else:
1051
                rx_tone = 0
1052
                rx_tmode = ""
1053
        elif rx_tmode == "DTCS":
1054
            if _mem.rxcode < len(DTCS_CODES):
1055
                rx_tone = DTCS_CODES[_mem.rxcode]
1056
            else:
1057
                rx_tone = 0
1058
                rx_tmode = ""
1059

    
1060
        tx_pol = txtype == 0x03 and "R" or "N"
1061
        rx_pol = rxtype == 0x03 and "R" or "N"
1062

    
1063
        chirp_common.split_tone_decode(mem, (tx_tmode, tx_tone, tx_pol),
1064
                                       (rx_tmode, rx_tone, rx_pol))
1065

    
1066
    # Extract a high-level memory object from the low-level memory map
1067
    # This is called to populate a memory in the UI
1068
    def get_memory(self, number):
1069

    
1070
        mem = chirp_common.Memory()
1071

    
1072
        if isinstance(number, str):
1073
            ch_num = self._get_specials()[number]
1074
            mem.extd_number = number
1075
        else:
1076
            ch_num = number - 1
1077

    
1078
        mem.number = ch_num + 1
1079

    
1080
        _mem = self._memobj.channel[ch_num]
1081

    
1082
        is_empty = False
1083
        # We'll consider any blank (i.e. 0MHz frequency) to be empty
1084
        if (_mem.freq == 0xffffffff) or (_mem.freq == 0):
1085
            is_empty = True
1086

    
1087
        # We'll also look at the channel attributes if a memory has them
1088
        tmpscn = 0
1089
        tmp_comp = 0
1090
        if ch_num < 200:
1091
            _mem3 = self._memobj.ch_attr[ch_num]
1092
            # free memory bit
1093
            is_empty |= _mem3.is_free
1094
            # scanlists
1095
            tmpscn = _mem3.is_scanlist1 + _mem3.is_scanlist2 * 2
1096
            tmp_comp = list_def(_mem3.compander, COMPANDER_LIST, 0)
1097
        elif ch_num < 214:
1098
            att_num = 200 + int((ch_num - 200) / 2)
1099
            _mem3 = self._memobj.ch_attr[att_num]
1100
            is_empty |= _mem3.is_free
1101
            tmp_comp = list_def(_mem3.compander, COMPANDER_LIST, 0)
1102

    
1103
        if is_empty:
1104
            mem.empty = True
1105
            # set some sane defaults:
1106
            mem.power = UVK5_POWER_LEVELS[2]
1107
            mem.extra = RadioSettingGroup("Extra", "extra")
1108

    
1109
            val = RadioSettingValueBoolean(False)
1110
            rs = RadioSetting("busyChLockout", "BusyCL", val)
1111
            mem.extra.append(rs)
1112

    
1113
            val = RadioSettingValueBoolean(False)
1114
            rs = RadioSetting("frev", "FreqRev", val)
1115
            mem.extra.append(rs)
1116

    
1117
            val = RadioSettingValueList(PTTID_LIST)
1118
            rs = RadioSetting("pttid", "PTTID", val)
1119
            mem.extra.append(rs)
1120

    
1121
            val = RadioSettingValueBoolean(False)
1122
            rs = RadioSetting("dtmfdecode", "DTMF decode", val)
1123
            if self._memobj.BUILD_OPTIONS.ENABLE_DTMF_CALLING:
1124
                mem.extra.append(rs)
1125

    
1126
            val = RadioSettingValueList(SCRAMBLER_LIST)
1127
            rs = RadioSetting("scrambler", "Scrambler", val)
1128
            mem.extra.append(rs)
1129

    
1130
            val = RadioSettingValueList(COMPANDER_LIST)
1131
            rs = RadioSetting("compander", "Compander", val)
1132
            mem.extra.append(rs)
1133

    
1134
            val = RadioSettingValueList(SCANLIST_LIST)
1135
            rs = RadioSetting("scanlists", "Scanlists", val)
1136
            mem.extra.append(rs)
1137

    
1138
            # actually the step and duplex are overwritten by chirp based on
1139
            # bandplan. they are here to document sane defaults for IARU r1
1140
            # mem.tuning_step = 25.0
1141
            # mem.duplex = "off"
1142

    
1143
            return mem
1144

    
1145
        if ch_num > 199:
1146
            mem.name = self._get_vfo_channel_names()[ch_num-200]
1147
            mem.immutable = ["name", "scanlists"]
1148
        else:
1149
            _mem2 = self._memobj.channelname[ch_num]
1150
            for char in _mem2.name:
1151
                if str(char) == "\xFF" or str(char) == "\x00":
1152
                    break
1153
                mem.name += str(char)
1154
            mem.name = mem.name.rstrip()
1155

    
1156
        # Convert your low-level frequency to Hertz
1157
        mem.freq = int(_mem.freq)*10
1158
        mem.offset = int(_mem.offset)*10
1159

    
1160
        if mem.offset == 0:
1161
            mem.duplex = ''
1162
        else:
1163
            if _mem.offsetDir == FLAGS1_OFFSET_MINUS:
1164
                if _mem.freq == _mem.offset:
1165
                    # fake tx disable by setting tx to 0 MHz
1166
                    mem.duplex = 'off'
1167
                    mem.offset = 0
1168
                else:
1169
                    mem.duplex = '-'
1170
            elif _mem.offsetDir == FLAGS1_OFFSET_PLUS:
1171
                mem.duplex = '+'
1172
            else:
1173
                mem.duplex = ''
1174

    
1175
        # tone data
1176
        self._get_tone(mem, _mem)
1177

    
1178
        # mode
1179
        temp_modes = self.get_features().valid_modes
1180
        temp_modul = _mem.modulation*2 + _mem.bandwidth
1181
        if temp_modul < len(temp_modes):
1182
            mem.mode = temp_modes[temp_modul]
1183
        elif temp_modul == 5:  # USB with narrow setting
1184
            mem.mode = temp_modes[4]
1185
        elif temp_modul >= len(temp_modes):
1186
            mem.mode = "UNSUPPORTED BY CHIRP"
1187

    
1188
        # tuning step
1189
        tstep = _mem.step
1190
        if tstep < len(STEPS):
1191
            mem.tuning_step = STEPS[tstep]
1192
        else:
1193
            mem.tuning_step = 2.5
1194

    
1195
        # power
1196
        if _mem.txpower == POWER_HIGH:
1197
            mem.power = UVK5_POWER_LEVELS[2]
1198
        elif _mem.txpower == POWER_MEDIUM:
1199
            mem.power = UVK5_POWER_LEVELS[1]
1200
        else:
1201
            mem.power = UVK5_POWER_LEVELS[0]
1202

    
1203
        # We'll consider any blank (i.e. 0MHz frequency) to be empty
1204
        if (_mem.freq == 0xffffffff) or (_mem.freq == 0):
1205
            mem.empty = True
1206
        else:
1207
            mem.empty = False
1208

    
1209
        mem.extra = RadioSettingGroup("Extra", "extra")
1210

    
1211
        # BusyCL
1212
        val = RadioSettingValueBoolean(_mem.busyChLockout)
1213
        rs = RadioSetting("busyChLockout", "Busy Ch Lockout    (BusyCL)", val)
1214
        rs.set_doc('BusyCL: If the channel is Busy, do not allow TX.') 
1215
        mem.extra.append(rs)
1216

    
1217
        # Frequency reverse
1218
        val = RadioSettingValueBoolean(_mem.freq_reverse)
1219
        rs = RadioSetting("frev", "Reverse Frequencies (R)", val)
1220
        rs.set_doc('R: Is this need to be reverse ?') 
1221
        mem.extra.append(rs)
1222

    
1223
        # PTTID
1224
        pttid = list_def(_mem.dtmf_pttid, PTTID_LIST, 0)
1225
        val = RadioSettingValueList(PTTID_LIST, None, pttid)
1226
        rs = RadioSetting("pttid", "PTT ID (PTT ID)", val)
1227
        rs.set_doc('PTT ID :  How do you want the ID sent or sound.\n' + \
1228
                   '* NONE : Nothing send\n' + \
1229
                   '* UP CODE : Send UPCODE when TX.\n' + \
1230
                   '* DOWW CODE : Send DWCODE when return in RX\n' + \
1231
                   '* UP+DOWN Code : Send UPCODE et DWCODE\n' + \
1232
                   '* APOLLO QUINDAR : Send Beep at start and end of the TX')
1233
        mem.extra.append(rs)
1234

    
1235
        # DTMF DECODE
1236
        val = RadioSettingValueBoolean(_mem.dtmf_decode)
1237
        rs = RadioSetting("dtmfdecode", "DTMF decode (D Decd)", val)
1238
        if self._memobj.BUILD_OPTIONS.ENABLE_DTMF_CALLING:
1239
            mem.extra.append(rs)
1240

    
1241
        # Scrambler
1242
        enc = list_def(_mem.scrambler, SCRAMBLER_LIST, 0)
1243
        val = RadioSettingValueList(SCRAMBLER_LIST, None, enc)
1244
        rs = RadioSetting("scrambler", "Scrambler (Scramb)", val)
1245
        rs.set_doc('Scramb: How do you want scrambler on this frequency.') 
1246
        mem.extra.append(rs)
1247

    
1248
        # Compander
1249
        val = RadioSettingValueList(COMPANDER_LIST, None, tmp_comp)
1250
        rs = RadioSetting("compander", "Compander (Compnd)", val)
1251
        rs.set_doc('Compnd: How do you want Compander on this frequency.') 
1252
        mem.extra.append(rs)
1253

    
1254
        val = RadioSettingValueList(SCANLIST_LIST, None, tmpscn)
1255
        rs = RadioSetting("scanlists", "Scanlists (SList)", val)
1256
        rs.set_doc('SList: Is this frequency is part of a scan list.') 
1257
        mem.extra.append(rs)
1258

    
1259
        return mem
1260

    
1261
    def set_settings(self, settings):
1262
        _mem = self._memobj
1263
        for element in settings:
1264
            if not isinstance(element, RadioSetting):
1265
                self.set_settings(element)
1266
                continue
1267

    
1268
            elname = element.get_name()
1269

    
1270
            # basic settings
1271

    
1272
            # VFO_A e80 ScreenChannel_A
1273
            if elname == "VFO_A_chn":
1274
                _mem.ScreenChannel_A = int(element.value)
1275
                if _mem.ScreenChannel_A < 200:
1276
                    _mem.MrChannel_A = _mem.ScreenChannel_A
1277
                elif _mem.ScreenChannel_A < 207:
1278
                    _mem.FreqChannel_A = _mem.ScreenChannel_A
1279
                else:
1280
                    _mem.NoaaChannel_A = _mem.ScreenChannel_A
1281

    
1282
            # VFO_B e83
1283
            elif elname == "VFO_B_chn":
1284
                _mem.ScreenChannel_B = int(element.value)
1285
                if _mem.ScreenChannel_B < 200:
1286
                    _mem.MrChannel_B = _mem.ScreenChannel_B
1287
                elif _mem.ScreenChannel_B < 207:
1288
                    _mem.FreqChannel_B = _mem.ScreenChannel_B
1289
                else:
1290
                    _mem.NoaaChannel_B = _mem.ScreenChannel_B
1291

    
1292
            # TX_VFO  channel selected A,B
1293
            elif elname == "TX_VFO":
1294
                _mem.TX_VFO = int(element.value)
1295

    
1296
            # call channel
1297
            elif elname == "call_channel":
1298
                _mem.call_channel = int(element.value)
1299

    
1300
            # squelch
1301
            elif elname == "squelch":
1302
                _mem.squelch = int(element.value)
1303

    
1304
            # TOT
1305
            elif elname == "tot":
1306
                _mem.max_talk_time = int(element.value)
1307

    
1308
            # NOAA autoscan
1309
            elif elname == "noaa_autoscan":
1310
                _mem.noaa_autoscan = int(element.value)
1311

    
1312
            # VOX
1313
            elif elname == "vox":
1314
                voxvalue = int(element.value)
1315
                _mem.vox_switch = voxvalue > 0
1316
                _mem.vox_level = (voxvalue - 1) if _mem.vox_switch else 0
1317

    
1318
            # mic gain
1319
            elif elname == "mic_gain":
1320
                _mem.mic_gain = int(element.value)
1321

    
1322
            # Channel display mode
1323
            elif elname == "channel_display_mode":
1324
                _mem.channel_display_mode = int(element.value)
1325

    
1326
            # RX Mode
1327
            elif elname == "rx_mode":
1328
                tmptxmode = int(element.value)
1329
                tmpmainvfo = _mem.TX_VFO + 1
1330
                _mem.crossband = tmpmainvfo * bool(tmptxmode & 0b10)
1331
                _mem.dual_watch = tmpmainvfo * bool(tmptxmode & 0b01)
1332

    
1333
            # Battery Save
1334
            elif elname == "battery_save":
1335
                _mem.battery_save = int(element.value)
1336

    
1337
            # Backlight auto mode
1338
            elif elname == "backlight_time":
1339
                _mem.backlight_time = int(element.value)
1340

    
1341
            # Backlight min
1342
            elif elname == "backlight_min":
1343
                _mem.backlight_min = int(element.value)
1344

    
1345
            # Backlight max
1346
            elif elname == "backlight_max":
1347
                _mem.backlight_max = int(element.value)
1348

    
1349
            # Backlight TX_RX
1350
            elif elname == "backlight_on_TX_RX":
1351
                _mem.backlight_on_TX_RX = int(element.value)
1352
            # AM_fix
1353
            elif elname == "AM_fix":
1354
                _mem.AM_fix = int(element.value)
1355

    
1356
            # mic_bar
1357
            elif elname == "mem.mic_bar":
1358
                _mem.mic_bar = int(element.value)
1359

    
1360
            # Batterie txt
1361
            elif elname == "_mem.battery_text":
1362
                _mem.battery_text = int(element.value)
1363

    
1364
            # Tail tone elimination
1365
            elif elname == "ste":
1366
                _mem.ste = int(element.value)
1367

    
1368
            # VFO Open
1369
            elif elname == "freq_mode_allowed":
1370
                _mem.freq_mode_allowed = int(element.value)
1371

    
1372
            # Beep control
1373
            elif elname == "button_beep":
1374
                _mem.button_beep = int(element.value)
1375

    
1376
            # Scan resume mode
1377
            elif elname == "scan_resume_mode":
1378
                _mem.scan_resume_mode = int(element.value)
1379

    
1380
            # Keypad lock
1381
            elif elname == "key_lock":
1382
                _mem.key_lock = int(element.value)
1383

    
1384
            # Auto keypad lock
1385
            elif elname == "auto_keypad_lock":
1386
                _mem.auto_keypad_lock = int(element.value)
1387

    
1388
            # Power on display mode
1389
            elif elname == "welcome_mode":
1390
                _mem.power_on_dispmode = int(element.value)
1391

    
1392
            # Keypad Tone
1393
            elif elname == "voice":
1394
                _mem.voice = int(element.value)
1395

    
1396
            elif elname == "s0_level":
1397
                _mem.s0_level = -int(element.value)
1398

    
1399
            elif elname == "s9_level":
1400
                _mem.s9_level = -int(element.value)
1401

    
1402
#            elif elname == "password":
1403
#                if element.value.get_value() is None or element.value == "":
1404
#                    _mem.password = 0xFFFFFFFF
1405
#                else:
1406
#                    _mem.password = int(element.value)
1407

    
1408
            # Alarm mode
1409
            elif elname == "alarm_mode":
1410
                _mem.alarm_mode = int(element.value)
1411

    
1412
            # Reminding of end of talk
1413
            elif elname == "roger_beep":
1414
                _mem.roger_beep = int(element.value)
1415

    
1416
            # Repeater tail tone elimination
1417
            elif elname == "rp_ste":
1418
                _mem.rp_ste = int(element.value)
1419

    
1420
            # Logo string 1
1421
            elif elname == "logo1":
1422
                bts = str(element.value).rstrip("\x20\xff\x00")+"\x00"*12
1423
                _mem.logo_line1 = bts[0:12]+"\x00\xff\xff\xff"
1424

    
1425
            # Logo string 2
1426
            elif elname == "logo2":
1427
                bts = str(element.value).rstrip("\x20\xff\x00")+"\x00"*12
1428
                _mem.logo_line2 = bts[0:12]+"\x00\xff\xff\xff"
1429

    
1430
            # unlock settings
1431

    
1432
            # FLOCK
1433
            elif elname == "int_flock":
1434
                _mem.int_flock = int(element.value)
1435

    
1436
            # 350TX
1437
            elif elname == "int_350tx":
1438
                _mem.int_350tx = int(element.value)
1439

    
1440
            # KILLED
1441
            elif elname == "int_KILLED":
1442
                _mem.int_KILLED = int(element.value)
1443

    
1444
            # 200TX
1445
            elif elname == "int_200tx":
1446
                _mem.int_200tx = int(element.value)
1447

    
1448
            # 500TX
1449
            elif elname == "int_500tx":
1450
                _mem.int_500tx = int(element.value)
1451

    
1452
            # 350EN
1453
            elif elname == "int_350en":
1454
                _mem.int_350en = int(element.value)
1455

    
1456
            # SCREN
1457
            elif elname == "int_scren":
1458
                _mem.int_scren = int(element.value)
1459

    
1460
            # battery type
1461
            elif elname == "Battery_type":
1462
                _mem.Battery_type = int(element.value)
1463

    
1464
            # set low_power f4hwn
1465
            elif elname == "set_low":
1466
                _mem.set_low = int(element.value)
1467

    
1468
            # set ptt f4hwn
1469
            elif elname == "set_ptt":
1470
                _mem.set_ptt = int(element.value)
1471

    
1472
            # set tot f4hwn
1473
            elif elname == "set_tot":
1474
                _mem.set_tot = int(element.value)
1475

    
1476
            # set eot f4hwn
1477
            elif elname == "set_eot":
1478
                _mem.set_eot = int(element.value)
1479

    
1480
            # set_contrast f4hwn
1481
            elif elname == "set_contrast":
1482
                _mem.set_contrast = int(element.value)
1483

    
1484
            # set inv f4hwn
1485
            elif elname == "set_inv":
1486
                _mem.set_inv = int(element.value)
1487

    
1488
            # set lck f4hwn
1489
            elif elname == "set_lck":
1490
                _mem.set_lck = int(element.value)
1491

    
1492
            # set met f4hwn
1493
            elif elname == "set_met":
1494
                _mem.set_met = int(element.value)
1495

    
1496
            # set gui f4hwn
1497
            elif elname == "set_gui":
1498
                _mem.set_gui = int(element.value)
1499
                               
1500
            # fm radio
1501
            for i in range(1, 21):
1502
                freqname = "FM_" + str(i)
1503
                if elname == freqname:
1504
                    val = str(element.value).strip()
1505
                    try:
1506
                        val2 = int(float(val)*10)
1507
                    except Exception:
1508
                        val2 = 0xffff
1509

    
1510
                    if val2 < FMMIN*10 or val2 > FMMAX*10:
1511
                        val2 = 0xffff
1512
#                        raise errors.InvalidValueError(
1513
#                                "FM radio frequency should be a value "
1514
#                                "in the range %.1f - %.1f" % (FMMIN , FMMAX))
1515
                    _mem.fmfreq[i-1] = val2
1516

    
1517
            # dtmf settings
1518
            if elname == "dtmf_side_tone":
1519
                _mem.dtmf.side_tone = int(element.value)
1520

    
1521
            elif elname == "dtmf_separate_code":
1522
                _mem.dtmf.separate_code = str(element.value)
1523

    
1524
            elif elname == "dtmf_group_call_code":
1525
                _mem.dtmf.group_call_code = element.value
1526

    
1527
            elif elname == "dtmf_decode_response":
1528
                _mem.dtmf.decode_response = int(element.value)
1529

    
1530
            elif elname == "dtmf_auto_reset_time":
1531
                _mem.dtmf.auto_reset_time = int(element.value)
1532

    
1533
            elif elname == "dtmf_preload_time":
1534
                _mem.dtmf.preload_time = int(int(element.value)/10)
1535

    
1536
            elif elname == "dtmf_first_code_persist_time":
1537
                _mem.dtmf.first_code_persist_time = int(int(element.value)/10)
1538

    
1539
            elif elname == "dtmf_hash_persist_time":
1540
                _mem.dtmf.hash_persist_time = int(int(element.value)/10)
1541

    
1542
            elif elname == "dtmf_code_persist_time":
1543
                _mem.dtmf.code_persist_time = \
1544
                        int(int(element.value)/10)
1545

    
1546
            elif elname == "dtmf_code_interval_time":
1547
                _mem.dtmf.code_interval_time = \
1548
                        int(int(element.value)/10)
1549

    
1550
            elif elname == "dtmf_permit_remote_kill":
1551
                _mem.dtmf.permit_remote_kill = \
1552
                        int(element.value)
1553

    
1554
            elif elname == "dtmf_dtmf_local_code":
1555
                k = str(element.value).rstrip("\x20\xff\x00") + "\x00"*3
1556
                _mem.dtmf.local_code = k[0:3]
1557

    
1558
            elif elname == "dtmf_dtmf_up_code":
1559
                k = str(element.value).strip("\x20\xff\x00") + "\x00"*16
1560
                _mem.dtmf.up_code = k[0:16]
1561

    
1562
            elif elname == "dtmf_dtmf_down_code":
1563
                k = str(element.value).rstrip("\x20\xff\x00") + "\x00"*16
1564
                _mem.dtmf.down_code = k[0:16]
1565

    
1566
            elif elname == "dtmf_kill_code":
1567
                k = str(element.value).strip("\x20\xff\x00") + "\x00"*5
1568
                _mem.dtmf.kill_code = k[0:5]
1569

    
1570
            elif elname == "dtmf_revive_code":
1571
                k = str(element.value).strip("\x20\xff\x00") + "\x00"*5
1572
                _mem.dtmf.revive_code = k[0:5]
1573

    
1574
            elif elname == "live_DTMF_decoder":
1575
                _mem.live_DTMF_decoder = int(element.value)
1576

    
1577
            # dtmf contacts
1578
            for i in range(1, 17):
1579
                varname = "DTMF_" + str(i)
1580
                if elname == varname:
1581
                    k = str(element.value).rstrip("\x20\xff\x00") + "\x00"*8
1582
                    _mem.dtmfcontact[i-1].name = k[0:8]
1583

    
1584
                varnumname = "DTMFNUM_" + str(i)
1585
                if elname == varnumname:
1586
                    k = str(element.value).rstrip("\x20\xff\x00") + "\xff"*3
1587
                    _mem.dtmfcontact[i-1].number = k[0:3]
1588

    
1589
            # scanlist stuff
1590
            if elname == "slDef":
1591
                _mem.slDef = int(element.value)
1592

    
1593
            elif elname == "sl1PriorEnab":
1594
                _mem.sl1PriorEnab = int(element.value)
1595

    
1596
            elif elname == "sl2PriorEnab":
1597
                _mem.sl2PriorEnab = int(element.value)
1598

    
1599
            elif elname in ["sl1PriorCh1", "sl1PriorCh2", "sl2PriorCh1",
1600
                            "sl2PriorCh2"]:
1601
                val = int(element.value)
1602

    
1603
                if val > 200 or val < 1:
1604
                    val = 0xff
1605
                else:
1606
                    val -= 1
1607

    
1608
                _mem[elname] = val
1609

    
1610
            if elname == "key1_shortpress_action":
1611
                _mem.key1_shortpress_action = KEYACTIONS_LIST.index(element.value)
1612

    
1613
            elif elname == "key1_longpress_action":
1614
                _mem.key1_longpress_action = KEYACTIONS_LIST.index(element.value)
1615

    
1616
            elif elname == "key2_shortpress_action":
1617
                _mem.key2_shortpress_action = KEYACTIONS_LIST.index(element.value)
1618

    
1619
            elif elname == "key2_longpress_action":
1620
                _mem.key2_longpress_action = KEYACTIONS_LIST.index(element.value)
1621

    
1622
            elif elname == "keyM_longpress_action":
1623
                _mem.keyM_longpress_action = KEYACTIONS_LIST.index(element.value)
1624

    
1625
# this change to send power level chan in the calibration but under macos it give error
1626
# bugfix calibration : remove the comment on next 2 line:
1627
#            elif elname == "upload_calibration":
1628
#                self._upload_calibration = bool(element.value)
1629

    
1630
            elif element.changed() and elname.startswith("_mem.cal."):
1631
                exec(elname + " = element.value.get_value()")
1632

    
1633
    def get_settings(self):
1634
        _mem = self._memobj
1635
        basic = RadioSettingGroup("basic", "Basic Settings")
1636
        advanced = RadioSettingGroup("advanced", "Advanced Settings")
1637
        keya = RadioSettingGroup("keya", "Programmable Keys")
1638
        dtmf = RadioSettingGroup("dtmf", "DTMF Settings")
1639
        dtmfc = RadioSettingGroup("dtmfc", "DTMF Contacts")
1640
        scanl = RadioSettingGroup("scn", "Scan Lists")
1641
        unlock = RadioSettingGroup("unlock", "Unlock Settings")
1642
        fmradio = RadioSettingGroup("fmradio", "FM Radio")
1643
        calibration = RadioSettingGroup("calibration", "Calibration")
1644
        help_user = RadioSettingGroup("help_user", "Help For User")
1645
        
1646
        roinfo = RadioSettingGroup("roinfo", "Driver Information + Link to get latest driver F4HWN")
1647
        top = RadioSettings()
1648
        top.append(basic)
1649
        top.append(advanced)
1650
        top.append(keya)
1651
        top.append(dtmf)
1652
        if _mem.BUILD_OPTIONS.ENABLE_DTMF_CALLING:
1653
            top.append(dtmfc)
1654
        top.append(scanl)
1655
        top.append(unlock)
1656
        if _mem.BUILD_OPTIONS.ENABLE_FMRADIO:
1657
            top.append(fmradio)
1658
        top.append(roinfo)
1659
        top.append(calibration)
1660
        top.append(help_user)
1661
        
1662
        # helper function
1663
        def append_label(radio_setting, label, descr=""):
1664
            if not hasattr(append_label, 'idx'):
1665
                append_label.idx = 0
1666

    
1667
            val = RadioSettingValueString(len(descr), len(descr), descr)
1668
            val.set_mutable(False)
1669
            rs = RadioSetting("label" + str(append_label.idx), label, val)
1670
            append_label.idx += 1
1671
            radio_setting.append(rs)
1672

    
1673
        # Programmable keys
1674
        def get_action(action_num):
1675
            """"get actual key action"""
1676
            has_alarm = self._memobj.BUILD_OPTIONS.ENABLE_ALARM
1677
            has1750 = self._memobj.BUILD_OPTIONS.ENABLE_TX1750
1678
            has_flashlight = self._memobj.BUILD_OPTIONS.ENABLE_FLASHLIGHT
1679

    
1680
            has_backlight_off = self._memobj.BUILD_OPTIONS.ENABLE_BLMIN_TMP_OFF
1681
            
1682
            lst = KEYACTIONS_LIST.copy()
1683
            if not has_alarm:
1684
                lst.remove("ALARM")
1685
            if not has1750:
1686
                lst.remove("1750Hz TONE")
1687
            if not has_flashlight:
1688
                lst.remove("FLASHLIGHT")
1689
            if not has_backlight_off:
1690
                lst.remove("Put the backlight OFF temporarily (BL_MIN_TMP_OFF)")
1691

    
1692
            action_num = int(action_num)
1693
            if action_num >= len(KEYACTIONS_LIST) or \
1694
               KEYACTIONS_LIST[action_num] not in lst:
1695
                action_num = 0
1696
            return lst, KEYACTIONS_LIST[action_num]
1697

    
1698
        val1s = RadioSettingValueList(*get_action(_mem.key1_shortpress_action))
1699
        rs = RadioSetting("key1_shortpress_action",
1700
                          "Side key 1 short press (F1Shrt)", val1s)
1701
        rs.set_doc('F1Shrt: Select what action do you want to do when press this F1 key for a ' + \
1702
                   'SHORT time, F1 key is locate on the side, the first key under the PTT ')                          
1703
        keya.append(rs)
1704

    
1705
        val1l = RadioSettingValueList(*get_action(_mem.key1_longpress_action))
1706
        rs = RadioSetting("key1_longpress_action",
1707
                          "Side key 1 long press (F1Long)", val1l)
1708
        rs.set_doc('F1Long: Select what action do you want to do when press this F1 key for a ' + \
1709
                   'LONG time, F1 key is locate on the side, the first key under the PTT ')                          
1710
        keya.append(rs)
1711

    
1712
        val2s = RadioSettingValueList(*get_action(_mem.key2_shortpress_action))
1713
        rs = RadioSetting("key2_shortpress_action",
1714
                          "Side key 2 short press (F2Shrt)", val2s)
1715
        rs.set_doc('F2Shrt: Select what action do you want to do when press this F2 key for a ' + \
1716
                   'SHORT time, F2 key is locate on the side, the second key under the PTT ')                          
1717
        keya.append(rs)
1718

    
1719
        val2l = RadioSettingValueList(*get_action(_mem.key2_longpress_action))
1720
        rs = RadioSetting("key2_longpress_action",
1721
                          "Side key 2 long press (F2Long)", val2l)
1722
        rs.set_doc('F2Long: Select what action do you want to do when press this F2 key for a ' + \
1723
                   'LONG time, F2 key is locate on the side, the second key under the PTT ')
1724
        keya.append(rs)
1725

    
1726
        valm = RadioSettingValueList(*get_action(_mem.keyM_longpress_action))
1727
        rs = RadioSetting("keyM_longpress_action",
1728
                          "Menu key long press (M Long)", valm)
1729
        rs.set_doc('M Long: Select what action do you want to do when press this M key for a ' + \
1730
                   'LONG time, M key is locate under the LCD, on the left side ')
1731
        keya.append(rs)
1732

    
1733
        # ----------------- DTMF settings
1734

    
1735
        tmpval = str(_mem.dtmf.separate_code)
1736
        if tmpval not in DTMF_CODE_CHARS:
1737
            tmpval = '*'
1738
        val = RadioSettingValueString(1, 1, tmpval)
1739
        val.set_charset(DTMF_CODE_CHARS)
1740
        sep_code_setting = RadioSetting("dtmf_separate_code",
1741
                                        "Separate Code", val)
1742
        sep_code_setting.set_doc('Separate Code: ')
1743

    
1744
        tmpval = str(_mem.dtmf.group_call_code)
1745
        if tmpval not in DTMF_CODE_CHARS:
1746
            tmpval = '#'
1747
        val = RadioSettingValueString(1, 1, tmpval)
1748
        val.set_charset(DTMF_CODE_CHARS)
1749
        group_code_setting = RadioSetting("dtmf_group_call_code",
1750
                                          "Group Call Code", val)
1751
        group_code_setting.set_doc('Group Call Code: ')
1752

    
1753
        tmpval = min_max_def(_mem.dtmf.first_code_persist_time * 10,
1754
                             30, 1000, 300)
1755
        val = RadioSettingValueInteger(30, 1000, tmpval, 10)
1756
        first_code_per_setting = \
1757
            RadioSetting("dtmf_first_code_persist_time",
1758
                         "First code persist time (ms)", val)
1759
        first_code_per_setting.set_doc('First code persist time: How long to you want the first dtmf ' + \
1760
                                       'will be sent in milisecond ')
1761

    
1762
        tmpval = min_max_def(_mem.dtmf.hash_persist_time * 10, 30, 1000, 300)
1763
        val = RadioSettingValueInteger(30, 1000, tmpval, 10)
1764
        spec_per_setting = RadioSetting("dtmf_hash_persist_time",
1765
                                        "#/* persist time (ms)", val)
1766
        spec_per_setting.set_doc('#/* persist time: How long this code # or / or * will be sent in milisecond ')
1767
        
1768
        tmpval = min_max_def(_mem.dtmf.code_persist_time * 10, 30, 1000, 300)
1769
        val = RadioSettingValueInteger(30, 1000, tmpval, 10)
1770
        code_per_setting = RadioSetting("dtmf_code_persist_time",
1771
                                        "Code persist time (ms)", val)
1772
        code_per_setting.set_doc('Code persist time: How long the code will be sent in milisecond ')
1773

    
1774
        tmpval = min_max_def(_mem.dtmf.code_interval_time * 10, 30, 1000, 300)
1775
        val = RadioSettingValueInteger(30, 1000, tmpval, 10)
1776
        code_int_setting = RadioSetting("dtmf_code_interval_time",
1777
                                        "Code interval time (ms)", val)
1778
        code_int_setting.set_doc('Code interval time: How long to wait between each code sent in milisecond ')
1779

    
1780
        tmpval = str(_mem.dtmf.local_code).upper().strip(
1781
                "\x00\xff\x20")
1782
        for i in tmpval:
1783
            if i in DTMF_CHARS_ID:
1784
                continue
1785
            tmpval = "103"
1786
            break
1787
        val = RadioSettingValueString(3, 3, tmpval)
1788
        val.set_charset(DTMF_CHARS_ID)
1789
        ani_id_setting = \
1790
            RadioSetting("dtmf_dtmf_local_code",
1791
                         "Local code (3 chars 0-9 ABCD) (ANI ID)", val)
1792
        ani_id_setting.set_doc('ANI ID: DTMF communication radio ID ')                         
1793

    
1794
        tmpval = str(_mem.dtmf.up_code).upper().strip(
1795
                "\x00\xff\x20")
1796
        for i in tmpval:
1797
            if i in DTMF_CHARS_UPDOWN or i == "":
1798
                continue
1799
            else:
1800
                tmpval = "123"
1801
                break
1802
        val = RadioSettingValueString(1, 16, tmpval)
1803
        val.set_charset(DTMF_CHARS_UPDOWN)
1804
        up_code_setting = \
1805
            RadioSetting("dtmf_dtmf_up_code",
1806
                         "Up code (1-16 chars 0-9 ABCD*#) (UPCode)", val)
1807
        up_code_setting.set_doc('UPCode: DTMF code that is sent at the beginning of transmission ')
1808

    
1809
        tmpval = str(_mem.dtmf.down_code).upper().strip(
1810
                "\x00\xff\x20")
1811
        for i in tmpval:
1812
            if i in DTMF_CHARS_UPDOWN:
1813
                continue
1814
            else:
1815
                tmpval = "456"
1816
                break
1817
        val = RadioSettingValueString(1, 16, tmpval)
1818
        val.set_charset(DTMF_CHARS_UPDOWN)
1819
        dw_code_setting = \
1820
            RadioSetting("dtmf_dtmf_down_code",
1821
                         "Down code (1-16 chars 0-9 ABCD*#) (DWCode)", val)
1822
        dw_code_setting.set_doc('DWCode: DTMF code that is sent at the end of a transmission ')
1823

    
1824
        val = RadioSettingValueBoolean(_mem.dtmf.side_tone)
1825
        dtmf_side_tone_setting = \
1826
            RadioSetting("dtmf_side_tone",
1827
                         "DTMF Sidetone on speaker when sent (D ST)", val)
1828
        dtmf_side_tone_setting.set_doc('D ST: DTMF side tone switch, lets you hear transmitted  ' + \
1829
                                       'tones in the radio speaker ')
1830

    
1831
        tmpval = list_def(_mem.dtmf.decode_response,
1832
                          DTMF_DECODE_RESPONSE_LIST, 0)
1833
        val = RadioSettingValueList(DTMF_DECODE_RESPONSE_LIST, None, tmpval)
1834
        dtmf_resp_setting = RadioSetting("dtmf_decode_response",
1835
                                         "Decode Response (D Resp)", val)
1836
        dtmf_resp_setting.set_doc('D Resp: DTMF decoding response ')
1837

    
1838
        tmpval = min_max_def(_mem.dtmf.auto_reset_time, 5, 60, 10)
1839
        val = RadioSettingValueInteger(5, 60, tmpval)
1840
        d_hold_setting = RadioSetting("dtmf_auto_reset_time",
1841
                                      "Auto reset time (s) (D Hold)", val)
1842
        d_hold_setting.set_doc('D Hold: DTMF auto reset time ')
1843

    
1844
        # D Prel
1845
        tmpval = min_max_def(_mem.dtmf.preload_time * 10, 30, 990, 300)
1846
        val = RadioSettingValueInteger(30, 990, tmpval, 10)
1847
        d_prel_setting = RadioSetting("dtmf_preload_time",
1848
                                      "Pre-load time (ms) (D Prel)", val)
1849
        d_prel_setting.set_doc('D Prel: DTMF pre-load time ')
1850
        
1851
        # D LIVE
1852
        val = RadioSettingValueBoolean(_mem.live_DTMF_decoder)
1853
        d_live_setting = \
1854
            RadioSetting("live_DTMF_decoder", "Displays DTMF codes"
1855
                         " received in the middle of the screen (D Live)", val)
1856
        d_live_setting.set_doc('D Live: Displays DTMF codes received by radio in the middle of the screen ')
1857
        
1858
        val = RadioSettingValueBoolean(_mem.dtmf.permit_remote_kill)
1859
        perm_kill_setting = RadioSetting("dtmf_permit_remote_kill",
1860
                                         "Permit remote kill", val)
1861

    
1862
        tmpval = str(_mem.dtmf.kill_code).upper().strip(
1863
                "\x00\xff\x20")
1864
        for i in tmpval:
1865
            if i in DTMF_CHARS_KILL:
1866
                continue
1867
            else:
1868
                tmpval = "77777"
1869
                break
1870
        if not len(tmpval) == 5:
1871
            tmpval = "77777"
1872
        val = RadioSettingValueString(5, 5, tmpval)
1873
        val.set_charset(DTMF_CHARS_KILL)
1874
        kill_code_setting = RadioSetting("dtmf_kill_code",
1875
                                         "Kill code (5 chars 0-9 ABCD)", val)
1876

    
1877
        tmpval = str(_mem.dtmf.revive_code).upper().strip(
1878
                "\x00\xff\x20")
1879
        for i in tmpval:
1880
            if i in DTMF_CHARS_KILL:
1881
                continue
1882
            else:
1883
                tmpval = "88888"
1884
                break
1885
        if not len(tmpval) == 5:
1886
            tmpval = "88888"
1887
        val = RadioSettingValueString(5, 5, tmpval)
1888
        val.set_charset(DTMF_CHARS_KILL)
1889
        rev_code_setting = RadioSetting("dtmf_revive_code",
1890
                                        "Revive code (5 chars 0-9 ABCD)", val)
1891

    
1892
        val = RadioSettingValueBoolean(_mem.int_KILLED)
1893
        killed_setting = RadioSetting("int_KILLED", "DTMF kill lock", val)
1894

    
1895
        # ----------------- DTMF Contacts
1896

    
1897
        append_label(dtmfc, "DTMF Contacts  (D List)",
1898
                     "All DTMF Contacts are 3 codes "
1899
                     "(valid: 0-9 * # ABCD), "
1900
                     "or an empty string")
1901

    
1902
        for i in range(1, 17):
1903
            varname = "DTMF_"+str(i)
1904
            varnumname = "DTMFNUM_"+str(i)
1905
            vardescr = "DTMF Contact "+str(i)+" name"
1906
            varinumdescr = "DTMF Contact "+str(i)+" number"
1907

    
1908
            cntn = str(_mem.dtmfcontact[i-1].name).strip("\x20\x00\xff")
1909
            cntnum = str(_mem.dtmfcontact[i-1].number).strip("\x20\x00\xff")
1910

    
1911
            val = RadioSettingValueString(0, 8, cntn)
1912
            rs = RadioSetting(varname, vardescr, val)
1913
            dtmfc.append(rs)
1914

    
1915
            val = RadioSettingValueString(0, 3, cntnum)
1916
            val.set_charset(DTMF_CHARS)
1917
            rs = RadioSetting(varnumname, varinumdescr, val)
1918
            dtmfc.append(rs)
1919

    
1920
        # ----------------- Scan Lists
1921

    
1922
        tmpscanl = list_def(_mem.slDef, SCANLIST_SELECT_LIST, 0)
1923
        val = RadioSettingValueList(SCANLIST_SELECT_LIST, None, tmpscanl)
1924
        rs = RadioSetting("slDef", "Default scanlist (SList)", val)
1925
        rs.set_doc('SList: Selects which channel is used by memory channel scanner\n' + \
1926
                    '* ALL : All memory of the 2 LIST\n' + \
1927
                    '* LIST1 : All memory of the LIST 1\n' + \
1928
                    '* LIST2 : All memory of the LIST 2')
1929
        scanl.append(rs)
1930

    
1931
        val = RadioSettingValueBoolean(_mem.sl1PriorEnab)
1932
        rs = RadioSetting("sl1PriorEnab", "List 1 priority channel scan", val)
1933
        rs.set_doc('List 1 priority: Is this list as priority ')        
1934
        scanl.append(rs)
1935

    
1936
        ch_list = ["None"]
1937
        for ch in range(1, 201):
1938
            ch_list.append("Channel M" + str(ch))
1939

    
1940
        tmpch = list_def(_mem.sl1PriorCh1 + 1, ch_list, 0)
1941
        val = RadioSettingValueList(ch_list, None, tmpch)
1942
        rs = RadioSetting("sl1PriorCh1", "List 1 priority channel 1", val)
1943
        rs.set_doc('List 1 priority channel 1: Select the channel you want for priority ')        
1944
        scanl.append(rs)
1945

    
1946
        tmpch = list_def(_mem.sl1PriorCh2 + 1, ch_list, 0)
1947
        val = RadioSettingValueList(ch_list, None, tmpch)
1948
        rs = RadioSetting("sl1PriorCh2", "List 1 priority channel 2", val)
1949
        rs.set_doc('List 1 priority channel 2: Select the channel you want for priority ')
1950
        scanl.append(rs)
1951

    
1952
        val = RadioSettingValueBoolean(_mem.sl2PriorEnab)
1953
        rs = RadioSetting("sl2PriorEnab", "List 2 priority channel scan", val)
1954
        rs.set_doc('List 2 priority: Is this list as priority ')
1955
        scanl.append(rs)
1956

    
1957
        tmpch = list_def(_mem.sl2PriorCh1 + 1, ch_list, 0)
1958
        val = RadioSettingValueList(ch_list, None, tmpch)
1959
        rs = RadioSetting("sl2PriorCh1", "List 2 priority channel 1", val)
1960
        rs.set_doc('List 2 priority channel 1: Select the channel you want for priority ')
1961
        scanl.append(rs)
1962

    
1963
        tmpch = list_def(_mem.sl2PriorCh2 + 1, ch_list, 0)
1964
        val = RadioSettingValueList(ch_list, None, tmpch)
1965
        rs = RadioSetting("sl2PriorCh2", "List 2 priority channel 2", val)
1966
        rs.set_doc('List 2 priority channel 2: Select the channel you want for priority ')
1967
        scanl.append(rs)
1968

    
1969
        # ----------------- Basic settings
1970

    
1971
        ch_list = []
1972
        for ch in range(1, 201):
1973
            ch_list.append("Channel M" + str(ch))
1974
        for bnd in range(1, 8):
1975
            ch_list.append("Band F" + str(bnd))
1976
        if _mem.BUILD_OPTIONS.ENABLE_NOAA:
1977
            for bnd in range(1, 11):
1978
                ch_list.append("NOAA N" + str(bnd))
1979

    
1980
        tmpfreq0 = list_def(_mem.ScreenChannel_A, ch_list, 0)
1981
        val = RadioSettingValueList(ch_list, None, tmpfreq0)
1982
        freq0_setting = RadioSetting("VFO_A_chn",
1983
                                     "VFO A current channel/band", val)
1984
        freq0_setting.set_doc('VFO A current channel/band: To select what is in the VFO A\n' + \
1985
                              '* CHANNEL number M1-M200\n' + \
1986
                              '* BAND F1-F7\n' + \
1987
                              'look in memory tab to view what is program their ')
1988

    
1989
        tmpfreq1 = list_def(_mem.ScreenChannel_B, ch_list, 0)
1990
        val = RadioSettingValueList(ch_list, None, tmpfreq1)
1991
        freq1_setting = RadioSetting("VFO_B_chn",
1992
                                      "VFO B current channel/band", val)
1993
        freq1_setting.set_doc('VFO B current channel/band: To select what is in the VFO B\n' + \
1994
                              '* CHANNEL number M1-M200\n' + \
1995
                              '* BAND F1-F7\n' + \
1996
                              'look in memory tab to view what is program their ')
1997

    
1998
        tmptxvfo = list_def(_mem.TX_VFO, TX_VFO_LIST, 0)
1999
        val = RadioSettingValueList(TX_VFO_LIST, None, tmptxvfo)
2000
        tx_vfo_setting = RadioSetting("TX_VFO", "Main VFO", val)
2001
        tx_vfo_setting.set_doc('Main VFO: To select the frequency that is active in the radio, ' + \
2002
                               '( A is on TOP, B is on the BOTTOM ) ')
2003
        val = RadioSettingValueBoolean(False)
2004

    
2005
        def validate_upload_f4hwn(value):
2006
            return value
2007

    
2008
        val.set_validate_callback(validate_upload_f4hwn)
2009
        Upload_f4hwn = RadioSetting("upload_f4hwn",
2010
                                     "Upload F4HWN setting to radio", val)
2011
        Upload_f4hwn.set_doc('Upload F4HWN: this section of feature is in a different range of memory. ' + \
2012
                             'So it need to be upload to the radio separately, check this case to ' + \
2013
                             'upload ONLY and ONLY all the section of F4HWN feature to the radio. ' + \
2014
                             'It will be realy fast to upload, then the radio will reboot. ' + \
2015
                             'After uploading, uncheck the case to be able to upload all other feature ')
2016
        self.upload_f4hwn = val
2017
                                     
2018
        # Set_Low_Power f4hwn
2019
        tmpsetlow = list_def(_mem.set_low, SET_LOW_LIST, 0)
2020
        val = RadioSettingValueList(SET_LOW_LIST, SET_LOW_LIST[tmpsetlow])
2021
        SetLowSetting = RadioSetting("set_low", "Set the power LOW level to specific power (SetLow)", val)
2022
        SetLowSetting.set_doc('SetLow: When the POWER TX is set to power Low ' + \
2023
                              '(see tab memories in colon POWER to know the TX LEVEL for each channel), ' + \
2024
                              'this Low will be at this power TX level ')
2025
        
2026
        # Set_Ptt f4hwn
2027
        tmpsetptt = list_def(_mem.set_ptt, SET_PTT_LIST, 0)
2028
        val = RadioSettingValueList(SET_PTT_LIST, SET_PTT_LIST[tmpsetptt])
2029
        SetPttSetting = RadioSetting("set_ptt", "Ptt Mode, Set how react the ptt (SetPtt)", val)
2030
        SetPttSetting.set_doc('SetPtt :\n' + \
2031
                              '* CLASSIC : Press = start transmission, release = stop transmission. \n' + \
2032
                              '* ONEPUSH : You no longer need to press the PTT continuously to transmit. \n' + \
2033
                              '                 Simply press once to start transmission, and press a second time to stop. \n' + \
2034
                              ' No more finger cramps :) ')
2035

    
2036
        # Set_tot f4hwn
2037
        tmpsettot = list_def(_mem.set_tot, SET_TOT_EOT_LIST, 0)
2038
        val = RadioSettingValueList(SET_TOT_EOT_LIST, SET_TOT_EOT_LIST[tmpsettot])
2039
        SetTotSetting = RadioSetting("set_tot", "Set TX timeout indicator (SetTot)", val)
2040
        SetTotSetting.set_doc('SetTot: To display the Set TX timeout indicator\n' + \
2041
                                '* NONE : Aucune indication\n' + \
2042
                                '* SOUND : indication sonore\n' + \
2043
                                '* VISUAL : Fait clignoter l\'ecran\n' + \
2044
                                '* ALL : Indicateur sonore et clignotement de l\'ecran.')
2045

    
2046
        # Set_eot f4hwn
2047
        tmpseteot = list_def(_mem.set_eot, SET_TOT_EOT_LIST, 0)
2048
        val = RadioSettingValueList(SET_TOT_EOT_LIST, SET_TOT_EOT_LIST[tmpseteot])
2049
        SetEotSetting = RadioSetting("set_eot", " Set End Of Transmission indicator (SetEot)", val)
2050
        SetEotSetting.set_doc('SetEot: To display the end of the Transmission\n' + \
2051
                                '* NONE : Aucune indication\n' + \
2052
                                '* SOUND : indication sonore\n' + \
2053
                                '* VISUAL : Fait clignoter l\'ecran\n' + \
2054
                                '* ALL : Indicateur sonore et clignotement de l\'ecran.')
2055

    
2056
        # Set_contrast f4hwn
2057
        tmpcontrast = min_max_def(_mem.set_contrast, 0, 15, 11)
2058
        val = RadioSettingValueInteger(0, 15, tmpcontrast)
2059
        contrastSetting = RadioSetting("set_contrast", "Set Contrast Level (SetCtr)", val)
2060
        contrastSetting.set_doc('SetCtr: To set contrast (0 to 15), default: 10 ')
2061
       
2062
        # Set_inv f4hwn
2063
        tmpsetinv = list_def(_mem.set_inv, SET_OFF_ON_LIST, 0)
2064
        val = RadioSettingValueList(SET_OFF_ON_LIST, SET_OFF_ON_LIST[tmpsetinv])
2065
        SetInvSetting = RadioSetting("set_inv", "Set inverse lcd (SetInv)", val)
2066
        SetInvSetting.set_doc('SetInv: The texte display is black on white or texte white on black, ' + \
2067
                              'default: black on white ')
2068

    
2069
        # Set_lck, uses
2070
        tmpsetlck = list_def(_mem.set_lck, SET_LCK_LIST, 0)
2071
        val = RadioSettingValueList(SET_LCK_LIST, SET_LCK_LIST[tmpsetlck])
2072
        SetLckSetting = RadioSetting("set_lck", "Lock the PTT when keypad is lock (SetLck)", val)
2073
        SetLckSetting.set_doc('SetLck: When keypad is lock, do you want to lock the ptt with it or not ')
2074
        
2075
        # Set_met f4hwn
2076
        tmpsetmet = list_def(_mem.set_met, SET_MET_LIST, 0)
2077
        val = RadioSettingValueList(SET_MET_LIST, SET_MET_LIST[tmpsetmet])
2078
        SetMetSetting = RadioSetting("set_met", "Display the Smeter style (SetMet)", val)
2079
        SetMetSetting.set_doc('SetMet: Change the style of the Smeter display, from CLASSIC to TINY ')
2080

    
2081
        # Set_gui f4hwn
2082
        tmpsetgui = list_def(_mem.set_gui, SET_MET_LIST, 0)
2083
        val = RadioSettingValueList(SET_MET_LIST, SET_MET_LIST[tmpsetgui])
2084
        SetGuiSetting = RadioSetting("set_gui", "Display text style (SetGui)", val)
2085
        SetGuiSetting.set_doc('SetGui: Change all the lcd display text style from CLASSIC to TINY ')
2086

    
2087
        tmpsq = min_max_def(_mem.squelch, 0, 9, 1)
2088
        val = RadioSettingValueInteger(0, 9, tmpsq)
2089
        squelch_setting = RadioSetting("squelch", "Squelch (Sql)", val)
2090
        squelch_setting.set_doc('Sql: Squelch sensitivity level 0 to 9, 0 = no restriction ')
2091
        
2092
        ch_list = []
2093
        for ch in range(1, 201):
2094
            ch_list.append("Channel M" + str(ch))
2095

    
2096
        tmpc = list_def(_mem.call_channel, ch_list, 0)
2097
        val = RadioSettingValueList(ch_list, None, tmpc)
2098
        call_channel_setting = RadioSetting("call_channel",
2099
                                            "One key call channel (1 Call)",
2100
                                            val)
2101
        call_channel_setting.set_doc('1 Call: One key call channel, lets you quickly switch to the ' + \
2102
                                     'channel with 9 Call button ')
2103

    
2104
        val = RadioSettingValueBoolean(_mem.key_lock)
2105
        keypad_lock_setting = RadioSetting("key_lock", "Keypad locked", val)
2106
        keypad_lock_setting.set_doc('Keypad locked: Activated the lock on the keypad now ')
2107
        
2108
        tmpval = list_def(_mem.auto_keypad_lock, AUTO_KEYPAD_LOCK_LIST, 1)
2109
        val = RadioSettingValueList(AUTO_KEYPAD_LOCK_LIST, None, tmpval)
2110
        auto_keypad_lock_setting = RadioSetting("auto_keypad_lock",
2111
                         "Auto keypad lock after inactivity ~15sec (KeyLck)", val)
2112
        auto_keypad_lock_setting.set_doc('KeyLck : Keypad lock \n' + \
2113
                                         '* AUTO : After inactivity ~15sec\n' + \
2114
                                         '* OFF : No lock keypad')
2115
        
2116
        tmptot = list_def(_mem.max_talk_time,  TALK_TIME_LIST, 1)
2117
        val = RadioSettingValueList(TALK_TIME_LIST, None, tmptot)
2118
        tx_t_out_setting = RadioSetting("tot",
2119
                                        "Max talk, TX Time Out (TxTOut)", val)
2120
        tx_t_out_setting.set_doc('TxTOut: Select the time limit max transmission\n' + \
2121
                                 'See option (SetTot) of F4HWN')
2122

    
2123
        tmpbatsave = list_def(_mem.battery_save, BATSAVE_LIST, 4)
2124
        val = RadioSettingValueList(BATSAVE_LIST, None, tmpbatsave)
2125
        bat_save_setting = RadioSetting("battery_save",
2126
                                        "Battery save (BatSav)", val)
2127
        bat_save_setting.set_doc('BatSav: Battery save option, a rate between active time and sleep time ')
2128
        
2129
        val = RadioSettingValueBoolean(_mem.noaa_autoscan)
2130
        noaa_auto_scan_setting = RadioSetting("noaa_autoscan",
2131
                                              "NOAA Autoscan (NOAA-S)", val)
2132
        noaa_auto_scan_setting.set_doc('NOAA-S: ')
2133
        
2134
        tmpmicgain = list_def(_mem.mic_gain, MIC_GAIN_LIST, 2)
2135
        val = RadioSettingValueList(MIC_GAIN_LIST, None, tmpmicgain)
2136
        mic_gain_setting = RadioSetting("mic_gain", "Mic Gain (Mic)", val)
2137
        mic_gain_setting.set_doc('Mic: Select the microphone sensitivity level ( Gain) ')
2138
        
2139
        val = RadioSettingValueBoolean(_mem.mic_bar)
2140
        mic_bar_setting = RadioSetting("mic_bar",
2141
                                       "Microphone Bar display (MicBar)", val)
2142
        mic_bar_setting.set_doc('MicBar: Display microphone bar that appears while transmitting ')
2143

    
2144
        tmpchdispmode = list_def(_mem.channel_display_mode,
2145
                                 CHANNELDISP_LIST, 0)
2146
        val = RadioSettingValueList(CHANNELDISP_LIST, None, tmpchdispmode)
2147
        ch_disp_setting = RadioSetting("channel_display_mode",
2148
                                       "Channel display mode (ChDisp)", val)
2149
        ch_disp_setting.set_doc('ChDisp : What to display on screen.\n' + \
2150
                                '* The channel number\n' + \
2151
                                '* The name\n' + \
2152
                                '* The name plus the frequency ')
2153

    
2154
        tmpdispmode = list_def(_mem.power_on_dispmode, WELCOME_LIST, 0)
2155
        val = RadioSettingValueList(WELCOME_LIST, None, tmpdispmode)
2156
        p_on_msg_setting = RadioSetting("welcome_mode",
2157
                                        "Power ON display message (POnMsg)",
2158
                                        val)
2159
        p_on_msg_setting.set_doc('POnMsg : On power up of the radio, what do you want to display: \n' + \
2160
                                 '* ALL : Your Message line 1 + Voltage + Sound\n' + \
2161
                                 '* Sound : beep beep 2\n' + \
2162
                                 '* Message : Your Message line 1 and line 2.\n' + \
2163
                                 '* Voltage : Voltage of the battery\n' + \
2164
                                 '* NONE : do not show anything')
2165

    
2166
        logo1 = str(_mem.logo_line1).strip("\x20\x00\xff") + "\x00"
2167
        logo1 = _getstring(logo1.encode('ascii', errors='ignore'), 0, 12)
2168
        val = RadioSettingValueString(0, 12, logo1)
2169
        logo1_setting = RadioSetting("logo1",
2170
                                     "Message line 1 ( MAX 12 characters ) ",
2171
                                     val)
2172
        logo1_setting.set_doc('Message line 1 : The first line you can put your message,\n' + \
2173
                              'their is a maximum of 12 characters\n' + \
2174
                              'See option (POnMsg) to display it')
2175
                               
2176
        logo2 = str(_mem.logo_line2).strip("\x20\x00\xff") + "\x00"
2177
        logo2 = _getstring(logo2.encode('ascii', errors='ignore'), 0, 12)
2178
        val = RadioSettingValueString(0, 12, logo2)
2179
        logo2_setting = RadioSetting("logo2",
2180
                                     "Message line 2 ( MAX 12 characters )",
2181
                                     val)
2182
        logo2_setting.set_doc('Message line 2 : The first line you can put your message,\n' + \
2183
                              'their is a maximum of 12 characters\n' + \
2184
                              'See option (POnMsg) to display it')
2185

    
2186
        tmpbattxt = list_def(_mem.battery_text, BAT_TXT_LIST, 2)
2187
        val = RadioSettingValueList(BAT_TXT_LIST, None, tmpbattxt)
2188
        bat_txt_setting = RadioSetting("battery_text",
2189
                                       "Battery Level Display (BatTXT)", val)
2190
        bat_txt_setting.set_doc('BatTXT : Display additional battery value on the status bar \n' + \
2191
                                'in % or volts ')
2192

    
2193
        tmpback = list_def(_mem.backlight_time, BACKLIGHT_LIST, 0)
2194
        val = RadioSettingValueList(BACKLIGHT_LIST, None, tmpback)
2195
        back_lt_setting = RadioSetting("backlight_time",
2196
                                       "Backlight time (BackLt)", val)
2197
        back_lt_setting.set_doc('BackLt : Backlight duration, how long the backlight will stay ON \n' + \
2198
                                'after a action end ')
2199

    
2200
        tmpback = list_def(_mem.backlight_min, BACKLIGHT_LVL_LIST, 0)
2201
        val = RadioSettingValueList(BACKLIGHT_LVL_LIST, None, tmpback)
2202
        bl_min_setting = RadioSetting("backlight_min",
2203
                                      "Backlight level min (BLMin)", val)
2204
        bl_min_setting.set_doc('BLMin : Minimal backlight brightness, when the screen backlight turns OFF\n' + \
2205
                               'it will go dim to this value ')
2206

    
2207
        tmpback = list_def(_mem.backlight_max, BACKLIGHT_LVL_LIST, 10)
2208
        val = RadioSettingValueList(BACKLIGHT_LVL_LIST, None, tmpback)
2209
        bl_max_setting = RadioSetting("backlight_max",
2210
                                      "Backlight level max (BLMax)", val)
2211
        bl_max_setting.set_doc('BLMax : Maximal backlight brightness, when the screen backlight turns ON\n' + \
2212
                               'it will turn bright to this value ')
2213
        
2214
        tmpback = list_def(_mem.backlight_on_TX_RX, BACKLIGHT_TX_RX_LIST, 0)
2215
        val = RadioSettingValueList(BACKLIGHT_TX_RX_LIST, None, tmpback)
2216
        blt_trx_setting = RadioSetting("backlight_on_TX_RX",
2217
                                       "Backlight on TX/RX (BltTRX)", val)
2218
        blt_trx_setting.set_doc('BltTRX : Backlight activation on TX or RX or both TX and RX or no backlight\n' + \
2219
                                '* OFF : Stay off\n' + \
2220
                                '* TX : Turn ON when TX\n' + \
2221
                                '* RX : Turn ON when RX\n' + \
2222
                                '* TX/RX : Turn ON when TX or RX')
2223
        
2224
        val = RadioSettingValueBoolean(_mem.button_beep)
2225
        beep_setting = RadioSetting("button_beep",
2226
                                    "Key press beep sound (Beep)", val)
2227
        beep_setting.set_doc('Beep : Keypad press beep sound ')
2228

    
2229
        tmpalarmmode = list_def(_mem.roger_beep, ROGER_LIST, 0)
2230
        val = RadioSettingValueList(ROGER_LIST, None, tmpalarmmode)
2231
        roger_setting = RadioSetting("roger_beep",
2232
                                     "End of transmission beep (Roger)", val)
2233
        roger_setting.set_doc('Roger : Squelch tail eliminator, eliminates noise at the end of a transmission ')
2234

    
2235
        val = RadioSettingValueBoolean(_mem.ste)
2236
        ste_setting = RadioSetting("ste", "Squelch tail elimination (STE)", val)
2237
        ste_setting.set_doc('STE : Squelch tail eliminator, eliminates noise at the end of a transmission ')
2238

    
2239
        tmprte = list_def(_mem.rp_ste, RTE_LIST, 0)
2240
        val = RadioSettingValueList(RTE_LIST, None, tmprte)
2241
        rp_ste_setting = \
2242
            RadioSetting("rp_ste",
2243
                         "Repeater squelch tail elimination (RP STE)", val)
2244
        rp_ste_setting.set_doc('RP STE : Repeater squelch tail eliminator ')
2245

    
2246
        val = RadioSettingValueBoolean(_mem.AM_fix)
2247
        am_fix_setting = RadioSetting("AM_fix",
2248
                                      "AM reception fix (AM Fix)", val)
2249
        am_fix_setting.set_doc('AM Fix : Activates autogain AM fix function ')
2250

    
2251
        tmpvox = min_max_def((_mem.vox_level + 1) * _mem.vox_switch, 0, 10, 0)
2252
        val = RadioSettingValueList(VOX_LIST, None, tmpvox)
2253
        vox_setting = RadioSetting("vox", "Voice-operated switch (VOX)", val)
2254
        vox_setting.set_doc('VOX : Voice TX activation sensitivity level VOX Setting ')
2255
        
2256
        tmprxmode = list_def((bool(_mem.crossband) << 1)
2257
                             + bool(_mem.dual_watch),
2258
                             RXMODE_LIST, 0)
2259
        val = RadioSettingValueList(RXMODE_LIST, None, tmprxmode)
2260
        rx_mode_setting = RadioSetting("rx_mode", "RX Mode (RxMode)", val)
2261
        rx_mode_setting.set_doc('RxMode :\n' + \
2262
                                '* MAIN ONLY : Transmits and listens on the main frequency.\n' + \
2263
                                '* DUAL RX RESPOND : Listens both frequencies, if signal received on the secondary frequency, it locks to \n' + \
2264
                                'it for a couple of seconds so you can respond to the call (DWR).\n' + \
2265
                                '* CROSS BAND : Always transmits on the primary and listens on the secondary frequency (XB).\n' + \
2266
                                '* MAIN TX DUAL RX : Always transmits on the primary, listens to both (DW). ')
2267

    
2268
        val = RadioSettingValueBoolean(_mem.freq_mode_allowed)
2269
        freq_mode_allowed_setting = RadioSetting("freq_mode_allowed",
2270
                                                 "Frequency mode allowed", val)
2271
        freq_mode_allowed_setting.set_doc('Frequency mode allowed ')
2272

    
2273
        tmpscanres = list_def(_mem.scan_resume_mode, SCANRESUME_LIST, 0)
2274
        val = RadioSettingValueList(SCANRESUME_LIST, None, tmpscanres)
2275
        scn_rev_setting = RadioSetting("scan_resume_mode",
2276
                                       "Scan resume mode (ScnRev)", val)
2277
        scn_rev_setting.set_doc('ScnRev : Scan resume mode \n' + \
2278
                                '* CARRIER : Resume scan after signal disappears.\n' + \
2279
                                '* TIMEOUT : Resume scan after 5 seconds pause.\n' + \
2280
                                '* STOP : After receiving a signal, stop the scan ')
2281
        
2282
        tmpvoice = list_def(_mem.voice, VOICE_LIST, 0)
2283
        val = RadioSettingValueList(VOICE_LIST, None, tmpvoice)
2284
        voice_setting = RadioSetting("voice", "Voice", val)
2285
        voice_setting.set_doc('Voice : Voice signal :\n' + \
2286
                                '* NONE : No voice\n' + \
2287
                                '* CHINA : China voice.\n' + \
2288
                                '* ENGLISH : English voice')
2289

    
2290
        tmpalarmmode = list_def(_mem.alarm_mode, ALARMMODE_LIST, 0)
2291
        val = RadioSettingValueList(ALARMMODE_LIST, None, tmpalarmmode)
2292
        alarm_setting = RadioSetting("alarm_mode", "Alarm mode", val)
2293

    
2294
        # ----------------- Extra settings
2295

    
2296
        # S-meter
2297
        tmp_s0 = -int(_mem.s0_level)
2298
        tmp_s9 = -int(_mem.s9_level)
2299

    
2300
        if tmp_s0 not in range(-200, -91) or tmp_s9 not in range(-160, -51) \
2301
           or tmp_s9 < tmp_s0+9:
2302

    
2303
            tmp_s0 = -130
2304
            tmp_s9 = -76
2305
        val = RadioSettingValueInteger(-200, -90, tmp_s0)
2306
        s0_level_setting = RadioSetting("s0_level",
2307
                                        "S-meter S0 level [dBm]", val)
2308
        s0_level_setting.set_doc('S-meter S0 level [dBm]: To set the level calibration for S0 ')
2309

    
2310
        val = RadioSettingValueInteger(-160, -50, tmp_s9)
2311
        s9_level_setting = RadioSetting("s9_level",
2312
                                        "S-meter S9 level [dBm]", val)
2313
        s9_level_setting.set_doc('S-meter S9 level [dBm]: To set the level calibration for S9 ')
2314

    
2315
        # Battery Type
2316
        tmpbtype = list_def(_mem.Battery_type, BATTYPE_LIST, 0)
2317
        val = RadioSettingValueList(BATTYPE_LIST, BATTYPE_LIST[tmpbtype])
2318
        bat_type_setting = RadioSetting("Battery_type",
2319
                                        "Battery Type (BatTyp)", val)
2320
        bat_type_setting.set_doc('BatTyp : What type of battery the radio is using, this affect \n' + \
2321
                                 'the level value of the battery in the display ')
2322

    
2323
        # Power on password
2324
#        def validate_password(value):
2325
#            value = value.strip(" ")
2326
#            if value.isdigit():
2327
#                return value.zfill(6)
2328
#            if value != "":
2329
#                raise InvalidValueError("Power on password "
2330
#                                        "can only have digits")
2331
#            return ""
2332

    
2333
#        pswd_str = str(int(_mem.password)).zfill(6) \
2334
#            if _mem.password < 1000000 else ""
2335
#        val = RadioSettingValueString(0, 6, pswd_str)
2336
#        val.set_validate_callback(validate_password)
2337
#        pswd_setting = RadioSetting("password", "Power on password", val)
2338

    
2339
        # ----------------- FM radio
2340

    
2341
        append_label(fmradio, "Channel Memory Radio (MR)", "Frequency [MHz]")
2342

    
2343
        for i in range(1, 21):
2344
            fmfreq = _mem.fmfreq[i-1]/10.0
2345
            freq_name = str(fmfreq)
2346
            if fmfreq < FMMIN or fmfreq > FMMAX:
2347
                freq_name = ""
2348
            rs = RadioSetting("FM_" + str(i), "Ch " + str(i),
2349
                              RadioSettingValueString(0, 5, freq_name))
2350
            rs.set_doc('Radio Frequency : Enter the radio frequency in your area in MHZ, example: 96.9\n' + \
2351
                       'To listen the FM radio, Long press on the 5 key, then if you want to scan FM radio\n' + \
2352
                       'Station near you, press the *. but scan will erase the existing FM radio list ')
2353
            fmradio.append(rs)
2354

    
2355
        # ----------------- Unlock settings
2356

    
2357
        # F-LOCK
2358
        def validate_int_flock(value):
2359
            mem_val = self._memobj.int_flock
2360
            if mem_val != 7 and value == FLOCK_LIST[7]:
2361
                msg = "\"" + value + "\" can only be enabled from radio menu"
2362
                raise InvalidValueError(msg)
2363
            return value
2364

    
2365
        tmpflock = list_def(_mem.int_flock, FLOCK_LIST, 0)
2366
        val = RadioSettingValueList(FLOCK_LIST, None, tmpflock)
2367
        val.set_validate_callback(validate_int_flock)
2368
        f_lock_setting = RadioSetting("int_flock",
2369
                                      "TX Frequency Lock (F Lock)", val)
2370
        f_lock_setting.set_doc('F Lock : Sets the TX frequency band plan ')                                     
2371

    
2372
        val = RadioSettingValueBoolean(_mem.int_200tx)
2373
        tx200_setting = RadioSetting("int_200tx",
2374
                                     "Unlock 174-350MHz TX (Tx 200)", val)
2375
        tx200_setting.set_doc('Enables transmission on 200MHz ')
2376

    
2377
        val = RadioSettingValueBoolean(_mem.int_350tx)
2378
        tx350_setting = RadioSetting("int_350tx",
2379
                                     "Unlock 350-400MHz TX (Tx 350)", val)
2380
        tx350_setting.set_doc('Enables transmission on 350MHz ')
2381
        
2382
        val = RadioSettingValueBoolean(_mem.int_500tx)
2383
        tx500_setting = RadioSetting("int_500tx",
2384
                                     "Unlock 500-600MHz TX (Tx 500)", val)
2385
        tx500_setting.set_doc('Enables transmission on 500MHz ')
2386
        
2387
        val = RadioSettingValueBoolean(_mem.int_350en)
2388
        en350_setting = RadioSetting("int_350en",
2389
                                     "Unlock 350-400MHz RX (350 En)", val)
2390
        en350_setting.set_doc('Enables reception on 350MHz ')
2391
        
2392
        val = RadioSettingValueBoolean(_mem.int_scren)
2393
        en_scrambler_setting = RadioSetting("int_scren",
2394
                                            "Scrambler enabled (ScraEn)", val)
2395
        en_scrambler_setting.set_doc('Enables Scrambler ')
2396
        
2397
        # ----------------- Driver Info
2398

    
2399
        if self.FIRMWARE_VERSION == "":
2400
            firmware = "To get the firmware version please download" \
2401
                       "the image from the radio first"
2402
        else:
2403
            firmware = self.FIRMWARE_VERSION
2404

    
2405
        append_label(roinfo,
2406
                     "=" * 6 + " Firmware F4HWN " + "=" * 300, "=" * 300)
2407

    
2408
        append_label(roinfo, "Firmware Version", firmware)
2409
        val = RadioSettingValueString(0,75,FIRMWARE_DRIVER_VERSION_UPDATE)
2410
        rs = RadioSetting("Update","Latest Firmware F4HWN" + ", copy link:(CTRL-C), paste:(CTRL-V) to your browser -> ", val)                      
2411
        rs.set_doc('Be sure you have the latest firmware available ')
2412
        roinfo.append(rs)
2413
        
2414
        append_label(roinfo,
2415
                     "=" * 6 + " Chirp Driver F4HWN " + "=" * 300, "=" * 300)
2416
                     
2417
        append_label(roinfo, "Driver Chirp Version", DRIVER_VERSION)
2418
        val = RadioSettingValueString(0,75,CHIRP_DRIVER_VERSION_UPDATE)
2419
        rs = RadioSetting("Update1","Latest Driver " + self.MODEL + ", copy link:(CTRL-C), paste:(CTRL-V) to your browser -> ", val)                      
2420
        rs.set_doc('Be sure you have the latest chirp driver available ')
2421
        roinfo.append(rs)
2422

    
2423
        append_label(roinfo,
2424
                     "=" * 6 + "=" * 300, "=" * 300)
2425

    
2426
        append_label(roinfo, "The driver module is done by VE2ZJM", )
2427

    
2428
        append_label(roinfo,
2429
                     "=" * 6 + " Special Thank to : " + "=" * 300, "=" * 300)
2430

    
2431
        append_label(roinfo, "The french traduction was done by FLIXX", )
2432

    
2433
                
2434
        # ----------------- Calibration
2435

    
2436
        val = RadioSettingValueBoolean(False)
2437

    
2438
        def validate_upload_calibration(value):
2439
            if value and not self.upload_calibration:
2440
                msg = "This option may brake your radio!!!\n" \
2441
                    "You are doing this at your own risk.\n" \
2442
                    "Make sure you have a working calibration backup.\n" \
2443
                    "Don't use it unless you know what you're doing."
2444
                ret = wx.MessageBox(msg, "Warning", wx.OK | wx.CANCEL |
2445
                                    wx.CANCEL_DEFAULT | wx.ICON_WARNING)
2446
                value = ret == wx.OK
2447
            self.upload_calibration = value
2448
            return value
2449

    
2450
        val.set_validate_callback(validate_upload_calibration)
2451
        radio_setting = RadioSetting("upload_calibration",
2452
                                     "Upload calibration", val)
2453
        radio_setting.set_doc('To Upload to the radio only the setting in the calibration section, ' + \
2454
                              'you need to check the case, then upload to the radio ')
2455
        calibration.append(radio_setting)
2456

    
2457
        radio_setting_group = RadioSettingGroup("squelch_calibration",
2458
                                                "Squelch")
2459
        calibration.append(radio_setting_group)
2460

    
2461
        bands = {"sqlBand1_3": "Frequency Band 1-3",
2462
                 "sqlBand4_7": "Frequency Band 4-7"}
2463
        for bnd, bndn in bands.items():
2464
            append_label(radio_setting_group,
2465
                         "=" * 6 + " " + bndn + " " + "=" * 300, "=" * 300)
2466
            for sql in range(0, 10):
2467
                prefix = "_mem.cal." + bnd + "."
2468
                postfix = "[" + str(sql) + "]"
2469
                append_label(radio_setting_group, "Squelch " + str(sql))
2470

    
2471
                name = prefix + "openRssiThr" + postfix
2472
                tempval = min_max_def(eval(name), 0, 255, 0)
2473
                val = RadioSettingValueInteger(0, 255, tempval)
2474
                radio_setting = RadioSetting(name, "RSSI threshold open", val)
2475
                radio_setting_group.append(radio_setting)
2476

    
2477
                name = prefix + "closeRssiThr" + postfix
2478
                tempval = min_max_def(eval(name), 0, 255, 0)
2479
                val = RadioSettingValueInteger(0, 255, tempval)
2480
                radio_setting = RadioSetting(name, "RSSI threshold close", val)
2481
                radio_setting_group.append(radio_setting)
2482

    
2483
                name = prefix + "openNoiseThr" + postfix
2484
                tempval = min_max_def(eval(name), 0, 127, 0)
2485
                val = RadioSettingValueInteger(0, 127, tempval)
2486
                radio_setting = RadioSetting(name, "Noise threshold open", val)
2487
                radio_setting_group.append(radio_setting)
2488

    
2489
                name = prefix + "closeNoiseThr" + postfix
2490
                tempval = min_max_def(eval(name), 0, 127, 0)
2491
                val = RadioSettingValueInteger(0, 127, tempval)
2492
                radio_setting = RadioSetting(name, "Noise threshold close",
2493
                                             val)
2494
                radio_setting_group.append(radio_setting)
2495

    
2496
                name = prefix + "openGlitchThr" + postfix
2497
                tempval = min_max_def(eval(name), 0, 255, 0)
2498
                val = RadioSettingValueInteger(0, 255, tempval)
2499
                radio_setting = RadioSetting(name, "Glitch threshold open",
2500
                                             val)
2501
                radio_setting_group.append(radio_setting)
2502

    
2503
                name = prefix + "closeGlitchThr" + postfix
2504
                tempval = min_max_def(eval(name), 0, 255, 0)
2505
                val = RadioSettingValueInteger(0, 255, tempval)
2506
                radio_setting = RadioSetting(name, "Glitch threshold close",
2507
                                             val)
2508
                radio_setting_group.append(radio_setting)
2509

    
2510
        radio_setting_group = RadioSettingGroup("rssi_level_calibration",
2511
                                                "RSSI levels")
2512
        calibration.append(radio_setting_group)
2513

    
2514
        bands = {"rssiLevelsBands1_2": "1-2 ", "rssiLevelsBands3_7": "3-7 "}
2515
        for bnd, bndn in bands.items():
2516
            append_label(radio_setting_group,
2517
                         "=" * 6 +
2518
                         " RSSI levels for QS original small bar graph, bands "
2519
                         + bndn + "=" * 300, "=" * 300)
2520
            for lvl in [1, 2, 4, 6]:
2521
                name = "_mem.cal." + bnd + ".level" + str(lvl)
2522
                tempval = min_max_def(eval(name), 0, 65535, 0)
2523
                val = RadioSettingValueInteger(0, 65535, tempval)
2524
                radio_setting = RadioSetting(name, "Level " + str(lvl), val)
2525
                radio_setting_group.append(radio_setting)
2526

    
2527
#
2528

    
2529
        radio_setting_group = RadioSettingGroup("tx_power_calibration",
2530
                                                "TX power")
2531
        calibration.append(radio_setting_group)
2532

    
2533
        for bnd in range(0, 7):
2534
            append_label(radio_setting_group, "=" * 6 + " TX power band "
2535
                         + str(bnd+1) + " " + "=" * 300, "=" * 300)
2536
            powers = {"low": "Low", "mid": "Medium", "hi": "High"}
2537
            for pwr, pwrn in powers.items():
2538
                append_label(radio_setting_group, pwrn)
2539
                bounds = ["lower", "center", "upper"]
2540
                for bound in bounds:
2541
                    name = f"_mem.cal.txp[{bnd}].{pwr}.{bound}"
2542
                    tempval = min_max_def(eval(name), 0, 255, 0)
2543
                    val = RadioSettingValueInteger(0, 255, tempval)
2544
                    radio_setting = RadioSetting(name, bound.capitalize(), val)
2545
                    radio_setting_group.append(radio_setting)
2546

    
2547
#
2548

    
2549
        radio_setting_group = RadioSettingGroup("battery_calibration",
2550
                                                "Battery")
2551
        calibration.append(radio_setting_group)
2552

    
2553
        for lvl in range(0, 6):
2554
            name = "_mem.cal.batLvl[" + str(lvl) + "]"
2555
            temp_val = min_max_def(eval(name), 0, 4999, 4999)
2556
            val = RadioSettingValueInteger(0, 4999, temp_val)
2557
            radio_setting = \
2558
                RadioSetting(name, "Level " + str(lvl) +
2559
                             (" (voltage calibration)" if lvl == 3 else ""),
2560
                             val)
2561
            radio_setting_group.append(radio_setting)
2562

    
2563
        radio_setting_group = RadioSettingGroup("vox_calibration", "VOX")
2564
        calibration.append(radio_setting_group)
2565

    
2566
        for lvl in range(0, 10):
2567
            append_label(radio_setting_group, "Level " + str(lvl + 1))
2568

    
2569
            name = "_mem.cal.vox1Thr[" + str(lvl) + "]"
2570
            val = RadioSettingValueInteger(0, 65535, eval(name))
2571
            radio_setting = RadioSetting(name, "On", val)
2572
            radio_setting_group.append(radio_setting)
2573

    
2574
            name = "_mem.cal.vox0Thr[" + str(lvl) + "]"
2575
            val = RadioSettingValueInteger(0, 65535, eval(name))
2576
            radio_setting = RadioSetting(name, "Off", val)
2577
            radio_setting_group.append(radio_setting)
2578

    
2579
        radio_setting_group = RadioSettingGroup("mic_calibration",
2580
                                                "Microphone sensitivity")
2581
        calibration.append(radio_setting_group)
2582

    
2583
        for lvl in range(0, 5):
2584
            name = "_mem.cal.micLevel[" + str(lvl) + "]"
2585
            tempval = min_max_def(eval(name), 0, 31, 31)
2586
            val = RadioSettingValueInteger(0, 31, tempval)
2587
            radio_setting = RadioSetting(name, "Level " + str(lvl), val)
2588
            radio_setting_group.append(radio_setting)
2589

    
2590
        radio_setting_group = RadioSettingGroup("other_calibration", "Other")
2591
        calibration.append(radio_setting_group)
2592

    
2593
        name = "_mem.cal.xtalFreqLow"
2594
        temp_val = min_max_def(eval(name), -1000, 1000, 0)
2595
        val = RadioSettingValueInteger(-1000, 1000, temp_val)
2596
        radio_setting = RadioSetting(name, "Xtal frequency low", val)
2597
        radio_setting_group.append(radio_setting)
2598

    
2599
        name = "_mem.cal.volumeGain"
2600
        temp_val = min_max_def(eval(name), 0, 63, 58)
2601
        val = RadioSettingValueInteger(0, 63, temp_val)
2602
        radio_setting = RadioSetting(name, "Volume gain", val)
2603
        radio_setting_group.append(radio_setting)
2604

    
2605
        name = "_mem.cal.dacGain"
2606
        temp_val = min_max_def(eval(name), 0, 15, 8)
2607
        val = RadioSettingValueInteger(0, 15, temp_val)
2608
        radio_setting = RadioSetting(name, "DAC gain", val)
2609
        radio_setting_group.append(radio_setting)
2610

    
2611

    
2612

    
2613
        # ----------------- Help User
2614

    
2615
        help_group = RadioSettingGroup("Explain_Display_HELP",
2616
                                                "Documentation Display")
2617
        help_user.append(help_group)
2618

    
2619
        append_label(help_group, "=" * 6 + " Documentation Display " + "=" * 50, "=" * 6  + " Location on display " + "=" * 50)
2620
                         
2621
        append_label(help_group,"Status Line" , "It's the first line on the display, on the top of the display" )
2622
        append_label(help_group,"(PS) = Power Save" , "Status Ligne " )
2623
        append_label(help_group,"(S) = SCAN activ " , "Status Ligne " )
2624
        append_label(help_group,"(I) = SCAN Memory on list 1 " , "Status Ligne " )
2625
        append_label(help_group,"(II) = SCAN Memory on list 2 " , "Status Ligne " )
2626
        append_label(help_group,"(INFINI) = SCAN ALL Memory " , "Status Ligne " )
2627
        append_label(help_group,"(MO) = Main Only - Display only one VFO " , "Status Ligne " )
2628
        append_label(help_group,"(DWR) = Double  " , "Status Ligne " )
2629
        append_label(help_group,"(DW) = Double " , "Status Ligne " )
2630
        append_label(help_group,"(XD) = Cross Bande " , "Status Ligne " )
2631
        append_label(help_group,"(VX) = VOX activer " , "Status Ligne " )
2632
        append_label(help_group,"(CL) = PTT Classique " , "Status Ligne " )
2633
        append_label(help_group,"(OP) = PTT mode ONE PUSH " , "Status Ligne " )
2634
        append_label(help_group,"(F) = Make the second function of the key " , "Status Ligne " )
2635

    
2636
        append_label(help_group,"" , "" )
2637

    
2638
        append_label(help_group,"(Dark Arrow) = VFO active" , "On the left side of the VFO " )
2639
        append_label(help_group,"(Mxxx) = Memory Number M1-M200", "On the left side of the VFO " )        
2640
        append_label(help_group,"(Fx) =  Band Number F1-F7 " , "On the left side of the VFO " )
2641
        append_label(help_group,"(TX) = VFO Uses when TX" , "On the left side of the VFO " )
2642
        append_label(help_group,"(RX) = Radio receiving" , "On the left side of the VFO " )
2643

    
2644
        append_label(help_group,"" , "" )
2645

    
2646
        append_label(help_group, "=" * 6 + " if (SetGui) is select on CLASSIC " + "=" * 50, "=" * 6  + "=" * 100)
2647

    
2648
        append_label(help_group,"(FM,USB,AM) = Reception Mode" , "Under each VFO " )
2649
        append_label(help_group,"(DC) = RX DCS Activate" , "Under each VFO " )
2650
        append_label(help_group,"(CT) = RX CTCSS Activate" , "Under each VFO " )
2651
        append_label(help_group,"(H) = Power HIGH" , "Under each VFO " )
2652
        append_label(help_group,"(M) = Power MEDIUM" , "Under each VFO " )
2653
        append_label(help_group,"(Lx) = Power LOW x, x = Level 1 to 5" , "Under each VFO " )
2654
        append_label(help_group,"(W) = Filter BF Wide : 12.5kHz (WIDE)" , "Under each VFO " )
2655
        append_label(help_group,"(N) = Filter BF Narrow : 6.25kHz (NARROW)" , "Under each VFO " )
2656

    
2657
        append_label(help_group,"" , "" )
2658

    
2659
        append_label(help_group, "=" * 6 + " If (SetGui) is select on TINY " + "=" * 50, "=" * 6  + "=" * 100)
2660

    
2661
        append_label(help_group,"(FM,USB,AM) = Reception Mode" , "Under each VFO " )
2662
        append_label(help_group,"(HIGH) = Power HIGH" , "Under each VFO " )
2663
        append_label(help_group,"(MID) = Power MEDIUM" , "Under each VFO " )
2664
        append_label(help_group,"(LOWx) = Power LOW x, x = Level 1 to 5" , "Under each VFO " )
2665
        append_label(help_group,"(CT x) = RX CTCSS Activate, x value" , "Under each VFO " )
2666
        append_label(help_group,"(DC x) = RX CTCSS Activate, x value" , "Under each VFO " )
2667
        append_label(help_group,"(x.xxK) = no incrementation of the frequency" , "Under each VFO " )
2668
        append_label(help_group,"(WIDE) = Filter BF WIDE : 12.5kHz (WIDE)" , "Under each VFO " )
2669
        append_label(help_group,"(NAR) = Filter NARROW : 6.25kHz (NARROW)" , "Under each VFO " )
2670
       
2671
        append_label(help_group,"" , "" )
2672

    
2673
        append_label(help_group, "=" * 6 + "=" * 100, "=" * 6  + "=" * 100)
2674
       
2675
        append_label(help_group,"(SQLx) = Level of Squelch, x :value 1 to 9" , "Under each VFO " )
2676
        append_label(help_group,"(MONI) = Reception Monitoring (Squelch desactivated)" , "Under each VFO " )
2677
       
2678
        help_group = RadioSettingGroup("Explain_Keyboard_HELP", "Documentation keypad " )
2679
        help_user.append(help_group)
2680

    
2681
        append_label(help_group, "=" * 6 + " Fonction keypad and buttons " + "=" * 50, "=" * 6  + " When press on (F #) before the key " + "=" * 50)
2682
                                                   
2683
        append_label(help_group,"(1 BAND) = long press, change the active band frequency (F1 to F7)  " , "Same a long press  " )
2684
        append_label(help_group,"(1 BAND) = short press, equal to number 1  "," " )
2685
        append_label(help_group,"","" )
2686

    
2687
        append_label(help_group,"(2 A/B) = long press, change the active VFO, A(top) / B(bottom) " , "Same a long press " )
2688
        append_label(help_group,"(2 A/B) = short press, equal to number 2  "," " )
2689

    
2690
        append_label(help_group,"","" )
2691
        
2692
        append_label(help_group,"(3 VFO MR) = long press, change band frequency to Memory Channel " , "Same a long press  " )
2693
        append_label(help_group,"(3 VFO MR) = short press, equal to number 3  "," " )
2694

    
2695
        append_label(help_group,"","" )
2696
        
2697
        append_label(help_group,"(* SCAN) = long press, if VFO = Fx, Scan ban ", "Make a scan list from the active channel" )
2698
        append_label(help_group,"(* SCAN) = long press, if VFO = Mxxx, switch scan list (1,2,*) ", "Make a scan list from the active channel" )
2699
        append_label(help_group,"(* SCAN) = short press go to dtmf input mode (S) ", " " )
2700

    
2701
        append_label(help_group,"","" )
2702

    
2703
        append_label(help_group,"(4 FC) = long press,  launches the CTC frequency scan " , "Same a long press " )
2704
        append_label(help_group,"(4 FC) = short press, equal to number 4  "," " )
2705

    
2706
        append_label(help_group,"","" ) 
2707
        
2708
        append_label(help_group,"(5 NOAA) = long press, if VFO = Fx, ScnRnG will activated " , "Go to fagci spectrum analyze " )
2709
        append_label(help_group,"(5 NOAA) = long press, if VFO = Mxxx,  Select scan list " , " ")
2710
        append_label(help_group,"(5 NOAA) = short press equal to number 5  "," " )
2711

    
2712
        append_label(help_group,"","" )
2713
                
2714
        append_label(help_group,"(6 H/M/L) = long press, change the Power Tx level(High/Medium/Low)of the active VFO " , "Same a long press " )
2715
        append_label(help_group,"(6 H/M/L) = short press equal to number 6  "," " )
2716

    
2717
        append_label(help_group,"","" )
2718
                
2719
        append_label(help_group,"(0 FM) = long press, to go in listen the radio fm band ", "Same a long press " )
2720
        append_label(help_group,"(0 FM) = short press, equal to number 0  "," " )
2721

    
2722
        append_label(help_group,"","")
2723
        
2724
        append_label(help_group,"(7 VOX) = long press, change Voice Activation (VX) " , "Same a long press " )
2725
        append_label(help_group,"(7 VOX) = short press equal to number 7  "," " )
2726

    
2727
        append_label(help_group,"","" )
2728
                
2729
        append_label(help_group,"(8 R) = long press, Reverse (R) " , "Same a long press ")
2730
        append_label(help_group,"(8 R) = short press equal to number 8  ","Force backlight min or backlight max " )
2731
        append_label(help_group,"","To dot care of the setting (BackLt)")
2732

    
2733
        append_label(help_group,"","" )
2734
        
2735
        append_label(help_group,"(9 CALL) = long press, switches current channel to the 1-Call channel " , "Same a long press ")
2736
        append_label(help_group,"(9 CALL) = short press equal to number 9  ","Return to normal backlight setting (BackLt) " )
2737

    
2738
        append_label(help_group,"","" )
2739
        
2740
        append_label(help_group,"(F #) = long, press will Lock or unlock the keypad ", )
2741
        append_label(help_group,"(F #) = short, press activated the second funetion ", )
2742

    
2743
        append_label(help_group,"","" )
2744
        
2745
        append_label(help_group,"(M A) = long press, Bouton programable by user","" )
2746
        append_label(help_group,"(M A) = short press, Go to menu of the radio","" )
2747

    
2748
        append_label(help_group,"","" )
2749

    
2750
        append_label(help_group,"(Arrow UP B) = long press, Change le VFO ou MR actif rapidement vers le haut","" )
2751
        append_label(help_group,"(Arrow UP B) = short press, Change le VFO ou MR actif vers le haut ","increase squelch" )
2752

    
2753
        append_label(help_group,"","" )
2754

    
2755
        append_label(help_group,"(Arrow DOWN C) = long press, change VFO or MR active quick up","" )
2756
        append_label(help_group,"(Arrow DOWN C) = short press, change VFO or MR actve quick down ","decrease squelch" )
2757

    
2758
        append_label(help_group,"","" )
2759
        
2760
        append_label(help_group, "=" * 6 + " Button on the side " + "=" * 50, "=" * 6  + "" + "=" * 50)
2761

    
2762
        append_label(help_group,"","" )
2763
        
2764
        append_label(help_group,"(PTT)  = long press, Transmit on active TX","" )
2765
        append_label(help_group,"(PTT)  = short press, Transmit on active TX","" )
2766

    
2767
        append_label(help_group,"","" )
2768
        
2769
        append_label(help_group,"(.)  = long press, Button programable by user","" )
2770
        append_label(help_group,"(.)  = short press, Button programable by user","" )
2771

    
2772
        append_label(help_group,"","" )
2773

    
2774
        append_label(help_group,"(..)  = long press, Button programable by user","" )
2775
        append_label(help_group,"(..)  = short press, Button programable by user","" )
2776

    
2777
        append_label(help_group,"","" )
2778
        
2779
        append_label(help_group, "=" * 6 + " Fonction SPECIAL " + "=" * 50, "=" * 6  + "" + "=" * 50)
2780

    
2781
        append_label(help_group,"","" )
2782
        
2783
        append_label(help_group,"To put the radio in programming MODE firmware","Hold button (PTT) then open radio, The Flashlight will turn ON" )        
2784

    
2785
        append_label(help_group,"","" )
2786
        
2787
        append_label(help_group,"To activated hidden MENU ", "Hold button (PTT) and the button (.) then open radio" )
2788
        append_label(help_group,"", "The menus are at the end ot the regular menus" )        
2789

    
2790

    
2791
 
2792
        help_group = RadioSettingGroup("How_To_Upload_F4HWN_HELP",
2793
                                                "How To Upload F4HWN")
2794
        help_user.append(help_group)
2795

    
2796
        append_label(help_group,"Special upload need to be done to upload F4HWN to the radio ", )
2797
        append_label(help_group,"Since the feature of F4HWN is locates in a different range of memory ", )
2798
        append_label(help_group,"You need to select the box Upload F4HWN, to upload ONLY and ONLY the ", )
2799
        append_label(help_group,"section of F4HWN to the radio. Go in menu Radio, Upload to Radio... ", )
2800
        append_label(help_group,"It will be realy fast to upload, then the radio will reboot. ", )
2801
        append_label(help_group,"After uploading, uncheck the box Upload F4HWN to be able to upload all other feature ", )
2802
        append_label(help_group,"of the radio. Go in menu Radio, Upload to Radio... now all the feature will be sent to the radio ", )
2803
        
2804
        help_group = RadioSettingGroup("Chirp_Language_HELP",
2805
                                                "How To Change Chirp Language")
2806
        help_user.append(help_group)
2807
        
2808
        append_label(help_group,"it's not possible to change the language directely in chirp but...", )
2809
        append_label(help_group,"You need to change the windows parameter, since chirp read windows setting to set language ", )
2810
        append_label(help_group,"it's a bit hard to explain with description in text only, so go see the file ", )
2811
        append_label(help_group,"(how_to_do_for_module_in_chirp_release_1.doc) include in the release version ", )
2812
        append_label(help_group,"or go on the web site github to see the file ", CHIRP_DRIVER_VERSION_UPDATE )
2813

    
2814
        # -------- LAYOUT
2815
        append_label(basic,
2816
                     "=" * 6 + " F4HWN, Begin Setting, if this area need to be upload select the upload F4HWN" + "=" * 300, "=" * 300)
2817
        basic.append(Upload_f4hwn)
2818
        basic.append(SetLowSetting)
2819
        basic.append(SetPttSetting)
2820
        basic.append(SetTotSetting)
2821
        basic.append(SetEotSetting)
2822
        basic.append(contrastSetting)
2823
        basic.append(SetInvSetting)
2824
        basic.append(SetLckSetting)
2825
        basic.append(SetMetSetting)
2826
        basic.append(SetGuiSetting)
2827
        append_label(basic,
2828
                     "=" * 6 + " F4HWN, End Setting " + "=" * 300, "=" * 300)
2829

    
2830
        append_label(basic,
2831
                     "=" * 6 + " General settings " + "=" * 300, "=" * 300)
2832

    
2833
        basic.append(squelch_setting)
2834
        basic.append(rx_mode_setting)
2835
        basic.append(call_channel_setting)
2836
        basic.append(auto_keypad_lock_setting)
2837
        basic.append(tx_t_out_setting)
2838
        basic.append(bat_save_setting)
2839
        basic.append(scn_rev_setting)
2840
        if _mem.BUILD_OPTIONS.ENABLE_NOAA:
2841
            basic.append(noaa_auto_scan_setting)
2842
        if _mem.BUILD_OPTIONS.ENABLE_AM_FIX:
2843
            basic.append(am_fix_setting)
2844

    
2845
        append_label(basic,
2846
                     "=" * 6 + " Display settings " + "=" * 300, "=" * 300)
2847

    
2848
        basic.append(bat_txt_setting)
2849
        basic.append(mic_bar_setting)
2850
        basic.append(ch_disp_setting)
2851
        basic.append(p_on_msg_setting)
2852
        basic.append(logo1_setting)
2853
        basic.append(logo2_setting)
2854

    
2855
        append_label(basic, "=" * 6 + " Backlight settings "
2856
                     + "=" * 300, "=" * 300)
2857

    
2858
        basic.append(back_lt_setting)
2859
        basic.append(bl_min_setting)
2860
        basic.append(bl_max_setting)
2861
        basic.append(blt_trx_setting)
2862

    
2863
        append_label(basic, "=" * 6 + " Audio related settings "
2864
                     + "=" * 300, "=" * 300)
2865

    
2866
        if _mem.BUILD_OPTIONS.ENABLE_VOX:
2867
            basic.append(vox_setting)
2868
        basic.append(mic_gain_setting)
2869
        basic.append(beep_setting)
2870
        basic.append(roger_setting)
2871
        basic.append(ste_setting)
2872
        basic.append(rp_ste_setting)
2873
        if _mem.BUILD_OPTIONS.ENABLE_VOICE:
2874
            basic.append(voice_setting)
2875
        if _mem.BUILD_OPTIONS.ENABLE_ALARM:
2876
            basic.append(alarm_setting)
2877

    
2878
        append_label(basic, "=" * 6 + " Radio state " + "=" * 300, "=" * 300)
2879

    
2880
        basic.append(freq0_setting)
2881
        basic.append(freq1_setting)
2882
        basic.append(tx_vfo_setting)
2883
        basic.append(keypad_lock_setting)
2884

    
2885
        advanced.append(freq_mode_allowed_setting)
2886
        advanced.append(bat_type_setting)
2887
        advanced.append(s0_level_setting)
2888
        advanced.append(s9_level_setting)
2889
#        if _mem.BUILD_OPTIONS.ENABLE_PWRON_PASSWORD:
2890
#            advanced.append(pswd_setting)
2891

    
2892
        if _mem.BUILD_OPTIONS.ENABLE_DTMF_CALLING:
2893
            dtmf.append(sep_code_setting)
2894
            dtmf.append(group_code_setting)
2895
        dtmf.append(first_code_per_setting)
2896
        dtmf.append(spec_per_setting)
2897
        dtmf.append(code_per_setting)
2898
        dtmf.append(code_int_setting)
2899
        if _mem.BUILD_OPTIONS.ENABLE_DTMF_CALLING:
2900
            dtmf.append(ani_id_setting)
2901
        dtmf.append(up_code_setting)
2902
        dtmf.append(dw_code_setting)
2903
        dtmf.append(d_prel_setting)
2904
        dtmf.append(dtmf_side_tone_setting)
2905
        if _mem.BUILD_OPTIONS.ENABLE_DTMF_CALLING:
2906
            dtmf.append(dtmf_resp_setting)
2907
            dtmf.append(d_hold_setting)
2908
            dtmf.append(d_live_setting)
2909
            dtmf.append(perm_kill_setting)
2910
            dtmf.append(kill_code_setting)
2911
            dtmf.append(rev_code_setting)
2912
            dtmf.append(killed_setting)
2913

    
2914
        unlock.append(f_lock_setting)
2915
        unlock.append(tx200_setting)
2916
        unlock.append(tx350_setting)
2917
        unlock.append(tx500_setting)
2918
        unlock.append(en350_setting)      
2919
# desable by f4hwn
2920
#       unlock.append(en_scrambler_setting)
2921

    
2922
        return top
2923

    
2924
    def set_memory(self, memory):
2925
        """
2926
        Store details about a high-level memory to the memory map
2927
        This is called when a user edits a memory in the UI
2928
        """
2929
        number = memory.number-1
2930
        att_num = number if number < 200 else 200 + int((number - 200) / 2)
2931

    
2932
        # Get a low-level memory object mapped to the image
2933
        _mem_chan = self._memobj.channel[number]
2934
        _mem_attr = self._memobj.ch_attr[att_num]
2935

    
2936
        _mem_attr.is_scanlist1 = 0
2937
        _mem_attr.is_scanlist2 = 0
2938
        _mem_attr.compander = 0
2939
        _mem_attr.is_free = 1
2940
        _mem_attr.band = 0x7
2941

    
2942
        # empty memory
2943
        if memory.empty:
2944
            _mem_chan.set_raw("\xFF" * 16)
2945
            if number < 200:
2946
                _mem_chname = self._memobj.channelname[number]
2947
                _mem_chname.set_raw("\xFF" * 16)
2948
            return memory
2949

    
2950
        # find band
2951
        band = self._find_band(memory.freq)
2952

    
2953
        # mode
2954
        tmp_mode = self.get_features().valid_modes.index(memory.mode)
2955
        _mem_chan.modulation = tmp_mode / 2
2956
        _mem_chan.bandwidth = tmp_mode % 2
2957
        if memory.mode == "USB":
2958
            _mem_chan.bandwidth = 1  # narrow
2959

    
2960
        # frequency/offset
2961
        _mem_chan.freq = memory.freq/10
2962
        _mem_chan.offset = memory.offset/10
2963

    
2964
        if memory.duplex == "":
2965
            _mem_chan.offset = 0
2966
            _mem_chan.offsetDir = 0
2967
        elif memory.duplex == '-':
2968
            _mem_chan.offsetDir = FLAGS1_OFFSET_MINUS
2969
        elif memory.duplex == '+':
2970
            _mem_chan.offsetDir = FLAGS1_OFFSET_PLUS
2971
        elif memory.duplex == 'off':
2972
            # we fake tx disable by setting the tx freq to 0 MHz
2973
            _mem_chan.offsetDir = FLAGS1_OFFSET_MINUS
2974
            _mem_chan.offset = _mem_chan.freq
2975
        # set band
2976

    
2977
        _mem_attr.is_free = 0
2978
        _mem_attr.band = band
2979

    
2980
        # channels >200 are the 14 VFO chanells and don't have names
2981
        if number < 200:
2982
            _mem_chname = self._memobj.channelname[number]
2983
            tag = memory.name.ljust(10) + "\x00"*6
2984
            _mem_chname.name = tag  # Store the alpha tag
2985

    
2986
        # tone data
2987
        self._set_tone(memory, _mem_chan)
2988

    
2989
        # step
2990
        _mem_chan.step = STEPS.index(memory.tuning_step)
2991

    
2992
        # tx power
2993
        if str(memory.power) == str(UVK5_POWER_LEVELS[2]):
2994
            _mem_chan.txpower = POWER_HIGH
2995
        elif str(memory.power) == str(UVK5_POWER_LEVELS[1]):
2996
            _mem_chan.txpower = POWER_MEDIUM
2997
        else:
2998
            _mem_chan.txpower = POWER_LOW
2999

    
3000
        # -------- EXTRA SETTINGS
3001

    
3002
        def get_setting(name, def_val):
3003
            if name in memory.extra:
3004
                return int(memory.extra[name].value)
3005
            return def_val
3006

    
3007
        _mem_chan.busyChLockout = get_setting("busyChLockout", False)
3008
        _mem_chan.dtmf_pttid = get_setting("pttid", 0)
3009
        _mem_chan.freq_reverse = get_setting("frev", False)
3010
        _mem_chan.dtmf_decode = get_setting("dtmfdecode", False)
3011
        _mem_chan.scrambler = get_setting("scrambler", 0)
3012
        _mem_attr.compander = get_setting("compander", 0)
3013
        if number < 200:
3014
            tmp_val = get_setting("scanlists", 0)
3015
            _mem_attr.is_scanlist1 = bool(tmp_val & 1)
3016
            _mem_attr.is_scanlist2 = bool(tmp_val & 2)
3017

    
3018
        return memory
(1-1/2)