Subscription and Notification

Introduction

A-Stack allows to listen to TQL changes using Subscription and Notification mechanism. This feature in A-Stack is enabled using TopicFacet FacetType.

Enabling Subscriptions on TQL FacetType

In order to use any Atomiton A-Stack capabilities you need to chose appropriate FactTypes. FacetTypes are configured using NewFacetInstance as part of your deployment package definition. Here are additional configuration steps required as part of TQL Facettype (SffTqlFacet) in order to do subscription and notification over the TQL Models.

Steps to Enable Subscription and Notification is a two step process:

  1. Define a new TopicFacet type : Note that on Activate facet lifecycle phase we issue a Subscription request to topic with value TQL.* A-Stack publishes all the Model changes whose topic name starts with TQL.

    NewFacetInstance - TopicFacet
    <NewFacetInstance fid="[:RuntimeParams.TopicFacetIDName:]" name="TQLGenericTopic" type="SffTopicFacet">
        <OnActivate>
          <DoRequest>
            <Process>
              <Message type="xml">
                <Value>
                  <Subscribe sid="GenericTopicID" topic="TQL.*">
                    <Action>
                      <Log>CacheUpdate -> [:[:@Log:]$Request:]</Log>
                    </Action>
                  </Subscribe>
                </Value>
              </Message>
            </Process>
          </DoRequest>
        </OnActivate>
        <OnOpen ModifyPipeline="HttpServerExtensionArgs"/>
    </NewFacetInstance>
  2. Add Reference to the TopicFacet within TqlFacet: Note that on the OnActivate Lifecycle event of TqlFacet Type we add a reference to Topic facet using <TopicFacet> Facetscript command.

    Loading Macro Definition as part of Activate
    <NewFacetInstance fid="[:RuntimeParams.WSFacetIDName:]" Name="TQL" Type="SffTqlFacet">
        <OnActivate>
          <NewFacetInstance name="tqlwfws" type="SffWdlFacet"/>
          <TopicFacet>?TQLGenericTopic</TopicFacet>
            ....
        </OnActivate>
    </NewFacetInstance>

Consuming Subscription and Notifications

Internally using Pipelines

TQL Model developers can subscribe and receive notifications while writing ThingFacets, AppFacets, or building control or device logic. Since subscription requests are going to be reusable within a modeling domain or across domain, it is always a good idea to wrap them into Macros.

Create a pipeline:

Example of TQLSubscription Pipeline Macro:

Arguments:

  • TopicName - Topic value to subscribe 
  • TopicID - Unique Identifier of this subscription.
  • ActionCode - Segment of code to be execute on receiving notification on the subscribed request.
    Note that Target FacetType i.e. Topic Facet is Parameterized. The value will be replaced dynamically at package deployment time. The parameter value must be specified via some configuration file.

Subscription over Pipeline
<Macro Name="SubscribeToTQLChanges">
  <Argument>
    <TopicName>TQL.*</TopicName>
    <TopicId>GenericTopicID</TopicId>
    <ActionName/>
  </Argument>
  <Result>
    <OnRequest target="[:RuntimeParams.TopicFacetIDName:]" Disable="CMD_SERVER">
      <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>

Example:

Here is an example to display sensor value on Phidget Text LCD Display.

We create a Action SubscribeAc which can be called by Instantiating the Model with which  SensorDataToLCDText is associated.

Web Subscribe to Sensor changes using PhidgetSensors model and called Macro DisplayLCDText that triggers display write operation (Another ThingFacet)

Subscription over Pipeline
<AppFacet Name="SensorDataToLCDText">
	<String Name="startDisplay" KnownBy="SubscribeAc" />
	<AA>[:#o#Output.ActionArgument:]</AA>
	<Action Name="SubscribeAc"
		Documentation="Start the subscription (with action to display to LCD) to sensors data">
		<Workflow Limit="1" Live="1" Timeout="-1">
			<Task name="Main">
				<Event name="Argument" as="ActionArgument" />
				<Invoke name="SubscribeAC" waitFor="Argument">
					<FacetScript>
					    <SetLocalData Key="EmptyKey" value="" />
						<SubscribeToTQL
							TopicName="TQL.Update.AtomitonLab.AtomitonLabDevices.PhidgetSensors.*">
							<ActionName>
								<DisplayLCDText DisplayString="[:[:$LocalData.EmptyKey:]$Response.Message.Value.Known:]" />
							</ActionName>
						</SubscribeToTQL>
					</FacetScript>
				</Invoke>
				<Output name="ActionResult">
					<Value>
						<startDisplay>[%:Event.Argument.startDisplay.Value:%]</startDisplay>
					</Value>
				</Output>
			</Task>
		</Workflow>
	</Action>
</AppFacet>

Externally over WebSockets

Client applications using can subscribe to topics and receive notifications over websocket connection. Websocket transport must be exposed for applications.

External Subscription Request

Standard Query to Subscribe

A-Stack exposes TQLSubscription Data Model to make a subscription request.

TqlSubscription Model
<DataModel name="TqlSubscription" modifiers="system" documentation="This is used by clients">
     <Attribute name="sid" value="TqlSubscriptionBase.sid" ne=""/>
     <String name="Label" cardinality="0..1" documentation="User defined label"/>
     <String name="Topic" cardinality="0..1000" documentation="Qualified target type glob"/>
     <String name="Event" cardinality="0..10" documentation="Any combination of Create,Update,Delete"/>
     <String name="Instance" cardinality="0..1000" documentation="A collection of system IDs"/>
     <String name="MessageType" cardinality="0..1" documentation="Can be xml or json"/>
     <String name="OnStart" cardinality="0..1" documentation="Text sent upon subscription start; null means no message"/>
     <String name="OnExpire" cardinality="0..1" documentation="Text sent upon subscription expiration; null means no message"/>
     <DateTime name="StartAt" cardinality="0..1" format="$SimpleDateFormat(yyyy-MM-dd'T'HH:mm:ss.SSSZ)" documentation="Subscription begins time; null means now"/>
     <DateTime name="ExpireAt" cardinality="0..1" format="$SimpleDateFormat(yyyy-MM-dd'T'HH:mm:ss.SSSZ)" documentation="Subscription ends time; null means no expiration"/>
</DataModel>
Receiving Notifications 
TqlNotification Model
<DataModel name="TqlNotification" modifiers="system" documentation="This is private to the system">
              <Attribute name="sid" value="TqlNotificationBase.sid"/>
              <Attribute name="Label" value="TqlSubscription.Label"/>
              <Attribute name="Topic" value="TqlSubscription.Topic"/>
              <Attribute name="Event" value="TqlSubscription.Event"/>
              <Attribute name="Instance" value="TqlSubscription.Instance"/>
              <Attribute name="XPathFilter" value="TqlSubscription.XPathFilter"/>
              <Attribute name="MessageType" value="TqlSubscription.MessageType"/>
              <Attribute name="OnStart" value="TqlSubscription.OnStart"/>
              <Attribute name="OnExpire" value="TqlSubscription.OnExpire"/>
              <Attribute name="Limit" value="TqlSubscription.Limit"/>
              <Attribute name="StartAt" value="TqlSubscription.StartAt" format=""/>
              <Attribute name="ExpireAt" value="TqlSubscription.ExpireAt" format=""/>
              <Attribute name="ExpireIn" value="TqlSubscription.ExpireIn"/>
</DataModel>

Lifecycle of Subscription & Notification

  • Subscriptions are local to the channel. That is, each WebSocket client need to create and manage its own subscriptions.

    In order to create a subscription a client needs to create/save an instance of TqlSubscription model to TqlSubscription storage. That is:

    Create Subscription Request
    <Query Storage="TqlSubscription">
        <Save>
            <TqlSubscription Label="MyLabel" sid="1">
                <Topic>*</Topic>
            </TqlSubscription>
        </Save>
    </Query>

    For subscription evaluation only “StartAt”, “ExpireAt”, “Event”, “Topic” and “Instance” attributes are considered.

    Subscriptions are managed by the client as any other instances (i.e. it can create/update/delete subscriptions or their attributes).

    Note, however, that total number of subscriptions per client is limited to 100

    The range -99..0 (negative numbers) is reserved for system use (and currently not used)


  • The model, however, allows for multiple values of matching attributes so a single subscription instance can match multiple topics, for example. Clients can address specific instance by its sid (subscription ID) number in 0..99 range. When subscription instance is created without specific sid, the system will pick up the next available sid from the allowed range. Once the range is exhausted, the system will drop the counter back to 0 and start overriding existing subscription if any. It is strongly recommended that clients manage their subscription IDs explicitly.

  • Notifications are currently delivered in the following fixed format

    Create Subscription Request
    <TqlNotification>
      <Create>
        <TS74627698566492569#002>
          <Atomiton.TqlSystem.TqlSubscription.Label Value="MyLabel" Version="1" Timestamp="1430617757361"/>
          <Atomiton.TqlSystem.TqlSubscription.Topic Value="*.TqlSubscription.*" Order="0x0000" Version="1" Timestamp="1430617757362"/>
          <Atomiton.TqlSystem.TqlSubscription.MessageType Value="xml" Version="1" Timestamp="1430617757362"/>
          <Atomiton.TqlSystem.TqlSubscription.ExpireIn Value="PT5M" Version="1" Timestamp="1430617757362"/>
        </TS74627698566492569#002>
      </Create>
    </TqlNotification>


Use-Case - Archiving Data Using Subscription 

One of the obvious use-case of subscribing to data changes would be to archive the data.

In HelloTQL Example that is available as part of the TQLConsole Starter projects; Let's assume that we are interested in archiving the temperature values that get periodically updated. The code below examples the steps to do that:

  1. Create a data model to store the archive data
DataModel to Store TempSensor History
<DataModel Name="TempSensorHistory">
  <Sid Name="THistID"/>
  <DateTime Name="CreateDate" Format="$SimpleDateFormat(yyyy-MM-dd'T'HH:mm:ss'Z')"/>
  <String Name="TempValue"/>
</DataModel>

2. Let's start the subscription to the TempSensor model and in the action create data into TempSensorHistory Model. You  can execute this query through normal Query Editor Window

Start Subscription to store the historical tempsensor data
<SubscribeToTQLChanges>
  <TopicName>
    *Atomiton.Sensors.TempSensor.*
  </TopicName>
  <TopicID>
    ArchiveTempDataID
  </TopicID>
  <ActionName>
    <ExecuteQuery>
      <QueryString>
        <Query>
          <Find>
            <TempSensor>
              <SensorID ne=""/>
            </TempSensor>
          </Find>
          <Create>
            <TempSensorHistory>
              <CreateDate>
                [:$Now('%1$tY-%1$tm-%1$tdT%1$tTZ'):]
              </CreateDate>
              <TempValue>
                [:$Response.Message.Value.Find.Result.TempSensor.TempValue:]
              </TempValue>
            </TempSensorHistory>
          </Create>
        </Query>
      </QueryString>
    </ExecuteQuery>
  </ActionName>
</SubscribeToTQLChanges>

3. Query the Historical Data. You can also apply Time-based constraints.

Query Historical Data
<Query>
  <Find>
    <TempSensorHistory>
      <THistID ne=""/>
    </TempSensorHistory>
  </Find>
</Query>