Bose T20 Plugin Help

Hi All,

I’m new to The Home Remote, but what an amazing piece of software. Finally a good replacement for the old Pronto remotes I used to use. I currently use Roomie remote for my set up which is good but customising the layout is not easy and it is not as flexible as the home remote.

Part of my set up uses a Bose Lifestyle T20 system, which can only be controlled via serial for which I use a Global Cache IP2SL. To do this I created the plugin below (which needs some tidying up), however where I am struggling is getting the volume feedback to link into back onto a page. I can’t attach my HRP yet as a new user, but the plugin is below:


plugin.Name = "BOSE_T20";
plugin.OnChangeRequest = onChangeRequest;
plugin.OnConnect = onConnect;
plugin.OnDisconnect = onDisconnect;
plugin.OnPoll = onPoll;
plugin.OnSynchronizeDevices = onSynchronizeDevices;
plugin.PollingInterval = 10000;
plugin.DefaultSettings = {
    "Host"  : "192.168.10.90",
    "Port"  : "4999",
};


var socket = new TCPClient();

var mediaCommandMappings = {
    "VolumeUp"      : "\x0b\x00\x01\x04\x03\x00\x2c\x01\x00\x00\x20\x0d",
    "VolumeDown"       : "\x0b\x00\x01\x04\x02\x00\x2c\x01\x00\x00\x21\x0d",
    "Mute"       : "\x0b\x00\x01\x04\x01\x00\x2c\x01\x00\x00\x22\x0d",
    "PowerToggle"   : "\x0b\x00\x01\x04\x4C\x00\x2c\x01\x00\x00\x6F\x0d",

};

var inputSourceMappings = {

};


function onChangeRequest(device, attribute, value) {
    console.log(plugin.Name + " onChangeRequest called...");
    switch(attribute) {
        case "MediaCommand":
        case "InputSource":

            var cmd = "";

            if(attribute == "MediaCommand") {
                cmd = mediaCommandMappings[value];
            } else if(attribute == "InputSource") {
                cmd = inputSourceMappings[value];
            }

            if(cmd) {
                console.log(plugin.Name + " sending command: " + cmd);
                socket.send(cmd);
				console.log(plugin.Name + " Received: " + socket.receive({encoding: null}))
            } else {
                console.log(plugin.Name + " ERROR: unsupported command: " + value);
            }
            break;

        default:
            console.log("ERROR: unsupported attribute type: " + attribute);
            break;
    }
}

function onConnect() {
	console.log(plugin.Name + " onConnect called...");

    var host = plugin.Settings["Host"];
    var port = plugin.Settings["Port"];

    console.log(plugin.Name + "  Host: " + host);
    console.log(plugin.Name + "  Port: " + port);    
    console.log(plugin.Name + "  connecting...");
    socket.connect(host, port);
    console.log(plugin.Name + "  connected");
}

function onDisconnect() {
    console.log("onDisconnect called...");
    socket.close();
    console.log("  disconnected");
}

function onPoll() {
    console.log(plugin.Name + " onPoll called...");
    //We need to check the status of the system to confirm if power is on.
    socket.send("\x07\x00\x01\x1d\x00\x00\x1b\x0d");
    var data = socket.receive({encoding: null});
    console.log(plugin.Name + " status data received: " + data);
    //We look to see if the power is on or not we need to remove the sysReady String
    var hexdata="";
    var tempbit="";
    var i=0;
    while (i <data.length) {    // loop till all decimals are converted to hex
    	tempbit=data[i].toString(16); // Get one Hex
    	if(tempbit.length<2){tempbit="0"+tempbit;}  // if it is not 2 letters/number add a 0 in front for easier reading
  		hexdata=hexdata+tempbit + " ";  // add that hex value to hexdata and seperate them with a space
  		i=i+1;
	}

    console.log(plugin.Name + " status hexdata: " + hexdata)
    data = data.toString();
    var bytes = data.split(",");
    console.log(plugin.Name + " split string 1: " + bytes[0]);
    console.log(plugin.Name + " split string 5: " + bytes[4]);
    if(bytes[2]==1&&bytes[3]==29&&bytes[4]==0){
    	console.log(plugin.Name + ":Power Off");
    	throw "Bose-Power Off"
    }else if(bytes[2]==1&&bytes[3]==29&&bytes[4]==1){
    	console.log(plugin.Name + ":Power On");
    	//if power on then we can look to get the volume feedback
    	//sleep(2500);
    	var loop=0;
    	while(loop<3){
    	socket.send("\x07\x00\x01\x15\x00\x1e\x0d");
    	sleep(250)
    	var data = socket.receive({encoding: null});
    	console.log(plugin.Name + " volume data received: " + data);
    	var hexdata="";
    	var tempbit="";
    	var i=0;
    	while (i <data.length) {    // loop till all decimals are converted to hex
    	tempbit=data[i].toString(16); // Get one Hex
    	if(tempbit.length<2){tempbit="0"+tempbit;}  // if it is not 2 letters/number add a 0 in front for easier reading
  		hexdata=hexdata+tempbit + " ";  // add that hex value to hexdata and seperate them with a space
  		i=i+1;
		}
		console.log(plugin.Name + " volume hexdata: " + hexdata)
    	//We look to see if the power is on or not we need to remove the sysReady String
    	data = data.toString();
    	var bytes = data.split(",");
    	var device = plugin.Devices
    	//first byte should be 5 if volume command
    	if(bytes[2]==1&&bytes[3]==21){
    		console.log(plugin.Name + " split string: " + bytes[4]);
    		device.Volume = bytes[4];
    		loop=10;
    	}else{
    		console.log(plugin.name + " Not volume Feedback!")
    		loop=loop + 1;
    	}
    	}
    }
    
    

}



function onSynchronizeDevices() {
    console.log("onSynchronizeDevices called...");

    var device = new Device();
    device.Id = plugin.Name;
    device.DisplayName = "Bose T20";
    device.Icon = "TV";
    device.Capabilities = [ "MediaControl", "MediaInputSource", "AudioVolume" ];
    device.Attributes = [ "AudioVolume.Volume" ];
    plugin.Devices[plugin.Name] = device;

    console.log("  done syncing");
}
‘’’

When embedding code in the post, you can use the 3 back-ticks “```” at the start & end of the code. That way it’s a little easier to read.

Hi Bill,

I’ve updated the post to make it easier to read, are you able to point me in the right direction to setting the device volume property so that I can read it back into the template?

Thanks Joe

Is your log message above the volume set line working?

Does your device object have the AudioVolume capability assigned to it. I see your sync does assign it but double-check in the Properties pane to make sure it’s actually there.

You can also get rid of that custom “AudioVolume.Volume” attribute. You only need the capability.

Hi Bill,

The AudioVolume capability is present in the list and I’ve removed the custom attribute.

The log line does work, although the loop is a bit clunky the response from the Bose is not always entirely predictable so the loop is there the catch the Volume feedback when it comes. Unfortunately the Bose system dose not respond with the volume when you change it and you have to specifically request it.

Within the HRP file I have put a label in and set the binding to {Binding BoseT20.Volume}, but this does not seem to report the volume value? The ultimate time is to add a slider to control the volume, there is a specific command for set volume in the Bose API which is the next thing to add to the plugin.

Regards,

Joe

I see the mistake.

This line of code you have:

var device = plugin.Devices

Should look like this:

var device = plugin.Devices["BOSE_T20"];

The value between the quotes should be the Id of your device object. I’m assuming your device Id is “BOSE_T20”.

Bill, perfect Thankyou! Also by the way this really is a great piece of software!.

Next step is to get the volume slider working :slight_smile: which might take me a while.

Thanks again!

1 Like

No problem!

The hard part is usually setting up device feedback. You’ve already got that finished. All you really have to do next is add a “case” in onChangeRequest that handles the Volume attribute.

When I’m done is it ok to upload the file for other members? I’ve spent hours on the internet trying to find a way to get the Bose to work on a universal remote.

Yes, definitely! I just bumped your user level up to “member” so you shouldn’t have any issues uploading.

Thanks Bill! :+1:

I’ll carry on with the handling the the volume now.

Hi Bill,

So to set the volume from the slider I need to convert the value into hex and then calculate a checksum, the code below should work (I think!). However when I run the code I can see that the string is not built correctly it ignores the first part of the command string?

cmd = “\x07\x00\x01\x15\x01\x” + value;

is reported as the value only in the console?

if(attribute == "MediaCommand") {
                cmd = mediaCommandMappings[value];
                console.log(plugin.Name + cmd);
            } else if(attribute == "InputSource") {
                cmd = inputSourceMappings[value];
            } else if(attribute == "Volume") {
            	console.log(plugin.Name + " Volume Value: " + value);
            	value=parseInt(value)
            	value=value.toString(16);
            	if(value.length<2){value="0"+value;}
            	console.log(plugin.Name + " Volume Hex Value: " + value);
            	cmd = "\x07\x00\x01\x15\x01\x" + value;
            	//cmd = "\x07\x00\x01\x15\x01" + cmd;
            	console.log(plugin.Name + cmd);
            	var i=0;
            	cs=0;
            	while (i <cmd.length) {    // loop till all decimals are converted to hex
    			cs^= cmd[i].charCodeAt(0);
    			cs=cs.toString(16);
  				i=i+1;
				}
            	if(cs.length<2){cs="0"+cs;}
            	cmd = cmd + "\x" + cs;
            	console.log(plugin.Name + " Volume cmd: " + cmd);

Those aren’t printable characters. You won’t see those hex characters in the console log.

Hi Bill,

I was clearly getting tired at that point! I’ve since realised that that the XOR checksum code was incorrect in what I posted last night, in the code below I have some code for calculating the checksum that works when tested in an javascript editor/tester, but when inserted into my plugin I get an error for unexpected identifier?

specifically I get this error for line:

for (var str of cmd) {

I presume I’m missing something really obvious but I really can’t see where.

            if(attribute == "MediaCommand") {
                cmd = mediaCommandMappings[value];
                console.log(plugin.Name + cmd);
            } else if(attribute == "InputSource") {
                cmd = inputSourceMappings[value];
            } else if(attribute == "Volume") {
            	console.log(plugin.Name + " Volume Value: " + value);
            	cmd = ['\x07\x00\x01\x15\x01' + String.fromCharCode(value)]
            	console.log(plugin.Name + cmd);
				for (var str of cmd) {
  				cs = 0;
  				for ( var char of str)
    			cs ^= char.charCodeAt(0);
  				cs = cs.toString(16);
  				if(cs.length<2){cs="0"+cs.toString(16);}
  				console.log(cs.toString(16))
				}
            	console.log(plugin.Name + " Checksum: " + cs);
            	cmd = cmd + "\x" + cs;
			}

“for-of” is not supported by the Home Remote’s JavaScript engine. Use a standard “for-in”.

    for(var strIndex in cmd)
    {
       var str = cmd[strIndex];
       // Your code
    }

“^=” is not supported. You need to find a different way of writing that code.

Has “cmd” ever been initialized? Has “cs” been intialized? I don’t see “var cmd” or “var cs” anywhere in your code block. I see you are initializing cmd for the “MediaCommand” & “InputSource” cases but you don’t appear to be initializing to for “Volume”. If you plan on using the exact same variable name for all of these cases then declare it above the “switch” statement so all of those cases can access it.

Don’t use “char” as a variable name. That’s not what is causing your issue here, but just as a general rule of thumb, don’t use variable names that the intellisense highlights in the blue color. That generally means it’s a reserved word.

Try sending a fixed value just to make sure the code you have works. Ignore the value from the slider for your initial development.

And break things down further. There’s a lot of code in that function. Find a way to test each thing individually. I myself suspected the “for-of” might be an issue so I wrote a simple example to test. I ran the code below in a simple test plugin & was quickly able to see it did not perform as expected.

// This code does not work. This is test code to see if for-of is supported
var cmd = "Hello";
for(var str of cmd)
{
   console.log(str);
}

I also suspected “^=” might be an issue just because I’ve never seen that before. Again here, I wrote a simple code block & ran it in a test plugin. This code also fails to run. These are things that you could & should be doing to find out where potential issues are.

// This is test code to see if ^= works. It does not. That is not supported
var testChar = "H";
var cs ^= testChar.charCodeAt(0);

Hi Bill,

Thanks for the pointers, I’ll make a point of testing the blocks individually in the Home Remote from now on. As I run mac I tend to test in a mac javascript editor and then bolt the blocks together, I’ll make a point and go back and tidy the variable declarations up (just messy programming at the minute).

Do you have any thoughts on how to calculate the XOR of the bits as if the ^ is not supported I can’t think of any obvious way to create the checksum. But I’ll start having a crack at it!.

Again thank you for the feedback i really appreciate it.

I think the main issue with your XOR is that you were trying to use it as an assignment operator. You can use it in expressions just like it’s being shown here.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_XOR

// This is how to do an XOR in JavaScript.
var a = 5;        // 00000000000000000000000000000101
var b = 3;        // 00000000000000000000000000000011
var c = a ^ b;
console.log(c);

Hi Bill,

I misread the response, and I’ve got a check sum calculated now and the volume value as a hex figure which is needed for the command which for the Bose takes the format:

07 00 01 15 01 V C

where V is the volume as a hex figure and C is the XOR check sum

var cs //Bose requires XOR checksum for all commands
var hexvol //the volume value as hex
//convert volume to hex
hexvol = value.toString(16).padStart(2, '0')
//calculate checksum
cs=['07']^['00']^['01']^['15']^['01']^[hexvol]

The above works even if the checksum calculation is a bit clunky, however where I am coming unstuck is how to to stick the original hex value together with the hex volume figure and the check sum. I can get it a string but converting it to hex changes the values. I’m sure i’m missing something but Java is new to me still.

I don’t know if you should be using those square brackets. Those are usually used to represent array values & I don’t think that’s what you are intending here. This just doesn’t look right. I’m also not sure why you are using ‘07’. If you want to have 7 hex use this => 0x07

I would use something like this after you’ve finally calculated the checksum.

var sendData = [0x07, 0x00, 0x01, 0x15, 0x01, hexvol, cs];
socket.send(sendData);

I would try not to jump ahead of yourself. Use a hardcoded discrete volume value. Make sure everything else you have works with this discrete value before you try using a dynamic value. For example, just use a fixed volume of 10 & make sure that works.

var hexvol = 0x10;