11#!/usr/bin/env python3
22
3+ # v1.0.3
4+ # shellrecharge-wallbox-monitor - by bjoerrrn
5+ # github: https://github.com/bjoerrrn/shellrecharge-wallbox-monitor
6+ # This script is licensed under GNU GPL version 3.0 or above
7+
8+ import os
39import logging
410import time
511import re
1016from selenium .webdriver .common .by import By
1117from selenium .webdriver .support .ui import WebDriverWait
1218from selenium .webdriver .support import expected_conditions as EC
19+ from logging .handlers import RotatingFileHandler
20+
21+ # Get script directory
22+ SCRIPT_DIR = os .path .dirname (os .path .abspath (__file__ ))
1323
14- LOG_FILE = "/home/pi/wallbox-monitor/wallbox_monitor.log"
15- logging .basicConfig (filename = LOG_FILE , level = logging .INFO , format = "%(asctime)s - %(levelname)s - %(message)s" )
24+ # Logging configuration with rotation
25+ LOG_FILE = os .path .join (SCRIPT_DIR , "wallbox_monitor.log" )
26+ log_handler = RotatingFileHandler (LOG_FILE , maxBytes = 10 * 1024 * 1024 , backupCount = 0 )
27+ log_handler .setFormatter (logging .Formatter ("%(asctime)s - %(levelname)s - %(message)s" ))
28+ logging .basicConfig (level = logging .INFO , handlers = [log_handler ])
1629
17- # Load configuration from credentials file
18- CONFIG_FILE = "/home/pi/wallbox-monitor/ wallbox_monitor.credo"
30+ # Load configuration file
31+ CONFIG_FILE = os . path . join ( SCRIPT_DIR , " wallbox_monitor.credo")
1932config = configparser .ConfigParser ()
20- config .read (CONFIG_FILE )
33+
34+ if not os .path .exists (CONFIG_FILE ):
35+ logging .error ("Configuration file missing. Ensure 'wallbox_monitor.credo' exists." )
36+ print ("Error: Missing 'wallbox_monitor.credo'." )
37+ raise SystemExit ("Error: Missing 'wallbox_monitor.credo'." )
2138
2239try :
40+ config .read (CONFIG_FILE )
2341 WALLBOX_URL = config .get ("CREDENTIALS" , "WALLBOX_URL" )
2442 DISCORD_WEBHOOK_URL = config .get ("CREDENTIALS" , "DISCORD_WEBHOOK_URL" )
25- except (configparser .NoSectionError , configparser .NoOptionError , FileNotFoundError ) as e :
43+ except (configparser .NoSectionError , configparser .NoOptionError ) as e :
2644 logging .error (f"Configuration error: { e } " )
45+ print (f"Error loading credentials: { e } " )
2746 raise SystemExit ("Error loading credentials. Check 'wallbox_monitor.credo'." )
2847
2948STATE_FILE = "/tmp/wallbox_state.txt"
@@ -79,6 +98,7 @@ def fetch_charging_status(driver):
7998
8099 except Exception as e :
81100 logging .error (f"Error fetching charging status: { e } " )
101+ print (f"Error fetching charging status: { e } " )
82102 return None , None
83103
84104
@@ -99,8 +119,10 @@ def send_discord_notification(message):
99119 try :
100120 requests .post (DISCORD_WEBHOOK_URL , json = payload , timeout = 5 )
101121 logging .info (f"Sent Discord notification: { message } " )
122+ print (f"Sent Discord notification: { message } " )
102123 except requests .RequestException as e :
103124 logging .error (f"Error sending Discord notification: { e } " )
125+ print (f"Error sending Discord notification: { e } " )
104126
105127def get_last_state ():
106128 """Reads the last state and charging start time from the state file."""
@@ -113,10 +135,14 @@ def get_last_state():
113135 timestamp = float (parts [1 ]) if parts [1 ] != "None" else 0.0
114136 power = float (parts [2 ]) if parts [2 ] != "None" else 0.0
115137 return "charging" , timestamp , power
116- except ValueError :
138+ except ValueError as e :
139+ logging .error (f"Value Error: { e } " )
140+ print (f"Value Error: { e } " )
117141 return "idle" , None , None # Reset to idle if parsing fails
118142 return data , None , None # "idle"
119- except FileNotFoundError :
143+ except FileNotFoundError as e :
144+ with open (STATE_FILE , "w" ) as f :
145+ f .write ("idle" )
120146 return "idle" , None , None
121147
122148def save_last_state (state , charging_power = 0.0 ):
@@ -136,6 +162,7 @@ def main():
136162 charging_rate , consumed_energy_wh = fetch_charging_status (driver )
137163
138164 print (f"🔍 Charging Rate: { charging_rate } , Consumed Energy: { consumed_energy_wh } " )
165+ logging .info (f"🔍 Charging Rate: { charging_rate } , Consumed Energy: { consumed_energy_wh } " )
139166
140167 if charging_rate is not None :
141168 new_state = "charging" if charging_rate >= 1.0 else "idle"
@@ -144,36 +171,42 @@ def main():
144171 timestamp = german_timestamp ()
145172
146173 print (f"🔄 Last State: { last_state } , New State: { new_state } " )
174+ logging .info (f"🔄 Last State: { last_state } , New State: { new_state } " )
147175
148176 if last_state != new_state : # Only trigger on state change
149177 if new_state == "charging" :
150178 message = f"⚡ { timestamp } : charging started."
151179 print (f"📢 Sending Discord Notification: { message } " )
180+ logging .info (f"📢 Sending Discord Notification: { message } " )
152181 send_discord_notification (message )
153182 save_last_state (new_state , charging_rate ) # Store start time & power
154183 else :
155184 message = f"🔋 { timestamp } : charging stopped."
156185 print (f"📢 Sending Discord Notification: { message } " )
186+ logging .info (f"📢 Sending Discord Notification: { message } " )
157187 send_discord_notification (message )
158188 save_last_state (new_state ) # Reset state
159189
160190 # If charging, check if 5 minutes have passed
161191 elif new_state == "charging" and start_time is not None :
162192 elapsed_time = time .time () - start_time
163193 print (f"⏳ Elapsed Charging Time: { elapsed_time :.2f} seconds" )
194+ logging .info (f"⏳ Elapsed Charging Time: { elapsed_time :.2f} seconds" )
164195
165- if elapsed_time >= 300 : # 300 seconds = 5 minutes
196+ if elapsed_time >= 300 and elapsed_time < 359 : # 300 seconds = 5 minutes
166197 latest_charging_rate , _ = fetch_charging_status (driver ) # Fetch latest power
167- message = f"⚡ charging power: { latest_charging_rate :.2f} kW"
198+ message = f"⏳ charging power: { latest_charging_rate :.2f} kW"
168199 print (f"📢 Sending Discord Notification: { message } " )
200+ logging .info (f"📢 Sending Discord Notification: { message } " )
169201 send_discord_notification (message )
170- save_last_state ("charging" ) # Prevent duplicate notifications
202+ save_last_state ("charging" )
171203
172204 # If charging stopped, send a separate consumption message
173205 if last_state == "charging" and new_state == "idle" and consumed_energy_wh is not None :
174206 formatted_energy = format_energy (consumed_energy_wh )
175- message = f"⚡ consumed energy: { formatted_energy } "
207+ message = f"🔍 consumed energy: { formatted_energy } "
176208 print (f"📢 Sending Discord Notification: { message } " )
209+ logging .info (f"📢 Sending Discord Notification: { message } " )
177210 send_discord_notification (message )
178211
179212 finally :
0 commit comments