Project

General

Profile

Bug #10916 » ft2800_initialize_unknowns.py

Jim Unroe, 10/29/2023 09:37 AM

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

    
16
import time
17
import logging
18

    
19
from chirp import util, memmap, chirp_common, bitwise, directory, errors
20
from chirp.drivers.yaesu_clone import YaesuCloneModeRadio
21

    
22
LOG = logging.getLogger(__name__)
23

    
24
CHUNK_SIZE = 16
25

    
26

    
27
def _send(s, data):
28
    for i in range(0, len(data), CHUNK_SIZE):
29
        chunk = data[i:i+CHUNK_SIZE]
30
        s.write(chunk)
31
        echo = s.read(len(chunk))
32
        if chunk != echo:
33
            raise Exception("Failed to read echo chunk")
34

    
35

    
36
# The IDBLOCK is the first thing sent during an upload or download
37
# and indicates the radio subtype:
38
#   USA Unmodified:            b"\x0c\x01\x41\x33\x35\x02\x00\xb8"
39
#   USA With extended TX mod:  b"\x0c\x01\x41\x33\x35\x03\x00\xb9"
40
SUPPORTED_IDBLOCKS = [b"\x0c\x01\x41\x33\x35\x02\x00\xb8",
41
                      b"\x0c\x01\x41\x33\x35\x03\x00\xb9"]
42
TRAILER = b"\x0c\x02\x41\x33\x35\x00\x00\xb7"
43
ACK = b"\x0C\x06\x00"
44

    
45

    
46
def _download(radio):
47
    data = b""
48
    attempts = 30
49
    for _i in range(0, attempts):
50
        data = radio.pipe.read(8)
51
        if data in SUPPORTED_IDBLOCKS:
52
            radio.subtype = data
53
            break
54
        LOG.debug('Download attempt %i received %i: %s',
55
                  _i, len(data), util.hexprint(data))
56
        if radio.status_fn:
57
            status = chirp_common.Status()
58
            status.max = 1
59
            status.cur = 0
60
            status.msg = "Waiting for radio (%i)" % (
61
                attempts - (_i + 1))
62
            radio.status_fn(status)
63

    
64
    LOG.debug("Header:\n%s" % util.hexprint(data))
65

    
66
    if len(data) != 8:
67
        raise Exception("Failed to read header")
68

    
69
    _send(radio.pipe, ACK)
70

    
71
    data = b""
72

    
73
    while len(data) < radio._block_sizes[1]:
74
        time.sleep(0.1)
75
        chunk = radio.pipe.read(38)
76
        LOG.debug("Got: %i:\n%s" % (len(chunk), util.hexprint(chunk)))
77
        if len(chunk) == 8:
78
            LOG.debug("END?")
79
        elif len(chunk) != 38:
80
            LOG.debug("Should fail?")
81
            break
82
            # raise Exception("Failed to get full data block")
83
        else:
84
            cs = 0
85
            for byte in chunk[:-1]:
86
                cs += byte
87
            if chunk[-1] != (cs & 0xFF):
88
                raise Exception("Block failed checksum!")
89

    
90
            data += chunk[5:-1]
91

    
92
        _send(radio.pipe, ACK)
93
        if radio.status_fn:
94
            status = chirp_common.Status()
95
            status.max = radio._block_sizes[1]
96
            status.cur = len(data)
97
            status.msg = "Cloning from radio"
98
            radio.status_fn(status)
99

    
100
    LOG.debug("Total: %i" % len(data))
101

    
102
    return memmap.MemoryMapBytes(data)
103

    
104

    
105
def _upload(radio):
106
    for _i in range(0, 10):
107
        data = radio.pipe.read(256)
108
        if not data:
109
            break
110
        LOG.debug("What is this garbage?\n%s" % util.hexprint(data))
111

    
112
    _send(radio.pipe, radio.subtype)
113
    time.sleep(1)
114
    ack = radio.pipe.read(300)
115
    LOG.debug("Ack was (%i):\n%s" % (len(ack), util.hexprint(ack)))
116
    if ack != ACK:
117
        raise Exception("Radio did not ack ID")
118

    
119
    block = 0
120
    while block < (radio.get_memsize() // 32):
121
        data = b"\x0C\x03\x00\x00" + bytes([block])
122
        data += radio.get_mmap()[block*32:(block+1)*32]
123
        cs = 0
124
        for byte in data:
125
            cs += byte
126
        data += bytes([cs & 0xFF])
127

    
128
        LOG.debug("Writing block %i:\n%s" % (block, util.hexprint(data)))
129

    
130
        _send(radio.pipe, data)
131
        time.sleep(0.1)
132
        ack = radio.pipe.read(3)
133
        if ack != ACK:
134
            raise Exception("Radio did not ack block %i" % block)
135

    
136
        if radio.status_fn:
137
            status = chirp_common.Status()
138
            status.max = radio._block_sizes[1]
139
            status.cur = block * 32
140
            status.msg = "Cloning to radio"
141
            radio.status_fn(status)
142
        block += 1
143

    
144
    _send(radio.pipe, TRAILER)
145

    
146

    
147
MEM_FORMAT = """
148
struct {
149
  bbcd freq[4];
150
  u8 unknown1[4];
151
  bbcd offset[2];
152
  u8 unknown2[2];
153
  u8 pskip:1,
154
     skip:1,
155
     unknown3:1,
156
     isnarrow:1,
157
     power:2,
158
     duplex:2;
159
  u8 unknown4:6,
160
     tmode:2;
161
  u8 tone;
162
  u8 dtcs;
163
} memory[200];
164

    
165
#seekto 0x0E00;
166
struct {
167
  char name[6];
168
} names[200];
169
"""
170

    
171
MODES = ["FM", "NFM"]
172
TMODES = ["", "Tone", "TSQL", "DTCS"]
173
DUPLEX = ["", "-", "+", ""]
174
POWER_LEVELS = [chirp_common.PowerLevel("Hi", watts=65),
175
                chirp_common.PowerLevel("Mid", watts=25),
176
                chirp_common.PowerLevel("Low2", watts=10),
177
                chirp_common.PowerLevel("Low1", watts=5),
178
                ]
179
CHARSET = chirp_common.CHARSET_UPPER_NUMERIC + "()+-=*/???|_"
180

    
181

    
182
@directory.register
183
class FT2800Radio(YaesuCloneModeRadio):
184
    """Yaesu FT-2800"""
185
    VENDOR = "Yaesu"
186
    MODEL = "FT-2800M"
187

    
188
    _block_sizes = [8, 7680]
189
    _memsize = 7680
190

    
191
    @property
192
    def subtype(self):
193
        # If our image is from before the subtype was stashed, assume
194
        # the default unmodified US ID block
195
        return bytes(self.metadata.get('subtype_idblock',
196
                                       SUPPORTED_IDBLOCKS[0]))
197

    
198
    @subtype.setter
199
    def subtype(self, value):
200
        self.metadata = {'subtype_idblock': [x for x in value]}
201

    
202
    @classmethod
203
    def get_prompts(cls):
204
        rp = chirp_common.RadioPrompts()
205
        rp.pre_download = _(
206
            "1. Turn radio off.\n"
207
            "2. Connect cable\n"
208
            "3. Press and hold in the MHz, Low, and D/MR keys on the radio "
209
            "while turning it on\n"
210
            "4. Radio is in clone mode when TX/RX is flashing\n"
211
            "5. <b>After clicking OK</b>, "
212
            "press the MHz key on the radio to send"
213
            " image.\n"
214
            "    (\"TX\" will appear on the LCD). \n")
215
        rp.pre_upload = _(
216
            "1. Turn radio off.\n"
217
            "2. Connect cable\n"
218
            "3. Press and hold in the MHz, Low, and D/MR keys on the radio "
219
            "while turning it on\n"
220
            "4. Radio is in clone mode when TX/RX is flashing\n"
221
            "5. Press the Low key on the radio "
222
            "(\"RX\" will appear on the LCD).\n"
223
            "6. Click OK.")
224
        return rp
225

    
226
    def get_features(self):
227
        rf = chirp_common.RadioFeatures()
228

    
229
        rf.memory_bounds = (0, 199)
230

    
231
        rf.has_ctone = False
232
        rf.has_tuning_step = False
233
        rf.has_dtcs_polarity = False
234
        rf.has_bank = False
235

    
236
        rf.valid_tuning_steps = [5.0, 10.0, 12.5, 15.0,
237
                                 20.0, 25.0, 50.0, 100.0]
238
        rf.valid_modes = MODES
239
        rf.valid_tmodes = TMODES
240
        rf.valid_bands = [(137000000, 174000000)]
241
        rf.valid_power_levels = POWER_LEVELS
242
        rf.valid_duplexes = DUPLEX
243
        rf.valid_skips = ["", "S", "P"]
244
        rf.valid_name_length = 6
245
        rf.valid_characters = CHARSET
246

    
247
        return rf
248

    
249
    def sync_in(self):
250
        self.pipe.parity = "E"
251
        self.pipe.timeout = 1
252
        start = time.time()
253
        try:
254
            self._mmap = _download(self)
255
        except errors.RadioError:
256
            raise
257
        except Exception as e:
258
            LOG.exception('Failed download')
259
            raise errors.RadioError("Failed to communicate with radio: %s" % e)
260
        LOG.info("Downloaded in %.2f sec" % (time.time() - start))
261
        self.process_mmap()
262

    
263
    def sync_out(self):
264
        self.pipe.timeout = 1
265
        self.pipe.parity = "E"
266
        start = time.time()
267
        try:
268
            _upload(self)
269
        except errors.RadioError:
270
            raise
271
        except Exception as e:
272
            LOG.exception('Failed upload')
273
            raise errors.RadioError("Failed to communicate with radio: %s" % e)
274
        LOG.info("Uploaded in %.2f sec" % (time.time() - start))
275

    
276
    def process_mmap(self):
277
        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
278

    
279
    def get_raw_memory(self, number):
280
        return repr(self._memobj.memory[number])
281

    
282
    def get_memory(self, number):
283
        _mem = self._memobj.memory[number]
284
        _nam = self._memobj.names[number]
285
        mem = chirp_common.Memory()
286

    
287
        mem.number = number
288

    
289
        if _mem.get_raw()[0] == "\xFF":
290
            mem.empty = True
291
            return mem
292

    
293
        mem.freq = int(_mem.freq) * 10
294
        mem.offset = int(_mem.offset) * 100000
295
        mem.duplex = DUPLEX[_mem.duplex]
296
        mem.tmode = TMODES[_mem.tmode]
297
        mem.rtone = chirp_common.TONES[_mem.tone]
298
        mem.dtcs = chirp_common.DTCS_CODES[_mem.dtcs]
299
        mem.name = str(_nam.name).rstrip()
300
        mem.mode = _mem.isnarrow and "NFM" or "FM"
301
        mem.skip = _mem.pskip and "P" or _mem.skip and "S" or ""
302
        mem.power = POWER_LEVELS[_mem.power]
303

    
304
        return mem
305

    
306
    def set_memory(self, mem):
307
        _mem = self._memobj.memory[mem.number]
308
        _nam = self._memobj.names[mem.number]
309

    
310
        if mem.empty:
311
            _mem.set_raw("\xFF" * (_mem.size() // 8))
312
            return
313

    
314
        if _mem.get_raw()[0] == "\xFF":
315
            # Empty -> Non-empty, so initialize
316
            _mem.set_raw("\x00" * (_mem.size() // 8))
317

    
318
        # initializing unknowns
319
        _mem.unknown1 = (0xFF, 0xFF, 0xFF, 0xFF)
320
        _mem.unknown2 = (0xFF, 0xFF)
321
        _mem.unknown3 = 0x01
322
        _mem.unknown4 = 0x3C
323

    
324
        _mem.freq = mem.freq / 10
325
        _mem.offset = mem.offset / 100000
326
        _mem.duplex = DUPLEX.index(mem.duplex)
327
        _mem.tmode = TMODES.index(mem.tmode)
328
        _mem.tone = chirp_common.TONES.index(mem.rtone)
329
        _mem.dtcs = chirp_common.DTCS_CODES.index(mem.dtcs)
330
        _mem.isnarrow = MODES.index(mem.mode)
331
        _mem.pskip = mem.skip == "P"
332
        _mem.skip = mem.skip == "S"
333
        if mem.power:
334
            _mem.power = POWER_LEVELS.index(mem.power)
335
        else:
336
            _mem.power = 0
337

    
338
        _nam.name = mem.name.ljust(6)[:6]
339

    
340
    @classmethod
341
    def match_model(cls, filedata, filename):
342
        return len(filedata) == cls._memsize
(11-11/16)