Project

General

Profile

Bug #10396 » lt725uv_400-520.py

Jim Unroe, 02/28/2023 01:23 AM

 
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
120
                     // hex values for color
121
                     // Note   sky_blue and black look the same on the
122
                     // BJ-318 screen
123
  u16 fm_318;        // FM stored Frequency in BJ-318
124
} hello_lims;
125

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

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

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

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

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

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

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

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

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

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

    
264

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

    
272

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

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

    
288
    return data
289

    
290

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

    
298

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

    
308

    
309
def _recv(radio, addr, length):
310
    """Get data from the radio """
311

    
312
    data = _rawrecv(radio, length)
313

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

    
318
    return data
319

    
320

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

    
328
    # Flush input buffer
329
    _clean_buffer(radio)
330

    
331
    magic = b"PROM_LIN"
332

    
333
    _rawsend(radio, magic)
334

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

    
342
    return True
343

    
344

    
345
def _exit_program_mode(radio):
346
    endframe = b"EXIT"
347
    _rawsend(radio, endframe)
348

    
349

    
350
def _download(radio):
351
    """Get the memory map"""
352

    
353
    # Put radio in program mode and identify it
354
    _do_ident(radio)
355

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

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

    
370
        # Sending the read request
371
        _rawsend(radio, frame)
372

    
373
        # Now we read
374
        d = _recv(radio, addr, BLOCK_SIZE)
375

    
376
        # Aggregate the data
377
        data += d
378

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

    
384
    _exit_program_mode(radio)
385

    
386
    return data
387

    
388

    
389
def _upload(radio):
390
    """Upload procedure"""
391

    
392
    # Put radio in program mode and identify it
393
    _do_ident(radio)
394

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

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

    
407
        frame = _make_frame(b"WRIE", addr, BLOCK_SIZE, data)
408

    
409
        _rawsend(radio, frame)
410

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

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

    
423
    _exit_program_mode(radio)
424

    
425

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

    
434

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

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

    
446
    # If you get here is because the freq pairs are split
447
    return True
448

    
449

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

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

    
473
    VALID_BANDS = [(136000000, 176000000),
474
                   (400000000, 520000000)]
475

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

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

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

    
520
        rp.info = msg
521

    
522
        rp.pre_download = _(dedent("""\
523
            Follow this instructions to download your info:
524

    
525
            1 - Turn off your radio
526
            2 - Connect your interface cable
527
            3 - Turn on your radio
528
            4 - Do the download of your radio data
529
            """))
530
        rp.pre_upload = _(dedent("""\
531
            Follow this instructions to upload your info:
532

    
533
            1 - Turn off your radio
534
            2 - Connect your interface cable
535
            3 - Turn on your radio
536
            4 - Do the upload of your radio data
537
            """))
538
        return rp
539

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

    
576
    def get_sub_devices(self):
577
        return [LT725UVUpper(self._mmap), LT725UVLower(self._mmap)]
578

    
579
    def sync_in(self):
580
        """Download from radio"""
581
        try:
582
            data = _download(self)
583
        except errors.RadioError:
584
            # Pass through any real errors we raise
585
            raise
586
        except:
587
            # If anything unexpected happens, make sure we raise
588
            # a RadioError and log the problem
589
            LOG.exception('Unexpected error during download')
590
            raise errors.RadioError('Unexpected error communicating '
591
                                    'with the radio')
592
        self._mmap = memmap.MemoryMapBytes(data)
593
        self.process_mmap()
594

    
595
    def sync_out(self):
596
        """Upload to radio"""
597
        try:
598
            _upload(self)
599
        except:
600
            # If anything unexpected happens, make sure we raise
601
            # a RadioError and log the problem
602
            LOG.exception('Unexpected error during upload')
603
            raise errors.RadioError('Unexpected error communicating '
604
                                    'with the radio')
605

    
606
    def process_mmap(self):
607
        """Process the mem map into the mem object"""
608
        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
609

    
610
    def get_raw_memory(self, number):
611
        return repr(self._memobj.memory[number - 1])
612

    
613
    def _memory_obj(self, suffix=""):
614
        return getattr(self._memobj, "%s_memory%s" % (self._vfo, suffix))
615

    
616
    def _get_dcs(self, val):
617
        return int(str(val)[2:-18])
618

    
619
    def _set_dcs(self, val):
620
        return int(str(val), 16)
621

    
622
    def get_memory(self, number):
623
        _mem = self._memory_obj()[number - 1]
624

    
625
        mem = chirp_common.Memory()
626
        mem.number = number
627

    
628
        if _mem.get_raw()[0] == "\xff":
629
            mem.empty = True
630
            return mem
631

    
632
        mem.freq = int(_mem.rxfreq) * 10
633

    
634
        if _mem.txfreq == 0xFFFFFFFF:
635
            # TX freq not set
636
            mem.duplex = "off"
637
            mem.offset = 0
638
        elif int(_mem.rxfreq) == int(_mem.txfreq):
639
            mem.duplex = ""
640
            mem.offset = 0
641
        elif _split(self.get_features(), mem.freq, int(_mem.txfreq) * 10):
642
            mem.duplex = "split"
643
            mem.offset = int(_mem.txfreq) * 10
644
        else:
645
            mem.duplex = int(_mem.rxfreq) > int(_mem.txfreq) and "-" or "+"
646
            mem.offset = abs(int(_mem.rxfreq) - int(_mem.txfreq)) * 10
647

    
648
        for char in _mem.name[0:_mem.namelen]:
649
            mem.name += chr(char)
650

    
651
        dtcs_pol = ["N", "N"]
652

    
653
        if _mem.rxtone == 0x3FFF:
654
            rxmode = ""
655
        elif _mem.is_rxdigtone == 0:
656
            # CTCSS
657
            rxmode = "Tone"
658
            mem.ctone = int(_mem.rxtone) / 10.0
659
        else:
660
            # Digital
661
            rxmode = "DTCS"
662
            mem.rx_dtcs = self._get_dcs(_mem.rxtone)
663
            if _mem.rxdtcs_pol == 1:
664
                dtcs_pol[1] = "R"
665

    
666
        if _mem.txtone == 0x3FFF:
667
            txmode = ""
668
        elif _mem.is_txdigtone == 0:
669
            # CTCSS
670
            txmode = "Tone"
671
            mem.rtone = int(_mem.txtone) / 10.0
672
        else:
673
            # Digital
674
            txmode = "DTCS"
675
            mem.dtcs = self._get_dcs(_mem.txtone)
676
            if _mem.txdtcs_pol == 1:
677
                dtcs_pol[0] = "R"
678

    
679
        if txmode == "Tone" and not rxmode:
680
            mem.tmode = "Tone"
681
        elif txmode == rxmode and txmode == "Tone" and mem.rtone == mem.ctone:
682
            mem.tmode = "TSQL"
683
        elif txmode == rxmode and txmode == "DTCS" and mem.dtcs == mem.rx_dtcs:
684
            mem.tmode = "DTCS"
685
        elif rxmode or txmode:
686
            mem.tmode = "Cross"
687
            mem.cross_mode = "%s->%s" % (txmode, rxmode)
688

    
689
        mem.dtcs_polarity = "".join(dtcs_pol)
690

    
691
        mem.mode = _mem.wide and "FM" or "NFM"
692

    
693
        mem.power = self.POWER_LEVELS[_mem.power]
694

    
695
        # Extra
696
        mem.extra = RadioSettingGroup("extra", "Extra")
697

    
698
        if _mem.recvmode == 0xFF:
699
            val = 0x00
700
        else:
701
            val = _mem.recvmode
702
        recvmode = RadioSetting("recvmode", "Receiving mode",
703
                                RadioSettingValueList(LIST_RECVMODE,
704
                                                      LIST_RECVMODE[val]))
705
        mem.extra.append(recvmode)
706

    
707
        if _mem.botsignal == 0xFF:
708
            val = 0x00
709
        else:
710
            val = _mem.botsignal
711
        botsignal = RadioSetting("botsignal", "Launch signaling",
712
                                 RadioSettingValueList(LIST_SIGNAL,
713
                                                       LIST_SIGNAL[val]))
714
        mem.extra.append(botsignal)
715

    
716
        if _mem.eotsignal == 0xFF:
717
            val = 0x00
718
        else:
719
            val = _mem.eotsignal
720

    
721
        rx = RadioSettingValueList(LIST_SIGNAL, LIST_SIGNAL[val])
722
        eotsignal = RadioSetting("eotsignal", "Transmit end signaling", rx)
723
        mem.extra.append(eotsignal)
724

    
725
        rx = RadioSettingValueBoolean(bool(_mem.compandor))
726
        compandor = RadioSetting("compandor", "Compandor", rx)
727
        mem.extra.append(compandor)
728

    
729
        rx = RadioSettingValueBoolean(bool(_mem.scrambler))
730
        scrambler = RadioSetting("scrambler", "Scrambler", rx)
731
        mem.extra.append(scrambler)
732

    
733
        return mem
734

    
735
    def set_memory(self, mem):
736
        _mem = self._memory_obj()[mem.number - 1]
737

    
738
        if mem.empty:
739
            _mem.set_raw("\xff" * 24)
740
            _mem.namelen = 0
741
            return
742

    
743
        _mem.set_raw("\xFF" * 15 + "\x00\x00" + "\xFF" * 7)
744

    
745
        _mem.rxfreq = mem.freq / 10
746
        if mem.duplex == "off":
747
            _mem.txfreq = 0xFFFFFFFF
748
        elif mem.duplex == "split":
749
            _mem.txfreq = mem.offset / 10
750
        elif mem.duplex == "+":
751
            _mem.txfreq = (mem.freq + mem.offset) / 10
752
        elif mem.duplex == "-":
753
            _mem.txfreq = (mem.freq - mem.offset) / 10
754
        else:
755
            _mem.txfreq = mem.freq / 10
756

    
757
        _mem.namelen = len(mem.name.rstrip())
758
        _namelength = self.get_features().valid_name_length
759
        for i in range(_namelength):
760
            try:
761
                _mem.name[i] = ord(mem.name[i])
762
            except IndexError:
763
                _mem.name[i] = 0xFF
764

    
765
        rxmode = ""
766
        txmode = ""
767

    
768
        if mem.tmode == "Tone":
769
            txmode = "Tone"
770
        elif mem.tmode == "TSQL":
771
            rxmode = "Tone"
772
            txmode = "TSQL"
773
        elif mem.tmode == "DTCS":
774
            rxmode = "DTCSSQL"
775
            txmode = "DTCS"
776
        elif mem.tmode == "Cross":
777
            txmode, rxmode = mem.cross_mode.split("->", 1)
778

    
779
        if rxmode == "":
780
            _mem.rxdtcs_pol = 1
781
            _mem.is_rxdigtone = 1
782
            _mem.rxtone = 0x3FFF
783
        elif rxmode == "Tone":
784
            _mem.rxdtcs_pol = 0
785
            _mem.is_rxdigtone = 0
786
            _mem.rxtone = int(mem.ctone * 10)
787
        elif rxmode == "DTCSSQL":
788
            _mem.rxdtcs_pol = 1 if mem.dtcs_polarity[1] == "R" else 0
789
            _mem.is_rxdigtone = 1
790
            _mem.rxtone = self._set_dcs(mem.dtcs)
791
        elif rxmode == "DTCS":
792
            _mem.rxdtcs_pol = 1 if mem.dtcs_polarity[1] == "R" else 0
793
            _mem.is_rxdigtone = 1
794
            _mem.rxtone = self._set_dcs(mem.rx_dtcs)
795

    
796
        if txmode == "":
797
            _mem.txdtcs_pol = 1
798
            _mem.is_txdigtone = 1
799
            _mem.txtone = 0x3FFF
800
        elif txmode == "Tone":
801
            _mem.txdtcs_pol = 0
802
            _mem.is_txdigtone = 0
803
            _mem.txtone = int(mem.rtone * 10)
804
        elif txmode == "TSQL":
805
            _mem.txdtcs_pol = 0
806
            _mem.is_txdigtone = 0
807
            _mem.txtone = int(mem.ctone * 10)
808
        elif txmode == "DTCS":
809
            _mem.txdtcs_pol = 1 if mem.dtcs_polarity[0] == "R" else 0
810
            _mem.is_txdigtone = 1
811
            _mem.txtone = self._set_dcs(mem.dtcs)
812

    
813
        _mem.wide = self.MODES.index(mem.mode)
814
        _mem.power = mem.power == self.POWER_LEVELS[1]
815

    
816
        # Extra settings
817
        for setting in mem.extra:
818
            setattr(_mem, setting.get_name(), setting.value)
819

    
820
    def get_settings(self):
821
        """Translate the bit in the mem_struct into settings in the UI"""
822
        # Define mem struct write-back shortcuts
823
        _sets = self._memobj.settings
824
        _vfoa = self._memobj.upper.vfoa
825
        _vfob = self._memobj.lower.vfob
826
        _lims = self._memobj.hello_lims
827
        _codes = self._memobj.codes
828
        _dtmf = self._memobj.dtmf_tab
829

    
830
        basic = RadioSettingGroup("basic", "Basic Settings")
831
        a_band = RadioSettingGroup("a_band", "VFO A-Upper Settings")
832
        b_band = RadioSettingGroup("b_band", "VFO B-Lower Settings")
833
        codes = RadioSettingGroup("codes", "Codes & DTMF Groups")
834
        lims = RadioSettingGroup("lims", "PowerOn & Freq Limits")
835
        group = RadioSettings(basic, a_band, b_band, lims, codes)
836

    
837
        # Basic Settings
838
        bnd_mode = RadioSetting("settings.init_bank", "TDR Band Default",
839
                                RadioSettingValueList(LIST_TDR_DEF,
840
                                                      LIST_TDR_DEF[
841
                                                          _sets.init_bank]))
842
        basic.append(bnd_mode)
843

    
844
        # BJ-318 set by vfo, skip
845
        if self.MODEL != "BJ-318":
846
            rs = RadioSettingValueInteger(0, 20, _sets.volume)
847
            volume = RadioSetting("settings.volume", "Volume", rs)
848
            basic.append(volume)
849

    
850
        # BJ-318 set by vfo, skip
851
        if self.MODEL != "BJ-318":
852
            vala = _vfoa.bpower        # 2bits values 0,1,2= Low, Mid, High
853
            rx = RadioSettingValueList(LIST_BPOWER, LIST_BPOWER[vala])
854
            powera = RadioSetting("upper.vfoa.bpower", "Power (Upper)", rx)
855
            basic.append(powera)
856

    
857
        # BJ-318 set by vfo, skip
858
        if self.MODEL != "BJ-318":
859
            valb = _vfob.bpower
860
            rx = RadioSettingValueList(LIST_BPOWER, LIST_BPOWER[valb])
861
            powerb = RadioSetting("lower.vfob.bpower", "Power (Lower)", rx)
862
            basic.append(powerb)
863

    
864
        def my_word2raw(setting, obj, atrb, mlt=10):
865
            """Callback function to convert UI floating value to u16 int"""
866
            if str(setting.value) == "Off":
867
                frq = 0x0FFFF
868
            else:
869
                frq = int(float(str(setting.value)) * float(mlt))
870
            if frq == 0:
871
                frq = 0xFFFF
872
            setattr(obj, atrb, frq)
873
            return
874

    
875
        def my_adjraw(setting, obj, atrb, fix):
876
            """Callback: add or subtract fix from value."""
877
            vx = int(str(setting.value))
878
            value = vx + int(fix)
879
            if value < 0:
880
                value = 0
881
            if atrb == "frq_chn_mode" and int(str(setting.value)) == 2:
882
                value = vx * 2         # Special handling for frq_chn_mode
883
            setattr(obj, atrb, value)
884
            return
885

    
886
        def my_dbl2raw(setting, obj, atrb, flg=1):
887
            """Callback: convert from freq 146.7600 to 14760000 U32."""
888
            value = chirp_common.parse_freq(str(setting.value)) / 10
889
            # flg=1 means 0 becomes ff, else leave as possible 0
890
            if flg == 1 and value == 0:
891
                value = 0xFFFFFFFF
892
            setattr(obj, atrb, value)
893
            return
894

    
895
        def my_val_list(setting, obj, atrb):
896
            """Callback:from ValueList with non-sequential, actual values."""
897
            value = int(str(setting.value))            # Get the integer value
898
            if atrb == "tot":
899
                value = int(value / 30)    # 30 second increments
900
            setattr(obj, atrb, value)
901
            return
902

    
903
        def my_spcl(setting, obj, atrb):
904
            """Callback: Special handling based on atrb."""
905
            if atrb == "frq_chn_mode":
906
                idx = LIST_VFOMODE.index(str(setting.value))  # Returns 0 or 1
907
                value = idx * 2            # Set bit 1
908
            setattr(obj, atrb, value)
909
            return
910

    
911
        def my_tone_strn(obj, is_atr, pol_atr, tone_atr):
912
            """Generate the CTCS/DCS tone code string."""
913
            vx = int(getattr(obj, tone_atr))
914
            if vx == 16383 or vx == 0:
915
                return "Off"                 # 16383 is all bits set
916
            if getattr(obj, is_atr) == 0:             # Simple CTCSS code
917
                tstr = str(vx / 10.0)
918
            else:        # DCS
919
                if getattr(obj, pol_atr) == 0:
920
                    tstr = "D{:03x}R".format(vx)
921
                else:
922
                    tstr = "D{:03x}N".format(vx)
923
            return tstr
924

    
925
        def my_set_tone(setting, obj, is_atr, pol_atr, tone_atr):
926
            """Callback- create the tone setting from string code."""
927
            sx = str(setting.value)        # '131.8'  or 'D231N' or 'Off'
928
            if sx == "Off":
929
                isx = 1
930
                polx = 1
931
                tonx = 0x3FFF
932
            elif sx[0] == "D":         # DCS
933
                isx = 1
934
                if sx[4] == "N":
935
                    polx = 1
936
                else:
937
                    polx = 0
938
                tonx = int(sx[1:4], 16)
939
            else:                                     # CTCSS
940
                isx = 0
941
                polx = 0
942
                tonx = int(float(sx) * 10.0)
943
            setattr(obj, is_atr, isx)
944
            setattr(obj, pol_atr, polx)
945
            setattr(obj, tone_atr, tonx)
946
            return
947

    
948
        # not used by BJ-318 skip
949
        if self.MODEL != "BJ-318":
950
            val = _sets.fm_freq / 10.0
951
            if val == 0:
952
                val = 88.9            # 0 is not valid
953
            rx = RadioSettingValueFloat(65, 108.0, val, 0.1, 1)
954
            rs = RadioSetting("settings.fm_freq",
955
                              "FM Broadcast Freq (MHz)", rx)
956
            rs.set_apply_callback(my_word2raw, _sets, "fm_freq")
957
            basic.append(rs)
958

    
959
        # BJ-318 fm frequency
960
        if self.MODEL == "BJ-318":
961
            val = _lims.fm_318 / 10.0
962
            if val == 0:
963
                val = 88.9            # 0 is not valid
964
            rx = RadioSettingValueFloat(65, 108.0, val, 0.1, 1)
965
            rs = RadioSetting("hello_lims.fm_318",
966
                              "FM Broadcast Freq (MHz)", rx)
967
            rs.set_apply_callback(my_word2raw, _lims, "fm_318")
968
            basic.append(rs)
969

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

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

    
984
        # not used in BJ-318, skip
985
        if self.MODEL != "BJ-318":
986
            rs = RadioSettingValueList(LIST_COLOR,
987
                                       LIST_COLOR[_sets.txled])
988
            txled = RadioSetting("settings.txled", "TX LED Color", rs)
989
            basic.append(txled)
990

    
991
        ledsw = RadioSetting("settings.ledsw", "Back light mode",
992
                             RadioSettingValueList(LIST_LEDSW, LIST_LEDSW[
993
                                 _sets.ledsw]))
994
        basic.append(ledsw)
995

    
996
        beep = RadioSetting("settings.beep", "Beep",
997
                            RadioSettingValueBoolean(bool(_sets.beep)))
998
        basic.append(beep)
999

    
1000
        ring = RadioSetting("settings.ring", "Ring",
1001
                            RadioSettingValueList(LIST_RING, LIST_RING[
1002
                                _sets.ring]))
1003
        basic.append(ring)
1004

    
1005
        bcl = RadioSetting("settings.bcl", "Busy channel lockout",
1006
                           RadioSettingValueBoolean(bool(_sets.bcl)))
1007
        basic.append(bcl)
1008

    
1009
        # squelch for non-BJ-318 models
1010
        # BJ-318 squelch set by VFO basis
1011
        if self.MODEL != "BJ-318":
1012
            if _vfoa.sql == 0xFF:
1013
                val = 0x04
1014
            else:
1015
                val = _vfoa.sql
1016
            sqla = RadioSetting("upper.vfoa.sql", "Squelch (Upper)",
1017
                                RadioSettingValueInteger(0, 9, val))
1018
            basic.append(sqla)
1019

    
1020
            if _vfob.sql == 0xFF:
1021
                val = 0x04
1022
            else:
1023
                val = _vfob.sql
1024
            sqlb = RadioSetting("lower.vfob.sql", "Squelch (Lower)",
1025
                                RadioSettingValueInteger(0, 9, val))
1026
            basic.append(sqlb)
1027

    
1028
        tmp = str(int(_sets.tot) * 30)     # 30 sec step counter
1029
        rs = RadioSetting("settings.tot", "Transmit Timeout (Secs)",
1030
                          RadioSettingValueList(LIST_TIMEOUT, tmp))
1031
        rs.set_apply_callback(my_val_list, _sets, "tot")
1032
        basic.append(rs)
1033

    
1034
        tmp = str(int(_sets.sig_freq))
1035
        rs = RadioSetting("settings.sig_freq", "Single Signaling Tone (Htz)",
1036
                          RadioSettingValueList(LIST_SSF, tmp))
1037
        rs.set_apply_callback(my_val_list, _sets, "sig_freq")
1038
        basic.append(rs)
1039

    
1040
        tmp = str(int(_sets.dtmf_txms))
1041
        rs = RadioSetting("settings.dtmf_txms", "DTMF Tx Duration (mSecs)",
1042
                          RadioSettingValueList(LIST_DTMFTX, tmp))
1043
        rs.set_apply_callback(my_val_list, _sets, "dtmf_txms")
1044
        basic.append(rs)
1045

    
1046
        rs = RadioSetting("settings.rptr_mode", "Repeater Mode",
1047
                          RadioSettingValueBoolean(bool(_sets.rptr_mode)))
1048
        basic.append(rs)
1049

    
1050
        # UPPER BAND SETTINGS
1051

    
1052
        # Freq Mode, convert bit 1 state to index pointer
1053
        val = _vfoa.frq_chn_mode // 2
1054

    
1055
        rx = RadioSettingValueList(LIST_VFOMODE, LIST_VFOMODE[val])
1056
        rs = RadioSetting("upper.vfoa.frq_chn_mode", "Default Mode", rx)
1057
        rs.set_apply_callback(my_spcl, _vfoa, "frq_chn_mode")
1058
        a_band.append(rs)
1059

    
1060
        val = _vfoa.chan_num + 1                  # Add 1 for 1-128 displayed
1061
        rs = RadioSetting("upper.vfoa.chan_num", "Initial Chan",
1062
                          RadioSettingValueInteger(1, 128, val))
1063
        rs.set_apply_callback(my_adjraw, _vfoa, "chan_num", -1)
1064
        a_band.append(rs)
1065

    
1066
        val = _vfoa.rxfreq / 100000.0
1067
        if (val < 136.0 or val > 176.0):
1068
            val = 146.520            # 2m calling
1069
        rs = RadioSetting("upper.vfoa.rxfreq ", "Default Recv Freq (MHz)",
1070
                          RadioSettingValueFloat(136.0, 176.0, val, 0.001, 5))
1071
        rs.set_apply_callback(my_dbl2raw, _vfoa, "rxfreq")
1072
        a_band.append(rs)
1073

    
1074
        tmp = my_tone_strn(_vfoa, "is_rxdigtone", "rxdtcs_pol", "rx_tone")
1075
        rs = RadioSetting("rx_tone", "Default Recv CTCSS (Htz)",
1076
                          RadioSettingValueList(LIST_CTCSS, tmp))
1077
        rs.set_apply_callback(my_set_tone, _vfoa, "is_rxdigtone",
1078
                              "rxdtcs_pol", "rx_tone")
1079
        a_band.append(rs)
1080

    
1081
        rx = RadioSettingValueList(LIST_RECVMODE,
1082
                                   LIST_RECVMODE[_vfoa.rx_mode])
1083
        rs = RadioSetting("upper.vfoa.rx_mode", "Default Recv Mode", rx)
1084
        a_band.append(rs)
1085

    
1086
        tmp = my_tone_strn(_vfoa, "is_txdigtone", "txdtcs_pol", "tx_tone")
1087
        rs = RadioSetting("tx_tone", "Default Xmit CTCSS (Htz)",
1088
                          RadioSettingValueList(LIST_CTCSS, tmp))
1089
        rs.set_apply_callback(my_set_tone, _vfoa, "is_txdigtone",
1090
                              "txdtcs_pol", "tx_tone")
1091
        a_band.append(rs)
1092

    
1093
        rs = RadioSetting("upper.vfoa.launch_sig", "Launch Signaling",
1094
                          RadioSettingValueList(LIST_SIGNAL,
1095
                                                LIST_SIGNAL[_vfoa.launch_sig]))
1096
        a_band.append(rs)
1097

    
1098
        rx = RadioSettingValueList(LIST_SIGNAL, LIST_SIGNAL[_vfoa.tx_end_sig])
1099
        rs = RadioSetting("upper.vfoa.tx_end_sig", "Xmit End Signaling", rx)
1100
        a_band.append(rs)
1101

    
1102
        rx = RadioSettingValueList(LIST_BW, LIST_BW[_vfoa.fm_bw])
1103
        rs = RadioSetting("upper.vfoa.fm_bw", "Wide/Narrow Band", rx)
1104
        a_band.append(rs)
1105

    
1106
        rx = RadioSettingValueBoolean(bool(_vfoa.cmp_nder))
1107
        rs = RadioSetting("upper.vfoa.cmp_nder", "Compandor", rx)
1108
        a_band.append(rs)
1109

    
1110
        rs = RadioSetting("upper.vfoa.scrm_blr", "Scrambler",
1111
                          RadioSettingValueBoolean(bool(_vfoa.scrm_blr)))
1112
        a_band.append(rs)
1113

    
1114
        rx = RadioSettingValueList(LIST_SHIFT, LIST_SHIFT[_vfoa.shift])
1115
        rs = RadioSetting("upper.vfoa.shift", "Xmit Shift", rx)
1116
        a_band.append(rs)
1117

    
1118
        val = _vfoa.offset / 100000.0
1119
        rs = RadioSetting("upper.vfoa.offset", "Xmit Offset (MHz)",
1120
                          RadioSettingValueFloat(0, 100.0, val, 0.001, 3))
1121
        # Allow zero value
1122
        rs.set_apply_callback(my_dbl2raw, _vfoa, "offset", 0)
1123
        a_band.append(rs)
1124

    
1125
        tmp = str(_vfoa.step / 100.0)
1126
        rs = RadioSetting("step", "Freq step (KHz)",
1127
                          RadioSettingValueList(LIST_STEPS, tmp))
1128
        rs.set_apply_callback(my_word2raw, _vfoa, "step", 100)
1129
        a_band.append(rs)
1130

    
1131
        # BJ-318 upper band squelch
1132
        if self.MODEL == "BJ-318":
1133
            if _vfoa.sql == 0xFF:
1134
                valq = 0x04    # setting default squelch to 04
1135
            else:
1136
                valq = _vfoa.sql
1137
                sqla = RadioSetting("upper.vfoa.sql", "Squelch",
1138
                                    RadioSettingValueInteger(0, 9, valq))
1139
            a_band.append(sqla)
1140

    
1141
        # BJ-318 upper band volume
1142
        if self.MODEL == "BJ-318":
1143
            bvolume_u = RadioSetting("upper.vfoa.bvol",
1144
                                     "Volume", RadioSettingValueInteger(
1145
                                         0, 15, _vfoa.bvol))
1146
            a_band.append(bvolume_u)
1147

    
1148
        # BJ-318 Upper Screen Color
1149
        if self.MODEL == "BJ-318":
1150
            rs_u = RadioSettingValueList(LIST_COLOR318,
1151
                                         LIST_COLOR318[_lims.up_scr_color])
1152
            up_scr_color = RadioSetting("hello_lims.up_scr_color",
1153
                                        "Screen Color", rs_u)
1154
            a_band.append(up_scr_color)
1155

    
1156
        # BJ-318 Upper band power
1157
        if self.MODEL == "BJ-318":
1158
            val_b = _vfoa.bpower        # 2bits values 0,1,2= Low, Mid, High
1159
            rx = RadioSettingValueList(LIST_BPOWER, LIST_BPOWER[val_b])
1160
            power_u = RadioSetting("upper.vfoa.bpower", "Power", rx)
1161
            a_band.append(power_u)
1162

    
1163
        # LOWER BAND SETTINGS
1164

    
1165
        val = _vfob.frq_chn_mode // 2
1166
        rx = RadioSettingValueList(LIST_VFOMODE, LIST_VFOMODE[val])
1167
        rs = RadioSetting("lower.vfob.frq_chn_mode", "Default Mode", rx)
1168
        rs.set_apply_callback(my_spcl, _vfob, "frq_chn_mode")
1169
        b_band.append(rs)
1170

    
1171
        val = _vfob.chan_num + 1
1172
        rs = RadioSetting("lower.vfob.chan_num", "Initial Chan",
1173
                          RadioSettingValueInteger(1, 128, val))
1174
        rs.set_apply_callback(my_adjraw, _vfob, "chan_num", -1)
1175
        b_band.append(rs)
1176

    
1177
        val = _vfob.rxfreq / 100000.0
1178
        if (val < 400.0 or val > 520.0):
1179
            val = 446.0          # UHF calling
1180
        rs = RadioSetting("lower.vfob.rxfreq ", "Default Recv Freq (MHz)",
1181
                          RadioSettingValueFloat(400.0, 520.0, val, 0.001, 5))
1182
        rs.set_apply_callback(my_dbl2raw, _vfob, "rxfreq")
1183
        b_band.append(rs)
1184

    
1185
        tmp = my_tone_strn(_vfob, "is_rxdigtone", "rxdtcs_pol", "rx_tone")
1186
        rs = RadioSetting("rx_tone", "Default Recv CTCSS (Htz)",
1187
                          RadioSettingValueList(LIST_CTCSS, tmp))
1188
        rs.set_apply_callback(my_set_tone, _vfob, "is_rxdigtone",
1189
                              "rxdtcs_pol", "rx_tone")
1190
        b_band.append(rs)
1191

    
1192
        rx = RadioSettingValueList(LIST_RECVMODE, LIST_RECVMODE[_vfob.rx_mode])
1193
        rs = RadioSetting("lower.vfob.rx_mode", "Default Recv Mode", rx)
1194
        b_band.append(rs)
1195

    
1196
        tmp = my_tone_strn(_vfob, "is_txdigtone", "txdtcs_pol", "tx_tone")
1197
        rs = RadioSetting("tx_tone", "Default Xmit CTCSS (Htz)",
1198
                          RadioSettingValueList(LIST_CTCSS, tmp))
1199
        rs.set_apply_callback(my_set_tone, _vfob, "is_txdigtone",
1200
                              "txdtcs_pol", "tx_tone")
1201
        b_band.append(rs)
1202

    
1203
        rx = RadioSettingValueList(LIST_SIGNAL, LIST_SIGNAL[_vfob.launch_sig])
1204
        rs = RadioSetting("lower.vfob.launch_sig", "Launch Signaling", rx)
1205
        b_band.append(rs)
1206

    
1207
        rx = RadioSettingValueList(LIST_SIGNAL, LIST_SIGNAL[_vfob.tx_end_sig])
1208
        rs = RadioSetting("lower.vfob.tx_end_sig", "Xmit End Signaling", rx)
1209
        b_band.append(rs)
1210

    
1211
        rx = RadioSettingValueList(LIST_BW, LIST_BW[_vfob.fm_bw])
1212
        rs = RadioSetting("lower.vfob.fm_bw", "Wide/Narrow Band", rx)
1213
        b_band.append(rs)
1214

    
1215
        rs = RadioSetting("lower.vfob.cmp_nder", "Compandor",
1216
                          RadioSettingValueBoolean(bool(_vfob.cmp_nder)))
1217
        b_band.append(rs)
1218

    
1219
        rs = RadioSetting("lower.vfob.scrm_blr", "Scrambler",
1220
                          RadioSettingValueBoolean(bool(_vfob.scrm_blr)))
1221
        b_band.append(rs)
1222

    
1223
        rx = RadioSettingValueList(LIST_SHIFT, LIST_SHIFT[_vfob.shift])
1224
        rs = RadioSetting("lower.vfob.shift", "Xmit Shift", rx)
1225
        b_band.append(rs)
1226

    
1227
        val = _vfob.offset / 100000.0
1228
        rs = RadioSetting("lower.vfob.offset", "Xmit Offset (MHz)",
1229
                          RadioSettingValueFloat(0, 100.0, val, 0.001, 3))
1230
        rs.set_apply_callback(my_dbl2raw, _vfob, "offset", 0)
1231
        b_band.append(rs)
1232

    
1233
        tmp = str(_vfob.step / 100.0)
1234
        rs = RadioSetting("step", "Freq step (KHz)",
1235
                          RadioSettingValueList(LIST_STEPS, tmp))
1236
        rs.set_apply_callback(my_word2raw, _vfob, "step", 100)
1237
        b_band.append(rs)
1238

    
1239
        # BJ-318 lower band squelch
1240
        if self.MODEL == "BJ-318":
1241
            if _vfob.sql == 0xFF:
1242
                val_l = 0x04    # setting default squelch to 04
1243
            else:
1244
                val_l = _vfob.sql
1245
                sql_b = RadioSetting("lower.vfob.sql", "Squelch",
1246
                                     RadioSettingValueInteger(0, 9, val_l))
1247
                b_band.append(sql_b)
1248

    
1249
        # BJ-318 lower band volume
1250
        if self.MODEL == "BJ-318":
1251
            bvolume_l = RadioSetting("lower.vfob.bvol",
1252
                                     "Volume", RadioSettingValueInteger(
1253
                                         0, 15, _vfob.bvol))
1254
            b_band.append(bvolume_l)
1255

    
1256
        # BJ-318 Lower Screen Color
1257
        if self.MODEL == "BJ-318":
1258
            rs_l = RadioSettingValueList(LIST_COLOR318,
1259
                                         LIST_COLOR318[_lims.dn_scr_color])
1260
            dn_scr_color = RadioSetting("hello_lims.dn_scr_color",
1261
                                        "Screen Color", rs_l)
1262
            b_band.append(dn_scr_color)
1263

    
1264
        # BJ-318 lower band power
1265
        if self.MODEL == "BJ-318":
1266
            val_l = _vfoa.bpower        # 2bits values 0,1,2= Low, Mid, High
1267
            rx = RadioSettingValueList(LIST_BPOWER, LIST_BPOWER[val_l])
1268
            powera = RadioSetting("lower.vfob.bpower", "Power", rx)
1269
            b_band.append(powera)
1270

    
1271
        # PowerOn & Freq Limits Settings
1272
        def chars2str(cary, knt):
1273
            """Convert raw memory char array to a string: NOT a callback."""
1274
            stx = ""
1275
            for char in cary[0:knt]:
1276
                stx += chr(int(char))
1277
            return stx
1278

    
1279
        def my_str2ary(setting, obj, atrba, atrbc):
1280
            """Callback: convert 7-char string to char array with count."""
1281
            ary = ""
1282
            knt = 7
1283
            for j in range(6, -1, -1):       # Strip trailing spaces
1284
                if str(setting.value)[j] == "" or str(setting.value)[j] == " ":
1285
                    knt = knt - 1
1286
                else:
1287
                    break
1288
            for j in range(0, 7, 1):
1289
                if j < knt:
1290
                    ary += str(setting.value)[j]
1291
                else:
1292
                    ary += chr(0xFF)
1293
            setattr(obj, atrba, ary)
1294
            setattr(obj, atrbc, knt)
1295
            return
1296

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

    
1305
        # not used in BJ-318 startup screen
1306
        if self.MODEL != "BJ-318":
1307
            tmp = chars2str(_lims.hello2, _lims.hello2_cnt)
1308
            rs = RadioSetting("hello_lims.hello2", "Power-On Message 2",
1309
                              RadioSettingValueString(0, 7, tmp))
1310
            rs.set_apply_callback(my_str2ary, _lims, "hello2", "hello2_cnt")
1311
            lims.append(rs)
1312

    
1313
        # VALID_BANDS = [(136000000, 176000000),400000000, 520000000)]
1314

    
1315
        lval = _lims.vhf_low / 100000.0
1316
        uval = _lims.vhf_high / 100000.0
1317
        if lval >= uval:
1318
            lval = 136.0
1319
            uval = 176.0
1320

    
1321
        rs = RadioSetting("hello_lims.vhf_low", "Lower VHF Band Limit (MHz)",
1322
                          RadioSettingValueFloat(136.0, 176.0, lval, 0.001, 3))
1323
        rs.set_apply_callback(my_dbl2raw, _lims, "vhf_low")
1324
        lims.append(rs)
1325

    
1326
        rs = RadioSetting("hello_lims.vhf_high", "Upper VHF Band Limit (MHz)",
1327
                          RadioSettingValueFloat(136.0, 176.0, uval, 0.001, 3))
1328
        rs.set_apply_callback(my_dbl2raw, _lims, "vhf_high")
1329
        lims.append(rs)
1330

    
1331
        lval = _lims.uhf_low / 100000.0
1332
        uval = _lims.uhf_high / 100000.0
1333
        if lval >= uval:
1334
            lval = 400.0
1335
            uval = 520.0
1336

    
1337
        rs = RadioSetting("hello_lims.uhf_low", "Lower UHF Band Limit (MHz)",
1338
                          RadioSettingValueFloat(400.0, 520.0, lval, 0.001, 3))
1339
        rs.set_apply_callback(my_dbl2raw, _lims, "uhf_low")
1340
        lims.append(rs)
1341

    
1342
        rs = RadioSetting("hello_lims.uhf_high", "Upper UHF Band Limit (MHz)",
1343
                          RadioSettingValueFloat(400.0, 520.0, uval, 0.001, 3))
1344
        rs.set_apply_callback(my_dbl2raw, _lims, "uhf_high")
1345
        lims.append(rs)
1346

    
1347
        # Codes and DTMF Groups Settings
1348

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

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

    
1416
        tmp = make_dtmf(_codes.native_id_code, _codes.native_id_cnt)
1417
        rs = RadioSetting("codes.native_id_code", "Native ID Code",
1418
                          RadioSettingValueString(0, 7, tmp))
1419
        rs.set_apply_callback(my_dtmf2raw, _codes, "native_id_code",
1420
                              "native_id_cnt", 7)
1421
        codes.append(rs)
1422

    
1423
        tmp = make_dtmf(_codes.master_id_code, _codes.master_id_cnt)
1424
        rs = RadioSetting("codes.master_id_code", "Master Control ID Code",
1425
                          RadioSettingValueString(0, 7, tmp))
1426
        rs.set_apply_callback(my_dtmf2raw, _codes, "master_id_code",
1427
                              "master_id_cnt", 7)
1428
        codes.append(rs)
1429

    
1430
        tmp = make_dtmf(_codes.alarm_code, _codes.alarm_cnt)
1431
        rs = RadioSetting("codes.alarm_code", "Alarm Code",
1432
                          RadioSettingValueString(0, 5, tmp))
1433
        rs.set_apply_callback(my_dtmf2raw, _codes, "alarm_code",
1434
                              "alarm_cnt", 5)
1435
        codes.append(rs)
1436

    
1437
        tmp = make_dtmf(_codes.id_disp_code, _codes.id_disp_cnt)
1438
        rs = RadioSetting("codes.id_disp_code", "Identify Display Code",
1439
                          RadioSettingValueString(0, 5, tmp))
1440
        rs.set_apply_callback(my_dtmf2raw, _codes, "id_disp_code",
1441
                              "id_disp_cnt", 5)
1442
        codes.append(rs)
1443

    
1444
        tmp = make_dtmf(_codes.revive_code, _codes.revive_cnt)
1445
        rs = RadioSetting("codes.revive_code", "Revive Code",
1446
                          RadioSettingValueString(0, 5, tmp))
1447
        rs.set_apply_callback(my_dtmf2raw, _codes, "revive_code",
1448
                              "revive_cnt", 5)
1449
        codes.append(rs)
1450

    
1451
        tmp = make_dtmf(_codes.stun_code, _codes.stun_cnt)
1452
        rs = RadioSetting("codes.stun_code", "Remote Stun Code",
1453
                          RadioSettingValueString(0, 5, tmp))
1454
        rs.set_apply_callback(my_dtmf2raw,  _codes, "stun_code",
1455
                              "stun_cnt", 5)
1456
        codes.append(rs)
1457

    
1458
        tmp = make_dtmf(_codes.kill_code, _codes.kill_cnt)
1459
        rs = RadioSetting("codes.kill_code", "Remote KILL Code",
1460
                          RadioSettingValueString(0, 5, tmp))
1461
        rs.set_apply_callback(my_dtmf2raw, _codes, "kill_code",
1462
                              "kill_cnt", 5)
1463
        codes.append(rs)
1464

    
1465
        tmp = make_dtmf(_codes.monitor_code, _codes.monitor_cnt)
1466
        rs = RadioSetting("codes.monitor_code", "Monitor Code",
1467
                          RadioSettingValueString(0, 5, tmp))
1468
        rs.set_apply_callback(my_dtmf2raw, _codes, "monitor_code",
1469
                              "monitor_cnt", 5)
1470
        codes.append(rs)
1471

    
1472
        val = _codes.state_now
1473
        if val > 2:
1474
            val = 0
1475

    
1476
        rx = RadioSettingValueList(LIST_STATE, LIST_STATE[val])
1477
        rs = RadioSetting("codes.state_now", "Current State", rx)
1478
        codes.append(rs)
1479

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

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

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

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

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

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

    
1516
        dtm = make_dtmf(_dtmf.dtmf7, _dtmf.dtmf7_cnt)
1517
        rs = RadioSetting("dtmf_tab.dtmf7", "DTMF7 String",
1518
                          RadioSettingValueString(0, 7, dtm))
1519
        rs.set_apply_callback(my_dtmf2raw, _dtmf, "dtmf7", "dtmf7_cnt")
1520
        codes.append(rs)
1521

    
1522
        dtm = make_dtmf(_dtmf.dtmf8, _dtmf.dtmf8_cnt)
1523
        rs = RadioSetting("dtmf_tab.dtmf8", "DTMF8 String",
1524
                          RadioSettingValueString(0, 7, dtm))
1525
        rs.set_apply_callback(my_dtmf2raw, _dtmf, "dtmf8", "dtmf8_cnt")
1526
        codes.append(rs)
1527

    
1528
        return group       # END get_settings()
1529

    
1530
    def set_settings(self, settings):
1531
        _settings = self._memobj.settings
1532
        _mem = self._memobj
1533
        for element in settings:
1534
            if not isinstance(element, RadioSetting):
1535
                self.set_settings(element)
1536
                continue
1537
            else:
1538
                try:
1539
                    name = element.get_name()
1540
                    if "." in name:
1541
                        bits = name.split(".")
1542
                        obj = self._memobj
1543
                        for bit in bits[:-1]:
1544
                            if "/" in bit:
1545
                                bit, index = bit.split("/", 1)
1546
                                index = int(index)
1547
                                obj = getattr(obj, bit)[index]
1548
                            else:
1549
                                obj = getattr(obj, bit)
1550
                        setting = bits[-1]
1551
                    else:
1552
                        obj = _settings
1553
                        setting = element.get_name()
1554

    
1555
                    if element.has_apply_callback():
1556
                        LOG.debug("Using apply callback")
1557
                        element.run_apply_callback()
1558
                    elif element.value.get_mutable():
1559
                        LOG.debug("Setting %s = %s" % (setting, element.value))
1560
                        setattr(obj, setting, element.value)
1561
                except Exception as e:
1562
                    LOG.debug(element.get_name())
1563
                    raise
1564

    
1565
    @classmethod
1566
    def match_model(cls, filedata, filename):
1567
        match_size = False
1568
        match_model = False
1569

    
1570
        # Testing the file data size
1571
        if len(filedata) == MEM_SIZE + 8:
1572
            match_size = True
1573

    
1574
        # Testing the firmware model fingerprint
1575
        match_model = model_match(cls, filedata)
1576

    
1577
        if match_size and match_model:
1578
            return True
1579
        else:
1580
            return False
1581

    
1582

    
1583
class LT725UVUpper(LT725UV):
1584
    VARIANT = "Upper"
1585
    _vfo = "upper"
1586

    
1587

    
1588
class LT725UVLower(LT725UV):
1589
    VARIANT = "Lower"
1590
    _vfo = "lower"
1591

    
1592

    
1593
class Zastone(chirp_common.Alias):
1594
    """Declare BJ-218 alias for Zastone BJ-218."""
1595
    VENDOR = "Zastone"
1596
    MODEL = "BJ-218"
1597

    
1598

    
1599
class Hesenate(chirp_common.Alias):
1600
    """Declare BJ-218 alias for Hesenate BJ-218."""
1601
    VENDOR = "Hesenate"
1602
    MODEL = "BJ-218"
1603

    
1604

    
1605
class Baojie218Upper(LT725UVUpper):
1606
    VENDOR = "Baojie"
1607
    MODEL = "BJ-218"
1608

    
1609

    
1610
class Baojie218Lower(LT725UVLower):
1611
    VENDOR = "Baojie"
1612
    MODEL = "BJ-218"
1613

    
1614

    
1615
@directory.register
1616
class Baojie218(LT725UV):
1617
    """Baojie BJ-218"""
1618
    VENDOR = "Baojie"
1619
    MODEL = "BJ-218"
1620
    ALIASES = [Zastone, Hesenate, ]
1621

    
1622
    def get_sub_devices(self):
1623
        return [Baojie218Upper(self._mmap), Baojie218Lower(self._mmap)]
1624

    
1625

    
1626
class Baojie318Upper(LT725UVUpper):
1627
    VENDOR = "Baojie"
1628
    MODEL = "BJ-318"
1629

    
1630

    
1631
class Baojie318Lower(LT725UVLower):
1632
    VENDOR = "Baojie"
1633
    MODEL = "BJ-318"
1634

    
1635

    
1636
@directory.register
1637
class Baojie318(LT725UV):
1638
    """Baojie BJ-318"""
1639
    VENDOR = "Baojie"
1640
    MODEL = "BJ-318"
1641

    
1642
    def get_sub_devices(self):
1643
        return [Baojie318Upper(self._mmap), Baojie318Lower(self._mmap)]
(6-6/6)