Logo Pico-Framework A web-first embedded framework for C++
Loading...
Searching...
No Matches
DashboardView.h
Go to the documentation of this file.
1#pragma once
3
4class DashboardView : public StaticView {
5public:
7
8private:
9 static constexpr const char* dashboard_html = R"!(
10 <!DOCTYPE html>
11 <html lang="en">
12
13 <head>
14 <meta charset="UTF-8">
15 <title>Pico GPIO Dashboard</title>
16 <meta name="viewport" content="width=device-width, initial-scale=1.0">
17 <style>
18 body {
19 font-family: 'Raleway', sans-serif;
20 background: #e0e0e0;
21 margin: 0;
22 padding: 2rem 1rem;
23 display: flex;
24 flex-direction: column;
25 align-items: center;
26 }
27
28 h1 {
29 color: #333;
30 margin-bottom: 1.5rem;
31 }
32
33 .gpio-banks {
34 display: flex;
35 flex-wrap: wrap;
36 justify-content: center;
37 gap: 2rem;
38 margin-bottom: 2.5rem;
39 width: 100%;
40 max-width: 900px;
41 }
42
43 .bank {
44 display: grid;
45 grid-template-columns: repeat(2, 1fr);
46 gap: 1rem;
47 background: #e0e0e0;
48 padding: 1rem;
49 border-radius: 20px;
50 box-shadow: 9px 9px 16px #bebebe,
51 -9px -9px 16px #ffffff;
52 width: 220px;
53 }
54
55 .gpio-card {
56 background: #e0e0e0;
57 border-radius: 16px;
58 padding: 1rem;
59 text-align: center;
60 box-shadow: inset 2px 2px 6px #bebebe,
61 inset -2px -2px 6px #ffffff;
62 }
63
64 .gpio-card button {
65 margin-top: 0.5rem;
66 padding: 0.5rem 1rem;
67 border: none;
68 border-radius: 12px;
69 font-weight: bold;
70 background: #f0f0f0;
71 box-shadow: 5px 5px 10px #bebebe,
72 -5px -5px 10px #ffffff;
73 cursor: pointer;
74 transition: background 0.2s;
75 }
76
77 .gpio-card button.active {
78 background-color: #a5d6a7;
79 }
80
81 .gauge-section {
82 display: flex;
83 flex-wrap: wrap;
84 justify-content: center;
85 gap: 2rem;
86 align-items: center;
87 }
88
89 .gauge-shell {
90 width: 220px;
91 height: 220px;
92 border-radius: 50%;
93 background: #e0e0e0;
94 box-shadow: 9px 9px 16px #bebebe,
95 -9px -9px 16px #ffffff,
96 inset 5px 5px 15px #bebebe,
97 inset -5px -5px 15px #ffffff;
98 display: flex;
99 align-items: center;
100 justify-content: center;
101 position: relative;
102 }
103
104 svg.gauge {
105 width: 160px;
106 height: 160px;
107 transform: rotate(-90deg);
108 }
109
110 .gauge-text {
111 position: absolute;
112 font-size: 1.6rem;
113 font-weight: bold;
114 color: #333;
115 }
116
117 .led-toggle {
118 background: #e0e0e0;
119 border-radius: 20px;
120 box-shadow: 9px 9px 16px #bebebe,
121 -9px -9px 16px #ffffff;
122 padding: 1rem 2rem;
123 text-align: center;
124 min-width: 160px;
125 }
126
127 .led-toggle button {
128 padding: 0.5rem 1rem;
129 border: none;
130 border-radius: 12px;
131 background: #f0f0f0;
132 font-weight: bold;
133 cursor: pointer;
134 margin-top: 1.5rem;
135 box-shadow: 5px 5px 10px #bebebe,
136 -5px -5px 10px #ffffff;
137 }
138
139 .led-toggle button.active {
140 background-color: #ffca28;
141 }
142
143 @media (max-width: 768px) {
144 .gpio-banks {
145 flex-direction: column;
146 align-items: center;
147 }
148
149 .gauge-section {
150 flex-direction: column;
151 }
152 }
153 </style>
154 </head>
155
156 <body>
157 <h1>Pico GPIO Dashboard</h1>
158
159 <div class="gpio-banks" id="gpioBanks">
160 <!-- JS adds GPIOs -->
161 </div>
162
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" />
169 </svg>
170 <div class="gauge-text" id="tempValue">--°C</div>
171 </div>
172
173 <div class="led-toggle">
174 <div>Wi-Fi LED</div>
175 <button id="ledBtn" onclick="toggleLED()">OFF</button>
176 </div>
177 </div>
178
179 <script>
180 const gpioBanks = [
181 [2, 3, 4, 5],
182 [6, 7, 8, 9],
183 [16, 17, 18, 19]
184 ];
185
186 const gpioButtons = {}; // pin -> button
187
188 function createGpioCards() {
189 const container = document.getElementById("gpioBanks");
190
191 gpioBanks.forEach(bank => {
192 const bankDiv = document.createElement("div");
193 bankDiv.className = "bank";
194
195 bank.forEach(pin => {
196 const card = document.createElement("div");
197 card.className = "gpio-card";
198
199 const label = document.createElement("div");
200 label.textContent = `GPIO ${pin}`;
201
202 const btn = document.createElement("button");
203 btn.textContent = "OFF";
204 btn.onclick = () => toggleGPIO(pin, btn);
205
206 card.appendChild(label);
207 card.appendChild(btn);
208 bankDiv.appendChild(card);
209
210 gpioButtons[pin] = btn;
211 });
212
213 container.appendChild(bankDiv);
214 });
215 }
216
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));
223 }
224
225 function syncAllGpios() {
226 const pins = Object.keys(gpioButtons);
227
228 if (pins.length === 0) {
229 console.warn("No GPIO pins to sync");
230 return;
231 }
232
233 const params = new URLSearchParams();
234 pins.forEach(pin => params.append("pin", pin));
235
236 fetch(`/api/v1/gpios?${params.toString()}`)
237 .then(res => res.json())
238 .then(data => {
239 data.forEach(pinData => {
240 const pin = pinData.pin;
241 const isOn = pinData.state === 1;
242 const btn = gpioButtons[pin];
243 if (btn) {
244 btn.classList.toggle("active", isOn);
245 btn.textContent = isOn ? "ON" : "OFF";
246 }
247 });
248 })
249 .catch(err => console.warn("Failed to sync GPIOs", err));
250 }
251
252 function setupLedButton(ledBtn) {
253 function applyLedState(isOn) {
254 ledBtn.classList.toggle("active", isOn);
255 ledBtn.textContent = isOn ? "ON" : "OFF";
256 }
257
258 ledBtn.onclick = () => {
259 const isOn = !ledBtn.classList.contains("active");
260 applyLedState(isOn);
261 fetch(`/api/v1/led/${isOn ? 1 : 0}`, { method: "POST" })
262 .catch(err => console.warn("LED toggle failed", err));
263 };
264
265 fetch('/api/v1/led')
266 .then(res => res.json())
267 .then(data => {
268 const isOn = data.state === 1 || data.state === "on";
269 applyLedState(isOn);
270 })
271 .catch(err => console.warn("LED sync failed", err));
272 }
273
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);
279 arc.style.stroke =
280 temp < 50 ? "#81c784" :
281 temp < 65 ? "#ffb74d" :
282 "#e57373";
283 }
284
285 function refresh() {
286 fetch('/api/v1/temperature')
287 .then(res => res.json())
288 .then(data => update(data.temperature))
289 .catch(err => {
290 console.warn("Temp fetch failed", err);
291 tempValue.textContent = "--°C";
292 arc.style.strokeDashoffset = 283;
293 arc.style.stroke = "#ccc";
294 });
295 }
296
297 refresh(); // initial
298 setInterval(refresh, 10000); // every 10s
299 }
300
301 // ----- Full load: all layout and rendering done -----
302 window.addEventListener("load", () => {
303 createGpioCards();
304 syncAllGpios();
305
306 const ledBtn = document.getElementById("ledBtn");
307 setupLedButton(ledBtn);
308
309 const arc = document.getElementById("gaugeArc");
310 const tempValue = document.getElementById("tempValue");
311 setupTemperatureGauge(arc, tempValue);
312 });
313 </script>
314 </body>
315 </html>
316 )!";
317};
static constexpr const char * dashboard_html
StaticView sends a fixed HTML string with no template substitution.
Definition StaticView.h:13