From d13eaa1d64223ef837605a7922b262ffbc35599a Mon Sep 17 00:00:00 2001 From: Tom Hendrikx Date: Tue, 13 Jun 2017 15:50:48 +0200 Subject: [PATCH 1/6] Improve timeout handling --- eSSP/eSSP.py | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/eSSP/eSSP.py b/eSSP/eSSP.py index 50d50fd..d5b68c1 100644 --- a/eSSP/eSSP.py +++ b/eSSP/eSSP.py @@ -21,7 +21,12 @@ class eSSP(object): # noqa def __init__(self, serialport='/dev/ttyUSB0', eSSPId=0, timeout=None): # noqa """Initialize a new eSSP object.""" - self.__ser = serial.Serial(serialport, 9600, timeout=timeout) + if timeout is None or timeout == 0: + serial_timeout = timeout + else: + serial_timeout = 1 + self.timeout = timeout + self.__ser = serial.Serial(serialport, 9600, timeout=serial_timeout) self.__eSSPId = eSSPId self.__sequence = '0x80' @@ -392,13 +397,27 @@ def send(self, command, no_process=0): return response def read(self, no_process=0): - response = self.__ser.read(3) - if len(response) < 3: - # we need the response length in order to continue - raise eSSPTimeoutError() - response += self.__ser.read(ord(response[2]) + 2) + attempt = 0 + bytes_read = [] + target_length = 3 + while True: + byte = self.__ser.read(1) + if byte: + bytes_read += byte + else: + attempt += 1 + if attempt > self.timeout: + raise eSSPTimeoutError() + + if len(bytes_read) >= 3 and target_length == 3: + # extract actual message length + target_length += ord(bytes_read[2]) + 2 + + if target_length > 3 and len(bytes_read) == target_length: + # read the complete response + break - response = self.arrayify_response(response) + response = self.arrayify_response(bytes_read) self._logger.debug("IN: " + ' '.join(response)) if no_process == 0: From 0ec07ac72de3d8c5a4d937db13b856790811022a Mon Sep 17 00:00:00 2001 From: Tom Hendrikx Date: Tue, 13 Jun 2017 16:03:22 +0200 Subject: [PATCH 2/6] Fix timeout calculation --- eSSP/eSSP.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eSSP/eSSP.py b/eSSP/eSSP.py index d5b68c1..e1e3013 100644 --- a/eSSP/eSSP.py +++ b/eSSP/eSSP.py @@ -406,7 +406,7 @@ def read(self, no_process=0): bytes_read += byte else: attempt += 1 - if attempt > self.timeout: + if attempt >= self.timeout: raise eSSPTimeoutError() if len(bytes_read) >= 3 and target_length == 3: From 8d3bedaa7a9dd196d0ef8735ae6d22177a49df3b Mon Sep 17 00:00:00 2001 From: Tom Hendrikx Date: Wed, 14 Jun 2017 11:13:30 +0200 Subject: [PATCH 3/6] Better timeout implementation --- eSSP/eSSP.py | 37 ++++++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/eSSP/eSSP.py b/eSSP/eSSP.py index e1e3013..f579d54 100644 --- a/eSSP/eSSP.py +++ b/eSSP/eSSP.py @@ -20,11 +20,20 @@ class eSSP(object): # noqa """General class for talking to an eSSP device.""" def __init__(self, serialport='/dev/ttyUSB0', eSSPId=0, timeout=None): # noqa - """Initialize a new eSSP object.""" + """ + Initialize a new eSSP object. + + The timeout parameter corresponds to the pySerial timeout parameter, + but is used a bit diffreent internally. When the parameter isn't set + to None (blocking, no timeout) or 0, (non-blocking, return directly), + we set a timeout of 0.1 seconds on the serial port, and perform reads + until the specified timeout is expired. When the timeout is reached + before the requested data is read, a eSSPTimeoutError will be raised. + """ if timeout is None or timeout == 0: serial_timeout = timeout else: - serial_timeout = 1 + serial_timeout = 0.1 self.timeout = timeout self.__ser = serial.Serial(serialport, 9600, timeout=serial_timeout) self.__eSSPId = eSSPId @@ -397,24 +406,26 @@ def send(self, command, no_process=0): return response def read(self, no_process=0): - attempt = 0 + """Read the requested data from the serial port.""" bytes_read = [] - target_length = 3 + # initial response length is only the header. + expected_bytes = 3 + timeout_expired = datetime.datetime.now() + datetime.timedelta(seconds=self.timeout) while True: - byte = self.__ser.read(1) + byte = self.__ser.read() if byte: bytes_read += byte else: - attempt += 1 - if attempt >= self.timeout: - raise eSSPTimeoutError() + if datetime.datetime.now() > timeout_expired: + raise eSSPTimeoutError('Unable to read the expected response of {} bytes within {} seconds'.format( + expected_bytes, self.timeout)) - if len(bytes_read) >= 3 and target_length == 3: - # extract actual message length - target_length += ord(bytes_read[2]) + 2 + if expected_bytes == 3 and len(bytes_read) >= 3: + # extract the actual message length + expected_bytes += ord(bytes_read[2]) + 2 - if target_length > 3 and len(bytes_read) == target_length: - # read the complete response + if expected_bytes > 3 and len(bytes_read) == expected_bytes: + # we've read the complete response break response = self.arrayify_response(bytes_read) From edb675fc550a0beb1530cd673a86cd7a6528cd49 Mon Sep 17 00:00:00 2001 From: Tom Hendrikx Date: Wed, 14 Jun 2017 11:29:31 +0200 Subject: [PATCH 4/6] Add comment about handling timeout --- eSSP/eSSP.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/eSSP/eSSP.py b/eSSP/eSSP.py index f579d54..af63c59 100644 --- a/eSSP/eSSP.py +++ b/eSSP/eSSP.py @@ -24,7 +24,7 @@ def __init__(self, serialport='/dev/ttyUSB0', eSSPId=0, timeout=None): # noqa Initialize a new eSSP object. The timeout parameter corresponds to the pySerial timeout parameter, - but is used a bit diffreent internally. When the parameter isn't set + but is used a bit different internally. When the parameter isn't set to None (blocking, no timeout) or 0, (non-blocking, return directly), we set a timeout of 0.1 seconds on the serial port, and perform reads until the specified timeout is expired. When the timeout is reached @@ -416,6 +416,7 @@ def read(self, no_process=0): if byte: bytes_read += byte else: + # when the socket doesn't give us any data, evaluate the timeout if datetime.datetime.now() > timeout_expired: raise eSSPTimeoutError('Unable to read the expected response of {} bytes within {} seconds'.format( expected_bytes, self.timeout)) From 510a45122fd428a2118fc4df8cc0bbc804581d40 Mon Sep 17 00:00:00 2001 From: Tom Hendrikx Date: Wed, 14 Jun 2017 11:40:30 +0200 Subject: [PATCH 5/6] Python-ify process argument --- eSSP/eSSP.py | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/eSSP/eSSP.py b/eSSP/eSSP.py index af63c59..8c81e4e 100644 --- a/eSSP/eSSP.py +++ b/eSSP/eSSP.py @@ -81,7 +81,7 @@ def setup_request(self): # 2 = Std Security # 3 = High Security # 4 = Inhibited - result = self.send([self.getseq(), '0x1', '0x5'], 1) + result = self.send([self.getseq(), '0x1', '0x5'], False) unittype = int(result[4], 16) @@ -146,7 +146,7 @@ def poll(self): 0xE3 = Cash Box Removed (Protocol v3) 0xE4 = Cash Box Replaced (Protocol v3) """ - result = self.send([self.getseq(), '0x1', '0x7'], 1) + result = self.send([self.getseq(), '0x1', '0x7'], False) poll_data = [] for i in range(3, int(result[2], 16) + 3): @@ -181,7 +181,7 @@ def enable(self): def serial_number(self): """Return formatted serialnumber.""" - result = self.send([self.getseq(), '0x1', '0xC'], 1) + result = self.send([self.getseq(), '0x1', '0xC'], False) serial = 0 for i in range(4, 8): @@ -196,7 +196,7 @@ def unit_data(self): # Country-Code # Value-Multiplier # Protocol-Version - result = self.send([self.getseq(), '0x1', '0xD'], 1) + result = self.send([self.getseq(), '0x1', '0xD'], False) unittype = int(result[4], 16) @@ -225,7 +225,7 @@ def channel_values(self): - Number of Channels - Values of Channels """ - result = self.send([self.getseq(), '0x1', '0xE'], 1) + result = self.send([self.getseq(), '0x1', '0xE'], False) channels = int(result[4], 16) @@ -249,7 +249,7 @@ def channel_security(self): 3 = High Security 4 = Inhibited """ - result = self.send([self.getseq(), '0x1', '0xF'], 1) + result = self.send([self.getseq(), '0x1', '0xF'], False) channels = int(result[4], 16) @@ -267,7 +267,7 @@ def channel_reteach(self): Number of Channels Value of Reteach-Date array() """ - result = self.send([self.getseq(), '0x1', '0x10'], 1) + result = self.send([self.getseq(), '0x1', '0x10'], False) channels = int(result[4], 16) @@ -324,7 +324,7 @@ def last_reject(self): 0x19 = Width Detect Fail 0x1A = Short Note Detected """ - result = self.send([self.getseq(), '0x1', '0x17'], 1) + result = self.send([self.getseq(), '0x1', '0x17'], False) return result[4] def hold(self): @@ -379,7 +379,7 @@ def crc(self, command): crc = [hex((crc & 0xFF)), hex(((crc >> 8) & 0xFF))] return crc - def send(self, command, no_process=0): + def send(self, command, process=True): crc = self.crc(command) prepedstring = '7F' @@ -399,13 +399,10 @@ def send(self, command, no_process=0): self.__ser.write(prepedstring) - if no_process == 1: - response = self.read(1) - else: - response = self.read() + response = self.read(process) return response - def read(self, no_process=0): + def read(self, process=True): """Read the requested data from the serial port.""" bytes_read = [] # initial response length is only the header. @@ -432,7 +429,7 @@ def read(self, no_process=0): response = self.arrayify_response(bytes_read) self._logger.debug("IN: " + ' '.join(response)) - if no_process == 0: + if process: response = self.process_response(response) return response From ef74d17ef92045a16979fece49f8f1da781ae085 Mon Sep 17 00:00:00 2001 From: Tom Hendrikx Date: Wed, 14 Jun 2017 11:43:08 +0200 Subject: [PATCH 6/6] replac xrange with range, which is python3 compatible using 'xrange' in stead of 'range' in py2 has no advantage here --- eSSP/eSSP.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eSSP/eSSP.py b/eSSP/eSSP.py index 8c81e4e..03884c5 100644 --- a/eSSP/eSSP.py +++ b/eSSP/eSSP.py @@ -393,7 +393,7 @@ def send(self, command, process=True): prepedstring += command[i][2:] self._logger.debug("OUT: 0x" + ' 0x'.join([prepedstring[x:x + 2] - for x in xrange(0, len(prepedstring), 2)])) + for x in range(0, len(prepedstring), 2)])) prepedstring = prepedstring.decode('hex')