This commit is contained in:
Marcelo
2025-11-28 09:11:59 -06:00
commit b66cb97f16
34 changed files with 17756 additions and 0 deletions

240
session_state_functions.js Normal file
View File

@@ -0,0 +1,240 @@
// ========================================
// SESSION STATE PERSISTENCE FUNCTIONS
// ========================================
// Function 1: Sync Session State to Database
// Usage: Call this from Machine cycles and Work Order buttons functions
// Throttled to avoid database overload (updates every 5 seconds or on state changes)
const syncSessionState = function(msg, node, forceUpdate = false) {
const now = Date.now();
const lastSync = flow.get("lastSessionSync") || 0;
// Throttle: only sync every 5 seconds unless forced
if (!forceUpdate && (now - lastSync) < 5000) {
return null;
}
flow.set("lastSessionSync", now);
const sessionId = global.get("currentSessionId") || "session_" + Date.now();
global.set("currentSessionId", sessionId);
const activeOrder = global.get("activeWorkOrder");
const activeOrderId = activeOrder ? activeOrder.id : null;
const activeOrderData = activeOrder ? JSON.stringify(activeOrder) : null;
const sessionData = {
session_id: sessionId,
production_start_time: global.get("productionStartTime") || null,
operating_time: global.get("operatingTime") || 0,
last_update_time: now,
tracking_enabled: global.get("trackingEnabled") ? 1 : 0,
cycle_count: global.get("cycleCount") || 0,
active_work_order_id: activeOrderId,
active_work_order_data: activeOrderData,
machine_online: global.get("machineOnline") ? 1 : 0,
production_started: global.get("productionStarted") ? 1 : 0,
last_cycle_time: global.get("lastCycleTime") || null,
last_machine_state: flow.get("lastMachineState") || 0,
created_at: now,
updated_at: now,
is_active: 1
};
// Create upsert query (INSERT ... ON DUPLICATE KEY UPDATE)
msg.topic = `
INSERT INTO session_state (
session_id, production_start_time, operating_time, last_update_time,
tracking_enabled, cycle_count, active_work_order_id, active_work_order_data,
machine_online, production_started, last_cycle_time, last_machine_state,
created_at, updated_at, is_active
) VALUES (
'${sessionData.session_id}',
${sessionData.production_start_time},
${sessionData.operating_time},
${sessionData.last_update_time},
${sessionData.tracking_enabled},
${sessionData.cycle_count},
${sessionData.active_work_order_id ? "'" + sessionData.active_work_order_id + "'" : "NULL"},
${sessionData.active_work_order_data ? "'" + sessionData.active_work_order_data.replace(/'/g, "''") + "'" : "NULL"},
${sessionData.machine_online},
${sessionData.production_started},
${sessionData.last_cycle_time},
${sessionData.last_machine_state},
${sessionData.created_at},
${sessionData.updated_at},
${sessionData.is_active}
)
ON DUPLICATE KEY UPDATE
production_start_time = VALUES(production_start_time),
operating_time = VALUES(operating_time),
last_update_time = VALUES(last_update_time),
tracking_enabled = VALUES(tracking_enabled),
cycle_count = VALUES(cycle_count),
active_work_order_id = VALUES(active_work_order_id),
active_work_order_data = VALUES(active_work_order_data),
machine_online = VALUES(machine_online),
production_started = VALUES(production_started),
last_cycle_time = VALUES(last_cycle_time),
last_machine_state = VALUES(last_machine_state),
updated_at = VALUES(updated_at),
is_active = VALUES(is_active);
`;
msg._mode = "session-sync";
msg.sessionData = sessionData;
return msg;
};
// ========================================
// Function 2: Startup Recovery
// This function should run on Node-RED startup to check for and restore previous session
const startupRecovery = function(msg, node) {
// Query for most recent active session
msg.topic = `
SELECT * FROM session_state
WHERE is_active = 1
ORDER BY updated_at DESC
LIMIT 1;
`;
msg._mode = "startup-recovery";
return msg;
};
// ========================================
// Function 3: Process Recovery Data
// This processes the database response from startup recovery query
const processRecoveryData = function(msg, node) {
if (!msg.payload || msg.payload.length === 0) {
// No previous session found
node.warn("No previous session found. Starting fresh.");
msg._recoveryStatus = "no-session";
return msg;
}
const sessionData = msg.payload[0];
const now = Date.now();
const timeSinceLastUpdate = now - sessionData.last_update_time;
const hoursElapsed = timeSinceLastUpdate / (1000 * 60 * 60);
// If last update was more than 24 hours ago, consider it stale
if (hoursElapsed > 24) {
node.warn(`Previous session is stale (${hoursElapsed.toFixed(1)} hours old). Starting fresh.`);
msg._recoveryStatus = "session-stale";
return msg;
}
// Prompt user for recovery
msg._recoveryStatus = "session-found";
msg.recoveryData = {
sessionId: sessionData.session_id,
cycleCount: sessionData.cycle_count,
operatingTime: sessionData.operating_time,
workOrderId: sessionData.active_work_order_id,
trackingEnabled: sessionData.tracking_enabled === 1,
hoursElapsed: hoursElapsed.toFixed(1),
lastUpdateTime: new Date(sessionData.last_update_time).toISOString()
};
// Prepare prompt message for user
msg.payload = {
type: "recovery-prompt",
message: `Previous session found from ${hoursElapsed.toFixed(1)} hours ago:
- Work Order: ${sessionData.active_work_order_id || "None"}
- Cycles completed: ${sessionData.cycle_count}
- Operating time: ${(sessionData.operating_time / 3600).toFixed(2)} hours
Would you like to restore this session?`,
data: sessionData
};
return msg;
};
// ========================================
// Function 4: Restore Session
// Call this when user confirms they want to restore the session
const restoreSession = function(msg, node) {
const sessionData = msg.payload;
if (!sessionData) {
node.error("No session data provided for restoration");
return null;
}
try {
// Restore global context variables
global.set("currentSessionId", sessionData.session_id);
global.set("productionStartTime", sessionData.production_start_time);
global.set("operatingTime", sessionData.operating_time || 0);
global.set("trackingEnabled", sessionData.tracking_enabled === 1);
global.set("cycleCount", sessionData.cycle_count || 0);
global.set("machineOnline", sessionData.machine_online === 1);
global.set("productionStarted", sessionData.production_started === 1);
global.set("lastCycleTime", sessionData.last_cycle_time);
// Restore flow context variables
flow.set("lastMachineState", sessionData.last_machine_state || 0);
// Restore active work order if exists
if (sessionData.active_work_order_data) {
try {
const workOrder = JSON.parse(sessionData.active_work_order_data);
global.set("activeWorkOrder", workOrder);
} catch (e) {
node.error("Failed to parse work order data: " + e.message);
}
}
node.warn("Session restored successfully: " + sessionData.session_id);
msg._recoveryStatus = "restored";
msg.payload = {
success: true,
message: "Session restored successfully",
sessionId: sessionData.session_id
};
} catch (error) {
node.error("Error restoring session: " + error.message);
msg._recoveryStatus = "restore-failed";
msg.payload = {
success: false,
message: "Failed to restore session: " + error.message
};
}
return msg;
};
// ========================================
// Function 5: Deactivate Old Sessions
// Call this when starting a new work order to mark old sessions as inactive
const deactivateOldSessions = function(msg, node) {
const currentSessionId = global.get("currentSessionId");
msg.topic = `
UPDATE session_state
SET is_active = 0, updated_at = ${Date.now()}
WHERE is_active = 1
${currentSessionId ? "AND session_id != '" + currentSessionId + "'" : ""};
`;
msg._mode = "deactivate-sessions";
return msg;
};
// ========================================
// EXPORT for use in function nodes
// ========================================
module.exports = {
syncSessionState,
startupRecovery,
processRecoveryData,
restoreSession,
deactivateOldSessions
};