Project

General

Profile

Bug #10210 » lt725uv_10210-1.py

Jim Unroe, 01/01/2023 05:44 PM

 
1
# Copyright 2016-2021:
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
#
8
# This program is free software: you can redistribute it and/or modify
9
# it under the terms of the GNU General Public License as published by
10
# the Free Software Foundation, either version 2 of the License, or
11
# (at your option) any later version.
12
#
13
# This program is distributed in the hope that it will be useful,
14
# but WITHOUT ANY WARRANTY; without even the implied warranty of
15
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
# GNU General Public License for more details.
17
#
18
# You should have received a copy of the GNU General Public License
19
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
20

    
21
import time
22
import struct
23
import logging
24
import re
25

    
26
from chirp import chirp_common, directory, memmap
27
from chirp import bitwise, errors, util
28
from chirp.settings import RadioSettingGroup, RadioSetting, \
29
    RadioSettingValueBoolean, RadioSettingValueList, \
30
    RadioSettingValueString, RadioSettingValueInteger, \
31
    RadioSettingValueFloat, RadioSettings, InvalidValueError
32
from textwrap import dedent
33

    
34
LOG = logging.getLogger(__name__)
35

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

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

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

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

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

    
148
#seekto 0x0300;
149
struct {
150
  struct vfo vfoa;
151
} upper;
152

    
153
#seekto 0x0380;
154
struct {
155
  struct vfo vfob;
156
} lower;
157

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

    
183
#seekto 0x0400;
184
struct mem upper_memory[128];
185

    
186
#seekto 0x1000;
187
struct mem lower_memory[128];
188

    
189
#seekto 0x1C00;
190
struct {
191
  char  mod_num[6];
192
} mod_id;
193
"""
194

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

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

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

    
257

    
258
def _clean_buffer(radio):
259
    radio.pipe.timeout = 0.005
260
    junk = radio.pipe.read(256)
261
    radio.pipe.timeout = STIMEOUT
262
    if junk:
263
        Log.debug("Got %i bytes of junk before starting" % len(junk))
264

    
265

    
266
def _rawrecv(radio, amount):
267
    """Raw read from the radio device"""
268
    data = b""
269
    try:
270
        data = radio.pipe.read(amount)
271
    except:
272
        _exit_program_mode(radio)
273
        msg = "Generic error reading data from radio; check your cable."
274
        raise errors.RadioError(msg)
275

    
276
    if len(data) != amount:
277
        _exit_program_mode(radio)
278
        msg = "Error reading from radio: not the amount of data we want."
279
        raise errors.RadioError(msg)
280

    
281
    return data
282

    
283

    
284
def _rawsend(radio, data):
285
    """Raw send to the radio device"""
286
    try:
287
        radio.pipe.write(data)
288
    except:
289
        raise errors.RadioError("Error sending data to radio")
290

    
291

    
292
def _make_frame(cmd, addr, length, data=""):
293
    """Pack the info in the headder format"""
294
    frame = struct.pack(">4sHH", cmd, addr, length)
295
    # Add the data if set
296
    if len(data) != 0:
297
        frame += data
298
    # Return the data
299
    return frame
300

    
301

    
302
def _recv(radio, addr, length):
303
    """Get data from the radio """
304

    
305
    data = _rawrecv(radio, length)
306

    
307
    # DEBUG
308
    LOG.info("Response:")
309
    LOG.debug(util.hexprint(data))
310

    
311
    return data
312

    
313

    
314
def _do_ident(radio):
315
    """Put the radio in PROGRAM mode & identify it"""
316
    # Set the serial discipline
317
    radio.pipe.baudrate = 19200
318
    radio.pipe.parity = "N"
319
    radio.pipe.timeout = STIMEOUT
320

    
321
    # Flush input buffer
322
    _clean_buffer(radio)
323

    
324
    magic = b"PROM_LIN"
325

    
326
    _rawsend(radio, magic)
327

    
328
    ack = _rawrecv(radio, 1)
329
    if ack != b"\x06":
330
        _exit_program_mode(radio)
331
        if ack:
332
            LOG.debug(repr(ack))
333
        raise errors.RadioError("Radio did not respond")
334

    
335
    return True
336

    
337

    
338
def _exit_program_mode(radio):
339
    endframe = b"EXIT"
340
    _rawsend(radio, endframe)
341

    
342

    
343
def _download(radio):
344
    """Get the memory map"""
345

    
346
    # Put radio in program mode and identify it
347
    _do_ident(radio)
348

    
349
    # UI progress
350
    status = chirp_common.Status()
351
    status.cur = 0
352
    status.max = MEM_SIZE // BLOCK_SIZE
353
    status.msg = "Cloning from radio..."
354
    radio.status_fn(status)
355

    
356
    data = b""
357
    for addr in range(0, MEM_SIZE, BLOCK_SIZE):
358
        frame = _make_frame(b"READ", addr, BLOCK_SIZE)
359
        # DEBUG
360
        LOG.info("Request sent:")
361
        LOG.debug(util.hexprint(frame))
362

    
363
        # Sending the read request
364
        _rawsend(radio, frame)
365

    
366
        # Now we read
367
        d = _recv(radio, addr, BLOCK_SIZE)
368

    
369
        # Aggregate the data
370
        data += d
371

    
372
        # UI Update
373
        status.cur = addr // BLOCK_SIZE
374
        status.msg = "Cloning from radio..."
375
        radio.status_fn(status)
376

    
377
    _exit_program_mode(radio)
378

    
379
    return data
380

    
381

    
382
def _upload(radio):
383
    """Upload procedure"""
384

    
385
    # Put radio in program mode and identify it
386
    _do_ident(radio)
387

    
388
    # UI progress
389
    status = chirp_common.Status()
390
    status.cur = 0
391
    status.max = MEM_SIZE // BLOCK_SIZE
392
    status.msg = "Cloning to radio..."
393
    radio.status_fn(status)
394

    
395
    # The fun starts here
396
    for addr in range(0, MEM_SIZE, BLOCK_SIZE):
397
        # Sending the data
398
        data = radio.get_mmap()[addr:addr + BLOCK_SIZE]
399

    
400
        frame = _make_frame(b"WRIE", addr, BLOCK_SIZE, data)
401

    
402
        _rawsend(radio, frame)
403

    
404
        # Receiving the response
405
        ack = _rawrecv(radio, 1)
406
        if ack != b"\x06":
407
            _exit_program_mode(radio)
408
            msg = "Bad ack writing block 0x%04x" % addr
409
            raise errors.RadioError(msg)
410

    
411
        # UI Update
412
        status.cur = addr // BLOCK_SIZE
413
        status.msg = "Cloning to radio..."
414
        radio.status_fn(status)
415

    
416
    _exit_program_mode(radio)
417

    
418

    
419
def model_match(cls, data):
420
    """Match the opened/downloaded image to the correct version"""
421
    if len(data) == 0x1C08:
422
        rid = data[0x1C00:0x1C08]
423
        return rid.startswith(cls.MODEL.encode())
424
    else:
425
        return False
426

    
427

    
428
def _split(rf, f1, f2):
429
    """Returns False if the two freqs are in the same band (no split)
430
    or True otherwise"""
431

    
432
    # Determine if the two freqs are in the same band
433
    for low, high in rf.valid_bands:
434
        if f1 >= low and f1 <= high and \
435
                f2 >= low and f2 <= high:
436
            # If the two freqs are on the same Band this is not a split
437
            return False
438

    
439
    # If you get here is because the freq pairs are split
440
    return True
441

    
442

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

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

    
466
    VALID_BANDS = [(136000000, 176000000),
467
                   (400000000, 480000000)]
468

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

    
473
    @classmethod
474
    def get_prompts(cls):
475
        rp = chirp_common.RadioPrompts()
476
        if cls.MODEL == "BJ-318":
477
            msg = \
478
                ('              BJ-318 EXPERIMENTAL\n'
479
                 '            PLEASE REPORT ANY ISSUES \n'
480
                 '\n'
481
                 'The individual channel power settings are ignored by \n'
482
                 'the radio. They are allowed to be set in hopes of a \n'
483
                 'future firmware change. Changing a stored channel power \n'
484
                 'setting must be done manually using the front panel menu \n'
485
                 'control (or the microphone control controls while in VFO \n'
486
                 'mode) to change the VFO power. The CHIRP driver will \n'
487
                 'allow setting the individual VFO power to High (25W), \n'
488
                 'Med (10W) or Low (5W) and setting the unused individual \n'
489
                 'channel power setting to High (25W) or Low (5W)'
490
                 )
491

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

    
508
        rp.info = msg
509

    
510
        rp.pre_download = _(dedent("""\
511
            Follow this instructions to download your info:
512

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

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

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

    
564
    def get_sub_devices(self):
565
        return [LT725UVUpper(self._mmap), LT725UVLower(self._mmap)]
566

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

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

    
594
    def process_mmap(self):
595
        """Process the mem map into the mem object"""
596
        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
597

    
598
    def get_raw_memory(self, number):
599
        return repr(self._memobj.memory[number - 1])
600

    
601
    def _memory_obj(self, suffix=""):
602
        return getattr(self._memobj, "%s_memory%s" % (self._vfo, suffix))
603

    
604
    def _get_dcs(self, val):
605
        return int(str(val)[2:-18])
606

    
607
    def _set_dcs(self, val):
608
        return int(str(val), 16)
609

    
610
    def get_memory(self, number):
611
        _mem = self._memory_obj()[number - 1]
612

    
613
        mem = chirp_common.Memory()
614
        mem.number = number
615

    
616
        if _mem.get_raw()[0] == "\xff":
617
            mem.empty = True
618
            return mem
619

    
620
        mem.freq = int(_mem.rxfreq) * 10
621

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

    
636
        for char in _mem.name[0:_mem.namelen]:
637
            mem.name += chr(char)
638

    
639
        dtcs_pol = ["N", "N"]
640

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

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

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

    
677
        mem.dtcs_polarity = "".join(dtcs_pol)
678

    
679
        mem.mode = _mem.wide and "FM" or "NFM"
680

    
681
        mem.power = self.POWER_LEVELS[_mem.power]
682

    
683
        # Extra
684
        mem.extra = RadioSettingGroup("extra", "Extra")
685

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

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

    
704
        if _mem.eotsignal == 0xFF:
705
            val = 0x00
706
        else:
707
            val = _mem.eotsignal
708

    
709
        rx = RadioSettingValueList(LIST_SIGNAL, LIST_SIGNAL[val])
710
        eotsignal = RadioSetting("eotsignal", "Transmit end signaling", rx)
711
        mem.extra.append(eotsignal)
712

    
713
        rx = RadioSettingValueBoolean(bool(_mem.compandor))
714
        compandor = RadioSetting("compandor", "Compandor", rx)
715
        mem.extra.append(compandor)
716

    
717
        rx = RadioSettingValueBoolean(bool(_mem.scrambler))
718
        scrambler = RadioSetting("scrambler", "Scrambler", rx)
719
        mem.extra.append(scrambler)
720

    
721
        return mem
722

    
723
    def set_memory(self, mem):
724
        _mem = self._memory_obj()[mem.number - 1]
725

    
726
        if mem.empty:
727
            _mem.set_raw("\xff" * 24)
728
            _mem.namelen = 0
729
            return
730

    
731
        _mem.set_raw("\xFF" * 15 + "\x00\x00" + "\xFF" * 7)
732

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

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

    
753
        rxmode = ""
754
        txmode = ""
755

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

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

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

    
801
        _mem.wide = self.MODES.index(mem.mode)
802
        _mem.power = mem.power == self.POWER_LEVELS[1]
803

    
804
        # Extra settings
805
        for setting in mem.extra:
806
            setattr(_mem, setting.get_name(), setting.value)
807

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
979
        ledsw = RadioSetting("settings.ledsw", "Back light mode",
980
                             RadioSettingValueList(LIST_LEDSW, LIST_LEDSW[
981
                                 _sets.ledsw]))
982
        basic.append(ledsw)
983

    
984
        beep = RadioSetting("settings.beep", "Beep",
985
                            RadioSettingValueBoolean(bool(_sets.beep)))
986
        basic.append(beep)
987

    
988
        ring = RadioSetting("settings.ring", "Ring",
989
                            RadioSettingValueList(LIST_RING, LIST_RING[
990
                                _sets.ring]))
991
        basic.append(ring)
992

    
993
        bcl = RadioSetting("settings.bcl", "Busy channel lockout",
994
                           RadioSettingValueBoolean(bool(_sets.bcl)))
995
        basic.append(bcl)
996

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

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

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

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

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

    
1034
        rs = RadioSetting("settings.rptr_mode", "Repeater Mode",
1035
                          RadioSettingValueBoolean(bool(_sets.rptr_mode)))
1036
        basic.append(rs)
1037

    
1038
        # UPPER BAND SETTINGS
1039

    
1040
        # Freq Mode, convert bit 1 state to index pointer
1041
        val = _vfoa.frq_chn_mode // 2
1042

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

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

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

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

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

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

    
1081
        rs = RadioSetting("upper.vfoa.launch_sig", "Launch Signaling",
1082
                          RadioSettingValueList(LIST_SIGNAL,
1083
                                                LIST_SIGNAL[_vfoa.launch_sig]))
1084
        a_band.append(rs)
1085

    
1086
        rx = RadioSettingValueList(LIST_SIGNAL, LIST_SIGNAL[_vfoa.tx_end_sig])
1087
        rs = RadioSetting("upper.vfoa.tx_end_sig", "Xmit End Signaling", rx)
1088
        a_band.append(rs)
1089

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

    
1094
        rx = RadioSettingValueBoolean(bool(_vfoa.cmp_nder))
1095
        rs = RadioSetting("upper.vfoa.cmp_nder", "Compandor", rx)
1096
        a_band.append(rs)
1097

    
1098
        rs = RadioSetting("upper.vfoa.scrm_blr", "Scrambler",
1099
                          RadioSettingValueBoolean(bool(_vfoa.scrm_blr)))
1100
        a_band.append(rs)
1101

    
1102
        rx = RadioSettingValueList(LIST_SHIFT, LIST_SHIFT[_vfoa.shift])
1103
        rs = RadioSetting("upper.vfoa.shift", "Xmit Shift", rx)
1104
        a_band.append(rs)
1105

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

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

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

    
1129
        # BJ-318 upper band volume
1130
        if self.MODEL == "BJ-318":
1131
            bvolume_u = RadioSetting("upper.vfoa.bvol",
1132
                                     "Volume", RadioSettingValueInteger(
1133
                                         0, 10, _vfoa.bvol))
1134
            a_band.append(bvolume_u)
1135

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

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

    
1151
        # LOWER BAND SETTINGS
1152

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

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

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

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

    
1180
        rx = RadioSettingValueList(LIST_RECVMODE, LIST_RECVMODE[_vfob.rx_mode])
1181
        rs = RadioSetting("lower.vfob.rx_mode", "Default Recv Mode", rx)
1182
        b_band.append(rs)
1183

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

    
1191
        rx = RadioSettingValueList(LIST_SIGNAL, LIST_SIGNAL[_vfob.launch_sig])
1192
        rs = RadioSetting("lower.vfob.launch_sig", "Launch Signaling", rx)
1193
        b_band.append(rs)
1194

    
1195
        rx = RadioSettingValueList(LIST_SIGNAL, LIST_SIGNAL[_vfob.tx_end_sig])
1196
        rs = RadioSetting("lower.vfob.tx_end_sig", "Xmit End Signaling", rx)
1197
        b_band.append(rs)
1198

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

    
1203
        rs = RadioSetting("lower.vfob.cmp_nder", "Compandor",
1204
                          RadioSettingValueBoolean(bool(_vfob.cmp_nder)))
1205
        b_band.append(rs)
1206

    
1207
        rs = RadioSetting("lower.vfob.scrm_blr", "Scrambler",
1208
                          RadioSettingValueBoolean(bool(_vfob.scrm_blr)))
1209
        b_band.append(rs)
1210

    
1211
        rx = RadioSettingValueList(LIST_SHIFT, LIST_SHIFT[_vfob.shift])
1212
        rs = RadioSetting("lower.vfob.shift", "Xmit Shift", rx)
1213
        b_band.append(rs)
1214

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

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

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

    
1237
        # BJ-318 lower band volume
1238
        if self.MODEL == "BJ-318":
1239
            bvolume_l = RadioSetting("lower.vfob.bvol",
1240
                                     "Volume", RadioSettingValueInteger(
1241
                                         0, 10, _vfob.bvol))
1242
            b_band.append(bvolume_l)
1243

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

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

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

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

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

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

    
1301
        # VALID_BANDS = [(136000000, 176000000),400000000, 480000000)]
1302

    
1303
        lval = _lims.vhf_low / 100000.0
1304
        uval = _lims.vhf_high / 100000.0
1305
        if lval >= uval:
1306
            lval = 144.0
1307
            uval = 158.0
1308

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

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

    
1319
        lval = _lims.uhf_low / 100000.0
1320
        uval = _lims.uhf_high / 100000.0
1321
        if lval >= uval:
1322
            lval = 420.0
1323
            uval = 470.0
1324

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

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

    
1335
        # Codes and DTMF Groups Settings
1336

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

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

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

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

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

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

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

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

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

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

    
1460
        val = _codes.state_now
1461
        if val > 2:
1462
            val = 0
1463

    
1464
        rx = RadioSettingValueList(LIST_STATE, LIST_STATE[val])
1465
        rs = RadioSetting("codes.state_now", "Current State", rx)
1466
        codes.append(rs)
1467

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

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

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

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

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

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

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

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

    
1516
        return group       # END get_settings()
1517

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

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

    
1553
    @classmethod
1554
    def match_model(cls, filedata, filename):
1555
        match_size = False
1556
        match_model = False
1557

    
1558
        # Testing the file data size
1559
        if len(filedata) == MEM_SIZE + 8:
1560
            match_size = True
1561

    
1562
        # Testing the firmware model fingerprint
1563
        match_model = model_match(cls, filedata)
1564

    
1565
        if match_size and match_model:
1566
            return True
1567
        else:
1568
            return False
1569

    
1570

    
1571
class LT725UVUpper(LT725UV):
1572
    VARIANT = "Upper"
1573
    _vfo = "upper"
1574

    
1575

    
1576
class LT725UVLower(LT725UV):
1577
    VARIANT = "Lower"
1578
    _vfo = "lower"
1579

    
1580

    
1581
class Zastone(chirp_common.Alias):
1582
    """Declare BJ-218 alias for Zastone BJ-218."""
1583
    VENDOR = "Zastone"
1584
    MODEL = "BJ-218"
1585

    
1586

    
1587
class Hesenate(chirp_common.Alias):
1588
    """Declare BJ-218 alias for Hesenate BJ-218."""
1589
    VENDOR = "Hesenate"
1590
    MODEL = "BJ-218"
1591

    
1592

    
1593
class Baojie218Upper(LT725UVUpper):
1594
    VENDOR = "Baojie"
1595
    MODEL = "BJ-218"
1596

    
1597

    
1598
class Baojie218Lower(LT725UVLower):
1599
    VENDOR = "Baojie"
1600
    MODEL = "BJ-218"
1601

    
1602

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

    
1610
    def get_sub_devices(self):
1611
        return [Baojie218Upper(self._mmap), Baojie218Lower(self._mmap)]
1612

    
1613

    
1614
class Baojie318Upper(LT725UVUpper):
1615
    VENDOR = "Baojie"
1616
    MODEL = "BJ-318"
1617

    
1618

    
1619
class Baojie318Lower(LT725UVLower):
1620
    VENDOR = "Baojie"
1621
    MODEL = "BJ-318"
1622

    
1623

    
1624
@directory.register
1625
class Baojie318(LT725UV):
1626
    """Baojie BJ-318"""
1627
    VENDOR = "Baojie"
1628
    MODEL = "BJ-318"
1629

    
1630
    def get_sub_devices(self):
1631
        return [Baojie318Upper(self._mmap), Baojie318Lower(self._mmap)]
(5-5/7)