from enum import Enum, unique # TODO Throughout, decide whether instance variables that could be # an ENUM type should hold their ENUM (which indicates its meaning) # or their VALUE (which is usually just an integer). # The following Enum specifies the code that should be sent to the # Arduino to determine the pin to use. @unique class SIGNAL(Enum): LED = 0 buzzer = 1 button_sensor = 2 left_bump_sensor = 3 right_bump_sensor = 4 left_reflectance_sensor = 5 middle_reflectance_sensor = 6 right_reflectance_sensor = 7 left_encoder = 8 right_encoder = 9 left_proximity_sensor = 10 front_proximity_sensor = 11 right_proximity_sensor = 12 left_motor_control_1 = 13 left_motor_control_2 = 14 left_motor_pwm = 15 right_motor_control_1 = 16 right_motor_control_2 = 17 right_motor_pwm = 18 pixy_camera = 19 class COMMAND_NUMBER(Enum): analog_read = 0 analog_write = 1 digital_read = 2 digital_write = 3 pin_mode = 4 tone = 5 no_tone = 6 # Not used, implemented as a tone of 0 on Arduino pixy_camera = 7 class Command(object): """ Represents a robot command that can be sent to the Arduino for execution. """ def __init__(self, command_number, pin_number=None, number_of_bytes_to_receive=1, number_received_varies=False): self.command_number = command_number self.pin_number = pin_number self.number_of_bytes_to_receive = number_of_bytes_to_receive self.number_of_bytes_to_receive_varies = number_received_varies # TODO: implement a __repr__ and/or __str__ def to_bytes(self, data=None): """ This is a slow, default implementation. Subclasses can improve upon it. In this implementation: -- The command_number is byte 1. -- The signal is byte 2. -- The data is sent as one might expect: -- byte as a byte The rest of this is not yet implemented: -- bytes as bytes -- strings as sequences of characters (left to right??) -- 16-bit ints as 2 bytes (big-endian??) -- TODO: floats et al. Is there a library for this? """ command_number_byte = self._enum_to_value(self.command_number) signal_number_byte = self._enum_to_value(self.pin_number) data = self._enum_to_value(data) byte_array = bytearray() byte_array.append(command_number_byte) if signal_number_byte is not None: byte_array.append(signal_number_byte) else: byte_array.append(0) # Any byte would be fine here # FIXME: for now, assume data is a small integer # (that fits into a single byte). if data is not None: byte_array.append(data) else: byte_array.append(0) # Any byte would be fine here # FIXME: Need error-handling with good messages here # and elsewhere. return bytes(byte_array) @staticmethod def _enum_to_value(x): return x.value if isinstance(x, Enum) else x class LEDCommand(Command): def __init__(self): super().__init__(COMMAND_NUMBER.digital_write, SIGNAL.LED) class BuzzerCommand(Command): def __init__(self): super().__init__(COMMAND_NUMBER.tone, 0) class MotorControlCommand(Command): def __init__(self, signal): super().__init__(COMMAND_NUMBER.digital_write, signal) class MotorPWMCommand(Command): def __init__(self, signal): super().__init__(COMMAND_NUMBER.analog_write, signal) class LeftMotorCommand(Command): pass class RightMotorCommand(Command): pass class EncoderResetCommand(Command): def __init__(self, signal): super().__init__(COMMAND_NUMBER.digital_write, signal) class SensorCommand(Command): def __init__(self, command_number, pin_number, number_of_bytes_to_receive=1, number_received_varies=False): super().__init__(command_number, pin_number, number_of_bytes_to_receive, number_received_varies) def value_of(self, bytes_received): """ Returns the CommandData that the given bytes object encodes. """ # TODO: Implement CommandData so that we can encode data # received if we want to. # TODO: Different Commands may return different types # of CommandData, I think. # For now, just pass along whatever the message contains, # assuming that it is 2 bytes (10-bit analog for many sensors). if type(bytes_received) is int: return bytes_received else: return ((bytes_received[0] << 8) # high 8 bits + bytes_received[1]) # low 8 bits class AnalogReadSensorCommand(SensorCommand): def __init__(self, signal): super().__init__(COMMAND_NUMBER.analog_read, signal, 2) class DigitalReadSensorCommand(SensorCommand): def __init__(self, signal): super().__init__(COMMAND_NUMBER.digital_read, signal, 1) class VariableBytesCommand(Command): def indicates_end_of_message(self, byte_received): # TODO Don't bury this rule here return byte_received == 0xff class PixyBlock: """ An object that the Pixy Camera sees. """ def __init__(self, x, y, width, height): # TODO Incorporate the signature and angle (and maybe ???) # self.signature = pixy_block_dictionary["signature"] self.x = x self.y = y self.width = width self.height = height # self.angle = pixy_block_dictionary["angle"] def size(self): return self.width * self.height class PixyCameraCommand(SensorCommand, VariableBytesCommand): def __init__(self): # TODO Verify that super is OK with this multiple inheritance super().__init__(COMMAND_NUMBER.pixy_camera, 1, 6, True) def value_of(self, bytes_received): if type(bytes_received) is int and bytes_received == 255: return None else: x = ((bytes_received[0] << 8) # high bit for x + bytes_received[1]) # low 8 bits for x y = bytes_received[2] width = ((bytes_received[3] << 8) # high bit for width + bytes_received[4]) # low 8 bits for width height = bytes_received[5] return PixyBlock(x, y, width, height) # The current implementation of the Arduino code does not # support these 4 commands. class AnalogReadCommand(Command): def __init__(self, pin_number): super().__init__(COMMAND_NUMBER.analog_read, pin_number) class AnalogWriteCommand(Command): def __init__(self, pin_number): super().__init__(COMMAND_NUMBER.analog_write, pin_number) class DigitalReadCommand(Command): def __init__(self, pin_number): super().__init__(COMMAND_NUMBER.digital_read, pin_number) class DigitalWriteCommand(Command): def __init__(self, pin_number): super().__init__(COMMAND_NUMBER.digital_write, pin_number) # PIN_LEFT_MOTOR_CONTROL_1 = 2 # PIN_LEFT_MOTOR_CONTROL_2 = 4 # PIN_LEFT_MOTOR_PWM = 5 # # PIN_RIGHT_MOTOR_CONTROL_1 = 7 # PIN_RIGHT_MOTOR_CONTROL_2 = 8 # PIN_RIGHT_MOTOR_PWM = 6 # # CODE_FOR_INPUT = 0 # CODE_FOR_OUTPUT = 1 # CODE_FOR_INPUT_PULLUP = 2 # # FORWARD = 1 # BACKWARD = -1 # STOP = 0 # class MotorCommand(Command): # def __init__(self, pin_number): # super().__init__(COMMAND_NUMBERS['analog write'], pin_number) def main(): """ Calls the TEST functions in this module. """ pass #----------------------------------------------------------------------- # If this module is running at the top level (as opposed to being # imported by another module), then call the 'main' function. #----------------------------------------------------------------------- if __name__ == '__main__': main()