Project

General

Profile

New Model #4787 » d.py

Daniel Clemmensen, 02/23/2019 12:28 PM

 
1
# Copyright 2019 Dan Clemmensen <DanClemmensen@Gmail.com>
2
#
3
# test the serial io code from ft4.py
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 3 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
Unit test for ft4.py serial io.
18
unit-test module has stubs for averything in CHIRP that is needed by the
19
serial FT4 serial i/o functions. I copied those functions into this module
20
I don't know how to do real Python debugging. I'm trying to make this compatible
21
with both Python 2 and Python 3.
22
"""
23
import serial
24
import os
25
import time
26
import struct
27
       
28
class Util:
29
	def hexprint(self, msg):     #given bytes, return UTF-8 hex string
30
	    return u"".join(u"%02x"% x for x in bytearray(msg))
31
	    	    
32
class Log:
33
	def debug(self, msg):
34
	    print ("Debug: " + msg + "\n")
35
	    	    
36
class Errors(Exception):
37
	def RadioError(self,msg):
38
	    return(msg)
39
	    
40
util = Util()
41
LOG = Log()
42
errors = Errors()
43

    
44

    
45
# Begin Serial transfer utilities for the SCU-35 cable.
46

    
47
# The serial transfer protocol was implemented here after snooping the wire.
48
# After it was implemented, we noticed that it is identical to the protocol
49
# implemented in th9000.py. A non-echo version is implemented in anytone_ht.py.
50
#
51
# The pipe.read and pipe.write functions use bytes, not strings. The serial
52
# transfer utilities operate only to move data between the memory object and
53
# the serial port. The code runs on either Python 2 or Python3, so some
54
# constructs could be better optimized for one or the other, but not both. 
55

    
56

    
57
def checkSum8(data):
58
    """
59
    Calculate the 8 bit checksum of buffer
60
    Input: buffer   - bytes
61
    returns: integer
62
    """
63
    return sum(x for x in bytearray(data)) & 0xFF
64

    
65

    
66
def sendcmd(pipe, cmd, response_len):
67
    """
68
    send a command bytelist to radio,receive and return the resulting bytelist.
69
    Input: pipe         - serial port object to use
70
           cmd          - bytes to send
71
           response_len - number of bytes of expected response,
72
                           not including the ACK.
73
    This cable is "two-wire": The TxD and RxD are "or'ed" so we receive
74
    whatever we send and then whatever response the radio sends. We check the
75
    echo and strip it, returning only the radio's response.
76
    We also check and strip the ACK character at the end of the response.
77
    """
78
    pipe.write(cmd)
79
    echo = pipe.read(len(cmd))
80
    if echo != cmd:
81
        msg = "Bad echo. Sent:" + util.hexprint(cmd) + ", "
82
        msg += "Received:" + util.hexprint(echo)
83
        LOG.debug(msg)
84
        raise errors.RadioError("Incorrect echo on serial port.")
85
    if response_len > 0:
86
        response = pipe.read(response_len)
87
    else:
88
        response = b""
89
    ack = pipe.read(1)
90
    if ack != b'\x06':
91
        LOG.debug("missing ack: expected 0x06, got" + util.hexprint(ack))
92
        raise errors.RadioError("Incorrect ACK on serial port.")
93
    return response
94

    
95

    
96
def getblock(pipe, addr):
97
    """
98
    read a single 16-byte block from the radio
99
    send the command and check the response
100
    returns the 16-byte bytearray
101
    """
102
    cmd = struct.pack(">cHb", b"R", addr, 16)
103
    response = sendcmd(pipe, cmd, 21)
104
    if (response[0] != b"W"[0]) or (response[1:4] != cmd[1:4]):
105
        msg = "Bad response. Sent:" + util.hexprint(cmd) + ", "
106
        msg += b"Received:" + util.hexprint(response)
107
        LOG.debug(msg)
108
        raise errors.RadioError("Incorrect response to read.")
109
    if checkSum8(response[1:20]) != bytearray(response)[20]:
110
        LOG.debug(b"Bad checksum: " + util.hexprint(response))
111
        raise errors.RadioError("bad block checksum.")
112
    return response[4:20]
113

    
114

    
115
def do_download(radio):
116
    """
117
    Read memory from the radio.
118
      send "PROGRAM" to command the radio into clone mode,
119
      read the initial string (version?)
120
      read the memory blocks
121
      send "END"
122
    """
123
    pipe = radio.pipe  # Get the serial port connection
124

    
125
    if b"QX" != sendcmd(pipe, b"PROGRAM", 2):
126
        raise errors.RadioError("expected QX from radio.")
127
    version = sendcmd(pipe, b'\x02', 15)
128
    data = b""
129
    for _i in range(radio.numblocks):
130
        data += getblock(pipe, 16 * _i)
131
    sendcmd(pipe, b"END", 0)
132
    return (version, data)
133

    
134

    
135
def putblock(pipe, addr, data):
136
    """
137
    write a single 16-byte block to the radio
138
    send the command and check the response
139
    """
140
    chkstr = struct.pack(">Hb",  addr, 16) + data
141
    msg = b'W' + chkstr + struct.pack('B', checkSum8(chkstr)) + b'\x06'
142
    sendcmd(pipe, msg, 0)
143

    
144

    
145
def do_upload(radio):
146
    """
147
    Write memory image to radio
148
      send "PROGRAM" to command the radio into clone mode,
149
      write the memory blocks. Skip the first block
150
      send "END"
151
    """
152
    pipe = radio.pipe  # Get the serial port connection
153

    
154
    if b"QX" != sendcmd(pipe, b"PROGRAM", 2):
155
        raise errors.RadioError("expected QX from radio.")
156
    data = radio.get_mmap()
157
    sendcmd(pipe, b'\x02', 15)
158
    for _i in range(1, radio.numblocks):
159
        putblock(pipe, 16*_i, data[16*_i:16*(_i+1)])
160
    sendcmd(pipe, b"END", 0)
161
    return
162
# End serial transfer utilities
163

    
164
# stub radio class for test
165
class Radio(object):
166
    BAUDRATE      = 9600
167
    
168
    def __init__(self, port=None, imgFN=None,numblocks=0x215):
169
            """
170
            imgFN:   image file name 
171
            port:   Serial Port name ie com? or /dev/ttyUSB0?
172
            """    
173
            
174
            self.port = port
175
            self.imgFN = imgFN
176
            
177
            self.pipe = None      #Current I/O device file or port
178
            self.memMap = None
179
            self.numblocks = numblocks
180
            self.mem_map_size = 16 * numblocks +15
181
            
182
    def openPort(self, port=None, to=4):
183
        """
184
        Open serial port set parameters
185
        raise exception not valid
186
        """
187
        self.close()
188
        
189
        if port is None:
190
            port = self.port        # maybe None
191
        
192
        try:
193
            self.pipe = serial.Serial(port, 9600, timeout=to)
194
            self.pipe.dtr = True
195
            self.pipe.flushInput()
196
            self.pipe.flushOutput()            
197
        except (serial.SerialException, ValueError) as e:
198
            raise e
199

    
200
    def get_mmap(self):
201
        return self.memMap
202

    
203
    def close(self):
204
        """
205
        close any open port/file
206
        """
207
        try:
208
            self.pipe.close()
209
        except:
210
            pass
211
        finally:
212
            self.pipe = None
213

    
214
UPLOAD = False     
215
def test():
216
    print("Power On Radio")
217
    time.sleep(3)
218
    io=Radio(r'/dev/ttyUSB0',None,0x215)    #FT-4 on Linux
219
    io.openPort()
220
    version, data=do_download(io)
221
    io.memMap = data
222
    io.close()
223
    print(b"Version=" + version)
224
    with open(r'./dump.ft4', 'wb') as f:
225
        f.write(data)
226
    f.close()
227
    print("Wrote raw image to ./dump.ft4")
228
    if UPLOAD:
229
        print("Power Radio off and back on")   
230
        time.sleep(3)
231
        io.openPort()
232
        print("sending to radio")
233
        do_upload(io)
234
        print("transfer complete")
235

    
236
# main program
237
if __name__ =="__main__":
238
	test()
239
	
240

    
(2-2/22)