Skip to end of metadata
Go to start of metadata

You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 25 Next »

Cloud Service Account  Setup

Most of the Cloud Service Providers require you to setup an account with their service, generate authorization key and get access to their APIs. For the purpose of this tutorial we have selected -  http://openweathermap.org/current as a cloud service provider to gather external environment data.

OpenWeatherMap provides current weather data for any location on Earth including over 200,000 cities. OpenWeather frequently updates current weather based on global models and data from more than 40,000 weather stations. Data is available in JSON, XML or HTML format.

Pre-registration:

1. Signup for the user account on openweathermap.

2. Make a note of the API key that is provided after successful signup. This key will be used to invoke openweathermap APIs.

 

3. Select the list of APIs to be integrated in the TQL Model (ThingFacets). For this tutorial we have selected API to get temperature, humidity, light, pressure for a given City and Country. 

API NameParametersResponse
http://api.openweathermap.org/data/2.5/weather

q - Query String. Example, Santa Clara,CA

mode - XML or JSON or HTML

APPID - Your API Key from step (2)

Cloud Service API Response
<current>
  <city id="5393015" name="Santa Clara">
    <coord lon="-121.96" lat="37.35"/>
    <country>US</country>
    <sun rise="2016-03-17T14:13:28" set="2016-03-18T02:18:25"/>
  </city>
  <temperature value="297.67" min="296.15" max="301.15" unit="kelvin"/>
  <humidity value="41" unit="%"/>
  <pressure value="1012" unit="hPa"/>
  <wind>
    <speed value="3.1" name="Light breeze"/>
    <gusts/>
    <direction value="0" code="N" name="North"/>
  </wind>
  <clouds value="5" name="clear sky"/>
  <visibility value="16093"/>
  <precipitation mode="no"/>
  <weather number="800" value="clear sky" icon="02d"/>
  <lastupdate value="2016-03-17T23:30:57"/>
</current>

Write a ThingFacet 

To abstract the interactions with the OpenWeatherMap Cloud Service we write a ExternalEnvFacet ThingFacet.

 ThingFacet

ThingFacet is a reusable TQL component that defines the software interactions with a "thing" and represent the "thing" in the software. A "thing" can be sensors, actuators, devices, or a particular aspect or function of a more complex machine. A ThingFacet has a name, Model Attributes and Actions.

Attributes

Attributes are containers of values. They can be used for the following purposes in ThingFacets:

(1) Store parameters required to make a protocol specific invocation to the thing, such as <String Name="InterfacePort"/>

(2) Store information received from the thing, such as <Number Name="TempValue"/> (The TempValue from the temperature sensor)

(3) Hold fixed information about the thing, such as <String Name="Unit" default="Celsius"/>

(4) Act as control variables - variable that can activate certain actions. They are called "actionable attributes". Actionable attributes are linked with one or more actions in the same ThingFacet by a modifier "KnownBy". For example: <Number Name="TempValue" KnownBy="SerialReadAction"/>

Actions

Action is where the processes to interact with things are defined.


An example of Camera ThingFacet.

Go to page

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 cloud service API as part of the response.

Protocol Specific Parameters are: Cloud Service Base URL, API Key, Output Format and Query Parameter.

Let's create Type (Using Def) EnvInfo to store the partial list of the API response

EnvInfo Definition
<Def Name="EnvInfo">
  <Double Name="Temperature"/>
  <Double Name="Humidity"/>
  <Double Name="Light"/>
  <Double Name="Pressure"/>
  <String Name="LastUpdatedByProvider"/>
</Def>

Lets now create a ExternalEnvFacet ThingFacet using the EnvInfo type. 

ExternalEnvFacet ThingFacet
<ThingFacet Name="ExternalEnvFacet">
 <String Name="DCBaseURL"/>
 <String Name="DCAPIKey"/>
 <String Name="OutputFormat"/>
 <String Name="DCQueryParam"/>
 <Integer Name="SensingInterval"/>
 <EnvInfo Name="EnvDetails"/>
</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 OpenWeatherMap Service we need simple HTTP request, which is part of the TQLEngine.

1. Write an Action name as EnvInfoAction

EnvInfoAction
<ThingFacet Name="ExternalEnvFacet">
	...
	<Action Name="EnvInfoAction">
		...
	</Action>
</ThingFacet>
 Action

An Action is a named element that describes a unit of executable functionality.

An Action has a name:  

Action Named Element
#
Action(Name: "RotateCameraAction", Documentation: "Simple Action to Rotate IP-based Camera by x degrees") 

An Action must define executable code in any Atomic Domain Languages (Workflow, FacetScript, TQL, Subscribe, Sequence) or any of their combinations, within one of the following elements:

NOTE: In the case of ThingFacet or AppFacet an Action must start with a Workflow.

Action is associated with attribute(s)

An Action must be associated or attached to a model attribute. In ThingFacets or AppFacets, an Action is associated with an model attribute of the same ThingFacet or AppFacet using the "KnownBy" modifier as below. The associated attribute becomes an actionable attribute.

Action Named Element
#
Double(Name: "RotateValue", KnownBy: "RotateCameraAction")

Please refer to <subscribe> and <sequence> for their respective action associations.

See also Associate multiple thing actions.

Action results in changes of the "Known" value(s) of any attributes

From the Action, using the output of the workflow, you can update any attributes of the model. Note that It is always the "Known" value of the attribute that will be updated by Actions. In this example, Action is changing the State and Image attributes.

Action Result
#
Output(name: "Result", as: "ActionResult"):
  Value:
    State: "[%:[%:@Output:%]/if([:IOK:]) then 'ON' else 'OFF':%]"
    Image: "[%:[%:@Output:%]/if([:IOK:]) then Invoke/GetImage/Message/Value/text() else '/img/no-image.jpg':%]"

Note: the output of the workflow can update any attribute of the model. This is not limited to the actionable attribute.

Action is triggered by the modification of associated (KnownBy) attribute values

Trigger is an execution phase of an Action. Execution of an Action is asynchronous and can be triggered in any number of ways. Please refer to <subscribe> and <sequence> for their respective action triggers.

In ThingModels and AppModels any updates to the associated attribute(s) values will trigger the Action. This include the initialization of the attributes (i.e. when it first acquires its value). The attribute value updates can only be achieved via TQL queries, in one of the following forms: 

  • External applications using TQL queries to update the value of the attributes. This includes the following example of a Save query to instantiate the model. Here giving the attribute RFID a value of $Null( ) will trigger the Action "KnownBy" this attribute.
  • Internally TQL queries updating the value of the attributes (e.g. TQL queries can be used by models to update values of other model's attributes).

Instantiate a Device using TQL Query
#
Query:
  Save(format: "version,current"):
    RfidReader(ReaderId: "R1", URL: "perif://"):
        RFID(value: "$Null()", version: "1")

See also Automatic Action trigger.

Go to page

 

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

 Workflow

A workflow consists of a sequence of tasks that are orchestrated and repeatable. Each task may process events, generate events, or process information. A workflow can be defined as part of an Action or as part of a Query.

Workflows are defined by Workflow Definition Language (WDL). Workflow can have a WorkflowId (wid). In run time, a workflow can have multiple instances (or called processes). Each process or instance is a single incarnation of the workflow definition.

TQL workflow should not be confused with business workflows. Business workflow is a set of business activities driven by data exchanges, work executions and people interactions. TQL workflow is a sequence of tasks that are strictly computer system based and purely data driven. So TQL workflow can execute very "low level" device communications such as messaging, as well as very "high level" application logic. 

A task can have the following structure: 

Input - any data which is required for the task to be started. Unless all inputs are available, the task will wait.

Output - any data which is generated by the task for externals to consume. One task's output may be the input for another task in the same workflow.  

Invoke - the process part of the task. Invoke may execute script, listen to protocol handler or send data to protocol handlers.

The sequence between multiple tasks in the same workflow is driven by the dependencies between their inputs and outputs. Since tasks generally only start on receiving their inputs, their sequences are only determined instance by instance at runtime.

Workflow example
<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>
					...
				</Value>
			</Message>
		</Invoke>
		<Output name="Result" as="ActionResult">
			<Value>
				...
			</Value>
		</Output>
	</Task>
</Workflow>

Go to page

 Workflow structure

Workflow contains one or more sequential or parallel, single time execution or repeatable tasks.

 

Go to page

Workflow
<Action Name="EnvInfoAction">
	<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.

 

 ActionArgument

ActionArgument is automatically passed argument to the action of a ThingModel or AppModel (instance) by A-Stack at the action trigger step.


  • ActionArgument contains both internal and user-defined attributes of the ThingModel / AppModel instance that triggered the action.
  • ActionArgument is local and transient - any changes made to the ActionArgument does not get reflected in the original copy of ThingModel / AppModel.


Example: Here is the ThingModel and ThingFacet (Partial Action) Definition for a Phidget Servo Motor

TempFacetSerial attributes
#
ThingFacet(Name: "PhidgetServoFacet"):
  String(Name: "ServoProtocolURL", Default: "phid://")
  String(Name: "DeviceInterfaceIndex")
  String(Name: "PhidgetDeviceType")
  Integer(Name: "ServoAngle", KnownBy: "PhidgetServoAction")
  Action(Name: "PhidgetServoAction", Documentation: "Control Servo Motor"):
    Workflow Limit: "1", Live: "1", Timeout: "-1"):
      Task(Name: "Main", while: "true"):
        Event(Name: "Argument", as: "ActionArgument")
          #...
ThingModel(Name: "PhidgetServoModel", Combines: "PhidgetServoFacet"):
  Sid(Name: "PhidServoId")

Here is the Phidget Servo Motor Instance creation TQL Query that triggers an Action

PhidgetServo Motor Instance Creation
#
Query:
  DeleteAll:
    PhidgetServoModel:
      PhidServoId(ne: "")
  Create:
    PhidgetServoModel:
      ServoProtocolURL: phid://
      PhidgetDeviceType: PhidgetAdvancedServo
      DeviceInterfaceIndex: 0
      ServoAngle: 110

Here is the ActionArgument Structure that is passed by A-Stack

ActionArgument Value of Phidget Servo Motor
#
Event:
  Argument:
    PhidServoId: KNI5JYVPAAAAUAABA4ONNHF2
    QName: 
      Atomiton.PhidgetServos.PhidgetServoModel
    $Guid:
      "#f3guorsmvr4qhomryacl5m3dha3ctaoe"
    $sid:
      KNI5JYVPAAAAUAABA4ONNHF2
    PhidgetDeviceType(Value: "PhidgetAdvancedServo", Known: "PhidgetAdvancedServo", Order: "", Version: "1", Timestamp: "1457366819505", DateTime: "2016-03-07 08:06:59.505"
      QName: "Atomiton.PhidgetServos.PhidgetServoModel.PhidgetDeviceType", FName: "PhidgetDeviceType")
    ServoAngle(Value: "110", Known: "110", Order: "", Version: "1", Timestamp: "1457366819505", DateTime: "2016-03-07 08:06:59.505", QName: "Atomiton.PhidgetServos.PhidgetServoModel.ServoAngle"
      FName: "ServoAngle")
    DeviceInterfaceIndex(Value: "0", Known: "0", Order: "", Version: "1", Timestamp: "1457366819505", DateTime: "2016-03-07 08:06:59.505"
      QName: "Atomiton.PhidgetServos.PhidgetServoModel.DeviceInterfaceIndex", FName: "DeviceInterfaceIndex")
    ServoProtocolURL(Value: "phid://", Known: "phid://", Order: "", Version: "1", Timestamp: "1457366819503", DateTime: "2016-03-07 08:06:59.503"
      QName: "Atomiton.PhidgetServos.PhidgetServoModel.ServoProtocolURL", FName: "ServoProtocolURL")

The following system generated tags are for each attribute:


A-Stack automatically generates tags as part of every attribute that makes up the model definition. System tags can be read (using FIND) with 

Format="all" modifier with the <Find> Query.

System Tag NameDescriptionPersisted?Query Constraint?
ValueThe value of the attribute is stored in "Value". This is often specified by the user at the time of create query (instantiation)YesYes
Known

Applicable to ThingModel/AppModel - this is the value that is only set by the output of Actions. Known CANNOT be

set using CRUD operations (Create / Update) via TQL Queries. The system also maintains a "Known" for DataModel attributes, for consistency purposes, but it will always be the same as Value.

YesNo
VersionThe version number of the attribute. This number is automatically incremented each time the value changes.Yes

No. You can request the value

using Format on <Find>

TimestampUnix epoch time at the time (in millisecond) of the attribute creation. For example: Timestamp value
1457663102004 is same as Fri, 11 Mar 2016 02:25:02 GMT
YesNo
DatetimeFormatted data time value of the attribute creation. For example:
2016-03-10 18:25:02.004
YesNo
QName

Fully qualified name of the attribute. <NamSpace>.<Domain>.<ModelName>.<AttributeName>. For example

SimpleModel.MyModels.VendorInfo.vendorName given that VendorInfo is defined within

Namespace - SimpleModel, with Domain as MyModels and DataModel Name as VendorInfo

YesNo
FNameGiven name of the attribute name.YesNo

Go to page

$Guid here is a global unique identifier of this model definition

$Sid is a unique Identifier of this Model Instance. Note that this value will be same as PhidServoId that is defined by PhidgetServoModel Thing Model using type Sid.


Accessing ActionArgument:

ActionArgument values can be accessed using standard Template Processor (TP) Notation. For example:

Access to PhidgetDeviceType Value
[%:Event.Argument.PhidgetDeviceType.Value:%]

Go to page

3. Invoke HTTP call: Method of HTTP is Get. We use Invoke modifier Get for this purpose. The parameters required for HTTP Get are passed as directly as the value of Get.

Invoke HTTP Handler
<Invoke Name="ReadValues" WaitFor="Argument" Documentation="Invoke HTTP GET"
         Get="[%:Event.Argument.DCBaseURL.Value:%]?q=[%:Event.Argument.DCQueryParam.Value:%]&amp;mode=[%:Event.Argument.OutputFormat.Value:%]&amp;APPID=[%:Event.Argument.DCAPIKey.Value:%]"/>

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.DCBaseURL.Value:%]
DCBaseURL value passed when creating/updating model instance.
[%:Event.Argument.DCQueryParam.Value:%]
DCQueryParam value passed when creating/updating model instance.
&amp;
Escape the XML "&". Remember that the query string is q=xxx&... here & must be escaped.
[%:Event.Argument.OutputFormat.Value:%]
OutputFormat value passed when creating/updating model instance.
[%:Event.Argument.DCAPIKey.Value:%]
DCAPIKey value passed when creating/updating model instance.

4. Process Response.

  • Check if HTTP code is 200 else log the error message
  • Convert the temperature value from Kelvin to Centigrade.

    HandleResponse to GET
    <Invoke Name="HandleResponse">
      <FacetScript>
        <If Condition="/'[%:Invoke.ReadValues.Status:%]' eq '200'">
          <Then>
            <Log Message="Response Status is 200"/>
            <If Condition="/'[%:Invoke.ReadValues.Message.Value.current.temperature.unit:%]' eq 'kelvin'">
             <Then>
               <Log Message="Temperature Unit is Kelvin.."/>
                <SetContextData Key="CelTemp" Value="[%:/number([%:Invoke.ReadValues.Message.Value.current.temperature.value:%]-273):]"/>
                <Log Message="Temperature Value in Centigrade is.. [:$ContextData.CelTemp:]"/>
              </Then>
              <Else>
                <SetContextData Key="CelTemp" Value="[%:Invoke.ReadValues.Message.Value.current.temperature.value:%]"/>
              </Else>
            </If>
          </Then>
          <Else>
            <Log Message="Non-200 Response Status. Send Alert to DevOps"/>
          </Else>
        </If>
      </FacetScript>
    </Invoke>
    TagResolution
    "/'[%:Invoke.ReadValues.Message.Value.current.temperature.unit:%]' eq 'kelvin'"
    This is an XPath Expression to compare string. Example: "/<StringValue>' eq 'StringValue2'
    SetContextData
    This is a temporary key/value storage provided by TQLEngine as part of FacetScript. Here Key is CelTemp, Value is converted value to Centigrade to Original Value (if original value is not in Kelvin).
    [%:/number([%:Invoke.ReadValues.Message.Value.current.temperature.value:%]-273):]
    This is XPath Expression to perform Subtraction.

5. Store the output value in EvnDetails Attribute.

Store the output value
<Output Name="Result" As="ActionResult">
  <Value>
    <EnvDetails>
      <Temperature>[:$ContextData.CelTemp:]</Temperature>
      <Humidity>[%:Invoke.ReadValues.Message.Value.current.humidity.value:%]</Humidity>
      <Pressure>[%:Invoke.ReadValues.Message.Value.current.pressure.value:%]</Pressure>
      <LastUpdatedByProvider>[%:Invoke.ReadValues.Message.Value.current.lastupdate.value:%]</LastUpdatedByProvider>
    </EnvDetails>
  </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="ExternalEnvFacet">
	...
   <EnvInfo Name="EnvDetails" KnownBy="EnvInfoAction"/>
</ThingFacet>
 Actionable attributes

An actionable attribute is a model attribute with a modifier of "KnownBy" pointing to one or multiple Action(s). For example:



Example
String(name: "Image", KnownBy: "SyncImage")

String(name: "State", KnownBy: "SyncImage,SyncPreset")



An actionable attribute defines the active property of a ThingFacets or AppFacets.

In runtime, an actionable attribute will activate the associated Action(s) and their workflows whenever its value is modified.

For more on the relationship between Actions and actionable attributes, see here.

Go to page

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="ExternalEnv" Combines="ExternalEnvFacet">
      <Sid Name="ExtEnvId"/>
</ThingModel>
 Models

A model is a definition of a container, which represents the functionality or knowledge about some kind of entity. At run time, a model can be instantiated into model instances. Models are defined by Thing Definition Language (TDL). 

 In general, there are three types of models in an IoT application:

  • ThingModel - representing any entity external to the system that can have interactions with the system (e.g. exchanging messages). These "things" can be sensors, actuators, machines, or other devices.
  • DataModel - representing a data structure.
  • AppModel - representing any unit of application logic which can contain one or more processes. These processes may interact with other components within the system (e.g. other Models) or with external applications.

The basic structures of Models

  • A name
  • A list of Attributes
  • A number of Actions (only ThingModels and AppModels have Actions)
  • A number of Constraints (including Unique)

All models by default contain an Attribute of type SystemId (or Sid). If Sid is not defined by the author of the model, system will assign Sid to the model.

Model "combines" model facets

To increase reusability, model facets are adopted as reusable units.

There are two types of model facets:

  • ThingFacets
  • AppFacets

Model facets have the same structural components of Models (name, attributes, actions, constraints). However, model facets are not instantiated or persisted. They can only be "combined"  as part of a model, with the model being instantiated, and persisted.

  • ThingModel can combine ThingFacets;
  • AppModel can combine AppFacets;
  • There is no DataFacets.

As a good and recommended practice pattern, actions and attributes of a particular device logic or application logic are always defined in the model facets, and the model will acquire those attributes and actions via the keyword "combines". A model can combine multiple model facets. And vice versa, model facet can by combined by multiple model definitions.

For example:

ThingModel
#
ThingModel(Name: "TempSensor", combines: "TempFacetSerial"):
	Sid(Name: "sensorId");

Go to page

 ThingModels

ThingModel is one kind of Model (the other kinds are DataModels and AppModels). Therefore it has all the properties of Models (Lifecycle of modelsModel AttributesUnique and Constraints).

A ThingModel has

However, as a recommended practice, the Attributes and Actions related to the interactions with an external thing (sensors, actuators, machines) are often defined in a reusable structure called ThingFacet, and combined into a ThingModel. This relationship was explained in the concept of Models and Model Facets. Application related information are often defined directly as attributes of the ThingModel.

In the following example, the ThingModel PhidgetServoModel combines the ThingFacet PhidgetServoFacet, where: 

  • PhidgetServoFacet contains the device interaction specific Attributes (e.g. ServoProtocolURL) and Actions (e.g. PhidgetServoAction);
  • PhidgetServoModel contains application related Attributes: PhidServoId and InstalledAtZone (assuming the Phidget servo motor is installed in a particular zone within a Greenhouse environment, which has multiple zones).
  • Combines is used as a modifier of ThingModel

In the simplest case, a ThingModel may only have one single directly defined attribute of type Sid. Although not required, it is best practice to always create a ThingModel with one attribute that is of type SystemId or Sid.

PhidgetServo Thing Model
#
ThingModel(Name: "PhidgetServoModel", Combines: "PhidgetServoFacet"):
    Sid(Name: "PhidServoId")
    String(Name: "InstalledAtZone")
    
ThingFacet(Name: "PhidgetServoFacet"):
    String(Name: "ServoProtocolURL", Default: "phid://")
    String(Name: "DeviceInterfaceIndex")
    String(Name: "PhidgetDeviceType")
    Integer(Name: "ServoAngle", KnownBy: "PhidgetServoAction")
    Action(Name: "PhidgetServoAction", Documentation: "Control servo motor"):
      #...

This pattern allows the ThingFacet to be a reusable artifact, and potentially combined by multiple ThingModels.

Note: One can combine multiple ThingFacets within a single ThingModel using comma separated list of ThingFacets.

For Example Esky ThingModel combining EskyPreset and EskyImage Thing Facets

PhidgetServo Thing Model Part of a Zone
#
ThingFacet(Name: "EskyPreset"):
  String(Name: "State", KnownBy: "SyncPreset")
  String(Name: "Preset", KnownBy: "SyncPreset")
  Action(Name: "SyncPreset", documentation: "Synchronize camera state and preset"):
    Workflow Limit: "1", Live: "1", Timeout: "PT20S"):
      Task(Name: "Main", while: "true"):
        Event(Name: "Argument", as: "ActionArgument")
          #...

ThingFacet(Name: "EskyImage", combines: "Login"):
  String(Name: "State", KnownBy: "SyncImage")
  Clob(Name: "Image", KnownBy: "SyncImage")
  
  #Actions
  Action(Name: "SyncImage", documentation: "Synchronize camera state and snapshot image"):
    Workflow Limit: "1", Live: "1", Timeout: "PT10S"):
      Task(Name: "Main", while: "true"):
        Event(Name: "Argument", as: "ActionArgument")

ThingModel(Name: "Esky", combines: "EskyPreset,EskyImage", documentation: "actual single instance camera model"):
  Sid(Name: "CameraId")

In summary

  • ThingModels are normally used to represent and define interactions with external physical things. The specifics of interactions are often modeled via ThingFacet.
  • ThingModels combine one or more ThingFacets. ThingFacets are reusable artifacts. In runtime, only instances of ThingModels are created and persisted (not ThingFacets). 
  • ThingModels can contain application related information that are related to things.

When ThingModels do not have any Actions or combine any ThingFacets they are equivalent to DataModel i.e pure Attributes only.

ThingModel can be created, read, update and deleted using TQL queries.


 CRUD operations on ThingModels

Examples of CRUD Operation on a ThingModel:

CREATE:

Create PhidgetServo Thing Model
#
Create: 
    PhidgetServoModel: 
      ServoProtocolURL: "phid://"
      PhidgetDeviceType: "PhidgetAdvancedServo"
      DeviceInterfaceIndex: 0
      ServoAngle: 110

CREATE RESULT:

Create PhidgetServoModel Result
#
Create(Status: "Success"):
  PhidgetServoModel: 
    PhidServoId: "KNJN2YRVAAAAUAABA7LIQ4Y6" 
    ServoProtocolURL(Status: "Success+Created:1:1457384153653;", Value: "phid://")
    DeviceInterfaceIndex(Status: "Success+Created:1:1457384153653;", Value: "0")
    PhidgetDeviceType(Status: "Success+Created:1:1457384153653;", Value: "PhidgetAdvancedServo")
    ServoAngle(Status: "Success+Created:1:1457384153654;", Value: "110")

Note: If a ThingModel is combined with a ThingFacet, creation of a ThingModel results in instantiating (Trigger) of a ThingFacet action.


UPDATE:

PhidgetServo Thing Model
#
Query: 
  Find(format: "version"):
    PhidgetServoModel as: "var.PS"):
      PhidServoId(ne: "")
  SetResponseData: 
    key: Message.Value.Find.Result.PhidgetServoModel.ServoAngle.Value
    value: 120
  Update: 
    from: Result
    Include: $Response.Message.Value.Find


UPDATE Result:

PhidgetServo Thing Model
#
Find(Status: "Success", Format: "version"):
  Result: 
    PhidgetServoModel: 
      PhidServoId: "KNJPIFR6AAAAUAABA67ZRBQO" 
      PhidgetDeviceType(Value: "PhidgetAdvancedServo", Version: "1")
      ServoAngle(Version: "1", Value: "120")
      DeviceInterfaceIndex(Value: "0", Version: "1")
      ServoProtocolURL(Value: "phid://", Version: "1")

Note: If a ThingModel is combined with a ThingFacet updating a ThingModel attribute results in Trigger of a Action if that attribute is associated with an action.


DELETE:

Delete PhidgetServoModel Thing Model
#
Query: 
  DeleteAll: 
    PhidgetServoModel: 
      PhidServoId(ne: "")


DELETE Result

Delete PhidgetServoModel Thing Model Result
#
DeleteAll(Status: "Success"):
  Result: 
    PhidgetServoModel: 
      PhidServoId: "KNJOOOBZAAAAUAABA72MMI6E"
      PhidgetDeviceType: "PhidgetAdvancedServo"
      ServoAngle: 110
      DeviceInterfaceIndex: 0
      ServoProtocolURL: "phid://"

Note: If a ThingModel is combined with a ThingFacet, creation of a ThingModel results in deleting an instance results in cleanup of connections with actual things as per the protocol Handler used.



FIND

PhidgetServo Thing Model
#
Query: 
  Find(format: "all"):
    PhidgetServoModel:
      PhidServoId(ne: "")


Find result: Format="all" - returns all the internal attribute data as well.

PhidgetServoModel ThingModel Find Result
#
Find(Status: "Success", Format: "all"):
  Result: 
    PhidgetServoModel(QName: "Atomiton.PhidgetServos.PhidgetServoModel"):
      PhidServoId: "KNJOOOBZAAAAUAABA72MMI6E" 
      PhidgetDeviceType(Value: "PhidgetAdvancedServo", Known: "PhidgetAdvancedServo", Version: "1", Timestamp: "1457384798266", DateTime: "2016-03-07 13:06:38.266", QName: "Atomiton.PhidgetServos.PhidgetServoModel.PhidgetDeviceType", FName: "PhidgetDeviceType")
      ServoAngle(Value: "110", Known: "110", Version: "1", Timestamp: "1457384798266", DateTime: "2016-03-07 13:06:38.266", QName: "Atomiton.PhidgetServos.PhidgetServoModel.ServoAngle", FName: "ServoAngle")
      DeviceInterfaceIndex(Value: "0", Known: "0", Version: "1", Timestamp: "1457384798266", DateTime: "2016-03-07 13:06:38.266", QName: "Atomiton.PhidgetServos.PhidgetServoModel.DeviceInterfaceIndex", FName: "DeviceInterfaceIndex")
      ServoProtocolURL(Value: "phid://", Known: "phid://", Version: "1", Timestamp: "1457384798265", DateTime: "2016-03-07 13:06:38.265", QName: "Atomiton.PhidgetServos.PhidgetServoModel.ServoProtocolURL", FName: "ServoProtocolURL")


Note:

Go to page


Go to page

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

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

Checking Weather Periodically

Let's assume that you would like to check the weather periodically - say every morning or based on some other external factors. We can automatically add the behavior of checking periodically by triggering the EnvInfoAction.

The building blocks to Checking Weather Periodically are:

  • Ability to reset the current value of EnvInfo Attribute so that the action is triggered.
  • Ability to schedule the reset code at certain periodic interval.

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

Generic Macro to Execute a TQL Query

Macro Definition to run any Query
<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 to Reset EnvInfo values i.e. Update Query

We can call the Update Query to reset the EnvInfo parameter. Let's wrap this update query in another Macro called ResetEnvInfo

ResetEnvInfo Macro
<Macro Name="ResetEnvInfo">
  <Argument>
    <ExternalEnvId/>
  </Argument>
  <Result>
    <Log Message="Resetting External Env: [:$Macro.Argument.ExternalEnvId:]"/>
    <ExecuteQuery>
      <QueryString>
        <Query>
          <Find Format="all">
            <ExternalEnv>
              <ExtEnvId>[:$Macro.Argument.ExternalEnvId:]</ExtEnvId>
            </ExternalEnv>
          </Find>
          <SetResponseData>
            <Key>Message.Value.Find.Result.ExternalEnv.EnvDetails.Temperature.Value</Key>
            <Value>$Null()</Value>
          </SetResponseData>
          <SetResponseData>
            <Key>Message.Value.Find.Result.ExternalEnv.EnvDetails.Humidity.Value</Key>
            <Value>$Null()</Value>
          </SetResponseData>
          <SetResponseData>
            <Key>Message.Value.Find.Result.ExternalEnv.EnvDetails.Pressure.Value</Key>
            <Value>$Null()</Value>
          </SetResponseData>
          <SetResponseData>
            <Key>Message.Value.Find.Result.ExternalEnv.EnvDetails.LastUpdatedByProvider.Value</Key>
            <Value>$Null()</Value>
          </SetResponseData>
          <Update>
            <From>Result</From>
            <Include>$Response.Message.Value.Find</Include>
          </Update>
        </Query>
      </QueryString>
    </ExecuteQuery>
  </Result>
</Macro>

Scheduling ResetEnvInfo Macro

Now we need a mechanism to schedule a piece of Modeling code to run periodically. In this case we schedule RsetEnvInfo Macro. TQLEngine provides Scheduling mechanism using Sequence Capability. Similar to Executing a TQL Query from a Model we need to schedule and unschedule code by firing a request to Sequence Target. Let's write two macros to handle schedule and deleteschedule models.

Generic Macro to Schedule
Add Schedule 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>
Generic Macro to Delete Schedule
Delete Schedule 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>

 

Start / Stop Scheduling Action

So far, we have ability to run a resetEnvInfo Query, Schedule resetEnvInfo, and UnSchedule resetEnvInfo. We need a mechanism to start and stop the schedule. We can achieve this using a attribute and an associated action. For this purpose let's add an attribute called EmulateSensing with an action EmulateSensingAction to EnvInfoFacet. The Pseudo logic to be implemented in this action is:

if EmulateSensing is true

   schedule resetInfo Macro

else

  unschedule resetInfo Macro

 

EmulateSensingAction
<String Name="EmulateSensing" KnownBy="EmulateSensingAction"/>
<Action Name="EmulateSensingAction">
  <Workflow Limit="1" Live="1" Timeout="0">
    <Task Name="GetEnvInfo" While="true">
      <Event Name="Argument" As="ActionArgument"/>
      <Invoke Name="ToggleSchedule" WaitFor="Argument">
        <FacetScript>
          <If condition="/'[%:Event.Argument.EmulateSensing.Value:%]' eq 'true'">
            <then>
              <ScheduleJob>
                <Name>UpdateEnvDetails</Name>
                <ScheduleInterval>[%:Event.Argument.SensingInterval.Value:%]sec</ScheduleInterval>
                <ActionCode>
                  <ResetEnvInfo>
                    <ExternalEnvId>[%:Event.Argument.ExtEnvId:%]</ExternalEnvId>
                  </ResetEnvInfo>
                </ActionCode>
              </ScheduleJob>
            </then>
            <else>
              <Log Message="Inside DeleteScheduleJob else condition ======= "/>
              <DeleteScheduleJob>
                <JobName>UpdateEnvDetails</JobName>
              </DeleteScheduleJob>
            </else>
          </If>
        </FacetScript>
      </Invoke>
      <Output Name="Result" As="ActionResult">
        <Value>
          <EmulateSensing>[%:Event.Argument.EmulateSensing.Value:%]</EmulateSensing>
        </Value>
      </Output>
    </Task>
  </Workflow>
</Action>

Query and subscription

Queries we need are:

  • Find External Env Weather Information
  • Initialize External Env Weather Model
  • Subscribe to External Env Weather Model
  • Start Sensing (i.e. Simulation)
  • Stop Sensing (i.e. Simulation)

Queries

Find External Env
 <Query>
  <Find format="known,version">
    <ExternalEnv>
      <DCBaseURL ne=""/>
    </ExternalEnv>
  </Find>
</Query>
Init External Env Info
<Query>
  <DeleteAll>
    <ExternalEnv>
      <DCBaseURL ne=""/>
    </ExternalEnv>
  </DeleteAll>
  <Create>
    <ExternalEnv>
      <DCBaseURL>
        http://api.openweathermap.org/data/2.5/weather
      </DCBaseURL>
      <DCAPIKey>
        2b8f32bc27def9f56804c03516516f4d
      </DCAPIKey>
      <OutputFormat>
        xml
      </OutputFormat>
      <DCQueryParam>
        Pune,IN
      </DCQueryParam>
      <EnvDetails>
        <Temperature>
          0
        </Temperature>
        <Humidity>
          0
        </Humidity>
        <Light>
          0
        </Light>
        <Pressure>
          0
        </Pressure>
        <LastUpdatedByProvider>
          0
        </LastUpdatedByProvider>
      </EnvDetails>
      <EmulateSensing>
        False
      </EmulateSensing>
      <SensingInterval>
        60
      </SensingInterval>
    </ExternalEnv>
  </Create>
</Query>
Start Sensing
<Query>
  <Find format="known, version">
    <ExternalEnv>
      <DCBaseURL ne=""/>
    </ExternalEnv>
  </Find>
  <SetResponseData>
    <Key>
      Message.Value.Find.Result.ExternalEnv.EmulateSensing.Value
    </Key>
    <Value>
      true
    </Value>
  </SetResponseData>
  <Update>
    <From>
      Result
    </From>
    <Include>
      $Response.Message.Value.Find
    </Include>
  </Update>
</Query>
Stop Sensing
<Query>
  <Find format="known, version">
    <ExternalEnv>
      <DCBaseURL ne=""/>
    </ExternalEnv>
  </Find>
  <SetResponseData>
    <Key>
      Message.Value.Find.Result.ExternalEnv.EmulateSensing.Value
    </Key>
    <Value>
      False
    </Value>
  </SetResponseData>
  <Update>
    <From>
      Result
    </From>
    <Include>
      $Response.Message.Value.Find
    </Include>
  </Update>
</Query>
Subscribe To External Env
<Query Storage="TqlSubscription">
  <Save>
    <TqlSubscription Label="Changes To External Env" sid="1">
      <Topic>
        *Atomiton.ExternalEnvService.ExternalEnv*
      </Topic>
    </TqlSubscription>
  </Save>
</Query>
Cloud Service Model
<Namespace Name="DeviceCloudProvider">
  <Domain Name="CloudThings">
    <Def Name="EnvInfo">
      <Double Name="Temperature"/>
      <Double Name="Humidity"/>
      <Double Name="Light"/>
      <Double Name="Pressure"/>
      <String Name="LastUpdatedByProvider"/>
    </Def>

    <ThingFacet Name="ExternalEnvFacet">
      <String Name="DCBaseURL"/>
      <String Name="DCAPIKey"/>
      <String Name="OutputFormat"/>
      <String Name="DCQueryParam"/>
      <Integer Name="SensingInterval"/>
      <String Name="EmulateSensing" KnownBy="EmulateSensingAction"/>
      <EnvInfo Name="EnvDetails" KnownBy="EnvInfoAction"/>
      <Action Name="EnvInfoAction" Documentation="Action to interact with Device Cloud API">
        <Workflow Limit="1" Live="1" Timeout="0">
          <Task Name="GetEnvInfo" While="true">
            <Event Name="Argument" As="ActionArgument"/>
            <Invoke Name="ReadValues" WaitFor="Argument" Documentation="Invoke HTTP GET"
              Get="[%:Event.Argument.DCBaseURL.Value:%]?q=[%:Event.Argument.DCQueryParam.Value:%]&amp;mode=[%:Event.Argument.OutputFormat.Value:%]&amp;APPID=[%:Event.Argument.DCAPIKey.Value:%]"/>
            <Log Message="Output value from openweathermap url invoke ====[%:Invoke.ReadValues:%]"/>
            <Invoke Name="HandleResponse">
              <FacetScript>
                <If Condition="/'[%:Invoke.ReadValues.Status:%]' eq '200'">
                  <Then>
                    <Log Message="Response Status is 200"/>
                    <If Condition="/'[%:Invoke.ReadValues.Message.Value.current.temperature.unit:%]' eq 'kelvin'">
                      <Then>
                        <Log Message="Temperature Unit is Kelvin.."/>
                        <SetContextData Key="CelTemp" Value="[%:/number([%:Invoke.ReadValues.Message.Value.current.temperature.value:%]-273):]"/>
                        <Log Message="Temperature Value in Centigrade is.. [:$ContextData.CelTemp:]"/>
                      </Then>
                      <Else>
                        <SetContextData Key="CelTemp" Value="[%:Invoke.ReadValues.Message.Value.current.temperature.value:%]"/>
                      </Else>
                    </If>
                  </Then>
                  <Else>
                    <Log Message="Non-200 Response Status. Send Alert to DevOps"/>
                  </Else>
                </If>
              </FacetScript>

            </Invoke>
            <Output Name="Result" As="ActionResult">
              <Value>
                <EnvDetails>
                  <Temperature>[:$ContextData.CelTemp:]</Temperature>
                  <Humidity>[%:Invoke.ReadValues.Message.Value.current.humidity.value:%]</Humidity>
                  <!-- <Light>[%:Invoke.ReadValues.Message.Value.current.visibility.value:%]</Light> -->
                  <Pressure>[%:Invoke.ReadValues.Message.Value.current.pressure.value:%]</Pressure>
                  <LastUpdatedByProvider>[%:Invoke.ReadValues.Message.Value.current.lastupdate.value:%]</LastUpdatedByProvider>
                </EnvDetails>
              </Value>
            </Output>
          </Task>
        </Workflow>
      </Action>

      <Action Name="EmulateSensingAction">
        <Workflow Limit="1" Live="1" Timeout="0">
          <Task Name="GetEnvInfo" While="true">
            <Event Name="Argument" As="ActionArgument"/>
            <Invoke Name="ToggleSchedule" WaitFor="Argument">
              <FacetScript>
                <If condition="/'[%:Event.Argument.EmulateSensing.Value:%]' eq 'true'">
                  <then>
                    <ScheduleJob>
                      <Name>UpdateEnvDetails</Name>
                      <ScheduleInterval>[%:Event.Argument.SensingInterval.Value:%]sec</ScheduleInterval>
                      <ActionCode>
                        <ResetEnvInfo>
                          <ExternalEnvId>[%:Event.Argument.ExtEnvId:%]</ExternalEnvId>
                        </ResetEnvInfo>
                      </ActionCode>
                    </ScheduleJob>
                  </then>
                  <else>
                    <Log Message="Inside DeleteScheduleJob else condition ======= "/>
                    <DeleteScheduleJob>
                      <JobName>UpdateEnvDetails</JobName>
                    </DeleteScheduleJob>
                  </else>
                </If>
              </FacetScript>
            </Invoke>
            <Output Name="Result" As="ActionResult">
              <Value>
                <EmulateSensing>[%:Event.Argument.EmulateSensing.Value:%]</EmulateSensing>
              </Value>
            </Output>
          </Task>
        </Workflow>
      </Action>
    </ThingFacet>

    <ThingModel Name="ExternalEnv" Combines="ExternalEnvFacet">
      <Sid Name="ExtEnvId"/>
    </ThingModel>

    <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="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="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="ResetEnvInfo">
      <Argument>
        <ExternalEnvId/>
      </Argument>
      <Result>
        <Log Message="Resetting External Env: [:$Macro.Argument.ExternalEnvId:]"/>
        <ExecuteQuery>
          <QueryString>
            <Query>
              <Find Format="all">
                <ExternalEnv>
                  <ExtEnvId>[:$Macro.Argument.ExternalEnvId:]</ExtEnvId>
                </ExternalEnv>
              </Find>
              <SetResponseData>
                <Key>Message.Value.Find.Result.ExternalEnv.EnvDetails.Temperature.Value</Key>
                <Value>$Null()</Value>
              </SetResponseData>
              <SetResponseData>
                <Key>Message.Value.Find.Result.ExternalEnv.EnvDetails.Humidity.Value</Key>
                <Value>$Null()</Value>
              </SetResponseData>
              <SetResponseData>
                <Key>Message.Value.Find.Result.ExternalEnv.EnvDetails.Pressure.Value</Key>
                <Value>$Null()</Value>
              </SetResponseData>
              <SetResponseData>
                <Key>Message.Value.Find.Result.ExternalEnv.EnvDetails.LastUpdatedByProvider.Value</Key>
                <Value>$Null()</Value>
              </SetResponseData>
              <Update>
                <From>Result</From>
                <Include>$Response.Message.Value.Find</Include>
              </Update>
            </Query>
          </QueryString>
        </ExecuteQuery>
      </Result>
    </Macro>
  </Domain>
</Namespace>
  • No labels