u
This commit is contained in:
71
server.js
71
server.js
@@ -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,35 +94,36 @@ function checkAndLogEvent(mac, component, field, type, event, connectionId = nul
|
|||||||
//}]);
|
//}]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
getOrCreateChannel(mac, component, field, type, (err, channelId) => {
|
||||||
|
if (err) {
|
||||||
|
console.error("Error getting/creating channel:", err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (field === 'button') {
|
if (field === 'button') {
|
||||||
|
// Button events are always logged (no deduplication)
|
||||||
if (connectionId) console.log(`[ID: ${connectionId}] Event logged: ${event} on ${component} (${field})`);
|
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 (?, ?, ?, ?, ?, ?)");
|
db.run("INSERT INTO events (channel_id, event, timestamp) VALUES (?, ?, ?)", [channelId, String(event), new Date().toISOString()]);
|
||||||
stmt.run(mac, component, field, type, String(event), new Date().toISOString());
|
|
||||||
stmt.finalize();
|
|
||||||
forwardToUpstream();
|
forwardToUpstream();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deduplication key: mac + component + field
|
// Deduplication: check last event for this channel
|
||||||
db.get("SELECT event FROM events WHERE mac = ? AND component = ? AND field = ? ORDER BY id DESC LIMIT 1", [mac, component, field], (err, row) => {
|
db.get("SELECT event FROM events WHERE channel_id = ? ORDER BY id DESC LIMIT 1", [channelId], (err, row) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error("Error querying events for deduplication:", err);
|
console.error("Error querying events for deduplication:", err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compare stringified versions to be safe (e.g. "true" vs "true")
|
|
||||||
const currentEventStr = String(event);
|
const currentEventStr = String(event);
|
||||||
|
|
||||||
if (!row || row.event !== currentEventStr) {
|
if (!row || row.event !== currentEventStr) {
|
||||||
if (connectionId) console.log(`[ID: ${connectionId}] Status change logged: ${event} on ${component} (${field})`);
|
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 (?, ?, ?, ?, ?, ?)");
|
db.run("INSERT INTO events (channel_id, event, timestamp) VALUES (?, ?, ?)", [channelId, currentEventStr, new Date().toISOString()]);
|
||||||
stmt.run(mac, component, field, type, currentEventStr, new Date().toISOString());
|
|
||||||
stmt.finalize();
|
|
||||||
forwardToUpstream();
|
forwardToUpstream();
|
||||||
} else {
|
|
||||||
// console.log(`[ID: ${connectionId}] Duplicate event suppressed: ${event} on ${component} (${field})`);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const wss = new WebSocketServer({ port: 8080 });
|
const wss = new WebSocketServer({ port: 8080 });
|
||||||
|
|||||||
Reference in New Issue
Block a user