feat(lstv2 move): moved lstv2 into this app to keep them combined and easier to maintain

This commit is contained in:
2025-09-19 22:22:05 -05:00
parent caf2315191
commit e4477402ad
847 changed files with 165801 additions and 0 deletions

View File

@@ -0,0 +1,54 @@
import { eq } from "drizzle-orm";
import { db } from "../../../../../../database/dbclient.js";
import { settings } from "../../../../../../database/schema/settings.js";
import { tryCatch } from "../../../../../globalUtils/tryCatch.js";
import { createLog } from "../../../../logger/logger.js";
const st = 5;
const sm = 55;
const et = 6;
const em = 5;
export const billingCheck = async () => {
const now = new Date();
const startTime = new Date();
const endTime = new Date();
let isOutsideBilling = false;
const { data, error } = await tryCatch(db.select().from(settings));
if (error) {
return { success: false, message: "Error in getting settings." };
}
const plant = data.filter((n) => n.name === "plantToken");
// set everything up
startTime.setHours(st, sm, 0, 0);
endTime.setHours(et, em, 0, 0);
if (plant[0].value === "usflo1") {
if (now >= startTime && now <= endTime) {
createLog(
"warn",
"specialProcess",
"ocp",
`You are inside the billing window no labels will print! please wait`
);
isOutsideBilling = true;
} else {
createLog(
"info",
"specialProcess",
"ocp",
"Out side billing window ok to print"
);
}
} else {
createLog(
"debug",
"specialProcess",
"ocp",
"This plant dose not check for billing"
);
}
return isOutsideBilling;
};

View File

@@ -0,0 +1,147 @@
import { createLog } from "../../../../logger/logger.js";
import { query } from "../../../../sqlServer/prodSqlServer.js";
export const dualPrintingProcess = async (lotInfo: any) => {
// currently we will only allow for 2 printers to be linked only crazy people want more than 2
createLog(
"info",
"ocp",
"ocp",
"Dual printing running logic here to see what to print"
);
// each check
let lastPrinted;
let firstLine;
let secondLine;
// this query checks the last line to print a label
const printedQuery = `SELECT TOP(1) IdProdPlanung,
IdArtikelvarianten,
IdMaschine,
LfdNr,
ArtikelVariantenBez,
Add_User,
Add_Date,
Upd_User,
Upd_Date,
IdProdBereich
FROM AlplaPROD_test1.dbo.T_EtikettenGedruckt (nolock)
where IdMaschine in (${lotInfo[0].machineID},${lotInfo[1].machineID}) order by Add_Date desc`;
const fisrtLineDT = `SELECT top(1) IdMaschine,
case when Endzeit ='1900-01-01 00:00:00.000' and DATEDIFF(MINUTE, Startzeit, GETDATE()) >= 3 Then 1 else 0 end as 'LineCheck'
FROM AlplaPROD_test1.dbo.T_HistoryStillstandsereignis (nolock) where IdMaschine = ${lotInfo[0].machineID} order by Add_Date desc`;
const secondLineDT = `SELECT top(1) IdMaschine,
case when Endzeit ='1900-01-01 00:00:00.000' and DATEDIFF(MINUTE, Startzeit, GETDATE()) >= 3 Then 1 else 0 end as 'LineCheck'
FROM AlplaPROD_test1.dbo.T_HistoryStillstandsereignis (nolock) where IdMaschine = ${lotInfo[1].machineID} order by Add_Date desc`;
try {
// get what was last printed
const result: any = await query(printedQuery, "Last Printed Query");
lastPrinted = result.data[0].IdMaschine;
} catch (err) {
createLog(
"error",
"ocp",
"ocp",
`Error: getting last printed querty: ${err}`
);
}
try {
// get if if running or down
const first: any = await query(fisrtLineDT, "First line DT Check");
firstLine = first.data[0].LineCheck;
} catch (err) {
createLog(
"error",
"ocp",
"ocp",
`Error: with getting ${lotInfo[0].machineID} data query: ${err}`
);
}
try {
const second: any = await query(secondLineDT, "Second line DT Check");
secondLine = second.data[0].LineCheck;
} catch (err) {
createLog(
"error",
"ocp",
"ocp",
`Error: with getting ${lotInfo[1].machineID} data query: ${err}`
);
}
// first check if both are up and running
if (firstLine === 0 && secondLine === 0) {
createLog("info", "ocp", "ocp", "Both lines are up");
// what line last printed
if (lastPrinted === lotInfo[0].machineID) {
// IE 7 then print 8, usslc was the main example for all of this
let whatToPrint = lotInfo.filter(
(m: any) => m.machineID === lotInfo[1].machineID
);
// send to label
createLog(
"info",
"ocp",
"ocp",
`Printing label for ${whatToPrint[0].MachineDescription}`
);
return whatToPrint;
}
if (lastPrinted === lotInfo[1].machineID) {
// IE 8 then print 7, usslc was the main example for all of this
let whatToPrint = lotInfo.filter(
(m: any) => m.machineID === lotInfo[0].machineID
);
// send to label
createLog(
"info",
"ocp",
"ocp",
`Printing label for ${whatToPrint[0].MachineDescription}`
);
return whatToPrint;
}
}
// as we are only allowing 2 lines if one is down the other must be up... still going to conditional it to be sure
createLog(
"info",
"ocp",
"ocp",
"Looking at what line is down and printing the other one!"
);
if (firstLine === 1 && secondLine === 0) {
// print the other line
let whatToPrint = lotInfo.filter(
(m: any) => m.machineID === lotInfo[1].machineID
);
// send to label
createLog(
"info",
"ocp",
"ocp",
`Printing label for ${whatToPrint.MachineDescription}`
);
return whatToPrint;
} else if (firstLine === 0 && secondLine === 1) {
// print the other line
let whatToPrint = lotInfo.filter(
(m: any) => m.machineID === lotInfo[0].machineID
);
// send to label
createLog(
"info",
"ocp",
"ocp",
`Printing label for ${whatToPrint.MachineDescription}`
);
return whatToPrint;
}
};

View File

@@ -0,0 +1,103 @@
import { Controller, Tag } from "st-ethernet-ip";
import { createLog } from "../../../../logger/logger.js";
import { labelerTagRead } from "./plcTags/labelerTag.js";
import { palletSendTag } from "./plcTags/palletSendTag.js";
import { strapperFaults } from "./plcTags/strapperFault.js";
let PLC = new Controller();
let isDycoRunning = false;
// PLC address
let plcAddress = "10.44.5.4";
let isReading = false;
// Initialize the interval variable outside the function
let plcCycle: any;
let plcInterval = 500;
// Create Tag Instances
const labelerTag: any = new Tag("labeler.line_info");
const palletSend = new Tag("Zone_6.Ready_to_Send");
const strapperError = new Tag("Zone_3.Strapper_Faulted");
export const dycoConnect = async () => {
// if we crash or start over reset the timers so we dont get duplicates
clearInterval(plcCycle);
if (isDycoRunning)
return { success: false, message: "Dyco is already connected." };
// Remove all listeners before adding a new one to prevent memory leaks
PLC.removeAllListeners("error");
try {
await PLC.connect(plcAddress, 0).then(async () => {
createLog("info", "dyco", "ocp", `We are connected to the dyco.`);
isDycoRunning = true;
plcCycle = setInterval(async () => {
if (isReading) {
createLog(
"debug",
"dyco",
"ocp",
"Skipping cycle: previous read still in progress."
);
return;
}
isReading = true; // Set flag
try {
await PLC.readTag(labelerTag);
await PLC.readTag(palletSend);
await PLC.readTag(strapperError);
// strapper check
strapperFaults(strapperError);
// send the labeler tag data over
labelerTagRead(labelerTag);
// send the end of line check over.
palletSendTag(palletSend);
} catch (error: any) {
createLog(
"error",
"dyco",
"ocp",
`Error reading PLC tag: ${error.message}`
);
} finally {
isReading = false; // Reset flag
}
}, plcInterval);
});
} catch (error) {
createLog(
"error",
"dyco",
"ocp",
`There was an error in the dyco: ${error}`
);
await PLC.disconnect();
isDycoRunning = false;
}
};
export const closeDyco = async () => {
if (!isDycoRunning)
return { success: false, message: "Dyco is not connected." };
console.log(`Closing the connection`);
try {
await PLC.disconnect();
isDycoRunning = false;
return {
success: true,
message: "Dyco Connection is now closed.",
};
} catch (error) {
console.log(error);
return {
success: false,
message: "There was an error closing the dyco connection.",
};
}
};

View File

@@ -0,0 +1,119 @@
import { db } from "../../../../../../../database/dbclient.js";
import { settings } from "../../../../../../../database/schema/settings.js";
import { tryCatch } from "../../../../../../globalUtils/tryCatch.js";
import { createLog } from "../../../../../logger/logger.js";
import { readTags } from "../../../../../rfid/controller/readTags.js";
import { labelingProcess } from "../../../labeling/labelProcess.js";
import { autoLabelCreated } from "../../../labeling/labelRatio.js";
import { stapperFaulted, strapperFaults } from "./strapperFault.js";
export let cameraPalletCheck = 20;
export let currentPalletCheck = 0;
let lastProcessedTimestamp = 0;
export const labelerTagRead = async (tagData: any) => {
/**
* Reads the tag data from the Dyco PLC and processes it based on the feedback.
*/
// Convert tag data buffer to a string and extract numeric values
const buffer = Buffer.from(tagData.value);
const numericString = buffer.toString("utf8").replace(/[^0-9#]/g, "");
// Ignore empty or invalid tag data
if (numericString === "#") {
return;
}
const tagTime = new Date(tagData.state.timestamp).getTime();
// get the settings
const { data: settingData, error: settingError } = await tryCatch(
db.select().from(settings)
);
if (settingError) {
createLog(
"error",
"dyco",
"ocp",
"There was an error getting the settings"
);
return;
}
// check the dyco settings
const dycoPrint = settingData.filter((n) => n.name === "dycoPrint");
// Only process if this is a new timestamp within the last 5 seconds
if (tagTime !== lastProcessedTimestamp && Date.now() - tagTime <= 8000) {
lastProcessedTimestamp = tagTime;
/**
* add logic in to see if this is the first time we run this so we return an error to validate we are in sync.
*/
createLog(
"info",
"dyco",
"ocp",
`Current pallet at the wrapper is: ${numericString}`
);
if (dycoPrint[0].value === "1") {
createLog("info", "dyco", "ocp", "Dyco will be printing the label");
// if (!dycoControlCheck) {
// createLog(
// "error",
// "dyco",
// "ocp",
// `Dyco was switch to be the printer guys. please validate the line is in sync, Current line is ${numericString}`
// );
// dycoControlCheck = true;
// return;
// }
// check the stapper error logic.
if (stapperFaulted) {
createLog("error", "dyco", "ocp", `Strapper is faulted.`);
return;
}
// check if we need to manual check due to 20 pallets.
if (currentPalletCheck <= cameraPalletCheck) {
currentPalletCheck++;
labelingProcess({ line: numericString });
createLog(
"info",
"dyco",
"ocp",
`You have printed ${currentPalletCheck} pallets, remaining until ${
cameraPalletCheck - currentPalletCheck
}.`
);
autoLabelCreated();
} else {
currentPalletCheck = 0;
createLog(
"warn",
"dyco",
"ocp",
`You have reached 20 pallets since the last check please validate the labeler is still in sync.`
);
return;
}
}
if (dycoPrint[0].value === "0") {
createLog(
"info",
"dyco",
"ocp",
"Rfid system is contorlling the printing"
);
// trigger the reader so we can get the label from the tag readers.
setTimeout(async () => {
await readTags("wrapper1");
}, 500); // half second delay on this guy
}
}
};
/**
* setting to switch between rfid and dyco labeling
*/

View File

@@ -0,0 +1,59 @@
import { createLog } from "../../../../../logger/logger.js";
import { pickedup } from "../../../../../ocme/controller/pickedup.js";
import { triggerScanner } from "../../../../../ocme/controller/triggerCamera.js";
import { serverSettings } from "../../../../../server/controller/settings/getSettings.js";
let lastProcessedTimestamp = 0;
export const palletSendTag = async (tagData: any) => {
/**
* Reads the tag data from the Dyco PLC and processes it based on the feedback.
* We will only trigger the camera and removal of pending tags
*/
const ocmeActive = serverSettings.filter((n) => n.name === "ocmeService");
const tagTime = new Date(tagData.state.timestamp).getTime();
// Only process if this is a new timestamp within the last 5 seconds
if (
tagTime !== lastProcessedTimestamp &&
Date.now() - tagTime <= 5000 &&
tagData.value &&
ocmeActive[0].value === "1"
) {
lastProcessedTimestamp = tagTime;
//console.log(tagData.state.timestamp);
createLog(
"info",
"dyco",
"ocp",
`Station 6 is ${tagData.value ? "full" : "empty"}`
);
// take the picture
setTimeout(async () => {
const scan: any = await triggerScanner();
if (!scan.success) {
createLog(
"error",
"dyco",
"ocp",
`Scanner failed to take a picture trying one more time in 10 seconds`
);
setTimeout(async () => {
await triggerScanner();
}, 10 * 1000);
}
}, 15 * 1000);
}
if (
tagTime !== lastProcessedTimestamp &&
Date.now() - tagTime <= 5000 &&
!tagData.value &&
ocmeActive[0].value === "1"
) {
await pickedup({ runningNr: 1234, all: true, areaFrom: "wrapper_1" });
}
};

View File

@@ -0,0 +1,86 @@
import { db } from "../../../../../../../database/dbclient.js";
import { settings } from "../../../../../../../database/schema/settings.js";
import { tryCatch } from "../../../../../../globalUtils/tryCatch.js";
import { createLog } from "../../../../../logger/logger.js";
// strapper related issues
export let strapperActive = false;
export let stapperFaulted = false;
export let strapperFaultCount = 3; // move to db so we can control it outside the app
export const strapperFaults = async (tagData: any) => {
const { data, error } = await tryCatch(db.select().from(settings));
// failed to get settings
if (error) {
return { success: false, message: "Failed to get settings." };
}
const strapperCheckSetting = data.filter((n) => n.name === "strapperCheck");
// strapper error is off
if (strapperCheckSetting[0]?.value === "0") {
return;
}
// strapper error is on
if (strapperCheckSetting[0]?.value === "1") {
// faulted and still has a check or 2 to go
if (stapperFaulted && strapperFaultCount > 0) {
createLog(
"warn",
"dyco",
"ocp",
`There was a strapper error, remaining pallets to check ${strapperFaultCount}.`
);
strapperFaultCount = strapperFaultCount - 1;
return {
success: true,
message: `There was a strapper error, remaining pallets to check ${strapperFaultCount}.`,
};
} else {
// no more checks needed clearing everything
createLog(
"debug",
"dyco",
"ocp",
`Strapper check is active but not faulted, remaining pallets to check ${strapperFaultCount}.`
);
// reset everything
stapperFaulted = false;
strapperFaultCount = 3; // move to db as well
return {
success: false,
message: `Strapper check is active but not faulted, remaining pallets to check ${strapperFaultCount}.`,
};
}
}
// strapper was triggered turning on the counter.
//console.log(`Strapper fault is ${strapperError.value}`);
if (tagData.value && strapperFaultCount > 0) {
// strapper faulted we want to start the trigger to force the check
if (!stapperFaulted) {
createLog(
"error",
"dyco",
"ocp",
`Strapper errored triggering, manual checks will be required for the next ${strapperFaultCount}`
);
stapperFaulted = true;
// change move fault count to db....
strapperFaultCount = 3;
return {
success: true,
message: `Strapper errored triggering, manual checks will be required for the next ${strapperFaultCount}`,
};
}
}
return {
success: false,
message: `Some how we made it here and just going to say we are good. :)`,
};
};

View File

@@ -0,0 +1,91 @@
import axios from "axios";
import { prodEndpointCreation } from "../../../../../globalUtils/createUrl.js";
import { lstAuth } from "../../../../../index.js";
import { createLog } from "../../../../logger/logger.js";
import { db } from "../../../../../../database/dbclient.js";
import { prodlabels } from "../../../../../../database/schema/prodLabels.js";
import { eq, sql } from "drizzle-orm";
export const delieryInhouse = async (data: any) => {
// update sscc so we can book in
const SSCC = data.SSCC.slice(2);
// api url
const url = await prodEndpointCreation(
"/public/v1.0/Warehousing/InhouseDelivery"
);
// create bookin
const newDelivery = {
scannerId: 99,
sscc: SSCC,
};
try {
const res = await axios.post(url, newDelivery, {
headers: {
Authorization: `Basic ${lstAuth}`,
accept: "text/plain",
},
});
if (res.data.Result !== 0) {
createLog(
"error",
"labeling",
"ocp",
`${data.printer?.name}, Error:${res.data.Message}`
);
//printerUpdate(data.printer, 7, "Error while deliverying inhouse.");
return {
success: true,
message: `${data.printer?.name} had an error while trying to deliver.`,
data: res.data,
};
} // label was just delivered
createLog(
"info",
"labeling",
"ocp",
`${parseInt(data.SSCC.slice(10, -1))}, was just delivered`
);
try {
await db
.update(prodlabels)
.set({
status: "Delivered",
upd_date: sql`NOW()`,
})
.where(
eq(prodlabels.runningNr, parseInt(data.SSCC.slice(10, -1)))
);
} catch (error) {
createLog(
"error",
"labeling",
"ocp",
`Error updating ${parseInt(data.SSCC.slice(10, -1))} in the DB.`
);
return {
success: true,
message: `Error updating ${parseInt(
data.SSCC.slice(10, -1)
)} in the DB.`,
data: error,
};
}
} catch (error) {
createLog(
"error",
"labeling",
"ocp",
`${data.printer.name}, "Error: ${error}`
);
return {
success: true,
message: `Error deliverying ${parseInt(data.SSCC.slice(10, -1))}.`,
data: error,
};
}
};

View File

@@ -0,0 +1,72 @@
import { db } from "../../../../../../database/dbclient.js";
import { settings } from "../../../../../../database/schema/settings.js";
import { tryCatch } from "../../../../../globalUtils/tryCatch.js";
import { createLog } from "../../../../logger/logger.js";
import { query } from "../../../../sqlServer/prodSqlServer.js";
export const firstLotLabel = async (lot: any) => {
/*
currently this is only requested by slc
when a lot is finished and not set to over run we will trigger this function to insert data into the custom T_firstLotLabel table.
in nice label we look for unique id to run the process and then if the add date is older than 7 days we delete it for tracablility purpose.
we will also only do this for specific customers currently henkel is the requested.
*/
/* work on the server side
new table created in the _cus db
CREATE TABLE T_firstLotLabel
(
ID INT IDENTITY(1,1) PRIMARY KEY,
printerName varchar(max),
add_date datetime
-- Add other columns here as needed
);
the nicelabel trigger to be activated.
*/
const { data: settingData, error: settingError } = await tryCatch(
db.select().from(settings)
);
if (settingError) {
return {
success: false,
message: "There was an error getting the settings.",
settingError,
};
}
// get the plantToken
const plantToken = settingData.filter((n) => n.name === "plantToken");
createLog(
"info",
"ocp",
"ocp",
`Checking if we can create the first label lot.`
);
if (plantToken[0].value === "usslc1") {
const newLabel = `insert into T_firstLotLabel (printerName, add_date) VALUES('${lot.PrinterName}', getdate())`;
// if (lot.CustomerId === 40) {
createLog(
"info",
"ocp",
"ocp",
`Creating first lot label for ${lot.MachineDescription}`
);
await query(newLabel, "newLotLabel");
createLog(
"info",
"ocp",
"ocp",
`Was created for ${lot.MachineDescription}`
);
//}
} else {
return;
}
};

View File

@@ -0,0 +1,177 @@
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);
}
}