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-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 address above device management requirements.
Bulk Initialization Design Pattern
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 pattern can be broken down into two steps:
Step # | Name | Description | Output | Implementation Flow |
---|---|---|---|---|
1 | Gather Metadata | Input to gathering of metadata step can be:
| The output of gathering metadata step could be:
|
|
2 | Initialize | Initialize step includes:
| The output of initialize steps are:
|
|
Bulk Initialization Steps:
Greenhouse UseCase
Please refer to 8. Simulating sensors, actuators, and devices for the description of Greenhouse usecase. The data initalization requirement for this case is shown below
Greenhouse Configurator Application Flow
Greenhouse Configuration Requirements
- Ability to add greenhouses on-the-fly
- Each greenhouse must be able to dynamically generate number of lanes, zones per lane
- Add Crop type per lane
Bulk Initialization Implementation
Import baseline Greenhouse Data and Thing Models
- Create a new Project in TQLStudio by importing content from Baseline models.
<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
<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
<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
<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
<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
<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
<Query> <Create> <Heater> <OnOffState> on </OnOffState> <HeatingLevel> 2 </HeatingLevel> </Heater> </Create> </Query>
Start Simulation
<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
<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>