...
Key Concepts
Tip | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
| ||||||||||
|
Background
Modeling and Simulation are central to the IoT Applications
...
and employ a wide range of technologies and methodologies. Most of
...
these technologies focus on
...
rather specific
...
aspects and a
...
narrow purpose (e.g., discrete event simulation in industrial systems vs. continuous simulation in electrical and computer engineering). There is a need for an all-encompassing approach, especially in the IoT arena. Atomiton TQL provides right set of hooks to create an all-encompassing approach to simulation.
Greenhouse Problem Statement
For the purpose of this tutorial to explain implementation approaches to simulation we look at the Greenhouse monitoring system with following scenario.
For the purpose of this tutorial let's model the Greenhouse monitoring system with following configuration.
- Greenhouse: size: 40 feet by 110 feet
- crop types, tomatoes and peppers
- 3 lanes, each lane has 10 zones
- Total 30 zones (each zone is 10 feet by 10 feet. Lay out is 3 by 10): 20 zones (2 by 10) are tomatoes, 10 zones are peppers
Each zone subdivided into 9 grids. Each grid is 3.33 feet by 3.33 feet. Total 270 grids
Zone Content:
- 1 temperature sensor (total 30)
- 1 humidity sensor (total 30)
- 1 camera (total 30)
- 1 irrigation motor (total 30)
- 1 heater (total 30)
Zone Grid Content:
- 1 ambient light sensor (total 270)
- 1 light (total 270)
- 1 irrigation nozzle (total 270)
- 1 soil moisture sensor (total 270)
External Conditions:
- 1 ambient light sensor
- 1 temperature sensor
- 1 humidity sensor
Simulation Design Patterns
...
The existing Atomic domain languages capabilities can be exploited to achieve simple to complex simulation. The simulation design patterns can be classified as follows:
Pattern # | Name | Description | Use Cases | Implementation Flow |
---|---|---|---|---|
1 | @ThingFacet Level | Seamless integration within the attribute's associate action |
|
|
2 | @ThingFacet Level | Separate Action to start the simulation |
|
|
3 | @Protocol Handler Level | Push the simulation logic down into protocol handler level |
|
|
4 | @Behavior Tree | Simulation logic is driven primarily by Behavior Trees. |
|
|
Simulating Temperature Sensor
Simulation Logic
- GHCurrentTemp is the general temperature inside the greenhouse
- It is affected by CurrentTemp (external), the vent and fans, and all the heaters
- The heaters are controlled by the application
- The vent and fans controls are not exposed to the application. So vent and fan behavior are simulated by BT, described in the next section.
- GHCurrentTemp is the default value for ZoneTemp if the heater is not on in that zone (if the heater is on in that zone, ZoneTemp will be higher than GHCurrentTemp)
- Value updates every 5
...
- minutes
GHTempAdj On Sunnyday: formula to calculate GHTempAdj in Celcius: GHTempAdj = (100/(CurrentTemp+237.1))3 * 100
For example, when (external) CurrenTemp = -5 Celcius, GHTempAdj = 5.186; when CurrentTemp = 28 Celcius, GHTempAdj = 3.661
On NOT Sunnyday: formula to calculate GHTempAdj in Celcius: GHTempAdj = (100/(CurrentTemp+237.1))3 * 60
For example, when (external) CurrenTemp = -5 Celcius, GHTempAdj = 3.111; when CurrentTemp = 28 Celcius, GHTempAdj = 2.197- Since temperature has an effect on humidity, always determine temperature value before determining humidity value
Add Greenhouse and Heater Model
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
<Def Name="EnvInfo"> <Number Name="Temperature"/> |
...
<Number Name="Humidity"/> <Number Name="Light"/> <Number Name="Pressure"/> <Number Name="SoilMoisture"/> <Number Name="Wind"/> <String Name="LastUpdatedByProvider"/> </Def> <Def Name="BoundingBox"> <GeoLocation Name="Vertex1"/> <GeoLocation Name="Vertex2"/> <GeoLocation Name="Vertex3"/> <GeoLocation Name="Vertex4"/> </Def> <Def Name="GeoLocation"> <Number Name="latitude"/> <Number Name="longitude"/> </Def> <DataModel Name="Greenhouse"> <Sid Name="GreenhouseID"/> |
...
<String Name="GreenhouseName"/> <GeoLocation Name="Location"/> <String Name="LocationName"/> <BoundingBox Name="Boundary"/> <Integer Name="LaneCount"/> <Double Name="LaneWidth"/> <Double Name="ZoneLength"/> <Integer Name="ZoneInLane"/> <Integer Name="ZoneCount"/> <Double Name="GHLength"/> <Double Name="GHWidth"/> <EnvInfo Name="ExternalEnv"/> <EnvInfo Name="InternalEnv"/> <String Name="VentOnOffState"/> <String Name="FansOnOffState"/> <Boolean Name="SunnyDay"/> <Boolean Name="Running" default="false"/> <!-- true / false --> </DataModel> <ThingModel Name="Heater"> |
...
<Sid Name="HeaterID"/> <String Name="OnOffState"/> <Integer Name="HeatingLevel"/> </ThingModel> |
...
Pipeline Macros
- Execute Internal Query
- Schedule and UnSchedule TQL Code
...
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
<Macro Name="executeQuery">
<Argument>
<QueryString>
<Query/>
</QueryString>
</Argument>
<Result>
<OnRequest Target="[:RuntimeParams.FacetIDName:]" Disable="CMD_SERVER">
<Process Return="CMD_NOP">
<Message>
<Value>
[:$Macro.Argument.QueryString:]
</Value>
</Message>
</Process>
</OnRequest>
</Result>
</Macro>
<Macro Name="DeleteScheduleJob">
<Argument>
<JobName/>
</Argument>
<Result>
<DoRequest target="[:RuntimeParams.SequenceFacetIDName:]">
<Process>
<Message type="xml">
<Value>
<Remove eid="[:$Macro.Argument.JobName:]"/>
</Value>
</Message>
</Process>
</DoRequest>
</Result>
</Macro>
<Macro Name="ScheduleJob">
<Argument>
<ScheduleInterval>
</ScheduleInterval>
<StartTime>
0
</StartTime>
<EndTime>
</EndTime>
<ActionCode>
</ActionCode>
<Name>
</Name>
</Argument>
<Result>
<Log Message="Inside ScheduleJob macro"/>
<DoRequest target="[:RuntimeParams.SequenceFacetIDName:]">
<Process>
<Message>
<Value>
<Execute eid="[:$Macro.Argument.Name:]" schedule="[[:$Macro.Argument.StartTime:]..[:$Macro.Argument.EndTime:]/[:$Macro.Argument.ScheduleInterval:]]">
<Action>
<ExecuteQuery>
<QueryString>
[:$Macro.Argument.ActionCode:]
</QueryString>
</ExecuteQuery>
</Action>
</Execute>
</Value>
</Message>
</Process>
</DoRequest>
</Result>
</Macro> |
...
Macro to Simulate Sensor
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
<Macro Name="SetGHCurrentTemp"> <Argument> <TempSensorId></TempSensorId> <GreenhouseID></GreenhouseID> </Argument> <Result> |
...
<Log Message="*********Calculating |
...
Temperature..."/> <executeQuery> |
...
<QueryString> |
...
<Query> |
...
<Find> |
...
<Greenhouse> <GreenhouseID>[:$Macro.Argument.GreenhouseID:]</GreenhouseID> </Greenhouse> </Find> </Query> </QueryString> </executeQuery> <if condition="$Response.Message.Value/Find/Status eq 'Success'"> <then> <SetLocalData key="CurrentTemp"> <Value>[:$Response.Message.Value.Find.Result.Greenhouse.ExternalEnv.Temperature:]</Value> </SetLocalData> <SetLocalData key="GHCurrentTemp"> |
...
<Value>[:$Response.Message.Value.Find.Result.Greenhouse.InternalEnv.Temperature:]</Value> </SetLocalData> <SetLocalData key="Vent"> <Value>[:$Response.Message.Value.Find.Result.Greenhouse.VentOnOffState:]</Value> </SetLocalData> <SetLocalData key="Fans"> <Value>[:$Response.Message.Value.Find.Result.Greenhouse.FansOnOffState:]</Value> </SetLocalData> <SetLocalData key="SunnyDay"> <Value>[:$Response.Message.Value.Find.Result.Greenhouse.SunnyDay:]</Value> </SetLocalData> <executeQuery> |
...
<QueryString> |
...
<Query> |
...
<Find> |
...
<Heater> |
...
|
...
<OnOffState eq='On'/> </Heater> </Find> </Query> </QueryString> </executeQuery> <Javascript> <resp> <Include>$Response.Message.Value</Include> </resp> var resultList = resp.Find; var heaterLevels = []; var heaterLevelsSum = 0; for each (var result in resultList.iterEntries("Result")){ var heaters = result.getValue(); heaterLevels.push(heaters.Heater.HeatingLevel); heaterLevelsSum = heaterLevelsSum + heaters.Heater.HeatingLevel; |
...
} |
...
|
...
|
...
var GHTempAdj = |
...
0; if([:$LocalData.SunnyDay:]){ GHTempAdj = Math.pow((100/([:$LocalData.CurrentTemp:]+237.1)), 3) * 100; }else{ GHTempAdj = Math.pow((100/([:$LocalData.CurrentTemp:]+237.1)), 3) * 60; } var GHBaseTemp = [:$LocalData.CurrentTemp:] + GHTempAdj; var TempVentImp = ([:$LocalData.CurrentTemp:] - [:$LocalData.GHCurrentTemp:]) * 0.03; var HeaterEffectRatio = heaterLevelsSum / 90; |
...
|
...
|
...
var TempHeaterImp = HeaterEffectRatio * (Math.sqrt(Math.max(0,(45-[:$LocalData.GHCurrentTemp:])))); var GHCurrentTemp = GHBaseTemp + TempVentImp + TempHeaterImp; sffContext.execute("SetContextData","key","GHCurrentTemp","value",GHCurrentTemp); </Javascript> <executeQuery> <QueryString> <Query> <Find format="Version"> <TempSensor> <sensorId>[:$Macro.Argument.TempSensorId:]</sensorId> </TempSensor> </Find> <SetResponseData> <Key>Message.Value.Find.Result.TempSensor.TempValue.Value</Key> <Value>[:$ContextData.GHCurrentTemp:]</Value> |
...
</SetResponseData> |
...
<Save> |
...
<from>Result</from> <Include>$Response.Message.Value.Find</Include> </Save> </Query> </QueryString> </executeQuery> <executeQuery> <QueryString> <Query> <Find format="Version"> <Greenhouse> <GreenhouseID>[:$Macro.Argument.GreenhouseID:]</GreenhouseID> </Greenhouse> </Find> <SetResponseData> <Key>Message.Value.Find.Result.Greenhouse.InternalEnv.Temperature.Value</Key> <Value>[:$ContextData.GHCurrentTemp:]</Value> </SetResponseData> <Save> <from>Result</from> <Include>$Response.Message.Value.Find</Include> </Save> </Query> </QueryString> </executeQuery> </then> </if> </Result> </Macro> |
Queries
Let's start writing some queries to create and read the data.
Create Greenhouse
Add Action to Start Simulate
Code Block | |||
---|---|---|---|
|
...
|
...
<Action Name="SimulatedReadAction" Documentation="Start |
...
reading temperature from simulated env"> |
...
|
...
|
...
<Workflow Limit="1" Live="1" Timeout="-1"> |
...
<Task |
...
Name="Main1" While="true"> |
...
<Event Name="Argument" As="ActionArgument"/> |
...
|
...
<Invoke Name="StartSimulation" waitFor="Argument"> |
...
|
...
<FacetScript> |
...
|
...
<If |
...
condition="/'[%:Event.Argument.Simulated.Value:%]' eq 'true'"> |
...
|
...
<then> |
...
|
...
|
...
<Log Message="Starting |
...
simulation.."/> |
...
|
...
<SetGHCurrentTemp> |
...
|
...
|
...
<TempSensorId> |
...
|
...
|
...
[%:Event.Argument.sensorId:%] |
...
|
...
|
...
</ |
...
TempSensorId> |
...
|
...
<GreenhouseID> |
...
|
...
|
...
|
...
[%:Event.Argument.GreenhouseID.Value:%] |
...
|
...
</GreenhouseID>
</SetGHCurrentTemp>
<ScheduleJob>
<Name>
SetGHCurrentTemperature
</Name>
<ScheduleInterval>
5min
</ScheduleInterval>
<ActionCode>
<SetGHCurrentTemp>
<TempSensorId>
[%:Event.Argument.sensorId:%]
</TempSensorId>
<GreenhouseID>
[%:Event.Argument.GreenhouseID.Value:%]
</GreenhouseID>
</SetGHCurrentTemp>
</ActionCode>
</ScheduleJob>
</then>
<else>
<Log Message="Stopping simulation.."/>
<DeleteScheduleJob>
<JobName>
SetGHCurrentTemperature
</JobName>
</DeleteScheduleJob>
</else>
</If>
</FacetScript>
</Invoke>
<Output Name="Result" As="ActionResult">
<Value>
<Simulated>
[%:Event.Argument.Simulated.Value:%]
</Simulated>
</Value>
</Output>
</Task>
</Workflow>
</Action> |
Changes to TempFacetSerial
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
<ThingFacet Name="TempFacetSerial">
<String Name="TempValue"/>
<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"/>
<Reference Name="GreenhouseID" Type="Greenhouse"/>
<String Name="Simulated" KnownBy="SimulatedReadAction"/>
</ThingFacet> |
Queries
Let's start writing some queries to create and read the data.
Create Greenhouse
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
<Query>
<Create>
<Greenhouse>
<GreenhouseName>Khan Farmhouse</GreenhouseName>
<BoundingBox>
<Vertex1>0</Vertex1>
<Vertex2>0</Vertex2>
<Vertex3>110</Vertex3>
<Vertex4>40</Vertex4>
</BoundingBox>
<LaneCount>
3
</LaneCount>
<LaneWidth>110</LaneWidth>
<ZoneLength>10</ZoneLength>
<ZoneInLane>
10
</ZoneInLane>
<ZoneCount>3</ZoneCount>
<GHLength>110</GHLength>
<GHWidth>40</GHWidth>
<ExternalEnv>
<Temperature>1</Temperature>
<Humidity>1</Humidity>
<Light>1</Light>
<SoilMoisture>1</SoilMoisture>
<Wind>19.5</Wind>
</ExternalEnv>
<InternalEnv>
<Temperature>1</Temperature>
<Humidity>1</Humidity>
<Light>1</Light>
<SoilMoisture>1</SoilMoisture>
</InternalEnv>
<SunnyDay>True</SunnyDay>
<Location>
<latitude>-122.1</latitude>
<longitude>32.0</longitude>
</Location>
</Greenhouse>
</Create>
</Query> |
Add Heater
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
<Query>
<Create>
<Heater>
<OnOffState>
on
</OnOffState>
<HeatingLevel>
2
</HeatingLevel>
</Heater>
</Create>
</Query> |
Start Simulation
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
<Query> <DeleteAll> <TempSensor> <sensorId ne=""/> </TempSensor> </DeleteAll> <Find format="Version"> <Greenhouse> <GreenhouseID ne =""/> </Greenhouse> </ |
...
Start / Stop Simulation
...
language | xml |
---|---|
title | Create Zone |
linenumbers | true |
...
Find> <Create> <TempSensor> <Simulated> |
...
true |
...
</ |
...
Simulated> <GreenhouseID> |
...
|
...
|
...
[:$Response.Message.Value.Find.Result. |
...
Greenhouse.GreenhouseID:] |
...
</ |
...
GreenhouseID> |
...
|
...
</TempSensor> </ |
...
Create> </Query> |
Find Greenhouse / TempSensor
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
<Query>
<Find>
<Greenhouse>
<GreenhouseID ne=""/>
</Greenhouse>
</Find>
</Query>
<!-- Custom container -->
<Query as="Khans.GreenDen">
<Find>
<Greenhouse>
<GreenhouseID ne=""/>
</Greenhouse>
</Find>
</Query>
<!-- Filter the result -->
<Query as="Khans.GreenDen">
<Find only="Greenhouse:ExternalEnv">
<Greenhouse>
<GreenhouseID ne=""/>
</Greenhouse>
</Find>
</Query> |
Source Code
Import into TQLStudio
ProjectName | Import Link |
---|---|
Temp Sensor Serial | SimulateTempSensor |