""" A wrapper for the Webots motor device. The motor will apply a small amount of variation to the power setting to simulate inaccuracies in the motor. """ import logging from abc import ABC, abstractmethod from sbot_interface.devices.util import ( WebotsDevice, add_jitter, get_globals, get_robot_device, map_to_range, ) MAX_POWER = 1000 MIN_POWER = -1000 class BaseMotor(ABC): """Base class for motor devices.""" @abstractmethod def disable(self) -> None: """Disable the motor.""" pass @abstractmethod def set_power(self, value: int) -> None: """Set the power of the motor (±1000).""" pass @abstractmethod def get_power(self) -> int: """Get the power of the motor.""" pass @abstractmethod def get_current(self) -> int: """Get the current draw of the motor in mA.""" pass @abstractmethod def enabled(self) -> bool: """Check if the motor is enabled.""" pass class NullMotor(BaseMotor): """Null motor device. Allows the robot to run without a motor device attached.""" def __init__(self) -> None: self.power = 0 self._enabled = False def disable(self) -> None: """Disable the motor.""" self._enabled = False def set_power(self, value: int) -> None: """Set the power of the motor.""" self.power = value self._enabled = True def get_power(self) -> int: """Get the power of the motor.""" return self.power def get_current(self) -> int: """Get the current draw of the motor in mA.""" return 0 def enabled(self) -> bool: """Check if the motor is enabled.""" return self._enabled class Motor(BaseMotor): """ A wrapper for the Webots motor device. The motor will apply a small amount of variation to the power setting to simulate inaccuracies in the motor. :param device_name: The name of the motor device. """ def __init__(self, device_name: str) -> None: self.power = 0 self._enabled = False g = get_globals() self._device = get_robot_device(g.robot, device_name, WebotsDevice.Motor) # Put the motor in velocity control mode self._device.setPosition(float('inf')) self._device.setVelocity(0) self._max_speed = self._device.getMaxVelocity() # Limit the torque the motor can apply to have realistic acceleration self._device.setAvailableTorque(1) def disable(self) -> None: """Disable the motor.""" self._device.setVelocity(0) self._enabled = False def set_power(self, value: int) -> None: """ Set the power of the motor. :param value: The power setting for the motor. A value between -1000 and 1000. """ if value != 0: if abs(value) < 0.05: logging.warning( "Motor power is too low, values below 0.05 will not move the motor." ) value = 0 else: # Apply a small amount of variation to the power setting to simulate # inaccuracies in the motor value = int(add_jitter(value, (MIN_POWER, MAX_POWER))) self._device.setVelocity(map_to_range( value, (MIN_POWER, MAX_POWER), (-self._max_speed, self._max_speed), )) self.power = value self._enabled = True def get_power(self) -> int: """ Get the power of the motor. :return: The power setting for the motor. A value between -1000 and 1000. """ return self.power def get_current(self) -> int: """ Get the current draw of the motor in mA. :return: The current draw of the motor in mA. """ # TODO calculate from torque feedback return 0 def enabled(self) -> bool: """ Check if the motor is enabled. :return: True if the motor is enabled, False otherwise. """ return self._enabled