feat: Implement HOLD mode functionality with temporary and permanent options

This commit is contained in:
Aaron 2025-11-06 17:26:53 -05:00
parent f8269f8f9d
commit 52562bd8e6

View File

@ -80,7 +80,8 @@ class TempWebServer:
'heater_target': 80.0, 'heater_target': 80.0,
'heater_swing': 2.0, 'heater_swing': 2.0,
'schedules': [], 'schedules': [],
'schedule_enabled': False 'schedule_enabled': 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):
@ -97,33 +98,74 @@ class TempWebServer:
# Load current config # Load current config
config = self._load_config() config = self._load_config()
# Check if this is a "Resume Schedule" request # ===== START: Handle mode actions =====
if params.get('resume_schedule') == 'true': mode_action = params.get('mode_action', '')
if mode_action == 'resume':
# Resume automatic scheduling
config['schedule_enabled'] = True config['schedule_enabled'] = True
config['permanent_hold'] = False
# Save to file
if self._save_config_to_file(config): if self._save_config_to_file(config):
print("▶️ Schedule resumed") print("▶️ Schedule resumed - Automatic mode")
# Reload schedule monitor
if schedule_monitor: if schedule_monitor:
schedule_monitor.reload_config(config) schedule_monitor.reload_config(config)
# Send Discord notification
try: try:
from scripts.discord_webhook import send_discord_message from scripts.discord_webhook import send_discord_message
message = "▶️ Schedule resumed - Automatic temperature control active" send_discord_message("▶️ Schedule resumed - Automatic temperature control active")
send_discord_message(message)
except: except:
pass pass
return self._get_status_page(sensors, ac_monitor, heater_monitor, show_success=True) return self._get_status_page(sensors, ac_monitor, heater_monitor, show_success=True)
# Otherwise, handle normal schedule update elif mode_action == 'temporary_hold':
# Update schedule enabled status # Enter temporary hold (pause schedules temporarily)
config['schedule_enabled'] = params.get('schedule_enabled') == 'on' config['schedule_enabled'] = False
config['permanent_hold'] = False
if self._save_config_to_file(config):
print("⏸️ Temporary hold activated")
if schedule_monitor:
schedule_monitor.reload_config(config)
try:
from scripts.discord_webhook import send_discord_message
send_discord_message("⏸️ Temporary hold - Schedules paused, manual control active")
except:
pass
return self._get_status_page(sensors, ac_monitor, heater_monitor, show_success=True)
# Parse schedules elif mode_action == 'permanent_hold':
# Enter permanent hold (disable schedules permanently)
config['schedule_enabled'] = False
config['permanent_hold'] = True
if self._save_config_to_file(config):
print("🛑 Permanent hold activated")
if schedule_monitor:
schedule_monitor.reload_config(config)
try:
from scripts.discord_webhook import send_discord_message
send_discord_message("🛑 Permanent hold - Schedules disabled, manual control only")
except:
pass
return self._get_status_page(sensors, ac_monitor, heater_monitor, show_success=True)
# ===== END: Handle mode actions =====
elif mode_action == 'save_schedules':
# Just fall through to schedule parsing below
pass
# ===== END: Handle mode actions =====
# ===== START: Handle schedule configuration save =====
# Parse schedules from form
schedules = [] schedules = []
for i in range(4): for i in range(4):
time_key = 'schedule_{}_time'.format(i) time_key = 'schedule_{}_time'.format(i)
@ -144,22 +186,22 @@ class TempWebServer:
# Save to file # Save to file
if self._save_config_to_file(config): if self._save_config_to_file(config):
print("Schedule settings saved") print("Schedule configuration saved")
# Reload schedule monitor config
if schedule_monitor: if schedule_monitor:
schedule_monitor.reload_config(config) schedule_monitor.reload_config(config)
# Send Discord notification # Send Discord notification
try: try:
from scripts.discord_webhook import send_discord_message from scripts.discord_webhook import send_discord_message
status = "enabled" if config['schedule_enabled'] else "disabled" mode = "automatic" if config.get('schedule_enabled') else "hold"
message = "📅 Schedules updated ({}) - {} schedules configured".format( message = "📅 Schedules updated ({} mode) - {} schedules configured".format(
status, len(schedules) mode, len(schedules)
) )
send_discord_message(message) send_discord_message(message)
except: except:
pass pass
# ===== END: Handle schedule configuration save =====
except Exception as e: except Exception as e:
print("Error updating schedule: {}".format(e)) print("Error updating schedule: {}".format(e))
@ -209,7 +251,8 @@ class TempWebServer:
# ===== START: Disable scheduling and enter HOLD mode ===== # ===== START: Disable scheduling and enter HOLD mode =====
if config.get('schedule_enabled'): if config.get('schedule_enabled'):
config['schedule_enabled'] = False config['schedule_enabled'] = False
print("⏸️ Schedule disabled - entering HOLD mode") config['permanent_hold'] = False # ✅ ADD THIS - ensure it's temporary
print("⏸️ Schedule disabled - entering TEMPORARY HOLD mode")
# Reload schedule monitor to disable it # Reload schedule monitor to disable it
if schedule_monitor: if schedule_monitor:
@ -279,22 +322,26 @@ class TempWebServer:
# Load config # Load config
config = self._load_config() config = self._load_config()
# **NEW: Check if in HOLD mode** # ===== START: Determine schedule status display =====
is_hold_mode = not config.get('schedule_enabled', False) and len(config.get('schedules', [])) > 0 has_schedules = len([s for s in config.get('schedules', []) if s.get('time')]) > 0
# Build schedule display if not has_schedules:
if is_hold_mode: schedule_status = "NO SCHEDULES"
schedule_status = "HOLD MODE"
schedule_color = "#f39c12" # Orange color for hold
schedule_icon = "⏸️"
elif config.get('schedule_enabled'):
schedule_status = "ENABLED"
schedule_color = "#2ecc71"
schedule_icon = ""
else:
schedule_status = "DISABLED"
schedule_color = "#95a5a6" schedule_color = "#95a5a6"
schedule_icon = "⚠️" schedule_icon = "⚠️"
elif config.get('schedule_enabled'):
schedule_status = "AUTOMATIC"
schedule_color = "#2ecc71"
schedule_icon = ""
elif config.get('permanent_hold', False):
schedule_status = "PERMANENT HOLD"
schedule_color = "#e74c3c"
schedule_icon = "🛑"
else:
schedule_status = "TEMPORARY HOLD"
schedule_color = "#f39c12"
schedule_icon = "⏸️"
# ===== END: Determine schedule status display =====
# Build schedule cards # Build schedule cards
schedule_cards = "" schedule_cards = ""
@ -316,8 +363,8 @@ class TempWebServer:
</div> </div>
</div> </div>
""".format( """.format(
time=schedule.get('time', 'N/A'), time=time_value, # Use decoded value
name=schedule.get('name', 'Unnamed'), name=name_value, # Use decoded value
ac_temp=schedule.get('ac_target', 'N/A'), ac_temp=schedule.get('ac_target', 'N/A'),
heater_temp=schedule.get('heater_target', 'N/A') heater_temp=schedule.get('heater_target', 'N/A')
) )
@ -343,14 +390,17 @@ class TempWebServer:
outside_temp_str = "{:.1f}".format(outside_temp) if isinstance(outside_temp, float) else str(outside_temp) outside_temp_str = "{:.1f}".format(outside_temp) if isinstance(outside_temp, float) else str(outside_temp)
# ===== START: Add HOLD mode banner ===== # ===== START: Add HOLD mode banner =====
# Check if in HOLD mode (schedules exist but are disabled)
is_hold_mode = not config.get('schedule_enabled', False) and len(config.get('schedules', [])) > 0
hold_banner = "" hold_banner = ""
if is_hold_mode: if config.get('permanent_hold', False):
hold_banner = """
<div style="background: linear-gradient(135deg, #e74c3c, #c0392b); color: white; padding: 15px; border-radius: 10px; text-align: center; font-weight: bold; margin-bottom: 20px; box-shadow: 0 4px 8px rgba(0,0,0,0.2); animation: fadeIn 0.5s;">
🛑 PERMANENT HOLD - Schedules disabled (Manual control only)
</div>
"""
elif not config.get('schedule_enabled', False) and has_schedules:
hold_banner = """ hold_banner = """
<div style="background: linear-gradient(135deg, #f39c12, #e67e22); color: white; padding: 15px; border-radius: 10px; text-align: center; font-weight: bold; margin-bottom: 20px; box-shadow: 0 4px 8px rgba(0,0,0,0.2); animation: fadeIn 0.5s;"> <div style="background: linear-gradient(135deg, #f39c12, #e67e22); color: white; padding: 15px; border-radius: 10px; text-align: center; font-weight: bold; margin-bottom: 20px; box-shadow: 0 4px 8px rgba(0,0,0,0.2); animation: fadeIn 0.5s;">
HOLD MODE ACTIVE - Manual settings in use (Schedule paused) TEMPORARY HOLD - Manual override active (Schedule paused)
</div> </div>
""" """
# ===== END: Add HOLD mode banner ===== # ===== END: Add HOLD mode banner =====
@ -683,38 +733,94 @@ class TempWebServer:
while len(schedules) < 4: while len(schedules) < 4:
schedules.append({'time': '', 'name': '', 'ac_target': 77.0, 'heater_target': 80.0}) schedules.append({'time': '', 'name': '', 'ac_target': 77.0, 'heater_target': 80.0})
enabled_checked = 'checked' if config.get('schedule_enabled') else '' # ===== START: Determine current mode =====
# Check if schedules exist
has_schedules = len([s for s in schedules if s.get('time')]) > 0
# Check if in HOLD mode # Determine mode based on config
is_hold_mode = not config.get('schedule_enabled', False) and len(config.get('schedules', [])) > 0 if not has_schedules:
current_mode = "no_schedules" # No schedules configured yet
elif config.get('schedule_enabled'):
current_mode = "automatic" # Schedules are running
elif config.get('permanent_hold', False):
current_mode = "permanent_hold" # User disabled schedules permanently
else:
current_mode = "temporary_hold" # Manual override (HOLD mode)
# ===== END: Determine current mode =====
# Build header with toggle or resume button # ===== START: Build mode control buttons =====
if is_hold_mode: if current_mode == "no_schedules":
# Show Resume Schedule button instead of toggle # No mode buttons if no schedules configured
header = """ mode_buttons = """
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;"> <div style="background: #f8f9fa; padding: 15px; border-radius: 8px; text-align: center; color: #7f8c8d; margin-bottom: 20px;">
<h3 style="color: #34495e; margin: 0;"> Edit Schedules</h3> Configure schedules below, then choose a mode
<button type="submit" name="resume_schedule" value="true" style="padding: 10px 20px; background: linear-gradient(135deg, #2ecc71, #27ae60); color: white; border: none; border-radius: 8px; font-weight: bold; cursor: pointer; box-shadow: 0 2px 4px rgba(0,0,0,0.2); transition: transform 0.2s;">
Resume Schedule
</button>
</div> </div>
""" """
else: elif current_mode == "automatic":
# Show toggle switch for enable/disable # Automatic mode active
header = """ mode_buttons = """
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;"> <div style="background: linear-gradient(135deg, #2ecc71, #27ae60); color: white; padding: 15px; border-radius: 10px; margin-bottom: 20px; box-shadow: 0 4px 8px rgba(0,0,0,0.2);">
<h3 style="color: #34495e; margin: 0;"> Edit Schedules</h3> <div style="font-weight: bold; font-size: 18px; margin-bottom: 10px;">
<label class="toggle-switch"> Automatic Mode Active
<input type="checkbox" name="schedule_enabled" {enabled_checked}> </div>
<span class="slider"></span> <div style="font-size: 14px; margin-bottom: 15px;">
</label> Temperatures automatically adjust based on schedule
</div>
<div style="display: flex; gap: 10px; justify-content: center;">
<button type="submit" name="mode_action" value="temporary_hold" style="padding: 8px 16px; background: #f39c12; color: white; border: none; border-radius: 6px; cursor: pointer; font-weight: bold;">
Temporary Hold
</button>
<button type="submit" name="mode_action" value="permanent_hold" style="padding: 8px 16px; background: #e74c3c; color: white; border: none; border-radius: 6px; cursor: pointer; font-weight: bold;">
🛑 Permanent Hold
</button>
</div>
</div> </div>
""".format(enabled_checked=enabled_checked) """
elif current_mode == "temporary_hold":
# Temporary hold (manual override)
mode_buttons = """
<div style="background: linear-gradient(135deg, #f39c12, #e67e22); color: white; padding: 15px; border-radius: 10px; margin-bottom: 20px; box-shadow: 0 4px 8px rgba(0,0,0,0.2);">
<div style="font-weight: bold; font-size: 18px; margin-bottom: 10px;">
Temporary Hold Active
</div>
<div style="font-size: 14px; margin-bottom: 15px;">
Manual settings in use - Schedule paused
</div>
<div style="display: flex; gap: 10px; justify-content: center;">
<button type="submit" name="mode_action" value="resume" style="padding: 8px 16px; background: #2ecc71; color: white; border: none; border-radius: 6px; cursor: pointer; font-weight: bold;">
Resume Schedule
</button>
<button type="submit" name="mode_action" value="permanent_hold" style="padding: 8px 16px; background: #e74c3c; color: white; border: none; border-radius: 6px; cursor: pointer; font-weight: bold;">
🛑 Make Permanent
</button>
</div>
</div>
"""
else: # permanent_hold
# Permanent hold (schedules disabled by user)
mode_buttons = """
<div style="background: linear-gradient(135deg, #e74c3c, #c0392b); color: white; padding: 15px; border-radius: 10px; margin-bottom: 20px; box-shadow: 0 4px 8px rgba(0,0,0,0.2);">
<div style="font-weight: bold; font-size: 18px; margin-bottom: 10px;">
🛑 Permanent Hold Active
</div>
<div style="font-size: 14px; margin-bottom: 15px;">
Schedules disabled - Manual control only
</div>
<div style="display: flex; gap: 10px; justify-content: center;">
<button type="submit" name="mode_action" value="resume" style="padding: 8px 16px; background: #2ecc71; color: white; border: none; border-radius: 6px; cursor: pointer; font-weight: bold;">
Enable Schedules
</button>
</div>
</div>
"""
# ===== END: Build mode control buttons =====
form = """ form = """
<form method="POST" action="/schedule" class="controls" style="margin-top: 20px;"> <form method="POST" action="/schedule" class="controls" style="margin-top: 20px;">
{header} <h3 style="color: #34495e; margin-bottom: 15px;"> Schedule Configuration</h3>
""".format(header=header)
{mode_buttons}
""".format(mode_buttons=mode_buttons)
for i, schedule in enumerate(schedules[:4]): for i, schedule in enumerate(schedules[:4]):
form += """ form += """
@ -746,7 +852,7 @@ class TempWebServer:
form += """ form += """
<div class="control-group" style="margin-top: 20px;"> <div class="control-group" style="margin-top: 20px;">
<button type="submit" class="btn">💾 Save Schedule</button> <button type="submit" name="mode_action" value="save_schedules" class="btn">💾 Save Schedule Changes</button>
</div> </div>
</form> </form>
""" """