From ab85511c1505280fedcd7ce73f6c52c19a4b7fcb Mon Sep 17 00:00:00 2001 From: Sylvain Date: Thu, 3 Nov 2022 15:57:10 -0300 Subject: [PATCH 1/8] feat: document action state machines --- source/coordination/action_methods.html.md | 4 +- .../action_state_machines.html.md | 309 +++++++++++++++++- source/coordination/generalities.html.md | 44 ++- 3 files changed, 341 insertions(+), 16 deletions(-) diff --git a/source/coordination/action_methods.html.md b/source/coordination/action_methods.html.md index 7bf53eb..dba79f0 100644 --- a/source/coordination/action_methods.html.md +++ b/source/coordination/action_methods.html.md @@ -13,7 +13,7 @@ sort_info: 30 So far, we know of one way to create actions, by importing a whole profile in an action interface using the `use_profile` statement. This is rather static: the only parametrization is by passing arguments to the toplevel composition -of the network, which may trickle down to lower levels through +of the network, which may trickle down to lower levels. This section will present a second way, which allows to actually run code to "implement" the action. It essentially runs a method on the action interface, @@ -22,7 +22,7 @@ whose job is to create a small "plan" later executed by Syskit. This section will go through the general syntax for such actions, and will list a few use-cases for such actions. -## Definition +## Definition {#definition} Action methods are methods that are defined on an action interface. To "export" them as actions, one must do a declaration before the method definition using diff --git a/source/coordination/action_state_machines.html.md b/source/coordination/action_state_machines.html.md index 26b00d3..f94af59 100644 --- a/source/coordination/action_state_machines.html.md +++ b/source/coordination/action_state_machines.html.md @@ -10,4 +10,311 @@ sort_info: 40 - TOC {:toc} -Before \ No newline at end of file +We have seen so far a way for tasks to emit **events**. That is, "export" +synchronization points on top of which one can build. The action state machines +is a powerful primitive that allows to combine actions temporally, leveraging +the events to do so. + +## Concept + +While they are called "state machines", the action state machines are between +"pure" state machines and what has recently appeared in the ROS community under +the "behavior tree" name. Which also had the name of HTNs before. + +In an action state machine, a "state" is a combination of Syskit actions. +Actions in this case can either be behaviours defined as Syskit definitions, +[action methods](./action_methods.html) or other action state machines. Syskit +will ensure that the set of actions associated with a state runs when that state +is active, and only one state may be active at any given time (in a given action +state machine ... multiple state machines can run in parallel). + +As an example, let's use a hypothetical docking maneuver. Medium range, the +maneuver would use an approach that uses a global localization mechanism. When +getting close, the system has to switch to a more precise system. The general +behavior will be to + +- approach +- stay in position until the more precise localization system is running and working +- switch to the final docking maneuver + +![Action state machine example for docking](./action_state_machine_high_level_docking.svg) + +## Declaration + +Action state machines are declared in an [action interface](./generalities.html) using +the `action_state_machine` statement. As for action methods, they must be preceded by +a "describe" statement that declares what the action does. + +~~~ ruby +module MyApp + module Actions + class Navigation < Roby::Actions::Interface + action_state_machine "docking" do + end + end + end +end +~~~ + +## States and transitions + +A machine's state is based on a single action. Assuming that our docking's +sequence coarse approach is a single profile definition called `docking_coarse_approach`, +we would do + +~~~ ruby +action_state_machine "docking" do + coarse = state(docking_coarse_approach_def) +end +~~~ + +You must convert an action into a state object using the `state` statement, +because the same action can be the basis of two different states (which would +allow having different in/out transitions) + +If we now assume that the station keeping and final approach actions are +`station_keep_def` and `final_approach_def`, we would define our three main +states with the following snippet. We also tell Syskit which state is the machine's +start state: + +~~~ ruby +action_state_machine "docking" do + coarse = state docking_coarse_approach_def + station = state station_keep_def + final = statedocking_final_approach_def + + start(coarse) +end +~~~ + +Let's now assume we have defined a `reached_position` event on the +`docking_coarse_approach_def` action. This event can be the basis of the transition +between `coarse` and `station`: + +~~~ ruby +action_state_machine "docking" do + coarse = state docking_coarse_approach_def + station = state station_keep_def + final = state docking_final_approach_def + + start coarse + transition coarse.reached_position_event, station +end +~~~ + +The `reached_position` event should **not** be a terminal event for +`docking_coarse_approach`, i.e. it should not be terminating the task. Syskit is +far from being a realtime engine, so each action should have a "stable" end +state. In this case, I would design `coarse_approach` to actually hover at the +target point. It may make the `station` state useless unless `station_keep` does +it better. We will see how we could do without the station keep action later. +{: .note} + +Now, during the `station` state, we want to run the localization method that +will be used during the final approach, and make sure it works before we go +for the final approach (for instance, that it found visual markers). Such combination +of actions in a state is a staple of the actions state machines: + +~~~ ruby +action_state_machine "docking" do + coarse = state docking_coarse_approach_def + station = state station_keep_def + localization = state visual_localization_def + final = state docking_final_approach_def + + start coarse + transition coarse.reached_position_event, station +end +~~~ + +However, `visual_localization_def` should be running the localization, but won't be +providing us with the events we need for our synchronization. It is common to build +a library of tiny steps for this. In this case, we would create a +`validate_visual_localization` action that would run the action and emit success. We +can then use the success event for our purposes + +~~~ ruby +action_state_machine "docking" do + coarse = state docking_coarse_approach_def + station = state station_keep_def + validate = state validate_visual_localization + station.depends_on(validate) + final = state docking_final_approach_def + + start coarse + transition coarse.reached_position_event, station + transition station, validate.success_event, final +end +~~~ + +Because of how Syskit handles transitions, the visual localization network will +remain unchanged between the station and final states. +{: .note} + +Finally, we would build the final_approach action to emit `success` when docked, +which would become the success criteria for the state machine itself: + +~~~ ruby +action_state_machine "docking" do + coarse = state docking_coarse_approach_def + station = state station_keep_def + validate = state validate_visual_localization + station.depends_on(validate) + final = state docking_final_approach_def + + start coarse + transition coarse.reached_position_event, station + transition station, station.validate_child.success_event, final + final.success_event.forward_to success_event +end +~~~ + +## Parameters + +The action state machine may take arguments of its own. They are declared the same way +than with [action methods](./action_methods.html#definition), that is using the +`required_arg` and `optional_arg` statements to `describe`. The arguments are then +made available with `_arg` accessors and can thus be used as arguments to the underlying +actions. + +As an example, let's assume we want to parametrize the target point for the `coarse` +and `station` states: + +~~~ ruby +describe("action that docks") + .required_arg(:target, "the target point as { x:, y:, z: } object") +action_state_machine "docking" do + coarse = state docking_coarse_approach_def(target: target_arg) + station = state station_keep_def(point: target_arg) + validate = state validate_visual_localization + station.depends_on(validate) + final = state docking_final_approach_def + + start(coarse) + transition coarse.reached_position_event, station + transition station, station.validate_child.success_event, final + final.success_event.forward_to success_event +end +~~~ + +## Using Dynamic Data - Captures + +It is sometimes useful to get information passed from one action to another. This is +done through a combination of event and arguments. I.e. the action state machines provide +you with the means to 'read' data associated with an event and use it to compute another +state's argument. + +Indeed, tasks, when emitting events, may "attach" an object to them (the "event +context"), e.g. + +~~~ ruby +success_event.emit(some_data) +~~~ + +In action state machines, the event's context can be captured and transformed into +another state's argument with the `capture` statement: + +~~~ ruby +c = capture(my_state.success_event) do |context| + ... convert `context` into something usable as an argument ... +end +state some_action(target: c) +~~~ + +As an example, let's assume we want to use a generic line follower to go from +the current system position to the coarse approach target. We would read the +current system position first and then transition to the line follower: + +~~~ ruby +describe("action that docks") + .required_arg(:target, "the target point as { x:, y:, z: } object") +action_state_machine "docking" do + acquire_pose = state acquire_pose_def + from = capture(acquire_pose.success_event) do |pose| + p = pose.position + { x: p.x, y: p.y, z: p.z } + end + + coarse = state follow_line(from: from, to: target_arg) + station = state station_keep_def(point: target_arg) + validate = state validate_visual_localization + station.depends_on(validate) + final = state docking_final_approach_def + + start(acquire_pose) + transition acquire_pose.success_event, coarse + transition coarse.reached_target_event, station + transition station, validate.success_event, final + final.success_event.forward_to success_event +end +~~~ + +If repeated, the "go there using a line from the current position" can be +factored in a separate action state machine, using the state machine's support +to get custom events: + +~~~ ruby +describe("go to a target following a line from the current position") + .required_arg(:to, "the target point as { x:, y:, z: } object") +action_state_machine "follow_line_from_here" do + acquire_pose = state acquire_pose_def + from = capture(acquire_pose.success_event) do |pose| + p = pose.position + { x: p.x, y: p.y, z: p.z } + end + line = state follow_line(from: from, to: target_arg) + + start acquire_pose + transition acquire_pose.success_event, line + + event :reached_target + line.reached_target_event reached_target_event +end + +describe("action that docks") + .required_arg(:target, "the target point as { x:, y:, z: } object") +action_state_machine "docking" do + coarse = state follow_line_from_here(to: target_arg) + station = state station_keep_def(point: target_arg) + validate = state validate_visual_localization + station.depends_on(validate) + final = state docking_final_approach_def + + start coarse + transition coarse.reached_target_event, station + transition station, validate.success_event, final + final.success_event.forward_to success_event +end +~~~ + +### The Acquire functor + +`bundles/common_models` defines the `Acquire` "functor" that creates a composition +suitable to follow the "acquire data and pass it to success" pattern. Acquire takes +a component model and will emit `success` once it received data on all of its ports. + +For instance, one use the following definition to read the global pose: + +~~~ ruby +define "acquire_global_pose", + CommonModels::Compositions.Acquire(Services::Pose) + .use("data_source" => global_pose_def) +~~~ + +## Dynamic State Machine Creation + +State machines can be defined at toplevel, the way we just saw, but may also be +defined dynamically in an [action method](./action_methods.html). When doing so, +one has to first define a root task and attach the machine to that task. For +instance, + +~~~ ruby +root_task = MyActionMethodRoot.new +action_state_machine root_task do +end +root_task +~~~ + +The `action_state_machine` block behaves the same than in "toplevel" state +machines, but has access to instance methods and action method arguments +directly. \ No newline at end of file diff --git a/source/coordination/generalities.html.md b/source/coordination/generalities.html.md index d4f3d18..0bc6505 100644 --- a/source/coordination/generalities.html.md +++ b/source/coordination/generalities.html.md @@ -95,25 +95,24 @@ the one task that is registered as a mission in the plan. Within action interfaces, actions are only _definitions_ of what the system can do. To run them, one needs to instanciate them and add them to Syskit's plan. - Manually, one can use the Syskit IDE or the syskit shell to start an action. Programatically, they can be added in two ways: -Either using the `Robot` global interface, using the `Robot.action_name!(arg: -...)` syntax. For instance, the following would look for a `go_to` action on -the main interface and activate it +- using the `Robot` global interface with the `Robot.action_name!(arg: ...)` + syntax. For instance, the following would look for a `go_to` action on the main + interface and activate it -~~~ ruby -Robot.go_to!(x: 10, y: 20) -~~~ + ~~~ ruby + Robot.go_to!(x: 10, y: 20) + ~~~ -Or by adding the action to the plan manually, for instance: +- by adding the action to the plan manually, for instance: -~~~ ruby -Roby.plan.add_mission_task( - MyApp::Actions::Navigation.go_to(x: 10, y: 20) -) -~~~ + ~~~ ruby + Roby.plan.add_mission_task( + MyApp::Actions::Navigation.go_to(x: 10, y: 20) + ) + ~~~ ## What happens when actions are instanciated @@ -137,6 +136,7 @@ action-represented-in-an-abstract-way task) by the actual tasks that can be exec Which will then be scheduled (again) by Syskit's scheduler. This representation has two advantages: + 1. it allows to explicitely order the plan generation (the planning step) using Syskit's [event scheduling primitives](event_scheduling.html) 2. it allows to do resolve all the planning tasks of a certain kind globally, as it is @@ -145,6 +145,24 @@ This representation has two advantages: ## What happens when missions are dropped +Dropping a mission is telling Syskit that the mission is not anymore an +objective of the system. Syskit will terminate anything that is not in use by +other missions, but leave the rest of the system alone. + +## "Restarting" + +In the Syskit IDE, "restarting" a job is dropping it and creating a new one +_at the same time_. When dealing with stateless actions (i.e. all profile +definitions), this essentially does nothing: + +- _dropping_ the current mission is marking it as "not needed" +- _creating_ the same mission with the same arguments will make syskit deploy it + and generate a network that is exactly like the currently running one +- since the current network and the old ones are identical, Syskit will just keep + the current system. + +To actually restart the current component(s), you need to drop and then start. + ## Refining and reusing existing actions Generally speaking, action interfaces can be refined and reused. From d6ca793554467346b054befc722d8df3482f732a Mon Sep 17 00:00:00 2001 From: Sylvain Date: Thu, 3 Nov 2022 16:37:54 -0300 Subject: [PATCH 2/8] fix: capture syntax --- source/coordination/action_state_machines.html.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/source/coordination/action_state_machines.html.md b/source/coordination/action_state_machines.html.md index f94af59..b136570 100644 --- a/source/coordination/action_state_machines.html.md +++ b/source/coordination/action_state_machines.html.md @@ -215,8 +215,9 @@ In action state machines, the event's context can be captured and transformed in another state's argument with the `capture` statement: ~~~ ruby -c = capture(my_state.success_event) do |context| - ... convert `context` into something usable as an argument ... +c = capture(my_state.success_event) do |event| + data = event.context.first # 'context' is an array + ... convert `data` into something usable as an argument ... end state some_action(target: c) ~~~ @@ -301,6 +302,7 @@ define "acquire_global_pose", .use("data_source" => global_pose_def) ~~~ + ## Dynamic State Machine Creation State machines can be defined at toplevel, the way we just saw, but may also be From 51cfcf23b0101898421fbeba36d460e2c21788a2 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Thu, 3 Nov 2022 16:38:09 -0300 Subject: [PATCH 3/8] feat: mention testing in relation to the state machines --- .../action_state_machines.html.md | 51 ++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/source/coordination/action_state_machines.html.md b/source/coordination/action_state_machines.html.md index b136570..b84e81b 100644 --- a/source/coordination/action_state_machines.html.md +++ b/source/coordination/action_state_machines.html.md @@ -319,4 +319,53 @@ root_task The `action_state_machine` block behaves the same than in "toplevel" state machines, but has access to instance methods and action method arguments -directly. \ No newline at end of file +directly. + +## Testing + +State machines defined at toplevel are evaluated at loading time, and the presence +of actions and events is validated at loading time as well. + +Therefore, there are only mainly two points that are needed to test in relation +with action state machines: + +- captures +- dynamically generated state machines + +### Captures + +In the action interface's test suite, create the state machine's task instance +and then use `run_state_machine_capture` to execute the capture: + +~~~ ruby +task = run_planners my_action(x: 5, y: 10) +result = run_state_machine_capture task, "capture_name", context: [42] +~~~ + +In the rare occasion that the capture would need some more information from the +event, pass a full `Roby::Event` as the `event` argument instead of `context` + +### Dynamically Created State Machines + +To test that a state machine was properly generated, create the toplevel task and +then use the `validate_state_machine` helper to get into a context that allows +you to "play" with the machine's state tasks: + +~~~ ruby +it "transitions to 'coarse' once the pose is acquired" do + interface = MyInterface.new + root_task = interface.some_action + validate_state_machine root_task do + next_task = assert_transitions_to(:state_name) do |current_task| + # Act on 'current_task' using the normal test primitives, e.g. + # syskit_configure_and_start, expect_execution, ... + end + end +end +~~~ + +Do not get into the trap of testing the states themselves, the "test space" will +get very big very quickly. The point of these tests is to check the state +machine's own structure. Test each state's implementation in separate unit tests. +{: .warning} + From 00149c65c584f2cd3d717113029390450a7c5187 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Thu, 3 Nov 2022 16:38:16 -0300 Subject: [PATCH 4/8] fix: add missing diagram --- ...ction_state_machine_high_level_docking.svg | 254 ++++++++++++++++++ 1 file changed, 254 insertions(+) create mode 100644 source/coordination/action_state_machine_high_level_docking.svg diff --git a/source/coordination/action_state_machine_high_level_docking.svg b/source/coordination/action_state_machine_high_level_docking.svg new file mode 100644 index 0000000..a576e59 --- /dev/null +++ b/source/coordination/action_state_machine_high_level_docking.svg @@ -0,0 +1,254 @@ + + + + + + + + + + + + + CoarseApproach + + StationKeeping + + Docking + + PreciseLocalization + + PreciseLocalization OK + + In Position + + State 1 + + State 2 + + State 3 + + From 87648e2c57338d2330443f3190edbbd591ce39e7 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Thu, 3 Nov 2022 16:52:09 -0300 Subject: [PATCH 5/8] fix: remove the stubs --- .../coordination/creating_abstractions.html.md | 15 --------------- source/coordination/event_scheduling.html.md | 14 -------------- source/coordination/final_recap.html.md | 16 ---------------- source/coordination/first_recap.html.md | 14 -------------- .../coordination/higher_level_control.html.md | 17 ----------------- source/coordination/recap.html.md | 0 6 files changed, 76 deletions(-) delete mode 100644 source/coordination/creating_abstractions.html.md delete mode 100644 source/coordination/event_scheduling.html.md delete mode 100644 source/coordination/final_recap.html.md delete mode 100644 source/coordination/first_recap.html.md delete mode 100644 source/coordination/higher_level_control.html.md delete mode 100644 source/coordination/recap.html.md diff --git a/source/coordination/creating_abstractions.html.md b/source/coordination/creating_abstractions.html.md deleted file mode 100644 index 0d4d912..0000000 --- a/source/coordination/creating_abstractions.html.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -layout: documentation -title: Creating Abstractions -sort_info: 70 ---- - -# Creating Abstractions -{:.no_toc} - -- TOC -{:toc} - -**STUB** - - diff --git a/source/coordination/event_scheduling.html.md b/source/coordination/event_scheduling.html.md deleted file mode 100644 index 503301c..0000000 --- a/source/coordination/event_scheduling.html.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -layout: documentation -title: Event Scheduling -sort_info: 60 ---- - -# Event Scheduling -{:.no_toc} - -- TOC -{:toc} - -**STUB** - diff --git a/source/coordination/final_recap.html.md b/source/coordination/final_recap.html.md deleted file mode 100644 index 9c3337b..0000000 --- a/source/coordination/final_recap.html.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -layout: documentation -title: Final Recap -sort_info: 90 ---- - -# Final Recap -{:.no_toc} - -- TOC -{:toc} - -**STUB** - - - diff --git a/source/coordination/first_recap.html.md b/source/coordination/first_recap.html.md deleted file mode 100644 index d03a1b9..0000000 --- a/source/coordination/first_recap.html.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -layout: documentation -title: First Recap -sort_info: 50 ---- - -# First Recap -{:.no_toc} - -- TOC -{:toc} - -**STUB** - diff --git a/source/coordination/higher_level_control.html.md b/source/coordination/higher_level_control.html.md deleted file mode 100644 index 08faca2..0000000 --- a/source/coordination/higher_level_control.html.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -layout: documentation -title: Higher Level Control -sort_info: 45 ---- - -# Higher Level Control -{:.no_toc} - -- TOC -{:toc} - -**STUB** - - - - diff --git a/source/coordination/recap.html.md b/source/coordination/recap.html.md deleted file mode 100644 index e69de29..0000000 From 9405da0004d9d722d677f4713a59f143a203bacb Mon Sep 17 00:00:00 2001 From: Sylvain Date: Fri, 18 Nov 2022 14:54:21 -0300 Subject: [PATCH 6/8] fix: typos and add comments for unclear examples --- .../action_state_machines.html.md | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/source/coordination/action_state_machines.html.md b/source/coordination/action_state_machines.html.md index b84e81b..6c926ec 100644 --- a/source/coordination/action_state_machines.html.md +++ b/source/coordination/action_state_machines.html.md @@ -64,7 +64,7 @@ we would do ~~~ ruby action_state_machine "docking" do - coarse = state(docking_coarse_approach_def) + coarse = state docking_coarse_approach_def end ~~~ @@ -81,7 +81,7 @@ start state: action_state_machine "docking" do coarse = state docking_coarse_approach_def station = state station_keep_def - final = statedocking_final_approach_def + final = state docking_final_approach_def start(coarse) end @@ -127,21 +127,28 @@ action_state_machine "docking" do end ~~~ -However, `visual_localization_def` should be running the localization, but won't be -providing us with the events we need for our synchronization. It is common to build -a library of tiny steps for this. In this case, we would create a -`validate_visual_localization` action that would run the action and emit success. We -can then use the success event for our purposes +However, `visual_localization_def` should be running the localization, but won't +be providing us with the events we need for our synchronization. It is common to +build a library of tiny steps for this. In this case, we would create a +`validate_visual_localization` action that would run the action and emit success +once the visual location has a hit. We can then use the success event for our +purposes ~~~ ruby action_state_machine "docking" do coarse = state docking_coarse_approach_def station = state station_keep_def + # States can be made of any action, not only of profile definitions. + # Here `validate_visual_localization` is built on top of the + # visual_localizations_def (see paragraph above) validate = state validate_visual_localization station.depends_on(validate) final = state docking_final_approach_def start coarse + # Since reached_position_event is an event of a state, you do not need + # to specify the state, i.e. the following is equivalent to + # transition coarse, coarse.reached_position_event, station transition coarse.reached_position_event, station transition station, validate.success_event, final end @@ -151,7 +158,7 @@ Because of how Syskit handles transitions, the visual localization network will remain unchanged between the station and final states. {: .note} -Finally, we would build the final_approach action to emit `success` when docked, +Finally, we would build the final_approach_def action to emit `success` when docked, which would become the success criteria for the state machine itself: ~~~ ruby From 5d62f6434d30e48042d9ddfbea321161e9f7c976 Mon Sep 17 00:00:00 2001 From: rodrigoleonello Date: Tue, 22 Nov 2022 13:31:37 -0300 Subject: [PATCH 7/8] fix: starts acquire_pose before the capture. The capture's state must be toplevel. --- source/coordination/action_state_machines.html.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/source/coordination/action_state_machines.html.md b/source/coordination/action_state_machines.html.md index 6c926ec..c9d5114 100644 --- a/source/coordination/action_state_machines.html.md +++ b/source/coordination/action_state_machines.html.md @@ -238,6 +238,9 @@ describe("action that docks") .required_arg(:target, "the target point as { x:, y:, z: } object") action_state_machine "docking" do acquire_pose = state acquire_pose_def + # the state must have been passed to `start` or as a state + # in `transition` before it can be used in `capture` + start(acquire_pose) from = capture(acquire_pose.success_event) do |pose| p = pose.position { x: p.x, y: p.y, z: p.z } @@ -249,7 +252,6 @@ action_state_machine "docking" do station.depends_on(validate) final = state docking_final_approach_def - start(acquire_pose) transition acquire_pose.success_event, coarse transition coarse.reached_target_event, station transition station, validate.success_event, final From 2d8382a1e2a2d75ad4707081521cedc51b0fa580 Mon Sep 17 00:00:00 2001 From: Sylvain Joyeux Date: Tue, 22 Nov 2022 15:42:07 -0300 Subject: [PATCH 8/8] doc: add some details about testing --- source/coordination/action_state_machines.html.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/source/coordination/action_state_machines.html.md b/source/coordination/action_state_machines.html.md index c9d5114..ec01fea 100644 --- a/source/coordination/action_state_machines.html.md +++ b/source/coordination/action_state_machines.html.md @@ -365,10 +365,17 @@ it "transitions to 'coarse' once the pose is acquired" do interface = MyInterface.new root_task = interface.some_action validate_state_machine root_task do - next_task = assert_transitions_to(:state_name) do |current_task| + # The start sate is available as `current_state_task`. It is ready to + # start, but is unstarted. If you need to read/write ports, you will + # have to call syskit_configure_and_start(current_state_task) + next_task = assert_transitions_to_state(:state_name) do |current_task| # Act on 'current_task' using the normal test primitives, e.g. # syskit_configure_and_start, expect_execution, ... end + + # next_task here is the new state's toplevel task. It is ready to start, + # but is unstarted. Call syskit_configure_and_start for a Syskit task, + # or execute { next_task.start! } for a plain Roby task. end end ~~~