|
# Copyright 2019 Dan Clemmensen <DanClemmensen@Gmail.com>
|
|
#
|
|
# test the serial io code from ft4.py
|
|
# This program is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
"""
|
|
Unit test for ft4.py serial io.
|
|
unit-test module has stubs for averything in CHIRP that is needed by the
|
|
serial FT4 serial i/o functions. I copied those functions into this module
|
|
I don't know how to do real Python debugging. I'm trying to make this compatible
|
|
with both Python 2 and Python 3.
|
|
"""
|
|
import serial
|
|
import os
|
|
import time
|
|
import struct
|
|
|
|
class Util:
|
|
def hexprint(self, msg): #given bytes, return UTF-8 hex string
|
|
return u"".join(u"%02x"% x for x in bytearray(msg))
|
|
|
|
class Log:
|
|
def debug(self, msg):
|
|
print ("Debug: " + msg + "\n")
|
|
|
|
class Errors(Exception):
|
|
def RadioError(self,msg):
|
|
return(msg)
|
|
|
|
util = Util()
|
|
LOG = Log()
|
|
errors = Errors()
|
|
|
|
|
|
# Begin Serial transfer utilities for the SCU-35 cable.
|
|
|
|
# The serial transfer protocol was implemented here after snooping the wire.
|
|
# After it was implemented, we noticed that it is identical to the protocol
|
|
# implemented in th9000.py. A non-echo version is implemented in anytone_ht.py.
|
|
#
|
|
# The pipe.read and pipe.write functions use bytes, not strings. The serial
|
|
# transfer utilities operate only to move data between the memory object and
|
|
# the serial port. The code runs on either Python 2 or Python3, so some
|
|
# constructs could be better optimized for one or the other, but not both.
|
|
|
|
|
|
def checkSum8(data):
|
|
"""
|
|
Calculate the 8 bit checksum of buffer
|
|
Input: buffer - bytes
|
|
returns: integer
|
|
"""
|
|
return sum(x for x in bytearray(data)) & 0xFF
|
|
|
|
|
|
def sendcmd(pipe, cmd, response_len):
|
|
"""
|
|
send a command bytelist to radio,receive and return the resulting bytelist.
|
|
Input: pipe - serial port object to use
|
|
cmd - bytes to send
|
|
response_len - number of bytes of expected response,
|
|
not including the ACK.
|
|
This cable is "two-wire": The TxD and RxD are "or'ed" so we receive
|
|
whatever we send and then whatever response the radio sends. We check the
|
|
echo and strip it, returning only the radio's response.
|
|
We also check and strip the ACK character at the end of the response.
|
|
"""
|
|
pipe.write(cmd)
|
|
echo = pipe.read(len(cmd))
|
|
if echo != cmd:
|
|
msg = "Bad echo. Sent:" + util.hexprint(cmd) + ", "
|
|
msg += "Received:" + util.hexprint(echo)
|
|
LOG.debug(msg)
|
|
raise errors.RadioError("Incorrect echo on serial port.")
|
|
if response_len > 0:
|
|
response = pipe.read(response_len)
|
|
else:
|
|
response = b""
|
|
ack = pipe.read(1)
|
|
if ack != b'\x06':
|
|
LOG.debug("missing ack: expected 0x06, got" + util.hexprint(ack))
|
|
raise errors.RadioError("Incorrect ACK on serial port.")
|
|
return response
|
|
|
|
|
|
def getblock(pipe, addr):
|
|
"""
|
|
read a single 16-byte block from the radio
|
|
send the command and check the response
|
|
returns the 16-byte bytearray
|
|
"""
|
|
cmd = struct.pack(">cHb", b"R", addr, 16)
|
|
response = sendcmd(pipe, cmd, 21)
|
|
if (response[0] != b"W"[0]) or (response[1:4] != cmd[1:4]):
|
|
msg = "Bad response. Sent:" + util.hexprint(cmd) + ", "
|
|
msg += b"Received:" + util.hexprint(response)
|
|
LOG.debug(msg)
|
|
raise errors.RadioError("Incorrect response to read.")
|
|
if checkSum8(response[1:20]) != bytearray(response)[20]:
|
|
LOG.debug(b"Bad checksum: " + util.hexprint(response))
|
|
raise errors.RadioError("bad block checksum.")
|
|
return response[4:20]
|
|
|
|
|
|
def do_download(radio):
|
|
"""
|
|
Read memory from the radio.
|
|
send "PROGRAM" to command the radio into clone mode,
|
|
read the initial string (version?)
|
|
read the memory blocks
|
|
send "END"
|
|
"""
|
|
pipe = radio.pipe # Get the serial port connection
|
|
|
|
if b"QX" != sendcmd(pipe, b"PROGRAM", 2):
|
|
raise errors.RadioError("expected QX from radio.")
|
|
version = sendcmd(pipe, b'\x02', 15)
|
|
data = b""
|
|
for _i in range(radio.numblocks):
|
|
data += getblock(pipe, 16 * _i)
|
|
sendcmd(pipe, b"END", 0)
|
|
return (version, data)
|
|
|
|
|
|
def putblock(pipe, addr, data):
|
|
"""
|
|
write a single 16-byte block to the radio
|
|
send the command and check the response
|
|
"""
|
|
chkstr = struct.pack(">Hb", addr, 16) + data
|
|
msg = b'W' + chkstr + struct.pack('B', checkSum8(chkstr)) + b'\x06'
|
|
sendcmd(pipe, msg, 0)
|
|
|
|
|
|
def do_upload(radio):
|
|
"""
|
|
Write memory image to radio
|
|
send "PROGRAM" to command the radio into clone mode,
|
|
write the memory blocks. Skip the first block
|
|
send "END"
|
|
"""
|
|
pipe = radio.pipe # Get the serial port connection
|
|
|
|
if b"QX" != sendcmd(pipe, b"PROGRAM", 2):
|
|
raise errors.RadioError("expected QX from radio.")
|
|
data = radio.get_mmap()
|
|
sendcmd(pipe, b'\x02', 15)
|
|
for _i in range(1, radio.numblocks):
|
|
putblock(pipe, 16*_i, data[16*_i:16*(_i+1)])
|
|
sendcmd(pipe, b"END", 0)
|
|
return
|
|
# End serial transfer utilities
|
|
|
|
# stub radio class for test
|
|
class Radio(object):
|
|
BAUDRATE = 9600
|
|
|
|
def __init__(self, port=None, imgFN=None,numblocks=0x215):
|
|
"""
|
|
imgFN: image file name
|
|
port: Serial Port name ie com? or /dev/ttyUSB0?
|
|
"""
|
|
|
|
self.port = port
|
|
self.imgFN = imgFN
|
|
|
|
self.pipe = None #Current I/O device file or port
|
|
self.memMap = None
|
|
self.numblocks = numblocks
|
|
self.mem_map_size = 16 * numblocks +15
|
|
|
|
def openPort(self, port=None, to=4):
|
|
"""
|
|
Open serial port set parameters
|
|
raise exception not valid
|
|
"""
|
|
self.close()
|
|
|
|
if port is None:
|
|
port = self.port # maybe None
|
|
|
|
try:
|
|
self.pipe = serial.Serial(port, 9600, timeout=to)
|
|
self.pipe.dtr = True
|
|
self.pipe.flushInput()
|
|
self.pipe.flushOutput()
|
|
except (serial.SerialException, ValueError) as e:
|
|
raise e
|
|
|
|
def get_mmap(self):
|
|
return self.memMap
|
|
|
|
def close(self):
|
|
"""
|
|
close any open port/file
|
|
"""
|
|
try:
|
|
self.pipe.close()
|
|
except:
|
|
pass
|
|
finally:
|
|
self.pipe = None
|
|
|
|
UPLOAD = False
|
|
def test():
|
|
print("Power On Radio")
|
|
time.sleep(3)
|
|
io=Radio(r'/dev/ttyUSB0',None,0x215) #FT-4 on Linux
|
|
io.openPort()
|
|
version, data=do_download(io)
|
|
io.memMap = data
|
|
io.close()
|
|
print(b"Version=" + version)
|
|
with open(r'./dump.ft4', 'wb') as f:
|
|
f.write(data)
|
|
f.close()
|
|
print("Wrote raw image to ./dump.ft4")
|
|
if UPLOAD:
|
|
print("Power Radio off and back on")
|
|
time.sleep(3)
|
|
io.openPort()
|
|
print("sending to radio")
|
|
do_upload(io)
|
|
print("transfer complete")
|
|
|
|
# main program
|
|
if __name__ =="__main__":
|
|
test()
|
|
|
|
|