Compare commits
8 Commits
ce816af9e7
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| e90e56c3f8 | |||
| 5cfa36e558 | |||
| c8102e62ee | |||
| d76b11430c | |||
| cb274545a3 | |||
| 6cd1349633 | |||
| bcecf2a81a | |||
| 621a48f011 |
69
README.md
69
README.md
@@ -146,56 +146,28 @@ RUN pin → Button → GND
|
|||||||
|
|
||||||
### 4. Configuration
|
### 4. Configuration
|
||||||
|
|
||||||
**Create `secrets.py`** (copy from `secrets.example.py`):
|
**Edit `config.json`** (created automatically on first boot, or edit manually):
|
||||||
|
|
||||||
```python
|
```json
|
||||||
secrets = {
|
|
||||||
'ssid': 'YOUR_WIFI_NAME',
|
|
||||||
'password': 'YOUR_WIFI_PASSWORD',
|
|
||||||
'discord_webhook_url': 'https://discord.com/api/webhooks/...',
|
|
||||||
'discord_alert_webhook_url': 'https://discord.com/api/webhooks/...',
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Sensor Configuration in `main.py`:**
|
|
||||||
|
|
||||||
```python
|
|
||||||
# Sensor configuration
|
|
||||||
SENSOR_CONFIG = {
|
|
||||||
'inside': {
|
|
||||||
'pin': 10,
|
|
||||||
'label': 'Inside',
|
|
||||||
'alert_high': 80.0,
|
|
||||||
'alert_low': 70.0
|
|
||||||
},
|
|
||||||
'outside': {
|
|
||||||
'pin': 11,
|
|
||||||
'label': 'Outside',
|
|
||||||
'alert_high': 85.0,
|
|
||||||
'alert_low': 68.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Default Climate Settings (auto-saved to config.json):**
|
|
||||||
|
|
||||||
```python
|
|
||||||
# Default config (created on first boot)
|
|
||||||
{
|
{
|
||||||
"ac_target": 77.0, # AC target temperature (°F)
|
"ssid": "YOUR_WIFI_NAME",
|
||||||
"ac_swing": 1.0, # AC turns on at 78°F, off at 76°F
|
"password": "YOUR_WIFI_PASSWORD",
|
||||||
"heater_target": 72.0, # Heater target temperature (°F)
|
"discord_webhook_url": "https://discord.com/api/webhooks/...",
|
||||||
"heater_swing": 2.0, # Heater turns on at 70°F, off at 74°F
|
"discord_alert_webhook_url": "https://discord.com/api/webhooks/...",
|
||||||
"temp_hold_duration": 3600, # Temporary hold lasts 1 hour (3600 seconds)
|
"ac_target": 77.0,
|
||||||
"schedule_enabled": true, # Schedules active by default
|
"ac_swing": 1.0,
|
||||||
"schedules": [ # 4 time-based schedules
|
"heater_target": 72.0,
|
||||||
|
"heater_swing": 2.0,
|
||||||
|
"temp_hold_duration": 3600,
|
||||||
|
"schedule_enabled": true,
|
||||||
|
"schedules": [
|
||||||
{
|
{
|
||||||
"time": "06:00",
|
"time": "06:00",
|
||||||
"name": "Morning",
|
"name": "Morning",
|
||||||
"ac_target": 75.0,
|
"ac_target": 75.0,
|
||||||
"heater_target": 72.0
|
"heater_target": 72.0
|
||||||
},
|
}
|
||||||
# ... 3 more schedules
|
// ... 3 more schedules
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -207,9 +179,8 @@ All settings can be changed via the web interface and persist through reboots.
|
|||||||
Upload all files to your Pico:
|
Upload all files to your Pico:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
/
|
/
|
||||||
├── main.py
|
├── main.py
|
||||||
├── secrets.py
|
|
||||||
├── config.json # Auto-generated on first boot
|
├── config.json # Auto-generated on first boot
|
||||||
└── scripts/
|
└── scripts/
|
||||||
├── air_conditioning.py # AC/Heater controller classes
|
├── air_conditioning.py # AC/Heater controller classes
|
||||||
@@ -228,9 +199,7 @@ The Pico will auto-start `main.py` on boot and be accessible at **<http://192.16
|
|||||||
```text
|
```text
|
||||||
Auto-Garden/
|
Auto-Garden/
|
||||||
├── main.py # Entry point, configuration, system initialization
|
├── main.py # Entry point, configuration, system initialization
|
||||||
├── secrets.py # WiFi & Discord credentials (gitignored)
|
├── config.json # Persistent configuration and credentials (auto-generated)
|
||||||
├── secrets.example.py # Template for secrets.py
|
|
||||||
├── config.json # Persistent configuration (auto-generated)
|
|
||||||
└── scripts/
|
└── scripts/
|
||||||
├── air_conditioning.py # AC & Heater controllers with short-cycle protection
|
├── air_conditioning.py # AC & Heater controllers with short-cycle protection
|
||||||
├── discord_webhook.py # Discord notification handling
|
├── discord_webhook.py # Discord notification handling
|
||||||
@@ -447,14 +416,14 @@ report_interval=30 # Discord report frequency
|
|||||||
|
|
||||||
**WiFi not connecting:**
|
**WiFi not connecting:**
|
||||||
|
|
||||||
- Verify SSID/password in `secrets.py`
|
- Verify SSID/password in `config.json`
|
||||||
- Check 2.4GHz WiFi (Pico W doesn't support 5GHz)
|
- Check 2.4GHz WiFi (Pico W doesn't support 5GHz)
|
||||||
- LED should be solid when connected
|
- LED should be solid when connected
|
||||||
- Check serial console for connection status
|
- Check serial console for connection status
|
||||||
|
|
||||||
**Discord messages not sending:**
|
**Discord messages not sending:**
|
||||||
|
|
||||||
- Verify webhook URLs in `secrets.py`
|
- Verify webhook URLs in `config.json`
|
||||||
- Test webhooks with curl/Postman first
|
- Test webhooks with curl/Postman first
|
||||||
- Check Pico has internet access (ping test)
|
- Check Pico has internet access (ping test)
|
||||||
- Look for error messages in serial console
|
- Look for error messages in serial console
|
||||||
|
|||||||
@@ -55,12 +55,12 @@ def send_discord_message(message, username="Auto Garden Bot", is_alert=False, de
|
|||||||
import gc # type: ignore
|
import gc # type: ignore
|
||||||
import time # type: ignore
|
import time # type: ignore
|
||||||
|
|
||||||
gc.collect(); gc.collect()
|
gc.collect()
|
||||||
|
|
||||||
# Quick mem check before importing urequests/SSL
|
# Quick mem check before importing urequests/SSL
|
||||||
mem = getattr(gc, "mem_free", lambda: None)()
|
mem = getattr(gc, "mem_free", lambda: None)()
|
||||||
# Require larger headroom based on device testing (adjust if you re-test)
|
# Require larger headroom based on device testing (adjust if you re-test)
|
||||||
if mem is not None and mem < 105000:
|
if mem is not None and mem < 95000:
|
||||||
print("Discord send skipped: ENOMEM ({} bytes free)".format(mem))
|
print("Discord send skipped: ENOMEM ({} bytes free)".format(mem))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -131,6 +131,6 @@ def send_discord_message(message, username="Auto Garden Bot", is_alert=False, de
|
|||||||
pass
|
pass
|
||||||
try:
|
try:
|
||||||
import gc # type: ignore
|
import gc # type: ignore
|
||||||
gc.collect(); gc.collect()
|
gc.collect()
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
import ujson
|
|
||||||
# Reload discord module fresh and run the forced debug send once.
|
|
||||||
try:
|
|
||||||
# ensure we use latest module on device
|
|
||||||
import sys
|
|
||||||
if "scripts.discord_webhook" in sys.modules:
|
|
||||||
del sys.modules["scripts.discord_webhook"]
|
|
||||||
import scripts.discord_webhook as d
|
|
||||||
# load config.json to populate webhook URL
|
|
||||||
with open("config.json", "r") as f:
|
|
||||||
cfg = ujson.load(f)
|
|
||||||
d.set_config(cfg)
|
|
||||||
print("Running debug_force_send() — may trigger ENOMEM, run once only")
|
|
||||||
d.debug_force_send("memory test")
|
|
||||||
except Exception as e:
|
|
||||||
print("test_send error:", e)
|
|
||||||
@@ -194,6 +194,8 @@ class TempWebServer:
|
|||||||
print("ERROR: Failed to send response: {}".format(e))
|
print("ERROR: Failed to send response: {}".format(e))
|
||||||
finally:
|
finally:
|
||||||
conn.close()
|
conn.close()
|
||||||
|
import gc # type: ignore
|
||||||
|
gc.collect()
|
||||||
print("DEBUG: Client connection closed")
|
print("DEBUG: Client connection closed")
|
||||||
# ===== END: Send response =====
|
# ===== END: Send response =====
|
||||||
|
|
||||||
@@ -266,7 +268,8 @@ class TempWebServer:
|
|||||||
|
|
||||||
def _handle_schedule_update(self, request, sensors, ac_monitor, heater_monitor, schedule_monitor, config):
|
def _handle_schedule_update(self, request, sensors, ac_monitor, heater_monitor, schedule_monitor, config):
|
||||||
"""Handle schedule form submission."""
|
"""Handle schedule form submission."""
|
||||||
|
import gc # type: ignore
|
||||||
|
gc.collect()
|
||||||
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 ''
|
||||||
params = {}
|
params = {}
|
||||||
@@ -280,6 +283,7 @@ class TempWebServer:
|
|||||||
mode_action = params.get('mode_action', '')
|
mode_action = params.get('mode_action', '')
|
||||||
|
|
||||||
if mode_action == 'resume':
|
if mode_action == 'resume':
|
||||||
|
gc.collect()
|
||||||
# Resume automatic scheduling
|
# Resume automatic scheduling
|
||||||
config['schedule_enabled'] = True
|
config['schedule_enabled'] = True
|
||||||
config['permanent_hold'] = False
|
config['permanent_hold'] = False
|
||||||
@@ -313,6 +317,7 @@ class TempWebServer:
|
|||||||
return redirect_response
|
return redirect_response
|
||||||
|
|
||||||
elif mode_action == 'temporary_hold':
|
elif mode_action == 'temporary_hold':
|
||||||
|
gc.collect()
|
||||||
# Enter temporary hold (pause schedules temporarily)
|
# Enter temporary hold (pause schedules temporarily)
|
||||||
config['schedule_enabled'] = False
|
config['schedule_enabled'] = False
|
||||||
config['permanent_hold'] = False
|
config['permanent_hold'] = False
|
||||||
@@ -337,6 +342,7 @@ class TempWebServer:
|
|||||||
return redirect_response
|
return redirect_response
|
||||||
|
|
||||||
elif mode_action == 'permanent_hold':
|
elif mode_action == 'permanent_hold':
|
||||||
|
gc.collect()
|
||||||
# Enter permanent hold (disable schedules permanently)
|
# Enter permanent hold (disable schedules permanently)
|
||||||
config['schedule_enabled'] = False
|
config['schedule_enabled'] = False
|
||||||
config['permanent_hold'] = True
|
config['permanent_hold'] = True
|
||||||
@@ -362,6 +368,7 @@ class TempWebServer:
|
|||||||
return redirect_response
|
return redirect_response
|
||||||
|
|
||||||
elif mode_action == 'save_schedules':
|
elif mode_action == 'save_schedules':
|
||||||
|
gc.collect()
|
||||||
# Just fall through to schedule parsing below
|
# Just fall through to schedule parsing below
|
||||||
pass
|
pass
|
||||||
# ===== END: Handle mode actions =====
|
# ===== END: Handle mode actions =====
|
||||||
@@ -371,12 +378,6 @@ class TempWebServer:
|
|||||||
prev_schedules = prev.get('schedules', [])
|
prev_schedules = prev.get('schedules', [])
|
||||||
|
|
||||||
# ===== START: Handle schedule configuration save =====
|
# ===== START: Handle schedule configuration save =====
|
||||||
# DEBUG: Print what we received
|
|
||||||
print("DEBUG: Received POST body parameters:")
|
|
||||||
for key, value in params.items():
|
|
||||||
print(" {} = '{}'".format(key, value))
|
|
||||||
print("DEBUG: Total params received: {}".format(len(params)))
|
|
||||||
|
|
||||||
# Parse schedules (4 slots)
|
# Parse schedules (4 slots)
|
||||||
schedules = []
|
schedules = []
|
||||||
has_any_schedule_data = False
|
has_any_schedule_data = False
|
||||||
@@ -483,8 +484,6 @@ class TempWebServer:
|
|||||||
'heater_target': heater_target
|
'heater_target': heater_target
|
||||||
}
|
}
|
||||||
schedules.append(schedule)
|
schedules.append(schedule)
|
||||||
print("DEBUG: Parsed schedule {}: time='{}', name='{}', heater={}, ac={}".format(
|
|
||||||
i, schedule_time, schedule_name, heater_target, ac_target))
|
|
||||||
|
|
||||||
# Only update schedules if user submitted schedule form data
|
# Only update schedules if user submitted schedule form data
|
||||||
if has_any_schedule_data:
|
if has_any_schedule_data:
|
||||||
@@ -538,7 +537,8 @@ class TempWebServer:
|
|||||||
if heater_monitor:
|
if heater_monitor:
|
||||||
heater_monitor.target_temp = config['heater_target']
|
heater_monitor.target_temp = config['heater_target']
|
||||||
heater_monitor.temp_swing = config['heater_swing']
|
heater_monitor.temp_swing = config['heater_swing']
|
||||||
|
del params, prev_schedules, prev
|
||||||
|
gc.collect()
|
||||||
# Send Discord notification
|
# Send Discord notification
|
||||||
try:
|
try:
|
||||||
mode = "automatic" if config.get('schedule_enabled') else "hold"
|
mode = "automatic" if config.get('schedule_enabled') else "hold"
|
||||||
@@ -549,7 +549,8 @@ class TempWebServer:
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
# ===== END: Handle schedule configuration save =====
|
# ===== END: Handle schedule configuration save =====
|
||||||
|
del schedules
|
||||||
|
gc.collect()
|
||||||
# Redirect back to homepage with cache-busting headers
|
# Redirect back to homepage with cache-busting headers
|
||||||
redirect_response = 'HTTP/1.1 303 See Other\r\n'
|
redirect_response = 'HTTP/1.1 303 See Other\r\n'
|
||||||
redirect_response += 'Location: /\r\n'
|
redirect_response += 'Location: /\r\n'
|
||||||
@@ -560,6 +561,7 @@ class TempWebServer:
|
|||||||
redirect_response += 'Expires: 0\r\n'
|
redirect_response += 'Expires: 0\r\n'
|
||||||
redirect_response += '\r\n'
|
redirect_response += '\r\n'
|
||||||
print("DEBUG: Returning redirect to dashboard (with cache-busting)")
|
print("DEBUG: Returning redirect to dashboard (with cache-busting)")
|
||||||
|
gc.collect()
|
||||||
return redirect_response
|
return redirect_response
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -1343,6 +1345,8 @@ document.addEventListener('DOMContentLoaded', function() {{
|
|||||||
def _get_schedule_editor_page(self, sensors, ac_monitor, heater_monitor):
|
def _get_schedule_editor_page(self, sensors, ac_monitor, heater_monitor):
|
||||||
"""Generate schedule editor page (no auto-refresh, schedules only)."""
|
"""Generate schedule editor page (no auto-refresh, schedules only)."""
|
||||||
# Get current temps (read if not cached)
|
# Get current temps (read if not cached)
|
||||||
|
import gc # type: ignore
|
||||||
|
gc.collect()
|
||||||
inside_temp = getattr(sensors.get('inside'), 'last_temp', None)
|
inside_temp = getattr(sensors.get('inside'), 'last_temp', None)
|
||||||
if inside_temp is None:
|
if inside_temp is None:
|
||||||
inside_temps = sensors['inside'].read_all_temps(unit='F')
|
inside_temps = sensors['inside'].read_all_temps(unit='F')
|
||||||
@@ -1410,8 +1414,6 @@ document.addEventListener('DOMContentLoaded', function() {{
|
|||||||
# Add required attribute to force validation
|
# Add required attribute to force validation
|
||||||
schedule_inputs += "<input type=\"number\" name=\"schedule_" + str(i) + "_ac\" value=\"" + str(ac_value) + "\" step=\"0.5\" min=\"60\" max=\"90\" required oninput=\"schedSync(" + str(i) + ", 'ac')\" onchange=\"schedSync(" + str(i) + ", 'ac')\">\n"
|
schedule_inputs += "<input type=\"number\" name=\"schedule_" + str(i) + "_ac\" value=\"" + str(ac_value) + "\" step=\"0.5\" min=\"60\" max=\"90\" required oninput=\"schedSync(" + str(i) + ", 'ac')\" onchange=\"schedSync(" + str(i) + ", 'ac')\">\n"
|
||||||
schedule_inputs += '</div>\n'
|
schedule_inputs += '</div>\n'
|
||||||
|
|
||||||
print("DEBUG: HTML generated, length now: {} bytes".format(len(schedule_inputs)))
|
|
||||||
|
|
||||||
html = """
|
html = """
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
@@ -1616,7 +1618,8 @@ document.addEventListener('DOMContentLoaded', function() {{
|
|||||||
def _get_settings_page(self, sensors, ac_monitor, heater_monitor):
|
def _get_settings_page(self, sensors, ac_monitor, heater_monitor):
|
||||||
"""Generate advanced settings page."""
|
"""Generate advanced settings page."""
|
||||||
config = self._load_config()
|
config = self._load_config()
|
||||||
|
import gc # type: ignore
|
||||||
|
gc.collect()
|
||||||
# Get temperatures (read if not cached)
|
# Get temperatures (read if not cached)
|
||||||
inside_temp = getattr(sensors.get('inside'), 'last_temp', None)
|
inside_temp = getattr(sensors.get('inside'), 'last_temp', None)
|
||||||
if inside_temp is None:
|
if inside_temp is None:
|
||||||
@@ -1791,6 +1794,8 @@ document.addEventListener('DOMContentLoaded', function() {{
|
|||||||
|
|
||||||
def _handle_settings_update(self, request, sensors, ac_monitor, heater_monitor, schedule_monitor, config):
|
def _handle_settings_update(self, request, sensors, ac_monitor, heater_monitor, schedule_monitor, config):
|
||||||
"""Handle advanced settings update."""
|
"""Handle advanced settings update."""
|
||||||
|
import gc # type: ignore
|
||||||
|
gc.collect()
|
||||||
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 ''
|
||||||
params = {}
|
params = {}
|
||||||
@@ -1857,4 +1862,5 @@ document.addEventListener('DOMContentLoaded', function() {{
|
|||||||
redirect_response += 'Content-Length: 0\r\n'
|
redirect_response += 'Content-Length: 0\r\n'
|
||||||
redirect_response += 'Connection: close\r\n'
|
redirect_response += 'Connection: close\r\n'
|
||||||
redirect_response += '\r\n'
|
redirect_response += '\r\n'
|
||||||
|
gc.collect()
|
||||||
return redirect_response
|
return redirect_response
|
||||||
210
main.py
210
main.py
@@ -201,7 +201,7 @@ if wifi and wifi.isconnected():
|
|||||||
gc.collect()
|
gc.collect()
|
||||||
ram_free = gc.mem_free()
|
ram_free = gc.mem_free()
|
||||||
print(f"DEBUG: Free RAM before Discord send: {ram_free // 1024} KB")
|
print(f"DEBUG: Free RAM before Discord send: {ram_free // 1024} KB")
|
||||||
mem_ok = ram_free > 105000
|
mem_ok = ram_free > 95000
|
||||||
if mem_ok:
|
if mem_ok:
|
||||||
ok = discord_webhook.send_discord_message("Pico W online at http://{}".format(ifconfig[0]), debug=False)
|
ok = discord_webhook.send_discord_message("Pico W online at http://{}".format(ifconfig[0]), debug=False)
|
||||||
if ok:
|
if ok:
|
||||||
@@ -369,95 +369,119 @@ retry_ntp_attempts = 0
|
|||||||
max_ntp_attempts = 5 # Try up to 5 times after initial failure
|
max_ntp_attempts = 5 # Try up to 5 times after initial failure
|
||||||
last_ntp_sync = time.time() # Track when we last synced
|
last_ntp_sync = time.time() # Track when we last synced
|
||||||
|
|
||||||
# ===== START: Main Loop =====
|
try:
|
||||||
# Main monitoring loop (runs forever until Ctrl+C)
|
while True:
|
||||||
last_monitor_run = {
|
|
||||||
"wifi": 0,
|
# ===== START: Main Loop =====
|
||||||
"schedule": 0,
|
# Main monitoring loop (runs forever until Ctrl+C)
|
||||||
"ac": 0,
|
last_monitor_run = {
|
||||||
"heater": 0,
|
"wifi": 0,
|
||||||
"inside_temp": 0,
|
"schedule": 0,
|
||||||
"outside_temp": 0,
|
"ac": 0,
|
||||||
}
|
"heater": 0,
|
||||||
|
"inside_temp": 0,
|
||||||
while True:
|
"outside_temp": 0,
|
||||||
now = time.time()
|
}
|
||||||
|
|
||||||
# WiFi monitor every 5 seconds (can be stateless)
|
while True:
|
||||||
if now - last_monitor_run["wifi"] >= 5:
|
now = time.time()
|
||||||
from scripts.monitors import WiFiMonitor
|
|
||||||
wifi_monitor = WiFiMonitor(wifi, led, interval=5, reconnect_cooldown=60, config=config)
|
# WiFi monitor every 5 seconds (can be stateless)
|
||||||
try:
|
if now - last_monitor_run["wifi"] >= 5:
|
||||||
wifi_monitor.run()
|
from scripts.monitors import WiFiMonitor
|
||||||
except Exception as e:
|
wifi_monitor = WiFiMonitor(wifi, led, interval=5, reconnect_cooldown=60, config=config)
|
||||||
print("WiFiMonitor error:", e)
|
try:
|
||||||
del wifi_monitor
|
wifi_monitor.run()
|
||||||
gc.collect()
|
except Exception as e:
|
||||||
last_monitor_run["wifi"] = now
|
print("WiFiMonitor error:", e)
|
||||||
|
del wifi_monitor
|
||||||
# Schedule monitor every 60 seconds (persistent)
|
gc.collect()
|
||||||
if now - last_monitor_run["schedule"] >= 60:
|
last_monitor_run["wifi"] = now
|
||||||
try:
|
|
||||||
schedule_monitor.run()
|
# Schedule monitor every 60 seconds (persistent)
|
||||||
except Exception as e:
|
if now - last_monitor_run["schedule"] >= 60:
|
||||||
print("ScheduleMonitor error:", e)
|
try:
|
||||||
last_monitor_run["schedule"] = now
|
schedule_monitor.run()
|
||||||
|
except Exception as e:
|
||||||
# AC monitor every 30 seconds (persistent)
|
print("ScheduleMonitor error:", e)
|
||||||
if now - last_monitor_run["ac"] >= 30:
|
last_monitor_run["schedule"] = now
|
||||||
try:
|
|
||||||
ac_monitor.run()
|
# AC monitor every 30 seconds (persistent)
|
||||||
except Exception as e:
|
if now - last_monitor_run["ac"] >= 30:
|
||||||
print("ACMonitor error:", e)
|
try:
|
||||||
last_monitor_run["ac"] = now
|
ac_monitor.run()
|
||||||
|
except Exception as e:
|
||||||
# Heater monitor every 30 seconds (persistent)
|
print("ACMonitor error:", e)
|
||||||
if now - last_monitor_run["heater"] >= 30:
|
last_monitor_run["ac"] = now
|
||||||
try:
|
|
||||||
heater_monitor.run()
|
# Heater monitor every 30 seconds (persistent)
|
||||||
except Exception as e:
|
if now - last_monitor_run["heater"] >= 30:
|
||||||
print("HeaterMonitor error:", e)
|
try:
|
||||||
last_monitor_run["heater"] = now
|
heater_monitor.run()
|
||||||
|
except Exception as e:
|
||||||
# Inside temperature monitor every 10 seconds (can be stateless)
|
print("HeaterMonitor error:", e)
|
||||||
if now - last_monitor_run["inside_temp"] >= 10:
|
last_monitor_run["heater"] = now
|
||||||
from scripts.monitors import TemperatureMonitor
|
|
||||||
inside_monitor = TemperatureMonitor(
|
# Inside temperature monitor every 10 seconds (can be stateless)
|
||||||
sensor=sensors['inside'],
|
if now - last_monitor_run["inside_temp"] >= 10:
|
||||||
label=SENSOR_CONFIG['inside']['label'],
|
from scripts.monitors import TemperatureMonitor
|
||||||
check_interval=10,
|
inside_monitor = TemperatureMonitor(
|
||||||
report_interval=30,
|
sensor=sensors['inside'],
|
||||||
alert_high=SENSOR_CONFIG['inside']['alert_high'],
|
label=SENSOR_CONFIG['inside']['label'],
|
||||||
alert_low=SENSOR_CONFIG['inside']['alert_low'],
|
check_interval=10,
|
||||||
log_file="/temp_logs.csv",
|
report_interval=30,
|
||||||
send_alerts_to_separate_channel=True
|
alert_high=SENSOR_CONFIG['inside']['alert_high'],
|
||||||
)
|
alert_low=SENSOR_CONFIG['inside']['alert_low'],
|
||||||
inside_monitor.run()
|
log_file="/temp_logs.csv",
|
||||||
del inside_monitor
|
send_alerts_to_separate_channel=True
|
||||||
gc.collect()
|
)
|
||||||
last_monitor_run["inside_temp"] = now
|
inside_monitor.run()
|
||||||
|
del inside_monitor
|
||||||
# Outside temperature monitor every 10 seconds (can be stateless)
|
gc.collect()
|
||||||
if now - last_monitor_run["outside_temp"] >= 10:
|
last_monitor_run["inside_temp"] = now
|
||||||
from scripts.monitors import TemperatureMonitor
|
|
||||||
outside_monitor = TemperatureMonitor(
|
# Outside temperature monitor every 10 seconds (can be stateless)
|
||||||
sensor=sensors['outside'],
|
if now - last_monitor_run["outside_temp"] >= 10:
|
||||||
label=SENSOR_CONFIG['outside']['label'],
|
from scripts.monitors import TemperatureMonitor
|
||||||
check_interval=10,
|
outside_monitor = TemperatureMonitor(
|
||||||
report_interval=30,
|
sensor=sensors['outside'],
|
||||||
alert_high=SENSOR_CONFIG['outside']['alert_high'],
|
label=SENSOR_CONFIG['outside']['label'],
|
||||||
alert_low=SENSOR_CONFIG['outside']['alert_low'],
|
check_interval=10,
|
||||||
log_file="/temp_logs.csv",
|
report_interval=30,
|
||||||
send_alerts_to_separate_channel=False
|
alert_high=SENSOR_CONFIG['outside']['alert_high'],
|
||||||
)
|
alert_low=SENSOR_CONFIG['outside']['alert_low'],
|
||||||
outside_monitor.run()
|
log_file="/temp_logs.csv",
|
||||||
del outside_monitor
|
send_alerts_to_separate_channel=False
|
||||||
gc.collect()
|
)
|
||||||
last_monitor_run["outside_temp"] = now
|
outside_monitor.run()
|
||||||
|
del outside_monitor
|
||||||
# Web requests (keep web server loaded if needed)
|
gc.collect()
|
||||||
web_server.check_requests(sensors, ac_monitor, heater_monitor, schedule_monitor, config)
|
last_monitor_run["outside_temp"] = now
|
||||||
|
|
||||||
gc.collect()
|
# Web requests (keep web server loaded if needed)
|
||||||
time.sleep(0.1)
|
web_server.check_requests(sensors, ac_monitor, heater_monitor, schedule_monitor, config)
|
||||||
# ===== END: Main Loop =====
|
|
||||||
|
gc.collect()
|
||||||
|
time.sleep(0.1)
|
||||||
|
# ===== END: Main Loop =====
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\n" + "="*50)
|
||||||
|
print("Shutting down gracefully...")
|
||||||
|
print("="*50)
|
||||||
|
try:
|
||||||
|
print("Turning off AC...")
|
||||||
|
ac_controller.turn_off()
|
||||||
|
except Exception as e:
|
||||||
|
print("AC shutdown error:", e)
|
||||||
|
try:
|
||||||
|
print("Turning off heater...")
|
||||||
|
heater_controller.turn_off()
|
||||||
|
except Exception as e:
|
||||||
|
print("Heater shutdown error:", e)
|
||||||
|
try:
|
||||||
|
print("Turning off LED...")
|
||||||
|
led.low()
|
||||||
|
except Exception as e:
|
||||||
|
print("LED shutdown error:", e)
|
||||||
|
print("Shutdown complete!")
|
||||||
|
print("="*50)
|
||||||
Reference in New Issue
Block a user