Quarkus application

The last piece of the puzzle is a customer provided application. The application that wants to talk to the device, in order to provide some value-add.

For implementing this, we choose Quarkus. Java is great for business applications, there are plenty of tutorials on how to build and interface Quarkus application with enterprise systems. So in this workshop, we will focus on bridging the gap between enterprise and IoT.

The core idea here is that you use Drogue IoT cloud as a service. That means, we don’t plan on deploying our application inside the same cluster, but on a separate machine or cluster. Technically you can still do that! We just try to follow a more "as-a-service" approach, and you might see in a minute, that this also makes things easier, as you can run the application in your local machine, use our sandbox, and still be connected to the public TTN network.

We will be using the MQTT integration of Drogue IoT. This makes things easy, as we don’t need to bridge two Kafka clusters, and still can have multiple consumers. Also, it is possible to just re-use all your tools around MQTT that you already may know about, for testing and debugging.

Creating a Drogue IoT cloud API token

In order to get access to the application through the MQTT integration, we need an OAuth token or API token. As you need to periodically refresh an OAuth token, and most MQTT clients have no idea about that, we choose an API token here[1].

Getting a new API token currently requires to use a command line HTTP client, like HTTPie or curl. It is a simple operation though, and we will use drg whoami --token to acquire a fresh OAuth token for accessing the API.

The following examples require you to replace <api-endpoint> with the actual API endpoint. You can get this from the web console, from the page named "Home":

Screenshot home page showing the API endpoint information

The API endpoint URL is located in the box "API" in the "Services" column.

The following examples will use the command jq to pretty-format that the JSON result of the commands. If can’t use jq, you can also omit it as is it only used to improve readability of the result.

Create a new API token

curl -vs -H "Authorization: Bearer $(drg whoami --token)" -XPOST <api-endpoint>/api/tokens/v1alpha1 | jq

The output should look something like:

{
  "prefix": "drg_g0yAUq",
  "token": "drg_g0yAUq_kwjRLA40hrt81bbKdGbcDOmlq2WASx6UyQi"
}

The value of the field token is the actual API token. You will not be able to recover this token at a later time. So you need to note (copy) it somewhere. The "prefix" is used to identity the token, so that you can easily delete it later on.

List API tokens

You can also list your existing API tokens using:

curl -s -H "Authorization: Bearer $(drg whoami --token)" <api-endpoint>/api/tokens/v1alpha1 | jq

Which should provide you can output like:

[
  {
    "prefix": "drg_g0yAUq",
    "created": "2021-04-28T08:42:59.336402353Z"
  }
]

As you can see, the actual token is no longer part of the result.

Deleting API tokens

If you need to delete a token, you can easily achieve this using the DELETE verb:

curl -s -H "Authorization: Bearer $(drg whoami --token)" -XDELETE <api-endpoint>/api/tokens/v1alpha1/drg_g0yAUq

Finding your username

In addition to the API token, you will also need to know your username.

You can check your username using the Web Console. In the top right corner of the console, you will find the user menu, which shows you your username:

Screenshot of profile menu

Preparing the application

While we provide a ready to run container of this application, this workshop plans to make changes to the source code.

While all the following steps are possible without an IDE, you may wish to set up of your favorite IDE alongside the process. The tutorial doesn’t require any IDE specific settings or tasks, everything could be done using plain Maven and the most basic text editor. But, feel free to make yourself comfortable.

We will start directly by cloning the source code of this example, and run it locally:

git clone https://github.com/drogue-iot/quarkus-mqtt-integration-example

Next, we need to insert the parameters for connecting the application to the MQTT integration:

Create the file src/main/resources/application-dev.properties and add the following content:

drogue.application.name=my-app (1)
drogue.api.user=my-username (2)
drogue.api.key=drg_g0yAUq_3z5pasdcasdeI4YqOP123hdsa821VAtFs4x (3)
drogue.integration.mqtt.host=mqtt-integration.sandbox.drogue.cloud (4)
drogue.integration.mqtt.port=443 (5)
1 The Drogue IoT application name
2 Your Drogue IoT username, as described in Finding your username
3 Your Drogue IoT application token, as described in Create a new API token
4 The hostname of the MQTT integration.
5 The port number of the MQTT integration.

While 443 might look strange here for MQTT, it will still work in the case of OpenShift, as OpenShift routes patch through TLS based TCP connections directly through to the endpoint.

Testing the setup

You can now start the application, like any other Quarkus application:

mvn quarkus:dev

The output should look something like:

[INFO] Scanning for projects...
[INFO]
[INFO] --------< io.drogue.iot.demo:quarkus-mqtt-integration-example >---------
[INFO] Building quarkus-mqtt-integration-example 1.0.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- quarkus-maven-plugin:1.13.2.Final:dev (default-cli) @ quarkus-mqtt-integration-example ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 4 resources
[INFO] Nothing to compile - all classes are up to date
Listening for transport dt_socket at address: 5005
__  ____  __  _____   ___  __ ____  ______
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/
2021-05-04 08:43:08,141 INFO  [io.quarkus] (Quarkus Main Thread) quarkus-mqtt-integration-example 1.0.0-SNAPSHOT on JVM (powered by Quarkus 1.13.2.Final) started in 1.512s. Listening on: http://localhost:8080 (1)
2021-05-04 08:43:08,144 INFO  [io.quarkus] (Quarkus Main Thread) Profile dev activated. Live Coding activated.
2021-05-04 08:43:08,144 INFO  [io.quarkus] (Quarkus Main Thread) Installed features: [cdi, mutiny, oidc-client, resteasy-reactive, resteasy-reactive-jackson, smallrye-context-propagation, smallrye-health, smallrye-reactive-messaging, smallrye-reactive-messaging-mqtt, vertx]
2021-05-04 08:43:08,366 INFO  [io.ver.mqt.imp.MqttClientImpl] (vert.x-eventloop-thread-0)  Connection with mqtt-integration-drogue-dev.apps.wonderful.iot-playground.org:443 established successfully (2)
1 The URL to the web console
2 Note the line "Connection … established successfully"

The application will keep running until you terminate it, by pressing Ctrl+C.

Testing it out

Navigate your browser to the web console, as shows in the previous step’s log output. It should look something like:

Screenshot of Quarkus application

Once you press the blue button on the board, you should see an incoming message, and with that, an outgoing message too.

Screenshot of Quarkus application

Try changing the response to led:on, and press the blue button again. The blue LED on the board should turn on, once the green, send indicator, LED turns off again.

It may be that the blue LED doesn’t turn on. Give it a second try, by pressing the blue button again.

Why is that needed? A short period after the uplink (device-to-cloud) message, the LoRa device switches into receive mode, awaiting an optional downlink (cloud-to-device) message. If that time window is missed, then the device will not receive the downlink message, and go back to sleep. We will deal with this later, so read on.

Understanding the code

Let’s take a quick tour through the code.

Processing

The main logic is in class io.drogue.iot.demo.Processor, and it is actually pretty simple:

@Incoming("event-stream") (1)
@Outgoing("device-commands") (2)
@Broadcast (3)
public DeviceCommand process(DeviceEvent event) {

    var payload = event.getPayload();

    LOG.info("Received payload: {}", payload);

    if (!event.getPayload().startsWith("ping:")) {
        return null;
    }

    var command = new DeviceCommand();

    command.setDeviceId(event.getDeviceId());
    command.setPayload(this.response.getBytes(StandardCharsets.UTF_8));

    return command; (4)

}
1 Annotation for consuming messages from the event-stream channel.
2 Annotation for delivering messages, coming out of this method, to the device-commands channel.
3 Indication that all consumers of the device-commands channel should receive the event.

This is required so that all browsers that are attached to the web frontend, and the device will receive the event.

4 The actual message we generated and want to send out.

Receiving events

The processing part already expects messages of the type DeviceEvent. This is an application specific Java message, which we don’t send out in Drogue cloud.

The conversion takes place in the class io.drogue.iot.demo.integration.Receiver.

It will take the incoming MQTT message, which is a Cloud Events message in structured content mode, as with Quarkus, we are using MQTT v3.

We decode the data section as a JSON encoded TTN uplink message, and extract the payload from it.

As with the Processor class before, the return the processed (converted) message. The returned message will be sent to the event-stream, so that both the Processor and any attached web browser will receive it.

Sending commands

The output of the Processor will be received by the io.drogue.iot.demo.integration.Sender class.

This class will construct the MQTT message, which contains the command for the device. It will publish this as an MQTT message, which will then be forwarded by Drogue cloud to the command endpoint for the device. Which in our case here is the downlink API of The Things Network.


1. API tokens don’t expire, while OAuth access tokens do. Even when you can refresh an access token using a refresh token, you still need to do this.