UNPKG

domotz-mcp

Version:

MCP server for Domotz API and BMS (Building Management System) integration with IoT device monitoring

218 lines (215 loc) 7.78 kB
import Database from 'better-sqlite3'; import { mkdirSync } from 'fs'; import { join, dirname } from 'path'; import { fileURLToPath } from 'url'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); // Ensure data directory exists const dataDir = join(__dirname, '..', '..', 'data'); mkdirSync(dataDir, { recursive: true }); // Database singleton let db = null; export class MQTTDatabaseManager { static instance; db; constructor() { const dbPath = join(dataDir, 'mqtt_messages.db'); this.db = new Database(dbPath); this.initializeDatabase(); } static getInstance() { if (!MQTTDatabaseManager.instance) { MQTTDatabaseManager.instance = new MQTTDatabaseManager(); } return MQTTDatabaseManager.instance; } initializeDatabase() { // Create messages table this.db.exec(` CREATE TABLE IF NOT EXISTS mqtt_messages ( id INTEGER PRIMARY KEY AUTOINCREMENT, connection_id TEXT NOT NULL, topic TEXT NOT NULL, message TEXT NOT NULL, qos INTEGER DEFAULT 0, retain BOOLEAN DEFAULT FALSE, timestamp TEXT NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) `); // Create index for faster queries this.db.exec(` CREATE INDEX IF NOT EXISTS idx_mqtt_messages_topic ON mqtt_messages(topic); CREATE INDEX IF NOT EXISTS idx_mqtt_messages_connection ON mqtt_messages(connection_id); CREATE INDEX IF NOT EXISTS idx_mqtt_messages_timestamp ON mqtt_messages(timestamp); `); // Create subscriptions table this.db.exec(` CREATE TABLE IF NOT EXISTS mqtt_subscriptions ( id INTEGER PRIMARY KEY AUTOINCREMENT, connection_id TEXT NOT NULL, topic_pattern TEXT NOT NULL, qos INTEGER DEFAULT 0, active BOOLEAN DEFAULT TRUE, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE(connection_id, topic_pattern) ) `); // Create settings table for retention policy this.db.exec(` CREATE TABLE IF NOT EXISTS mqtt_settings ( key TEXT PRIMARY KEY, value TEXT NOT NULL ) `); // Set default retention period (7 days) const stmt = this.db.prepare(` INSERT OR IGNORE INTO mqtt_settings (key, value) VALUES ('retention_days', '7') `); stmt.run(); } // Store a received message storeMessage(message) { const stmt = this.db.prepare(` INSERT INTO mqtt_messages (connection_id, topic, message, qos, retain, timestamp) VALUES (@connection_id, @topic, @message, @qos, @retain, @timestamp) `); const result = stmt.run(message); return result.lastInsertRowid; } // Store a subscription storeSubscription(subscription) { const stmt = this.db.prepare(` INSERT OR REPLACE INTO mqtt_subscriptions (connection_id, topic_pattern, qos, active) VALUES (@connection_id, @topic_pattern, @qos, @active) `); const result = stmt.run(subscription); return result.lastInsertRowid; } // Get messages by topic pattern getMessagesByTopic(topicPattern, limit = 100, offset = 0) { // Convert MQTT wildcards to SQL LIKE pattern const sqlPattern = topicPattern .replace(/\+/g, '_') // + matches single level .replace(/#/g, '%'); // # matches multiple levels const stmt = this.db.prepare(` SELECT * FROM mqtt_messages WHERE topic LIKE ? ORDER BY timestamp DESC LIMIT ? OFFSET ? `); return stmt.all(sqlPattern, limit, offset); } // Get messages by connection getMessagesByConnection(connectionId, limit = 100) { const stmt = this.db.prepare(` SELECT * FROM mqtt_messages WHERE connection_id = ? ORDER BY timestamp DESC LIMIT ? `); return stmt.all(connectionId, limit); } // Get messages by time range getMessagesByTimeRange(startTime, endTime, topic) { let query = ` SELECT * FROM mqtt_messages WHERE timestamp >= ? AND timestamp <= ? `; if (topic) { const sqlPattern = topic.replace(/\+/g, '_').replace(/#/g, '%'); query += ` AND topic LIKE ?`; const stmt = this.db.prepare(query + ` ORDER BY timestamp DESC`); return stmt.all(startTime, endTime, sqlPattern); } else { const stmt = this.db.prepare(query + ` ORDER BY timestamp DESC`); return stmt.all(startTime, endTime); } } // Get active subscriptions getActiveSubscriptions(connectionId) { if (connectionId) { const stmt = this.db.prepare(` SELECT s.*, COUNT(m.id) as message_count FROM mqtt_subscriptions s LEFT JOIN mqtt_messages m ON s.connection_id = m.connection_id AND m.topic LIKE REPLACE(REPLACE(s.topic_pattern, '+', '_'), '#', '%') WHERE s.connection_id = ? AND s.active = TRUE GROUP BY s.id `); return stmt.all(connectionId); } else { const stmt = this.db.prepare(` SELECT s.*, COUNT(m.id) as message_count FROM mqtt_subscriptions s LEFT JOIN mqtt_messages m ON s.connection_id = m.connection_id AND m.topic LIKE REPLACE(REPLACE(s.topic_pattern, '+', '_'), '#', '%') WHERE s.active = TRUE GROUP BY s.id `); return stmt.all(); } } // Deactivate subscription deactivateSubscription(connectionId, topicPattern) { const stmt = this.db.prepare(` UPDATE mqtt_subscriptions SET active = FALSE WHERE connection_id = ? AND topic_pattern = ? `); stmt.run(connectionId, topicPattern); } // Clean up old messages based on retention policy cleanupOldMessages() { const retentionDays = this.getRetentionDays(); const stmt = this.db.prepare(` DELETE FROM mqtt_messages WHERE created_at < datetime('now', '-' || ? || ' days') `); const result = stmt.run(retentionDays); return result.changes; } // Get retention days setting getRetentionDays() { const stmt = this.db.prepare(` SELECT value FROM mqtt_settings WHERE key = 'retention_days' `); const result = stmt.get(); return result ? parseInt(result.value) : 7; } // Set retention days setRetentionDays(days) { const stmt = this.db.prepare(` UPDATE mqtt_settings SET value = ? WHERE key = 'retention_days' `); stmt.run(days.toString()); } // Get database statistics getStatistics() { const messageCount = this.db.prepare('SELECT COUNT(*) as count FROM mqtt_messages').get(); const subscriptionCount = this.db.prepare('SELECT COUNT(*) as count FROM mqtt_subscriptions WHERE active = TRUE').get(); const topicCount = this.db.prepare('SELECT COUNT(DISTINCT topic) as count FROM mqtt_messages').get(); const oldestMessage = this.db.prepare('SELECT MIN(timestamp) as oldest FROM mqtt_messages').get(); const newestMessage = this.db.prepare('SELECT MAX(timestamp) as newest FROM mqtt_messages').get(); return { total_messages: messageCount.count, active_subscriptions: subscriptionCount.count, unique_topics: topicCount.count, oldest_message: oldestMessage.oldest, newest_message: newestMessage.newest, retention_days: this.getRetentionDays() }; } // Close database connection close() { this.db.close(); } }