@@ -47,7 +47,7 @@ class MotionDetector:
4747 # Minimum time span (seconds) for the 3 park-confirming readings.
4848 # BMW sends GPS in bursts (3 readings within <1s at the same position).
4949 # Without this guard the burst immediately parks the car while driving.
50- MIN_PARK_SPAN_SECONDS : ClassVar [float ] = 30 .0
50+ MIN_PARK_SPAN_SECONDS : ClassVar [float ] = 90 .0
5151
5252 # Minutes without GPS update to consider GPS unavailable (switch to mileage fallback)
5353 # Longer than MOTION_ACTIVE_WINDOW to handle BMW's bursty GPS (every 2-3 min)
@@ -208,24 +208,31 @@ def update_location(self, vin: str, lat: float, lon: float) -> bool:
208208 for r in park_readings [- 3 :]
209209 )
210210 if all_within_park :
211- # Require readings to span a minimum time window to avoid
212- # treating a single GPS burst as parking
213- first_park_time = park_readings [- 3 ][2 ]
214- time_span = (now - first_park_time ).total_seconds ()
215- if time_span < self .MIN_PARK_SPAN_SECONDS :
211+ # Require readings from at least 2 distinct MQTT bursts
212+ # separated by MIN_PARK_SPAN_SECONDS. BMW sends GPS in
213+ # tight bursts (3-6 readings in <1s), so park_readings[-3]
214+ # is always from the current burst. Instead, find the
215+ # latest reading that preceded the current burst by at
216+ # least MIN_PARK_SPAN_SECONDS — proving the car was already
217+ # in the park zone that long ago.
218+ has_old_reading = any (
219+ (now - r [2 ]).total_seconds () >= self .MIN_PARK_SPAN_SECONDS for r in park_readings
220+ )
221+ if not has_old_reading :
216222 # Burst detection: readings too close together, not real parking
217223 # Stay in driving mode
218224 return True
219225
220226 # Vehicle has stopped - exit driving mode
221227 # Backdate last movement to when the car first entered the park zone
228+ time_in_zone = (now - park_readings [0 ][2 ]).total_seconds ()
222229 _LOGGER .debug (
223- "Motion: %s stopped (3 readings within park radius over %.0fs) - NOW PARKED" ,
230+ "Motion: %s stopped (readings in park zone for %.0fs) - NOW PARKED" ,
224231 redact_vin (vin ),
225- time_span ,
232+ time_in_zone ,
226233 )
227234 self ._is_driving [vin ] = False
228- self ._last_location_change [vin ] = first_park_time
235+ self ._last_location_change [vin ] = park_readings [ 0 ][ 2 ]
229236 self ._park_anchor [vin ] = self ._calculate_centroid (park_readings )
230237 return False
231238 else :
0 commit comments