u
This commit is contained in:
124
uiserver/api/readings.js
Normal file
124
uiserver/api/readings.js
Normal file
@@ -0,0 +1,124 @@
|
||||
/**
|
||||
* Readings API - Sensor and output data for charts
|
||||
*/
|
||||
|
||||
module.exports = function setupReadingsApi(app, { db }) {
|
||||
// GET /api/readings
|
||||
// Query params: since, until, selection (comma-separated device:channel pairs)
|
||||
app.get('/api/readings', (req, res) => {
|
||||
try {
|
||||
if (!db) throw new Error('Database not connected');
|
||||
const { since, until } = req.query;
|
||||
const startTime = since || new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString();
|
||||
const endTime = until || new Date().toISOString();
|
||||
|
||||
const requestedSensorChannels = []; // [{device, channel}]
|
||||
const requestedOutputChannels = []; // [channel]
|
||||
|
||||
if (req.query.selection) {
|
||||
const selections = req.query.selection.split(',');
|
||||
selections.forEach(s => {
|
||||
const lastColonIndex = s.lastIndexOf(':');
|
||||
if (lastColonIndex !== -1) {
|
||||
const d = s.substring(0, lastColonIndex);
|
||||
const c = s.substring(lastColonIndex + 1);
|
||||
if (d === 'output') {
|
||||
requestedOutputChannels.push(c);
|
||||
} else {
|
||||
requestedSensorChannels.push({ device: d, channel: c });
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const result = {};
|
||||
|
||||
// 1. Fetch sensor data
|
||||
if (requestedSensorChannels.length > 0) {
|
||||
let sql = 'SELECT * FROM sensor_events WHERE timestamp > ? AND timestamp <= ? ';
|
||||
const params = [startTime, endTime];
|
||||
|
||||
const placeholders = [];
|
||||
requestedSensorChannels.forEach(ch => {
|
||||
placeholders.push('(device = ? AND channel = ?)');
|
||||
params.push(ch.device, ch.channel);
|
||||
});
|
||||
if (placeholders.length > 0) {
|
||||
sql += `AND (${placeholders.join(' OR ')}) `;
|
||||
}
|
||||
sql += 'ORDER BY timestamp ASC';
|
||||
|
||||
const rows = db.prepare(sql).all(...params);
|
||||
|
||||
// Backfill for sensors
|
||||
const backfillStmt = db.prepare(`
|
||||
SELECT * FROM sensor_events
|
||||
WHERE device = ? AND channel = ?
|
||||
AND timestamp <= ?
|
||||
AND (until >= ? OR until IS NULL)
|
||||
ORDER BY timestamp DESC LIMIT 1
|
||||
`);
|
||||
|
||||
const backfillRows = [];
|
||||
requestedSensorChannels.forEach(ch => {
|
||||
const prev = backfillStmt.get(ch.device, ch.channel, startTime, startTime);
|
||||
if (prev) backfillRows.push(prev);
|
||||
});
|
||||
|
||||
[...backfillRows, ...rows].forEach(row => {
|
||||
const key = `${row.device}:${row.channel}`;
|
||||
if (!result[key]) result[key] = [];
|
||||
const pt = [row.timestamp, row.value];
|
||||
if (row.until) pt.push(row.until);
|
||||
result[key].push(pt);
|
||||
});
|
||||
}
|
||||
|
||||
// 2. Fetch output data
|
||||
if (requestedOutputChannels.length > 0) {
|
||||
let sql = 'SELECT * FROM output_events WHERE timestamp > ? AND timestamp <= ? ';
|
||||
const params = [startTime, endTime];
|
||||
|
||||
const placeholders = requestedOutputChannels.map(() => 'channel = ?');
|
||||
sql += `AND (${placeholders.join(' OR ')}) `;
|
||||
params.push(...requestedOutputChannels);
|
||||
sql += 'ORDER BY timestamp ASC';
|
||||
|
||||
const rows = db.prepare(sql).all(...params);
|
||||
|
||||
// Backfill for outputs
|
||||
const backfillStmt = db.prepare(`
|
||||
SELECT * FROM output_events
|
||||
WHERE channel = ?
|
||||
AND timestamp <= ?
|
||||
AND (until >= ? OR until IS NULL)
|
||||
ORDER BY timestamp DESC LIMIT 1
|
||||
`);
|
||||
|
||||
const backfillRows = [];
|
||||
requestedOutputChannels.forEach(ch => {
|
||||
const prev = backfillStmt.get(ch, startTime, startTime);
|
||||
if (prev) {
|
||||
backfillRows.push(prev);
|
||||
} else {
|
||||
// No data at all - add default 0 value at startTime
|
||||
backfillRows.push({ channel: ch, timestamp: startTime, value: 0, until: null });
|
||||
}
|
||||
});
|
||||
|
||||
[...backfillRows, ...rows].forEach(row => {
|
||||
const key = `output:${row.channel}`;
|
||||
if (!result[key]) result[key] = [];
|
||||
const pt = [row.timestamp, row.value];
|
||||
if (row.until) pt.push(row.until);
|
||||
result[key].push(pt);
|
||||
});
|
||||
}
|
||||
|
||||
res.json(result);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ error: err.message });
|
||||
}
|
||||
});
|
||||
};
|
||||
Reference in New Issue
Block a user