Skip to content

Subscription Triggering

Demonstrates OPC UA triggering: one monitored item (A) controls when another (B) delivers its data to the client.

Setup: A monitors IntegerVariable — MonitoringMode.Reporting (the trigger) B monitors DoubleVariable — MonitoringMode.Sampling (triggered, silent by itself)

Phase 1 — write only DoubleVariable: B is sampling but never reports on its own because its mode is Sampling. No callbacks arrive for B.

Phase 2 — write IntegerVariable (A): A fires → the server also delivers B's queued sample in the same publish. Both callbacks arrive.

setMonitoringMode(SAMPLING) puts item B in a mode where the server keeps its latest value queued but never reports it on its own. a.setTriggering (linksToAdd=[b]) links A as B's trigger: whenever A reports a data change, the server also flushes B's queued sample into the same publish.

async def main():
    async with o6.Client(endpoint_url) as client:
        print("--- 1. Set Up the Trigger Link ---")
        a_received: list = []
        b_received: list = []

        def on_a(value):
            a_received.append(value)
            print(f"A (IntegerVariable) -> {value}")

        def on_b(value):
            b_received.append(value)
            print(f"B (DoubleVariable) -> {value}")

        sub = await client.createSubscription(publishingInterval=500.0)

        # A: normal reporting trigger item
        a = await client.monitor(NODE_A, on_a, samplingInterval=500.0, subscription=sub)

        # B: sampling only — silent until triggered by A
        b = await client.monitor(NODE_B, on_b, samplingInterval=500.0, subscription=sub)
        await b.setMonitoringMode(ns0.datatypes.MonitoringMode.SAMPLING)

        # Link A → B: whenever A reports, also deliver B's queued sample
        await a.setTriggering(linksToAdd=[b])

2. Phase 1 — writing B alone stays silent

Because B is in Sampling mode, changing DoubleVariable updates its queued sample but produces no callback until something triggers it.

print()
print("--- 2. Phase 1: Writing B Alone ---")
await asyncio.sleep(0.2)
b_received.clear()
await client.write(NODE_B, o6.Double(1.23))
await asyncio.sleep(1.5)  # wait longer than the publishing interval
assert len(b_received) == 0, f"Expected no B callbacks, got {len(b_received)}"
print("No callbacks for B, as expected.")

3. Phase 2 — writing A fires both

Writing IntegerVariable makes A report, and the server piggybacks B's queued DoubleVariable sample onto the same publish response — both callbacks fire even though only A was written this time.

    print()
    print("--- 3. Phase 2: Writing A ---")
    # Write a value distinct from whatever A last reported to guarantee a data-change
    last_a = int(a_received[-1]) if a_received else 0
    phase2_val = (last_a + 1) % 10000
    a_received.clear()
    b_received.clear()
    await client.write(NODE_A, o6.UInt32(phase2_val))
    await asyncio.sleep(1.5)
    assert len(a_received) >= 1, f"Expected A callback, got {len(a_received)}"
    assert len(b_received) >= 1, f"Expected B callback (triggered by A), got {len(b_received)}"
    print(f"A callbacks = {len(a_received)}, B callbacks = {len(b_received)}, as expected.")

    await sub.delete()

print()
print("=== Example completed ===")

Source Code

Download C11_subscriptions_triggering.py to run it locally.