Mapp View OPC UA "valueChanged" not firing unless additional subscription exists

Hello,

I am experiencing unexpected OPC UA subscription behavior in mappView.

I dynamically create subscriptions like this:

getValue() works reliably.
However, valueChanged() only fires if I additionally create a separate “test” subscription outside this logic, for example:
obrázok
So, if this extra subscription is removed, no errors occur, PLC values change and valueChanged() callbacks inside my dynamic bindings never trigger.
And adding a single external subscription makes all dynamic subscriptions work.

Has anybody encountered or solved this kind of problem?

It raises some fundamental questions:

  • is the OPC UA client or monitored item creation in mapp View lazily initialized?
  • does getValue() guarantee monitored item creation, or does it only perform a read service call?
  • is there a known timing dependency between page/session initialization and subscription activation?
  • what is the officially recommended way to ensure reliable dynamic valueChanged() subscription setup during initialization?

The behavior suggests lazy activation or a timing dependency during initialization, but I would like confirmation whether this is expected behavior by design or an unintended issue.

I’m not sure whether this is an appropriate topic for the community, but I would appreciate any insights or experiences you can share.
Thank you.
Regards
Michal Malek

Hello Michal, in your mapp View Configuration, make sure you have “Initial ValueChanged events” set to “TRUE”.


Otherwise the mapp View OPC UA client will not trigger a “valueChanged” event for the initialization of the event bindings. See : B&R Online Help

Hi Rafael,
It is set to TRUE, but I don’t think this has any connection with dynamic subscriptions in .eventscript files.
Thanks.
Regards,
Michal Malek

Hi Malek,
this sounds strange to me. If you post a complete sample eventscript file I can take a look at it.

Best regards

Stephan

Hi Stephan,
I uploaded a file with code extracted from my project.
testCommunity.zip (1.1 KB)
When you uncomment the last lines, the valueChange inside InitBindings starts to work.
Thank you.
Regards
Michal Malek

Claude and I had a long discussion and I think we figured it out :slight_smile: I tested the fix and works.

mappView EventScript: valueChanged Not Firing Without Test Variable

Symptom

opcVariable.valueChanged(...) registered inside an async function did not fire —
unless an unrelated synchronous testVar.valueChanged(...) on the same node path was
present in the script.


Root Cause: async/await Breaks Synchronous Execution

When the script function is async and contains an await, JavaScript returns control
to the caller immediately
at the first await — the rest of the function body is
scheduled as a microtask to run later.

async function initBindings() {
    let opcVariable = opcua(fullPath);            // ✓ synchronous — runs immediately

    var initialState = await opcVariable.getValue(); // ← function PAUSES here
                                                     //   control returns to #initScript

    opcVariable.valueChanged(function(e) { ... }); // ✗ this line has NOT run yet
}

initBindings(); // starts executing, then pauses at await

// Framework continues:
opcuaScriptManager.activateMonitoredItems(); // fires NOW
// → #itemsToMonitor is EMPTY for this node → no subscription created on the server
// → valueChanged handler will never be triggered

The await essentially splits the function into two parts. The first part runs before
activateMonitoredItems, the second part — containing the valueChanged call — runs
after. By then the subscription window has already passed.


Why the Test Variable “Fixed” It

var testVar = opcua('::AsGlobalPV:gVisuMapp.safety.sN[0].S_State');

testVar.valueChanged(function(e) {      // ← synchronous, runs immediately
    console.log("CHANGED:", e.detail.newValue);
});

testVar used the identical node path as opcVariable inside initBindings.
OpcuaScopeAdapter caches node adapters in a Map keyed by fullNodeId:

opcua(nodeId) {
    if (!this.#nodeAdapters.has(fullNodeId)) {
        let opcuaNode = new OpcuaNode(nodeId, options, this.#opcuaConnection);
        this.#nodeAdapters.set(fullNodeId, new OpcuaNodeAdapter(opcuaNode));
    }
    return this.#nodeAdapters.get(fullNodeId); // ← returns the SAME instance
}

Because testVar.valueChanged() ran synchronously, it added the node to
#itemsToMonitor before activateMonitoredItems was called. When initBindings()
eventually continued after its await and called opcVariable.valueChanged(), it
received the same already-subscribed adapter instance from the cache. The subscription
already existed on the server, so the handler started working — purely by accident.


The Fix

Register valueChanged before any await, then read the initial value afterwards:

// ✗ BROKEN — valueChanged is placed after await
async function initBindings() {
    let opcVariable = opcua(fullPath);

    var initialState = await opcVariable.getValue(); // pauses here → fn() returns
    setModuleState(sessionVarName, initialState);

    opcVariable.valueChanged(function(e) {           // too late
        setModuleState(sessionVarName, e.detail.newValue);
    });
}

// ✓ FIXED — valueChanged is placed before await
async function initBindings() {
    let opcVariable = opcua(fullPath);

    opcVariable.valueChanged(function(e) {           // ✓ synchronous, registered in time
        setModuleState(sessionVarName, e.detail.newValue);
    });

    try {
        var initialState = await opcVariable.getValue(); // initial sync after subscription
        setModuleState(sessionVarName, initialState);
    } catch (err) {
        console.log("OPC init error:", fullPath, err);
    }
}

Rule of Thumb

In a mappView EventScript, always call opcua(...).valueChanged(...) synchronously
before any await. The framework calls activateMonitoredItems() immediately after
the script function returns — any valueChanged registration that happens asynchronously
(after an await) will be too late and the OPC UA subscription will never be created.