From 0a89ebe86b1b1dfbcff39b015eab8e5bcee9c836 Mon Sep 17 00:00:00 2001 From: Hayden Roche Date: Sun, 1 Oct 2023 10:57:45 -0700 Subject: [PATCH] Fix a couple problems. - Fail example scripts if an exception is raised at any point. - Rework `_crc_add` to produce a reliable CRC. This requires us to rely more on string manipulation and less on python dicts. Prior to this commit, the CRC was computed over the string representation of the request (via `json.dumps`) and then it was added to the request dict. Then, when it was time to serialize the request for transmission and we converted the dict to a string again, because the CRC field had been added, the order of the fields changed and the CRC was invalidated. This is because Python dicts are fundamentally unordered collections. - Add missing timeout reset to `OpenSerial`'s `receive` method. - Increase cpy_example.py UART RX buffer size. I was seeing the end of responses get cut off with the default size (64). Increased to 128. --- examples/notecard-basics/cpy_example.py | 42 ++++++------------------- examples/notecard-basics/mpy_example.py | 41 +++++------------------- notecard/notecard.py | 26 ++++++++++----- test/test_notecard.py | 14 +++++---- 4 files changed, 43 insertions(+), 80 deletions(-) diff --git a/examples/notecard-basics/cpy_example.py b/examples/notecard-basics/cpy_example.py index 5ab42ec..bf6db92 100644 --- a/examples/notecard-basics/cpy_example.py +++ b/examples/notecard-basics/cpy_example.py @@ -14,20 +14,6 @@ import busio # noqa: E402 -def NotecardExceptionInfo(exception): - """Construct a formatted Exception string. - - Args: - exception (Exception): An exception object. - - Returns: - string: a summary of the exception with line number and details. - """ - name = exception.__class__.__name__ - return sys.platform + ": " + name \ - + ": " + " ".join(map(str, exception.args)) - - def configure_notecard(card, product_uid): """Submit a simple JSON-based request to the Notecard. @@ -39,11 +25,7 @@ def configure_notecard(card, product_uid): req["product"] = product_uid req["mode"] = "continuous" - try: - card.Transaction(req) - except Exception as exception: - print("Transaction error: " + NotecardExceptionInfo(exception)) - time.sleep(5) + card.Transaction(req) def get_temp_and_voltage(card): @@ -53,20 +35,13 @@ def get_temp_and_voltage(card): card (object): An instance of the Notecard class """ - temp = 0 - voltage = 0 - - try: - req = {"req": "card.temp"} - rsp = card.Transaction(req) - temp = rsp["value"] + req = {"req": "card.temp"} + rsp = card.Transaction(req) + temp = rsp["value"] - req = {"req": "card.voltage"} - rsp = card.Transaction(req) - voltage = rsp["value"] - except Exception as exception: - print("Transaction error: " + NotecardExceptionInfo(exception)) - time.sleep(5) + req = {"req": "card.voltage"} + rsp = card.Transaction(req) + voltage = rsp["value"] return temp, voltage @@ -75,7 +50,8 @@ def run_example(product_uid, use_uart=True): """Connect to Notcard and run a transaction test.""" print("Opening port...") if use_uart: - port = busio.UART(board.TX, board.RX, baudrate=9600) + port = busio.UART(board.TX, board.RX, baudrate=9600, + receiver_buffer_size=128) else: port = busio.I2C(board.SCL, board.SDA) diff --git a/examples/notecard-basics/mpy_example.py b/examples/notecard-basics/mpy_example.py index ba0c68e..a1cee74 100644 --- a/examples/notecard-basics/mpy_example.py +++ b/examples/notecard-basics/mpy_example.py @@ -16,20 +16,6 @@ from machine import Pin -def NotecardExceptionInfo(exception): - """Construct a formatted Exception string. - - Args: - exception (Exception): An exception object. - - Returns: - string: a summary of the exception with line number and details. - """ - name = exception.__class__.__name__ - return sys.platform + ": " + name + ": " \ - + " ".join(map(str, exception.args)) - - def configure_notecard(card, product_uid): """Submit a simple JSON-based request to the Notecard. @@ -41,11 +27,7 @@ def configure_notecard(card, product_uid): req["product"] = product_uid req["mode"] = "continuous" - try: - card.Transaction(req) - except Exception as exception: - print("Transaction error: " + NotecardExceptionInfo(exception)) - time.sleep(5) + card.Transaction(req) def get_temp_and_voltage(card): @@ -55,20 +37,13 @@ def get_temp_and_voltage(card): card (object): An instance of the Notecard class """ - temp = 0 - voltage = 0 - - try: - req = {"req": "card.temp"} - rsp = card.Transaction(req) - temp = rsp["value"] - - req = {"req": "card.voltage"} - rsp = card.Transaction(req) - voltage = rsp["value"] - except Exception as exception: - print("Transaction error: " + NotecardExceptionInfo(exception)) - time.sleep(5) + req = {"req": "card.temp"} + rsp = card.Transaction(req) + temp = rsp["value"] + + req = {"req": "card.voltage"} + rsp = card.Transaction(req) + voltage = rsp["value"] return temp, voltage diff --git a/notecard/notecard.py b/notecard/notecard.py index 0aa9e84..15fdeb9 100644 --- a/notecard/notecard.py +++ b/notecard/notecard.py @@ -129,7 +129,7 @@ def __init__(self, debug=False): self._card_supports_crc = False self._reset_required = True - def _crc_add(self, req, seq_number): + def _crc_add(self, req_string, seq_number): """Add a CRC field to the request. The CRC field also contains a sequence number and has this format: @@ -139,10 +139,18 @@ def _crc_add(self, req, seq_number): SSSS is the sequence number encoded as a string of 4 hex digits. CCCCCCCC is the CRC32 encoded as a string of 8 hex digits. """ - req_bytes = json.dumps(req, separators=(',', ':')).encode('utf-8') + req_bytes = req_string.encode('utf-8') crc_hex = '{:08x}'.format(crc32(req_bytes)) seq_number_hex = '{:04x}'.format(seq_number) - req['crc'] = f'{seq_number_hex}:{crc_hex}' + crc_field = f'"crc":"{seq_number_hex}:{crc_hex}"' + req_string_w_crc = req_string[:-1] + if req_string[-2] == '{': + req_string_w_crc += f'{crc_field}' + else: + req_string_w_crc += f',{crc_field}' + req_string_w_crc += '}' + + return req_string_w_crc def _crc_error(self, rsp_bytes): """Check the CRC in a Notecard response.""" @@ -199,12 +207,13 @@ def _prepare_request(self, req): rsp_expected = 'req' in req # If this is a request and not a command, add a CRC. + req_string = json.dumps(req, separators=(',', ':')) if rsp_expected: - self._crc_add(req, self._last_request_seq_number) + req_string = self._crc_add(req_string, + self._last_request_seq_number) # Serialize the JSON request to a string, removing any unnecessary # whitespace. - req_string = json.dumps(req, separators=(',', ':')) if self._debug: print(req_string) @@ -320,7 +329,7 @@ def Transaction(self, req, lock=True): if '{io}' in rsp_json['err']: if self._debug: print(('Response has error field indicating I/O' - ' error.')) + f' error: {rsp_json}')) error = True retries_left -= 1 @@ -329,8 +338,8 @@ def Transaction(self, req, lock=True): elif '{bad-bin}' in rsp_json['err']: if self._debug: print(('Response has error field indicating ' - 'binary I/O error. Not eligible for ' - 'retry.')) + f'binary I/O error: {rsp_json}')) + print('Not eligible for retry.') error = True break @@ -433,6 +442,7 @@ def receive(self, timeout_secs=CARD_INTRA_TRANSACTION_TIMEOUT_SEC, time.sleep(.001) timeout_secs = CARD_INTRA_TRANSACTION_TIMEOUT_SEC + start = start_timeout() byte = self._read_byte() data.extend(byte) received_newline = byte == b'\n' diff --git a/test/test_notecard.py b/test/test_notecard.py index 395e381..fcf8416 100644 --- a/test/test_notecard.py +++ b/test/test_notecard.py @@ -49,23 +49,25 @@ def test_txn_manager_is_valid_after_pins_set(self): # _crc_add tests def test_crc_add_adds_a_crc_field(self): card = notecard.Notecard() - req = {'req': 'hub.status'} + req = '{"req":"hub.status"}' - card._crc_add(req, 0) + req_string = card._crc_add(req, 0) - assert 'crc' in req + req_json = json.loads(req_string) + assert 'crc' in req_json def test_crc_add_formats_the_crc_field_correctly(self): card = notecard.Notecard() - req = {'req': 'hub.status'} + req = '{"req":"hub.status"}' seq_number = 37 - card._crc_add(req, seq_number) + req_string = card._crc_add(req, seq_number) + req_json = json.loads(req_string) # The format should be SSSS:CCCCCCCC, where S and C are hex digits # comprising the sequence number and CRC32, respectively. pattern = r'^[0-9A-Fa-f]{4}:[0-9A-Fa-f]{8}$' - assert re.match(pattern, req['crc']) + assert re.match(pattern, req_json['crc']) # _crc_error tests. @pytest.mark.parametrize('crc_supported', [False, True])