jira
Table of Contents | |||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
|
In TQL, each instance of a workflow (or process) can only be executed once. However, in many circumstances, you may want a workflow to be executed multiple times, for instance, the process to update an attribute value may needs to be repeated every time a sensor event comes in. Such "repeatable workflows" are represented as process streams where each process instance is a self-contained independent copy of the original workflow [definition] running with specific arguments.1
Repeatable versus non-repeatable workflows
a. Non-repeatable (single-run, non-stream) workflows (used in AppFacets)
Such workflows runs for a single time and never repeats. If you do not specify the "While" modifier for the first workflow task, a workflow will run only once. (By default a task's while = "false".) Non-repeatable workflows should be started immediately after compilation (i.e. after it is deployed) without waiting for any events for it to activate or continuebe activated or continued. The originating pipeline, which instantiated the workflow, will wait until the workflow completes.
Examples of non-repeatable workflows are often used in AppFacets.
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
place holder for example of non-repeatable workflow |
...
<AppFacet name="CSVLoader">
<String name="destModelName" />
<String name="DriverId" />
<String name="ParkingLotId" />
<String name="fileName" KnownBy="csvLoaderAc" Update="auto" />
<Action name="csvLoaderAc" documentation="Data Facet to load CSV Data">
<Workflow Limit="1" Live="1" Timeout="-1">
<Task name="Main">
<Event name="Argument" as="ActionArgument" />
<Invoke name="CSVDataLoader" waitFor="Argument">
<FacetScript>
<Log Message="Loading file..."/>
<LoadFromCSV fileName="[%:Event.Argument.fileName.Value:%]"
destModelName="[%:Event.Argument.destModelName.Value:%]"
DriverId="[%:Event.Argument.DriverId.Value:%]"
ParkingLotId="[%:Event.Argument.ParkingLotId.Value:%]" />
<Log Message="Loading complete!!!"/>
</FacetScript>
</Invoke>
<Output name="ActionResult">
<Value>
<Log Message="Output file name: [%:Event.Argument.fileName.Value:%]"/>
<fileName>[%:Event.Argument.fileName.Value:%]</fileName>
</Value>
</Output>
</Task>
</Workflow>
</Action>
</AppFacet> |
b. Repeatable (multi-run, stream) workflows - usually used to process (sequence of) events These are workflows which have while=”true” on one of its tasks (it must be the first task due to compiler limitation, needs to be "texally" first - the the order of appearance in your source code. All others can have it, but it does not matter). These workflows are implemented
Such workflows are implemented as a stream (i.e. a sequence) of single-run instances which are called “process”, thus “stream of processes” or “process stream” terms. These fall into following categories:Default behavior is to be asynchronous They are usually used to process sequences of events. Repeatable workflows are defined by using the "While" modifier (While = "true") for its tasks. Most workflows used in ThingFacets (to interact with things) are repeatable workflows. The originating pipeline, which instantiated the workflow, does not wait for the workflow (fire-and-forget) start (the originating pipeline does not wait, it continues)
a) Non-waiting workflows
Instances of these are started right after creation.
Note | ||
---|---|---|
| ||
Due to the current compiler limitation, you must use While = "true" for the first task that appears in your workflow definition (source code) in order for the compiler to recognize that this is a repeatable workflow. You do not need to specify While = "true" for the subsequent tasks in the same workflow definition. |
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
<ThingFacet Name="TempFacetSerial">
<Number Name="tempValue" update="auto" KnownBy="SerialReadAction" />
<String Name="unit" default="Celsius" Documentation="Celsius or Fahrenheit" />
<String Name="peripheral" />
<String Name="interfacePort" />
<String Name="interface" />
<String Name="uniqueId" default="56789" />
<String Name="baudrate" />
<String Name="format" default="ascii" />
<String Name="operation" />
<String Name="payload" />
<Action Name="SerialReadAction" Documentation="Start the serial port">
<Workflow Limit="1" Live="1" Timeout="-1">
<Task name="Main" while="true">
<Event name="Argument" as="ActionArgument" />
<Invoke name="InvokeSerialRead" waitFor="Argument" get="perif://">
<Message>
<Value>
<InterfacePort>"[%:Event.Argument.interfacePort.Value:%]"
</InterfacePort>
<Baudrate>"[%:Event.Argument.baudrate.Value:%]"</Baudrate>
<Interface>"[%:Event.Argument.interface.Value:%]"</Interface>
<UniqueId>"[%:Event.Argument.uniqueId.Value:%]"</UniqueId>
<Operation>"[%:Event.Argument.operation.Value:%]"</Operation>
<format>"[%:Event.Argument.format.Value:%]"</format>
<Payload>"[%:Event.Argument.payload.Value:%]"</Payload>
<Peripheral>"[%:Event.Argument.peripheral.Value:%]"</Peripheral>
</Value>
</Message>
</Invoke>
<Output name="Result" as="ActionResult">
<Value>
<tempValue>
[%:Invoke.InvokeSerialRead.Message.Value/number(
substring-before(substring-after("[%:Invoke.InvokeSerialRead.Message.Value:%]",
'#TCB:'), '#')):%]
</tempValue>
</Value>
</Output>
</Task>
</Workflow>
</Action>
</ThingFacet> |
Non-waiting versus externally-activated workflows
a. Non-waiting workflows
Instances of such workflow are started right after they are created. This is done by ensuring that all the input values of the workflow's initial task(s) have their values assigned in your source code (versus waiting for external events to give value). They are only useful if they are doing something or communicating with some other entities [en-masse] which is difficult with non-repeatable workflows. (initial task(s) starts right away, make sure all inputs already have values assigned in your source code)
b) Externally-activated workflows
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
<Action name="SetOutput">
<Workflow Limit="1" Live="1" Timeout="-1">
<Task name="Step1">
<Input name="Argument" type="string" kind="literal" value="Hello World!"/>
<Output name="Result" type="string" value="Step1 says '[:Input.Argument:]'"/>
</Task>
<Task name="Step2">
<Input name="Argument" type="string" value="Step1.Result"/>
<Output name="Result" type="string" value="Step2 says that '[:Input.Argument:]'"/>
</Task>
<Task name="Step3">
<Input name="Argument" type="string" value="Step2.Result"/>
<Output name="Result" type="string" value="Step3 says that '[:Input.Argument:]'"/>
</Task>
<Task name="Step4">
<Input name="Argument" type="string" value="Step3.Result"/>
<Output name="Result" type="string" value="Step4 says that '[:Input.Argument:]'"/>
</Task>
</Workflow>
</Action> |
b. Externally-activated workflows
These are workflows which have explicit “event handler” (i.e. a special type of task with no inputs and no invokes) (output, "event"). Instances of these can be waiting workflows wait for the external event to come and then start . other tasks can have the event handler outputs as their inputs
c) Externally-continued workflows
These have an invoke with waitFor=”…” construct. Instances ("external" here means external to the workflow itself). The "event handler" is written as "Event" in the workflow definition. In ThingFacet (and AppFacet) workflows, the Event is typically the ActionArgument, which is triggered by the modification of actionable attributes of the model facet.
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
<Action name="SetOutput">
<Workflow Limit="1" Live="1" Timeout="-1">
<Task name="Step1">
<Event name="Argument" as="ActionArgument" />
<Output name="Result" type="string" value="Step1 says '[:Event.Argument:]'"/>
</Task>
<Task name="Step2">
<Input name="Argument" type="string" value="Step1.Result"/>
<Output name="Result" type="string" value="Step2 says that '[:Input.Argument:]'"/>
</Task>
<Task name="Step3">
<Input name="Argument" type="string" value="Step2.Result"/>
<Output name="Result" type="string" value="Step3 says that '[:Input.Argument:]'"/>
</Task>
<Task name="Step4">
<Input name="Argument" type="string" value="Step3.Result"/>
<Output name="Result" type="string" value="Step4 says that '[:Input.Argument:]'"/>
</Task>
</Workflow>
</Action> |
Externally-continued versus internally continued workflows
a. Externally-continued workflows
These have an invoke with the WaitFor modifier. Instances of such workflows can start, work for a while and then suspend and wait for an event in the middle of task (on the waitFor). Once wait is completed they continue until the next waitFor or process completion. (wait for is not associated with any pipeline)
d) Internally continued workflows
These may look like a, b or c, but suspend on pipeline operation instead of event handler or waitForWaitFor or process completion (The "external" here means external to the workflow itself).
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
<Action Name="SerialReadAction" Documentation="Start the serial port">
<Workflow Limit="1" Live="1" Timeout="-1">
<Task name="Main" while="true">
<Event name="Argument" as="ActionArgument" />
<Invoke name="InvokeSerialRead" waitFor="Argument" get="perif://">
<Message>
<Value>
<InterfacePort>
"[%:Event.Argument.interfacePort.Value:%]"
</InterfacePort>
<Baudrate>
"[%:Event.Argument.baudrate.Value:%]"
</Baudrate>
<Interface>
"[%:Event.Argument.interface.Value:%]"
</Interface>
<UniqueId>
"[%:Event.Argument.uniqueId.Value:%]"
</UniqueId>
<Operation>
"[%:Event.Argument.operation.Value:%]"
</Operation>
<format>
"[%:Event.Argument.format.Value:%]"
</format>
<Payload>
"[%:Event.Argument.payload.Value:%]"
</Payload>
<Peripheral>
"[%:Event.Argument.peripheral.Value:%]"
</Peripheral>
</Value>
</Message>
</Invoke>
<Log
Message="Invoke --> [%:Invoke.InvokeSerialRead.Message.Value.received:%]" />
<Output name="Result" as="ActionResult">
<Value>
<tempValue>
[%:Invoke.InvokeSerialRead.Message.Value/number(
substring-before(substring-after("[%:Invoke.InvokeSerialRead.Message.Value:%]",
'#TCB:'), '#')):%]
</tempValue>
</Value>
</Output>
</Task>
</Workflow>
</Action>
|
b. Internally continued workflows
Instances of these workflows suspend on pipeline operations. Conceptually it’s the same as waiting for a response after an HTTP request, only without the actual request. RFID reader is an example. The process starts by itself (1, 2a) of itself or by an external event (2b, 2c) and , but then suspends on the pipeline operation. Once a “response” is received from the device, the process continues to the next wait suspension or completion. (event originator is within the workflow itself, waiting for pipeline operation to happen, such as a message from a device coming, or from a remote system coming, it is a synchronous execution on the pipeline. not an event handler) timeout
...
title | unused notes |
---|
...
icon | false |
---|
Workflow instances run on top of pipelines, that is, they use pipelines to be instantiate, trigger or communicate. We call the pipeline that instantiate a workflow instance the originating pipeline. Based on the relationship between the workflow and its originating pipeline, workflow can be categorized into non-repeatable workflows and repeatable workflows.
...
The "internal" here means the pipeline operations is internal to the workflow itself. "Internal" is not related to the source of the "response". For example, the response may come from a device outside the application.
In such workflows, it is a good practice to have a Timeout modifier on the workflow.
Code Block | ||||
---|---|---|---|---|
| ||||
<ThingFacet Name="PhidgetRFID">
<Sid Name="RFIDId" />
<String Name="ReadTag" Update="auto" KnownBy="PhidgetReadRFIDTagAc" />
<String Name="WriteTag" KnownBy="PhidgetWriteRFIDTagAc" />
<String Name="RFIDURL" Default="phid://" />
<String Name="InterfaceType" />
<String Name="InterfaceIndex" />
<Unique Name="RFIDIndex" Value="InterfaceIndex" />
<DateTime Name="timestamp" Format="$SimpleDateFormat(yyyy-MM-dd'T'HH:mm:ss'Z')" />
<AA>
[:#o#Output.ActionArgument:]
</AA>
<Action Name="PhidgetReadRFIDTagAc" Documentation="Read the Tag Automatically in the vicinity">
<Workflow Limit="1" Live="1" Timeout="-1">
<Task name="Main" while="true">
<Event name="Argument" as="ActionArgument" />
<Invoke name="ReadValue" waitFor="Argument"
get="[%:Event.Argument.RFIDURL.Value:%]" SerialNumber="[%:Event.Argument.InterfaceIndex.Value:%]"
DeviceType="[%:Event.Argument.InterfaceType.Value:%]" />
<Output name="Result" as="ActionResult">
<Value>
<ReadTag>
[%:[%:@Output:%]Invoke.ReadValue.Message.Value:%]
</ReadTag>
</Value>
</Output>
</Task>
</Workflow>
</Action>
<Action Name="PhidgetWriteRFIDTagAc" Documentation="Write the Tag Value to RFID in the vicinity">
<Workflow Limit="1" Live="1" Timeout="-1">
<Task name="Main" while="true">
<Output name="ActionArgument" as="ActionArgument" />
<Invoke name="WriteValue" waitFor="ActionArgument"
skip-if="[%:[:AA:].WriteTag/no-value(Value):%]" post="[%:[:AA:].RFIDURL.Value:%]"
SerialNumber="[%:[:AA:].InterfaceIndex.Value:%]"
SensorType="WriteTag" DeviceType="[%:[:AA:].InterfaceType.Value:%]">
<Message Type="text" Value="[%:[:AA:].WriteTag.Value:%]" />
</Invoke>
<Output name="Result" as="ActionResult">
<Value>
<WriteTag>
[%:[%:@Output:%]Invoke.WriteValue.Message.Value:%]
</WriteTag>
</Value>
</Output>
</Task>
</Workflow>
</Action>
</ThingFacet> |