Project

General

Profile

Bug #10210 » lt725uv-BJ318Rev05.09.21.py

Driver - Mark Hartong, 01/01/2023 03:11 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
# Changes for Baojie-318: May 2021
8
#    -Removed Experimental Warning 
9
#    -Added Brown and Yellow as Color Selections
10
#    -Max VFO Volume Setting to 15 (vice 10)
11
#    Mark Hartong, AJ4YI <mark.hartong@verizon.net>
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
120
                     // required 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=00h
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;        // volume for vfo band
151
                        // integer values 0 (low) TO 15 (high)
152
                        // in the BJ-318
153
};
154

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

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

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

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

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

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

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

    
212
# Screen Color Settings
213
# BJ-218 Colors
214
LIST_COLOR = ["Off", "Orange", "Blue", "Purple"]
215
# BJ-318 Colors
216
LIST_COLOR318 = ["Purple", "Red", "Emerald", "Blue", "Sky_Blue", "Black", "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 = ""
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 = "PROM_LIN"
332

    
333
    _rawsend(radio, magic)
334

    
335
    ack = _rawrecv(radio, 1)
336
    if ack != "\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 = "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 = ""
364
    for addr in range(0, MEM_SIZE, BLOCK_SIZE):
365
        frame = _make_frame("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
    data += radio.MODEL.ljust(8)
387

    
388
    return data
389

    
390

    
391
def _upload(radio):
392
    """Upload procedure"""
393

    
394
    # Put radio in program mode and identify it
395
    _do_ident(radio)
396

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

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

    
409
        frame = _make_frame("WRIE", addr, BLOCK_SIZE, data)
410

    
411
        _rawsend(radio, frame)
412

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

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

    
425
    _exit_program_mode(radio)
426

    
427

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

    
436

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

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

    
448
    # If you get here is because the freq pairs are split
449
    return True
450

    
451

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

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

    
474
    VALID_BANDS = [(136000000, 176000000),
475
                   (400000000, 480000000)]
476

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

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

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

    
515
        rp.info = msg
516

    
517
        rp.pre_download = _(dedent("""\
518
            Follow this instructions to download your info:
519

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

    
528
            1 - Turn off your radio
529
            2 - Connect your interface cable
530
            3 - Turn on your radio
531
            4 - Do the upload of your radio data
532
            """))
533
        return rp
534

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

    
571
    def get_sub_devices(self):
572
        return [LT725UVUpper(self._mmap), LT725UVLower(self._mmap)]
573

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

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

    
601
    def process_mmap(self):
602
        """Process the mem map into the mem object"""
603
        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
604

    
605
    def get_raw_memory(self, number):
606
        return repr(self._memobj.memory[number - 1])
607

    
608
    def _memory_obj(self, suffix=""):
609
        return getattr(self._memobj, "%s_memory%s" % (self._vfo, suffix))
610

    
611
    def _get_dcs(self, val):
612
        return int(str(val)[2:-18])
613

    
614
    def _set_dcs(self, val):
615
        return int(str(val), 16)
616

    
617
    def get_memory(self, number):
618
        _mem = self._memory_obj()[number - 1]
619

    
620
        mem = chirp_common.Memory()
621
        mem.number = number
622

    
623
        if _mem.get_raw()[0] == "\xff":
624
            mem.empty = True
625
            return mem
626

    
627
        mem.freq = int(_mem.rxfreq) * 10
628

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

    
643
        for char in _mem.name[:_mem.namelen]:
644
            mem.name += chr(char)
645

    
646
        dtcs_pol = ["N", "N"]
647

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

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

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

    
684
        mem.dtcs_polarity = "".join(dtcs_pol)
685

    
686
        mem.mode = _mem.wide and "FM" or "NFM"
687

    
688
        mem.power = self.POWER_LEVELS[_mem.power]
689

    
690
        # Extra
691
        mem.extra = RadioSettingGroup("extra", "Extra")
692

    
693
        if _mem.recvmode == 0xFF:
694
            val = 0x00
695
        else:
696
            val = _mem.recvmode
697
        recvmode = RadioSetting("recvmode", "Receiving mode",
698
                                RadioSettingValueList(LIST_RECVMODE,
699
                                                      LIST_RECVMODE[val]))
700
        mem.extra.append(recvmode)
701

    
702
        if _mem.botsignal == 0xFF:
703
            val = 0x00
704
        else:
705
            val = _mem.botsignal
706
        botsignal = RadioSetting("botsignal", "Launch signaling",
707
                                 RadioSettingValueList(LIST_SIGNAL,
708
                                                       LIST_SIGNAL[val]))
709
        mem.extra.append(botsignal)
710

    
711
        if _mem.eotsignal == 0xFF:
712
            val = 0x00
713
        else:
714
            val = _mem.eotsignal
715

    
716
        rx = RadioSettingValueList(LIST_SIGNAL, LIST_SIGNAL[val])
717
        eotsignal = RadioSetting("eotsignal", "Transmit end signaling", rx)
718
        mem.extra.append(eotsignal)
719

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

    
724
        rx = RadioSettingValueBoolean(bool(_mem.scrambler))
725
        scrambler = RadioSetting("scrambler", "Scrambler", rx)
726
        mem.extra.append(scrambler)
727

    
728
        return mem
729

    
730
    def set_memory(self, mem):
731
        _mem = self._memory_obj()[mem.number - 1]
732

    
733
        if mem.empty:
734
            _mem.set_raw("\xff" * 24)
735
            _mem.namelen = 0
736
            return
737

    
738
        _mem.set_raw("\xFF" * 15 + "\x00\x00" + "\xFF" * 7)
739

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

    
752
        _mem.namelen = len(mem.name)
753
        _namelength = self.get_features().valid_name_length
754
        for i in range(_namelength):
755
            try:
756
                _mem.name[i] = ord(mem.name[i])
757
            except IndexError:
758
                _mem.name[i] = 0xFF
759

    
760
        rxmode = ""
761
        txmode = ""
762

    
763
        if mem.tmode == "Tone":
764
            txmode = "Tone"
765
        elif mem.tmode == "TSQL":
766
            rxmode = "Tone"
767
            txmode = "TSQL"
768
        elif mem.tmode == "DTCS":
769
            rxmode = "DTCSSQL"
770
            txmode = "DTCS"
771
        elif mem.tmode == "Cross":
772
            txmode, rxmode = mem.cross_mode.split("->", 1)
773

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

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

    
808
        _mem.wide = self.MODES.index(mem.mode)
809
        _mem.power = mem.power == self.POWER_LEVELS[1]
810

    
811
        # Extra settings
812
        for setting in mem.extra:
813
            setattr(_mem, setting.get_name(), setting.value)
814

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

    
825
        basic = RadioSettingGroup("basic", "Basic Settings")
826
        a_band = RadioSettingGroup("a_band", "VFO A-Upper Settings")
827
        b_band = RadioSettingGroup("b_band", "VFO B-Lower Settings")
828
        codes = RadioSettingGroup("codes", "Codes & DTMF Groups")
829
        lims = RadioSettingGroup("lims", "PowerOn & Freq Limits")
830
        group = RadioSettings(basic, a_band, b_band, lims, codes)
831

    
832
        # Basic Settings
833
        bnd_mode = RadioSetting("settings.init_bank", "TDR Band Default",
834
                                RadioSettingValueList(LIST_TDR_DEF,
835
                                                      LIST_TDR_DEF[
836
                                                          _sets.init_bank]))
837
        basic.append(bnd_mode)
838

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
986
        ledsw = RadioSetting("settings.ledsw", "Back light mode",
987
                             RadioSettingValueList(LIST_LEDSW, LIST_LEDSW[
988
                                 _sets.ledsw]))
989
        basic.append(ledsw)
990

    
991
        beep = RadioSetting("settings.beep", "Beep",
992
                            RadioSettingValueBoolean(bool(_sets.beep)))
993
        basic.append(beep)
994

    
995
        ring = RadioSetting("settings.ring", "Ring",
996
                            RadioSettingValueList(LIST_RING, LIST_RING[
997
                                _sets.ring]))
998
        basic.append(ring)
999

    
1000
        bcl = RadioSetting("settings.bcl", "Busy channel lockout",
1001
                           RadioSettingValueBoolean(bool(_sets.bcl)))
1002
        basic.append(bcl)
1003

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

    
1015
            if _vfob.sql == 0xFF:
1016
                val = 0x04
1017
            else:
1018
                val = _vfob.sql
1019
            sqlb = RadioSetting("lower.vfob.sql", "Squelch (Lower)",
1020
                                RadioSettingValueInteger(0, 9, val))
1021
            basic.append(sqlb)
1022

    
1023
        tmp = str(int(_sets.tot) * 30)     # 30 sec step counter
1024
        rs = RadioSetting("settings.tot", "Transmit Timeout (Secs)",
1025
                          RadioSettingValueList(LIST_TIMEOUT, tmp))
1026
        rs.set_apply_callback(my_val_list, _sets, "tot")
1027
        basic.append(rs)
1028

    
1029
        tmp = str(int(_sets.sig_freq))
1030
        rs = RadioSetting("settings.sig_freq", "Single Signaling Tone (Htz)",
1031
                          RadioSettingValueList(LIST_SSF, tmp))
1032
        rs.set_apply_callback(my_val_list, _sets, "sig_freq")
1033
        basic.append(rs)
1034

    
1035
        tmp = str(int(_sets.dtmf_txms))
1036
        rs = RadioSetting("settings.dtmf_txms", "DTMF Tx Duration (mSecs)",
1037
                          RadioSettingValueList(LIST_DTMFTX, tmp))
1038
        rs.set_apply_callback(my_val_list, _sets, "dtmf_txms")
1039
        basic.append(rs)
1040

    
1041
        rs = RadioSetting("settings.rptr_mode", "Repeater Mode",
1042
                          RadioSettingValueBoolean(bool(_sets.rptr_mode)))
1043
        basic.append(rs)
1044

    
1045
        # UPPER BAND SETTINGS
1046

    
1047
        # Freq Mode, convert bit 1 state to index pointer
1048
        val = _vfoa.frq_chn_mode / 2
1049

    
1050
        rx = RadioSettingValueList(LIST_VFOMODE, LIST_VFOMODE[val])
1051
        rs = RadioSetting("upper.vfoa.frq_chn_mode", "Default Mode", rx)
1052
        rs.set_apply_callback(my_spcl, _vfoa, "frq_chn_mode")
1053
        a_band.append(rs)
1054

    
1055
        val = _vfoa.chan_num + 1                  # Add 1 for 1-128 displayed
1056
        rs = RadioSetting("upper.vfoa.chan_num", "Initial Chan",
1057
                          RadioSettingValueInteger(1, 128, val))
1058
        rs.set_apply_callback(my_adjraw, _vfoa, "chan_num", -1)
1059
        a_band.append(rs)
1060

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

    
1069
        tmp = my_tone_strn(_vfoa, "is_rxdigtone", "rxdtcs_pol", "rx_tone")
1070
        rs = RadioSetting("rx_tone", "Default Recv CTCSS (Htz)",
1071
                          RadioSettingValueList(LIST_CTCSS, tmp))
1072
        rs.set_apply_callback(my_set_tone, _vfoa, "is_rxdigtone",
1073
                              "rxdtcs_pol", "rx_tone")
1074
        a_band.append(rs)
1075

    
1076
        rx = RadioSettingValueList(LIST_RECVMODE,
1077
                                   LIST_RECVMODE[_vfoa.rx_mode])
1078
        rs = RadioSetting("upper.vfoa.rx_mode", "Default Recv Mode", rx)
1079
        a_band.append(rs)
1080

    
1081
        tmp = my_tone_strn(_vfoa, "is_txdigtone", "txdtcs_pol", "tx_tone")
1082
        rs = RadioSetting("tx_tone", "Default Xmit CTCSS (Htz)",
1083
                          RadioSettingValueList(LIST_CTCSS, tmp))
1084
        rs.set_apply_callback(my_set_tone, _vfoa, "is_txdigtone",
1085
                              "txdtcs_pol", "tx_tone")
1086
        a_band.append(rs)
1087

    
1088
        rs = RadioSetting("upper.vfoa.launch_sig", "Launch Signaling",
1089
                          RadioSettingValueList(LIST_SIGNAL,
1090
                                                LIST_SIGNAL[_vfoa.launch_sig]))
1091
        a_band.append(rs)
1092

    
1093
        rx = RadioSettingValueList(LIST_SIGNAL, LIST_SIGNAL[_vfoa.tx_end_sig])
1094
        rs = RadioSetting("upper.vfoa.tx_end_sig", "Xmit End Signaling", rx)
1095
        a_band.append(rs)
1096

    
1097
        rx = RadioSettingValueList(LIST_BW, LIST_BW[_vfoa.fm_bw])
1098
        rs = RadioSetting("upper.vfoa.fm_bw", "Wide/Narrow Band", rx)
1099
        a_band.append(rs)
1100

    
1101
        rx = RadioSettingValueBoolean(bool(_vfoa.cmp_nder))
1102
        rs = RadioSetting("upper.vfoa.cmp_nder", "Compandor", rx)
1103
        a_band.append(rs)
1104

    
1105
        rs = RadioSetting("upper.vfoa.scrm_blr", "Scrambler",
1106
                          RadioSettingValueBoolean(bool(_vfoa.scrm_blr)))
1107
        a_band.append(rs)
1108

    
1109
        rx = RadioSettingValueList(LIST_SHIFT, LIST_SHIFT[_vfoa.shift])
1110
        rs = RadioSetting("upper.vfoa.shift", "Xmit Shift", rx)
1111
        a_band.append(rs)
1112

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

    
1120
        tmp = str(_vfoa.step / 100.0)
1121
        rs = RadioSetting("step", "Freq step (KHz)",
1122
                          RadioSettingValueList(LIST_STEPS, tmp))
1123
        rs.set_apply_callback(my_word2raw, _vfoa, "step", 100)
1124
        a_band.append(rs)
1125

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

    
1136
        # BJ-318 upper band volume
1137
        if self.MODEL == "BJ-318":
1138
            bvolume_u = RadioSetting("upper.vfoa.bvol",
1139
                                     "Volume", RadioSettingValueInteger(
1140
                                         0, 15, _vfoa.bvol))
1141
            a_band.append(bvolume_u)
1142

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

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

    
1158
        # LOWER BAND SETTINGS
1159

    
1160
        val = _vfob.frq_chn_mode / 2
1161
        rx = RadioSettingValueList(LIST_VFOMODE, LIST_VFOMODE[val])
1162
        rs = RadioSetting("lower.vfob.frq_chn_mode", "Default Mode", rx)
1163
        rs.set_apply_callback(my_spcl, _vfob, "frq_chn_mode")
1164
        b_band.append(rs)
1165

    
1166
        val = _vfob.chan_num + 1
1167
        rs = RadioSetting("lower.vfob.chan_num", "Initial Chan",
1168
                          RadioSettingValueInteger(1, 128, val))
1169
        rs.set_apply_callback(my_adjraw, _vfob, "chan_num", -1)
1170
        b_band.append(rs)
1171

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

    
1180
        tmp = my_tone_strn(_vfob, "is_rxdigtone", "rxdtcs_pol", "rx_tone")
1181
        rs = RadioSetting("rx_tone", "Default Recv CTCSS (Htz)",
1182
                          RadioSettingValueList(LIST_CTCSS, tmp))
1183
        rs.set_apply_callback(my_set_tone, _vfob, "is_rxdigtone",
1184
                              "rxdtcs_pol", "rx_tone")
1185
        b_band.append(rs)
1186

    
1187
        rx = RadioSettingValueList(LIST_RECVMODE, LIST_RECVMODE[_vfob.rx_mode])
1188
        rs = RadioSetting("lower.vfob.rx_mode", "Default Recv Mode", rx)
1189
        b_band.append(rs)
1190

    
1191
        tmp = my_tone_strn(_vfob, "is_txdigtone", "txdtcs_pol", "tx_tone")
1192
        rs = RadioSetting("tx_tone", "Default Xmit CTCSS (Htz)",
1193
                          RadioSettingValueList(LIST_CTCSS, tmp))
1194
        rs.set_apply_callback(my_set_tone, _vfob, "is_txdigtone",
1195
                              "txdtcs_pol", "tx_tone")
1196
        b_band.append(rs)
1197

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

    
1202
        rx = RadioSettingValueList(LIST_SIGNAL, LIST_SIGNAL[_vfob.tx_end_sig])
1203
        rs = RadioSetting("lower.vfob.tx_end_sig", "Xmit End Signaling", rx)
1204
        b_band.append(rs)
1205

    
1206
        rx = RadioSettingValueList(LIST_BW, LIST_BW[_vfob.fm_bw])
1207
        rs = RadioSetting("lower.vfob.fm_bw", "Wide/Narrow Band", rx)
1208
        b_band.append(rs)
1209

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

    
1214
        rs = RadioSetting("lower.vfob.scrm_blr", "Scrambler",
1215
                          RadioSettingValueBoolean(bool(_vfob.scrm_blr)))
1216
        b_band.append(rs)
1217

    
1218
        rx = RadioSettingValueList(LIST_SHIFT, LIST_SHIFT[_vfob.shift])
1219
        rs = RadioSetting("lower.vfob.shift", "Xmit Shift", rx)
1220
        b_band.append(rs)
1221

    
1222
        val = _vfob.offset / 100000.0
1223
        rs = RadioSetting("lower.vfob.offset", "Xmit Offset (MHz)",
1224
                          RadioSettingValueFloat(0, 100.0, val, 0.001, 3))
1225
        rs.set_apply_callback(my_dbl2raw, _vfob, "offset", 0)
1226
        b_band.append(rs)
1227

    
1228
        tmp = str(_vfob.step / 100.0)
1229
        rs = RadioSetting("step", "Freq step (KHz)",
1230
                          RadioSettingValueList(LIST_STEPS, tmp))
1231
        rs.set_apply_callback(my_word2raw, _vfob, "step", 100)
1232
        b_band.append(rs)
1233

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

    
1244
        # BJ-318 lower band volume
1245
        if self.MODEL == "BJ-318":
1246
            bvolume_l = RadioSetting("lower.vfob.bvol",
1247
                                     "Volume", RadioSettingValueInteger(
1248
                                         0, 15, _vfob.bvol))
1249
            b_band.append(bvolume_l)
1250

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

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

    
1266
        # PowerOn & Freq Limits Settings
1267
        def chars2str(cary, knt):
1268
            """Convert raw memory char array to a string: NOT a callback."""
1269
            stx = ""
1270
            for char in cary[:knt]:
1271
                stx += chr(char)
1272
            return stx
1273

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

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

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

    
1308
        # VALID_BANDS = [(136000000, 176000000),400000000, 480000000)]
1309

    
1310
        lval = _lims.vhf_low / 100000.0
1311
        uval = _lims.vhf_high / 100000.0
1312
        if lval >= uval:
1313
            lval = 144.0
1314
            uval = 158.0
1315

    
1316
        rs = RadioSetting("hello_lims.vhf_low", "Lower VHF Band Limit (MHz)",
1317
                          RadioSettingValueFloat(136.0, 176.0, lval, 0.001, 3))
1318
        rs.set_apply_callback(my_dbl2raw, _lims, "vhf_low")
1319
        lims.append(rs)
1320

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

    
1326
        lval = _lims.uhf_low / 100000.0
1327
        uval = _lims.uhf_high / 100000.0
1328
        if lval >= uval:
1329
            lval = 420.0
1330
            uval = 470.0
1331

    
1332
        rs = RadioSetting("hello_lims.uhf_low", "Lower UHF Band Limit (MHz)",
1333
                          RadioSettingValueFloat(400.0, 480.0, lval, 0.001, 3))
1334
        rs.set_apply_callback(my_dbl2raw, _lims, "uhf_low")
1335
        lims.append(rs)
1336

    
1337
        rs = RadioSetting("hello_lims.uhf_high", "Upper UHF Band Limit (MHz)",
1338
                          RadioSettingValueFloat(400.0, 480.0, uval, 0.001, 3))
1339
        rs.set_apply_callback(my_dbl2raw, _lims, "uhf_high")
1340
        lims.append(rs)
1341

    
1342
        # Codes and DTMF Groups Settings
1343

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

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

    
1411
        tmp = make_dtmf(_codes.native_id_code, _codes.native_id_cnt)
1412
        rs = RadioSetting("codes.native_id_code", "Native ID Code",
1413
                          RadioSettingValueString(0, 7, tmp))
1414
        rs.set_apply_callback(my_dtmf2raw, _codes, "native_id_code",
1415
                              "native_id_cnt", 7)
1416
        codes.append(rs)
1417

    
1418
        tmp = make_dtmf(_codes.master_id_code, _codes.master_id_cnt)
1419
        rs = RadioSetting("codes.master_id_code", "Master Control ID Code",
1420
                          RadioSettingValueString(0, 7, tmp))
1421
        rs.set_apply_callback(my_dtmf2raw, _codes, "master_id_code",
1422
                              "master_id_cnt", 7)
1423
        codes.append(rs)
1424

    
1425
        tmp = make_dtmf(_codes.alarm_code, _codes.alarm_cnt)
1426
        rs = RadioSetting("codes.alarm_code", "Alarm Code",
1427
                          RadioSettingValueString(0, 5, tmp))
1428
        rs.set_apply_callback(my_dtmf2raw, _codes, "alarm_code",
1429
                              "alarm_cnt", 5)
1430
        codes.append(rs)
1431

    
1432
        tmp = make_dtmf(_codes.id_disp_code, _codes.id_disp_cnt)
1433
        rs = RadioSetting("codes.id_disp_code", "Identify Display Code",
1434
                          RadioSettingValueString(0, 5, tmp))
1435
        rs.set_apply_callback(my_dtmf2raw, _codes, "id_disp_code",
1436
                              "id_disp_cnt", 5)
1437
        codes.append(rs)
1438

    
1439
        tmp = make_dtmf(_codes.revive_code, _codes.revive_cnt)
1440
        rs = RadioSetting("codes.revive_code", "Revive Code",
1441
                          RadioSettingValueString(0, 5, tmp))
1442
        rs.set_apply_callback(my_dtmf2raw, _codes, "revive_code",
1443
                              "revive_cnt", 5)
1444
        codes.append(rs)
1445

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

    
1453
        tmp = make_dtmf(_codes.kill_code, _codes.kill_cnt)
1454
        rs = RadioSetting("codes.kill_code", "Remote KILL Code",
1455
                          RadioSettingValueString(0, 5, tmp))
1456
        rs.set_apply_callback(my_dtmf2raw, _codes, "kill_code",
1457
                              "kill_cnt", 5)
1458
        codes.append(rs)
1459

    
1460
        tmp = make_dtmf(_codes.monitor_code, _codes.monitor_cnt)
1461
        rs = RadioSetting("codes.monitor_code", "Monitor Code",
1462
                          RadioSettingValueString(0, 5, tmp))
1463
        rs.set_apply_callback(my_dtmf2raw, _codes, "monitor_code",
1464
                              "monitor_cnt", 5)
1465
        codes.append(rs)
1466

    
1467
        val = _codes.state_now
1468
        if val > 2:
1469
            val = 0
1470

    
1471
        rx = RadioSettingValueList(LIST_STATE, LIST_STATE[val])
1472
        rs = RadioSetting("codes.state_now", "Current State", rx)
1473
        codes.append(rs)
1474

    
1475
        dtm = make_dtmf(_dtmf.dtmf1, _dtmf.dtmf1_cnt)
1476
        rs = RadioSetting("dtmf_tab.dtmf1", "DTMF1 String",
1477
                          RadioSettingValueString(0, 7, dtm))
1478
        rs.set_apply_callback(my_dtmf2raw, _dtmf, "dtmf1", "dtmf1_cnt")
1479
        codes.append(rs)
1480

    
1481
        dtm = make_dtmf(_dtmf.dtmf2, _dtmf.dtmf2_cnt)
1482
        rs = RadioSetting("dtmf_tab.dtmf2", "DTMF2 String",
1483
                          RadioSettingValueString(0, 7, dtm))
1484
        rs.set_apply_callback(my_dtmf2raw, _dtmf, "dtmf2", "dtmf2_cnt")
1485
        codes.append(rs)
1486

    
1487
        dtm = make_dtmf(_dtmf.dtmf3, _dtmf.dtmf3_cnt)
1488
        rs = RadioSetting("dtmf_tab.dtmf3", "DTMF3 String",
1489
                          RadioSettingValueString(0, 7, dtm))
1490
        rs.set_apply_callback(my_dtmf2raw, _dtmf, "dtmf3", "dtmf3_cnt")
1491
        codes.append(rs)
1492

    
1493
        dtm = make_dtmf(_dtmf.dtmf4, _dtmf.dtmf4_cnt)
1494
        rs = RadioSetting("dtmf_tab.dtmf4", "DTMF4 String",
1495
                          RadioSettingValueString(0, 7, dtm))
1496
        rs.set_apply_callback(my_dtmf2raw, _dtmf, "dtmf4", "dtmf4_cnt")
1497
        codes.append(rs)
1498

    
1499
        dtm = make_dtmf(_dtmf.dtmf5, _dtmf.dtmf5_cnt)
1500
        rs = RadioSetting("dtmf_tab.dtmf5", "DTMF5 String",
1501
                          RadioSettingValueString(0, 7, dtm))
1502
        rs.set_apply_callback(my_dtmf2raw, _dtmf, "dtmf5", "dtmf5_cnt")
1503
        codes.append(rs)
1504

    
1505
        dtm = make_dtmf(_dtmf.dtmf6, _dtmf.dtmf6_cnt)
1506
        rs = RadioSetting("dtmf_tab.dtmf6", "DTMF6 String",
1507
                          RadioSettingValueString(0, 7, dtm))
1508
        rs.set_apply_callback(my_dtmf2raw, _dtmf, "dtmf6", "dtmf6_cnt")
1509
        codes.append(rs)
1510

    
1511
        dtm = make_dtmf(_dtmf.dtmf7, _dtmf.dtmf7_cnt)
1512
        rs = RadioSetting("dtmf_tab.dtmf7", "DTMF7 String",
1513
                          RadioSettingValueString(0, 7, dtm))
1514
        rs.set_apply_callback(my_dtmf2raw, _dtmf, "dtmf7", "dtmf7_cnt")
1515
        codes.append(rs)
1516

    
1517
        dtm = make_dtmf(_dtmf.dtmf8, _dtmf.dtmf8_cnt)
1518
        rs = RadioSetting("dtmf_tab.dtmf8", "DTMF8 String",
1519
                          RadioSettingValueString(0, 7, dtm))
1520
        rs.set_apply_callback(my_dtmf2raw, _dtmf, "dtmf8", "dtmf8_cnt")
1521
        codes.append(rs)
1522

    
1523
        return group       # END get_settings()
1524

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

    
1550
                    if element.has_apply_callback():
1551
                        LOG.debug("Using apply callback")
1552
                        element.run_apply_callback()
1553
                    elif element.value.get_mutable():
1554
                        LOG.debug("Setting %s = %s" % (setting, element.value))
1555
                        setattr(obj, setting, element.value)
1556
                except Exception:
1557
                    LOG.debug(element.get_name())
1558
                    raise
1559

    
1560
    @classmethod
1561
    def match_model(cls, filedata, filename):
1562
        match_size = False
1563
        match_model = False
1564

    
1565
        # Testing the file data size
1566
        if len(filedata) == MEM_SIZE + 8:
1567
            match_size = True
1568

    
1569
        # Testing the firmware model fingerprint
1570
        match_model = model_match(cls, filedata)
1571

    
1572
        if match_size and match_model:
1573
            return True
1574
        else:
1575
            return False
1576

    
1577

    
1578
class LT725UVUpper(LT725UV):
1579
    VARIANT = "Upper"
1580
    _vfo = "upper"
1581

    
1582

    
1583
class LT725UVLower(LT725UV):
1584
    VARIANT = "Lower"
1585
    _vfo = "lower"
1586

    
1587

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

    
1593

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

    
1599

    
1600
class Baojie218Upper(LT725UVUpper):
1601
    VENDOR = "Baojie"
1602
    MODEL = "BJ-218"
1603

    
1604

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

    
1609

    
1610
@directory.register
1611
class Baojie218(LT725UV):
1612
    """Baojie BJ-218"""
1613
    VENDOR = "Baojie"
1614
    MODEL = "BJ-218"
1615
    ALIASES = [Zastone, Hesenate, ]
1616

    
1617
    def get_sub_devices(self):
1618
        return [Baojie218Upper(self._mmap), Baojie218Lower(self._mmap)]
1619

    
1620

    
1621
class Baojie318Upper(LT725UVUpper):
1622
    VENDOR = "Baojie"
1623
    MODEL = "BJ-318"
1624

    
1625

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

    
1630

    
1631
@directory.register
1632
class Baojie318(LT725UV):
1633
    """Baojie BJ-318"""
1634
    VENDOR = "Baojie"
1635
    MODEL = "BJ-318"
1636

    
1637
    def get_sub_devices(self):
1638
        return [Baojie318Upper(self._mmap), Baojie318Lower(self._mmap)]
(4-4/7)