4. Irrigation nozzle with flow control actuator

Key Concepts

 Related docs
  

 

Hardware Setup

The hardware setup needed to perform flow control actuation is show here.

 

Sketch to Actuate Flow Control
int incomingByte = 0;   // for incoming serial data
int solenoidPin = 5;    //This is the output pin on the Arduino we are using

void setup() {
  Serial.begin(9600);     // opens serial port, sets data rate to 9600 bps
  // put your setup code here, to run once:
  pinMode(solenoidPin, OUTPUT);           //Sets the pin as an output
}

void loop() {
  if (Serial.available() > 0) {
     // read the incoming byte:
     incomingByte = Serial.read();
     
     // say what you got:
     String responseStr = "Received:" + String(incomingByte);
     if (isDigit(incomingByte)) {
        int controlValue = int(incomingByte - '0');
        //Serial.print("Control: ");
        responseStr = responseStr + "#Control:";
        
        if (controlValue > 0){
             digitalWrite(solenoidPin, HIGH);    //Switch Solenoid ON
             responseStr = responseStr + "ON";
        }
        else if (controlValue == 0) {
             digitalWrite(solenoidPin, LOW);     //Switch Solenoid OFF
             responseStr = responseStr + "OFF";
        }
     }
     Serial.println(responseStr);  
  }
}

Write a ThingFacet, Workflow and Device Logic (Message Transformation)

Write a ThingFacet 

To abstract the interactions with the FlowControl setup in the previous section, we create a FlowControlFacet ThingFacet. 

We add two types of attributes in this ThingFacet

  1. Parameters required to make a protocol specific invocation.
  2. Attribute(s) to be used for actuation. 

    For the purpose of this tutorial we have protocol specific parameters as (Unit, Peripheral, InterfacePort, Interface, etc).
    Payload Value will be used for actuation of the flow control. 
FlowControlFacet attributes
<ThingFacet Name="FlowControlFacet">
   <String Name="Unit" default="Celsius"/>
   <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">

</ThingFacet>

Write a Action with Workflow

The next step in ThingFacet is writing a Action which contains a workflow responsible for making external protocol specific call and store the output in the appropriate ThingFacet attribute.  An important decision while writing Action is correct protocol selection. See the /wiki/spaces/TQLDocs/pages/1179871 in the Working with Things section. For Flow Control Actuator we need the perif:// protocol handler, which is part of the TQLEngine.

1. Write an Action name as SerialWriteAction

SerialWriteAction
<ThingFacet Name="FlowControlFacet">
	...
	<Action Name="SerialWriteAction">
		...
	</Action>
</ThingFacet>

 

2. Create a Workflow within the Action, with one single continuous running Task and waiting for the Event with its ActionArgument.

Workflow
<Action Name="SerialWriteAction">
	<Workflow Limit="1" Live="1" Timeout="-1">
    	<Task name="Main" while="true">
        	<Event name="Argument" as="ActionArgument"/>
			...
    	</Task>
	</Workflow>
</Action>

Here we used three modifiers for this workflow. Limit = "1" means there can be at most one instance of this workflow waiting. Live = "1" means there can be at most one instance of this workflow running. Timeout ="-1" means this workflow will never be timed out. We used a modifier while = "true" with the Task to make the workflow running in a continuous loop, because it needs to run repeatedly, not just once. For more details, refer to workflow modifiers and the lifecycle of a workflow.

The task will be activated by the event handler Event called ActionArgument. "ActionArgument" is the event generated whenever the attribute(s) associated with this Action is modified (See Associate Action with a ThingFacet Attribute). ActionArgument carries all the current values of the ThingFacet attributes, which can be used in the task if needed.

 

3. Invoke PERIF call: Method of PERIF is POST. We use Invoke modifier Post for this purpose. The parameters required PERIF are passed as part of <Message> and <Value>. More details of the perif:// handler are in the Working with Things Section.

Invoke serial handler
<Invoke name="InvokeSerialWrite" waitFor="Argument" post="perif://[%:Event.Argument.UniqueId.Value:%]">
  <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>
      <Peripheral>
        "[%:Event.Argument.Peripheral.Value:%]"
      </Peripheral>
      <format>
        "[%:Event.Argument.Format.Value:%]"
      </format>
      <Payload>
        "[%:Event.Argument.Payload.Value:%]"
      </Payload>
    </Value>
  </Message>
</Invoke>

Here Invoke gets the attribute values that the Event ActionArgument (or Argument) carries. It uses them as the parameter values for the PERIF by substitution. For example, 

<Baudrate>"[%:Event.Argument.baudrate.Value:%]"</Baudrate>

means replacing the value of Message.Value.Baudrate with the value of Event.Argument.baudrate.Value.

Value substitution (rather than assignment) is very common in TQL, given that it is a declarative language. The notation [%: is part of the /wiki/spaces/TEACH/pages/21170773. More details on /wiki/spaces/TEACH/pages/21170773 can be found in the Developer's Guide.

4. Process the message received from the actuator

Message parsing using TP and XPath
<Output Name="Result" As="ActionResult">
              <Value>
                <Payload>
                  [%:Invoke.InvokeSerialWrite.Message.Value.received:%]
                </Payload>
              </Value>
</Output>

Here we used an XPath statement to pass the message. Alternatively, you can use Javascript. An example of using JavaScript for message processing within an Action can be found in another tutorial here.

Associate Action with a ThingFacet Attribute

We will now have to associate a ThingFacet Attribute (TempValue) with the Action using the KnownBy modifier. When TempValue is "KnownBy" SerialReadAction, any TempValue value changes will activate the SerialReadAction.

Attribute TempValue
<ThingFacet Name="FlowControlFacet">
	...
	<String Name="Payload" KnownBy="SerialWriteAction"/>
</ThingFacet>

 

Combining ThingFacet with a ThingModel

Finally, in order to use ThingFacet we have to combine it with a ThingModel. We define ThingModel to contain only a unique system identifier.

ThingModel FlowControlModel
<ThingModel Name="FlowControlModel" combines="FlowControlFacet">
	<Sid Name="FCId"/>
</ThingModel>

 

When FlowControlModel ThingModel "combines" with FlowControlFacet, FlowContorlModel can be instantiated and accessible by TQL queries. FlowControlModel will inherit all the attributes from FlowControlFacet, in addition to its own attributes. The ThingFacet FlowControlFacet hence serves as a reusable component.

More details on the use of "combines" can be found here. Information on Sid can be found here.

Deploy and test via queries and subscriptions

Export, import and deploy

Once the models are built in a TQLStudio project, you can export the project. The URL of the Zip file containing the content of your project will be sent over to your email account. Once you have downloaded the engine – launch the TQLEngine User Interface in the browser and select Import Project. You can copy the URL from Export Project Email and hit import button. Go to ThingSpaces and Deploy the project.

 Deploy and instantiate

Deploy

In TQL, Model development is a three step process -

  1. Definition
  2. Deploy (compilation)
  3. Queries (instantiation, update, delete, subscription)

See also lifecycle of models.

In this section Deploy and Query (which includes Instantiation or Create) is discussed. Deploy and Query is the the runtime phase of the model development. In order to deploy the models you would need a running instance of A-Stack. A-Stack can be downloaded from your TQLStudio Account page and installed to the environment of your choice.

The deployment unit for A-Stack is Package, which has contains definition of all the capabilities (Facets) required as part of the package.

Package File (<Package Name>.mqp.xml) has following structure and it gets created by the system at the time of deploying a project by using TQLConsole.

A-Stack Package Definition

Package contains instances for Facet Types and give the unique Id as fid-<FacerID>. fid-<Facet ID> is used to refer to this instance both from inside the engine from other models and from outside of the engine.

Package Definition Template
#
NewPackage(Name: "Some-Name-for-the-package"): 
	# List of A-Stack Capabilities to deploy using FacetScript
 	NewFacetInstance(Name: "..", fid: "..", Type: "..."):
		#...
Package Definition File Naming

Package definitions are defined in a file and given and extension of "mqp.xml". The file is normally placed in the "deploy" folder subdirectory.

A-Stack Capabilities

Package definition contains instances of capabilities that are offered by A-Stack. Below is the list of the A-Stack Capabilities:

Name

Type NameDescription
MessageSffMsgFacetGeneric Message Communication.
NetworkSffNetworkFacetA-Stack cluster support. Define the list of A-Stack(s) that can participate in syncing their data
TQLSffTqlFacetThing Query Language Support (Thing Definition, Thing Interaction)
WorkflowSffWdlFacetWorkflow Language Support
Behavior TreesSffBdlFacetBehavior Tree Implementation
TopicSffTopicFacetSubscription and Notification of Model changes
SchedulingSffSequenceFacetSchedule tasks / jobs (piece of Model code) to be executed in a repeated sequence of defined interval.
Static FilesSffStaticFileFacetServe static files using A-Stack.

When a package definition is created you are defining the list of A-Stack capability instances that you would like to get started with. The capability instances are defined using FacetScript Language. Please refer to Facescript Introduction Section for more details on using FacetScript.

There are two ways to deploy the models:

  1. TQLStudio ThingSpaces
  2. A-Stack Management API against your running copy of A-Stack on your local machine (Gateway, MicroController, Laptop).

Deploying Project using TQLStudio ThingSpaces

Once the models are defined using Model Editor as part of your project, you can deploy the models by clicking "Deploy" as part of ThingSpaces. The models get deployed to the default A-Stack provided by Atomiton in the cloud. You can overwrite the destination engine by updating Host, where your engine is running.

When you run the A-Stack on your machine, there is default administration UI, which can be accessed through http://<Hostname>:<Port>/fid-TQLConsole/app/index.html. The project can be exported from cloud Studio and imported in the local engine by using this admin UI. The default username and password for this local engine is <TQLEngine@atomiton.com> and password is <tql123>.

Content of Package Definition when deploying using TQLStudio ThingSpaces

Once the project is deployed from ThingSpaces in TQLStudio (or A-Stack UI) default Package Layout is deployed as part of your project. The rational behind default package layout is to provide a instance of every A-Stack with standard capability to start with. Here we list the package definition snippets:

Runtime Parameters Section

Top section of the default package definition maintains all the reusable reference names. As developer, you can extend this list with your own names, which can be referred later.

Default Package Definiton Runtime Parameter Section
#
RuntimeParams:
	DbmLocation(Comment: "Name of the model file"): Model.Location
    ThingFacetLocation(Comment: "Folder for all the Facet definitions"): TF.Location
    MacroLocation(Comment: "Folder for all the macro definitions"): Macros.Location
    SpacesLocation(Comment: "Folder for all instances definition"): Spaces.Location
    TypesLocation(Comment: "Folder for all type definitions"): Types.Location
    WSFacetIDName(Comment: "WebSocket Facet Id for TQL subscriptions"): TQL.FacetWS
    FacetIDName(Comment: "Http Facte Id for TQL Queries"): TQL.Facet
    TQLCacheName(Comment: "Storage name for the current package"): TQL.CacheName
    TopicFacetIDName(Comment: "???"): TQLGenericTopic
    MacroFacetID(Comment: "???"): Macro.Location
    SequenceFacetIDName(Comment: "Unique name for the Sequence Facet Instance"): TQLSequenceFacet
    WdlFacetIDName(Comment: "Unique name for the Workflow Facet Instance"): TQLWdlFacet
    TQLOutputFormat(Comment: "???"): XML
    ProjectSettings_EnforceKey(Comment: "???"): False
    ProjectSettings_ProjectKey(Comment: "???"): TQLStudio.ProjectKey
Workflow - Standalone Use of Workflow using Workflow Definition Language (WDL)

Workflow is one of the basic capability in the engine. It is used in the Thing and App Models by default to support actions. In addition, you can add this capability in the engine by writing new facet Instance of type SffWdlFacet. This means now you can write Facet Script containing workflow definitions, which will be processed by engine at the time of deployment. By linking this to Http Server Facet, workflow can receive inputs on Http Channel and generate response on this channel as well.

Workflow Capability Instance
#
NewFacetInstance(fid: "[:RuntimeParams.WdlFacetIDName:]", Name: "wdl", Type: "SffWdlFacet"):
	OnActivate:
    	ImportFacet: [:RuntimeParams.MacroFacetID:]
    OnOpen(ModifyPipeline="HttpServerExtensionArgs"):
		#...
Topic - Standalone Use of Subscription and Notifications of Model changes. 

In A-Stack, every model artifact (model, attribute, instance) is defaulted to a topic in itself. Means clients can subscribe to any part of the model and start getting update events. In addition to model based subscriptions, independent pub-sub capability can be added in the engine, by writing SffTopicFacet Facet instance.

Topic Capability Instance
#
NewFacetInstance(fid: "[:RuntimeParams.TopicFacetIDName:]", name: "TQLGenericTopic", type: "SffTopicFacet"):
  OnActivate:
    DoRequest:
      Process:
        Message(type: "CDM"):
            Value:
              Subscribe(sid: "GenericTopicID", topic: "TQL.*"):
                Action:
                  logInfo: "CacheUpdate -> [:[:@Log:]$Request:]"
    OnOpen(ModifyPipeline: "HttpServerExtensionArgs"):
      #...
Sequence - Standalone Use of Scheduled jobs/task (Model code snippet)

Sequence provides the time based triggering, which can be against fixed time, delayed time, constant frequency, constant time intervals, etc.

Sequence Capability Instance
#
NewFacetInstance(fid: "[:RuntimeParams.SequenceFacetIDName:]", name: "seq", type: "SffSequenceFacet"):
  OnActivate:
    OnActivate:
      #...
    OnOpen ModifyPipeline: "HttpServerExtensionArgs")
      #...
TQL - Thing Query Language Usage over an WebSocket Transport

All models provide subscription capability on websocket and that is achieved by adding TQLFacet and connecting it with the websocket pipeline. Note that your model file(s) created using Model Editor is referenced in this section.

TQL Capability Instance over WebSocket
#
NewFacetInstance(fid: "[:RuntimeParams.WSFacetIDName:]", Name: "TQL", Type: "SffTqlFacet"):
    OnActivate:
      NewFacetInstance(name: "tqlwfws", type: "SffWdlFacet"):
      TopicFacet: TQLGenericTopic
      ImportFacet: [:RuntimeParams.MacroFacetID:]
      Process:
        Storage(Name: "[:RuntimeParams.TQLCacheName:]", Type: "SqlSff"):
           Comment: "[:RuntimeParams.TQLCacheName:] Database SFF Unstructured SQL database"
        Namespace: # Your Model file is referenced here
          TQLStudio.DbmLocation

    OnOpen(ModifyPipeline: "WsServerExtensionArgs")

    OnRequest:
      DoRequest:
        Process(Return: "CMD_NOP"):
          Message:
            Value:
              Include: $Request.Message.Value
      DoResponse:
        Process:
          Message(type: "[:RuntimeParams.TQLOutputFormat:]"):
            Value:
              Include:$Response.Message.Value
TQL - Thing Query Language Usage over an HTTP Transport

Normal queries are posted on http channel and to achieve this capability, TQLFacet is created with http pipeline extension.

  • Note that your model file(s) created using Model Editor is referenced in this section.
  • In this section there is a default FacetScript code to perform enforcing usage of Project Keys (if set in Project Settings Menu).

TQL Capability Instance over HTTP
#
NewFacetInstance(fid: "[:RuntimeParams.FacetIDName:]", Name: "TQL", Type: "SffTqlFacet"):
    OnActivate:
      Include: [:GetProjectModelsMacro:]
      Include: [:InstantiateModelMacro:]
      
      NewFacetInstance(name: "tqlwf", type: "SffWdlFacet"):
      TopicFacet: TQLGenericTopic
      ImportFacet: [:RuntimeParams.MacroFacetID:]
      Process:
        Storage(Name: "[:RuntimeParams.TQLCacheName:]", Type: "SqlSff"):
          Comment: "[:RuntimeParams.TQLCacheName:] Database SFF Unstructured SQL database"
        Namespace: TQLStudio.DbmLocation
        
    OnOpen(ModifyPipeline: "HttpServerExtensionArgs");
    
    OnRequest:
      SetContextData(key: "projectKey", value: "[:$Request.Arguments.x-atomiton-project-key:]")
      SetContextData(key: "projectSysId", value: "[:$Request.Arguments.x-atomiton-project-id:]")
      
      if("[:RuntimeParams.ProjectSettings_EnforceKey:]" == "true"):
        if($ContextData/projectKey == "[:RuntimeParams.ProjectSettings_ProjectKey:]"):        
              DoRequest:
                Process(Return: "CMD_NOP"):
                  Message:
                    Value:
                      Include: $Request.Message.Value
        else:
          SetResponse:
            Message(type: "[:RuntimeParams.TQLOutputFormat:]"):
              Value:
                Status: Failed
                Message: "Invalid Project Key"
      else:
        DoRequest:
          Process(Return: "CMD_NOP"):
            Message:
              Value:
                Include: $Request.Message.Value
                
      DoResponse:
        Process:
          Include: RESTHeaders
          Message(type: "[:RuntimeParams.TQLOutputFormat:]"):
            Value:
              Include: $Response.Message.Value

Note: The process of deploying Imported projects in A-Stack using User Interface (UI) is same as that of TQLStudio ThingSpaces.

For deployment directly to A-Stack using command line (without UI), see /wiki/spaces/TQLDocs/pages/1179774 in the Advanced Topics Section.

Instantiate

The final phase of Model development is actually using the Model using Queries. In some cases Models (Especially Thing / App Models) needs to be initialized to kick-start the whole process of interacting with Things or external application. In TQL, we use the term "Instantiate" to define the process of creating new instances on models. Model Instances can be created using either

  • Create TQL Query
  • Save TQL Query

Instantiate does the following tasks:

  • It creates an instance, or instances of a Model
  • It initialize the values of the model instances' attributes
  • If any of the attributes is an actionable attribute, the associated Action will be triggered (provided the attribute value is initialized)

For example - Here is the Instantiate Query to start the Phidget Servo Motor Thing Model. Since the Pidget Servo Motor Thing Model is combined with PhidgetServoFacet Thing Facet, and there is a action on ServoAngle attribute waiting for event (create/update) to occur, therefore creating an instance results in triggering of the Action. 

Instantiate Phidget Servo Model
#
Query:
	Create:
    	PhidgetServoModel:
      		ServoProtocolURL: 
				phid: "..."
      	PhidgetDeviceType: "PhidgetAdvancedServer"
      	DeviceInterfaceIndex: 0
      	ServoAngle: 110

You can instantiate in two ways.

(1) Instantiate by writing Save or Create TQL Queries in Query Editor.

(2) Instantiate using ThingSpace

ThingSpace provided easier way to instantiate the models. There are two ways to instantiate in ThingSpaces

a) Manual Entry of values

Once the model is instantiated using ThingSpaces it creates a file with Save Query automatically in Spaces folder of your project.

Save Query from Instantiate
#
Query:
	Save:
    	VendorInfo(instantiated: true):
      		vendorName: Phidget
      		vendorTitle: SensorProvider

b) Import Instantiate data using CSV file format.

CSV file need to have the columns in order, in which the definition is provided. If no value, you can leave the column empty. If no values for trailing attributes, you don't have to create columns.


Go to page

Instantiation

To perform an actuation:

a) Operation is Transmit

b) Payload contains the value we want to be sent to the Flow Controller Arduino.

Flow Control Actuation
<Query>
  <DeleteAll format="version,current">
    <FlowControlModel>
      <FCId ne=""/>
    </FlowControlModel>
  </DeleteAll>
  <Save format="version,current">
    <!-- This will read -->
    <FlowControlModel>
      <Peripheral>
        serial
      </Peripheral>
      <Baudrate>
        9600
      </Baudrate>
      <InterfacePort>
        /dev/cu.usbmodem1451
      </InterfacePort>
      <Interface>
        serial
      </Interface>
      <Format>
        ascii
      </Format>
      <Operation>
        transmit
      </Operation>
      <UniqueId>
        76522
      </UniqueId>
      <Payload>
        0
      </Payload>
    </FlowControlModel>
  </Save>
</Query>

Query and subscription

Now we can try a simple Find Query to get values from FlowControlModel

Find FlowControlModel query
<Query>
  <Find format="version,known">
    <FlowControlModel>
      <FCId ne="" />
    </FlowControlModel>
  </Find>
</Query>

You will see the result from the RESULTS window:

Find TempSensor query result
<Find Status="Success" Format="version,known">
  <Result>
    <FlowControlModel>
      <FCId>KOVAJLSEAAAAUAABA54SLLWF</FCId>
      <Peripheral Value="serial" Known="serial" Version="1"/>
      <Format Value="ascii" Known="ascii" Version="1"/>
      <Baudrate Value="9600" Known="9600" Version="1"/>
      <InterfacePort Value="/dev/cu.usbmodem1451" Known="/dev/cu.usbmodem1451" Version="1"/>
      <UniqueId Value="76522" Known="76522" Version="1"/>
      <Interface Value="serial" Known="serial" Version="1"/>
      <Operation Value="transmit" Known="transmit" Version="1"/>
      <Payload Value="80" Known="&lt;received&gt;Received:56#Control:ON
Received:48#Control:OFF&lt;/received&gt;" Version="1"/>
    </FlowControlModel>
  </Result>
</Find>

 


The full content of the code is here.

Flow Control Model
<Namespace Name="Atomiton">
  <Domain Name="Actuator">
    <ThingFacet Name="FlowControlFacet">
      <String Name="Unit" default="Celsius"/>
      <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" KnownBy="SerialWriteAction"/>
      <Action Name="SerialWriteAction">
        <Workflow Limit="1" Live="1" Timeout="-1">
          <Task name="Main" while="true">
            <Event name="Argument" as="ActionArgument"/>
            <Invoke name="InvokeSerialWrite" waitFor="Argument" post="perif://[%:Event.Argument.UniqueId.Value:%]">
              <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>
                  <Peripheral>
                    "[%:Event.Argument.Peripheral.Value:%]"
                  </Peripheral>
                  <format>
                     "[%:Event.Argument.Format.Value:%]"
                  </format>
                  <Payload>
                    "[%:Event.Argument.Payload.Value:%]"
                  </Payload>
                </Value>
              </Message>
            </Invoke>
            <Log Message="Data from Controller: [%:Invoke.InvokeSerialWrite.Message.Value:%]"/>
            <Output Name="Result" As="ActionResult">
              <Value>
                <Payload>
                  [%:Invoke.InvokeSerialWrite.Message.Value:%]
                </Payload>
              </Value>
            </Output>
          </Task>
        </Workflow>
      </Action>
    </ThingFacet>
    <ThingModel Name="FlowControlModel" combines="FlowControlFacet">
      <Sid Name="FCId"/>
    </ThingModel>
  </Domain>
</Namespace>