Table of Contents |
---|
minLevel | 3 |
---|
outline | true |
---|
style | none |
---|
|
Background
Device Management
Bulk initialization of Things is closely tied to the device management aspect of IoT Platform. The devices layer is the most important component of an IoT solution. A mature IoT platform comes with comprehensive device management features that let customers on:
- On-board existing and new devices with rich metadata.
- Identical devices that share the same metadata are grouped together. This feature makes it possible to search devices based on their capabilities.
- The platform also provides per-device authentication and authorization to enforce enhanced security.
...
-
- It makes it easy to define which devices can connect, send, and receive messages.
- Devices can be easily blacklisted and whitelisted through declarative policies.
TQLEngine platform provide mechanisms to easily interoperate with remote management capabilities. Through this feature, customers can perform tasks such as firmware upgrades, remote debugging, and maintenance of devices deployed in remote locations. Finally, the platform makes it easy to leverage an existing inventory of devices and assets maintained in a traditional MRP solution.address above device management requirements.
Bulk Initialization Design Patterns
At the time of writing this tutorial Atomiton TQLEngine does not provide specific language constructs to perform bulk initialization of Things or Data. But the existing Atomic domain languages capabilities can be exploited to achieve bulk initialization requirements. The bulk initialization 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 | - Simple Sensors and Actuators
| - JavaScript to capture simulation logic
- Emulate automatic trigger for sensors
|
2 | @ThingFacet Level | Separate Action to start the simulation | - Simple Sensors and Actuators
| - JavaScript to capture simulation logic
- Schedule frequency of simulation
|
3 | @Protocol Handler Level | Push the simulation logic down into protocol handler level | - Complex simulation profiles can be passed
- Automatically available at the Invoke level
| |
4 | @Behavior Tree | Simulation logic is driven primarily by Behavior Trees. | | - Within BDL Facet Instance
|
Simulating Temperature Sensor
Simulation Logic
Image Added
- 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 |
---|
language | xml |
---|
title | Greenhouse DataModel and Types |
---|
linenumbers | true |
---|
|
<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 |
---|
language | xml |
---|
title | Pipeline Macros |
---|
linenumbers | true |
---|
|
<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 |
---|
language | xml |
---|
title | Simulate Sensor Logic |
---|
linenumbers | true |
---|
|
<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> |
Add Action to Start Simulate
Code Block |
---|
language | xml |
---|
title | Action to Start Simulation |
---|
linenumbers | true |
---|
|
<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 |
---|
language | xml |
---|
title | Changes to Thing Facet |
---|
linenumbers | true |
---|
|
<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 |
---|
language | xml |
---|
title | Create Greenhouse |
---|
linenumbers | true |
---|
|
<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 |
---|
language | xml |
---|
title | Add Heater |
---|
linenumbers | true |
---|
|
<Query>
<Create>
<Heater>
<OnOffState>
on
</OnOffState>
<HeatingLevel>
2
</HeatingLevel>
</Heater>
</Create>
</Query> |
Start Simulation
Code Block |
---|
language | xml |
---|
title | Start Simulator |
---|
linenumbers | true |
---|
|
<Query>
<DeleteAll>
<TempSensor>
<sensorId ne=""/>
</TempSensor>
</DeleteAll>
<Find format="Version">
<Greenhouse>
<GreenhouseID ne =""/>
</Greenhouse>
</Find>
<Create>
<TempSensor>
<Simulated>
true
</Simulated>
<GreenhouseID>
[:$Response.Message.Value.Find.Result.Greenhouse.GreenhouseID:]
</GreenhouseID>
</TempSensor>
</Create>
</Query> |
Find Greenhouse / TempSensor
Code Block |
---|
language | xml |
---|
title | Find Greenhouse |
---|
linenumbers | true |
---|
|
<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> |