Background
In order to model a real-world IoT application or a solution it is imperative to combine things with data. Let's take an example of real-world use case.
Real-world use-case
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)
Zone Grid Content:
- 1 ambient light sensor (total 270)
- 1 light (total 270)
- 1 irrigation nozzle (total 270)
External Conditions:
- 1 ambient light sensor
- 1 temperature sensor
- 1 humidity sensor
Tutorial Requirement
For the purpose of this tutorial, let's create a Greenhouse, Lane, Zone, Grid and combine Temperature Sensor from tutorial 1 to a particular zone.
DataModel to store non-thing related data
TQLEngine provides DataModel to store "pure" data, DataModels do not combine facets. They are simply models to store application data that is essential to create a meaningful real-world IoT application.
Custom Types
We create two custom types to store GeoLocation and BoundingBox.
GeoLocation - To Store GeoLocation
GeoLocation Type<Def Name="GeoLocation"> <Number Name="latitude"/> <Number Name="longitude"/> </Def>
BoundingBox - To Store Geo Boundary of Zone, etc
BoundingBox Type<Def Name="BoundingBox"> <GeoLocation Name="Vertex1"/> <GeoLocation Name="Vertex2"/> <GeoLocation Name="Vertex3"/> <GeoLocation Name="Vertex4"/> </Def>
EnvInfo - Capture all the important environment related information like temperature, humidity, light, pressuue
EnvInfo Type<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>
List of DataModels
Greenhouse : Overall DataModel holder for Greenhouse. GHLanes is defined as Reference to include all the Lanes from Lane model.
Greenhouse DataModel<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 --> <Reference Name="GHLanes"/> <Reference Name="GHExternalEnv"/> <Reference Name="GHInternalEnv"/> </DataModel>
GHExternalEnv & GHInternalEnv
GHExternalEnv and GHInternalEnv Data Model<DataModel Name="ExternalEnv"> <Sid Name="ExternalEnvID"/> <Number Name="ExtTemperature"/> <Number Name="ExtHumidity"/> <Number Name="ExtLight"/> <Reference Name="GreenhouseID" Type="Greenhouse" Cardinality="1"/> </DataModel> <DataModel Name="InternalEnv"> <Sid Name="InternalEnvID"/> <Number Name="IntTemperature"/> <Number Name="IntHumidity"/> <Number Name="IntLight"/> <Reference Name="GreenhouseID" Type="Greenhouse" Cardinality="1"/> </DataModel>
Grid - DataModel for storing Grid information.
Grid DataModel<DataModel Name="Grid"> <Sid Name="GridID"/> <String Name="GridName"/> <BoundingBox Name="GridLocation"/> <Integer Name="GridNSPosition"/> <Integer Name="GridWEPosition"/> <Double Name="GridLength"/> <Double Name="GridWidth"/> <Double Name="SoilMoisture"/> <Double Name="AmbientLight"/> <Reference Name="ZoneID" Type="Zone" Cardinality="1"/> </DataModel>
Zone - DataModel for storing Zone information. Does a Smart Reference to Grid at instantiation time by simply defining ZoneGrids as Reference type. The Find query can be add at the instantiation time.
Zone DataModel<DataModel Name="Zone"> <Sid Name="ZoneID"/> <String Name="ZoneName"/> <BoundingBox Name="ZoneLocation"/> <GeoLocation Name="Location"/> <Double Name="ZoneWidth"/> <Double Name="ZoneLength"/> <Integer Name="GridOnLength"/> <Integer Name="GridOnWidth"/> <Integer Name="GridCount"/> <Double Name="GridLength"/> <Double Name="GridWidth"/> <String Name="EndZone"/> <Double Name="ZoneTemperature"/> <Double Name="ZoneHumidity"/> <Double Name="ZoneAvgSM"/> <Double Name="ZoneAvgLight"/> <String Name="CropType"/> <Date Name="SeedingDate" format="$SimpleDateFormat(dd-MM-yyyy)"/> <Date Name="LastFertilisationDate" format="$SimpleDateFormat(dd-MM-yyyy)"/> <String Name="HarvestingSeason"/> <Integer Name="NoOfPlants"/> <Double Name="VPD"/> <String Name="Live"/> <!-- Yes/No --> <Reference Name="LaneID" Type="Lane" Cardinality="1"/> <Reference Name="ZoneGrids"/> </DataModel>
Lane - DataModel to hold Lane information. Defines Smart Reference to Zones.
Lane DataModel<DataModel Name="Lane"> <Sid Name="LaneID"/> <String Name="LaneName"/> <Double Name="LaneLength"/> <Double Name="LaneWidth"/> <Integer Name="ZoneInLane"/> <String Name="BorderLane"/> <BoundingBox Name="LaneLocation"/> <Reference Name="GreenhouseID" Type="Greenhouse" Cardinality="1"/> <Reference Name="LaneZones"/ </DataModel>
Data + Things
We have to assign 1 temperature sensor per zone. We do this by:
- Use the content of MultipleSensors tutorial project and bring in all the model content into this tutorial.
- Use Simple Reference in TempSensorModel to a particular Zone using ZoneID
Other sensors and actuators can be referenced in a similar fashion.
<ThingFacet Name="TempFacetSerial"> <String Name="TempValue" update="auto" KnownBy="SerialReadAction"/> <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"/> <Action Name="SerialReadAction"> <Workflow Limit="1" Live="1" Timeout="-1"> <Task name="Main" while="true"> <Event name="Argument" as="ActionArgument" /> <Invoke name="InvokeSerialRead" waitFor="Argument" get="perif://"> <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> <format>"[%:Event.Argument.format.Value:%]"</format> <Payload>"[%:Event.Argument.payload.Value:%]"</Payload> <Peripheral>"[%:Event.Argument.peripheral.Value:%]"</Peripheral> </Value> </Message> </Invoke> <Output name="Result" as="ActionResult"> <Value> <tempValue>[%:Invoke.InvokeSerialRead.Message.Value/number(substring-before(substring-after ("[%:Invoke.InvokeSerialRead.Message.Value.received:%]",'#TCB:'), '#')):%] </tempValue> </Value> </Output> </Task> </Workflow> </Action> </ThingFacet> <ThingModel Name="TempSensor" combines="TempFacetSerial"> <Sid Name="sensorId"/> <Reference Name="ZoneID" Type="Zone" Cardinality="1"/> </ThingModel>
Queries
Let's start writing some queries to create and read the data.
Create Grids
<Query> <Find> <Zone> <ZoneName eq="Zone1"/> </Zone> </Find> <Create> <Grid> <GridName> Grid1 </GridName> <GridNSPosition> 1 </GridNSPosition> <GridWEPosition> 1 </GridWEPosition> <GridLength> 3.33 </GridLength> <GridWidth> 3.33 </GridWidth> <SoilMoisture> 0 </SoilMoisture> <AmbientLight> 0 </AmbientLight> <ZoneGrid>[:$Response.Message.Value.Find.Result.Zone.ZoneID:]</ZoneGrid> </Grid> </Create> </Query>
Create Zone
<Query> <DeleteAll> <Zone> <ZoneID ne=""/> </Zone> </DeleteAll> <Create> <Zone> <ZoneWidth> 10 </ZoneWidth> <ZoneLength> 10 </ZoneLength> <GridOnLength> 3 </GridOnLength> <GridOnWidth> 3 </GridOnWidth> <GridCount> 9 </GridCount> <GridLength> 3.33 </GridLength> <GridWidth> 3.33 </GridWidth> <EndZone> No </EndZone> <ZoneTemperature> 0 </ZoneTemperature> <ZoneHumidity> 0 </ZoneHumidity> <CropType>Tomato</CropType> </Zone> </Create> </Query>
Create Lane
<Query> <Create> <Lane> <LaneWidth> 110 </LaneWidth> <ZoneInLane> 10 </ZoneInLane> <BorderLane> Yes </BorderLane> <LaneZones> </LaneZones> </Lane> </Create> </Query>
Create Greenhouse
<Query> <Create> <Greenhouse> <LaneCount> 3 </LaneCount> <LaneWidth> 110 </LaneWidth> <ZoneLength> 10 </ZoneLength> <ZoneInLane> 10 </ZoneInLane> <ZoneCount> 90 </ZoneCount> <ExtEnvInfo> <Temperature> 0 </Temperature> <Humidity> 0 </Humidity> <Light> 0 </Light> </ExtEnvInfo> <GHLanes></GHLanes> </Greenhouse> </Create> </Query>
Create Temperature Sensor
TempSensorInZone value is the ZoneID value of the Zone. This is dynamically assigned by querying the Zone Model.
<Query> <DeleteAll> <TempSensorModel> <TempSensorId ne=""/> </TempSensorModel> </DeleteAll> <Find> <Zone> <ZoneName eq="Zone1"/> </Zone> </Find> <Create> <TempSensorModel> <TempValueInC> 0 </TempValueInC> <TempValueInF> 0 </TempValueInF> <TempSensorInZone> [:$Response.Message.Value.Find.Result.Zone.ZoneID:] </TempSensorInZone> </TempSensorModel> </Create> </Query>
Find Temperature above x degree C
<Query> <Find nested="*" format="Version"> <TempSensorModel> <TempSensorId ne="" /> </TempSensorModel> </Find> </Query>