@lib.api_call
    def move(self, speed, angle=0):
        """Move holonomically without rotation.

        :param speed: Magnitude of robot's translation speed (% of max).
        :type speed: float
        :param angle: Angle of translation in degrees (90=left, 270=right).
        :type angle: float
        """

        # Validate params
        self.logger.debug("speed: {}, angle: {}".format(speed, angle))
        try:
            assert MecDriver.min_speed <= speed <= MecDriver.max_speed
        except AssertionError:
            raise AssertionError("Speed is out of bounds")

        # Angle bounds may be unnecessary

        try:
            assert MecDriver.min_angle <= angle <= MecDriver.max_angle
        except __HOLE__:
            raise AssertionError("Angle is out of bounds")

        # Handle zero speed, prevent divide-by-zero error
        if speed == 0:  # TODO deadband (epsilon) check?
            self.logger.debug("Special case for speed == 0")
            self.set_motor("front_left", 0)
            self.set_motor("front_right", 0)
            self.set_motor("back_left", 0)
            self.set_motor("back_right", 0)
            return

        # Calculate motor speeds
        # Formulae from Mecanumdrive.pdf in google drive.
        # TODO Check math: why are all the phase offsets +pi/4?
        front_left = speed * sin(angle * pi / 180 + pi / 4)
        front_right = speed * cos(angle * pi / 180 + pi / 4)
        back_left = speed * cos(angle * pi / 180 + pi / 4)
        back_right = speed * sin(angle * pi / 180 + pi / 4)
        self.logger.debug((
            "pre-scale : front_left: {:6.2f}, front_right: {:6.2f},"
            " back_left: {:6.2f}, back_right: {:6.2f}").format(
                front_left, front_right, back_left, back_right))

        # Find largest motor speed,
        # use that to normalize multipliers and maintain maximum efficiency
        max_wheel_speed = max(
            [fabs(front_left), fabs(front_right),
                fabs(back_left), fabs(back_right)]
        )
        front_left = front_left * speed / max_wheel_speed
        front_right = front_right * speed / max_wheel_speed
        back_left = back_left * speed / max_wheel_speed
        back_right = back_right * speed / max_wheel_speed
        self.logger.debug(
            ("post-scale: front_left: {:6.2f}, front_right: {:6.2f},"
                " back_left: {:6.2f}, back_right: {:6.2f}").format(
                    front_left, front_right, back_left, back_right))

        # Set motor speeds
        self.set_motor("front_left", front_left)
        self.set_motor("front_right", front_right)
        self.set_motor("back_left", back_left)
        self.set_motor("back_right", back_right)
dataset/ETHPy150Open IEEERobotics/bot/bot/driver/mec_driver.py/MecDriver.move