From fd728d0b875886ef232a5bfcecef46a06a759376 Mon Sep 17 00:00:00 2001 From: Peter Law Date: Sat, 27 Jan 2018 21:41:21 +0000 Subject: [PATCH 1/6] Add a way to send responses to individual commands This will allow commands to send one-off replies without neeing to pollute the global board 'status' with command-specific response information. --- robotd/master.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/robotd/master.py b/robotd/master.py index 6f64454..2596d8e 100644 --- a/robotd/master.py +++ b/robotd/master.py @@ -115,6 +115,11 @@ def _send_board_status(self, connection): print('Sending board status:', board_status) connection.send(board_status) + def _send_command_response(self, connection, response): + message = {'response': response} + print('Sending command response:', message) + connection.send(message) + def run(self): """ Control this board. @@ -169,7 +174,9 @@ def run(self): continue if command != {}: - self.board.command(command) + response = self.board.command(command) + if response is not None: + self._send_command_response(connection, response) self._send_board_status(connection) From 10c0525639cc1fae2bffa46b2960b4437d4f3894 Mon Sep 17 00:00:00 2001 From: Peter Law Date: Sat, 27 Jan 2018 21:42:51 +0000 Subject: [PATCH 2/6] Make handling arduino command errors more specific --- robotd/devices.py | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/robotd/devices.py b/robotd/devices.py index 93be1c4..bca0083 100644 --- a/robotd/devices.py +++ b/robotd/devices.py @@ -4,6 +4,7 @@ import random import struct import subprocess +from typing import Any, List, Tuple import serial @@ -224,6 +225,18 @@ def command(self, cmd): self._buzz_piezo(cmd['buzz']) +class CommandError(RuntimeError): + """The servo assembly experienced an error in processing a command.""" + + def __init__(self, command: Tuple[Any, ...], error: str, comments: List[str]) -> None: + self.command = command + self.error = error + self.comments = comments + + def __str__(self): + return "\n".join([self.error, ''] + self.comments) + + class ServoAssembly(Board): """ A servo assembly. @@ -277,7 +290,7 @@ def start(self): self.make_safe() print('Finished initialising servo assembly on {}'.format(device)) - def _command(self, *args): + def _command(self, *args) -> List[str]: command_id = random.randint(1, 65535) while True: @@ -293,8 +306,8 @@ def _command(self, *args): print('Sending to servo assembly:', line) - comments = [] - results = [] + comments = [] # type: List[str] + results = [] # type: List[str] while True: line = self.connection.readline() @@ -323,10 +336,10 @@ def _command(self, *args): if b'unknown command' in line: break # try again else: - raise RuntimeError( - line[2:].decode('utf-8') + - '\n' + - '\n'.join(comments), + raise CommandError( + args, + line[2:].decode('utf-8'), + comments, ) elif line.startswith(b'# '): From 97e46764a9095c1947f4e476e96e4a7b11caaf60 Mon Sep 17 00:00:00 2001 From: Peter Law Date: Sat, 27 Jan 2018 22:18:48 +0000 Subject: [PATCH 3/6] Make arduino response errors more specific --- robotd/devices.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/robotd/devices.py b/robotd/devices.py index bca0083..1ce8c4c 100644 --- a/robotd/devices.py +++ b/robotd/devices.py @@ -237,6 +237,17 @@ def __str__(self): return "\n".join([self.error, ''] + self.comments) +class InvalidResponse(ValueError): + """The servo assembly emitted an response which could not be processed.""" + + def __init__(self, command: Tuple[Any, ...], response: bytes) -> None: + self.command = command + self.response = response + + def __str__(self): + return "Invalid response from Arduino: {!r}".format(self.response) + + class ServoAssembly(Board): """ A servo assembly. @@ -349,9 +360,7 @@ def _command(self, *args) -> List[str]: results.append(line[2:].decode('utf-8').strip()) else: - raise ValueError( - "Invalid response from Arduino: {!r}".format(line), - ) + raise InvalidResponse(args, line) except ValueError: break From 9e0049b178480bd0b1e55a4f165aeec21e982cfc Mon Sep 17 00:00:00 2001 From: Peter Law Date: Sat, 27 Jan 2018 22:19:39 +0000 Subject: [PATCH 4/6] Add support for generic commands This includes passing them through to the arduino as well as wrapping the responses in common boilerplate for interpretation by robot-api. --- robotd/devices.py | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/robotd/devices.py b/robotd/devices.py index 1ce8c4c..7525e8a 100644 --- a/robotd/devices.py +++ b/robotd/devices.py @@ -301,7 +301,7 @@ def start(self): self.make_safe() print('Finished initialising servo assembly on {}'.format(device)) - def _command(self, *args) -> List[str]: + def _command(self, *args, generic_command=False) -> List[str]: command_id = random.randint(1, 65535) while True: @@ -344,7 +344,7 @@ def _command(self, *args) -> List[str]: return results elif line.startswith(b'- '): - if b'unknown command' in line: + if b'unknown command' in line and not generic_command: break # try again else: raise CommandError( @@ -362,6 +362,12 @@ def _command(self, *args) -> List[str]: else: raise InvalidResponse(args, line) + except InvalidResponse: + if generic_command: + raise + else: + break + except ValueError: break @@ -407,6 +413,19 @@ def _read_ultrasound(self, trigger_pin, echo_pin): self._ultrasound_value = list(sorted(found_values))[1] / 1000.0 + def _generic_command(self, command): + try: + return { + 'status': 'ok', + 'data': self._command(*command), + } + except (CommandError, InvalidResponse) as e: + return { + 'status': 'error', + 'type': type(e).__name__, + 'description': str(e), + } + def status(self): return { 'servos': self._servo_status, @@ -449,6 +468,11 @@ def command(self, cmd): if len(read_ultrasound) == 2: self._read_ultrasound(read_ultrasound[0], read_ultrasound[1]) + # handle direct command access + command = cmd.get('command', []) + if command: + return self._generic_command(command) + # Grab the full list of boards from the workings of the metaclass BOARDS = BoardMeta.BOARDS From 4a68906ba5a498561a5f1ecdbf24d838bd9ddff0 Mon Sep 17 00:00:00 2001 From: Peter Law Date: Sun, 28 Jan 2018 00:22:37 +0000 Subject: [PATCH 5/6] Actually mark this as generic so we see the errors --- robotd/devices.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/robotd/devices.py b/robotd/devices.py index 7525e8a..b6bbffb 100644 --- a/robotd/devices.py +++ b/robotd/devices.py @@ -417,7 +417,7 @@ def _generic_command(self, command): try: return { 'status': 'ok', - 'data': self._command(*command), + 'data': self._command(*command, generic_command=True), } except (CommandError, InvalidResponse) as e: return { From bbf7ec912cd46896a2b7bb6b420145d1eecad029 Mon Sep 17 00:00:00 2001 From: Peter Law Date: Sun, 28 Jan 2018 15:48:21 +0000 Subject: [PATCH 6/6] Fix typo in docstring --- robotd/devices.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/robotd/devices.py b/robotd/devices.py index b6bbffb..bc213be 100644 --- a/robotd/devices.py +++ b/robotd/devices.py @@ -238,7 +238,7 @@ def __str__(self): class InvalidResponse(ValueError): - """The servo assembly emitted an response which could not be processed.""" + """The servo assembly emitted a response which could not be processed.""" def __init__(self, command: Tuple[Any, ...], response: bytes) -> None: self.command = command