server + getting sensor id + json in esp32
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>API Documentation - Sensor Readouts</title>
|
||||
<style>
|
||||
body { font-family: sans-serif; max-width: 900px; margin: 2rem auto; line-height: 1.5; }
|
||||
h1 { border-bottom: 2px solid #333; padding-bottom: 0.5rem; }
|
||||
h2 { margin-top: 2rem; color: #333; }
|
||||
.endpoint { background: #f8f8f8; border: 1px solid #ddd; border-radius: 6px; padding: 1rem 1.25rem; margin: 1rem 0; }
|
||||
.method { display: inline-block; font-weight: bold; padding: 0.15rem 0.5rem; border-radius: 3px; color: #fff; margin-right: 0.5rem; font-size: 0.85rem; }
|
||||
.get { background: #2563eb; }
|
||||
.post { background: #16a34a; }
|
||||
.path { font-family: monospace; font-size: 1rem; }
|
||||
.desc { margin: 0.5rem 0; }
|
||||
table { border-collapse: collapse; width: 100%; margin: 0.5rem 0; }
|
||||
th, td { border: 1px solid #ccc; padding: 0.35rem 0.6rem; text-align: left; font-size: 0.9rem; }
|
||||
th { background: #f0f0f0; }
|
||||
pre { background: #1e1e1e; color: #d4d4d4; padding: 0.75rem; border-radius: 4px; overflow-x: auto; font-size: 0.85rem; }
|
||||
code { font-family: monospace; }
|
||||
.note { background: #fef9c3; border-left: 3px solid #eab308; padding: 0.5rem 0.75rem; margin: 1rem 0; font-size: 0.9rem; }
|
||||
.section-label { font-size: 0.8rem; color: #888; margin: 0.75rem 0 0.25rem; text-transform: uppercase; letter-spacing: 0.05em; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Sensor Readouts — API Documentation</h1>
|
||||
|
||||
<h2>Base URL</h2>
|
||||
<p><code>http://localhost:5000/api</code></p>
|
||||
|
||||
<!-- GET /api/sensors -->
|
||||
<h2>GET /api/sensors</h2>
|
||||
<div class="endpoint">
|
||||
<span class="method get">GET</span><span class="path">/api/sensors</span>
|
||||
<p class="desc">Return a list of all sensors ordered by name.</p>
|
||||
<p class="section-label">curl</p>
|
||||
<pre><code>curl http://localhost:5000/api/sensors</code></pre>
|
||||
<p class="section-label">Response 200</p>
|
||||
<pre><code>[
|
||||
{ "id": 1, "name": "outdoor-station" },
|
||||
{ "id": 2, "name": "indoor-unit" }
|
||||
]</code></pre>
|
||||
</div>
|
||||
|
||||
<!-- POST /api/sensors -->
|
||||
<div class="endpoint">
|
||||
<span class="method post">POST</span><span class="path">/api/sensors</span>
|
||||
<p class="desc">Create a new sensor.</p>
|
||||
<p class="section-label">curl</p>
|
||||
<pre><code>curl -X POST http://localhost:5000/api/sensors \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"name": "new-sensor"}'</code></pre>
|
||||
<p class="section-label">Response 201</p>
|
||||
<pre><code>{ "id": 5, "name": "new-sensor" }</code></pre>
|
||||
<p><strong>Errors:</strong></p>
|
||||
<table>
|
||||
<tr><th>Code</th><th>Condition</th></tr>
|
||||
<tr><td>400</td><td><code>name</code> is missing or empty</td></tr>
|
||||
<tr><td>409</td><td>Sensor with this name already exists</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- GET /api/sensors/:id/types -->
|
||||
<h2>GET /api/sensors/:id/types</h2>
|
||||
<div class="endpoint">
|
||||
<span class="method get">GET</span><span class="path">/api/sensors/:id/types</span>
|
||||
<p class="desc">Return distinct readout types available for a given sensor.</p>
|
||||
<p class="section-label">curl</p>
|
||||
<pre><code>curl http://localhost:5000/api/sensors/1/types</code></pre>
|
||||
<p class="section-label">Response 200</p>
|
||||
<pre><code>["humidity", "pressure", "temperature"]</code></pre>
|
||||
<p><strong>Errors:</strong></p>
|
||||
<table>
|
||||
<tr><th>Code</th><th>Condition</th></tr>
|
||||
<tr><td>404</td><td>Sensor not found</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- GET /api/sensors/:id/readouts -->
|
||||
<h2>GET /api/sensors/:id/readouts</h2>
|
||||
<div class="endpoint">
|
||||
<span class="method get">GET</span><span class="path">/api/sensors/:id/readouts</span>
|
||||
<p class="desc">Return the latest readouts for a sensor. Optional <code>type</code> query parameter filters by readout type.</p>
|
||||
<p><strong>Query parameters:</strong></p>
|
||||
<table>
|
||||
<tr><th>Param</th><th>Type</th><th>Description</th></tr>
|
||||
<tr><td>type</td><td>string</td><td>Filter by type (e.g. <code>temperature</code>)</td></tr>
|
||||
</table>
|
||||
<p class="section-label">curl</p>
|
||||
<pre><code># all types
|
||||
curl http://localhost:5000/api/sensors/1/readouts
|
||||
|
||||
# filter by type
|
||||
curl "http://localhost:5000/api/sensors/1/readouts?type=temperature"</code></pre>
|
||||
<p class="section-label">Response 200</p>
|
||||
<pre><code>[
|
||||
{ "value": 24.31, "timestamp": "2026-09-11T10:30:00" },
|
||||
{ "value": 24.15, "timestamp": "2026-09-11T10:29:30" }
|
||||
]</code></pre>
|
||||
</div>
|
||||
|
||||
<!-- POST /api/sensors/:id/readouts -->
|
||||
<div class="endpoint">
|
||||
<span class="method post">POST</span><span class="path">/api/sensors/:id/readouts</span>
|
||||
<p class="desc">Add a new readout to an existing sensor. Timestamp is generated server-side.</p>
|
||||
<p class="section-label">curl</p>
|
||||
<pre><code>curl -X POST http://localhost:5000/api/sensors/1/readouts \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"type": "temperature", "value": 22.5}'</code></pre>
|
||||
<p class="section-label">Response 201</p>
|
||||
<pre><code>{
|
||||
"id": 2702,
|
||||
"sensor_id": 1,
|
||||
"type": "temperature",
|
||||
"value": 22.5,
|
||||
"timestamp": "2026-09-11T17:00:00.123456"
|
||||
}</code></pre>
|
||||
<p><strong>Errors:</strong></p>
|
||||
<table>
|
||||
<tr><th>Code</th><th>Condition</th></tr>
|
||||
<tr><td>400</td><td>Missing <code>type</code> or <code>value</code>, or <code>value</code> is not a number</td></tr>
|
||||
<tr><td>404</td><td>Sensor not found</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="note">
|
||||
<strong>Note:</strong> All request and response bodies use <code>application/json</code>.
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,160 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Sensor Readouts</title>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4"></script>
|
||||
<style>
|
||||
body { font-family: sans-serif; max-width: 800px; margin: 2rem auto; }
|
||||
select, table { width: 100%; margin-top: 0.5rem; }
|
||||
select { padding: 0.4rem; }
|
||||
table { border-collapse: collapse; margin-top: 1rem; }
|
||||
th, td { border: 1px solid #ccc; padding: 0.4rem 0.6rem; text-align: left; }
|
||||
th { background: #f5f5f5; }
|
||||
label { font-weight: bold; }
|
||||
.hidden { display: none; }
|
||||
.chart-box { margin-top: 1rem; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Sensor Readouts</h1>
|
||||
|
||||
<div>
|
||||
<label for="sensor-select">Sensor:</label>
|
||||
<select id="sensor-select">
|
||||
<option value="">-- select sensor --</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div id="type-section" class="hidden">
|
||||
<label for="type-select">Type:</label>
|
||||
<select id="type-select">
|
||||
<option value="">-- select type --</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div id="chart-section" class="hidden chart-box">
|
||||
<canvas id="chart"></canvas>
|
||||
</div>
|
||||
|
||||
<div id="readouts-section" class="hidden">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Timestamp</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="readouts-body"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const sensorSelect = document.getElementById('sensor-select');
|
||||
const typeSelect = document.getElementById('type-select');
|
||||
const typeSection = document.getElementById('type-section');
|
||||
const chartSection = document.getElementById('chart-section');
|
||||
const readoutsSection = document.getElementById('readouts-section');
|
||||
const readoutsBody = document.getElementById('readouts-body');
|
||||
let chart = null;
|
||||
|
||||
function formatTimestamp(iso) {
|
||||
const d = new Date(iso);
|
||||
const pad = (n) => String(n).padStart(2, '0');
|
||||
const months = ['Jan','Feb','Mar','Apr','May','Jun',
|
||||
'Jul','Aug','Sep','Oct','Nov','Dec'];
|
||||
return `${d.getDate()} ${months[d.getMonth()]} ${d.getFullYear()}, ` +
|
||||
`${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
|
||||
}
|
||||
|
||||
async function fetchJSON(url) {
|
||||
const res = await fetch(url);
|
||||
return res.json();
|
||||
}
|
||||
|
||||
async function loadSensors() {
|
||||
const sensors = await fetchJSON('/api/sensors');
|
||||
sensors.forEach(s => {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = s.id;
|
||||
opt.textContent = s.name;
|
||||
sensorSelect.appendChild(opt);
|
||||
});
|
||||
}
|
||||
|
||||
sensorSelect.addEventListener('change', async () => {
|
||||
const sensorId = sensorSelect.value;
|
||||
typeSection.classList.add('hidden');
|
||||
chartSection.classList.add('hidden');
|
||||
readoutsSection.classList.add('hidden');
|
||||
typeSelect.innerHTML = '<option value="">-- select type --</option>';
|
||||
readoutsBody.innerHTML = '';
|
||||
|
||||
if (!sensorId) return;
|
||||
|
||||
const types = await fetchJSON(`/api/sensors/${sensorId}/types`);
|
||||
types.forEach(t => {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = t;
|
||||
opt.textContent = t;
|
||||
typeSelect.appendChild(opt);
|
||||
});
|
||||
typeSection.classList.remove('hidden');
|
||||
});
|
||||
|
||||
typeSelect.addEventListener('change', async () => {
|
||||
const sensorId = sensorSelect.value;
|
||||
const type = typeSelect.value;
|
||||
chartSection.classList.add('hidden');
|
||||
readoutsSection.classList.add('hidden');
|
||||
readoutsBody.innerHTML = '';
|
||||
|
||||
if (!type) return;
|
||||
|
||||
// fetch hourly data for chart
|
||||
const hourly = await fetchJSON(`/api/sensors/${sensorId}/readouts/hourly?type=${type}`);
|
||||
const labels = hourly.map(r => r.timestamp.replace('T', ' '));
|
||||
const values = hourly.map(r => r.value);
|
||||
|
||||
if (chart) chart.destroy();
|
||||
const ctx = document.getElementById('chart').getContext('2d');
|
||||
chart = new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: labels,
|
||||
datasets: [{
|
||||
label: type,
|
||||
data: values,
|
||||
borderColor: '#2563eb',
|
||||
backgroundColor: 'rgba(37,99,235,0.1)',
|
||||
fill: true,
|
||||
tension: 0.3,
|
||||
pointRadius: 2,
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
plugins: { legend: { display: false } },
|
||||
scales: {
|
||||
x: { title: { display: true, text: 'Time (hourly avg)' } },
|
||||
y: { title: { display: true, text: type } }
|
||||
}
|
||||
}
|
||||
});
|
||||
chartSection.classList.remove('hidden');
|
||||
|
||||
// fetch raw readouts for table
|
||||
const readouts = await fetchJSON(`/api/sensors/${sensorId}/readouts?type=${type}`);
|
||||
readouts.forEach(r => {
|
||||
const tr = document.createElement('tr');
|
||||
tr.innerHTML = `<td>${formatTimestamp(r.timestamp)}</td><td>${r.value}</td>`;
|
||||
readoutsBody.appendChild(tr);
|
||||
});
|
||||
readoutsSection.classList.remove('hidden');
|
||||
});
|
||||
|
||||
loadSensors();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user