Compare commits
2 Commits
9c7ca86d86
...
99d92a6e90
| Author | SHA1 | Date | |
|---|---|---|---|
| 99d92a6e90 | |||
| b712c19740 |
@@ -1,4 +1,4 @@
|
|||||||
import gc
|
import gc # type: ignore
|
||||||
|
|
||||||
def check_memory_once():
|
def check_memory_once():
|
||||||
"""One-time memory check (for startup diagnostics)."""
|
"""One-time memory check (for startup diagnostics)."""
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import time
|
import time # type: ignore
|
||||||
from scripts.discord_webhook import send_discord_message
|
from scripts.discord_webhook import send_discord_message
|
||||||
from scripts.temperature_sensor import TemperatureSensor
|
from scripts.temperature_sensor import TemperatureSensor
|
||||||
|
|
||||||
@@ -262,7 +262,7 @@ class WiFiMonitor(Monitor):
|
|||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
"""Check WiFi status, blink LED, attempt reconnect if needed."""
|
"""Check WiFi status, blink LED, attempt reconnect if needed."""
|
||||||
import network
|
import network # type: ignore
|
||||||
from scripts.networking import connect_wifi
|
from scripts.networking import connect_wifi
|
||||||
|
|
||||||
is_connected = self.wifi.isconnected() if self.wifi else False
|
is_connected = self.wifi.isconnected() if self.wifi else False
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import time
|
import time # type: ignore
|
||||||
|
|
||||||
class ScheduleMonitor:
|
class ScheduleMonitor:
|
||||||
"""Monitor that checks and applies temperature schedules."""
|
"""Monitor that checks and applies temperature schedules."""
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import socket
|
import socket
|
||||||
import time
|
import time # type: ignore
|
||||||
import json
|
import json
|
||||||
|
|
||||||
class TempWebServer:
|
class TempWebServer:
|
||||||
@@ -22,7 +22,7 @@ class TempWebServer:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("Failed to start web server: {}".format(e))
|
print("Failed to start web server: {}".format(e))
|
||||||
|
|
||||||
def check_requests(self, sensors, ac_monitor=None, heater_monitor=None, schedule_monitor=None):
|
def check_requests(self, sensors, ac_monitor, heater_monitor, schedule_monitor, config):
|
||||||
"""Check for incoming requests (call in main loop)."""
|
"""Check for incoming requests (call in main loop)."""
|
||||||
if not self.socket:
|
if not self.socket:
|
||||||
return
|
return
|
||||||
@@ -32,7 +32,7 @@ class TempWebServer:
|
|||||||
request = conn.recv(1024).decode('utf-8')
|
request = conn.recv(1024).decode('utf-8')
|
||||||
|
|
||||||
if 'POST /update' in request:
|
if 'POST /update' in request:
|
||||||
response = self._handle_update(request, sensors, ac_monitor, heater_monitor, schedule_monitor)
|
response = self._handle_update(request, sensors, ac_monitor, heater_monitor, schedule_monitor, config)
|
||||||
|
|
||||||
elif 'GET /schedule' in request:
|
elif 'GET /schedule' in request:
|
||||||
response = self._get_schedule_editor_page(sensors, ac_monitor, heater_monitor)
|
response = self._get_schedule_editor_page(sensors, ac_monitor, heater_monitor)
|
||||||
@@ -42,7 +42,7 @@ class TempWebServer:
|
|||||||
return
|
return
|
||||||
|
|
||||||
elif 'POST /schedule' in request:
|
elif 'POST /schedule' in request:
|
||||||
response = self._handle_schedule_update(request, sensors, ac_monitor, heater_monitor, schedule_monitor)
|
response = self._handle_schedule_update(request, sensors, ac_monitor, heater_monitor, schedule_monitor, config)
|
||||||
# If handler returns a redirect response, send it raw and exit
|
# If handler returns a redirect response, send it raw and exit
|
||||||
if isinstance(response, str) and response.startswith('HTTP/1.1 303'):
|
if isinstance(response, str) and response.startswith('HTTP/1.1 303'):
|
||||||
conn.sendall(response.encode('utf-8'))
|
conn.sendall(response.encode('utf-8'))
|
||||||
@@ -61,6 +61,18 @@ class TempWebServer:
|
|||||||
if response is None:
|
if response is None:
|
||||||
response = self._get_status_page(sensors, ac_monitor, heater_monitor, schedule_monitor)
|
response = self._get_status_page(sensors, ac_monitor, heater_monitor, schedule_monitor)
|
||||||
|
|
||||||
|
# ===== START: Send response =====
|
||||||
|
print("DEBUG: Sending response ({} bytes)".format(len(response)))
|
||||||
|
try:
|
||||||
|
conn.send(response.encode('utf-8')) # ← Changed to 'conn'
|
||||||
|
print("DEBUG: Response sent successfully")
|
||||||
|
except Exception as e:
|
||||||
|
print("ERROR: Failed to send response: {}".format(e))
|
||||||
|
finally:
|
||||||
|
conn.close() # ← Changed to 'conn'
|
||||||
|
print("DEBUG: Client connection closed")
|
||||||
|
# ===== END: Send response =====
|
||||||
|
|
||||||
conn.send('HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8\r\nConnection: 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.sendall(response.encode('utf-8'))
|
||||||
conn.close()
|
conn.close()
|
||||||
@@ -75,6 +87,9 @@ class TempWebServer:
|
|||||||
"""Save configuration to config.json file (atomic write)."""
|
"""Save configuration to config.json file (atomic write)."""
|
||||||
try:
|
try:
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
print("DEBUG: Saving config with {} schedules".format(len(config.get('schedules', []))))
|
||||||
|
|
||||||
# Write to temp file first
|
# Write to temp file first
|
||||||
with open('config.tmp', 'w') as f:
|
with open('config.tmp', 'w') as f:
|
||||||
json.dump(config, f)
|
json.dump(config, f)
|
||||||
@@ -91,7 +106,9 @@ class TempWebServer:
|
|||||||
print("Settings saved to config.json")
|
print("Settings saved to config.json")
|
||||||
return True
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("Error saving config: {}".format(e))
|
print("❌ Error saving config: {}".format(e))
|
||||||
|
import sys
|
||||||
|
sys.print_exception(e)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _load_config(self):
|
def _load_config(self):
|
||||||
@@ -110,7 +127,7 @@ class TempWebServer:
|
|||||||
'permanent_hold': False
|
'permanent_hold': False
|
||||||
}
|
}
|
||||||
|
|
||||||
def _handle_schedule_update(self, request, sensors, ac_monitor, heater_monitor, schedule_monitor):
|
def _handle_schedule_update(self, request, sensors, ac_monitor, heater_monitor, schedule_monitor, config):
|
||||||
"""Handle schedule form submission."""
|
"""Handle schedule form submission."""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -122,9 +139,6 @@ class TempWebServer:
|
|||||||
key, value = pair.split('=', 1)
|
key, value = pair.split('=', 1)
|
||||||
params[key] = value.replace('+', ' ')
|
params[key] = value.replace('+', ' ')
|
||||||
|
|
||||||
# Load current config
|
|
||||||
config = self._load_config()
|
|
||||||
|
|
||||||
# ===== START: Handle mode actions =====
|
# ===== START: Handle mode actions =====
|
||||||
mode_action = params.get('mode_action', '')
|
mode_action = params.get('mode_action', '')
|
||||||
|
|
||||||
@@ -196,12 +210,18 @@ class TempWebServer:
|
|||||||
# ===== START: Handle schedule configuration save =====
|
# ===== START: Handle schedule configuration save =====
|
||||||
# Parse schedules (4 slots)
|
# Parse schedules (4 slots)
|
||||||
schedules = []
|
schedules = []
|
||||||
|
has_any_schedule_data = False # Track if user submitted ANY schedule data
|
||||||
|
|
||||||
for i in range(4):
|
for i in range(4):
|
||||||
time_key = 'schedule{}_time'.format(i)
|
time_key = 'schedule{}_time'.format(i)
|
||||||
name_key = 'schedule{}_name'.format(i)
|
name_key = 'schedule{}_name'.format(i)
|
||||||
ac_key = 'schedule{}_ac'.format(i)
|
ac_key = 'schedule{}_ac'.format(i)
|
||||||
heater_key = 'schedule{}_heater'.format(i)
|
heater_key = 'schedule{}_heater'.format(i)
|
||||||
|
|
||||||
|
# Check if this schedule slot has data (even if just a name/temp)
|
||||||
|
if time_key in params or name_key in params or ac_key in params or heater_key in params:
|
||||||
|
has_any_schedule_data = True
|
||||||
|
|
||||||
if time_key in params and params[time_key]:
|
if time_key in params and params[time_key]:
|
||||||
# URL decode the time (converts %3A back to :)
|
# URL decode the time (converts %3A back to :)
|
||||||
schedule_time = params[time_key].replace('%3A', ':')
|
schedule_time = params[time_key].replace('%3A', ':')
|
||||||
@@ -231,7 +251,13 @@ class TempWebServer:
|
|||||||
}
|
}
|
||||||
schedules.append(schedule)
|
schedules.append(schedule)
|
||||||
|
|
||||||
|
# Only update schedules if user submitted schedule form data
|
||||||
|
if has_any_schedule_data:
|
||||||
config['schedules'] = schedules
|
config['schedules'] = schedules
|
||||||
|
print("Updating schedules: {} schedules configured".format(len(schedules)))
|
||||||
|
else:
|
||||||
|
# No schedule data in form - preserve existing schedules
|
||||||
|
print("No schedule data in request - preserving existing schedules")
|
||||||
|
|
||||||
# ===== START: Validate all schedules =====
|
# ===== START: Validate all schedules =====
|
||||||
for i, schedule in enumerate(schedules):
|
for i, schedule in enumerate(schedules):
|
||||||
@@ -280,7 +306,7 @@ class TempWebServer:
|
|||||||
# Safety: avoid rendering an error page here; just redirect
|
# Safety: avoid rendering an error page here; just redirect
|
||||||
return 'HTTP/1.1 303 See Other\r\nLocation: /schedule\r\n\r\n'
|
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):
|
def _handle_update(self, request, sensors, ac_monitor, heater_monitor, schedule_monitor, config):
|
||||||
"""Handle form submission and update settings."""
|
"""Handle form submission and update settings."""
|
||||||
try:
|
try:
|
||||||
body = request.split('\r\n\r\n')[1] if '\r\n\r\n' in request else ''
|
body = request.split('\r\n\r\n')[1] if '\r\n\r\n' in request else ''
|
||||||
@@ -291,9 +317,6 @@ class TempWebServer:
|
|||||||
key, value = pair.split('=', 1)
|
key, value = pair.split('=', 1)
|
||||||
params[key] = float(value)
|
params[key] = float(value)
|
||||||
|
|
||||||
# Load current config
|
|
||||||
config = self._load_config()
|
|
||||||
|
|
||||||
# ===== START: Validate Heat <= AC =====
|
# ===== START: Validate Heat <= AC =====
|
||||||
# Get the values that will be set
|
# Get the values that will be set
|
||||||
new_heater_target = params.get('heater_target', config.get('heater_target', 80.0))
|
new_heater_target = params.get('heater_target', config.get('heater_target', 80.0))
|
||||||
|
|||||||
14
main.py
14
main.py
@@ -1,8 +1,8 @@
|
|||||||
from machine import Pin, WDT # type: ignore
|
from machine import Pin, WDT # type: ignore
|
||||||
import time
|
import time # type: ignore
|
||||||
import network
|
import network # type: ignore
|
||||||
import json
|
import json
|
||||||
import gc # ADD THIS - for garbage collection
|
import gc # type: ignore # ADD THIS - for garbage collection
|
||||||
|
|
||||||
# Enable watchdog (8 seconds timeout - auto-reboot if frozen)
|
# Enable watchdog (8 seconds timeout - auto-reboot if frozen)
|
||||||
# wdt = WDT(timeout=8000) # Maximum is 8388ms, use 8000ms (8 seconds)
|
# wdt = WDT(timeout=8000) # Maximum is 8388ms, use 8000ms (8 seconds)
|
||||||
@@ -77,7 +77,7 @@ def load_config():
|
|||||||
'heater_target': 72.0
|
'heater_target': 72.0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
'schedule_enabled': False, # Schedules disabled by default (user can enable via web)
|
'schedule_enabled': True, # Schedules disabled by default (user can enable via web)
|
||||||
'permanent_hold': False # Permanent hold disabled by default
|
'permanent_hold': False # Permanent hold disabled by default
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,7 +151,7 @@ if wifi and wifi.isconnected():
|
|||||||
# Attempt time sync non-blocking (short timeout + retry flag)
|
# Attempt time sync non-blocking (short timeout + retry flag)
|
||||||
ntp_synced = False
|
ntp_synced = False
|
||||||
try:
|
try:
|
||||||
import ntptime
|
import ntptime # type: ignore
|
||||||
ntptime.settime()
|
ntptime.settime()
|
||||||
ntp_synced = True
|
ntp_synced = True
|
||||||
print("Time synced with NTP server")
|
print("Time synced with NTP server")
|
||||||
@@ -316,13 +316,13 @@ while True:
|
|||||||
run_monitors(monitors)
|
run_monitors(monitors)
|
||||||
|
|
||||||
# Web requests
|
# Web requests
|
||||||
web_server.check_requests(sensors, ac_monitor, heater_monitor, schedule_monitor)
|
web_server.check_requests(sensors, ac_monitor, heater_monitor, schedule_monitor, config)
|
||||||
|
|
||||||
# Retry NTP sync every ~10s if not yet synced
|
# Retry NTP sync every ~10s if not yet synced
|
||||||
if not ntp_synced and retry_ntp_attempts < max_ntp_attempts:
|
if not ntp_synced and retry_ntp_attempts < max_ntp_attempts:
|
||||||
# Try once immediately, then whenever (time.time() % 10) < 1 (rough 10s window)
|
# Try once immediately, then whenever (time.time() % 10) < 1 (rough 10s window)
|
||||||
try:
|
try:
|
||||||
import ntptime
|
import ntptime # type: ignore
|
||||||
if retry_ntp_attempts == 0 or (time.time() % 10) < 1:
|
if retry_ntp_attempts == 0 or (time.time() % 10) < 1:
|
||||||
ntptime.settime()
|
ntptime.settime()
|
||||||
ntp_synced = True
|
ntp_synced = True
|
||||||
|
|||||||
Reference in New Issue
Block a user