Hold data for a particular time in function node

Hi,

I am trying to create an event box from PLC events. At a time, only one event can occur. I have four events: CrustBreaker, ANodeRemoval, CavityScoop, and AnodeInstallation. When any event is active, the event with the necessary parameters will be captured, including the trigger date and time. However, I only want to send it to the next node when the same trigger is off. This is because at that time, I will also capture the end time and date. Unfortunately, when I try the sequence, all of the data is not being captured. It was captured when the trigger was ON, but when the trigger is OFF, all of the data is erased.

Can you help me figure out why the data is not being captured when the trigger is OFF?

// Initialize the flag and eventBox
let flagged = false;
let eventBox = msg.eventBox || null; // Retain previous eventBox data

// Extract the Activities value
let Activities = msg.payload["LEVEL2TAGS[3]"];

// Interpret individual bits from Activities
const CrustBreaker = (Activities & (1 << 0)) !== 0;
const AnodeRemoval = (Activities & (1 << 1)) !== 0;
const CavityScoop = (Activities & (1 << 2)) !== 0;
const NewAnodeInstall = (Activities & (1 << 3)) !== 0;

// Get current timestamp
const currentTimestamp = new Date().toISOString();

// Create a function to generate event IDs
function generateEventID(eventNum, potNum, anodeNum) {
    return `${eventNum}_${potNum}_${anodeNum}`;
}

// Check if the event flag is set and the event has turned inactive
if (flagged && !CrustBreaker && !AnodeRemoval && !CavityScoop && !NewAnodeInstall) {
    if (eventBox && !eventBox.eventInactivatedTime) {
        eventBox.eventInactivatedTime = currentTimestamp.slice(11, 19);
        eventBox.eventInactivatedDate = currentTimestamp.slice(0, 10);
        // eventBox.totalEventSeconds = Math.floor((new Date(currentTimestamp) - new Date(eventBox.eventActivatedTime)) / 1000);
        flagged = false;
    }
}

// Handle the active event (using switch instead of if-else)
switch (true) {
    case CrustBreaker:
        eventBox = {
            event: "CrustBreaker",
            eventActivatedTime: currentTimestamp.slice(11, 19),
            eventActivatedDate: currentTimestamp.slice(0, 10),
            eventInactivatedTime: null,
            eventInactivatedDate: null,
            totalEventSeconds: 0
        };
        flagged = true;
        break;
    case AnodeRemoval:
        eventBox = {
            event: "AnodeRemoval",
            eventActivatedTime: currentTimestamp.slice(11, 19),
            eventActivatedDate: currentTimestamp.slice(0, 10),
            eventInactivatedTime: null,
            eventInactivatedDate: null,
            totalEventSeconds: 0
        };
        flagged = true;
        break;
    case CavityScoop:
        eventBox = {
            event: "CavityScoop",
            eventActivatedTime: currentTimestamp.slice(11, 19),
            eventActivatedDate: currentTimestamp.slice(0, 10),
            eventInactivatedTime: null,
            eventInactivatedDate: null,
            totalEventSeconds: 0
        };
        flagged = true;
        break;
    case NewAnodeInstall:
        eventBox = {
            event: "NewAnodeInstall",
            eventActivatedTime: currentTimestamp.slice(11, 19),
            eventActivatedDate: currentTimestamp.slice(0, 10),
            eventInactivatedTime: null,
            eventInactivatedDate: null,
            totalEventSeconds: 0
        };
        flagged = true;
        break;
    default:
        // Keep existing eventBox data
        flagged = false;
}

// Set the event box and flag as part of the message payload
msg.eventBox = eventBox;
msg.flagged = flagged;

// If all events are inactive and flagged, send the message to the next node
//if (!flagged) {
    return msg;
//} else {
//    return null; // Don't pass the message to the next node yet
//}

can you provide a demo flow - using INJECT/Template nodes to simulate your data.

Without this, we would be guessing

What is the trigger? There is no mention of trigger in the function.

Also, I suspect you could achieve this in a more graphical/low-code (flow oriented) manor rather than a single function node that hides functionality. But without a working demo, I am guessing.

From your title of your post it sounds like node-red-contrib-hold would be useful.

No, this function node is used to extract trigger/event data such as CrustBreak/AnodeRemoval/CavityScoop/AnodeInstallation from the PLC.

Let's take the CrustBreaker trigger as an example. When the trigger is active, I am able to get all the related data. However, I am only sending this data to the next node when the same CrustBreaker trigger is "off". At that time, all of the Crustbreak related data is cleared. I would like to prevent the data from being cleared.

When Crustbreaker trigger is true:
{"payload":{"LEVEL2TAGS[0]":27,"LEVEL2TAGS[1]":124,"LEVEL2TAGS[2]":8,"LEVEL2TAGS[3]":1},"_msgid":"b28bb8216cb67e78","eventBox":{"event":"CrustBreaker","eventActivatedTime":"19:53:29","eventActivatedDate":"2023-08-20","eventInactivatedTime":null,"eventInactivatedDate":null,"totalEventSeconds":0},"flagged":true}

When Crustbreaker Trigger off after it ON:
{"payload":{"LEVEL2TAGS[0]":27,"LEVEL2TAGS[1]":124,"LEVEL2TAGS[2]":8,"LEVEL2TAGS[3]":0},"_msgid":"e047e4f9978a4601","eventBox":null,"flagged":false}

demo video posted here:

I see your issue.

You seem to be (wrongly) assuming if you set msg.eventBox that the next time the function runs, it will still have msg.event

If you put a debug BEFORE the function, you will see msg.eventBox is ALWAYS empty. That is because every msg object is a new object. Always. Additionally, a function has no memory of previous execution. To "remember" something, you need to store it and retrieve it.

For that, you can use context

Here is your code modified to use context...

// Initialize the flag and eventBox
let flagged = false;
let eventBox = context.get('eventBox') || null; // restore previous eventBox data

// Extract the Activities value
let Activities = msg.payload["LEVEL2TAGS[3]"];

// Interpret individual bits from Activities
const CrustBreaker = (Activities & (1 << 0)) !== 0;
const AnodeRemoval = (Activities & (1 << 1)) !== 0;
const CavityScoop = (Activities & (1 << 2)) !== 0;
const NewAnodeInstall = (Activities & (1 << 3)) !== 0;

// Get current timestamp
const currentTimestamp = new Date().toISOString();

// Create a function to generate event IDs
function generateEventID(eventNum, potNum, anodeNum) {
    return `${eventNum}_${potNum}_${anodeNum}`;
}

// Check if the event flag is set and the event has turned inactive
if (flagged && !CrustBreaker && !AnodeRemoval && !CavityScoop && !NewAnodeInstall) {
    if (eventBox && !eventBox.eventInactivatedTime) {
        eventBox.eventInactivatedTime = currentTimestamp.slice(11, 19);
        eventBox.eventInactivatedDate = currentTimestamp.slice(0, 10);
        // eventBox.totalEventSeconds = Math.floor((new Date(currentTimestamp) - new Date(eventBox.eventActivatedTime)) / 1000);
        flagged = false;
    }
}

// Handle the active event (using switch instead of if-else)
switch (true) {
    case CrustBreaker:
        eventBox = {
            event: "CrustBreaker",
            eventActivatedTime: currentTimestamp.slice(11, 19),
            eventActivatedDate: currentTimestamp.slice(0, 10),
            eventInactivatedTime: null,
            eventInactivatedDate: null,
            totalEventSeconds: 0
        };
        flagged = true;
        break;
    case AnodeRemoval:
        eventBox = {
            event: "AnodeRemoval",
            eventActivatedTime: currentTimestamp.slice(11, 19),
            eventActivatedDate: currentTimestamp.slice(0, 10),
            eventInactivatedTime: null,
            eventInactivatedDate: null,
            totalEventSeconds: 0
        };
        flagged = true;
        break;
    case CavityScoop:
        eventBox = {
            event: "CavityScoop",
            eventActivatedTime: currentTimestamp.slice(11, 19),
            eventActivatedDate: currentTimestamp.slice(0, 10),
            eventInactivatedTime: null,
            eventInactivatedDate: null,
            totalEventSeconds: 0
        };
        flagged = true;
        break;
    case NewAnodeInstall:
        eventBox = {
            event: "NewAnodeInstall",
            eventActivatedTime: currentTimestamp.slice(11, 19),
            eventActivatedDate: currentTimestamp.slice(0, 10),
            eventInactivatedTime: null,
            eventInactivatedDate: null,
            totalEventSeconds: 0
        };
        flagged = true;
        break;
    default:
        // Keep existing eventBox data
        flagged = false;
}

// Set the event box and flag as part of the message payload
msg.eventBox = eventBox;
msg.flagged = flagged;

// store eventBox for next msg
context.set("eventBox", eventBox);

// If all events are inactive and flagged, send the message to the next node
//if (!flagged) {
return msg;
//} else {
//    return null; // Don't pass the message to the next node yet
//}

Thank you @Steve-Mcl for your help. Now I can track event, activated time and date. But 'eventInactivatedTime' and 'eventInactivatedDate' is not updating.

Thank you. I got it.

// Initialize the flag and eventBox
let flagged = context.get('flagged') || false;
let eventBox = context.get('eventBox') || null; // restore previous eventBox data

// Extract the Activities value
let Activities = msg.payload["LEVEL2TAGS[3]"];

// Interpret individual bits from Activities
const CrustBreaker = (Activities & (1 << 0)) !== 0;
const AnodeRemoval = (Activities & (1 << 1)) !== 0;
const CavityScoop = (Activities & (1 << 2)) !== 0;
const NewAnodeInstall = (Activities & (1 << 3)) !== 0;

// Get current timestamp
const currentTimestamp = new Date().toISOString();

// Create a function to generate event IDs
function generateEventID(eventNum, potNum, anodeNum) {
    return `${eventNum}_${potNum}_${anodeNum}`;
}

// Check if the event flag is set and the event has turned inactive
if (flagged && !CrustBreaker && !AnodeRemoval && !CavityScoop && !NewAnodeInstall) {
    if (eventBox && !eventBox.eventInactivatedTime) {
        eventBox.eventInactivatedTime = currentTimestamp.slice(11, 19);
        eventBox.eventInactivatedDate = currentTimestamp.slice(0, 10);
        flagged = false;
    }
}

// Handle the active event (using switch instead of if-else)
switch (true) {
    case CrustBreaker:
        eventBox = {
            box_id: generateEventID(1, msg.payload["LEVEL2TAGS[1]"], msg.payload["LEVEL2TAGS[2]"]),
            id: 1,
            event: "CrustBreaker",
            PotNum: msg.payload["LEVEL2TAGS[1]"],
            AnodeNum: msg.payload["LEVEL2TAGS[2]"],
            eventActivatedTime: currentTimestamp.slice(11, 19),
            eventActivatedDate: currentTimestamp.slice(0, 10),
            eventInactivatedTime: null,
            eventInactivatedDate: null,
            totalEventSeconds: 0
        };
        flagged = true;
        break;
    case AnodeRemoval:
        eventBox = {
            box_id: generateEventID(3, msg.payload["LEVEL2TAGS[1]"], msg.payload["LEVEL2TAGS[2]"]),
            id: 3,
            event: "AnodeRemoval",
            PotNum: msg.payload["LEVEL2TAGS[1]"],
            AnodeNum: msg.payload["LEVEL2TAGS[2]"],
            eventActivatedTime: currentTimestamp.slice(11, 19),
            eventActivatedDate: currentTimestamp.slice(0, 10),
            eventInactivatedTime: null,
            eventInactivatedDate: null,
            totalEventSeconds: 0
        };
        flagged = true;
        break;
    case CavityScoop:
        eventBox = {
            box_id: generateEventID(6, msg.payload["LEVEL2TAGS[1]"], msg.payload["LEVEL2TAGS[2]"]),
            id: 6,
            event: "CavityScoop",
            PotNum: msg.payload["LEVEL2TAGS[1]"],
            AnodeNum: msg.payload["LEVEL2TAGS[2]"],
            eventActivatedTime: currentTimestamp.slice(11, 19),
            eventActivatedDate: currentTimestamp.slice(0, 10),
            eventInactivatedTime: null,
            eventInactivatedDate: null,
            totalEventSeconds: 0
        };
        flagged = true;
        break;
    case NewAnodeInstall:
        eventBox = {
            box_id: generateEventID(9, msg.payload["LEVEL2TAGS[1]"], msg.payload["LEVEL2TAGS[2]"]),
            id: 9,
            event: "NewAnodeInstall",
            PotNum: msg.payload["LEVEL2TAGS[1]"],
            AnodeNum: msg.payload["LEVEL2TAGS[2]"],
            eventActivatedTime: currentTimestamp.slice(11, 19),
            eventActivatedDate: currentTimestamp.slice(0, 10),
            eventInactivatedTime: null,
            eventInactivatedDate: null,
            totalEventSeconds: 0
        };
        flagged = true;
        break;
    default:
        // Keep existing eventBox data
        flagged = false;
}

// Set the event box and flag as part of the message payload
msg.eventBox = eventBox;
msg.flagged = flagged;

// store eventBox and flagged for next msg
context.set('eventBox', eventBox);
context.set('flagged', flagged);

return msg;

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.