From 9e94edab9037ec95765366ae31a6f0971a009749 Mon Sep 17 00:00:00 2001 From: sebseb7 Date: Fri, 16 Jan 2026 15:28:31 -0500 Subject: [PATCH] u --- server.js | 93 +++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 67 insertions(+), 26 deletions(-) diff --git a/server.js b/server.js index 09de1dc..ab2bc57 100644 --- a/server.js +++ b/server.js @@ -17,14 +17,16 @@ if (!fs.existsSync(LOG_DIR)) { // Initialize SQLite database const db = new sqlite3.Database('devices.db'); db.serialize(() => { + // Devices table 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 db.run("UPDATE devices SET connected = 0"); - // Drop old events table to enforce new schema - db.run("DROP TABLE IF EXISTS events"); - // 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)"); + // Channels table - stores component/field/type per device + 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))"); + + // 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 @@ -41,6 +43,44 @@ const connectionDeviceMap = new Map(); // Map to track all active connection IDs for a given MAC 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 // type: 'enum' (button events), 'range' (level 0-100), 'boolean' (on/off, online) 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') { - 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) => { + getOrCreateChannel(mac, component, field, type, (err, channelId) => { if (err) { - console.error("Error querying events for deduplication:", err); + console.error("Error getting/creating channel:", err); return; } - // Compare stringified versions to be safe (e.g. "true" vs "true") - const currentEventStr = String(event); - - if (!row || row.event !== currentEventStr) { - 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(); + if (field === 'button') { + // Button events are always logged (no deduplication) + if (connectionId) console.log(`[ID: ${connectionId}] Event logged: ${event} on ${component} (${field})`); + db.run("INSERT INTO events (channel_id, event, timestamp) VALUES (?, ?, ?)", [channelId, String(event), new Date().toISOString()]); forwardToUpstream(); - } else { - // console.log(`[ID: ${connectionId}] Duplicate event suppressed: ${event} on ${component} (${field})`); + return; } + + // 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(); + } + }); }); }