@@ -108,6 +108,12 @@ def __init__(self, master):
108108 # Retry statistics tracking
109109 self .retry_stats = {}
110110
111+ # Per-VIN flag: True once the car has confirmed it is already in the
112+ # desired charge state (complete, is_charging, disconnected, etc.).
113+ # Prevents re-sending a start command on every poll cycle.
114+ # Reset to False when a stop-charge task arrives.
115+ self ._stopAskingToStartCharging = {}
116+
111117 # Persistent D-Bus session daemon shared across all tesla-control calls.
112118 # dbus-launch only spawns a new daemon when DBUS_SESSION_BUS_ADDRESS is
113119 # absent from the environment. By starting one daemon here and passing its
@@ -193,13 +199,24 @@ def car_api_charge(self, task):
193199 logger .error ("Task missing required 'charge' key" )
194200 return False
195201
202+ # When stopping, reset per-VIN "stop asking" flags so the next
203+ # start cycle starts fresh (mirrors TeslaAPI.car_api_charge).
204+ if not charge :
205+ self ._stopAskingToStartCharging .clear ()
206+
196207 # If we know the VIN of the vehicle connected to the TWC Slave, we'll send the command
197208 # directly to that vehicle
198209 vin = task .get ("vin" , None )
199210 if vin :
200211 logger .debug (f"BLE command for specific VIN: { vin } , charge: { charge } " )
201212
202213 if charge :
214+ if self ._stopAskingToStartCharging .get (vin ):
215+ logger .debug (
216+ "BLE: not re-requesting charge start for %s: already in desired state"
217+ % vin
218+ )
219+ return True
203220 if self ._scheduleBlocksStart (vin ):
204221 logger .info (
205222 f"{ vin } is waiting on its in-car charging schedule; not sending BLE start"
@@ -231,6 +248,13 @@ def car_api_charge(self, task):
231248 for vehicle in self .master .settings ["Vehicles" ].keys ():
232249 try :
233250 if charge :
251+ if self ._stopAskingToStartCharging .get (vehicle ):
252+ logger .debug (
253+ "BLE: not re-requesting charge start for %s: already in desired state"
254+ % vehicle
255+ )
256+ success_count += 1
257+ continue
234258 if self ._scheduleBlocksStart (vehicle ):
235259 logger .info (
236260 f"{ vehicle } is waiting on its in-car charging schedule; not sending BLE start"
@@ -267,6 +291,18 @@ def car_api_charge(self, task):
267291 logger .error (f"BLE car_api_charge failed with exception: { e } " )
268292 return False
269293
294+ def _is_already_satisfied (self , output ):
295+ """Return True if output indicates the car is already in the desired charge state."""
296+ if not output :
297+ return False
298+ if isinstance (output , bytes ):
299+ output = output .decode ("utf-8" , errors = "ignore" )
300+ output_lower = output .lower ()
301+ return any (
302+ ("car could not execute command: " + reason ) in output_lower
303+ for reason in ("complete" , "is_charging" , "charging" , "requested" , "disconnected" )
304+ )
305+
270306 def parseCommandOutput (self , output ):
271307 """
272308 Enhanced command output parsing with detailed error categorization.
@@ -286,14 +322,21 @@ def parseCommandOutput(self, output):
286322 "Command executed successfully" ,
287323 "Vehicle responded" ,
288324 "Success" ,
289- # "Already in desired state" responses — car is charging or done; goal achieved
290- "car could not execute command: complete" ,
291- "car could not execute command: is_charging" ,
292- "car could not execute command: charging" ,
293- "car could not execute command: requested" ,
294325 ]
295326
296- # Error indicators for detailed logging
327+ output_lower = output .lower ()
328+
329+ # Check for success
330+ for indicator in success_indicators :
331+ if indicator .lower () in output_lower :
332+ logger .debug (f"BLE command success: { indicator } " )
333+ return True
334+
335+ if self ._is_already_satisfied (output ):
336+ logger .debug ("BLE command success: already in desired state" )
337+ return True
338+
339+ # Categorize errors for detailed logging
297340 error_indicators = {
298341 "timeout" : ["timeout" , "timed out" , "no response" ],
299342 "connection" : ["connection failed" , "unable to connect" , "bluetooth error" ],
@@ -306,15 +349,6 @@ def parseCommandOutput(self, output):
306349 "command_failed" : ["command failed" , "error executing" , "operation failed" ],
307350 }
308351
309- output_lower = output .lower ()
310-
311- # Check for success
312- for indicator in success_indicators :
313- if indicator .lower () in output_lower :
314- logger .debug (f"BLE command success: { indicator } " )
315- return True
316-
317- # Categorize errors for better debugging
318352 error_type = "unknown"
319353 for category , indicators in error_indicators .items ():
320354 for indicator in indicators :
@@ -586,6 +620,15 @@ def startCharging(self, vin):
586620 return False
587621
588622 success = self .parseCommandOutput (ret )
623+
624+ # If the car reported it is already in the desired state, record
625+ # that so car_api_charge won't re-send the command next cycle
626+ # (mirrors TeslaAPI's stopAskingToStartCharging logic).
627+ if success and self ._is_already_satisfied (ret ):
628+ self ._stopAskingToStartCharging [vin ] = True
629+ logger .info (
630+ "BLE: %s already in desired charge state; will not re-request" % vin
631+ )
589632 logger .info (
590633 f"Start charging for { vin } : { 'success' if success else 'failed' } "
591634 )
0 commit comments