widget_ymodem.py
代码实现串口的Ymodem收发和进度条显示
# -*- coding: utf-8 -*-
import logging
import os
from data_check import crc16_ccitt
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
# ymodem data header byte
SOH = b'\x01'
STX = b'\x02'
EOT = b'\x04'
ACK = b'\x06'
NAK = b'\x15'
CAN = b'\x18'
CRC = b'C'
class YMODEM(object):
# initialize
def __init__(self, getc, putc, type, mode='ymodem', header_pad=b'\x00', pad=b'\x1a'):
self.getc = getc
self.putc = putc
self.type = type
self.mode = mode
self.header_pad = header_pad
self.pad = pad
self.log = logging.getLogger('YReporter')
# send abort(CAN) twice
def abort(self, count=2):
for _ in range(count):
self.putc(CAN)
'''
send entry
'''
def send(self, file_stream, file_name, file_size=0, retry=20, timeout=15, callback=None):
try:
packet_size = dict(
ymodem=1024,
ymodem128=128
)[self.mode]
except KeyError:
raise ValueError("Invalid mode specified: {self.mode!r}".format(self=self))
self.log.debug('Begin start sequence')
# Receive first character
error_count = 0
cancel = 0
sent = 0
def processResult(state):
if callable(callback):
callback(file_size, sent, state)
while True:
char = self.getc(1)
# print('Ymodem_Send filename', char)
if char:
if char == CRC:
# Expected CRC
self.log.info("<<< CRC")
break
elif char == CAN:
self.log.info("<<< CAN")
if cancel:
processResult(-1)
return False
else:
cancel = 1
else:
self.log.error("send error, expected CRC or CAN, but got " + hex(ord(char)))
error_count += 1
if error_count > retry:
processResult(-1)
self.abort()
self.log.error("send error: error_count reached %d aborting", retry)
return False
header = self._make_send_header(128, 0)
name = bytes(file_name, encoding="utf8")
size = bytes(str(file_size), encoding="utf8")
data = name + b'\x00' + size + b'\x20'
data = data.ljust(128, self.header_pad)
checksum = self._make_send_checksum(data)
data_for_send = header + data + checksum
self.putc(data_for_send)
# self.log.info("Packet 0 >>> " + str(len(data_for_send)))
error_count = 0
cancel = 0
# Receive reponse of first packet
if self.type == 'I2C':
while True:
char = self.getc(1)
if char:
if char == ACK:
self.log.info("<<< ACK")
char2 = self.getc(1)
if char2 == CRC:
self.log.info("<<< CRC")
break
else:
self.log.warn("ACK wasnt CRCd")
break
elif char == CAN:
self.log.info("<<< CAN")
if cancel:
processResult(-1)
return False
else:
cancel = 1
else:
if (ord(char)) >= 0x20 and (ord(char)) <= 0x7e:
self.log.error("test" + str(char))
else:
self.log.error("send error, expected ACK or CAN, but got " + hex(ord(char)))
else:
while True:
char = self.getc(2)
# print('Ymodem_Send filecontent', char)
if char:
if char == b'\x06C':
self.log.info("<<< ACK")
break
elif char == CAN:
self.log.info("<<< CAN")
if cancel:
processResult(-1)
return False
else:
cancel = 1
else:
if (error_count > 20):
processResult(-1)
return False
error_count += 1
if (ord(char)) >= 0x20 and (ord(char)) <= 0x7e:
self.log.error("test" + str(char))
else:
self.log.error("send error, expected ACK or CAN, but got " + hex(ord(char)))
error_count = 0
cancel = 0
total_packets = 1
sequence = 1
while True:
# Read raw data from file stream
data = file_stream.read(packet_size)
if not data:
self.log.debug('send: at EOF')
break
total_packets += 1
sent += len(data)
header = self._make_send_header(packet_size, sequence)
data = data.ljust(packet_size, self.pad)
checksum = self._make_send_checksum(data)
while True:
data_for_send = header + data + checksum
# data_in_hexstring = "".join("%02x" % b for b in data_for_send)
# Send file data packet
self.putc(data_for_send)
# self.log.info("Packet " + str(sequence) + " >>>" + str(len(data_for_send)))
error_count = 0
while True:
char = self.getc(1)
if char == ACK:
break
else:
error_count += 1
if error_count > retry:
processResult(-1)
self.abort()
return False
error_count = 0
if char == ACK:
# Expected response
# self.log.info("<<< ACK")
processResult(0)
error_count = 0
break
error_count += 1
if error_count > retry:
processResult(-1)
self.abort()
self.log.error('send error: NAK received %d , aborting', retry)
return False
sequence = (sequence + 1) % 0x100
# Send EOT and expect final ACK
error_count = 0
while True:
self.putc(EOT)
self.log.info(">>> EOT")
char = self.getc(1)
if char == ACK:
self.log.info("<<< ACK")
break
else:
error_count += 1
if error_count > retry:
processResult(-1)
self.abort()
self.log.warn('EOT wasnt ACKd, aborting transfer')
return False
header = self._make_send_header(128, 0)
data = bytearray(b'\x00')
data = data.ljust(128, self.header_pad)
checksum = self._make_send_checksum(data)
data_for_send = header + data + checksum
self.putc(data_for_send)
error_count = 0
while True:
char = self.getc(1)
if char == ACK:
break
else:
error_count += 1
if error_count > retry:
processResult(-1)
self.abort()
self.log.warn('SOH wasnt ACK, aborting transfer')
return False
self.log.info('Transmission successful (ACK received)')
processResult(1)
return True
def sendFile(self, filename, callback=None):
if not os.path.exists(filename):
if callable(callback):
callback(1, 0, -1)
return False
sz = os.path.getsize(filename)
if sz <= 0:
if callable(callback):
callback(1, 0, -1)
return False
f = open(filename, 'rb')
res = self.send(f, os.path.basename(filename), sz, callback=callback)
f.close()
return res
# Header byte
def _make_send_header(self, packet_size, sequence):
assert packet_size in (128, 1024), packet_size
_bytes = []
if packet_size == 128:
_bytes.append(ord(SOH))
elif packet_size == 1024:
_bytes.append(ord(STX))
_bytes.extend([sequence, 0xff - sequence])
return bytearray(_bytes)
# Make check code
def _make_send_checksum(self, data):
_bytes = []
crc = crc16_ccitt(data)
_bytes.extend([crc >> 8, crc & 0xff])
return bytearray(_bytes)
def _verify_recv_checksum(self, data):
_checksum = bytearray(data[-2:])
their_sum = (_checksum[0] << 8) + _checksum[1]
data = data[:-2]
our_sum = crc16_ccitt(data)
valid = bool(their_sum == our_sum)
return valid, data
if __name__ == '__main__':
pass