I mentioned in a recent post that I was chewing on how design apps might connect with building automation systems to display data via lights. My first step along this path was to see how Dynamo Studio might connect into my home’s Philips Hue bridge and get information about the “smart” lights it contains.
To do this I created a zero-touch node in C# that calls into the Philips Hue API exposed by my local bridge. (To make this piece work you need to follow the steps on this page that show how to create an authorised user ID for your app.)
The C# code defines a couple of nodes: one for the bridge itself and one for a light. This is just the beginning… the Philips Hue system supports a number of controls as well as some sensors, but you have to start somewhere. It’s also currently only read-only: the next step is to have the ability to change lighting based on the state of the Dynamo graph.
Here’s an image of how I was able to use this implementation to get the colours of the four lights in my living room as I cycle manually through the various Philips Hue scenes (Savanna Sunset, Tropical Twilight, Arctic Aurora and Spring Blossom – in no particular order). A sphere gets created for each light in the system, taking on the colour that’s reported for it. Also, the graph currently needs manual modification before it will repoll the API, but that’s hopefully something I’ll fix in a future update.
In case you’re wondering, the colours in the above animation have been flattened to those supported by the GIF format (it only supports 256, which is why it looks a bit ugly).
Here’s the current state of the C# code (I’ll post it to GitHub before too long). It makes use of the Colourful library to convert between CIE colours and RGB. (Philips has more information on the way the Hue system represents colours here.)
using Autodesk.DesignScript.Runtime;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
namespace DynamoHue
{
internal static class Extensions
{
internal static string[] GetNamesOfProperties(this JToken obj)
{
var names = new List<string>();
foreach (JProperty prop in obj)
{
names.Add(prop.Name);
}
return names.ToArray();
}
internal static async Task<string> InvokeWithoutAuth(this string uri)
{
using (var hc = new HttpClient())
{
// Get the response: if it's valid return the JSON string else null
var res = await hc.GetAsync(uri);
return
res.IsSuccessStatusCode ?
await res.Content.ReadAsStringAsync() :
null;
}
}
}
public class Bridge
{
// The values for our IP address and bridge ID
private string _ip, _bridge;
private string[] _lights;
public string[] Lights
{
[CanUpdatePeriodicallyAttribute(true)]
get
{
Refresh();
return _lights;
}
set
{
_lights = value;
}
}
internal Bridge(string ip, string bridge)
{
_ip = ip;
_bridge = bridge;
}
/// <summary>
/// Creates a bridge node based on an IP address and a bridge ID.
/// </summary>
/// <param name="ip">The IP address of the bridge</param>
/// <param name="bridge">The ID of the bridge</param>
/// <returns>A node representing the specified bridge</returns>
public static Bridge ByIpAndBridgeId(string ip, string bridge)
{
return new Bridge(ip, bridge);
}
/// <summary>
/// Refreshes the information for a bridge using the Philips Hue API
/// </summary>
/// <returns>Whether the API could find the specified bridge</returns>
public bool Refresh()
{
try
{
// Format the URL to return the sensor readings for a specified project
// and sensor
var url = String.Format("http://{0}/api/{1}", _ip, _bridge);
// Invoke our API with our credentials: returns null if auth expired
var res = url.InvokeWithoutAuth().Result;
// If we still haven't succeeded, return an invalid value
if (String.IsNullOrEmpty(res))
{
return false;
}
// Parse and sort the results
var jo = JObject.Parse(res);
var lights = jo["lights"];
if (lights == null)
return false;
_lights = lights.GetNamesOfProperties();
return true;
}
catch { return false; }
}
}
public class Light
{
// The values for our IP address, bridge and light IDs
private string _ip, _bridge, _light;
private bool _on;
private int _brightness, _hue, _saturation;
private double _x, _y, _r, _g, _b;
public bool On
{
[CanUpdatePeriodicallyAttribute(true)]
get
{
Refresh();
return _on;
}
set
{
_on = value;
}
}
public int Brightness
{
[CanUpdatePeriodicallyAttribute(true)]
get
{
Refresh();
return _brightness;
}
set
{
_brightness = value;
}
}
public int Hue
{
[CanUpdatePeriodicallyAttribute(true)]
get
{
Refresh();
return _hue;
}
set
{
_hue = value;
}
}
public int Saturation
{
[CanUpdatePeriodicallyAttribute(true)]
get
{
Refresh();
return _saturation;
}
set
{
_saturation = value;
}
}
public double R
{
[CanUpdatePeriodicallyAttribute(true)]
get
{
Refresh();
return _r;
}
set
{
_r = value;
}
}
public double G
{
[CanUpdatePeriodicallyAttribute(true)]
get
{
Refresh();
return _g;
}
set
{
_g = value;
}
}
public double B
{
[CanUpdatePeriodicallyAttribute(true)]
get
{
Refresh();
return _b;
}
set
{
_b = value;
}
}
internal Light(string ip, string bridge, string light)
{
_ip = ip;
_bridge = bridge;
_light = light;
}
/// <summary>
/// Creates a light node based on an IP address, a bridge and light ID.
/// </summary>
/// <param name="ip">The IP address of the bridge</param>
/// <param name="bridge">The ID of the bridge</param>
/// <param name="light">The ID of the light</param>
/// <returns>A node representing the specified light</returns>
public static Light ByIpBridgeAndLightId(string ip, string bridge, string light)
{
return new Light(ip, bridge, light);
}
/// <summary>
/// Refreshes the information for a light using the Philips Hue API
/// </summary>
/// <returns>Whether the API could find the specified light</returns>
public bool Refresh()
{
try
{
// Format the URL to return the sensor readings for a specified project
// and sensor
var url = String.Format("http://{0}/api/{1}/lights/{2}", _ip, _bridge, _light);
// Invoke our API with our credentials: returns null if auth expired
var res = url.InvokeWithoutAuth().Result;
// If we still haven't succeeded, return an invalid value
if (String.IsNullOrEmpty(res))
{
return false;
}
// Parse and sort the results
var jo = JObject.Parse(res);
var state = jo["state"];
if (state == null)
return false;
_on = (bool)state["on"];
_brightness = (int)state["bri"];
_hue = (int)state["hue"];
_saturation = (int)state["sat"];
_x = (double)state["xy"][0];
_y = (double)state["xy"][1];
// Convert the colour from CIE to RGB
var xy = new Colourful.xyYColor(_x, _y, _y);
var converter = new Colourful.Conversion.ColourfulConverter();
var col = converter.ToRGB(xy);
_r = col.R * 255;
_g = col.G * 255;
_b = col.B * 255;
return true;
}
catch { return false; }
}
}
}
So far, so good! I think it’s going to be interesting to see how home automation systems might be used to show additional visual information beyond what’s made visible on the screen. We’ll see!