Skip to content

Commit ca96276

Browse files
authored
Update wallbox_monitor.py
1 parent f831db8 commit ca96276

1 file changed

Lines changed: 102 additions & 20 deletions

File tree

wallbox_monitor.py

Lines changed: 102 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@
88
from selenium import webdriver
99
from selenium.webdriver.chrome.service import Service
1010
from selenium.webdriver.common.by import By
11+
from selenium.webdriver.support.ui import WebDriverWait
12+
from selenium.webdriver.support import expected_conditions as EC
13+
14+
LOG_FILE = "/home/pi/wallbox_monitor.log"
15+
logging.basicConfig(filename=LOG_FILE, level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
1116

1217
# Load configuration from credentials file
1318
CONFIG_FILE = "wallbox_monitor.credo"
@@ -21,11 +26,8 @@
2126
logging.error(f"Configuration error: {e}")
2227
raise SystemExit("Error loading credentials. Check 'wallbox_monitor.credo'.")
2328

24-
LOG_FILE = "/home/pi/wallbox_monitor.log"
2529
STATE_FILE = "/tmp/wallbox_state.txt"
2630

27-
logging.basicConfig(filename=LOG_FILE, level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
28-
2931
def get_browser():
3032
"""Starts a headless browser session using Chromium."""
3133
options = webdriver.ChromeOptions()
@@ -36,43 +38,60 @@ def get_browser():
3638
return webdriver.Chrome(service=service, options=options)
3739

3840
def fetch_charging_status(driver):
39-
"""Fetch charging rate and consumed energy from the Wallbox."""
41+
"""Uses Selenium to get the dynamically updated charging rate and consumed energy."""
4042
try:
4143
driver.get(WALLBOX_URL)
4244

4345
charging_rate = None
4446
consumed_energy_wh = None
4547

46-
for _ in range(30): # Retry for up to 30 seconds
48+
# Wait up to 30 seconds for both values to be populated
49+
for _ in range(30): # Retry every second for up to 30 seconds
4750
try:
48-
charging_text = driver.find_element(By.ID, "chargingRate").get_attribute("value").strip()
51+
# Get charging rate
52+
charging_element = driver.find_element(By.ID, "chargingRate")
53+
charging_text = charging_element.get_attribute("value").strip()
4954
if charging_text:
50-
match = re.search(r"([\d.]+)\s*kw", charging_text)
51-
if match:
52-
charging_rate = float(match.group(1))
55+
match_charging = re.search(r"([\d.]+)\s*kw", charging_text)
56+
if match_charging:
57+
charging_rate = float(match_charging.group(1))
5358

54-
consumed_text = driver.find_element(By.ID, "consumed").get_attribute("value").strip()
59+
# Get consumed energy
60+
consumed_element = driver.find_element(By.ID, "consumed")
61+
consumed_text = consumed_element.get_attribute("value").strip()
5562
if consumed_text:
56-
match = re.search(r"([\d.]+)\s*(wh|kWh)", consumed_text)
57-
if match:
58-
consumed_energy_wh = float(match.group(1))
63+
match_consumed = re.search(r"([\d.]+)\s*(wh|kWh)", consumed_text)
64+
if match_consumed:
65+
consumed_energy_wh = float(match_consumed.group(1))
5966
if "kWh" in consumed_text:
6067
consumed_energy_wh *= 1000 # Convert kWh to Wh
6168

69+
# Exit loop early if both values are found
6270
if charging_rate is not None and consumed_energy_wh is not None:
6371
break
6472

6573
except Exception:
66-
pass # Retry
74+
pass # Ignore errors and retry
6775

68-
time.sleep(1)
76+
time.sleep(1) # Wait 1 second before retrying
6977

7078
return charging_rate, consumed_energy_wh
7179

7280
except Exception as e:
7381
logging.error(f"Error fetching charging status: {e}")
7482
return None, None
7583

84+
85+
def format_energy(wh):
86+
"""Converts Wh to kWh if necessary and formats output with two decimal places."""
87+
if wh is None:
88+
return "0.00 kWh"
89+
return f"{wh / 1000:.2f} kWh" if wh >= 1000 else f"{wh:.2f} Wh"
90+
91+
def german_timestamp():
92+
"""Returns the current time in German short format: DD.MM.YY, HH:MM."""
93+
return datetime.now().strftime("%d.%m.%y, %H:%M")
94+
7695
def send_discord_notification(message):
7796
"""Sends a notification to Discord."""
7897
import requests
@@ -83,16 +102,79 @@ def send_discord_notification(message):
83102
except requests.RequestException as e:
84103
logging.error(f"Error sending Discord notification: {e}")
85104

105+
def get_last_state():
106+
"""Reads the last state and charging start time from the state file."""
107+
try:
108+
with open(STATE_FILE, "r") as f:
109+
data = f.read().strip()
110+
if data.startswith("charging:"):
111+
parts = data.split(":")
112+
try:
113+
timestamp = float(parts[1]) if parts[1] != "None" else 0.0
114+
power = float(parts[2]) if parts[2] != "None" else 0.0
115+
return "charging", timestamp, power
116+
except ValueError:
117+
return "idle", None, None # Reset to idle if parsing fails
118+
return data, None, None # "idle"
119+
except FileNotFoundError:
120+
return "idle", None, None
121+
122+
def save_last_state(state, charging_power=0.0):
123+
"""Saves the current state and timestamp if charging starts."""
124+
with open(STATE_FILE, "w") as f:
125+
if state == "charging":
126+
charging_power = charging_power if charging_power is not None else 0.0 # Ensure valid number
127+
f.write(f"charging:{time.time()}:{charging_power:.2f}") # Store timestamp + power
128+
else:
129+
f.write("idle") # Reset if charging stops
130+
86131
def main():
87-
"""Main execution function to monitor the Wallbox status."""
132+
"""Checks wallbox status and triggers notifications when state changes."""
88133
driver = get_browser()
134+
89135
try:
90-
charging_rate, _ = fetch_charging_status(driver)
136+
charging_rate, consumed_energy_wh = fetch_charging_status(driver)
137+
138+
print(f"🔍 Charging Rate: {charging_rate}, Consumed Energy: {consumed_energy_wh}")
91139

92140
if charging_rate is not None:
93-
message = f"⚡ Charging Rate: {charging_rate:.2f} kW"
94-
print(f"📢 Sending Discord Notification: {message}")
95-
send_discord_notification(message)
141+
new_state = "charging" if charging_rate >= 1.0 else "idle"
142+
last_state, start_time, stored_power = get_last_state()
143+
144+
timestamp = german_timestamp()
145+
146+
print(f"🔄 Last State: {last_state}, New State: {new_state}")
147+
148+
if last_state != new_state: # Only trigger on state change
149+
if new_state == "charging":
150+
message = f"⚡ {timestamp}: charging started."
151+
print(f"📢 Sending Discord Notification: {message}")
152+
send_discord_notification(message)
153+
save_last_state(new_state, charging_rate) # Store start time & power
154+
else:
155+
message = f"🔋 {timestamp}: charging stopped."
156+
print(f"📢 Sending Discord Notification: {message}")
157+
send_discord_notification(message)
158+
save_last_state(new_state) # Reset state
159+
160+
# If charging, check if 5 minutes have passed
161+
elif new_state == "charging" and start_time is not None:
162+
elapsed_time = time.time() - start_time
163+
print(f"⏳ Elapsed Charging Time: {elapsed_time:.2f} seconds")
164+
165+
if elapsed_time >= 300: # 300 seconds = 5 minutes
166+
latest_charging_rate, _ = fetch_charging_status(driver) # Fetch latest power
167+
message = f"⚡ charging power: {latest_charging_rate:.2f} kW"
168+
print(f"📢 Sending Discord Notification: {message}")
169+
send_discord_notification(message)
170+
save_last_state("charging") # Prevent duplicate notifications
171+
172+
# If charging stopped, send a separate consumption message
173+
if last_state == "charging" and new_state == "idle" and consumed_energy_wh is not None:
174+
formatted_energy = format_energy(consumed_energy_wh)
175+
message = f"⚡ consumed energy: {formatted_energy}"
176+
print(f"📢 Sending Discord Notification: {message}")
177+
send_discord_notification(message)
96178

97179
finally:
98180
driver.quit()

0 commit comments

Comments
 (0)