This commit is contained in:
sebseb7
2025-12-25 03:35:17 +01:00
parent ce87faa551
commit 0aa7995a8a

View File

@@ -545,14 +545,21 @@ module.exports = {
}); });
// GET /api/devices // GET /api/devices
// Returns list of unique device/channel pairs // Returns list of unique device/channel pairs (sensors + outputs)
app.get('/api/devices', (req, res) => { app.get('/api/devices', (req, res) => {
try { try {
if (!db) throw new Error('Database not connected'); if (!db) throw new Error('Database not connected');
// Filter to only numeric channels which can be charted // Get sensor channels
const stmt = db.prepare("SELECT DISTINCT device, channel FROM sensor_events WHERE data_type = 'number' ORDER BY device, channel"); const sensorStmt = db.prepare("SELECT DISTINCT device, channel FROM sensor_events WHERE data_type = 'number' ORDER BY device, channel");
const rows = stmt.all(); const sensorRows = sensorStmt.all();
res.json(rows);
// Add output channels with 'output' as device
const outputRows = OUTPUT_CHANNELS.map(ch => ({
device: 'output',
channel: ch.channel
}));
res.json([...sensorRows, ...outputRows]);
} catch (err) { } catch (err) {
res.status(500).json({ error: err.message }); res.status(500).json({ error: err.message });
} }
@@ -576,44 +583,45 @@ module.exports = {
const startTime = since || new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(); const startTime = since || new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString();
const endTime = until || new Date().toISOString(); const endTime = until || new Date().toISOString();
// 1. Fetch main data window const requestedSensorChannels = []; // [{device, channel}]
let sql = 'SELECT * FROM sensor_events WHERE timestamp > ? AND timestamp <= ? '; const requestedOutputChannels = []; // [channel]
const params = [startTime, endTime];
const requestedChannels = []; // [{device, channel}]
if (req.query.selection) { if (req.query.selection) {
const selections = req.query.selection.split(','); const selections = req.query.selection.split(',');
if (selections.length > 0) {
const placeholders = [];
selections.forEach(s => { selections.forEach(s => {
const lastColonIndex = s.lastIndexOf(':'); const lastColonIndex = s.lastIndexOf(':');
if (lastColonIndex !== -1) { if (lastColonIndex !== -1) {
const d = s.substring(0, lastColonIndex); const d = s.substring(0, lastColonIndex);
const c = s.substring(lastColonIndex + 1); const c = s.substring(lastColonIndex + 1);
placeholders.push('(device = ? AND channel = ?)'); if (d === 'output') {
params.push(d, c); requestedOutputChannels.push(c);
requestedChannels.push({ device: d, channel: 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) { if (placeholders.length > 0) {
sql += `AND (${placeholders.join(' OR ')}) `; sql += `AND (${placeholders.join(' OR ')}) `;
} }
}
}
sql += 'ORDER BY timestamp ASC'; sql += 'ORDER BY timestamp ASC';
const stmt = db.prepare(sql); const rows = db.prepare(sql).all(...params);
const rows = stmt.all(...params);
// 2. Backfill: Ensure we have a starting point for each channel // Backfill for sensors
// For each requested channel, check if we have data at/near start.
// If the first point for a channel is > startTime, we should try to find the previous value.
// We check for the value that started before (or at) startTime AND ended after (or at) startTime (or hasn't ended).
const backfillRows = [];
// Find the record that started most recently before startTime BUT was still valid at startTime
const backfillStmt = db.prepare(` const backfillStmt = db.prepare(`
SELECT * FROM sensor_events SELECT * FROM sensor_events
WHERE device = ? AND channel = ? WHERE device = ? AND channel = ?
@@ -622,29 +630,61 @@ module.exports = {
ORDER BY timestamp DESC LIMIT 1 ORDER BY timestamp DESC LIMIT 1
`); `);
requestedChannels.forEach(ch => { const backfillRows = [];
requestedSensorChannels.forEach(ch => {
const prev = backfillStmt.get(ch.device, ch.channel, startTime, startTime); const prev = backfillStmt.get(ch.device, ch.channel, startTime, startTime);
if (prev) { if (prev) backfillRows.push(prev);
// We found a point that covers the startTime.
backfillRows.push(prev);
}
}); });
// Combine and sort [...backfillRows, ...rows].forEach(row => {
const allRows = [...backfillRows, ...rows];
// Transform to Compact Dictionary Format
// { "device:channel": [ [timestamp, value, until], ... ] }
const result = {};
allRows.forEach(row => {
const key = `${row.device}:${row.channel}`; const key = `${row.device}:${row.channel}`;
if (!result[key]) result[key] = []; if (!result[key]) result[key] = [];
const pt = [row.timestamp, row.value]; const pt = [row.timestamp, row.value];
if (row.until) pt.push(row.until); if (row.until) pt.push(row.until);
result[key].push(pt); 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); res.json(result);
} catch (err) { } catch (err) {