Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ymodem fixes #10

Open
wants to merge 6 commits into
base: multi-protocol
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions modem/error.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@
DEBUG_START_FILENAME = _('Start sending "%s"')
DEBUG_TRY_CRC = _('Try CRC mode')
DEBUG_TRY_CHECKSUM = _('Try check sum mode')
DEBUG_SEND_EOT = _('Send <EOT>')
DEBUG_FILENAME_SENT = _('File name {} sent')
DEBUG_FILE_SENT = _('File {} sent')
DEBUG_SEND_PROGRESS = _('Progress: |{}>{:3.0f}%{}|')

ERROR_EXPECT_NAK_CRC = ERROR_WHY % _('expected <NAK>/<CRC>, got "%02x"')
ERROR_EXPECT_SOH_EOT = ERROR_WHY % _('expected <SOH>/<EOT>, got "%02x"')
Expand Down
160 changes: 93 additions & 67 deletions modem/protocol/xmodem.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from __future__ import print_function
import time
import sys
from modem import error
from modem.base import Modem
from modem.const import *
from modem import const
from modem.tools import log


Expand All @@ -21,14 +23,22 @@ class XMODEM(Modem):
'''

# Protocol identifier
protocol = PROTOCOL_XMODEM
protocol = const.PROTOCOL_XMODEM

def __init__(self, getc, putc):
Modem.__init__(self, getc, putc)
self.progress = 0
return

def get_progress(self):
return self.progress

def abort(self, count=2, timeout=60):
'''
Send an abort sequence using CAN bytes.
'''
for counter in xrange(0, count):
self.putc(CAN, timeout)
for counter in range(0, count):
self.putc(const.CAN, timeout)

def send(self, stream, retry=16, timeout=60, quiet=0):
'''
Expand All @@ -47,22 +57,22 @@ def send(self, stream, retry=16, timeout=60, quiet=0):
crc_mode = 0
cancel = 0
while True:
char = self.getc(1)
if char:
if char == NAK:
byte = self.getc(1)
if byte:
if byte == const.NAK:
crc_mode = 0
break
elif char == CRC:
elif byte == const.CRC:
crc_mode = 1
break
elif char == CAN:
elif byte == const.CAN:
# We abort if we receive two consecutive <CAN> bytes
if cancel:
return False
else:
cancel = 1
else:
log.error(error.ERROR_EXPECT_NAK_CRC % ord(char))
log.error(error.ERROR_EXPECT_NAK_CRC % ord(byte))

error_count += 1
if error_count >= retry:
Expand All @@ -87,7 +97,7 @@ def recv(self, stream, crc_mode=1, retry=16, timeout=60, delay=1, quiet=0):

# initiate protocol
error_count = 0
char = 0
byte = 0
cancel = 0
while True:
# first try CRC mode, if this fails,
Expand All @@ -98,23 +108,23 @@ def recv(self, stream, crc_mode=1, retry=16, timeout=60, delay=1, quiet=0):
return None
elif crc_mode and error_count < (retry / 2):
log.debug(error.DEBUG_TRY_CRC)
if not self.putc(CRC):
if not self.putc(const.CRC):
time.sleep(delay)
error_count += 1
else:
log.debug(error.DEBUG_TRY_CHECKSUM)
crc_mode = 0
if not self.putc(NAK):
if not self.putc(const.NAK):
time.sleep(delay)
error_count += 1

char = self.getc(1, timeout)
if char is None:
byte = self.getc(1, timeout)
if byte is None:
error_count += 1
continue
elif char in [SOH, STX]:
elif byte in [const.SOH, const.STX]:
break
elif char == CAN:
elif byte == const.CAN:
if cancel:
log.error(error.ABORT_RECV_CAN_CAN)
return None
Expand All @@ -132,21 +142,21 @@ def recv(self, stream, crc_mode=1, retry=16, timeout=60, delay=1, quiet=0):
cancel = 0
while True:
while True:
if char == SOH:
if byte == const.SOH:
packet_size = 128
break
elif char == EOT:
elif byte == const.EOT:
# Acknowledge end of transmission
self.putc(ACK)
self.putc(const.ACK)
return income_size
elif char == CAN:
elif byte == const.CAN:
# We abort if we receive two consecutive <CAN> bytes
if cancel:
return None
else:
cancel = 1
else:
log.debug(error.DEBUG_EXPECT_SOH_EOT % ord(char))
log.debug(error.DEBUG_EXPECT_SOH_EOT % ord(byte))
error_count += 1
if error_count >= retry:
self.abort()
Expand All @@ -160,26 +170,28 @@ def recv(self, stream, crc_mode=1, retry=16, timeout=60, delay=1, quiet=0):
if seq1 == sequence and seq2 == sequence:
# sequence is ok, read packet
# packet_size + checksum
data = self._check_crc(self.getc(packet_size + 1 + crc_mode),
crc_mode)
data = self._check_crc(
self.getc(packet_size + 1 + crc_mode),
crc_mode
)

# valid data, append chunk
if data:
income_size += len(data)
stream.write(data)
self.putc(ACK)
self.putc(const.ACK)
sequence = (sequence + 1) % 0x100
char = self.getc(1, timeout)
byte = self.getc(1, timeout)
continue
else:
# consume data
self.getc(packet_size + 1 + crc_mode)
log.warning(error.WARNS_SEQUENCE % (sequence, seq1, seq2))

# something went wrong, request retransmission
self.putc(NAK)
self.putc(const.NAK)

def _send_stream(self, stream, crc_mode, retry=16, timeout=0):
def _send_stream(self, stream, crc_mode, retry=16, timeout=0, filesize=0.0):
'''
Sends a stream according to the given protocol dialect:

Expand All @@ -191,12 +203,13 @@ def _send_stream(self, stream, crc_mode, retry=16, timeout=0):
'''

# Get packet size for current protocol
packet_size = PACKET_SIZE.get(self.protocol, 128)
packet_size = const.PACKET_SIZE.get(self.protocol, 128)

# ASSUME THAT I'VE ALREADY RECEIVED THE INITIAL <CRC> OR <NAK>
# SO START DIRECTLY WITH STREAM TRANSMISSION
sequence = 1
error_count = 0
total_sent = 0

while True:
data = stream.read(packet_size)
Expand All @@ -205,15 +218,15 @@ def _send_stream(self, stream, crc_mode, retry=16, timeout=0):
break

# Select optimal packet size when using YMODEM
if self.protocol == PROTOCOL_YMODEM:
if self.protocol == const.PROTOCOL_YMODEM:
packet_size = (len(data) <= 128) and 128 or 1024

# Align the packet
data = data.ljust(packet_size, '\x00')
data = data.ljust(packet_size, b'\x00')

# Calculate CRC or checksum
crc = crc_mode and self.calc_crc16(data) or \
self.calc_checksum(data)
if crc_mode == 1: crc = self.calc_crc16(data)
else: crc = self.calc_checksum(data)

# SENDS PACKET WITH CRC
if not self._send_packet(sequence, data, packet_size, crc_mode,
Expand All @@ -223,9 +236,21 @@ def _send_stream(self, stream, crc_mode, retry=16, timeout=0):

# Next sequence
sequence = (sequence + 1) % 0x100

if filesize > 0:
total_sent += packet_size
self.progress = float(total_sent)/float(filesize)
# remain = float(filesize - total_sent)/filesize
# print(error.DEBUG_SEND_PROGRESS.format(
# int(50 * self.progress) * '=',
# self.progress * 100,
# int(50 * remain) * ' ',
# ), end='\r'
# )
# sys.stdout.flush()

print()
# STREAM FINISHED, SEND EOT
log.debug(error.DEBUG_SEND_EOT)
log.error(error.DEBUG_SEND_EOT)
if self._send_eot(error_count, retry, timeout):
return True
else:
Expand All @@ -240,9 +265,9 @@ def _send_packet(self, sequence, data, packet_size, crc_mode, crc,

Return ``True`` on success, ``False`` in case of failure.
'''
start_char = SOH if packet_size == 128 else STX
start_byte = const.SOH if packet_size == 128 else const.STX
while True:
self.putc(start_char)
self.putc(start_byte)
self.putc(chr(sequence))
self.putc(chr(0xff - sequence))
self.putc(data)
Expand All @@ -254,12 +279,12 @@ def _send_packet(self, sequence, data, packet_size, crc_mode, crc,
self.putc(chr(crc))

# Wait for the <ACK>
char = self.getc(1, timeout)
if char == ACK:
byte = self.getc(1, timeout)
if byte == const.ACK:
# Transmission of the character was successful
return True

if char in [None, NAK]:
if byte in [b'', const.NAK]:
error_count += 1
if error_count >= retry:
# Excessive amounts of retransmissions requested
Expand All @@ -284,10 +309,10 @@ def _send_eot(self, error_count, retry, timeout):
Return ``True`` on success, ``False`` in case of failure.
'''
while True:
self.putc(EOT)
self.putc(const.EOT)
# Wait for <ACK>
char = self.getc(1, timeout)
if char == ACK:
byte = self.getc(1, timeout)
if byte == const.ACK:
# <EOT> confirmed
return True
else:
Expand All @@ -308,17 +333,18 @@ def _wait_recv(self, error_count, timeout):
cancel = 0
# Loop until the first character is a control character (NAK, CRC) or
# we reach the retry limit
retry = 16
while True:
char = self.getc(1)
if char:
if char in [NAK, CRC]:
return char
elif char == CAN:
byte = self.getc(1)
if byte:
if byte in [const.NAK, const.CRC]:
return byte
elif byte == const.CAN:
# Cancel at two consecutive cancels
if cancel:
log.error(error.ABORT_RECV_CAN_CAN)
self.abort(timeout=timeout)
return False
return None
else:
log.debug(error.DEBUG_RECV_CAN)
cancel = 1
Expand All @@ -329,7 +355,7 @@ def _wait_recv(self, error_count, timeout):
error_count += 1
if error_count >= retry:
self.abort(timeout=timeout)
return False
return None

def _recv_stream(self, stream, crc_mode, retry, timeout, delay):
'''
Expand All @@ -346,29 +372,29 @@ def _recv_stream(self, stream, crc_mode, retry, timeout, delay):
cancel = 0
sequence = 1
income_size = 0
self.putc(CRC)
self.putc(const.CRC)

char = self.getc(1, timeout)
byte = self.getc(1, timeout)
while True:
if char is None:
if byte is None:
error_count += 1
if error_count >= retry:
log.error(error.ABORT_ERROR_LIMIT)
self.abort(timeout=timeout)
return None
else:
continue
elif char == CAN:
elif byte == const.CAN:
if cancel:
return None
else:
cancel = 1
elif char in [SOH, STX]:
packet_size = 128 if char == SOH else 1024
elif byte in [const.SOH, const.STX]:
packet_size = 128 if byte == const.SOH else 1024
# Check the requested packet size, only YMODEM has a variable
# size
if self.protocol != PROTOCOL_YMODEM and \
PACKET_SIZE.get(self.protocol) != packet_size:
if self.protocol != const.PROTOCOL_YMODEM and \
const.PACKET_SIZE.get(self.protocol) != packet_size:
log.error(error.ABORT_PACKET_SIZE)
self.abort(timeout=timeout)
return False
Expand All @@ -384,32 +410,32 @@ def _recv_stream(self, stream, crc_mode, retry, timeout, delay):
# Append data to the stream
income_size += len(data)
stream.write(data)
self.putc(ACK)
self.putc(const.ACK)
sequence = (sequence + 1) % 0x100

# Waiting for new packet
char = self.getc(1, timeout)
byte = self.getc(1, timeout)
continue

# Sequence numbering is off or CRC is incorrect, request new
# packet
self.getc(packet_size + 1 + crc_mode)
self.putc(NAK)
elif char == EOT:
self.putc(const.NAK)
elif byte == const.EOT:
# We are done, acknowledge <EOT>
self.putc(ACK)
self.putc(const.ACK)
return income_size
elif char == CAN:
elif byte == const.CAN:
# Cancel at two consecutive cancels
if cancel:
return False
else:
cancel = 1
self.putc(ACK)
char = self.getc(1, timeout)
self.putc(const.ACK)
byte = self.getc(1, timeout)
continue
else:
log.debug(error.DEBUG_EXPECT_SOH_EOT % ord(char))
log.debug(error.DEBUG_EXPECT_SOH_EOT % byte.hex())
error_count += 1
if error_count >= retry:
log.error(error.ABORT_ERROR_LIMIT)
Expand Down
Loading