Subscriptions
Durable value-change notifications, with an optional Rx companion package that surfaces them as
IObservable<T> streams.
Typed subscription (preferred)
using var sub = await conn.SubscribeAsync<float>(
"GVL.Temp",
cycleTimeMs: 200,
(symbol, value) => Console.WriteLine($"{symbol} = {value}"),
CancellationToken.None);
// Subscription survives reconnects; dispose to remove permanently.
Untyped subscription
using var sub = await conn.SubscribeAsync(
"GVL.Counter",
cycleTimeMs: 500,
(symbol, value) => Console.WriteLine($"{symbol} = {value}"),
CancellationToken.None);
Subscriptions are durable: owned by the stable facade, not the underlying connection. When a
reconnect occurs the subscription is automatically re-registered against the new connection.
Callbacks fire on a background thread — they must be thread-safe and must not block. A null
notification value with a value-type T is dropped (Warning logged). Dispose is idempotent and
thread-safe.
Notification metadata
using var sub = await conn.SubscribeAsync("GVL.Temp", cycleTimeMs: 200,
n => Console.WriteLine($"[{n.Timestamp:O}] {n.SymbolPath} ({n.TypeName}) = {n.Value}"),
CancellationToken.None);
Carries the same durability guarantees as the untyped overload, plus the symbol's PLC type name and the PLC-reported timestamp of the change. Struct, function block and array notifications are decoded off the notification thread and so may be delivered slightly later — and, under a fast burst, out of order relative to scalar notifications.
Reactive (Rx) companion
The optional Dahlke.TwinCAT.Ads.Reactive package exposes subscriptions and connection state
as IObservable<T> streams (built on System.Reactive). Install it alongside the core package
only if you want Rx — the core package never depends on System.Reactive.
dotnet add package Dahlke.TwinCAT.Ads.Reactive
using Dahlke.TwinCAT.Ads.Reactive;
using System.Reactive.Linq;
var conn = pool.GetConnection("plc1");
// Typed value stream — cold: each Subscribe opens its own ADS notification,
// disposing it deletes the notification. Durable across reconnects.
using var sub = conn.ObserveValue<float>("GVL.Temp", cycleTimeMs: 200)
.Select(change => change.Value)
.Where(t => t > 50f)
.DistinctUntilChanged()
.Subscribe(t => Console.WriteLine($"Hot: {t} °C"));
// Connection state across every configured target (each event carries its PlcId).
using var states = pool.ObserveAllConnectionStates()
.Subscribe(e => Console.WriteLine($"{e.PlcId}: {e.PreviousState} -> {e.State}"));
Each notification is an AdsValueChange<T> record (Symbol, Value). Notifications arrive on a
background thread — add .ObserveOn(...) before updating UI. To share one underlying ADS
notification among multiple subscribers, add .Publish().RefCount(). See
examples/Dahlke.TwinCAT.Ads.Examples.Reactive
for a runnable demo.