This commit is contained in:
sebseb7
2026-01-16 15:28:31 -05:00
parent d460808a3f
commit 9e94edab90

View File

@@ -17,14 +17,16 @@ if (!fs.existsSync(LOG_DIR)) {
// Initialize SQLite database // Initialize SQLite database
const db = new sqlite3.Database('devices.db'); const db = new sqlite3.Database('devices.db');
db.serialize(() => { db.serialize(() => {
// Devices table
db.run("CREATE TABLE IF NOT EXISTS devices (mac TEXT PRIMARY KEY, model TEXT, connected INTEGER, last_seen TEXT)"); db.run("CREATE TABLE IF NOT EXISTS devices (mac TEXT PRIMARY KEY, model TEXT, connected INTEGER, last_seen TEXT)");
// Reset all devices to disconnected on server start // Reset all devices to disconnected on server start
db.run("UPDATE devices SET connected = 0"); db.run("UPDATE devices SET connected = 0");
// Drop old events table to enforce new schema // Channels table - stores component/field/type per device
db.run("DROP TABLE IF EXISTS events"); db.run("CREATE TABLE IF NOT EXISTS channels (id INTEGER PRIMARY KEY AUTOINCREMENT, mac TEXT, component TEXT, field TEXT, type TEXT, FOREIGN KEY(mac) REFERENCES devices(mac), UNIQUE(mac, component, field))");
// Create new events table with 'field' and 'type' columns
db.run("CREATE TABLE IF NOT EXISTS events (id INTEGER PRIMARY KEY AUTOINCREMENT, mac TEXT, component TEXT, field TEXT, type TEXT, event TEXT, timestamp TEXT)"); // Events table - references channels
db.run("CREATE TABLE IF NOT EXISTS events (id INTEGER PRIMARY KEY AUTOINCREMENT, channel_id INTEGER, event TEXT, timestamp TEXT, FOREIGN KEY(channel_id) REFERENCES channels(id))");
}); });
// Upstream Client Integration // Upstream Client Integration
@@ -41,6 +43,44 @@ const connectionDeviceMap = new Map();
// Map to track all active connection IDs for a given MAC // Map to track all active connection IDs for a given MAC
const macConnectionsMap = new Map(); const macConnectionsMap = new Map();
// Cache for channel IDs to avoid repeated DB lookups
const channelCache = new Map(); // key: "mac:component:field" -> channel_id
// Get or create a channel, returns channel_id via callback
function getOrCreateChannel(mac, component, field, type, callback) {
const cacheKey = `${mac}:${component}:${field}`;
// Check cache first
if (channelCache.has(cacheKey)) {
callback(null, channelCache.get(cacheKey));
return;
}
// Try to find existing channel
db.get("SELECT id FROM channels WHERE mac = ? AND component = ? AND field = ?", [mac, component, field], (err, row) => {
if (err) {
callback(err, null);
return;
}
if (row) {
channelCache.set(cacheKey, row.id);
callback(null, row.id);
} else {
// Insert new channel
db.run("INSERT INTO channels (mac, component, field, type) VALUES (?, ?, ?, ?)", [mac, component, field, type], function (err) {
if (err) {
callback(err, null);
return;
}
const channelId = this.lastID;
channelCache.set(cacheKey, channelId);
callback(null, channelId);
});
}
});
}
// Helper to deduplicate stateful events // Helper to deduplicate stateful events
// type: 'enum' (button events), 'range' (level 0-100), 'boolean' (on/off, online) // type: 'enum' (button events), 'range' (level 0-100), 'boolean' (on/off, online)
function checkAndLogEvent(mac, component, field, type, event, connectionId = null) { function checkAndLogEvent(mac, component, field, type, event, connectionId = null) {
@@ -54,34 +94,35 @@ function checkAndLogEvent(mac, component, field, type, event, connectionId = nul
//}]); //}]);
}; };
if (field === 'button') { getOrCreateChannel(mac, component, field, type, (err, channelId) => {
if (connectionId) console.log(`[ID: ${connectionId}] Event logged: ${event} on ${component} (${field})`);
const stmt = db.prepare("INSERT INTO events (mac, component, field, type, event, timestamp) VALUES (?, ?, ?, ?, ?, ?)");
stmt.run(mac, component, field, type, String(event), new Date().toISOString());
stmt.finalize();
forwardToUpstream();
return;
}
// Deduplication key: mac + component + field
db.get("SELECT event FROM events WHERE mac = ? AND component = ? AND field = ? ORDER BY id DESC LIMIT 1", [mac, component, field], (err, row) => {
if (err) { if (err) {
console.error("Error querying events for deduplication:", err); console.error("Error getting/creating channel:", err);
return; return;
} }
// Compare stringified versions to be safe (e.g. "true" vs "true") if (field === 'button') {
const currentEventStr = String(event); // Button events are always logged (no deduplication)
if (connectionId) console.log(`[ID: ${connectionId}] Event logged: ${event} on ${component} (${field})`);
if (!row || row.event !== currentEventStr) { db.run("INSERT INTO events (channel_id, event, timestamp) VALUES (?, ?, ?)", [channelId, String(event), new Date().toISOString()]);
if (connectionId) console.log(`[ID: ${connectionId}] Status change logged: ${event} on ${component} (${field})`);
const stmt = db.prepare("INSERT INTO events (mac, component, field, type, event, timestamp) VALUES (?, ?, ?, ?, ?, ?)");
stmt.run(mac, component, field, type, currentEventStr, new Date().toISOString());
stmt.finalize();
forwardToUpstream(); forwardToUpstream();
} else { return;
// console.log(`[ID: ${connectionId}] Duplicate event suppressed: ${event} on ${component} (${field})`);
} }
// Deduplication: check last event for this channel
db.get("SELECT event FROM events WHERE channel_id = ? ORDER BY id DESC LIMIT 1", [channelId], (err, row) => {
if (err) {
console.error("Error querying events for deduplication:", err);
return;
}
const currentEventStr = String(event);
if (!row || row.event !== currentEventStr) {
if (connectionId) console.log(`[ID: ${connectionId}] Status change logged: ${event} on ${component} (${field})`);
db.run("INSERT INTO events (channel_id, event, timestamp) VALUES (?, ?, ?)", [channelId, currentEventStr, new Date().toISOString()]);
forwardToUpstream();
}
});
}); });
} }