Release v2.0.0 with dual-nail control and hardened safety

This commit is contained in:
2026-03-12 02:06:42 +00:00
parent cd07703f67
commit c4c86747e5
10 changed files with 2046 additions and 451 deletions
+125 -9
View File
@@ -4,7 +4,13 @@
<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>
@@ -15,23 +21,62 @@
<h1>Controller</h1>
</div>
<div class="conn-wrap">
<span id="last-ack" class="last-ack">Last command: none</span>
<span id="backend-status" class="backend-status">Backend: Online</span>
<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>&deg;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>&deg;F</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>
<div class="simple-card" id="simple-card-nail2">
<h3>Nail 2</h3>
<div class="simple-temp"><span id="simple-temp-nail2">---</span>&deg;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>&deg;F</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>
</section>
<section class="hero">
<div class="temp-display">
<span id="current-temp" class="temp-value">---</span>
<span class="temp-unit">&deg;F</span>
<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">&deg;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>&deg;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>
@@ -52,9 +97,8 @@
<canvas id="temp-chart"></canvas>
</section>
<!-- Controls -->
<section class="controls">
<!-- Setpoint -->
<!-- Setpoint (always visible) -->
<section class="controls controls-setpoint">
<div class="control-group">
<h3>Setpoint</h3>
<div class="setpoint-controls">
@@ -69,9 +113,34 @@
<!-- 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="3000">
</label>
<label>
Sleep (s)
<input type="number" id="control-sleep-time" step="0.01" min="0.15" max="0.6" value="0.4">
</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">
<div class="control-group advanced-only">
<h3>PID Tuning</h3>
<div class="pid-controls">
<label>
@@ -104,8 +173,55 @@
</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">Use power button for Grounded/Cruise.</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="text" id="sched-time-input" value="23:00" placeholder="HH:MM" inputmode="numeric" maxlength="5">
</label>
<button class="adj-btn" onclick="addSchedulerTime()">Add Time</button>
</div>
<div id="sched-time-list" class="sched-time-list"></div>
<div class="autotune-controls">
<span class="autotune-status idle">Scheduler only triggers descent -> grounded. No auto takeoff.</span>
</div>
</div>
</section>
<!-- Status bar -->
<section class="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>