MVP
This commit is contained in:
200
phase1_machine_cycles_function.js
Normal file
200
phase1_machine_cycles_function.js
Normal file
@@ -0,0 +1,200 @@
|
||||
// ============================================================================
|
||||
// UPDATED "Machine cycles" Function
|
||||
// Location: flows.json, line 1150, node ID: 0d023d87a13bf56f
|
||||
// Outputs: 3 (added third output for database state backup)
|
||||
// ============================================================================
|
||||
|
||||
const current = Number(msg.payload) || 0;
|
||||
|
||||
// ============================================================================
|
||||
// SECTION 1: TIME TRACKING (NEW - Must run BEFORE any early returns)
|
||||
// ============================================================================
|
||||
const now = Date.now();
|
||||
const trackingEnabled = !!global.get("trackingEnabled");
|
||||
const lastUpdate = global.get("lastUpdateTime") || now;
|
||||
const deltaMs = now - lastUpdate;
|
||||
const deltaSeconds = deltaMs / 1000;
|
||||
|
||||
// Sanity check: Protect against clock skew (negative delta or >5 min gap)
|
||||
if (deltaSeconds < 0 || deltaSeconds > 300) {
|
||||
node.warn(`[TIME] Abnormal delta: ${deltaSeconds.toFixed(2)}s - clock skew or restart detected, resetting timer`);
|
||||
global.set("lastUpdateTime", now);
|
||||
// Don't accumulate time, but continue processing
|
||||
} else {
|
||||
// Normal delta, accumulate time if tracking is enabled
|
||||
if (trackingEnabled) {
|
||||
// Initialize timing vars if they don't exist (handles restart scenario)
|
||||
if (!global.get("productionStartTime")) {
|
||||
node.warn("[TIME] Production start time missing, initializing now");
|
||||
global.set("productionStartTime", now);
|
||||
global.set("operatingTime", 0);
|
||||
global.set("downtime", 0);
|
||||
}
|
||||
|
||||
// Accumulate operating time when machine is running (state 1)
|
||||
if (current === 1) {
|
||||
const opTime = global.get("operatingTime") || 0;
|
||||
global.set("operatingTime", opTime + deltaSeconds);
|
||||
}
|
||||
// Accumulate downtime when machine is stopped (state 0) while tracking
|
||||
else if (current === 0) {
|
||||
const downTime = global.get("downtime") || 0;
|
||||
global.set("downtime", downTime + deltaSeconds);
|
||||
}
|
||||
}
|
||||
global.set("lastUpdateTime", now);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// SECTION 2: EXISTING CYCLE COUNTING LOGIC (UNCHANGED)
|
||||
// ============================================================================
|
||||
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];
|
||||
}
|
||||
|
||||
// Check if tracking is enabled (START button clicked)
|
||||
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) {
|
||||
return [null, stateMsg, null];
|
||||
}
|
||||
|
||||
let cycles = Number(global.get("cycleCount") || 0) + 1;
|
||||
global.set("cycleCount", cycles);
|
||||
|
||||
// Calculate good parts: total produced minus accumulated scrap
|
||||
const scrapTotal = Number(activeOrder.scrap) || 0;
|
||||
const totalProduced = cycles * cavities;
|
||||
const produced = totalProduced - scrapTotal;
|
||||
const target = Number(activeOrder.target) || 0;
|
||||
const progress = target > 0 ? Math.min(100, Math.round((produced / target) * 100)) : 0;
|
||||
|
||||
activeOrder.good = produced;
|
||||
activeOrder.progressPercent = progress;
|
||||
activeOrder.lastUpdateIso = new Date().toISOString();
|
||||
global.set("activeWorkOrder", activeOrder);
|
||||
|
||||
const promptIssued = global.get("scrapPromptIssuedFor") || null;
|
||||
|
||||
if (!promptIssued && target > 0 && produced >= target) {
|
||||
node.warn(`[DEBUG] TRIGGERING PROMPT!`);
|
||||
global.set("scrapPromptIssuedFor", activeOrder.id);
|
||||
msg._mode = "scrap-prompt";
|
||||
msg.scrapPrompt = {
|
||||
id: activeOrder.id,
|
||||
sku: activeOrder.sku || "",
|
||||
target,
|
||||
produced
|
||||
};
|
||||
return [null, msg, null]; // bypass the DB update on this cycle
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// SECTION 3: DATABASE UPDATE MESSAGE (EXISTING)
|
||||
// ============================================================================
|
||||
const dbMsg = {
|
||||
_mode: "cycle",
|
||||
cycle: {
|
||||
id: activeOrder.id,
|
||||
sku: activeOrder.sku || "",
|
||||
target,
|
||||
good: produced,
|
||||
scrap: Number(activeOrder.scrap) || 0,
|
||||
cycleTime: Number(activeOrder.cycleTime || activeOrder.theoreticalCycleTime || 0),
|
||||
progressPercent: progress,
|
||||
lastUpdateIso: activeOrder.lastUpdateIso,
|
||||
machineOnline: true,
|
||||
productionStarted: productionRunning
|
||||
},
|
||||
topic: `
|
||||
UPDATE work_orders
|
||||
SET
|
||||
good_parts = ${produced},
|
||||
progress_percent = ${progress},
|
||||
updated_at = NOW()
|
||||
WHERE work_order_id = '${activeOrder.id}';
|
||||
`
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// SECTION 4: STATE BACKUP TO DATABASE (NEW - Every 10th cycle to reduce load)
|
||||
// ============================================================================
|
||||
let cyclesSinceBackup = flow.get("cyclesSinceBackup") || 0;
|
||||
cyclesSinceBackup++;
|
||||
flow.set("cyclesSinceBackup", cyclesSinceBackup);
|
||||
|
||||
let stateBackupMsg = null;
|
||||
if (cyclesSinceBackup >= 10) {
|
||||
// Reset counter
|
||||
flow.set("cyclesSinceBackup", 0);
|
||||
|
||||
// Backup current state to database
|
||||
const productionStartTime = global.get("productionStartTime") || null;
|
||||
const operatingTime = global.get("operatingTime") || 0;
|
||||
const downtime = global.get("downtime") || 0;
|
||||
const lastUpdateTime = global.get("lastUpdateTime") || null;
|
||||
const scrapPromptIssuedFor = global.get("scrapPromptIssuedFor") || null;
|
||||
|
||||
stateBackupMsg = {
|
||||
_mode: "state-backup",
|
||||
topic: `
|
||||
UPDATE session_state
|
||||
SET
|
||||
work_order_id = '${activeOrder.id}',
|
||||
cycle_count = ${cycles},
|
||||
production_start_time = ${productionStartTime},
|
||||
operating_time = ${operatingTime.toFixed(2)},
|
||||
downtime = ${downtime.toFixed(2)},
|
||||
last_update_time = ${lastUpdateTime},
|
||||
tracking_enabled = ${trackingEnabled ? 1 : 0},
|
||||
machine_state = ${current},
|
||||
scrap_prompt_issued_for = ${scrapPromptIssuedFor ? `'${scrapPromptIssuedFor}'` : 'NULL'}
|
||||
WHERE session_key = 'current_session';
|
||||
`
|
||||
};
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// OUTPUTS:
|
||||
// Output 1: Database update for work_orders table (existing)
|
||||
// Output 2: State message to UI (existing)
|
||||
// Output 3: State backup to session_state table (NEW)
|
||||
// ============================================================================
|
||||
return [dbMsg, stateMsg, stateBackupMsg];
|
||||
Reference in New Issue
Block a user