diff --git a/modem/error.py b/modem/error.py index 4e3e0b6..ca4f066 100644 --- a/modem/error.py +++ b/modem/error.py @@ -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 ') +DEBUG_FILENAME_SENT = _('File name {} sent') +DEBUG_FILE_SENT = _('File {} sent') +DEBUG_SEND_PROGRESS = _('Progress: |{}>{:3.0f}%{}|') ERROR_EXPECT_NAK_CRC = ERROR_WHY % _('expected /, got "%02x"') ERROR_EXPECT_SOH_EOT = ERROR_WHY % _('expected /, got "%02x"') diff --git a/modem/protocol/xmodem.py b/modem/protocol/xmodem.py index 804f895..aa0b93f 100644 --- a/modem/protocol/xmodem.py +++ b/modem/protocol/xmodem.py @@ -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 @@ -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): ''' @@ -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 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: @@ -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, @@ -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 @@ -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 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() @@ -160,16 +170,18 @@ 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 @@ -177,9 +189,9 @@ def recv(self, stream, crc_mode=1, retry=16, timeout=60, delay=1, quiet=0): 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: @@ -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 OR # SO START DIRECTLY WITH STREAM TRANSMISSION sequence = 1 error_count = 0 + total_sent = 0 while True: data = stream.read(packet_size) @@ -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, @@ -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: @@ -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) @@ -254,12 +279,12 @@ def _send_packet(self, sequence, data, packet_size, crc_mode, crc, self.putc(chr(crc)) # Wait for the - 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 @@ -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 - char = self.getc(1, timeout) - if char == ACK: + byte = self.getc(1, timeout) + if byte == const.ACK: # confirmed return True else: @@ -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 @@ -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): ''' @@ -346,11 +372,11 @@ 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) @@ -358,17 +384,17 @@ def _recv_stream(self, stream, crc_mode, retry, timeout, delay): 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 @@ -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 - 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) diff --git a/modem/protocol/ymodem.py b/modem/protocol/ymodem.py index 4f307cc..4c930b8 100644 --- a/modem/protocol/ymodem.py +++ b/modem/protocol/ymodem.py @@ -1,8 +1,11 @@ import glob import os -from modem.const import * +import time +from threading import Thread +from modem import const from modem.tools import log from modem.protocol.xmodem import XMODEM +from modem import error class YMODEM(XMODEM): @@ -11,7 +14,12 @@ class YMODEM(XMODEM): object to write to. ''' - protocol = PROTOCOL_YMODEM + protocol = const.PROTOCOL_YMODEM + + def __init__(self, getc, putc): + XMODEM.__init__(self, getc, putc) + self.thread = None + return def send(self, pattern, retry=16, timeout=60): ''' @@ -32,9 +40,9 @@ def send(self, pattern, retry=16, timeout=60): # initialize protocol error_count = 0 crc_mode = 0 - start_char = self._wait_recv(error_count, timeout) - if start_char: - crc_mode = 1 if (start_char == CRC) else 0 + start_byte = self._wait_recv(error_count, timeout) + if start_byte: + crc_mode = 1 if (start_byte == const.CRC) else 0 else: log.error(error.ABORT_PROTOCOL) # Already aborted @@ -45,23 +53,26 @@ def send(self, pattern, retry=16, timeout=60): sequence = 0 error_count = 0 # REQUIREMENT 1,1a,1b,1c,1d - data = ''.join([os.path.basename(filename), '\x00']) + data = ''.join([os.path.basename(filename), '\x00', str(os.path.getsize(filename))]) - log.debug(error.DEBUG_START_FILE % (filename,)) + log.debug(error.DEBUG_START_FILENAME % (filename,)) # Pick a suitable packet length for the filename packet_size = 128 if (len(data) < 128) else 1024 # Packet padding - data = data.ljust(packet_size, '\0') + data = data.ljust(packet_size, '\0').encode('utf-8') # Calculate checksum - crc = self.calc_crc(data) if crc_mode else self.calc_checksum(data) + crc = crc_mode and self.calc_crc16(data) or \ + self.calc_checksum(data) # Emit packet - if not self._send_packet(sequence, data, packet_size, crc_mode, - crc, error_count, retry, timeout): + if not self._send_packet( + sequence, data, packet_size, crc_mode, + crc, error_count, retry, timeout): self.abort(timeout=timeout) return False + log.debug(error.DEBUG_FILENAME_SENT.format(filename)) # Wait for before transmitting the file contents error_count = 0 @@ -70,14 +81,16 @@ def send(self, pattern, retry=16, timeout=60): return False filedesc = open(filename, 'rb') + filesize = os.path.getsize(filename) # AT THIS POINT # - PACKET 0 WITH METADATA TRANSMITTED # - INITIAL OR ALREADY RECEIVED - if not self._send_stream(filedesc, crc_mode, retry, timeout): + if not self._send_stream(filedesc, crc_mode, retry, timeout, filesize): log.error(error.ABORT_SEND_STREAM) return False + log.debug(error.DEBUG_FILE_SENT.format(filename)) # AT THIS POINT # - FILE CONTENTS TRANSMITTED @@ -96,12 +109,13 @@ def send(self, pattern, retry=16, timeout=60): sequence = 0 error_count = 0 packet_size = 128 - data = '\x00' * packet_size - crc = self.calc_crc(data) if crc_mode else self.calc_checksum(data) + data = b'\x00' * packet_size + crc = self.calc_crc16(data) if crc_mode else self.calc_checksum(data) # Emit packet - if not self._send_packet(sequence, data, packet_size, crc_mode, crc, - error_count, retry, timeout): + if not self._send_packet( + sequence, data, packet_size, crc_mode, crc, + error_count, retry, timeout): log.error(error.ABORT_SEND_PACKET) # Already aborted return False @@ -109,6 +123,20 @@ def send(self, pattern, retry=16, timeout=60): # All went fine return True + def send_threaded(self, pattern, retry=16, timeout=60): + if self.thread == None: + self.thread = Thread(target=self.send, args=(pattern, retry, timeout,)) + self.thread.daemon = True + self.thread.start() + return + + def send_join(self): + self.thread.join() + return + + def get_progress(self): + return XMODEM.get_progress(self) + def recv(self, basedir, crc_mode=1, retry=16, timeout=60, delay=1): ''' Receive some files via the YMODEM protocol and place them under @@ -125,7 +153,7 @@ def recv(self, basedir, crc_mode=1, retry=16, timeout=60, delay=1): ''' # Initiate protocol error_count = 0 - char = 0 + byte = 0 cancel = 0 sequence = 0 num_files = 0 @@ -135,21 +163,21 @@ def recv(self, basedir, crc_mode=1, retry=16, timeout=60, delay=1): self.abort(timeout=timeout) return None elif crc_mode and error_count < (retry / 2): - if not self.putc(CRC): + if not self.putc(const.CRC): time.sleep(delay) error_count += 1 else: crc_mode = 0 - if not self.putc(NAK): + if not self.putc(const.NAK): time.sleep(delay) error_count += 1 # or sent, waiting answer - char = self.getc(1, timeout) - if char is None: + byte = self.getc(1, timeout) + if byte is None: error_count += 1 continue - elif char == CAN: + elif byte == const.CAN: if cancel: log.error(error.ABORT_RECV_CAN_CAN) return None @@ -157,7 +185,7 @@ def recv(self, basedir, crc_mode=1, retry=16, timeout=60, delay=1): log.debug(error.DEBUG_RECV_CAN) cancel = 1 continue - elif char in [SOH, STX]: + elif byte in [const.SOH, const.STX]: break else: error_count += 1 @@ -168,59 +196,59 @@ def recv(self, basedir, crc_mode=1, retry=16, timeout=60, delay=1): while True: # Read next file in batch mode while True: - if char is None: + if byte is None: error_count += 1 - elif char == CAN: + elif byte == const.CAN: if cancel: log.error(error.ABORT_RECV_CAN_CAN) return None else: - log.debug(debug.DEBUG_RECV_CAN) + log.debug(error.DEBUG_RECV_CAN) cancel = 1 continue - elif char in [SOH, STX]: + elif byte in [const.SOH, const.STX]: seq1 = ord(self.getc(1)) seq2 = 0xff - ord(self.getc(1)) if seq1 == sequence and seq2 == sequence: - packet_size = 128 if char == SOH else 1024 + packet_size = 128 if byte == const.SOH else 1024 data = self.getc(packet_size + 1 + crc_mode) data = self._check_crc(data, crc_mode) if data: filename = data.split('\x00')[0] if not filename: # No filename, end of batch reception - self.putc(ACK) + self.putc(const.ACK) return num_files - log.info('Receiving %s to %s' % (filename, - basedir)) - fileout = open(os.path.join(basedir, - os.path.basename(filename)), 'wb') + log.info('Receiving %s to %s' % + (filename, basedir)) + fileout = open(os.path.join( + basedir, os.path.basename(filename)), 'wb') if not fileout: log.error(error.ABORT_OPEN_FILE) - self.putc(NAK) + self.putc(const.NAK) self.abort(timeout=timeout) return False else: - self.putc(ACK) + self.putc(const.ACK) break # Request retransmission if something went wrong self.getc(packet_size + 1 + crc_mode) - self.putc(NAK) + self.putc(const.NAK) self.getc(1, timeout) continue else: error_count += 1 self.getc(packet_size + 1 + crc_mode) - self.putc(NAK) + self.putc(const.NAK) self.getc(1, timeout) - stream_size = self._recv_stream(fileout, crc_mode, retry, timeout, - delay) + stream_size = self._recv_stream( + fileout, crc_mode, retry, timeout, delay) if not stream_size: log.error(error.ABORT_RECV_STREAM) @@ -232,5 +260,5 @@ def recv(self, basedir, crc_mode=1, retry=16, timeout=60, delay=1): sequence = 0 # Ask for the next sequence and receive the reply - self.putc(CRC) - char = self.getc(1, timeout) + self.putc(const.CRC) + byte = self.getc(1, timeout)