Project

General

Profile

Bug #10909 » ft450d.py

William Curlew, 10/23/2023 09:56 PM

 
1
# Copyright 2018 by Rick DeWitt (aa0rd@yahoo.com) V2.0
2
# PY3 Compliant release.
3
# Thanks to Filippi Marco <iz3gme.marco@gmail.com> for Yaesu processes
4
#
5
# This program is free software: you can redistribute it and/or modify
6
# it under the terms of the GNU General Public License as published by
7
# the Free Software Foundation, either version 3 of the License, or
8
# (at your option) any later version.
9
#
10
# This program is distributed in the hope that it will be useful,
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
# GNU General Public License for more details.
14
#
15
# You should have received a copy of the GNU General Public License
16
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
17

    
18
"""FT450D Yaesu Radio Driver"""
19

    
20
from chirp.drivers import yaesu_clone
21
from chirp import chirp_common, util, memmap, errors, directory, bitwise
22
from chirp.settings import RadioSetting, RadioSettingGroup, \
23
    RadioSettingValueInteger, RadioSettingValueList, \
24
    RadioSettingValueBoolean, RadioSettingValueString, \
25
    RadioSettings
26
import time
27
import struct
28
import logging
29

    
30
LOG = logging.getLogger(__name__)
31

    
32
CMD_ACK = b'\x06'
33
EX_MODES = ["USER-L", "USER-U", "LSB+CW", "USB+CW", "RTTY-L", "RTTY-U", "N/A"]
34
T_STEPS = sorted(list(chirp_common.TUNING_STEPS))
35
T_STEPS.remove(30.0)
36
T_STEPS.remove(100.0)
37
T_STEPS.remove(125.0)
38
T_STEPS.remove(200.0)
39

    
40

    
41
@directory.register
42
class FT450DRadio(yaesu_clone.YaesuCloneModeRadio):
43
    """Yaesu FT-450D"""
44
    BAUD_RATE = 38400
45
    COM_BITS = 8    # number of data bits
46
    COM_PRTY = 'N'   # parity checking
47
    COM_STOP = 1   # stop bits
48
    MODEL = "FT-450D"
49
    NEEDS_COMPAT_SERIAL = False
50

    
51
    DUPLEX = ["", "-", "+"]
52
    MODES = ["LSB", "USB",  "CW",  "AM", "FM", "RTTY-L",
53
             "USER-L", "USER-U", "NFM", "CWR"]
54
    TMODES = ["", "Tone", "TSQL"]
55
    STEPSFM = [5.0, 6.25, 10.0, 12.5, 15.0, 20.0, 25.0, 50.0]
56
    STEPSAM = [2.5, 5.0, 9.0, 10.0, 12.5, 25.0]
57
    STEPSSSB = [1.0, 2.5, 5.0]
58
    VALID_BANDS = [(100000, 33000000), (33000000, 56000000)]
59
    FUNC_LIST = ['MONI', 'N/A', 'PBAK', 'PLAY1', 'PLAY2', 'PLAY3', 'QSPLIT',
60
                 'SPOT', 'SQLOFF', 'SWR', 'TXW', 'VCC', 'VOICE2', 'VM1MONI',
61
                 'VM1REC', 'VM1TX', 'VM2MONI', 'VM2REC', 'VM2TX', 'DOWN', 'FAST',
62
                 'UP', 'DSP', 'IPO/ATT', 'NB', 'AGC', 'MODEDN',  'MODEUP',
63
                 'DSP/SEL', 'KEYER', 'CLAR', 'BANDDN', 'BANDUP', 'A=B', 'A/B',
64
                 'LOCK', 'TUNE', 'VOICE', 'MW', 'V/M', 'HOME', 'RCL', 'VOX', 'STO',
65
                 'STEP', 'SPLIT', 'PMS', 'SCAN', 'MENU', 'DIMMER', 'MTR']
66
    CHARSET = list(chirp_common.CHARSET_ASCII)
67
    CHARSET.remove("\\")
68

    
69
    # WDC MEM_SIZE = 15017
70
    MEM_SIZE = 15015
71
    # block 9 (135 Bytes long) is to be repeated 101 times
72
    #WDC _block_lengths = [4, 84, 135, 162, 135, 162, 151, 130, 135, 127, 189, 103]
73
    _block_lengths = [2, 84, 135, 162, 135, 162, 151, 130, 135, 127, 189, 103]
74

    
75
    MEM_FORMAT = """
76
        struct mem_struct {      // 27 bytes per channel
77
            u8  tag_on_off:2,    // @ Byte 0    1=Off, 2=On
78
                unk0:2,
79
                mode:4;
80
            u8  duplex:2,        // @ byte 1
81
                att:1,
82
                ipo:1,
83
                unka1:1,
84
                tunerbad:1,       // ?? Possible tuner failed
85
                unk1b:1,         // @@@???
86
                uprband:1;
87
            u8  cnturpk:1,       // @ Byte 2 Peak (clr), Null (set)
88
                cnturmd:3,       // Contour filter mode
89
                cnturgn:1,       // Contour filter gain Low/high
90
                mode2:3;         // When mode is data(5)
91
            u8  ssb_step:2,      // @ Byte 3
92
                am_step:3,
93
                fm_step:3;
94
            u8  tunerok:1,        // @ Byte 4 ?? Poss tuned ok
95
                cnturon:1,
96
                unk4b:1,
97
                dnr_on:1
98
                notch:1,
99
                unk4c:1,
100
                tmode:2;         // Tone/Cross/etc as Off/Enc/Enc+Dec
101
            u8  unk5a:4,         // @ byte 5
102
                dnr_val:4;
103
            u8  cw_width:2,		 // # byte 6, Notch width indexes
104
                fm_width:2,
105
                am_width:2,
106
                sb_width:2;
107
            i8  notch_pos;	     // @ Byte 7   Signed: - 0 +
108
            u8  tone;       	 // @ Byte 8
109
            u8  unk9;       	 // @ Byte 9    Always set to 0
110
            u8  unkA;            // @ Byte A
111
            u8  unkB;            // @ Byte B
112
            u32 freq;            // @ C-F
113
            u32 offset;          // @ 10-13
114
            u8  name[7];         // @ 14-1A
115
        };
116

    
117
        # seekto 0x02;
118
        struct {
119
            u8  set04;      // ?Checksum / Clone counter?
120
            u8  set05;      // Current VFO?
121
            u8  set06;
122
            u8  fast:1,
123
                lock:1,     // Inverted: 1 = Off
124
                nb:1,
125
                agc:5;
126
            u8  set08a:3,
127
                keyer:1,
128
                set08b:2,
129
                mtr_mode:2;
130
            u8  set09;
131
            u8  set0A;
132
            u8  set0B:2,
133
                clk_sft:1,
134
                cont:5;     // 1:1
135
            u8  beepvol_sgn:1,  // @x0C: set : Link @0x41, clear: fix @ 0x40
136
                set0Ca:3,
137
                clar_btn:1,     // 0 = Dial, 1= SEL
138
                cwstone_sgn:1,  // Set: Lnk @ x42, clear: Fixed at 0x43
139
                beepton:2;      // Index 0-3
140
            u8  set0Da:1,
141
                cw_key:1,
142
                set0Db:3,
143
                dialstp_mode:1,
144
                dialstp:2;
145
            u8  set0E:1,
146
                keyhold:1,
147
                lockmod:1,
148
                set0ea:1,
149
                amfmdial:1, // 0= Enabled. 1 = Disabled
150
                cwpitch:3;  // 0-based index
151
            u8  sql_rfg:1
152
                set0F:2,
153
                cwweigt:5;  // Index 1:2.5=0 -> 1:4.5=20
154
            u8  cw_dly;     // @x10  ms = val * 10
155
            u8  set11;
156
            u8  cwspeed;    // val 4-60 is wpm, *5 is cpm
157
            u8  vox_gain;   // val 1:1
158
            u8  set14:2,
159
                emergen:1,
160
                vox_dly:5;    // ms = val * 100
161
            u8  set15a:1,
162
                stby_beep:1
163
                set15b:1
164
                mem_grp:1,
165
                apo:4;
166
            u8  tot;        // Byte x16, 1:1
167
            u8  micscan:1,
168
                set17:5,
169
                micgain:2;
170
            u8  cwpaddl:1,  // @x18  0=Key, 1=Mic
171
                set18:7;
172
            u8  set19;
173
            u8  set1A;
174
            u8  set1B;
175
            u8  set1C;
176
            u8  dig_vox;    // 1:1
177
            u8  set1E;
178
            i16 d_disp;     //  @ x1F,x20   signed 16bit
179
            u8  pnl_cs;     // 0-based index
180
            u8  pm_up;
181
            u8  pm_fst;
182
            u8  pm_dwn;
183
            u8  set25;
184
            u8  set26;
185
            u8  set27;
186
            u8  set28;
187
            u8  beacon_time;    // 1:1
188
            u8  set2A;
189
            u8  cat_rts:1,      // @x2b: Enable=0, Disable=1
190
                peakhold:1,
191
                set2B:4,
192
                cat_tot:2;      // Index 0-3
193
            u8  set2CA:2,
194
                rtyrpol:1,
195
                rtytpol:1
196
                rty_sft:2,
197
                rty_ton:1,
198
                set2CC:1;
199
            u8  dig_vox;        // 1:1
200
            u8  ext_mnu:1,
201
                m_tune:1,
202
                set2E:2,
203
                scn_res:4;
204
            u8  cw_auto:1,      // Off=0, On=1
205
                cwtrain:2,      // Index
206
                set2F:1,
207
                cw_qsk:2,       // Index
208
                cw_bfo:2;       // Index
209
            u8  mic_eq;         // @x30  1:1
210
            u8  set31:5,
211
                catrate:3;      // Index 0-4
212
            u8  set32;
213
            u8  dimmer:4,
214
                set33:4;
215
            u8  set34;
216
            u8  set35;
217
            u8  set36;
218
            u8  set37;
219
            u8  set38a:1,
220
                rfpower:7;       // 1:1
221
            u8  set39a:2,
222
                tuner:3,        // Index 0-4
223
                seldial:3;      // Index 0-5
224
            u8  set3A;
225
            u8  set3B;
226
            u8  set3C;
227
            i8  qspl_f;         // Signed
228
            u8  set3E;
229
            u8  set3F;
230
            u8  beepvol_fix;        // 1:1
231
            i8  beepvol_lnk;        // SIGNED 2's compl byte
232
            u8  cwstone_fix;
233
            i8  cwstone_lnk;        // signed byte
234
            u8  set44:2,
235
                mym_data:1,         // My Mode: Data, set = OFF
236
                mym_fm:1,
237
                mym_am:1,
238
                mym_cw:1,
239
                mym_usb:1,
240
                mym_lsb:1;
241
            u8  myb_24:1,          // My Band: 24 MHz set = OFF
242
                myb_21:1,
243
                myb_18:1,
244
                myb_14:1,
245
                myb_10:1,
246
                myb_7:1,
247
                myb_3_5:1,
248
                myb_1_8:1;
249
            u8  set46:6,
250
                myb_28:1,
251
                myb_50:1;
252
            u8  set47;
253
            u8  set48;
254
            u8  set49;
255
            u8  set4A;
256
            u8  set4B;
257
            u8  set4C;
258
            u8  set4D;
259
            u8  set4E;
260
            u8  set4F;
261
            u8  set50;
262
            u8  set51;
263
            u8  set52;
264
            u8  set53;
265
            u8  set54;
266
            u8  set55;
267
            u8  set56a:3,
268
                split:1,
269
                set56b:4;
270
            u8  set57;
271
        } settings;
272

    
273
        # seekto 0x56;
274
        struct mem_struct vfoa[11]; // The current cfgs for each vfo 'band'
275
        struct mem_struct vfob[11];
276
        struct mem_struct home[2];  // The 2 Home cfgs (HF and 6m)
277
        struct mem_struct qmb;      // The Quick Memory Bank STO/RCL
278
        struct mem_struct mtqmb;    // Last QMB-MemTune cfg (not displayed)
279
        struct mem_struct mtune;    // Last MemTune cfg (not displayed)
280

    
281
        # seekto 0x341;          // chan status
282
        u8 visible[63];         // 1 bit per channel
283
        u8 pmsvisible;          // @ 0x382
284

    
285
        # seekto 0x381;
286
        u8 filled[63];
287
        u8 pmsfilled;           // @ 0x3c2
288

    
289
        # seekto 0x3C1;
290
        struct mem_struct memory[500];
291
        struct mem_struct pms[4];       // Programmed Scan limits @ x387F
292

    
293
        # seekto 0x3904;
294
        struct {
295
            char t1[40];     // CW Beacon Text
296
            char t2[40];
297
            char t3[40];
298
            } beacontext;   // to 0x397E
299

    
300
        # seekto 0x3983;
301
        struct mem_struct m60[5];   // to 0x3A0B
302

    
303
        # seekto 0x03a43;
304
        struct mem_struct current;
305

    
306
    """
307
    _CALLSIGN_CHARSET = [chr(x) for x in list(range(ord("0"), ord("9") + 1)) +
308
                         list(range(ord("A"), ord("Z") + 1)) + [ord(" ")]]
309
    _CALLSIGN_CHARSET_REV = dict(zip(_CALLSIGN_CHARSET,
310
                                     range(0, len(_CALLSIGN_CHARSET))))
311

    
312
    # WARNING Indecis are hard wired in get/set_memory code !!!
313
    # Channels print in + increasing index order (PMS first)
314
    SPECIAL_MEMORIES = {
315
        "VFOa-1.8M": -27,
316
        "VFOa-3.5M": -26,
317
        "VFOa-7M": -25,
318
        "VFOa-10M": -24,
319
        "VFOa-14M": -23,
320
        "VFOa-18M": -22,
321
        "VFOa-21M": -21,
322
        "VFOa-24M": -20,
323
        "VFOa-28M": -19,
324
        "VFOa-50M": -18,
325
        "VFOa-HF": -17,
326
        "VFOb-1.8M": -16,
327
        "VFOb-3.5M": -15,
328
        "VFOb-7M": -14,
329
        "VFOb-10M": -13,
330
        "VFOb-14M": -12,
331
        "VFOb-18M": -11,
332
        "VFOb-21M": - 10,
333
        "VFOb-24M": -9,
334
        "VFOb-28M": -8,
335
        "VFOb-50M": -7,
336
        "VFOb-HF": -6,
337
        "HOME-HF": -5,
338
        "HOME-50M": -4,
339
        "QMB": -3,
340
        "QMB-MTune": -2,
341
        "Mem-Tune": -1,
342
    }
343
    FIRST_VFOB_INDEX = -6
344
    LAST_VFOB_INDEX = -16
345
    FIRST_VFOA_INDEX = -17
346
    LAST_VFOA_INDEX = -27
347

    
348
    SPECIAL_PMS = {
349
        "PMS1-L": -36,
350
        "PMS1-U": -35,
351
        "PMS2-L": -34,
352
        "PMS2-U": -33,
353
    }
354
    LAST_PMS_INDEX = -36
355
    SPECIAL_MEMORIES.update(SPECIAL_PMS)
356

    
357
    SPECIAL_60M = {
358
        "60m-Ch1": -32,
359
        "60m-Ch2": -31,
360
        "60m-Ch3": -30,
361
        "60m-Ch4": -29,
362
        "60m-Ch5": -28,
363
    }
364
    LAST_60M_INDEX = -32
365
    SPECIAL_MEMORIES.update(SPECIAL_60M)
366

    
367
    SPECIAL_MEMORIES_REV = dict(zip(SPECIAL_MEMORIES.values(),
368
                                    SPECIAL_MEMORIES.keys()))
369

    
370
    @classmethod
371
    def get_prompts(cls):
372
        rp = chirp_common.RadioPrompts()
373
        rp.info = _(
374
            "The FT-450 radio driver loads the 'Special Channels' tab\n"
375
            "with the PMS scanning range memories (group 11), 60meter\n"
376
            "channels (group 12), the QMB (STO/RCL) memory, the HF and\n"
377
            "50m HOME memories and all the A and B VFO memories.\n"
378
            "There are VFO memories for the last frequency dialed in\n"
379
            "each band. The last mem-tune config is also stored.\n"
380
            "These Special Channels allow limited field editing.\n"
381
            "This driver also populates the 'Other' tab in the channel\n"
382
            "memory Properties window. This tab contains values for\n"
383
            "those channel memory settings that don't fall under the\n"
384
            "standard Chirp display columns.\n")
385
        rp.pre_download = _(
386
            "1. Turn radio off.\n"
387
            "2. Connect cable to ACC jack.\n"
388
            "3. Press and hold in the [MODE &lt;] and [MODE &gt;] keys"
389
            " while\n"
390
            "     turning the radio on (\"CLONE MODE\" will appear on the\n"
391
            "     display).\n"
392
            "4. <b>After clicking OK</b> here, press the [C.S.] key to\n"
393
            "    send image.\n")
394
        rp.pre_upload = _(
395
            "1. Turn radio off.\n"
396
            "2. Connect cable to ACC jack.\n"
397
            "3. Press and hold in the [MODE &lt;] and [MODE &gt;] keys"
398
            " while\n"
399
            "     turning the radio on (\"CLONE MODE\" will appear on the\n"
400
            "     display).\n"
401
            "4. Click OK here.\n"
402
            "    (\"Receiving\" will appear on the LCD).\n")
403
        return rp
404

    
405
    def _read(self, block, blocknum):
406
        # be very patient at first block
407
        if blocknum == 0:
408
            attempts = 60
409
        else:
410
            attempts = 5
411
        for _i in range(0, attempts):
412
            data = self.pipe.read(block + 2)    # Blocknum, data,checksum
413
            if data:
414
                break
415
            time.sleep(0.5)
416
        if len(data) == block + 2 and data[0] == blocknum:
417
            checksum = yaesu_clone.YaesuChecksum(1, block)
418
            if checksum.get_existing(data) != \
419
                    checksum.get_calculated(data):
420
                raise Exception("Checksum Failed [%02X<>%02X] block %02X" %
421
                                (checksum.get_existing(data),
422
                                 checksum.get_calculated(data), blocknum))
423
            # Remove the block number and checksum
424
            data = data[1:block + 1]
425
        else:   # Use this info to decode a new Yaesu model
426
            raise Exception("Unable to read block %i expected %i got %i"
427
                            % (blocknum, block + 2, len(data)))
428
        return data
429

    
430
    def _clone_in(self):
431
        # Be very patient with the radio
432
        self.pipe.timeout = 2
433
        self.pipe.baudrate = self.BAUD_RATE
434
        self.pipe.bytesize = self.COM_BITS
435
        self.pipe.parity = self.COM_PRTY
436
        self.pipe.stopbits = self.COM_STOP
437
        self.pipe.rtscts = False
438

    
439
        start = time.time()
440

    
441
        data = b""
442
        blocks = 0
443
        status = chirp_common.Status()
444
        status.msg = _("Cloning from radio")
445
        nblocks = len(self._block_lengths) + 100    # Block 8 repeats
446
        status.max = nblocks
447
        for block in self._block_lengths:
448
            if blocks == 8:
449
                # repeated read of block 8 same size (chan memory area)
450
                repeat = 101
451
            else:
452
                repeat = 1
453
            for _i in range(0, repeat):
454
                data += self._read(block, blocks)
455
                self.pipe.write(CMD_ACK)
456
                blocks += 1
457
                status.cur = blocks
458
                self.status_fn(status)
459
        data += self.MODEL.encode()
460
        return memmap.MemoryMapBytes(data)
461

    
462
    def _clone_out(self):
463
        self.pipe.baudrate = self.BAUD_RATE
464
        self.pipe.bytesize = self.COM_BITS
465
        self.pipe.parity = self.COM_PRTY
466
        self.pipe.stopbits = self.COM_STOP
467
        self.pipe.rtscts = False
468
        delay = 0.5
469
        start = time.time()
470
        blocks = 0
471
        pos = 0
472
        status = chirp_common.Status()
473
        status.msg = _("Cloning to radio")
474
        status.max = len(self._block_lengths) + 100
475
        for block in self._block_lengths:
476
            if blocks == 8:
477
                repeat = 101
478
            else:
479
                repeat = 1
480
            for _i in range(0, repeat):
481
                time.sleep(0.01)
482
                checksum = yaesu_clone.YaesuChecksum(pos, pos + block - 1)
483
                LOG.debug("Sending block %s" % hex(blocks))
484
                self.pipe.write(struct.pack('B', blocks))
485
                blkdat = self.get_mmap()[pos:pos + block]
486
                LOG.debug("Sending %d bytes:\n%s"
487
                          % (len(blkdat), util.hexprint(blkdat)))
488
                self.pipe.write(blkdat)
489
                xs = checksum.get_calculated(self.get_mmap())
490
                LOG.debug("Sending checksum %s" % hex(xs))
491
                self.pipe.write(struct.pack('B', xs))
492
                buf = self.pipe.read(1)
493
                if not buf or buf[:1] != CMD_ACK:
494
                    time.sleep(delay)
495
                    buf = self.pipe.read(1)
496
                if not buf or buf[:1] != CMD_ACK:
497
                    raise Exception(_("Radio did not ack block %i") % blocks)
498
                pos += block
499
                blocks += 1
500
                status.cur = blocks
501
                self.status_fn(status)
502

    
503
    def sync_in(self):
504
        try:
505
            self._mmap = self._clone_in()
506
        except errors.RadioError:
507
            raise
508
        except Exception as e:
509
            raise errors.RadioError("Failed to communicate with radio: %s"
510
                                    % e)
511
        self.process_mmap()
512

    
513
    def sync_out(self):
514
        try:
515
            self._clone_out()
516
        except errors.RadioError:
517
            raise
518
        except Exception as e:
519
            raise errors.RadioError("Failed to communicate with radio: %s"
520
                                    % e)
521

    
522
    def process_mmap(self):
523
        self._memobj = bitwise.parse(self.MEM_FORMAT, self._mmap)
524

    
525
    def get_features(self):
526
        rf = chirp_common.RadioFeatures()
527
        rf.has_bank = False
528
        rf.has_dtcs = False
529
        rf.valid_modes = [x for x in self.MODES if x in chirp_common.MODES]
530
        rf.valid_tmodes = list(self.TMODES)
531
        rf.valid_duplexes = list(self.DUPLEX)
532
        rf.valid_tuning_steps = list(T_STEPS)
533
        rf.valid_bands = self.VALID_BANDS
534
        rf.valid_power_levels = []
535
        rf.valid_characters = "".join(self.CHARSET)
536
        rf.valid_name_length = 7
537
        rf.valid_skips = []
538
        rf.valid_special_chans = sorted(self.SPECIAL_MEMORIES.keys())
539
        rf.memory_bounds = (1, 500)
540
        rf.has_ctone = True
541
        rf.has_settings = True
542
        rf.has_cross = True
543
        return rf
544

    
545
    def get_raw_memory(self, number):
546
        return repr(self._memobj.memory[number - 1])
547

    
548
    def _get_tmode(self, mem, _mem):
549
        mem.tmode = self.TMODES[_mem.tmode]
550
        mem.rtone = chirp_common.TONES[_mem.tone]
551
        mem.ctone = mem.rtone
552

    
553
    def _set_duplex(self, mem, _mem):
554
        _mem.duplex = self.DUPLEX.index(mem.duplex)
555

    
556
    def get_memory(self, number):
557
        if isinstance(number, str):
558
            return self._get_special(number)
559
        elif number < 0:
560
            # I can't stop delete operation from losing extd_number but
561
            # I know how to get it back
562
            return self._get_special(self.SPECIAL_MEMORIES_REV[number])
563
        else:
564
            return self._get_normal(number)
565

    
566
    def set_memory(self, memory):
567
        if memory.number < 0:
568
            return self._set_special(memory)
569
        else:
570
            return self._set_normal(memory)
571

    
572
    def _get_special(self, number):
573
        mem = chirp_common.Memory()
574
        mem.number = self.SPECIAL_MEMORIES[number]
575
        mem.extd_number = number
576

    
577
        if mem.number in range(self.FIRST_VFOA_INDEX,
578
                               self.LAST_VFOA_INDEX - 1, -1):
579
            _mem = self._memobj.vfoa[-self.LAST_VFOA_INDEX + mem.number]
580
            immutable = ["number", "extd_number", "name", "power"]
581
        elif mem.number in range(self.FIRST_VFOB_INDEX,
582
                                 self.LAST_VFOB_INDEX - 1, -1):
583
            _mem = self._memobj.vfob[-self.LAST_VFOB_INDEX + mem.number]
584
            immutable = ["number", "extd_number", "name", "power"]
585
        elif mem.number in range(-4, -6, -1):           # 2 Home Chans
586
            _mem = self._memobj.home[5 + mem.number]
587
            immutable = ["number", "extd_number", "name", "power"]
588
        elif mem.number == -3:
589
            _mem = self._memobj.qmb
590
            immutable = ["number", "extd_number", "name", "power"]
591
        elif mem.number == -2:
592
            _mem = self._memobj.mtqmb
593
            immutable = ["number", "extd_number", "name", "power"]
594
        elif mem.number == -1:
595
            _mem = self._memobj.mtune
596
            immutable = ["number", "extd_number", "name", "power"]
597
        elif mem.number in self.SPECIAL_PMS.values():
598
            bitindex = (-self.LAST_PMS_INDEX) + mem.number
599
            used = (self._memobj.pmsvisible >> bitindex) & 0x01
600
            valid = (self._memobj.pmsfilled >> bitindex) & 0x01
601
            if not used:
602
                mem.empty = True
603
            if not valid:
604
                mem.empty = True
605
                return mem
606
            mx = (-self.LAST_PMS_INDEX) + mem.number
607
            _mem = self._memobj.pms[mx]
608
            mx = mx + 1
609
            immutable = ["number", "rtone", "ctone", "extd_number",
610
                         "tmode", "cross_mode",
611
                         "power", "duplex", "offset"]
612
        elif mem.number in self.SPECIAL_60M.values():
613
            mx = (-self.LAST_60M_INDEX) + mem.number
614
            _mem = self._memobj.m60[mx]
615
            mx = mx + 1
616
            immutable = ["number", "rtone", "ctone", "extd_number",
617
                         "tmode", "cross_mode",
618
                         "frequency", "power", "duplex", "offset"]
619
        else:
620
            raise Exception("Sorry, you can't edit that special"
621
                            " memory channel %i." % mem.number)
622

    
623
        mem = self._get_memory(mem, _mem)
624
        mem.immutable = immutable
625

    
626
        return mem
627

    
628
    def _set_special(self, mem):
629
        if mem.empty and mem.number not in self.SPECIAL_PMS.values():
630
            # can't delete special memories!
631
            raise errors.RadioError("Sorry, special memory can't be deleted")
632

    
633
        cur_mem = self._get_special(self.SPECIAL_MEMORIES_REV[mem.number])
634

    
635
        if mem.number in range(self.FIRST_VFOA_INDEX,
636
                               self.LAST_VFOA_INDEX - 1, -1):
637
            _mem = self._memobj.vfoa[-self.LAST_VFOA_INDEX + mem.number]
638
        elif mem.number in range(self.FIRST_VFOB_INDEX,
639
                                 self.LAST_VFOB_INDEX - 1, -1):
640
            _mem = self._memobj.vfob[-self.LAST_VFOB_INDEX + mem.number]
641
        elif mem.number in range(-4, -6, -1):
642
            _mem = self._memobj.home[5 + mem.number]
643
        elif mem.number == -3:
644
            _mem = self._memobj.qmb
645
        elif mem.number == -2:
646
            _mem = self._memobj.mtqmb
647
        elif mem.number == -1:
648
            _mem = self._memobj.mtune
649
        elif mem.number in self.SPECIAL_PMS.values():
650
            bitindex = (-self.LAST_PMS_INDEX) + mem.number
651
            wasused = (self._memobj.pmsvisible >> bitindex) & 0x01
652
            wasvalid = (self._memobj.pmsfilled >> bitindex) & 0x01
653
            if mem.empty:
654
                if wasvalid and not wasused:
655
                    # pylint get confused by &= operator
656
                    self._memobj.pmsfilled = self._memobj.pmsfilled & \
657
                        ~ (1 << bitindex)
658
                # pylint get confused by &= operator
659
                self._memobj.pmsvisible = self._memobj.pmsvisible & \
660
                    ~ (1 << bitindex)
661
                return
662
            # pylint get confused by |= operator
663
            self._memobj.pmsvisible = self._memobj.pmsvisible | 1 << bitindex
664
            self._memobj.pmsfilled = self._memobj.pmsfilled | 1 << bitindex
665
            _mem = self._memobj.pms[-self.LAST_PMS_INDEX + mem.number]
666
        else:
667
            raise errors.RadioError("Sorry, you can't edit"
668
                                    " that special memory.")
669

    
670
        for key in cur_mem.immutable:
671
            if key != "extd_number":
672
                if cur_mem.__dict__[key] != mem.__dict__[key]:
673
                    raise errors.RadioError("Editing field `%s' " % key +
674
                                            "is not supported on this channel")
675

    
676
        self._set_memory(mem, _mem)
677

    
678
    def _get_normal(self, number):
679
        _mem = self._memobj.memory[number - 1]
680
        used = (self._memobj.visible[(number - 1) / 8] >> (number - 1) % 8) \
681
            & 0x01
682
        valid = (self._memobj.filled[(number - 1) / 8] >> (number - 1) % 8) \
683
            & 0x01
684

    
685
        mem = chirp_common.Memory()
686
        mem.number = number
687
        if not used:
688
            mem.empty = True
689
            if not valid or _mem.freq == 0xffffffff:
690
                return mem
691
        if mem.number == 1:
692
            mem.immutable = ['empty']
693
        return self._get_memory(mem, _mem)
694

    
695
    def _set_normal(self, mem):
696
        _mem = self._memobj.memory[mem.number - 1]
697
        wasused = (self._memobj.visible[(mem.number - 1) / 8] >>
698
                   (mem.number - 1) % 8) & 0x01
699
        wasvalid = (self._memobj.filled[(mem.number - 1) / 8] >>
700
                    (mem.number - 1) % 8) & 0x01
701

    
702
        if mem.empty:
703
            if mem.number == 1:
704
                raise Exception("Sorry, can't delete first memory")
705
            if wasvalid and not wasused:
706
                self._memobj.filled[(mem.number - 1) // 8] &= \
707
                    ~(1 << (mem.number - 1) % 8)
708
                _mem.set_raw("\xFF" * (_mem.size() // 8))    # clean up
709
            self._memobj.visible[(mem.number - 1) // 8] &= \
710
                ~(1 << (mem.number - 1) % 8)
711
            return
712
        if not wasvalid:
713
            _mem.set_raw("\x00" * (_mem.size() // 8))    # clean up
714

    
715
        self._memobj.visible[(mem.number - 1) // 8] |= 1 << (mem.number - 1) \
716
            % 8
717
        self._memobj.filled[(mem.number - 1) // 8] |= 1 << (mem.number - 1) \
718
            % 8
719
        self._set_memory(mem, _mem)
720

    
721
    def _get_memory(self, mem, _mem):
722
        mem.freq = int(_mem.freq)
723
        mem.offset = int(_mem.offset)
724
        mem.duplex = self.DUPLEX[_mem.duplex]
725
        # Mode gets tricky with dual (USB+DATA) options
726
        vx = _mem.mode
727
        if vx == 4:         # FM or NFM
728
            if _mem.mode2 == 2:
729
                vx = 4          # FM
730
            else:
731
                vx = 8          # NFM
732
        if vx == 10:         # CWR
733
            vx = 9
734
        if vx == 5:         # Data/Dual mode
735
            if _mem.mode2 == 0:          # RTTY-L
736
                vx = 5
737
            if _mem.mode2 == 1:     # USER-L
738
                vx = 6
739
            if _mem.mode2 == 2:      # USER-U
740
                vx = 7
741
        try:
742
            mem.mode = self.MODES[vx]
743
        except ValueError:
744
            LOG.error('The FT-450 driver is broken for unsupported modes')
745
        if mem.mode == "FM" or mem.mode == "NFM":
746
            mem.tuning_step = self.STEPSFM[_mem.fm_step]
747
        elif mem.mode == "AM":
748
            mem.tuning_step = self.STEPSAM[_mem.am_step]
749
        elif mem.mode[:2] == "CW":
750
            mem.tuning_step = self.STEPSSSB[_mem.ssb_step]
751
        else:
752
            try:
753
                mem.tuning_step = self.STEPSSSB[_mem.ssb_step]
754
            except IndexError:
755
                pass
756
        self._get_tmode(mem, _mem)
757

    
758
        if _mem.tag_on_off == 2:
759
            for i in _mem.name:
760
                if i == 0xFF:
761
                    break
762
                if chr(i) in self.CHARSET:
763
                    mem.name += chr(i)
764
                else:
765
                    # radio has some graphical chars that are not supported
766
                    # we replace those with a *
767
                    LOG.info("Replacing char %x with *" % i)
768
                    mem.name += "*"
769
            mem.name = mem.name.rstrip()
770
        else:
771
            mem.name = ""
772

    
773
        mem.extra = RadioSettingGroup("extra", "Extra")
774

    
775
        rs = RadioSetting("ipo", "IPO",
776
                          RadioSettingValueBoolean(bool(_mem.ipo)))
777
        rs.set_doc("Bypass preamp")
778
        mem.extra.append(rs)
779

    
780
        rs = RadioSetting("att", "ATT",
781
                          RadioSettingValueBoolean(bool(_mem.att)))
782
        rs.set_doc("10dB front end attenuator")
783
        mem.extra.append(rs)
784

    
785
        rs = RadioSetting("cnturon", "Contour Filter",
786
                          RadioSettingValueBoolean(_mem.cnturon))
787
        rs.set_doc("Contour filter on/off")
788
        mem.extra.append(rs)
789

    
790
        options = ["Peak", "Null"]
791
        rs = RadioSetting("cnturpk", "Contour Filter Mode",
792
                          RadioSettingValueList(options,
793
                                                options[_mem.cnturpk]))
794
        mem.extra.append(rs)
795

    
796
        options = ["Low", "High"]
797
        rs = RadioSetting("cnturgn", "Contour Filter Gain",
798
                          RadioSettingValueList(options,
799
                                                options[_mem.cnturgn]))
800
        rs.set_doc("Filter gain/attenuation")
801
        mem.extra.append(rs)
802

    
803
        options = ["-2", "-1", "Center", "+1", "+2"]
804
        rs = RadioSetting("cnturmd", "Contour Filter Notch",
805
                          RadioSettingValueList(options,
806
                                                options[_mem.cnturmd]))
807
        rs.set_doc("Filter notch offset")
808
        mem.extra.append(rs)
809

    
810
        rs = RadioSetting("notch", "Notch Filter",
811
                          RadioSettingValueBoolean(_mem.notch))
812
        rs.set_doc("IF bandpass filter")
813
        mem.extra.append(rs)
814

    
815
        vx = 1
816
        options = ["<-", "Center", "+>"]
817
        if _mem.notch_pos < 0:
818
            vx = 0
819
        if _mem.notch_pos > 0:
820
            vx = 2
821
        rs = RadioSetting("notch_pos", "Notch Position",
822
                          RadioSettingValueList(options, options[vx]))
823
        rs.set_doc("IF bandpass filter shift")
824
        mem.extra.append(rs)
825

    
826
        vx = 0
827
        if mem.mode[1:] == "SB":
828
            options = ["1.8 kHz", "2.4 kHz", "3.0 kHz"]
829
            vx = _mem.sb_width
830
            stx = "sb_width"
831
        elif mem.mode[:1] == "CW":
832
            options = ["300 Hz", "500 kHz", "2.4 kHz"]
833
            vx = _mem.cw_width
834
            stx = "cw_width"
835
        elif mem.mode[:4] == "USER" or mem.mode[:4] == "RTTY":
836
            options = ["300 Hz", "2.4 kHz", "3.0 kHz"]
837
            vx = _mem.sb_width
838
            stx = "sb_width"
839
        elif mem.mode == "AM":
840
            options = ["3.0 kHz", "6.0 kHz", "9.0 kHz"]
841
            vx = _mem.am_width
842
            stx = "am_width"
843
        else:
844
            options = ["2.5 kHz", "5.0 kHz"]
845
            vx = _mem.fm_width
846
            stx = "fm_width"
847
        rs = RadioSetting(stx, "IF Bandpass Filter Width",
848
                          RadioSettingValueList(options, options[vx]))
849
        rs.set_doc("DSP IF bandpass Notch width (Hz)")
850
        mem.extra.append(rs)
851

    
852
        rs = RadioSetting("dnr_on", "DSP Noise Reduction",
853
                          RadioSettingValueBoolean(bool(_mem.dnr_on)))
854
        rs.set_doc("Digital noise processing")
855
        mem.extra.append(rs)
856

    
857
        options = ["Off", "1", "2", "3", "4", "5", "6", "7",
858
                          "8", "9", "10", "11"]
859
        rs = RadioSetting("dnr_val", "DSP Noise Reduction Alg",
860
                          RadioSettingValueList(options,
861
                                                options[_mem.dnr_val]))
862
        rs.set_doc("Digital noise reduction algorithm number (1-11)")
863
        mem.extra.append(rs)
864

    
865
        return mem          # end get_memory
866

    
867
    def _set_memory(self, mem, _mem):
868
        if len(mem.name) > 0:
869
            _mem.tag_on_off = 2
870
        else:
871
            _mem.tag_on_off = 1
872
        self._set_duplex(mem, _mem)
873
        _mem.mode2 = 0
874
        if mem.mode == "USER-L":
875
            _mem.mode = 5
876
            _mem.mode2 = 1
877
        elif mem.mode == "USER-U":
878
            _mem.mode = 5
879
            _mem.mode2 = 2
880
        elif mem.mode == "RTTY-L":
881
            _mem.mode = 5
882
            _mem.mode2 = 0
883
        elif mem.mode == "CWR":
884
            _mem.mode = 10
885
            _mem.mode2 = 0
886
        elif mem.mode == "CW":
887
            _mem.mode = 2
888
            _mem.mode2 = 0
889
        elif mem.mode == "NFM":
890
            _mem.mode = 4
891
            _mem.mode2 = 1
892
        elif mem.mode == "FM":
893
            _mem.mode = 4
894
            _mem.mode2 = 2
895
        else:           # LSB, USB, AM
896
            _mem.mode = self.MODES.index(mem.mode)
897
            _mem.mode2 = 0
898
        try:
899
            _mem.ssb_step = self.STEPSSSB.index(mem.tuning_step)
900
        except ValueError:
901
            pass
902
        try:
903
            _mem.am_step = self.STEPSAM.index(mem.tuning_step)
904
        except ValueError:
905
            pass
906
        try:
907
            _mem.fm_step = self.STEPSFM.index(mem.tuning_step)
908
        except ValueError:
909
            pass
910
        _mem.freq = mem.freq
911
        _mem.uprband = 0
912
        if mem.freq >= 33000000:
913
            _mem.uprband = 1
914
        _mem.offset = mem.offset
915
        _mem.tmode = self.TMODES.index(mem.tmode)
916
        _mem.tone = chirp_common.TONES.index(mem.rtone)
917
        _mem.tunerok = 0            # Dont know what these two do...
918
        _mem.tunerbad = 0
919

    
920
        for i in range(0, 7):
921
            _mem.name[i] = ord(mem.name.ljust(7)[i])
922

    
923
        for setting in mem.extra:
924
            if setting.get_name() == "notch_pos":
925
                vx = 0          # Override list string with signed value
926
                stx = str(setting.value)
927
                if stx == "<-":
928
                    vx = -13
929
                if stx == "+>":
930
                    vx = 12
931
                setattr(_mem, "notch_pos", vx)
932
            elif setting.get_name() == "dnr_val":
933
                stx = str(setting.value)        # Convert string to int
934
                vx = 0
935
                if stx != "Off":
936
                    vx = int(stx)
937
                else:
938
                    setattr(_mem, "dnr_on", 0)
939
                setattr(_mem, setting.get_name(), vx)
940
            else:
941
                setattr(_mem, setting.get_name(), setting.value)
942

    
943
    @classmethod
944
    def match_model(cls, filedata, filename):
945
        """Match the opened/downloaded image to the correct version"""
946
        if len(filedata) == cls.MEM_SIZE + 7:    # +7 bytes of model name
947
            rid = filedata[cls.MEM_SIZE:cls.MEM_SIZE + 7]
948
            if rid.startswith(cls.MODEL.encode()):
949
                return True
950
        else:
951
            return False
952

    
953
    def _invert_me(self, setting, obj, atrb):
954
        """Callback: from inverted logic 1-bit booleans"""
955
        invb = not setting.value
956
        setattr(obj, atrb, invb)
957
        return
958

    
959
    def _chars2str(self, cary, knt):
960
        """Convert raw memory char array to a string: NOT a callback."""
961
        stx = ""
962
        for char in cary[0:knt]:
963
            stx += chr(int(char))
964
        return stx
965

    
966
    def _my_str2ary(self, setting, obj, atrba, knt):
967
        """Callback: convert string to fixed-length char array.."""
968
        ary = ""
969
        for j in range(0, knt, 1):
970
            chx = ord(str(setting.value)[j])
971
            if chx < 32 or chx > 125:     # strip non-printing
972
                ary += " "
973
            else:
974
                ary += str(setting.value)[j]
975
        setattr(obj, atrba, ary)
976
        return
977

    
978
    def get_settings(self):
979
        _settings = self._memobj.settings
980
        _beacon = self._memobj.beacontext
981
        gen = RadioSettingGroup("gen", "General")
982
        cw = RadioSettingGroup("cw", "CW")
983
        pnlcfg = RadioSettingGroup("pnlcfg", "Panel buttons")
984
        pnlset = RadioSettingGroup("pnlset", "Panel settings")
985
        voxdat = RadioSettingGroup("voxdat", "VOX and Data")
986
        mic = RadioSettingGroup("mic", "Microphone")
987
        mybands = RadioSettingGroup("mybands", "My Bands")
988
        mymodes = RadioSettingGroup("mymodes", "My Modes")
989

    
990
        top = RadioSettings(gen,  cw, pnlcfg, pnlset, voxdat, mic,
991
                            mymodes, mybands)
992

    
993
        self._do_general_settings(gen)
994
        self._do_cw_settings(cw)
995
        self._do_panel_buttons(pnlcfg)
996
        self._do_panel_settings(pnlset)
997
        self._do_vox_settings(voxdat)
998
        self._do_mic_settings(mic)
999
        self._do_mymodes_settings(mymodes)
1000
        self._do_mybands_settings(mybands)
1001

    
1002
        return top
1003

    
1004
    def _do_general_settings(self, tab):
1005
        _settings = self._memobj.settings
1006

    
1007
        rs = RadioSetting("ext_mnu", "Extended menu",
1008
                          RadioSettingValueBoolean(_settings.ext_mnu))
1009
        rs.set_doc("Enables access to extended settings in the radio")
1010
        tab.append(rs)
1011
        # Issue #8183 bugfix
1012
        rs = RadioSetting("apo", "APO time (Hrs)",
1013
                          RadioSettingValueInteger(0, 12, _settings.apo))
1014
        tab.append(rs)
1015

    
1016
        options = ["%i" % i for i in range(0, 21)]
1017
        options[0] = "Off"
1018
        rs = RadioSetting("tot", "TX 'TOT' time-out (mins)",
1019
                          RadioSettingValueList(options,
1020
                                                options[_settings.tot]))
1021
        tab.append(rs)
1022

    
1023
        bx = not _settings.cat_rts     # Convert from Enable=0
1024
        rs = RadioSetting("cat_rts", "CAT RTS flow control",
1025
                          RadioSettingValueBoolean(bx))
1026
        rs.set_apply_callback(self._invert_me, _settings, "cat_rts")
1027
        tab.append(rs)
1028

    
1029
        options = ["0", "100ms", "1000ms", "3000ms"]
1030
        rs = RadioSetting("cat_tot", "CAT Timeout",
1031
                          RadioSettingValueList(options,
1032
                                                options[_settings.cat_tot]))
1033
        tab.append(rs)
1034

    
1035
        options = ["4800", "9600", "19200", "38400", "Data"]
1036
        rs = RadioSetting("catrate", "CAT rate",
1037
                          RadioSettingValueList(options,
1038
                                                options[_settings.catrate]))
1039
        tab.append(rs)
1040

    
1041
        rs = RadioSetting("mem_grp", "Mem groups",
1042
                          RadioSettingValueBoolean(_settings.mem_grp))
1043
        tab.append(rs)
1044

    
1045
        rs = RadioSetting("scn_res", "Resume scan (secs)",
1046
                          RadioSettingValueInteger(0, 10, _settings.scn_res))
1047
        tab.append(rs)
1048

    
1049
        rs = RadioSetting("clk_sft", "CPU clock shift",
1050
                          RadioSettingValueBoolean(_settings.clk_sft))
1051
        tab.append(rs)
1052

    
1053
        rs = RadioSetting("split", "TX/RX Frequency Split",
1054
                          RadioSettingValueBoolean(_settings.split))
1055
        tab.append(rs)
1056

    
1057
        rs = RadioSetting("qspl_f", "Quick-Split freq offset (kHz)",
1058
                          RadioSettingValueInteger(-20, 20, _settings.qspl_f))
1059
        tab.append(rs)
1060

    
1061
        rs = RadioSetting("emergen", "Alaska Emergency Mem 5167.5 kHz",
1062
                          RadioSettingValueBoolean(_settings.emergen))
1063
        tab.append(rs)
1064

    
1065
        rs = RadioSetting("stby_beep", "PTT release 'Standby' beep",
1066
                          RadioSettingValueBoolean(_settings.stby_beep))
1067
        tab.append(rs)
1068

    
1069
        options = ["ATAS", "EXT ATU", "INT ATU", "INTRATU", "F-TRANS"]
1070
        rs = RadioSetting("tuner", "Antenna Tuner",
1071
                          RadioSettingValueList(options,
1072
                                                options[_settings.tuner]))
1073
        tab.append(rs)
1074

    
1075
        rs = RadioSetting("rfpower", "RF power (watts)",
1076
                          RadioSettingValueInteger(5, 100, _settings.rfpower))
1077
        tab.append(rs)      # End of _do_general_settings
1078

    
1079
    def _do_cw_settings(self, cw):        # - - - CW - - -
1080
        _settings = self._memobj.settings
1081
        _beacon = self._memobj.beacontext
1082

    
1083
        rs = RadioSetting("cw_dly", "CW break-in delay (ms * 10)",
1084
                          RadioSettingValueInteger(0, 300, _settings.cw_dly))
1085
        cw.append(rs)
1086

    
1087
        options = ["%i Hz" % i for i in range(400, 801, 100)]
1088
        rs = RadioSetting("cwpitch", "CW pitch",
1089
                          RadioSettingValueList(options,
1090
                                                options[_settings.cwpitch]))
1091
        cw.append(rs)
1092

    
1093
        rs = RadioSetting("cwspeed", "CW speed (wpm)",
1094
                          RadioSettingValueInteger(4, 60, _settings.cwspeed))
1095
        rs.set_doc("Cpm is Wpm * 5")
1096
        cw.append(rs)
1097

    
1098
        options = ["1:%1.1f" % (i / 10) for i in range(25, 46, 1)]
1099
        rs = RadioSetting("cwweigt", "CW weight",
1100
                          RadioSettingValueList(options,
1101
                                                options[_settings.cwweigt]))
1102
        cw.append(rs)
1103

    
1104
        options = ["15ms", "20ms", "25ms", "30ms"]
1105
        rs = RadioSetting("cw_qsk", "CW delay before TX in QSK mode",
1106
                          RadioSettingValueList(options,
1107
                                                options[_settings.cw_qsk]))
1108
        cw.append(rs)
1109

    
1110
        rs = RadioSetting("cwstone_sgn", "CW sidetone volume Linked",
1111
                          RadioSettingValueBoolean(_settings.cwstone_sgn))
1112
        rs.set_doc("If set; volume is relative to AF Gain knob.")
1113
        cw.append(rs)
1114

    
1115
        rs = RadioSetting("cwstone_lnk", "CW sidetone linked volume",
1116
                          RadioSettingValueInteger(-50, 50,
1117
                                                   _settings.cwstone_lnk))
1118
        cw.append(rs)
1119

    
1120
        rs = RadioSetting("cwstone_fix", "CW sidetone fixed volume",
1121
                          RadioSettingValueInteger(0, 100,
1122
                                                   _settings.cwstone_fix))
1123
        cw.append(rs)
1124

    
1125
        options = ["Numeric", "Alpha", "Mixed"]
1126
        rs = RadioSetting("cwtrain", "CW Training mode",
1127
                          RadioSettingValueList(options,
1128
                                                options[_settings.cwtrain]))
1129
        cw.append(rs)
1130

    
1131
        rs = RadioSetting("cw_auto", "CW key jack- auto CW mode",
1132
                          RadioSettingValueBoolean(_settings.cw_auto))
1133
        rs.set_doc("Enable for CW mode auto-set when keyer pluuged in.")
1134
        cw.append(rs)
1135

    
1136
        options = ["Normal", "Reverse"]
1137
        rs = RadioSetting("cw_key", "CW paddle wiring",
1138
                          RadioSettingValueList(options,
1139
                                                options[_settings.cw_key]))
1140
        cw.append(rs)
1141

    
1142
        rs = RadioSetting("beacon_time", "CW beacon Tx interval (secs)",
1143
                          RadioSettingValueInteger(0, 255,
1144
                                                   _settings.beacon_time))
1145
        cw.append(rs)
1146

    
1147
        tmp = self._chars2str(_beacon.t1, 40)
1148
        rs = RadioSetting("t1", "CW Beacon Line 1",
1149
                          RadioSettingValueString(0, 40, tmp))
1150
        rs.set_apply_callback(self._my_str2ary, _beacon, "t1", 40)
1151
        cw.append(rs)
1152

    
1153
        tmp = self._chars2str(_beacon.t2, 40)
1154
        rs = RadioSetting("t2", "CW Beacon Line 2",
1155
                          RadioSettingValueString(0, 40, tmp))
1156
        rs.set_apply_callback(self._my_str2ary, _beacon, "t2", 40)
1157
        cw.append(rs)
1158

    
1159
        tmp = self._chars2str(_beacon.t3, 40)
1160
        rs = RadioSetting("t3", "CW Beacon Line 3",
1161
                          RadioSettingValueString(0, 40, tmp))
1162
        rs.set_apply_callback(self._my_str2ary, _beacon, "t3", 40)
1163
        cw.append(rs)       # END _do_cw_settings
1164

    
1165
    def _do_panel_settings(self, pnlset):    # - - - Panel settings
1166
        _settings = self._memobj.settings
1167

    
1168
        bx = not _settings.amfmdial     # Convert from Enable=0
1169
        rs = RadioSetting("amfmdial", "AM&FM Dial",
1170
                          RadioSettingValueBoolean(bx))
1171
        rs.set_apply_callback(self._invert_me, _settings, "amfmdial")
1172
        pnlset.append(rs)
1173

    
1174
        options = ["440 Hz", "880 Hz", "1760 Hz"]
1175
        rs = RadioSetting("beepton", "Beep frequency",
1176
                          RadioSettingValueList(options,
1177
                                                options[_settings.beepton]))
1178
        pnlset.append(rs)
1179

    
1180
        rs = RadioSetting("beepvol_sgn", "Beep volume Linked",
1181
                          RadioSettingValueBoolean(_settings.beepvol_sgn))
1182
        rs.set_doc("If set; volume is relative to AF Gain knob.")
1183
        pnlset.append(rs)
1184

    
1185
        rs = RadioSetting("beepvol_lnk", "Linked beep volume",
1186
                          RadioSettingValueInteger(-50, 50,
1187
                                                   _settings.beepvol_lnk))
1188
        rs.set_doc("Relative to AF-Gain setting.")
1189
        pnlset.append(rs)
1190

    
1191
        rs = RadioSetting("beepvol_fix", "Fixed beep volume",
1192
                          RadioSettingValueInteger(0, 100,
1193
                                                   _settings.beepvol_fix))
1194
        rs.set_doc("When Linked setting is unchecked.")
1195
        pnlset.append(rs)
1196

    
1197
        rs = RadioSetting("cont", "LCD Contrast",
1198
                          RadioSettingValueInteger(1, 24, _settings.cont))
1199
        rs.set_doc("This setting does not appear to do anything...")
1200
        pnlset.append(rs)
1201

    
1202
        rs = RadioSetting("dimmer", "LCD Dimmer",
1203
                          RadioSettingValueInteger(1, 8,  _settings.dimmer))
1204
        pnlset.append(rs)
1205

    
1206
        options = ["RF-Gain", "Squelch"]
1207
        rs = RadioSetting("sql_rfg", "Squelch/RF-Gain",
1208
                          RadioSettingValueList(options,
1209
                                                options[_settings.sql_rfg]))
1210
        pnlset.append(rs)
1211

    
1212
        options = ["Frequencies", "Panel", "All"]
1213
        rs = RadioSetting("lockmod", "Lock Mode",
1214
                          RadioSettingValueList(options,
1215
                                                options[_settings.lockmod]))
1216
        pnlset.append(rs)
1217

    
1218
        options = ["Dial", "SEL"]
1219
        rs = RadioSetting("clar_btn", "CLAR button control",
1220
                          RadioSettingValueList(options,
1221
                                                options[_settings.clar_btn]))
1222
        pnlset.append(rs)
1223

    
1224
        if _settings.dialstp_mode == 0:             # AM/FM
1225
            options = ["SSB/CW:1Hz", "SSB/CW:10Hz", "SSB/CW:20Hz"]
1226
        else:
1227
            options = ["AM/FM:100Hz", "AM/FM:200Hz"]
1228
        rs = RadioSetting("dialstp", "Dial tuning step",
1229
                          RadioSettingValueList(options,
1230
                                                options[_settings.dialstp]))
1231
        pnlset.append(rs)
1232

    
1233
        options = ["0.5secs", "1.0secs", "1.5secs", "2.0secs"]
1234
        rs = RadioSetting("keyhold", "Buttons hold-to-activate time",
1235
                          RadioSettingValueList(options,
1236
                                                options[_settings.keyhold]))
1237
        pnlset.append(rs)
1238

    
1239
        rs = RadioSetting("m_tune", "Memory tune",
1240
                          RadioSettingValueBoolean(_settings.m_tune))
1241
        pnlset.append(rs)
1242

    
1243
        rs = RadioSetting("peakhold", "S-Meter display hold (1sec)",
1244
                          RadioSettingValueBoolean(_settings.peakhold))
1245
        pnlset.append(rs)
1246

    
1247
        options = ["CW Sidetone", "CW Speed", "100 kHz step", "1 MHz Step",
1248
                   "Mic Gain", "RF Power"]
1249
        rs = RadioSetting("seldial", "SEL dial 2nd function (push)",
1250
                          RadioSettingValueList(options,
1251
                                                options[_settings.seldial]))
1252
        pnlset.append(rs)
1253
    # End _do_panel_settings
1254

    
1255
    def _do_panel_buttons(self, pnlcfg):      # - - - Current Panel Config
1256
        _settings = self._memobj.settings
1257

    
1258
        rs = RadioSetting("pnl_cs", "C.S. Function",
1259
                          RadioSettingValueList(self.FUNC_LIST,
1260
                                                self.FUNC_LIST[_settings.pnl_cs]))
1261
        pnlcfg.append(rs)
1262

    
1263
        rs = RadioSetting("nb", "Noise blanker",
1264
                          RadioSettingValueBoolean(_settings.nb))
1265
        pnlcfg.append(rs)
1266

    
1267
        options = ["Auto", "Fast",  "Slow", "Auto/Fast", "Auto/Slow", "?5?"]
1268
        rs = RadioSetting("agc", "AGC",
1269
                          RadioSettingValueList(options,
1270
                                                options[_settings.agc]))
1271
        pnlcfg.append(rs)
1272

    
1273
        rs = RadioSetting("keyer", "Keyer",
1274
                          RadioSettingValueBoolean(_settings.keyer))
1275
        pnlcfg.append(rs)
1276

    
1277
        rs = RadioSetting("fast", "Fast step",
1278
                          RadioSettingValueBoolean(_settings.fast))
1279
        pnlcfg.append(rs)
1280

    
1281
        rs = RadioSetting("lock", "Lock (per Lock Mode)",
1282
                          RadioSettingValueBoolean(_settings.lock))
1283
        pnlcfg.append(rs)
1284

    
1285
        options = ["PO",  "ALC", "SWR"]
1286
        rs = RadioSetting("mtr_mode", "S-Meter mode",
1287
                          RadioSettingValueList(options,
1288
                                                options[_settings.mtr_mode]))
1289
        pnlcfg.append(rs)
1290
        # End _do_panel_Buttons
1291

    
1292
    def _do_vox_settings(self, voxdat):     # - - VOX and DATA Settings
1293
        _settings = self._memobj.settings
1294

    
1295
        rs = RadioSetting("vox_dly", "VOX delay (x 100 ms)",
1296
                          RadioSettingValueInteger(1, 30, _settings.vox_dly))
1297
        voxdat.append(rs)
1298

    
1299
        rs = RadioSetting("vox_gain", "VOX Gain",
1300
                          RadioSettingValueInteger(0, 100,
1301
                                                   _settings.vox_gain))
1302
        voxdat.append(rs)
1303

    
1304
        rs = RadioSetting("dig_vox", "Digital VOX Gain",
1305
                          RadioSettingValueInteger(0, 100,
1306
                                                   _settings.dig_vox))
1307
        voxdat.append(rs)
1308

    
1309
        rs = RadioSetting("d_disp", "User-L/U freq offset (Hz)",
1310
                          RadioSettingValueInteger(-3000, 30000,
1311
                                                   _settings.d_disp, 10))
1312
        voxdat.append(rs)
1313

    
1314
        options = ["170 Hz", "200 Hz", "425 Hz", "850 Hz"]
1315
        rs = RadioSetting("rty_sft", "RTTY FSK Freq Shift",
1316
                          RadioSettingValueList(options,
1317
                                                options[_settings.rty_sft]))
1318
        voxdat.append(rs)
1319

    
1320
        options = ["1275 Hz", "2125 Hz"]
1321
        rs = RadioSetting("rty_ton", "RTTY FSK Mark tone",
1322
                          RadioSettingValueList(options,
1323
                                                options[_settings.rty_ton]))
1324
        voxdat.append(rs)
1325

    
1326
        options = ["Normal", "Reverse"]
1327
        rs = RadioSetting("rtyrpol", "RTTY Mark/Space RX polarity",
1328
                          RadioSettingValueList(options,
1329
                                                options[_settings.rtyrpol]))
1330
        voxdat.append(rs)
1331

    
1332
        rs = RadioSetting("rtytpol", "RTTY Mark/Space TX polarity",
1333
                          RadioSettingValueList(options,
1334
                                                options[_settings.rtytpol]))
1335
        voxdat.append(rs)
1336
        # End _do_vox_settings
1337

    
1338
    def _do_mic_settings(self, mic):    # - - MIC Settings
1339
        _settings = self._memobj.settings
1340

    
1341
        rs = RadioSetting("mic_eq", "Mic Equalizer",
1342
                          RadioSettingValueInteger(0, 9, _settings.mic_eq))
1343
        mic.append(rs)
1344

    
1345
        options = ["Low", "Normal", "High"]
1346
        rs = RadioSetting("micgain", "Mic Gain",
1347
                          RadioSettingValueList(options,
1348
                                                options[_settings.micgain]))
1349
        mic.append(rs)
1350

    
1351
        rs = RadioSetting("micscan", "Mic scan enabled",
1352
                          RadioSettingValueBoolean(_settings.micscan))
1353
        rs.set_doc("Enables channel scanning via mic up/down buttons.")
1354
        mic.append(rs)
1355

    
1356
        rs = RadioSetting("pm_dwn", "Mic Down button function",
1357
                          RadioSettingValueList(self.FUNC_LIST,
1358
                                                self.FUNC_LIST[_settings.pm_dwn]))
1359
        mic.append(rs)
1360

    
1361
        rs = RadioSetting("pm_fst", "Mic Fast button function",
1362
                          RadioSettingValueList(self.FUNC_LIST,
1363
                                                self.FUNC_LIST[_settings.pm_fst]))
1364
        mic.append(rs)
1365

    
1366
        rs = RadioSetting("pm_up", "Mic Up button function",
1367
                          RadioSettingValueList(self.FUNC_LIST,
1368
                                                self.FUNC_LIST[_settings.pm_up]))
1369
        mic.append(rs)
1370
        # End _do_mic_settings
1371

    
1372
    def _do_mymodes_settings(self, mymodes):    # - - MYMODES
1373
        _settings = self._memobj.settings  # Inverted Logic requires callback
1374

    
1375
        bx = not _settings.mym_lsb
1376
        rs = RadioSetting("mym_lsb", "LSB", RadioSettingValueBoolean(bx))
1377
        rs.set_apply_callback(self._invert_me, _settings, "mym_lsb")
1378
        mymodes.append(rs)
1379

    
1380
        bx = not _settings.mym_usb
1381
        rs = RadioSetting("mym_usb", "USB", RadioSettingValueBoolean(bx))
1382
        rs.set_apply_callback(self._invert_me, _settings, "mym_usb")
1383
        mymodes.append(rs)
1384

    
1385
        bx = not _settings.mym_cw
1386
        rs = RadioSetting("mym_cw", "CW", RadioSettingValueBoolean(bx))
1387
        rs.set_apply_callback(self._invert_me, _settings, "mym_cw")
1388
        mymodes.append(rs)
1389

    
1390
        bx = not _settings.mym_am
1391
        rs = RadioSetting("mym_am", "AM", RadioSettingValueBoolean(bx))
1392
        rs.set_apply_callback(self._invert_me, _settings, "mym_am")
1393
        mymodes.append(rs)
1394

    
1395
        bx = not _settings.mym_fm
1396
        rs = RadioSetting("mym_fm", "FM", RadioSettingValueBoolean(bx))
1397
        rs.set_apply_callback(self._invert_me, _settings, "mym_fm")
1398
        mymodes.append(rs)
1399

    
1400
        bx = not _settings.mym_data
1401
        rs = RadioSetting("mym_data", "DATA", RadioSettingValueBoolean(bx))
1402
        rs.set_apply_callback(self._invert_me, _settings, "mym_data")
1403
        mymodes.append(rs)
1404
        # End _do_mymodes_settings
1405

    
1406
    def _do_mybands_settings(self, mybands):    # - - MYBANDS Settings
1407
        _settings = self._memobj.settings  # Inverted Logic requires callback
1408

    
1409
        bx = not _settings.myb_1_8
1410
        rs = RadioSetting("myb_1_8", "1.8 MHz", RadioSettingValueBoolean(bx))
1411
        rs.set_apply_callback(self._invert_me, _settings, "myb_1_8")
1412
        mybands.append(rs)
1413

    
1414
        bx = not _settings.myb_3_5
1415
        rs = RadioSetting("myb_3_5", "3.5 MHz", RadioSettingValueBoolean(bx))
1416
        rs.set_apply_callback(self._invert_me, _settings, "myb_3_5")
1417
        mybands.append(rs)
1418

    
1419
        bx = not _settings.myb_7
1420
        rs = RadioSetting("myb_7", "7 MHz", RadioSettingValueBoolean(bx))
1421
        rs.set_apply_callback(self._invert_me, _settings, "myb_7")
1422
        mybands.append(rs)
1423

    
1424
        bx = not _settings.myb_10
1425
        rs = RadioSetting("myb_10", "10 MHz", RadioSettingValueBoolean(bx))
1426
        rs.set_apply_callback(self._invert_me, _settings, "myb_10")
1427
        mybands.append(rs)
1428

    
1429
        bx = not _settings.myb_14
1430
        rs = RadioSetting("myb_14", "14 MHz", RadioSettingValueBoolean(bx))
1431
        rs.set_apply_callback(self._invert_me, _settings, "myb_14")
1432
        mybands.append(rs)
1433

    
1434
        bx = not _settings.myb_18
1435
        rs = RadioSetting("myb_18", "18 MHz", RadioSettingValueBoolean(bx))
1436
        rs.set_apply_callback(self._invert_me, _settings, "myb_18")
1437
        mybands.append(rs)
1438

    
1439
        bx = not _settings.myb_21
1440
        rs = RadioSetting("myb_21", "21 MHz", RadioSettingValueBoolean(bx))
1441
        rs.set_apply_callback(self._invert_me, _settings, "myb_21")
1442
        mybands.append(rs)
1443

    
1444
        bx = not _settings.myb_24
1445
        rs = RadioSetting("myb_24", "24 MHz", RadioSettingValueBoolean(bx))
1446
        rs.set_apply_callback(self._invert_me, _settings, "myb_24")
1447
        mybands.append(rs)
1448

    
1449
        bx = not _settings.myb_28
1450
        rs = RadioSetting("myb_28", "28 MHz", RadioSettingValueBoolean(bx))
1451
        rs.set_apply_callback(self._invert_me, _settings, "myb_28")
1452
        mybands.append(rs)
1453

    
1454
        bx = not _settings.myb_50
1455
        rs = RadioSetting("myb_50", "50 MHz", RadioSettingValueBoolean(bx))
1456
        rs.set_apply_callback(self._invert_me, _settings, "myb_50")
1457
        mybands.append(rs)
1458
        # End _do_mybands_settings
1459

    
1460
    def set_settings(self, settings):
1461
        _settings = self._memobj.settings
1462
        _mem = self._memobj
1463
        for element in settings:
1464
            if not isinstance(element, RadioSetting):
1465
                self.set_settings(element)
1466
                continue
1467
            else:
1468
                try:
1469
                    name = element.get_name()
1470
                    if "." in name:
1471
                        bits = name.split(".")
1472
                        obj = self._memobj
1473
                        for bit in bits[: -1]:
1474
                            if "/" in bit:
1475
                                bit, index = bit.split("/", 1)
1476
                                index = int(index)
1477
                                obj = getattr(obj, bit)[index]
1478
                            else:
1479
                                obj = getattr(obj, bit)
1480
                        setting = bits[-1]
1481
                    else:
1482
                        obj = _settings
1483
                        setting = element.get_name()
1484

    
1485
                    if element.has_apply_callback():
1486
                        LOG.debug("Using apply callback")
1487
                        element.run_apply_callback()
1488
                    elif element.value.get_mutable():
1489
                        LOG.debug("Setting %s = %s" % (setting, element.value))
1490
                        setattr(obj, setting, element.value)
1491
                except Exception:
1492
                    LOG.debug(element.get_name())
1493
                    raise
(6-6/8)