13. Smart Greenhouse - a real-world IoT application at scale
Background
In order to model a real-world IoT application or a solution it is imperative to distribute load. In Greenhouse example we have a total of 270+ Sensors and Actuators.
It is impractical to run entire sensors and actuators on one single node (either OnPrem or Cloud)
TQLEngine Deployment Matrix
TQLEngine | Lane# | Zone# | Grid# | DNS Name | Lanes Covered | Zones Covered | Grids Covered | Simulated |
---|---|---|---|---|---|---|---|---|
TQLEngine-1 | 1 | 1-5 | 1-45 | ghsim.atomiton.com | 0.5 | 5 | 45 | Yes |
TQLEngine-2 | 1 | 6-10 | 46-90 | ghsim2.atomiton.com | 0.5 | 5 | 45 | Yes |
TQLEngine-3 | 2 | 1-5 | 1-45 | ghsim3.atomiton.com | 0.5 | 5 | 45 | Yes |
TQLEngine-4 | 2 | 6-10 | 46-90 | ghsim4.atomiton.com | 0.5 | 5 | 45 | Yes |
TQLEngine-5 | 3 | 1-5 | 1-45 | ghsim5.atomiton.com | 0.5 | 5 | 45 | Yes |
TQLEngine-6 | 3 | 6-10 | 1-44 | ghsim6.atomiton.com | 0.4 | 4.9 | 44 | Yes |
TQLEngine-7 | 3 | 5 | 90 | 10.0.1.52 | 0.1 | 0.1 | 1 | No |
TQLEngine-C | 1-3 | 1-5 | 1-270 | greenhouse.atomiton.com | 3 (Receive only) | 30 (Receive only) | 270 (Receive only) | Cloud |
Complete Greenhouse Model
Complete Greenhouse Model can be found here
Key Modeling Takeaways
Pipeline Macros
Pipeline Macros
<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="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 Name="SubscribeToTQL"> <Argument> <TopicName>TQL.*</TopicName> <TopicId>GenericTopicID</TopicId> <ActionName /> </Argument> <Result> <DoRequest target="[:RuntimeParams.TopicFacetIDName:]"> <Process> <Message type="xml"> <Value> <Subscribe sid="[:$Macro.Argument.TopicId:]" topic="[:$Macro.Argument.TopicName:]"> <Action>[:$Macro.Argument.ActionName:]</Action> </Subscribe> </Value> </Message> </Process> </DoRequest> </Result> </Macro>
Start / Stop Simulation
Start and Stop Simulation
<Macro Name="StartSimulation"> <Argument> <GreenhouseID/> </Argument> <Result> <ExecuteQuery> <QueryString> <Query> <Find format="Version"> <Greenhouse> <GreenhouseID>[:$Macro.Argument.GreenhouseID:]</GreenhouseID> </Greenhouse> </Find> <SetResponseData key="Message.Value.Find.Result.Greenhouse.Running.value"> <Value>True</Value> </SetResponseData> <Save> <from>Result</from> <Include>$Response.Message.Value.Find</Include> </Save> </Query> </QueryString> </ExecuteQuery> <ExecuteQuery> <QueryString> <Query> <Find format="Version"> <ExtTempSensor> <sensorId ne=""/> </ExtTempSensor> </Find> <SetResponseData key="Message.Value.Find.Result.ExtTempSensor.simulated.value"> <Value>True</Value> </SetResponseData> <Save> <from>Result</from> <Include>$Response.Message.Value.Find</Include> </Save> </Query> </QueryString> </ExecuteQuery> <ExecuteQuery> <QueryString> <Query> <Find format="Version"> <GHTempSensor> <GreenhouseID>[:$Macro.Argument.GreenhouseID:]</GreenhouseID> </GHTempSensor> </Find> <SetResponseData key="Message.Value.Find.Result.GHTempSensor.simulated.value"> <Value>True</Value> </SetResponseData> <Save> <from>Result</from> <Include>$Response.Message.Value.Find</Include> </Save> </Query> </QueryString> </ExecuteQuery> <ExecuteQuery> <QueryString> <Query> <Find format="Version"> <ExtHumiditySensor> <sensorId ne=""/> </ExtHumiditySensor> </Find> <SetResponseData key="Message.Value.Find.Result.ExtHumiditySensor.simulated.value"> <Value>True</Value> </SetResponseData> <Save> <from>Result</from> <Include>$Response.Message.Value.Find</Include> </Save> </Query> </QueryString> </ExecuteQuery> <ExecuteQuery> <QueryString> <Query> <Find format="Version"> <GHHumiditySensor> <GreenhouseID>[:$Macro.Argument.GreenhouseID:]</GreenhouseID> </GHHumiditySensor> </Find> <SetResponseData key="Message.Value.Find.Result.GHHumiditySensor.simulated.value"> <Value>True</Value> </SetResponseData> <Save> <from>Result</from> <Include>$Response.Message.Value.Find</Include> </Save> </Query> </QueryString> </ExecuteQuery> <ExecuteQuery> <QueryString> <Query> <Find format="Version"> <ExtLightSensor> <sensorId ne=""/> </ExtLightSensor> </Find> <SetResponseData key="Message.Value.Find.Result.ExtLightSensor.simulated.value"> <Value>True</Value> </SetResponseData> <Save> <from>Result</from> <Include>$Response.Message.Value.Find</Include> </Save> </Query> </QueryString> </ExecuteQuery> <SetSunnyDaysFlag> <GreenhouseID>[:$Macro.Argument.GreenhouseID:]</GreenhouseID> </SetSunnyDaysFlag> <ScheduleJob> <Name> SetSunnyDaysFlag </Name> <ScheduleInterval> 1Day </ScheduleInterval> <ActionCode> <SetSunnyDaysFlag> <GreenhouseID>[:$Macro.Argument.GreenhouseID:]</GreenhouseID> </SetSunnyDaysFlag> </ActionCode> </ScheduleJob> <SetVentAndFansOnOffState> <GreenhouseID>[:$Macro.Argument.GreenhouseID:]</GreenhouseID> </SetVentAndFansOnOffState> <ScheduleJob> <Name> SetVentAndFansOnOffState </Name> <ScheduleInterval> 5min </ScheduleInterval> <ActionCode> <SetVentAndFansOnOffState> <GreenhouseID>[:$Macro.Argument.GreenhouseID:]</GreenhouseID> </SetVentAndFansOnOffState> </ActionCode> </ScheduleJob> <UpdateGridAmbientLight> <GreenhouseID>[:$Macro.Argument.GreenhouseID:]</GreenhouseID> </UpdateGridAmbientLight> <ScheduleJob> <Name> UpdateGridAmbientLightValue </Name> <ScheduleInterval> 5min </ScheduleInterval> <ActionCode> <UpdateGridAmbientLight> <GreenhouseID>[:$Macro.Argument.GreenhouseID:]</GreenhouseID> </UpdateGridAmbientLight> </ActionCode> </ScheduleJob> <UpdateZoneAvgLight> <GreenhouseID>[:$Macro.Argument.GreenhouseID:]</GreenhouseID> </UpdateZoneAvgLight> <ScheduleJob> <Name> UpdateZoneAvgLightValue </Name> <ScheduleInterval> 5min </ScheduleInterval> <ActionCode> <UpdateZoneAvgLight> <GreenhouseID>[:$Macro.Argument.GreenhouseID:]</GreenhouseID> </UpdateZoneAvgLight> </ActionCode> </ScheduleJob> <UpdateGridSoilMoisture> <GreenhouseID>[:$Macro.Argument.GreenhouseID:]</GreenhouseID> </UpdateGridSoilMoisture> <ScheduleJob> <Name> UpdateGridSoilMoistureValue </Name> <ScheduleInterval> 5min </ScheduleInterval> <ActionCode> <UpdateGridSoilMoisture> <GreenhouseID>[:$Macro.Argument.GreenhouseID:]</GreenhouseID> </UpdateGridSoilMoisture> </ActionCode> </ScheduleJob> <UpdateZoneAvgSoilMoisture> <GreenhouseID>[:$Macro.Argument.GreenhouseID:]</GreenhouseID> </UpdateZoneAvgSoilMoisture> <ScheduleJob> <Name> UpdateZoneAvgSoilMoistureValue </Name> <ScheduleInterval> 5min </ScheduleInterval> <ActionCode> <UpdateZoneAvgSoilMoisture> <GreenhouseID>[:$Macro.Argument.GreenhouseID:]</GreenhouseID> </UpdateZoneAvgSoilMoisture> </ActionCode> </ScheduleJob> <CalculateZonesVPD> <GreenhouseID>[:$Macro.Argument.GreenhouseID:]</GreenhouseID> </CalculateZonesVPD> <ScheduleJob> <Name> CalculateZonesVPDValue </Name> <ScheduleInterval> 5min </ScheduleInterval> <ActionCode> <CalculateZonesVPD> <GreenhouseID>[:$Macro.Argument.GreenhouseID:]</GreenhouseID> </CalculateZonesVPD> </ActionCode> </ScheduleJob> </Result> </Macro>
Calculate VPD
Calculate VPD
<Macro Name="CalculateZonesVPD"> <Argument> <GreenhouseID></GreenhouseID> </Argument> <Result> <GetLiveZone> <GreenhouseID>[:$Macro.Argument.GreenhouseID:]</GreenhouseID> </GetLiveZone> <if condition="$Response.Message/Value/Zone/count(ZoneID) gt 0"> <then> <ExecuteQuery> <QueryString> <Query> <Find> <Zone> <ZoneID>[:$Response.Message.Value.Zone.ZoneID:]</ZoneID> </Zone> </Find> </Query> </QueryString> </ExecuteQuery> <if condition="$Response.Message.Value/Find/Status eq 'Success'"> <then> <for each="findRes" in="Find.Result" using="$ProcessData"> <SetLocalData key="ZoneID" value="[:$ProcessData.findRes.Zone.ZoneID:]"/> <SetLocalData key="ZoneTemp" value="[:$ProcessData.findRes.Zone.ZoneTemperature:]"/> <SetLocalData key="ZoneHumidity" value="[:$ProcessData.findRes.Zone.ZoneHumidity:]"/> <JavaScript> var temperature = [:$LocalData.ZoneTemp:]; var humidity = [:$LocalData.ZoneHumidity:]; if(!isNaN(temperature) && !isNaN(humidity)){ var A= -1.044*Math.pow(10,4); var B= -1.129*10; var C= -2.702*Math.pow(10,-2); var D= 1.289*Math.pow(10,-5); var E= -2.489*Math.pow(10,-9); var F= 6.456; var Vsat = Math.exp((A/temperature) + B + (C*temperature) + (D*Math.pow(temperature, 2)) + (E*Math.pow(temperature,3)) + (F*Math.log(temperature))) * 6894.7; var Vair = Vsat * (humidity/100); var Vpd = Vsat - Vair; sffContext.execute("SetContextData","key","VPD","value",Vpd); }else{ sffContext.execute("SetContextData","key","VPD","value",0); } <!-- Saturation Vapor Pressure --> <!-- var es = 0.6108 * Math.exp(17.27 * [:$LocalData.ZoneTemp:] / ([:$LocalData.ZoneTemp:] + 237.3)); --> <!-- Actual Vapor Pressure --> <!-- var ea = ([:$LocalData.ZoneHumidity:] / 100) * es ; --> <!-- Vapor Pressure Deficit --> <!-- var vpd = ea - es; --> <!-- sffContext.execute("SetContextData","key","VPD","value",vpd); --> </JavaScript> <Log message="VPD for Zones =============== [:$ContextData.VPD:]"/> <ExecuteQuery> <QueryString> <Query> <Find format="Version"> <Zone> <ZoneID eq='[:$LocalData.ZoneID:]'/> </Zone> </Find> <SetResponseData> <Key>Message.Value.Find.Result.Zone.VPD.Value</Key> <Value>[:$ContextData.VPD:]</Value> </SetResponseData> <Save> <from>Result</from> <Include>$Response.Message.Value.Find</Include> </Save> </Query> </QueryString> </ExecuteQuery> </for> </then> </if> </then> </if> </Result> </Macro>
Queries Used by User Interface
Query Name | Query | |
---|---|---|
Pipeline Macros <Query> <Find> <Greenhouse> <GreenhouseID ne=""/> </Greenhouse> </Find> </Query> | ||
| ||
Pipeline Macros <Query> <GetIrrigationMotorList> <GreenhouseID>{0}</GreenhouseID> </GetIrrigationMotorList> </Query> | ||
Pipeline Macros <Query> <GetHeaterList> <GreenhouseID>{0}</GreenhouseID> </GetHeaterList> </Query> | ||
Pipeline Macros Query> <Find> <Greenhouse> <GreenhouseID ne=""/> </Greenhouse> </Find> </Query> | ||
| ||
| ||
Zone details <Find><Heater><ZoneID>{0}</ZoneID></Heater></Find> | ||
Pipeline Macros <Query><Find><Grid><ZoneID eq="{0}" /></Grid></Find></Query> | ||
Pipeline Macros <Query> <Find> <LightModel> <GridID>{0}</GridID> </LightModel> </Find> </Query> | ||
Pipeline Macros <Query> <Find format="All"> <IrrigationMotor> <ZoneID>{0}</ZoneID> </IrrigationMotor> </Find> </Query> | ||
Pipeline Macros <Query> <UpdateIrrigationMotorState> <ZoneID>{0}</ZoneID> <OnOffState>{1}</OnOffState> </UpdateIrrigationMotorState> </Query> | ||
Pipeline Macros <Query> <UpdateHeaterState> <ZoneID>{0}</ZoneID> <OnOffState>{1}</OnOffState> <HeatingLevel>{2}</HeatingLevel> </UpdateHeaterState> </Query> | ||
Pipeline Macros <Query> <Find> <TempSensor> <ZoneID eq="{0}"/> </TempSensor> <HumiditySensor> <ZoneID eq="{0}"/> </HumiditySensor> <Camera> <ZoneID eq="{0}"/> </Camera> <IrrigationMotor> <ZoneID eq="{0}"/> </IrrigationMotor> <Heater> <ZoneID eq="{0}"/> </Heater> </Find> </Query> | ||
Pipeline Macros <Query> <Find> <SoilSensorModel> <GridID eq="{0}"/> </SoilSensorModel> <LightModel> <GridID eq="{0}"/> </LightModel> <AmbientLightSensorModel> <GridID eq="{0}"/> </AmbientLightSensorModel> </Find> </Query> | ||
Pipeline Macros <Query> <Find format="All"> <GHTempSensorCurrentTemp> <GreenhouseID>{0}</GreenhouseID> </GHTempSensorCurrentTemp> </Find> </Query> | ||
Pipeline Macros <Query> <Find format="All"> <GHHumiditySensorCurrentHumidity> <GreenhouseID>{0}</GreenhouseID> </GHHumiditySensorCurrentHumidity> </Find> </Query> | ||
Pipeline Macros <Query> <GetHeaterList> <GreenhousID>{0}</GreenhouseID> </GetHeaterList> </Query> <Query> <GetIrrigationMotorList> <GreenhouseID>{0}</GreenhouseID> </GetIrrigationMotorList> </Query> | ||
|