An Exercise in Code: DIY Philips Hue Sync App for Linux

Dave Russell
FlexMR Dev Blog
Published in
5 min readJan 8, 2020

I really enjoy my smart lighting set up on my desktop. I have two Philips Hue Play units tucked behind my primary monitor, and when I use them with the Hue Sync app, the units illuminate the wall with colour based on what’s currently on the screen. This creates a fantastic ambience when I’m watching Netflix or working through my Steam library.

Unfortunately, the Hue Sync app is only available on Windows and Mac. I dual boot Linux and spend at least half my time there. I can still make use of the lights through the phone app, I just can’t sync their colours to the contents of my monitor.

There’s a solution, though. The Hue system has an API and I’m a Ruby programmer, so I thought I’d have a go at making my own (rough approximation of the) Hue Sync app!

In this exercise, there are two details that need to be implemented:

  • Decide the colour for each light, based on the colours of pixels currently shown on the screen.
  • Use the Hue API to tell each light what colour to transition to.

Getting the Colour of a Pixel

To keep things simple, I’ll just grab a couple of pixels from my primary monitor rather than trying to work out an average or dominant colour for each half of the screen.

ImageMagick’s convert tool can do this really well:

The -crop option is the where we tell import which pixel we want the colour of. 1x1 is the size of the sample we want (i.e. just one pixel) and the +20+20 refers to the x and y co-ordinates of the screen.

The output gives the colour information in hex and srgb formats. I’ll need the red, green, and blue values to be separate later, so pulling from the srgb value will help with this. Everybody stand back. I know regular expressions.

pixel_colour will take the x and y co-ordinates that we send and give back a hash of the separated RGB values. I still need to determine which pixels I want to capture. I haven’t made the leap to Wayland yet, so I can make use of xrandr. I have two monitors sitting side-by-side each at 2560x1440 resolutions. I also have them running as a single virtual screen, which makes it easier to get the required data.

The current 5120 x 1440 is the bit I’m looking for. I’m only interested in my right hand side monitor, so I need to offset against the first 2560 x values.

I want to take two pixels, one for each Play light. I’ll take both from halfway down the y-axis, then at the 1/3 and 2/3 marks along the x-axis.

I’ve made the monitor offsets easily configurable as I’m probably going to be shunting things around soon. I’ve otherwise hard-coded the target_pixels array as I won’t be adding or changing the actual Play lights in the short term.

The value of target_pixels ends up as [[3413, 720], [4266, 720]]. These co-ordinates can then be passed through pixel_colour to get the RGB values.

Update the State of the Hue Play lights

The Hue system has a REST API, and it’s really well documented. I won’t be using it directly, though, as Sam Soffes created a Ruby gem for Hue which handles all of the authentication and lets you work with the API in an OOP fashion.

I have several Hue lights in my house, so the first task is to gather an array of just the two Play lights that I’ll be using. As these are the only Plays I have, this can be done by matching against the name. If I had more, I’d likely utilise groups instead.

I have control of the lights and a way of grabbing the colours from the screen, I now just to hook them up together and run both processes in a loop.

There’s a small problem, though. I have the pixel colour in RGB, but Hue does not support this. Hue takes XY co-ordinates for CIE Color space, so I’ll need to convert the RGB values to that. There’s a lot of science around this, and there’s no way you’ll get identical colours shown on the monitor and the wall, but it’ll be ‘close enough’ to give the atmospheric ambience that I’m going for.

Rather than implement the RGB -> XY conversion algorithm myself, I’ll use another gem: the Color gem supports colour manipulation and conversion between many formats. It doesn’t directly support the XY co-ordinates we need, but it does do XYZ which itself is easy to map to XY.

Here’s how the main loop looks. For each light, it:

  1. Grabs the RGB colour of the relevant pixel
  2. Converts the RGB values into XYZ values
  3. Converts the XYZ values into XY values
  4. Sets the state of the light to the new colour, with a transition time of 400ms
  5. Waits for 2 seconds before going to the next light

In the event of an exception, or me telling the script to end via CTRL+c the script will gracefully terminate, turning off the Play lights as it does so.

Mission Accomplished

As you can see from the video below, it’s not bad for a couple of hours work! Check out the final, full script, which is available here.

Now if you have a bit more time on your hands to play about with, there are a few areas that could be improved:

  • Using import the way I’m doing is slower than I’d like and doesn’t scale well with shorter intervals or more lights. To fix this, we could possibly tap in to /dev/fb0 or X internals?
  • Choose a better colour than the one at an arbitrary pixel of the screen. Taking the dominant colour from the left/right half of the screen would be better.
  • Be smarter about when to change the light state. If the chosen colour hasn’t changed since the last check, there’s no need to send another request to the API.

After this attempt it would be interesting to hear how others would have attempted this exercise; I’d love to hear your thoughts and suggestions, please do leave a comment below!

--

--

Dave Russell
FlexMR Dev Blog

Possibly the nation’s favourite Rails developer. Diabetic AF, Mental Health advocate, 100% Capricorn, ISTJ. Enjoys Git a bit too much. Instagram is life.