I may have made corrective changes in the past couple of years. Please try this version which is working on both my Anthem receivers reliably.
plugin.Name = "AnthemAVMController";
plugin.OnChangeRequest = onChangeRequest;
plugin.OnConnect = onConnect;
plugin.OnDisconnect = onDisconnect;
plugin.OnPoll = onPoll;
plugin.OnSynchronizeDevices = onSynchronizeDevices;
// Note the polling interval can dynamically change
plugin.PollingInterval = 250;
plugin.DefaultSettings = { "Host": "10.1.1.97", "Port": "14999" };
var socket = new TCPClient();
var zoneCount = 1; // Change to 2 if you want to use both zones supported
var unprocessedData = "";
var rawVolume;
var data = null;
var _readBuffer = "";
var _eol = ";";
function readMessage() {
for(var wait = 1; wait < 50; wait++) {
if(socket.available > 0)
break;
sleep(1);
}
if(socket.available <= 0)
return "";
while (socket.available) {
var eolIndex = _readBuffer.indexOf(_eol);
if (eolIndex != -1) {
var message = _readBuffer.substring(0, eolIndex + 1);
if (eolIndex + 2 < _readBuffer.length)
_readBuffer = _readBuffer.substring(eolIndex + 1);
else
_readBuffer = "";
return message;
}
else {
var data = socket.receive();
if (!data) {
console.log("AVM: end of stream reached");
throw "End of the stream";
}
_readBuffer = _readBuffer + data;
}
}
}
function onChangeRequest(device, attribute, value) {
console.log("AVM: onChangeRequest-" + attribute + " : " + value);
var cmd = null;
switch (attribute) {
case "MediaCommand":
if (value == "VolumeUp") {
cmd = "Z" + device.Id + "VUP01;"
}
else if (value == "VolumeDown") {
cmd = "Z" + device.Id + "VDN01;"
}
else {
throw "MediaCommand value not implemented";
}
break;
case "InputSource":
cmd = "Z" + device.Id + "INP" + parseInt(value).toString().padStart(2, '0') + ";";
break;
case "Mute":
cmd = "Z" + device.Id + "MUT" + ((value == "Muted") ? "1" : "0") + ";";
break;
case "Switch":
var cmdPwr = "Z" + device.Id + "POW" + ((value == "On") ? "1" : "0") + ";";
if (value == "On")
// send power-on twice, the first to wake up device from ECO mode (if enabled)
cmd = cmdPwr + cmdPwr;
else
cmd = cmdPwr;
break;
case "Volume":
var newvol = Math.round(((value - 100) * 90 / 100));
cmd = "Z" + device.Id + "VOL" + newvol.toString() + ";";
break;
case "SoundMode":
var val1 = String(value).substring(0,1);
if(val1 < '0' || val1 > '9') {
throw "AVM: AudioMode out of range (0-9)";
}
if (val1 == '0') {
cmd = "SDVL000;SDVS000;"; // turn off dolby volume leveler
}
else {
cmd = "SDVS001;SDVL00" + val1 + ";"; // turn on dolby volume leveler
}
break;
default:
throw "Not implemented!";
}
console.log("AVM: command: " + cmd);
socket.send(cmd);
}
function onConnect() {
console.log("AVM: onConnect");
socket.connect(plugin.Settings["Host"], parseInt(plugin.Settings["Port"]));
socket.send("SIP1;"); // enable standby IP control
}
function onDisconnect() {
console.log("AVM: onDisconnect");
socket.close();
}
function onPoll() {
for (var i = 1; i <= zoneCount; i++) {
var id = i.toString();
socket.send("Z" + id + "POW?;");
}
var message = null;
while(true) {
try {
message = readMessage();
}
catch(err)
{
console.log("AVM: Exception during readMessage()");
}
if (!message || message == "") {
break;
}
if(message.substring(0,2) == "!E") {
console.log("AVM: Processor signalled error: " + message);
continue; // get another message if available
}
console.log("Msg: " + message);
if (message.length <= 5)
continue;
switch(message.charAt(0)) {
case 'Z':
var id = message.substring(1,2).toString();
var messageId = message.substring(2, 5);
var val1 = message.substring(5,6);
var val2 = message.substring(5,7);
var val3 = message.substring(5,8);
var device = plugin.Devices[id];
switch (messageId) {
case "VOL":
var vol = parseInt(val3);
var vol2 = Math.round(100 + (100 * vol / 90));
device.Volume = vol2;
console.log("AVM: Volume: " + device.Volume.toString());
break;
case "POW":
device.Switch = (val1 == "1" ? "On" : "Off");
if (device.Switch == "On") {
// If this device is on, let's query the mute status,
// volume and input selection
plugin.PollingInterval = 200;
socket.send("Z" + id + "MUT?;");
socket.send("Z" + id + "VOL?;");
socket.send("Z" + id + "INP?;");
socket.send("SDVL00?;");
}
else
{
plugin.PollingInterval = 1000;
}
break;
case "MUT":
device.Mute = (val1 == "1" ? "Muted" : "Unmuted");
break;
case "INP":
device.InputSource = parseInt(val2).toString();
break;
default:
console.log("AVM: unknown Z data read: " + message);
break;
}
break;
case 'S':
var messageId = message.substring(1, 4);
// "S" commands operate on the primary zone (device)
var device = plugin.Devices[1];
switch (messageId) {
case "DVL":
device.SoundMode = message.substring(6, 7);
console.log("Sound mode: " + device.SoundMode.toString());
break;
case "DVS":
break;
default:
console.log("AVM: unknown S data read: " + message);
break;
}
break;
}
}
}
function onSynchronizeDevices() {
console.log("AVM: onSynchronizeDevices");
//Generate zones
//First digit in Id is the zone number.
for (var i = 1; i <= zoneCount; i++) {
var device = new Device();
device.Id = i.toString();
device.DisplayName = "AVM Zone " + i.toString();
device.Icon = "Receiver";
device.Capabilities = ["AudioMute", "AudioVolume", "MediaControl", "MediaInputSource", "Switch", "AudioSoundMode"];
device.SupportedSoundModes = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9" ];
device.DeviceType = "AudioZone";
device.TileTemplate = "AudioZoneTile.xaml";
device.DetailsTemplate = "AudioZoneDetails.xaml";
plugin.Devices[device.Id] = device;
}
}