Project

General

Profile

New Model #4787 » d.py

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

 
# 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()

(2-2/22)