Project

General

Profile

Bug #10909 » ft450d.py

William Curlew, 10/24/2023 03:24 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;          // @ 0x380
284

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

    
289
        # seekto 0x3C1;
290
        struct mem_struct memory[500];
291
        struct mem_struct pms[4];       // Programmed Scan limits @ x387D
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. ? 7 more bytes of stuff in block
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

    
607
# ft450 only has 1 bit to expose PMS, and does not set filled indicator
608
            bitindex = 0
609
            used = (self._memobj.pmsvisible >> bitindex) & 0x01
610
            valid = used
611
            if not used:
612
                mem.empty = True
613
            if not valid:
614
                mem.empty = True
615
                return mem
616

    
617

    
618
            mx = (-self.LAST_PMS_INDEX) + mem.number
619
            _mem = self._memobj.pms[mx]
620
            mx = mx + 1
621
            immutable = ["number", "rtone", "ctone", "extd_number",
622
                         "tmode", "cross_mode",
623
                         "power", "duplex", "offset"]
624
        elif mem.number in self.SPECIAL_60M.values():
625
            mx = (-self.LAST_60M_INDEX) + mem.number
626
            _mem = self._memobj.m60[mx]
627
            mx = mx + 1
628
            immutable = ["number", "rtone", "ctone", "extd_number",
629
                         "tmode", "cross_mode",
630
                         "frequency", "power", "duplex", "offset"]
631
        else:
632
            raise Exception("Sorry, you can't edit that special"
633
                            " memory channel %i." % mem.number)
634

    
635
        mem = self._get_memory(mem, _mem)
636
        mem.immutable = immutable
637

    
638
        return mem
639

    
640
    def _set_special(self, mem):
641
        if mem.empty and mem.number not in self.SPECIAL_PMS.values():
642
            # can't delete special memories!
643
            raise errors.RadioError("Sorry, special memory can't be deleted")
644

    
645
        cur_mem = self._get_special(self.SPECIAL_MEMORIES_REV[mem.number])
646

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

    
682
        for key in cur_mem.immutable:
683
            if key != "extd_number":
684
                if cur_mem.__dict__[key] != mem.__dict__[key]:
685
                    raise errors.RadioError("Editing field `%s' " % key +
686
                                            "is not supported on this channel")
687

    
688
        self._set_memory(mem, _mem)
689

    
690
    def _get_normal(self, number):
691
        _mem = self._memobj.memory[number - 1]
692
        used = (self._memobj.visible[(number - 1) / 8] >> (number - 1) % 8) \
693
            & 0x01
694
        valid = (self._memobj.filled[(number - 1) / 8] >> (number - 1) % 8) \
695
            & 0x01
696

    
697
        mem = chirp_common.Memory()
698
        mem.number = number
699
        if not used:
700
            mem.empty = True
701
            if not valid or _mem.freq == 0xffffffff:
702
                return mem
703
        if mem.number == 1:
704
            mem.immutable = ['empty']
705
        return self._get_memory(mem, _mem)
706

    
707
    def _set_normal(self, mem):
708
        _mem = self._memobj.memory[mem.number - 1]
709
        wasused = (self._memobj.visible[(mem.number - 1) / 8] >>
710
                   (mem.number - 1) % 8) & 0x01
711
        wasvalid = (self._memobj.filled[(mem.number - 1) / 8] >>
712
                    (mem.number - 1) % 8) & 0x01
713

    
714
        if mem.empty:
715
            if mem.number == 1:
716
                raise Exception("Sorry, can't delete first memory")
717
            if wasvalid and not wasused:
718
                self._memobj.filled[(mem.number - 1) // 8] &= \
719
                    ~(1 << (mem.number - 1) % 8)
720
                _mem.set_raw("\xFF" * (_mem.size() // 8))    # clean up
721
            self._memobj.visible[(mem.number - 1) // 8] &= \
722
                ~(1 << (mem.number - 1) % 8)
723
            return
724
        if not wasvalid:
725
            _mem.set_raw("\x00" * (_mem.size() // 8))    # clean up
726

    
727
        self._memobj.visible[(mem.number - 1) // 8] |= 1 << (mem.number - 1) \
728
            % 8
729
        self._memobj.filled[(mem.number - 1) // 8] |= 1 << (mem.number - 1) \
730
            % 8
731
        self._set_memory(mem, _mem)
732

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

    
770
        if _mem.tag_on_off == 2:
771
            for i in _mem.name:
772
                if i == 0xFF:
773
                    break
774
                if chr(i) in self.CHARSET:
775
                    mem.name += chr(i)
776
                else:
777
                    # radio has some graphical chars that are not supported
778
                    # we replace those with a *
779
                    LOG.info("Replacing char %x with *" % i)
780
                    mem.name += "*"
781
            mem.name = mem.name.rstrip()
782
        else:
783
            mem.name = ""
784

    
785
        mem.extra = RadioSettingGroup("extra", "Extra")
786

    
787
        rs = RadioSetting("ipo", "IPO",
788
                          RadioSettingValueBoolean(bool(_mem.ipo)))
789
        rs.set_doc("Bypass preamp")
790
        mem.extra.append(rs)
791

    
792
        rs = RadioSetting("att", "ATT",
793
                          RadioSettingValueBoolean(bool(_mem.att)))
794
        rs.set_doc("10dB front end attenuator")
795
        mem.extra.append(rs)
796

    
797
        rs = RadioSetting("cnturon", "Contour Filter",
798
                          RadioSettingValueBoolean(_mem.cnturon))
799
        rs.set_doc("Contour filter on/off")
800
        mem.extra.append(rs)
801

    
802
        options = ["Peak", "Null"]
803
        rs = RadioSetting("cnturpk", "Contour Filter Mode",
804
                          RadioSettingValueList(options,
805
                                                options[_mem.cnturpk]))
806
        mem.extra.append(rs)
807

    
808
        options = ["Low", "High"]
809
        rs = RadioSetting("cnturgn", "Contour Filter Gain",
810
                          RadioSettingValueList(options,
811
                                                options[_mem.cnturgn]))
812
        rs.set_doc("Filter gain/attenuation")
813
        mem.extra.append(rs)
814

    
815
        options = ["-2", "-1", "Center", "+1", "+2"]
816
        rs = RadioSetting("cnturmd", "Contour Filter Notch",
817
                          RadioSettingValueList(options,
818
                                                options[_mem.cnturmd]))
819
        rs.set_doc("Filter notch offset")
820
        mem.extra.append(rs)
821

    
822
        rs = RadioSetting("notch", "Notch Filter",
823
                          RadioSettingValueBoolean(_mem.notch))
824
        rs.set_doc("IF bandpass filter")
825
        mem.extra.append(rs)
826

    
827
        vx = 1
828
        options = ["<-", "Center", "+>"]
829
        if _mem.notch_pos < 0:
830
            vx = 0
831
        if _mem.notch_pos > 0:
832
            vx = 2
833
        rs = RadioSetting("notch_pos", "Notch Position",
834
                          RadioSettingValueList(options, options[vx]))
835
        rs.set_doc("IF bandpass filter shift")
836
        mem.extra.append(rs)
837

    
838
        vx = 0
839
        if mem.mode[1:] == "SB":
840
            options = ["1.8 kHz", "2.4 kHz", "3.0 kHz"]
841
            vx = _mem.sb_width
842
            stx = "sb_width"
843
        elif mem.mode[:1] == "CW":
844
            options = ["300 Hz", "500 kHz", "2.4 kHz"]
845
            vx = _mem.cw_width
846
            stx = "cw_width"
847
        elif mem.mode[:4] == "USER" or mem.mode[:4] == "RTTY":
848
            options = ["300 Hz", "2.4 kHz", "3.0 kHz"]
849
            vx = _mem.sb_width
850
            stx = "sb_width"
851
        elif mem.mode == "AM":
852
            options = ["3.0 kHz", "6.0 kHz", "9.0 kHz"]
853
            vx = _mem.am_width
854
            stx = "am_width"
855
        else:
856
            options = ["2.5 kHz", "5.0 kHz"]
857
            vx = _mem.fm_width
858
            stx = "fm_width"
859
        rs = RadioSetting(stx, "IF Bandpass Filter Width",
860
                          RadioSettingValueList(options, options[vx]))
861
        rs.set_doc("DSP IF bandpass Notch width (Hz)")
862
        mem.extra.append(rs)
863

    
864
        rs = RadioSetting("dnr_on", "DSP Noise Reduction",
865
                          RadioSettingValueBoolean(bool(_mem.dnr_on)))
866
        rs.set_doc("Digital noise processing")
867
        mem.extra.append(rs)
868

    
869
        options = ["Off", "1", "2", "3", "4", "5", "6", "7",
870
                          "8", "9", "10", "11"]
871
        rs = RadioSetting("dnr_val", "DSP Noise Reduction Alg",
872
                          RadioSettingValueList(options,
873
                                                options[_mem.dnr_val]))
874
        rs.set_doc("Digital noise reduction algorithm number (1-11)")
875
        mem.extra.append(rs)
876

    
877
        return mem          # end get_memory
878

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

    
932
        for i in range(0, 7):
933
            _mem.name[i] = ord(mem.name.ljust(7)[i])
934

    
935
        for setting in mem.extra:
936
            if setting.get_name() == "notch_pos":
937
                vx = 0          # Override list string with signed value
938
                stx = str(setting.value)
939
                if stx == "<-":
940
                    vx = -13
941
                if stx == "+>":
942
                    vx = 12
943
                setattr(_mem, "notch_pos", vx)
944
            elif setting.get_name() == "dnr_val":
945
                stx = str(setting.value)        # Convert string to int
946
                vx = 0
947
                if stx != "Off":
948
                    vx = int(stx)
949
                else:
950
                    setattr(_mem, "dnr_on", 0)
951
                setattr(_mem, setting.get_name(), vx)
952
            else:
953
                setattr(_mem, setting.get_name(), setting.value)
954

    
955
    @classmethod
956
    def match_model(cls, filedata, filename):
957
        """Match the opened/downloaded image to the correct version"""
958
        if len(filedata) == cls.MEM_SIZE + 7:    # +7 bytes of model name
959
            rid = filedata[cls.MEM_SIZE:cls.MEM_SIZE + 7]
960
            if rid.startswith(cls.MODEL.encode()):
961
                return True
962
        else:
963
            return False
964

    
965
    def _invert_me(self, setting, obj, atrb):
966
        """Callback: from inverted logic 1-bit booleans"""
967
        invb = not setting.value
968
        setattr(obj, atrb, invb)
969
        return
970

    
971
    def _chars2str(self, cary, knt):
972
        """Convert raw memory char array to a string: NOT a callback."""
973
        stx = ""
974
        for char in cary[0:knt]:
975
            stx += chr(int(char))
976
        return stx
977

    
978
    def _my_str2ary(self, setting, obj, atrba, knt):
979
        """Callback: convert string to fixed-length char array.."""
980
        ary = ""
981
        for j in range(0, knt, 1):
982
            chx = ord(str(setting.value)[j])
983
            if chx < 32 or chx > 125:     # strip non-printing
984
                ary += " "
985
            else:
986
                ary += str(setting.value)[j]
987
        setattr(obj, atrba, ary)
988
        return
989

    
990
    def get_settings(self):
991
        _settings = self._memobj.settings
992
        _beacon = self._memobj.beacontext
993
        gen = RadioSettingGroup("gen", "General")
994
        cw = RadioSettingGroup("cw", "CW")
995
        pnlcfg = RadioSettingGroup("pnlcfg", "Panel buttons")
996
        pnlset = RadioSettingGroup("pnlset", "Panel settings")
997
        voxdat = RadioSettingGroup("voxdat", "VOX and Data")
998
        mic = RadioSettingGroup("mic", "Microphone")
999
        mybands = RadioSettingGroup("mybands", "My Bands")
1000
        mymodes = RadioSettingGroup("mymodes", "My Modes")
1001

    
1002
        top = RadioSettings(gen,  cw, pnlcfg, pnlset, voxdat, mic,
1003
                            mymodes, mybands)
1004

    
1005
        self._do_general_settings(gen)
1006
        self._do_cw_settings(cw)
1007
        self._do_panel_buttons(pnlcfg)
1008
        self._do_panel_settings(pnlset)
1009
        self._do_vox_settings(voxdat)
1010
        self._do_mic_settings(mic)
1011
        self._do_mymodes_settings(mymodes)
1012
        self._do_mybands_settings(mybands)
1013

    
1014
        return top
1015

    
1016
    def _do_general_settings(self, tab):
1017
        _settings = self._memobj.settings
1018

    
1019
        rs = RadioSetting("ext_mnu", "Extended menu",
1020
                          RadioSettingValueBoolean(_settings.ext_mnu))
1021
        rs.set_doc("Enables access to extended settings in the radio")
1022
        tab.append(rs)
1023
        # Issue #8183 bugfix
1024
        rs = RadioSetting("apo", "APO time (Hrs)",
1025
                          RadioSettingValueInteger(0, 12, _settings.apo))
1026
        tab.append(rs)
1027

    
1028
        options = ["%i" % i for i in range(0, 21)]
1029
        options[0] = "Off"
1030
        rs = RadioSetting("tot", "TX 'TOT' time-out (mins)",
1031
                          RadioSettingValueList(options,
1032
                                                options[_settings.tot]))
1033
        tab.append(rs)
1034

    
1035
        bx = not _settings.cat_rts     # Convert from Enable=0
1036
        rs = RadioSetting("cat_rts", "CAT RTS flow control",
1037
                          RadioSettingValueBoolean(bx))
1038
        rs.set_apply_callback(self._invert_me, _settings, "cat_rts")
1039
        tab.append(rs)
1040

    
1041
        options = ["0", "100ms", "1000ms", "3000ms"]
1042
        rs = RadioSetting("cat_tot", "CAT Timeout",
1043
                          RadioSettingValueList(options,
1044
                                                options[_settings.cat_tot]))
1045
        tab.append(rs)
1046

    
1047
        options = ["4800", "9600", "19200", "38400", "Data"]
1048
        rs = RadioSetting("catrate", "CAT rate",
1049
                          RadioSettingValueList(options,
1050
                                                options[_settings.catrate]))
1051
        tab.append(rs)
1052

    
1053
        rs = RadioSetting("mem_grp", "Mem groups",
1054
                          RadioSettingValueBoolean(_settings.mem_grp))
1055
        tab.append(rs)
1056

    
1057
        rs = RadioSetting("scn_res", "Resume scan (secs)",
1058
                          RadioSettingValueInteger(0, 10, _settings.scn_res))
1059
        tab.append(rs)
1060

    
1061
        rs = RadioSetting("clk_sft", "CPU clock shift",
1062
                          RadioSettingValueBoolean(_settings.clk_sft))
1063
        tab.append(rs)
1064

    
1065
        rs = RadioSetting("split", "TX/RX Frequency Split",
1066
                          RadioSettingValueBoolean(_settings.split))
1067
        tab.append(rs)
1068

    
1069
        rs = RadioSetting("qspl_f", "Quick-Split freq offset (kHz)",
1070
                          RadioSettingValueInteger(-20, 20, _settings.qspl_f))
1071
        tab.append(rs)
1072

    
1073
        rs = RadioSetting("emergen", "Alaska Emergency Mem 5167.5 kHz",
1074
                          RadioSettingValueBoolean(_settings.emergen))
1075
        tab.append(rs)
1076

    
1077
        rs = RadioSetting("stby_beep", "PTT release 'Standby' beep",
1078
                          RadioSettingValueBoolean(_settings.stby_beep))
1079
        tab.append(rs)
1080

    
1081
        options = ["ATAS", "EXT ATU", "INT ATU", "INTRATU", "F-TRANS"]
1082
        rs = RadioSetting("tuner", "Antenna Tuner",
1083
                          RadioSettingValueList(options,
1084
                                                options[_settings.tuner]))
1085
        tab.append(rs)
1086

    
1087
        rs = RadioSetting("rfpower", "RF power (watts)",
1088
                          RadioSettingValueInteger(5, 100, _settings.rfpower))
1089
        tab.append(rs)      # End of _do_general_settings
1090

    
1091
    def _do_cw_settings(self, cw):        # - - - CW - - -
1092
        _settings = self._memobj.settings
1093
        _beacon = self._memobj.beacontext
1094

    
1095
        rs = RadioSetting("cw_dly", "CW break-in delay (ms * 10)",
1096
                          RadioSettingValueInteger(0, 300, _settings.cw_dly))
1097
        cw.append(rs)
1098

    
1099
        options = ["%i Hz" % i for i in range(400, 801, 100)]
1100
        rs = RadioSetting("cwpitch", "CW pitch",
1101
                          RadioSettingValueList(options,
1102
                                                options[_settings.cwpitch]))
1103
        cw.append(rs)
1104

    
1105
        rs = RadioSetting("cwspeed", "CW speed (wpm)",
1106
                          RadioSettingValueInteger(4, 60, _settings.cwspeed))
1107
        rs.set_doc("Cpm is Wpm * 5")
1108
        cw.append(rs)
1109

    
1110
        options = ["1:%1.1f" % (i / 10) for i in range(25, 46, 1)]
1111
        rs = RadioSetting("cwweigt", "CW weight",
1112
                          RadioSettingValueList(options,
1113
                                                options[_settings.cwweigt]))
1114
        cw.append(rs)
1115

    
1116
        options = ["15ms", "20ms", "25ms", "30ms"]
1117
        rs = RadioSetting("cw_qsk", "CW delay before TX in QSK mode",
1118
                          RadioSettingValueList(options,
1119
                                                options[_settings.cw_qsk]))
1120
        cw.append(rs)
1121

    
1122
        rs = RadioSetting("cwstone_sgn", "CW sidetone volume Linked",
1123
                          RadioSettingValueBoolean(_settings.cwstone_sgn))
1124
        rs.set_doc("If set; volume is relative to AF Gain knob.")
1125
        cw.append(rs)
1126

    
1127
        rs = RadioSetting("cwstone_lnk", "CW sidetone linked volume",
1128
                          RadioSettingValueInteger(-50, 50,
1129
                                                   _settings.cwstone_lnk))
1130
        cw.append(rs)
1131

    
1132
        rs = RadioSetting("cwstone_fix", "CW sidetone fixed volume",
1133
                          RadioSettingValueInteger(0, 100,
1134
                                                   _settings.cwstone_fix))
1135
        cw.append(rs)
1136

    
1137
        options = ["Numeric", "Alpha", "Mixed"]
1138
        rs = RadioSetting("cwtrain", "CW Training mode",
1139
                          RadioSettingValueList(options,
1140
                                                options[_settings.cwtrain]))
1141
        cw.append(rs)
1142

    
1143
        rs = RadioSetting("cw_auto", "CW key jack- auto CW mode",
1144
                          RadioSettingValueBoolean(_settings.cw_auto))
1145
        rs.set_doc("Enable for CW mode auto-set when keyer pluuged in.")
1146
        cw.append(rs)
1147

    
1148
        options = ["Normal", "Reverse"]
1149
        rs = RadioSetting("cw_key", "CW paddle wiring",
1150
                          RadioSettingValueList(options,
1151
                                                options[_settings.cw_key]))
1152
        cw.append(rs)
1153

    
1154
        rs = RadioSetting("beacon_time", "CW beacon Tx interval (secs)",
1155
                          RadioSettingValueInteger(0, 255,
1156
                                                   _settings.beacon_time))
1157
        cw.append(rs)
1158

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

    
1165
        tmp = self._chars2str(_beacon.t2, 40)
1166
        rs = RadioSetting("t2", "CW Beacon Line 2",
1167
                          RadioSettingValueString(0, 40, tmp))
1168
        rs.set_apply_callback(self._my_str2ary, _beacon, "t2", 40)
1169
        cw.append(rs)
1170

    
1171
        tmp = self._chars2str(_beacon.t3, 40)
1172
        rs = RadioSetting("t3", "CW Beacon Line 3",
1173
                          RadioSettingValueString(0, 40, tmp))
1174
        rs.set_apply_callback(self._my_str2ary, _beacon, "t3", 40)
1175
        cw.append(rs)       # END _do_cw_settings
1176

    
1177
    def _do_panel_settings(self, pnlset):    # - - - Panel settings
1178
        _settings = self._memobj.settings
1179

    
1180
        bx = not _settings.amfmdial     # Convert from Enable=0
1181
        rs = RadioSetting("amfmdial", "AM&FM Dial",
1182
                          RadioSettingValueBoolean(bx))
1183
        rs.set_apply_callback(self._invert_me, _settings, "amfmdial")
1184
        pnlset.append(rs)
1185

    
1186
        options = ["440 Hz", "880 Hz", "1760 Hz"]
1187
        rs = RadioSetting("beepton", "Beep frequency",
1188
                          RadioSettingValueList(options,
1189
                                                options[_settings.beepton]))
1190
        pnlset.append(rs)
1191

    
1192
        rs = RadioSetting("beepvol_sgn", "Beep volume Linked",
1193
                          RadioSettingValueBoolean(_settings.beepvol_sgn))
1194
        rs.set_doc("If set; volume is relative to AF Gain knob.")
1195
        pnlset.append(rs)
1196

    
1197
        rs = RadioSetting("beepvol_lnk", "Linked beep volume",
1198
                          RadioSettingValueInteger(-50, 50,
1199
                                                   _settings.beepvol_lnk))
1200
        rs.set_doc("Relative to AF-Gain setting.")
1201
        pnlset.append(rs)
1202

    
1203
        rs = RadioSetting("beepvol_fix", "Fixed beep volume",
1204
                          RadioSettingValueInteger(0, 100,
1205
                                                   _settings.beepvol_fix))
1206
        rs.set_doc("When Linked setting is unchecked.")
1207
        pnlset.append(rs)
1208

    
1209
        rs = RadioSetting("cont", "LCD Contrast",
1210
                          RadioSettingValueInteger(1, 24, _settings.cont))
1211
        rs.set_doc("This setting does not appear to do anything...")
1212
        pnlset.append(rs)
1213

    
1214
        rs = RadioSetting("dimmer", "LCD Dimmer",
1215
                          RadioSettingValueInteger(1, 8,  _settings.dimmer))
1216
        pnlset.append(rs)
1217

    
1218
        options = ["RF-Gain", "Squelch"]
1219
        rs = RadioSetting("sql_rfg", "Squelch/RF-Gain",
1220
                          RadioSettingValueList(options,
1221
                                                options[_settings.sql_rfg]))
1222
        pnlset.append(rs)
1223

    
1224
        options = ["Frequencies", "Panel", "All"]
1225
        rs = RadioSetting("lockmod", "Lock Mode",
1226
                          RadioSettingValueList(options,
1227
                                                options[_settings.lockmod]))
1228
        pnlset.append(rs)
1229

    
1230
        options = ["Dial", "SEL"]
1231
        rs = RadioSetting("clar_btn", "CLAR button control",
1232
                          RadioSettingValueList(options,
1233
                                                options[_settings.clar_btn]))
1234
        pnlset.append(rs)
1235

    
1236
        if _settings.dialstp_mode == 0:             # AM/FM
1237
            options = ["SSB/CW:1Hz", "SSB/CW:10Hz", "SSB/CW:20Hz"]
1238
        else:
1239
            options = ["AM/FM:100Hz", "AM/FM:200Hz"]
1240
        rs = RadioSetting("dialstp", "Dial tuning step",
1241
                          RadioSettingValueList(options,
1242
                                                options[_settings.dialstp]))
1243
        pnlset.append(rs)
1244

    
1245
        options = ["0.5secs", "1.0secs", "1.5secs", "2.0secs"]
1246
        rs = RadioSetting("keyhold", "Buttons hold-to-activate time",
1247
                          RadioSettingValueList(options,
1248
                                                options[_settings.keyhold]))
1249
        pnlset.append(rs)
1250

    
1251
        rs = RadioSetting("m_tune", "Memory tune",
1252
                          RadioSettingValueBoolean(_settings.m_tune))
1253
        pnlset.append(rs)
1254

    
1255
        rs = RadioSetting("peakhold", "S-Meter display hold (1sec)",
1256
                          RadioSettingValueBoolean(_settings.peakhold))
1257
        pnlset.append(rs)
1258

    
1259
        options = ["CW Sidetone", "CW Speed", "100 kHz step", "1 MHz Step",
1260
                   "Mic Gain", "RF Power"]
1261
        rs = RadioSetting("seldial", "SEL dial 2nd function (push)",
1262
                          RadioSettingValueList(options,
1263
                                                options[_settings.seldial]))
1264
        pnlset.append(rs)
1265
    # End _do_panel_settings
1266

    
1267
    def _do_panel_buttons(self, pnlcfg):      # - - - Current Panel Config
1268
        _settings = self._memobj.settings
1269

    
1270
        rs = RadioSetting("pnl_cs", "C.S. Function",
1271
                          RadioSettingValueList(self.FUNC_LIST,
1272
                                                self.FUNC_LIST[_settings.pnl_cs]))
1273
        pnlcfg.append(rs)
1274

    
1275
        rs = RadioSetting("nb", "Noise blanker",
1276
                          RadioSettingValueBoolean(_settings.nb))
1277
        pnlcfg.append(rs)
1278

    
1279
        options = ["Auto", "Fast",  "Slow", "Auto/Fast", "Auto/Slow", "?5?"]
1280
        rs = RadioSetting("agc", "AGC",
1281
                          RadioSettingValueList(options,
1282
                                                options[_settings.agc]))
1283
        pnlcfg.append(rs)
1284

    
1285
        rs = RadioSetting("keyer", "Keyer",
1286
                          RadioSettingValueBoolean(_settings.keyer))
1287
        pnlcfg.append(rs)
1288

    
1289
        rs = RadioSetting("fast", "Fast step",
1290
                          RadioSettingValueBoolean(_settings.fast))
1291
        pnlcfg.append(rs)
1292

    
1293
        rs = RadioSetting("lock", "Lock (per Lock Mode)",
1294
                          RadioSettingValueBoolean(_settings.lock))
1295
        pnlcfg.append(rs)
1296

    
1297
        options = ["PO",  "ALC", "SWR"]
1298
        rs = RadioSetting("mtr_mode", "S-Meter mode",
1299
                          RadioSettingValueList(options,
1300
                                                options[_settings.mtr_mode]))
1301
        pnlcfg.append(rs)
1302
        # End _do_panel_Buttons
1303

    
1304
    def _do_vox_settings(self, voxdat):     # - - VOX and DATA Settings
1305
        _settings = self._memobj.settings
1306

    
1307
        rs = RadioSetting("vox_dly", "VOX delay (x 100 ms)",
1308
                          RadioSettingValueInteger(1, 30, _settings.vox_dly))
1309
        voxdat.append(rs)
1310

    
1311
        rs = RadioSetting("vox_gain", "VOX Gain",
1312
                          RadioSettingValueInteger(0, 100,
1313
                                                   _settings.vox_gain))
1314
        voxdat.append(rs)
1315

    
1316
        rs = RadioSetting("dig_vox", "Digital VOX Gain",
1317
                          RadioSettingValueInteger(0, 100,
1318
                                                   _settings.dig_vox))
1319
        voxdat.append(rs)
1320

    
1321
        rs = RadioSetting("d_disp", "User-L/U freq offset (Hz)",
1322
                          RadioSettingValueInteger(-3000, 30000,
1323
                                                   _settings.d_disp, 10))
1324
        voxdat.append(rs)
1325

    
1326
        options = ["170 Hz", "200 Hz", "425 Hz", "850 Hz"]
1327
        rs = RadioSetting("rty_sft", "RTTY FSK Freq Shift",
1328
                          RadioSettingValueList(options,
1329
                                                options[_settings.rty_sft]))
1330
        voxdat.append(rs)
1331

    
1332
        options = ["1275 Hz", "2125 Hz"]
1333
        rs = RadioSetting("rty_ton", "RTTY FSK Mark tone",
1334
                          RadioSettingValueList(options,
1335
                                                options[_settings.rty_ton]))
1336
        voxdat.append(rs)
1337

    
1338
        options = ["Normal", "Reverse"]
1339
        rs = RadioSetting("rtyrpol", "RTTY Mark/Space RX polarity",
1340
                          RadioSettingValueList(options,
1341
                                                options[_settings.rtyrpol]))
1342
        voxdat.append(rs)
1343

    
1344
        rs = RadioSetting("rtytpol", "RTTY Mark/Space TX polarity",
1345
                          RadioSettingValueList(options,
1346
                                                options[_settings.rtytpol]))
1347
        voxdat.append(rs)
1348
        # End _do_vox_settings
1349

    
1350
    def _do_mic_settings(self, mic):    # - - MIC Settings
1351
        _settings = self._memobj.settings
1352

    
1353
        rs = RadioSetting("mic_eq", "Mic Equalizer",
1354
                          RadioSettingValueInteger(0, 9, _settings.mic_eq))
1355
        mic.append(rs)
1356

    
1357
        options = ["Low", "Normal", "High"]
1358
        rs = RadioSetting("micgain", "Mic Gain",
1359
                          RadioSettingValueList(options,
1360
                                                options[_settings.micgain]))
1361
        mic.append(rs)
1362

    
1363
        rs = RadioSetting("micscan", "Mic scan enabled",
1364
                          RadioSettingValueBoolean(_settings.micscan))
1365
        rs.set_doc("Enables channel scanning via mic up/down buttons.")
1366
        mic.append(rs)
1367

    
1368
        rs = RadioSetting("pm_dwn", "Mic Down button function",
1369
                          RadioSettingValueList(self.FUNC_LIST,
1370
                                                self.FUNC_LIST[_settings.pm_dwn]))
1371
        mic.append(rs)
1372

    
1373
        rs = RadioSetting("pm_fst", "Mic Fast button function",
1374
                          RadioSettingValueList(self.FUNC_LIST,
1375
                                                self.FUNC_LIST[_settings.pm_fst]))
1376
        mic.append(rs)
1377

    
1378
        rs = RadioSetting("pm_up", "Mic Up button function",
1379
                          RadioSettingValueList(self.FUNC_LIST,
1380
                                                self.FUNC_LIST[_settings.pm_up]))
1381
        mic.append(rs)
1382
        # End _do_mic_settings
1383

    
1384
    def _do_mymodes_settings(self, mymodes):    # - - MYMODES
1385
        _settings = self._memobj.settings  # Inverted Logic requires callback
1386

    
1387
        bx = not _settings.mym_lsb
1388
        rs = RadioSetting("mym_lsb", "LSB", RadioSettingValueBoolean(bx))
1389
        rs.set_apply_callback(self._invert_me, _settings, "mym_lsb")
1390
        mymodes.append(rs)
1391

    
1392
        bx = not _settings.mym_usb
1393
        rs = RadioSetting("mym_usb", "USB", RadioSettingValueBoolean(bx))
1394
        rs.set_apply_callback(self._invert_me, _settings, "mym_usb")
1395
        mymodes.append(rs)
1396

    
1397
        bx = not _settings.mym_cw
1398
        rs = RadioSetting("mym_cw", "CW", RadioSettingValueBoolean(bx))
1399
        rs.set_apply_callback(self._invert_me, _settings, "mym_cw")
1400
        mymodes.append(rs)
1401

    
1402
        bx = not _settings.mym_am
1403
        rs = RadioSetting("mym_am", "AM", RadioSettingValueBoolean(bx))
1404
        rs.set_apply_callback(self._invert_me, _settings, "mym_am")
1405
        mymodes.append(rs)
1406

    
1407
        bx = not _settings.mym_fm
1408
        rs = RadioSetting("mym_fm", "FM", RadioSettingValueBoolean(bx))
1409
        rs.set_apply_callback(self._invert_me, _settings, "mym_fm")
1410
        mymodes.append(rs)
1411

    
1412
        bx = not _settings.mym_data
1413
        rs = RadioSetting("mym_data", "DATA", RadioSettingValueBoolean(bx))
1414
        rs.set_apply_callback(self._invert_me, _settings, "mym_data")
1415
        mymodes.append(rs)
1416
        # End _do_mymodes_settings
1417

    
1418
    def _do_mybands_settings(self, mybands):    # - - MYBANDS Settings
1419
        _settings = self._memobj.settings  # Inverted Logic requires callback
1420

    
1421
        bx = not _settings.myb_1_8
1422
        rs = RadioSetting("myb_1_8", "1.8 MHz", RadioSettingValueBoolean(bx))
1423
        rs.set_apply_callback(self._invert_me, _settings, "myb_1_8")
1424
        mybands.append(rs)
1425

    
1426
        bx = not _settings.myb_3_5
1427
        rs = RadioSetting("myb_3_5", "3.5 MHz", RadioSettingValueBoolean(bx))
1428
        rs.set_apply_callback(self._invert_me, _settings, "myb_3_5")
1429
        mybands.append(rs)
1430

    
1431
        bx = not _settings.myb_7
1432
        rs = RadioSetting("myb_7", "7 MHz", RadioSettingValueBoolean(bx))
1433
        rs.set_apply_callback(self._invert_me, _settings, "myb_7")
1434
        mybands.append(rs)
1435

    
1436
        bx = not _settings.myb_10
1437
        rs = RadioSetting("myb_10", "10 MHz", RadioSettingValueBoolean(bx))
1438
        rs.set_apply_callback(self._invert_me, _settings, "myb_10")
1439
        mybands.append(rs)
1440

    
1441
        bx = not _settings.myb_14
1442
        rs = RadioSetting("myb_14", "14 MHz", RadioSettingValueBoolean(bx))
1443
        rs.set_apply_callback(self._invert_me, _settings, "myb_14")
1444
        mybands.append(rs)
1445

    
1446
        bx = not _settings.myb_18
1447
        rs = RadioSetting("myb_18", "18 MHz", RadioSettingValueBoolean(bx))
1448
        rs.set_apply_callback(self._invert_me, _settings, "myb_18")
1449
        mybands.append(rs)
1450

    
1451
        bx = not _settings.myb_21
1452
        rs = RadioSetting("myb_21", "21 MHz", RadioSettingValueBoolean(bx))
1453
        rs.set_apply_callback(self._invert_me, _settings, "myb_21")
1454
        mybands.append(rs)
1455

    
1456
        bx = not _settings.myb_24
1457
        rs = RadioSetting("myb_24", "24 MHz", RadioSettingValueBoolean(bx))
1458
        rs.set_apply_callback(self._invert_me, _settings, "myb_24")
1459
        mybands.append(rs)
1460

    
1461
        bx = not _settings.myb_28
1462
        rs = RadioSetting("myb_28", "28 MHz", RadioSettingValueBoolean(bx))
1463
        rs.set_apply_callback(self._invert_me, _settings, "myb_28")
1464
        mybands.append(rs)
1465

    
1466
        bx = not _settings.myb_50
1467
        rs = RadioSetting("myb_50", "50 MHz", RadioSettingValueBoolean(bx))
1468
        rs.set_apply_callback(self._invert_me, _settings, "myb_50")
1469
        mybands.append(rs)
1470
        # End _do_mybands_settings
1471

    
1472
    def set_settings(self, settings):
1473
        _settings = self._memobj.settings
1474
        _mem = self._memobj
1475
        for element in settings:
1476
            if not isinstance(element, RadioSetting):
1477
                self.set_settings(element)
1478
                continue
1479
            else:
1480
                try:
1481
                    name = element.get_name()
1482
                    if "." in name:
1483
                        bits = name.split(".")
1484
                        obj = self._memobj
1485
                        for bit in bits[: -1]:
1486
                            if "/" in bit:
1487
                                bit, index = bit.split("/", 1)
1488
                                index = int(index)
1489
                                obj = getattr(obj, bit)[index]
1490
                            else:
1491
                                obj = getattr(obj, bit)
1492
                        setting = bits[-1]
1493
                    else:
1494
                        obj = _settings
1495
                        setting = element.get_name()
1496

    
1497
                    if element.has_apply_callback():
1498
                        LOG.debug("Using apply callback")
1499
                        element.run_apply_callback()
1500
                    elif element.value.get_mutable():
1501
                        LOG.debug("Setting %s = %s" % (setting, element.value))
1502
                        setattr(obj, setting, element.value)
1503
                except Exception:
1504
                    LOG.debug(element.get_name())
1505
                    raise
(7-7/8)