diff --git a/lstV2/server/services/ocp/controller/labeling/labelProcess.ts b/lstV2/server/services/ocp/controller/labeling/labelProcess.ts index 427ea03..6a0e115 100644 --- a/lstV2/server/services/ocp/controller/labeling/labelProcess.ts +++ b/lstV2/server/services/ocp/controller/labeling/labelProcess.ts @@ -78,6 +78,7 @@ export const labelingProcess = async ({ // if we are running the zechettii if (zechette) { + if (zechette.line === "0") return; const macId = await getMac(zechette.line); // filter out the lot for the line filteredLot = lots.data.filter( @@ -163,7 +164,11 @@ export const labelingProcess = async ({ } // if there are more than 2 lots it might be an auto labeler, autolabeler will be defined by its id for now only dayton - if (filteredLot?.length > 2 && plantToken[0].value !== "usday1") { + const plantsCanHaveMultiLots = ["usday1", "usmcd1"]; + if ( + filteredLot?.length > 2 && + !plantsCanHaveMultiLots.includes(plantToken[0].value) + ) { createLog( "error", "labeling", @@ -205,59 +210,65 @@ export const labelingProcess = async ({ }; } - // check the material... mm,color (auto and manual combined), pkg - const mmStaged = await isMainMatStaged(filteredLot[0]); + // as we want to allow zechetti to print no matter what if we zechettii is true skip all this and just print the label + if (!zechette) { + // check the material... mm,color (auto and manual combined), pkg + const mmStaged = await isMainMatStaged(filteredLot[0]); - if (!mmStaged.success) { - createLog("error", "labeling", "ocp", mmStaged.message); + if (!mmStaged.success) { + createLog("error", "labeling", "ocp", mmStaged.message); - return { - success: false, - message: mmStaged.message, - }; - } + return { + success: false, + message: mmStaged.message, + }; + } - // comment only but will check for color - createLog( - "info", - "labeling", - "ocp", - `Remaining pallets for: ${filteredLot[0].MachineDescription} is ${filteredLot[0].Remaining}` - ); - - // do we want to over run - if (filteredLot[0].overPrinting === "no" && filteredLot[0].Remaining <= 0) { + // comment only but will check for color createLog( - "error", + "info", "labeling", "ocp", - `Over Printing on ${filteredLot[0].MachineDescription} is not allowed please change the lot` + `Remaining pallets for: ${filteredLot[0].MachineDescription} is ${filteredLot[0].Remaining}` ); - // for slc we want to run the first label for henkel - firstLotLabel(filteredLot[0]); - return { - success: false, - message: `Over Printing on ${filteredLot[0].MachineDescription} is not allowed please change the lot`, - }; - } - // prolink check by lot - const prolink = await prolinkCheck(filteredLot[0].lot); + // do we want to over run + if ( + filteredLot[0].overPrinting === "no" && + filteredLot[0].Remaining <= 0 + ) { + createLog( + "error", + "labeling", + "ocp", + `Over Printing on ${filteredLot[0].MachineDescription} is not allowed please change the lot` + ); + // for slc we want to run the first label for henkel + firstLotLabel(filteredLot[0]); + return { + success: false, + message: `Over Printing on ${filteredLot[0].MachineDescription} is not allowed please change the lot`, + }; + } - if (!prolink) { - //console.error(`Prolink does not match for ${filteredLot[0].MachineDescription}`); - createLog( - "error", - "labeling", - "ocp", - `Prolink does not match for ${filteredLot[0].MachineDescription}` - ); - return { - success: false, - message: `Prolink does not match for ${filteredLot[0].MachineDescription}`, - }; + // prolink check by lot + const prolink = await prolinkCheck(filteredLot[0].lot); + + if (!prolink) { + //console.error(`Prolink does not match for ${filteredLot[0].MachineDescription}`); + createLog( + "error", + "labeling", + "ocp", + `Prolink does not match for ${filteredLot[0].MachineDescription}` + ); + return { + success: false, + message: `Prolink does not match for ${filteredLot[0].MachineDescription}`, + }; + } + createLog("info", "labeling", "ocp", `Is prolink good? ${prolink}`); } - createLog("info", "labeling", "ocp", `Is prolink good? ${prolink}`); // create the label const label = await createLabel(filteredLot[0], userPrinted); @@ -273,9 +284,12 @@ export const labelingProcess = async ({ } // send over to be booked in if we can do it. + + // same here if we are passing zechettii dont try to book in + const bookin = settingData.filter((s) => s.name === "bookin"); let book: any = []; - if (bookin[0].value === "1") { + if (bookin[0].value === "1" && !zechette) { book = await bookInLabel(label.data); if (!book.success) { diff --git a/lstV2/server/services/ocp/controller/specialProcesses/zechettis/zechetti1.ts b/lstV2/server/services/ocp/controller/specialProcesses/zechettis/zechetti1.ts deleted file mode 100644 index b541af8..0000000 --- a/lstV2/server/services/ocp/controller/specialProcesses/zechettis/zechetti1.ts +++ /dev/null @@ -1,177 +0,0 @@ -import { Controller, Tag } from "st-ethernet-ip"; -import { labelingProcess } from "../../labeling/labelProcess.js"; -import { createLog } from "../../../../logger/logger.js"; - -let plcAddress = "192.168.193.97"; // zechetti 2 -let lastProcessedTimestamp = 0; -let PLC = new Controller() as any; -const labelerTag = new Tag("N7[0]"); // change the car to a or b depending on what zechetti. -//const t = new Tag("CONV_M01_SHTL_UNLD_IN_FROM_PREV_CONV_TRACK_CODE.PAL_ORIGIN_LINE_N") // this is for the new zechette to reach the pallet form - -let pollingInterval: any = null; -let heartbeatInterval: any = null; -let reconnecting = false; -let lastTag = 0; -// Track last successful read -let lastHeartbeat: number = Date.now(); - -export async function zechitti1Connect() { - try { - createLog( - "info", - "zechitti1", - "ocp", - `Connecting to PLC at ${plcAddress}...` - ); - await PLC.connect(plcAddress, 0); - createLog("info", "zechitti1", "ocp", "Zechetti 2 connected."); - - // Start polling tags - startPolling(); - - // Start heartbeat - // startHeartbeat(); - - // Handle disconnects/errors - PLC.on("close", () => { - console.warn("PLC connection closed."); - handleReconnect(); - }); - - PLC.on("error", (err: any) => { - createLog("error", "zechitti1", "ocp", `PLC error: ${err.message}`); - handleReconnect(); - }); - } catch (err: any) { - createLog( - "error", - "zechitti1", - "ocp", - `Initial connection failed: ${err.message}` - ); - handleReconnect(); - } -} - -function startPolling() { - if (pollingInterval) clearInterval(pollingInterval); - - pollingInterval = setInterval(async () => { - try { - await PLC.readTag(labelerTag); - //lastHeartbeat = Date.now(); - - const tagTime: any = new Date(labelerTag.timestamp); - - // so we make sure we are not missing a pallet remove it from the lastTag so we can get this next label correctly - if ( - labelerTag.value == 0 && - Date.now() - lastProcessedTimestamp >= 45000 - ) { - lastTag = labelerTag.value; - } - - // if the tag is not zero and its been longer than 30 seconds and the last tag is not equal to the current tag we can print - if ( - labelerTag.value !== 0 && - lastTag !== labelerTag.value && - tagTime !== lastProcessedTimestamp && - Date.now() - lastProcessedTimestamp >= 30000 - ) { - lastProcessedTimestamp = tagTime; - lastTag = labelerTag.value; - console.log( - `Time since last check: ${ - Date.now() - tagTime - }, greater than 30000, ${ - Date.now() - lastProcessedTimestamp >= 30000 - }, the line to be printed is ${labelerTag.value}` - ); - //console.log(labelerTag); - const zechette = { - line: labelerTag.value.toString(), - printer: 22, // this is the id of the zechetti 2 to print we should move this to the db - printerName: "Zechetti1", - }; - labelingProcess({ zechette: zechette }); - } - } catch (err: any) { - createLog( - "error", - "zechitti1", - "ocp", - `Polling error: ${err.message}` - ); - handleReconnect(); - } - }, 1000); -} - -// function startHeartbeat() { -// if (heartbeatInterval) clearInterval(heartbeatInterval); - -// heartbeatInterval = setInterval(() => { -// const diff = Date.now() - lastHeartbeat; -// if (diff > 60000) { -// // 1 minute -// console.warn(`⚠️ Heartbeat timeout: no data for ${diff / 1000}s`); -// handleReconnect(); -// } -// }, 10000); // check every 10s -// } - -async function handleReconnect() { - if (reconnecting) return; - reconnecting = true; - - if (pollingInterval) { - clearInterval(pollingInterval); - pollingInterval = null; - } - - let delay = 2000; // start at 2s - let attempts = 0; - const maxAttempts = 10; // or limit by time, e.g. 2 min total - - while (!PLC.connected && attempts < maxAttempts) { - attempts++; - createLog( - "info", - "zechitti1", - "ocp", - `Reconnect attempt ${attempts}/${maxAttempts} in ${ - delay / 1000 - }s...` - ); - await new Promise((res) => setTimeout(res, delay)); - - try { - PLC = new Controller(); // fresh instance - await PLC.connect(plcAddress, 0); - createLog("info", "zechitti1", "ocp", "Reconnected to PLC!"); - reconnecting = false; - startPolling(); - return; - } catch (err: any) { - createLog( - "error", - "zechitti1", - "ocp", - `Reconnect attempt failed: ${err.message}` - ); - delay = Math.min(delay * 2, 30000); // exponential backoff up to 30s - } - } - - if (!PLC.connected) { - createLog( - "error", - "zechitti1", - "ocp", - "Max reconnect attempts reached. Stopping retries." - ); - reconnecting = false; - // optional: exit process or alert someone here - // process.exit(1); - } -} diff --git a/lstV2/server/services/ocp/controller/specialProcesses/zechettis/zechettiConnect.ts b/lstV2/server/services/ocp/controller/specialProcesses/zechettis/zechettiConnect.ts new file mode 100644 index 0000000..aa59cdc --- /dev/null +++ b/lstV2/server/services/ocp/controller/specialProcesses/zechettis/zechettiConnect.ts @@ -0,0 +1,27 @@ +import { createPlcMonitor } from "../../../utils/plcController.js"; + +export const zechettiConnect = () => { + const config: any = { + controllers: [ + { + id: "Z1", + ip: "192.168.193.97", + slot: 0, + rpi: 250, + tags: ["N7[0]"], + }, + { + id: "Z2", + ip: "192.168.193.111", + slot: 0, + rpi: 100, + tags: ["N8[0]"], + }, + ], + }; + + const monitor = createPlcMonitor(config); + + // Start + monitor.start(); +}; diff --git a/lstV2/server/services/ocp/ocpService.ts b/lstV2/server/services/ocp/ocpService.ts index 4460ded..0ac1c48 100644 --- a/lstV2/server/services/ocp/ocpService.ts +++ b/lstV2/server/services/ocp/ocpService.ts @@ -17,7 +17,6 @@ import { assignedPrinters } from "./utils/checkAssignments.js"; import { printerCycle } from "./controller/printers/printerCycle.js"; import stopPrinterCycle from "./routes/printers/stopCycle.js"; import startPrinterCycle from "./routes/printers/startCycle.js"; -import { printerCycleAutoLabelers } from "./controller/printers/printerCycleAutoLabelers.js"; import AutostartPrinterCycle from "./routes/printers/autoLabelerStart.js"; import AutostopPrinterCycle from "./routes/printers/autoLabelerStop.js"; import { deleteLabels } from "../../globalUtils/dbCleanUp/labelCleanUp.js"; @@ -26,7 +25,8 @@ import labelRatio from "./routes/labeling/getLabelRatio.js"; import resetRatio from "./routes/labeling/resetLabelRatio.js"; import materialTransferLot from "./routes/materials/lotTransfer.js"; import pendingTransfers from "./routes/materials/currentPending.js"; -import { zechitti1Connect } from "./controller/specialProcesses/zechettis/zechetti1.js"; +import { createPlcMonitor } from "./utils/plcController.js"; +import { zechettiConnect } from "./controller/specialProcesses/zechettis/zechettiConnect.js"; const app = new OpenAPIHono(); @@ -85,7 +85,7 @@ setTimeout(() => { // if zechetti plc is wanted we will connect setTimeout(() => { if (zechetti[0]?.value === "1") { - zechitti1Connect(); + zechettiConnect(); } }, 3 * 1000); diff --git a/lstV2/server/services/ocp/utils/plcController.ts b/lstV2/server/services/ocp/utils/plcController.ts new file mode 100644 index 0000000..97c3f1a --- /dev/null +++ b/lstV2/server/services/ocp/utils/plcController.ts @@ -0,0 +1,155 @@ +import { ControllerManager } from "st-ethernet-ip"; +import { getMac } from "./getMachineId.js"; +import { format } from "date-fns-tz"; +import { tryCatch } from "../../../globalUtils/tryCatch.js"; + +import { getCurrentLabel } from "../../sqlServer/querys/ocp/getLabel.js"; +import { query } from "../../sqlServer/prodSqlServer.js"; +import { createLog } from "../../logger/logger.js"; + +export const createPlcMonitor = (config: any) => { + let cm: any; + let controllers: any = {}; + let stats: any = {}; + let isRunning = false; + + const nowISO = () => { + return new Date().toISOString(); + }; + + const start = () => { + if (isRunning) return; + + cm = new ControllerManager(); + + config.controllers.forEach((cfg: any) => { + const plc: any = cm.addController( + cfg.ip, + cfg.slot, + cfg.rpi, + true, + cfg.retrySP || 3000 + ); + + plc.connect(); + controllers[cfg.id] = plc; + + // initialize stats + stats[cfg.id] = { + id: cfg.id, + ip: cfg.ip, + slot: cfg.slot, + scanRate: cfg.rpi, + connected: false, + lastConnectedAt: null, + lastDisconnectedAt: null, + reconnectCount: 0, + }; + // Add tags + cfg.tags.forEach((tag: any) => plc.addTag(tag)); + + // Events + plc.on("Connected", () => { + const s = stats[cfg.id]; + s.connected = true; + s.lastConnectedAt = nowISO(); + if (s.lastDisconnectedAt) { + s.reconnectCount++; + } + console.log(`[${cfg.id}] Connected @ ${cfg.ip}:${cfg.slot}`); + }); + + plc.on("Disconnected", () => { + const s = stats[cfg.id]; + s.connected = false; + s.lastDisconnectedAt = nowISO(); + console.log(`[${cfg.id}] Disconnected`); + }); + + plc.on("error", (err: any) => { + console.error(`[${cfg.id}] Error:`, err.message); + }); + + plc.on("TagChanged", async (tag: any, prevVal: any) => { + if (tag.value !== 0) { + const time = nowISO(); + if (tag.value === 0) return; + setTimeout(async () => { + if (tag.value === 0) return; + const macId = await getMac(tag.value); + console.log(macId); + const { data, error } = (await tryCatch( + query( + getCurrentLabel + .replace( + "[macId]", + macId[0]?.HumanReadableId + ) + .replace( + "[time]", + format(time, "yyyy-MM-dd HH:mm") + ), + "Current label data" + ) + )) as any; + + createLog( + "info", + "zechettii", + "zechettii", + `${format(time, "yyyy-MM-dd HH:mm")} [${cfg.id}] ${ + tag.name + }: ${prevVal} -> ${ + tag.value + }, the running number is ${ + error ? null : data.data[0]?.LfdNr + }}` + ); + }, 1000); + } + }); + }); + + isRunning = true; + }; + + const stop = () => { + if (!isRunning) return; + Object.values(controllers).forEach((plc: any) => { + try { + plc.disconnect(); + } catch {} + }); + controllers = {}; + cm = null; + isRunning = false; + console.log("Monitor stopped"); + }; + + const restart = () => { + console.log("Restarting the plc(s)"); + stop(); + new Promise((resolve) => setTimeout(resolve, 1500)); + start(); + }; + + const status = () => { + const result: any = {}; + + for (const [id, s] of Object.entries(stats)) { + let s: any; + let uptimeMs = null, + downtimeMs = null; + if (s.connected && s.lastConnectedAt) { + uptimeMs = Date.now() - new Date(s.lastConnectedAt).getTime(); + } else if (!s.connected && s.lastDisconnectedAt) { + downtimeMs = + Date.now() - new Date(s.lastDisconnectedAt).getTime(); + } + result[id] = { ...s, uptimeMs, downtimeMs }; + } + return result; + }; + + return { start, stop, restart, status }; +};