Module isotope.port.motor

Contains MotorPort and Motor classes, used to handle the communication with the MOT ports on the Isotope board.

MotorPort class inherits from the IsotopePort class as the actual implementation of the communicaiton protocol while the Motor class inherits from the IsotopePortContainer class as a list-like container that holds MotorPort instances for all available MOT ports on the Isotope board.

Notes

Users are encouraged to use the isotope.Isotope class to access the ports instead of creating their own instances of these class directly.

Example

import isotope

resolution = 7.5
current = 400
rpm = 50

usb_address = 'COM3'
port_id = 0

# Start the communication
isot = isotope.Isotope(usb_address)
isot.connect()

# Get motor port at port_id
motor = isot.motors[port_id]

# Configure motor port
motor.configure(resolution, current, rpm)

# Enable the motor port
result = motor.enable()

if not result or not motor.is_enabled():
    raise Exception("Failed to enable motor port.")

# Rotate the motor by 100 steps
motor.rotate_by_steps(100)

# Rotate the motor by 90 degrees
motor.rotate_by_degrees(90)

# Disable the motor port
motor.disable()

# Close the connection
isot.disconnect()

See Also

isotope.isotope

Classes

class Motor (comms: Isotope_comms_protocol)

The Motor class is a list-like container for MotorPort objects representing all the MOT ports on the Isotope board.

Args

comms : isotope_comms_lib.Isotope_comms_protocol
The instance of the Isotope_comms_protocol class that is used to communicate with the Isotope board.
Expand source code
class Motor(IsotopePortContainer[MotorPort]):
    """The Motor class is a list-like container for MotorPort objects representing all the MOT ports on the Isotope board.
    """

    def __init__(self, comms: icl.Isotope_comms_protocol) -> None:
        """
        Args:
            comms (isotope_comms_lib.Isotope_comms_protocol): The instance of the Isotope_comms_protocol class 
                that is used to communicate with the Isotope board.
        """
        super().__init__(comms, 4)
        self._ports = [MotorPort(comms, i) for i in range(self._max_port_count)]

Ancestors

class MotorPort (comms: Isotope_comms_protocol, port_id: int)

The MotorPort class is used to control the MOT ports, i.e. MOT 0, 1, 2 and 3, on the Isotope board.

Args

comms : isotope_comms_lib.Isotope_comms_protocol
The instance of the Isotope_comms_protocol class that is used to communicate with the Isotope board.
port_id : int
ID of the MOT port on the Isotope board. Valid values are 0, 1, 2 and 3.

Raises

ValueError
Invalid port ID. Valid values are 0, 1, 2 and 3.
Expand source code
class MotorPort(IsotopePort):
    """The MotorPort class is used to control the MOT ports, i.e. MOT 0, 1, 2 and 3, on the Isotope board.
    """

    def __init__(self, comms: icl.Isotope_comms_protocol, port_id: int) -> None:
        """
        Args:
            comms (isotope_comms_lib.Isotope_comms_protocol): The instance of the Isotope_comms_protocol class 
                that is used to communicate with the Isotope board.
            port_id (int): ID of the MOT port on the Isotope board. Valid values are 0, 1, 2 and 3.

        Raises:
            ValueError: Invalid port ID. Valid values are 0, 1, 2 and 3.
        """
        if port_id < 0 or port_id > 3:
            raise ValueError("Invalid port ID. Valid values are 0, 1, 2 and 3.")
        super().__init__(comms, port_id)
           
        self._resolution = 0
        self._rpm = 0
        self._current = 0
        self._enabled = False
        self._configure_requested = False
        self._configured = False

    def configure(self, resolution: int, current: int, rpm: int = 100) -> bool:
        """Setup the motor with the specified parameters.

        Args:
            resolution (int): Step resolution of the motor in degrees. Refer to the motor's data sheet for this value.
            current (int): Desired current value in milli amperes. Refer to the motor's data sheet for this value.
                    Note that higher current values will allow the motor to deliver more torque, but will also cause 
                    the motor to heat up more quickly. 
            rpm (int, optional): Desired speed value in RPM. Defaults to 100.
                    Note that the motor may skip steps if the RPM is set too high.

        Returns:
            bool: True if the motor was successfully configured, False otherwise.

        Raises:
            ValueError: Resolution must be positive values.
            ValueError: RPM must be positive values.
            ValueError: Current must be positive values.
        """
        if resolution < 0:
            raise ValueError("Resolution must be a positive value.")
        if rpm < 0:
            raise ValueError("RPM must be a positive value.")
        if current < 0:
            raise ValueError("Current must be a positive value.")

        self._configured = False
        self._configure_requested = True
        self._resolution = resolution
        self._rpm = rpm
        self._current = current

        try:
            self._configure()
        except icl.IsotopeCommsError:
            pass

    @property
    def rpm(self) -> int:
        """Get the configured RPM value of the motor.

        Returns:
            int: The current RPM value of the motor.
        """
        return self._rpm

    @property
    def current(self) -> int:
        """Get the configured current value of the motor in milli amperes.

        Returns:
            int: The current value of the motor in milli amperes.
        """
        return self._current

    @property
    def resolution(self) -> int:
        """Get the configured step resolution of the motor in degrees.

        Returns:
            int: The step resolution of the motor in degrees.
        """
        return self._resolution

    def enable(self) -> bool:
        """Enable the MOT ports. When this function is called, the MOT ports will be switched on and you 
            may notice the connected motor start to heat up.

        Warning: 
            Do not leave the MOT port switched on for extended periods of time at a high current setting 
            as this may cause the motor to overheat.

        Returns:
            bool: True if the MOT port was successfully enabled, False otherwise.
        """
        self._logger.debug(f"Enabling motor {self._id}...")
        if not self._configured:
            self._configure()
        msg = self._comms.send_cmd(
            icl.CMD_TYPE_SET, icl.SEC_MOTOR_ENABLE, self._id, 1)
        if self._comms.is_resp_ok(msg):
            self._enabled = True
            return True
        return False

    def disable(self) -> bool:
        """Disable the MOT ports. When this function is called, the MOT ports will be switched off.

        Returns:
            bool: True if the MOT port was successfully disabled, False otherwise.
        """
        self._logger.debug(f"Disabling motor {self._id}...")
        msg = self._comms.send_cmd(icl.CMD_TYPE_SET, icl.SEC_MOTOR_ENABLE, self._id, 0)
        if self._comms.is_resp_ok(msg):
            self._enabled = False
            return True
        return False

    def is_enabled(self) -> bool:
        """Check if the MOT port is enabled.

        Returns:
            bool: True if the MOT port is enabled, False otherwise.
        """
        # Potential vulnerability: The state is not read from the Isotope board, but stored in the class instance.
        return self._enabled

    def rotate_by_steps(self, steps: int) -> bool:
        """Command the motor to rotate by the specified number of steps.
        Note that this function blocks until the motor has completed the specified number of steps. 
        A low RPM value may cause the motor to rotate slowly and cause this function to block for an undesirably long time.

        Args:
            steps (int): The number of steps to rotate the motor by.

        Returns:
            bool: True if the command was successful, False otherwise.
        """
        self._logger.debug(f"Rotating motor {self._id} by {steps} steps...")
        if not self.is_enabled():
            return False
        msg = self._comms.send_cmd(icl.CMD_TYPE_SET, icl.SEC_MOTOR_STEP, self._id, steps)
        return self._comms.is_resp_ok(msg)

    def rotate_by_degrees(self, degrees: int) -> bool:
        """Command the motor to rotate by the specified angle in degrees.
        Note that this function blocks until the motor has completed the specified number of steps. 
        A low RPM value may cause the motor to rotate slowly and cause this function to block for an undesirably long time.

        Args:
            degrees (int): The angle in degrees.

        Returns:
            bool: True if the command was successful, False otherwise.
        """
        self._logger.debug(f"Rotating motor {self._id} by {degrees} degrees...")
        steps = degrees/self._resolution
        return self.rotate_by_steps(steps)

    def set_rpm(self, value: int) -> bool:
        """Set the RPM value of the motor.

        Args:
            value (int): The desired RPM value of the motor.

        Returns:
            bool: True if the RPM value was successfully set, False otherwise.
        """
        self._logger.debug(f"Setting RPM to {value}...")
        msg = self._comms.send_cmd(
            icl.CMD_TYPE_SET, icl.SEC_MOTOR_RPM_SPEED, self._id, value)
        if self._comms.is_resp_ok(msg):
            self._rpm = value
            return True
        return False

    def set_current(self, value: int) -> bool:
        """Set the current value of the motor in milli amperes.

        Args:
            value (int): The desired current value of the motor in milli amperes.

        Returns:
            bool: True if the current value was successfully set, False otherwise.
        """
        self._logger.debug(f"Setting current to {value} mA...")
        msg = self._comms.send_cmd(
            icl.CMD_TYPE_SET, icl.SEC_MOTOR_CURRENT_MILLIAMP, self._id, value)
        if self._comms.is_resp_ok(msg):
            self._current = value
            return True
        return False

    def _configure(self) -> bool:
        """Configure the motor with the specified parameters.

        Returns:
            bool: True if the configuration was successful, False otherwise.
        
        Raises:
            IsotopeCommsError: The MotorPort.configure method must be called before calling this method to set the correct parameters.
        """
        if not self._configure_requested:
            raise icl.IsotopeCommsError("Parameters are not set. Have you called the MotorPort.configure method?")
        self._logger.debug(f"Configuring motor {self._id}...")
        result = self.set_rpm(self._rpm) and self.set_current(self._current)
        if result:
            self._configured = True
            self._configure_requested = False
        self._logger.debug(f"{'Successfully configured' if result else 'Failed to configure'} motor {self._id}.")
        return result

Ancestors

Instance variables

prop current : int

Get the configured current value of the motor in milli amperes.

Returns

int
The current value of the motor in milli amperes.
Expand source code
@property
def current(self) -> int:
    """Get the configured current value of the motor in milli amperes.

    Returns:
        int: The current value of the motor in milli amperes.
    """
    return self._current
prop id : int

Inherited from: IsotopePort.id

Get the ID of the port …

prop resolution : int

Get the configured step resolution of the motor in degrees.

Returns

int
The step resolution of the motor in degrees.
Expand source code
@property
def resolution(self) -> int:
    """Get the configured step resolution of the motor in degrees.

    Returns:
        int: The step resolution of the motor in degrees.
    """
    return self._resolution
prop rpm : int

Get the configured RPM value of the motor.

Returns

int
The current RPM value of the motor.
Expand source code
@property
def rpm(self) -> int:
    """Get the configured RPM value of the motor.

    Returns:
        int: The current RPM value of the motor.
    """
    return self._rpm

Methods

def configure(self, resolution: int, current: int, rpm: int = 100) ‑> bool

Setup the motor with the specified parameters.

Args

resolution : int
Step resolution of the motor in degrees. Refer to the motor's data sheet for this value.
current : int
Desired current value in milli amperes. Refer to the motor's data sheet for this value. Note that higher current values will allow the motor to deliver more torque, but will also cause the motor to heat up more quickly.
rpm : int, optional
Desired speed value in RPM. Defaults to 100. Note that the motor may skip steps if the RPM is set too high.

Returns

bool
True if the motor was successfully configured, False otherwise.

Raises

ValueError
Resolution must be positive values.
ValueError
RPM must be positive values.
ValueError
Current must be positive values.
def disable(self) ‑> bool

Disable the MOT ports. When this function is called, the MOT ports will be switched off.

Returns

bool
True if the MOT port was successfully disabled, False otherwise.
def enable(self) ‑> bool

Enable the MOT ports. When this function is called, the MOT ports will be switched on and you may notice the connected motor start to heat up.

Warning: Do not leave the MOT port switched on for extended periods of time at a high current setting as this may cause the motor to overheat.

Returns

bool
True if the MOT port was successfully enabled, False otherwise.
def is_enabled(self) ‑> bool

Check if the MOT port is enabled.

Returns

bool
True if the MOT port is enabled, False otherwise.
def rotate_by_degrees(self, degrees: int) ‑> bool

Command the motor to rotate by the specified angle in degrees. Note that this function blocks until the motor has completed the specified number of steps. A low RPM value may cause the motor to rotate slowly and cause this function to block for an undesirably long time.

Args

degrees : int
The angle in degrees.

Returns

bool
True if the command was successful, False otherwise.
def rotate_by_steps(self, steps: int) ‑> bool

Command the motor to rotate by the specified number of steps. Note that this function blocks until the motor has completed the specified number of steps. A low RPM value may cause the motor to rotate slowly and cause this function to block for an undesirably long time.

Args

steps : int
The number of steps to rotate the motor by.

Returns

bool
True if the command was successful, False otherwise.
def set_current(self, value: int) ‑> bool

Set the current value of the motor in milli amperes.

Args

value : int
The desired current value of the motor in milli amperes.

Returns

bool
True if the current value was successfully set, False otherwise.
def set_rpm(self, value: int) ‑> bool

Set the RPM value of the motor.

Args

value : int
The desired RPM value of the motor.

Returns

bool
True if the RPM value was successfully set, False otherwise.