4. Irrigation nozzle with flow control actuator
Key Concepts
Hardware Setup
The hardware setup needed to perform flow control actuation is show here.
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
- Parameters required to make a protocol specific invocation.
- 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.
<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
<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.
<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 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
<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.
<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 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.
Instantiation
To perform an actuation:
a) Operation is Transmit
b) Payload contains the value we want to be sent to the Flow Controller Arduino.
<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
<Query> <Find format="version,known"> <FlowControlModel> <FCId ne="" /> </FlowControlModel> </Find> </Query>
You will see the result from the RESULTS window:
The full content of the code is here.