Watching Scene Events
SceneEventService.WatchSceneEvents is a server-streaming RPC and the
only change-notification channel for the scene. It sends one SceneEvent for
every committed change, in order. Use it to keep a GUI or a cache in sync with
the server.
Events
Field |
Meaning |
|---|---|
|
|
|
|
|
Config type and name. Set only when |
|
Names of frames created or moved. May be empty for a config event whose geometry was already current; that is normal. |
|
For |
WatchSceneEventsRequest can filter by origin_filter (ORIGIN_UNKNOWN
streams all) and type_filter (CONFIG_TYPE_ALL streams all; applies to
config events). An empty request streams everything.
Python
flexiv.pdk.watch offers two ways to consume a stream. Both take an
open_stream callable that opens the RPC, and call it again to reconnect
automatically with exponential backoff if the stream drops. A stream the
server ends, including with CANCELLED, is reopened too; only close(),
stop_all() or leaving the loop stops it.
Subscriber+ callbackRuns the stream on a background thread and calls your callback for each event. Best for GUIs and long-lived subscriptions. Call
close()on the returnedSubscription, orstop_all()on theSubscriber, to stop. Both also work as context managers.watch()generatorA blocking generator you iterate yourself. Best for scripts and tests. Breaking out of the loop cancels the call.
def describe(event) -> str:
entity = (f"{ConfigType.Name(event.entity.type)}:{event.entity.name!r}"
if event.HasField("entity") else "-")
return (
f"change={SceneEvent.Change.Name(event.change)} "
f"origin={SceneEvent.Origin.Name(event.origin)} "
f"entity={entity} frames={list(event.frames)}"
)
def main():
target = os.environ.get("PDK_SERVER_ADDR", "localhost:50052")
print(f"Connecting to the PDK server at {target}...")
with Client(target) as client:
request = WatchSceneEventsRequest() # empty filters => all origins/types
# ---- Option 1: background Subscriber with a callback ----------------
print("Watching scene events on a background thread for 3 seconds...")
subscriber = Subscriber()
subscription = subscriber.subscribe(
lambda: client.scene_event.stub.WatchSceneEvents(request),
lambda event: print(f" [callback] {describe(event)}"),
)
try:
time.sleep(3.0) # do other work; events arrive on the worker thread
finally:
subscription.close() # stops the stream and joins the worker
print("Stopped background subscription.")
# ---- Option 2: blocking generator -----------------------------------
print("Watching scene events synchronously (Ctrl-C to stop)...")
try:
for event in watch(lambda: client.scene_event.stub.WatchSceneEvents(request)):
print(f" [generator] {describe(event)}")
except KeyboardInterrupt:
print("Interrupted; the underlying stream is cancelled on exit.")
except grpc.RpcError as error:
print(f"WatchSceneEvents failed or no server running: {error}")
Reconnection and threading are controlled by Options:
from flexiv.pdk.watch import Options
options = Options(
executor=None, # callable(fn) that runs fn on the thread you want
reconnect=True,
backoff_initial_s=0.5,
backoff_max_s=10.0,
backoff_factor=2.0,
max_retries=-1, # -1: retry forever
)
subscription = subscriber.subscribe(open_stream, on_event, options)
An executor lets you deliver events on a GUI thread. For example, with Qt:
executor=lambda fn: QTimer.singleShot(0, fn). watch() is
single-threaded and ignores executor.
Exceptions raised in your callback are logged (logger flexiv.pdk.watch)
and the stream continues.
C++
flexiv::pdk::watch::Subscriber (<flexiv/pdk/watch/subscriber.hpp>):
#include <flexiv/pdk/client.hpp>
#include <flexiv/pdk/watch/subscriber.hpp>
using flexiv::pdk::SceneEvent;
using flexiv::pdk::WatchSceneEventsRequest;
flexiv::pdk::watch::Subscriber subscriber;
flexiv::pdk::watch::Subscription sub = subscriber.Subscribe<SceneEvent>(
// Open the stream; called again on each reconnect.
[&client](grpc::ClientContext* ctx) {
WatchSceneEventsRequest req;
return client.scene_event().stub->WatchSceneEvents(ctx, req);
},
// Handle each event.
[](const SceneEvent& event) {
std::cout << event.ShortDebugString() << std::endl;
});
// Events arrive while `sub` is alive. sub.reset() stops the stream and
// waits until delivery has finished.
Subscriptionis move-only. Discarding the return value ends the watch immediately.Optionshas the same fields as in Python, except that the backoff fields arebackoff_initialandbackoff_max(std::chrono::milliseconds) andexecutoris astd::shared_ptr<Executor>.Implement
watch::Executor::Post(std::function<void()>)to forward events to your UI thread.watch::InlineExecutorruns them directly.Subscriber::StopAll()stops every subscription; the destructor does too.Do not let exceptions escape the C++ callback.
Command line
pdk-scene watch # prints events until Ctrl-C