Skip to content

Smanor/mpc fix#78

Merged
ShayManor merged 71 commits into
mainfrom
smanor/mpc-fix
May 21, 2026
Merged

Smanor/mpc fix#78
ShayManor merged 71 commits into
mainfrom
smanor/mpc-fix

Conversation

@ShayManor

Copy link
Copy Markdown
Contributor

This pull request introduces several improvements and new features across the autonomous kart software stack, with a focus on enhanced localization accuracy, better simulation fidelity, and improved telemetry for offline analysis. The most significant changes are the addition of per-GPS-event EKF state snapshots for offline analysis, improved IMU calibration and handling, support for steering slop in the simulation model, and expanded data handling in the pathfinder and master nodes.

Localization and Telemetry Enhancements:

  • Added publishing of EKF state snapshots before and after each GPS update in localization_node.py, enabling detailed offline analysis of dead-reckoning drift and GPS corrections. This includes a new publisher for /localization/gps_event and logic to capture and broadcast EKF prior/posterior state vectors. [1] [2]
  • The pathfinder node now subscribes to GPS pose, wheel speed, and per-GPS-event EKF snapshots, caching these for passthrough to the MPC and telemetry. This ensures that all relevant localization data is available for downstream modules and logging. [1] [2]
  • The master node's MPC status callback is updated to handle and store the expanded telemetry, including GPS pose and EKF snapshot data, increasing the minimum expected data length and parsing new fields. [1] [2]

IMU Calibration and Handling:

  • Improved IMU calibration by computing and applying a level-correction rotation matrix (R) to account for chassis tilt, storing and loading this matrix in the calibration cache for more accurate orientation estimation. [1] [2] [3]
  • The IMU callback in the localization node now negates the gyro_z reading to ensure correct yaw direction in the chassis reference frame.

Simulation Model Improvements:

  • The bicycle simulation model now supports a configurable steering slop (deadzone/backlash), more accurately reflecting real actuator behavior. This is exposed as a parameter and implemented in the simulation step logic. [1] [2] [3] [4]

Miscellaneous and Infrastructure:

  • The docker-compose configuration now enables device passthrough for several hardware interfaces, making it easier to run the stack with real hardware in Docker.
  • Minor changes include incrementing a command counter in the CAN message handler and doubling the steering input in the drive command conversion for improved control. [1] [2]

Summary of Most Important Changes:

Localization and Telemetry:

  • EKF state snapshots (prior/posterior) are published on every GPS event for offline analysis, and new subscriptions in pathfinder and master nodes ensure all relevant telemetry is captured and logged. [1] [2] [3] [4] [5] [6]

IMU Calibration:

  • IMU calibration now computes and applies a chassis-level correction matrix, storing it in the calibration cache for improved orientation accuracy. [1] [2] [3]

Simulation Model:

  • The simulation bicycle model supports configurable steering slop/backlash, improving simulation realism and allowing parameterization via ROS parameters. [1] [2] [3] [4]

Data Handling and Passthrough:

  • Pathfinder node caches and passes through raw GPS, wheel speed, and EKF snapshot data for telemetry and MPC, ensuring comprehensive data is available for analysis. [1] [2]

Infrastructure:

  • Docker-compose now enables device passthrough for key hardware interfaces, facilitating hardware-in-the-loop testing.

ShayManor added 30 commits May 17, 2026 14:03

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 62 out of 68 changed files in this pull request and generated 8 comments.

Comment on lines +575 to +582
# Corridor enforcement is a soft quadratic penalty (c_bnd above).
# Pick the action by averaging the top `mppi_elite_frac × K` samples'
# control sequences (CEM-style elite mean). The reported "best"
# cost is still the elite minimum so feasibility / failsafe still
# gate on the truly-best trajectory.
n_elite = max(1, int(round(self.mppi_elite_frac * self.K)))
elite_idx = np.argpartition(cost, n_elite - 1)[:n_elite]
best = int(elite_idx[np.argmin(cost[elite_idx])])
Comment on lines 676 to +716
@@ -623,7 +695,49 @@ def _publish_status(self, mode, success, t0, s, d, psi_track, v, v_target,
float({"off": 0, "shadow": 1, "apply": 2}.get(self.residual.mode, 1)),
float(np.linalg.norm(self.residual.theta_s)),
float(np.linalg.norm(self.residual.theta_d)),
# Raw GPS pose (EKF-vs-GPS diff). NaN until first /gps fix.
float(self._gps_x), float(self._gps_y),
float(self._gps_yaw), float(self._gps_v),
# Wheel speed + last-GPS-event EKF prior/posterior. seq lets the
# analyst dedupe MPC ticks (60 Hz) against GPS events (10 Hz).
float(self._wheel_v), float(self._gps_event_seq),
float(self._gps_have_yaw), float(self._gps_have_speed),
float(self._ekf_prior_x), float(self._ekf_prior_y),
float(self._ekf_prior_yaw), float(self._ekf_prior_v),
float(self._ekf_post_x), float(self._ekf_post_y),
float(self._ekf_post_yaw), float(self._ekf_post_v),
# ---- Phase 1 residual telemetry (Phase 2 fields stay 0/NaN here) ----
float(self.residual.buffer.size if self.residual.buffer is not None else 0),
float(self.residual.buffer.capacity if self.residual.buffer is not None else 0),
float(last.train_wall_ms if last is not None else float("nan")),
float(last.n_samples if last is not None else 0),
float(trainer.train_seq if trainer else 0),
float(last.train_mae_s if last is not None else float("nan")),
float(last.train_mae_d if last is not None else float("nan")),
Comment on lines +11 to +21
class TrainBuffer:
def __init__(self, capacity: int, feature_dim: int):
self.capacity = int(capacity)
self.feature_dim = int(feature_dim)
self._X = np.zeros((self.capacity, self.feature_dim), dtype=np.float64)
self._ys = np.zeros(self.capacity, dtype=np.float64)
self._yd = np.zeros(self.capacity, dtype=np.float64)
self._t = np.zeros(self.capacity, dtype=np.int64)
self._write_idx = 0 # next index to write
self._count = 0 # min(filled, capacity)
self._lock = threading.Lock()
Comment thread docs/README/quickstart.md
Comment on lines +38 to +41
```bash
ros2 bag record -s mcap -o /ws/bags/$(date +%Y%m%d_%H%M%S) /mpc/status /mpc/residual_mode /mpc/residual_revert /cmd_drive /odom /gps /imu /imu/calibration_status /e_comms/kart_speed_m_per_s /e_comms/throttle_pwm /e_comms/steering_pwm /e_comms/adcb_state /e_comms/rc_mode /system_state /track_angles /manual_commands /pathfinder_params
```
docker cp ros2-dev:/ws/bags .
Comment thread sim/tests/test_smoke.py
Comment on lines +1 to +7
def test_package_importable():
import sim # noqa: F401


def test_torch_available():
import torch
assert torch.__version__
Comment on lines +1 to +8
import numpy as np
import torch

from sim.residual_mlp import (
FEATURE_DIM,
ResidualMLP,
build_features,
)

def test_mlp_reduces_to_bicycle_off_distribution(tmp_path, temp_artifacts):
import json
import torch
Comment on lines 4 to +6
min_speed: 0.0 # m/s
max_steering: 100.0 # %
min_steering: -100.0 # %
max_steering: 60.0 # %
min_steering: -60.0 # %
@ShayManor ShayManor merged commit 4fe2f33 into main May 21, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants