u
This commit is contained in:
@@ -31,6 +31,22 @@ try {
|
||||
)
|
||||
`);
|
||||
|
||||
// Create output_configs table (unified channels + bindings)
|
||||
// Note: binding_type derived from device (ac=level, tapo=switch)
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS output_configs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
channel TEXT UNIQUE NOT NULL,
|
||||
description TEXT,
|
||||
value_type TEXT NOT NULL,
|
||||
min_value REAL DEFAULT 0,
|
||||
max_value REAL DEFAULT 1,
|
||||
device TEXT,
|
||||
device_channel TEXT,
|
||||
position INTEGER DEFAULT 0
|
||||
)
|
||||
`);
|
||||
|
||||
// Helper to insert changelog entry
|
||||
global.insertChangelog = (user, text) => {
|
||||
try {
|
||||
@@ -47,15 +63,36 @@ try {
|
||||
console.error(`[UI Server] Failed to connect to database at ${dbPath}:`, err.message);
|
||||
}
|
||||
|
||||
// Output bindings: map virtual outputs to physical devices
|
||||
// Format: outputChannel -> { device, channel, type }
|
||||
const OUTPUT_BINDINGS = {
|
||||
'BigDehumid': { device: 'tapo', channel: 'r0', type: 'switch' },
|
||||
'CO2Valve': { device: 'tapo', channel: 'c', type: 'switch' },
|
||||
'TentExhaust': { device: 'tapo', channel: 'fantent', type: 'switch' },
|
||||
'CircFanLevel': { device: 'ac', channel: 'tent:fan', type: 'level' },
|
||||
'RoomExhaust': { device: 'ac', channel: 'wall-fan', type: 'level' },
|
||||
};
|
||||
// Load output channels from database (replaces hardcoded OUTPUT_CHANNELS)
|
||||
function getOutputChannels() {
|
||||
if (!db) return [];
|
||||
const rows = db.prepare('SELECT * FROM output_configs ORDER BY position ASC').all();
|
||||
return rows.map(r => ({
|
||||
channel: r.channel,
|
||||
type: r.value_type,
|
||||
min: r.min_value,
|
||||
max: r.max_value,
|
||||
description: r.description
|
||||
}));
|
||||
}
|
||||
|
||||
// Load output bindings from database (replaces hardcoded OUTPUT_BINDINGS)
|
||||
// Binding type derived: ac=level, tapo=switch
|
||||
function getOutputBindings() {
|
||||
if (!db) return {};
|
||||
const rows = db.prepare('SELECT * FROM output_configs WHERE device IS NOT NULL').all();
|
||||
const bindings = {};
|
||||
for (const r of rows) {
|
||||
if (r.device && r.device_channel) {
|
||||
bindings[r.channel] = {
|
||||
device: r.device,
|
||||
channel: r.device_channel,
|
||||
type: r.device === 'ac' ? 'level' : 'switch'
|
||||
};
|
||||
}
|
||||
}
|
||||
return bindings;
|
||||
}
|
||||
|
||||
// =============================================
|
||||
// WebSocket Server for Agents (port 3962)
|
||||
@@ -315,6 +352,7 @@ function syncOutputStates() {
|
||||
if (!db) return;
|
||||
|
||||
try {
|
||||
const bindings = getOutputBindings();
|
||||
// Get current output values
|
||||
const stmt = db.prepare(`
|
||||
SELECT channel, value FROM output_events
|
||||
@@ -325,7 +363,7 @@ function syncOutputStates() {
|
||||
for (const row of rows) {
|
||||
// Only sync non-zero values
|
||||
if (row.value > 0) {
|
||||
const binding = OUTPUT_BINDINGS[row.channel];
|
||||
const binding = bindings[row.channel];
|
||||
if (binding) {
|
||||
let commandValue = row.value;
|
||||
if (binding.type === 'switch') {
|
||||
@@ -356,15 +394,6 @@ setInterval(syncOutputStates, 60000);
|
||||
// RULE ENGINE (Global Scope)
|
||||
// =============================================
|
||||
|
||||
// Virtual output channel definitions
|
||||
const OUTPUT_CHANNELS = [
|
||||
{ channel: 'CircFanLevel', type: 'number', min: 0, max: 10, description: 'Circulation Fan Level' },
|
||||
{ channel: 'CO2Valve', type: 'boolean', min: 0, max: 1, description: 'CO2 Valve' },
|
||||
{ channel: 'BigDehumid', type: 'boolean', min: 0, max: 1, description: 'Big Dehumidifier' },
|
||||
{ channel: 'TentExhaust', type: 'boolean', min: 0, max: 1, description: 'Tent Exhaust Fan' },
|
||||
{ channel: 'RoomExhaust', type: 'number', min: 0, max: 10, description: 'Room Exhaust Fan' },
|
||||
];
|
||||
|
||||
// Get current sensor value
|
||||
function getSensorValue(channel) {
|
||||
// channel format: "device:channel" e.g. "ac:controller:co2"
|
||||
@@ -420,7 +449,8 @@ function writeOutputValue(channel, value) {
|
||||
console.log(`[RuleRunner] Output changed: ${channel} = ${value}`);
|
||||
|
||||
// Send command to bound physical device
|
||||
const binding = OUTPUT_BINDINGS[channel];
|
||||
const bindings = getOutputBindings();
|
||||
const binding = bindings[channel];
|
||||
if (binding) {
|
||||
let commandValue = value;
|
||||
if (binding.type === 'switch') {
|
||||
@@ -533,7 +563,8 @@ function runRules() {
|
||||
|
||||
// Default all outputs to OFF (0) - if no rule sets them, they stay off
|
||||
const desiredOutputs = {};
|
||||
for (const ch of OUTPUT_CHANNELS) {
|
||||
const outputChannels = getOutputChannels();
|
||||
for (const ch of outputChannels) {
|
||||
desiredOutputs[ch.channel] = 0;
|
||||
}
|
||||
|
||||
@@ -651,8 +682,8 @@ module.exports = {
|
||||
bcrypt,
|
||||
jwt,
|
||||
JWT_SECRET,
|
||||
OUTPUT_CHANNELS,
|
||||
OUTPUT_BINDINGS,
|
||||
getOutputChannels,
|
||||
getOutputBindings,
|
||||
runRules,
|
||||
activeRuleIds
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user