Add example configuration file, moved everything from secrets.py to here.

Feat: Also refactored some of the logic in discord_webhook.py and networking.py to be more friendly towards the pico with ram usage.

Fixes #26
This commit is contained in:
Aaron 2025-11-14 16:50:53 -05:00
parent a20bbd7cdf
commit d95f212d2e
8 changed files with 266 additions and 134 deletions

View File

@ -1,8 +1,23 @@
import urequests as requests # type: ignore # Minimal module-level state (only what we need)
from secrets import secrets _CONFIG = {"discord_webhook_url": None, "discord_alert_webhook_url": None}
def set_config(cfg: dict):
"""Initialize module with minimal values from loaded config (call from main)."""
global _CONFIG
if not cfg:
_CONFIG = {"discord_webhook_url": None, "discord_alert_webhook_url": None}
return
_CONFIG = {
"discord_webhook_url": cfg.get("discord_webhook_url"),
"discord_alert_webhook_url": cfg.get("discord_alert_webhook_url"),
}
def _get_webhook_url(is_alert: bool = False):
if is_alert:
return _CONFIG.get("discord_alert_webhook_url") or _CONFIG.get("discord_webhook_url")
return _CONFIG.get("discord_webhook_url")
def _escape_json_str(s: str) -> str: def _escape_json_str(s: str) -> str:
# minimal JSON string escaper for quotes/backslashes and control chars
s = s.replace("\\", "\\\\") s = s.replace("\\", "\\\\")
s = s.replace('"', '\\"') s = s.replace('"', '\\"')
s = s.replace("\n", "\\n") s = s.replace("\n", "\\n")
@ -11,44 +26,52 @@ def _escape_json_str(s: str) -> str:
return s return s
def send_discord_message(message, username="Auto Garden Bot", is_alert=False): def send_discord_message(message, username="Auto Garden Bot", is_alert=False):
"""Send Discord message with 3-second timeout to prevent blocking.""" """
Send Discord message. Import urequests locally to avoid occupying RAM when idle.
Returns True on success, False otherwise.
"""
resp = None resp = None
url = _get_webhook_url(is_alert=is_alert)
# Use alert webhook if specified, otherwise normal webhook if not url:
if is_alert: return False
url = secrets.get('discord_alert_webhook_url') or secrets.get('discord_webhook_url')
else:
url = secrets.get('discord_webhook_url')
try: try:
if not url: # local import to save RAM
return False import urequests as requests # type: ignore
import gc
url = url.strip().strip('\'"') url = str(url).strip().strip('\'"')
# Build JSON manually to preserve emoji/unicode as UTF-8
content = _escape_json_str(message) content = _escape_json_str(message)
user = _escape_json_str(username) user = _escape_json_str(username)
body_bytes = ('{"content":"%s","username":"%s"}' % (content, user)).encode("utf-8") body_bytes = ('{"content":"%s","username":"%s"}' % (content, user)).encode("utf-8")
headers = {"Content-Type": "application/json; charset=utf-8"} headers = {"Content-Type": "application/json; charset=utf-8"}
# Make POST request (urequests has built-in ~5s timeout)
resp = requests.post(url, data=body_bytes, headers=headers) resp = requests.post(url, data=body_bytes, headers=headers)
status = getattr(resp, "status", getattr(resp, "status_code", None)) status = getattr(resp, "status", getattr(resp, "status_code", None))
success = bool(status and 200 <= status < 300)
if status and 200 <= status < 300: if not success:
return True # optional: print status for debugging, but avoid spamming
else: print("Discord webhook failed, status:", status)
return False return success
except Exception as e: except Exception as e:
# Silently fail (don't spam console with Discord errors) # avoid raising to prevent crashing monitors; print minimal info
print("Discord webhook exception:", e)
return False return False
finally: finally:
if resp: try:
try: if resp:
resp.close() resp.close()
except: except:
pass pass
# free large objects and modules, then force GC
try:
del resp
except:
pass
try:
gc.collect()
except:
pass

View File

@ -1,5 +1,5 @@
import time # type: ignore import time # type: ignore
from scripts.discord_webhook import send_discord_message import scripts.discord_webhook as discord_webhook
from scripts.temperature_sensor import TemperatureSensor from scripts.temperature_sensor import TemperatureSensor
class Monitor: class Monitor:
@ -108,13 +108,11 @@ class TemperatureMonitor(Monitor):
self.alert_start_time = current_time self.alert_start_time = current_time
print(alert_message) print(alert_message)
# Send to appropriate Discord channel # send alert (use module-level discord_webhook; set_config must be called in main)
if self.send_alerts_to_separate_channel: if self.send_alerts_to_separate_channel:
from scripts.discord_webhook import send_discord_message discord_webhook.send_discord_message(alert_message, is_alert=True)
send_discord_message(alert_message, is_alert=True)
else: else:
from scripts.discord_webhook import send_discord_message discord_webhook.send_discord_message(alert_message)
send_discord_message(alert_message)
self.alert_sent = True self.alert_sent = True
@ -139,13 +137,11 @@ class TemperatureMonitor(Monitor):
) )
print(recovery_message) print(recovery_message)
# Send to appropriate Discord channel # send recovery message
if self.send_alerts_to_separate_channel: if self.send_alerts_to_separate_channel:
from scripts.discord_webhook import send_alert_message discord_webhook.send_discord_message(recovery_message, is_alert=True)
send_alert_message(recovery_message)
else: else:
from scripts.discord_webhook import send_discord_message discord_webhook.send_discord_message(recovery_message)
send_discord_message(recovery_message)
self.alert_sent = False self.alert_sent = False
self.alert_start_time = None self.alert_start_time = None
@ -198,14 +194,14 @@ class ACMonitor(Monitor):
# Too hot, turn AC on # Too hot, turn AC on
if self.ac.turn_on(): if self.ac.turn_on():
if not self.last_notified_state: if not self.last_notified_state:
send_discord_message(f"❄️ AC turned ON - Current: {current_temp:.1f}°F, Target: {self.target_temp:.1f}°F") discord_webhook.send_discord_message(f"❄️ AC turned ON - Current: {current_temp:.1f}°F, Target: {self.target_temp:.1f}°F")
self.last_notified_state = True self.last_notified_state = True
elif current_temp < (self.target_temp - self.temp_swing): elif current_temp < (self.target_temp - self.temp_swing):
# Cool enough, turn AC off # Cool enough, turn AC off
if self.ac.turn_off(): if self.ac.turn_off():
if self.last_notified_state: if self.last_notified_state:
send_discord_message(f"✅ AC turned OFF - Current: {current_temp:.1f}°F, Target: {self.target_temp:.1f}°F") discord_webhook.send_discord_message(f"✅ AC turned OFF - Current: {current_temp:.1f}°F, Target: {self.target_temp:.1f}°F")
self.last_notified_state = False self.last_notified_state = False
# Else: within temp_swing range, maintain current state # Else: within temp_swing range, maintain current state
@ -244,27 +240,28 @@ class HeaterMonitor(Monitor):
# Too cold, turn heater on # Too cold, turn heater on
if self.heater.turn_on(): if self.heater.turn_on():
if not self.last_notified_state: if not self.last_notified_state:
send_discord_message(f"🔥 Heater turned ON - Current: {current_temp:.1f}°F, Target: {self.target_temp:.1f}°F") discord_webhook.send_discord_message(f"🔥 Heater turned ON - Current: {current_temp:.1f}°F, Target: {self.target_temp:.1f}°F")
self.last_notified_state = True self.last_notified_state = True
elif current_temp > (self.target_temp + self.temp_swing): elif current_temp > (self.target_temp + self.temp_swing):
# Warm enough, turn heater off # Warm enough, turn heater off
if self.heater.turn_off(): if self.heater.turn_off():
if self.last_notified_state: if self.last_notified_state:
send_discord_message(f"✅ Heater turned OFF - Current: {current_temp:.1f}°F, Target: {self.target_temp:.1f}°F") discord_webhook.send_discord_message(f"✅ Heater turned OFF - Current: {current_temp:.1f}°F, Target: {self.target_temp:.1f}°F")
self.last_notified_state = False self.last_notified_state = False
# Else: within temp_swing range, maintain current state # Else: within temp_swing range, maintain current state
class WiFiMonitor(Monitor): class WiFiMonitor(Monitor):
"""Monitor WiFi connection and handle reconnection.""" """Monitor WiFi connection and handle reconnection."""
def __init__(self, wifi, led, interval=5, reconnect_cooldown=60): def __init__(self, wifi, led, interval=5, reconnect_cooldown=60, config=None):
super().__init__(interval) super().__init__(interval)
self.wifi = wifi self.wifi = wifi
self.led = led self.led = led
self.reconnect_cooldown = reconnect_cooldown self.reconnect_cooldown = reconnect_cooldown
self.last_reconnect_attempt = 0 self.last_reconnect_attempt = 0
self.was_connected = wifi.isconnected() if wifi else False self.was_connected = wifi.isconnected() if wifi else False
self.config = config
def run(self): def run(self):
"""Check WiFi status, blink LED, attempt reconnect if needed.""" """Check WiFi status, blink LED, attempt reconnect if needed."""
@ -284,10 +281,10 @@ class WiFiMonitor(Monitor):
if time.ticks_diff(now, self.last_reconnect_attempt) >= (self.reconnect_cooldown * 1000): if time.ticks_diff(now, self.last_reconnect_attempt) >= (self.reconnect_cooldown * 1000):
self.last_reconnect_attempt = now self.last_reconnect_attempt = now
# print("Attempting WiFi reconnect...") # print("Attempting WiFi reconnect...")
self.wifi = connect_wifi(self.led) self.wifi = connect_wifi(self.led, config=self.config)
if self.wifi and self.wifi.isconnected(): if self.wifi and self.wifi.isconnected():
send_discord_message("WiFi connection restored 🔄") discord_webhook.send_discord_message("WiFi connection restored 🔄")
self.was_connected = True self.was_connected = True
else: else:
# Slow blink when connected # Slow blink when connected
@ -297,7 +294,7 @@ class WiFiMonitor(Monitor):
# Notify if connection was just restored # Notify if connection was just restored
if not self.was_connected: if not self.was_connected:
send_discord_message("WiFi connection restored 🔄") discord_webhook.send_discord_message("WiFi connection restored 🔄")
self.was_connected = True self.was_connected = True
def run_monitors(monitors): def run_monitors(monitors):

View File

@ -1,30 +1,43 @@
import network import network
import time import time
from secrets import secrets
def connect_wifi(led=None, max_retries=3, timeout=20): def connect_wifi(led=None, max_retries=3, timeout=20, config=None):
""" """
Connect to WiFi using credentials from secrets.py Connect to WiFi using credentials from provided config dict.
Args: Args:
led: Optional LED pin for visual feedback led: Optional LED pin for visual feedback
max_retries: Number of connection attempts (default: 3) max_retries: Number of connection attempts (default: 3)
timeout: Seconds to wait for connection per attempt (default: 20) timeout: Seconds to wait for connection per attempt (default: 20)
config: Dict loaded from config.json, must contain config['wifi'] with 'ssid' and 'password'
Returns: Returns:
WLAN object if connected, None if failed WLAN object if connected, None if failed
""" """
if config is None:
print("connect_wifi: config is required")
return None
wifi_cfg = config.get('wifi') or {}
# support either config['wifi'] = {'ssid','password'} OR top-level 'ssid'/'password'
ssid = wifi_cfg.get('ssid') or config.get('ssid')
password = wifi_cfg.get('password') or config.get('password')
if not ssid or not password:
print("connect_wifi: missing wifi credentials in config['wifi']")
return None
wlan = network.WLAN(network.STA_IF) wlan = network.WLAN(network.STA_IF)
# Ensure clean state # Ensure clean state
try: try:
if wlan.active(): if wlan.active():
wlan.active(False) wlan.active(False)
time.sleep(1) time.sleep(1)
wlan.active(True) wlan.active(True)
time.sleep(1) time.sleep(1)
except OSError as e: except OSError as e:
print(f"WiFi activation error: {e}") print(f"WiFi activation error: {e}")
print("Attempting reset...") print("Attempting reset...")
@ -37,66 +50,83 @@ def connect_wifi(led=None, max_retries=3, timeout=20):
except Exception as e2: except Exception as e2:
print(f"WiFi reset failed: {e2}") print(f"WiFi reset failed: {e2}")
return None return None
# Try connecting with retries # Try connecting with retries
for attempt in range(1, max_retries + 1): for attempt in range(1, max_retries + 1):
if wlan.isconnected(): if wlan.isconnected():
print(f"Already connected to WiFi") print("Already connected to WiFi")
break break
print(f'Connecting to WiFi (attempt {attempt}/{max_retries})...') print(f'Connecting to WiFi SSID: {ssid} (attempt {attempt}/{max_retries})...')
try: try:
wlan.connect(secrets['ssid'], secrets['password']) wlan.connect(ssid, password)
except Exception as e: except Exception as e:
print(f"Connection attempt failed: {e}") print(f"Connection attempt failed: {e}")
if attempt < max_retries: if attempt < max_retries:
print("Retrying in 3 seconds...") print("Retrying in 3 seconds...")
time.sleep(3) time.sleep(3)
continue continue
# Wait for connection with timeout # Wait for connection with timeout
wait_time = 0 wait_time = 0
while wait_time < timeout: while wait_time < timeout:
if wlan.isconnected(): if wlan.isconnected():
break break
if led: if led:
led.toggle() try:
# some LED wrappers use toggle(), others use on/off
if hasattr(led, "toggle"):
led.toggle()
else:
# flash quickly to show activity
led.on()
time.sleep(0.05)
led.off()
except Exception:
pass
time.sleep(0.5) time.sleep(0.5)
wait_time += 0.5 wait_time += 0.5
# Print progress dots every 2 seconds # Print progress dots every 2 seconds
if int(wait_time * 2) % 4 == 0: if int(wait_time * 2) % 4 == 0:
print('.', end='') print('.', end='')
print() # New line after dots print() # New line after dots
if wlan.isconnected(): if wlan.isconnected():
break break
print(f'Connection attempt {attempt} failed') print(f'Connection attempt {attempt} failed')
if attempt < max_retries: if attempt < max_retries:
print("Retrying in 3 seconds...") print("Retrying in 3 seconds...")
time.sleep(3) time.sleep(3)
# Final connection check # Final connection check
if not wlan.isconnected(): if not wlan.isconnected():
print('WiFi connection failed after all attempts!') print('WiFi connection failed after all attempts!')
if led: if led:
led.off() try:
# prefer available method names
if hasattr(led, "off"):
led.off()
except Exception:
pass
return None return None
# Success feedback # Success feedback
if led: if led:
# Double pulse on successful connection try:
for _ in range(2): for _ in range(2):
led.on() led.on()
time.sleep(0.2) time.sleep(0.2)
led.off() led.off()
time.sleep(0.2) time.sleep(0.2)
except Exception:
pass
print('Connected to WiFi successfully!') print('Connected to WiFi successfully!')
return wlan return wlan

View File

@ -90,32 +90,61 @@ class ScheduleMonitor:
return # Already applied return # Already applied
try: try:
# Track whether we changed persisted values to avoid unnecessary writes
changed = False
# Update AC settings if provided # Update AC settings if provided
if 'ac_target' in schedule: if 'ac_target' in schedule:
self.ac_monitor.target_temp = float(schedule['ac_target']) new_ac = float(schedule['ac_target'])
self.config['ac_target'] = float(schedule['ac_target']) if self.config.get('ac_target') != new_ac:
self.config['ac_target'] = new_ac
changed = True
self.ac_monitor.target_temp = new_ac
if 'ac_swing' in schedule: if 'ac_swing' in schedule:
self.ac_monitor.temp_swing = float(schedule['ac_swing']) new_ac_swing = float(schedule['ac_swing'])
self.config['ac_swing'] = float(schedule['ac_swing']) if self.config.get('ac_swing') != new_ac_swing:
self.config['ac_swing'] = new_ac_swing
changed = True
self.ac_monitor.temp_swing = new_ac_swing
# Update heater settings if provided # Update heater settings if provided
if 'heater_target' in schedule: if 'heater_target' in schedule:
self.heater_monitor.target_temp = float(schedule['heater_target']) new_ht = float(schedule['heater_target'])
self.config['heater_target'] = float(schedule['heater_target']) if self.config.get('heater_target') != new_ht:
self.config['heater_target'] = new_ht
changed = True
self.heater_monitor.target_temp = new_ht
if 'heater_swing' in schedule: if 'heater_swing' in schedule:
self.heater_monitor.temp_swing = float(schedule['heater_swing']) new_ht_swing = float(schedule['heater_swing'])
self.config['heater_swing'] = float(schedule['heater_swing']) if self.config.get('heater_swing') != new_ht_swing:
self.config['heater_swing'] = new_ht_swing
changed = True
self.heater_monitor.temp_swing = new_ht_swing
# Save updated config to file so targets persist # Save updated config only if something changed
try: if changed:
import json try:
with open('config.json', 'w') as f: import json
json.dump(self.config, f) with open('config.json', 'w') as f:
print("✅ Config updated with active schedule targets") json.dump(self.config, f)
except Exception as e: except Exception as e:
print("⚠️ Could not save config: {}".format(e)) print("⚠️ Could not save config: {}".format(e))
else:
# import once and update module-level webhook config
try:
import scripts.discord_webhook as discord_webhook
discord_webhook.set_config(self.config)
except Exception:
pass
print("✅ Config updated with active schedule targets")
else:
# nothing to persist
try:
import scripts.discord_webhook as discord_webhook
except Exception:
discord_webhook = None
# Log the change # Log the change
schedule_name = schedule.get('name', 'Unnamed') schedule_name = schedule.get('name', 'Unnamed')
@ -125,21 +154,22 @@ class ScheduleMonitor:
print("AC Target: {}°F".format(self.ac_monitor.target_temp)) print("AC Target: {}°F".format(self.ac_monitor.target_temp))
print("Heater Target: {}°F".format(self.heater_monitor.target_temp)) print("Heater Target: {}°F".format(self.heater_monitor.target_temp))
print("="*50 + "\n") print("="*50 + "\n")
# Send Discord notification # Send Discord notification (use discord_webhook if available)
try: try:
from scripts.discord_webhook import send_discord_message if 'discord_webhook' not in locals() or discord_webhook is None:
import scripts.discord_webhook as discord_webhook
message = "🕐 Schedule '{}' applied - AC: {}°F | Heater: {}°F".format( message = "🕐 Schedule '{}' applied - AC: {}°F | Heater: {}°F".format(
schedule_name, schedule_name,
self.ac_monitor.target_temp, self.ac_monitor.target_temp,
self.heater_monitor.target_temp self.heater_monitor.target_temp
) )
send_discord_message(message) discord_webhook.send_discord_message(message)
except: except Exception:
pass pass
self.last_applied_schedule = schedule_id self.last_applied_schedule = schedule_id
except Exception as e: except Exception as e:
print("Error applying schedule: {}".format(e)) print("Error applying schedule: {}".format(e))
@ -165,16 +195,24 @@ class ScheduleMonitor:
import json import json
with open('config.json', 'w') as f: with open('config.json', 'w') as f:
json.dump(self.config, f) json.dump(self.config, f)
print("✅ Config updated - automatic mode resumed")
except Exception as e: except Exception as e:
print("⚠️ Could not save config: {}".format(e)) print("⚠️ Could not save config: {}".format(e))
else:
# Notify user # ensure in-memory webhook config updated
try: try:
from scripts.discord_webhook import send_discord_message import scripts.discord_webhook as discord_webhook
send_discord_message("⏰ Temporary hold expired - Schedule resumed automatically") discord_webhook.set_config(self.config)
except: except Exception:
pass pass
print("✅ Config updated - automatic mode resumed")
# Notify user
try:
import scripts.discord_webhook as discord_webhook
discord_webhook.send_discord_message("⏰ Temporary hold expired - Schedule resumed automatically")
except Exception:
pass
# ===== END: Check if temporary hold has expired ===== # ===== END: Check if temporary hold has expired =====
# Find and apply active schedule # Find and apply active schedule

View File

@ -1,6 +1,9 @@
import socket import socket
import time # type: ignore import time # type: ignore
import json import json
import scripts.discord_webhook as discord_webhook
import os
class TempWebServer: class TempWebServer:
"""Simple web server for viewing temperatures and adjusting settings.""" """Simple web server for viewing temperatures and adjusting settings."""
@ -106,8 +109,8 @@ class TempWebServer:
response_bytes = response.encode('utf-8') response_bytes = response.encode('utf-8')
# Send headers # Send headers
conn.send('HTTP/1.1 200 OK\r\n') conn.sendall(b'HTTP/1.1 200 OK\r\n')
conn.send('Content-Type: text/html; charset=utf-8\r\n') conn.sendall(b'Content-Type: text/html; charset=utf-8\r\n')
conn.send('Content-Length: {}\r\n'.format(len(response_bytes))) conn.send('Content-Length: {}\r\n'.format(len(response_bytes)))
conn.send('Connection: close\r\n') conn.send('Connection: close\r\n')
conn.send('\r\n') conn.send('\r\n')
@ -224,6 +227,12 @@ class TempWebServer:
# Rename temp to config (atomic on most filesystems) # Rename temp to config (atomic on most filesystems)
os.rename('config.tmp', 'config.json') os.rename('config.tmp', 'config.json')
# Update discord module in-memory config so webhook URLs are current
try:
discord_webhook.set_config(config)
except Exception:
pass
print("Settings saved to config.json") print("Settings saved to config.json")
return True return True
except Exception as e: except Exception as e:
@ -276,8 +285,7 @@ class TempWebServer:
# Send Discord notification # Send Discord notification
try: try:
from scripts.discord_webhook import send_discord_message discord_webhook.send_discord_message("▶️ Schedule resumed - Automatic temperature control active")
send_discord_message("▶️ Schedule resumed - Automatic temperature control active")
except: except:
pass pass
@ -302,8 +310,7 @@ class TempWebServer:
schedule_monitor.reload_config(config) schedule_monitor.reload_config(config)
try: try:
from scripts.discord_webhook import send_discord_message discord_webhook.send_discord_message("⏸️ Temporary hold - Schedules paused, manual control active")
send_discord_message("⏸️ Temporary hold - Schedules paused, manual control active")
except: except:
pass pass
@ -327,8 +334,7 @@ class TempWebServer:
schedule_monitor.reload_config(config) schedule_monitor.reload_config(config)
try: try:
from scripts.discord_webhook import send_discord_message discord_webhook.send_discord_message("🛑 Permanent hold - Schedules disabled, manual control only")
send_discord_message("🛑 Permanent hold - Schedules disabled, manual control only")
except: except:
pass pass
@ -490,12 +496,11 @@ class TempWebServer:
# Send Discord notification # Send Discord notification
try: try:
from scripts.discord_webhook import send_discord_message
mode = "automatic" if config.get('schedule_enabled') else "hold" mode = "automatic" if config.get('schedule_enabled') else "hold"
message = "📅 Schedules updated ({} mode) - {} schedules configured".format( message = "📅 Schedules updated ({} mode) - {} schedules configured".format(
mode, len(schedules) mode, len(schedules)
) )
send_discord_message(message) discord_webhook.send_discord_message(message)
except: except:
pass pass
# ===== END: Handle schedule configuration save ===== # ===== END: Handle schedule configuration save =====
@ -611,7 +616,6 @@ class TempWebServer:
# ===== START: Send Discord notification ===== # ===== START: Send Discord notification =====
try: try:
from scripts.discord_webhook import send_discord_message
hold_label = "PERMANENT HOLD" if is_permanent else "TEMPORARY HOLD" hold_label = "PERMANENT HOLD" if is_permanent else "TEMPORARY HOLD"
duration = "" if is_permanent else " (1 hour)" duration = "" if is_permanent else " (1 hour)"
@ -622,7 +626,7 @@ class TempWebServer:
params.get('heater_target', 'N/A'), params.get('heater_target', 'N/A'),
duration duration
) )
send_discord_message(message) discord_webhook.send_discord_message(message)
except Exception as discord_error: except Exception as discord_error:
print("Discord notification failed: {}".format(discord_error)) print("Discord notification failed: {}".format(discord_error))
# ===== END: Send Discord notification ===== # ===== END: Send Discord notification =====
@ -1754,8 +1758,7 @@ class TempWebServer:
# Discord notification # Discord notification
try: try:
from scripts.discord_webhook import send_discord_message discord_webhook.send_discord_message("⚙️ Advanced settings updated")
send_discord_message("⚙️ Advanced settings updated")
except: except:
pass pass

45
config.json.Example Normal file
View File

@ -0,0 +1,45 @@
{
"static_ip": "192.168.1.69",
"subnet": "255.255.255.0",
"gateway": "192.168.1.1",
"dns": "192.168.1.1",
"timezone_offset": -5,
"ssid": " Change_to_wifi_SSID",
"password": "Change_to_wifi_Pasword",
"discord_webhook_url": "https://discord.com/api/webhooks/key/long-Combination_1234-ChangeMe",
"discord_alert_webhook_url": "https://discord.com/api/webhooks/key/long-Combination_1234-ChangeMe",
"ac_target": 77.0,
"ac_swing": 1.0,
"heater_target": 72.0,
"heater_swing": 2.0,
"temp_hold_duration": 3600,
"schedules": [
{
"time": "06:00",
"ac_target": 75.0,
"heater_target": 72.0,
"name": "Morning"
},
{
"time": "12:00",
"ac_target": 75.0,
"heater_target": 72.0,
"name": "Midday"
},
{
"time": "18:00",
"ac_target": 75.0,
"heater_target": 72.0,
"name": "Evening"
},
{
"time": "22:00",
"ac_target": 75.0,
"heater_target": 72.0,
"name": "Night"
}
],
"schedule_enabled": true,
"permanent_hold": false,
"temp_hold_start_time": null
}

10
main.py
View File

@ -21,7 +21,7 @@ except Exception as e:
# Import after WiFi reset # Import after WiFi reset
from scripts.networking import connect_wifi from scripts.networking import connect_wifi
from scripts.discord_webhook import send_discord_message import scripts.discord_webhook as discord_webhook
from scripts.monitors import TemperatureMonitor, WiFiMonitor, ACMonitor, HeaterMonitor, run_monitors from scripts.monitors import TemperatureMonitor, WiFiMonitor, ACMonitor, HeaterMonitor, run_monitors
from scripts.temperature_sensor import TemperatureSensor from scripts.temperature_sensor import TemperatureSensor
from scripts.air_conditioning import ACController from scripts.air_conditioning import ACController
@ -146,6 +146,8 @@ def load_config():
# Load configuration from file # Load configuration from file
config = load_config() config = load_config()
# Initialize discord webhook module with loaded config (must be done BEFORE any send_discord_message calls)
discord_webhook.set_config(config)
# Get timezone offset from config (with fallback to -6 if not present) # Get timezone offset from config (with fallback to -6 if not present)
TIMEZONE_OFFSET = config.get('timezone_offset', -6) TIMEZONE_OFFSET = config.get('timezone_offset', -6)
@ -171,7 +173,7 @@ except Exception as e:
# ===== START: WiFi Connection ===== # ===== START: WiFi Connection =====
# Connect to WiFi using credentials from secrets.py # Connect to WiFi using credentials from secrets.py
wifi = connect_wifi(led) wifi = connect_wifi(led, config=config)
# Set static IP and print WiFi details # Set static IP and print WiFi details
if wifi and wifi.isconnected(): if wifi and wifi.isconnected():
@ -199,7 +201,7 @@ if wifi and wifi.isconnected():
# Send startup notification to Discord (with timeout, non-blocking) # Send startup notification to Discord (with timeout, non-blocking)
try: try:
success = send_discord_message(f"Pico W online at http://{ifconfig[0]}") success = discord_webhook.send_discord_message(f"Pico W online at http://{ifconfig[0]}")
if success: if success:
print("Discord startup notification sent") print("Discord startup notification sent")
else: else:
@ -345,7 +347,7 @@ check_memory_once()
# Set up all monitoring systems (run in order during main loop) # Set up all monitoring systems (run in order during main loop)
monitors = [ monitors = [
# WiFi monitor: Checks connection, reconnects if needed, blinks LED # WiFi monitor: Checks connection, reconnects if needed, blinks LED
WiFiMonitor(wifi, led, interval=5, reconnect_cooldown=60), WiFiMonitor(wifi, led, interval=5, reconnect_cooldown=60, config=config),
# Schedule monitor: Changes temp targets based on time of day # Schedule monitor: Changes temp targets based on time of day
schedule_monitor, schedule_monitor,

View File

@ -1,6 +0,0 @@
secrets = {
'ssid': ' Change_to_wifi_SSID',
'password': 'Change_to_wifi_Pasword',
'discord_webhook_url': 'https://discord.com/api/webhooks/key/long-Combination_1234-ChangeMe', # normal updates
'discord_alert_webhook_url': 'https://discord.com/api/webhooks/key/long-Combination_1234-ChangeMe', # alerts only
}