Project

General

Profile

Bug #3919 » ft2800.py

Andy Knitt, 10/22/2020 08:45 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 os
18
import logging
19

    
20
from chirp import util, memmap, chirp_common, bitwise, directory, errors
21
from yaesu_clone import YaesuCloneModeRadio
22

    
23
LOG = logging.getLogger(__name__)
24

    
25
CHUNK_SIZE = 16
26

    
27

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

    
36
IDBLOCK1 = "\x0c\x01\x41\x33\x35\x02\x00\xb8"  #Unmodified USA Radio
37
IDBLOCK2 = "\x0c\x01\x41\x33\x35\x03\x00\xb9"  #USA Radio modified for extended TX
38

    
39
IDBLOCK = [IDBLOCK1,IDBLOCK2]
40
TRAILER = "\x0c\x02\x41\x33\x35\x00\x00\xb7"
41
ACK = "\x0C\x06\x00"
42

    
43
my_idblock = IDBLOCK[0]  #default IDBLOCK
44

    
45
def _download(radio):
46
    data = ""
47
    for _i in range(0, 10):
48
        data = radio.pipe.read(8)
49
        #print(':'.join(data.encode('hex') for x in 'Hello World!'))
50
        print(util.hexprint(data))
51
        #if data == IDBLOCK:
52
        if data in IDBLOCK:
53
            my_idblock = data
54
            break
55

    
56
    LOG.debug("Header:\n%s" % util.hexprint(data))
57

    
58
    if len(data) != 8:
59
        raise Exception("Failed to read header")
60

    
61
    _send(radio.pipe, ACK)
62

    
63
    data = ""
64

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

    
82
            data += chunk[5:-1]
83

    
84
        _send(radio.pipe, ACK)
85
        if radio.status_fn:
86
            status = chirp_common.Status()
87
            status.max = radio._block_sizes[1]
88
            status.cur = len(data)
89
            status.msg = "Cloning from radio"
90
            radio.status_fn(status)
91

    
92
    LOG.debug("Total: %i" % len(data))
93
    print(util.hexprint(data))
94
    return my_idblock, memmap.MemoryMap(data)
95

    
96

    
97
def _upload(radio):
98
    for _i in range(0, 10):
99
        data = radio.pipe.read(256)
100
        if not data:
101
            break
102
        LOG.debug("What is this garbage?\n%s" % util.hexprint(data))
103
    
104
    _send(radio.pipe,radio.radio_id)
105
    
106
    time.sleep(1)
107
    ack = radio.pipe.read(300)
108
    LOG.debug("Ack was (%i):\n%s" % (len(ack), util.hexprint(ack)))
109
    if ack != ACK:
110
        raise Exception("Radio did not ack ID")
111

    
112
    block = 0
113
    while block < (radio.get_memsize() / 32):
114
        data = "\x0C\x03\x00\x00" + chr(block)
115
        data += radio.get_mmap()[block*32:(block+1)*32]
116
        cs = 0
117
        for byte in data:
118
            cs += ord(byte)
119
        data += chr(cs & 0xFF)
120

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

    
123
        _send(radio.pipe, data)
124
        time.sleep(0.1)
125
        ack = radio.pipe.read(3)
126
        if ack != ACK:
127
            raise Exception("Radio did not ack block %i" % block)
128

    
129
        if radio.status_fn:
130
            status = chirp_common.Status()
131
            status.max = radio._block_sizes[1]
132
            status.cur = block * 32
133
            status.msg = "Cloning to radio"
134
            radio.status_fn(status)
135
        block += 1
136

    
137
    _send(radio.pipe, TRAILER)
138

    
139
MEM_FORMAT = """
140
struct {
141
  bbcd freq[4];
142
  u8 unknown1[4];
143
  bbcd offset[2];
144
  u8 unknown2[2];
145
  u8 pskip:1,
146
     skip:1,
147
     unknown3:1,
148
     isnarrow:1,
149
     power:2,
150
     duplex:2;
151
  u8 unknown4:6,
152
     tmode:2;
153
  u8 tone;
154
  u8 dtcs;
155
} memory[200];
156

    
157
#seekto 0x0E00;
158
struct {
159
  char name[6];
160
} names[200];
161
"""
162

    
163
MODES = ["FM", "NFM"]
164
TMODES = ["", "Tone", "TSQL", "DTCS"]
165
DUPLEX = ["", "-", "+", ""]
166
POWER_LEVELS = [chirp_common.PowerLevel("Hi", watts=65),
167
                chirp_common.PowerLevel("Mid", watts=25),
168
                chirp_common.PowerLevel("Low2", watts=10),
169
                chirp_common.PowerLevel("Low1", watts=5),
170
                ]
171
CHARSET = chirp_common.CHARSET_UPPER_NUMERIC + "()+-=*/???|_"
172

    
173

    
174
@directory.register
175
class FT2800Radio(YaesuCloneModeRadio):
176
    """Yaesu FT-2800"""
177
    VENDOR = "Yaesu"
178
    MODEL = "FT-2800M"
179

    
180
    _block_sizes = [8, 7680]
181
    _memsize = 7680
182

    
183
    def get_features(self):
184
        rf = chirp_common.RadioFeatures()
185

    
186
        rf.memory_bounds = (0, 199)
187

    
188
        rf.has_ctone = False
189
        rf.has_tuning_step = False
190
        rf.has_dtcs_polarity = False
191
        rf.has_bank = False
192

    
193
        rf.valid_tuning_steps = [5.0, 10.0, 12.5, 15.0,
194
                                 20.0, 25.0, 50.0, 100.0]
195
        rf.valid_modes = MODES
196
        rf.valid_tmodes = TMODES
197
        rf.valid_bands = [(137000000, 174000000)]
198
        rf.valid_power_levels = POWER_LEVELS
199
        rf.valid_duplexes = DUPLEX
200
        rf.valid_skips = ["", "S", "P"]
201
        rf.valid_name_length = 6
202
        rf.valid_characters = CHARSET
203

    
204
        return rf
205

    
206
    def sync_in(self):
207
        self.pipe.parity = "E"
208
        start = time.time()
209
        try:
210
            self.radio_id, self._mmap = _download(self)
211
        except errors.RadioError:
212
            raise
213
        except Exception, e:
214
            raise errors.RadioError("Failed to communicate with radio: %s" % e)
215
        LOG.info("Downloaded in %.2f sec" % (time.time() - start))
216
        self.process_mmap()
217

    
218
    def sync_out(self):
219
        self.pipe.timeout = 1
220
        self.pipe.parity = "E"
221
        start = time.time()
222
        try:
223
            _upload(self)
224
        except errors.RadioError:
225
            raise
226
        except Exception, e:
227
            raise errors.RadioError("Failed to communicate with radio: %s" % e)
228
        LOG.info("Uploaded in %.2f sec" % (time.time() - start))
229

    
230
    def process_mmap(self):
231
        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
232

    
233
    def get_raw_memory(self, number):
234
        return repr(self._memobj.memory[number])
235

    
236
    def get_memory(self, number):
237
        _mem = self._memobj.memory[number]
238
        _nam = self._memobj.names[number]
239
        mem = chirp_common.Memory()
240

    
241
        mem.number = number
242

    
243
        if _mem.get_raw()[0] == "\xFF":
244
            mem.empty = True
245
            return mem
246

    
247
        mem.freq = int(_mem.freq) * 10
248
        mem.offset = int(_mem.offset) * 100000
249
        mem.duplex = DUPLEX[_mem.duplex]
250
        mem.tmode = TMODES[_mem.tmode]
251
        mem.rtone = chirp_common.TONES[_mem.tone]
252
        mem.dtcs = chirp_common.DTCS_CODES[_mem.dtcs]
253
        mem.name = str(_nam.name).rstrip()
254
        mem.mode = _mem.isnarrow and "NFM" or "FM"
255
        mem.skip = _mem.pskip and "P" or _mem.skip and "S" or ""
256
        mem.power = POWER_LEVELS[_mem.power]
257

    
258
        return mem
259

    
260
    def set_memory(self, mem):
261
        _mem = self._memobj.memory[mem.number]
262
        _nam = self._memobj.names[mem.number]
263

    
264
        if mem.empty:
265
            _mem.set_raw("\xFF" * (_mem.size() / 8))
266
            return
267

    
268
        if _mem.get_raw()[0] == "\xFF":
269
            # Emtpy -> Non-empty, so initialize
270
            _mem.set_raw("\x00" * (_mem.size() / 8))
271

    
272
        _mem.freq = mem.freq / 10
273
        _mem.offset = mem.offset / 100000
274
        _mem.duplex = DUPLEX.index(mem.duplex)
275
        _mem.tmode = TMODES.index(mem.tmode)
276
        _mem.tone = chirp_common.TONES.index(mem.rtone)
277
        _mem.dtcs = chirp_common.DTCS_CODES.index(mem.dtcs)
278
        _mem.isnarrow = MODES.index(mem.mode)
279
        _mem.pskip = mem.skip == "P"
280
        _mem.skip = mem.skip == "S"
281
        if mem.power:
282
            _mem.power = POWER_LEVELS.index(mem.power)
283
        else:
284
            _mem.power = 0
285

    
286
        _nam.name = mem.name.ljust(6)[:6]
287

    
288
    @classmethod
289
    def match_model(cls, filedata, filename):
290
        return len(filedata) == cls._memsize
(4-4/4)