# Alarms and Conditions (OPC UA Part 9) This guide covers the stack's OPC UA Part 9 *Alarms & Conditions* support. It explains how to build alarm-aware servers and consume alarm events on clients. It also introduces the helpers for latched alarms, alarm groups, suppression, alarm metrics, and streaming alarm records. For the formal model, see [OPC UA Part 9 — Alarms and Conditions](https://reference.opcfoundation.org/specs/OPC-10000-9/full). - [Quick reference](#quick-reference) - [Server side](#server-side) - [Creating an alarm](#creating-an-alarm) - [Driving state from your process](#driving-state-from-your-process) - [Latched alarms](#latched-alarms) - [Re-alarming](#re-alarming) - [Audible alarms and silencing](#audible-alarms-and-silencing) - [Suppression, out-of-service, shelving](#suppression-out-of-service-shelving) - [Filtered retain](#filtered-retain) - [Alarm groups and first-in-group](#alarm-groups-and-first-in-group) - [Alarm metrics (rate tracking)](#alarm-metrics-rate-tracking) - [Vetoing alarm operations](#vetoing-alarm-operations) - [Audit events](#audit-events) - [Client side](#client-side) - [`AlarmClient` — typed operations](#alarmclient--typed-operations) - [Subscribing to alarms with `IAsyncEnumerable`](#subscribing-to-alarms-with-iasyncenumerable) - [Typed alarm records](#typed-alarm-records) - [Per-record event filters](#per-record-event-filters) - [Dialog conditions](#dialog-conditions) - [`ConditionRefresh`](#conditionrefresh) - [Reference](#reference) ## Quick reference | Concern | Server entry point | Client entry point | |---|---|---| | Create an alarm | `new AlarmConditionState(telemetry, parent); alarm.Create(...)` | n/a | | Acknowledge / Confirm | `AcknowledgeableConditionState.OnAcknowledgeCalled` (auto-wired) | `AlarmClient.AcknowledgeAsync` / `ConfirmAsync` | | Silence | `AlarmConditionState.SetSilenceState` | `AlarmClient.SilenceAsync` | | Suppress / Unsuppress | `SetSuppressedState` | `AlarmClient.SuppressAsync` / `UnsuppressAsync` | | Out-of-service | `SetOutOfServiceState` | `AlarmClient.RemoveFromServiceAsync` / `PlaceInServiceAsync` | | Latched + Reset | `SetLatchedState` + auto from `SetActiveState` | `AlarmClient.ResetAsync` | | Shelve / Unshelve | `SetShelvingState` | `AlarmClient.TimedShelveAsync` / `OneShotShelveAsync` / `UnshelveAsync` | | Re-alarm | `ProcessReAlarm` | n/a (server timer) | | Filtered retain | `ConditionState.SupportsFilteredRetain` | n/a (per-subscription) | | Additional retention | `ConditionState.RetainStateProvider` | read Retain / observe condition events | | Alarm groups | `AlarmGroup`, `AlarmSuppressionEngine` | `AlarmClient.GetGroupMembershipsAsync` | | Alarm rate | `AlarmRateTracker` | read `AlarmMetricsType` attributes | | Refresh state | `Server.ConditionRefresh` (server-driven) | `AlarmClient.ConditionRefreshAsync` / `ConditionRefresh2Async` | | Stream alarm events | n/a | `IStreamingSubscription.SubscribeAlarmsAsync` | | Decode raw event fields | n/a | `EventRecordDecoderRegistry.Default.Decode` | | Build an event filter | n/a | `{Type}Record.EventFilters.Build(registry?)` | Create `AlarmClient` from any `ISession` and a telemetry context. It delegates each Part 9 method call to the matching source-generated `*TypeClient` proxy. The generated proxy is the single source for each method NodeId: ```csharp AlarmClient alarms = session.GetAlarmClient(telemetry); ``` Dependency-injected hosts can call the [`builder.AddAlarms()`](DependencyInjection.md#alarms-and-conditions) extension and resolve `AlarmClientFactory`. The factory passes the host's telemetry context to the client: ```csharp var factory = sp.GetRequiredService(); AlarmClient alarms = factory.Create(session); ``` If you already have a typed handle to a specific condition node you can also construct a proxy directly — `AlarmClient` is a convenience façade over these: ```csharp await new AlarmConditionTypeClient(session, conditionId, telemetry) .AcknowledgeAsync(eventId, comment); ``` ## Server side An enabled condition can supply `RetainStateProvider` for an additional application retention requirement, such as an unresolved Machine Vision error that must remain visible after acknowledgement. Returning false does not bypass standard acknowledgement, confirmation, or branch retention. Disabled conditions are not retained by this provider. The callback must be non-blocking and receives the branch being evaluated when the condition is branched. Fluent-created alarms have distinct per-instance NodeIds for their entire subtree, including condition Methods and state properties. Configuring `WithLimits` materializes the standard typed limit properties, assigns instance identifiers, and registers them for browse/read access. Repeated limit configuration updates the same property in place; omitted limits remain absent. ### Creating an alarm `AlarmConditionState` is the central server-side state type for all Part 9 alarms. It is *source-generated* from the standard NodeSet and extended with hand-written behavior. The behavior partial file (`src/Opc.Ua.Core.Types/State/AlarmConditionState.Methods.cs`) wires every Part 9 method handler during `OnAfterCreate`, so as soon as you populate the optional state nodes the corresponding methods are callable. ```csharp var alarm = new AlarmConditionState(telemetry, parent); alarm.Create( context, nodeId: NodeId.Null, // server-assigned browseName: new QualifiedName("MyAlarm"), displayName: null, assignNodeIds: true); alarm.SetEnableState(context, enabled: true); alarm.SetSeverity(context, EventSeverity.High); alarm.SetActiveState(context, active: true); alarm.ReportEvent(context, alarm); ``` Before calling `Create`, assign any optional state nodes to properties such as `alarm.SilenceState`, `alarm.OutOfServiceState`, and `alarm.LatchedState`. The quickstart reference server creates these nodes through `AlarmConditionTypeHolder.Initialize` in `samples/Quickstarts.Servers/Alarms/AlarmHolders/`. ### Driving state from your process All state transitions go through typed setters that: - update the `TwoStateVariableState` value + `Id` - stamp `TransitionTime` - recompute `EffectiveDisplayName` and the composite `SuppressedOrShelved` flag - clear `ChangeMasks` so the next publish cycle sees the new state | Setter | Notes | |---|---| | `SetEnableState(context, enabled)` | Inherited from `ConditionState`. Disabling clears `Retain` per Part 9 §5.5.2. | | `SetSeverity(context, severity)` | Records `LastSeverity` before updating. | | `SetActiveState(context, active)` | On *true*: if `LatchedState` is present, sets it true; if `SilenceState` is present and silenced, clears it. On *false*: if shelved as `OneShotShelve`, unshelves. | | `SetSuppressedState(context, suppressed)` | Updates `SuppressedOrShelved` taking `OutOfServiceState` and `ShelvingState` into account. | | `SetOutOfServiceState(context, outOfService)` | Sets `SuppressedOrShelved` true when out of service (Part 9 §5.8.2). | | `SetShelvingState(context, shelved, oneShot, shelvingTime)` | Drives the `ShelvedStateMachineState`, runs the unshelve timer, computes `UnshelveTime`. | | `SetLatchedState(context, latched)` | Direct latched-state setter; usually called automatically from `SetActiveState`. | | `SetSilenceState(context, silenced)` | Direct silence-state setter; usually called automatically on activation / re-alarm. | ### Latched alarms If you populate `alarm.LatchedState`, the alarm becomes a *latching alarm* (Part 9 §4.8). The semantics: - `SetActiveState(context, true)` — also sets `LatchedState = true`. - `SetActiveState(context, false)` — `ActiveState` reflects the real process state, but `LatchedState` stays `true`. - `Reset` (server-side method, auto-wired) — clears `LatchedState`. `Reset` accepts the request only when the alarm is enabled, inactive, and acknowledged, and when it is confirmed if `ConfirmedState` is present. Otherwise, it returns `Bad_InvalidState`. The server retains latched alarms (`Retain = true`) while `LatchedState.Id` is `true`, so clients can see them during a refresh. ### Re-alarming A re-alarm reminder fires when an alarm has been active and unacknowledged longer than `ReAlarmTime`. The state type provides a helper rather than an automatic timer (so the host owns scheduling and event generation): ```csharp // In your re-alarm scheduler (e.g. an external Timer) if (alarm.IsReAlarmEnabled && alarm.ActiveState.Id.Value && alarm.AckedState?.Id.Value != true) { alarm.ProcessReAlarm(context); } // On deactivation / acknowledge: alarm.ResetReAlarmRepeatCount(context); ``` `ProcessReAlarm` performs these actions: - clears `AckedState` (forces re-acknowledgement) - clears `SilenceState` (audible annunciation resumes) - increments `ReAlarmRepeatCount` - calls `ReportStateChange` to publish the new event ### Audible alarms and silencing When `AudibleEnabled = true` and `AudibleSound` has a value, `UpdateAudibleState` clears the silence state on activation. The next activation is then audible: ```csharp ByteString sound = LoadWavFile(); alarm.UpdateAudibleState(context, active: true, soundData: sound); ``` `SilenceAsync` from the client (or the `Silence` method handler on the server) sets `SilenceState.Id = true`. ### Suppression, out-of-service, shelving All three states contribute to the `SuppressedOrShelved` boolean. The state-type setters keep the flag in sync. Clearing one state does **not** clear `SuppressedOrShelved` while another state remains active: ```csharp alarm.SetSuppressedState(context, true); // SuppressedOrShelved = true alarm.SetOutOfServiceState(context, true); // SuppressedOrShelved stays true alarm.SetSuppressedState(context, false); // SuppressedOrShelved stays true alarm.SetOutOfServiceState(context, false); // SuppressedOrShelved = false ``` ### Filtered retain Part 9 B.1.4 lets a server set a client-specific `Retain` flag based on that client's event filter. When a condition no longer matches a client's where clause, the server sends that client a final event with `Retain = false`. The alarm remains active for everyone else. Opt a condition in by setting `SupportsFilteredRetain`: ```csharp alarm.SupportsFilteredRetain = PropertyState.With(alarm, value: true); ``` Notes on the shape of that property: * Part 9 provides `SupportsFilteredRetain` **on the ConditionType only** — the standard nodeset declares no modelling rule for it, so condition instances do not carry it as a child. The C# property is therefore the server side switch, not an address space node: `FindChild(BrowseNames.SupportsFilteredRetain)` on an instance returns null by design. Clients read the flag from the type node, which the generated address space already builds. * Branches inherit the parent's setting — `CreateBranch` copies it, since nothing that walks the instance children would. * Each condition and branch is tracked separately, so one branch leaving filter scope does not affect its siblings or its parent. * `ConditionRefresh` reevaluates refreshed conditions against the where clause and primes the tracking again. * Changing a monitored item's where clause with `ModifyMonitoredItems` discards tracking for the previous filter. Durable subscriptions preserve tracking across a restart. * Each client receives a trailing event with `Retain = false` (Part 9, 5.5.2, Figure 11), regardless of the server's `Retain` value. Otherwise, the client would keep an alarm that it should drop. The override applies only to that client's event fields; it leaves the shared event snapshot unchanged. Other clients whose filters still include the condition keep receiving the server's actual value. ### Alarm groups and first-in-group `src/Opc.Ua.Server/Alarms/AlarmGroup.cs` wraps a generated `AlarmGroupState` and provides typed add/remove/enumerate: ```csharp var group = new AlarmGroup(motorAlarmGroupState); group.AddMember(motorHighTempAlarm); group.AddMember(motorLowOilAlarm); foreach (NodeId id in group.GetMemberIds(context)) { // ... } ``` `AlarmSuppressionEngine` centralizes both the **AlarmSuppressionGroup** pattern and the **FirstInGroup** pattern. Register on startup, call `Evaluate` from your simulation/process loop, and the engine routes suppression to the right alarm members: ```csharp using var engine = new AlarmSuppressionEngine(); engine.RegisterSuppressionGroup( suppressionGroup: motorShutdownGroup, suppressionSource: () => motorIsShutDown.Value, alarmMembers: new[] { motorHighTempAlarm, motorLowOilAlarm }); engine.RegisterFirstInGroupAlarm( firstAlarm: masterTripAlarm, group: tripGroup, otherMembers: dependentTripAlarms); // In your periodic update: engine.Evaluate(context); // On master trip activation: engine.OnFirstInGroupActiveChanged(context, masterTripAlarm, tripGroup, firstActive: true); ``` The first `Evaluate` call applies the current state. Clients therefore see a coherent suppression state immediately after registration; no state transition is required. > **Live demo:** The reference implementation in > `samples/Quickstarts.Servers/Alarms/AlarmNodeManager.cs` exposes an > `/Alarms/AnalogGroup` (`AlarmGroupType`) containing every analog-source > alarm and a writable `/Alarms/MaintenanceMode` boolean. Writing `true` to > `MaintenanceMode` runs `AlarmSuppressionEngine.Evaluate(...)` and > suppresses every group member. Writing `false` clears suppression on the > next evaluation. The reference node manager derives from > `AsyncCustomNodeManager`, so this file also demonstrates the modern async > base class. ### Alarm metrics (rate tracking) `AlarmRateTracker` records activations into a sliding window and exposes `CurrentAlarmRate` / `MaximumAlarmRate` suitable for surfacing through an `AlarmMetricsType` instance: ```csharp var tracker = new AlarmRateTracker(TimeSpan.FromMinutes(1)); alarm.OnSilenceRequested = (ctx, a) => { tracker.RecordActivation(); return ServiceResult.Good; }; // Periodically push to AlarmMetrics: metrics.CurrentAlarmRate.Value = tracker.CurrentAlarmRate; metrics.MaximumAlarmRate.Value = tracker.MaximumAlarmRate; ``` ### Vetoing alarm operations Each Part 9 alarm method has an *optional* delegate that runs **before** the default state transition. Returning a `Bad` status from the delegate aborts the operation; returning `Good` (or `null`) lets the default behavior run: | Delegate | Triggered by | |---|---| | `alarm.OnSilenceRequested` | `Silence` method | | `alarm.OnSuppressRequested(suppressing: bool)` | `Suppress` / `Unsuppress` | | `alarm.OnOutOfServiceRequested(outOfService: bool)` | `RemoveFromService` / `PlaceInService` | | `alarm.OnResetRequested` | `Reset` (latched alarms) | | `alarm.OnShelve` | `OneShotShelve` / `TimedShelve` / `Unshelve` (existing) | ```csharp alarm.OnResetRequested = (ctx, a) => { if (!hardwareDiagnosticPassed) { return new ServiceResult(StatusCodes.BadUserAccessDenied, "Hardware diagnostic must pass before reset."); } return ServiceResult.Good; }; ``` ### Audit events Every Part 9 alarm method generates the spec-mandated audit event type automatically. You do not have to call `ReportEvent` for the audit event — it happens inside the method handler when `AreEventsMonitored` is true: | Method | Audit event type | |---|---| | `Silence` | `AuditConditionSilenceEventType` | | `Suppress` / `Unsuppress` / `*2` | `AuditConditionSuppressionEventType` | | `RemoveFromService` / `PlaceInService` / `*2` | `AuditConditionOutOfServiceEventType` | | `Reset` / `Reset2` | `AuditConditionResetEventType` | | Existing: `Acknowledge` / `Confirm` | `AuditConditionAcknowledgeEventType` / `AuditConditionConfirmEventType` | | Existing: shelving | `AuditConditionShelvingEventType` | ## Client side ### `AlarmClient` — typed operations `AlarmClient` is the strongly-typed client API for Part 9 methods. Each method delegates to the matching source-generated proxy: `ConditionTypeClient`, `AcknowledgeableConditionTypeClient`, `AlarmConditionTypeClient`, `DialogConditionTypeClient`, or `ShelvedStateMachineTypeClient`. The proxy receives the caller-supplied `conditionId` as its `ObjectId`, following the Part 9 §5.5.4 idiom that accepts `ConditionId` as `ObjectId`. The generated proxy remains the single source for each method NodeId and argument shape. ```csharp AlarmClient alarms = session.GetAlarmClient(telemetry); // ConditionType methods await alarms.EnableAsync(conditionId); await alarms.DisableAsync(conditionId); await alarms.AddCommentAsync(conditionId, eventId, new LocalizedText("en", "Looks like flow sensor drift")); await alarms.ConditionRefreshAsync(subscriptionId); await alarms.ConditionRefresh2Async(subscriptionId, monitoredItemId); // AcknowledgeableConditionType await alarms.AcknowledgeAsync(conditionId, eventId, new LocalizedText("en", "Operator review complete")); await alarms.ConfirmAsync(conditionId, eventId, new LocalizedText("en", "Maintenance verified")); // AlarmConditionType await alarms.SilenceAsync(conditionId); await alarms.SuppressAsync(conditionId, comment: new LocalizedText("en", "Routine maintenance")); await alarms.UnsuppressAsync(conditionId); await alarms.RemoveFromServiceAsync(conditionId); await alarms.PlaceInServiceAsync(conditionId); await alarms.ResetAsync(conditionId); // latched-alarm reset await alarms.TimedShelveAsync(conditionId, shelvingTime: 30000); // 30s await alarms.OneShotShelveAsync(conditionId); await alarms.UnshelveAsync(conditionId); ArrayOf groups = await alarms.GetGroupMembershipsAsync(conditionId); ``` The `*Async(... comment ...)` overloads call the spec-defined `*2` method when the comment is non-empty: `Suppress2`, `Unsuppress2`, `RemoveFromService2`, `PlaceInService2`, and `Reset2`. ### Subscribing to alarms with `IAsyncEnumerable` Alarm events flow through the [streaming subscription API](Subscriptions.md#streaming-subscriptions). The `AlarmStreamExtensions.SubscribeAlarmsAsync` extension returns strongly-typed records: ```csharp ManagedSession session = ...; IStreamingSubscription streaming = session.DefaultStreaming; await foreach (ConditionTypeRecord record in streaming .SubscribeAlarmsAsync(notifierId: ObjectIds.Server, ct: ct) .ConfigureAwait(false)) { switch (record) { case ExclusiveLimitAlarmTypeRecord limit: Console.WriteLine($"{limit.SourceName} limit-state-id={limit.LimitState}"); break; case AlarmConditionTypeRecord alarm when alarm.ActiveStateId == true: await alarms.AcknowledgeAsync(alarm.ConditionId, alarm.EventId, new LocalizedText("en", "Auto-ack")).ConfigureAwait(false); break; case DialogConditionTypeRecord dialog: // Pick a response index from dialog.ResponseOptionSet await alarms.RespondAsync(dialog.ConditionId, selectedResponse: 0).ConfigureAwait(false); break; } } ``` State-machine waits compose naturally with `TakeUntilAsync` / `WithTimeoutAsync`: ```csharp // Wait for myAlarm to clear, or 5 minutes — whichever comes first. await streaming.SubscribeAlarmsAsync(ObjectIds.Server) .TakeUntilAsync(r => r is AlarmConditionTypeRecord a && a.ConditionId == myAlarmId && a.ActiveStateId == false) .WithTimeoutAsync(TimeSpan.FromMinutes(5)) .LastAsync(ct); ``` ### Typed alarm records Alarm and condition records are **source-generated** by the `Opc.Ua.SourceGeneration` analyzer (see the `EventRecordGenerator`). For every `ObjectType` derived from `BaseEventType`, the generator emits a `partial record {Type}Record`. Each generated record derives from its parent record and exposes one init-only property for each declared field. The standard NodeSet produces the following hierarchy, abridged to show the most commonly observed types: ``` EventRecord (anchor; hand-written) └── BaseEventTypeRecord (i=2041) └── ConditionTypeRecord (i=2782) ├── DialogConditionTypeRecord (i=2830) └── AcknowledgeableConditionTypeRecord └── AlarmConditionTypeRecord ├── LimitAlarmTypeRecord │ ├── ExclusiveLimitAlarmTypeRecord │ └── NonExclusiveLimitAlarmTypeRecord ├── DiscreteAlarmTypeRecord │ └── OffNormalAlarmTypeRecord │ └── CertificateExpirationAlarmTypeRecord └── DiscrepancyAlarmTypeRecord ``` Vendor models that derive from these types automatically get their own `*TypeRecord`, which derives from the closest standard ancestor. No checked-in code or manual class definition is needed. Add a hand-written `partial record VibrationAlarmTypeRecord` to your project to extend the generated declaration with computed properties or custom helpers. The decoder upgrades the record type based on which fields are populated in the event. A simple `switch` on the record type gives you the right field set: ```csharp EventRecord? record = EventRecordDecoderRegistry.Default.Decode(eventFields); if (record is CertificateExpirationAlarmTypeRecord cert) { Console.WriteLine($"Cert {cert.CertificateType} expires {cert.ExpirationDate}"); } ``` The shared `ConditionTypeRecord.ConditionId` property is a hand-written alias for `SourceNode`. Part 9 defines `ConditionId` as the NodeId of the condition object that raised the event. The event reports this NodeId in the `SourceNode` field. ### Source-generated decoders + `EventRecordDecoderRegistry` Every record emitted by `EventRecordGenerator` also exposes a nested `static class Decoder`. The decoder provides a positional `StandardFields` table and a `Decode(IReadOnlyList)` method that populates the record's own and inherited init-only properties. A per-file `Register{ModelPrefix}Decoders(this EventRecordDecoderRegistry)` extension registers each generated decoder with a caller-supplied registry. The process-wide `EventRecordDecoderRegistry.Default` registers the standard UA model. It routes by the event's `EventType` field. If the exact type is not registered, it walks the OPC UA event-type hierarchy through an optional `SuperTypeResolver`. Vendor models can register their generated extensions with `Register{Prefix}Decoders` or `CreateChildScope().Register{Prefix}Decoders()` for test isolation. ```csharp // EventType-keyed dispatch backed by source-generated decoders: EventRecord? rec = EventRecordDecoderRegistry.Default.Decode(eventFields); // Vendor scenario — register extra decoders on an app-scoped child: var app = EventRecordDecoderRegistry.Default .CreateChildScope() .RegisterMyVendorDecoders(); EventRecord? vendorRec = app.Decode(eventFields); ``` ### Per-record event filters Every generated `{Type}Record` exposes a nested `static class EventFilters` alongside its `Decoder` block. The `Build(registry?)` factory returns an `EventFilter`. Its where clause restricts events to `OfType({recordTypeId})`, and its select clauses come from the supplied registry's composed `StandardFields` (by default, `EventRecordDecoderRegistry.Default`). Use the filter with `EventRecordDecoderRegistry.Decode`. The registry remaps composed field positions to each decoder's layout before decoding, so vendor models extend transparently. ```csharp // Filter for alarm events: EventFilter filter = AlarmConditionTypeRecord.EventFilters.Build(); // Or for any condition: EventFilter f2 = ConditionTypeRecord.EventFilters.Build(); // Or for dialog events: EventFilter f3 = DialogConditionTypeRecord.EventFilters.Build(); // Or for a specific subtype: EventFilter f4 = CertificateExpirationAlarmTypeRecord.EventFilters.Build(); // Vendor scenario — pass a child registry so the filter superset // includes the vendor model's fields: var app = EventRecordDecoderRegistry.Default .CreateChildScope() .RegisterMyVendorDecoders(); EventFilter vendorFilter = VibrationAlarmTypeRecord.EventFilters.Build(app); ``` Pass the same registry to `Subscribe*Async` through its `registry:` parameter. This ensures the streaming side uses the registry that built the filter. ### Dialog conditions A `DialogConditionType` event arrives as a `DialogConditionTypeRecord`. The `Respond` and `Respond2` methods on `IDialogConditionOperations` close out the dialog. The decoded record exposes the prompt and available response option set so the caller can pick an index: ```csharp await foreach (DialogConditionTypeRecord dialog in streaming.SubscribeDialogsAsync(notifierId)) { Console.WriteLine($"Prompt: {dialog.Prompt}"); LocalizedText[] options = dialog.ResponseOptionSet ?? Array.Empty(); Console.WriteLine($"Options: {string.Join(", ", options)}"); // Pick whichever option matches your scenario. int selectedIndex = 0; await alarms.Respond2Async(dialog.ConditionId, selectedIndex, new LocalizedText("en", "Approved by operator-1")).ConfigureAwait(false); } ``` The server-side `DialogConditionType` properties `OkResponse`, `CancelResponse`, and `DefaultResponse` carry canonical indices (Part 9 §5.6.2). Applications can read these indices separately through the Read service. The standard `DialogConditionTypeRecord` does not include them; it contains only the dialog prompt and active state. ### `ConditionRefresh` Both Part 9 refresh methods are available: ```csharp // Refresh all conditions for the subscription await alarms.ConditionRefreshAsync(subscriptionId); // Refresh just one monitored item's conditions (Part 9 §5.5.8) await alarms.ConditionRefresh2Async(subscriptionId, monitoredItemId); ``` The streaming subscription also provides `ISubscription.ConditionRefreshAsync`. Both refresh methods are equivalent when `AlarmClient` and `IStreamingSubscription` use the same session. ## Reference - [OPC UA Part 9 — Alarms and Conditions](https://reference.opcfoundation.org/specs/OPC-10000-9/full) - [IEC 62682](https://webstore.iec.ch/publication/61256) — Management of alarm systems for the process industries - [ISA 18.2](https://www.isa.org/products/ansi-isa-18-2-2016-management-of-alarm-systems-) — Management of Alarm Systems for the Process Industries - [Subscriptions and Monitored Items Service Set](Subscriptions.md) — `IStreamingSubscription` and the V2 subscription engine - [State Machines](StateMachines.md) — generic Part 16 state-machine API used by `AlarmClient.GetShelvingStateAsync` / `ObserveShelvingTransitionsAsync` - [Model Change Tracking](ModelChangeTracking.md) — client cache invalidation on address-space changes - Source: `src/Opc.Ua.Server/Alarms/`, `src/Opc.Ua.Client/Alarms/`, `src/Opc.Ua.Core.Types/State/AlarmConditionState.Methods.cs` - Reference client sample: `samples/Reference/ConsoleReferenceClient/AlarmClientSample.cs` - Conformance tests: `tests/Opc.Ua.History.Tests/AlarmsAndConditions*.cs`