Project

General

Profile

New Model #10865 » baofeng_uv17.py

Latest version also in pull request - Sander van der Wel, 11/24/2023 08:45 AM

 
1
# Copyright 2023:
2
# * Sander van der Wel, <svdwel@icloud.com>
3
#
4
# This program is free software: you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation, either version 2 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU General Public License for more details.
13
#
14
# You should have received a copy of the GNU General Public License
15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16

    
17
import logging
18

    
19
from chirp import chirp_common, directory, memmap
20
from chirp.settings import RadioSettingGroup, RadioSetting, \
21
    RadioSettingValueBoolean, RadioSettingValueList, \
22
    RadioSettings, RadioSettingValueString
23
import struct
24
from chirp.drivers import baofeng_common, baofeng_uv17Pro
25
from chirp import errors, util
26

    
27
LOG = logging.getLogger(__name__)
28

    
29
DTMF_CHARS = "0123456789 *#ABCD"
30
STEPS = [2.5, 5.0, 6.25, 10.0, 12.5, 20.0, 25.0, 50.0]
31

    
32
LIST_AB = ["A", "B"]
33
LIST_BANDWIDTH = ["Wide", "Narrow"]
34
LIST_DTMFST = ["Off", "DT-ST", "ANI-ST", "DT+ANI"]
35
LIST_MODE = ["Name", "Frequency"]
36
LIST_OFF1TO9 = ["Off"] + list("123456789")
37
LIST_PONMSG = ["Full", "Message", "Voltage"]
38
LIST_PTTID = ["Off", "BOT", "EOT", "Both"]
39
LIST_SCODE = ["%s" % x for x in range(1, 16)]
40
LIST_SAVE = ["Off", "1:1", "1:2", "1:3", "1:4"]
41
LIST_TIMEOUT = ["Off"] + ["%s sec" % x for x in range(15, 615, 15)]
42
LIST_VOICE = ["Chinese", "English"]
43
LIST_BCKLIGHT_TIMEOUT = ["Always On"] + ["%s sec" % x for x in range(1, 11)]
44
LIST_SCANMODE = ["Time", "Carrier", "Search"]
45

    
46

    
47
def _make_read_frame(addr, length):
48
    """Pack the info in the header format"""
49
    frame = _make_frame(b'\x52', addr, length)
50
    # Return the data
51
    return frame
52

    
53

    
54
def _make_frame(cmd, addr, length, data=""):
55
    """Pack the info in the header format"""
56
    frame = struct.pack("<cH", cmd, addr) + struct.pack(">H", length)
57
    # add the data if set
58
    if len(data) != 0:
59
        frame += data
60
    # return the data
61
    return frame
62

    
63

    
64
def _sendmagic(radio, magic, response_len):
65
    baofeng_common._rawsend(radio, magic)
66
    baofeng_common._rawrecv(radio, response_len)
67

    
68

    
69
def _do_ident(radio):
70
    """Put the radio in PROGRAM mode & identify it"""
71
    # Flush input buffer
72
    baofeng_common._clean_buffer(radio)
73

    
74
    # Ident radio
75
    magic = radio._ident
76
    baofeng_common._rawsend(radio, magic)
77
    ack = baofeng_common._rawrecv(radio, 8)
78

    
79
    if not ack.startswith(radio._fingerprint):
80
        if ack:
81
            LOG.debug(repr(ack))
82
        raise errors.RadioError("Radio did not respond as expected.")
83

    
84
    return True
85

    
86

    
87
# Locations of memory may differ each time, so a mapping has to be made first
88
def _get_memory_map(radio):
89
    # Get memory map
90
    memory_map = []
91
    for addr in range(0x1FFF, 0x10FFF, 0x1000):
92
        frame = _make_frame(b"R", addr, 1)
93
        baofeng_common._rawsend(radio, frame)
94
        blocknr = ord(baofeng_common._rawrecv(radio, 6)[5:])
95
        blocknr = (blocknr >> 4 & 0xf) * 10 + (blocknr & 0xf)
96
        memory_map += [blocknr]
97
        _sendmagic(radio, b"\x06", 1)
98
    return memory_map
99

    
100

    
101
def _start_communication(radio):
102
    for magic in radio._magics:
103
        _sendmagic(radio, magic[0], magic[1])
104

    
105

    
106
def _download(radio):
107
    """Get the memory map"""
108

    
109
    # Put radio in program mode and identify it
110
    _do_ident(radio)
111

    
112
    data = b""
113
    # Start communication
114
    _start_communication(radio)
115

    
116
    # Get memory map
117
    memory_map = _get_memory_map(radio)
118

    
119
    # UI progress
120
    status = chirp_common.Status()
121
    status.cur = 0
122
    status.max = radio.MEM_TOTAL // radio.BLOCK_SIZE
123
    status.msg = "Cloning from radio..."
124
    radio.status_fn(status)
125

    
126
    for block_number in radio.BLOCK_ORDER:
127
        block_index = memory_map.index(block_number) + 1
128
        start_addr = block_index * 0x1000
129
        for addr in range(start_addr, start_addr + 0x1000, radio.BLOCK_SIZE):
130
            frame = _make_read_frame(addr, radio.BLOCK_SIZE)
131
            # DEBUG
132
            LOG.debug("Frame=" + util.hexprint(frame))
133

    
134
            # Sending the read request
135
            baofeng_common._rawsend(radio, frame)
136

    
137
            # Now we read data
138
            d = baofeng_common._rawrecv(radio, radio.BLOCK_SIZE + 5)
139

    
140
            LOG.debug("Response Data= " + util.hexprint(d))
141

    
142
            # Aggregate the data
143
            data += d[5:]
144

    
145
            # UI Update
146
            status.cur = len(data) // radio.BLOCK_SIZE
147
            status.msg = "Cloning from radio..."
148
            radio.status_fn(status)
149

    
150
            # ACK ACK
151
            _sendmagic(radio, b"\x06", 1)
152
    data += bytes(radio.MODEL, 'ascii')
153
    return data
154

    
155

    
156
def _upload(radio):
157
    """Upload procedure"""
158
    # Put radio in program mode and identify it
159
    _do_ident(radio)
160

    
161
    # Start communication
162
    _start_communication(radio)
163

    
164
    # Get memory map
165
    memory_map = _get_memory_map(radio)
166

    
167
    # UI progress
168
    status = chirp_common.Status()
169
    status.cur = 0
170
    status.max = radio.WRITE_MEM_TOTAL // radio.BLOCK_SIZE
171
    status.msg = "Cloning to radio..."
172
    radio.status_fn(status)
173

    
174
    for block_number in radio.BLOCK_ORDER:
175
        # Choose a block number is memory map is corrupt
176
        # This happens when te upload process is interrupted
177
        if block_number not in memory_map:
178
            memory_map[memory_map.index(165)] = block_number
179
        block_index = memory_map.index(block_number) + 1
180
        start_addr = block_index * 0x1000
181
        data_start_addr = radio.BLOCK_ORDER.index(block_number) * 0x1000
182
        for addr in range(start_addr, start_addr + 0x1000, radio.BLOCK_SIZE):
183

    
184
            # sending the data
185
            data_addr = data_start_addr + addr - start_addr
186
            data = radio.get_mmap()[data_addr:data_addr + radio.BLOCK_SIZE]
187
            frame = _make_frame(b"W", addr, radio.BLOCK_SIZE, data)
188
            baofeng_common._rawsend(radio, frame)
189

    
190
            # receiving the response
191
            ack = baofeng_common._rawrecv(radio, 1)
192
            if ack != b"\x06":
193
                msg = "Bad ack writing block 0x%04x" % addr
194
                raise errors.RadioError(msg)
195

    
196
            # UI Update
197
            status.cur = (data_addr) // radio.BLOCK_SIZE
198
            status.msg = "Cloning to radio..."
199
            radio.status_fn(status)
200

    
201

    
202
@directory.register
203
class UV17(baofeng_uv17Pro.UV17Pro):
204
    """Baofeng UV-17"""
205
    VENDOR = "Baofeng"
206
    MODEL = "UV-17"
207
    NEEDS_COMPAT_SERIAL = False
208

    
209
    BLOCK_ORDER = [16, 17, 18, 19,  24, 25, 26, 4, 6]
210

    
211
    MEM_TOTAL = 0x9000
212
    WRITE_MEM_TOTAL = 0x9000
213
    BLOCK_SIZE = 0x40
214
    BAUD_RATE = 57600
215
    _ident = b"PSEARCH"
216
    _magics = [[b"PASSSTA", 3], [b"SYSINFO", 1],
217
               [b"\x56\x00\x00\x0A\x0D", 13], [b"\x06", 1],
218
               [b"\x56\x00\x10\x0A\x0D", 13], [b"\x06", 1],
219
               [b"\x56\x00\x20\x0A\x0D", 13], [b"\x06", 1],
220
               [b"\x56\x00\x00\x00\x0A", 11], [b"\x06", 1],
221
               [b"\xFF\xFF\xFF\xFF\x0C\x55\x56\x31\x35\x39\x39\x39", 1],
222
               [b"\02", 8], [b"\x06", 1]]
223
    _fingerprint = b"\x06" + b"UV15999"
224

    
225
    _tri_band = False
226
    POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=1.00),
227
                    chirp_common.PowerLevel("High",  watts=5.00)]
228

    
229
    LENGTH_NAME = 11
230
    VALID_BANDS = [baofeng_uv17Pro.UV17Pro._vhf_range,
231
                   baofeng_uv17Pro.UV17Pro._uhf_range]
232
    SCODE_LIST = LIST_SCODE
233

    
234
    MEM_FORMAT = """
235
    #seekto 0x0030;
236
    struct {
237
      lbcd rxfreq[4];
238
      lbcd txfreq[4];
239
      u8 unused1:8;
240
      ul16 rxtone;
241
      ul16 txtone;
242
      u8 unknown1:1,
243
         bcl:1,
244
         pttid:2,
245
         unknown2:1,
246
         wide:1,
247
         lowpower:1,
248
         unknown:1;
249
      u8 scode:4,
250
         unknown3:3,
251
         scan:1;
252
      u8 unknown4:8;
253
    } memory[1002];
254

    
255
    #seekto 0x7000;
256
    struct {
257
      u8 bootscreen;
258
      u8 unknown0[15];
259
      char boottext1[10];
260
      u8 unknown1[6];
261
      char boottext2[10];
262
      u8 unknown2[22];
263
      u8 timeout;
264
      u8 squelch;
265
      u8 vox;
266
      u8 powersave: 4,
267
         unknown3:2,
268
         language: 1,
269
         voicealert: 1;
270
      u8 backlight_timeout:8;
271
      u8 beep:1,
272
         autolock:1,
273
         unknown4:1,
274
         tail:1,
275
         scanmode:2,
276
         dtmfst:2;
277
      u8 unknown5:1,
278
         dualstandby:1,
279
         roger:1,
280
         unknown6:3,
281
         fmenable:1,
282
         unknown7:1;
283
      u8 unknown8[9];
284
      u8 unknown9:6,
285
         chbdistype:1,
286
         chadistype:1;
287
    } settings;
288

    
289
    struct vfo {
290
      lbcd rxfreq[4];
291
      lbcd txfreq[4];
292
      u8 unused1:8;
293
      ul16 rxtone;
294
      ul16 txtone;
295
      u8 unknown1:1,
296
         bcl:1,
297
         pttid:2,
298
         unknown2:1,
299
         wide:1,
300
         lowpower:1,
301
         unknown:1;
302
      u8 scode:4,
303
         unknown3:3,
304
         scan:1;
305
      u8 unknown4:8;
306
    };
307

    
308
    #seekto 0x0010;
309
    struct {
310
      struct vfo a;
311
      struct vfo b;
312
    } vfo;
313

    
314
    #seekto 0x4000;
315
    struct {
316
      char name[11];
317
    } names[999];
318

    
319
    #seekto 0x8000;
320
    struct {
321
      u8 code[5];
322
    } pttid[15];
323

    
324
    struct {
325
      u8 unknown[5];
326
      u8 code[5];
327
    } aniid;
328

    
329
    """
330

    
331
    def sync_in(self):
332
        """Download from radio"""
333
        try:
334
            data = _download(self)
335
        except errors.RadioError:
336
            raise
337
        except Exception:
338
            LOG.exception('Unexpected error during download')
339
            raise errors.RadioError('Unexpected error communicating '
340
                                    'with the radio')
341
        self._mmap = memmap.MemoryMapBytes(data)
342

    
343
        self.process_mmap()
344

    
345
    def sync_out(self):
346
        """Upload to radio"""
347
        try:
348
            _upload(self)
349
        except errors.RadioError:
350
            raise
351
        except Exception:
352
            LOG.exception('Unexpected error during upload')
353
            raise errors.RadioError('Unexpected error communicating '
354
                                    'with the radio')
355

    
356
    def get_settings(self):
357
        """Translate the bit in the mem_struct into settings in the UI"""
358
        _mem = self._memobj
359
        basic = RadioSettingGroup("basic", "Basic Settings")
360
        dtmfe = RadioSettingGroup("dtmfe", "DTMF Encode Settings")
361
        top = RadioSettings(basic, dtmfe)
362
        print(_mem.settings)
363

    
364
        # Basic settings
365
        if _mem.settings.squelch > 0x09:
366
            val = 0x00
367
        else:
368
            val = _mem.settings.squelch
369
        rs = RadioSetting("settings.squelch", "Squelch",
370
                          RadioSettingValueList(
371
                              LIST_OFF1TO9, LIST_OFF1TO9[val]))
372
        basic.append(rs)
373

    
374
        if _mem.settings.timeout > 0x27:
375
            val = 0x03
376
        else:
377
            val = _mem.settings.timeout
378
        rs = RadioSetting("settings.timeout", "Timeout Timer",
379
                          RadioSettingValueList(
380
                              LIST_TIMEOUT, LIST_TIMEOUT[val]))
381
        basic.append(rs)
382

    
383
        if _mem.settings.language > 0x02:
384
            val = 0x01
385
        else:
386
            val = _mem.settings.language
387
        rs = RadioSetting("settings.language", "Voice Prompt",
388
                          RadioSettingValueList(
389
                              LIST_VOICE, LIST_VOICE[val]))
390
        basic.append(rs)
391

    
392
        rs = RadioSetting("settings.voicealert", "Voice Alert",
393
                          RadioSettingValueBoolean(_mem.settings.voicealert))
394
        basic.append(rs)
395

    
396
        if _mem.settings.bootscreen > 0x02:
397
            val = 0x00
398
        else:
399
            val = _mem.settings.bootscreen
400
        rs = RadioSetting("settings.bootscreen", "Bootscreen",
401
                          RadioSettingValueList(
402
                              LIST_PONMSG, LIST_PONMSG[val]))
403
        basic.append(rs)
404

    
405
        def _filter(name):
406
            filtered = ""
407
            for char in str(name):
408
                if char in chirp_common.CHARSET_ASCII:
409
                    filtered += char
410
                else:
411
                    filtered += " "
412
            return filtered
413

    
414
        rs = RadioSetting("settings.boottext1", "Power-On Message 1",
415
                          RadioSettingValueString(
416
                              0, 10, _filter(self._memobj.settings.boottext1)))
417
        basic.append(rs)
418

    
419
        rs = RadioSetting("settings.boottext2", "Power-On Message 2",
420
                          RadioSettingValueString(
421
                              0, 10, _filter(self._memobj.settings.boottext2)))
422
        basic.append(rs)
423

    
424
        if _mem.settings.powersave > 0x04:
425
            val = 0x00
426
        else:
427
            val = _mem.settings.powersave
428
        rs = RadioSetting("settings.powersave", "Battery Saver",
429
                          RadioSettingValueList(
430
                              LIST_SAVE, LIST_SAVE[val]))
431
        basic.append(rs)
432

    
433
        if _mem.settings.backlight_timeout > 0x0A:
434
            val = 0x00
435
        else:
436
            val = _mem.settings.backlight_timeout
437
        rs = RadioSetting("settings.backlight_timeout", "Backlight Timeout",
438
                          RadioSettingValueList(
439
                              LIST_BCKLIGHT_TIMEOUT,
440
                              LIST_BCKLIGHT_TIMEOUT[val]))
441
        basic.append(rs)
442

    
443
        rs = RadioSetting("settings.beep", "Beep",
444
                          RadioSettingValueBoolean(_mem.settings.beep))
445
        basic.append(rs)
446

    
447
        rs = RadioSetting("settings.autolock", "Auto Lock",
448
                          RadioSettingValueBoolean(_mem.settings.autolock))
449
        basic.append(rs)
450

    
451
        rs = RadioSetting("settings.roger", "Roger",
452
                          RadioSettingValueBoolean(_mem.settings.roger))
453
        basic.append(rs)
454

    
455
        rs = RadioSetting("settings.scanmode", "Scan Mode",
456
                          RadioSettingValueList(
457
                              LIST_SCANMODE,
458
                              LIST_SCANMODE[_mem.settings.scanmode]))
459
        basic.append(rs)
460

    
461
        rs = RadioSetting("settings.dtmfst", "DTMF Sidetone",
462
                          RadioSettingValueList(LIST_DTMFST, LIST_DTMFST[
463
                              _mem.settings.dtmfst]))
464
        basic.append(rs)
465

    
466
        rs = RadioSetting("settings.dualstandby", "Dual Watch",
467
                          RadioSettingValueBoolean(_mem.settings.dualstandby))
468
        basic.append(rs)
469

    
470
        rs = RadioSetting("settings.fmenable", "Enable FM radio",
471
                          RadioSettingValueBoolean(_mem.settings.fmenable))
472
        basic.append(rs)
473

    
474
        rs = RadioSetting("settings.chadistype", "Channel A display type",
475
                          RadioSettingValueList(
476
                              LIST_MODE, LIST_MODE[_mem.settings.chadistype]))
477
        basic.append(rs)
478

    
479
        rs = RadioSetting("settings.chbdistype", "Channel B display type",
480
                          RadioSettingValueList(
481
                              LIST_MODE, LIST_MODE[_mem.settings.chbdistype]))
482
        basic.append(rs)
483

    
484
        # DTMF settings
485
        def apply_code(setting, obj, length):
486
            code = []
487
            for j in range(0, length):
488
                try:
489
                    code.append(DTMF_CHARS.index(str(setting.value)[j]))
490
                except IndexError:
491
                    code.append(0xFF)
492
            obj.code = code
493

    
494
        for i in range(0, 15):
495
            _codeobj = self._memobj.pttid[i].code
496
            _code = "".join([
497
                DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F])
498
            val = RadioSettingValueString(0, 5, _code, False)
499
            val.set_charset(DTMF_CHARS)
500
            pttid = RadioSetting("pttid/%i.code" % i,
501
                                 "Signal Code %i" % (i + 1), val)
502
            pttid.set_apply_callback(apply_code, self._memobj.pttid[i], 5)
503
            dtmfe.append(pttid)
504

    
505
        _codeobj = self._memobj.aniid.code
506
        _code = "".join([
507
            DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F])
508
        val = RadioSettingValueString(0, 5, _code, False)
509
        val.set_charset(DTMF_CHARS)
510
        aniid = RadioSetting("aniid.code",
511
                             "ANI ID", val)
512
        aniid.set_apply_callback(apply_code, self._memobj.aniid, 5)
513
        dtmfe.append(aniid)
514

    
515
        return top
516

    
517
    def get_features(self):
518
        """Get the radio's features"""
519

    
520
        rf = chirp_common.RadioFeatures()
521
        rf.has_settings = True
522
        rf.has_bank = False
523
        rf.has_tuning_step = False
524
        rf.can_odd_split = True
525
        rf.has_name = True
526
        rf.has_offset = True
527
        rf.has_mode = True
528
        rf.has_dtcs = True
529
        rf.has_rx_dtcs = True
530
        rf.has_dtcs_polarity = True
531
        rf.has_ctone = True
532
        rf.has_cross = True
533
        rf.valid_modes = self.MODES
534
        rf.valid_characters = self.VALID_CHARS
535
        rf.valid_name_length = self.LENGTH_NAME
536
        if self._gmrs:
537
            rf.valid_duplexes = ["", "+", "off"]
538
        else:
539
            rf.valid_duplexes = ["", "-", "+", "split", "off"]
540
        rf.valid_tmodes = ['', 'Tone', 'TSQL', 'DTCS', 'Cross']
541
        rf.valid_cross_modes = [
542
            "Tone->Tone",
543
            "DTCS->",
544
            "->DTCS",
545
            "Tone->DTCS",
546
            "DTCS->Tone",
547
            "->Tone",
548
            "DTCS->DTCS"]
549
        rf.valid_skips = self.SKIP_VALUES
550
        rf.valid_dtcs_codes = self.DTCS_CODES
551
        rf.memory_bounds = (0, 998)
552
        rf.valid_power_levels = self.POWER_LEVELS
553
        rf.valid_bands = self.VALID_BANDS
554
        rf.valid_tuning_steps = STEPS
555

    
556
        return rf
557

    
558
    def decode_tone(self, val):
559
        pol = "N"
560
        mode = ""
561
        if val in [0, 0xFFFF]:
562
            xval = 0
563
        elif (val & 0x8000) > 0:
564
            mode = "DTCS"
565
            xval = (val & 0x0f) + (val >> 4 & 0xf)\
566
                * 10 + (val >> 8 & 0xf) * 100
567
            if (val & 0xC000) == 0xC000:
568
                pol = "R"
569
        else:
570
            mode = "Tone"
571
            xval = int((val & 0x0f) + (val >> 4 & 0xf) * 10 +
572
                       (val >> 8 & 0xf) * 100 + (val >> 12 & 0xf)
573
                       * 1000) / 10.0
574

    
575
        return mode, xval, pol
576

    
577
    def get_memory(self, number):
578
        offset = 0
579
        # Skip 16 bytes at memory block boundary
580
        # This is because these 16 bytes contain memory page numbers
581
        if number >= 252:
582
            offset += 1
583
        if number >= 507:
584
            offset += 1
585
        if number >= 762:
586
            offset += 1
587

    
588
        _mem = self._memobj.memory[number + offset]
589
        _nam = self._memobj.names[number]
590

    
591
        mem = chirp_common.Memory()
592
        mem.number = number
593

    
594
        if _mem.get_raw()[0] == 255:
595
            mem.empty = True
596
            return mem
597

    
598
        mem.freq = int(_mem.rxfreq) * 10
599

    
600
        if self._is_txinh(_mem):
601
            # TX freq not set
602
            mem.duplex = "off"
603
            mem.offset = 0
604
        else:
605
            # TX freq set
606
            offset = (int(_mem.txfreq) * 10) - mem.freq
607
            if offset != 0:
608
                if baofeng_common._split(self.get_features(), mem.freq, int(
609
                          _mem.txfreq) * 10):
610
                    mem.duplex = "split"
611
                    mem.offset = int(_mem.txfreq) * 10
612
                elif offset < 0:
613
                    mem.offset = abs(offset)
614
                    mem.duplex = "-"
615
                elif offset > 0:
616
                    mem.offset = offset
617
                    mem.duplex = "+"
618
            else:
619
                mem.offset = 0
620

    
621
        for char in _nam.name:
622
            if (str(char) == "\xFF") | (str(char) == "\x00"):
623
                char = " "  # The OEM software may have 0xFF mid-name
624
            mem.name += str(char)
625
        mem.name = mem.name.rstrip()
626

    
627
        txtone = self.decode_tone(_mem.txtone)
628
        rxtone = self.decode_tone(_mem.rxtone)
629
        chirp_common.split_tone_decode(mem, txtone, rxtone)
630

    
631
        if not _mem.scan:
632
            mem.skip = "S"
633

    
634
        levels = self.POWER_LEVELS
635
        try:
636
            mem.power = levels[_mem.lowpower]
637
        except IndexError:
638
            LOG.error("Radio reported invalid power level %s (in %s)" %
639
                      (_mem.power, levels))
640
            mem.power = levels[0]
641

    
642
        mem.mode = _mem.wide and "FM" or "NFM"
643

    
644
        mem.extra = RadioSettingGroup("Extra", "extra")
645

    
646
        rs = RadioSetting("bcl", "BCL",
647
                          RadioSettingValueBoolean(_mem.bcl))
648
        mem.extra.append(rs)
649

    
650
        rs = RadioSetting("pttid", "PTT ID",
651
                          RadioSettingValueList(self.PTTID_LIST,
652
                                                self.PTTID_LIST[_mem.pttid]))
653
        mem.extra.append(rs)
654

    
655
        rs = RadioSetting("scode", "S-CODE",
656
                          RadioSettingValueList(self.SCODE_LIST,
657
                                                self.SCODE_LIST[
658
                                                    _mem.scode - 1]))
659
        mem.extra.append(rs)
660

    
661
        return mem
662

    
663
    def encode_tone(self, memtone, mode, tone, pol):
664
        if mode == "Tone":
665
            xtone = str(int(tone * 10)).rjust(4, '0')
666
            memtone.set_value((int(xtone[0]) << 12) + (int(xtone[1]) << 8) +
667
                              (int(xtone[2]) << 4) + int(xtone[3]))
668
        elif mode == "TSQL":
669
            xtone = str(int(tone * 10)).rjust(4, '0')
670
            memtone.set_value((int(tone[0]) << 12) + (int(xtone[1]) << 8) +
671
                              (int(xtone[2]) << 4) + int(xtone[3]))
672
        elif mode == "DTCS":
673
            xtone = str(int(tone)).rjust(4, '0')
674
            memtone.set_value((0x8000 + (int(xtone[0]) << 12) +
675
                               (int(xtone[1]) << 8) + (int(xtone[2]) << 4) +
676
                               int(xtone[3])))
677
        else:
678
            memtone.set_value(0)
679

    
680
        if mode == "DTCS" and pol == "R":
681
            memtone.set_value(memtone + 0x4000)
682

    
683
    def set_memory(self, mem):
684
        offset = 0
685
        # skip 16 bytes at memory block boundary
686
        if mem.number >= 252:
687
            offset += 1
688
        if mem.number >= 507:
689
            offset += 1
690
        if mem.number >= 762:
691
            offset += 1
692
        _mem = self._memobj.memory[mem.number + offset]
693
        _nam = self._memobj.names[mem.number]
694

    
695
        if mem.empty:
696
            _mem.set_raw("\xff" * 16)
697
            return
698

    
699
        _mem.set_raw("\x00" * 16)
700
        _mem.rxfreq = mem.freq / 10
701

    
702
        if mem.duplex == "off":
703
            for i in range(0, 4):
704
                _mem.txfreq[i].set_raw("\xFF")
705
        elif mem.duplex == "split":
706
            _mem.txfreq = mem.offset / 10
707
        elif mem.duplex == "+":
708
            _mem.txfreq = (mem.freq + mem.offset) / 10
709
        elif mem.duplex == "-":
710
            _mem.txfreq = (mem.freq - mem.offset) / 10
711
        else:
712
            _mem.txfreq = mem.freq / 10
713

    
714
        _namelength = self.get_features().valid_name_length
715
        for i in range(_namelength):
716
            try:
717
                _nam.name[i] = mem.name[i]
718
            except IndexError:
719
                _nam.name[i] = "\xFF"
720

    
721
        ((txmode, txtone, txpol), (rxmode, rxtone, rxpol)) = \
722
            chirp_common.split_tone_encode(mem)
723
        self.encode_tone(_mem.txtone, txmode, txtone, txpol)
724
        self.encode_tone(_mem.rxtone, rxmode, rxtone, rxpol)
725

    
726
        _mem.scan = mem.skip != "S"
727
        _mem.wide = mem.mode == "FM"
728

    
729
        if mem.power:
730
            _mem.lowpower = self.POWER_LEVELS.index(mem.power)
731
        else:
732
            _mem.lowpower = 0
733

    
734
        # extra settings
735
        if len(mem.extra) > 0:
736
            # there are setting, parse
737
            for setting in mem.extra:
738
                if setting.get_name() == "scode":
739
                    setattr(_mem, setting.get_name(), str(int(setting.value)
740
                                                          + 1))
741
                else:
742
                    setattr(_mem, setting.get_name(), setting.value)
743
        else:
744
            # there are no extra settings, load defaults
745
            _mem.bcl = 0
746
            _mem.pttid = 0
747
            _mem.scode = 0
(3-3/3)