// ===== MACHINE CYCLES FUNCTION (MODIFIED WITH DB PERSISTENCE) ===== const current = Number(msg.payload) || 0; let zeroStreak = flow.get("zeroStreak") || 0; zeroStreak = current === 0 ? zeroStreak + 1 : 0; flow.set("zeroStreak", zeroStreak); const prev = flow.get("lastMachineState") ?? 0; flow.set("lastMachineState", current); global.set("machineOnline", true); // force ONLINE for now let productionRunning = !!global.get("productionStarted"); let stateChanged = false; if (current === 1 && !productionRunning) { productionRunning = true; stateChanged = true; } else if (current === 0 && zeroStreak >= 2 && productionRunning) { productionRunning = false; stateChanged = true; } global.set("productionStarted", productionRunning); const stateMsg = stateChanged ? { _mode: "production-state", machineOnline: true, productionStarted: productionRunning } : null; const activeOrder = global.get("activeWorkOrder"); const cavities = Number(global.get("moldActive") || 0); if (!activeOrder || !activeOrder.id || cavities <= 0) { // We still want to pass along any state change even if there's no active WO. return [null, stateMsg, null]; // Added third output for DB sync } // Check if tracking is enabled (START button clicked) const trackingEnabled = !!global.get("trackingEnabled"); if (!trackingEnabled) { // Cycles are happening but we're not tracking them yet return [null, stateMsg, null]; } // only count rising edges (0 -> 1) for production totals if (prev === 1 || current !== 1) { // *** NEW: Sync to DB on state changes (throttled) *** if (stateChanged) { const dbSyncMsg = createDbSyncMessage(false); // throttled sync return [null, stateMsg, dbSyncMsg]; } return [null, stateMsg, null]; } let cycles = Number(global.get("cycleCount") || 0) + 1; global.set("cycleCount", cycles); // ===== PHASE 2: OPERATING TIME TRACKING ===== // Track actual operating time between cycles const now = Date.now(); const lastCycleTime = global.get("lastCycleTime"); if (lastCycleTime) { const deltaMs = now - lastCycleTime; const deltaSec = deltaMs / 1000; const currentOperatingTime = global.get("operatingTime") || 0; global.set("operatingTime", currentOperatingTime + deltaSec); } global.set("lastCycleTime", now); // Auto-prompt for scrap when target is reached const scrapPromptIssuedFor = global.get("scrapPromptIssuedFor"); if (cycles === Number(activeOrder.target) && scrapPromptIssuedFor !== activeOrder.id) { // Mark that we've prompted for this work order to prevent repeated prompts global.set("scrapPromptIssuedFor", activeOrder.id); // Send scrap prompt message const scrapPromptMsg = { _mode: "scrap-prompt", payload: { workOrderId: activeOrder.id, cycles: cycles, target: activeOrder.target, message: `Target of ${activeOrder.target} cycles reached. Do you have scrap parts to report?` } }; // *** NEW: Sync to DB when target is reached (forced) *** const dbSyncMsg = createDbSyncMessage(true); // forced sync return [scrapPromptMsg, stateMsg, dbSyncMsg]; } // *** NEW: Sync to DB on each cycle (throttled) *** const dbSyncMsg = createDbSyncMessage(false); // throttled sync return [null, stateMsg, dbSyncMsg]; // ======================================== // HELPER FUNCTION: Create DB Sync Message // ======================================== function createDbSyncMessage(forceUpdate) { 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 }; // Escape single quotes in JSON data const escapedOrderData = sessionData.active_work_order_data ? sessionData.active_work_order_data.replace(/'/g, "''") : null; // Create upsert query const query = ` 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"}, ${escapedOrderData ? "'" + escapedOrderData + "'" : "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); `; return { _mode: "session-sync", topic: query, sessionData: sessionData }; }