Skip to content

Commit 7574715

Browse files
committed
add: docstrings
1 parent 9e63e7f commit 7574715

9 files changed

Lines changed: 322 additions & 4 deletions

File tree

actuator_control/api.py

Lines changed: 124 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,31 @@ class ActuatorState:
2626

2727

2828
class 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]] = {}

actuator_control/erob/bus.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010

1111

1212
class ERobBus(BusBase):
13+
"""High-level Python wrapper for the eRob CAN backend."""
14+
1315
protocol = EROB_PROTOCOL
1416

1517
def __init__(
@@ -19,6 +21,14 @@ def __init__(
1921
calibration: dict[str, dict[str, Any]] | None = None,
2022
bitrate: int = 1_000_000,
2123
) -> None:
24+
"""Create an eRob bus wrapper.
25+
26+
Args:
27+
channel: CAN interface name, e.g. `can0`.
28+
actuators: Actuators keyed by actuator name.
29+
calibration: Optional per-actuator calibration overrides.
30+
bitrate: CAN bitrate in bits per second.
31+
"""
2232
super().__init__(channel, actuators, calibration, bitrate)
2333
self._core = _ErobBus(
2434
channel=channel,
@@ -28,7 +38,9 @@ def __init__(
2838
)
2939

3040
def read(self, actuator: str, parameter: int) -> int:
41+
"""Read one eRob parameter as an integer value."""
3142
return self._core.read(actuator, int(parameter))
3243

3344
def write(self, actuator: str, parameter: int, value: int) -> None:
45+
"""Write one integer eRob parameter."""
3446
self._core.write(actuator, int(parameter), int(value))

actuator_control/erob/protocol.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,16 @@
66

77

88
class ErobCommandType:
9+
"""eRob command byte constants."""
10+
911
START_MOTION = 0x83
1012
STOP_MOTION = 0x84
1113
SAVE_PARAMS = 0xE8
1214

1315

1416
class ErobParameterType:
17+
"""eRob parameter ID constants used by `ERobBus.read` and `write`."""
18+
1519
ACTUAL_POSITION = 0x0002
1620
ACTUAL_SPEED = 0x0005
1721
MOTOR_CURRENT = 0x0008
@@ -34,6 +38,8 @@ class ErobParameterType:
3438

3539

3640
class ErobMode:
41+
"""eRob control mode constants."""
42+
3743
TORQUE = 1
3844
SPEED = 2
3945
POSITION = 3

actuator_control/protocol.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,12 @@
88

99
@dataclass(frozen=True, slots=True)
1010
class ActuatorProtocol:
11-
"""Static protocol metadata for one actuator family."""
11+
"""Static protocol metadata for one actuator family.
12+
13+
Attributes:
14+
name: Stable protocol name used in the Python API.
15+
parameter_data_types: Mapping from parameter ID to ``"int"`` or ``"float"``.
16+
"""
1217

1318
name: str
1419
parameter_data_types: dict[int, str]
@@ -59,7 +64,15 @@ def get(self, key: str, default: ActuatorProtocol | None = None) -> ActuatorProt
5964

6065

6166
def get_parameter_data_type(actuator_type: str, parameter: int) -> str | None:
62-
"""Return the declared Python-side type for a protocol parameter."""
67+
"""Return the declared Python-side type for a protocol parameter.
68+
69+
Args:
70+
actuator_type: Protocol name such as ``"robstride"``.
71+
parameter: Backend parameter ID.
72+
73+
Returns:
74+
``"int"`` or ``"float"`` when the parameter is known, otherwise ``None``.
75+
"""
6376

6477
protocol = ACTUATOR_PROTOCOLS.get(actuator_type)
6578
if protocol is None:

0 commit comments

Comments
 (0)