In the previous article we clarified the idea of the challenge and set up a demo machine. In this article we take on the implementation of the first route of events in the tapio-IFTTT-Connector: From IFTTT to tapio-ready machines.
As previously specified we want to use the IFTTT Webhook service so that when an applet using the Webhook service is triggered we will receive a HTTP request at the endpoint specified in the applet. We then want to process the request and forward the event to the machine using tapios Commanding API. The Commanding API is typically used to interact with OPC UA servers running on tapio-ready machines.
When we recognized that our tapio-IFTTT-Connector simply has to receive a HTTP request, then process it and finally make another HTTP request we opted for a serverless implementation approach. As you can tell by its name there are no servers in a serverless architecture but rather snippets of code which execute on certain conditions. Of course these snippets still run on a server but not on our servers. This way we can safe money because we don't have to operate or rent a server 24/7 and in addition we don't have to worry about setting up a server, installing a runtime environment etc. There are multiple products available for implementing serverless architectures. We picked Azure Functions from Microsofts cloud platform Azure.
When we started coding the desire for proper debugging arised rather quickly. So how does one debug an Azure Function? We can't simply establish a debugging session to our deployed function. Instead we have to run the Azure Function locally and forward the HTTP requests from IFTTT to our local machine and this is where ngrok came in handy.
With ngrok we're able to expose a local server behind a NAT or firewall to the internet and therefore IFTTT and it's dead simple:
We only have to register on ngrok, download the executable and create ourselves an authentication token (replace some_token
with the key provided for our account on ngrok).
Now we're already able to expose a local port. In the example below we're exposing the local port our Azure Function is using:
After running the above we're given a monitor screen in our terminal. Behind the Forwarding
keyword we can see active tunnels. In our example below our local Azure Function is now exposed behind http://qyt7q40w03.ngrok.io
. We can verify if everything is working correctly by opening the ngrok URL in our web browser (sending a GET request). As we can see at the bottom of the output a GET request was logged as expected. It works! If you'd like to have a deeper look in incoming requests you can explore ngroks web interface running locally on port 4040
.
To actually develop and debug with real HTTP requests from IFTTT we have to create an applet with a trigger under our control and an action which sends HTTP requests to our local Azure Function exposed through ngrok. Therefore we create an applet which sends a POST request into our ngrok tunnel when we press a button on our smartphone:
Due to our limited time frame we opted for a plain text body with a simple event name to lower complexity. Further down the line one could however transmit full blown JSON objects.
Now for the good stuff: The implementation of the Azure Function. The code below basically waits for GET or POST requests, parses the body of the request, interprets it as event and then forwards it to our machine using the Commanding API.
The Commanding API is normally used to alter item values or call methods on a OPC UA server associated with the CloudConnector but we figured we can use an item write request as well to transmit an event. On OPC UA server side we then just have to wait for item state changes and interpret them as events.
The EventFactory
class is responsible for mapping event data from IFTTT to our generic event model:
Due to time constraints we didn't finish the implementation of the support for events with payload. However being able to transmit any payload would for example enable an IFTTT user to transmit complex statements like these to his machine.
If we tried to test our system at this point events wouldn't reach their target machine because we didn't modify the tapio CloudConnector XML configuration of the machine yet. By default we can't access any OPC UA node through Commanding API for security reasons until we configure the access in the configuration file.
In the example below we extend the configuration of DataModule01
with our OPC UA event processor server SensorServer
which has an item write command configured: ProcessEvent
.
Now we're almost good to go. Events will now be forwarded to the OPC UA server running on our demo machine. But this server still lacks the configured node and the logic behind it.
So lets add a DataVariableState
node of type String
to the address space of our OPC UA server. To also be able to process incoming events we have to be able to react to every single status change. We implemented that by attaching a custom event handler to the WriteCalled
event of our node:
In order to physically show that our demo machine has received an event we want to flash the LED attached to our demo machine in different ways. Therefore we have to implement an abstraction layer to control the LED connected through the GPIO interface of our demo machine.
So we have to define an interface for a LED to abstract the GPIO logic and create another one for a LED controller which is additionally capable of processing light sequences:
Now we have to implement the LED interface using the Unosquare.RaspberryIO NuGet package:
Now we're finally able to process events in our event handler by interpreting the new value of the node as event and acting differently based on the name of the event. This part would naturally be fully dynamic and the sequence processor would work in a proper version of the tapio-IFTTT-Connector which wasn't implemented in two days.
We're done!
We're now able to process events coming from IFTTT using just one Azure Function and a OPC UA server! In the next article in this series we're looking at the implementation of the reversed route: Forwarding events from a tapio-ready machine to IFTTT.