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:
@@ -1,8 +1,23 @@
|
||||
import urequests as requests # type: ignore
|
||||
from secrets import secrets
|
||||
# Minimal module-level state (only what we need)
|
||||
_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:
|
||||
# minimal JSON string escaper for quotes/backslashes and control chars
|
||||
s = s.replace("\\", "\\\\")
|
||||
s = s.replace('"', '\\"')
|
||||
s = s.replace("\n", "\\n")
|
||||
@@ -11,44 +26,52 @@ def _escape_json_str(s: str) -> str:
|
||||
return s
|
||||
|
||||
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
|
||||
|
||||
# Use alert webhook if specified, otherwise normal webhook
|
||||
if is_alert:
|
||||
url = secrets.get('discord_alert_webhook_url') or secrets.get('discord_webhook_url')
|
||||
else:
|
||||
url = secrets.get('discord_webhook_url')
|
||||
|
||||
url = _get_webhook_url(is_alert=is_alert)
|
||||
if not url:
|
||||
return False
|
||||
|
||||
try:
|
||||
if not url:
|
||||
return False
|
||||
# local import to save RAM
|
||||
import urequests as requests # type: ignore
|
||||
import gc
|
||||
|
||||
url = url.strip().strip('\'"')
|
||||
|
||||
# Build JSON manually to preserve emoji/unicode as UTF-8
|
||||
url = str(url).strip().strip('\'"')
|
||||
content = _escape_json_str(message)
|
||||
user = _escape_json_str(username)
|
||||
body_bytes = ('{"content":"%s","username":"%s"}' % (content, user)).encode("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)
|
||||
|
||||
status = getattr(resp, "status", getattr(resp, "status_code", None))
|
||||
|
||||
if status and 200 <= status < 300:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
success = bool(status and 200 <= status < 300)
|
||||
if not success:
|
||||
# optional: print status for debugging, but avoid spamming
|
||||
print("Discord webhook failed, status:", status)
|
||||
return success
|
||||
|
||||
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
|
||||
|
||||
finally:
|
||||
if resp:
|
||||
try:
|
||||
try:
|
||||
if resp:
|
||||
resp.close()
|
||||
except:
|
||||
pass
|
||||
except:
|
||||
pass
|
||||
# free large objects and modules, then force GC
|
||||
try:
|
||||
del resp
|
||||
except:
|
||||
pass
|
||||
try:
|
||||
gc.collect()
|
||||
except:
|
||||
pass
|
||||
@@ -1,5 +1,5 @@
|
||||
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
|
||||
|
||||
class Monitor:
|
||||
@@ -108,13 +108,11 @@ class TemperatureMonitor(Monitor):
|
||||
self.alert_start_time = current_time
|
||||
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:
|
||||
from scripts.discord_webhook import send_discord_message
|
||||
send_discord_message(alert_message, is_alert=True)
|
||||
discord_webhook.send_discord_message(alert_message, is_alert=True)
|
||||
else:
|
||||
from scripts.discord_webhook import send_discord_message
|
||||
send_discord_message(alert_message)
|
||||
discord_webhook.send_discord_message(alert_message)
|
||||
|
||||
self.alert_sent = True
|
||||
|
||||
@@ -139,13 +137,11 @@ class TemperatureMonitor(Monitor):
|
||||
)
|
||||
print(recovery_message)
|
||||
|
||||
# Send to appropriate Discord channel
|
||||
# send recovery message
|
||||
if self.send_alerts_to_separate_channel:
|
||||
from scripts.discord_webhook import send_alert_message
|
||||
send_alert_message(recovery_message)
|
||||
discord_webhook.send_discord_message(recovery_message, is_alert=True)
|
||||
else:
|
||||
from scripts.discord_webhook import send_discord_message
|
||||
send_discord_message(recovery_message)
|
||||
discord_webhook.send_discord_message(recovery_message)
|
||||
|
||||
self.alert_sent = False
|
||||
self.alert_start_time = None
|
||||
@@ -198,14 +194,14 @@ class ACMonitor(Monitor):
|
||||
# Too hot, turn AC on
|
||||
if self.ac.turn_on():
|
||||
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
|
||||
|
||||
elif current_temp < (self.target_temp - self.temp_swing):
|
||||
# Cool enough, turn AC off
|
||||
if self.ac.turn_off():
|
||||
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
|
||||
|
||||
# Else: within temp_swing range, maintain current state
|
||||
@@ -244,27 +240,28 @@ class HeaterMonitor(Monitor):
|
||||
# Too cold, turn heater on
|
||||
if self.heater.turn_on():
|
||||
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
|
||||
|
||||
elif current_temp > (self.target_temp + self.temp_swing):
|
||||
# Warm enough, turn heater off
|
||||
if self.heater.turn_off():
|
||||
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
|
||||
|
||||
# Else: within temp_swing range, maintain current state
|
||||
|
||||
class WiFiMonitor(Monitor):
|
||||
"""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)
|
||||
self.wifi = wifi
|
||||
self.led = led
|
||||
self.reconnect_cooldown = reconnect_cooldown
|
||||
self.last_reconnect_attempt = 0
|
||||
self.was_connected = wifi.isconnected() if wifi else False
|
||||
self.config = config
|
||||
|
||||
def run(self):
|
||||
"""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):
|
||||
self.last_reconnect_attempt = now
|
||||
# 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():
|
||||
send_discord_message("WiFi connection restored 🔄")
|
||||
discord_webhook.send_discord_message("WiFi connection restored 🔄")
|
||||
self.was_connected = True
|
||||
else:
|
||||
# Slow blink when connected
|
||||
@@ -297,7 +294,7 @@ class WiFiMonitor(Monitor):
|
||||
|
||||
# Notify if connection was just restored
|
||||
if not self.was_connected:
|
||||
send_discord_message("WiFi connection restored 🔄")
|
||||
discord_webhook.send_discord_message("WiFi connection restored 🔄")
|
||||
self.was_connected = True
|
||||
|
||||
def run_monitors(monitors):
|
||||
|
||||
@@ -1,30 +1,43 @@
|
||||
import network
|
||||
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:
|
||||
led: Optional LED pin for visual feedback
|
||||
max_retries: Number of connection attempts (default: 3)
|
||||
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:
|
||||
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)
|
||||
|
||||
|
||||
# Ensure clean state
|
||||
try:
|
||||
if wlan.active():
|
||||
wlan.active(False)
|
||||
time.sleep(1)
|
||||
|
||||
|
||||
wlan.active(True)
|
||||
time.sleep(1)
|
||||
|
||||
|
||||
except OSError as e:
|
||||
print(f"WiFi activation error: {e}")
|
||||
print("Attempting reset...")
|
||||
@@ -37,66 +50,83 @@ def connect_wifi(led=None, max_retries=3, timeout=20):
|
||||
except Exception as e2:
|
||||
print(f"WiFi reset failed: {e2}")
|
||||
return None
|
||||
|
||||
|
||||
# Try connecting with retries
|
||||
for attempt in range(1, max_retries + 1):
|
||||
if wlan.isconnected():
|
||||
print(f"Already connected to WiFi")
|
||||
print("Already connected to WiFi")
|
||||
break
|
||||
|
||||
print(f'Connecting to WiFi (attempt {attempt}/{max_retries})...')
|
||||
|
||||
|
||||
print(f'Connecting to WiFi SSID: {ssid} (attempt {attempt}/{max_retries})...')
|
||||
|
||||
try:
|
||||
wlan.connect(secrets['ssid'], secrets['password'])
|
||||
wlan.connect(ssid, password)
|
||||
except Exception as e:
|
||||
print(f"Connection attempt failed: {e}")
|
||||
if attempt < max_retries:
|
||||
print("Retrying in 3 seconds...")
|
||||
time.sleep(3)
|
||||
continue
|
||||
|
||||
|
||||
# Wait for connection with timeout
|
||||
wait_time = 0
|
||||
while wait_time < timeout:
|
||||
if wlan.isconnected():
|
||||
break
|
||||
|
||||
|
||||
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)
|
||||
wait_time += 0.5
|
||||
|
||||
|
||||
# Print progress dots every 2 seconds
|
||||
if int(wait_time * 2) % 4 == 0:
|
||||
print('.', end='')
|
||||
|
||||
|
||||
print() # New line after dots
|
||||
|
||||
|
||||
if wlan.isconnected():
|
||||
break
|
||||
|
||||
|
||||
print(f'Connection attempt {attempt} failed')
|
||||
if attempt < max_retries:
|
||||
print("Retrying in 3 seconds...")
|
||||
time.sleep(3)
|
||||
|
||||
|
||||
# Final connection check
|
||||
if not wlan.isconnected():
|
||||
print('WiFi connection failed after all attempts!')
|
||||
if led:
|
||||
led.off()
|
||||
try:
|
||||
# prefer available method names
|
||||
if hasattr(led, "off"):
|
||||
led.off()
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
# Success feedback
|
||||
if led:
|
||||
# Double pulse on successful connection
|
||||
for _ in range(2):
|
||||
led.on()
|
||||
time.sleep(0.2)
|
||||
led.off()
|
||||
time.sleep(0.2)
|
||||
|
||||
try:
|
||||
for _ in range(2):
|
||||
led.on()
|
||||
time.sleep(0.2)
|
||||
led.off()
|
||||
time.sleep(0.2)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
print('Connected to WiFi successfully!')
|
||||
|
||||
return wlan
|
||||
@@ -90,32 +90,61 @@ class ScheduleMonitor:
|
||||
return # Already applied
|
||||
|
||||
try:
|
||||
# Track whether we changed persisted values to avoid unnecessary writes
|
||||
changed = False
|
||||
|
||||
# Update AC settings if provided
|
||||
if 'ac_target' in schedule:
|
||||
self.ac_monitor.target_temp = float(schedule['ac_target'])
|
||||
self.config['ac_target'] = float(schedule['ac_target'])
|
||||
new_ac = 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:
|
||||
self.ac_monitor.temp_swing = float(schedule['ac_swing'])
|
||||
self.config['ac_swing'] = float(schedule['ac_swing'])
|
||||
new_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
|
||||
if 'heater_target' in schedule:
|
||||
self.heater_monitor.target_temp = float(schedule['heater_target'])
|
||||
self.config['heater_target'] = float(schedule['heater_target'])
|
||||
new_ht = 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:
|
||||
self.heater_monitor.temp_swing = float(schedule['heater_swing'])
|
||||
self.config['heater_swing'] = float(schedule['heater_swing'])
|
||||
new_ht_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
|
||||
try:
|
||||
import json
|
||||
with open('config.json', 'w') as f:
|
||||
json.dump(self.config, f)
|
||||
print("✅ Config updated with active schedule targets")
|
||||
except Exception as e:
|
||||
print("⚠️ Could not save config: {}".format(e))
|
||||
# Save updated config only if something changed
|
||||
if changed:
|
||||
try:
|
||||
import json
|
||||
with open('config.json', 'w') as f:
|
||||
json.dump(self.config, f)
|
||||
except Exception as 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
|
||||
schedule_name = schedule.get('name', 'Unnamed')
|
||||
@@ -125,21 +154,22 @@ class ScheduleMonitor:
|
||||
print("AC Target: {}°F".format(self.ac_monitor.target_temp))
|
||||
print("Heater Target: {}°F".format(self.heater_monitor.target_temp))
|
||||
print("="*50 + "\n")
|
||||
|
||||
# Send Discord notification
|
||||
|
||||
# Send Discord notification (use discord_webhook if available)
|
||||
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(
|
||||
schedule_name,
|
||||
self.ac_monitor.target_temp,
|
||||
self.heater_monitor.target_temp
|
||||
)
|
||||
send_discord_message(message)
|
||||
except:
|
||||
discord_webhook.send_discord_message(message)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
self.last_applied_schedule = schedule_id
|
||||
|
||||
|
||||
except Exception as e:
|
||||
print("Error applying schedule: {}".format(e))
|
||||
|
||||
@@ -165,16 +195,24 @@ class ScheduleMonitor:
|
||||
import json
|
||||
with open('config.json', 'w') as f:
|
||||
json.dump(self.config, f)
|
||||
print("✅ Config updated - automatic mode resumed")
|
||||
except Exception as e:
|
||||
print("⚠️ Could not save config: {}".format(e))
|
||||
|
||||
# Notify user
|
||||
try:
|
||||
from scripts.discord_webhook import send_discord_message
|
||||
send_discord_message("⏰ Temporary hold expired - Schedule resumed automatically")
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
# ensure in-memory webhook config updated
|
||||
try:
|
||||
import scripts.discord_webhook as discord_webhook
|
||||
discord_webhook.set_config(self.config)
|
||||
except Exception:
|
||||
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 =====
|
||||
|
||||
# Find and apply active schedule
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import socket
|
||||
import time # type: ignore
|
||||
import json
|
||||
import scripts.discord_webhook as discord_webhook
|
||||
import os
|
||||
|
||||
|
||||
class TempWebServer:
|
||||
"""Simple web server for viewing temperatures and adjusting settings."""
|
||||
@@ -106,8 +109,8 @@ class TempWebServer:
|
||||
response_bytes = response.encode('utf-8')
|
||||
|
||||
# Send headers
|
||||
conn.send('HTTP/1.1 200 OK\r\n')
|
||||
conn.send('Content-Type: text/html; charset=utf-8\r\n')
|
||||
conn.sendall(b'HTTP/1.1 200 OK\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('Connection: close\r\n')
|
||||
conn.send('\r\n')
|
||||
@@ -224,6 +227,12 @@ class TempWebServer:
|
||||
# Rename temp to config (atomic on most filesystems)
|
||||
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")
|
||||
return True
|
||||
except Exception as e:
|
||||
@@ -276,8 +285,7 @@ class TempWebServer:
|
||||
|
||||
# Send Discord notification
|
||||
try:
|
||||
from scripts.discord_webhook import send_discord_message
|
||||
send_discord_message("▶️ Schedule resumed - Automatic temperature control active")
|
||||
discord_webhook.send_discord_message("▶️ Schedule resumed - Automatic temperature control active")
|
||||
except:
|
||||
pass
|
||||
|
||||
@@ -302,8 +310,7 @@ class TempWebServer:
|
||||
schedule_monitor.reload_config(config)
|
||||
|
||||
try:
|
||||
from scripts.discord_webhook import send_discord_message
|
||||
send_discord_message("⏸️ Temporary hold - Schedules paused, manual control active")
|
||||
discord_webhook.send_discord_message("⏸️ Temporary hold - Schedules paused, manual control active")
|
||||
except:
|
||||
pass
|
||||
|
||||
@@ -327,8 +334,7 @@ class TempWebServer:
|
||||
schedule_monitor.reload_config(config)
|
||||
|
||||
try:
|
||||
from scripts.discord_webhook import send_discord_message
|
||||
send_discord_message("🛑 Permanent hold - Schedules disabled, manual control only")
|
||||
discord_webhook.send_discord_message("🛑 Permanent hold - Schedules disabled, manual control only")
|
||||
except:
|
||||
pass
|
||||
|
||||
@@ -490,12 +496,11 @@ class TempWebServer:
|
||||
|
||||
# Send Discord notification
|
||||
try:
|
||||
from scripts.discord_webhook import send_discord_message
|
||||
mode = "automatic" if config.get('schedule_enabled') else "hold"
|
||||
message = "📅 Schedules updated ({} mode) - {} schedules configured".format(
|
||||
mode, len(schedules)
|
||||
)
|
||||
send_discord_message(message)
|
||||
discord_webhook.send_discord_message(message)
|
||||
except:
|
||||
pass
|
||||
# ===== END: Handle schedule configuration save =====
|
||||
@@ -611,7 +616,6 @@ class TempWebServer:
|
||||
|
||||
# ===== START: Send Discord notification =====
|
||||
try:
|
||||
from scripts.discord_webhook import send_discord_message
|
||||
hold_label = "PERMANENT HOLD" if is_permanent else "TEMPORARY HOLD"
|
||||
duration = "" if is_permanent else " (1 hour)"
|
||||
|
||||
@@ -622,7 +626,7 @@ class TempWebServer:
|
||||
params.get('heater_target', 'N/A'),
|
||||
duration
|
||||
)
|
||||
send_discord_message(message)
|
||||
discord_webhook.send_discord_message(message)
|
||||
except Exception as discord_error:
|
||||
print("Discord notification failed: {}".format(discord_error))
|
||||
# ===== END: Send Discord notification =====
|
||||
@@ -1754,8 +1758,7 @@ class TempWebServer:
|
||||
|
||||
# Discord notification
|
||||
try:
|
||||
from scripts.discord_webhook import send_discord_message
|
||||
send_discord_message("⚙️ Advanced settings updated")
|
||||
discord_webhook.send_discord_message("⚙️ Advanced settings updated")
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
Reference in New Issue
Block a user