Skip to content

Commit 1975419

Browse files
authored
Update wallbox_monitor.py
Added: External Log Script Functionality * Introduced support for external logging scripts via the EXTERNAL_LOG_SCRIPT configuration in wallbox_monitor.credo. * When configured, the script will call an external script after every state change, passing charging data in JSON format. * Added external_script() function to execute the external script with relevant charging session details. * Improved save_last_state() to trigger external logging only when needed. * Prevents unnecessary script calls for duplicate idle states or repeat-check cases. * Enhanced logging with explicit debug messages for external script execution.
1 parent 17c5205 commit 1975419

1 file changed

Lines changed: 80 additions & 22 deletions

File tree

wallbox_monitor.py

Lines changed: 80 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#!/usr/bin/env python3
22

3-
# v1.2.2
3+
# v1.3
44
# wallbox-monitoring - by bjoerrrn
55
# github: https://github.com/bjoerrrn/wallbox-monitoring
66
# This script is licensed under GNU GPL version 3.0 or above
@@ -13,6 +13,8 @@
1313
import time
1414
import re
1515
import configparser
16+
import subprocess
17+
import json
1618
from datetime import datetime
1719
from selenium import webdriver
1820
from selenium.webdriver.chrome.service import Service
@@ -72,7 +74,8 @@ def load_config():
7274
"NTFY_TOPIC": config.get("CREDENTIALS", "NTFY_TOPIC", fallback="").strip(),
7375
"PUSHOVER_USER_KEY": config.get("CREDENTIALS", "PUSHOVER_USER_KEY", fallback="").strip(),
7476
"PUSHOVER_API_TOKEN": config.get("CREDENTIALS", "PUSHOVER_API_TOKEN", fallback="").strip(),
75-
"FIXED_PRICE": float(config.get("CREDENTIALS", "FIXED_PRICE", fallback="0")) or 0
77+
"FIXED_PRICE": float(config.get("CREDENTIALS", "FIXED_PRICE", fallback="0")) or 0,
78+
"EXTERNAL_LOG_SCRIPT": config.get("CREDENTIALS", "EXTERNAL_LOG_SCRIPT", fallback="").strip()
7679
}
7780

7881
debug(f"Loaded configuration: Wallbox URL: {cfg['WALLBOX_URL']}, "
@@ -87,6 +90,7 @@ def load_config():
8790
raise SystemExit(f"Error loading credentials: {e}")
8891

8992
CONFIG = load_config()
93+
EXTERNAL_LOG_SCRIPT = CONFIG["EXTERNAL_LOG_SCRIPT"] # Load globally
9094

9195
def get_browser():
9296
options = webdriver.ChromeOptions()
@@ -213,7 +217,58 @@ def get_last_state():
213217
"notified": False,
214218
"repeat_check": False,
215219
}
220+
216221

222+
def external_script(last_state, new_state, start_time, stored_power, notified, total_energy_wh_for_summary, repeat_check):
223+
if not EXTERNAL_LOG_SCRIPT:
224+
print(f"⚠️ no external log script configured.")
225+
return # No script configured, do nothing
226+
227+
if new_state == "idle" and last_state == "idle":
228+
return # Do nothing if already idle
229+
230+
if repeat_check:
231+
return # Do nothing if new status is currently being verified
232+
233+
event_data = {
234+
"state": new_state,
235+
"start_time": str(start_time) if start_time else "0",
236+
"stored_power": stored_power,
237+
"total_energy_kWh": (total_energy_wh_for_summary or 0.0) / 1000.0, # Convert Wh to kWh
238+
"notified": int(notified),
239+
"repeat_check": int(repeat_check)
240+
}
241+
242+
try:
243+
subprocess.run([EXTERNAL_LOG_SCRIPT, json.dumps(event_data)], check=True)
244+
logger.info(f"✅ External script executed successfully: {EXTERNAL_LOG_SCRIPT}")
245+
except Exception as e:
246+
logger.error(f"⚠️ Error executing external script: {e}")
247+
248+
249+
def save_last_state(last_state, new_state, stored_power=0.0, total_energy_wh_for_summary=0.0, notified=False, start_time=None, repeat_check=False):
250+
with open(STATE_FILE, "w") as f:
251+
if new_state in ["charging", "idle"]:
252+
start_time_str = str(start_time) if new_state == "charging" and start_time is not None else "0"
253+
state_str = f"{new_state}:{start_time_str}:{stored_power:.2f}:{int(notified)}:{total_energy_wh_for_summary or 0.0}:{int(repeat_check)}"
254+
f.write(state_str)
255+
debug(f".. save_last_state(): {state_str}")
256+
external_script(last_state, new_state, start_time_str, stored_power, notified, total_energy_wh_for_summary, repeat_check)
257+
258+
elif new_state == "disconnected":
259+
state_str = f"disconnected:{total_energy_wh_for_summary if total_energy_wh_for_summary is not None else '0.0'}:{int(notified)}:{int(repeat_check)}"
260+
f.write(state_str)
261+
debug(f".. save_last_state(): {state_str}")
262+
external_script(last_state, new_state, None, 0.0, notified, total_energy_wh_for_summary, repeat_check)
263+
264+
else: # Default to idle
265+
state_str = f"idle:0.0:0.0:{int(notified)}:{int(repeat_check)}"
266+
f.write(state_str)
267+
debug(f".. save_last_state(): {state_str}")
268+
external_script(last_state, new_state, None, 0.0, notified, 0.0, repeat_check)
269+
270+
271+
"""
217272
def save_last_state(state, stored_power=0.0, total_energy_wh_for_summary=0.0, notified=False, start_time=None, repeat_check=False):
218273
with open(STATE_FILE, "w") as f:
219274
if state in ["charging", "idle"]:
@@ -228,8 +283,10 @@ def save_last_state(state, stored_power=0.0, total_energy_wh_for_summary=0.0, no
228283
else:
229284
f.write(f"idle:0.0:0.0:{int(notified)}:{int(repeat_check)}")
230285
debug(f".. save_last_state(): idle:0.0:0.0:{int(notified)}:{int(repeat_check)}")
231-
232-
def send_energy_summary(total_energy_wh_for_summary):
286+
"""
287+
288+
289+
def send_energy_summary(last_state, total_energy_wh_for_summary):
233290
"""Sends a summary of total consumed energy when the cable is disconnected."""
234291
if total_energy_wh_for_summary is None or total_energy_wh_for_summary <= 0:
235292
debug("Skipping energy summary (no energy recorded).")
@@ -246,7 +303,7 @@ def send_energy_summary(total_energy_wh_for_summary):
246303
logger.info("✅ Energy summary sent. Resetting stored energy summary.")
247304

248305
# Reset `total_energy_wh_for_summary` after reporting to avoid reuse
249-
save_last_state("disconnected", total_energy_wh_for_summary=0)
306+
save_last_state(last_state, "disconnected", total_energy_wh_for_summary=0)
250307

251308
def fetch_charging_status(driver):
252309
"""Fetches charging rate and total energy from the charger."""
@@ -335,54 +392,55 @@ def main():
335392
if last_state == "charging" and (total_energy_wh is None or charging_rate == 0):
336393
if not repeat_check: # First detection, re-run once
337394
debug(f"🔌 {timestamp}: charging interruption detected, verifying...")
338-
save_last_state("charging", stored_power=stored_power, total_energy_wh_for_summary=total_energy_wh_for_summary, notified=notified, start_time=start_time, repeat_check=True)
395+
save_last_state(last_state, "charging", stored_power=stored_power, total_energy_wh_for_summary=total_energy_wh_for_summary, notified=notified, start_time=start_time, repeat_check=True)
339396
return # Exit, script will retry next run
340397

341398
# Second run, confirmed interruption
342-
send_notification(f"🔌 {timestamp}: charging interrupted - cable unplugged.")
343-
send_energy_summary(total_energy_wh_for_summary)
344-
save_last_state("disconnected", total_energy_wh_for_summary=total_energy_wh_for_summary, repeat_check=False) # Reset repeat_check
399+
send_notification(f"🔌 {timestamp}: interrupted.")
400+
send_energy_summary(last_state, total_energy_wh_for_summary)
401+
save_last_state(last_state, "disconnected", total_energy_wh_for_summary=total_energy_wh_for_summary, repeat_check=False) # Reset repeat_check
345402
return # Exit after confirming
346403

347404
# 🚨 If normal disconnect detected (idle state), set repeat_check and exit
348405
if total_energy_wh is None and last_state == "idle":
349406
if not repeat_check: # First detection, re-run once
350407
debug(f"🔌 {timestamp}: cable disconnect detected, verifying...")
351-
save_last_state("idle", stored_power=stored_power, total_energy_wh_for_summary=total_energy_wh_for_summary, notified=notified, repeat_check=True)
408+
save_last_state(last_state, "idle", stored_power=stored_power, total_energy_wh_for_summary=total_energy_wh_for_summary, notified=notified, repeat_check=True)
352409
return # Exit, script will retry next run
353410

354411
# Second run, confirmed disconnection
355-
send_notification(f"🔌 {timestamp}: cable disconnected.")
356-
send_energy_summary(total_energy_wh_for_summary)
357-
save_last_state("disconnected", total_energy_wh_for_summary=total_energy_wh_for_summary, repeat_check=False) # Reset repeat_check
412+
send_notification(f"🔌 {timestamp}: disconnected.")
413+
send_energy_summary(last_state, total_energy_wh_for_summary)
414+
save_last_state(last_state, "disconnected", total_energy_wh_for_summary=total_energy_wh_for_summary, repeat_check=False) # Reset repeat_check
358415
return # Exit after confirming
359416

360417
# 🚀 Handle cable reconnection
361418
if last_state == "disconnected" and total_energy_wh is not None:
362-
send_notification(f"🔌 {timestamp}: cable connected.")
363-
save_last_state("idle", stored_power=stored_power, repeat_check=False)
419+
send_notification(f"🔌 {timestamp}: connected.")
420+
# save_last_state(last_state, "idle", stored_power=stored_power, repeat_check=False)
421+
# redundant call of save_last_state() ?
364422

365423
# 🔋 Determine new state
366424
new_state = "idle" if not isinstance(charging_rate, (int, float)) or charging_rate < 1.0 else "charging"
367425

368426
# 📌 Store latest total energy for summary
369427
if total_energy_wh is not None:
370-
save_last_state(new_state, stored_power=stored_power, total_energy_wh_for_summary=total_energy_wh, notified=notified, repeat_check=False, start_time=start_time)
428+
save_last_state(last_state, new_state, stored_power=stored_power, total_energy_wh_for_summary=total_energy_wh, notified=notified, repeat_check=False, start_time=start_time)
371429

372430
# ⚡ Handle charging start
373431
if last_state != "charging" and new_state == "charging":
374-
send_notification(f"🪫 {timestamp}: charging started.")
375-
save_last_state("charging", stored_power=total_energy_wh, total_energy_wh_for_summary=total_energy_wh_for_summary, notified=False, start_time=current_time)
432+
send_notification(f"🪫 {timestamp}: started.")
433+
save_last_state(last_state, "charging", stored_power=total_energy_wh, total_energy_wh_for_summary=total_energy_wh_for_summary, notified=False, start_time=current_time)
376434

377435
# ⚡ Notify charging rate once per session
378436
if last_state == "charging" and charging_rate > 0 and not notified:
379-
send_notification(f"⚡ {timestamp}: charging rate {charging_rate} kW")
380-
save_last_state("charging", stored_power=stored_power, total_energy_wh_for_summary=total_energy_wh_for_summary, notified=True, start_time=start_time)
437+
send_notification(f"⚡ {timestamp}: rate {charging_rate} kW")
438+
save_last_state(last_state, "charging", stored_power=stored_power, total_energy_wh_for_summary=total_energy_wh_for_summary, notified=True, start_time=start_time)
381439

382440
# 🔋 Handle charging stop
383441
if last_state == "charging" and new_state == "idle":
384-
send_notification(f"🔋 {timestamp}: charging stopped.")
385-
save_last_state("idle", stored_power=stored_power, total_energy_wh_for_summary=total_energy_wh, start_time=start_time)
442+
send_notification(f"🔋 {timestamp}: stopped.")
443+
save_last_state(last_state, "idle", stored_power=stored_power, total_energy_wh_for_summary=total_energy_wh, start_time=start_time)
386444

387445
if total_energy_wh is not None and start_time:
388446
elapsed_time = max(current_time - start_time, 60)

0 commit comments

Comments
 (0)