Revert feetech hack and monkeypatch instead
This commit is contained in:
@@ -24,7 +24,7 @@ from enum import Enum
|
|||||||
|
|
||||||
from lerobot.common.utils.encoding_utils import decode_twos_complement, encode_twos_complement
|
from lerobot.common.utils.encoding_utils import decode_twos_complement, encode_twos_complement
|
||||||
|
|
||||||
from ..motors_bus import Motor, MotorCalibration, MotorsBus, NameOrID, Value, get_address
|
from ..motors_bus import Motor, MotorCalibration, MotorsBus, NameOrID, Value
|
||||||
from .tables import (
|
from .tables import (
|
||||||
AVAILABLE_BAUDRATES,
|
AVAILABLE_BAUDRATES,
|
||||||
MODEL_BAUDRATE_TABLE,
|
MODEL_BAUDRATE_TABLE,
|
||||||
@@ -192,20 +192,3 @@ class DynamixelMotorsBus(MotorsBus):
|
|||||||
return data_list if data_list else None
|
return data_list if data_list else None
|
||||||
|
|
||||||
return {id_: data[0] for id_, data in data_list.items()}
|
return {id_: data[0] for id_, data in data_list.items()}
|
||||||
|
|
||||||
def _write(self, data_name: str, motor_id: int, value: int, num_retry: int = 0) -> tuple[int, int]:
|
|
||||||
model = self._id_to_model(motor_id)
|
|
||||||
addr, n_bytes = get_address(self.model_ctrl_table, model, data_name)
|
|
||||||
value = self._encode_value(value, data_name, n_bytes)
|
|
||||||
data = self._split_int_to_bytes(value, n_bytes)
|
|
||||||
|
|
||||||
for n_try in range(1 + num_retry):
|
|
||||||
comm, error = self.packet_handler.writeTxRx(self.port_handler, motor_id, addr, n_bytes, data)
|
|
||||||
if self._is_comm_success(comm):
|
|
||||||
break
|
|
||||||
logger.debug(
|
|
||||||
f"Failed to write '{data_name}' ({addr=} {n_bytes=}) on {motor_id=} with '{value}' ({n_try=})"
|
|
||||||
)
|
|
||||||
logger.debug(self.packet_handler.getRxPacketError(comm))
|
|
||||||
|
|
||||||
return comm, error
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ from pprint import pformat
|
|||||||
|
|
||||||
from lerobot.common.utils.encoding_utils import decode_sign_magnitude, encode_sign_magnitude
|
from lerobot.common.utils.encoding_utils import decode_sign_magnitude, encode_sign_magnitude
|
||||||
|
|
||||||
from ..motors_bus import Motor, MotorCalibration, MotorsBus, NameOrID, Value, get_address
|
from ..motors_bus import Motor, MotorCalibration, MotorsBus, NameOrID, Value
|
||||||
from .tables import (
|
from .tables import (
|
||||||
AVAILABLE_BAUDRATES,
|
AVAILABLE_BAUDRATES,
|
||||||
ENCODINGS,
|
ENCODINGS,
|
||||||
@@ -61,6 +61,19 @@ class TorqueMode(Enum):
|
|||||||
DISABLED = 0
|
DISABLED = 0
|
||||||
|
|
||||||
|
|
||||||
|
def patch_setPacketTimeout(self, packet_length): # noqa: N802
|
||||||
|
"""
|
||||||
|
HACK: This patches the PortHandler behavior to set the correct packet timeouts.
|
||||||
|
|
||||||
|
It fixes https://gitee.com/ftservo/SCServoSDK/issues/IBY2S6
|
||||||
|
The bug is fixed on the official Feetech SDK repo (https://gitee.com/ftservo/FTServo_Python)
|
||||||
|
but because that version is not published on PyPI, we rely on the (unofficial) on that is, which needs
|
||||||
|
patching.
|
||||||
|
"""
|
||||||
|
self.packet_start_time = self.getCurrentTime()
|
||||||
|
self.packet_timeout = (self.tx_time_per_byte * packet_length) + (self.tx_time_per_byte * 3.0) + 50
|
||||||
|
|
||||||
|
|
||||||
class FeetechMotorsBus(MotorsBus):
|
class FeetechMotorsBus(MotorsBus):
|
||||||
"""
|
"""
|
||||||
The FeetechMotorsBus class allows to efficiently read and write to the attached motors. It relies on the
|
The FeetechMotorsBus class allows to efficiently read and write to the attached motors. It relies on the
|
||||||
@@ -88,6 +101,10 @@ class FeetechMotorsBus(MotorsBus):
|
|||||||
import scservo_sdk as scs
|
import scservo_sdk as scs
|
||||||
|
|
||||||
self.port_handler = scs.PortHandler(self.port)
|
self.port_handler = scs.PortHandler(self.port)
|
||||||
|
# HACK: monkeypatch
|
||||||
|
self.port_handler.setPacketTimeout = patch_setPacketTimeout.__get__(
|
||||||
|
self.port_handler, scs.PortHandler
|
||||||
|
)
|
||||||
self.packet_handler = scs.PacketHandler(PROTOCOL_VERSION)
|
self.packet_handler = scs.PacketHandler(PROTOCOL_VERSION)
|
||||||
self.sync_reader = scs.GroupSyncRead(self.port_handler, self.packet_handler, 0, 0)
|
self.sync_reader = scs.GroupSyncRead(self.port_handler, self.packet_handler, 0, 0)
|
||||||
self.sync_writer = scs.GroupSyncWrite(self.port_handler, self.packet_handler, 0, 0)
|
self.sync_writer = scs.GroupSyncWrite(self.port_handler, self.packet_handler, 0, 0)
|
||||||
@@ -261,36 +278,3 @@ class FeetechMotorsBus(MotorsBus):
|
|||||||
return model_numbers if model_numbers else None
|
return model_numbers if model_numbers else None
|
||||||
|
|
||||||
return model_numbers
|
return model_numbers
|
||||||
|
|
||||||
def _is_eprom(self, address: int) -> bool:
|
|
||||||
"""
|
|
||||||
HACK: because of https://gitee.com/ftservo/SCServoSDK/issues/IBY2S6
|
|
||||||
When writing to EPROM on for Feetech motors with Lock=0, we need to:
|
|
||||||
- 1. write several times
|
|
||||||
- 2. reset serial's buffer
|
|
||||||
"""
|
|
||||||
return address < 40
|
|
||||||
|
|
||||||
def _write(self, data_name: str, motor_id: int, value: int, num_retry: int = 0) -> tuple[int, int]:
|
|
||||||
model = self._id_to_model(motor_id)
|
|
||||||
addr, n_bytes = get_address(self.model_ctrl_table, model, data_name)
|
|
||||||
value = self._encode_value(value, data_name, n_bytes)
|
|
||||||
data = self._split_int_to_bytes(value, n_bytes)
|
|
||||||
|
|
||||||
if self._is_eprom(addr): # HACK
|
|
||||||
num_retry = max(num_retry, 5)
|
|
||||||
|
|
||||||
for n_try in range(1 + num_retry):
|
|
||||||
comm, error = self.packet_handler.writeTxRx(self.port_handler, motor_id, addr, n_bytes, data)
|
|
||||||
if self._is_comm_success(comm):
|
|
||||||
break
|
|
||||||
logger.debug(
|
|
||||||
f"Failed to write '{data_name}' ({addr=} {n_bytes=}) on {motor_id=} with '{value}' ({n_try=})"
|
|
||||||
)
|
|
||||||
logger.debug(self.packet_handler.getRxPacketError(comm))
|
|
||||||
|
|
||||||
if self._is_eprom(addr): # HACK
|
|
||||||
self.port_handler.ser.reset_output_buffer()
|
|
||||||
self.port_handler.ser.reset_input_buffer()
|
|
||||||
|
|
||||||
return comm, error
|
|
||||||
|
|||||||
@@ -468,21 +468,6 @@ class MotorsBus(abc.ABC):
|
|||||||
if self.port_handler.getBaudRate() != baudrate:
|
if self.port_handler.getBaudRate() != baudrate:
|
||||||
raise OSError("Failed to write bus baud rate.")
|
raise OSError("Failed to write bus baud rate.")
|
||||||
|
|
||||||
def reset_calibration(self, motors: NameOrID | list[NameOrID] | None = None) -> None:
|
|
||||||
if motors is None:
|
|
||||||
motors = self.names
|
|
||||||
elif isinstance(motors, (str, int)):
|
|
||||||
motors = [motors]
|
|
||||||
elif not isinstance(motors, list):
|
|
||||||
raise TypeError(motors)
|
|
||||||
|
|
||||||
for motor in motors:
|
|
||||||
model = self._get_motor_model(motor)
|
|
||||||
max_res = self.model_resolution_table[model] - 1
|
|
||||||
self.write("Homing_Offset", motor, 0, normalize=False)
|
|
||||||
self.write("Min_Position_Limit", motor, 0, normalize=False)
|
|
||||||
self.write("Max_Position_Limit", motor, max_res, normalize=False)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_calibrated(self) -> bool:
|
def is_calibrated(self) -> bool:
|
||||||
return self.calibration == self.read_calibration()
|
return self.calibration == self.read_calibration()
|
||||||
@@ -517,6 +502,23 @@ class MotorsBus(abc.ABC):
|
|||||||
|
|
||||||
self.calibration = calibration_dict
|
self.calibration = calibration_dict
|
||||||
|
|
||||||
|
def reset_calibration(self, motors: NameOrID | list[NameOrID] | None = None) -> None:
|
||||||
|
if motors is None:
|
||||||
|
motors = self.names
|
||||||
|
elif isinstance(motors, (str, int)):
|
||||||
|
motors = [motors]
|
||||||
|
elif not isinstance(motors, list):
|
||||||
|
raise TypeError(motors)
|
||||||
|
|
||||||
|
for motor in motors:
|
||||||
|
model = self._get_motor_model(motor)
|
||||||
|
max_res = self.model_resolution_table[model] - 1
|
||||||
|
self.write("Homing_Offset", motor, 0, normalize=False)
|
||||||
|
self.write("Min_Position_Limit", motor, 0, normalize=False)
|
||||||
|
self.write("Max_Position_Limit", motor, max_res, normalize=False)
|
||||||
|
|
||||||
|
self.calibration = {}
|
||||||
|
|
||||||
def set_half_turn_homings(self, motors: NameOrID | list[NameOrID] | None = None) -> dict[NameOrID, Value]:
|
def set_half_turn_homings(self, motors: NameOrID | list[NameOrID] | None = None) -> dict[NameOrID, Value]:
|
||||||
"""
|
"""
|
||||||
This assumes motors present positions are roughly in the middle of their desired range
|
This assumes motors present positions are roughly in the middle of their desired range
|
||||||
@@ -892,9 +894,22 @@ class MotorsBus(abc.ABC):
|
|||||||
f"\n{self.packet_handler.getRxPacketError(error)}"
|
f"\n{self.packet_handler.getRxPacketError(error)}"
|
||||||
)
|
)
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def _write(self, data_name: str, motor_id: int, value: int, num_retry: int = 0) -> tuple[int, int]:
|
def _write(self, data_name: str, motor_id: int, value: int, num_retry: int = 0) -> tuple[int, int]:
|
||||||
pass
|
model = self._id_to_model(motor_id)
|
||||||
|
addr, n_bytes = get_address(self.model_ctrl_table, model, data_name)
|
||||||
|
value = self._encode_value(value, data_name, n_bytes)
|
||||||
|
data = self._split_int_to_bytes(value, n_bytes)
|
||||||
|
|
||||||
|
for n_try in range(1 + num_retry):
|
||||||
|
comm, error = self.packet_handler.writeTxRx(self.port_handler, motor_id, addr, n_bytes, data)
|
||||||
|
if self._is_comm_success(comm):
|
||||||
|
break
|
||||||
|
logger.debug(
|
||||||
|
f"Failed to write '{data_name}' ({addr=} {n_bytes=}) on {motor_id=} with '{value}' ({n_try=})"
|
||||||
|
)
|
||||||
|
logger.debug(self.packet_handler.getRxPacketError(comm))
|
||||||
|
|
||||||
|
return comm, error
|
||||||
|
|
||||||
def disconnect(self, disable_torque: bool = True) -> None:
|
def disconnect(self, disable_torque: bool = True) -> None:
|
||||||
if not self.is_connected:
|
if not self.is_connected:
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import serial
|
|||||||
from mock_serial import MockSerial
|
from mock_serial import MockSerial
|
||||||
|
|
||||||
from lerobot.common.motors.feetech import SCS_SERIES_CONTROL_TABLE, FeetechMotorsBus
|
from lerobot.common.motors.feetech import SCS_SERIES_CONTROL_TABLE, FeetechMotorsBus
|
||||||
|
from lerobot.common.motors.feetech.feetech import patch_setPacketTimeout
|
||||||
|
|
||||||
from .mock_serial_patch import WaitableStub
|
from .mock_serial_patch import WaitableStub
|
||||||
|
|
||||||
@@ -308,6 +309,9 @@ class MockPortHandler(scs.PortHandler):
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def setPacketTimeout(self, packet_length): # noqa: N802
|
||||||
|
return patch_setPacketTimeout(self, packet_length)
|
||||||
|
|
||||||
|
|
||||||
class MockMotors(MockSerial):
|
class MockMotors(MockSerial):
|
||||||
"""
|
"""
|
||||||
|
|||||||
Reference in New Issue
Block a user