284 lines
14 KiB
HTML
284 lines
14 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>piNail2 Controller</title>
|
|
<meta name="theme-color" content="#000000">
|
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
|
<meta name="apple-mobile-web-app-title" content="piNail2">
|
|
<link rel="icon" type="image/png" href="/static/img/pi_favicon.png">
|
|
<link rel="apple-touch-icon" href="/static/img/pi_favicon.png">
|
|
<link rel="manifest" href="/static/manifest.webmanifest">
|
|
<link rel="stylesheet" href="/static/style.css">
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
|
|
</head>
|
|
<body>
|
|
<header>
|
|
<div class="brand">
|
|
<img class="brand-logo" src="/static/img/pinail_logo.svg" alt="PINAIL logo">
|
|
<h1>Controller</h1>
|
|
</div>
|
|
<div class="conn-wrap">
|
|
<div class="mode-switch" role="group" aria-label="UI mode">
|
|
<button id="mode-simple-btn" class="mode-btn" onclick="setUiMode('simple')">Simple</button>
|
|
<button id="mode-nail1-btn" class="mode-btn" onclick="setUiMode('nail1')">Nail 1</button>
|
|
<button id="mode-nail2-btn" class="mode-btn" onclick="setUiMode('nail2')">Nail 2</button>
|
|
</div>
|
|
<button id="install-btn" class="install-btn" onclick="installApp()" hidden>Install App</button>
|
|
<span id="last-ack" class="last-ack advanced-only">Last command: none</span>
|
|
<span id="backend-status" class="backend-status advanced-only">Backend: Online</span>
|
|
<div id="connection-status" class="status-dot connected" title="Connected"></div>
|
|
</div>
|
|
</header>
|
|
|
|
<main>
|
|
<!-- Top row: Big temp display + power toggle -->
|
|
<section class="simple-only dual-simple" id="simple-dual">
|
|
<div class="simple-card" id="simple-card-nail1">
|
|
<h3>Nail 1</h3>
|
|
<div class="simple-temp"><span id="simple-temp-nail1">---</span>°F</div>
|
|
<div class="simple-line">Mode: <span id="simple-mode-nail1">grounded</span></div>
|
|
<div class="simple-line">Target: <span id="simple-target-nail1">---</span>°F</div>
|
|
<div class="simple-stats">
|
|
<span class="simple-stat">Relay <strong id="simple-relay-nail1">OFF</strong></span>
|
|
<span class="simple-stat">Output <strong id="simple-output-nail1">0</strong></span>
|
|
<span class="simple-stat">TC <strong id="simple-tc-nail1">--</strong></span>
|
|
<span class="simple-stat">Uptime <strong id="simple-uptime-nail1">--</strong></span>
|
|
</div>
|
|
<div class="simple-line">ETA: <span id="simple-eta-nail1">--</span></div>
|
|
<div class="simple-line">Next descent: <span id="simple-cutoff-nail1">--</span></div>
|
|
<div class="simple-alert" id="simple-alert-nail1">No active safety alerts.</div>
|
|
<div class="simple-controls">
|
|
<input type="number" id="simple-setpoint-nail1" value="530" min="0" max="800" step="5">
|
|
<button class="apply-btn" onclick="simpleApplySetpoint(1)">Set</button>
|
|
<button id="simple-power-nail1" class="power-mini off" onclick="simpleTogglePower(1)">OFF</button>
|
|
</div>
|
|
<div class="simple-flight-controls">
|
|
<button class="adj-btn" onclick="simpleSetFlightMode(1, 'takeoff')">Takeoff</button>
|
|
<button class="adj-btn" onclick="simpleSetFlightMode(1, 'descent')">Descent</button>
|
|
</div>
|
|
</div>
|
|
<div class="simple-card" id="simple-card-nail2">
|
|
<h3>Nail 2</h3>
|
|
<div class="simple-temp"><span id="simple-temp-nail2">---</span>°F</div>
|
|
<div class="simple-line">Mode: <span id="simple-mode-nail2">grounded</span></div>
|
|
<div class="simple-line">Target: <span id="simple-target-nail2">---</span>°F</div>
|
|
<div class="simple-stats">
|
|
<span class="simple-stat">Relay <strong id="simple-relay-nail2">OFF</strong></span>
|
|
<span class="simple-stat">Output <strong id="simple-output-nail2">0</strong></span>
|
|
<span class="simple-stat">TC <strong id="simple-tc-nail2">--</strong></span>
|
|
<span class="simple-stat">Uptime <strong id="simple-uptime-nail2">--</strong></span>
|
|
</div>
|
|
<div class="simple-line">ETA: <span id="simple-eta-nail2">--</span></div>
|
|
<div class="simple-line">Next descent: <span id="simple-cutoff-nail2">--</span></div>
|
|
<div class="simple-alert" id="simple-alert-nail2">No active safety alerts.</div>
|
|
<div class="simple-controls">
|
|
<input type="number" id="simple-setpoint-nail2" value="530" min="0" max="800" step="5">
|
|
<button class="apply-btn" onclick="simpleApplySetpoint(2)">Set</button>
|
|
<button id="simple-power-nail2" class="power-mini off" onclick="simpleTogglePower(2)">OFF</button>
|
|
</div>
|
|
<div class="simple-flight-controls">
|
|
<button class="adj-btn" onclick="simpleSetFlightMode(2, 'takeoff')">Takeoff</button>
|
|
<button class="adj-btn" onclick="simpleSetFlightMode(2, 'descent')">Descent</button>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="simple-only simple-chart-wrap">
|
|
<h3>Combined Temperature View</h3>
|
|
<canvas id="simple-temp-chart"></canvas>
|
|
</section>
|
|
|
|
<section class="hero">
|
|
<div class="temp-display">
|
|
<div id="advanced-nail-label" class="wall-clock">Nail 1</div>
|
|
<div id="wall-clock" class="wall-clock">--:--:--</div>
|
|
<div class="temp-main">
|
|
<span id="current-temp" class="temp-value">---</span>
|
|
<span class="temp-unit">°F</span>
|
|
</div>
|
|
<div id="error-stats" class="error-stats">Err(3m): --</div>
|
|
</div>
|
|
<div class="hero-right">
|
|
<div class="setpoint-display">
|
|
Target: <span id="current-setpoint">---</span>°F
|
|
</div>
|
|
<div id="mode-pill" class="mode-pill">Mode: grounded</div>
|
|
<div id="mode-eta" class="mode-eta">ETA: --</div>
|
|
<div id="sched-eta" class="mode-eta">Next descent: --</div>
|
|
<div id="autotune-pill" class="autotune-pill idle">Autotune: Idle</div>
|
|
<button id="power-btn" class="power-btn off" onclick="togglePower()">OFF</button>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Safety alert banner (hidden by default) -->
|
|
<section id="safety-banner" class="safety-banner hidden">
|
|
<span id="safety-message"></span>
|
|
<button onclick="resetSafety()">Reset</button>
|
|
</section>
|
|
|
|
<section id="action-banner" class="action-banner hidden">
|
|
<span id="action-message"></span>
|
|
</section>
|
|
|
|
<!-- Chart -->
|
|
<section class="chart-section">
|
|
<canvas id="temp-chart"></canvas>
|
|
</section>
|
|
|
|
<!-- Setpoint (always visible) -->
|
|
<section class="controls controls-setpoint">
|
|
<div class="control-group">
|
|
<h3>Setpoint</h3>
|
|
<div class="setpoint-controls">
|
|
<button class="adj-btn" onclick="adjustSetpoint(-10)">-10</button>
|
|
<button class="adj-btn" onclick="adjustSetpoint(-5)">-5</button>
|
|
<input type="number" id="setpoint-input" value="530" min="0" max="800" step="5">
|
|
<button class="adj-btn" onclick="adjustSetpoint(+5)">+5</button>
|
|
<button class="adj-btn" onclick="adjustSetpoint(+10)">+10</button>
|
|
<button class="apply-btn" onclick="applySetpoint()">Set</button>
|
|
</div>
|
|
<div class="presets" id="presets-container">
|
|
<!-- Filled by JS -->
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Advanced controls row -->
|
|
<section class="controls controls-advanced-row advanced-only">
|
|
<!-- Loop Timing -->
|
|
<div class="control-group">
|
|
<h3>Loop Timing</h3>
|
|
<div class="pid-controls">
|
|
<label>
|
|
Loop Size (ms)
|
|
<input type="number" id="control-loop-size" step="100" min="1500" max="5000" value="1800">
|
|
</label>
|
|
<label>
|
|
Sleep (s)
|
|
<input type="number" id="control-sleep-time" step="0.01" min="0.15" max="0.6" value="0.2">
|
|
</label>
|
|
<button class="apply-btn" onclick="applyControlTiming()">Apply Timing</button>
|
|
</div>
|
|
<div class="autotune-controls">
|
|
<button class="adj-btn" onclick="applyTimingProfile('conservative')">Conservative</button>
|
|
<button class="adj-btn" onclick="applyTimingProfile('balanced')">Balanced</button>
|
|
<button class="adj-btn" onclick="applyTimingProfile('responsive')">Responsive</button>
|
|
<span class="autotune-status idle">Limits: 1500-5000ms, 0.15-0.6s</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- PID Tuning -->
|
|
<div class="control-group advanced-only">
|
|
<h3>PID Tuning</h3>
|
|
<div class="pid-controls">
|
|
<label>
|
|
kP
|
|
<input type="number" id="pid-kp" step="0.1" value="10">
|
|
</label>
|
|
<label>
|
|
kI
|
|
<input type="number" id="pid-ki" step="0.1" value="5">
|
|
</label>
|
|
<label>
|
|
kD
|
|
<input type="number" id="pid-kd" step="0.1" value="1">
|
|
</label>
|
|
<label>
|
|
P Mode
|
|
<select id="pid-pmode">
|
|
<option value="error">P-on-Error</option>
|
|
<option value="measurement">P-on-Measurement</option>
|
|
</select>
|
|
</label>
|
|
<button class="apply-btn" onclick="applyPID()">Apply</button>
|
|
<button class="apply-btn" onclick="resetPID()">Reset I</button>
|
|
</div>
|
|
<div class="autotune-controls">
|
|
<button id="autotune-start-btn" class="apply-btn" onclick="startAutotune()">Start Autotune</button>
|
|
<button id="autotune-stop-btn" class="adj-btn" onclick="stopAutotune()">Stop Autotune</button>
|
|
<span id="autotune-status" class="autotune-status idle">Idle</span>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="controls controls-advanced-row advanced-only">
|
|
<div class="control-group">
|
|
<h3>Flight Modes</h3>
|
|
<div class="autotune-controls">
|
|
<button class="adj-btn" onclick="setFlightMode('takeoff')">Takeoff</button>
|
|
<button class="adj-btn" onclick="setFlightMode('descent')">Descent</button>
|
|
<span id="flight-mode-status" class="autotune-status idle"></span>
|
|
</div>
|
|
<div class="pid-controls">
|
|
<label>
|
|
Takeoff (s)
|
|
<input type="number" id="flight-takeoff-seconds" step="5" min="5" max="1800" value="300">
|
|
</label>
|
|
<label>
|
|
Descent (s)
|
|
<input type="number" id="flight-descent-seconds" step="5" min="5" max="1800" value="300">
|
|
</label>
|
|
<label>
|
|
Descent target (F)
|
|
<input type="number" id="flight-descent-target" step="5" min="80" max="500" value="120">
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="control-group">
|
|
<h3>Descent Scheduler</h3>
|
|
<div class="pid-controls">
|
|
<label>
|
|
Scheduler
|
|
<select id="sched-enabled" onchange="schedulerEnabledChanged()">
|
|
<option value="true">Enabled</option>
|
|
<option value="false">Disabled</option>
|
|
</select>
|
|
</label>
|
|
<label>
|
|
Add time (HH:MM)
|
|
<input type="time" id="sched-time-input" value="23:00" step="60">
|
|
</label>
|
|
<button class="adj-btn" onclick="addSchedulerTime()">Add Time</button>
|
|
</div>
|
|
<div id="sched-time-list" class="sched-time-list"></div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Status bar -->
|
|
<section class="status-bar advanced-only">
|
|
<div class="status-item">
|
|
<span class="label">Output</span>
|
|
<span id="status-output" class="value">0</span>
|
|
</div>
|
|
<div class="status-item">
|
|
<span class="label">Relay</span>
|
|
<span id="status-relay" class="value relay-off">OFF</span>
|
|
</div>
|
|
<div class="status-item">
|
|
<span class="label">Uptime</span>
|
|
<span id="status-uptime" class="value">--</span>
|
|
</div>
|
|
<div class="status-item">
|
|
<span class="label">Loops</span>
|
|
<span id="status-loops" class="value">0</span>
|
|
</div>
|
|
<div class="status-item">
|
|
<span class="label">TC</span>
|
|
<span id="status-tc" class="value">--</span>
|
|
</div>
|
|
</section>
|
|
|
|
<footer class="app-footer">
|
|
<span>{{ app_version }}</span>
|
|
<span>Copyright © {{ copyright_year }} SethPC Labs</span>
|
|
</footer>
|
|
</main>
|
|
|
|
<script src="/static/app.js"></script>
|
|
</body>
|
|
</html>
|