Comprehensive integration with external systems

Description of the configuration using the relay output as an example. 

System

Assume we have a simple system consisting of the following elements:

  • Z-Wave CLU - named CluZ
  • Relay module - for the purpose of the example we will use one output named Relay
  • Gate Http - named GateHttp


Output control

In order to be able to control the relay output from an external system, we create a new object of type HttpListener on GateHttp and configure as below:

  • Name: RelayControlListener
  • Path: /relaycontrol

We leave the remaining parameters as default.


Script

In order for the RelayControlListener object to work, a script must be created to handle incoming Http requests.

 

Here it is worth noting that from this script we have access to the entire system and all its functionality. This opens virtually unlimited possibilities, but also generates some risks, especially if the Gate's functionality is not well thought out. Therefore, we pay particular attention that the implementation of Gate's functionality should be well planned, the way we want to achieve and how the Gate's action may depend on or affect other elements of the system. Examples of this approach will also be discussed further on.

 

Back to the Relay control script. We want to be able to turn Relay on or off by sending it the expected state (On/Off) or by executing a switch method. This approach to the implementation allows you to connect both bistable and monostable switch type control.

 

Moving on to action, we create a script on GateHttp called RelayControlOnRequest, and in the text mode of creating scripts, we enter what follows:

-- RelayControlOnRequest()
local data = GateHttp->RelayControlListener->QueryStringParams
if data == nil then
  CluZ->Relay->Switch(0)
else
  if data.cmd == "setValue" then
    local val = tonumber(data.val)
    if(val == 1) then
      CluZ->Relay->SwitchOn(0)
    elseif(val == 0) then 
      CluZ->Relay->SwitchOff(0)
    end
  end
end

GateHttp->RelayControlListener->StatusCode = 200
GateHttp->RelayControlListener->ResponseBody = "OK"
GateHttp->RelayControlListener->SendResponse()

 

Next, we assign the script to the OnRequest event of the RelayControlListener object and send the configuration to the system.

 

The above script gets the values of request parameters from the RelayControlListener object and - depending on what them contain - performs the appropriate actions. Then it sends back the operation status to the client - in this case 200, OK.

 

You can easily test the operation using a regular web browser by entering the following URLs (the IP address should be replaced by the real address of your Gate Http):


http://192.168.88.4/relaycontrol?cmd=setValue&val=1 - turn the Relay on

http://192.168.88.4/relaycontrol?cmd=setValue&val=0 - turn the Relay off

http://192.168.88.4/relaycontrol - switch the Relay’s state


As we see, we can use the Listener object in two ways. If the parameter "cmd" (command to execute) and "val" (the value to set) are properly defined then they set the specific state of the Relay. If we omit these parameters from the URL, the object acts as a switch.

 

The above example can be further extended by adding more commands if there is a need to execute other commands. You can also add more parameters identifying the object on which the commands should be executed.



Retrieving the state


In the previous step, we made it possible to control an object in the system from the outside. Very often in the next step there appears a need to provide also a possibility to retrieve the current state of this object.

 

One of the fastest and most intuitive methods (not necessarily the best one) is to define another Listener, which retrieves a Value from Relay object and sends it to a client. The simplest script that implements this functionality may look like the one below.

-- RelayStateOnRequest()
GateHttp->RelayState->StatusCode = 200
GateHttp->RelayState->ResponseBody = "Relay State: "..CluZ->Relay->Value
GateHttp->RelayState->SendResponse()

 

Entering the following URL into the browser you can see that you get the response with the state of the Relay object (in a simple text form, but the format of sending the data is not the topic of this example).


http://192.168.88.4/relaystate  - returns "Relay State: 0" or "Relay State: 1" depending on the state of the object.


The above example works fine at first glance but let's take a closer look.



Sequence of events


We have just build the Http interface (API) two methods:

  • /relaycontrol - allows to control the Relay object
  • /relaystate - returns the current state (value) of the Relay object

 

After a first testing everything works fine, but as we wrote above, we still need to think about how such methods will be used. It is easy to imagine that in the external system these two methods will be used right after each other: calling the switch action and after receiving the response, reading the state in order to confirm that the action occurred and to synchronize the status.

 

And here an unexpected action of the system may occur, Relay is switched on but the returned status is 0, that is incorrect. The reason for this is that these operations are performed asynchronously on two different devices. There is no guarantee that the Relay's state change operation will be performed before its state is queried. Calling the state change action in the RelayControlOnRequest() script is called asynchronously, which means that the script does not wait for CluZ to perform the task.

 

The considered case is very simple and practically always works, but in case of more complex operations (where different target objects are involved, and in order to perform the operation it is still necessary to exchange data, send user features, etc.) the risk that the status will be collected before the real change of the object(s) state is realized. In complex systems we often observe such effects.


Event synchronization


The above problem can be solved by forcing the RelayControlOnRequest() script to wait until CluZ actually efforts a state change action on the target device. This can be easily achieved using the clu.await() function. E.g.: call:

CluZ->Relay->Switch(0)

is replaced with:

clu.await(CluZ->Relay->Switch(0))

After this change, Listener will not send "200, OK" confirmation before the action on CluZ is actually executed. Therefore, the client using this interface will not be misled by too fast confirmation of task execution.

 

However, the clu.await() function has a limitation. The timeout for the call execution is 800 ms and if the task is not completed within this time, the script will terminate with a timeout. The Http client will get the following Http error: "500 Internal Server Error".

 

In most cases such timeout is not a problem and the system will work correctly but in case of complex operations and/or when CluZ will be loaded with other tasks this may happen. How to solve the problem in such case is described in the next section.

 

Confirmation back


In systems where we care about high reliability and stability of the integration operation, the response of the Http Listener should be delayed until it receives a direct confirmation from CluZ that the task has been completed.

For this purpose, we split the operation into two stages. Instead of one script performing the whole task, we define two of them: the first one performs the task, the second one sends a Http response after CluZ confirms that the task has been completed.

 

For clarity, we define a new script and assign it to the RelayControlListener:

  • OnRequest event: GateHttp->SplitSyncOnRequest()

The SplitSyncOnRequest()  script looks as follows: 

-- SplitSyncOnRequest()
local data = GateHttp->RelayControlListener->QueryStringParams
if data == nil then
  CluZ->SplitSyncCluzTask("Switch")
  return
else
  if data.cmd == "setValue" then
    local val = tonumber(data.val)
    if(val == 1) then
      CluZ->SplitSyncCluzTask("On")
      return
    elseif(val == 0) then 
      CluZ->SplitSyncCluzTask("Off")
      return
    end
  end
end

GateHttp->RelayControlListener->StatusCode = 400
GateHttp->RelayControlListener->ResponseBody = "Bad request"
GateHttp->RelayControlListener->SendResponse()

 

In each place of the script when we delegate a task to CluZ (this time through an additional script, which will be discussed in a moment) we finish the operation of our script, without sending Http response to the client. If the script execution comes to the end lines, it means that the request was not interpreted correctly and we send back the error "400, Bad request". Additionally, we have added another level of protection against invalid call parameters.

 

At this moment, the task is not executed directly on the target Relay object (as before) but delegated to the script on CluZ named SplitSyncCluzTask(action: string). The notation used means that the script is called with a parameter named "action", which is of type "string". Notice that this is not LUA notation where we do not define the type of the function call parameter. The "action" parameter defines a specific action to call on the Relay object. The action is identical to the previous case.

-- SplitSyncCluzTask(action: string)
if(action == "On") then
  CluZ->Relay->SwitchOn(0)
elseif (action == "Off") then
  CluZ->Relay->SwitchOff(0)
elseif (action == "Switch") then
  CluZ->Relay->Switch(0)
else
  -- Unknown action
  GateHttp->SplitSyncRequestCompleted(false)
  -- Return to avoid double completion 
  return
end
GateHttp->SplitSyncRequestCompleted(true)

 

Depending on the defined action, an appropriate method is executed on the Relay object. At the end we inform GateHttp that we have completed the task and a response must be sent back to the client. For this purpose, a method has been created in GateHttp called SplitSyncRequestCompleted(success: boolean) that takes a Boolean value as parameter: true if the action was successful, false otherwise.

-- SplitSyncRequestCompleted(success: boolean)
if success then
  GateHttp->RelayControlListener->StatusCode = 200
  GateHttp->RelayControlListener->ResponseBody = "OK"
else 
  GateHttp->RelayControlListener->StatusCode = 405
  GateHttp->RelayControlListener->ResponseBody = "Not allowed"
end
GateHttp->RelayControlListener->SendResponse()

 

GateHttp through the above method sends a response to the client indicating success or failure depending on the parameter received. In this way we have implemented a fully synchronous Http method that has no time limit on its operation. In more advanced cases it is possible to improve the system operation even more by calling the SplitSyncRequestCompleted(success: boolean) function in response to events informing about a change in the value of a particular object. This solution provides reassurance that a change has occurred and further increases the stability of the system.

 

In case of receiving data from external systems, always use the method of limited trust as to its correctness. We recommend not to pass the values directly to methods and scripts inside the system but to use specific actions depending on the values of methods as you can see in the above scripts. If you need to directly use variables received from outside, pass them through user features (which are addressable throughout the Grenton system and can be freely transferred between CLU devices). Additionally, each variable received externally should be validated in the script for correctness, value, range. Lack of proper verification of received values may cause unexpected operation of the system, open access to unwanted functionality and even cause errors and CLU going into emergency mode.

 

Timeout


The created Listener already works almost reliably. Why almost? Let's consider what happens if CluZ for some reason never calls the SplitSyncRequestCompleted(success: boolean) method. GateHttp is then left waiting for the current request to complete and stops responding to subsequent requests.

Of course, in a well-configured system, this should not happen. However, an unexpected situation may always occur, and that is why each element of the system should be configured so that it works as independently as possible and is resistant to errors in other areas. Therefore, our Listener should also be fully resist to such situations.

For this purpose, we will define a Timer object on GateHttp that will make sure that the wait for the CluZ response does not go on indefinitely. The parameters of the new object:

  • Name: SplitSyncTimeout
  • Event OnTimer: GateHttp->SplitSyncTimeoutOnTimer()
  • Time: 3000 - here the time should be chosen according to the specific situation, for the purpose of the example we assume 3 s (3000 ms)
  • Mode: CountDown

 

The script executed after the specified time looks as follows:

-- SplitSyncTimeoutOnTimer()
GateHttp->RelayControlListener->StatusCode = 408
GateHttp->RelayControlListener->ResponseBody = "Timeout"
GateHttp->RelayControlListener->SendResponse()

 

The operation of the script is quite simple, it returns a "408, Timeout" error.

 

To make it work you need to modify the SplitSyncOnRequest() and SplitSyncRequestCompleted(success: boolean) scripts accordingly.

-- SplitSyncOnRequest()
local data = GateHttp->RelayControlListener->QueryStringParams
if data == nil then
  CluZ->SplitSyncCluzTask("Switch")
  GateHttp->SplitSyncTimeout->Start()
  return
else
  if data.cmd == "setValue" then
    local val = tonumber(data.val)
    if(val == 1) then
      CluZ->SplitSyncCluzTask("On")
      GateHttp->SplitSyncTimeout->Start()
      return
    elseif(val == 0) then 
      CluZ->SplitSyncCluzTask("Off")
      GateHttp->SplitSyncTimeout->Start()
      return
    end
  end
end

GateHttp->RelayControlListener->StatusCode = 400
GateHttp->RelayControlListener->ResponseBody = "Bad request"
GateHttp->RelayControlListener->SendResponse()


Each time we delegate a task to CluZ we start the SplitSyncTimeout timer.

-- SplitSyncRequestCompleted(success: boolean)
if(GateHttp->SplitSyncTimeout->State == 1) then
  GateHttp->SplitSyncTimeout->Stop()
  if success then
    GateHttp->RelayControlListener->StatusCode = 200
    GateHttp->RelayControlListener->ResponseBody = "OK"
  else 
    GateHttp->RelayControlListener->StatusCode = 405
    GateHttp->RelayControlListener->ResponseBody = "Not allowed"
  end
  GateHttp->RelayControlListener->SendResponse()
end

 

In the SplitSyncRequestCompleted(success: boolean) script we first check if the timer is still in state "1" (enabled). This prevents unnecessary sending of the response in the situation, when the timeout has already occurred - the response time is over and the response informing about the error "408, Timeout" has been sent. If the timer is still running (normal situation, response timeout has not run out) we stop the timer and continue as before.

 

Multiple objects


Let's return for a moment to the method that retrieves Relay's state. In particular, let's take another look at the following line:

GateHttp->RelayStateListener->ResponseBody = "Relay State: "..CluZ->Relay->Value

 

The key point here is to retrieve the value of the Relay object's Value property:

CluZ->Relay->Value

 

This method works well but you should be aware that the value of this variable is retrieved when the script is executed. It results in communication between GateHttp and CluZ via the network. It is a synchronous call, i.e. the method waits for the response with the value of the Relay object. We already know about some limitations of such call. In this particular case there are even more threats. The value of this property is retrieved each time the client asks for its value through the Http interface, which generates unnecessary traffic in the system. Additionally, it causes unnecessary delay in the system. If there are many such queries, it may affect the system performance. In some especially simple cases it is acceptable and the system will cope with it well. But not always.

 

Let's imagine that there are many objects in the system and we need to provide in response the statuses of all of them (in JSON or CSV form). If in such a case we use the same method, the script performing such a task may look more or less like the one below:

GateHttp->RelayState->StatusCode = 200

local response = CluZ->Relay01->Value
response = response .. "," .. CluZ->Relay02->Value
response = response .. "," .. CluZ->Relay03->Value
response = response .. "," .. CluZ->Relay04->Value
response = response .. "," .. CluZ->Relay05->Value
response = response .. "," .. CluZ->Relay06->Value
response = response .. "," .. CluZ->Relay07->Value
response = response .. "," .. CluZ->Relay08->Value
response = response .. "," .. CluZ->Relay09->Value
response = response .. "," .. CluZ->Relay10->Value
response = response .. "," .. CluZ->Relay11->Value

GateHttp->RelayState->ResponseBody = "System State: ".. response
GateHttp->RelayState->SendResponse()

 

In a real system, there may be many more relay objects. Each line makes a request to CluZ for the state of the Value feature over the network. Collecting the state of all objects can take quite a while. This situation delays the response significantly and blocks GateHttp for the duration of the operation.

 

A series of requests occurs each time a client queries about the system state. In most cases, the value of a variable between queries only changes for one object (for the one just changed object). All this causes a lot of unnecessary traffic and negatively affects the speed of the system. From the point of view of the end user, the system may in such cases work unstably, have unexpected delays, freeze for a short or longer period of time and even omit some events.

 

State for a complex system


In order to solve the above problem, it is necessary to approach the task of retrieving device state a little differently. Further in this section, for simplicity of examples, we will return to a single Relay object, but the provided method will work for practically any number of objects.

 

Let's consider that instead of querying the remote CluZ for the state of a Relay object every time when the client asks for it, we could keep its value locally in a GateHttp user feature. That way when the client queries without any delay we return its value immediately without any delay. Let's call it RelayValueOnGateHttp. What is more, we would like to eliminate all the queries that synchronize its value and get information only when it is needed, that is when the value of the CluZ->Relay->Value  property changes. To achieve this we assign the following command to the OnValueChange event of the Relay object:

GateHttp->RelayValueOnGateHttp=CluZ->Relay->Value

 


Which means: Every time the state of the Value feature changes, assign that new value to the RelayValueOnGateHttp user feature on GateHttp. From now we will always have the current value of the Relay object on the GateHttp side. At the time of the request, we simply send this value to the client. To do this we modify the RelayStateOnRequest() script as follows:

-- RelayStateOnRequest()
GateHttp->RelayState->StatusCode = 200
GateHttp->RelayState->ResponseBody = "Relay State: "..GateHttp->RelayValueOnGateHttp
GateHttp->RelayState->SendResponse()

 

As mentioned earlier, this can be used for any number of objects and does not cause any negative impact on system performance because only changes to individual values are communicated when they occur.

 

Push notifications


Going a step further on the road to perfect integration, let us implement one more improvement. Until now, the customer himself had to ask every now and then whether anything had changed in the system. If the system is to be responsive, such queries must take place frequently. Frequent queries generate unnecessary traffic and increase the risk of delays, especially in the handling of events very sensitive to delays, such as switching on lights, where the user immediately feels that the action did not take place immediately after touching the button.

 

In addition, the customer is not immediately notified of a change in the system, but only when he asks if anything has changed.

 

The solution is the state push method, where the system itself actively sends notification about a change in the state of a device in the system. In order to implement such a mechanism, we create a new GateHttp object of the HttpRequest type:

  • Name: StatePushNotification
  • Host: IP:Port serwera http nasłuchującego informacji o zmianach stanu
  • Path: /statechanged
  • Method: PUT

The rest of the settings remains unchanged.

 

Then we add a new script SendStatePushNotification(newValue: number):

-- SendStatePushNotification(newValue: number)
GateHttp->StatePushNotification->SetQueryStringParams("val="..newValue)
GateHttp->StatePushNotification->SendRequest()

 

To inform the client about the new status we need to call the script passing the new value of the Value feature as a parameter. The best way of it is to make a change in OnValueChange event of the Relay object. Since we assigned the value a step earlier to the GateHttp->RelayValueOnGateHttp user feature, we can use it to avoid unnecessary resending of this value. So the assignment would look as follows:


GateHttp->SendStatePushNotification(GateHttp->RelayValueOnGateHttp)

Note that the copying of the value to the RelayValueOnGateHttp variable must occur first.

 

From now, whenever the state of the Relay object's Value feature changes, a notification with the new value will automatically be sent.

 

The selected method sends the new value as a URL parameter but you can format the response any way you like and send it in the message body by setting the value using the SetRequestBody(value) method.

 

 

Summary

We have discussed the basic aspects of integrating the Grenton system using Http Gate. We discussed typical problems that may occur with such configurations and methods for solving them. By following these guidelines, you can meet any complex requirements and implement a complex system to make it stable, reliable and fast. It should be remembered that the Gate Http module opens unlimited possibilities of cooperation with the system, and you can use it to perform any operation, even disadvantageous. Therefore, it is important that the Gate Http configuration is carefully considered and performed with the utmost care.