Skip to content

Instantly share code, notes, and snippets.

@kierdavis
Created January 26, 2019 14:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kierdavis/d4114454aa655c974eb5ce0c55988615 to your computer and use it in GitHub Desktop.
Save kierdavis/d4114454aa655c974eb5ce0c55988615 to your computer and use it in GitHub Desktop.
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