7 <title>Pico GPIO Dashboard</title>
8 <meta name="viewport" content="width=device-width, initial-scale=1.0">
11 font-family: 'Raleway', sans-serif;
16 flex-direction: column;
22 margin-bottom: 1.5rem;
28 justify-content: center;
30 margin-bottom: 2.5rem;
37 grid-template-columns: repeat(2, 1fr);
42 box-shadow: 9px 9px 16px #bebebe,
43 -9px -9px 16px #ffffff;
52 box-shadow: inset 2px 2px 6px #bebebe,
53 inset -2px -2px 6px #ffffff;
63 box-shadow: 5px 5px 10px #bebebe,
64 -5px -5px 10px #ffffff;
66 transition: background 0.2s;
69 .gpio-card button.active {
70 background-color: #a5d6a7;
76 justify-content: center;
86 box-shadow: 9px 9px 16px #bebebe,
87 -9px -9px 16px #ffffff,
88 inset 5px 5px 15px #bebebe,
89 inset -5px -5px 15px #ffffff;
92 justify-content: center;
99 transform: rotate(-90deg);
112 box-shadow: 9px 9px 16px #bebebe,
113 -9px -9px 16px #ffffff;
120 padding: 0.5rem 1rem;
127 box-shadow: 5px 5px 10px #bebebe,
128 -5px -5px 10px #ffffff;
131 .led-toggle button.active {
132 background-color: #ffca28;
135 @media (max-width: 768px) {
137 flex-direction: column;
142 flex-direction: column;
149 <h1>Pico GPIO Dashboard</h1>
151 <div class="gpio-banks" id="gpioBanks">
152 <!-- JS adds GPIOs -->
155 <div class="gauge-section">
156 <div class="gauge-shell">
157 <svg class="gauge" viewBox="0 0 100 100">
158 <circle cx="50" cy="50" r="45" stroke="#ddd" stroke-width="10" fill="none" />
159 <circle id="gaugeArc" cx="50" cy="50" r="45" stroke="#81c784" stroke-width="10" fill="none"
160 stroke-linecap="round" stroke-dasharray="283" stroke-dashoffset="283" />
162 <div class="gauge-text" id="tempValue">--°C</div>
165 <div class="led-toggle">
167 <button id="ledBtn" onclick="toggleLED()">OFF</button>
178 const gpioButtons = {}; // pin -> button
180 function createGpioCards() {
181 const container = document.getElementById("gpioBanks");
183 gpioBanks.forEach(bank => {
184 const bankDiv = document.createElement("div");
185 bankDiv.className = "bank";
187 bank.forEach(pin => {
188 const card = document.createElement("div");
189 card.className = "gpio-card";
191 const label = document.createElement("div");
192 label.textContent = `GPIO ${pin}`;
194 const btn = document.createElement("button");
195 btn.textContent = "OFF";
196 btn.onclick = () => toggleGPIO(pin, btn);
198 card.appendChild(label);
199 card.appendChild(btn);
200 bankDiv.appendChild(card);
202 gpioButtons[pin] = btn;
205 container.appendChild(bankDiv);
209 function toggleGPIO(pin, btn) {
210 const isActive = btn.classList.toggle("active");
211 const state = isActive ? 1 : 0;
212 btn.textContent = state ? "ON" : "OFF";
213 fetch(`/api/v1/gpio/${pin}/${state}`, { method: "POST" })
214 .catch(err => console.warn(`GPIO ${pin} toggle failed`, err));
217 function syncAllGpios() {
218 const pins = Object.keys(gpioButtons);
220 if (pins.length === 0) {
221 console.warn("No GPIO pins to sync");
225 const params = new URLSearchParams();
226 pins.forEach(pin => params.append("pin", pin));
228 fetch(`/api/v1/gpios?${params.toString()}`)
229 .then(res => res.json())
231 data.forEach(pinData => {
232 const pin = pinData.pin;
233 const isOn = pinData.state === 1;
234 const btn = gpioButtons[pin];
236 btn.classList.toggle("active", isOn);
237 btn.textContent = isOn ? "ON" : "OFF";
241 .catch(err => console.warn("Failed to sync GPIOs", err));
244 function setupLedButton(ledBtn) {
245 function applyLedState(isOn) {
246 ledBtn.classList.toggle("active", isOn);
247 ledBtn.textContent = isOn ? "ON" : "OFF";
250 ledBtn.onclick = () => {
251 const isOn = !ledBtn.classList.contains("active");
253 fetch(`/api/v1/led/${isOn ? 1 : 0}`, { method: "POST" })
254 .catch(err => console.warn("LED toggle failed", err));
258 .then(res => res.json())
260 const isOn = data.state === 1 || data.state === "on";
263 .catch(err => console.warn("LED sync failed", err));
266 function setupTemperatureGauge(arc, tempValue) {
267 function update(temp) {
268 tempValue.textContent = `${temp.toFixed(1)}°C`;
269 const percent = Math.min(Math.max(temp / 60, 0), 1);
270 arc.style.strokeDashoffset = 283 * (1 - percent);
272 temp < 50 ? "#81c784" :
273 temp < 65 ? "#ffb74d" :
278 fetch('/api/v1/temperature')
279 .then(res => res.json())
280 .then(data => update(data.temperature))
282 console.warn("Temp fetch failed", err);
283 tempValue.textContent = "--°C";
284 arc.style.strokeDashoffset = 283;
285 arc.style.stroke = "#ccc";
289 refresh(); // initial
290 setInterval(refresh, 10000); // every 10s
293 // ----- Full load: all layout and rendering done -----
294 window.addEventListener("load", () => {
298 const ledBtn = document.getElementById("ledBtn");
299 setupLedButton(ledBtn);
301 const arc = document.getElementById("gaugeArc");
302 const tempValue = document.getElementById("tempValue");
303 setupTemperatureGauge(arc, tempValue);
const char * dashboard_html