Description of the configuration using the relay output as an example.
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
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:
We leave the remaining parameters as default.
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):
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.
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:
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.
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:
-- 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)
-- 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.
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:
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)
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()
-- 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.
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:
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:
-- 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.
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:
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()
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.
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.