Created
January 26, 2019 14:56
-
-
Save kierdavis/d4114454aa655c974eb5ce0c55988615 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from abc import ABCMeta, abstractmethod | |
class Component(metaclass=ABCMeta): | |
""" | |
Represents a single element of the robot hardware that may be operated by the user logic. | |
Examples include: | |
- a motor | |
- a servo | |
- a power board output | |
- a GPIO pin | |
- an ultrasonic proximity sensor | |
- a competition-mode USB stick carrying start zone information | |
""" | |
class Servo(Component): | |
@abstractmethod | |
def set_position(self, position: float) -> None: | |
raise NotImplementedError | |
class Board(metaclass=ABCMeta): | |
""" | |
Represents a collection of Components accessible through a single hardware driver, or the simulated equivalent thereof. | |
Implementations of this abstract class should be *specific* boards such as an SR V4 power board | |
rather than a generic "power board". | |
Examples include: | |
- SR V4 power board | |
- SR V4 motor board | |
- SR V4 servo board | |
- Arduino running Student Robotics firmware | |
- Arduino running Source Bots firmware (this provides a different set of capabilities to an SR Arduino and hence must be a different Board subclass). | |
""" | |
class V4ServoBoard(Board): | |
""" | |
Represents an SR V4 servo board which may be real or simulated. | |
""" | |
@abstractmethod | |
def get_servos(self) -> Mapping[int, Servo]: | |
pass | |
class SBServoAssembly(Board): | |
""" | |
Represents a SourceBots servo assembly (Arduino + servo shield). | |
""" | |
@abstractmethod | |
def get_servos(self) -> Mapping[int, Servo]: | |
pass | |
# Other accessors e.g. get_gpio_pins, get_led, get_ultrasound_sensor would also be defined here. | |
B = TypeVar("B", Board) | |
class Environment: | |
""" | |
Represents a way of mapping a set of abstract Board APIs to corresponding concrete implementations for a particular environment. | |
""" | |
_mapping: Mapping[B, B] = None | |
def __init__(self): | |
self._mapping = {} | |
def register(self, abstract_board: B, concrete_board: B) -> None: | |
# TODO: not sure how to specify to the type checker in this section that concrete_board must be a subclass of abstract_board. | |
assert issubclass(concrete_board, abstract_board) | |
self._mapping[abstract_board] = concrete_board | |
def get_implementation(self, abstract_board: B) -> B: | |
return self._mapping[abstract_board] | |
hardware_environment = Environment() | |
class HardwareV4ServoBoard(V4ServoBoard): | |
""" | |
Represents a real SR V4 servo board, as opposed to a simulated one. | |
""" | |
class HardwareServo(Servo): | |
""" | |
Represents a servo attached to a real SR V4 servo board. | |
""" | |
_board: HardwareV4ServoBoard = None | |
_index: int = None | |
def __init__(self, board, index): | |
self._board = board | |
self._index = index | |
def set_position(self, position: float) -> None: | |
# Psuedocode: | |
self._board._usb_connection.send(("set servo position", self._index, position)) | |
@classmethod | |
def discover(class_) -> Set[HardwareV4ServoBoard]: | |
# Psuedocode: | |
return set(class_(usb_device) for usb_device in list_usb_devices() if is_v4_servo_board(usb_device)) | |
def __init__(self, usb_device): | |
# Psuedocode: | |
self._usb_connection = open_usb_connection(usb_device) | |
def get_servos(self) -> Mapping[int, Servo]: | |
return {n: HardwareServo(self, n) for n in range(12)} | |
# This causes hardware_environment to resolve V4ServoBoards to HardwareV4ServoBoards | |
hardware_environment.register(V4ServoBoard, HardwareV4ServoBoard) | |
class HardwareSBServoAssembly(SBServoAssembly): | |
class HardwareServo(Servo): | |
def __init__(self, board, index): | |
self._board = board | |
self._index = index | |
def set_position(self, position: float) -> None: | |
# Psuedocode: | |
self._board._serial_connection.send(("set servo position", self._index, position)) | |
@classmethod | |
def discover(class_) -> Set[HardwareSBServoAssembly]: | |
# Psuedocode: | |
return set(class_(usb_device) for usb_device in list_usb_devices() if is_arduino(usb_device)) | |
def __init__(self, usb_device): | |
# Psuedocode: | |
self._serial_connection = open_serial_connection(usb_device) | |
def get_servos(self) -> Mapping[int, Servo]: | |
return {n: HardwareServo(self, n) for n in range(16)} | |
hardware_environment.register(SBServoAssembly, HardwareSBServoAssembly) | |
class SRRobot: | |
servo_boards: Set[V4ServoBoard] = None | |
servo_board: V4ServoBoard = None | |
servos: Mapping[int, Servo] = None | |
def __init__(self, environment: Environment = hardware_environment): | |
self.servo_boards = environment.get_implementation(V4ServoBoard).discover() | |
self.servo_board = singleton(self.servo_boards) | |
self.servos = self.servo_board.get_servos() | |
class SBRobot: | |
servo_assemblies: Set[SBServoAssembly] = None | |
servo_assembly: SBServoAssembly = None | |
servos: Mapping[int, Servo] = None | |
def __init__(self, environment: Environment = hardware_environment): | |
self.servo_assemblies = environment.get_implementation(SBServoAssembly).discover() | |
self.servo_assembly = singleton(self.servo_assemblies) | |
self.servos = self.servo_assembly.get_servos() | |
# Note that both SRRobot and SBRobot have an identical 'servos' attribute despite them being backed by different hardware implementations. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment