So here is my Bose T20 plugin, the volume feedback code is probably a little clunky but it does work. Unfortunately the protocol used by Bose seems to be a little bit out of the ordinary.
At some point in the future I plan on adding additional functionality as per the API, but i haven’t got round to that yet, if anyone needs a copy of the API document I can upload it somewhere for everyone.
In terms of functionality this version so far covers:
- Volume Up
- Volume Down
- Volume Feedback - so you can have a slider in your remote
- Mute
- Power Toggle
plugin.Name = "BOSE_T20";
plugin.OnChangeRequest = onChangeRequest;
plugin.OnConnect = onConnect;
plugin.OnDisconnect = onDisconnect;
plugin.OnPoll = onPoll;
plugin.OnSynchronizeDevices = onSynchronizeDevices;
plugin.PollingInterval = 1200000;
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...");
var cmd;
switch(attribute) {
case "MediaCommand":
case "InputSource":
case "Volume":
if(attribute == "MediaCommand") {
cmd = mediaCommandMappings[value];
console.log(plugin.Name + cmd);
} else if(attribute == "InputSource") {
cmd = inputSourceMappings[value];
} else if(attribute == "Volume") {
//hexvol=20
cs = 0x07 ^ 0x00 ^ 0x01 ^ 0x15 ^ 0x01 ^ value
console.log("CS" + cs)
cmd = [0x07, 0x00, 0x01, 0x15, 0x01, value, cs];
console.log("cmd:" + cmd);
//socket.send(cmd);
//break;
}
if(cmd) {
console.log(plugin.Name + " sending command: " + cmd);
socket.send(cmd);
console.log(plugin.Name + " Received: " + socket.receive({encoding: null}))
GetVolume();
} 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 GetVolume(){
var loop=0;
while(loop<5){
socket.send("\x07\x00\x01\x15\x00\x1e\x0d");
sleep(150);
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["BOSE_T20"];
//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 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){
GetVolume();
}
}
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 = [ ];
plugin.Devices[plugin.Name] = device;
console.log(" done syncing");
}