Stop your plants from dying - Part 2: Slack Integration
Tags: Slack, Ttn, Slackbot, Expressjs

In the first part of this series, I described how a LilyGo TTGO board could be used to measure, interpret and send data to TTN with an external soil moisture sensor. My goal though was to send that data directly to my smartphone, so I would never forget to water my plants. See how I managed to gather and upload the humidity data into our company Slack channel.

Also, make sure to check out part 1 here and feel free to take a look at the source code.

Introduction

Checking the TTN console every few days was no option for me. However, Slack offers a nice API enabling bot users to share any data they would want to. All you need is an application registered on Slack as well as something deciding when and what data to share. I thought a NodeJS Express server should do the trick.

If you would like to send the status to any other messaging app, you might want to try Discord or Threema. They both offer similar solutions, where bots can post into channels (Discord) or directly to any other user (Threema). But let’s first take a look at how we can schedule, receive and process downlink messages from TTN.

Scheduling Downlinks

After creating my application in TTN I added the webhook integration. That integration allows me to send JSON data to any web address based on events in the TTN application. I chose to forward all the incoming uplink messages from my sensor to make sure I’ll receive every humidity value.

At first, I played around with RequestBin, a quick and easy way to dump requests, to find out what data will be sent in a downlink. I shortened the whole JSON data I received due to simplicity (and privacy, since I do not want to share my EUI’s), but roughly that’s what I received.

If you take a look at the above payload, you’ll quickly see that the decoded humidity also mentioned in the first blog post is contained there. Also in there is the sensor’s name assigned via the TTN console. So basically that is all I need to be sure which plant has which humidity. So let’s build a Node.js server with Express added.

Slack API Prerequisites

So after taking a look at Slack’s API, I figured out that I need a Slack app that can act as a bot user for my automated requests and is able to send messages. Even though Slack offers to send messages acting as regular users I decided to use a bot. Slack provides detailed documentation on the requirements for automated messaging here and provides information on how to create such an app here.

After creating the app I started looking at Slack’s post message API method. Three things are needed to place a call there. First, an authorization token received from the Slack app settings in the OAuth & Permissions section. Secondly, the channel ID can be gathered from the channel’s info in Slack directly. And finally the required permissions within the Slack channel you want to send it to. Since those might change, take a look at what is needed in the API doc linked above and then apply them.

Build the Express server

Now all requirements are met to start sending messages from the bot user. Since creating a basic Node.js Express server is already covered on their website, I won’t go into too many details about the creation process itself. Also on there is a hello world application for express and that’s where I started to build my application on top.

Slightly modified version of the Hello World example on expressjs.com

To test, if Slack will accept my messages I first tried to just send a basic Hello Slack before dealing with TTN’s downlink messages. So I included Axios in my project to send HTTP requests and set up a post request to Slack’s post message URL. I also decided to deal with post requests on a separate URL /ttn, but that’s completely optional.

The Express server can be run with node <path-to-your-index.js> and will then listen for incoming requests on localhost:8080 . Since the request is not considered in the above code, I could just send an empty POST request to that URL with cURL.

curl --header "Content-Type: application.json" --request POST --data '{}' http://localhost:8080/ttn

And well the Express server does his job and places the message in a Slack channel I set up for testing.

Dealing with a TTN downlink

Sending messages to Slack works. Let’s now take care of the incoming downlink messages from TTN. As mentioned before the downlink is in JSON format and can therefore be easily dealt with in JavaScript. However, I needed to add Express’ built-in JSON converter (see line 15) to be able to decode the data correctly.

I first read out the device ID and the humidity attached to the payload and slightly adjusted the text object in my request to Slack to feature the device ID and humidity. I used the below request to send data to the Express server.

curl --header "Content-Type: application/json" -X POST --data '{"end_device_ids": { "device_id": "ttgo-sensor-02" }, "correlation_ids": [ ], "received_at": "2021-08-18T10:55:40.850879628Z", "uplink_message": { "session_key_id": "AXtY5vJ/Cc851E1ah8SFHQ==", "f_port": 1, "frm_payload": "RHJ5AAAAAABwH/s=", "decoded_payload": { "humidity": "Dry" }, "rx_metadata": [ { } ], "settings": { }, "received_at": "2021-08-18T10:55:40.647503177Z", "consumed_airtime": "0.061696s", "network_ids": { } } }' http://localhost:8080/ttn

And that resulted in the following message in Slack.

Even though the basic principle is already working, I didn’t want Slack to send a message every two minutes with hardly any effects. Therefore I implemented a key, value map to deal with multiple sensors and their values.

With that out of the way, there’s one thing still bothering me. The message looks very basic and if you know Slack you probably have already seen fancy bot messages which look more like a canvas than an actual text message. I do not fancy a canvas, but I wanted to try applying some styling to the message.

Styling the Slack message

Slack’s messaging API supports markdown, which enables basic text styling. The API also supports preformatted blocks featuring headers, images, etc. I’ll add a header as well as a dynamic image for each plant sensor, so each plant has its own visually supported style.

To get a header I used the built-in header block, which just makes the font a little bigger and bold. To also feature an image in the message I defined a separate function returning the image URL. To prototype this I used some images from Unsplash, though you could also put in any other pictures. The adapted code sample looks like this now.

To test the final stylized version I used the same cURL command mentioned above. And this is how it looks in Slack:

Conclusion

I explained in this blog post how I connected TTN to Slack via a self-developed Express server. In the end, I also showed how you can style Slack messages to display different images for different plants. What I also did, was deploying the application via a docker container on our server. Though, I won’t explicitly cover this here due to brevity and many other sources covering a docker deployment already. However, I feel that there’s still one major flaw about the whole project — Battery consumption. Right now it is not possible to power the sensor’s with a battery for longer than 4 days. Hence stay tuned for the third part.

Before you leave: take a look at the full source code here and also make sure to check out the first part of this series, if you haven’t done already. Please also feel free to share your thoughts down below.