In the last post we looked at some initial code to get basic information about the lights connected to a Philips Hue bridge. In this post we’re going to extend the code to expose more information but also to query the bridge repeatedly, allowing the graph to display the latest light colours as they change.
Here’s a view of the updated graph. A few things have changed: firstly the Bridge object exposes some new information – in our case we can see the names of the lights, but we could also access the names and IDs of the various groups, scenes, rules, schedules and sensors stored in the bridge. Secondly we’ve inserted a Light.RefreshData node, which has the “CanUpdatePeriodicallyAttribute” set to true. It simply refreshes the data for the light and returns the Light object to the downstream node. This means we can change the execution of the graph from manual to periodic, so that it polls the bridge repeatedly.
Let’s take a look at the graph in action, with the Dynamo graph polling for data from the Philips Hue bridge once per second. Here’s a video showing the Philips Hue app on my phone next to Dynamo Studio, so you can see its response.
Here’s the updated C# code for the zero-touch node:
using Autodesk.DesignScript.Runtime;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
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 string[] GetNameFieldOfProperties(this JToken obj)
{
var names = new List<string>();
foreach (JObject prop in obj.Values())
{
names.Add(prop.GetValue("name").ToString());
}
return names.ToArray();
}
internal static async Task<string> GetUrl(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;
}
}
internal static bool RgbFromCie(
double x, double y, out double r, out double g, out double b
)
{
// 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;
}
}
public class Bridge
{
// The values for our IP address and bridge ID
private string _ip, _bridge;
internal Bridge(string ip, string bridge)
{
_ip = ip;
_bridge = bridge;
RefreshData();
}
public string[] LightIds { get; set; }
public string[] LightNames { get; set; }
public string[] GroupIds { get; set; }
public string[] GroupNames { get; set; }
public string[] SceneIds { get; set; }
public string[] SceneNames { get; set; }
public string[] RuleIds { get; set; }
public string[] RuleNames { get; set; }
public string[] ScheduleIds { get; set; }
public string[] ScheduleNames { get; set; }
public string[] SensorIds { get; set; }
public string[] SensorNames { get; set; }
/// <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>
[CanUpdatePeriodicallyAttribute(true)]
public Bridge RefreshData()
{
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.GetUrl().Result;
// If we still haven't succeeded, return an invalid value
if (String.IsNullOrEmpty(res))
return null;
// Parse and extract the results
var jo = JObject.Parse(res);
var lights = jo["lights"];
var groups = jo["groups"];
var scenes = jo["scenes"];
var rules = jo["rules"];
var schedules = jo["schedules"];
var sensors = jo["sensors"];
if (lights == null || groups == null || scenes == null ||
rules == null || schedules == null || sensors == null)
return null;
LightIds = lights.GetNamesOfProperties();
LightNames = lights.GetNameFieldOfProperties();
GroupIds = groups.GetNamesOfProperties();
GroupNames = groups.GetNameFieldOfProperties();
SceneIds = scenes.GetNamesOfProperties();
SceneNames = scenes.GetNameFieldOfProperties();
RuleIds = rules.GetNamesOfProperties();
RuleNames = rules.GetNameFieldOfProperties();
ScheduleIds = schedules.GetNamesOfProperties();
ScheduleNames = schedules.GetNameFieldOfProperties();
SensorIds = sensors.GetNamesOfProperties();
SensorNames = sensors.GetNameFieldOfProperties();
return this;
}
catch { return null; }
}
}
public class Light
{
// The values for our IP address, bridge and light IDs
private string _ip, _bridge, _light;
private bool _on;
private double _x, _y, _r, _g, _b;
internal Light(string ip, string bridge, string light)
{
_ip = ip;
_bridge = bridge;
_light = light;
RefreshData();
}
public bool On { get; set; }
public double R { get { return _r; } set { _r = value; }}
public double G { get { return _g; } set { _g = value; }}
public double B { get { return _b; } set { _b = value; }}
/// <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>The light itself</returns>
[CanUpdatePeriodicallyAttribute(true)]
public Light RefreshData()
{
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.GetUrl().Result;
// If we still haven't succeeded, return an invalid value
if (String.IsNullOrEmpty(res))
return null;
// Parse and extract the results
var jo = JObject.Parse(res);
var state = jo["state"];
if (state == null)
return null;
_on = (bool)state["on"];
_x = (double)state["xy"][0];
_y = (double)state["xy"][1];
Extensions.RgbFromCie(_x, _y, out _r, out _g, out _b);
return this;
}
catch { return null; }
}
}
}
That’s it for reading data from Hue… the fun piece will clearly be when we can change the colours of the lights based on calculations made inside Dynamo Studio. We’ll see that next time.