Skip to content

Commit 011a3ee

Browse files
romanettclaude
andauthored
Add sub state machines and the materialized Part 16 model to the StateMachines sample (#829) (#860)
The sample was written against 2.0.0-preview.2, whose fluent builder materialized no state or transition nodes and could not attach a sub state machine to a state. preview.4 does both, so the two parts issue #829 deferred can be built. Server: the Operation machine now carries a Production sub state machine below its Running state - Loading (initial), Processing and Unloading, with StartBatch as a cause of the child and the rest of the batch on the child's own timers. The framework activates and resets it when the parent enters Running and suspends it when the parent leaves. The states, transitions, their numbers and the Part 16 references are materialized by the builder, so the server code only declares the machine. Client: the window lists the model of the machine - a row per state and per transition with its number and NodeId - read through GetAvailableStatesAsync and GetAvailableTransitionsAsync, finds the sub state machine with GetSubStateMachineAsync on the state node it hangs off, and watches the Operation machine through ObserveEffectiveStateAsync so a transition of the parent or of the child is reported as one combined snapshot. The cause of the child is offered only while the child is active, which is what its Executable attribute says. Tests: the browse shape assertion covers the new children; new cases hold the machine to AvailableStates / AvailableTransitions in declaration order with the declared numbers, to the FromState / ToState / HasCause / HasEffect references of a transition, to HasSubStateMachine hanging off the state node rather than the machine root, to Bad_StateNotActive and BadNotExecutable while the child is suspended, and to a batch running to completion and starting over on re-entry. The tier 2 client case checks the model list, the cause becoming executable and the displayed sub state. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
1 parent 60d20fa commit 011a3ee

7 files changed

Lines changed: 1067 additions & 42 deletions

File tree

‎README.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,8 @@ These paired client/server samples each demonstrate a specific OPC UA feature se
8585
| [RoleManagement Client](Workshop/RoleManagement/Client) | **Client** | Connects as different accounts to see how the address space and the permissions change, and manages the RoleSet of the server. |
8686
| [SimpleEvents Server](Workshop/SimpleEvents/Server) | **Server** | Generates simple OPC UA events to demonstrate event subscriptions. |
8787
| [SimpleEvents Client](Workshop/SimpleEvents/Client) | **Client** | Subscribes to and displays simple events from an events server. |
88-
| [StateMachines Server](Workshop/StateMachines/Server) | **Server** | Demonstrates OPC UA Part 16 state machines: one declared with the fluent `StateMachineBuilder` (states, transitions, causes, guards, timed transitions) and the standard `ProgramStateMachineType` with behaviour attached to it. |
89-
| [StateMachines Client](Workshop/StateMachines/Client) | **Client** | Drives both state machines of a StateMachines server and streams their transitions as they happen. |
88+
| [StateMachines Server](Workshop/StateMachines/Server) | **Server** | Demonstrates OPC UA Part 16 state machines: one declared with the fluent `StateMachineBuilder` (states, transitions, causes, guards, timed transitions and a sub state machine below one of its states) and the standard `ProgramStateMachineType` with behaviour attached to it. |
89+
| [StateMachines Client](Workshop/StateMachines/Client) | **Client** | Drives both state machines of a StateMachines server, reads the states and transitions each of them declares, and streams the transitions of a machine and of its sub state machine as they happen. |
9090
| [UserAuthentication Server](Workshop/UserAuthentication/Server) | **Server** | Demonstrates OPC UA user authentication with username/password and certificate-based identity tokens. |
9191
| [UserAuthentication Client](Workshop/UserAuthentication/Client) | **Client** | Connects using different user identity tokens (anonymous, username, certificate) to a UserAuthentication server. |
9292
| [Views Server](Workshop/Views/Server) | **Server** | Demonstrates OPC UA Views — subsets of the address space exposed as named views. |

‎Tests/SampleClients.Tests/WorkshopClientSubscriptionTests.cs‎

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,56 @@ private static async Task PowerOnTheMachineAsync(Form form, CancellationToken ct
365365
Is.True,
366366
"Start is declared for Idle, so the client has to offer it once the machine " +
367367
"has been powered on.");
368+
369+
// the client also reads the model of the machine - a row per state and per
370+
// transition - which a server materializes only if it exposes AvailableStates and
371+
// AvailableTransitions.
372+
Assert.That(
373+
HasRows(form, "ModelLV"),
374+
Is.True,
375+
"The StateMachines client did not read the states and transitions of the " +
376+
"machine, so the server does not publish them.");
377+
378+
// Running has a machine of its own below it, which is suspended until the parent
379+
// gets there: the client offers its cause only once it is active.
380+
var startBatch = (Button)WinFormsHarness.FindControl(form, "StartBatchBTN");
381+
382+
Assert.That(
383+
startBatch,
384+
Is.Not.Null,
385+
"The StateMachines client no longer has a 'StartBatchBTN'. Rename it here too.");
386+
387+
Assert.That(
388+
startBatch.Enabled,
389+
Is.False,
390+
"The sub state machine is not active while the machine is Idle, so none of " +
391+
"its causes may be offered.");
392+
393+
SampleFormDriver.TryInvokeHandler(form, "OperationCauseBTN_ClickAsync", start);
394+
395+
bool producing = await WaitAsync(() => startBatch.Enabled, ct).ConfigureAwait(true);
396+
397+
Assert.That(
398+
producing,
399+
Is.True,
400+
"Entering Running has to activate the sub state machine, and the client has " +
401+
"to offer the cause of the machine it activated.");
402+
403+
Control production = WinFormsHarness.FindControl(form, "ProductionStateTB");
404+
405+
Assert.That(
406+
production,
407+
Is.Not.Null,
408+
"The StateMachines client no longer has a 'ProductionStateTB'. Rename it here too.");
409+
410+
bool loading = await WaitAsync(() => production.Text == "Loading", ct)
411+
.ConfigureAwait(true);
412+
413+
Assert.That(
414+
loading,
415+
Is.True,
416+
"The client has to show the state of the sub state machine, which starts over " +
417+
$"in Loading whenever the parent enters Running. It showed '{production.Text}'.");
368418
}
369419

370420
/// <summary>

0 commit comments

Comments
 (0)