Source: org/terraswarm/accessor/accessors/web/devices/Lifx.js

// Copyright (c) 2015-2017 The Regents of the University of California.
// All rights reserved.
//
// Permission is hereby granted, without written agreement and without
// license or royalty fees, to use, copy, modify, and distribute this
// software and its documentation for any purpose, provided that the above
// copyright notice and the following two paragraphs appear in all copies
// of this software.
//
// IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
// FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
// ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
// THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE.
//
// THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
// PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
// CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
// ENHANCEMENTS, OR MODIFICATIONS.
//

/** This accessor discovers and controls a Lifx lightbulb.
 *
 *  If the bulbs are already set up (through the Lifx mobile app for example),
 *  then the host running this accessor needs to be connected to the same LAN.
 *
 *  Otherwise, with an unconfigured bulb, you can connect your computer to the Lifx
 *  network provided by the bulb (an open Wifi network with a name like
 *  "LIFX_Axx_xxxxxx").
 *  
 *  If you have a bulb that has been configured to operate on some other network, you can
 *  reset it to factory defaults by turning it on and off five times in succession.
 *
 *  While it is possible to interact with Lifx bulbs over the Internet by 
 *  sending HTTP requests to the Cloud, this module does not provide such
 *  mechanism.
 *
 *  The communication with Lifx light bulbs is done over UDP/IP. Messages are 
 *  arrays of numeric bytes ordered in little-endian. The packets construction
 *  can be found in this link:
 *  https://lan.developer.lifx.com/docs/introduction
 * 
 *  Upon initialization, this accessor creates a UDP socket for communication.
 *  There are two ways to configure this accessor in order to control a bulb:
 *  * the first one consists on discovering Lifx bulbs on the network and then 
 *    selecting the one to use.
 *  * the second is by running a manual setup, where the light parameters are
 *    received in the input port. A use case of this scenario is when a server 
 *    sends information about available devices and their parameters.
 *    
 *  Discovery starts if an input is provided in 'triggerDiscovery' input port. 
 *  The accessor will broadcast discovery messages. Since available bulbs will 
 *  be asynchronously sending State messages, the accessor will be listening.
 *  Discovery messages will be repeatedly sent every 'discoveryInterval'
 *  parameter value, if no device has been selected. Each newly discovered 
 *  light bulb will be added to discoveredLifxLights array. Selecting a device  
 *  is done by providing the index of the LifxLight in the array of discovered 
 *  devices in the input port 'selectLight'.
 *  
 *  Once the light bulb is chosen and successfully configured, discovery will 
 *  stop and the accessor will start to react to 'control' input.
 *
 *  The *control* input is a JSON object that may have the following properties:
 *  * on: 'on' to turn on; 'off' to turn off.
 *  * hue: Color, for bulbs that support color. This is a number in the
 *    range 0-65535.
 *  * color: Color, for bulbs that support color. This is a string that is converted
 *    into a hue number using the colorToHexHue variable.
 *
 *  HSBK is used to represent the color and color temperature of a light. The
 *  color is represented as an HSB (Hue, Saturation, Brightness) value.
 *  Please refer to https://en.wikipedia.org/wiki/HSL_and_HSV

 *  A LifxLight is a class that has the following parameters:
 *  * **ipAddress**: IP address of the bulb in the LAN.
 *  * **port**: integer. It defaults to 56700 as noted in the Lifx developer
 *      API.
 *  * **macAddress**: a string of 12 hex numbers (6bytes) that refer to the 
 *      bulb macAddress.
 *  * **color**: the current hue values.
 *  * **power**: if true, then the bulb is switched on, false if switched off.
 *  * **userName**: this is the name of the user. It can be used to filter the 
 *      received messages.
 *      
 *  LifxLight class declares a set of functions for light control:
 *  * **swithOn()**: switches the light on. The latest selected color is the one
 *      used. 
 *  * **swithOff()**: switches the light off.
 *  * **setColor()**: changes the light color.
 *  * **probe()**: sends a 'getState' message to ckeck if the light bulb is still
 *      working. This feature allows for fault-tolerance.
 *  
 *   The supported communication schemas between the accessor and the bulbs are the 
 *   following:
 *   * To run discovery, a discovery message will be broadcasted. Bulbs will answer with
 *     a 'stateService' message.
 *   * To switch the light on or off, a 'setPower' message is sent. The bulb will answer 
 *     with a 'statePower' message.
 *   * To set the light color, a 'setColor' message is sent. The bulb will answer 
 *     with a 'stateLight' message.
 *   * To probe the light, a 'getLight' message is sent. The bulb will answer 
 *     with a 'stateLight' message.  
 *
 *  @accessor devices/Lifx
 *  @input {JSON} control JSON control for the Hue, for example,
 *                {"on" : "on", "color" : "red"}
 *  @input triggerDiscovery signal to start discovery
 *  @input {int} selectLight index in discoveredLifxLights array of the light to 
 *   be configured.
 *  @input {JSON} manualBulbSetup JSON object for the light configuration. At least,
 *   the mac address and the ip address should be provided. Example:
 *            {"ipAddress": "192.168.1.100", "macAddress": "d073d523995c"}
 *  @input probe signal to probe the bulb
 *  @output {JSON} data Outputs the received information from the bulb
 *  @parameter {int} discoveryInterval The time interval to re-send discovery messages,
 *   if no light has been configured
 *  @parameter {string} listeningIpAddress the IP address of the accessor to listen to the
 *   bulb packets. This defaults to 0.0.0.0 to listen to all UDP packets. 
 *  @parameter {int} listeningPort the port number for listening. If a swarmlet is using two 
 *   or more instances of Lifx accessor, than each one needs to have its own distinct listening 
 *   port.
 *  @parameter {string} userName Name of the user. Should be 8bytes.
 *  @author Chadlia Jerad
 *  @version $$Id: Lifx.js 1597 2017-04-29 15:41:50Z cxh $$
 */

// Stop extra messages from jslint.  Note that there should be no
// space between the / and the * and global.
/*globals alert, clearTimeout, console, error, exports, httpRequest, require, setTimeout  */
/*jshint globalstrict: true*/
"use strict";

var udpSocket = require('@accessors-modules/udp-socket');

// Socket for UDP communication
var socket = null;

// Says if the socket is running or closed
var running = false;

// Instance of LifxLight class, that is the configured light
var lifxLightBulb = null;

// Handle for re-triggering discovery if no Bulb is discovered or chosen
var handleDiscovery;

// Array of the discovered Lifx light bulbs
var discoveredLifxLights = [];

// Says if the discovery is running of not.
var discoveryMode = false;

// Says if we are waiting for an answer to light probe
var probe = false;

exports.setup = function () {
	// Inputs
	this.input('control', {
		'type': 'JSON',
		'value': {}
	});
	this.input('manualBulbSetup', {
		'type': 'JSON',
			'value': {}
	});
	this.input('probe');
	this.input('selectLight', {
		'type': 'int',
		'value': 0
	});
	this.input('triggerDiscovery');
	
	// Outputs
	this.output('data', {
		'type': 'JSON',
		'value': {}
	});
	this.output('discovered');

	// Parameters
	this.parameter('discoveryInterval', {
		type: 'int',
		value: 3500
	});
	this.parameter('listeningIpAddress', {
		type: 'string',
		value: '0.0.0.0'
	});
	this.parameter('listeningPort', {
		type: 'int',
		value: 50000
	});
	this.parameter('setFaultTolerent', {
		type: 'boolean',
		value: false
	});
	this.parameter('userName', {
		type: 'string',
		value: 'abcdefgh'
	});
};

exports.initialize = function () {
	var thiz = this;

	// Initialize and set the socket	
	closeAndOpenSocket.call(this);
	running = true;
	
	// Trigger discovery 
	this.addInputHandler('triggerDiscovery', function() {
		lifxLightBulb = null;
		discoveryMode = true;
		discoverLifx(socket);
		
		// Make sure to re-execute discovery if no bulb is already set
		handleDiscovery = setInterval(function () {
			if (!discoveryMode) {
				clearInterval(handleDiscovery);
			} else {
				discoverLifx(socket);
			};
		}, this.getParameter('discoveryInterval'));
	});
	
	// Select a discovered LifxLight, if applicable
	this.addInputHandler('selectLight', function() {
		var selectLight = thiz.get('selectLight');
		if (discoveryMode) {
			if (selectLight < discoveredLifxLights.length) {
				lifxLightBulb = discoveredLifxLights[selectLight];
				// console.log("selected light at "+selectLight+" with: " + util.inspect(lifxLightBulb));
				discoveryMode = false;
				discoveredLifxLights = [];
			}
		}
	});
	
	// Manual bulb setup
	this.addInputHandler('manualBulbSetup', function() {
		discoveryMode = false;
		discoveredLifxLights = [];
		var bulb = this.get('manualBulbSetup');
		if (bulb.port) {
			lifxLightBulb = new LifxLight(bulb.ipAddress, bulb.port, bulb.macAddress, this.getParameter('userName'));
		} else {
			lifxLightBulb = new LifxLight(bulb.ipAddress, 56700, bulb.macAddress, this.getParameter('userName'));
		}
	});

	// Input handler for handling control 
	this.addInputHandler('control', function () {
		if (lifxLightBulb) {
			var control = this.get('control');
			// Control switching the light on or off
			if (control.on && control.on === 'on') {
				lifxLightBulb.switchOn(socket);
				thiz.send('data', {'id': lifxLightBulb.macAddress, 'light': 'on'});
			} else if (control.on && control.on === 'off') {
				lifxLightBulb.switchOff(socket);
				thiz.send('data', {'id': lifxLightBulb.macAddress, 'light': 'off', 'color': lifxLightBulb.color});
			}
			// Control the light color. The color value can be a string (attribute color) or
			// a hue value (number)
			if (control.color) {
				var hue = colorToHexHue[control.color];
				if (hue) {
					lifxLightBulb.setColor(socket, hue);
					thiz.send('data', {'id': lifxLightBulb.macAddress, 'light': lifxLightBulb.power, 'color': lifxLightBulb.color});
				} else {
					console.log('No supported hue value of the color: ' + control.color);
				}
			} else if (control.hue){
				if (typeof control.hue === 'number' && control.hue >= 0 && control.hue <= 65535) {
					lifxLightBulb.setColor(socket, getHexStringAt(control.hue, 0));
					thiz.send('data', {'id': lifxLightBulb.macAddress, 'light': lifxLightBulb.power, 'color': lifxLightBulb.color});
				} else {
					console.log('Non valid hue value: ' + control.hue);
				}
			}
		}
	});

	// Input handler for probe
	this.addInputHandler('probe', function() {
		var thiz = this;
		if (!discoveryMode) {
			if (lifxLightBulb) {
				probe = true;
				setTimeout(function(){
					if (probe) {
						// If the light disappears, launch discovery again
						// FIXME: is launching discovery OK?
						lifxLightBulb = null;
						thiz.provideInput('triggerDiscovery', 1);
						thiz.react();
					}
				}, 3000);
			}
		}
	});
};

exports.wrapup = function () {
	if (socket) {
		socket.close();
		socket = null;
	}
	lifxLightBulb = null;
	running = false;
	handleDiscovery = null;
	discoveryMode = false;
}

/////////////////////////////////////////////////////////////////////////
//// Helper functions.

/** For the passed light characteristics, add it to discoveredLifxLights array only if
 *  it is new. The light is new if the macAddress is new.
 *  
 *  @param packet A JSON object containing the new light bulb characteristics.
 *  @return true if added, false if not.
 */
var addToDiscovredLightsIfNew = function(packet) {
	var isNew = true;
	discoveredLifxLights.forEach(function(light) {
		if (light.macAddress === packet.macAddress) {
			isNew = false;
			// TODO: Update the color and the power of the lifxLightBulb instance
		}
	});
	if (isNew) {
		discoveredLifxLights.push(new LifxLight(packet.ipAddress,
				packet.port,
				packet.macAddress,
				this.getParameter('userName')));
		return true;
	}
	return false;
}

/** Builds a UDP packet to be sent, based on the provided options.
 *  Please refer to https://lan.developer.lifx.com/docs/building-a-lifx-packet
 *  to know about the message format.
 *
 *  @param options A JSON object that describes the packet features
 *  @return UDP Packet to be sent 
 */
var buildPacket = function (options) {
	console.log('options = '+options);
    var packet = '';

    // ============================= Construct the header
    // ----------------------- Frame
    // -- size = 16bits
    // The size will be computed and added at the end
    // -- origin+tagged+addressable+protocol = 16bits
    if (options.toAll) {
        packet += '0034';
    } else {
        packet += '0014';
    }
    // -- source: set by the client (if all 0 then response broadcast) 32bits
    packet += this.userName;

    // ----------------------- Frame address
    // -- target mac address (48bits)+0000
    if (options.toAll) {
        packet += '000000000000' +'0000';
    } else {
        packet += this.macAddress + '0000';        
    }
    // -- reserved (48bits)
    packet += '000000000000';
    // -- reserved + ack_required + res_required (8bits);
    if (!options.ackRequired && !options.resRequired) {
        packet += '00';
    } else if (!options.resRequired) {
        packet += '02';
    } else {
        packet += '01';
    };
    // -- sequence (8bits): reference to the message
    if (options.sequence) {
        packet += ''+ options.sequence;
    } else {
        packet += '00';
    }

    // ----------------------- Protocol header
    // -- reserved (64bits)
    packet += '0000000000000000'; 
    // -- message type (16bits) + reserved (16bits)
    if (options.getLight) {
        packet += codesForGetMessages['getLight'] + '00' + '0000'; // Get --> 101
    } else if (options.setColor) { 
        packet += '6600' + '0000'; // SetColor --> 102
    } else if (options.setPower) {
        packet += '7500' + '0000'; // SetPower --> 117
    }

    // ============================= Construct the Payload
    if (options.setPower) {
        if (options.setPower.on) {
            packet += 'ffff00000000';
        } else {
            packet += '000000000000';
        }
    } else if (options.setColor) {
    	// -- reserved (8bits)
    	packet += '00';
    	// -- Hue value (16bits)
    	packet += options.setColor.h;
    	// -- Saturation value (16bits)
    	packet += options.setColor.s;
    	// -- Brightness value (16bits)
    	packet += options.setColor.b;
    	// -- Temperature value (16bits).Set to 3500 Kelvin
    	packet += 'ac0d';
    	// -- duration (32bits)
    	packet += '00000000';
    }
    
    // Compute the size, convert it to hexString then add it to the packet.
    // Note that it is a little endian encoding.
    var packetSize = (packet.length + 4) / 2;
    packetSize = (packetSize & 0xFFFF).toString(16);
    if (packetSize.length <= 2) {
		packet = packetSize + '00' + packet;
	} else {
		packetSize = '0' + packetSize;
		packetSize = packetSize.slice(-4);
		var _tp = packetSize.slice(-2);
		packetSize += _tp;
	}
	
    return packet;
}

/** 
 * Creates and opens a socket. Also sets the ractions to recieved messages.
 */
var closeAndOpenSocket = function () {
	var thiz  = this;
	var listeningPort = null;

	if (socket) {
		socket.on('close', function() {
			socket = null;
			closeAndOpenSocket.call(thiz);
		});
		socket.close();
	} else {
		var listeningPort = this.getParameter('listeningPort');
		var listeningAddress = this.getParameter('listeningIpAddress');
		var enableBroadcast = true;
		
		socket = udpSocket.createSocket(null, null, enableBroadcast);
		    
	    socket.setReceiveType('string');
	    socket.setSendType('string');
		socket.setRawBytes(true);
	
		socket.on('error', function (message) {
	        error(message);
	    });
		socket.on('message', function (message, sender){
		    if (running) {	        	
		    	var packet = parseReceivedPacket(message, sender);
	    		console.log('Message received from: ' + packet.macAddress +
	    				' * Message code is: ' + packet.messageCode);
	    		if (discoveryMode) {
		    		// Check if this is a state message. Recall that a state message
		    		// is sent after receiving a discovery message
		    		if (packet.messageCode === 'stateService') {
		    			if (addToDiscovredLightsIfNew.call(thiz, packet)) {
		    				var index = discoveredLifxLights.length;
		    				thiz.send('discovered', discoveredLifxLights);
		    				console.log('New discovered Lifx Light Bulb: ' + 
		    						packet.macAddress + '@' +  
		    						packet.ipAddress + ':' + packet.port +
		    						' * Added at index: ' + index);
		    			}
		    		}
		    	} else {
		    		if (lifxLightBulb) {
		    			if (lifxLightBulb.ipAddress === packet.ipAddress &&
		    					lifxLightBulb.macAddress === packet.macAddress &&
		    					lifxLightBulb.port === packet.port) {
		    				switch (packet.messageCode) {
		    					case 'stateLight':
		    						lifxLightBulb.power = packet.power;
		    						lifxLightBulb.color = packet.color;
		    						lifxLightBulb.label = packet.label;
		    						if (probe) {
		    							probe =false;
		    						};
		    						break;
		    					case 'statePower':
		    						lifxLightBulb.power = packet.power;
		    						break;
		    				}
		    			}
		    		}
		    	}
		    }
		});
		    		
	    socket.on('listening', function () {
	        if (running) {
	        	console.log('listening: ' + true);
	        }
	    });
	    socket.on('close', function () {
	        if (running) {
	            console.log('listening: ' + false);
	        }
	    });
	
	    try {
	    	socket.bind(listeningPort, listeningAddress, function () {
				console.log('bind success');
			});
	    } catch (e) {
	    	this.setDefault('listeningPort', this.getParameter('listeningPort') + 1);
	    	listeningPort = this.getParameter('listeningPort');
	    	socket.bind(listeningPort, listeningAddress, function () {
				console.log('bind success');
			});
	    }
	}
}

/** Convenience function for converting a string, which each character is an
 *  hexadecimal number to an array buffer of bytes. This latter will contain 
 *  unsigned bytes with the value of two consecutive characters from the provided
 *  string.
 *
 *  @param hexString String of hexadecimal values in each character
 *  @return converted hexString into ArrayBuffer
 */
var convertHexStringToByteArray = function (hexString) {
    var buffer = new ArrayBuffer(hexString.length/2);
    var i = 0;
    for (i = 0 ; i < hexString.length ; i=i+2 ) {
        var hs = hexString.slice(i, i+2);
        buffer[ i / 2] = (parseInt(hs, 16)) & 0xFF;
    }
    return buffer; 
}

/** Broadcasts UPD discovery messages. If Lifx bubls are in the network, they will 
 *  send back a State message.  
 * 
 *  @param socket The socket instance to use for sending the discovery message
 */
var discoverLifx = function (socket) {
    // needs more elaboration
    var hexPacket = '24000034abcdefgh00000000000000000000000000000003000000000000000002000000';
    var packet = convertHexStringToByteArray(hexPacket);

    socket.send(packet, 56700, '255.255.255.255', function (er) {
        console.log('Start discovery: Broadcast at 255.255.255.255:56700... '+er);
    });
}

/** Convenience function for converting a bytesArray to a string of hexadecimal 
 *  characters. For this, each character of is mapped into two characters that 
 *  represent hexadecimal values.
 *
 *  @param bytesArray Array of raw bytes.
 *  @param start index from where to start the conversion.
 *  @param end index where to stop the conversion (index not included).
 *  @return converted bytes to a string of hexa values.
 */
var getHexStringAt = function (bytesArray, start, end) {
    var hexString = '', hex;
    if (!end) {
    	end = bytesArray.length;
    }
    for (var i = start ; i < end ; i++ ) {
        hex = bytesArray[i];
        hex = (hex & 0xFF).toString(16);
        hexString += ("000"+hex).slice(-2);
    }
    return hexString; 
}

/** Returns a JSON object that describes the received packet. This object contains:
 *  * ipAddress
 *
 *  @param messageBytes The received message during listening as a bytes array.
 *  @param sender String containing the IP Address and port of the message sender. 
 *  @return JSON object describing the device and the message features.
 */
var parseReceivedPacket = function (messageBytes, sender) {
	var packetObject = {};
	
	// Extract packet information
	if (typeof sender === 'string') {
		sender = JSON.parse(sender);
	};
	packetObject.ipAddress = sender.ipAddress;
	packetObject.port = Number(sender.port);
	packetObject.macAddress = getHexStringAt(messageBytes, 8, 14);
	packetObject.messageCode = getHexStringAt(messageBytes, 32, 33);
	packetObject.messageCode = codesForStateMessages[packetObject.messageCode]; 
	
	// TODO: add payload parsing, if applicable
	switch (packetObject.messageCode) {
		case 'stateService':
			break;
		case 'stateLight':
			packetObject.color = {};
			packetObject.color.h = parseInt(
					getHexStringAt(messageBytes, 37, 38) + 
					getHexStringAt(messageBytes, 36, 37), 16);
			packetObject.color.s = parseInt(
					getHexStringAt(messageBytes, 39, 40) + 
					getHexStringAt(messageBytes, 38, 39), 16);
			packetObject.color.b = parseInt(
					getHexStringAt(messageBytes, 41, 42) + 
					getHexStringAt(messageBytes, 40, 41), 16);
			packetObject.color.k = parseInt(
					getHexStringAt(messageBytes, 43, 44) + 
					getHexStringAt(messageBytes, 42, 43), 16);
			packetObject.power = parseInt(
					getHexStringAt(messageBytes, 47, 48) + 
					getHexStringAt(messageBytes, 46, 48), 16);
			packetObject.label = getHexStringAt(messageBytes, 49, 53);			
			break; 
		case 'statePower':
			packetObject.power = parseInt(getHexStringAt(messageBytes, 37, 38) + getHexStringAt(messageBytes, 36, 37), 16);
			break;
	}
	
    return packetObject;
}

/////////////////////////////////////////////////////////////////////////
//// LifxLight class and its functions.

/** Create using new a Lifx light bulb object. The created object includes the 
*  following properties:
*  * **ipAddress**: The IP address of the bulb in the Local Area Network.
*  * **port**: The port number. This usually defaults to 56700.
*  * **macAddress**: The bulb's mac address.
*  * **userName**: the userName, it is copied from the accessor parameter 
*      'userName' and checked to be 8 characters long.
*  * **color**: The current light color
*  * **power**: Boolean. Says if the light is on or off
*
*  @param ipAddress String with the ipAddress of the bulb
*  @param port Bulb's communication port, it defaults to 56700
*  @param macAddress A 12 bytes string of the mac address of the bulb.
*  @param userName An 8 bytes string of the user name. If a wrong value is
*   provided, then it is corrected.
*/
function LifxLight (ipAddress, port, macAddress, userName) {
	if (ipAddress && typeof ipAddress === 'string') {
	   this.ipAddress = ipAddress;        
	} else {
	   this.ipAddress = '';
	}
	
	if (port && typeof port === 'number') {
	   this.port = Math.round(port);        
	} else {
	   this.port = 56700;
	}        
	
	if (macAddress && typeof macAddress === 'string') {
	   this.macAddress = macAddress;        
	} else {
	   this.macAddress = '';
	}
	
	// Force the userName to be 8 characters
	if (userName && typeof userName === 'string') {
		if (userName.lenght > 8) {
			userName = userName.substring(0, 8);
		} else if (userName.lenght < 8) {
			userName += 'abcdefgh';
			userName = userName.substring(0, 8);
		}
		this.userName = userName;
	} else {
		this.userName = 'abcdefgh';
	}
	
	this.color = {};
	this.power = 0;
}

/** Switch the light on. First, the packet options are set. Then, the
 *  packet is build as a string of hexadecimal numbers. Finally, the packet
 *  is converted to a byte array format and sent via the socket.
 *
 *  @param socket The socket used for sending the udp message
 */
LifxLight.prototype.switchOn = function(socket) {
	var thiz = this;
	// Set the options for switching the light on
	console.log('this is switch on and the ip address is: '+this.ipAddress+'\n'+util.inspect(this));
	var options = {};
	options.resRequired = 1;
	options.setPower = {}; 
	options.setPower.on = true;
	
	// Build the hexadecimal packet and then convert it to an array of bytes
	var hexPacket = buildPacket.call(this, options);
	console.log('prePacket = ' + hexPacket);
	var packet = convertHexStringToByteArray(hexPacket);
	
	// Send the packet over the provided socket
	socket.send(packet, thiz.port, thiz.ipAddress, function () {
	   console.log('Switch light on ' + thiz.macAddress + '@' + thiz.ipAddress + ':' + thiz.port + ' msg = ' + packet);
	});
};

/** Switch the light off. First, the packet options are set. Then, the
 *  packet is build as a string of hexadecimal numbers. Finally, the packet 
 *  is converted to a byte array format and sent via the socket.
 *
 *  @param socket The socket used for sending the udp message
*/
LifxLight.prototype.switchOff = function(socket) {
	// Set the options for switching the light off
	var options = {};
	options.resRequired = 1;
	options.setPower = {}; 
	options.setPower.on = false;
	
	// Build the hexadecimal packet and then convert it to an array of bytes
	var hexPacket = buildPacket.call(this, options);
	var packet = convertHexStringToByteArray(hexPacket);
	
	// Send the packet over the provided socket
	socket.send(packet, this.port, this.ipAddress, function () {
	   console.log('Switch light off ' + this.macAddress + ' at ' + this.ipAddress + ':' + this.port + ' msg = ' + message);
	});
};

/** Set the Lifx light color. 
 *
 *  @param socket The socket used for sending the udp message
 *  @param hue A JSON object with 3 hexadecimal-little endian representations
 *   of the values of hue, saturation and brightness ranging between 0 and 65535.
 */
LifxLight.prototype.setColor = function(socket, hue) {
	// Set the options for switching the light off
	var options = {};
	options.resRequired = 1;
	options.setColor = {}; 
	options.setColor.h = hue.h;
	options.setColor.s = hue.s; 
	options.setColor.b = hue.b;
	
	// Build the hexadecimal packet and then convert it to an array of bytes
	var hexPacket = buildPacket.call(this, options);
	var packet = convertHexStringToByteArray(hexPacket);
	
	// Send the packet over the provided socket
	socket.send(packet, this.port, this.ipAddress, function () {
	   console.log('Switch light off ' + this.macAddress + ' at ' + this.ipAddress + ':' + this.port + ' msg = ' + message);
	});
}

/** Probe the Lifx bulb to check if it is working. Probe sends a 'getLight' message.
 *  If the bulb is working, it will answer with a 'stateLight' message. If no answer 
 *  is received within 3000 ms, then the current lifxLightBulb will be removed. 
 *  
 *  @param socket The socket used for sending the udp message
 */
LifxLight.prototype.probe = function(socket) {
	// Set the options for switching the light off
	var options = {};
	options.ackRequired = 1; options.resRequired = 1;
	options.getLight = true ;
	
	// Build the hexadecimal packet and then convert it to an array of bytes
	var hexPacket = buildPacket.call(this, options);
	var packet = convertHexStringToByteArray(hexPacket);
	
	// Send the packet over the provided socket
	socket.send(packet, this.port, this.ipAddress, function () {
	   console.log('Switch light off ' + this.macAddress + ' at ' + this.ipAddress + ':' + this.port + ' msg = ' + message);
	});
}

/////////////////////////////////////////////////////////////////////////
//// Constants 

// The following 3 objects contain a self describing textual values of the codes
// exchanged between the Lifx bulb and its accessor.
// The list is not exhaustive, as only the most useful ones are kept. The remaining 
// are omitted. The codes are given in hexadecimal.

var codesForStateMessages = {
	'03': 'stateService',       // 0x03 = 3 d
	'6b': 'stateLight',         // 0x6b = 107 d  
	'76': 'statePower'          // 0x76 = 118 d  
};

var codesForSetMessages = {
    'setColor': '66',           // 0x66 = 102 d
    'setPower': '75'            // 0x75 = 117 d
};

var codesForGetMessages = {
    'getLight': '65',           // 0x65 = 101 d
    'getPower': '74'            // 0x74 = 116 d
};

var colorToHexHue = {
	'red':   {'h': '0000', 's': 'ffff', 'b': 'ffff'},
	'blue':  {'h': 'aaaa', 's': 'ffff', 'b': 'ffff'},
    'green': {'h': '5555', 's': 'ffff', 'b': 'ffff'},
    'yellow':{'h': '2d28', 's': 'ffff', 'b': 'ffff'},
    'white': {'h': '0000', 's': 'ffff', 'b': 'ffff'}
};