@@ -169,58 +169,38 @@ def format_duration(seconds):
169169
170170def german_timestamp ():
171171 return datetime .now ().strftime ("%d.%m.%y, %H:%M" )
172-
172+
173173def get_last_state ():
174- """Reads the last state and ensures `notified` is retained persistently."""
175174 try :
176175 with open (STATE_FILE , "r" ) as f :
177176 data = f .read ().strip ()
178177
179- if data .startswith ("charging:" ):
178+ if data .startswith ("charging:" ) or data . startswith ( "idle:" ) :
180179 parts = data .split (":" )
181- if len (parts ) >= 5 :
182- _ , start_time , stored_power , notified , total_energy_wh_for_summary = parts
180+ if len (parts ) >= 6 :
181+ _ , start_time , stored_power , notified , total_energy_wh_for_summary , repeat_check = parts
183182 return {
184- "state" : "charging" ,
185- "start_time" : float (start_time ),
183+ "state" : "charging" if data . startswith ( "charging:" ) else "idle" ,
184+ "start_time" : float (start_time ) if data . startswith ( "charging:" ) else None ,
186185 "stored_power" : float (stored_power ),
187186 "total_energy_wh_for_summary" : float (total_energy_wh_for_summary ),
188- "notified" : bool (int (notified )),
187+ "notified" : bool (int (notified )),
188+ "repeat_check" : bool (int (repeat_check )), # NEW FLAG
189189 }
190190
191- elif data .startswith ("idle :" ):
191+ elif data .startswith ("disconnected :" ):
192192 parts = data .split (":" )
193- if len (parts ) >= 3 :
194- _ , stored_power , total_energy_wh_for_summary , notified = parts
195- return {
196- "state" : "idle" ,
197- "start_time" : None ,
198- "stored_power" : float (stored_power ),
199- "total_energy_wh_for_summary" : float (total_energy_wh_for_summary ),
200- "notified" : bool (int (notified )),
201- }
202-
203- elif data .startswith ("disconnected:" ):
204- parts = data .split (":" )
205- if len (parts ) == 3 :
206- _ , total_energy_wh_for_summary , notified = parts
193+ if len (parts ) >= 4 :
194+ _ , total_energy_wh_for_summary , notified , repeat_check = parts
207195 return {
208196 "state" : "disconnected" ,
209197 "start_time" : None ,
210198 "stored_power" : 0.0 ,
211199 "total_energy_wh_for_summary" : float (total_energy_wh_for_summary ),
212- "notified" : bool (int (notified )),
200+ "notified" : bool (int (notified )),
201+ "repeat_check" : bool (int (repeat_check )), # NEW FLAG
213202 }
214203
215- elif data == "disconnected" :
216- return {
217- "state" : "disconnected" ,
218- "start_time" : None ,
219- "stored_power" : 0.0 ,
220- "total_energy_wh_for_summary" : None ,
221- "notified" : False ,
222- }
223-
224204 except (FileNotFoundError , ValueError , IndexError ):
225205 logger .error ("State file corrupted or missing. Resetting to default." )
226206 logger .error (f"State file content: { data } " ) # Debugging
@@ -229,24 +209,26 @@ def get_last_state():
229209 "state" : "idle" ,
230210 "start_time" : None ,
231211 "stored_power" : 0.0 ,
232- "total_energy_wh_for_summary" : None ,
212+ "total_energy_wh_for_summary" : 0.0 ,
233213 "notified" : False ,
214+ "repeat_check" : False ,
234215 }
235216
236- def save_last_state (state , charging_power = 0.0 , total_energy_wh = None , total_energy_wh_for_summary = None , notified = False , start_time = None ):
217+
218+ def save_last_state (state , charging_power = 0.0 , total_energy_wh = None , total_energy_wh_for_summary = None , notified = False , start_time = None , repeat_check = False ):
237219 with open (STATE_FILE , "w" ) as f :
238- if state == "charging" :
239- f .write (f"charging:{ start_time if start_time is not None else 0 } :{ charging_power :.2f} :{ int (notified )} :{ total_energy_wh_for_summary or 0.0 } " )
240- debug (f"charging:{ start_time if start_time is not None else 0 } :{ charging_power :.2f} :{ int (notified )} :{ total_energy_wh_for_summary or 0.0 } " )
241- elif state == "idle" and total_energy_wh is not None :
242- f .write (f"idle:{ total_energy_wh :.2f} :{ total_energy_wh_for_summary or 0.0 } :{ int (notified )} " )
243- debug (f".. save_last_state(): idle:{ total_energy_wh :.2f} :{ total_energy_wh_for_summary or 0.0 } :{ int (notified )} " )
220+ if state in ["charging" , "idle" ]:
221+ start_time_str = str (start_time ) if state == "charging" and start_time is not None else "0"
222+ f .write (f"{ state } :{ start_time_str } :{ charging_power :.2f} :{ int (notified )} :{ total_energy_wh_for_summary or 0.0 } :{ int (repeat_check )} " )
223+ debug (f".. save_last_state(): { state } :{ start_time_str } :{ charging_power :.2f} :{ int (notified )} :{ total_energy_wh_for_summary or 0.0 } :{ int (repeat_check )} " )
224+
244225 elif state == "disconnected" :
245- f .write (f"disconnected:{ total_energy_wh_for_summary if total_energy_wh_for_summary is not None else '0.0' } :{ int (notified )} " )
246- debug (f".. save_last_state(): disconnected:{ total_energy_wh_for_summary if total_energy_wh_for_summary is not None else '0.0' } :{ int (notified )} " )
226+ f .write (f"disconnected:{ total_energy_wh_for_summary if total_energy_wh_for_summary is not None else '0.0' } :{ int (notified )} :{ int (repeat_check )} " )
227+ debug (f".. save_last_state(): disconnected:{ total_energy_wh_for_summary if total_energy_wh_for_summary is not None else '0.0' } :{ int (notified )} :{ int (repeat_check )} " )
228+
247229 else :
248- f .write (f"idle:0.0:0.0:{ int (notified )} " )
249- debug (f".. save_last_state(): idle:0.0:0.0:{ int (notified )} " )
230+ f .write (f"idle:0.0:0.0:{ int (notified )} : { int ( repeat_check ) } " )
231+ debug (f".. save_last_state(): idle:0.0:0.0:{ int (notified )} : { int ( repeat_check ) } " )
250232
251233def send_energy_summary (total_energy_wh_for_summary ):
252234 """Sends a summary of total consumed energy when the cable is disconnected."""
@@ -339,6 +321,7 @@ def main():
339321 stored_power = state_data ["stored_power" ]
340322 total_energy_wh_for_summary = state_data ["total_energy_wh_for_summary" ]
341323 notified = state_data ["notified" ]
324+ repeat_check = state_data ["repeat_check" ] # NEW FLAG
342325
343326 if total_energy_wh is None :
344327 total_energy_wh = get_last_state ().get ("total_energy_wh" )
@@ -347,86 +330,79 @@ def main():
347330 current_time = time .time ()
348331
349332 print (f"🔄 Last State: { last_state } , New Fetch: { charging_rate } , Total Energy: { total_energy_wh } " )
350- debug (f".. get_last_state() #1 \n -- state file: \n Last State: { last_state } \n Start Time: { start_time } \n Stored Power: { stored_power } \n Total Energy for Summary: { total_energy_wh_for_summary } \n Notified: { notified } \n -- new fetch: \n Charging Rate: { charging_rate } \n Total Energy: { total_energy_wh } " )
333+ debug (f".. get_last_state() #1 \n -- state file: \n Last State: { last_state } \n Start Time: { start_time } \n Stored Power: { stored_power } \n Total Energy for Summary: { total_energy_wh_for_summary } \n Notified: { notified } \n Repeat Check: { repeat_check } \n -- new fetch: \n Charging Rate: { charging_rate } \n Total Energy: { total_energy_wh } " )
351334
352- # Handle cable disconnection DURING charging
335+ # 🚨 If disconnect detected DURING charging, set repeat_check and exit
353336 if last_state == "charging" and (total_energy_wh is None or charging_rate == 0 ):
354- send_notification (f"🔌 { timestamp } : charging interrupted - cable unplugged." )
337+ if not repeat_check : # First detection, re-run once
338+ send_notification (f"🔌 { timestamp } : charging interruption detected, verifying..." )
339+ save_last_state ("charging" , charging_power = charging_rate , total_energy_wh = total_energy_wh , total_energy_wh_for_summary = total_energy_wh_for_summary , notified = notified , start_time = start_time , repeat_check = True )
340+ return # Exit, script will retry next run
355341
356- # Use the last known total_energy_wh_for_summary
342+ # Second run, confirmed interruption
343+ send_notification (f"🔌 { timestamp } : charging interrupted - cable unplugged." )
357344 send_energy_summary (total_energy_wh_for_summary )
345+ save_last_state ("disconnected" , total_energy_wh_for_summary = total_energy_wh_for_summary , repeat_check = False ) # Reset repeat_check
346+ return # Exit after confirming
347+
348+ # 🚨 If normal disconnect detected (idle state), set repeat_check and exit
349+ if total_energy_wh is None and last_state == "idle" :
350+ if not repeat_check : # First detection, re-run once
351+ send_notification (f"🔌 { timestamp } : cable disconnect detected, verifying..." )
352+ save_last_state ("idle" , total_energy_wh = total_energy_wh , total_energy_wh_for_summary = total_energy_wh_for_summary , notified = notified , repeat_check = True )
353+ return # Exit, script will retry next run
354+
355+ # Second run, confirmed disconnection
356+ send_notification (f"🔌 { timestamp } : cable disconnected." )
357+ send_energy_summary (total_energy_wh_for_summary )
358+ save_last_state ("disconnected" , total_energy_wh_for_summary = total_energy_wh_for_summary , repeat_check = False ) # Reset repeat_check
359+ return # Exit after confirming
358360
359- # Set state to "disconnected" instead of transitioning through "idle"
360- save_last_state ("disconnected" )
361-
362- return # Exit early since cable unplugging overrides other transitions
363-
364- # Handle cable disconnection
365- if total_energy_wh is None :
366- if last_state != "disconnected" :
367- send_notification (f"🔌 { timestamp } : cable disconnected." )
368-
369- # Use last known `total_energy_wh_for_summary`
370- send_energy_summary (total_energy_wh_for_summary )
371-
372- # Set state to disconnected
373- save_last_state ("disconnected" )
374-
375- return # Exit early, no further processing needed
376-
377- # Handle cable connection
361+ # 🚀 Handle cable reconnection
378362 if last_state == "disconnected" and total_energy_wh is not None :
379363 send_notification (f"🔌 { timestamp } : cable connected." )
380- save_last_state ("idle" , total_energy_wh = total_energy_wh )
364+ save_last_state ("idle" , total_energy_wh = total_energy_wh , repeat_check = False )
381365
382- # Determine new state
366+ # 🔋 Determine new state
383367 new_state = "idle" if charging_rate < 1.0 else "charging"
384368
385- # **STORE total_energy_wh_for_summary WHENEVER total_energy_wh IS NOT NONE**
386- # required for the summary report after cable disconnection
369+ # 📌 Store latest total energy for summary
387370 if total_energy_wh is not None :
388- save_last_state (new_state , charging_power = charging_rate , total_energy_wh = total_energy_wh , total_energy_wh_for_summary = total_energy_wh , notified = notified )
371+ save_last_state (new_state , charging_power = charging_rate , total_energy_wh = total_energy_wh , total_energy_wh_for_summary = total_energy_wh , notified = notified , repeat_check = False )
389372
390- # Handle charging start
391- if last_state != "charging" and new_state == "charging" and not notified :
373+ # ⚡ Handle charging start
374+ if last_state != "charging" and new_state == "charging" :
392375 send_notification (f"⚡ { timestamp } : charging started." )
393- save_last_state (new_state , charging_power = charging_rate , total_energy_wh = total_energy_wh , total_energy_wh_for_summary = total_energy_wh_for_summary , notified = notified , start_time = current_time )
376+ save_last_state ("charging" , charging_power = charging_rate , total_energy_wh = total_energy_wh , total_energy_wh_for_summary = total_energy_wh_for_summary , notified = False , start_time = current_time )
394377
395- # notify once per session about charging rate
378+ # ⚡ Notify charging rate once per session
396379 if last_state == "charging" and charging_rate > 0 and not notified :
397380 send_notification (f"⚡ { timestamp } : charging rate { charging_rate } kW" )
398- save_last_state (new_state , charging_power = charging_rate , total_energy_wh = total_energy_wh , total_energy_wh_for_summary = total_energy_wh_for_summary , notified = True , start_time = start_time )
381+ save_last_state ("charging" , charging_power = charging_rate , total_energy_wh = total_energy_wh , total_energy_wh_for_summary = total_energy_wh_for_summary , notified = True , start_time = start_time )
399382
400- # Handle charging stop
383+ # 🔋 Handle charging stop
401384 if last_state == "charging" and new_state == "idle" :
402385 send_notification (f"🔋 { timestamp } : charging stopped." )
403- save_last_state (new_state , total_energy_wh = total_energy_wh , total_energy_wh_for_summary = total_energy_wh , start_time = start_time )
386+ save_last_state ("idle" , total_energy_wh = total_energy_wh , total_energy_wh_for_summary = total_energy_wh , start_time = start_time )
404387
405388 if total_energy_wh is not None and start_time :
406389 elapsed_time = max (current_time - start_time , 60 )
407390 elapsed_formatted = format_duration (elapsed_time )
408-
391+
409392 previous_stored_power = stored_power or total_energy_wh or 0
410393 session_energy_wh = max (total_energy_wh - previous_stored_power , 0 )
411-
394+
412395 debug (f".. session-summary \n -- stored_power: { stored_power } \n total_energy_wh: { total_energy_wh } \n previous_stored_power: { previous_stored_power } \n session_energy_wh: { session_energy_wh } \n start_time: { start_time } \n current_time: { current_time } \n elapsed_time: { elapsed_time } " )
413396
414397 if format_energy (session_energy_wh ) == format_energy (total_energy_wh ):
415398 message = f"🔍 { format_energy (session_energy_wh )} in { elapsed_formatted } "
416399 else :
417400 message = f"🔍 { format_energy (session_energy_wh )} of { format_energy (total_energy_wh )} in { elapsed_formatted } "
418-
419401 send_notification (message )
420402
421- # get current values from the state file in order to log it
403+ # 🔍 Log final state
422404 state_data = get_last_state ()
423- last_state = state_data ["state" ]
424- start_time = state_data ["start_time" ]
425- stored_power = state_data ["stored_power" ]
426- total_energy_wh_for_summary = state_data ["total_energy_wh_for_summary" ]
427- notified = state_data ["notified" ]
428-
429- debug (f".. get_last_state() #2 \n -- state file: \n Last State: { last_state } \n Start Time: { start_time } \n Stored Power: { stored_power } \n Total Energy for Summary: { total_energy_wh_for_summary } \n Notified: { notified } \n -- new fetch: \n Charging Rate: { charging_rate } \n Total Energy: { total_energy_wh } " )
405+ debug (f".. get_last_state() #2 \n -- state file: \n Last State: { state_data ['state' ]} \n Start Time: { state_data ['start_time' ]} \n Stored Power: { state_data ['stored_power' ]} \n Total Energy for Summary: { state_data ['total_energy_wh_for_summary' ]} \n Notified: { state_data ['notified' ]} \n Repeat Check: { state_data ['repeat_check' ]} \n -- new fetch: \n Charging Rate: { charging_rate } \n Total Energy: { total_energy_wh } " )
430406
431407 except Exception as e :
432408 send_notification (f"🚨 ALERT: { e } " )
0 commit comments