From 9fda192f0b9abb1e0425c563a247cb9d17f0b209 Mon Sep 17 00:00:00 2001 From: sickprodigy Date: Sat, 8 Nov 2025 15:47:39 -0500 Subject: [PATCH] Bug: Enhance schedule handling with improved request processing and validation Sometimes page loads, sometimes doesn't trying to implement something to figure out why the page isn't loading. In python everything loads in certain order so if something hangs, it could prevent something else from running. (Like web page from loading :() --- Scripts/web_server.py | 414 +++++++++++++++++++++++++++--------------- 1 file changed, 264 insertions(+), 150 deletions(-) diff --git a/Scripts/web_server.py b/Scripts/web_server.py index bb4218b..9c4edd9 100644 --- a/Scripts/web_server.py +++ b/Scripts/web_server.py @@ -8,6 +8,7 @@ class TempWebServer: self.port = port self.socket = None self.sensors = {} + self.last_page_render = 0 # Track last successful HTML generation def start(self): """Start the web server (non-blocking).""" @@ -20,34 +21,47 @@ class TempWebServer: print("Web server started on port {}".format(self.port)) except Exception as e: print("Failed to start web server: {}".format(e)) - + 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 - try: conn, addr = self.socket.accept() conn.settimeout(3.0) request = conn.recv(1024).decode('utf-8') - - # Check if this is a POST request (form submission) + if 'POST /update' in request: response = self._handle_update(request, sensors, ac_monitor, heater_monitor, schedule_monitor) + + elif 'GET /schedule' in request: + response = self._get_schedule_editor_page(sensors, ac_monitor, heater_monitor) + conn.send('HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8\r\nConnection: close\r\n\r\n') + conn.sendall(response.encode('utf-8')) + conn.close() + return + elif 'POST /schedule' in request: response = self._handle_schedule_update(request, sensors, ac_monitor, heater_monitor, schedule_monitor) + # If handler returns a redirect response, send it raw and exit + if isinstance(response, str) and response.startswith('HTTP/1.1 303'): + conn.sendall(response.encode('utf-8')) + conn.close() + return + elif 'GET /ping' in request: + # Quick health check endpoint (no processing) + conn.send('HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nConnection: close\r\n\r\n') + conn.sendall(b'OK') + conn.close() + return + 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.send('HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8\r\nConnection: close\r\n\r\n') conn.sendall(response.encode('utf-8')) conn.close() except OSError: @@ -56,12 +70,24 @@ class TempWebServer: print("Web server error: {}".format(e)) import sys sys.print_exception(e) - + def _save_config_to_file(self, config): - """Save configuration to config.json file.""" + """Save configuration to config.json file (atomic write).""" try: - with open('config.json', 'w') as f: + import os + # Write to temp file first + with open('config.tmp', 'w') as f: json.dump(config, f) + + # Remove old config if exists + try: + os.remove('config.json') + except: + pass + + # Rename temp to config (atomic on most filesystems) + os.rename('config.tmp', 'config.json') + print("Settings saved to config.json") return True except Exception as e: @@ -86,6 +112,7 @@ class TempWebServer: 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 = {} @@ -160,7 +187,6 @@ class TempWebServer: # Redirect back to homepage return 'HTTP/1.1 303 See Other\r\nLocation: /\r\n\r\n' - # ===== END: Handle mode actions ===== elif mode_action == 'save_schedules': # Just fall through to schedule parsing below @@ -177,6 +203,20 @@ class TempWebServer: heater_key = 'schedule_{}_heater'.format(i) if time_key in params and params[time_key]: + # Validate time format (HH:MM) + time_val = params[time_key] + if ':' not in time_val or len(time_val.split(':')) != 2: + print("Invalid time format: {}".format(time_val)) + return 'HTTP/1.1 303 See Other\r\nLocation: /schedule\r\n\r\n' + + try: + hours, mins = time_val.split(':') + if not (0 <= int(hours) <= 23 and 0 <= int(mins) <= 59): + raise ValueError + except: + print("Invalid time value: {}".format(time_val)) + return 'HTTP/1.1 303 See Other\r\nLocation: /schedule\r\n\r\n' + schedule = { 'time': params[time_key], 'name': params.get(name_key, 'Schedule {}'.format(i+1)), @@ -223,13 +263,16 @@ class TempWebServer: except: pass # ===== END: Handle schedule configuration save ===== - + + # Redirect back to schedule page + return 'HTTP/1.1 303 See Other\r\nLocation: /schedule\r\n\r\n' + 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) + # Safety: avoid rendering an error page here; just redirect + return 'HTTP/1.1 303 See Other\r\nLocation: /schedule\r\n\r\n' def _handle_update(self, request, sensors, ac_monitor, heater_monitor, schedule_monitor): """Handle form submission and update settings.""" @@ -342,12 +385,18 @@ class TempWebServer: """Generate HTML status page.""" print("DEBUG: Generating status page...") try: - # Get current temperatures - inside_temps = sensors['inside'].read_all_temps(unit='F') - outside_temps = sensors['outside'].read_all_temps(unit='F') + # Get current temperatures (use cached values to avoid blocking) + inside_temp = getattr(sensors.get('inside'), 'last_temp', None) + outside_temp = getattr(sensors.get('outside'), 'last_temp', None) - 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" + # Fallback to sensor read if no cached value (first load only) + if inside_temp is None: + inside_temps = sensors['inside'].read_all_temps(unit='F') + inside_temp = list(inside_temps.values())[0] if inside_temps else "N/A" + + if outside_temp is None: + outside_temps = sensors['outside'].read_all_temps(unit='F') + 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" @@ -386,6 +435,9 @@ class TempWebServer: # Build schedule cards schedule_cards = "" + + # Build mode buttons for dashboard + mode_buttons = self._build_mode_buttons(config) if config.get('schedules'): for schedule in config.get('schedules', []): # ===== START: Decode URL-encoded values ===== @@ -416,9 +468,6 @@ class TempWebServer: """ - # Build schedule form - schedule_form = self._build_schedule_form(config) - # Success message success_html = """
@@ -732,7 +781,13 @@ class TempWebServer:
{schedule_cards}
- {schedule_form} + {mode_buttons} + +
+ + ⚙️ Edit Schedules + +