Project

General

Profile

Feature #677 » vxa700-ul.py

Joseph Peterson, 03/12/2013 02:13 PM

 
1
# Copyright 2012 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
from chirp import chirp_common, util, directory, memmap, errors
17
from chirp import bitwise
18

    
19
import time
20
import struct
21

    
22
def _debug(string):
23
    pass
24
    print string
25

    
26
def _send(radio, data):
27
    _debug("Sending %s" % repr(data))
28
    radio.pipe.write(data)
29
    radio.pipe.flush()
30
    echo = radio.pipe.read(len(data))
31
    if len(echo) != len(data):
32
        raise errors.RadioError("Invalid echo")
33

    
34
def _spoonfeed(radio, data):
35
    #count = 0
36
    _debug("Writing %i:\n%s" % (len(data), util.hexprint(data)))
37
    for byte in data:
38
        radio.pipe.write(byte)
39
        radio.pipe.flush()
40
        time.sleep(0.01)
41
        continue
42
        # This is really unreliable for some reason,
43
        # so just blindly send the data
44
        echo = radio.pipe.read(1)
45
        if echo != byte:
46
            print "%02x != %02x" % (ord(echo), ord(byte))
47
            raise errors.RadioError("No echo?")
48
        #count += 1
49

    
50
def _download(radio):
51
    count = 0
52
    data = ""
53
    while len(data) < radio.get_memsize():
54
        count += 1
55
        chunk = radio.pipe.read(133)
56
        if len(chunk) == 0 and len(data) == 0 and count < 30:
57
            continue
58
        if len(chunk) != 132:
59
            raise errors.RadioError("Got short block (length %i)" % len(chunk))
60

    
61
        checksum = ord(chunk[-1])
62
        _flag, _length, _block, _data, checksum = \
63
            struct.unpack("BBB128sB", chunk)
64

    
65
        cs = 0
66
        for byte in chunk[:-1]:
67
            cs += ord(byte)
68
        if (cs % 256) != checksum:
69
            raise errors.RadioError("Invalid checksum at 0x%02x" % len(data))
70

    
71
        data += _data
72
        _send(radio, "\x06")
73

    
74
        if radio.status_fn:
75
            status = chirp_common.Status()
76
            status.msg = "Cloning from radio"
77
            status.cur = len(data)
78
            status.max = radio.get_memsize()
79
            radio.status_fn(status)
80

    
81
    return memmap.MemoryMap(data)
82

    
83
def _upload(radio):
84
    for i in range(0, radio.get_memsize(), 128):
85
        chunk = radio.get_mmap()[i:i+128]
86
        cs = 0x20 + 130 + (i / 128)
87
        for byte in chunk:
88
            cs += ord(byte)
89
        _spoonfeed(radio,
90
                   struct.pack("BBB128sB",
91
                               0x20,
92
                               130,
93
                               i / 128,
94
                               chunk,
95
                               cs % 256))
96
        radio.pipe.write("")
97
        # This is really unreliable for some reason, so just
98
        # blindly proceed
99
        # ack = radio.pipe.read(1)
100
        ack = "\x06"
101
        time.sleep(0.5)
102
        if ack != "\x06":
103
            print repr(ack)
104
            raise errors.RadioError("Radio did not ack block %i" % (i / 132))
105
        #radio.pipe.read(1)
106
        if radio.status_fn:
107
            status = chirp_common.Status()
108
            status.msg = "Cloning to radio"
109
            status.cur = i
110
            status.max = radio.get_memsize()
111
            radio.status_fn(status)
112

    
113
MEM_FORMAT = """
114
struct memory_struct {
115
  u8 unknown1;
116
  u8 unknown2:2,
117
     isfm:1,
118
     power:2,
119
     step:3;
120
  u8 unknown5:2,
121
     showname:1,
122
     skip:1,
123
     duplex:2,
124
     unknown6:2;
125
  u8 tmode:2,
126
     unknown7:6;
127
  u8 unknown8;
128
  u8 unknown9:2,
129
     tone:6;
130
  u8 dtcs;
131
  u8 name[8];
132
  u16 freq;
133
  u8 offset;
134
};
135

    
136
u8 headerbytes[6];
137

    
138
#seekto 0x0006;
139
u8 invisible_bits[13];
140
u8 bitfield_pad[3];
141
u8 invalid_bits[13];
142

    
143
#seekto 0x017F;
144
struct memory_struct memory[100];
145
"""
146

    
147
CHARSET = "".join(["%i" % i for i in range(0, 10)]) + \
148
    "".join([chr(ord("A") + i) for i in range(0, 26)]) + \
149
    "".join([chr(ord("a") + i) for i in range(0,26)]) + \
150
    "., :;!\"#$%&'()*+-/=<>?@[?]^_`{|}????~??????????????????????????"
151
            
152
TMODES = ["", "Tone", "TSQL", "DTCS"]
153
DUPLEX = ["", "-", "+", ""]
154
POWER = [chirp_common.PowerLevel("Low1", watts=0.050),
155
         chirp_common.PowerLevel("Low2", watts=1.000),
156
         chirp_common.PowerLevel("Low3", watts=2.500),
157
         chirp_common.PowerLevel("High", watts=5.000)]
158

    
159
def _wipe_memory(_mem):
160
    _mem.set_raw("\x00" * (_mem.size() / 8))
161

    
162
@directory.register
163
class VXA700Radio(chirp_common.CloneModeRadio):
164
    """Vertex Standard VXA-700"""
165
    VENDOR = "Vertex Standard"
166
    MODEL = "VXA-700"
167
    _memsize = 4096
168

    
169
    def sync_in(self):
170
        try:
171
            self.pipe.setTimeout(2)
172
            self._mmap = _download(self)
173
        except errors.RadioError:
174
            raise
175
        except Exception, e:
176
            raise errors.RadioError("Failed to communicate " +
177
                                    "with the radio: %s" % e)
178
        self.process_mmap()
179

    
180
    def sync_out(self):
181
        #header[4] = 0x00 <- default
182
        #            0xFF <- air band only
183
        #            0x01 <- air band only
184
        #            0x02 <- air band only
185
        try:
186
            self.pipe.setTimeout(2)
187
            _upload(self)
188
        except errors.RadioError:
189
            raise
190
        except Exception, e:
191
            raise errors.RadioError("Failed to communicate " +
192
                                    "with the radio: %s" % e)
193

    
194
    def process_mmap(self):
195
        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
196

    
197
    def get_features(self):
198
        rf = chirp_common.RadioFeatures()
199
        rf.has_bank = False
200
        rf.has_ctone = False
201
        rf.has_dtcs_polarity = False
202
        rf.has_tuning_step = False
203
        rf.valid_tmodes = TMODES
204
        rf.valid_name_length = 8
205
        rf.valid_characters = CHARSET
206
        rf.valid_skips = ["", "S"]
207
        rf.valid_bands = [(88000000, 165000000)]
208
        rf.valid_tuning_steps = [5.0, 10.0, 12.5, 15.0, 20.0, 25.0, 50.0, 100.0]
209
        rf.valid_modes = ["AM", "FM"]
210
        rf.valid_power_levels = POWER
211
        rf.memory_bounds = (1, 100)
212
        return rf
213

    
214
    def _get_mem(self, number):
215
        return self._memobj.memory[number - 1]
216

    
217
    def get_raw_memory(self, number):
218
        _mem = self._get_mem(number)
219
        return repr(_mem) + util.hexprint(_mem.get_raw())
220

    
221
    def get_memory(self, number):
222
        _mem = self._get_mem(number)
223
        byte = (number - 1) / 8
224
        bit = 1 << ((number - 1) % 8)
225

    
226
        mem = chirp_common.Memory()
227
        mem.number = number
228

    
229
        if self._memobj.invisible_bits[byte] & bit:
230
            mem.empty = True
231
        if self._memobj.invalid_bits[byte] & bit:
232
            mem.empty = True
233
            return mem
234

    
235
        if _mem.step & 0x05: # Not sure this is right, but it seems to be
236
            mult = 6250
237
        else:
238
            mult = 5000
239

    
240
        mem.freq = int(_mem.freq) * mult
241
        mem.rtone = chirp_common.TONES[_mem.tone]
242
        mem.dtcs = chirp_common.DTCS_CODES[_mem.dtcs]
243
        mem.tmode = TMODES[_mem.tmode]
244
        mem.duplex = DUPLEX[_mem.duplex]
245
        mem.offset = int(_mem.offset) * 5000 * 10
246
        mem.mode = _mem.isfm and "FM" or "AM"
247
        mem.skip = _mem.skip and "S" or ""
248
        mem.power = POWER[_mem.power]
249

    
250
        for char in _mem.name:
251
            try:
252
                mem.name += CHARSET[char]
253
            except IndexError:
254
                break
255
        mem.name = mem.name.rstrip()
256

    
257
        return mem
258

    
259
    def set_memory(self, mem):
260
        _mem = self._get_mem(mem.number)
261
        byte = (mem.number - 1) / 8
262
        bit = 1 << ((mem.number - 1) % 8)
263

    
264
        if mem.empty and self._memobj.invisible_bits[byte] & bit:
265
            self._memobj.invalid_bits[byte] |= bit
266
            return
267
        if mem.empty:
268
            self._memobj.invisible_bits[byte] |= bit
269
            return
270

    
271
        if self._memobj.invalid_bits[byte] & bit:
272
            _wipe_memory(_mem)
273

    
274
        self._memobj.invisible_bits[byte] &= ~bit
275
        self._memobj.invalid_bits[byte] &= ~bit
276

    
277
        _mem.unknown2 = 0x02 # Channels don't display without this
278
        _mem.unknown7 = 0x01 # some bit in this field is related to
279
        _mem.unknown8 = 0xFF # being able to transmit
280

    
281
        # HACK: testing
282
        _mem.unknown7 = 0x2F
283

    
284
        if chirp_common.required_step(mem.freq) == 12.5:
285
            mult = 6250
286
            _mem.step = 0x05
287
        else:
288
            mult = 5000
289
            _mem.step = 0x00
290

    
291
        _mem.freq = mem.freq / mult
292
        _mem.tone = chirp_common.TONES.index(mem.rtone)
293
        _mem.dtcs = chirp_common.DTCS_CODES.index(mem.dtcs)
294
        _mem.tmode = TMODES.index(mem.tmode)
295
        _mem.duplex = DUPLEX.index(mem.duplex)
296
        _mem.offset = mem.offset / 5000 / 10
297
        _mem.isfm = mem.mode == "FM"
298
        _mem.skip = mem.skip == "S"
299
        try:
300
            _mem.power = POWER.index(mem.power)
301
        except ValueError:
302
            _mem.power = 3 # High
303

    
304
        for i in range(0, 8):
305
            try:
306
                _mem.name[i] = CHARSET.index(mem.name[i])
307
            except IndexError:
308
                _mem.name[i] = 0x40
309
        _mem.showname = bool(mem.name.strip())
310

    
311
    @classmethod
312
    def match_model(cls, filedata, filename):
313
        return len(filedata) == cls._memsize and \
314
            ord(filedata[5]) == 0x0F
(2-2/3)