opcua-cli · master
Docs · Command · watch

`watch`

Watch a node value in real time. Subscription-based by default (OPC UA CreateSubscription + monitored items); --interval=N switches to a polling loop. Press Ctrl-C to stop.

Watch a node value in real time. Two modes:

  • Default (no --interval) — opens a real OPC UA subscription (CreateSubscription + CreateMonitoredItems) and prints every notification the server pushes.
  • Polling (--interval=N) — falls back to a read() loop at the given interval.

Long-running until interrupted with Ctrl-C.

Usage

text signature
opcua-cli watch <endpoint> <nodeId> [--interval=N] [global-options]
Argument Meaning
<endpoint> OPC UA server URL. Required.
<nodeId> Node to watch. NodeId or browse path. Required.
Option Default Effect
--interval=N (unset → subscription) Switch to polling and sleep N milliseconds between reads. N is cast to int, so non-integer values truncate.

Plus the global options.

Examples

Default — subscription

bash terminal — subscription
opcua-cli watch opc.tcp://plc.local:4840 "ns=2;s=PLC/Speed"
text console output
[10:30:00.123] 42.5
[10:30:00.487] 42.7
[10:30:01.012] 43.1
[10:30:02.901] 43.5
^C

One line per change notification the server pushes (publishing interval defaults to 250 ms inside the CLI). Each line: local timestamp HH:MM:SS.mmm + the new value. Ctrl-C interrupts the process; PHP exits with 130 (128 + SIGINT) since the CLI does not install a signal handler.

Polling — explicit interval

bash terminal — polling
opcua-cli watch opc.tcp://plc.local:4840 "ns=2;s=PLC/Speed" --interval=1000

--interval=1000 polls once per second. The interval is milliseconds, not seconds — the source code casts it to int and sleeps interval * 1000 microseconds. A few examples:

You passed What happens
--interval=1000 One read() per second
--interval=250 Four read()s per second
--interval=1 ~1000 read()s per second (likely to overload a small server)
--interval=0.25 Cast to int → 0 → unbounded polling

Cap how aggressively you poll based on the server's tolerance — some PLCs throttle excessive reads on the same NodeId.

JSON output

bash terminal — JSON
opcua-cli watch opc.tcp://plc.local:4840 "ns=2;s=PLC/Speed" --json
text JSON output
{"message":"[10:30:00.123] 42.5"}
{"message":"[10:30:00.487] 42.7"}
{"message":"[10:30:01.012] 43.1"}

NDJSON — one JSON object per notification (or per poll), separated by \n. The JsonOutput backend wraps the same console line in {"message": "<line>"}; there is no separate value, statusCode, or structured timestamp field. To extract the value or timestamp, parse the string inside .message.

Pipe to a file

bash terminal — to file
opcua-cli watch opc.tcp://plc.local:4840 "ns=2;s=PLC/Speed" --interval=500 --json \
    > /var/log/speed.ndjson

The file grows for the lifetime of the watch — rotate / cap by external tooling (logrotate, journal piping, …) if you let it run overnight.

Subscription vs polling

watch defaults to a real OPC UA subscription. --interval=N opts into polling. Trade-offs:

Aspect Default (subscription) --interval=N (polling)
Setup cost CreateSubscription + CreateMonitoredItems once None — just read() in a loop
Server resources used Long-lived subscription state on the server Per-read transient
Captures changes between ticks Yes — server pushes every change No — only the value at each poll tick
Bandwidth One round-trip per notification (efficient on stable values) One round-trip per poll, regardless of changes
Suitable when Long-running monitoring, dense change streams Servers that reject subscriptions, or rate-limited probing

For richer subscription control (custom publishing interval per subscription, multiple monitored items per request) drop down to opcua-client or use opcua-session-manager with auto-publish.

How it maps to the library

You ran The CLI does
opcua-cli watch <endpoint> <nodeId> $sub = $client->createSubscription(...) + $client->createMonitoredItems(...) + publish() loop
opcua-cli watch <endpoint> <nodeId> --interval=N while (true) { $dv = $client->read($nodeId); print(...); usleep(N * 1000); }

The polling path uses the same read() call as opcua-cli read, repeated.

Stopping

Ctrl-C (SIGINT) is the stop signal. The CLI does not install a pcntl_signal handler, so PHP terminates with the default exit code (typically 130) on interrupt. The OS reclaims the TCP socket; on the server side, the subscription is closed at the next keep-alive timeout.

Ctrl-Z (SIGTSTP) suspends; resume with fg. Tight polling intervals tolerate suspension poorly — values may bunch up on resume.

When the connection drops mid-watch

The CLI does not automatically reconnect. If the server restarts or the network blip lasts longer than the read / publish timeout, the underlying client throws and the command exits non-zero.

For resilient monitoring, wrap the command in a shell loop:

bash terminal — auto-restart
while ! opcua-cli watch opc.tcp://plc.local:4840 "ns=2;s=PLC/Speed"; do
    sleep 5
done

For more robust patterns, see Recipes · Live monitoring with watch.

Common pitfalls

  • Confusing --interval units. It is milliseconds, not seconds. --interval=1 polls 1000 times per second, not once.
  • Float intervals. The source casts --interval to int, so --interval=0.25 becomes 0 (unbounded loop). Pass whole milliseconds.
  • Subscription rejected by the server. Some sandbox servers do not implement the subscription service set. Switch to polling with --interval=1000.
  • Watching a complex structure. The console output prints PHP's default representation, which is ugly for arrays / DTOs. Use --json and parse the message string yourself.
  • Forgetting to Ctrl-C. Running with nohup or in screen and forgetting about it produces a multi-gigabyte log. Set a max line count externally if running unattended.