reachy2_sdk.dynamixel.dynamixel_motor

Reachy DynamixelMotor module.

Handles all specific methods to DynamixelMotor.

  1"""Reachy DynamixelMotor module.
  2
  3Handles all specific methods to DynamixelMotor.
  4"""
  5
  6import logging
  7from typing import Optional
  8
  9from google.protobuf.wrappers_pb2 import BoolValue, FloatValue
 10from grpc import Channel
 11from reachy2_sdk_api.component_pb2 import ComponentId
 12from reachy2_sdk_api.dynamixel_motor_pb2 import (
 13    DynamixelMotorCommand,
 14    DynamixelMotorsCommand,
 15    DynamixelMotorState,
 16)
 17from reachy2_sdk_api.dynamixel_motor_pb2_grpc import DynamixelMotorServiceStub
 18
 19from ..orbita.utils import to_internal_position, to_position
 20
 21
 22class DynamixelMotor:
 23    """The DynamixelMotor class represents any Dynamixel motor.
 24
 25    The DynamixelMotor class is used to store the up-to-date state of the motor, especially:
 26    - its present_position (RO)
 27    - its goal_position (RW)
 28    """
 29
 30    def __init__(
 31        self,
 32        uid: int,
 33        name: str,
 34        initial_state: DynamixelMotorState,
 35        grpc_channel: Channel,
 36    ):
 37        """Initialize the DynamixelMotor with its initial state and configuration.
 38
 39        This sets up the motor by assigning its state based on the provided initial values.
 40
 41        Args:
 42            uid: The unique identifier of the component.
 43            name: The name of the component.
 44            initial_state: A dictionary containing the initial state of the joint, with
 45                each entry representing a specific parameter of the joint (e.g., present position).
 46            grpc_channel: The gRPC channel used to communicate with the DynamixelMotor service.
 47        """
 48        self._logger = logging.getLogger(__name__)
 49        self._name = name
 50        self._id = uid
 51        self._stub = DynamixelMotorServiceStub(grpc_channel)
 52        self._update_with(initial_state)
 53        self._outgoing_goal_position: Optional[float] = None
 54
 55    def __repr__(self) -> str:
 56        """Clean representation of the DynamixelMotor."""
 57        repr_template = "<DynamixelMotor on={dxl_on} present_position={present_position} goal_position={goal_position} >"
 58        return repr_template.format(
 59            dxl_on=self.is_on(),
 60            present_position=round(self.present_position, 2),
 61            goal_position=round(self.goal_position, 2),
 62        )
 63
 64    def turn_on(self) -> None:
 65        """Turn on the motor."""
 66        self._set_compliant(False)
 67
 68    def turn_off(self) -> None:
 69        """Turn off the motor."""
 70        self._set_compliant(True)
 71
 72    def is_on(self) -> bool:
 73        """Check if the dynamixel motor is currently stiff.
 74
 75        Returns:
 76            `True` if the motor is stiff (not compliant), `False` otherwise.
 77        """
 78        return not self._compliant
 79
 80    @property
 81    def present_position(self) -> float:
 82        """Get the present position of the joint in degrees."""
 83        return to_position(self._present_position)
 84
 85    @property
 86    def goal_position(self) -> float:
 87        """Get the goal position of the joint in degrees."""
 88        return to_position(self._goal_position)
 89
 90    @goal_position.setter
 91    def goal_position(self, value: float | int) -> None:
 92        """Set the goal position of the joint in degrees.
 93
 94        The goal position is not send to the joint immediately, it is stored locally until the `send_goal_positions` method
 95        is called.
 96
 97        Args:
 98            value: The goal position to set, specified as a float or int.
 99
100        Raises:
101            TypeError: If the provided value is not a float or int.
102        """
103        if isinstance(value, float) or isinstance(value, int):
104            self._outgoing_goal_position = to_internal_position(value)
105        else:
106            raise TypeError("goal_position must be a float or int")
107
108    def _set_compliant(self, compliant: bool) -> None:
109        """Set the compliance mode of the motor.
110
111        Compliance mode determines whether the motor is stiff or compliant.
112
113        Args:
114            compliant: A boolean value indicating whether to set the motor to
115                compliant (`True`) or stiff (`False`).
116        """
117        command = DynamixelMotorsCommand(
118            cmd=[
119                DynamixelMotorCommand(
120                    id=ComponentId(id=self._id),
121                    compliant=BoolValue(value=compliant),
122                )
123            ]
124        )
125        self._stub.SendCommand(command)
126
127    def _get_goal_positions_message(self, check_positions: bool = True) -> Optional[DynamixelMotorsCommand]:
128        """Get the DynamixelMotorsCommand message to send the goal positions to the actuator."""
129        if self._outgoing_goal_position is not None:
130            if not self.is_on():
131                self._logger.warning(f"{self._name} is off. Command not sent.")
132                return None
133            command = DynamixelMotorsCommand(
134                cmd=[
135                    DynamixelMotorCommand(
136                        id=ComponentId(id=self._id),
137                        goal_position=FloatValue(value=self._outgoing_goal_position),
138                    )
139                ]
140            )
141            return command
142        return None
143
144    def _clean_outgoing_goal_positions(self) -> None:
145        """Clean the outgoing goal positions."""
146        self._outgoing_goal_position = None
147
148    def send_goal_positions(self, check_positions: bool = False) -> None:
149        """Send goal positions to the motor.
150
151        If goal positions have been specified, sends them to the motor.
152        Args :
153            check_positions: A boolean indicating whether to check the positions after sending the command.
154                Defaults to True.
155        """
156        command = self._get_goal_positions_message()
157        if command is not None:
158            self._clean_outgoing_goal_positions()
159            self._stub.SendCommand(command)
160            if check_positions:
161                pass
162
163    def set_speed_limits(self, speed_limit: float | int) -> None:
164        """Set the speed limit as a percentage of the maximum speed the motor.
165
166        Args:
167            speed_limit: The desired speed limit as a percentage (0-100) of the maximum speed. Can be
168                specified as a float or int.
169        """
170        if not isinstance(speed_limit, float | int):
171            raise TypeError(f"Expected one of: float, int for speed_limit, got {type(speed_limit).__name__}")
172        if not (0 <= speed_limit <= 100):
173            raise ValueError(f"speed_limit must be in [0, 100], got {speed_limit}.")
174        speed_limit = speed_limit / 100.0
175        command = DynamixelMotorsCommand(
176            cmd=[
177                DynamixelMotorCommand(
178                    id=ComponentId(id=self._id),
179                    speed_limit=FloatValue(value=speed_limit),
180                )
181            ]
182        )
183        self._stub.SendCommand(command)
184
185    def _update_with(self, new_state: DynamixelMotorState) -> None:
186        """Update the present and goal positions of the joint with new state values.
187
188        Args:
189            new_state: A dictionary containing the new state values for the joint. The keys should include
190                "present_position" and "goal_position", with corresponding FloatValue objects as values.
191        """
192        self._present_position = new_state.present_position.value
193        self._goal_position = new_state.goal_position.value
194        self._compliant = new_state.compliant.value
class DynamixelMotor:
 23class DynamixelMotor:
 24    """The DynamixelMotor class represents any Dynamixel motor.
 25
 26    The DynamixelMotor class is used to store the up-to-date state of the motor, especially:
 27    - its present_position (RO)
 28    - its goal_position (RW)
 29    """
 30
 31    def __init__(
 32        self,
 33        uid: int,
 34        name: str,
 35        initial_state: DynamixelMotorState,
 36        grpc_channel: Channel,
 37    ):
 38        """Initialize the DynamixelMotor with its initial state and configuration.
 39
 40        This sets up the motor by assigning its state based on the provided initial values.
 41
 42        Args:
 43            uid: The unique identifier of the component.
 44            name: The name of the component.
 45            initial_state: A dictionary containing the initial state of the joint, with
 46                each entry representing a specific parameter of the joint (e.g., present position).
 47            grpc_channel: The gRPC channel used to communicate with the DynamixelMotor service.
 48        """
 49        self._logger = logging.getLogger(__name__)
 50        self._name = name
 51        self._id = uid
 52        self._stub = DynamixelMotorServiceStub(grpc_channel)
 53        self._update_with(initial_state)
 54        self._outgoing_goal_position: Optional[float] = None
 55
 56    def __repr__(self) -> str:
 57        """Clean representation of the DynamixelMotor."""
 58        repr_template = "<DynamixelMotor on={dxl_on} present_position={present_position} goal_position={goal_position} >"
 59        return repr_template.format(
 60            dxl_on=self.is_on(),
 61            present_position=round(self.present_position, 2),
 62            goal_position=round(self.goal_position, 2),
 63        )
 64
 65    def turn_on(self) -> None:
 66        """Turn on the motor."""
 67        self._set_compliant(False)
 68
 69    def turn_off(self) -> None:
 70        """Turn off the motor."""
 71        self._set_compliant(True)
 72
 73    def is_on(self) -> bool:
 74        """Check if the dynamixel motor is currently stiff.
 75
 76        Returns:
 77            `True` if the motor is stiff (not compliant), `False` otherwise.
 78        """
 79        return not self._compliant
 80
 81    @property
 82    def present_position(self) -> float:
 83        """Get the present position of the joint in degrees."""
 84        return to_position(self._present_position)
 85
 86    @property
 87    def goal_position(self) -> float:
 88        """Get the goal position of the joint in degrees."""
 89        return to_position(self._goal_position)
 90
 91    @goal_position.setter
 92    def goal_position(self, value: float | int) -> None:
 93        """Set the goal position of the joint in degrees.
 94
 95        The goal position is not send to the joint immediately, it is stored locally until the `send_goal_positions` method
 96        is called.
 97
 98        Args:
 99            value: The goal position to set, specified as a float or int.
100
101        Raises:
102            TypeError: If the provided value is not a float or int.
103        """
104        if isinstance(value, float) or isinstance(value, int):
105            self._outgoing_goal_position = to_internal_position(value)
106        else:
107            raise TypeError("goal_position must be a float or int")
108
109    def _set_compliant(self, compliant: bool) -> None:
110        """Set the compliance mode of the motor.
111
112        Compliance mode determines whether the motor is stiff or compliant.
113
114        Args:
115            compliant: A boolean value indicating whether to set the motor to
116                compliant (`True`) or stiff (`False`).
117        """
118        command = DynamixelMotorsCommand(
119            cmd=[
120                DynamixelMotorCommand(
121                    id=ComponentId(id=self._id),
122                    compliant=BoolValue(value=compliant),
123                )
124            ]
125        )
126        self._stub.SendCommand(command)
127
128    def _get_goal_positions_message(self, check_positions: bool = True) -> Optional[DynamixelMotorsCommand]:
129        """Get the DynamixelMotorsCommand message to send the goal positions to the actuator."""
130        if self._outgoing_goal_position is not None:
131            if not self.is_on():
132                self._logger.warning(f"{self._name} is off. Command not sent.")
133                return None
134            command = DynamixelMotorsCommand(
135                cmd=[
136                    DynamixelMotorCommand(
137                        id=ComponentId(id=self._id),
138                        goal_position=FloatValue(value=self._outgoing_goal_position),
139                    )
140                ]
141            )
142            return command
143        return None
144
145    def _clean_outgoing_goal_positions(self) -> None:
146        """Clean the outgoing goal positions."""
147        self._outgoing_goal_position = None
148
149    def send_goal_positions(self, check_positions: bool = False) -> None:
150        """Send goal positions to the motor.
151
152        If goal positions have been specified, sends them to the motor.
153        Args :
154            check_positions: A boolean indicating whether to check the positions after sending the command.
155                Defaults to True.
156        """
157        command = self._get_goal_positions_message()
158        if command is not None:
159            self._clean_outgoing_goal_positions()
160            self._stub.SendCommand(command)
161            if check_positions:
162                pass
163
164    def set_speed_limits(self, speed_limit: float | int) -> None:
165        """Set the speed limit as a percentage of the maximum speed the motor.
166
167        Args:
168            speed_limit: The desired speed limit as a percentage (0-100) of the maximum speed. Can be
169                specified as a float or int.
170        """
171        if not isinstance(speed_limit, float | int):
172            raise TypeError(f"Expected one of: float, int for speed_limit, got {type(speed_limit).__name__}")
173        if not (0 <= speed_limit <= 100):
174            raise ValueError(f"speed_limit must be in [0, 100], got {speed_limit}.")
175        speed_limit = speed_limit / 100.0
176        command = DynamixelMotorsCommand(
177            cmd=[
178                DynamixelMotorCommand(
179                    id=ComponentId(id=self._id),
180                    speed_limit=FloatValue(value=speed_limit),
181                )
182            ]
183        )
184        self._stub.SendCommand(command)
185
186    def _update_with(self, new_state: DynamixelMotorState) -> None:
187        """Update the present and goal positions of the joint with new state values.
188
189        Args:
190            new_state: A dictionary containing the new state values for the joint. The keys should include
191                "present_position" and "goal_position", with corresponding FloatValue objects as values.
192        """
193        self._present_position = new_state.present_position.value
194        self._goal_position = new_state.goal_position.value
195        self._compliant = new_state.compliant.value

The DynamixelMotor class represents any Dynamixel motor.

The DynamixelMotor class is used to store the up-to-date state of the motor, especially:

  • its present_position (RO)
  • its goal_position (RW)
DynamixelMotor( uid: int, name: str, initial_state: dynamixel_motor_pb2.DynamixelMotorState, grpc_channel: grpc.Channel)
31    def __init__(
32        self,
33        uid: int,
34        name: str,
35        initial_state: DynamixelMotorState,
36        grpc_channel: Channel,
37    ):
38        """Initialize the DynamixelMotor with its initial state and configuration.
39
40        This sets up the motor by assigning its state based on the provided initial values.
41
42        Args:
43            uid: The unique identifier of the component.
44            name: The name of the component.
45            initial_state: A dictionary containing the initial state of the joint, with
46                each entry representing a specific parameter of the joint (e.g., present position).
47            grpc_channel: The gRPC channel used to communicate with the DynamixelMotor service.
48        """
49        self._logger = logging.getLogger(__name__)
50        self._name = name
51        self._id = uid
52        self._stub = DynamixelMotorServiceStub(grpc_channel)
53        self._update_with(initial_state)
54        self._outgoing_goal_position: Optional[float] = None

Initialize the DynamixelMotor with its initial state and configuration.

This sets up the motor by assigning its state based on the provided initial values.

Arguments:
  • uid: The unique identifier of the component.
  • name: The name of the component.
  • initial_state: A dictionary containing the initial state of the joint, with each entry representing a specific parameter of the joint (e.g., present position).
  • grpc_channel: The gRPC channel used to communicate with the DynamixelMotor service.
def turn_on(self) -> None:
65    def turn_on(self) -> None:
66        """Turn on the motor."""
67        self._set_compliant(False)

Turn on the motor.

def turn_off(self) -> None:
69    def turn_off(self) -> None:
70        """Turn off the motor."""
71        self._set_compliant(True)

Turn off the motor.

def is_on(self) -> bool:
73    def is_on(self) -> bool:
74        """Check if the dynamixel motor is currently stiff.
75
76        Returns:
77            `True` if the motor is stiff (not compliant), `False` otherwise.
78        """
79        return not self._compliant

Check if the dynamixel motor is currently stiff.

Returns:

True if the motor is stiff (not compliant), False otherwise.

present_position: float
81    @property
82    def present_position(self) -> float:
83        """Get the present position of the joint in degrees."""
84        return to_position(self._present_position)

Get the present position of the joint in degrees.

goal_position: float
86    @property
87    def goal_position(self) -> float:
88        """Get the goal position of the joint in degrees."""
89        return to_position(self._goal_position)

Get the goal position of the joint in degrees.

def send_goal_positions(self, check_positions: bool = False) -> None:
149    def send_goal_positions(self, check_positions: bool = False) -> None:
150        """Send goal positions to the motor.
151
152        If goal positions have been specified, sends them to the motor.
153        Args :
154            check_positions: A boolean indicating whether to check the positions after sending the command.
155                Defaults to True.
156        """
157        command = self._get_goal_positions_message()
158        if command is not None:
159            self._clean_outgoing_goal_positions()
160            self._stub.SendCommand(command)
161            if check_positions:
162                pass

Send goal positions to the motor.

If goal positions have been specified, sends them to the motor.

Args :

check_positions: A boolean indicating whether to check the positions after sending the command. Defaults to True.

def set_speed_limits(self, speed_limit: float | int) -> None:
164    def set_speed_limits(self, speed_limit: float | int) -> None:
165        """Set the speed limit as a percentage of the maximum speed the motor.
166
167        Args:
168            speed_limit: The desired speed limit as a percentage (0-100) of the maximum speed. Can be
169                specified as a float or int.
170        """
171        if not isinstance(speed_limit, float | int):
172            raise TypeError(f"Expected one of: float, int for speed_limit, got {type(speed_limit).__name__}")
173        if not (0 <= speed_limit <= 100):
174            raise ValueError(f"speed_limit must be in [0, 100], got {speed_limit}.")
175        speed_limit = speed_limit / 100.0
176        command = DynamixelMotorsCommand(
177            cmd=[
178                DynamixelMotorCommand(
179                    id=ComponentId(id=self._id),
180                    speed_limit=FloatValue(value=speed_limit),
181                )
182            ]
183        )
184        self._stub.SendCommand(command)

Set the speed limit as a percentage of the maximum speed the motor.

Arguments:
  • speed_limit: The desired speed limit as a percentage (0-100) of the maximum speed. Can be specified as a float or int.