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":
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.
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 |
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:
Once you press the blue button on the board, you should see an incoming message, and with that, an outgoing message too.
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.