feat: Implement a rule engine for scriptable device automation, including an example timer light rule.
This commit is contained in:
41
server.js
41
server.js
@@ -3,6 +3,7 @@ import fs from 'fs';
|
||||
import path from 'path';
|
||||
import sqlite3 from 'sqlite3';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { initRuleEngine, loadRules, runRules, watchRules } from './rule_engine.js';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
@@ -46,6 +47,35 @@ const macConnectionsMap = new Map();
|
||||
// Cache for channel IDs to avoid repeated DB lookups
|
||||
const channelCache = new Map(); // key: "mac:component:field" -> channel_id
|
||||
|
||||
// Map MAC to WebSocket for sending RPC commands
|
||||
const macToSocket = new Map();
|
||||
|
||||
// Send RPC command to a device by MAC
|
||||
async function sendRPCToDevice(mac, method, params = {}) {
|
||||
const connections = macConnectionsMap.get(mac);
|
||||
if (!connections || connections.size === 0) {
|
||||
console.log(`[RPC] No connection for MAC=${mac}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get the first connection ID for this MAC
|
||||
const connectionId = connections.values().next().value;
|
||||
|
||||
// Find the socket
|
||||
for (const client of wss.clients) {
|
||||
if (client.connectionId === connectionId) {
|
||||
const rpcId = Math.floor(Math.random() * 10000);
|
||||
const rpcRequest = { id: rpcId, src: 'server', method, params };
|
||||
console.log(`[RPC] Sending to ${mac}: ${method}`, params);
|
||||
client.send(JSON.stringify(rpcRequest));
|
||||
return rpcId;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`[RPC] Socket not found for MAC=${mac}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get or create a channel, returns channel_id via callback
|
||||
function getOrCreateChannel(mac, component, field, type, callback) {
|
||||
const cacheKey = `${mac}:${component}:${field}`;
|
||||
@@ -105,6 +135,7 @@ function checkAndLogEvent(mac, component, field, type, event, connectionId = nul
|
||||
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();
|
||||
runRules(mac, component, field, type, event);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -121,6 +152,7 @@ function checkAndLogEvent(mac, component, field, type, event, connectionId = nul
|
||||
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();
|
||||
runRules(mac, component, field, type, currentEventStr);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -130,6 +162,15 @@ const wss = new WebSocketServer({ port: 8080 });
|
||||
|
||||
console.log('Shelly Agent Server listening on port 8080');
|
||||
|
||||
// Initialize and load rules
|
||||
initRuleEngine(db, sendRPCToDevice);
|
||||
loadRules().then(() => {
|
||||
console.log('Rule engine ready');
|
||||
watchRules(); // Auto-reload rules when files change
|
||||
}).catch(err => {
|
||||
console.error('Error loading rules:', err);
|
||||
});
|
||||
|
||||
// Global counter for connection IDs
|
||||
let connectionIdCounter = 0;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user