Project

General

Profile

New Model #9793 » baofeng_uv17.py

88598f92 base - Dan Smith, 02/09/2024 06:45 PM

 
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
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
LIST_DTMFST = ["Off", "DT-ST", "ANI-ST", "DT+ANI"]
29
LIST_SAVE = ["Off", "1:1", "1:2", "1:3", "1:4"]
30
LIST_SCANMODE = ["Time", "Carrier", "Search"]
31

    
32

    
33
def _sendmagic(radio, magic, response_len):
34
    baofeng_common._rawsend(radio, magic)
35
    baofeng_common._rawrecv(radio, response_len)
36

    
37

    
38
# Locations of memory may differ each time, so a mapping has to be made first
39
def _get_memory_map(radio):
40
    # Get memory map
41
    memory_map = []
42
    for addr in range(0x1FFF, 0x10FFF, 0x1000):
43
        frame = radio._make_frame(b"R", addr, 1)
44
        baofeng_common._rawsend(radio, frame)
45
        blocknr = ord(baofeng_common._rawrecv(radio, 6)[5:])
46
        blocknr = (blocknr >> 4 & 0xf) * 10 + (blocknr & 0xf)
47
        memory_map += [blocknr]
48
        _sendmagic(radio, b"\x06", 1)
49
    return memory_map
50

    
51

    
52
def _download(radio):
53
    """Get the memory map"""
54
    baofeng_uv17Pro._do_ident(radio)
55

    
56
    data = b""
57
    _start_communication(radio)
58

    
59
    memory_map = _get_memory_map(radio)
60

    
61
    status = chirp_common.Status()
62
    status.cur = 0
63
    status.max = radio.MEM_TOTAL // radio.BLOCK_SIZE
64
    status.msg = "Cloning from radio..."
65
    radio.status_fn(status)
66

    
67
    for block_number in radio.BLOCK_ORDER:
68
        block_index = memory_map.index(block_number) + 1
69
        start_addr = block_index * 0x1000
70
        for addr in range(start_addr, start_addr + 0x1000,
71
                          radio.BLOCK_SIZE):
72
            frame = radio._make_read_frame(addr, radio.BLOCK_SIZE)
73
            # DEBUG
74
            LOG.debug("Frame=" + util.hexprint(frame))
75

    
76
            baofeng_common._rawsend(radio, frame)
77

    
78
            d = baofeng_common._rawrecv(radio, radio.BLOCK_SIZE + 5)
79

    
80
            LOG.debug("Response Data= " + util.hexprint(d))
81

    
82
            data += d[5:]
83

    
84
            status.cur = len(data) // radio.BLOCK_SIZE
85
            status.msg = "Cloning from radio..."
86
            radio.status_fn(status)
87

    
88
            _sendmagic(radio, b"\x06", 1)
89
    data += bytes(radio.MODEL, 'ascii')
90
    return data
91

    
92

    
93
def _upload(radio):
94
    """Upload procedure"""
95
    baofeng_uv17Pro._do_ident(radio)
96

    
97
    _start_communication(radio)
98

    
99
    memory_map = _get_memory_map(radio)
100

    
101
    status = chirp_common.Status()
102
    status.cur = 0
103
    status.max = radio.WRITE_MEM_TOTAL // radio.BLOCK_SIZE
104
    status.msg = "Cloning to radio..."
105
    radio.status_fn(status)
106

    
107
    for block_number in radio.BLOCK_ORDER:
108
        # Choose a block number is memory map is corrupt
109
        # This happens when te upload process is interrupted
110
        if block_number not in memory_map:
111
            memory_map[memory_map.index(165)] = block_number
112
        block_index = memory_map.index(block_number) + 1
113
        start_addr = block_index * 0x1000
114
        data_start_addr = radio.BLOCK_ORDER.index(block_number) * 0x1000
115
        for addr in range(start_addr, start_addr + 0x1000,
116
                          radio.BLOCK_SIZE):
117

    
118
            # sending the data
119
            data_addr = data_start_addr + addr - start_addr
120
            data = radio.get_mmap()[data_addr:data_addr + radio.BLOCK_SIZE]
121
            frame = radio._make_frame(b"W", addr, radio.BLOCK_SIZE, data)
122
            baofeng_common._rawsend(radio, frame)
123

    
124
            # receiving the response
125
            ack = baofeng_common._rawrecv(radio, 1)
126
            if ack != b"\x06":
127
                msg = "Bad ack writing block 0x%04x" % addr
128
                raise errors.RadioError(msg)
129

    
130
            # UI Update
131
            status.cur = (data_addr) // radio.BLOCK_SIZE
132
            status.msg = "Cloning to radio..."
133
            radio.status_fn(status)
134

    
135

    
136
def _start_communication(radio):
137
    for magic in radio._magics:
138
        _sendmagic(radio, magic[0], magic[1])
139

    
140

    
141
@directory.register
142
class UV17(baofeng_uv17Pro.UV17Pro):
143
    """Baofeng UV-17"""
144
    VENDOR = "Baofeng"
145
    MODEL = "UV-17"
146
    NEEDS_COMPAT_SERIAL = False
147

    
148
    download_function = _download
149
    upload_function = _upload
150

    
151
    BLOCK_ORDER = [16, 17, 18, 19,  24, 25, 26, 4, 6]
152
    MEM_TOTAL = 0x9000
153
    WRITE_MEM_TOTAL = 0x9000
154
    BLOCK_SIZE = 0x40
155
    BAUD_RATE = 57600
156
    _magic = b"PSEARCH"
157
    _magic_response_length = 8
158
    _magics = [[b"PASSSTA", 3], [b"SYSINFO", 1],
159
               [b"\x56\x00\x00\x0A\x0D", 13], [b"\x06", 1],
160
               [b"\x56\x00\x10\x0A\x0D", 13], [b"\x06", 1],
161
               [b"\x56\x00\x20\x0A\x0D", 13], [b"\x06", 1],
162
               [b"\x56\x00\x00\x00\x0A", 11], [b"\x06", 1],
163
               [b"\xFF\xFF\xFF\xFF\x0C\x55\x56\x31\x35\x39\x39\x39", 1],
164
               [b"\02", 8], [b"\x06", 1]]
165
    _fingerprint = b"\x06" + b"UV15999"
166
    _scode_offset = 1
167

    
168
    _tri_band = False
169
    POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=1.00),
170
                    chirp_common.PowerLevel("High",  watts=5.00)]
171

    
172
    LENGTH_NAME = 11
173
    VALID_BANDS = [baofeng_uv17Pro.UV17Pro._vhf_range,
174
                   baofeng_uv17Pro.UV17Pro._uhf_range]
175
    SCODE_LIST = ["%s" % x for x in range(1, 16)]
176
    SQUELCH_LIST = ["Off"] + list("123456789")
177
    LIST_POWERON_DISPLAY_TYPE = ["Full", "Message", "Voltage"]
178
    LIST_TIMEOUT = ["Off"] + ["%s sec" % x for x in range(15, 615, 15)]
179
    LIST_VOICE = ["Chinese", "English"]
180
    LIST_BACKLIGHT_TIMER = ["Always On"] + ["%s sec" % x for x in range(1, 11)]
181
    LIST_MODE = ["Name", "Frequency"]
182
    CHANNELS = 999
183

    
184
    MEM_FORMAT = """
185
    #seekto 0x0030;
186
    struct channel {
187
      lbcd rxfreq[4];
188
      lbcd txfreq[4];
189
      u8 unused1;
190
      ul16 rxtone;
191
      ul16 txtone;
192
      u8 unknown1:1,
193
         bcl:1,
194
         pttid:2,
195
         unknown2:1,
196
         wide:1,
197
         lowpower:1,
198
         unknown:1;
199
      u8 scode:4,
200
         unknown3:3,
201
         scan:1;
202
      u8 unknown4;
203
    };
204

    
205
    struct {
206
      struct channel mem[252];
207
    } mem1;
208

    
209
    #seek 0x10;
210
    struct {
211
      struct channel mem[255];
212
    } mem2;
213

    
214
    #seek 0x10;
215
    struct {
216
      struct channel mem[255];
217
    } mem3;
218

    
219
    #seek 0x10;
220
    struct {
221
      struct channel mem[237];
222
    } mem4;
223

    
224
    #seekto 0x7000;
225
    struct {
226
      u8 powerondistype;
227
      u8 unknown0[15];
228
      char boottext1[10];
229
      u8 unknown1[6];
230
      char boottext2[10];
231
      u8 unknown2[22];
232
      u8 tot;
233
      u8 squelch;
234
      u8 vox;
235
      u8 powersave: 4,
236
         unknown3:2,
237
         voice: 1,
238
         voicesw: 1;
239
      u8 backlight;
240
      u8 beep:1,
241
         autolock:1,
242
         unknown4:1,
243
         tail:1,
244
         scanmode:2,
245
         dtmfst:2;
246
      u8 unknown5:1,
247
         dualstandby:1,
248
         roger:1,
249
         unknown6:3,
250
         fmenable:1,
251
         unknown7:1;
252
      u8 unknown8[9];
253
      u8 unknown9:6,
254
         chbdistype:1,
255
         chadistype:1;
256
    } settings;
257

    
258
    struct vfo {
259
      lbcd rxfreq[4];
260
      lbcd txfreq[4];
261
      u8 unused1;
262
      ul16 rxtone;
263
      ul16 txtone;
264
      u8 unknown1:1,
265
         bcl:1,
266
         pttid:2,
267
         unknown2:1,
268
         wide:1,
269
         lowpower:1,
270
         unknown:1;
271
      u8 scode:4,
272
         unknown3:3,
273
         scan:1;
274
      u8 unknown4;
275
    };
276

    
277
    #seekto 0x0010;
278
    struct {
279
      struct vfo a;
280
      struct vfo b;
281
    } vfo;
282

    
283
    #seekto 0x4000;
284
    struct {
285
      char name[11];
286
    } names[999];
287

    
288
    #seekto 0x8000;
289
    struct {
290
      u8 code[5];
291
    } pttid[15];
292

    
293
    struct {
294
      u8 unknown[5];
295
      u8 code[5];
296
    } ani;
297

    
298
    """
299

    
300
    def _make_frame(self, cmd, addr, length, data=""):
301
        """Pack the info in the header format"""
302
        frame = struct.pack("<cH", cmd, addr) + struct.pack(">H", length)
303
        # add the data if set
304
        if len(data) != 0:
305
            frame += data
306
        # return the data
307
        return frame
308

    
309
    def get_settings(self):
310
        """Translate the bit in the mem_struct into settings in the UI"""
311
        _mem = self._memobj
312
        basic = RadioSettingGroup("basic", "Basic Settings")
313
        dtmfe = RadioSettingGroup("dtmfe", "DTMF Encode Settings")
314
        top = RadioSettings(basic, dtmfe)
315

    
316
        self.get_settings_common_basic(basic, _mem)
317

    
318
        def _filter(name):
319
            filtered = ""
320
            for char in str(name):
321
                if char in chirp_common.CHARSET_ASCII:
322
                    filtered += char
323
                else:
324
                    filtered += " "
325
            return filtered
326

    
327
        rs = RadioSetting("settings.boottext1", "Power-On Message 1",
328
                          RadioSettingValueString(
329
                              0, 10, _filter(self._memobj.settings.boottext1)))
330
        basic.append(rs)
331

    
332
        rs = RadioSetting("settings.boottext2", "Power-On Message 2",
333
                          RadioSettingValueString(
334
                              0, 10, _filter(self._memobj.settings.boottext2)))
335
        basic.append(rs)
336

    
337
        if _mem.settings.powersave > 0x04:
338
            val = 0x00
339
        else:
340
            val = _mem.settings.powersave
341
        rs = RadioSetting("settings.powersave", "Battery Saver",
342
                          RadioSettingValueList(
343
                              LIST_SAVE, LIST_SAVE[val]))
344
        basic.append(rs)
345

    
346
        rs = RadioSetting("settings.scanmode", "Scan Mode",
347
                          RadioSettingValueList(
348
                              LIST_SCANMODE,
349
                              LIST_SCANMODE[_mem.settings.scanmode]))
350
        basic.append(rs)
351

    
352
        rs = RadioSetting("settings.dtmfst", "DTMF Sidetone",
353
                          RadioSettingValueList(LIST_DTMFST, LIST_DTMFST[
354
                              _mem.settings.dtmfst]))
355
        basic.append(rs)
356

    
357
        rs = RadioSetting("settings.fmenable", "Enable FM radio",
358
                          RadioSettingValueBoolean(_mem.settings.fmenable))
359
        basic.append(rs)
360

    
361
        self.get_settings_common_dtmf(dtmfe)
362

    
363
        return top
364

    
365
    def decode_tone(self, val):
366
        pol = "N"
367
        mode = ""
368
        if val in [0, 0xFFFF]:
369
            xval = 0
370
        elif (val & 0x8000) > 0:
371
            mode = "DTCS"
372
            xval = (val & 0x0f) + (val >> 4 & 0xf)\
373
                * 10 + (val >> 8 & 0xf) * 100
374
            if (val & 0xC000) == 0xC000:
375
                pol = "R"
376
        else:
377
            mode = "Tone"
378
            xval = int((val & 0x0f) + (val >> 4 & 0xf) * 10 +
379
                       (val >> 8 & 0xf) * 100 + (val >> 12 & 0xf)
380
                       * 1000) / 10.0
381

    
382
        return mode, xval, pol
383

    
384
    def get_raw_memory(self, number):
385
        # The flash memory contains page_numbers
386
        # This is probably to do wear leveling on the memory
387
        # These numbers are needed, but make the channel memory
388
        # not continuous.
389
        number = number - 1
390
        if number >= 762:
391
            _mem = self._memobj.mem4.mem[number - 762]
392
            return _mem
393
        if number >= 507:
394
            _mem = self._memobj.mem3.mem[number - 507]
395
            return _mem
396
        if number >= 252:
397
            _mem = self._memobj.mem2.mem[number - 252]
398
            return _mem
399
        _mem = self._memobj.mem1.mem[number]
400
        return _mem
401

    
402
    def get_memory(self, number):
403
        _mem = self.get_raw_memory(number)
404
        _nam = self._memobj.names[number - 1]
405

    
406
        mem = chirp_common.Memory()
407
        mem.number = number
408

    
409
        self.get_memory_common(_mem, _nam.name, mem)
410

    
411
        return mem
412

    
413
    def encode_tone(self, memtone, mode, tone, pol):
414
        if mode == "Tone":
415
            xtone = '%04i' % (tone * 10)
416
            memtone.set_value((int(xtone[0]) << 12) + (int(xtone[1]) << 8) +
417
                              (int(xtone[2]) << 4) + int(xtone[3]))
418
        elif mode == "TSQL":
419
            xtone = '%04i' % (tone * 10)
420
            memtone.set_value((int(tone[0]) << 12) + (int(xtone[1]) << 8) +
421
                              (int(xtone[2]) << 4) + int(xtone[3]))
422
        elif mode == "DTCS":
423
            xtone = str(int(tone)).rjust(4, '0')
424
            memtone.set_value((0x8000 + (int(xtone[0]) << 12) +
425
                               (int(xtone[1]) << 8) + (int(xtone[2]) << 4) +
426
                               int(xtone[3])))
427
        else:
428
            memtone.set_value(0)
429

    
430
        if mode == "DTCS" and pol == "R":
431
            memtone.set_value(memtone + 0x4000)
432

    
433
    def set_memory(self, mem):
434
        _mem = self.get_raw_memory(mem.number)
435
        _nam = self._memobj.names[mem.number - 1]
436

    
437
        if mem.empty:
438
            _mem.set_raw(b"\xff" * 16)
439
            return
440

    
441
        _mem.set_raw(b"\x00" * 16)
442

    
443
        _namelength = self.get_features().valid_name_length
444
        _nam.name = mem.name.ljust(_namelength, '\xFF')
445

    
446
        self.set_memory_common(mem, _mem)
(6-6/12)