Skip to content

Instantly share code, notes, and snippets.

@tgck
Last active October 2, 2015 19:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tgck/11185861 to your computer and use it in GitHub Desktop.
Save tgck/11185861 to your computer and use it in GitHub Desktop.
HueRequestBuilder.js - a Bridge Script for Max/MSP and Philips Hue
// HueRequestBuilder.js
// a Bridge Script for Max/MSP and Philips Hue
// depends on cURL command.
//
// version: 2.2.0
// date: 2014.5.18
// author: tgck (_@taikutz.org)
//
// based on: LEKIRST's patch ... http://bit.ly/1pgasm8
// Hue API ... http://developers.meethue.com/
//
// issues unix command like these:
// curl http://192.168.0.2/api/newdeveloper/lights/1
// curl -X PUT -d '{"on":true}' http://192.168.0.2/api/newdeveloper/lights/3/state
//
// TODO : ollOn, ollOf, turnOn $1, turnOff $1
// : "search Lamps"
inlets = 1;
outlets = 1;
// consts for API
// used as a url template.
var url = {
"getState" : "http://IP/api/USERNAME/lights/INDEX",
"changeState" : "http://IP/api/USERNAME/lights/INDEX/state",
"api" : "http://IP/api",
"broker" : "http://www.meethue.com/api/nupnp"
}
// cURL
var unixCmd = {
"get" : "curl ",
"put" : "curl -X PUT -d ",
"post": "curl -X POST -d "
}
// network
var bridge = {
"ip" : "192.168.0.2",
"user" : "newdeveloper"
}
// user's bulbs
var NBULB = 4; // max bulb
var bulbs = [];
// current controller for attribute-like usage.
var currTarget; // as bulb index (0 -> (NBULB -1))
var bPost = 1; // debug Flag
// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
//
// = F U N C T I O N S U S E D I N T E R N A L =
//
// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
function loadbang() {
po ("[INFO]INIT:" + jsarguments[0] + "\n");
currTarget = 0;
setupBulbs(NBULB);
}
//
// prepare Bulb objects and bind urls to them.
// bulb オブジェクトの準備
//
function setupBulbs(n) {
for (i = 1; i <= n; i++ ) {
var bulb = { "index" : i };
bulbs.push(bulb);
}
for (i = 0; i < n; i++) {
bindUrl(bulbs[i]);
}
}
//
// add Url property to the object.
//
function bindUrl(me) {
me.urlGetState = url.getState.replace("IP", bridge.ip)
.replace("USERNAME", bridge.user)
.replace("INDEX", me.index);
me.urlChangeState = url.changeState.replace("IP", bridge.ip)
.replace("USERNAME", bridge.user)
.replace("INDEX", me.index);
}
// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
// F U N C T I O N S
// - - - - - - - - -
// F O R _ U S E R S
// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
//
// general message handler
//
function anything() {
var argarr = arrayfromargs(messagename, arguments); // cf. Max JavaScript Refference
// po ("[DEBUG]list length[XX]\n".replace("XX", argarr.length) );
// loop starts here.
var ourList = argarr;
var targetState = {}; // diffObj
while (ourList.length > 0) {
// check the head element.
var head = ourList.shift();
var type = typeof(head);
if ( typeof(head) != "number" && typeof(head) != "string" ) {
po ("[ERROR]can't process. [XX] is neither Number or String.\n".replace("XX", head));
return;
}
if ( type == "number" ) {
changeTargetTo(head);
continue;
} else if ( type == "string" ) {
// //
// process 'single pattern' //
// //
switch (head){
case "on":
targetState.on = true;
continue;
break;
case "off":
targetState.on = false;
continue;
break;
case "getstate":
issueGetState();
continue;
break;
case "newuser":
registUser();
continue;
break;
case "discover":
discoverBridge();
continue;
break;
default:
break;
}
// //
// process 'double pattern' //
// //
var val = ourList.shift();
if ( typeof(val) != "number" && typeof(val) != "string") {
po ("[ERROR]illeagal value. [XX] is not valid for [YY].\n".replace("XX", val).replace("YY", head));
return;
}
switch (head) {
case "target":
changeTargetTo(val);
break;
case "bri":
targetState.bri = val;
break;
case "hue":
targetState.hue = val;
break;
case "sat":
targetState.sat = val;
break;
case "transitiontime":
targetState.transitiontime = val;
break;
case "bridge":
setBridgeIpTo(val);
break;
default:
break;
}
}
} // end while
// send only if there is a state update.
if (countPropeties(targetState) > 0) {
issueWithObj(targetState);
}
}
//
// change target to another bulb
//
function changeTargetTo(val){
if( 1 <= val && val <= NBULB) {
currTarget = val - 1;
po ("[INFO]target changed to[XX]".replace("XX", val));
}
}
//
// issue a cURL command
//
function issueWithObj(diffObj){
var updatings = singleQuote( JSON.stringify(diffObj) ) + " ";
po("[TRACE]updatings:" + updatings);
var s = unixCmd.put + updatings + bulbs[ currTarget ].urlChangeState;
outlet(0, s);
}
function issueGetState(){
var s = unixCmd.get + bulbs[ currTarget ].urlGetState;
outlet(0, s);
}
//
// set Bridge Ip
//
function setBridgeIpTo(val){
if (typeof(val) != "string") {
po ("[ERROR]illeagal value. [XX] is not valid for [bridge].\n".replace("XX", val));
return;
}
bridge.ip = val;
setupBulbs(NBULB);
}
//
// regist new user (as "newdeveloper")
//
function registUser(){
var body = {"devicetype":"test user", "username":"newdeveloper"};
var serealBody = singleQuote( JSON.stringify(body) ) + " ";
var s = unixCmd.post + serealBody + url.api.replace("IP", bridge.ip);
outlet (0, s);
}
//
// discover the bridge
// returns bridge ip in stdout.
//
function discoverBridge(){
var ipAddressFilter = " | egrep -o '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]'"
var s = unixCmd.get + url.broker + ipAddressFilter;
outlet (0, s);
}
// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
//
// Utilities
//
// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
// count properties
function countPropeties(obj) {
var cnt = 0;
for (var prop in obj) {cnt += 1;}
return cnt;
}
// dump object
function dump(obj) {
var str = {};
for (var prop in obj) {
str += "[DUMP]" + prop + ":" + obj[prop] + "\n";
}
}
// quote for cURL
function singleQuote(str){
return "'" + str + "'";
}
// post and out
function po(str){
if (bPost == 0) {return;}
post (str + "\n");
// outlet (0, str);
}
// serialize json
// https://github.com/douglascrockford/JSON-js/blob/master/json.js
if (typeof JSON.stringify !== 'function') {
escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;
meta = { // table of character substitutions
'\b': '\\b',
'\t': '\\t',
'\n': '\\n',
'\f': '\\f',
'\r': '\\r',
'"' : '\\"',
'\\': '\\\\'
};
JSON.stringify = function (value, replacer, space) {
var i; gap = ''; indent = '';
if (typeof space === 'number') {
for (i = 0; i < space; i += 1) {
indent += ' ';
}
} else if (typeof space === 'string') {
indent = space;
}
rep = replacer;
if (replacer && typeof replacer !== 'function' && (typeof replacer !== 'object' ||
typeof replacer.length !== 'number')) {
throw new Error('JSON.stringify');
}
return str('', {'': value});
};
}
<pre><code>
----------begin_max5_patcher----------
1321.3oc4Z97bahCEG+r8eELL6wTGdR7ydq6ksm2q6tSFrQwQY.AKRjltc5+
6qPBbbRskYZHJhoIyXOHKDO8guumjdRea8J+s0OR39dez6u7Vs5aqWsRUTeA
qFtdkeU9i6Jy4pp4WQ3778D+qz+lf7nPUNi7kNNocrbVWEkURDpaBMT3s0LA
m9ej9x.zlfmpacmXrxvPoMsDNgIxEzZ1MsjcBsUljFKuQurv9OCF9v6eFtIc
6H9ZCQWae+C+TStX2cT19m0VgO0VQp1EROp0nEptV816+.Jz+nNAKuR8.7+T
KMuzu+G99508eb0qDiET9t5GrBG088z4iiXEAihLwQHcd43t5pJYu6G33m6H
dU4LZSWormy16M.bt2lW0em78Bbp2Kgm48xXo21teq55C06TjMdSjDo8DMLV
8EJ7LBT7IA6ePXjGxmaxxIhtlmgTayEbvFzHYf3DijAMyj4b9taaoE6IdPhz
kT9OLe9v+TtiwXkCdhQ2wL63NxuiTV5IehRizix+al2cBQyGu95cecWoz5SB
2Hu0qE00kxGv0aIsLtfPYePciWO1dxpR1U2wDGSxKK4PygjCqCUh0vEklMFx
6TbMzRRNQd6dhvC4cmzqDBB7Ds4LNsOVufVoJJ3mQFtc+t5x5VcOOXS5UG8A
bTWdNjqQHEXgLjBrAXiiCamggAEPQHOoOsGFKeQqJHJRUPZpGOW3kk45nMDh
UgIg.kZEAlYKXS3tvUq5nqPblIhFhmWhJ6.aOybCg2BDgNGhn8w3ki3ky1aV
.FqGmFFlaXf1E+Lzxj96vynUVtfzdCgkusTUifYTa1qL+Mv48qGwpVEFZbL9
Pzu3hvgQtmjFDm89qA6Gaw80fiTcPBZbjkvfe0kfgnoKASe+kf8yxYAHAGn5
fDDLIAwI1YxM02dqyiMPQJThRLhQ8oX3rXKxRXisTnVX5Dn1LmsPF4Kxl8Gf
VSqL9iGWTH6I9y1pgOQeGghOZsuIlCeEXk9tJw.u9N8YirOov5ibIJQ+kItf
xrimTen6vP0Rrv3EhSkN.dpw.3Pfc32BbMoCxPHV4dB.x3p7isTJ+nUMkDi4
c+cIY55ofgBMmM8HKk.u945+TBpccAllclmqOJvN5qptRAUpvd7RasikUY3.
jZs4i5LvjNCRrahhc9ADxB58QGnVj4ADhsz.BC4XegfNPukglQWjUQGrLP2v
NPbAzYoCBvBwScXNGlWNDfsCyVHtnfd.zKvLasSWKDlgPWlYI1bEVKj4rklb
4biAVZi+VFqIcbxt5kQYduUrzZ5kyAtgVRbcxAQ5bio2B+HiKCM0NjSN8CtH
WPVFQ4zNqQFWeUrM2jdLVcfGBiVHIRZ3fNjYLbmkl7KZYkCtAsmwvcXqjZ26
4deti7mj+sivE+dGsrfzt4d9qOYuupwCvXsnBmZJKuS3HLvyefTbi9rvcStP
zR21IzGB6UG.krAjA7Oz.F3ggMCT+9X7shxbUGgtWbfuU849xe9qJdcW6tQB
MdFn8dpWWHsFJScthOpRvypyczhBB63bnTP481Xw42wxoZN8GkXGxbxbJqws
XShSYMwNk0D4TVS+IVykLGjaYNvDLmWXyuo1SfSgG7ThA9Ba9M0dlRTPbh8r
G2JNH1sB8fcqoW3VABQSw0BaOWKjaMHJBbKywshK6VChBt0LkA2RICtUPYvw
VymaEUFbLOK2JJH3VQAmBbdwvrS2bzIWIuo4ARKenMUVheU985D4Eek5RJSe
opE8aIOPGqe559V66q+e.1ogcSC
-----------end_max5_patcher-----------
</code></pre>
@tgck
Copy link
Author

tgck commented May 18, 2014

update 2.1.0 (2014.5.18)

  • change bridge ip with (bridge X.X.X.X) message.
  • discover the bridge with (discover) message.

@tgck
Copy link
Author

tgck commented May 18, 2014

update 2.2.0 (2014.5.18)

  • regists new user to the bridge with (newuser) message.

@Realeyz
Copy link

Realeyz commented Oct 2, 2015

Greetings. I am using the xxHueTest in Max to try this code.

I only changed the IP to the IP of the device. I am experience the following error with the HueRequest Builder code:

js: HueRequestBuilder.js: Javascript TypeError: bulbs[currTarget] is undefined, line 213
js: error calling function getstate [HueRequestBuilder.js]

Do you having ideas?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment