Skip to content

Commit 1bcc2d4

Browse files
Merge pull request #482 from neurobionics/454-no-easy-way-to-zero-encoders-and-set-their-direction
454 no easy way to zero encoders and set their direction
2 parents 1e67ea3 + d629d10 commit 1bcc2d4

8 files changed

Lines changed: 148 additions & 48 deletions

File tree

docs/tutorials/robots/homing_joints.md

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ This example shows how to:
1010
- Configure `AS5048B` magnetic absolute encoders via I2C
1111
- Instantiate the `OpenSourceLeg` platform with the defined components
1212
- Use the `home()` method to detect joint limits and assign calibrated offsets
13+
- Use callback functions to execute code at the homed positions
1314
- Log joint and encoder positions to the terminal in real-time
1415

1516
## Hardware Setup
@@ -33,7 +34,7 @@ The [tutorial script](https://github.com/neurobionics/opensourceleg/blob/main/tu
3334
### 1. Configuration
3435

3536
```python
36-
--8<-- "tutorials/robots/osl/homing_joints.py:3:14"
37+
--8<-- "tutorials/robots/osl/homing_joints.py:1:14"
3738
```
3839

3940
Key parameters:
@@ -45,7 +46,7 @@ Key parameters:
4546
### 2. Real-time loop and Logger
4647

4748
```python
48-
--8<-- "tutorials/robots/osl/homing_joints.py:17:22"
49+
--8<-- "tutorials/robots/osl/homing_joints.py:16:22"
4950
```
5051

5152
This section:
@@ -56,7 +57,7 @@ This section:
5657
### 3. Actuator Initialization
5758

5859
```python
59-
--8<-- "tutorials/robots/osl/homing_joints.py:24:37"
60+
--8<-- "tutorials/robots/osl/homing_joints.py:24:39"
6061
```
6162

6263
This section:
@@ -67,7 +68,7 @@ This section:
6768
### 4. Sensor Initialization
6869

6970
```python
70-
--8<-- "tutorials/robots/osl/homing_joints.py:41:56"
71+
--8<-- "tutorials/robots/osl/homing_joints.py:41:58"
7172
```
7273

7374
This section:
@@ -76,20 +77,32 @@ This section:
7677
- Each encoder is tagged and assigned its A1/A2 pin configuration
7778
- Zero position is temporarily set to 0
7879

79-
### 5. Initialize OSL Platform
80+
### 5. Using Callbacks for Homing Completion
81+
82+
The `home()` method also supports an optional `callback_functions` argument. This allows you to specify a list of functions (one per actuator) that will be called when each actuator finishes its homing routine. This is useful for custom notifications, logging, or triggering additional actions like zeroing joint encoders.
83+
84+
For example, to print a message when each joint completes homing:
85+
86+
```python
87+
--8<-- "tutorials/robots/osl/homing_joints.py:60:73"
88+
```
89+
90+
If you do not wish to use callbacks, you can omit the `callback_functions` argument or pass a list of `None` values.
91+
92+
### 6. Initialize OSL Platform
8093

8194
```python
82-
--8<-- "tutorials/robots/osl/homing_joints.py:60:63"
95+
--8<-- "tutorials/robots/osl/homing_joints.py:75:79"
8396
```
8497

8598
This section:
8699

87100
- Creates an instance of `OpenSourceLeg` and passes in the actuator and sensor dictionaries
88101

89-
### 6. Run Homing Routine
102+
### 7. Run Homing Routine
90103

91104
```python
92-
--8<-- "tutorials/robots/osl/homing_joints.py:67:73"
105+
--8<-- "tutorials/robots/osl/homing_joints.py:81:90"
93106
```
94107

95108
This section:
@@ -100,10 +113,10 @@ This section:
100113
- Current exceeds `5000 mA`
101114
- Once stopped, the motor is zeroed based on its position, and a calibrated offset is added (30° for ankle, 0° for knee)
102115

103-
### 7. Reset Torque and Start Logging
116+
### 8. Reset Torque and Start Logging
104117

105118
```python
106-
--8<-- "tutorials/robots/osl/homing_joints.py:75:89"
119+
--8<-- "tutorials/robots/osl/homing_joints.py:92:110"
107120
```
108121

109122
This section:

examples/fsm_walking_python_controller.py

Lines changed: 44 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -190,15 +190,15 @@ def lswing_to_estance(osl: OpenSourceLeg) -> bool:
190190
actuators = {
191191
"knee": DephyActuator(
192192
tag="knee",
193-
port="/dev/ttyACM1",
193+
port="/dev/ttyACM0",
194194
gear_ratio=GEAR_RATIO,
195195
frequency=FREQUENCY,
196196
debug_level=0,
197197
dephy_log=False,
198198
),
199199
"ankle": DephyActuator(
200200
tag="ankle",
201-
port="/dev/ttyACM0",
201+
port="/dev/ttyACM1",
202202
gear_ratio=GEAR_RATIO,
203203
frequency=FREQUENCY,
204204
debug_level=0,
@@ -211,21 +211,25 @@ def lswing_to_estance(osl: OpenSourceLeg) -> bool:
211211
# calibration_matrix=LOADCELL_CALIBRATION_MATRIX,
212212
# ),
213213
"loadcell": NBLoadcellDAQ(
214-
LOADCELL_CALIBRATION_MATRIX, tag="loadcell", excitation_voltage=5.0, amp_gain=[34] * 3 + [151] * 3
214+
LOADCELL_CALIBRATION_MATRIX,
215+
tag="loadcell",
216+
excitation_voltage=5.0,
217+
amp_gain=[34] * 3 + [151] * 3,
218+
spi_bus=1,
215219
),
216220
"joint_encoder_knee": AS5048B(
217221
tag="joint_encoder_knee",
218-
bus=3,
219-
A1_adr_pin=True,
222+
bus="/dev/i2c-2",
223+
A1_adr_pin=False,
220224
A2_adr_pin=False,
221225
zero_position=0,
222226
enable_diagnostics=False,
223227
),
224228
"joint_encoder_ankle": AS5048B(
225229
tag="joint_encoder_ankle",
226-
bus=2,
230+
bus="/dev/i2c-3",
227231
A1_adr_pin=False,
228-
A2_adr_pin=True,
232+
A2_adr_pin=False,
229233
zero_position=0,
230234
enable_diagnostics=False,
231235
),
@@ -245,10 +249,25 @@ def lswing_to_estance(osl: OpenSourceLeg) -> bool:
245249

246250
osl_fsm = create_simple_walking_fsm(osl)
247251

252+
# Zeroing the joint encoders
253+
def knee_homing_complete():
254+
osl.joint_encoder_knee.update()
255+
osl.joint_encoder_knee.zero_position = osl.joint_encoder_knee.counts
256+
print("Knee homing complete!")
257+
258+
def ankle_homing_complete():
259+
osl.joint_encoder_ankle.update()
260+
# The hard stop for ankle is at 30 deg from the zero position
261+
osl.joint_encoder_ankle.zero_position = osl.joint_encoder_ankle.counts - osl.joint_encoder_ankle.deg_to_counts(
262+
30
263+
)
264+
print("Ankle homing complete!")
265+
266+
callbacks = {"knee": knee_homing_complete, "ankle": ankle_homing_complete}
267+
248268
with osl, osl_fsm:
249269
osl.update()
250-
osl.home()
251-
270+
osl.home(callbacks=callbacks)
252271
input("Press Enter to start walking...")
253272

254273
# knee
@@ -261,20 +280,25 @@ def lswing_to_estance(osl: OpenSourceLeg) -> bool:
261280
osl.ankle.set_impedance_cc_pidf_gains()
262281
osl.ankle.set_output_impedance()
263282

283+
osl.loadcell.reset()
284+
osl.loadcell.calibrate()
285+
264286
for t in clock:
265287
osl.update()
288+
print("Ankle position", np.rad2deg(osl.sensors["joint_encoder_ankle"].position))
289+
print("Knee position", np.rad2deg(osl.sensors["joint_encoder_knee"].position))
266290
osl_fsm.update(osl=osl)
267-
osl.knee.set_output_impedance(
268-
k=osl_fsm.current_state.knee_stiffness,
269-
b=osl_fsm.current_state.knee_damping,
270-
)
271-
osl.ankle.set_output_impedance(
272-
k=osl_fsm.current_state.ankle_stiffness,
273-
b=osl_fsm.current_state.ankle_damping,
274-
)
275-
276-
osl.knee.set_output_position(np.deg2rad(osl_fsm.current_state.knee_theta))
277-
osl.ankle.set_output_position(np.deg2rad(osl_fsm.current_state.ankle_theta))
291+
# osl.knee.set_output_impedance(
292+
# k=osl_fsm.current_state.knee_stiffness,
293+
# b=osl_fsm.current_state.knee_damping,
294+
# )
295+
# osl.ankle.set_output_impedance(
296+
# k=osl_fsm.current_state.ankle_stiffness,
297+
# b=osl_fsm.current_state.ankle_damping,
298+
# )
299+
300+
# osl.knee.set_output_position(np.deg2rad(osl_fsm.current_state.knee_theta))
301+
# osl.ankle.set_output_position(np.deg2rad(osl_fsm.current_state.ankle_theta))
278302

279303
fsm_logger.info(
280304
f"T: {t:.3f}s, "

opensourceleg/actuators/base.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -834,6 +834,7 @@ def home(
834834
output_position_offset: float = 0.0,
835835
current_threshold: int = 5000,
836836
velocity_threshold: float = 0.001,
837+
callback: Optional[Callable[[], None]] = None,
837838
) -> None:
838839
"""
839840
Home the actuator.
@@ -848,6 +849,8 @@ def home(
848849
output_position_offset (float): Offset to add to the output position.
849850
current_threshold (int): Current threshold to stop homing.
850851
velocity_threshold (float): Velocity threshold to stop homing.
852+
callback (Optional[Callable[[], None]]): Optional callback function to be
853+
called when homing completes. The function should take no arguments and return None.
851854
852855
Examples:
853856
>>> actuator.home()

opensourceleg/actuators/dephy.py

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import os
33
import time
44
from ctypes import c_int
5-
from typing import Optional
5+
from typing import Callable, Optional
66

77
import numpy as np
88
from flexsea.device import Device
@@ -328,6 +328,7 @@ def home(
328328
output_position_offset: float = 0.0,
329329
current_threshold: int = 5000,
330330
velocity_threshold: float = 0.001,
331+
callback: Optional[Callable[[], None]] = None,
331332
) -> None:
332333
"""
333334
@@ -337,14 +338,22 @@ def home(
337338
to joint position in radians. This is useful for more accurate joint position estimation.
338339
339340
Args:
340-
homing_voltage (int): Voltage in mV to use for homing. Default is 2000 mV.
341-
homing_frequency (int): Frequency in Hz to use for homing. Default is the actuator's frequency.
342-
homing_direction (int): Direction to move the actuator during homing. Default is -1.
343-
output_position_offset (float): Offset in radians to add to the output position. Default is 0.0.
341+
homing_voltage (int): Voltage in mV to use for homing.
342+
Default is 2000 mV.
343+
homing_frequency (int): Frequency in Hz to use for homing.
344+
Default is the actuator's frequency.
345+
homing_direction (int): Direction to move the actuator during homing.
346+
Default is -1.
347+
output_position_offset (float): Offset in radians to add to the output position.
348+
Default is 0.0.
344349
current_threshold (int): Current threshold in mA to stop homing the joint or actuator.
345-
This is used to detect if the actuator or joint has hit a hard stop. Default is 5000 mA.
350+
This is used to detect if the actuator or joint has hit a hard stop.
351+
Default is 5000 mA.
346352
velocity_threshold (float): Velocity threshold in rad/s to stop homing the joint or actuator.
347-
This is also used to detect if the actuator or joint has hit a hard stop. Default is 0.001 rad/s.
353+
This is also used to detect if the actuator or joint has hit a hard stop.
354+
Default is 0.001 rad/s.
355+
callback (Optional[Callable[[], None]]): Optional callback function to be called when homing completes.
356+
The function should take no arguments and return None.
348357
Examples:
349358
>>> actuator = DephyActuator(port='/dev/ttyACM0')
350359
>>> actuator.start()
@@ -373,6 +382,8 @@ def home(
373382

374383
if abs(self.output_velocity) <= velocity_threshold or abs(self.motor_current) >= current_threshold:
375384
self.set_motor_voltage(value=0)
385+
if callback is not None:
386+
callback()
376387
is_homing = False
377388

378389
except KeyboardInterrupt:

opensourceleg/actuators/tmotor.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import time
22
import warnings
33
from math import isfinite
4-
from typing import Optional
4+
from typing import Callable, Optional
55

66
import can
77
import numpy as np
@@ -25,6 +25,7 @@
2525
check_actuator_open,
2626
check_actuator_stream,
2727
)
28+
from opensourceleg.logging import LOGGER
2829
from opensourceleg.math import ThermalModel
2930
from opensourceleg.utilities import SoftRealtimeLoop
3031

@@ -196,7 +197,30 @@ def home(
196197
output_position_offset: float = 0.0,
197198
current_threshold: int = 5000,
198199
velocity_threshold: float = 0.001,
200+
callback: Optional[Callable[[], None]] = None,
199201
):
202+
"""
203+
Home the actuator and corresponding joint by moving it to the zero position.
204+
The zero position is defined as the position where the joint is fully extended.
205+
206+
Args:
207+
homing_voltage (int): Voltage in mV to use for homing.
208+
Default is 2000 mV.
209+
homing_frequency (Optional[int]): Frequency in Hz to use for homing.
210+
Default is the actuator's frequency.
211+
homing_direction (int): Direction to move the actuator during homing.
212+
Default is -1.
213+
output_position_offset (float): Offset in radians to add to the output position.
214+
Default is 0.0.
215+
current_threshold (int): Current threshold in mA to stop homing the joint or actuator.
216+
Default is 5000 mA.
217+
velocity_threshold (float): Velocity threshold in rad/s to stop homing the joint or actuator.
218+
Default is 0.001 rad/s.
219+
callback (Optional[Callable[[], None]]): Optional callback function to be called when homing completes.
220+
The function should take no arguments and return None.
221+
"""
222+
# TODO: implement homing
223+
LOGGER.info(msg=f"[{self.__repr__()}] Homing not implemented.")
200224
pass
201225

202226
def update(self): # noqa: C901

opensourceleg/robots/osl.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import os
22
import time
3-
from typing import Optional, Union
3+
from typing import Callable, Optional, Union
44

55
import numpy as np
66

@@ -43,6 +43,7 @@ def home(
4343
output_position_offset: Optional[dict[str, float]] = None,
4444
current_threshold: int = 5000,
4545
velocity_threshold: float = 0.001,
46+
callbacks: Optional[dict[str, Callable]] = None,
4647
) -> None:
4748
"""
4849
Call the home method for all actuators.
@@ -56,19 +57,25 @@ def home(
5657
Default is 0.0 for knee and 30.0 for ankle.
5758
current_threshold: The current threshold to apply to the actuators during homing. Default is 5000.
5859
velocity_threshold: The velocity threshold to apply to the actuators during homing. Default is 0.001.
60+
callbacks Optional[dict[str, Callable]]:
61+
Optional dictionary of callback functions, one per actuator, to be called when each actuator's
62+
homing completes. Only one callback per actuator is supported, and the tag must match.
63+
Each function should take no arguments and return None. If None, no callbacks are used.
5964
"""
6065
if output_position_offset is None:
6166
output_position_offset = {"knee": 0.0, "ankle": np.deg2rad(30.0)}
6267
if homing_direction is None:
6368
homing_direction = {"knee": -1, "ankle": -1}
6469
for actuator in self.actuators.values():
70+
callback = callbacks.get(actuator.tag, None) if callbacks is not None else None
6571
actuator.home(
6672
homing_voltage=homing_voltage,
6773
homing_frequency=homing_frequency,
6874
homing_direction=homing_direction[actuator.tag],
6975
output_position_offset=output_position_offset[actuator.tag],
7076
current_threshold=current_threshold,
7177
velocity_threshold=velocity_threshold,
78+
callback=callback,
7279
)
7380

7481
LOGGER.info(

0 commit comments

Comments
 (0)