14 <meta charset="UTF-8">
15 <title>Pico GPIO Dashboard</title>
16 <meta name="viewport" content="width=device-width, initial-scale=1.0">
19 font-family: 'Raleway', sans-serif;
24 flex-direction: column;
30 margin-bottom: 1.5rem;
36 justify-content: center;
38 margin-bottom: 2.5rem;
45 grid-template-columns: repeat(2, 1fr);
50 box-shadow: 9px 9px 16px #bebebe,
51 -9px -9px 16px #ffffff;
60 box-shadow: inset 2px 2px 6px #bebebe,
61 inset -2px -2px 6px #ffffff;
71 box-shadow: 5px 5px 10px #bebebe,
72 -5px -5px 10px #ffffff;
74 transition: background 0.2s;
77 .gpio-card button.active {
78 background-color: #a5d6a7;
84 justify-content: center;
94 box-shadow: 9px 9px 16px #bebebe,
95 -9px -9px 16px #ffffff,
96 inset 5px 5px 15px #bebebe,
97 inset -5px -5px 15px #ffffff;
100 justify-content: center;
107 transform: rotate(-90deg);
120 box-shadow: 9px 9px 16px #bebebe,
121 -9px -9px 16px #ffffff;
128 padding: 0.5rem 1rem;
135 box-shadow: 5px 5px 10px #bebebe,
136 -5px -5px 10px #ffffff;
139 .led-toggle button.active {
140 background-color: #ffca28;
143 @media (max-width: 768px) {
145 flex-direction: column;
150 flex-direction: column;
157 <h1>Pico GPIO Dashboard</h1>
159 <div class="gpio-banks" id="gpioBanks">
160 <!-- JS adds GPIOs -->
163 <div class="gauge-section">
164 <div class="gauge-shell">
165 <svg class="gauge" viewBox="0 0 100 100">
166 <circle cx="50" cy="50" r="45" stroke="#ddd" stroke-width="10" fill="none" />
167 <circle id="gaugeArc" cx="50" cy="50" r="45" stroke="#81c784" stroke-width="10" fill="none"
168 stroke-linecap="round" stroke-dasharray="283" stroke-dashoffset="283" />
170 <div class="gauge-text" id="tempValue">--°C</div>
173 <div class="led-toggle">
175 <button id="ledBtn" onclick="toggleLED()">OFF</button>
186 const gpioButtons = {}; // pin -> button
188 function createGpioCards() {
189 const container = document.getElementById("gpioBanks");
191 gpioBanks.forEach(bank => {
192 const bankDiv = document.createElement("div");
193 bankDiv.className = "bank";
195 bank.forEach(pin => {
196 const card = document.createElement("div");
197 card.className = "gpio-card";
199 const label = document.createElement("div");
200 label.textContent = `GPIO ${pin}`;
202 const btn = document.createElement("button");
203 btn.textContent = "OFF";
204 btn.onclick = () => toggleGPIO(pin, btn);
206 card.appendChild(label);
207 card.appendChild(btn);
208 bankDiv.appendChild(card);
210 gpioButtons[pin] = btn;
213 container.appendChild(bankDiv);
217 function toggleGPIO(pin, btn) {
218 const isActive = btn.classList.toggle("active");
219 const state = isActive ? 1 : 0;
220 btn.textContent = state ? "ON" : "OFF";
221 fetch(`/api/v1/gpio/${pin}/${state}`, { method: "POST" })
222 .catch(err => console.warn(`GPIO ${pin} toggle failed`, err));
225 function syncAllGpios() {
226 const pins = Object.keys(gpioButtons);
228 if (pins.length === 0) {
229 console.warn("No GPIO pins to sync");
233 const params = new URLSearchParams();
234 pins.forEach(pin => params.append("pin", pin));
236 fetch(`/api/v1/gpios?${params.toString()}`)
237 .then(res => res.json())
239 data.forEach(pinData => {
240 const pin = pinData.pin;
241 const isOn = pinData.state === 1;
242 const btn = gpioButtons[pin];
244 btn.classList.toggle("active", isOn);
245 btn.textContent = isOn ? "ON" : "OFF";
249 .catch(err => console.warn("Failed to sync GPIOs", err));
252 function setupLedButton(ledBtn) {
253 function applyLedState(isOn) {
254 ledBtn.classList.toggle("active", isOn);
255 ledBtn.textContent = isOn ? "ON" : "OFF";
258 ledBtn.onclick = () => {
259 const isOn = !ledBtn.classList.contains("active");
261 fetch(`/api/v1/led/${isOn ? 1 : 0}`, { method: "POST" })
262 .catch(err => console.warn("LED toggle failed", err));
266 .then(res => res.json())
268 const isOn = data.state === 1 || data.state === "on";
271 .catch(err => console.warn("LED sync failed", err));
274 function setupTemperatureGauge(arc, tempValue) {
275 function update(temp) {
276 tempValue.textContent = `${temp.toFixed(1)}°C`;
277 const percent = Math.min(Math.max(temp / 60, 0), 1);
278 arc.style.strokeDashoffset = 283 * (1 - percent);
280 temp < 50 ? "#81c784" :
281 temp < 65 ? "#ffb74d" :
286 fetch('/api/v1/temperature')
287 .then(res => res.json())
288 .then(data => update(data.temperature))
290 console.warn("Temp fetch failed", err);
291 tempValue.textContent = "--°C";
292 arc.style.strokeDashoffset = 283;
293 arc.style.stroke = "#ccc";
297 refresh(); // initial
298 setInterval(refresh, 10000); // every 10s
301 // ----- Full load: all layout and rendering done -----
302 window.addEventListener("load", () => {
306 const ledBtn = document.getElementById("ledBtn");
307 setupLedButton(ledBtn);
309 const arc = document.getElementById("gaugeArc");
310 const tempValue = document.getElementById("tempValue");
311 setupTemperatureGauge(arc, tempValue);
static constexpr const char * dashboard_html
StaticView sends a fixed HTML string with no template substitution.