MVP
This commit is contained in:
240
session_state_functions.js
Normal file
240
session_state_functions.js
Normal 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
|
||||
};
|
||||
Reference in New Issue
Block a user