simple-autonomous-car 0.1.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- simple_autonomous_car/__init__.py +96 -0
- simple_autonomous_car/alerts/__init__.py +5 -0
- simple_autonomous_car/alerts/track_bounds_alert.py +276 -0
- simple_autonomous_car/car/__init__.py +5 -0
- simple_autonomous_car/car/car.py +234 -0
- simple_autonomous_car/constants.py +112 -0
- simple_autonomous_car/control/__init__.py +7 -0
- simple_autonomous_car/control/base_controller.py +152 -0
- simple_autonomous_car/control/controller_viz.py +282 -0
- simple_autonomous_car/control/pid_controller.py +153 -0
- simple_autonomous_car/control/pure_pursuit_controller.py +578 -0
- simple_autonomous_car/costmap/__init__.py +12 -0
- simple_autonomous_car/costmap/base_costmap.py +187 -0
- simple_autonomous_car/costmap/grid_costmap.py +507 -0
- simple_autonomous_car/costmap/inflation.py +126 -0
- simple_autonomous_car/detection/__init__.py +5 -0
- simple_autonomous_car/detection/error_detector.py +165 -0
- simple_autonomous_car/filters/__init__.py +7 -0
- simple_autonomous_car/filters/base_filter.py +119 -0
- simple_autonomous_car/filters/kalman_filter.py +131 -0
- simple_autonomous_car/filters/particle_filter.py +162 -0
- simple_autonomous_car/footprint/__init__.py +7 -0
- simple_autonomous_car/footprint/base_footprint.py +128 -0
- simple_autonomous_car/footprint/circular_footprint.py +73 -0
- simple_autonomous_car/footprint/rectangular_footprint.py +123 -0
- simple_autonomous_car/frames/__init__.py +21 -0
- simple_autonomous_car/frames/frenet.py +267 -0
- simple_autonomous_car/maps/__init__.py +9 -0
- simple_autonomous_car/maps/frenet_map.py +97 -0
- simple_autonomous_car/maps/grid_ground_truth_map.py +83 -0
- simple_autonomous_car/maps/grid_map.py +361 -0
- simple_autonomous_car/maps/ground_truth_map.py +64 -0
- simple_autonomous_car/maps/perceived_map.py +169 -0
- simple_autonomous_car/perception/__init__.py +5 -0
- simple_autonomous_car/perception/perception.py +107 -0
- simple_autonomous_car/planning/__init__.py +7 -0
- simple_autonomous_car/planning/base_planner.py +184 -0
- simple_autonomous_car/planning/goal_planner.py +261 -0
- simple_autonomous_car/planning/track_planner.py +199 -0
- simple_autonomous_car/sensors/__init__.py +6 -0
- simple_autonomous_car/sensors/base_sensor.py +105 -0
- simple_autonomous_car/sensors/lidar_sensor.py +145 -0
- simple_autonomous_car/track/__init__.py +5 -0
- simple_autonomous_car/track/track.py +463 -0
- simple_autonomous_car/visualization/__init__.py +25 -0
- simple_autonomous_car/visualization/alert_viz.py +316 -0
- simple_autonomous_car/visualization/utils.py +169 -0
- simple_autonomous_car-0.1.2.dist-info/METADATA +324 -0
- simple_autonomous_car-0.1.2.dist-info/RECORD +50 -0
- simple_autonomous_car-0.1.2.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"""PID controller for path following."""
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Optional
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
from simple_autonomous_car.car.car import CarState
|
|
8
|
+
from simple_autonomous_car.constants import DEFAULT_GOAL_TOLERANCE
|
|
9
|
+
from simple_autonomous_car.control.base_controller import BaseController
|
|
10
|
+
from simple_autonomous_car.perception.perception import PerceptionPoints
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from simple_autonomous_car.costmap.base_costmap import BaseCostmap
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class PIDController(BaseController):
|
|
17
|
+
"""
|
|
18
|
+
PID (Proportional-Integral-Derivative) controller.
|
|
19
|
+
|
|
20
|
+
A simple PID controller for following a path or maintaining a target state.
|
|
21
|
+
|
|
22
|
+
Parameters
|
|
23
|
+
----------
|
|
24
|
+
kp : float, default=1.0
|
|
25
|
+
Proportional gain.
|
|
26
|
+
ki : float, default=0.0
|
|
27
|
+
Integral gain.
|
|
28
|
+
kd : float, default=0.1
|
|
29
|
+
Derivative gain.
|
|
30
|
+
target_velocity : float, default=10.0
|
|
31
|
+
Target velocity in m/s.
|
|
32
|
+
name : str, default="pid"
|
|
33
|
+
Controller name.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(
|
|
37
|
+
self,
|
|
38
|
+
kp: float = 1.0,
|
|
39
|
+
ki: float = 0.0,
|
|
40
|
+
kd: float = 0.1,
|
|
41
|
+
target_velocity: float = 10.0,
|
|
42
|
+
name: str = "pid",
|
|
43
|
+
):
|
|
44
|
+
super().__init__(name=name)
|
|
45
|
+
self.kp = kp
|
|
46
|
+
self.ki = ki
|
|
47
|
+
self.kd = kd
|
|
48
|
+
self.target_velocity = target_velocity
|
|
49
|
+
self.integral_error = 0.0
|
|
50
|
+
self.last_error = 0.0
|
|
51
|
+
|
|
52
|
+
def compute_control(
|
|
53
|
+
self,
|
|
54
|
+
car_state: CarState,
|
|
55
|
+
perception_data: dict[str, PerceptionPoints] | None = None,
|
|
56
|
+
costmap: Optional["BaseCostmap"] = None,
|
|
57
|
+
plan: np.ndarray | None = None,
|
|
58
|
+
goal: np.ndarray | None = None,
|
|
59
|
+
goal_tolerance: float | None = None,
|
|
60
|
+
dt: float = 0.1,
|
|
61
|
+
) -> dict[str, float]:
|
|
62
|
+
"""
|
|
63
|
+
Compute control using PID algorithm.
|
|
64
|
+
|
|
65
|
+
Parameters
|
|
66
|
+
----------
|
|
67
|
+
car_state : CarState
|
|
68
|
+
Current car state.
|
|
69
|
+
perception_data : dict, optional
|
|
70
|
+
Not used by basic PID.
|
|
71
|
+
plan : np.ndarray, optional
|
|
72
|
+
Planned path. If provided, computes error to nearest point.
|
|
73
|
+
dt : float, default=0.1
|
|
74
|
+
Time step in seconds.
|
|
75
|
+
|
|
76
|
+
Returns
|
|
77
|
+
-------
|
|
78
|
+
Dict[str, float]
|
|
79
|
+
Control commands with "acceleration" and "steering_rate".
|
|
80
|
+
"""
|
|
81
|
+
if not self.enabled:
|
|
82
|
+
return {"acceleration": 0.0, "steering_rate": 0.0}
|
|
83
|
+
|
|
84
|
+
# Simple heading control if plan provided
|
|
85
|
+
steering_rate = 0.0
|
|
86
|
+
if plan is not None and len(plan) > 0:
|
|
87
|
+
car_pos = car_state.position()
|
|
88
|
+
distances = np.linalg.norm(plan - car_pos, axis=1)
|
|
89
|
+
closest_idx = np.argmin(distances)
|
|
90
|
+
target_point = plan[closest_idx]
|
|
91
|
+
|
|
92
|
+
target_vector = target_point - car_pos
|
|
93
|
+
target_angle = np.arctan2(target_vector[1], target_vector[0])
|
|
94
|
+
angle_error = target_angle - car_state.heading
|
|
95
|
+
angle_error = np.arctan2(np.sin(angle_error), np.cos(angle_error))
|
|
96
|
+
|
|
97
|
+
# PID control
|
|
98
|
+
self.integral_error += angle_error * dt
|
|
99
|
+
derivative_error = (angle_error - self.last_error) / dt
|
|
100
|
+
self.last_error = angle_error
|
|
101
|
+
|
|
102
|
+
steering_rate = (
|
|
103
|
+
self.kp * angle_error + self.ki * self.integral_error + self.kd * derivative_error
|
|
104
|
+
)
|
|
105
|
+
steering_rate = np.clip(steering_rate, -1.0, 1.0)
|
|
106
|
+
|
|
107
|
+
# Velocity control (adapt based on goal distance)
|
|
108
|
+
target_velocity = self.target_velocity
|
|
109
|
+
|
|
110
|
+
# Reduce velocity if approaching goal (smooth deceleration)
|
|
111
|
+
if goal is not None:
|
|
112
|
+
distance_to_goal = np.linalg.norm(car_state.position() - goal)
|
|
113
|
+
tolerance = goal_tolerance if goal_tolerance is not None else DEFAULT_GOAL_TOLERANCE
|
|
114
|
+
|
|
115
|
+
# If within tolerance, stop completely
|
|
116
|
+
if distance_to_goal <= tolerance:
|
|
117
|
+
target_velocity = 0.0
|
|
118
|
+
else:
|
|
119
|
+
# Start slowing down when within 10 meters of goal
|
|
120
|
+
# Use a smaller slow_down_distance to ensure smooth deceleration even when close
|
|
121
|
+
slow_down_distance = max(10.0, tolerance * 5.0) # At least 5x tolerance, or 10m
|
|
122
|
+
if distance_to_goal < slow_down_distance:
|
|
123
|
+
# Smooth velocity reduction: linear from full speed to zero
|
|
124
|
+
# At distance=tolerance, velocity=0; at distance=slow_down_distance, velocity=target_velocity
|
|
125
|
+
# Map distance from [tolerance, slow_down_distance] to velocity [0, target_velocity]
|
|
126
|
+
if slow_down_distance > tolerance:
|
|
127
|
+
velocity_factor = (distance_to_goal - tolerance) / (
|
|
128
|
+
slow_down_distance - tolerance
|
|
129
|
+
)
|
|
130
|
+
velocity_factor = max( # type: ignore[arg-type]
|
|
131
|
+
0.0, min(1.0, velocity_factor) # type: ignore[arg-type]
|
|
132
|
+
) # Clamp to [0, 1] # type: ignore[assignment]
|
|
133
|
+
target_velocity = self.target_velocity * velocity_factor # type: ignore[assignment]
|
|
134
|
+
else:
|
|
135
|
+
target_velocity = 0.0
|
|
136
|
+
# Ensure we always slow down when very close (even if above tolerance)
|
|
137
|
+
if distance_to_goal < tolerance * 2.0:
|
|
138
|
+
# Extra safety: cap velocity when very close
|
|
139
|
+
max_velocity_near_goal = float(
|
|
140
|
+
self.target_velocity * (distance_to_goal / (tolerance * 2.0))
|
|
141
|
+
)
|
|
142
|
+
target_velocity = float(min(target_velocity, max_velocity_near_goal))
|
|
143
|
+
|
|
144
|
+
velocity_error = target_velocity - car_state.velocity
|
|
145
|
+
acceleration = 0.5 * velocity_error
|
|
146
|
+
acceleration = np.clip(acceleration, -2.0, 2.0)
|
|
147
|
+
|
|
148
|
+
return {"acceleration": acceleration, "steering_rate": steering_rate}
|
|
149
|
+
|
|
150
|
+
def reset(self) -> None:
|
|
151
|
+
"""Reset PID controller state."""
|
|
152
|
+
self.integral_error = 0.0
|
|
153
|
+
self.last_error = 0.0
|