2. Multiple Sensors - Temperature, Humidity, Ambiance (Light)

Key Concepts

 Related docs
Model-to-Model Subscription 
Type 
Composit attributes 
FacetScript 
 Related docs
ContextData 
Macro 
ExecuteQuery Macro 
Condition 

 

Multiple Sensors Hardware  Setup

We can attach multiple sensors (Humidity, Ambiance, Temperature) to Arduino Uno using a Sensor shield.

Sketch to read sensor data
#include <math.h>
// Temperature Analog Sensor
int sensorPinT = A0; // select the input pin for the potentiometer
int sensorPinL = A4; // select the input pin for the potentiometer
 
double Thermistor(int RawADC) {
  double Temp;
  Temp = log(10000.0*((1024.0/RawADC-1))); 
  Temp = 1 / (0.001129148 + (0.000234125 + (0.0000000876741 * Temp * Temp ))* Temp );
  Temp = Temp - 273.15;            // Convert Kelvin to Celcius
   //Temp = (Temp * 9.0)/ 5.0 + 32.0; // Convert Celcius to Fahrenheit
   return Temp;
}

String getTemperature() {
 String tempStr = "TEMPC:";
 int readVal=analogRead(sensorPinT);
 double tempC =  Thermistor(readVal);
 // now convert to Fahrenheit
 double tempF = (tempC * 9.0 / 5.0) + 32.0;
 tempStr = "TEMPC:" + String(tempC) + "#TEMPF:" + String(tempF);
 return tempStr; 
}

//KY015 DHT11 Temperature and humidity sensor 
int DHpin = 8;
byte dat [5];
byte read_data () {
  byte data;
  for (int i = 0; i < 8; i ++) {
    if (digitalRead (DHpin) == LOW) {
      while (digitalRead (DHpin) == LOW); // wait for 50us
      delayMicroseconds (30); // determine the duration of the high level to determine the data is '0 'or '1'
      if (digitalRead (DHpin) == HIGH)
        data |= (1 << (7-i)); // high front and low in the post
      while (digitalRead (DHpin) == HIGH); // data '1 ', wait for the next one receiver
     }
  }
return data;
}
 
void start_test () {
  digitalWrite (DHpin, LOW); // bus down, send start signal
  delay (30); // delay greater than 18ms, so DHT11 start signal can be detected
 
  digitalWrite (DHpin, HIGH);
  delayMicroseconds (40); // Wait for DHT11 response
 
  pinMode (DHpin, INPUT);
  while (digitalRead (DHpin) == HIGH);
  delayMicroseconds (80); // DHT11 response, pulled the bus 80us
  if (digitalRead (DHpin) == LOW);
  delayMicroseconds (80); // DHT11 80us after the bus pulled to start sending data
 
  for (int i = 0; i < 4; i ++) // receive temperature and humidity data, the parity bit is not considered
    dat[i] = read_data ();
 
  pinMode (DHpin, OUTPUT);
  digitalWrite (DHpin, HIGH); // send data once after releasing the bus, wait for the host to open the next Start signal
}

 
String getHumidity () {
  start_test ();
  String hum = "";
  hum = "HUMPCT:" + String(dat [0], DEC) + "." + String(dat [1], DEC);
  return hum;
}

int ledPin = 13; // select the pin for the LED

String getLight() {
  String amb = "";
  int sensorValue = 0; // variable to store the value coming from the sensor
  sensorValue = analogRead(sensorPinL);
  digitalWrite(ledPin, HIGH);
  delay(sensorValue);
  digitalWrite(ledPin, LOW);
  delay(sensorValue);
  amb = "AMB:" + String(sensorValue, DEC);
  return amb;
}

 
void setup () {
  Serial.begin (9600);
  pinMode (DHpin, OUTPUT);
  pinMode (ledPin, OUTPUT);
}

void loop() {
  //TEMPC:25#TEMPF:110#HUMPCT:40#AMB:20
  Serial.println(getTemperature() + "#" + getHumidity() + "#" + getLight());
  delay (1000);
}

 

SensorData Output Format of 

Sensor data Output Format
TEMPC:25#TEMPF:110#HUMPCT:40#AMB:20
Sensor Data KeyDescription
TEMPC

Temperature in Centigrade

TEMPFTemperature in Fahrenheit
HUMPCTHumidity Percentage Value
AMBAmbiance Light Value.

250 is used as a threshold. Below 250 – low and above – high.

Modeling Design Overview

Note that there is only ONE physical connection from the Controller (Laptop / Raspberry Pi or other  Gateway Running TQLEngine) to the MCU. This connection is via USB serial.  In order subscribe and query each of the individual sensors we follow the following steps:

  1. Create MCUSensor ThingModel to manage receiving data from MCU (and all the sensors).
  2. Create a Subscriber Action tied to a attribute of MCUSensor Model to handle updates from SensorData.
  3. Parse the SensorData and store it into the Context using Key as (TEMPC, TEMPF, etc)
  4. Create individual Sensor Models to update their respective readings from the Context.

Write a ThingFacet 

To abstract the interactions with the MCU attached to the controller we write a MCUSensorFacet ThingFacet.

There are two kinds of attributes that are part of a ThingFacet.

  1. Attributes required to make a protocol specific invocation 
  2. Attributes to store the information received from the MCU as part of the continuous stream.

Protocol Specific Parameters are: InterfacePort, Interface, Baudrate etc.

Let's create Type (Using Def) PeripheralParams to store the list of the protocol specific parameters

PeripheralParams
<Def Name="PeripheralParams">
      <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"/>
</Def>

Lets now create a MCUSensorFacet ThingFacet using the PeripheralParams type. The sensor data will be stored in SensorData attribute of type String.

ExternalEnvFacet ThingFacet
<ThingFacet Name="MCUSensorFacet">
      <String Name="SensorData"/>
      <PeripheralParams Name="PerifParams"/>
</ThingFacet>

Write a Action with Workflow

The next step in ThingFacet is writing a Action which contains a workflow responsible for making external protocol specific call and store the output in the appropriate ThingFacet attribute.  An important decision while writing Action is correct protocol selection. See the /wiki/spaces/TQLDocs/pages/1179871 in the Working with Things section. For this Multiple Sensors we need PERIF request, which is part of the TQLEngine.

1. Write an Action name as SensorDataReadAction

SensorDataReadAction
<ThingFacet Name="MCUSensorFacet">
	...
	<Action Name="SensorDataReadAction">
		...
	</Action>
</ThingFacet>

 

2. Create a Workflow within the Action, with one single continuous running Task and waiting for the Event with its ActionArgument.

Workflow
<Action Name="SensorDataReadAction">
	<Workflow Limit="1" Live="1" Timeout="-1">
    	<Task name="Main" while="true">
        	<Event name="Argument" as="ActionArgument"/>
			...
    	</Task>
	</Workflow>
</Action>

Here we used three modifiers for this workflow. Limit = "1" means there can be at most one instance of this workflow waiting. Live = "1" means there can be at most one instance of this workflow running. Timeout ="-1" means this workflow will never be timed out. We used a modifier while = "true" with the Task to make the workflow running in a continuous loop, because it needs to run repeatedly, not just once. For more details, refer to workflow modifiers and the lifecycle of a workflow.

The task will be activated by the event handler Event called ActionArgument. "ActionArgument" is the event generated whenever the attribute(s) associated with this Action is modified (See Associate Action with a ThingFacet Attribute). ActionArgument carries all the current values of the ThingFacet attributes, which can be used in the task if needed.

 

3. Invoke PERIF call: Method of PERIF is Get. We use Invoke modifier Get for this purpose. The parameters required for PERIF Get are passed as Message / Value Container.

Invoke PERIF Handler
<Invoke name="InvokeSerialRead" waitFor="Argument" get="perif://">
  <Message>
    <Value>
      <InterfacePort>
        "[%:Event.Argument.PerifParams.InterfacePort.Value:%]"
      </InterfacePort>
      <Baudrate>
        "[%:Event.Argument.PerifParams.Baudrate.Value:%]"
      </Baudrate>
      <Interface>
        "[%:Event.Argument.PerifParams.Interface.Value:%]"
      </Interface>
      <UniqueId>
        "[%:Event.Argument.PerifParams.UniqueId.Value:%]"
      </UniqueId>
      <Operation>
        "[%:Event.Argument.PerifParams.Operation.Value:%]"
      </Operation>
      <Peripheral>
        "[%:Event.Argument.PerifParams.Peripheral.Value:%]"
      </Peripheral>
      <Payload>
        "[%:Event.Argument.PerifParams.Payload.Value:%]"
      </Payload>
      <Format>
        "[%:Event.Argument.PerifParams.Format.Value:%]"
      </Format>
    </Value>
  </Message>
</Invoke>

Note that the Event Parameter values are dereferenced using TP Notation. The notation [%: is part of the /wiki/spaces/TEACH/pages/21170773. More details on /wiki/spaces/TEACH/pages/21170773 can be found in the Developer's Guide.

TagResolution
[%:Event.Argument.PerifParams.InterfacePort.Value:%]
InterfacePort value passed when creating/updating model instance.

 

4. Store the output value in SensorData Attribute.

Store the output value
<Output Name="Result" As="ActionResult">
  <Value>
    <SensorData>
         [%:Invoke.InvokeSerialRead.Message.Value/normalize-space(received):%]
    </SensorData>
  </Value>
</Output>

 

Associate Action with a ThingFacet Attribute

We will now have to associate a ThingFacet Attribute (State) with the Action using the KnownBy modifier. When State is "KnownBy" PhilipLightAction, any State value changes will activate the PhilipLightAction.

Attribute State tied to Action
<ThingFacet Name="MCUSensorFacet">
	...
   <String Name="SensorData" update="auto" KnownBy="SensorDataReadAction"/>
</ThingFacet>

The attribute modifier update= "auto" makes sure that once the action associated with this attribute is triggered, its workflow continues to run and wait for subsequent sensor events (not just the first event). This modifier is only used with actionable attributes. For more details, refer to Automatic Action trigger.

Combining ThingFacet with a ThingModel

Finally, in order to use ThingFacet we have to combine it with a ThingModel. We define ThingModel to contain only a unique system identifier.

ThingModel ExternalEvn
<ThingModel Name="MCUSensorModel" Combines="MCUSensorFacet">
      <Sid Name="SensorId"/>
</ThingModel>

 

MCUSensorFacet can only be instantiated (and write TQL Queries) when it is combined with MCUSensorModel Thing Model. 
MCUSensorModel will inherit all the attributes from MCUSensorFacet, in addition to its own attributes. 
The ThingFacet MCUSensorFacet hence serves as a reusable component.

More details on the use of "combines" can be found here. Information on Sid can be found here.

Temperature ThingModel

Let's create individual sensor models with respective ThingFacets. The difference between this Facet and regular MCUSensorFacet is that not physical connection is opened to the sensor.

We simply read the values from the Context. Using ContextData.

ContextData - Will have keys TEMPC an TEMPF. See below on who creates this ContextData keys.

TempSensorFacet and TempSensorModel
<ThingFacet Name="TempSensorFacet">
  <Double Name="TempValueInC" KnownBy="TempValueAction"/>
  <Double Name="TempValueInF" KnownBy="TempValueAction"/>
  <Action Name="TempValueAction">
    <Workflow Limit="1" Live="1" Timeout="-1">
      <Task name="Main" while="true">
        <Event name="Argument" as="ActionArgument"/>
        <Invoke name="ReadValueFromContext" waitFor="Argument">
          <FacetScript>
            <Log Message="Reading TEMPC/TEMPF value from context [:$ContextData.TEMPC:] / [:$ContextData.TEMPF:]"/>
            <If Condition="$ContextData/not(boolean(TEMPC))">
              <SetContextData>
                <Key>TEMPC</Key>
                <Value>0</Value>
              </SetContextData>
              <SetContextData>
                <Key>TEMPF</Key>
                <Value>0</Value>
              </SetContextData>
            </If>
          </FacetScript>
        </Invoke>
        <Output Name="Result" As="ActionResult">
          <Value>
            <TempValueInC>
              [:$ContextData.TEMPC:]
            </TempValueInC>
            <TempValueInF>
              [:$ContextData.TEMPF:]
            </TempValueInF>
          </Value>
        </Output>
      </Task>
    </Workflow>
  </Action>
</ThingFacet>

<ThingModel Name="TempSensorModel" combines="TempSensorFacet">
  <Sid Name="TempSensorId"/>
</ThingModel>

Humidity ThingModel

Let's create individual sensor models with respective ThingFacets. The difference between this Facet and regular MCUSensorFacet is that not physical connection is opened to the sensor.

We simply read the values from the Context. Using ContextData.

ContextData - Will have key HUMPCT. See below on who creates this ContextData key.

HumiditySensorFacet and HumiditySensorModel
<ThingFacet Name="HumiditySensorFacet">
  <Double Name="HumidityValue" KnownBy="HumidityValueAction"/>
  <Action Name="HumidityValueAction">
    <Workflow Limit="1" Live="1" Timeout="-1">
      <Task name="Main" while="true">
        <Event name="Argument" as="ActionArgument"/>
        <Invoke name="ReadValueFromContext" waitFor="Argument">
          <FacetScript>
            <Log Message="Reading HUMPCT value from context [:$ContextData.HUMPCT:]"/>
            <If Condition="$ContextData/not(boolean(HUMPCT))">
              <SetContextData>
                <Key>HUMPCT</Key>
                <Value>0</Value>
              </SetContextData>
            </If>
          </FacetScript>
        </Invoke>
        <Output Name="Result" As="ActionResult">
          <Value>
            <HumidityValue>
              [:$ContextData.HUMPCT:]
            </HumidityValue>
          </Value>
        </Output>
      </Task>
    </Workflow>
  </Action>
</ThingFacet>
<ThingModel Name="HumiditySensorModel" combines="HumiditySensorFacet">
  <Sid Name="HumSensorId"/>
</ThingModel>

Ambiance ThingModel

Let's create individual sensor models with respective ThingFacets. The difference between this Facet and regular MCUSensorFacet is that not physical connection is opened to the sensor.

We simply read the values from the Context. Using ContextData.

ContextData - Will have key AMB. See below on who creates this ContextData key.

AmbianceSensorFacet and AmbianceSensorModel
<ThingFacet Name="AmbianceSensorFacet">
  <Double Name="AmbianceValue" KnownBy="AmbianceValueAction"/>
  <Action Name="AmbianceValueAction">
    <Workflow Limit="1" Live="1" Timeout="-1">
      <Task name="Main" while="true">
        <Event name="Argument" as="ActionArgument"/>
        <Invoke name="ReadValueFromContext" waitFor="Argument">
          <FacetScript>
            <Log Message="Reading AMB value from context [:$ContextData.AMB:]"/>
            <If Condition="$ContextData/not(boolean(AMB))">
              <SetContextData>
                <Key>AMB</Key>
                <Value>0</Value>
              </SetContextData>
            </If>
          </FacetScript>
        </Invoke>
        <Output Name="Result" As="ActionResult">
          <Value>
            <AmbianceValue>
              [:$ContextData.AMB:]
            </AmbianceValue>
          </Value>
        </Output>
      </Task>
    </Workflow>
  </Action>
</ThingFacet>

<ThingModel Name="AmbianceSensorModel" combines="AmbianceSensorFacet">
  <Sid Name="AmbSensorId"/>
</ThingModel>

Update Queries to Trigger Sensor Model Actions

Trigger Sensor Actions
<Macro Name="UpdateTempValueToNull">
  <Argument/>
  <Result>
    <ExecuteQuery>
      <QueryString>
        <Query>
          <Find format="version">
            <TempSensorModel>
              <TempSensorId ne=""/>
            </TempSensorModel>
          </Find>
          <SetResponseData>
            <key>Message.Value.Find.Result.TempSensorModel.TempValueInC.Value</key>
            <value>$Null()</value>
          </SetResponseData>
          <SetResponseData>
            <key>Message.Value.Find.Result.TempSensorModel.TempValueInF.Value</key>
            <value>$Null()</value>
          </SetResponseData>
          <Update>
            <from>Result</from>
            <Include>$Response.Message.Value.Find</Include>
          </Update>
        </Query>
      </QueryString>
    </ExecuteQuery>
  </Result>
</Macro>

<Macro Name="UpdateHumidityValueToNull">
  <Argument/>
  <Result>
    <ExecuteQuery>
      <QueryString>
        <Query>
          <Find format="version">
            <HumiditySensorModel>
              <HumSensorId ne=""/>
            </HumiditySensorModel>
          </Find>
          <SetResponseData>
            <key>Message.Value.Find.Result.HumiditySensorModel.HumidityValue.Value</key>
            <value>$Null()</value>
          </SetResponseData>
          <Update>
            <from>Result</from>
            <Include>$Response.Message.Value.Find</Include>
          </Update>
        </Query>
      </QueryString>
    </ExecuteQuery>
  </Result>
</Macro>

<Macro Name="UpdateAmbianceValueToNull">
  <Argument/>
  <Result>
    <ExecuteQuery>
      <QueryString>
        <Query>
          <Find format="version">
            <AmbianceSensorModel>
              <AmbSensorId ne=""/>
            </AmbianceSensorModel>
          </Find>
          <SetResponseData>
            <key>Message.Value.Find.Result.AmbianceSensorModel.AmbianceValue.Value</key>
            <value>$Null()</value>
          </SetResponseData>
          <Update>
            <from>Result</from>
            <Include>$Response.Message.Value.Find</Include>
          </Update>
        </Query>
      </QueryString>
    </ExecuteQuery>
  </Result>
</Macro>

Subscribe to SensorData

In order to distribute the sensor data to different Sensor Models (Temp, Humidity and Ambiance) we need following building blocks:

The building blocks to Checking Weather Periodically are:

  • Ability to Subscribe to SensorData attribute change.
  • Parse the Sensor Data.

TQLEngine allows us to run subscribe to model changes from the Model itself using <OnRequest> Tag. We need to provide the Target ID to make this request. Since subscription can be called any number of times, it is always a good idea to wrap calling of a Subscription request in a generic Macro Definition. Let's call this SubscribeToTQL.

Generic Macro to Subscribe to a change in Model

Macro Definition to Subscribe to Model changes
<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>

Macro to Execute a TQL Query

TQLEngine allows us to run TQL Query from the Model itself using <OnRequest> Tag. We need to provide the Target ID to make this request. Since TQL Query can be called any number of times, it is always a good idea to wrap calling of a TQL Query request in a generic Macro Definition. Let's call this ExecuteQuery.

Execute Macro
<Macro Name="ExecuteQuery">
      <Argument>
        <QueryString>
          <Query/>
        </QueryString>
      </Argument>
      <Result>
        <OnRequest>
          <Target>[:RuntimeParams.FacetIDName:]</Target>
          <Process>
            <Message>
              <Value>[:$Macro.Argument.QueryString:]</Value>
            </Message>
          </Process>
        </OnRequest>
      </Result>
</Macro>

Parse SensorData Using JavaScript

TQLEngine allows us to use JavaScript code that can executed as part of the model flow. In this example we will use JavaScript to Parse the SensorData string.

Parse SensorData using JavaScript
<Macro Name="ProxySerialResponse">
  <Argument></Argument>
  <Result>
    <ExecuteQuery>
      <QueryString>
        <Query>
          <Find format="known">
            <MCUSensorModel>
              <SensorId ne=""/>
            </MCUSensorModel>
          </Find>
        </Query>
      </QueryString>
    </ExecuteQuery>
    <JavaScript>
      var str = "[:$Response.Message.Value.Find.Result.MCUSensorModel.SensorData.Known:]";
      sffLog.info("***************SensorData**************" + str);
      var sensorToken = str.split("#");
      var updateFacetData = ListMap.static.newInstance();
      for (i=0; i&lt;sensorToken.length; i++) {
      sval = sensorToken[i].split(":");
      sffContext.setContextData(sval[0], sval[1]);
      }
      updateFacetData.instanceAdd("UpdateTempValueToNull");
      updateFacetData.instanceAdd("UpdateHumidityValueToNull");
      updateFacetData.instanceAdd("UpdateAmbianceValueToNull");
      sffLog.info(updateFacetData);
      updateFacetData;
    </JavaScript>
  </Result>
</Macro>

Start / Stop Subscription Action

So far we have all the building blocks required to subscribe, parse and distribute. We need a mechanism to start and stop the subscription. We can achieve this using a attribute and an associated action. For this purpose let's add an attribute called InitSubscribers with an action InitSubAction to MCUSensorFacet. The Pseudo logic to be implemented in this action is:

if InitSubscribers is true

   subscribe using SubscribeToTQL Macro

else

  unsubscribe

 

InitSubAction
<String Name="InitSubscribers" default="false" KnownBy="InitSubAction"/>
  <Action Name="InitSubAction">
  <Workflow Limit="1" Live="1" Timeout="-1">
    <Task name="Main" while="true">
      <Event name="Argument" as="ActionArgument"/>
      <Invoke name="InvokeSubs" waitFor="Argument">
        <FacetScript>
          <If Condition="/'[%:Event.Argument.InitSubscribers.Value:%]' eq 'true'">
            <Log Message="Subscribing to: *Atomiton.Sensor.MCUSensorModel.SensorData.[%:Event.Argument.SensorId:%]*"/>
            <SubscribeToTQL>
              <TopicName>*Atomiton.Sensor.MCUSensorModel.SensorData.[%:Event.Argument.SensorId:%]*</TopicName>
              <ActionName>
                <ProxySerialResponse/>
              </ActionName>
            </SubscribeToTQL>
          </If>
        </FacetScript>
      </Invoke>
      <Output Name="Result" As="ActionResult">
        <Value>
          <InitSubscribers>
            [%:Event.Argument.InitSubscribers.Value:%]
          </InitSubscribers>
        </Value>
      </Output>
    </Task>
  </Workflow>
</Action>

Query and subscription

Queries we need are:

  • Find Sensor Information
  • Initialize MCU Sensor Model
  • Initialize Other Sensor Models
  • Start Subscribers
  • Externally subscribe to Sensor Model
  • Externally subscribe to Other Sensors (Temperature)

Queries

FindSensors
<Query>
  <Find format="version,known">
    <MCUSensorModel>
      <sensorId ne=""/>
    </MCUSensorModel>
  </Find>
  <Find format="version,known">
    <TempSensorModel>
      <TempSensorId ne=""/>
    </TempSensorModel>
  </Find>
  <Find format="version,known">
    <HumiditySensorModel>
      <HumSensorId ne=""/>
    </HumiditySensorModel>
  </Find>
  <Find format="version,known">
    <AmbianceSensorModel>
      <AmbSensorId ne=""/>
    </AmbianceSensorModel>
  </Find>
</Query>
Init MCU Sensor
<Query>
  <DeleteAll format="version,current">
    <MCUSensorModel>
      <sensorId ne=""/>
    </MCUSensorModel>
  </DeleteAll>
  <Save format="version,current">
    <!-- This will read -->
    <MCUSensorModel>
      <PerifParams>
        <Peripheral>
          serial
        </Peripheral>
        <Baudrate>
          9600
        </Baudrate>
        <InterfacePort>
          /dev/cu.usbserial-A1025R0Y
        </InterfacePort>
        <Interface>
          serial
        </Interface>
        <Format>
          ascii
        </Format>
        <Operation>
          receive
        </Operation>
        <UniqueId>
          76522
        </UniqueId>
        <Payload>
          $Null()
        </Payload>
      </PerifParams>
      <SensorData>
        $Null()
      </SensorData>
      <InitSubscribers>
        false
      </InitSubscribers>
    </MCUSensorModel>
  </Save>
</Query>
Initialize Other Sensors
<Query>
  <DeleteAll format="version,current">
    <TempSensorModel>
      <TempSensorId ne=""/>
    </TempSensorModel>
  </DeleteAll>
  <DeleteAll format="version,current">
    <HumiditySensorModel>
      <HumSensorId ne=""/>
    </HumiditySensorModel>
  </DeleteAll>
  <DeleteAll format="version,current">
    <AmbianceSensorModel>
      <AmbSensorId ne=""/>
    </AmbianceSensorModel>
  </DeleteAll>
  <Create>
    <TempSensorModel>
      <TempValueInC>
        0
      </TempValueInC>
      <TempValueInF>
        0
      </TempValueInF>
    </TempSensorModel>
  </Create>
  <Create>
    <HumiditySensorModel>
      <HumidityValue>
        0
      </HumidityValue>
    </HumiditySensorModel>
  </Create>
  <Create>
    <AmbianceSensorModel>
      <AmbianceValue>
        0
      </AmbianceValue>
    </AmbianceSensorModel>
  </Create>
</Query>
Init Subscribers
<Query>
  <Find format="version">
    <MCUSensorModel>
      <SensorId ne=""/>
    </MCUSensorModel>
  </Find>
  <SetResponseData>
    <key>
      Message.Value.Find.Result.MCUSensorModel.InitSubscribers.Value
    </key>
    <value>
      true
    </value>
  </SetResponseData>
  <Update>
    <from>
      Result
    </from>
    <Include>
      $Response.Message.Value.Find
    </Include>
  </Update>
</Query>
Subscribe To MCU Sensor Model
<Query Storage='TqlSubscription'>
  <Save>
    <TqlSubscription Label='MCUSensorModel' sid='20'>
      <Topic>
        *Atomiton.Sensor.MCUSensorModel*
      </Topic>
    </TqlSubscription>
  </Save>
</Query>
Subscribe To Temp Sensor Model
<Query Storage='TqlSubscription'>
  <Save>
    <TqlSubscription Label='MCUSensorModel' sid='21'>
      <Topic>
        *Atomiton.Sensor.TempSensorModel*
      </Topic>
    </TqlSubscription>
  </Save>
</Query>

Source Code

Import into TQLStudio

ProjectNameImport Link
Multiple SensorsMultipleSensors

Complete Source Code

 

Multiple Sensor Model
<Namespace Name="Atomiton">
  <Domain Name="Sensor">
    <Macro Name="UpdateTempValueToNull">
      <Argument/>
      <Result>
        <ExecuteQuery>
          <QueryString>
            <Query>
              <Find format="version">
                <TempSensorModel>
                  <TempSensorId ne=""/>
                </TempSensorModel>
              </Find>
              <SetResponseData>
                <key>Message.Value.Find.Result.TempSensorModel.TempValueInC.Value</key>
                <value>$Null()</value>
              </SetResponseData>
              <SetResponseData>
                <key>Message.Value.Find.Result.TempSensorModel.TempValueInF.Value</key>
                <value>$Null()</value>
              </SetResponseData>
              <Update>
                <from>Result</from>
                <Include>$Response.Message.Value.Find</Include>
              </Update>
            </Query>
          </QueryString>
        </ExecuteQuery>
      </Result>
    </Macro>

    <Macro Name="UpdateHumidityValueToNull">
      <Argument/>
      <Result>
        <ExecuteQuery>
          <QueryString>
            <Query>
              <Find format="version">
                <HumiditySensorModel>
                  <HumSensorId ne=""/>
                </HumiditySensorModel>
              </Find>
              <SetResponseData>
                <key>Message.Value.Find.Result.HumiditySensorModel.HumidityValue.Value</key>
                <value>$Null()</value>
              </SetResponseData>
              <Update>
                <from>Result</from>
                <Include>$Response.Message.Value.Find</Include>
              </Update>
            </Query>
          </QueryString>
        </ExecuteQuery>
      </Result>
    </Macro>

    <Macro Name="UpdateAmbianceValueToNull">
      <Argument/>
      <Result>
        <ExecuteQuery>
          <QueryString>
            <Query>
              <Find format="version">
                <AmbianceSensorModel>
                  <AmbSensorId ne=""/>
                </AmbianceSensorModel>
              </Find>
              <SetResponseData>
                <key>Message.Value.Find.Result.AmbianceSensorModel.AmbianceValue.Value</key>
                <value>$Null()</value>
              </SetResponseData>
              <Update>
                <from>Result</from>
                <Include>$Response.Message.Value.Find</Include>
              </Update>
            </Query>
          </QueryString>
        </ExecuteQuery>
      </Result>
    </Macro>

    <Macro Name="ExecuteQuery">
      <Argument>
        <QueryString>
          <Query/>
        </QueryString>
      </Argument>
      <Result>
        <OnRequest>
          <Target>[:RuntimeParams.FacetIDName:]</Target>
          <Process>
            <Message>
              <Value>[:$Macro.Argument.QueryString:]</Value>
            </Message>
          </Process>
        </OnRequest>
      </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>

    <Macro Name="ProxySerialResponse">
      <Argument></Argument>
      <Result>
        <ExecuteQuery>
          <QueryString>
            <Query>
              <Find format="known">
                <MCUSensorModel>
                  <SensorId ne=""/>
                </MCUSensorModel>
              </Find>
            </Query>
          </QueryString>
        </ExecuteQuery>
        <JavaScript>
          var str = "[:$Response.Message.Value.Find.Result.MCUSensorModel.SensorData.Known:]";
          sffLog.info("***************SensorData**************" + str);
          var sensorToken = str.split("#");
          var updateFacetData = ListMap.static.newInstance();
          for(i=0; i&lt;sensorToken.length; i++) {
          sval =sensorToken[i].split(":");
          sffContext.setContextData(sval[0], sval[1]);
          }
          updateFacetData.instanceAdd("UpdateTempValueToNull");
          updateFacetData.instanceAdd("UpdateHumidityValueToNull");
          updateFacetData.instanceAdd("UpdateAmbianceValueToNull");
          sffLog.info(updateFacetData);
          updateFacetData;
        </JavaScript>
      </Result>
    </Macro>

    <Def Name="PeripheralParams">
      <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"/>
    </Def>

    <ThingFacet Name="MCUSensorFacet">
      <String Name="InitSubscribers" default="false" KnownBy="InitSubAction"/>
      <String Name="SensorData" update="auto" KnownBy="SensorDataReadAction"/>
      <PeripheralParams Name="PerifParams"/>
      <Action Name="InitSubAction">
        <Workflow Limit="1" Live="1" Timeout="-1">
          <Task name="Main" while="true">
            <Event name="Argument" as="ActionArgument"/>
            <Invoke name="InvokeSubs" waitFor="Argument">
              <FacetScript>
                <If Condition="/'[%:Event.Argument.InitSubscribers.Value:%]' eq 'true'">
                  <Log Message="Subscribing to: *Atomiton.Sensor.MCUSensorModel.SensorData.[%:Event.Argument.SensorId:%]*"/>
                  <SubscribeToTQL>
                    <TopicName>*Atomiton.Sensor.MCUSensorModel.SensorData.[%:Event.Argument.SensorId:%]*</TopicName>
                    <ActionName>
                      <ProxySerialResponse/>
                    </ActionName>
                  </SubscribeToTQL>
                </If>
              </FacetScript>
            </Invoke>
            <Output Name="Result" As="ActionResult">
              <Value>
                <InitSubscribers>
                  [%:Event.Argument.InitSubscribers.Value:%]
                </InitSubscribers>
              </Value>
            </Output>
          </Task>
        </Workflow>
      </Action>

      <Action Name="SensorDataReadAction">
        <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.PerifParams.InterfacePort.Value:%]"
                  </InterfacePort>
                  <Baudrate>
                    "[%:Event.Argument.PerifParams.Baudrate.Value:%]"
                  </Baudrate>
                  <Interface>
                    "[%:Event.Argument.PerifParams.Interface.Value:%]"
                  </Interface>
                  <UniqueId>
                    "[%:Event.Argument.PerifParams.UniqueId.Value:%]"
                  </UniqueId>
                  <Operation>
                    "[%:Event.Argument.PerifParams.Operation.Value:%]"
                  </Operation>
                  <Peripheral>
                    "[%:Event.Argument.PerifParams.Peripheral.Value:%]"
                  </Peripheral>
                  <Payload>
                    "[%:Event.Argument.PerifParams.Payload.Value:%]"
                  </Payload>
                  <Format>
                    "[%:Event.Argument.PerifParams.Format.Value:%]"
                  </Format>
                </Value>
              </Message>
            </Invoke>
            <Output Name="Result" As="ActionResult">
              <Value>
                <SensorData>
                  [%:Invoke.InvokeSerialRead.Message.Value/normalize-space(received):%]
                </SensorData>
              </Value>
            </Output>
          </Task>
        </Workflow>
      </Action>
    </ThingFacet>

    <ThingFacet Name="TempSensorFacet">
      <Double Name="TempValueInC" KnownBy="TempValueAction"/>
      <Double Name="TempValueInF" KnownBy="TempValueAction"/>
      <Action Name="TempValueAction">
        <Workflow Limit="1" Live="1" Timeout="-1">
          <Task name="Main" while="true">
            <Event name="Argument" as="ActionArgument"/>
            <Invoke name="ReadValueFromContext" waitFor="Argument">
              <FacetScript>
                <Log Message="Reading TEMPC/TEMPF value from context [:$ContextData.TEMPC:] / [:$ContextData.TEMPF:]"/>
                <If Condition="$ContextData/not(boolean(TEMPC))">
                  <SetContextData>
                    <Key>TEMPC</Key>
                    <Value>0</Value>
                  </SetContextData>
                  <SetContextData>
                    <Key>TEMPF</Key>
                    <Value>0</Value>
                  </SetContextData>
                </If>
              </FacetScript>
            </Invoke>
            <Output Name="Result" As="ActionResult">
              <Value>
                <TempValueInC>
                  [:$ContextData.TEMPC:]
                </TempValueInC>
                <TempValueInF>
                  [:$ContextData.TEMPF:]
                </TempValueInF>
              </Value>
            </Output>
          </Task>
        </Workflow>
      </Action>
    </ThingFacet>

    <ThingFacet Name="HumiditySensorFacet">
      <Double Name="HumidityValue" KnownBy="HumidityValueAction"/>
      <Action Name="HumidityValueAction">
        <Workflow Limit="1" Live="1" Timeout="-1">
          <Task name="Main" while="true">
            <Event name="Argument" as="ActionArgument"/>
            <Invoke name="ReadValueFromContext" waitFor="Argument">
              <FacetScript>
                <Log Message="Reading HUMPCT value from context [:$ContextData.HUMPCT:]"/>
                <If Condition="$ContextData/not(boolean(HUMPCT))">
                  <SetContextData>
                    <Key>HUMPCT</Key>
                    <Value>0</Value>
                  </SetContextData>
                </If>
              </FacetScript>
            </Invoke>
            <Output Name="Result" As="ActionResult">
              <Value>
                <HumidityValue>
                  [:$ContextData.HUMPCT:]
                </HumidityValue>
              </Value>
            </Output>
          </Task>
        </Workflow>
      </Action>
    </ThingFacet>

    <ThingFacet Name="AmbianceSensorFacet">
      <Double Name="AmbianceValue" KnownBy="AmbianceValueAction"/>
      <Action Name="AmbianceValueAction">
        <Workflow Limit="1" Live="1" Timeout="-1">
          <Task name="Main" while="true">
            <Event name="Argument" as="ActionArgument"/>
            <Invoke name="ReadValueFromContext" waitFor="Argument">
              <FacetScript>
                <Log Message="Reading AMB value from context [:$ContextData.AMB:]"/>
                <If Condition="$ContextData/not(boolean(AMB))">
                  <SetContextData>
                    <Key>AMB</Key>
                    <Value>0</Value>
                  </SetContextData>
                </If>
              </FacetScript>
            </Invoke>
            <Output Name="Result" As="ActionResult">
              <Value>
                <AmbianceValue>
                  [:$ContextData.AMB:]
                </AmbianceValue>
              </Value>
            </Output>
          </Task>
        </Workflow>
      </Action>
    </ThingFacet>

    <ThingModel Name="TempSensorModel" combines="TempSensorFacet">
      <Sid Name="TempSensorId"/>
    </ThingModel>
    <ThingModel Name="HumiditySensorModel" combines="HumiditySensorFacet">
      <Sid Name="HumSensorId"/>
    </ThingModel>
    <ThingModel Name="AmbianceSensorModel" combines="AmbianceSensorFacet">
      <Sid Name="AmbSensorId"/>
    </ThingModel>
    <ThingModel Name="MCUSensorModel" combines="MCUSensorFacet">
      <Sid Name="SensorId"/>
    </ThingModel>
  </Domain>
</Namespace>