Sending Alerts from Kapacitor via MQTT

Originally published at: Sending Alerts from Kapacitor via MQTT | InfluxData

A long long time ago (in a galaxy far far away) I wrote about using this little GlowOrb I had to show alerts from Kapacitor. It was really pretty cool, and I’ve been using it for demos ever since to great effect. But it always seemed like a bit of a kludge to me to have to pipe it through a Node-Red endpoint to transform the alert into a color value to display on the GlowOrb.

As I was working on something else the other day—isn’t that how it always happens? You’re working on something, and you have this brilliant idea about something entirely unrelated and the next thing you know, you’re off chasing a squirrel! So I chased that squirrel and here’s what I came up with.

I posted to one of our internal Slack channels and said it would be awesome if I could send Kapacitor alerts to an MQTT broker, at which point I was informed that as of v1.4 I could do exactly that— turns out I didn’t know that because the ARM64 versions had stopped updating at v1.2. Something that has now been fixed. So if I could make it work, I could bypass the Node-Red instance and write my color alerts directly to the controlling MQTT broker.

[caption id=“attachment_213853” align=“aligncenter” width=“216”] My Happy Dance[/caption]

So off I went to make it happen.

Not so fast. Turns out it was harder than I thought. I read the docs —who does that?—and it still wouldn’t work. That’s because there was some significant information missing from the docs. Most importantly, you have to edit the kapacitor.conf file and uncomment the lines:

# MQTT client configuration.
# Mutliple different clients may be configured by
# repeating [[mqtt]] sections.
[[mqtt]]
 enabled = true
 # Unique name for this broker configuration
 name = "localhost"
 # Whether this broker configuration is the default
 default = true
 # URL of the MQTT broker.
 # Possible protocols include:
 # tcp - Raw TCP network connection
 # ssl - TLS protected TCP network connection
 # ws - Websocket network connection
 url = "tcp://localhost:1883

Turns out that’s a critical step in the process. So now I could get messages posted to my MQTT server. Step one done!

But how to post what information. Again, the docs were a bit sparse (I have a pull request against the docs coming shortly to make some of this more clear for everyone else who comes after).

So in my Chronograf instance I created a new TICK script—well, actually I began altering the one I wrote for that old post!—and I came up with this:

trigger
 |alert()
 .crit(lambda: abs("chng") > crit)
 .stateChangesOnly()
 .message(parseTemp)
 .id(idVar)
 .idTag(idTag)
 .levelTag(levelTag)
 .durationField(durationField)
 .mqtt('colorChange')
 .qos(1)

It does the trick, so I’ll walk through it for you. First, we’re triggering an alert() when the ‘change’ variable is greater than the value of ‘crit’. Simple enough. We’re going to post the message(parseTemp) with the id, idTag, levelTag, etc. included — none of that is actually necessary, as I’m only using the message() portion though. And I’m going to put this on the mqtt broker under the topic colorChange with the qos value of 1.

Now comes the tricky part. The parseTemp business. The GlowOrb expects to get messages that simply contain a color value as a Hex string. The Node-Red node did a simple transformation on the incoming value:

node.log(msg.payload.data.Series[0].values[0][2])
const currentTemp = msg.payload.data.Series[0].values[0][2];
node.log(currentTemp);
const colorCodes = [
    [90, "#ff0000"],
    [88, "#ff4000"],
    [86, "#ff8000"],
    [84, "#ff8000"],
    [82, "#ffff00"],
    [80, "#bfff00"],
    [78, "#80ff00"],
    [76, "#40ff00"],
    [74, "#00ff00"],
    [72, "#00ff40"],
    [70, "#00ff80"],
    [68, "#00ffbf"],
    [66, "#00ffff"],
    [64, "#00bfff"],
    [62, "#0080ff"],
    [60, "#0040ff"],
    [58, "#0000ff"],
    [56, "#4000ff"],
    [54, "#8000ff"],
];
const code = colorCodes.find((temp, code) => currentTemp >= temp)
const payload = code ? code[1] : "#bf00ff"
return {payload, val: currentTemp};

But that’s JavaScript, and I need to do this in TICKScript. So hold on, because this is going to get a bit ugly.

First of all, I had to brush up on template variables. Turns out I was already using one:

var message = '{{ index .Fields "value" }}'

But this one’s going to be a lot hairier. A. Lot.

I’m calculating the difference between the min and the max during a window of time, and if the change is greater than the threshold, I trigger the alert. So I’ll need to set the color of the GlowOrb based on the max value that triggered the alert. The template variable for that looks like this:

var message = '{{ index .Fields “max" }}'

Don’t make the mistake I made and look for “max.value” or you’ll spend half of your day wondering why it doesn’t work. Trust me on that one. Now, for the if-then-else portion of the fun. Again, I’ll use a template variable for this, but as I said before, it’s an ugly one.

var parseTemp = ''' {{ if (( index .Fields "max" ) gt 90 ) }} #ff0000 {{ else if 
    (( index .Fields "max" ) gt 88 ) }} #ff4000{ { else if 
    (( index .Fields "max" ) gt 86 ) }} #ff8000 {{ else if 
    (( index .Fields "max" ) gt 84 ) }} #ff8000 {{ else if 
    (( index .Fields "max" ) gt 82 ) }} #ffff00 {{ else if 
    (( index .Fields "max" ) gt 80 ) }} #bfff00 {{ else if 
    (( index .Fields "max" ) gt 78 ) }} #80ff00 {{ else if 
    (( index .Fields "max" ) gt 76 ) }} #40ff00 {{ else if 
    (( index .Fields "max" ) gt 74 ) }} #00ff00 {{ else if 
    (( index .Fields "max" ) gt 72 ) }} #00ff40 {{ else if 
    (( index .Fields "max" ) gt 70 ) }} #00ff80 {{ else if 
    (( index .Fields "max" ) gt 68 ) }} #00ffbf {{ else if 
    (( index .Fields "max" ) gt 66 ) }} #00ffff {{ else if 
    (( index .Fields "max" ) gt 64 ) }} #00bfff {{ else if 
    (( index .Fields "max" ) gt 62 ) }} #0080ff {{ else if 
    (( index .Fields "max" ) gt 60 ) }} #0040ff {{ else if 
    (( index .Fields "max" ) gt 58 ) }} #0000ff {{ else if 
    (( index .Fields "max" ) gt 56 ) }} #4000ff {{ else if 
    (( index .Fields "max" ) gt 54 ) }} #8000ff {{ else }} #bf00ff {{ end }}'''

You can’t say I didn’t warn you! Unfortunately I couldn’t find a way to make it more readable. That’s just how it is. Once I saved it, and triggered a temperature change event, my MQTT broker had this to say:

Success!! And now all I had to do was change my GlowOrb to read from the colorChange topic on the broker and I was all done. No more writing to an http server where Node-Red was running, running a bunch of Javascript, and posting back to the MQTT broker. Now I can just post directly to the MQTT broker from Kapacitor and be done with it!

I’m going to start moving a bunch of my other alerts over to MQTT as well.

Go ahead and install InfluxDB and the rest of the TICK Stack and start doing your data collection, analysis and alerting all with MQTT.