mecha10_controllers/
l298n.rs

1//! L298N Motor Controller
2//!
3//! High-level motor controller wrapping the L298N driver for differential drive robots.
4//!
5//! # Overview
6//!
7//! This controller provides a `MotorController` implementation for L298N-based
8//! differential drive robots. It wraps the low-level `L298NDriver` and provides:
9//!
10//! - Differential drive kinematics (twist to wheel velocities)
11//! - Velocity normalization and clamping
12//! - Health monitoring and diagnostics
13//! - Dead-reckoning odometry estimation (no encoder feedback)
14//!
15//! # Example
16//!
17//! ```rust,no_run
18//! use mecha10_controllers::{Controller, MotorController};
19//! use mecha10_controllers::l298n::{L298nMotorController, L298nControllerConfig};
20//!
21//! #[tokio::main]
22//! async fn main() -> anyhow::Result<()> {
23//!     let config = L298nControllerConfig {
24//!         gpio_chip: "gpiochip0".to_string(),
25//!         motor_a: L298nMotorPins { in1: 17, in2: 27, enable: 18, invert: false },
26//!         motor_b: L298nMotorPins { in1: 22, in2: 23, enable: 12, invert: false },
27//!         pwm_frequency_hz: 1000,
28//!         wheel_base: 0.3,
29//!         max_velocity: 1.0,
30//!         max_duty_cycle: 0.8,
31//!     };
32//!
33//!     let mut controller = L298nMotorController::init(config).await?;
34//!     controller.start().await?;
35//!
36//!     // Set velocity (normalized -1.0 to 1.0)
37//!     controller.set_velocity(0.5, 0.5).await?; // Forward
38//!     controller.set_velocity(0.5, -0.5).await?; // Rotate
39//!
40//!     controller.stop().await?;
41//!     Ok(())
42//! }
43//! ```
44//!
45//! # Limitations
46//!
47//! - No encoder feedback - odometry is estimated from commanded velocities
48//! - No current/torque sensing - cannot detect stalls or measure actual torque
49//! - Open-loop control only - no closed-loop position or velocity control
50
51use crate::motor::*;
52use crate::{Controller, ControllerCapabilities, ControllerError, ControllerHealth, ControllerState};
53use async_trait::async_trait;
54use mecha10_core::actuator::MotorStatus;
55use mecha10_drivers_motor::l298n::{L298NConfig, L298NDriver, L298NMotorPins as DriverMotorPins};
56use mecha10_drivers_motor::{ControlMode, MotorCommand, MotorDriver};
57use serde::{Deserialize, Serialize};
58use std::collections::HashMap;
59use std::time::{SystemTime, UNIX_EPOCH};
60use tracing::{debug, info, warn};
61
62/// L298N controller configuration
63#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct L298nControllerConfig {
65    /// GPIO chip device (e.g., "gpiochip0")
66    pub gpio_chip: String,
67
68    /// Motor A (left) pin configuration
69    pub motor_a: L298nMotorPins,
70
71    /// Motor B (right) pin configuration
72    pub motor_b: L298nMotorPins,
73
74    /// PWM frequency in Hz
75    #[serde(default = "default_pwm_frequency")]
76    pub pwm_frequency_hz: u32,
77
78    /// Wheel base (distance between wheels) in meters
79    #[serde(default = "default_wheel_base")]
80    pub wheel_base: f32,
81
82    /// Maximum velocity (normalized, for scaling)
83    #[serde(default = "default_max_velocity")]
84    pub max_velocity: f32,
85
86    /// Maximum duty cycle (0.0-1.0) as safety limit
87    #[serde(default = "default_max_duty_cycle")]
88    pub max_duty_cycle: f32,
89}
90
91fn default_pwm_frequency() -> u32 {
92    1000
93}
94
95fn default_wheel_base() -> f32 {
96    0.3
97}
98
99fn default_max_velocity() -> f32 {
100    1.0
101}
102
103fn default_max_duty_cycle() -> f32 {
104    1.0
105}
106
107impl Default for L298nControllerConfig {
108    fn default() -> Self {
109        Self {
110            gpio_chip: "gpiochip0".to_string(),
111            motor_a: L298nMotorPins::default_motor_a(),
112            motor_b: L298nMotorPins::default_motor_b(),
113            pwm_frequency_hz: default_pwm_frequency(),
114            wheel_base: default_wheel_base(),
115            max_velocity: default_max_velocity(),
116            max_duty_cycle: default_max_duty_cycle(),
117        }
118    }
119}
120
121/// Pin configuration for a single motor
122#[derive(Debug, Clone, Serialize, Deserialize)]
123pub struct L298nMotorPins {
124    /// Direction control pin 1 (IN1 or IN3)
125    pub in1: u32,
126
127    /// Direction control pin 2 (IN2 or IN4)
128    pub in2: u32,
129
130    /// Enable/PWM pin (ENA or ENB)
131    pub enable: u32,
132
133    /// Invert motor direction
134    #[serde(default)]
135    pub invert: bool,
136}
137
138impl L298nMotorPins {
139    /// Default pins for Motor A (left motor, common Raspberry Pi setup)
140    pub fn default_motor_a() -> Self {
141        Self {
142            in1: 17,
143            in2: 27,
144            enable: 18,
145            invert: false,
146        }
147    }
148
149    /// Default pins for Motor B (right motor, common Raspberry Pi setup)
150    pub fn default_motor_b() -> Self {
151        Self {
152            in1: 22,
153            in2: 23,
154            enable: 12,
155            invert: false,
156        }
157    }
158}
159
160impl From<L298nMotorPins> for DriverMotorPins {
161    fn from(pins: L298nMotorPins) -> Self {
162        DriverMotorPins {
163            in1: pins.in1,
164            in2: pins.in2,
165            enable: pins.enable,
166            invert: pins.invert,
167        }
168    }
169}
170
171/// L298N Motor Controller
172///
173/// Provides differential drive control for robots using L298N dual H-bridge driver.
174pub struct L298nMotorController {
175    config: L298nControllerConfig,
176    driver: L298NDriver,
177    state: ControllerState,
178    left_velocity: f32,
179    right_velocity: f32,
180    // Estimated encoder counts (dead reckoning)
181    left_encoder: i32,
182    right_encoder: i32,
183    last_update_us: u64,
184}
185
186#[async_trait]
187impl Controller for L298nMotorController {
188    type Config = L298nControllerConfig;
189    type Error = ControllerError;
190
191    async fn init(config: Self::Config) -> Result<Self, Self::Error> {
192        info!("Initializing L298N motor controller");
193        info!("Wheel base: {} m", config.wheel_base);
194        info!("Max velocity: {}", config.max_velocity);
195        info!("Max duty cycle: {}%", config.max_duty_cycle * 100.0);
196
197        // Create driver config from controller config
198        let driver_config = L298NConfig {
199            gpio_chip: config.gpio_chip.clone(),
200            motor_a: config.motor_a.clone().into(),
201            motor_b: config.motor_b.clone().into(),
202            pwm_frequency_hz: config.pwm_frequency_hz,
203            max_duty_cycle: config.max_duty_cycle,
204            update_rate_hz: 50.0,
205        };
206
207        let mut driver = L298NDriver::new(driver_config);
208        driver
209            .init()
210            .await
211            .map_err(|e| ControllerError::InitializationFailed(e.to_string()))?;
212
213        Ok(Self {
214            config,
215            driver,
216            state: ControllerState::Initialized,
217            left_velocity: 0.0,
218            right_velocity: 0.0,
219            left_encoder: 0,
220            right_encoder: 0,
221            last_update_us: now_us(),
222        })
223    }
224
225    async fn start(&mut self) -> Result<(), Self::Error> {
226        info!("Starting L298N motor controller");
227
228        // Enable both motors
229        self.driver
230            .enable(0)
231            .await
232            .map_err(|e| ControllerError::CommunicationError(e.to_string()))?;
233        self.driver
234            .enable(1)
235            .await
236            .map_err(|e| ControllerError::CommunicationError(e.to_string()))?;
237
238        self.state = ControllerState::Running;
239        self.last_update_us = now_us();
240        info!("L298N motor controller started");
241
242        Ok(())
243    }
244
245    async fn stop(&mut self) -> Result<(), Self::Error> {
246        info!("Stopping L298N motor controller");
247
248        // Stop motors first
249        self.set_velocity(0.0, 0.0).await?;
250
251        // Disable motors
252        self.driver
253            .disable(0)
254            .await
255            .map_err(|e| ControllerError::CommunicationError(e.to_string()))?;
256        self.driver
257            .disable(1)
258            .await
259            .map_err(|e| ControllerError::CommunicationError(e.to_string()))?;
260
261        self.state = ControllerState::Stopped;
262        info!("L298N motor controller stopped");
263
264        Ok(())
265    }
266
267    async fn health_check(&self) -> ControllerHealth {
268        match self.state {
269            ControllerState::Running => ControllerHealth::Healthy,
270            ControllerState::Initialized => ControllerHealth::Degraded {
271                reason: "Controller initialized but not started".to_string(),
272            },
273            ControllerState::Stopped => ControllerHealth::Degraded {
274                reason: "Controller stopped".to_string(),
275            },
276            ControllerState::Error => ControllerHealth::Unhealthy {
277                reason: "Controller in error state".to_string(),
278            },
279            ControllerState::Uninitialized => ControllerHealth::Unknown,
280        }
281    }
282
283    fn capabilities(&self) -> ControllerCapabilities {
284        ControllerCapabilities::new("motor", "l298n")
285            .with_vendor("STMicroelectronics")
286            .with_model("L298N Dual H-Bridge")
287            .with_feature("velocity_control", true)
288            .with_feature("encoders", false) // No encoder feedback
289            .with_feature("position_control", false)
290            .with_feature("torque_control", false)
291            .with_feature("current_sensing", false)
292            .with_feature("temperature_sensing", false)
293            .with_metadata("wheel_base", format!("{} m", self.config.wheel_base))
294            .with_metadata("max_duty_cycle", format!("{}%", self.config.max_duty_cycle * 100.0))
295    }
296
297    async fn diagnostics(&self) -> HashMap<String, String> {
298        let mut diag = HashMap::new();
299        diag.insert("state".to_string(), format!("{:?}", self.state));
300        diag.insert("left_velocity".to_string(), format!("{:.2}", self.left_velocity));
301        diag.insert("right_velocity".to_string(), format!("{:.2}", self.right_velocity));
302        diag.insert("left_encoder_est".to_string(), self.left_encoder.to_string());
303        diag.insert("right_encoder_est".to_string(), self.right_encoder.to_string());
304        diag.insert("gpio_chip".to_string(), self.config.gpio_chip.clone());
305        diag
306    }
307}
308
309#[async_trait]
310impl MotorController for L298nMotorController {
311    async fn set_velocity(&mut self, left: f32, right: f32) -> Result<(), Self::Error> {
312        if self.state != ControllerState::Running {
313            return Err(ControllerError::InvalidState("Controller not running".to_string()));
314        }
315
316        // Clamp velocities
317        let max_vel = self.config.max_velocity;
318        let left_clamped = left.clamp(-max_vel, max_vel);
319        let right_clamped = right.clamp(-max_vel, max_vel);
320
321        // Normalize to -1.0 to 1.0 range for PWM
322        let left_norm = left_clamped / max_vel;
323        let right_norm = right_clamped / max_vel;
324
325        // Update estimated encoder counts based on velocity
326        self.update_encoder_estimates(left_norm, right_norm);
327
328        // Store velocities
329        self.left_velocity = left_clamped;
330        self.right_velocity = right_clamped;
331
332        // Send commands to driver
333        // Motor A = left (ID 0), Motor B = right (ID 1)
334        let left_cmd = MotorCommand {
335            motor_id: 0,
336            mode: ControlMode::PWM,
337            target: left_norm as f64,
338            velocity_limit: None,
339            torque_limit: None,
340            profile_velocity: None,
341            profile_acceleration: None,
342        };
343
344        let right_cmd = MotorCommand {
345            motor_id: 1,
346            mode: ControlMode::PWM,
347            target: right_norm as f64,
348            velocity_limit: None,
349            torque_limit: None,
350            profile_velocity: None,
351            profile_acceleration: None,
352        };
353
354        self.driver
355            .send_command(&left_cmd)
356            .await
357            .map_err(|e| ControllerError::CommunicationError(e.to_string()))?;
358
359        self.driver
360            .send_command(&right_cmd)
361            .await
362            .map_err(|e| ControllerError::CommunicationError(e.to_string()))?;
363
364        debug!(
365            "Set velocity: left={:.2}, right={:.2} (norm: {:.2}, {:.2})",
366            left_clamped, right_clamped, left_norm, right_norm
367        );
368
369        Ok(())
370    }
371
372    async fn get_encoders(&mut self) -> Result<(i32, i32), Self::Error> {
373        // L298N has no encoders - return estimated values from dead reckoning
374        warn!("L298N has no encoders - returning estimated values");
375        Ok((self.left_encoder, self.right_encoder))
376    }
377
378    async fn get_status(&mut self) -> Result<MotorStatus, Self::Error> {
379        // Convert velocity to RPM (assuming some wheel radius)
380        let wheel_radius = 0.05; // 5cm wheels assumed
381        let vel_to_rpm = 60.0 / (2.0 * std::f32::consts::PI * wheel_radius);
382
383        Ok(MotorStatus {
384            timestamp: now_us(),
385            left_motor_rpm: self.left_velocity * vel_to_rpm,
386            right_motor_rpm: self.right_velocity * vel_to_rpm,
387            battery_voltage: 0.0, // Unknown - no sensing
388            temperature_c: 0.0,   // Unknown - no sensing
389        })
390    }
391
392    async fn emergency_stop(&mut self) -> Result<(), Self::Error> {
393        info!("Emergency stop triggered!");
394
395        // Immediately stop motors
396        self.left_velocity = 0.0;
397        self.right_velocity = 0.0;
398
399        // Send stop commands
400        let stop_cmd = MotorCommand {
401            motor_id: 0,
402            mode: ControlMode::PWM,
403            target: 0.0,
404            velocity_limit: None,
405            torque_limit: None,
406            profile_velocity: None,
407            profile_acceleration: None,
408        };
409
410        // Stop both motors
411        let _ = self.driver.send_command(&stop_cmd).await;
412        let mut stop_cmd_b = stop_cmd;
413        stop_cmd_b.motor_id = 1;
414        let _ = self.driver.send_command(&stop_cmd_b).await;
415
416        // Disable motors
417        let _ = self.driver.disable(0).await;
418        let _ = self.driver.disable(1).await;
419
420        self.state = ControllerState::Stopped;
421        Ok(())
422    }
423
424    async fn enable(&mut self) -> Result<(), Self::Error> {
425        self.driver
426            .enable(0)
427            .await
428            .map_err(|e| ControllerError::CommunicationError(e.to_string()))?;
429        self.driver
430            .enable(1)
431            .await
432            .map_err(|e| ControllerError::CommunicationError(e.to_string()))?;
433        Ok(())
434    }
435
436    async fn disable(&mut self) -> Result<(), Self::Error> {
437        // Stop first
438        self.set_velocity(0.0, 0.0).await?;
439
440        self.driver
441            .disable(0)
442            .await
443            .map_err(|e| ControllerError::CommunicationError(e.to_string()))?;
444        self.driver
445            .disable(1)
446            .await
447            .map_err(|e| ControllerError::CommunicationError(e.to_string()))?;
448        Ok(())
449    }
450
451    async fn reset_encoders(&mut self) -> Result<(), Self::Error> {
452        self.left_encoder = 0;
453        self.right_encoder = 0;
454        info!("Reset estimated encoder counts");
455        Ok(())
456    }
457}
458
459impl L298nMotorController {
460    /// Update estimated encoder counts based on velocity (dead reckoning)
461    fn update_encoder_estimates(&mut self, left_norm: f32, right_norm: f32) {
462        let now = now_us();
463        let dt_us = now.saturating_sub(self.last_update_us);
464        let dt_s = dt_us as f32 / 1_000_000.0;
465
466        // Assume 1000 encoder counts per second at full speed
467        let counts_per_second = 1000.0;
468
469        self.left_encoder += (left_norm * counts_per_second * dt_s) as i32;
470        self.right_encoder += (right_norm * counts_per_second * dt_s) as i32;
471
472        self.last_update_us = now;
473    }
474
475    /// Get current state
476    pub fn state(&self) -> ControllerState {
477        self.state
478    }
479
480    /// Get configuration
481    pub fn config(&self) -> &L298nControllerConfig {
482        &self.config
483    }
484}
485
486/// Get current time in microseconds
487fn now_us() -> u64 {
488    SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_micros() as u64
489}
490
491#[cfg(test)]
492mod tests {
493    use super::*;
494
495    #[tokio::test]
496    async fn test_controller_init() {
497        let config = L298nControllerConfig::default();
498        let controller = L298nMotorController::init(config).await;
499        assert!(controller.is_ok());
500    }
501
502    #[tokio::test]
503    async fn test_controller_start_stop() {
504        let config = L298nControllerConfig::default();
505        let mut controller = L298nMotorController::init(config).await.unwrap();
506
507        controller.start().await.unwrap();
508        assert_eq!(controller.state(), ControllerState::Running);
509
510        controller.stop().await.unwrap();
511        assert_eq!(controller.state(), ControllerState::Stopped);
512    }
513
514    #[tokio::test]
515    async fn test_set_velocity() {
516        let config = L298nControllerConfig::default();
517        let mut controller = L298nMotorController::init(config).await.unwrap();
518        controller.start().await.unwrap();
519
520        let result = controller.set_velocity(0.5, 0.5).await;
521        assert!(result.is_ok());
522    }
523
524    #[tokio::test]
525    async fn test_emergency_stop() {
526        let config = L298nControllerConfig::default();
527        let mut controller = L298nMotorController::init(config).await.unwrap();
528        controller.start().await.unwrap();
529
530        controller.set_velocity(1.0, 1.0).await.unwrap();
531        controller.emergency_stop().await.unwrap();
532
533        assert_eq!(controller.state(), ControllerState::Stopped);
534    }
535
536    #[tokio::test]
537    async fn test_capabilities() {
538        let config = L298nControllerConfig::default();
539        let controller = L298nMotorController::init(config).await.unwrap();
540
541        let caps = controller.capabilities();
542        assert_eq!(caps.controller_type, "motor");
543        assert_eq!(caps.implementation, "l298n");
544        assert!(!caps.supports("encoders"));
545        assert!(caps.supports("velocity_control"));
546    }
547}