Slack Orb Notifier

Background

orb-mainI’ve worked with Blue Tooth in the past when using a Wii Remote to control my two Mouse Droids. The protocol is well documented, but how a manufacturer communicates with devices over the protocol is a whole other, sometimes obfuscated, process. Luckily with the Wii Remote, plenty of other people had been working on cracking the communication methods and published a wiki to explain it. At least what I needed to get that project done. But I’d like to learn about the method they used in case I ever have to do the same for a future device and no wiki exists.

I noticed Ada Fruit sells a USB device they’ve recoded to sniff Blue Tooth traffic and pass it over to Wireshark where you can get a good peek into packets going back and forth between devices. They also have a tutorial showing how to sniff traffic going to an RGB LED bulb called the Colorific. I thought this would be a perfect opportunity to dive into the process, but also extend the objective of the tutorial to accomplish something moderately useful.

Goal

  • Learn how to use a Blue Tooth sniffer to analyze traffic between devices.
  • Put that knowledge to use with a simple, fun project:
    • Change the color of a Blue Tooth LED bulb when someone mentions me in Slack.

Materials

Code

Experimentation

Blue Tooth Sniffing

I’m not going to go into too much detail here since Ada Fruit has a nice writeup on the process already, which you can find here:

https://learn.adafruit.com/reverse-engineering-a-bluetooth-low-energy-light-bulb

orb-desk-home-box-150622There are a few things to note, based on when I went through this tutorial. If you’re using iOS, there are a few GATT tools in the app store but none of them show you the MAC address, at the time of this writing at least. I ended up skipping ahead to the Control with Bluez step and used hcitool scan to get the MAC. Also, when using the sniffer app to launch Wireshark, I found it likes to throw a “btle” filter on – I’m assuming it’s auto-detecting the protocol or something. It’s a busy looking UI, so if you’ve not used it before the filter can be overlooked easily, but it’s also not too hard to locate if you know to look out for this behavior. Just remove it or replace it with the ATT filter they mention in the tutorial.

Controlling the Bulb

Going through the tutorial mentioned above, you’ll find that full control of the bulb can be gained through the 0x0028 handle and that the last 6 hex digits of that register are a standard hex RGB value, like you’ll find when working with HTML. But there are several other functions of the bulb which you can control from this register, and you may have already discovered them: On-Off and Dim. I’ll explain them, though, just in case.

  • a – Switch
    • 01 = Light ON
    • 00 = Light OFF
  • b – Dim
    • 00 to FF, 00 being the dimmest and FF being the brightest
  • c – Color
    • Hex RGB Color code, E12D00 or more commonly represented as #E12D00

Application

We’ve been using Slack more and more at work and sometimes I miss the notification when someone mentions me or PMs me. I think a good, practical application of what’s been learned here, would be to create a visual notification using the LED bulb.

Slack

There are two pieces of information you’ll need from Slack:

  1. Slack API Key/Token (request one here)
  2. Your Slack User ID

Slack provides real time messaging via a web socket connection. Every action that happens in your instance will be piped to you over that socket, even the event of someone starting to type a message to you. These messages are serialized JSON strings, and are queued up on Slack’s end so that if you lose connection, it will pick up on the message you left off on when you reconnect.

To start a socketed session, you will first need to authenticate through Slack’s rtm.start web API method. The only thing you have to pass to it is the auth token you requested earlier. I recommend testing this out via their web form found in the documentation for this method here. You’ll get back quite a lot of information, but for the purpose of this project just locate the web socket URL and your user code – which isn’t the friendly username you’re used to but is a unique code made up of letters and numbers (try a text search on the JSON for “URL” and your username or first/last name). Only that code will be present in the raw message text you’ll receive via real time messaging, so note that somewhere.

Implementation

There will be at least three different major tasks to perform in the process of notifying a user they’ve been mentioned. To make programing simple, those tasks will be modularized and threaded. I’ve never used any language that makes threading as simple as Python, so that’s the language I’ll use. Each task will be coded in a separate file and class:

  1. Control Colorific LED Bulb
  2. Monitor Slack Real Time Messages (RTM)
  3. Shell Interaction (Main)
Controlling the Colorific LED Bulb

The Colorific control will be best implemented as a true worker service that queues up actions and executes them one at a time. It should be able to operate without holding up the Slack monitor or prevent Stop commands for the app as a whole. I’ll create a class to do this, which extends the standard Threading class. To handle message queuing, I’ll use the standard Queue class, which is already thread safe and was designed for this exact sort of scenario – queuing up tasks for a thread. Any other thread can load the queue and the worker can pull tasks off and execute them as it gets to them.

There are two external libraries you may need to install:

  • pexpect
    • sudo pip install pexpect
  • numpy (pip had trouble compiling the package on the Pi)
    • sudo apt-get install python-numpy

The core worker thread structure of the class will consist of a constructor that accepts a Queue object, a run method which will contain code for the main task of the worker, and a close method which shuts things down as cleanly as possible.

The constructor takes in a Queue object and sets it to a class variable and then sets up a threading event to handle the Stop command. After that, any ancillary setup is preformed, which I’ll discuss later.

The run method’s main function is to continually loop until a stop command is received, checking the Queue each go-round. If there’s no messages in the queue, an exception is thrown, so a Try/Expect is used to capture that and continue looping. Later some off-time tasks will be added to execute when not handling a message – ancillary tasks such as color shifting.

The close method’s job is to cleanly shut things down. When it’s called, first thing it does is trigger the stop event, breaking the continual loop in run. Then the thread is joined back to the parent thread which effectively kills the thread, in a nice clean way.

With good worker thread mechanics in place, it’s time to consider what this worker will actually do. Think about the specific steps needed to control the bulb, perform an alert, and do any other fun animations on the bulb. These individual tasks will define any other methods needed in the class. What I came up with was:

  • Connect to the bulb
  • Alert animation
  • Shift from one color to another smoothly
  • Manipulate RGB vectors
  • Convert RGB vectors to hex string
  • Cycle through color spectrum slowly during off-times

Communication with the bulb will be handled by an external command line program called gatttool. The app will need to periodically connect/reconnect to the bulb so encapsulating that into a method will be quite handy. In my code it’s called bulb_connect.

This is where I use the pexpect library. The class in that library allows you to call an external app and has built in methods to listen for responses or output on the command line. In this case I’m looking for the in-console message that lets me know I’m connected. It is supposed to print out “successful”, but it quickly overwrites it with its internal virtual console so I also check for the text “[CON]”, which shows in the virtual console gatttool creates when connected.

For the alert, I wanted to animate the bulb with a quick flash between yellow and red, or maybe pulsate from yellow to red. Since it’s a unique animation, I’m going to encapsulate it into a method I call alert_red. My thought is that later I may want different types of alert animation for different notification events, and I can name them by color – such as alert_blue, alert_green, etc.

As you can see, it basically loops through and changes the color to Red then Yellow, with a slight pause on Red. A flag is set so that other tasks know an alert is currently running. That will prevent the periodic color fading feature, which I’ll discuss a little later, from interrupting this alert.

In order to get a pulsating animation for the alert, I need a smooth shift between the colors of the alert. That means changing the color of the bulb many times, altering the color values only slightly each time. Since I’m dealing with RGB color definitions, this ends up being a simple linear algebra exercise – equation of  a line between two points in 3D space. Each access is one of the three primary colors: Red, Green, Blue. Here’s a good explanation of the math here:

As the video explains, the following equation will give us points/colors along the vector between the two color points where t is the position between the two (think of it as a percentage of the way from the original color to the new color):

\vec{r}(t) = \vec{a}+t(\vec{a}-\vec{b})

There’s a nice Python library that will help simplify the code to implement this equation, it’s called numpy. On some systems, this library is likely pre-installed with Python 2.7, however, on the Pi, I had to install it separately. It wraps quite a few C modules, so if you use pip, it will actually compile these modules and it takes for-ev-er. Plus, for me, it erred at the end during a unicode-ascii conversion. I highly recommend just installing a pre-compiled package using apt-get: sudo apt-get install python-numpy.

With the numpy library, I can represent RGB colors as 3D vector arrays of Red, Green, and Blue values from 0 to 255. Numpy implements operators that allow you to perform arithmetic operations on those vectors, making it much easier to code an algorithm. This also means I can pass around these arrays instead of hex strings, and I can also preset a few common colors as class variables, for easier readability.

I created a method called color_shift. It takes in a numpy array representing an RGB color you want to change the bulb to. Using the equation discussed earlier as a model, I set up a loop that steps between 0.05 and 1.00 in steps of 0.05, this will give me the t values as the color moves from the current color (t=0) and the new color (t=1) – starting at 0.05, because t=0 represents the current color, and it’s already that color.

Since I’m using numpy arrays to pass around and work with RGB color values, I’ll need a way to get them into hex strings. I created two methods to help with this: nrgbstr and rgbstr.

Nrgbstr takes in a numpy array color and breaks it out into three separate integer variables. This is done with the map function and a very simple lambda function. Map basically loops through a given array and applies a given function to each element, then returns a tuple, which I’m assigning to three separate variables (Python handles the magic behind breaking that tuple into separate variables).

Rgbstr takes in three separate values for Red, Green and Blue and then constructs a hex string.

So now I have all the basic needs covered for an animated alert on the bulb, but there’s going to be a lot of down time between alerts. I think it would be nice if it just slowly cycled through colors when it’s not alerting me. Doing that will require a little bit more than just another method.

First, I setup a few numpy array class variables to represent several common colors. Then I added several flags and control variables to help keep track of what’s going on, what color it’s on, and where it needs to go next:

  • currentcolor – holds the numpy array representing the current color displayed on the bulb.
  • faderorder – is a dictionary of numpy arrays in the order I want to cycle through.
  • currentfadecolor – holds the numpy color the fader is on, this is important to track because an alert could interrupt and I want to be able to return the bulb to this color when the alert is over.
  • fadertrigger – this is a bit flag used in to debounce the next-fade conditions, more on that later.
  • alerton – this is another bit flag, but it’s simply used to avoid fading when an alert is in process – a semaphore.
  • reconnecttrigger – this is another debounce flag used to ensure a reconnect event only happens once per trigger pull.


The fader_next method cycles to the next color using the color_shift method discussed earlier. Its basic function is to figure out what color it’s on currently (currentfadecolor), and then move to the next one in the order defined by the dictionary setup in the constructor.

In order to fire off the fader, I go back to the run method. During the Queue.Empty state, I want to check the current time and see if it’s been a minute – using modulus to confirm the current time, in seconds, is evenly devisable by 60 (one minute). If it’s at the top of the minute, fire off the next fader color. But I can’t just call fader_next, if I do, the color will quickly cycle through many different colors all at once. This is due to the speed of the CPU. It cycles way faster than a second – 900MHz to be exact. That means this section of code could be executed and evaluate True as much as 15 million times. If I just called fader_next here, the color would attempt to cycle that fast, but I doubt the bulb could keep up. It would change as fast as it could, but it certainly wouldn’t be the effect I’m looking for.

I want to ensure the fader_next method is called only once during that period, that’s where the concept of debounce comes in. I do that by setting a trigger bit to 1, indicating there’s some action to be taken, instead of calling the method. Then when the current time no longer evenly divides by 60, a secondary IF statement checks the bit, sees it’s 1, calls fader_next, and then sets the bit back to 0. Now the bit is getting set to 1, millions of times through the whole minute, but no action is taken until the condition is over and then only one action is taken because the bit is immediately reset after one call to fader_next. No more bounce!

You’ll also notice that I do this for the reconnecttrigger flag too. I’ve found that the bulb sometimes looses connection over time, for reasons I can’t explain, though I have my suspicions. To combat this, I am explicitly disconnecting and reconnecting just before cycling colors, to ensure the connection is open. There are many other ways to accomplish the same thing. You could only connect when sending a color command, for instance, but that will leave your bulb open for others to connect via the free app – and they will once they find out they can. Going about it this way ensures the bulb is always connected to your app and Blue Tooth LE enforces a one-connection-at-a-time policy, meaning others are locked out.

Monitoring Slack for Mentions

Since messages are sent by Slack in real time, as they happen, I’ll need something dedicated to waiting for those messages. When a message comes in, and if that message has the specific mention I want, it needs to load the Colorific Worker’s queue to trigger an event. So this is a good candidate for a dedicated thread as well, though it’s not exactly the same sort of worker scenario as the bulb control was. Still, I’m going to model it roughly after the core threading structure I used in colorificworker.py.

There’s just one external library you may need to install:

I won’t go over the worker structure again in detail, but this class will have the same base set of methods to handle the threaded portion:

  • run – where the work is done
  • close – shuts things down nicely

The constructor takes in three parameters used to configure the app as a whole:

  • token – The authentication token assigned by Slack
  • userid – The coded user ID used in Slack messages i.e. “@ABC1234”
  • mac – The mac address for the Colorific bulb

The token is used to authenticate with Slack, which yields a web socket URL. The URL is then used to start listening on a web socket. And finally, an instance of the Colorific Worker is created and a Queue and mac is passed to it.

This worker doesn’t have quite as many tasks to perform either, all that needs to be done in this service is:

  • Authenticate with Slack via web Post
  • Listen to web socket and receive messages

Authentication is done via Slack’s rtm.start web service. All it needs is the token passed in from the constructor, which is stored as a instance variable. Slack will hand back a really long JSON message, but all I need is the URL. Maybe at some point I will add some intelligence here, and look up the userid and maybe even other users or condition flags.

Data is passed over a web socket in frames, much like other networking protocols. The recv method simply catches frames as they come in, and confirms validity. If it’s a valid frame, it checks for two conditions: CLOSE and PING. If the other end explicitly closes the connection, an OPCODE_CLOSE is passed in the frame. If I get that, I just close things down. If the frame is OPCODE_PING, the other end is asking if the app is still listening, so I just send back a bong message to let it know I’m still here.

To tie it all together, the run method calls out to recv in a loop, so long as a Stop command hasn’t been issued. Any messages received are scanned for an instance of userid or “!channel” – which is an @channel mention. If either is found to be within the message, an alert is loaded into Colorific Worker’s queue, which will signal it to change the bulb color.

Main Thread

There’s nothing particularly significant to the main thread, so I won’t go into too much detail. You can take a look at the source code for orbnotifier.py from the repository here. It just reads data from a config file, passes that along to a new instance of slackmonitorworker, then waits for keyboard input to signal a Stop of all threads.

Conclusion

This was a really fun exercise in Blue Tooth sniffing. I think this will be a really great tool for debugging and reverse engineering devices on future projects. Plus I’ve got a really neat way to receive Slack notifications now. There’s always room for improvement, however, and I have a list of things I’d like to revisit later:

  • Implement code to handle Blue Tooth communication right from Python instead of depending on the external app gatttool.
  • Parse User IDs right out of the JSON returned by rtm.start.
  • Create alerts for other channel notifications, like any message in a watched channel (edit: have separate alerts for Mention, @Channel, and Private Message. Still need some for watched channels).
  • If implementing different alerts, refactor alert_red into a more generic function that takes in the colors as parameters.
  • Integrate with JIRA and alert on mentions in that system too.
  • If JIRA or any other triggers are added, the management of Queues should probably be moved to the Main thread, or a gateway thread.
  • Maybe even replace the BTLE bulb with a home grown LED system – possibly out of a combination of Particle core and Neopixels.

Leave a Reply

Your email address will not be published. Required fields are marked *