Project

General

Profile

Bug #10210 » lt725uv_Baojie318-01-02-23.py

Updatede Driver Rev 1 - Mark Hartong, 01/02/2023 06:27 PM

 
1
# Copyright 2016-2023:
2
# * Jim Unroe KC9HI, <rock.unroe@gmail.com>
3
# Modified for Baojie BJ-218: 2018
4
#    Rick DeWitt (RJD), <aa0rd@yahoo.com>
5
# Modified for Baojie BJ-318: April 2021
6
#    Mark Hartong, AJ4YI <mark.hartong@verizon.net>
7
# Modified for Baojie BJ-318: January 2023
8
#    Mark Hartong, AJ4YI <mark.hartong@verizon.net>
9
#    1. Removed Experimental Warning
10
#    2. Added Brown & Yellow as Color Selections
11
#    3. Max VFO Volume Setting to 15 ( vice 10)
12
#        
13
# This program is free software: you can redistribute it and/or modify
14
# it under the terms of the GNU General Public License as published by
15
# the Free Software Foundation, either version 2 of the License, or
16
# (at your option) any later version.
17
#
18
# This program is distributed in the hope that it will be useful,
19
# but WITHOUT ANY WARRANTY; without even the implied warranty of
20
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21
# GNU General Public License for more details.
22
#
23
# You should have received a copy of the GNU General Public License
24
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
25

    
26
import time
27
import struct
28
import logging
29
import re
30

    
31
from chirp import chirp_common, directory, memmap
32
from chirp import bitwise, errors, util
33
from chirp.settings import RadioSettingGroup, RadioSetting, \
34
    RadioSettingValueBoolean, RadioSettingValueList, \
35
    RadioSettingValueString, RadioSettingValueInteger, \
36
    RadioSettingValueFloat, RadioSettings, InvalidValueError
37
from textwrap import dedent
38

    
39
LOG = logging.getLogger(__name__)
40

    
41
MEM_FORMAT = """
42
#seekto 0x0200;
43
struct {
44
  u8  init_bank; // determines which VFO is primary A or B
45
  u8  volume;    // not used BJ-318, band volume is controlled vfo u8 bvol
46
  u16 fm_freq;   // not used BJ-318, freq is controlled hello_lims u16 fm_318
47
  u8  wtled;     // not used BJ-318
48
  u8  rxled;     // not used BJ-318
49
  u8  txled;     // not used BJ-318
50
  u8  ledsw;
51
  u8  beep;
52
  u8  ring;
53
  u8  bcl;
54
  u8  tot;
55
  u16 sig_freq;
56
  u16 dtmf_txms;
57
  u8  init_sql;  // not used BJ-318, band squelch is controlled vfo u8 sql
58
  u8  rptr_mode;
59
} settings;
60

    
61
#seekto 0x0240;
62
struct {
63
  u8  dtmf1_cnt;
64
  u8  dtmf1[7];
65
  u8  dtmf2_cnt;
66
  u8  dtmf2[7];
67
  u8  dtmf3_cnt;
68
  u8  dtmf3[7];
69
  u8  dtmf4_cnt;
70
  u8  dtmf4[7];
71
  u8  dtmf5_cnt;
72
  u8  dtmf5[7];
73
  u8  dtmf6_cnt;
74
  u8  dtmf6[7];
75
  u8  dtmf7_cnt;
76
  u8  dtmf7[7];
77
  u8  dtmf8_cnt;
78
  u8  dtmf8[7];
79
} dtmf_tab;
80

    
81
#seekto 0x0280;
82
struct {
83
  u8  native_id_cnt;
84
  u8  native_id_code[7];
85
  u8  master_id_cnt;
86
  u8  master_id_code[7];
87
  u8  alarm_cnt;
88
  u8  alarm_code[5];
89
  u8  id_disp_cnt;
90
  u8  id_disp_code[5];
91
  u8  revive_cnt;
92
  u8  revive_code[5];
93
  u8  stun_cnt;
94
  u8  stun_code[5];
95
  u8  kill_cnt;
96
  u8  kill_code[5];
97
  u8  monitor_cnt;
98
  u8  monitor_code[5];
99
  u8  state_now;
100
} codes;
101

    
102
#seekto 0x02d0;
103
struct {
104
  u8  hello1_cnt;    // not used in BJ-318, is set in memory map
105
  char  hello1[7];   // not used in BJ-318, is set in memory map
106
  u8  hello2_cnt;    // not used in BJ-318, is set in memory map
107
  char  hello2[7];   // not used in BJ-318, is set in memory map
108
  u32  vhf_low;
109
  u32  vhf_high;
110
  u32  uhf_low;
111
  u32  uhf_high;
112
  u8  lims_on;       // first byte @ at address 0x02f0;
113
  u8 unknown_1;      // use in BJ-318 unknown
114
  u8 lang;           // BJ-318 Display language
115
                     // 02 = English 00 and 01 are Asian
116
  u8 up_scr_color;   // BJ-318 upper screen character color
117
  u8 dn_scr_color;   // BJ-318 lower screen character color
118
                     // purple=00, red=01, emerald=02, blue=03,
119
                     // sky_blue=04, black=05, brown=06, yellow=07 required hex values for color
120
                     // Note   sky_blue and black look the same on the
121
                     // BJ-318 screen
122
  u16 fm_318;        // FM stored Frequency in BJ-318
123
} hello_lims;
124

    
125
struct vfo {
126
  u8  frq_chn_mode;
127
  u8  chan_num;
128
  u32 rxfreq;
129
  u16 is_rxdigtone:1,
130
      rxdtcs_pol:1,
131
      rx_tone:14;
132
  u8  rx_mode;
133
  u8  unknown_ff;
134
  u16 is_txdigtone:1,
135
      txdtcs_pol:1,
136
      tx_tone:14;
137
  u8  launch_sig;
138
  u8  tx_end_sig;
139
  u8  bpower;        // sets power Hi=02h, Medium=01h, Low=00
140
                     // sets power for entire vfo band
141
  u8  fm_bw;
142
  u8  cmp_nder;
143
  u8  scrm_blr;
144
  u8  shift;
145
  u32 offset;
146
  u16 step;
147
  u8  sql;           // squelch for entire vfo band
148
                     // integer values 0 (low) to 9 (high)
149
  u8  bvol;          // sets volume for vfo band
150
                     // integer values 0  (low) TO 15 (high)
151
};
152

    
153
#seekto 0x0300;
154
struct {
155
  struct vfo vfoa;
156
} upper;
157

    
158
#seekto 0x0380;
159
struct {
160
  struct vfo vfob;
161
} lower;
162

    
163
struct mem {
164
  u32 rxfreq;
165
  u16 is_rxdigtone:1,
166
      rxdtcs_pol:1,
167
      rxtone:14;
168
  u8  recvmode;
169
  u32 txfreq;
170
  u16 is_txdigtone:1,
171
      txdtcs_pol:1,
172
      txtone:14;
173
  u8  botsignal;
174
  u8  eotsignal;
175
  u8  power:1,            // binary value for
176
                          // individual channel power
177
                          // set to "High" or "Low"
178
                          // BJ-318 band power overrides any
179
                          // individual channel power setting
180
      wide:1,
181
      compandor:1
182
      scrambler:1
183
      unknown:4;
184
  u8  namelen;
185
  u8  name[7];
186
};
187

    
188
#seekto 0x0400;
189
struct mem upper_memory[128];
190

    
191
#seekto 0x1000;
192
struct mem lower_memory[128];
193

    
194
#seekto 0x1C00;
195
struct {
196
  char  mod_num[6];
197
} mod_id;
198
"""
199

    
200
MEM_SIZE = 0x1C00
201
BLOCK_SIZE = 0x40
202
STIMEOUT = 2
203
LIST_RECVMODE = ["QT/DQT", "QT/DQT + Signaling"]
204
LIST_SIGNAL = ["Off"] + ["DTMF%s" % x for x in range(1, 9)] + \
205
              ["DTMF%s + Identity" % x for x in range(1, 9)] + \
206
              ["Identity code"]
207
# Band Power settings, can be different than channel power
208
LIST_BPOWER = ["Low", "Mid", "High"]    # Tri-power models
209

    
210
# Screen Color Settings
211
# BJ-218 Colors
212
LIST_COLOR = ["Off", "Orange", "Blue", "Purple"]
213
# BJ-318 Colors
214
LIST_COLOR318 = ["Purple", "Red", "Emerald", "Blue", "Sky_Blue", "Black", "Brown", "Yellow"]
215
LIST_LEDSW = ["Auto", "On"]
216
LIST_RING = ["Off"] + ["%s" % x for x in range(1, 10)]
217
LIST_TDR_DEF = ["A-Upper", "B-Lower"]
218
LIST_TIMEOUT = ["Off"] + ["%s" % x for x in range(30, 630, 30)]
219
LIST_VFOMODE = ["Frequency Mode", "Channel Mode"]
220
# Tones are numeric, Defined in \chirp\chirp_common.py
221
TONES_CTCSS = sorted(chirp_common.TONES)
222
# Converted to strings
223
LIST_CTCSS = ["Off"] + [str(x) for x in TONES_CTCSS]
224
# Now append the DxxxN and DxxxI DTCS codes from chirp_common
225
for x in chirp_common.DTCS_CODES:
226
    LIST_CTCSS.append("D{:03d}N".format(x))
227
for x in chirp_common.DTCS_CODES:
228
    LIST_CTCSS.append("D{:03d}R".format(x))
229
LIST_BW = ["Narrow", "Wide"]
230
LIST_SHIFT = ["Off", " + ", " - "]
231
STEPS = [2.5, 5.0, 6.25, 10.0, 12.5, 20.0, 25.0, 50.0]
232
LIST_STEPS = [str(x) for x in STEPS]
233
LIST_STATE = ["Normal", "Stun", "Kill"]
234
LIST_SSF = ["1000", "1450", "1750", "2100"]
235
LIST_DTMFTX = ["50", "100", "150", "200", "300", "500"]
236

    
237
SETTING_LISTS = {
238
    "init_bank": LIST_TDR_DEF,
239
    "tot": LIST_TIMEOUT,
240
    "wtled": LIST_COLOR,   # not used in BJ-318, other radios use
241
    "rxled": LIST_COLOR,   # not used in BJ-318, other radios use
242
    "txled": LIST_COLOR,   # not used in BJ-318, other radios use
243
    "sig_freq": LIST_SSF,
244
    "dtmf_txms": LIST_DTMFTX,
245
    "ledsw": LIST_LEDSW,
246
    "frq_chn_mode": LIST_VFOMODE,
247
    "rx_tone": LIST_CTCSS,
248
    "tx_tone": LIST_CTCSS,
249
    "rx_mode": LIST_RECVMODE,
250
    "launch_sig": LIST_SIGNAL,
251
    "tx_end_sig": LIST_SIGNAL,
252
    "bpower": LIST_BPOWER,
253
    "fm_bw": LIST_BW,
254
    "shift": LIST_SHIFT,
255
    "step": LIST_STEPS,
256
    "ring": LIST_RING,
257
    "state_now": LIST_STATE,
258
    "up_scr_color": LIST_COLOR318,  # unique to BJ-318
259
    "dn_scr_color": LIST_COLOR318,  # unique to BJ-318
260
}
261

    
262

    
263
def _clean_buffer(radio):
264
    radio.pipe.timeout = 0.005
265
    junk = radio.pipe.read(256)
266
    radio.pipe.timeout = STIMEOUT
267
    if junk:
268
        Log.debug("Got %i bytes of junk before starting" % len(junk))
269

    
270

    
271
def _rawrecv(radio, amount):
272
    """Raw read from the radio device"""
273
    data = b""
274
    try:
275
        data = radio.pipe.read(amount)
276
    except:
277
        _exit_program_mode(radio)
278
        msg = "Generic error reading data from radio; check your cable."
279
        raise errors.RadioError(msg)
280

    
281
    if len(data) != amount:
282
        _exit_program_mode(radio)
283
        msg = "Error reading from radio: not the amount of data we want."
284
        raise errors.RadioError(msg)
285

    
286
    return data
287

    
288

    
289
def _rawsend(radio, data):
290
    """Raw send to the radio device"""
291
    try:
292
        radio.pipe.write(data)
293
    except:
294
        raise errors.RadioError("Error sending data to radio")
295

    
296

    
297
def _make_frame(cmd, addr, length, data=""):
298
    """Pack the info in the headder format"""
299
    frame = struct.pack(">4sHH", cmd, addr, length)
300
    # Add the data if set
301
    if len(data) != 0:
302
        frame += data
303
    # Return the data
304
    return frame
305

    
306

    
307
def _recv(radio, addr, length):
308
    """Get data from the radio """
309

    
310
    data = _rawrecv(radio, length)
311

    
312
    # DEBUG
313
    LOG.info("Response:")
314
    LOG.debug(util.hexprint(data))
315

    
316
    return data
317

    
318

    
319
def _do_ident(radio):
320
    """Put the radio in PROGRAM mode & identify it"""
321
    # Set the serial discipline
322
    radio.pipe.baudrate = 19200
323
    radio.pipe.parity = "N"
324
    radio.pipe.timeout = STIMEOUT
325

    
326
    # Flush input buffer
327
    _clean_buffer(radio)
328

    
329
    magic = b"PROM_LIN"
330

    
331
    _rawsend(radio, magic)
332

    
333
    ack = _rawrecv(radio, 1)
334
    if ack != b"\x06":
335
        _exit_program_mode(radio)
336
        if ack:
337
            LOG.debug(repr(ack))
338
        raise errors.RadioError("Radio did not respond")
339

    
340
    return True
341

    
342

    
343
def _exit_program_mode(radio):
344
    endframe = b"EXIT"
345
    _rawsend(radio, endframe)
346

    
347

    
348
def _download(radio):
349
    """Get the memory map"""
350

    
351
    # Put radio in program mode and identify it
352
    _do_ident(radio)
353

    
354
    # UI progress
355
    status = chirp_common.Status()
356
    status.cur = 0
357
    status.max = MEM_SIZE // BLOCK_SIZE
358
    status.msg = "Cloning from radio..."
359
    radio.status_fn(status)
360

    
361
    data = b""
362
    for addr in range(0, MEM_SIZE, BLOCK_SIZE):
363
        frame = _make_frame(b"READ", addr, BLOCK_SIZE)
364
        # DEBUG
365
        LOG.info("Request sent:")
366
        LOG.debug(util.hexprint(frame))
367

    
368
        # Sending the read request
369
        _rawsend(radio, frame)
370

    
371
        # Now we read
372
        d = _recv(radio, addr, BLOCK_SIZE)
373

    
374
        # Aggregate the data
375
        data += d
376

    
377
        # UI Update
378
        status.cur = addr // BLOCK_SIZE
379
        status.msg = "Cloning from radio..."
380
        radio.status_fn(status)
381

    
382
    _exit_program_mode(radio)
383

    
384
    return data
385

    
386

    
387
def _upload(radio):
388
    """Upload procedure"""
389

    
390
    # Put radio in program mode and identify it
391
    _do_ident(radio)
392

    
393
    # UI progress
394
    status = chirp_common.Status()
395
    status.cur = 0
396
    status.max = MEM_SIZE // BLOCK_SIZE
397
    status.msg = "Cloning to radio..."
398
    radio.status_fn(status)
399

    
400
    # The fun starts here
401
    for addr in range(0, MEM_SIZE, BLOCK_SIZE):
402
        # Sending the data
403
        data = radio.get_mmap()[addr:addr + BLOCK_SIZE]
404

    
405
        frame = _make_frame(b"WRIE", addr, BLOCK_SIZE, data)
406

    
407
        _rawsend(radio, frame)
408

    
409
        # Receiving the response
410
        ack = _rawrecv(radio, 1)
411
        if ack != b"\x06":
412
            _exit_program_mode(radio)
413
            msg = "Bad ack writing block 0x%04x" % addr
414
            raise errors.RadioError(msg)
415

    
416
        # UI Update
417
        status.cur = addr // BLOCK_SIZE
418
        status.msg = "Cloning to radio..."
419
        radio.status_fn(status)
420

    
421
    _exit_program_mode(radio)
422

    
423

    
424
def model_match(cls, data):
425
    """Match the opened/downloaded image to the correct version"""
426
    if len(data) == 0x1C08:
427
        rid = data[0x1C00:0x1C08]
428
        return rid.startswith(cls.MODEL.encode())
429
    else:
430
        return False
431

    
432

    
433
def _split(rf, f1, f2):
434
    """Returns False if the two freqs are in the same band (no split)
435
    or True otherwise"""
436

    
437
    # Determine if the two freqs are in the same band
438
    for low, high in rf.valid_bands:
439
        if f1 >= low and f1 <= high and \
440
                f2 >= low and f2 <= high:
441
            # If the two freqs are on the same Band this is not a split
442
            return False
443

    
444
    # If you get here is because the freq pairs are split
445
    return True
446

    
447

    
448
@directory.register
449
class LT725UV(chirp_common.CloneModeRadio):
450
    """LUITON LT-725UV Radio"""
451
    VENDOR = "LUITON"
452
    MODEL = "LT-725UV"
453
    NEEDS_COMPAT_SERIAL = False
454
    MODES = ["NFM", "FM"]
455
    TONES = chirp_common.TONES
456
    DTCS_CODES = sorted(chirp_common.DTCS_CODES + [645])
457
    NAME_LENGTH = 7
458
    DTMF_CHARS = list("0123456789ABCD*#")
459

    
460
    # Channel Power: 2 levels
461
    # BJ-318 uses 3 power levels for each VFO "Band"
462
    # Low = 5W, Med = 10W, High = 25W
463
    # The band power selection in a VFO applies to the VFO and overrides
464
    # the stored channel power selection
465
    # The firmware channel memory structure provides only 1 bit for
466
    # individual channel power settings, limiting potential channel
467
    # power selection options to 2 levels: Low or High.
468
    POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=5.00),
469
                    chirp_common.PowerLevel("High", watts=30.00)]
470

    
471
    VALID_BANDS = [(136000000, 176000000),
472
                   (400000000, 480000000)]
473

    
474
    # Valid chars on the LCD
475
    VALID_CHARS = chirp_common.CHARSET_ALPHANUMERIC + \
476
        "`{|}!\"#$%&'()*+,-./:;<=>?@[]^_"
477

    
478
    @classmethod
479
    def get_prompts(cls):
480
        rp = chirp_common.RadioPrompts()
481
        if cls.MODEL == "BJ-318":
482
            msg = \
483
                ('\n'
484
                 'The individual BJ-318 channel power settings set in CHIRP are ignored by \n'
485
                 'the radio. They are allowed to be set in hopes of a future firmware \n'
486
                 'change by the manufacturer. While the CHIRP driver will allow setting \n'
487
                 'the individual VFO power to High (25W), Med (10W) or Low (5W) they \n' 
488
                 'are converted to just Low (5W) and High (25W) on upload. The Med setting \n'
489
                 'reverts to Low. \n'
490
                 '\n'
491
                 'Changing a channel power setting must be done manually using the front \n'
492
                 'panel menu control (or the microphone controls while in VFO mode.)'
493
                 )
494

    
495
        else:
496
            msg = \
497
                ('Some notes about POWER settings:\n'
498
                 '- The individual channel power settings are ignored'
499
                 ' by the radio.\n'
500
                 '  They are allowed to be set (and downloaded) in hopes of'
501
                 ' a future firmware update.\n'
502
                 '- Power settings done \'Live\' in the radio apply to the'
503
                 ' entire upper or lower band.\n'
504
                 '- Tri-power radio models will set and download the three'
505
                 ' band-power'
506
                 ' levels, but they are converted to just Low and High at'
507
                 ' upload.'
508
                 ' The Mid setting reverts to Low.'
509
                 )
510

    
511
        rp.info = msg
512

    
513
        rp.pre_download = _(dedent("""\
514
            Follow this instructions to download your info:
515

    
516
            1 - Turn off your radio
517
            2 - Connect your interface cable
518
            3 - Turn on your radio
519
            4 - Do the download of your radio data
520
            """))
521
        rp.pre_upload = _(dedent("""\
522
            Follow this instructions to upload your info:
523

    
524
            1 - Turn off your radio
525
            2 - Connect your interface cable
526
            3 - Turn on your radio
527
            4 - Do the upload of your radio data
528
            """))
529
        return rp
530

    
531
    def get_features(self):
532
        rf = chirp_common.RadioFeatures()
533
        rf.has_settings = True
534
        rf.has_bank = False
535
        rf.has_tuning_step = False
536
        rf.can_odd_split = True
537
        rf.has_name = True
538
        rf.has_offset = True
539
        rf.has_mode = True
540
        rf.has_dtcs = True
541
        rf.has_rx_dtcs = True
542
        rf.has_dtcs_polarity = True
543
        rf.has_ctone = True
544
        rf.has_cross = True
545
        rf.has_sub_devices = self.VARIANT == ""
546
        rf.valid_modes = self.MODES
547
        rf.valid_characters = self.VALID_CHARS
548
        rf.valid_duplexes = ["", "-", "+", "split", "off"]
549
        rf.valid_tmodes = ['', 'Tone', 'TSQL', 'DTCS', 'Cross']
550
        rf.valid_cross_modes = [
551
            "Tone->Tone",
552
            "DTCS->",
553
            "->DTCS",
554
            "Tone->DTCS",
555
            "DTCS->Tone",
556
            "->Tone",
557
            "DTCS->DTCS"]
558
        rf.valid_skips = []
559
        rf.valid_power_levels = self.POWER_LEVELS
560
        rf.valid_name_length = self.NAME_LENGTH
561
        rf.valid_dtcs_codes = self.DTCS_CODES
562
        rf.valid_bands = self.VALID_BANDS
563
        rf.memory_bounds = (1, 128)
564
        rf.valid_tuning_steps = STEPS
565
        return rf
566

    
567
    def get_sub_devices(self):
568
        return [LT725UVUpper(self._mmap), LT725UVLower(self._mmap)]
569

    
570
    def sync_in(self):
571
        """Download from radio"""
572
        try:
573
            data = _download(self)
574
        except errors.RadioError:
575
            # Pass through any real errors we raise
576
            raise
577
        except:
578
            # If anything unexpected happens, make sure we raise
579
            # a RadioError and log the problem
580
            LOG.exception('Unexpected error during download')
581
            raise errors.RadioError('Unexpected error communicating '
582
                                    'with the radio')
583
        self._mmap = memmap.MemoryMapBytes(data)
584
        self.process_mmap()
585

    
586
    def sync_out(self):
587
        """Upload to radio"""
588
        try:
589
            _upload(self)
590
        except:
591
            # If anything unexpected happens, make sure we raise
592
            # a RadioError and log the problem
593
            LOG.exception('Unexpected error during upload')
594
            raise errors.RadioError('Unexpected error communicating '
595
                                    'with the radio')
596

    
597
    def process_mmap(self):
598
        """Process the mem map into the mem object"""
599
        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
600

    
601
    def get_raw_memory(self, number):
602
        return repr(self._memobj.memory[number - 1])
603

    
604
    def _memory_obj(self, suffix=""):
605
        return getattr(self._memobj, "%s_memory%s" % (self._vfo, suffix))
606

    
607
    def _get_dcs(self, val):
608
        return int(str(val)[2:-18])
609

    
610
    def _set_dcs(self, val):
611
        return int(str(val), 16)
612

    
613
    def get_memory(self, number):
614
        _mem = self._memory_obj()[number - 1]
615

    
616
        mem = chirp_common.Memory()
617
        mem.number = number
618

    
619
        if _mem.get_raw()[0] == "\xff":
620
            mem.empty = True
621
            return mem
622

    
623
        mem.freq = int(_mem.rxfreq) * 10
624

    
625
        if _mem.txfreq == 0xFFFFFFFF:
626
            # TX freq not set
627
            mem.duplex = "off"
628
            mem.offset = 0
629
        elif int(_mem.rxfreq) == int(_mem.txfreq):
630
            mem.duplex = ""
631
            mem.offset = 0
632
        elif _split(self.get_features(), mem.freq, int(_mem.txfreq) * 10):
633
            mem.duplex = "split"
634
            mem.offset = int(_mem.txfreq) * 10
635
        else:
636
            mem.duplex = int(_mem.rxfreq) > int(_mem.txfreq) and "-" or "+"
637
            mem.offset = abs(int(_mem.rxfreq) - int(_mem.txfreq)) * 10
638

    
639
        for char in _mem.name[0:_mem.namelen]:
640
            mem.name += chr(char)
641

    
642
        dtcs_pol = ["N", "N"]
643

    
644
        if _mem.rxtone == 0x3FFF:
645
            rxmode = ""
646
        elif _mem.is_rxdigtone == 0:
647
            # CTCSS
648
            rxmode = "Tone"
649
            mem.ctone = int(_mem.rxtone) / 10.0
650
        else:
651
            # Digital
652
            rxmode = "DTCS"
653
            mem.rx_dtcs = self._get_dcs(_mem.rxtone)
654
            if _mem.rxdtcs_pol == 1:
655
                dtcs_pol[1] = "R"
656

    
657
        if _mem.txtone == 0x3FFF:
658
            txmode = ""
659
        elif _mem.is_txdigtone == 0:
660
            # CTCSS
661
            txmode = "Tone"
662
            mem.rtone = int(_mem.txtone) / 10.0
663
        else:
664
            # Digital
665
            txmode = "DTCS"
666
            mem.dtcs = self._get_dcs(_mem.txtone)
667
            if _mem.txdtcs_pol == 1:
668
                dtcs_pol[0] = "R"
669

    
670
        if txmode == "Tone" and not rxmode:
671
            mem.tmode = "Tone"
672
        elif txmode == rxmode and txmode == "Tone" and mem.rtone == mem.ctone:
673
            mem.tmode = "TSQL"
674
        elif txmode == rxmode and txmode == "DTCS" and mem.dtcs == mem.rx_dtcs:
675
            mem.tmode = "DTCS"
676
        elif rxmode or txmode:
677
            mem.tmode = "Cross"
678
            mem.cross_mode = "%s->%s" % (txmode, rxmode)
679

    
680
        mem.dtcs_polarity = "".join(dtcs_pol)
681

    
682
        mem.mode = _mem.wide and "FM" or "NFM"
683

    
684
        mem.power = self.POWER_LEVELS[_mem.power]
685

    
686
        # Extra
687
        mem.extra = RadioSettingGroup("extra", "Extra")
688

    
689
        if _mem.recvmode == 0xFF:
690
            val = 0x00
691
        else:
692
            val = _mem.recvmode
693
        recvmode = RadioSetting("recvmode", "Receiving mode",
694
                                RadioSettingValueList(LIST_RECVMODE,
695
                                                      LIST_RECVMODE[val]))
696
        mem.extra.append(recvmode)
697

    
698
        if _mem.botsignal == 0xFF:
699
            val = 0x00
700
        else:
701
            val = _mem.botsignal
702
        botsignal = RadioSetting("botsignal", "Launch signaling",
703
                                 RadioSettingValueList(LIST_SIGNAL,
704
                                                       LIST_SIGNAL[val]))
705
        mem.extra.append(botsignal)
706

    
707
        if _mem.eotsignal == 0xFF:
708
            val = 0x00
709
        else:
710
            val = _mem.eotsignal
711

    
712
        rx = RadioSettingValueList(LIST_SIGNAL, LIST_SIGNAL[val])
713
        eotsignal = RadioSetting("eotsignal", "Transmit end signaling", rx)
714
        mem.extra.append(eotsignal)
715

    
716
        rx = RadioSettingValueBoolean(bool(_mem.compandor))
717
        compandor = RadioSetting("compandor", "Compandor", rx)
718
        mem.extra.append(compandor)
719

    
720
        rx = RadioSettingValueBoolean(bool(_mem.scrambler))
721
        scrambler = RadioSetting("scrambler", "Scrambler", rx)
722
        mem.extra.append(scrambler)
723

    
724
        return mem
725

    
726
    def set_memory(self, mem):
727
        _mem = self._memory_obj()[mem.number - 1]
728

    
729
        if mem.empty:
730
            _mem.set_raw("\xff" * 24)
731
            _mem.namelen = 0
732
            return
733

    
734
        _mem.set_raw("\xFF" * 15 + "\x00\x00" + "\xFF" * 7)
735

    
736
        _mem.rxfreq = mem.freq / 10
737
        if mem.duplex == "off":
738
            _mem.txfreq = 0xFFFFFFFF
739
        elif mem.duplex == "split":
740
            _mem.txfreq = mem.offset / 10
741
        elif mem.duplex == "+":
742
            _mem.txfreq = (mem.freq + mem.offset) / 10
743
        elif mem.duplex == "-":
744
            _mem.txfreq = (mem.freq - mem.offset) / 10
745
        else:
746
            _mem.txfreq = mem.freq / 10
747

    
748
        _mem.namelen = len(mem.name)
749
        _namelength = self.get_features().valid_name_length
750
        for i in range(_namelength):
751
            try:
752
                _mem.name[i] = ord(mem.name[i])
753
            except IndexError:
754
                _mem.name[i] = 0xFF
755

    
756
        rxmode = ""
757
        txmode = ""
758

    
759
        if mem.tmode == "Tone":
760
            txmode = "Tone"
761
        elif mem.tmode == "TSQL":
762
            rxmode = "Tone"
763
            txmode = "TSQL"
764
        elif mem.tmode == "DTCS":
765
            rxmode = "DTCSSQL"
766
            txmode = "DTCS"
767
        elif mem.tmode == "Cross":
768
            txmode, rxmode = mem.cross_mode.split("->", 1)
769

    
770
        if rxmode == "":
771
            _mem.rxdtcs_pol = 1
772
            _mem.is_rxdigtone = 1
773
            _mem.rxtone = 0x3FFF
774
        elif rxmode == "Tone":
775
            _mem.rxdtcs_pol = 0
776
            _mem.is_rxdigtone = 0
777
            _mem.rxtone = int(mem.ctone * 10)
778
        elif rxmode == "DTCSSQL":
779
            _mem.rxdtcs_pol = 1 if mem.dtcs_polarity[1] == "R" else 0
780
            _mem.is_rxdigtone = 1
781
            _mem.rxtone = self._set_dcs(mem.dtcs)
782
        elif rxmode == "DTCS":
783
            _mem.rxdtcs_pol = 1 if mem.dtcs_polarity[1] == "R" else 0
784
            _mem.is_rxdigtone = 1
785
            _mem.rxtone = self._set_dcs(mem.rx_dtcs)
786

    
787
        if txmode == "":
788
            _mem.txdtcs_pol = 1
789
            _mem.is_txdigtone = 1
790
            _mem.txtone = 0x3FFF
791
        elif txmode == "Tone":
792
            _mem.txdtcs_pol = 0
793
            _mem.is_txdigtone = 0
794
            _mem.txtone = int(mem.rtone * 10)
795
        elif txmode == "TSQL":
796
            _mem.txdtcs_pol = 0
797
            _mem.is_txdigtone = 0
798
            _mem.txtone = int(mem.ctone * 10)
799
        elif txmode == "DTCS":
800
            _mem.txdtcs_pol = 1 if mem.dtcs_polarity[0] == "R" else 0
801
            _mem.is_txdigtone = 1
802
            _mem.txtone = self._set_dcs(mem.dtcs)
803

    
804
        _mem.wide = self.MODES.index(mem.mode)
805
        _mem.power = mem.power == self.POWER_LEVELS[1]
806

    
807
        # Extra settings
808
        for setting in mem.extra:
809
            setattr(_mem, setting.get_name(), setting.value)
810

    
811
    def get_settings(self):
812
        """Translate the bit in the mem_struct into settings in the UI"""
813
        # Define mem struct write-back shortcuts
814
        _sets = self._memobj.settings
815
        _vfoa = self._memobj.upper.vfoa
816
        _vfob = self._memobj.lower.vfob
817
        _lims = self._memobj.hello_lims
818
        _codes = self._memobj.codes
819
        _dtmf = self._memobj.dtmf_tab
820

    
821
        basic = RadioSettingGroup("basic", "Basic Settings")
822
        a_band = RadioSettingGroup("a_band", "VFO A-Upper Settings")
823
        b_band = RadioSettingGroup("b_band", "VFO B-Lower Settings")
824
        codes = RadioSettingGroup("codes", "Codes & DTMF Groups")
825
        lims = RadioSettingGroup("lims", "PowerOn & Freq Limits")
826
        group = RadioSettings(basic, a_band, b_band, lims, codes)
827

    
828
        # Basic Settings
829
        bnd_mode = RadioSetting("settings.init_bank", "TDR Band Default",
830
                                RadioSettingValueList(LIST_TDR_DEF,
831
                                                      LIST_TDR_DEF[
832
                                                          _sets.init_bank]))
833
        basic.append(bnd_mode)
834

    
835
        # BJ-318 set by vfo, skip
836
        if self.MODEL != "BJ-318":
837
            rs = RadioSettingValueInteger(0, 20, _sets.volume)
838
            volume = RadioSetting("settings.volume", "Volume", rs)
839
            basic.append(volume)
840

    
841
        # BJ-318 set by vfo, skip
842
        if self.MODEL != "BJ-318":
843
            vala = _vfoa.bpower        # 2bits values 0,1,2= Low, Mid, High
844
            rx = RadioSettingValueList(LIST_BPOWER, LIST_BPOWER[vala])
845
            powera = RadioSetting("upper.vfoa.bpower", "Power (Upper)", rx)
846
            basic.append(powera)
847

    
848
        # BJ-318 set by vfo, skip
849
        if self.MODEL != "BJ-318":
850
            valb = _vfob.bpower
851
            rx = RadioSettingValueList(LIST_BPOWER, LIST_BPOWER[valb])
852
            powerb = RadioSetting("lower.vfob.bpower", "Power (Lower)", rx)
853
            basic.append(powerb)
854

    
855
        def my_word2raw(setting, obj, atrb, mlt=10):
856
            """Callback function to convert UI floating value to u16 int"""
857
            if str(setting.value) == "Off":
858
                frq = 0x0FFFF
859
            else:
860
                frq = int(float(str(setting.value)) * float(mlt))
861
            if frq == 0:
862
                frq = 0xFFFF
863
            setattr(obj, atrb, frq)
864
            return
865

    
866
        def my_adjraw(setting, obj, atrb, fix):
867
            """Callback: add or subtract fix from value."""
868
            vx = int(str(setting.value))
869
            value = vx + int(fix)
870
            if value < 0:
871
                value = 0
872
            if atrb == "frq_chn_mode" and int(str(setting.value)) == 2:
873
                value = vx * 2         # Special handling for frq_chn_mode
874
            setattr(obj, atrb, value)
875
            return
876

    
877
        def my_dbl2raw(setting, obj, atrb, flg=1):
878
            """Callback: convert from freq 146.7600 to 14760000 U32."""
879
            value = chirp_common.parse_freq(str(setting.value)) / 10
880
            # flg=1 means 0 becomes ff, else leave as possible 0
881
            if flg == 1 and value == 0:
882
                value = 0xFFFFFFFF
883
            setattr(obj, atrb, value)
884
            return
885

    
886
        def my_val_list(setting, obj, atrb):
887
            """Callback:from ValueList with non-sequential, actual values."""
888
            value = int(str(setting.value))            # Get the integer value
889
            if atrb == "tot":
890
                value = int(value / 30)    # 30 second increments
891
            setattr(obj, atrb, value)
892
            return
893

    
894
        def my_spcl(setting, obj, atrb):
895
            """Callback: Special handling based on atrb."""
896
            if atrb == "frq_chn_mode":
897
                idx = LIST_VFOMODE.index(str(setting.value))  # Returns 0 or 1
898
                value = idx * 2            # Set bit 1
899
            setattr(obj, atrb, value)
900
            return
901

    
902
        def my_tone_strn(obj, is_atr, pol_atr, tone_atr):
903
            """Generate the CTCS/DCS tone code string."""
904
            vx = int(getattr(obj, tone_atr))
905
            if vx == 16383 or vx == 0:
906
                return "Off"                 # 16383 is all bits set
907
            if getattr(obj, is_atr) == 0:             # Simple CTCSS code
908
                tstr = str(vx / 10.0)
909
            else:        # DCS
910
                if getattr(obj, pol_atr) == 0:
911
                    tstr = "D{:03x}R".format(vx)
912
                else:
913
                    tstr = "D{:03x}N".format(vx)
914
            return tstr
915

    
916
        def my_set_tone(setting, obj, is_atr, pol_atr, tone_atr):
917
            """Callback- create the tone setting from string code."""
918
            sx = str(setting.value)        # '131.8'  or 'D231N' or 'Off'
919
            if sx == "Off":
920
                isx = 1
921
                polx = 1
922
                tonx = 0x3FFF
923
            elif sx[0] == "D":         # DCS
924
                isx = 1
925
                if sx[4] == "N":
926
                    polx = 1
927
                else:
928
                    polx = 0
929
                tonx = int(sx[1:4], 16)
930
            else:                                     # CTCSS
931
                isx = 0
932
                polx = 0
933
                tonx = int(float(sx) * 10.0)
934
            setattr(obj, is_atr, isx)
935
            setattr(obj, pol_atr, polx)
936
            setattr(obj, tone_atr, tonx)
937
            return
938

    
939
        # not used by BJ-318 skip
940
        if self.MODEL != "BJ-318":
941
            val = _sets.fm_freq / 10.0
942
            if val == 0:
943
                val = 88.9            # 0 is not valid
944
            rx = RadioSettingValueFloat(65, 108.0, val, 0.1, 1)
945
            rs = RadioSetting("settings.fm_freq",
946
                              "FM Broadcast Freq (MHz)", rx)
947
            rs.set_apply_callback(my_word2raw, _sets, "fm_freq")
948
            basic.append(rs)
949

    
950
        # BJ-318 fm frequency
951
        if self.MODEL == "BJ-318":
952
            val = _lims.fm_318 / 10.0
953
            if val == 0:
954
                val = 88.9            # 0 is not valid
955
            rx = RadioSettingValueFloat(65, 108.0, val, 0.1, 1)
956
            rs = RadioSetting("hello_lims.fm_318",
957
                              "FM Broadcast Freq (MHz)", rx)
958
            rs.set_apply_callback(my_word2raw, _lims, "fm_318")
959
            basic.append(rs)
960

    
961
        # not used in BJ-318, skip
962
        if self.MODEL != "BJ-318":
963
            rs = RadioSettingValueList(LIST_COLOR,
964
                                       LIST_COLOR[_sets.wtled])
965
            wtled = RadioSetting("settings.wtled", "Standby LED Color", rs)
966
            basic.append(wtled)
967

    
968
        # not used in BJ-318, skip
969
        if self.MODEL != "BJ-318":
970
            rs = RadioSettingValueList(LIST_COLOR,
971
                                       LIST_COLOR[_sets.rxled])
972
            rxled = RadioSetting("settings.rxled", "RX LED Color", rs)
973
            basic.append(rxled)
974

    
975
        # not used in BJ-318, skip
976
        if self.MODEL != "BJ-318":
977
            rs = RadioSettingValueList(LIST_COLOR,
978
                                       LIST_COLOR[_sets.txled])
979
            txled = RadioSetting("settings.txled", "TX LED Color", rs)
980
            basic.append(txled)
981

    
982
        ledsw = RadioSetting("settings.ledsw", "Back light mode",
983
                             RadioSettingValueList(LIST_LEDSW, LIST_LEDSW[
984
                                 _sets.ledsw]))
985
        basic.append(ledsw)
986

    
987
        beep = RadioSetting("settings.beep", "Beep",
988
                            RadioSettingValueBoolean(bool(_sets.beep)))
989
        basic.append(beep)
990

    
991
        ring = RadioSetting("settings.ring", "Ring",
992
                            RadioSettingValueList(LIST_RING, LIST_RING[
993
                                _sets.ring]))
994
        basic.append(ring)
995

    
996
        bcl = RadioSetting("settings.bcl", "Busy channel lockout",
997
                           RadioSettingValueBoolean(bool(_sets.bcl)))
998
        basic.append(bcl)
999

    
1000
        # squelch for non-BJ-318 models
1001
        # BJ-318 squelch set by VFO basis
1002
        if self.MODEL != "BJ-318":
1003
            if _vfoa.sql == 0xFF:
1004
                val = 0x04
1005
            else:
1006
                val = _vfoa.sql
1007
            sqla = RadioSetting("upper.vfoa.sql", "Squelch (Upper)",
1008
                                RadioSettingValueInteger(0, 9, val))
1009
            basic.append(sqla)
1010

    
1011
            if _vfob.sql == 0xFF:
1012
                val = 0x04
1013
            else:
1014
                val = _vfob.sql
1015
            sqlb = RadioSetting("lower.vfob.sql", "Squelch (Lower)",
1016
                                RadioSettingValueInteger(0, 9, val))
1017
            basic.append(sqlb)
1018

    
1019
        tmp = str(int(_sets.tot) * 30)     # 30 sec step counter
1020
        rs = RadioSetting("settings.tot", "Transmit Timeout (Secs)",
1021
                          RadioSettingValueList(LIST_TIMEOUT, tmp))
1022
        rs.set_apply_callback(my_val_list, _sets, "tot")
1023
        basic.append(rs)
1024

    
1025
        tmp = str(int(_sets.sig_freq))
1026
        rs = RadioSetting("settings.sig_freq", "Single Signaling Tone (Htz)",
1027
                          RadioSettingValueList(LIST_SSF, tmp))
1028
        rs.set_apply_callback(my_val_list, _sets, "sig_freq")
1029
        basic.append(rs)
1030

    
1031
        tmp = str(int(_sets.dtmf_txms))
1032
        rs = RadioSetting("settings.dtmf_txms", "DTMF Tx Duration (mSecs)",
1033
                          RadioSettingValueList(LIST_DTMFTX, tmp))
1034
        rs.set_apply_callback(my_val_list, _sets, "dtmf_txms")
1035
        basic.append(rs)
1036

    
1037
        rs = RadioSetting("settings.rptr_mode", "Repeater Mode",
1038
                          RadioSettingValueBoolean(bool(_sets.rptr_mode)))
1039
        basic.append(rs)
1040

    
1041
        # UPPER BAND SETTINGS
1042

    
1043
        # Freq Mode, convert bit 1 state to index pointer
1044
        val = _vfoa.frq_chn_mode // 2
1045

    
1046
        rx = RadioSettingValueList(LIST_VFOMODE, LIST_VFOMODE[val])
1047
        rs = RadioSetting("upper.vfoa.frq_chn_mode", "Default Mode", rx)
1048
        rs.set_apply_callback(my_spcl, _vfoa, "frq_chn_mode")
1049
        a_band.append(rs)
1050

    
1051
        val = _vfoa.chan_num + 1                  # Add 1 for 1-128 displayed
1052
        rs = RadioSetting("upper.vfoa.chan_num", "Initial Chan",
1053
                          RadioSettingValueInteger(1, 128, val))
1054
        rs.set_apply_callback(my_adjraw, _vfoa, "chan_num", -1)
1055
        a_band.append(rs)
1056

    
1057
        val = _vfoa.rxfreq / 100000.0
1058
        if (val < 136.0 or val > 176.0):
1059
            val = 146.520            # 2m calling
1060
        rs = RadioSetting("upper.vfoa.rxfreq ", "Default Recv Freq (MHz)",
1061
                          RadioSettingValueFloat(136.0, 176.0, val, 0.001, 5))
1062
        rs.set_apply_callback(my_dbl2raw, _vfoa, "rxfreq")
1063
        a_band.append(rs)
1064

    
1065
        tmp = my_tone_strn(_vfoa, "is_rxdigtone", "rxdtcs_pol", "rx_tone")
1066
        rs = RadioSetting("rx_tone", "Default Recv CTCSS (Htz)",
1067
                          RadioSettingValueList(LIST_CTCSS, tmp))
1068
        rs.set_apply_callback(my_set_tone, _vfoa, "is_rxdigtone",
1069
                              "rxdtcs_pol", "rx_tone")
1070
        a_band.append(rs)
1071

    
1072
        rx = RadioSettingValueList(LIST_RECVMODE,
1073
                                   LIST_RECVMODE[_vfoa.rx_mode])
1074
        rs = RadioSetting("upper.vfoa.rx_mode", "Default Recv Mode", rx)
1075
        a_band.append(rs)
1076

    
1077
        tmp = my_tone_strn(_vfoa, "is_txdigtone", "txdtcs_pol", "tx_tone")
1078
        rs = RadioSetting("tx_tone", "Default Xmit CTCSS (Htz)",
1079
                          RadioSettingValueList(LIST_CTCSS, tmp))
1080
        rs.set_apply_callback(my_set_tone, _vfoa, "is_txdigtone",
1081
                              "txdtcs_pol", "tx_tone")
1082
        a_band.append(rs)
1083

    
1084
        rs = RadioSetting("upper.vfoa.launch_sig", "Launch Signaling",
1085
                          RadioSettingValueList(LIST_SIGNAL,
1086
                                                LIST_SIGNAL[_vfoa.launch_sig]))
1087
        a_band.append(rs)
1088

    
1089
        rx = RadioSettingValueList(LIST_SIGNAL, LIST_SIGNAL[_vfoa.tx_end_sig])
1090
        rs = RadioSetting("upper.vfoa.tx_end_sig", "Xmit End Signaling", rx)
1091
        a_band.append(rs)
1092

    
1093
        rx = RadioSettingValueList(LIST_BW, LIST_BW[_vfoa.fm_bw])
1094
        rs = RadioSetting("upper.vfoa.fm_bw", "Wide/Narrow Band", rx)
1095
        a_band.append(rs)
1096

    
1097
        rx = RadioSettingValueBoolean(bool(_vfoa.cmp_nder))
1098
        rs = RadioSetting("upper.vfoa.cmp_nder", "Compandor", rx)
1099
        a_band.append(rs)
1100

    
1101
        rs = RadioSetting("upper.vfoa.scrm_blr", "Scrambler",
1102
                          RadioSettingValueBoolean(bool(_vfoa.scrm_blr)))
1103
        a_band.append(rs)
1104

    
1105
        rx = RadioSettingValueList(LIST_SHIFT, LIST_SHIFT[_vfoa.shift])
1106
        rs = RadioSetting("upper.vfoa.shift", "Xmit Shift", rx)
1107
        a_band.append(rs)
1108

    
1109
        val = _vfoa.offset / 100000.0
1110
        rs = RadioSetting("upper.vfoa.offset", "Xmit Offset (MHz)",
1111
                          RadioSettingValueFloat(0, 100.0, val, 0.001, 3))
1112
        # Allow zero value
1113
        rs.set_apply_callback(my_dbl2raw, _vfoa, "offset", 0)
1114
        a_band.append(rs)
1115

    
1116
        tmp = str(_vfoa.step / 100.0)
1117
        rs = RadioSetting("step", "Freq step (KHz)",
1118
                          RadioSettingValueList(LIST_STEPS, tmp))
1119
        rs.set_apply_callback(my_word2raw, _vfoa, "step", 100)
1120
        a_band.append(rs)
1121

    
1122
        # BJ-318 upper band squelch
1123
        if self.MODEL == "BJ-318":
1124
            if _vfoa.sql == 0xFF:
1125
                valq = 0x04    # setting default squelch to 04
1126
            else:
1127
                valq = _vfoa.sql
1128
                sqla = RadioSetting("upper.vfoa.sql", "Squelch",
1129
                                    RadioSettingValueInteger(0, 9, valq))
1130
            a_band.append(sqla)
1131

    
1132
        # BJ-318 upper band volume
1133
        if self.MODEL == "BJ-318":
1134
            bvolume_u = RadioSetting("upper.vfoa.bvol",
1135
                                     "Volume", RadioSettingValueInteger(
1136
                                         0, 15, _vfoa.bvol))
1137
            a_band.append(bvolume_u)
1138

    
1139
        # BJ-318 Upper Screen Color
1140
        if self.MODEL == "BJ-318":
1141
            rs_u = RadioSettingValueList(LIST_COLOR318,
1142
                                         LIST_COLOR318[_lims.up_scr_color])
1143
            up_scr_color = RadioSetting("hello_lims.up_scr_color",
1144
                                        "Screen Color", rs_u)
1145
            a_band.append(up_scr_color)
1146

    
1147
        # BJ-318 Upper band power
1148
        if self.MODEL == "BJ-318":
1149
            val_b = _vfoa.bpower        # 2bits values 0,1,2= Low, Mid, High
1150
            rx = RadioSettingValueList(LIST_BPOWER, LIST_BPOWER[val_b])
1151
            power_u = RadioSetting("upper.vfoa.bpower", "Power", rx)
1152
            a_band.append(power_u)
1153

    
1154
        # LOWER BAND SETTINGS
1155

    
1156
        val = _vfob.frq_chn_mode // 2
1157
        rx = RadioSettingValueList(LIST_VFOMODE, LIST_VFOMODE[val])
1158
        rs = RadioSetting("lower.vfob.frq_chn_mode", "Default Mode", rx)
1159
        rs.set_apply_callback(my_spcl, _vfob, "frq_chn_mode")
1160
        b_band.append(rs)
1161

    
1162
        val = _vfob.chan_num + 1
1163
        rs = RadioSetting("lower.vfob.chan_num", "Initial Chan",
1164
                          RadioSettingValueInteger(1, 128, val))
1165
        rs.set_apply_callback(my_adjraw, _vfob, "chan_num", -1)
1166
        b_band.append(rs)
1167

    
1168
        val = _vfob.rxfreq / 100000.0
1169
        if (val < 400.0 or val > 480.0):
1170
            val = 446.0          # UHF calling
1171
        rs = RadioSetting("lower.vfob.rxfreq ", "Default Recv Freq (MHz)",
1172
                          RadioSettingValueFloat(400.0, 480.0, val, 0.001, 5))
1173
        rs.set_apply_callback(my_dbl2raw, _vfob, "rxfreq")
1174
        b_band.append(rs)
1175

    
1176
        tmp = my_tone_strn(_vfob, "is_rxdigtone", "rxdtcs_pol", "rx_tone")
1177
        rs = RadioSetting("rx_tone", "Default Recv CTCSS (Htz)",
1178
                          RadioSettingValueList(LIST_CTCSS, tmp))
1179
        rs.set_apply_callback(my_set_tone, _vfob, "is_rxdigtone",
1180
                              "rxdtcs_pol", "rx_tone")
1181
        b_band.append(rs)
1182

    
1183
        rx = RadioSettingValueList(LIST_RECVMODE, LIST_RECVMODE[_vfob.rx_mode])
1184
        rs = RadioSetting("lower.vfob.rx_mode", "Default Recv Mode", rx)
1185
        b_band.append(rs)
1186

    
1187
        tmp = my_tone_strn(_vfob, "is_txdigtone", "txdtcs_pol", "tx_tone")
1188
        rs = RadioSetting("tx_tone", "Default Xmit CTCSS (Htz)",
1189
                          RadioSettingValueList(LIST_CTCSS, tmp))
1190
        rs.set_apply_callback(my_set_tone, _vfob, "is_txdigtone",
1191
                              "txdtcs_pol", "tx_tone")
1192
        b_band.append(rs)
1193

    
1194
        rx = RadioSettingValueList(LIST_SIGNAL, LIST_SIGNAL[_vfob.launch_sig])
1195
        rs = RadioSetting("lower.vfob.launch_sig", "Launch Signaling", rx)
1196
        b_band.append(rs)
1197

    
1198
        rx = RadioSettingValueList(LIST_SIGNAL, LIST_SIGNAL[_vfob.tx_end_sig])
1199
        rs = RadioSetting("lower.vfob.tx_end_sig", "Xmit End Signaling", rx)
1200
        b_band.append(rs)
1201

    
1202
        rx = RadioSettingValueList(LIST_BW, LIST_BW[_vfob.fm_bw])
1203
        rs = RadioSetting("lower.vfob.fm_bw", "Wide/Narrow Band", rx)
1204
        b_band.append(rs)
1205

    
1206
        rs = RadioSetting("lower.vfob.cmp_nder", "Compandor",
1207
                          RadioSettingValueBoolean(bool(_vfob.cmp_nder)))
1208
        b_band.append(rs)
1209

    
1210
        rs = RadioSetting("lower.vfob.scrm_blr", "Scrambler",
1211
                          RadioSettingValueBoolean(bool(_vfob.scrm_blr)))
1212
        b_band.append(rs)
1213

    
1214
        rx = RadioSettingValueList(LIST_SHIFT, LIST_SHIFT[_vfob.shift])
1215
        rs = RadioSetting("lower.vfob.shift", "Xmit Shift", rx)
1216
        b_band.append(rs)
1217

    
1218
        val = _vfob.offset / 100000.0
1219
        rs = RadioSetting("lower.vfob.offset", "Xmit Offset (MHz)",
1220
                          RadioSettingValueFloat(0, 100.0, val, 0.001, 3))
1221
        rs.set_apply_callback(my_dbl2raw, _vfob, "offset", 0)
1222
        b_band.append(rs)
1223

    
1224
        tmp = str(_vfob.step / 100.0)
1225
        rs = RadioSetting("step", "Freq step (KHz)",
1226
                          RadioSettingValueList(LIST_STEPS, tmp))
1227
        rs.set_apply_callback(my_word2raw, _vfob, "step", 100)
1228
        b_band.append(rs)
1229

    
1230
        # BJ-318 lower band squelch
1231
        if self.MODEL == "BJ-318":
1232
            if _vfob.sql == 0xFF:
1233
                val_l = 0x04    # setting default squelch to 04
1234
            else:
1235
                val_l = _vfob.sql
1236
                sql_b = RadioSetting("lower.vfob.sql", "Squelch",
1237
                                     RadioSettingValueInteger(0, 9, val_l))
1238
                b_band.append(sql_b)
1239

    
1240
        # BJ-318 lower band volume
1241
        if self.MODEL == "BJ-318":
1242
            bvolume_l = RadioSetting("lower.vfob.bvol",
1243
                                     "Volume", RadioSettingValueInteger(
1244
                                         0, 15, _vfob.bvol))
1245
            b_band.append(bvolume_l)
1246

    
1247
        # BJ-318 Lower Screen Color
1248
        if self.MODEL == "BJ-318":
1249
            rs_l = RadioSettingValueList(LIST_COLOR318,
1250
                                         LIST_COLOR318[_lims.dn_scr_color])
1251
            dn_scr_color = RadioSetting("hello_lims.dn_scr_color",
1252
                                        "Screen Color", rs_l)
1253
            b_band.append(dn_scr_color)
1254

    
1255
        # BJ-318 lower band power
1256
        if self.MODEL == "BJ-318":
1257
            val_l = _vfoa.bpower        # 2bits values 0,1,2= Low, Mid, High
1258
            rx = RadioSettingValueList(LIST_BPOWER, LIST_BPOWER[val_l])
1259
            powera = RadioSetting("lower.vfob.bpower", "Power", rx)
1260
            b_band.append(powera)
1261

    
1262
        # PowerOn & Freq Limits Settings
1263
        def chars2str(cary, knt):
1264
            """Convert raw memory char array to a string: NOT a callback."""
1265
            stx = ""
1266
            for char in cary[0:knt]:
1267
                stx += chr(int(char))
1268
            return stx
1269

    
1270
        def my_str2ary(setting, obj, atrba, atrbc):
1271
            """Callback: convert 7-char string to char array with count."""
1272
            ary = ""
1273
            knt = 7
1274
            for j in range(6, -1, -1):       # Strip trailing spaces
1275
                if str(setting.value)[j] == "" or str(setting.value)[j] == " ":
1276
                    knt = knt - 1
1277
                else:
1278
                    break
1279
            for j in range(0, 7, 1):
1280
                if j < knt:
1281
                    ary += str(setting.value)[j]
1282
                else:
1283
                    ary += chr(0xFF)
1284
            setattr(obj, atrba, ary)
1285
            setattr(obj, atrbc, knt)
1286
            return
1287

    
1288
        # not used in BJ-318 startup screen
1289
        if self.MODEL != "BJ-318":
1290
            tmp = chars2str(_lims.hello1, _lims.hello1_cnt)
1291
            rs = RadioSetting("hello_lims.hello1", "Power-On Message 1",
1292
                              RadioSettingValueString(0, 7, tmp))
1293
            rs.set_apply_callback(my_str2ary, _lims, "hello1", "hello1_cnt")
1294
            lims.append(rs)
1295

    
1296
        # not used in BJ-318 startup screen
1297
        if self.MODEL != "BJ-318":
1298
            tmp = chars2str(_lims.hello2, _lims.hello2_cnt)
1299
            rs = RadioSetting("hello_lims.hello2", "Power-On Message 2",
1300
                              RadioSettingValueString(0, 7, tmp))
1301
            rs.set_apply_callback(my_str2ary, _lims, "hello2", "hello2_cnt")
1302
            lims.append(rs)
1303

    
1304
        # VALID_BANDS = [(136000000, 176000000),400000000, 480000000)]
1305

    
1306
        lval = _lims.vhf_low / 100000.0
1307
        uval = _lims.vhf_high / 100000.0
1308
        if lval >= uval:
1309
            lval = 144.0
1310
            uval = 158.0
1311

    
1312
        rs = RadioSetting("hello_lims.vhf_low", "Lower VHF Band Limit (MHz)",
1313
                          RadioSettingValueFloat(136.0, 176.0, lval, 0.001, 3))
1314
        rs.set_apply_callback(my_dbl2raw, _lims, "vhf_low")
1315
        lims.append(rs)
1316

    
1317
        rs = RadioSetting("hello_lims.vhf_high", "Upper VHF Band Limit (MHz)",
1318
                          RadioSettingValueFloat(136.0, 176.0, uval, 0.001, 3))
1319
        rs.set_apply_callback(my_dbl2raw, _lims, "vhf_high")
1320
        lims.append(rs)
1321

    
1322
        lval = _lims.uhf_low / 100000.0
1323
        uval = _lims.uhf_high / 100000.0
1324
        if lval >= uval:
1325
            lval = 420.0
1326
            uval = 470.0
1327

    
1328
        rs = RadioSetting("hello_lims.uhf_low", "Lower UHF Band Limit (MHz)",
1329
                          RadioSettingValueFloat(400.0, 480.0, lval, 0.001, 3))
1330
        rs.set_apply_callback(my_dbl2raw, _lims, "uhf_low")
1331
        lims.append(rs)
1332

    
1333
        rs = RadioSetting("hello_lims.uhf_high", "Upper UHF Band Limit (MHz)",
1334
                          RadioSettingValueFloat(400.0, 480.0, uval, 0.001, 3))
1335
        rs.set_apply_callback(my_dbl2raw, _lims, "uhf_high")
1336
        lims.append(rs)
1337

    
1338
        # Codes and DTMF Groups Settings
1339

    
1340
        def make_dtmf(ary, knt):
1341
            """Generate the DTMF code 1-8, NOT a callback."""
1342
            tmp = ""
1343
            if knt > 0 and knt != 0xff:
1344
                for val in ary[0:knt]:
1345
                    if val > 0 and val <= 9:
1346
                        tmp += chr(val + 48)
1347
                    elif val == 0x0a:
1348
                        tmp += "0"
1349
                    elif val == 0x0d:
1350
                        tmp += "A"
1351
                    elif val == 0x0e:
1352
                        tmp += "B"
1353
                    elif val == 0x0f:
1354
                        tmp += "C"
1355
                    elif val == 0x00:
1356
                        tmp += "D"
1357
                    elif val == 0x0b:
1358
                        tmp += "*"
1359
                    elif val == 0x0c:
1360
                        tmp += "#"
1361
                    else:
1362
                        msg = ("Invalid Character. Must be: 0-9,A,B,C,D,*,#")
1363
                        raise InvalidValueError(msg)
1364
            return tmp
1365

    
1366
        def my_dtmf2raw(setting, obj, atrba, atrbc, syz=7):
1367
            """Callback: DTMF Code; sends 5 or 7-byte string."""
1368
            draw = []
1369
            knt = syz
1370
            for j in range(syz - 1, -1, -1):       # Strip trailing spaces
1371
                if str(setting.value)[j] == "" or str(setting.value)[j] == " ":
1372
                    knt = knt - 1
1373
                else:
1374
                    break
1375
            for j in range(0, syz):
1376
                bx = str(setting.value)[j]
1377
                obx = ord(bx)
1378
                dig = 0x0ff
1379
                if j < knt and knt > 0:      # (Else) is pads
1380
                    if bx == "0":
1381
                        dig = 0x0a
1382
                    elif bx == "A":
1383
                        dig = 0x0d
1384
                    elif bx == "B":
1385
                        dig = 0x0e
1386
                    elif bx == "C":
1387
                        dig = 0x0f
1388
                    elif bx == "D":
1389
                        dig = 0x00
1390
                    elif bx == "*":
1391
                        dig = 0x0b
1392
                    elif bx == "#":
1393
                        dig = 0x0c
1394
                    elif obx >= 49 and obx <= 57:
1395
                        dig = obx - 48
1396
                    else:
1397
                        msg = ("Must be: 0-9,A,B,C,D,*,#")
1398
                        raise InvalidValueError(msg)
1399
                    # - End if/elif/else for bx
1400
                # - End if J<=knt
1401
                draw.append(dig)         # Generate string of bytes
1402
            # - End for j
1403
            setattr(obj, atrba, draw)
1404
            setattr(obj, atrbc, knt)
1405
            return
1406

    
1407
        tmp = make_dtmf(_codes.native_id_code, _codes.native_id_cnt)
1408
        rs = RadioSetting("codes.native_id_code", "Native ID Code",
1409
                          RadioSettingValueString(0, 7, tmp))
1410
        rs.set_apply_callback(my_dtmf2raw, _codes, "native_id_code",
1411
                              "native_id_cnt", 7)
1412
        codes.append(rs)
1413

    
1414
        tmp = make_dtmf(_codes.master_id_code, _codes.master_id_cnt)
1415
        rs = RadioSetting("codes.master_id_code", "Master Control ID Code",
1416
                          RadioSettingValueString(0, 7, tmp))
1417
        rs.set_apply_callback(my_dtmf2raw, _codes, "master_id_code",
1418
                              "master_id_cnt", 7)
1419
        codes.append(rs)
1420

    
1421
        tmp = make_dtmf(_codes.alarm_code, _codes.alarm_cnt)
1422
        rs = RadioSetting("codes.alarm_code", "Alarm Code",
1423
                          RadioSettingValueString(0, 5, tmp))
1424
        rs.set_apply_callback(my_dtmf2raw, _codes, "alarm_code",
1425
                              "alarm_cnt", 5)
1426
        codes.append(rs)
1427

    
1428
        tmp = make_dtmf(_codes.id_disp_code, _codes.id_disp_cnt)
1429
        rs = RadioSetting("codes.id_disp_code", "Identify Display Code",
1430
                          RadioSettingValueString(0, 5, tmp))
1431
        rs.set_apply_callback(my_dtmf2raw, _codes, "id_disp_code",
1432
                              "id_disp_cnt", 5)
1433
        codes.append(rs)
1434

    
1435
        tmp = make_dtmf(_codes.revive_code, _codes.revive_cnt)
1436
        rs = RadioSetting("codes.revive_code", "Revive Code",
1437
                          RadioSettingValueString(0, 5, tmp))
1438
        rs.set_apply_callback(my_dtmf2raw, _codes, "revive_code",
1439
                              "revive_cnt", 5)
1440
        codes.append(rs)
1441

    
1442
        tmp = make_dtmf(_codes.stun_code, _codes.stun_cnt)
1443
        rs = RadioSetting("codes.stun_code", "Remote Stun Code",
1444
                          RadioSettingValueString(0, 5, tmp))
1445
        rs.set_apply_callback(my_dtmf2raw,  _codes, "stun_code",
1446
                              "stun_cnt", 5)
1447
        codes.append(rs)
1448

    
1449
        tmp = make_dtmf(_codes.kill_code, _codes.kill_cnt)
1450
        rs = RadioSetting("codes.kill_code", "Remote KILL Code",
1451
                          RadioSettingValueString(0, 5, tmp))
1452
        rs.set_apply_callback(my_dtmf2raw, _codes, "kill_code",
1453
                              "kill_cnt", 5)
1454
        codes.append(rs)
1455

    
1456
        tmp = make_dtmf(_codes.monitor_code, _codes.monitor_cnt)
1457
        rs = RadioSetting("codes.monitor_code", "Monitor Code",
1458
                          RadioSettingValueString(0, 5, tmp))
1459
        rs.set_apply_callback(my_dtmf2raw, _codes, "monitor_code",
1460
                              "monitor_cnt", 5)
1461
        codes.append(rs)
1462

    
1463
        val = _codes.state_now
1464
        if val > 2:
1465
            val = 0
1466

    
1467
        rx = RadioSettingValueList(LIST_STATE, LIST_STATE[val])
1468
        rs = RadioSetting("codes.state_now", "Current State", rx)
1469
        codes.append(rs)
1470

    
1471
        dtm = make_dtmf(_dtmf.dtmf1, _dtmf.dtmf1_cnt)
1472
        rs = RadioSetting("dtmf_tab.dtmf1", "DTMF1 String",
1473
                          RadioSettingValueString(0, 7, dtm))
1474
        rs.set_apply_callback(my_dtmf2raw, _dtmf, "dtmf1", "dtmf1_cnt")
1475
        codes.append(rs)
1476

    
1477
        dtm = make_dtmf(_dtmf.dtmf2, _dtmf.dtmf2_cnt)
1478
        rs = RadioSetting("dtmf_tab.dtmf2", "DTMF2 String",
1479
                          RadioSettingValueString(0, 7, dtm))
1480
        rs.set_apply_callback(my_dtmf2raw, _dtmf, "dtmf2", "dtmf2_cnt")
1481
        codes.append(rs)
1482

    
1483
        dtm = make_dtmf(_dtmf.dtmf3, _dtmf.dtmf3_cnt)
1484
        rs = RadioSetting("dtmf_tab.dtmf3", "DTMF3 String",
1485
                          RadioSettingValueString(0, 7, dtm))
1486
        rs.set_apply_callback(my_dtmf2raw, _dtmf, "dtmf3", "dtmf3_cnt")
1487
        codes.append(rs)
1488

    
1489
        dtm = make_dtmf(_dtmf.dtmf4, _dtmf.dtmf4_cnt)
1490
        rs = RadioSetting("dtmf_tab.dtmf4", "DTMF4 String",
1491
                          RadioSettingValueString(0, 7, dtm))
1492
        rs.set_apply_callback(my_dtmf2raw, _dtmf, "dtmf4", "dtmf4_cnt")
1493
        codes.append(rs)
1494

    
1495
        dtm = make_dtmf(_dtmf.dtmf5, _dtmf.dtmf5_cnt)
1496
        rs = RadioSetting("dtmf_tab.dtmf5", "DTMF5 String",
1497
                          RadioSettingValueString(0, 7, dtm))
1498
        rs.set_apply_callback(my_dtmf2raw, _dtmf, "dtmf5", "dtmf5_cnt")
1499
        codes.append(rs)
1500

    
1501
        dtm = make_dtmf(_dtmf.dtmf6, _dtmf.dtmf6_cnt)
1502
        rs = RadioSetting("dtmf_tab.dtmf6", "DTMF6 String",
1503
                          RadioSettingValueString(0, 7, dtm))
1504
        rs.set_apply_callback(my_dtmf2raw, _dtmf, "dtmf6", "dtmf6_cnt")
1505
        codes.append(rs)
1506

    
1507
        dtm = make_dtmf(_dtmf.dtmf7, _dtmf.dtmf7_cnt)
1508
        rs = RadioSetting("dtmf_tab.dtmf7", "DTMF7 String",
1509
                          RadioSettingValueString(0, 7, dtm))
1510
        rs.set_apply_callback(my_dtmf2raw, _dtmf, "dtmf7", "dtmf7_cnt")
1511
        codes.append(rs)
1512

    
1513
        dtm = make_dtmf(_dtmf.dtmf8, _dtmf.dtmf8_cnt)
1514
        rs = RadioSetting("dtmf_tab.dtmf8", "DTMF8 String",
1515
                          RadioSettingValueString(0, 7, dtm))
1516
        rs.set_apply_callback(my_dtmf2raw, _dtmf, "dtmf8", "dtmf8_cnt")
1517
        codes.append(rs)
1518

    
1519
        return group       # END get_settings()
1520

    
1521
    def set_settings(self, settings):
1522
        _settings = self._memobj.settings
1523
        _mem = self._memobj
1524
        for element in settings:
1525
            if not isinstance(element, RadioSetting):
1526
                self.set_settings(element)
1527
                continue
1528
            else:
1529
                try:
1530
                    name = element.get_name()
1531
                    if "." in name:
1532
                        bits = name.split(".")
1533
                        obj = self._memobj
1534
                        for bit in bits[:-1]:
1535
                            if "/" in bit:
1536
                                bit, index = bit.split("/", 1)
1537
                                index = int(index)
1538
                                obj = getattr(obj, bit)[index]
1539
                            else:
1540
                                obj = getattr(obj, bit)
1541
                        setting = bits[-1]
1542
                    else:
1543
                        obj = _settings
1544
                        setting = element.get_name()
1545

    
1546
                    if element.has_apply_callback():
1547
                        LOG.debug("Using apply callback")
1548
                        element.run_apply_callback()
1549
                    elif element.value.get_mutable():
1550
                        LOG.debug("Setting %s = %s" % (setting, element.value))
1551
                        setattr(obj, setting, element.value)
1552
                except Exception as e:
1553
                    LOG.debug(element.get_name())
1554
                    raise
1555

    
1556
    @classmethod
1557
    def match_model(cls, filedata, filename):
1558
        match_size = False
1559
        match_model = False
1560

    
1561
        # Testing the file data size
1562
        if len(filedata) == MEM_SIZE + 8:
1563
            match_size = True
1564

    
1565
        # Testing the firmware model fingerprint
1566
        match_model = model_match(cls, filedata)
1567

    
1568
        if match_size and match_model:
1569
            return True
1570
        else:
1571
            return False
1572

    
1573

    
1574
class LT725UVUpper(LT725UV):
1575
    VARIANT = "Upper"
1576
    _vfo = "upper"
1577

    
1578

    
1579
class LT725UVLower(LT725UV):
1580
    VARIANT = "Lower"
1581
    _vfo = "lower"
1582

    
1583

    
1584
class Zastone(chirp_common.Alias):
1585
    """Declare BJ-218 alias for Zastone BJ-218."""
1586
    VENDOR = "Zastone"
1587
    MODEL = "BJ-218"
1588

    
1589

    
1590
class Hesenate(chirp_common.Alias):
1591
    """Declare BJ-218 alias for Hesenate BJ-218."""
1592
    VENDOR = "Hesenate"
1593
    MODEL = "BJ-218"
1594

    
1595

    
1596
class Baojie218Upper(LT725UVUpper):
1597
    VENDOR = "Baojie"
1598
    MODEL = "BJ-218"
1599

    
1600

    
1601
class Baojie218Lower(LT725UVLower):
1602
    VENDOR = "Baojie"
1603
    MODEL = "BJ-218"
1604

    
1605

    
1606
@directory.register
1607
class Baojie218(LT725UV):
1608
    """Baojie BJ-218"""
1609
    VENDOR = "Baojie"
1610
    MODEL = "BJ-218"
1611
    ALIASES = [Zastone, Hesenate, ]
1612

    
1613
    def get_sub_devices(self):
1614
        return [Baojie218Upper(self._mmap), Baojie218Lower(self._mmap)]
1615

    
1616

    
1617
class Baojie318Upper(LT725UVUpper):
1618
    VENDOR = "Baojie"
1619
    MODEL = "BJ-318"
1620

    
1621

    
1622
class Baojie318Lower(LT725UVLower):
1623
    VENDOR = "Baojie"
1624
    MODEL = "BJ-318"
1625

    
1626

    
1627
@directory.register
1628
class Baojie318(LT725UV):
1629
    """Baojie BJ-318"""
1630
    VENDOR = "Baojie"
1631
    MODEL = "BJ-318"
1632

    
1633
    def get_sub_devices(self):
1634
        return [Baojie318Upper(self._mmap), Baojie318Lower(self._mmap)]
(7-7/7)