Adding first bash of codi flashing - modified ymodem support, listing of newer server versions if available, lock file support to allow killing of codiServer
This commit is contained in:
parent
11d266b8fc
commit
fe385178b7
8 changed files with 459 additions and 162 deletions
|
|
@ -1,6 +1,7 @@
|
|||
import serial
|
||||
import struct
|
||||
import threading
|
||||
import time
|
||||
import codi_st32_generated_functions as st32Cmd
|
||||
|
||||
isRunning = True
|
||||
|
|
@ -22,30 +23,51 @@ def init():
|
|||
def stop():
|
||||
global isRunning
|
||||
global socket
|
||||
global thread
|
||||
|
||||
isRunning = False
|
||||
socket.cancel_read()
|
||||
time.sleep(0.1)
|
||||
socket.close()
|
||||
thread.join(4)
|
||||
|
||||
|
||||
def getSocket():
|
||||
global socket
|
||||
global isRunning
|
||||
global thread
|
||||
global inUpload
|
||||
|
||||
isRunning = False
|
||||
inUpload = False
|
||||
if socket is not None:
|
||||
socket.cancel_read()
|
||||
if thread is not None:
|
||||
thread.join(4)
|
||||
return socket
|
||||
|
||||
|
||||
def readFromSerial():
|
||||
global socket
|
||||
global isRunning
|
||||
|
||||
msgHeader = bytes.fromhex('58 21 58 21')
|
||||
print('Listening...')
|
||||
print('[115200]Listening...')
|
||||
while isRunning:
|
||||
header = socket.read_until(msgHeader, size=300)
|
||||
# print('Found header', header)
|
||||
# print('[115200]Found header', header)
|
||||
|
||||
# Read Size
|
||||
if len(header) >= 4:
|
||||
if len(header) >= 4 and isRunning and header[0:4] == msgHeader:
|
||||
msgSize = struct.unpack('>I', socket.read(4))[0]
|
||||
# print('Found message size', msgSize)
|
||||
if msgSize <= 300:
|
||||
# print('[115200]Found message size', msgSize)
|
||||
if msgSize <= 300 and isRunning:
|
||||
msg = socket.read(msgSize-8)
|
||||
st32Cmd.readMessage(msg)
|
||||
else:
|
||||
if isRunning:
|
||||
print('Message length wrong, ignoring msg')
|
||||
print('[115200]Message length wrong, ignoring msg')
|
||||
|
||||
|
||||
def sendCommand(cmd):
|
||||
global socket
|
||||
|
|
@ -57,3 +79,60 @@ def sendCommand(cmd):
|
|||
lock.release()
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
|
||||
def uploadReadFromSerial():
|
||||
global socket
|
||||
global isRunning
|
||||
|
||||
print('[230400]Listening...')
|
||||
while isRunning:
|
||||
uploadResponse = socket.read()
|
||||
print('[230400]Response', uploadResponse)
|
||||
|
||||
|
||||
def switchToUploadMode():
|
||||
global socket
|
||||
global lock
|
||||
global thread
|
||||
global isRunning
|
||||
|
||||
try:
|
||||
isRunning = False
|
||||
if socket is not None:
|
||||
socket.cancel_read()
|
||||
time.sleep(1)
|
||||
socket.close()
|
||||
if thread is not None:
|
||||
thread.join(4)
|
||||
|
||||
socket = serial.Serial('/dev/ttyS1', baudrate=230400, timeout=4)
|
||||
thread = threading.Thread(target=uploadReadFromSerial)
|
||||
isRunning = True
|
||||
thread.start()
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
|
||||
def switchToCmdMode():
|
||||
global socket
|
||||
global lock
|
||||
global thread
|
||||
global isRunning
|
||||
|
||||
try:
|
||||
isRunning = False
|
||||
if socket is not None:
|
||||
socket.cancel_read()
|
||||
time.sleep(1)
|
||||
socket.close()
|
||||
if thread is not None:
|
||||
thread.join(4)
|
||||
|
||||
socket = serial.Serial('/dev/ttyS1', baudrate=115200)
|
||||
thread = threading.Thread(target=readFromSerial)
|
||||
isRunning = True
|
||||
thread.start()
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
|
|
|
|||
26
codi-app/codiReset.py
Executable file
26
codi-app/codiReset.py
Executable file
|
|
@ -0,0 +1,26 @@
|
|||
#!/usr/bin/env python3
|
||||
import time
|
||||
import lock_file
|
||||
|
||||
def stm32_hardware_reset():
|
||||
print("Reset STM32 1")
|
||||
with open('/proc/AEON_RESET_STM32', 'w') as f:
|
||||
f.write("1")
|
||||
time.sleep(1)
|
||||
print("Reset STM32 0")
|
||||
with open('/proc/AEON_RESET_STM32', 'w') as f:
|
||||
f.write("0")
|
||||
time.sleep(4)
|
||||
|
||||
|
||||
def stm32_into_download_mode(prepare="0"):
|
||||
print("STM32_DL_FW", prepare)
|
||||
with open('/proc/AEON_STM32_DL_FW', 'w') as f:
|
||||
f.write(prepare)
|
||||
|
||||
lock = "/tmp/.codi.lock"
|
||||
killed = lock_file.check_and_kill(lock)
|
||||
lock_file.lock(lock)
|
||||
|
||||
stm32_hardware_reset()
|
||||
stm32_into_download_mode()
|
||||
|
|
@ -16,6 +16,7 @@ import signal
|
|||
import CodiStatus
|
||||
import EventListener
|
||||
import Addressbook
|
||||
import lock_file
|
||||
|
||||
def signalHandler(_signo, _stack_frame):
|
||||
# mtkCmd.SetMouse(0, 1)
|
||||
|
|
@ -27,7 +28,9 @@ signal.signal(signal.SIGTERM, signalHandler)
|
|||
signal.signal(signal.SIGABRT, signalHandler)
|
||||
signal.signal(signal.SIGHUP, signalHandler)
|
||||
|
||||
|
||||
lock = "/tmp/.codi.lock"
|
||||
lock_file.check_and_kill(lock)
|
||||
lock_file.lock(lock)
|
||||
|
||||
CodiStatus.init()
|
||||
|
||||
|
|
|
|||
172
codi-app/codiUpdate.py
Executable file
172
codi-app/codiUpdate.py
Executable file
|
|
@ -0,0 +1,172 @@
|
|||
#!/usr/bin/env python3
|
||||
import logging
|
||||
import os
|
||||
import time
|
||||
import struct
|
||||
import urllib.request
|
||||
import optparse
|
||||
from distutils.version import LooseVersion
|
||||
from xmodem import YMODEM
|
||||
import codi_st32_generated_functions as st32Cmd
|
||||
import SerialPortManager
|
||||
import lock_file
|
||||
|
||||
ospi_url = None
|
||||
resources_url = None
|
||||
|
||||
def writeUint8(p):
|
||||
return struct.pack(">B", p)
|
||||
|
||||
|
||||
def writeUint32(p):
|
||||
return struct.pack(">I", p)
|
||||
|
||||
|
||||
def writeString(s):
|
||||
return writeUint32(len(s)) + s.encode()
|
||||
|
||||
|
||||
def sendMessage(commandId, args=None, sessionId='00 00 00 01'):
|
||||
if args is None:
|
||||
args = []
|
||||
msgHeader = bytes.fromhex('58 21 58 21')
|
||||
cmdId = writeUint32(commandId)
|
||||
cmdSessionId = bytes.fromhex(sessionId)
|
||||
# cmdSessionId = bytes.fromhex('00 00 00 01')
|
||||
msgLength = len(msgHeader) + 4 + len(cmdId) + len(cmdSessionId)
|
||||
for i in args:
|
||||
msgLength += len(i)
|
||||
|
||||
cmd = msgHeader + writeUint32(msgLength) + cmdId + cmdSessionId
|
||||
for i in args:
|
||||
cmd += i
|
||||
SerialPortManager.sendCommand(list(cmd))
|
||||
|
||||
|
||||
def check_new_fota_versions_available():
|
||||
global ospi_url
|
||||
global resources_url
|
||||
|
||||
time.sleep(1) # Wait for listening thread to get started
|
||||
sendMessage(st32Cmd.CMD_MTK_GET_CODI_FLASH_VERSION)
|
||||
sendMessage(st32Cmd.CMD_MTK_GET_PROTOCOL_VERSION)
|
||||
# download available versions - http://fota.planetcom.co.uk/stm32flash/cosmo_stm32_firmware_versions.txt
|
||||
base_url = None
|
||||
newest_version = None
|
||||
for line in urllib.request.urlopen("http://fota.planetcom.co.uk/stm32flash/cosmo_stm32_firmware_versions.txt"):
|
||||
firmware_line = line.decode('utf-8')
|
||||
firmware_parts = firmware_line.split(':')
|
||||
if len(firmware_parts) >= 3 and firmware_parts[0] == 'L':
|
||||
base_url = firmware_parts[1] + ':' + firmware_parts[2].strip()
|
||||
if len(firmware_parts) >= 8 and firmware_parts[0] == 'F':
|
||||
version = firmware_parts[1].replace(',', '.').replace('V', '')
|
||||
if newest_version == None or LooseVersion(version) > LooseVersion(newest_version):
|
||||
newest_version = version
|
||||
ospi_url = base_url+'/'+firmware_parts[2]
|
||||
ospi_size = firmware_parts[3]
|
||||
ospi_checksum = firmware_parts[4]
|
||||
resources_url = base_url+'/'+firmware_parts[5]
|
||||
resources_size = firmware_parts[6]
|
||||
resources_checksum = firmware_parts[7]
|
||||
|
||||
time.sleep(2) # Wait for CODI to reply
|
||||
print("CODI versions:", st32Cmd.get_codi_version(), st32Cmd.get_resources_version(), st32Cmd.get_protocol_major(),
|
||||
st32Cmd.get_protocol_minor())
|
||||
print("NewestVersion:", newest_version)
|
||||
# print("BaseUrl:",base_url)
|
||||
# print("OspiUrl:",base_url+'/'+ospi_url)
|
||||
# print("ResourcesUrl:",base_url+'/'+resources_url)
|
||||
|
||||
if st32Cmd.get_codi_version() is not None and st32Cmd.get_resources_version() is not None:
|
||||
print(LooseVersion(newest_version) > LooseVersion(st32Cmd.get_codi_version().replace('V', '')),
|
||||
LooseVersion(newest_version) > LooseVersion(st32Cmd.get_resources_version().replace('R', '')))
|
||||
return LooseVersion(newest_version) > LooseVersion(st32Cmd.get_codi_version().replace('V', '')) or \
|
||||
LooseVersion(newest_version) > LooseVersion(st32Cmd.get_resources_version().replace('R', ''))
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def stm32_hardware_reset():
|
||||
print("Reset STM32 1")
|
||||
with open('/proc/AEON_RESET_STM32', 'w') as f:
|
||||
f.write("1")
|
||||
time.sleep(1)
|
||||
print("Reset STM32 0")
|
||||
with open('/proc/AEON_RESET_STM32', 'w') as f:
|
||||
f.write("0")
|
||||
time.sleep(4)
|
||||
|
||||
|
||||
def stm32_into_download_mode(prepare="0"):
|
||||
print("STM32_DL_FW", prepare)
|
||||
with open('/proc/AEON_STM32_DL_FW', 'w') as f:
|
||||
f.write(prepare)
|
||||
|
||||
|
||||
ser = None
|
||||
|
||||
|
||||
def callback(total_packets, success_count, error_count):
|
||||
print(total_packets, success_count, error_count)
|
||||
|
||||
|
||||
def send_file(file):
|
||||
global ser
|
||||
|
||||
time.sleep(1) # Wait for listening thread to get started
|
||||
print("Switch to upload")
|
||||
SerialPortManager.switchToUploadMode()
|
||||
time.sleep(1)
|
||||
stm32_into_download_mode("1")
|
||||
time.sleep(4)
|
||||
print("Sending 140 '0d oa' session 5 - requesting reset")
|
||||
sendMessage(140, [writeUint8(0x0d), writeUint8(0x0a)], '00 00 00 05')
|
||||
time.sleep(2)
|
||||
stm32_hardware_reset()
|
||||
stm32_into_download_mode()
|
||||
print("Send Command 1")
|
||||
SerialPortManager.sendCommand(writeString("1"))
|
||||
time.sleep(1)
|
||||
|
||||
logging.basicConfig(filename='ymodem.log', level=logging.DEBUG)
|
||||
ser = SerialPortManager.getSocket()
|
||||
|
||||
modem = YMODEM(ser)
|
||||
|
||||
print("Sending", file)
|
||||
try:
|
||||
print("Send completed:", modem.send([file], callback=callback))
|
||||
except Exception as e:
|
||||
print("Exception", e)
|
||||
SerialPortManager.switchToCmdMode()
|
||||
print("Finished")
|
||||
|
||||
|
||||
parser = optparse.OptionParser(usage='%prog [filename]')
|
||||
|
||||
options, args = parser.parse_args()
|
||||
|
||||
lock = "/tmp/.codi.lock"
|
||||
killed = lock_file.check_and_kill(lock)
|
||||
lock_file.lock(lock)
|
||||
|
||||
SerialPortManager.init()
|
||||
if len(args) > 0:
|
||||
send_file(args[0])
|
||||
|
||||
if check_new_fota_versions_available():
|
||||
print("")
|
||||
print("Newer version available")
|
||||
print("Please flash resources first")
|
||||
print("R:",resources_url)
|
||||
print("M:",ospi_url)
|
||||
|
||||
elif st32Cmd.get_codi_version() is not None:
|
||||
print("Your all up to date - no new firmware")
|
||||
else:
|
||||
print("CODI Error getting existing version")
|
||||
SerialPortManager.stop()
|
||||
|
||||
#if killed:
|
||||
# print("Restarting codi server")
|
||||
# os.system("/usr/lib/codi/codiServer.py & disown")
|
||||
|
|
@ -1,6 +1,27 @@
|
|||
import struct
|
||||
import CodiFunctions as cf
|
||||
|
||||
codi_version = None
|
||||
resources_version = None
|
||||
majorVer = None
|
||||
minVer = None
|
||||
|
||||
def get_codi_version():
|
||||
global codi_version
|
||||
return codi_version
|
||||
|
||||
def get_resources_version():
|
||||
global resources_version
|
||||
return resources_version
|
||||
|
||||
def get_protocol_major():
|
||||
global majorVer
|
||||
return majorVer
|
||||
|
||||
def get_protocol_minor():
|
||||
global minVer
|
||||
return minVer
|
||||
|
||||
def readUint8(p):
|
||||
return struct.unpack(">B", p[:1])[0], p[1:]
|
||||
|
||||
|
|
@ -168,6 +189,11 @@ CMD_SYNC_RIGHT_USB_OTG_STATUS = 144
|
|||
CMD_ST_ENTRY_DEEP_SLEEP_STATUS = 145
|
||||
|
||||
def readMessage(msg):
|
||||
global codi_version
|
||||
global resources_version
|
||||
global protocol_major
|
||||
global protocol_minor
|
||||
|
||||
cmdId, msg = readUint32(msg)
|
||||
# print("Got cmdId", cmdId)
|
||||
sessionId, msg = readUint32(msg)
|
||||
|
|
@ -179,6 +205,10 @@ def readMessage(msg):
|
|||
try:
|
||||
version, msg = readString(msg)
|
||||
print("version =", version)
|
||||
parts = version.split(b':')
|
||||
if parts[0] == b'CODI' and len(parts[3]) > 0:
|
||||
codi_version = parts[1].decode("utf-8")
|
||||
resources_version = parts[2].decode("utf-8")
|
||||
cf.CoDiFlashVersionInfo(version)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
|
|
|||
38
codi-app/lock_file.py
Normal file
38
codi-app/lock_file.py
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
import os
|
||||
import time
|
||||
import psutil
|
||||
|
||||
def check_and_kill(file):
|
||||
pid = None
|
||||
try:
|
||||
f = open(file, 'r')
|
||||
pidstr = f.read()
|
||||
f.close()
|
||||
pid = int(pidstr)
|
||||
except Exception as e:
|
||||
return False
|
||||
|
||||
print("Killing PID:",pid)
|
||||
count = 0
|
||||
while pid > 0 and psutil.pid_exists(pid):
|
||||
try:
|
||||
os.kill(pid, count)
|
||||
if count == 9:
|
||||
os.waitpid(pid, 0)
|
||||
count = count + 1
|
||||
time.sleep(1)
|
||||
print("killed",count)
|
||||
except OSError:
|
||||
print("can't deliver signal", count, "to", pid)
|
||||
pid = 0
|
||||
os.remove(file)
|
||||
return count > 0
|
||||
|
||||
def lock(file):
|
||||
pidfd = os.open(file, os.O_CREAT|os.O_WRONLY|os.O_EXCL)
|
||||
os.write(pidfd, str(os.getpid()).encode())
|
||||
os.close(pidfd)
|
||||
|
||||
def remove(file):
|
||||
print('removed pidfile %s' % file)
|
||||
os.remove(file)
|
||||
35
codi-app/test_lock_file.py
Executable file
35
codi-app/test_lock_file.py
Executable file
|
|
@ -0,0 +1,35 @@
|
|||
#!/usr/bin/env python3
|
||||
import lock_file
|
||||
import os
|
||||
import time
|
||||
|
||||
lock = "/tmp/test.lock"
|
||||
try:
|
||||
os.remove(lock)
|
||||
print("Test file was present, unexpected")
|
||||
except FileNotFoundError:
|
||||
print("Test file not present as expected")
|
||||
|
||||
killed = lock_file.check_and_kill(lock)
|
||||
print("Should be False:", killed)
|
||||
|
||||
pid = os.fork()
|
||||
if pid == 0:
|
||||
#lock and keep spinning forever
|
||||
lock_file.lock(lock)
|
||||
while True:
|
||||
time.sleep(100)
|
||||
print("Child running PID:",pid)
|
||||
time.sleep(0.1)
|
||||
killed = lock_file.check_and_kill(lock)
|
||||
print("Should be True:", killed)
|
||||
|
||||
# Create stale lock file
|
||||
pidfd = os.open(lock, os.O_CREAT|os.O_WRONLY|os.O_EXCL)
|
||||
os.write(pidfd, "123456789".encode())
|
||||
os.close(pidfd)
|
||||
|
||||
print("Expect: can't deliver signal 1&2 to 123456789")
|
||||
killed = lock_file.check_and_kill(lock)
|
||||
print("Should be False:", killed)
|
||||
lock_file.lock(lock)
|
||||
|
|
@ -127,11 +127,15 @@ SOH = b'\x01'
|
|||
STX = b'\x02'
|
||||
EOT = b'\x04'
|
||||
ACK = b'\x06'
|
||||
ACK2 = b'\x86'
|
||||
DLE = b'\x10'
|
||||
NAK = b'\x15'
|
||||
CAN = b'\x18'
|
||||
CRC = b'C'
|
||||
|
||||
CAN2 = b'\x98'
|
||||
CRC = b'C' #0x43
|
||||
CRC2 = b'\xc3'
|
||||
CRC3 = b'\x83'
|
||||
ABT = b'a' #0x61 - flash fail - abort
|
||||
|
||||
class XMODEM(object):
|
||||
'''
|
||||
|
|
@ -143,25 +147,11 @@ class XMODEM(object):
|
|||
>>> import serial
|
||||
>>> from xmodem import XMODEM
|
||||
>>> ser = serial.Serial('/dev/ttyUSB0', timeout=0) # or whatever you need
|
||||
>>> def getc(size, timeout=1):
|
||||
... return ser.read(size) or None
|
||||
...
|
||||
>>> def putc(data, timeout=1):
|
||||
... return ser.write(data) or None
|
||||
...
|
||||
>>> modem = XMODEM(getc, putc)
|
||||
>>> modem = XMODEM(ser)
|
||||
|
||||
|
||||
:param getc: Function to retrieve bytes from a stream. The function takes
|
||||
the number of bytes to read from the stream and a timeout in seconds as
|
||||
parameters. It must return the bytes which were read, or ``None`` if a
|
||||
timeout occured.
|
||||
:type getc: callable
|
||||
:param putc: Function to transmit bytes to a stream. The function takes the
|
||||
bytes to be written and a timeout in seconds as parameters. It must
|
||||
return the number of bytes written to the stream, or ``None`` in case of
|
||||
a timeout.
|
||||
:type putc: callable
|
||||
:param ser: serial port to read from or write to.
|
||||
:type getc: class
|
||||
:param mode: XMODEM protocol mode
|
||||
:type mode: string
|
||||
:param pad: Padding character to make the packets match the packet size
|
||||
|
|
@ -205,9 +195,8 @@ class XMODEM(object):
|
|||
0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0,
|
||||
]
|
||||
|
||||
def __init__(self, getc, putc, mode='xmodem', pad=b'\x1a'):
|
||||
self.getc = getc
|
||||
self.putc = putc
|
||||
def __init__(self, ser, mode='xmodem', pad=b'\x1a'):
|
||||
self.ser = ser
|
||||
self.mode = mode
|
||||
self.pad = pad
|
||||
self.log = logging.getLogger('xmodem.XMODEM')
|
||||
|
|
@ -222,9 +211,9 @@ class XMODEM(object):
|
|||
:type timeout: int
|
||||
'''
|
||||
for _ in range(count):
|
||||
self.putc(CAN, timeout)
|
||||
self.ser.write(CAN)
|
||||
|
||||
def send(self, stream, retry=16, timeout=60, quiet=False, callback=None):
|
||||
def send(self, stream, retry=20, timeout=60, quiet=False, callback=None):
|
||||
'''
|
||||
Send a stream via the XMODEM protocol.
|
||||
|
||||
|
|
@ -270,7 +259,7 @@ class XMODEM(object):
|
|||
crc_mode = 0
|
||||
cancel = 0
|
||||
while True:
|
||||
char = self.getc(1)
|
||||
char = self.ser.read(1)
|
||||
if char:
|
||||
if char == NAK:
|
||||
self.log.debug('standard checksum requested (NAK).')
|
||||
|
|
@ -280,7 +269,7 @@ class XMODEM(object):
|
|||
self.log.debug('16-bit CRC requested (CRC).')
|
||||
crc_mode = 1
|
||||
break
|
||||
elif char == CAN:
|
||||
elif char == CAN or char == CAN2:
|
||||
if not quiet:
|
||||
print('received CAN', file=sys.stderr)
|
||||
if cancel:
|
||||
|
|
@ -305,6 +294,7 @@ class XMODEM(object):
|
|||
error_count = 0
|
||||
success_count = 0
|
||||
total_packets = 0
|
||||
header_sent = False
|
||||
if self.mode == 'ymodem':
|
||||
sequence = 0
|
||||
filenames = stream
|
||||
|
|
@ -312,7 +302,7 @@ class XMODEM(object):
|
|||
sequence = 1
|
||||
while True:
|
||||
# build packet
|
||||
if self.mode == 'ymodem' and success_count == 0:
|
||||
if not header_sent:
|
||||
# send packet sequence 0 containing:
|
||||
# Filename Length [Modification-Date [Mode [Serial-Number]]]
|
||||
# 'stream' is actually the filename
|
||||
|
|
@ -337,10 +327,8 @@ class XMODEM(object):
|
|||
header = self._make_send_header(header_size, sequence)
|
||||
data = data.ljust(header_size, NUL)
|
||||
checksum = self._make_send_checksum(crc_mode, data)
|
||||
header_sent = True
|
||||
else:
|
||||
# happens after sending ymodem empty filename
|
||||
if not stream:
|
||||
return True
|
||||
# normal data packet
|
||||
data = stream.read(packet_size)
|
||||
if not data:
|
||||
|
|
@ -354,27 +342,38 @@ class XMODEM(object):
|
|||
checksum = self._make_send_checksum(crc_mode, data)
|
||||
|
||||
# emit packet & get ACK
|
||||
self.ser.write(header + data + checksum)
|
||||
|
||||
while True:
|
||||
self.log.debug('send: block %d', sequence)
|
||||
self.putc(header + data + checksum)
|
||||
char = self.getc(1, timeout)
|
||||
if char == ACK:
|
||||
char = self.ser.read(1)
|
||||
if char == CRC or char == CRC2 or char == CRC3:
|
||||
if self.ser.in_waiting == 0:
|
||||
self.log.debug('re-send: block %d, pks: %d', sequence, packet_size)
|
||||
self.ser.write(header + data + checksum)
|
||||
else:
|
||||
rubbish = self.ser.read(self.ser.in_waiting-1)
|
||||
self.log.error('got NAK rubbish %r for block %d', rubbish, sequence)
|
||||
continue
|
||||
if char == ACK or char == ACK2 or char == NAK:
|
||||
success_count += 1
|
||||
if callable(callback):
|
||||
callback(total_packets, success_count, error_count)
|
||||
error_count = 0
|
||||
if self.mode == 'ymodem' and success_count == 1 and len(filename):
|
||||
char = self.getc(1, timeout)
|
||||
if char == DLE: # dunno why
|
||||
char = self.getc(1, timeout)
|
||||
if char == CRC:
|
||||
break
|
||||
self.log.error('send error: ymodem expected CRC; got %r for block %d',
|
||||
char, sequence)
|
||||
else:
|
||||
break
|
||||
if char == NAK:
|
||||
rubbish = self.ser.read(1024)
|
||||
self.log.error('got NAK rubbish %r for block %d', rubbish, sequence)
|
||||
rubbish = self.ser.read(1024)
|
||||
self.log.error('got NAK rubbish %r for block %d', rubbish, sequence)
|
||||
rubbish = self.ser.read(1024)
|
||||
self.log.error('got NAK rubbish %r for block %d', rubbish, sequence)
|
||||
rubbish = self.ser.read(1024)
|
||||
self.log.error('got NAK rubbish %r for block %d', rubbish, sequence)
|
||||
break
|
||||
if char == ABT:
|
||||
self.log.debug('got abort')
|
||||
return False
|
||||
|
||||
self.log.error('send error: expected ACK; got %r for block %d',
|
||||
self.log.error('send error: expected CRC|ACK; got %r for block %d',
|
||||
char, sequence)
|
||||
error_count += 1
|
||||
if callable(callback):
|
||||
|
|
@ -382,7 +381,7 @@ class XMODEM(object):
|
|||
if error_count > retry:
|
||||
# excessive amounts of retransmissions requested,
|
||||
# abort transfer
|
||||
self.log.error('send error: NAK received %d times, '
|
||||
self.log.error('send error: Unexpected received %d times, '
|
||||
'aborting.', error_count)
|
||||
self.abort(timeout=timeout)
|
||||
return False
|
||||
|
|
@ -394,26 +393,26 @@ class XMODEM(object):
|
|||
while True:
|
||||
self.log.debug('sending EOT, awaiting ACK')
|
||||
# end of transmission
|
||||
self.putc(EOT)
|
||||
self.ser.write(EOT)
|
||||
self.ser.write(EOT)
|
||||
self.ser.write(EOT)
|
||||
|
||||
# An ACK should be returned
|
||||
char = self.getc(1, timeout)
|
||||
char = self.ser.read(1)
|
||||
if char == ACK:
|
||||
break
|
||||
else:
|
||||
self.log.error('send error: expected ACK; got %r', char)
|
||||
error_count += 1
|
||||
if error_count > retry:
|
||||
self.log.warn('EOT was not ACKd, aborting transfer')
|
||||
self.log.warning('EOT was not ACKd, aborting transfer')
|
||||
self.abort(timeout=timeout)
|
||||
return False
|
||||
|
||||
self.log.info('Transmission successful (ACK received).')
|
||||
if self.mode == 'ymodem':
|
||||
# YMODEM - recursively send next file
|
||||
# or empty filename header to end the xfer batch.
|
||||
# YMODEM - close the stream
|
||||
stream.close()
|
||||
return self.send(filenames, retry, timeout, quiet, callback)
|
||||
return True
|
||||
|
||||
def _make_send_header(self, packet_size, sequence):
|
||||
|
|
@ -476,22 +475,22 @@ class XMODEM(object):
|
|||
self.abort(timeout=timeout)
|
||||
return None
|
||||
elif crc_mode and error_count < (retry // 2):
|
||||
if not self.putc(CRC):
|
||||
if not self.ser.write(CRC):
|
||||
self.log.debug('recv error: putc failed, '
|
||||
'sleeping for %d', delay)
|
||||
time.sleep(delay)
|
||||
error_count += 1
|
||||
else:
|
||||
crc_mode = 0
|
||||
if not self.putc(NAK):
|
||||
if not self.ser.write(NAK):
|
||||
self.log.debug('recv error: putc failed, '
|
||||
'sleeping for %d', delay)
|
||||
time.sleep(delay)
|
||||
error_count += 1
|
||||
|
||||
char = self.getc(1, timeout)
|
||||
char = self.ser.read(1)
|
||||
if char is None:
|
||||
self.log.warn('recv error: getc timeout in start sequence')
|
||||
self.log.warn('recv error: read timeout in start sequence')
|
||||
error_count += 1
|
||||
continue
|
||||
elif char == SOH:
|
||||
|
|
@ -500,7 +499,7 @@ class XMODEM(object):
|
|||
elif char == STX:
|
||||
self.log.debug('recv: STX')
|
||||
break
|
||||
elif char == CAN:
|
||||
elif char == CAN or char == CAN2:
|
||||
if cancel:
|
||||
self.log.info('Transmission canceled: received 2xCAN '
|
||||
'at start-sequence')
|
||||
|
|
@ -532,11 +531,11 @@ class XMODEM(object):
|
|||
elif char == EOT:
|
||||
# We received an EOT, so send an ACK and return the
|
||||
# received data length.
|
||||
self.putc(ACK)
|
||||
self.ser.write(ACK)
|
||||
self.log.info("Transmission complete, %d bytes",
|
||||
income_size)
|
||||
return income_size
|
||||
elif char == CAN:
|
||||
elif char == CAN or char == CAN2:
|
||||
# cancel at two consecutive cancels
|
||||
if cancel:
|
||||
self.log.info('Transmission canceled: received 2xCAN '
|
||||
|
|
@ -562,15 +561,15 @@ class XMODEM(object):
|
|||
error_count = 0
|
||||
cancel = 0
|
||||
self.log.debug('recv: data block %d', sequence)
|
||||
seq1 = self.getc(1, timeout)
|
||||
seq1 = self.ser.read(1)
|
||||
if seq1 is None:
|
||||
self.log.warn('getc failed to get first sequence byte')
|
||||
self.log.warn('read failed to get first sequence byte')
|
||||
seq2 = None
|
||||
else:
|
||||
seq1 = ord(seq1)
|
||||
seq2 = self.getc(1, timeout)
|
||||
seq2 = self.ser.read(1)
|
||||
if seq2 is None:
|
||||
self.log.warn('getc failed to get second sequence byte')
|
||||
self.log.warn('read failed to get second sequence byte')
|
||||
else:
|
||||
# second byte is the same as first as 1's complement
|
||||
seq2 = 0xff - ord(seq2)
|
||||
|
|
@ -582,21 +581,21 @@ class XMODEM(object):
|
|||
'got (seq1=%r, seq2=%r), '
|
||||
'receiving next block, will NAK.',
|
||||
sequence, seq1, seq2)
|
||||
self.getc(packet_size + 1 + crc_mode)
|
||||
self.ser.read(packet_size + 1 + crc_mode)
|
||||
else:
|
||||
# sequence is ok, read packet
|
||||
# packet_size + checksum
|
||||
data = self.getc(packet_size + 1 + crc_mode, timeout)
|
||||
data = self.ser.read(packet_size + 1 + crc_mode)
|
||||
valid, data = self._verify_recv_checksum(crc_mode, data)
|
||||
|
||||
# valid data, append chunk
|
||||
if valid:
|
||||
income_size += len(data)
|
||||
stream.write(data)
|
||||
self.putc(ACK)
|
||||
self.ser.write(ACK)
|
||||
sequence = (sequence + 1) % 0x100
|
||||
# get next start-of-header byte
|
||||
char = self.getc(1, timeout)
|
||||
char = self.ser.read(1)
|
||||
continue
|
||||
|
||||
# something went wrong, request retransmission
|
||||
|
|
@ -610,12 +609,12 @@ class XMODEM(object):
|
|||
# call the character receive subroutine, specifying a 1-second
|
||||
# timeout, and looping back to PURGE until a timeout occurs.
|
||||
# The <nak> is then sent, ensuring the other end will see it.
|
||||
data = self.getc(1, timeout=1)
|
||||
data = self.ser.read(1)
|
||||
if data is None:
|
||||
break
|
||||
self.putc(NAK)
|
||||
self.ser.write(NAK)
|
||||
# get next start-of-header byte
|
||||
char = self.getc(1, timeout)
|
||||
char = self.ser.read(1)
|
||||
continue
|
||||
|
||||
def _verify_recv_checksum(self, crc_mode, data):
|
||||
|
|
@ -679,88 +678,3 @@ class XMODEM(object):
|
|||
XMODEM1k = partial(XMODEM, mode='xmodem1k')
|
||||
YMODEM = partial(XMODEM, mode='ymodem')
|
||||
|
||||
|
||||
def run():
|
||||
import optparse
|
||||
import subprocess
|
||||
|
||||
parser = optparse.OptionParser(
|
||||
usage='%prog [<options>] <send|recv> filename filename')
|
||||
parser.add_option('-m', '--mode', default='xmodem',
|
||||
help='XMODEM mode (xmodem, xmodem1k, ymodem)')
|
||||
|
||||
options, args = parser.parse_args()
|
||||
if options.mode != 'ymodem' and len(args) != 3:
|
||||
parser.error('invalid arguments')
|
||||
return 1
|
||||
elif len(args) < 2:
|
||||
parser.error('invalid arguments')
|
||||
return 1
|
||||
elif args[0] not in ('send', 'recv'):
|
||||
parser.error('invalid mode')
|
||||
return 1
|
||||
|
||||
def _func(so, si):
|
||||
import select
|
||||
|
||||
print(('si', si))
|
||||
print(('so', so))
|
||||
|
||||
def getc(size, timeout=3):
|
||||
read_ready, _, _ = select.select([so], [], [], timeout)
|
||||
if read_ready:
|
||||
data = so.read(size)
|
||||
else:
|
||||
data = None
|
||||
|
||||
print(('getc(', repr(data), ')'))
|
||||
return data
|
||||
|
||||
def putc(data, timeout=3):
|
||||
_, write_ready, _ = select.select([], [si], [], timeout)
|
||||
if write_ready:
|
||||
si.write(data)
|
||||
si.flush()
|
||||
size = len(data)
|
||||
else:
|
||||
size = None
|
||||
|
||||
print(('putc(', repr(data), repr(size), ')'))
|
||||
return size
|
||||
|
||||
return getc, putc
|
||||
|
||||
def _pipe(*command):
|
||||
pipe = subprocess.Popen(command,
|
||||
stdout=subprocess.PIPE,
|
||||
stdin=subprocess.PIPE)
|
||||
return pipe.stdout, pipe.stdin
|
||||
|
||||
if args[0] == 'recv':
|
||||
getc, putc = _func(*_pipe('sz', '--xmodem', args[2]))
|
||||
stream = open(args[1], 'wb')
|
||||
xmodem = XMODEM(getc, putc, mode=options.mode)
|
||||
status = xmodem.recv(stream, retry=8)
|
||||
assert status, ('Transfer failed, status is', False)
|
||||
stream.close()
|
||||
|
||||
elif args[0] == 'send':
|
||||
rzargs = ['rz']
|
||||
if options.mode == 'ymodem':
|
||||
rzargs += ['--ymodem']
|
||||
else:
|
||||
rzargs += ['--xmodem']
|
||||
rzargs += args[2]
|
||||
getc, putc = _func(*_pipe(*rzargs))
|
||||
if options.mode != 'ymodem':
|
||||
stream = open(args[1], 'rb')
|
||||
else:
|
||||
stream = args[1:]
|
||||
xmodem = XMODEM(getc, putc, mode=options.mode)
|
||||
sent = xmodem.send(stream, retry=8)
|
||||
assert sent is not None, ('Transfer failed, sent is', sent)
|
||||
if options.mode != 'ymodem':
|
||||
stream.close()
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(run())
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue