Basic Hue Lighting Control: Part 1

screenshot
Screenshot of Chrome application for controlling Hue lighting.

Continuing from the post I made on SSDP discovery with Chrome, I’m making an application that will do more than just discovery. For this post I’m going to show the starting point of a Chrome application for controlling your home Hue lighting. I’ve divided this into two parts. In this first part I’m showing the process of pairing with the bridge. In the second part I’ll control the lights.

The features that this application will implement will include bridge discovery and pairing; the power state of the light; and the brightness level of the light. There’s many other features that could still be implemented.  Given the full range of capabilities that the Hue kits support (changing color, timers, response to motion sensors, etc.) this will not be an application that utilizes the full capability of the Hue lighting sets.

Chrome Only

This application is designed to only run in Chrome. If you want to adapt it to run outside of Chrome, you can do so by first disabling SSDP discovery. (Other HTML application platforms might not support UDP for discovery.)

The other discovery methods (querying Hue’s discovery web service or asking the user to enter the IP address) can still work. A non-chrome target will also need to allow CORS to be ignored and allow communication without SSL.

What is Hue Lighting?

Hue Lighting is an automated lighting solution made by Philips. Generally the lighting kits are sold in a package that contains three LED based light bulbs and a bridge. The bridge is a device that connects to your home network with an Ethernet jack and communicates with the light bulbs.

Philips also makes free applications for iOS and Android for controlling the lights. For any Hue light the light’s brightness and whether or not it is turned on can be controlled through the applications. Some lights also allow the color temperature to be changed (adjusting the tint between red, yellow and blue). Some lights support RGB (Red, Green, Blue) parameters so that their colors can be changed.  These settings can be individually adjusted or settings for a collection of the lights can be defined together as a “scene.” When a scene is activated the state of all of the lights that make up the scene are updated. Scenes can be activated through special light switches, through an app, through a schedule, or in response to a Hue motion sensor detecting motion.

Discovery: Review and New Methods

The central piece of hardware for the Hue lighting is the Hue Bridge. At the time of this writing there are two versions of the bridge. For the functionality that this application will utilize, the differences between the two bridges will not matter. The messaging and interaction to both versions of the bridge will be the same. My UI will properly represent the bridge that the system discovers. The first version of the Hue Bridge is round. The second version of the Hue Bridge is square. In either case we must first find the bridge’s IP address before we can begin interaction.

phillipsbridge
Phillips Hue Bridge Version 1 (left) and Version 2 (right)

The Hue bridge can be discovered in multiple ways. It can be discovered using SSDP. The basics of SSDP discovery were previously discussed here. Please refer back to it if you need more detail than what is found in this brief overview.  Devices that support SSDP discovery join a multicast group on the network that they are connected to. These devices generally wait for a request for discovery to be received. An SSDP request is sent as an HTTP over UDP message and every SSDP device that receives it responds with some basic information about itself and a URL to where more information on the device can be found. Examples of some devices that support SSDP are network attached storage; set top boxes like Android TVs and Rokus; printers; and home automation kits.

Two other methods of discovering a bridge include asking the user to enter an IP address and asking for a list of IP addresses of bridges on your network through the Hue discovery service.  If you have a Hue bridge connected to your network right now you can see it’s IP address by visiting https://discovery.meethue.com/ . If you are on a shared network then you may also see IP addresses of other bridges on your network. It is also possible that not all bridges on your network are reachable.  This method is much easier to implement than SSDP based discovery. But on a network for which there is no Internet connection (whether by design or from an outage) this method will not work. The SSDP method is only dependent on the local network.

function discoverBridge() { 
    discovredHueBridgeList = [];
    fetch(' https://discovery.meethue.com')
        .then(response => response.json())
    .then(function (hueBridgeList) {
        console.info(hueBridgeList);
        hueBridgeList.forEach((item)=> {
         // each item processed here has a bridge IP address
         // and serial number exposed through item.id and 
         // item.internalipaddress
       }
     );
}

Once I have a bridge IP address I attempt to query it for more information. If communication succeeds, then I show a representation of the bridge with an icon that matches the version of the bridge that the user has. The UI layout has two images ( one named hueBridgev1 and the other hueBridgev2) I show the appropriate image and hide the other.

Pairing

Now that the bridges have been discovered, it is up to the user to select one with which to pair. After the user selects a bridge, she is instructed to press the pairing button on the bridge. While this instruction is displayed the application is repeatedly attempting to request a new user ID name from the bridge. This should be viewed more as an access token. The Hue documentation uses the term “user name” but the actual value is what appears to be a random sequence of characters. To request a user name a JSON payload with one member named devicetype is posted to the bridge. The value assigned to devicetype matters little. It is recommended that it be a string that is unique to your application. The payload is posted to http://%5Byour bridge IP address]/api. A failure response will result. This is expected. The application must repeatedly make this request and prompt the user to press the link button on the bridge.  The request will fail until the pairing button on the bridge is passed.

function pairBridge(ipAddress) {
   console.info('attempting pairing with address ', ipAddress);
   var req = { devicetype: "hue.j2i.net#browser" };
   var reqStr = JSON.stringify(req);
   var tryCount = 0;
   return new Promise(function(resolve, reject)  {
      var tryInterval = setInterval(function () {
      console.log('attempt ', tryCount);
      ++tryCount;
      if (tryCount > 60) {
        clearInterval(tryInterval);
         reject();
         return;
      }
      fetch(`http://${ipAddress}/api`, {
         method: "POST",
         headers: {
            "Content-Type": "application/json"
         },
         body: reqStr
      })
      .then(function(response)  {
         console.log('text:',response);
         return response.json();
      })
      .then(function(data)  {
          console.log(data);
          if (data.length > 0) {
             var success = data[0].success;
             var error = data[0].error;
             if (success) {
                console.log('username:', success.username);
                var bridge = {
                   ipAddress: ipAddress,
                   username: success.username
                };
                clearInterval(tryInterval);
                 resolve(bridge);
                  return;
               }
               else if (error) {
                  if (error.type === 101) {
                     console.log('the user has not pressed the link button');
                  }
               }
            }
         });
      }, 2000);
   });
}

Once the button is pressed the bridge will respond to the first pairing request it receives with a user name that the application can use. This user name must be saved and used for calls to most of the functionality that is present in the bridge. I save the bridge’s serial number, IP address, and the name that must be used for the various API calls to an indexedDB object store. The access information for multiple paired bridges could be stored in the object store at once. But the application will only be able to communicate with one bridge at a time.

Continued in Part II

Advertisements

Graphing with the HTML Canvas

Among other places I went to Peru and saw Machu Picchu! The trip back home though was long and boring.  There’s lots of ways that one can keep their mind when busy when in a non-stimulating situation though. Reading, Sudoku , cross word puzzles, so on. Sometimes I like playing with code during these times. I had started off reading a math heave book though. I generally have a notebook with me, and my notebooks are generally graph ruled. But graphing polynomials can be tedious. But hey, I had a Chromebook  with me, plenty of battery life, and no Internet access. So I decided to see what I could do with the HTML canvas. I cam up with something that worked and was useful. It’s not something that I would package and call a library by any means. After all, this is something that was put together during a layover in an airport. But nonetheless I thought it to be something that is worthy of sharing.

Drawing with the Canvas

Let’s review how to draw with the HTML5 canvas. First a <canvas> element would need to be declared within a page. Within the page’s script we grab a reference to the canvas and get the 2d context and hold onto it; the context object will be the object used for most of our interaction with the canvas. Here I take a canvas
and draw a red rectangle in it.
<canvas id="myCanvas" width="320" height="200"></canvas>

    var canvas = document.getElementById('myCanvas');
    var ctx = canvas.getContext('2d');
    ctx.fillStyle="#FF0000";
	ctx.fillRect(10,10,100,100);

Cool, we can draw on the surface. But before graphing anything we need to be able to map the data that we are graphing to the coordinate space for the graph. That’s going to be a linear transformation. Linear transformations are easy but something that I’m sure I’ll want to do several times. So I’ve made a class to figure out the the transformation for me.

Linear Transformation

Given a pair of starting and ending values this class is able to convert from on value range to another. The class has two methods. the map() method will do the conversion and rmap will perform a reverse conversion. Given the the freezing and boiling points of water in Farenheit and Celcius it could perform conversions back and forth between the two.

function linearMapping(iMin, iMax, oMin, oMax) {
	this.slope =  (oMax - oMin) / (iMax - iMin);
	this.base = oMin - this.slope*iMin;
	this.map = function(x) {
		if(typeof x != 'number')
			return null;	
		return x*this.slope+this.base;
	}
	this.rmap = function(x) {
		if(typeof x != 'number')
			return null;
		return (x - this.base)/this.slope;
	}
}

I sat for a while and I thought about the parameters that I would want to pass when creating a graph. I first envisioned passing this information as individual parameters to a function. But I didn’t have to think for long before I realized this would be a lot of parameters most of which I probably would not feel like specifying. I took a slightly different approach and decided to instead pass a parameter object. The object would be initialized with a set of workable default values that could be changed as the graph needed to be customized.

Graphing Options

Some of the basic information needed is the the size of the graph within the HTML page and the ranges of the X and Y values being displayed. I allow these values to be optionally passed in when constructing the object. I’ve also defined a color palette to be used for the graph.

function graphOptions(width, height, minX, maxX, minY, maxY) {
	this.width = width || 300;
	this.height = height || 300;
	this.minX = minX || -10;
	this.maxX = maxX ||  10;
	this.minY = minY || -10;
	this.maxY = maxY ||  10;
	this.stepX = 2;
	this.stepY = 2;
	this.backgroundColor = '#F0F0F0';
	this.lineColor = '#C0C0FF'
	this.minorLineWidth = 1;
	this.majorLineWidth = 4;
	this.dataColors = [
		"#000000",
		"#ff0000",
		"#00ff00",
		"#0000FF",
		"#FF8000",
		"#ff0080",
		"#80FF00",
		"#8000FF"
	]
}

With that we are ready to start creating our graph. We need a way of specifying where in the page the graph should be generated. I’ve made a method named makeGraph() that will accept two arguments; the parent element for the graph and the parameter object for the graph options. If for some reason the parent element isn’t specified the function will just append the graph to the page’s DOM.

Creating the Graph

This class will take care of creating the canvas object. The canvas’s context will be retained along with the options and packaged in an object as both will be needed later for plotting graphs. When the graph is created I render the graph lines on it. The canvas object is added to the page

function makeGraph(parentElement, options) {
	options = options || new graphOptions();
	var graphElement = document.createElement('canvas');

	graphElement.width = options.width;
	graphElement.height = options.height;

	var ctx = graphElement.getContext('2d');
	var newGraph = new graph(ctx, options);
	ctx.fillStyle=options.backgroundColor;
	ctx.fillRect(0,0,options.width, options.height);

	ctx.strokeStyle = options.lineColor;
	ctx.beginPath();
	ctx.lineWidth = options.majorLineWidth;
	var xOrigin = newGraph.xMapping.map(0);
	var yOrigin = newGraph.yMapping.map(0);
	ctx.moveTo(xOrigin,0);
	ctx.lineTo(xOrigin, options.height);
	ctx.moveTo(0, yOrigin);
	ctx.lineTo(options.width, yOrigin)
	ctx.stroke();
	ctx.strokeStyle = options.lineColor;
	ctx.beginPath();
	for(var i = options.minX; i<options.maxX;i+=options.stepX) {
		var xPos = newGraph.xMapping.map(i);
		ctx.moveTo(xPos,0);
		ctx.lineTo(xPos,options.height);
		ctx.lineWidth = options.minorLineWidth;
	}
	for(var i = options.minY; i<options.maxY;i+=options.stepY) {
		var yPos = newGraph.yMapping.map(i);
		ctx.moveTo(0, yPos);
		ctx.lineTo(options.width, yPos);
		ctx.lineWidth = options.minorLineWidth;
	}
	ctx.stroke();

	if(parentElement != null)
		parentElement.appendChild(graphElement)
	else
		document.body.appendChild(graphElement)
	return newGraph;
}

The result of the above is the return of a graph object that hasn’t been defined here yet. The graph object packages together the context for rendering, the graph objects, and the objects for mapping the values being represented to canvas coordinates. Something that may look odd at first is that for the Y-mapping I placed a maximum value in a minimum parameter and vice versa. This is because the coordinate system that many of us use when thinking about graphs is reversed along the Y-axis than the canvas coordinates. The general way that people think about graphs is that numbers of greater value will appear higher up on the graph. But in the canvas coordinate space the highest position has a Y-coordinate of zero and as the number increases the position maps to a position further down on a page. There’s more than one way to address this, but since the linear transfor object can already handle this if I
specify the values in a certain order that’s the solution that I used.

The function made for public use on this class ia named plot. It accepts a list of functions to be graphed. While I expect an array of functions to be passed to it if a singular function were passed that’s converted to an array of one function so that it can be treated the same way. The plot function interates through the functions passed through it passing each one to another function that does the actual work. A different color index is passed for each function.

The real work is done in plotFunction. First the X-values that fall within the range of the limits of the graph are passed to the function being mapped and the canvas-mapped result is saved to an array. The result for each X-value could either be a number or a non-number. Non-numbers will not be represented in the graph. This allows for the generation of graphs for which there may be X-values that are not part of the functions domain. If an exception occurs when calling the function being graphed the X-value association with that exception is treated as a value for which the function returns nothing. After this first pass we have an array of the canvas-mapped output values from the function.

Rendering the Graph

Next we perform the acual rendering of the output onto the graph. The function will scan ahead in the array of results to the first numerical value. This first numerical value may or may not be in the first position of the array. Once a value is found it is used as the first coordinate for a line segment. Each numerical value in the array that follows this is added to the line segment. This continues until either the end of the array is reached or a non-numerical value is encountered. In either case the accumulated points for the line segment are stroked ending. If there are more values within the array the process is repeated until the end of the array is encountered.

function graph(context, options) {
	this.ctx = context;
	this.options = options;
	this.xMapping = new linearMapping(options.minX, options.maxX, 0, options.width);
	this.yMapping = new linearMapping(options.minY, options.maxY, options.height, 0);

	this.plot = function(sourceFunctions) {
		if(!Array.isArray(sourceFunctions))
			sourceFunctions = [sourceFunctions];
		var colorNumber = 0;
		sourceFunctions.forEach((x)=> {
			this.plotFunction(x,colorNumber);
			++colorNumber;
		})
	}

	this.plotFunction = function (plotFunction, colorNumber) {
		colorNumber = colorNumber || 0;
		colorNumber = colorNumber % this.options.dataColors.length;
		var values = new Array(this.options.width);
		for(var xPos=0;xPos<this.options.width;++xPos) {
			var y = null;
			var x= this.xMapping.rmap(xPos);
			try {
				values[xPos] = this.yMapping.map(plotFunction(x))
			} catch(exc) {
				values[xPos] = null;
			}
		}
		//Find the first value that we can map
		var xPos = 0;
		while((typeof values[xPos] != 'number')&&(!Array.isArray(values[xPos]))&&(xPos < values.length))
			++xPos;
		if(xPos == values.length)
			return;
		
		while(xPos<values.length) {
			this.ctx.beginPath();
			this.ctx.strokeStyle = this.options.dataColors[colorNumber];
			this.ctx.moveTo(xPos, values[xPos]);
			while(xPos+1<values.length && typeof values[xPos+1] == 'number') {
				++xPos;
				this.ctx.lineTo(xPos, values[xPos]);
			}
			++xPos;
			this.ctx.stroke();
			while((typeof values[xPos] != 'number')&&(xPos < values.length))
				++xPos;
		}
	}
}

How Does it Look?

The graphing class is complete. Showing a graph is now only a matter of including the JavaScript in a page and using it. The simplest example of using it would be the following.

var g = makeGraph(randomPlotsArea, options);
g.plot((x)=>{return Math.sin(x); });

Here’s the result!

I’ve made a page with a few place holders for graphs. Among other things it contains the following.

<p id="randomPlots">
	
Random plots
</p> <p id="trajectoryPlotArea" >
Plot of a trajectory height for an object thrown up at 10 m/s near the earth's surface.
</p>

To render the graphs within their appropriate places I acquire a reference to the parent element in which the graph will be contained and I pass that to the makeGraph() function. Here I render the SIN function, the COSINE function (with no values returned from 2.0 to 3.0), and a x-squared

var randomPlotsArea = document.getElementById('randomPlots');				
var options = new graphOptions();
var g = makeGraph(randomPlotsArea, options);
g.plot([
	function(x){return 5*Math.sin(x);},
	(x)=>{
			if(Math.floor(x)!=2)
				return 6 * Math.cos(x);
			return null;
		},
	(x)=>{return x * x; }
	]);

Here is the result. The range for which no value is returned is apparent from the area in which the red line on the graph is not rendered.

 

Where to From Here?

Awesome! The code works! But now what?  Well, nothing for now. This was something I wrote with temporary intentions and to keep myself from being bored. That’s not to say that I’m giving up on developing a graphing library. Once back home I checked online to see what types of other graphing libraries are available. There’s a number of them, each having their own strengths. I have a direction in which I’d like to take this that is different from the others that are out there. I may revisit this, but only after a lot more thought of what I want to do, how I want this to work,  and how the variations on graphs can be specified.