diff --git a/src/main/kotlin/at/ac/uibk/dps/cirrina/execution/object/Action.kt b/src/main/kotlin/at/ac/uibk/dps/cirrina/execution/object/Action.kt index d63caa37..08eaa7f5 100644 --- a/src/main/kotlin/at/ac/uibk/dps/cirrina/execution/object/Action.kt +++ b/src/main/kotlin/at/ac/uibk/dps/cirrina/execution/object/Action.kt @@ -34,6 +34,7 @@ sealed interface Action { description.type, description.mode, buildVariables(description.input), + description.output, buildEvents(description.raises), ) @@ -88,6 +89,7 @@ internal constructor( val type: String, val mode: InvocationMode, val input: List, + val output: List, val raises: List, ) : EventRaisingAction { override fun raises(): List = raises diff --git a/src/main/kotlin/at/ac/uibk/dps/cirrina/execution/object/ActionCommand.kt b/src/main/kotlin/at/ac/uibk/dps/cirrina/execution/object/ActionCommand.kt index 9eb4c1c1..b3ca54fe 100644 --- a/src/main/kotlin/at/ac/uibk/dps/cirrina/execution/object/ActionCommand.kt +++ b/src/main/kotlin/at/ac/uibk/dps/cirrina/execution/object/ActionCommand.kt @@ -80,7 +80,10 @@ internal constructor( commandExecutionContext.coroutineScope.launch { runCatching { service.invoke(input) } - .onSuccess { raiseEvents(it) } + .onSuccess { + assignServiceOutput(it, commandExecutionContext.scope.extent) + raiseEvents(it) + } .onFailure { logger.error(it) { "service invocation failed" } } } @@ -96,6 +99,24 @@ internal constructor( private fun prepareInput(extent: Extent): List = invokeAction.input.map { it.evaluate(extent) } + private fun assignServiceOutput(output: List, extent: Extent) { + invokeAction.output.forEach { variable -> + output + .firstOrNull { it.name == variable.reference } + ?.let { + runCatching { extent.set(variable.reference, it.value) } + .onFailure { e -> + logger.warn(e) { + "failed to assign service output to variable '${variable.reference}'" + } + } + } + ?: logger.warn { + "service output does not contain expected variable '${variable.reference}'" + } + } + } + private fun raiseEvents(output: List) { invokeAction.raises.forEach { eventTemplate -> val event = eventTemplate.copy(data = output) diff --git a/src/main/resources/pkl/csm/csml.pkl b/src/main/resources/pkl/csm/csml.pkl index 57275ad5..f413203e 100644 --- a/src/main/resources/pkl/csm/csml.pkl +++ b/src/main/resources/pkl/csm/csml.pkl @@ -99,6 +99,7 @@ class InvokeDescription extends ActionDescription { type: InvocationType mode: InvocationMode = "remote" input: Context + output: Listing raises: Listing } @@ -134,6 +135,10 @@ class LogDescription extends ActionDescription { message: Expression } +class ContextVariableReferenceDescription { + reference: String +} + typealias EventTopic = String(matches(Regex(#"^[a-zA-Z_]\w*$"#))) typealias EventChannel = "internal"|"external"|"global"|"peripheral" diff --git a/src/test/kotlin/at/ac/uibk/dps/cirrina/CompleteTest.kt b/src/test/kotlin/at/ac/uibk/dps/cirrina/CompleteTest.kt index cea46fbe..8365a561 100644 --- a/src/test/kotlin/at/ac/uibk/dps/cirrina/CompleteTest.kt +++ b/src/test/kotlin/at/ac/uibk/dps/cirrina/CompleteTest.kt @@ -29,7 +29,10 @@ class CompleteTest { val context = ContextInMemory() val server = mockHttpServer { input -> val v = input.firstOrNull { it.name == "v" } ?: error("variable 'v' not found") - listOf(ContextVariable("v", (v.value as Int) + 1)) + listOf( + ContextVariable("v", (v.value as Int) + 1), + ContextVariable("s", (v.value + 1) * 10), + ) } try { @@ -45,6 +48,7 @@ class CompleteTest { assertEquals(100, context.get("v")) assertEquals(true, context.get("b")) assertEquals(true, context.get("e")) + assertEquals(50500, context.get("f")) } finally { server.stop(1) } diff --git a/src/test/resources/pkl/complete/completeStateMachine.pkl b/src/test/resources/pkl/complete/completeStateMachine.pkl index 9865716b..bec7a420 100644 --- a/src/test/resources/pkl/complete/completeStateMachine.pkl +++ b/src/test/resources/pkl/complete/completeStateMachine.pkl @@ -18,11 +18,13 @@ sm = new csml.StateMachine { type = "increment" mode = "local" input { ["v"] = "x" } + output { new csml.ContextVariableReferenceDescription { reference = "s" } } raises { new csml.Internal { topic = "e2" } } } } exit { new csml.Eval { expression = "e = true" } + new csml.Eval { expression = "f = f + s" } } on { ["e2"] = new csml.Transition { to = "c"; do { new csml.Eval { expression = "x = $v" } } } @@ -52,5 +54,8 @@ sm = new csml.StateMachine { } ["e"] = new csml.Terminal { entry { new csml.Eval { expression = "b = true" } } } } - transient { ["x"] = "0" } + transient { + ["x"] = "0" + ["s"] = "0" + } } \ No newline at end of file diff --git a/src/test/resources/pkl/complete/main.pkl b/src/test/resources/pkl/complete/main.pkl index 297cc3a3..32b71978 100644 --- a/src/test/resources/pkl/complete/main.pkl +++ b/src/test/resources/pkl/complete/main.pkl @@ -6,7 +6,7 @@ collaborativeStateMachine { stateMachines { ["completeStateMachine"] = completeStateMachine.sm } - persistent { ["b"] = "false"; ["v"] = "0"; ["e"] = "false" } + persistent { ["b"] = "false"; ["v"] = "0"; ["e"] = "false"; ["f"] = "0" } } instances { ["complete"] = "completeStateMachine"