diff --git a/actuator/__init__.py b/actuator/__init__.py index 93c28ef..618a48d 100644 --- a/actuator/__init__.py +++ b/actuator/__init__.py @@ -1,10 +1,12 @@ """Defines the top-level API for the actuator package.""" -__version__ = "0.0.22" +__version__ = "0.0.23" from .rust.bindings import ( + PyRobstrideMotorConfig as RobstrideMotorConfig, PyRobstrideMotorControlParams as RobstrideMotorControlParams, PyRobstrideMotorFeedback as RobstrideMotorFeedback, PyRobstrideMotors as RobstrideMotors, PyRobstrideMotorsSupervisor as RobstrideMotorsSupervisor, + PyRobstrideMotorType as RobstrideMotorType, ) diff --git a/actuator/rust/bindings/bindings.pyi b/actuator/rust/bindings/bindings.pyi index 706504a..5882e61 100644 --- a/actuator/rust/bindings/bindings.pyi +++ b/actuator/rust/bindings/bindings.pyi @@ -3,6 +3,21 @@ import typing +class PyRobstrideMotorConfig: + p_min: float + p_max: float + v_min: float + v_max: float + kp_min: float + kp_max: float + kd_min: float + kd_max: float + t_min: float + t_max: float + zero_on_init: bool + can_timeout_command: int + can_timeout_factor: float + class PyRobstrideMotorControlParams: position: float velocity: float @@ -29,6 +44,21 @@ class PyRobstrideMotorFeedback: ... +class PyRobstrideMotorType: + def __repr__(self) -> str: + ... + + @staticmethod + def from_str(s:str) -> PyRobstrideMotorType: + ... + + def __hash__(self) -> int: + ... + + def __eq__(self, other:typing.Any) -> bool: + ... + + class PyRobstrideMotors: def __new__(cls,port_name,motor_infos,verbose = ...): ... def send_get_mode(self) -> dict[int, str]: @@ -46,6 +76,10 @@ class PyRobstrideMotors: def __repr__(self) -> str: ... + @staticmethod + def get_default_configs() -> dict[PyRobstrideMotorType, PyRobstrideMotorConfig]: + ... + class PyRobstrideMotorsSupervisor: total_commands: int diff --git a/actuator/rust/bindings/src/lib.rs b/actuator/rust/bindings/src/lib.rs index 596da0f..ce7fc4a 100644 --- a/actuator/rust/bindings/src/lib.rs +++ b/actuator/rust/bindings/src/lib.rs @@ -3,12 +3,13 @@ use pyo3_stub_gen::define_stub_info_gatherer; use pyo3_stub_gen::derive::{gen_stub_pyclass, gen_stub_pymethods}; use robstride::{ motor_mode_from_str as robstride_motor_mode_from_str, - motor_type_from_str as robstride_motor_type_from_str, + motor_type_from_str as robstride_motor_type_from_str, MotorConfig as RobstrideMotorConfig, MotorControlParams as RobstrideMotorControlParams, MotorFeedback as RobstrideMotorFeedback, MotorType as RobstrideMotorType, Motors as RobstrideMotors, - MotorsSupervisor as RobstrideMotorsSupervisor, + MotorsSupervisor as RobstrideMotorsSupervisor, ROBSTRIDE_CONFIGS as RobstrideDefaultConfigs, }; use std::collections::HashMap; +use std::hash::{Hash, Hasher}; #[gen_stub_pyclass] #[pyclass] @@ -78,6 +79,14 @@ impl PyRobstrideMotors { fn __repr__(&self) -> PyResult { Ok(format!("PyRobstrideMotors")) } + + #[staticmethod] + fn get_default_configs() -> PyResult> { + Ok(RobstrideDefaultConfigs + .iter() + .map(|(motor_type, config)| ((*motor_type).into(), (*config).into())) + .collect()) + } } #[gen_stub_pyclass] @@ -390,12 +399,150 @@ impl PyRobstrideMotorsSupervisor { } } +#[gen_stub_pyclass] +#[pyclass] +#[derive(Clone)] +struct PyRobstrideMotorConfig { + #[pyo3(get)] + p_min: f32, + #[pyo3(get)] + p_max: f32, + #[pyo3(get)] + v_min: f32, + #[pyo3(get)] + v_max: f32, + #[pyo3(get)] + kp_min: f32, + #[pyo3(get)] + kp_max: f32, + #[pyo3(get)] + kd_min: f32, + #[pyo3(get)] + kd_max: f32, + #[pyo3(get)] + t_min: f32, + #[pyo3(get)] + t_max: f32, + #[pyo3(get)] + zero_on_init: bool, + #[pyo3(get)] + can_timeout_command: u16, + #[pyo3(get)] + can_timeout_factor: f32, +} + +impl From for PyRobstrideMotorConfig { + fn from(config: RobstrideMotorConfig) -> Self { + PyRobstrideMotorConfig { + p_min: config.p_min, + p_max: config.p_max, + v_min: config.v_min, + v_max: config.v_max, + kp_min: config.kp_min, + kp_max: config.kp_max, + kd_min: config.kd_min, + kd_max: config.kd_max, + t_min: config.t_min, + t_max: config.t_max, + zero_on_init: config.zero_on_init, + can_timeout_command: config.can_timeout_command, + can_timeout_factor: config.can_timeout_factor, + } + } +} + +#[gen_stub_pyclass] +#[pyclass] +#[derive(Copy, Clone, Debug, Eq)] +struct PyRobstrideMotorType { + value: u8, +} + +impl Hash for PyRobstrideMotorType { + fn hash(&self, state: &mut H) { + self.value.hash(state); + } +} + +impl PartialEq for PyRobstrideMotorType { + fn eq(&self, other: &Self) -> bool { + self.value == other.value + } +} + +#[gen_stub_pymethods] +#[pymethods] +impl PyRobstrideMotorType { + #[classattr] + const TYPE01: Self = PyRobstrideMotorType { value: 0 }; + #[classattr] + const TYPE02: Self = PyRobstrideMotorType { value: 1 }; + #[classattr] + const TYPE03: Self = PyRobstrideMotorType { value: 2 }; + #[classattr] + const TYPE04: Self = PyRobstrideMotorType { value: 3 }; + + fn __repr__(&self) -> PyResult { + let type_name = match self.value { + 0 => "TYPE01", + 1 => "TYPE02", + 2 => "TYPE03", + 3 => "TYPE04", + _ => "Unknown", + }; + Ok(format!("PyRobstrideMotorType::{}", type_name)) + } + + #[staticmethod] + fn from_str(s: &str) -> PyResult { + let motor_type = robstride_motor_type_from_str(s)?; + Ok(PyRobstrideMotorType::from(motor_type)) + } + + fn __hash__(&self) -> PyResult { + Ok(self.value as isize) + } + + fn __eq__(&self, other: &Bound<'_, PyAny>) -> PyResult { + if let Ok(other) = other.extract::() { + Ok(self.value == other.value) + } else { + Ok(false) + } + } +} + +impl From for PyRobstrideMotorType { + fn from(motor_type: RobstrideMotorType) -> Self { + match motor_type { + RobstrideMotorType::Type01 => PyRobstrideMotorType::TYPE01, + RobstrideMotorType::Type02 => PyRobstrideMotorType::TYPE02, + RobstrideMotorType::Type03 => PyRobstrideMotorType::TYPE03, + RobstrideMotorType::Type04 => PyRobstrideMotorType::TYPE04, + } + } +} + +impl From for RobstrideMotorType { + fn from(py_motor_type: PyRobstrideMotorType) -> Self { + match py_motor_type.value { + 0 => RobstrideMotorType::Type01, + 1 => RobstrideMotorType::Type02, + 2 => RobstrideMotorType::Type03, + 3 => RobstrideMotorType::Type04, + _ => RobstrideMotorType::Type04, + } + } +} + #[pymodule] fn bindings(m: &Bound) -> PyResult<()> { m.add_class::()?; m.add_class::()?; m.add_class::()?; m.add_class::()?; + m.add_class::()?; + m.add_class::()?; Ok(()) } diff --git a/actuator/rust/robstride/src/lib.rs b/actuator/rust/robstride/src/lib.rs index 58f9437..bb6a8a3 100644 --- a/actuator/rust/robstride/src/lib.rs +++ b/actuator/rust/robstride/src/lib.rs @@ -22,10 +22,11 @@ pub const CAN_ID_DEBUG_UI: u8 = 0xFD; pub const BAUD_RATE: nix::sys::termios::BaudRate = nix::sys::termios::BaudRate::B921600; // WARNING: NOT A VALID BAUDRATE -// This is just a configuration to build on MacOS +// This is just a configuration to build without errors on MacOS #[cfg(target_os = "macos")] pub const BAUD_RATE: nix::sys::termios::BaudRate = nix::sys::termios::BaudRate::B115200; +#[derive(Debug, Clone, Copy)] pub struct MotorConfig { pub p_min: f32, pub p_max: f32, diff --git a/actuator/rust/robstride/src/port.rs b/actuator/rust/robstride/src/port.rs index 9f2b928..8e1a6c2 100644 --- a/actuator/rust/robstride/src/port.rs +++ b/actuator/rust/robstride/src/port.rs @@ -7,7 +7,7 @@ use std::time::Duration; pub const BAUD_RATE: nix::sys::termios::BaudRate = nix::sys::termios::BaudRate::B921600; // WARNING: NOT A VALID BAUDRATE -// This is just a configuration to build on MacOS +// This is just a configuration to build without errors on MacOS #[cfg(target_os = "macos")] pub const BAUD_RATE: nix::sys::termios::BaudRate = nix::sys::termios::BaudRate::B115200; diff --git a/actuator/sim/robstride.py b/actuator/sim/robstride.py index 54fa043..a459214 100644 --- a/actuator/sim/robstride.py +++ b/actuator/sim/robstride.py @@ -1,119 +1,10 @@ """Simulated Robstride motors.""" import time -from enum import Enum, auto - -from actuator import RobstrideMotorControlParams, RobstrideMotorFeedback - - -class MotorType(Enum): - Type01 = auto() - Type02 = auto() - Type03 = auto() - Type04 = auto() - - @classmethod - def from_str(cls, s: str) -> "MotorType": - try: - return cls[f"Type{s}"] - except KeyError: - raise ValueError(f"Invalid motor type: {s}") - - -class MotorConfig: - def __init__( - self, - p_min: float, - p_max: float, - v_min: float, - v_max: float, - kp_min: float, - kp_max: float, - kd_min: float, - kd_max: float, - t_min: float, - t_max: float, - zero_on_init: bool, - can_timeout_command: int, - can_timeout_factor: float, - ) -> None: - self.p_min = p_min - self.p_max = p_max - self.v_min = v_min - self.v_max = v_max - self.kp_min = kp_min - self.kp_max = kp_max - self.kd_min = kd_min - self.kd_max = kd_max - self.t_min = t_min - self.t_max = t_max - self.zero_on_init = zero_on_init - self.can_timeout_command = can_timeout_command - self.can_timeout_factor = can_timeout_factor - - -ROBSTRIDE_CONFIGS = { - MotorType.Type01: MotorConfig( - p_min=-12.5, - p_max=12.5, - v_min=-44.0, - v_max=44.0, - kp_min=0.0, - kp_max=500.0, - kd_min=0.0, - kd_max=5.0, - t_min=-12.0, - t_max=12.0, - zero_on_init=True, - can_timeout_command=0x200C, - can_timeout_factor=12000.0, - ), - MotorType.Type02: MotorConfig( - p_min=-12.5, - p_max=12.5, - v_min=-44.0, - v_max=44.0, - kp_min=0.0, - kp_max=500.0, - kd_min=0.0, - kd_max=5.0, - t_min=-12.0, - t_max=12.0, - zero_on_init=False, - can_timeout_command=0x200B, - can_timeout_factor=12000.0, - ), - MotorType.Type03: MotorConfig( - p_min=-12.5, - p_max=12.5, - v_min=-44.0, - v_max=44.0, - kp_min=0.0, - kp_max=500.0, - kd_min=0.0, - kd_max=5.0, - t_min=-12.0, - t_max=12.0, - zero_on_init=False, - can_timeout_command=0x200B, - can_timeout_factor=12000.0, - ), - MotorType.Type04: MotorConfig( - p_min=-12.5, - p_max=12.5, - v_min=-44.0, - v_max=44.0, - kp_min=0.0, - kp_max=500.0, - kd_min=0.0, - kd_max=5.0, - t_min=-12.0, - t_max=12.0, - zero_on_init=False, - can_timeout_command=0x200B, - can_timeout_factor=12000.0, - ), -} + +from actuator import RobstrideMotorControlParams, RobstrideMotorFeedback, RobstrideMotors, RobstrideMotorType + +ROBSTRIDE_CONFIGS = RobstrideMotors.get_default_configs() class _MotorSim: @@ -142,8 +33,9 @@ def get_feedback(self) -> RobstrideMotorFeedback: class RobstrideMotorsSim: def __init__(self, port_name: str, motor_infos: dict[int, str], verbose: bool = False) -> None: self.port_name = port_name + # breakpoint() self.motor_configs = { - id: ROBSTRIDE_CONFIGS[MotorType.from_str(motor_type)] for id, motor_type in motor_infos.items() + id: ROBSTRIDE_CONFIGS[RobstrideMotorType.from_str(motor_type)] for id, motor_type in motor_infos.items() } self.verbose = verbose diff --git a/setup.py b/setup.py index 25a17e6..8f0b05a 100644 --- a/setup.py +++ b/setup.py @@ -4,8 +4,10 @@ import glob import re +import subprocess from setuptools import find_packages, setup +from setuptools.command.build_ext import build_ext from setuptools_rust import Binding, RustExtension with open("README.md", "r", encoding="utf-8") as f: @@ -31,6 +33,14 @@ package_data.extend(glob.iglob(f"actuator/**/*.{ext}", recursive=True)) +class RustBuildExt(build_ext): + def run(self) -> None: + # Run the stub generator + subprocess.run(["cargo", "run", "--bin", "stub_gen"], check=True) + # Call the original build_ext command + super().run() + + setup( name="actuator", version=version, @@ -53,5 +63,6 @@ extras_require={"dev": requirements_dev}, include_package_data=True, package_data={"actuator": package_data}, - packages=find_packages(include=["actuator", "actuator.*"]), + packages=find_packages(include=["actuator"]), + cmdclass={"build_ext": RustBuildExt}, )