u
This commit is contained in:
93
server.js
93
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();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user