...
- Parameters required to make a protocol specific invocation
Attributes to store the information received from the light.
There are two important patterns
...
to look for:
...
Mapping of Device Vendor Complex Structure: The response from GetLights is a complex hierarchical response.
...
Helper Tags: Tags that are helpful in simplifying long name as well as parameterizing the Protocol Parameters instead of attributes of a ThingFacet
Let's create response structure using Types (Def) to match to the response structure of Lights REST API
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
<Def Name="PhilipLightState"> <Boolean Name="On"/> <Integer Name="bri"/> <Integer Name="hue"/> <Integer Name="sat"/> <String Name="effect"/> <Double Name="xy" cardinality="2"/> <Integer Name="ct"/> <String Name="alert"/> <String Name="colormode"/> <Boolean Name="reachable"/> </Def> |
Helper Tag - 1: Simply define the Philip Light Bridge API URL using PhilipLightBaseURI Tag
...
Lets now create a ThingFacet using the PhilipLightState type and rest of the parameters like - Type, modelId, etc as parameters of the PhilipLightFacet ThingFacet. Now the entire ThingFacet definition maps to the exact Response of the GetLight structure.
Helper Tag - 2: Tag AA to designate Event.Argument
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
<ThingFacet Name="PhilipLightFacet">
<String Name="LightNumber"/>
<PhilipLightState Name="State"/>
<String Name="Type"/>
<String Name="modelid"/>
<String Name="uniqueid"/>
<String Name="swversion"/>
<!-- Helper Tag -->
<AA>[:#o#Event.Argument:]</AA>
</ThingFacet> |
Write a Action with Workflow
...
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
<ThingFacet Name="TempFacetSerialPhilipLightFacet"> ... <Action Name="PhilipLightAction"> ... </Action> </ThingFacet> |
...
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
<Invoke Name="SetState" waitFor="ActionArgument" Put="[:PhilipLightBaseURI:]/lights/[%:[:AA:].LightNumber.Value:%]/state"> <Message <Message typexmlns="PLight" Type="json"> <on_true> <Value>{"on": <on>true</on> </on_true> <on_false> <on>false</on> </on_false> <Value> <Include>[:@WFRT:]on_[%:[:AA:].state.On.Value}:%]</Value>Include> </Value> </Message> </Invoke> |
Note that PhilipLightBaseURI is referenced using TP tag: [:PhilipLightBaseURI:] 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.
4. Process the message received from the Philip Light Bridge
...
Tag | Resolution |
---|---|
[:PhilipLightBaseURI:] | http://10.0.2.16/api/newdeveloper |
[:AA:] | Event.Argument |
[%:[:AA:].LightNumber.Value:%] | Runtime value of structure: Event.Argument.LightNumber.Value |
<on_true> and <on_false> | contains <on>true</on> and <on>false</on>; this is stop gap to avoid string to boolean conversion in TP |
[:@WFRT:] | Delay processing of Tags at Workflow execution time. Note the TQLEngine goes through number of processing / pre-processing phases. Prefixing it with WFRT tells the Engine to delay resolving the Tags until the execution of the workflow |
[%:[:AA:].state.On.Value:%] | Runtime value of structure: Event.Argument.state.On.Value |
<Include>[:@WFRT:]on_[%:[:AA:].state.On.Value:%]</Include> | Includes the content of either <on_true> or <on_false> |
4. Process the message received from the Philip Light Bridge
Code Block | ||||
---|---|---|---|---|
| ||||
<Output name="Result" as="ActionResult"> <Value> <State> <On>[%:[:AA:].state.Value:%]</On> </State> </Value> </Output> |
...
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
<ThingFacet Name="PhilipLightActionPhilipLightFacet"> ... <PhilipLightState Name="State" KnownBy="PhilipLightActionUsingPutPhilipLightAction"/> </ThingFacet> |
Expand | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
|
...
Typically ThingModels are instantiated by writing external Create or Save Queries. In this case the Lights data is itself coming from another Provider API. This type of loading initial set of devices and their metadata is common to number of device providersNumber of device vendors follow the pattern of providing metadata loading APIs. This is a common approach. In PhilipLight example, we can load PhilipLightModel data by invoking HTTP Get on Lights. Let's create another ThingFacet and ThingModel for the purpose of loading the data. We also have to call the Create Query within the ThingFacet.
...
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 to. 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
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
<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> |
...
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
<Query>
<Find format="All">
<PhilipLightModel>
<LightNumber eq="1"/>
</PhilipLightModel>
</Find>
<SetResponseData>
<Key>
Message.Value.Find.Result.PhilipLightModel.state.On.Value
</Key>
<Value>
false
</Value>
</SetResponseData>
<Update>
<From>
Result
</From>
<Include>
$Response.Message.Value.Find
</Include>
</Update>
</Query>
|
Code Block | |||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
| <Namespace Name="Atomiton">
<Domain Name="Lights">
<PhilipLightBaseURI>http://10.0.2.16/api/newdeveloper</PhilipLightBaseURI>
<Macro Name="ExecuteQuery">
<Argument>
<QueryString>
<Query/
| ||||||||||
<Namespace Name="Atomiton"> <Domain Name="Lights"> </QueryString> </Argument><PhilipLightBaseURI>http://10.0.2.16/api/newdeveloper</PhilipLightBaseURI> <Def Name="PhilipLightState"> <Result> <Boolean Name="On"/> <OnRequest> <Integer Name="bri"/> <Target>[:RuntimeParams.FacetIDName:]</Target> <Integer Name="hue"/> <Process> <Integer Name="sat"/> <String Name="effect"/> <Message> <Double Name="xy" cardinality="2"/> <Integer Name="ct"/> <Value>[:$Macro.Argument.QueryString:]</Value> <String Name="alert"/> <String </Message>Name="colormode"/> <Boolean Name="reachable"/> </Process>Def> <Macro Name="ExecuteQuery"> </OnRequest> <Argument> </Result> <QueryString> </Macro> <Macro Name="AddLight" <Query/> <Argument> </QueryString> </Argument> <LightNumber/> <Result> <On/> <OnRequest> <bri/> <Target>[:RuntimeParams.FacetIDName:]</Target> <hue/> <Process> <sat/> </Argument> <Message> <Result> <Value>[:$Macro.Argument.QueryString:]</Value> <ExecuteQuery> </Message> <QueryString> </Process> <DeleteAll> </OnRequest> </Result> <PhilipLightModel> </Macro> <Macro Name="AddLight"> <Argument> <LightNumber eq="[:$Macro.Argument.LightNumber:]" <LightNumber/> <On/> </PhilipLightModel> <bri/> <hue/> </DeleteAll> <sat/> </Argument> <Create> <Result> <ExecuteQuery> <PhilipLightModel> <QueryString> <LightNumber>[:$Macro.Argument.LightNumber:]</LightNumber> <DeleteAll> <state> <PhilipLightModel> <LightNumber <On>Eq="[:$Macro.Argument.OnLightNumber:]</On>"/> </PhilipLightModel> <bri>[:$Macro.Argument.bri:]</bri>DeleteAll> <Create> <hue>[:$Macro.Argument.hue:]</hue> <PhilipLightModel> <sat><LightNumber>[:$Macro.Argument.satLightNumber:]</sat>LightNumber> <State> </state> <On>[:$Macro.Argument.On:]</PhilipLightModel>On> </Create> </QueryString><bri>[:$Macro.Argument.bri:]</bri> </ExecuteQuery> </Result> </Macro> <hue>[:$Macro.Argument.hue:]</hue> <Def Name="PhilipLightState"> <Boolean Name="On"/> <Integer Name="bri"/> <sat>[:$Macro.Argument.sat:]</sat> <Integer Name="hue"/> <Integer Name="sat"/></State> <String Name="effect"/> </PhilipLightModel> <Double Name="xy" cardinality="2"/> <Integer Name="ct"/></Create> <String Name="alert"/></QueryString> <String Name="colormode"/> </ExecuteQuery> <Boolean Name="reachable"/> </Result> </Def>Macro> <Def<ThingFacet Name="PointSymbolPhilipLightFacet"> <String nameName="1" default="noneLightNumber"/> <PhilipLightState <String nameName="2State" defaultKnownBy="nonePhilipLightAction"/> <String nameName="3" default="noneType"/> <String nameName="4" default="none"modelid"/> <String nameName="5" default="none"uniqueid"/> <String nameName="6" default="none"swversion"/> <!-- Helper Tags --> <String name="7" default="none"/> <AA>[:#o#Event.Argument:]</AA> <Action <String nameName="8PhilipLightAction" defaultDocumentation="none"/>Set the state of the Light </Def>(true/false)"> <ThingFacet<Workflow NameLimit="PhilipLightFacet1"> <String Name="LightNumber"/>Live="1" Timeout="-1"> <PhilipLightState <Task Namename="stateMain" KnownByWhile="PhilipLightActionUsingCurlTrue"/> <Event <String Namename="Argument" as="TypeActionArgument"/> <String <Invoke Name="Name"/"SetState" waitFor="ActionArgument" PUT="[:PhilipLightBaseURI:]/lights/[%:[:AA:].LightNumber.Value:%]/state"> <String Name="modelid"/> <Message <String Namexmlns="PLight" Type="uniqueidjson"/> <String Name="swversion"/> <PointSymbol Name="Pointsymbol"/><on_true> <AA>[:#o#Event.Argument:]</AA> <Ontrue>{"on":true}</Ontrue><on>true</on> <Onfalse>"on":false</Onfalse> </on_true> <Action Name="PhilipLightActionUsingCurl"> <Workflow Limit="1" Live="1" Timeout="-1"><on_false> <Task name="Main" While="True"> <on>false</on> <Event name="Argument" as="ActionArgument"/> </on_false> <Invoke name="SetState" waitFor="ActionArgument" <Value> execute="curl -X PUT -d '{"on":[%:[:AA:].state.On.Value:%]}' [:PhilipLightBaseURI:]/lights/ <Include>[:@WFRT:]on_[%:[:AA:].state.LightNumberOn.Value:%]/state"/></Include> </Value> </Message> <Log Message="[:Invoke.SetState:]"/> </Invoke> <Output name="Result" as="ActionResult"> <Value> <State> <state> <On>[%:[:AA:].state.On.Value:%] </On> </state> State> </Value> </Output> </Task> </Workflow> </Action> </ThingFacet> <ThingFacet Name="PhilipLightManagerFacet"> <String Name="LoadLights" KnownBy="LoadLightsAction"/> <Action Name="LoadLightsAction"> <Workflow Limit="1" Live="1" Timeout="-1"> <Task Name="Main" While="True"> <Event Name="Argument" asAs="ActionArgument"/> <Invoke Name="GetLights" waitFor="ActionArgument" Get="[:PhilipLightBaseURI:]/lights"/> <Log Message="Lights Loaded...[:Invoke.GetLights.Message.Value:]"/> <Invoke Name="ParseGetLightsResult"> <FacetScript> <AddLight> <LightNumber>1</LightNumber> <On>[:Invoke.GetLights.Message.Value.1.State.On:]</On> <bri>[:Invoke.GetLights.Message.Value.1.State.bri:]</bri> <hue>[:Invoke.GetLights.Message.Value.1.State.hue:]</hue> <sat>[:Invoke.GetLights.Message.Value.1.State.sat:]</sat> </AddLight> <AddLight> <LightNumber>2</LightNumber> <On>[:Invoke.GetLights.Message.Value.2.State.On:]</On> <bri>[:Invoke.GetLights.Message.Value.2.State.bri:]</bri> <hue>[:Invoke.GetLights.Message.Value.2.State.hue:]</hue> <sat>[:Invoke.GetLights.Message.Value.2.State.sat:]</sat> </AddLight> <AddLight> <LightNumber>3</LightNumber> <On>[:Invoke.GetLights.Message.Value.3.State.On:]</On> <bri>[:Invoke.GetLights.Message.Value.3.State.bri:]</bri> <hue>[:Invoke.GetLights.Message.Value.3.State.hue:]</hue> <sat>[:Invoke.GetLights.Message.Value.3.State.sat:]</sat> </AddLight> </FacetScript> </Invoke> <Output Name="Result" asAs="ActionResult"> <Value> <LoadLights>True</LoadLights> </Value> </Output> </Task> </Workflow> </Action> </ThingFacet> <ThingModel Name="PhilipLightModel" Combines="PhilipLightFacet"> <Sid Name="LightId"/> </ThingModel> <ThingModel Name="PhilipLightManagerModel" Combines="PhilipLightManagerFacet"> <sid<Sid Name="ManagerId"/> </ThingModel> </Domain> </Namespace> |