1#[derive(Debug, Clone)]
11pub struct PidState {
12 pub kp: f64,
13 pub ki: f64,
14 pub kd: f64,
15 pub integral: f64,
16 pub prev_error: f64,
17 pub integral_max: f64,
18}
19
20impl PidState {
21 pub fn new(kp: f64, ki: f64, kd: f64) -> Self {
22 Self {
23 kp,
24 ki,
25 kd,
26 integral: 0.0,
27 prev_error: 0.0,
28 integral_max: 100.0,
29 }
30 }
31
32 pub fn compute(&mut self, setpoint: f64, actual: f64, dt_s: f64) -> f64 {
34 let error = setpoint - actual;
35 let dt = if dt_s <= 0.0 { 0.005 } else { dt_s };
36
37 let p_term = self.kp * error;
39
40 self.integral += error * dt;
42 self.integral = self.integral.clamp(-self.integral_max, self.integral_max);
43 let i_term = self.ki * self.integral;
44
45 let d_error = (error - self.prev_error) / dt;
47 self.prev_error = error;
48 let d_term = self.kd * d_error;
49
50 p_term + i_term + d_term
51 }
52}
53
54use std::cell::RefCell;
55
56thread_local! {
57 static DEFAULT_PID: RefCell<PidState> = RefCell::new(PidState::new(1.0, 0.0, 0.0));
58}
59
60pub fn pid_compute(setpoint: f64, actual: f64, kp: f64, ki: f64, kd: f64) -> f64 {
62 DEFAULT_PID.with(|pid_cell| {
63 let mut pid = pid_cell.borrow_mut();
64 pid.kp = kp;
65 pid.ki = ki;
66 pid.kd = kd;
67 pid.compute(setpoint, actual, 0.005).clamp(-100.0, 100.0)
68 })
69}
70
71pub fn pure_pursuit(
74 rx: f64,
75 ry: f64,
76 rtheta: f64,
77 wx: f64,
78 wy: f64,
79 linear_v: f64,
80) -> (f64, f64) {
81 let dx = wx - rx;
82 let dy = wy - ry;
83 let local_x = dx * rtheta.cos() + dy * rtheta.sin();
84 let local_y = -dx * rtheta.sin() + dy * rtheta.cos();
85 let l2 = (local_x * local_x + local_y * local_y).max(0.01);
86 let curvature = 2.0 * local_y / l2;
87 let angular_w = linear_v * curvature;
88 (curvature, angular_w)
89}
90
91pub fn feedforward_pid(
93 setpoint: f64,
94 actual: f64,
95 kp: f64,
96 ki: f64,
97 kd: f64,
98 kf: f64,
99 prev_setpoint: f64,
100 dt_s: f64,
101) -> f64 {
102 let dt = if dt_s <= 0.0 { 0.005 } else { dt_s };
103 let ff_term = kf * ((setpoint - prev_setpoint) / dt);
104 let pid_term = pid_compute(setpoint, actual, kp, ki, kd);
105 (ff_term + pid_term).clamp(-100.0, 100.0)
106}
107
108pub fn ekf_update(
110 prior_x: f64,
111 meas_z: f64,
112 cov_p: f64,
113 meas_noise_r: f64,
114) -> (f64, f64) {
115 let r = if meas_noise_r <= 0.0 { 0.1 } else { meas_noise_r };
116 let kalman_gain = cov_p / (cov_p + r);
118 let updated_x = prior_x + kalman_gain * (meas_z - prior_x);
120 let updated_cov = (1.0 - kalman_gain) * cov_p;
122 (updated_x, updated_cov)
123}
124
125pub fn fuse_imu_gps(lat: f64, lon: f64, yaw_deg: f64) -> (f64, f64, f64) {
127 let mut yaw_norm = yaw_deg % 360.0;
129 if yaw_norm > 180.0 {
130 yaw_norm -= 360.0;
131 } else if yaw_norm < -180.0 {
132 yaw_norm += 360.0;
133 }
134 let corrected_lat = lat + (yaw_norm.to_radians().cos() * 0.00001);
136 let corrected_lon = lon + (yaw_norm.to_radians().sin() * 0.00001);
137 (corrected_lat, corrected_lon, yaw_norm)
138}
139
140pub fn ik_3dof_arm(x: f64, y: f64, z: f64) -> (f64, f64, f64) {
143 let l1 = 100.0_f64;
145 let l2 = 120.0_f64;
146 let l3 = 100.0_f64;
147
148 let base_rad = y.atan2(x);
150 let base_deg = base_rad.to_degrees().clamp(0.0, 180.0);
151
152 let r = (x * x + y * y).sqrt();
154 let r_eff = (r - 20.0).max(1.0); let z_eff = z - l1;
156
157 let d2 = r_eff * r_eff + z_eff * z_eff;
159 let d = d2.sqrt().clamp(10.0, l2 + l3 - 0.1);
160
161 let cos_elbow = ((d2 - l2 * l2 - l3 * l3) / (2.0 * l2 * l3)).clamp(-1.0, 1.0);
163 let elbow_rad = cos_elbow.acos();
164 let elbow_deg = elbow_rad.to_degrees().clamp(0.0, 180.0);
165
166 let alpha = z_eff.atan2(r_eff);
168 let cos_beta = ((l2 * l2 + d2 - l3 * l3) / (2.0 * l2 * d)).clamp(-1.0, 1.0);
169 let beta = cos_beta.acos();
170 let shoulder_rad = alpha + beta;
171 let shoulder_deg = shoulder_rad.to_degrees().clamp(0.0, 180.0);
172
173 (base_deg, shoulder_deg, elbow_deg)
174}
175
176pub fn diff_drive_ik(linear_v: f64, angular_w: f64) -> (i32, i32) {
180 let wheel_base = 0.30_f64; let max_velocity = 1.5_f64; let v_left = linear_v - (angular_w * wheel_base / 2.0);
184 let v_right = linear_v + (angular_w * wheel_base / 2.0);
185
186 let pwm_left = ((v_left / max_velocity) * 100.0).round().clamp(-100.0, 100.0) as i32;
187 let pwm_right = ((v_right / max_velocity) * 100.0).round().clamp(-100.0, 100.0) as i32;
188 (pwm_left, pwm_right)
189}
190
191pub fn lqr_compute(err_x: f64, err_v: f64, k1: f64, k2: f64) -> f64 {
193 let control_input = -(k1 * err_x + k2 * err_v);
194 control_input.clamp(-100.0, 100.0)
195}
196
197pub fn haversine_distance(lat1: f64, lon1: f64, lat2: f64, lon2: f64) -> f64 {
199 let r_earth = 6_371_000.0_f64; let d_lat = (lat2 - lat1).to_radians();
201 let d_lon = (lon2 - lon1).to_radians();
202
203 let a = (d_lat / 2.0).sin().powi(2)
204 + lat1.to_radians().cos() * lat2.to_radians().cos() * (d_lon / 2.0).sin().powi(2);
205 let c = 2.0 * a.sqrt().atan2((1.0 - a).sqrt());
206 r_earth * c
207}
208
209pub fn lidar_find_closest(scan: &[f64]) -> (f64, u32) {
211 let mut min_dist = f64::MAX;
212 let mut min_angle = 0;
213 for (angle, &dist) in scan.iter().enumerate() {
214 if dist > 0.1 && dist < min_dist {
215 min_dist = dist;
216 min_angle = angle as u32;
217 }
218 }
219 if min_dist == f64::MAX {
220 (999.0, 0)
221 } else {
222 (min_dist, min_angle)
223 }
224}
225
226pub struct StdlibModule {
228 pub name: &'static str,
229 pub description: &'static str,
230 pub functions: &'static [(&'static str, &'static str)],
231}
232
233pub const STDLIB_MODULES: &[StdlibModule] = &[
234 StdlibModule {
235 name: "std::gpio",
236 description: "General Purpose Input/Output Pin Control, Modes, Interrupts",
237 functions: &[
238 ("write(pin: u32, level: bool) -> void", "Write HIGH or LOW to a digital output pin"),
239 ("read(pin: u32) -> bool", "Read digital state from an input pin"),
240 ("set_mode(pin: u32, mode: PinMode) -> void", "Set pin mode to IN, OUT, ALT, or PWM"),
241 ("attach_interrupt(pin: u32, edge: String, callback: fn) -> void", "Attach a hardware interrupt handler"),
242 ],
243 },
244 StdlibModule {
245 name: "std::i2c",
246 description: "I2C Master/Slave Communication & Sensor Interface",
247 functions: &[
248 ("read(bus: u32, addr: u8, len: usize) -> [u8; 32]", "Read bytes from an I2C slave address"),
249 ("write(bus: u32, addr: u8, data: [u8; 32]) -> void", "Write bytes to an I2C slave address"),
250 ("scan(bus: u32) -> [u8; 16]", "Scan bus for active I2C slave addresses"),
251 ],
252 },
253 StdlibModule {
254 name: "std::spi",
255 description: "SPI High-Speed Peripheral Bus & Display/SD-Card Driver",
256 functions: &[
257 ("transfer(bus: u32, tx: u8) -> u8", "Simultaneous full-duplex SPI byte transfer"),
258 ("write_buffer(bus: u32, data: [u8; 64]) -> void", "Burst write buffer over SPI"),
259 ],
260 },
261 StdlibModule {
262 name: "std::uart",
263 description: "Serial UART Telemetry, GPS, and LIDAR Communication",
264 functions: &[
265 ("send(port: u32, data: string) -> void", "Transmit string telemetry over serial port"),
266 ("receive(port: u32) -> string", "Receive string packet from UART rx buffer"),
267 ("set_baudrate(port: u32, baud: u32) -> void", "Configure serial port baudrate (e.g. 115200)"),
268 ],
269 },
270 StdlibModule {
271 name: "std::can",
272 description: "CAN-Bus automotive and multi-node industrial robotics protocol",
273 functions: &[
274 ("send(id: u32, data: [u8; 8], extended: bool) -> void", "Send a standard or extended CAN frame"),
275 ("receive() -> (u32, [u8; 8])", "Read next incoming CAN frame from bus queue"),
276 ("set_filter(id: u32, mask: u32) -> void", "Configure hardware CAN acceptance filters"),
277 ],
278 },
279 StdlibModule {
280 name: "std::pwm",
281 description: "Pulse Width Modulation for Servo Motors and LED Dimming",
282 functions: &[
283 ("write(pin: u32, duty_cycle: u32) -> void", "Set PWM duty cycle between 0% and 100%"),
284 ("set_frequency(pin: u32, freq_hz: u32) -> void", "Set PWM frequency in Hertz"),
285 ],
286 },
287 StdlibModule {
288 name: "std::bluetooth",
289 description: "Bluetooth Low Energy (BLE) GATT Server & Remote Controller",
290 functions: &[
291 ("advertise(name: string) -> void", "Start BLE advertising with custom device name"),
292 ("notify(char_uuid: string, value: string) -> void", "Send BLE GATT notification to connected client"),
293 ],
294 },
295 StdlibModule {
296 name: "std::wifi",
297 description: "Wi-Fi AP/Station Mode & TCP/UDP Telemetry Sockets",
298 functions: &[
299 ("connect(ssid: string, psk: string) -> bool", "Connect to wireless access point"),
300 ("start_ap(ssid: string) -> void", "Start Access Point mode for local robot config"),
301 ],
302 },
303 StdlibModule {
304 name: "std::usb",
305 description: "USB CDC/ACM Serial, HID, and USB-OTG Device Mode",
306 functions: &[
307 ("send_cdc(data: string) -> void", "Send data over USB CDC Virtual Com Port"),
308 ("read_cdc() -> string", "Read data from USB CDC endpoint"),
309 ],
310 },
311 StdlibModule {
312 name: "std::ros",
313 description: "Native ROS 2 Pub/Sub Messaging Layer & Topic Bridge",
314 functions: &[
315 ("init(node_name: string) -> void", "Initialize ROS 2 node context"),
316 ("publish(topic: string, msg: string) -> void", "Publish payload to ROS 2 topic"),
317 ("subscribe(topic: string, callback: fn) -> void", "Subscribe to ROS 2 topic with callback"),
318 ],
319 },
320 StdlibModule {
321 name: "std::motor",
322 description: "DC, Stepper, ESC/BLDC Drivers, Differential Drive & PID Controller",
323 functions: &[
324 ("set_speed(channel: u32, speed: i32) -> void", "Set motor speed (-100 to 100)"),
325 ("pid_compute(setpoint: f64, actual: f64, kp: f64, ki: f64, kd: f64) -> f64", "Compute PID control output"),
326 ("stop_all() -> void", "Emergency stop all motor channels"),
327 ],
328 },
329 StdlibModule {
330 name: "std::sensor",
331 description: "High-level Sensor Abstractions (Ultrasonic, IMU, Encoders, LIDAR)",
332 functions: &[
333 ("ultrasonic_read(trig: u32, echo: u32) -> f64", "Read ultrasonic distance in centimeters"),
334 ("imu_read_rpy() -> (f64, f64, f64)", "Read roll, pitch, yaw from IMU sensor fusion"),
335 ("encoder_count(channel: u32) -> i64", "Get optical encoder pulse count"),
336 ],
337 },
338 StdlibModule {
339 name: "std::camera",
340 description: "Vision Pipeline, Frame Buffer Acquisition, Edge Detection & Blob Tracking",
341 functions: &[
342 ("capture() -> [u8; 1024]", "Capture frame from camera sensor"),
343 ("detect_color_blob(r: u8, g: u8, b: u8) -> (f64, f64)", "Detect center (X,Y) of color blob in image"),
344 ],
345 },
346 StdlibModule {
347 name: "std::ai",
348 description: "Lightweight Robotics Edge AI & Neural Network Inference Engine",
349 functions: &[
350 ("infer(model_id: string, inputs: [f64; 16]) -> [f64; 4]", "Run edge AI model inference on sensor tensor"),
351 ("classify_obstacle(distance_cm: f64, camera_score: f64) -> string", "Classify obstacle type (Wall, Person, Unknown)"),
352 ],
353 },
354 StdlibModule {
355 name: "std::thread",
356 description: "Concurrent Execution, Mutex Locks, and Message Queues",
357 functions: &[
358 ("spawn(task_fn: fn) -> void", "Spawn a concurrent worker thread"),
359 ("yield_now() -> void", "Yield execution to scheduler"),
360 ],
361 },
362 StdlibModule {
363 name: "std::realtime",
364 description: "Deterministic Timers, Priority Scheduling & Deadline Monitoring",
365 functions: &[
366 ("sleep_ms(ms: u32) -> void", "Sleep deterministic milliseconds without drift"),
367 ("deadline_monitor(task_name: string, budget_ms: u32) -> bool", "Verify if current cycle is within deadline"),
368 ("watchdog_reset() -> void", "Kick hardware watchdog timer"),
369 ],
370 },
371 StdlibModule {
372 name: "std::kalman",
373 description: "Kalman Filter & Extended Kalman Filter (EKF) Sensor Fusion Equations",
374 functions: &[
375 ("ekf_update(prior_x: f64, meas_z: f64, cov_p: f64) -> (f64, f64)", "1D/2D Extended Kalman Filter measurement step"),
376 ("fuse_imu_gps(lat: f64, lon: f64, yaw: f64) -> (f64, f64)", "Fuse IMU orientation with GPS coordinates"),
377 ],
378 },
379 StdlibModule {
380 name: "std::lidar",
381 description: "2D/3D LIDAR Scan Point Cloud Parsing, SLAM Scan Matching & Grid Map",
382 functions: &[
383 ("scan_360(port: u32) -> [f64; 360]", "Acquire 360-degree distance scan from LIDAR sensor"),
384 ("find_closest_obstacle(scan: [f64; 360]) -> (f64, u32)", "Return minimum distance and angle from scan"),
385 ],
386 },
387 StdlibModule {
388 name: "std::kinematics",
389 description: "Inverse & Forward Kinematics Solvers for Robotic Arms & Wheels",
390 functions: &[
391 ("ik_3dof_arm(x: f64, y: f64, z: f64) -> (f64, f64, f64)", "Compute joint angles for 3-DOF robotic manipulator"),
392 ("diff_drive_ik(linear_v: f64, angular_w: f64) -> (f64, f64)", "Compute left and right wheel velocities"),
393 ],
394 },
395 StdlibModule {
396 name: "std::path",
397 description: "Pure Pursuit Geometric Path Tracking & Curvature Guidance",
398 functions: &[
399 ("pure_pursuit(rx: f64, ry: f64, rtheta: f64, wx: f64, wy: f64, v: f64) -> (f64, f64)", "Compute curvature kappa and angular velocity omega"),
400 ],
401 },
402 StdlibModule {
403 name: "std::control",
404 description: "Advanced Controllers: LQR, Model Predictive Control (MPC), Feedforward PID",
405 functions: &[
406 ("lqr_compute(err_x: f64, err_v: f64, k1: f64, k2: f64) -> f64", "Compute optimal Linear Quadratic Regulator feedback"),
407 ("feedforward_pid(sp: f64, act: f64, kp: f64, ki: f64, kd: f64, kf: f64, prev_sp: f64, dt: f64) -> f64", "Compute Feed-Forward + PID output"),
408 ("mpc_step(state: [f64; 4], horizon: u32) -> f64", "Compute 1-step Model Predictive Control action"),
409 ],
410 },
411 StdlibModule {
412 name: "std::battery",
413 description: "Battery Management System (BMS), Voltage, SoC & Thermal Monitoring",
414 functions: &[
415 ("read_soc() -> f64", "Read State of Charge percentage (0.0 to 100.0%)"),
416 ("read_voltage() -> f64", "Read main pack voltage in Volts"),
417 ("is_charging() -> bool", "Check if external charger is attached"),
418 ],
419 },
420 StdlibModule {
421 name: "std::gps",
422 description: "GPS NMEA Sentence Parsing, Geodetic Coordinates & Waypoint Guidance",
423 functions: &[
424 ("read_lat_lon() -> (f64, f64)", "Read current latitude and longitude degrees"),
425 ("distance_to_waypoint(lat1: f64, lon1: f64, lat2: f64, lon2: f64) -> f64", "Haversine distance in meters"),
426 ],
427 },
428 StdlibModule {
429 name: "std::servo",
430 description: "RC PWM Servo Angular Positioning & Trajectory Profiling",
431 functions: &[
432 ("write_angle(pin: u32, degrees: f64) -> void", "Position servo motor from 0.0 to 180.0 degrees"),
433 ("set_pulse_range(pin: u32, min_us: u32, max_us: u32) -> void", "Configure servo pulse width limits"),
434 ],
435 },
436 StdlibModule {
437 name: "std::stepper",
438 description: "Stepper Motor Microstepping, Acceleration Ramps & Trapezoidal Profiler",
439 functions: &[
440 ("step_angle(channel: u32, steps: i32, rpm: f64) -> void", "Move stepper motor specified steps at RPM"),
441 ("set_microsteps(channel: u32, div: u32) -> void", "Configure microstep divisor (1, 2, 4, 8, 16, 32)"),
442 ],
443 },
444 StdlibModule {
445 name: "std::watchdog",
446 description: "Hardware Watchdog Timer Control, Safety Dead-Man's Switch & Panic Recovery",
447 functions: &[
448 ("start(timeout_ms: u32) -> void", "Start hardware watchdog timer"),
449 ("reset() -> void", "Kick/refresh watchdog timer before expiration"),
450 ("trigger_panic() -> void", "Trigger immediate MCU hardware safety reset"),
451 ],
452 },
453 StdlibModule {
454 name: "std::canopen",
455 description: "Industrial CANopen DS301/DS402 Protocol for Smart Motors & Servos",
456 functions: &[
457 ("send_pdo(node_id: u8, pdo_num: u8, data: [u8; 8]) -> void", "Transmit Process Data Object (PDO)"),
458 ("write_sdo(node_id: u8, index: u16, sub: u8, val: u32) -> bool", "Write Service Data Object (SDO)"),
459 ("nmt_command(node_id: u8, cmd: u8) -> void", "Send Network Management (NMT) state transition"),
460 ],
461 },
462 StdlibModule {
463 name: "std::ethercat",
464 description: "High-Speed Real-Time EtherCAT Fieldbus for Industrial Arm Manipulators",
465 functions: &[
466 ("init_master(iface: string) -> bool", "Initialize real-time EtherCAT master network"),
467 ("sync_dc(cycle_time_ns: u32) -> void", "Synchronize Distributed Clocks (DC) across slaves"),
468 ("write_pdo(slave_idx: u16, offset: u16, val: u32) -> void", "Write cyclic PDO process data"),
469 ],
470 },
471 StdlibModule {
472 name: "std::modbus",
473 description: "Industrial Modbus RTU/TCP RS485 Protocol for PLC & Sensor Interfacing",
474 functions: &[
475 ("read_holding_registers(slave: u8, addr: u16, count: u16) -> [u16; 16]", "Read Modbus holding registers"),
476 ("write_coil(slave: u8, addr: u16, state: bool) -> bool", "Write single Modbus coil output"),
477 ],
478 },
479 StdlibModule {
480 name: "std::slam",
481 description: "Autonomous SLAM 2D/3D Localization, Mapping & Occupancy Grid Engine",
482 functions: &[
483 ("update_grid(scan: [f64; 360], pose_x: f64, pose_y: f64) -> void", "Update Bayesian occupancy grid map"),
484 ("get_robot_pose() -> (f64, f64, f64)", "Get estimated map frame pose (X, Y, Theta)"),
485 ("raycast_obstacle(angle: f64) -> f64", "Raycast expected obstacle distance in grid"),
486 ],
487 },
488 StdlibModule {
489 name: "std::pid_autotune",
490 description: "Relay Autotuning Algorithm for Automated PID Gain Calibration",
491 functions: &[
492 ("tune_step(actual: f64, setpoint: f64) -> f64", "Execute relay autotune excitation step"),
493 ("get_gains_ziegler_nichols() -> (f64, f64, f64)", "Compute Ziegler-Nichols Kp, Ki, Kd gains"),
494 ],
495 },
496];
497
498pub fn get_stdlib_module(name: &str) -> Option<&'static StdlibModule> {
499 STDLIB_MODULES.iter().find(|m| m.name == name)
500}
501
502#[cfg(test)]
503mod tests {
504 use super::*;
505
506 #[test]
507 fn test_pid_computation() {
508 let out = pid_compute(100.0, 50.0, 1.5, 0.1, 0.05);
509 assert!(out > 0.0 && out <= 100.0);
510 }
511
512 #[test]
513 fn test_ekf_update() {
514 let (x, p) = ekf_update(10.0, 12.0, 0.5, 0.1);
515 assert!((x - 11.666).abs() < 0.1);
516 assert!(p < 0.5);
517 }
518
519 #[test]
520 fn test_ik_3dof_arm() {
521 let (base, shoulder, elbow) = ik_3dof_arm(150.0, 100.0, 200.0);
522 assert!(base >= 0.0 && base <= 180.0);
523 assert!(shoulder >= 0.0 && shoulder <= 180.0);
524 assert!(elbow >= 0.0 && elbow <= 180.0);
525 }
526
527 #[test]
528 fn test_diff_drive_ik() {
529 let (left, right) = diff_drive_ik(1.0, 0.5);
530 assert!(left > 0 && right > 0);
531 assert!(right > left); }
533
534 #[test]
535 fn test_haversine_distance() {
536 let d = haversine_distance(30.0, 31.0, 30.01, 31.01);
537 assert!(d > 1000.0 && d < 2000.0); }
539
540 #[test]
541 fn test_stateful_pid_damping() {
542 let out1 = pid_compute(100.0, 0.0, 1.0, 0.1, 0.05);
543 let out2 = pid_compute(100.0, 50.0, 1.0, 0.1, 0.05);
544 assert!(out2 < out1);
545 }
546
547 #[test]
548 fn test_pure_pursuit() {
549 let (kappa, w) = pure_pursuit(0.0, 0.0, 0.0, 10.0, 5.0, 1.5);
550 assert!(kappa > 0.0 && w > 0.0);
551 }
552
553 #[test]
554 fn test_feedforward_pid() {
555 let out = feedforward_pid(100.0, 90.0, 1.0, 0.01, 0.05, 0.5, 90.0, 0.005);
556 assert!(out > 0.0);
557 }
558}