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 + +