@@ -26,7 +26,31 @@ class ActuatorState:
2626
2727
2828class BusBase :
29- """Shared high-level wrapper behavior for Python bus objects."""
29+ """Shared high-level wrapper behavior for Python bus objects.
30+
31+ Args:
32+ channel: CAN interface name, e.g. `can0`.
33+ actuators: Actuators keyed by actuator name.
34+ calibration: Optional per-actuator calibration overrides.
35+ bitrate: CAN bitrate in bits per second.
36+
37+ We follow the LeRobot style actuator calibration logic, where the `calibration` is a
38+ dictionary of actuators with their rotation direction and homing offset.
39+
40+ An example would be:
41+ ```python
42+ calibration = {
43+ "actuator_1": {
44+ "direction": 1.0,
45+ "homing_offset": 3.14,
46+ },
47+ "actuator_2": {
48+ "direction": -1.0,
49+ "homing_offset": -0.1,
50+ },
51+ }
52+ ```
53+ """
3054
3155 def __init__ (
3256 self ,
@@ -35,34 +59,52 @@ def __init__(
3559 calibration : dict [str , dict [str , Any ]] | None ,
3660 bitrate : int ,
3761 ) -> None :
62+ """Store Python-side bus configuration before the backend is created."""
3863 self .channel = channel
3964 self .actuators = dict (actuators )
4065 self .calibration = calibration .copy () if calibration else {}
4166 self .bitrate = bitrate
4267 self ._is_connected = False
4368
4469 def __len__ (self ) -> int :
70+ """Return the number of configured actuators."""
4571 return len (self .actuators )
4672
4773 @property
4874 def is_connected (self ) -> bool :
75+ """Whether the Python wrapper currently considers the backend connected."""
4976 return self ._is_connected
5077
5178 def connect (self ) -> None :
79+ """Open the CAN backend and start its receiver thread."""
5280 self ._core .connect ()
5381 self ._is_connected = True
5482
5583 def disconnect (self , disable_torque : bool = True ) -> None :
84+ """Disconnect the backend and optionally disable torque first.
85+
86+ Args:
87+ disable_torque: Whether to issue per-actuator disable commands before closing.
88+ """
5689 self ._core .disconnect (disable_torque = disable_torque )
5790 self ._is_connected = False
5891
5992 def enable (self , actuator : str ) -> None :
93+ """Enable the specified actuator."""
6094 self ._core .enable (actuator )
6195
6296 def disable (self , actuator : str ) -> None :
97+ """Disable the specified actuator."""
6398 self ._core .disable (actuator )
6499
65100 def write_mit_kp_kd (self , actuator : str , kp : float , kd : float ) -> None :
101+ """Update MIT-mode proportional and derivative gains.
102+
103+ Args:
104+ actuator: Actuator name.
105+ kp: Proportional gain in backend-specific MIT units.
106+ kd: Derivative gain in backend-specific MIT units.
107+ """
66108 self ._core .write_mit_kp_kd (actuator , kp , kd )
67109
68110 def write_mit_control (
@@ -72,20 +114,39 @@ def write_mit_control(
72114 velocity : float = 0.0 ,
73115 torque : float = 0.0 ,
74116 ) -> None :
117+ """Send one MIT control command.
118+
119+ Args:
120+ actuator: Actuator name.
121+ position: Target output position in radians.
122+ velocity: Target output velocity in radians per second.
123+ torque: Feedforward output torque in newton-meters.
124+ """
75125 self ._core .write_mit_control (actuator , position , velocity , torque )
76126
77127 def request_state (self , actuator : str ) -> None :
128+ """Trigger an asynchronous state refresh for the specified actuator."""
78129 self ._core .request_state (actuator )
79130
80131 @property
81132 def tx_counter (self ) -> int :
133+ """Return the number of CAN frames transmitted by the backend."""
82134 return self ._core .read_tx_counter ()
83135
84136 @property
85137 def rx_counter (self ) -> int :
138+ """Return the number of CAN frames received by the backend."""
86139 return self ._core .read_rx_counter ()
87140
88141 def get_state (self , actuator : str ) -> ActuatorState | None :
142+ """Return the last cached actuator state.
143+
144+ Args:
145+ actuator: Actuator name.
146+
147+ Returns:
148+ The cached state, or ``None`` if no feedback has been received yet.
149+ """
89150 state = self ._core .read_state (actuator )
90151 if state is None :
91152 return None
@@ -100,30 +161,71 @@ def get_state(self, actuator: str) -> ActuatorState | None:
100161 )
101162
102163 def get_fault_status (self , actuator : str ) -> tuple [str , ...] | None :
164+ """Return the cached fault labels for the specified actuator."""
103165 state = self .get_state (actuator )
104166 if state is None :
105167 return None
106168 return state .faults
107169
108170 def get_mit_state (self , actuator : str ) -> tuple [float , float , float ] | None :
171+ """Return cached MIT feedback as `(position, velocity, torque)`.
172+
173+ Args:
174+ actuator: Actuator name.
175+
176+ Returns:
177+ The cached state, or `None` if no feedback has been received yet.
178+ """
109179 state = self .get_state (actuator )
110180 if state is None :
111181 return None
112182 return state .position , state .velocity , state .torque
113183
114184 def get_position (self , actuator : str ) -> float | None :
185+ """Return the cached output position in radians.
186+
187+ Args:
188+ actuator: Actuator name.
189+
190+ Returns:
191+ The cached position, or `None` if no feedback has been received yet.
192+ """
115193 state = self .get_state (actuator )
116194 return state .position if state is not None else None
117195
118196 def get_velocity (self , actuator : str ) -> float | None :
197+ """Return the cached output velocity in radians per second.
198+
199+ Args:
200+ actuator: Actuator name.
201+
202+ Returns:
203+ The cached velocity, or `None` if no feedback has been received yet.
204+ """
119205 state = self .get_state (actuator )
120206 return state .velocity if state is not None else None
121207
122208 def get_torque (self , actuator : str ) -> float | None :
209+ """Return the cached output torque in newton-meters.
210+
211+ Args:
212+ actuator: Actuator name.
213+
214+ Returns:
215+ The cached torque, or `None` if no feedback has been received yet.
216+ """
123217 state = self .get_state (actuator )
124218 return state .torque if state is not None else None
125219
126220 def get_temperature (self , actuator : str ) -> float | None :
221+ """Return the cached actuator temperature in degrees Celsius.
222+
223+ Args:
224+ actuator: Actuator name.
225+
226+ Returns:
227+ The cached temperature, or `None` if no feedback has been received yet.
228+ """
127229 state = self .get_state (actuator )
128230 return state .temperature if state is not None else None
129231
@@ -134,6 +236,16 @@ def ping_by_id(
134236 device_id : int ,
135237 timeout : float = 0.1 ,
136238 ) -> tuple [int , bytes ] | None :
239+ """Probe one device ID on one channel.
240+
241+ Args:
242+ channel: CAN interface name.
243+ device_id: Device ID to probe.
244+ timeout: Receive timeout in seconds.
245+
246+ Returns:
247+ Backend-specific response metadata and payload, or `None` if no device replied.
248+ """
137249 raise NotImplementedError (f"{ cls .__name__ } .ping_by_id() is not implemented yet." )
138250
139251 @classmethod
@@ -144,7 +256,17 @@ def scan_channel(
144256 end_id : int = 255 ,
145257 timeout : float = 0.1 ,
146258 ) -> dict [int , tuple [int , bytes ]]:
147- """Probe one channel and return scan responses keyed by device ID."""
259+ """Probe a channel and collect responses keyed by device ID.
260+
261+ Args:
262+ channel: CAN interface name.
263+ start_id: First device ID to probe, inclusive.
264+ end_id: Last device ID to probe, inclusive.
265+ timeout: Receive timeout per probe in seconds.
266+
267+ Returns:
268+ Mapping from detected device ID to the raw response tuple returned by `ping_by_id`.
269+ """
148270 _validate_scan_range (start_id , end_id )
149271
150272 device_ids : dict [int , tuple [int , bytes ]] = {}
0 commit comments