From 2817273ba48c22a346f2572cc74f8b6f17f634eb Mon Sep 17 00:00:00 2001 From: sickprodigy Date: Wed, 5 Nov 2025 21:42:27 -0500 Subject: [PATCH] Enhance web server to handle schedule updates and configuration loading --- Scripts/web_server.py | 937 ++++++++++++++++++++++++------------------ main.py | 2 +- 2 files changed, 545 insertions(+), 394 deletions(-) diff --git a/Scripts/web_server.py b/Scripts/web_server.py index d03c3d0..6cca202 100644 --- a/Scripts/web_server.py +++ b/Scripts/web_server.py @@ -1,5 +1,6 @@ import socket import time +import json class TempWebServer: """Simple web server for viewing temperatures and adjusting settings.""" @@ -15,12 +16,12 @@ class TempWebServer: self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.socket.bind(('0.0.0.0', self.port)) self.socket.listen(1) - self.socket.setblocking(False) # Non-blocking mode - print(f"Web server started on port {self.port}") + self.socket.setblocking(False) + print("Web server started on port {}".format(self.port)) except Exception as e: - print(f"Failed to start web server: {e}") + print("Failed to start web server: {}".format(e)) - def check_requests(self, sensors, ac_monitor=None, heater_monitor=None): + def check_requests(self, sensors, ac_monitor=None, heater_monitor=None, schedule_monitor=None): """Check for incoming requests (call in main loop).""" if not self.socket: return @@ -32,31 +33,33 @@ class TempWebServer: # Check if this is a POST request (form submission) if 'POST /update' in request: - response = self._handle_update(request, sensors, ac_monitor, heater_monitor) + response = self._handle_update(request, sensors, ac_monitor, heater_monitor, schedule_monitor) + elif 'POST /schedule' in request: + response = self._handle_schedule_update(request, sensors, ac_monitor, heater_monitor, schedule_monitor) else: # Regular GET request response = self._get_status_page(sensors, ac_monitor, heater_monitor) + # Make sure we have a valid response + if response is None: + print("Error: response is None, generating default page") + response = self._get_status_page(sensors, ac_monitor, heater_monitor) + conn.send('HTTP/1.1 200 OK\r\n') conn.send('Content-Type: text/html; charset=utf-8\r\n') conn.send('Connection: close\r\n\r\n') conn.sendall(response.encode('utf-8')) conn.close() except OSError: - pass # No connection, continue + pass except Exception as e: - print(f"Web server error: {e}") + print("Web server error: {}".format(e)) + import sys + sys.print_exception(e) - def _save_config_to_file(self, ac_monitor, heater_monitor): - """Save current settings to config.json file.""" + def _save_config_to_file(self, config): + """Save configuration to config.json file.""" try: - import json - config = { - 'ac_target': ac_monitor.target_temp, - 'ac_swing': ac_monitor.temp_swing, - 'heater_target': heater_monitor.target_temp, - 'heater_swing': heater_monitor.temp_swing - } with open('config.json', 'w') as f: json.dump(config, f) print("Settings saved to config.json") @@ -65,10 +68,86 @@ class TempWebServer: print("Error saving config: {}".format(e)) return False - def _handle_update(self, request, sensors, ac_monitor, heater_monitor): + def _load_config(self): + """Load configuration from file.""" + try: + with open('config.json', 'r') as f: + return json.load(f) + except: + return { + 'ac_target': 77.0, + 'ac_swing': 1.0, + 'heater_target': 80.0, + 'heater_swing': 2.0, + 'schedules': [], + 'schedule_enabled': False + } + + def _handle_schedule_update(self, request, sensors, ac_monitor, heater_monitor, schedule_monitor): + """Handle schedule form submission.""" + try: + body = request.split('\r\n\r\n')[1] if '\r\n\r\n' in request else '' + params = {} + + for pair in body.split('&'): + if '=' in pair: + key, value = pair.split('=', 1) + params[key] = value.replace('+', ' ') + + # Load current config + config = self._load_config() + + # Update schedule enabled status + config['schedule_enabled'] = params.get('schedule_enabled') == 'on' + + # Parse schedules + schedules = [] + for i in range(4): + time_key = 'schedule_{}_time'.format(i) + name_key = 'schedule_{}_name'.format(i) + ac_key = 'schedule_{}_ac'.format(i) + heater_key = 'schedule_{}_heater'.format(i) + + if time_key in params and params[time_key]: + schedule = { + 'time': params[time_key], + 'name': params.get(name_key, 'Schedule {}'.format(i+1)), + 'ac_target': float(params.get(ac_key, 77.0)), + 'heater_target': float(params.get(heater_key, 80.0)) + } + schedules.append(schedule) + + config['schedules'] = schedules + + # Save to file + if self._save_config_to_file(config): + print("Schedule settings saved") + + # Reload schedule monitor config + if schedule_monitor: + schedule_monitor.reload_config(config) + + # Send Discord notification + try: + from scripts.discord_webhook import send_discord_message + status = "enabled" if config['schedule_enabled'] else "disabled" + message = "📅 Schedules updated ({}) - {} schedules configured".format( + status, len(schedules) + ) + send_discord_message(message) + except: + pass + + except Exception as e: + print("Error updating schedule: {}".format(e)) + import sys + sys.print_exception(e) + + return self._get_status_page(sensors, ac_monitor, heater_monitor, show_success=True) + + def _handle_update(self, request, sensors, ac_monitor, heater_monitor, schedule_monitor): """Handle form submission and update settings.""" try: - # Extract form data from POST body body = request.split('\r\n\r\n')[1] if '\r\n\r\n' in request else '' params = {} @@ -77,427 +156,499 @@ class TempWebServer: key, value = pair.split('=', 1) params[key] = float(value) + # Load current config + config = self._load_config() + # Update AC settings if 'ac_target' in params and ac_monitor: ac_monitor.target_temp = params['ac_target'] + config['ac_target'] = params['ac_target'] print("AC target updated to {}°F".format(params['ac_target'])) if 'ac_swing' in params and ac_monitor: ac_monitor.temp_swing = params['ac_swing'] + config['ac_swing'] = params['ac_swing'] print("AC swing updated to {}°F".format(params['ac_swing'])) # Update heater settings if 'heater_target' in params and heater_monitor: heater_monitor.target_temp = params['heater_target'] + config['heater_target'] = params['heater_target'] print("Heater target updated to {}°F".format(params['heater_target'])) if 'heater_swing' in params and heater_monitor: heater_monitor.temp_swing = params['heater_swing'] + config['heater_swing'] = params['heater_swing'] print("Heater swing updated to {}°F".format(params['heater_swing'])) # Save settings to file - if self._save_config_to_file(ac_monitor, heater_monitor): + if self._save_config_to_file(config): print("Settings persisted to disk") # Send Discord notification - from scripts.discord_webhook import send_discord_message - ac_target_str = str(params.get('ac_target', 'N/A')) - ac_swing_str = str(params.get('ac_swing', 'N/A')) - heater_target_str = str(params.get('heater_target', 'N/A')) - heater_swing_str = str(params.get('heater_swing', 'N/A')) - - message = "Settings Updated - AC: {}F +/- {}F | Heater: {}F +/- {}F".format( - ac_target_str, ac_swing_str, heater_target_str, heater_swing_str - ) - send_discord_message(message) + try: + from scripts.discord_webhook import send_discord_message + ac_target_str = str(params.get('ac_target', 'N/A')) + ac_swing_str = str(params.get('ac_swing', 'N/A')) + heater_target_str = str(params.get('heater_target', 'N/A')) + heater_swing_str = str(params.get('heater_swing', 'N/A')) + + message = "Settings Updated - AC: {}F +/- {}F | Heater: {}F +/- {}F".format( + ac_target_str, ac_swing_str, heater_target_str, heater_swing_str + ) + send_discord_message(message) + except Exception as discord_error: + print("Discord notification failed: {}".format(discord_error)) except Exception as e: print("Error updating settings: {}".format(e)) + import sys + sys.print_exception(e) - # Return updated page return self._get_status_page(sensors, ac_monitor, heater_monitor, show_success=True) def _get_status_page(self, sensors, ac_monitor, heater_monitor, show_success=False): """Generate HTML status page.""" - # Get current temperatures - inside_temps = sensors['inside'].read_all_temps(unit='F') - outside_temps = sensors['outside'].read_all_temps(unit='F') - - inside_temp = list(inside_temps.values())[0] if inside_temps else "N/A" - outside_temp = list(outside_temps.values())[0] if outside_temps else "N/A" - - # Get AC/Heater status - ac_status = "ON" if ac_monitor and ac_monitor.ac.get_state() else "OFF" - heater_status = "ON" if heater_monitor and heater_monitor.heater.get_state() else "OFF" - - # Get current time - current_time = time.localtime() - time_str = "{}-{:02d}-{:02d} {:02d}:{:02d}:{:02d}".format( - current_time[0], current_time[1], current_time[2], - current_time[3], current_time[4], current_time[5] - ) - - # Load config to show schedules try: - import json - with open('config.json', 'r') as f: - config = json.load(f) - except: - config = {'schedules': [], 'schedule_enabled': False} - - # Build schedule display - schedule_status = "ENABLED ✅" if config.get('schedule_enabled') else "DISABLED ⚠️" - - if config.get('schedules'): + # Get current temperatures + inside_temps = sensors['inside'].read_all_temps(unit='F') + outside_temps = sensors['outside'].read_all_temps(unit='F') + + inside_temp = list(inside_temps.values())[0] if inside_temps else "N/A" + outside_temp = list(outside_temps.values())[0] if outside_temps else "N/A" + + # Get AC/Heater status + ac_status = "ON" if ac_monitor and ac_monitor.ac.get_state() else "OFF" + heater_status = "ON" if heater_monitor and heater_monitor.heater.get_state() else "OFF" + + # Get current time + current_time = time.localtime() + time_str = "{}-{:02d}-{:02d} {:02d}:{:02d}:{:02d}".format( + current_time[0], current_time[1], current_time[2], + current_time[3], current_time[4], current_time[5] + ) + + # Load config + config = self._load_config() + + # Build schedule display + schedule_status = "ENABLED" if config.get('schedule_enabled') else "DISABLED" + schedule_color = "#2ecc71" if config.get('schedule_enabled') else "#95a5a6" + schedule_icon = "✅" if config.get('schedule_enabled') else "⚠️" + + # Build schedule cards schedule_cards = "" - for schedule in config.get('schedules', []): - schedule_cards += """ -
-
- 🕐 {time} - {name} -
-
- AC: {ac_temp}°F | Heater: {heater_temp}°F + if config.get('schedules'): + for schedule in config.get('schedules', []): + schedule_cards += """ +
+
+ 🕐 {time} - {name} +
+
+ AC: {ac_temp}°F | Heater: {heater_temp}°F +
+ """.format( + time=schedule.get('time', 'N/A'), + name=schedule.get('name', 'Unnamed'), + ac_temp=schedule.get('ac_target', 'N/A'), + heater_temp=schedule.get('heater_target', 'N/A') + ) + else: + schedule_cards = """ +
+ No schedules configured
- """.format( - time=schedule.get('time', 'N/A'), - name=schedule.get('name', 'Unnamed'), - ac_temp=schedule.get('ac_target', 'N/A'), - heater_temp=schedule.get('heater_target', 'N/A') - ) - else: - schedule_cards = """ -
- No schedules configured + """ + + # Build schedule form + schedule_form = self._build_schedule_form(config) + + # Success message + success_html = """ +
+ ✅ Settings updated successfully!
- """ - - # Success message - success_html = """ -
- ✅ Settings updated successfully! + """ if show_success else "" + + # Format temperature values + inside_temp_str = "{:.1f}".format(inside_temp) if isinstance(inside_temp, float) else str(inside_temp) + outside_temp_str = "{:.1f}".format(outside_temp) if isinstance(outside_temp, float) else str(outside_temp) + + html = """ + + + + 🌱 Auto Garden + + + + + + +

🌱 Auto Garden Dashboard

+ + {success_message} + +
+
+
🏠
+
Indoor Climate
+
{inside_temp}°F
- """ if show_success else "" - html = """ - - - - 🌱 Auto Garden - - - - - - -

🌱 Auto Garden Dashboard

- - {success_message} - -
-
-
🏠
-
Indoor Climate
-
{inside_temp}°F
-
- -
-
🌤️
-
Outdoor Climate
-
{outside_temp}°F
-
+
+
🌤️
+
Outdoor Climate
+
{outside_temp}°F
+
+
+ +
+
+
+
❄️
+
Air Conditioning
+
{ac_status}
+
Target: {ac_target}°F ± {ac_swing}°F
+
+
🔥
+
Heating System
+
{heater_status}
+
Target: {heater_target}°F ± {heater_swing}°F
+
+
+ +
+

⚙️ Adjust Settings

-
-
-
-
❄️
-
Air Conditioning
-
{ac_status}
-
Target: {ac_target}°F ± {ac_swing}°F
+
+
+
+ +
-
-
🔥
-
Heating System
-
{heater_status}
-
Target: {heater_target}°F ± {heater_swing}°F
+
+ +
- -

⚙️ Adjust Settings

- -
-
-
- - -
-
- - -
-
- -
-
- - -
-
- - -
-
+
+
+ +
- -
- +
+ +
- -
- -
-

📅 Daily Schedule

-
- Status: - - {schedule_status} - -
-
- {schedule_cards}
- + +
+

📅 Daily Schedule

+
+ Status: + + {schedule_status} {schedule_icon} + +
+
+ {schedule_cards} +
+ {schedule_form} +
+ + + + + """.format( + success_message=success_html, + inside_temp=inside_temp_str, + outside_temp=outside_temp_str, + ac_status=ac_status, + ac_class="on" if ac_status == "ON" else "off", + heater_status=heater_status, + heater_class="on" if heater_status == "ON" else "off", + ac_target=ac_monitor.target_temp if ac_monitor else "N/A", + ac_swing=ac_monitor.temp_swing if ac_monitor else "N/A", + heater_target=heater_monitor.target_temp if heater_monitor else "N/A", + heater_swing=heater_monitor.temp_swing if heater_monitor else "N/A", + time=time_str, + schedule_status=schedule_status, + schedule_color=schedule_color, + schedule_icon=schedule_icon, + schedule_cards=schedule_cards, + schedule_form=schedule_form + ) + return html + + except Exception as e: + print("Error generating page: {}".format(e)) + import sys + sys.print_exception(e) + return "

Error loading page

{}
".format(str(e)) + + def _build_schedule_form(self, config): + """Build the schedule editing form.""" + schedules = config.get('schedules', []) + + # Pad with empty schedules up to 4 + while len(schedules) < 4: + schedules.append({'time': '', 'name': '', 'ac_target': 77.0, 'heater_target': 80.0}) + + enabled_checked = 'checked' if config.get('schedule_enabled') else '' + + form = """ +
+
+

⚙️ Edit Schedules

+ +
+ """.format(enabled_checked=enabled_checked) + + for i, schedule in enumerate(schedules[:4]): + form += """ +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ """.format( + i=i, + time=schedule.get('time', ''), + name=schedule.get('name', ''), + ac=schedule.get('ac_target', 77.0), + heater=schedule.get('heater_target', 80.0) + ) + + form += """ +
+ +
+
+ """ + + return form \ No newline at end of file diff --git a/main.py b/main.py index 009e588..072853c 100644 --- a/main.py +++ b/main.py @@ -198,5 +198,5 @@ print("Press Ctrl+C to stop\n") # Main monitoring loop while True: run_monitors(monitors) - web_server.check_requests(sensors, ac_monitor, heater_monitor) + web_server.check_requests(sensors, ac_monitor, heater_monitor, schedule_monitor) time.sleep(0.1) \ No newline at end of file