11from gpiozero import DistanceSensor
22import time
33
4-
54class ObstacleAvoidance :
65 def __init__ (self , trigger_pin = 27 , echo_pin = 22 , motors = None , threshold = 0.5 ):
76 # Faster response (no smoothing delay)
@@ -11,80 +10,136 @@ def __init__(self, trigger_pin=27, echo_pin=22, motors=None, threshold=0.5):
1110 max_distance = 2.0 ,
1211 queue_len = 1
1312 )
14-
1513 self .motors = motors
1614 self .threshold = threshold
1715 self .turn_left_next = True
18-
16+
1917 # More realistic speeds for a Pi 3 robot
2018 self .min_speed = 0.3
2119 self .max_speed = 0.6
22-
20+
21+ # Stuck detection
22+ self .obstacle_count = 0
23+ self .last_obstacle_time = 0
24+ self .stuck_threshold = 3 # Number of obstacles in quick succession
25+ self .stuck_time_window = 2.0 # Within 2 seconds
26+ self .committed_direction = None # Will be 'left' or 'right'
27+ self .commitment_timeout = 5.0 # Stay committed for 5 seconds
28+ self .commitment_start_time = 0
29+
2330 def get_distance (self ):
2431 try :
2532 return self .sensor .distance
2633 except Exception :
2734 return None
28-
35+
2936 def _map_speed (self , distance ):
3037 if distance >= self .threshold :
3138 return self .max_speed
32-
3339 if distance <= 0 :
3440 return self .min_speed
35-
3641 ratio = distance / self .threshold
3742 speed = self .min_speed + (self .max_speed - self .min_speed ) * ratio
3843 return max (self .min_speed , min (self .max_speed , speed ))
39-
44+
45+ def _detect_stuck (self ):
46+ """Check if robot is repeatedly hitting obstacles (stuck pattern)"""
47+ current_time = time .time ()
48+
49+ # Check if we're still in the time window
50+ if current_time - self .last_obstacle_time < self .stuck_time_window :
51+ self .obstacle_count += 1
52+ else :
53+ # Reset counter if too much time has passed
54+ self .obstacle_count = 1
55+
56+ self .last_obstacle_time = current_time
57+
58+ # If we hit stuck threshold, commit to a direction
59+ if self .obstacle_count >= self .stuck_threshold :
60+ if self .committed_direction is None :
61+ # Choose direction based on current preference
62+ self .committed_direction = 'left' if self .turn_left_next else 'right'
63+ self .commitment_start_time = current_time
64+ print (f"🔄 STUCK DETECTED! Committing to turn { self .committed_direction .upper ()} " )
65+ return True
66+ return False
67+
68+ def _should_stay_committed (self ):
69+ """Check if we should still follow committed direction"""
70+ if self .committed_direction is None :
71+ return False
72+
73+ # Release commitment after timeout
74+ if time .time () - self .commitment_start_time > self .commitment_timeout :
75+ print (f"✅ Commitment timeout - resuming normal operation" )
76+ self .committed_direction = None
77+ self .obstacle_count = 0
78+ return False
79+
80+ return True
81+
4082 def check_and_avoid (self ):
4183 if self .motors is None :
4284 return False
43-
85+
4486 distance = self .get_distance ()
4587 if distance is None :
4688 return False
47-
48- # Debug (optional)
49- # print(f"Distance: {distance:.2f} m")
50-
89+
5190 # 🚨 OBSTACLE DETECTED
5291 if distance < self .threshold :
53-
92+ # Check if we're stuck
93+ is_stuck = self ._detect_stuck ()
94+
5495 self .motors .stop ()
5596 time .sleep (0.05 )
56-
57- # Reverse briefly (shorter, faster reaction)
97+
98+ # Reverse briefly
5899 self .motors .backward (self .min_speed )
59100 start_time = time .time ()
60101 while time .time () - start_time < 0.3 :
61102 pass
62-
63103 self .motors .stop ()
64104 time .sleep (0.05 )
65-
66- # Turn (short controlled turn)
67- if self .turn_left_next :
105+
106+ # Decide turn direction
107+ if self ._should_stay_committed ():
108+ # Stuck mode: always turn the same way
109+ turn_direction = self .committed_direction
110+ # Longer turn when stuck
111+ turn_duration = 0.7
112+ else :
113+ # Normal mode: alternate
114+ turn_direction = 'left' if self .turn_left_next else 'right'
115+ turn_duration = 0.55
116+ self .turn_left_next = not self .turn_left_next
117+
118+ # Execute turn
119+ if turn_direction == 'left' :
68120 self .motors .turn_left (self .min_speed )
69121 else :
70122 self .motors .turn_right (self .min_speed )
71-
72- self .turn_left_next = not self .turn_left_next
73-
123+
74124 start_time = time .time ()
75- while time .time () - start_time < 0.55 :
125+ while time .time () - start_time < turn_duration :
76126 pass
77-
127+
78128 self .motors .stop ()
79129 return True
80-
130+
81131 # ✅ PATH CLEAR
82132 else :
133+ # Reset stuck detection if we have clear path
134+ if distance > self .threshold * 1.5 : # Good clearance
135+ if self .obstacle_count > 0 :
136+ self .obstacle_count = max (0 , self .obstacle_count - 1 )
137+
83138 speed = self ._map_speed (distance )
84139 self .motors .forward (speed )
85140 return False
86-
141+
87142 def cleanup (self ):
88143 if self .motors :
89144 self .motors .stop ()
90- self .sensor .close ()
145+ self .sensor .close ()
0 commit comments