Here's the first cut of Anthem IP control pluging

I just loaded this up. Thanks! I wanted to know my soundmode and volume in decibels.

Couple of things to note… Your volume attribute is “VolumeDecibel”. I changed it to “Volume” otherwise I would have to remap all my elements.
Because decibels is a different number range (-90db to whatever your “Maximum Volume” is on the Anthem), anyone changing to decibels with need to update the MIN/MAX property for their volume slider.
Decibels feedback still shows a “%”… no biggie… just limit the size of the label to cut off the %

Thanks again! Love the sound mode displayed:

Thats the problem… the command can only be sent for the input you want to configure. I doubt Anthem will comeback with a command to do what you want. I think what you’re doing is a good idea BTW, I take it your Speaker Profiles pin-point the location of the listener with different distance/level settings?

I think your either going to have to create duplicate inputs like you mention, or, you will need to fenagle THR buttons to send the proper ISiPSp command based off of .InputSource. There’s probably a couple different ways you could it.

1 Like

Ye that’s what I intended; one setting for front row and one for both rows (or just rear).

For some reason I saw configuring the input with a different speaker profile to be undesirable. But in reality it’s not a big deal I suppose, and while I have already created the duplicate inputs and logic to implement, it should be easy to do through ISiPSp commands.

I’ll give it a whirl tomorrow, when I also plan to update the script to add nav support.

1 Like

So…this was so much easier than I thought :slight_smile:

In the older x20 series, you could set the profile to the active input. This isn’t available in the x40 for some reason. Also, there is a documentation bug, where the profiles actually start at ‘0’ for the first profile (not ‘1’) as documented, etc…

In my case, the only inputs that need to have different seating positions are the Apple TV (i=7) and Blu-Ray (i=6). My Front Row profile is in the first slot (p=0) and the Back Row profile is in the second slot (p=1).

Changes to the script:

added this block above case “MediaCommand”:

    case "SeatingPosition":
        if (value == "Front") { //format ISiSPp ... change i and p as needed
            cmd = "IS6SP0;IS7SP0"; //yes, you can string commands together!
            break;
        }
        else if (value == "Back") {
            cmd = "IS6SP1;IS7SP1";
            break;
        }

also added this in OnSynchronizeDevices after the device.Capabilities line

    device.Attributes = ["SeatingPosition"];

with these, I can now assign the action and trigger the change with a single click.

I also added some basic navigation commands to the MediaCommand case statement (would have been more efficient to use a nested select statement, but I was lazy :slight_smile:

        else if (value == "Setup") { 
            cmd = "Z" + device.Id + "SIM0012"
        }
        else if (value == "Info") { 
            cmd = "Z" + device.Id + "SIM0017"
        }
        else if (value == "DirectionUp") {
            cmd = "Z" + device.Id + "SIM0018"
        }
        else if (value == "DirectionDown") { 
            cmd = "Z" + device.Id + "SIM0019"
        }
        else if (value == "DirectionLeft") { 
            cmd = "Z" + device.Id + "SIM0020"
        }
        else if (value == "DirectionRight") { 
            cmd = "Z" + device.Id + "SIM0021"
        }
        else if (value == "Select") { 
            cmd = "Z" + device.Id + "SIM0022"
        }

Some more tweaks for the seating position.

Added the following to the onPoll function to read status:

if (message.substring(3,6) == 'SP0' ) {
    var deviceId = 1; //1 or 2 ... doesn't matter really since speaker profiles are global
    var device = plugin.Devices[deviceId]; //yes, I could simplify :) 
    console.log ("Front");
    device.SeatingPosition = "Front";
}
if (message.substring(3,6) == 'SP1' ) {
    var deviceId = 1;
    var device = plugin.Devices[deviceId];
    console.log ("Back");
    device.SeatingPosition = "Back";
}

With this, I now have a way to toggle between the two and set visual status accordingly:

Back
Front

(thanks, @Jdamore, for the inspiration to get this implemented…I would have taken a longer, more convoluted route to get this done without your comment!)

I’m attaching my customized version of the script if anyone wants to follow along.
anthem_plugin_script (LJR mods).txt (9.4 KB)

Nice work @LJR
Question… How did you get the “dB” after the value? is that just another label?
I used your plugin script and tossed together a quick example. I put in as many commands as I can think of except for your speaker profile stuff. I’m not sure how to handle that yet…

See Attached Example:
Anthem_Example_2022_12_23.hrp (1.2 MB)

Once you open the example, remember to change the IP (and port if different) of “Anthem_MRX_AVM” (right click > open). Also change the IP in the properties of the WebBrowser.

Sorry @Kryten67 'your original volume attribute has changed from " Volume" to VolumeDecibel". Is it possible to do both in the script? I’m not smart enough for that stuff ha!

Hey @Jdamore … I loaded it up - had to make some small changes to the plugin naming to get it to work.

What I recommend you do is copy the plugin text, delete both the device and plugin script, then then add the plugin via “Import from Code”. That will recreate the device and script, allow you to configure the IP, and it should work immediately without any further changes (I’ve tested this procedure and it works for me)…

Also, be sure to have decible set on the AVR. Otherwise, the integer reported will be incorrect.

Anthem_Example_for jdamore.hrp (1.1 MB)

Now its showing “dB” as it should. Yeah… it was set to % on the MRX (I must have switched it at some point in my tinkering :thinking:)

I updated my example project in my last post.

No need to delete anything. You just need to update your IP address…
Just right-click on the device and open:
image

Change the IP:
image

Also change the IP of the WebBrowser to point it at your Anthem:

LJR,
Hey I have been trying to get this plugin working for my MRX 1120 but I keep getting a message that the connection wont work as the device is actively not accepting it. Any suggestions? I have made sure the IP and ports are right.

Cheers

Make sure you can connect to the device manually first. Use a telnet-like package like PuTTY and make a connection to IP address, port 14999 (assuming this hasn’t been modified from original). Make sure that these are the values that are in the .plugin file, for example (line 9 in my file):
plugin.DefaultSettings = { “Host”: “192.168.100.91”, “Port”: “14999” };
If you open a session (note: do not use ‘enter’), you should be able to type in: IDM?;
And the device should respond with it’s model#, in my case: IDMAVM 60;

Still getting network error: connection refused.

image

I’ve tried it with and without port forwarding in the router but still not connecting. I have a hardwired connection to the receiver as well as wifi and tried both in isolation. Any other thoughts?
Thanks!

So an update - by changing the MRX1120 IP to auto then back to manual (and changing the IP back) I can connect telnet and use the commands with the right stuff returning. When I use the example hrp file and start - it comes up with the right status (i.e. input number illuminated and volume level). But as soon as I click on mute or volume down - the connection seems to freeze and the only way to get a connection again is to reset the IP to auto then manual again (or turn the AVR off then back on) Any thoughts?
Thanks!

10.0.0.x is an invalid subnet. Reconfigure your network equipment to use/issue 10.0.1.x subnet. That should resolve it.

Hmmm. My entire network runs on 10.0.0.x as it’s set by the router. Also there is no issue with using Telnet it appears. The subnet that is used for the network is 255.255.255.0.
Thanks for the help - its greatly appreciated. I’ll play around a bit more and let you know how I get on.
Thanks.

Yup, I think there’s nothing wrong with 10.0.0.x as long as you’re not trying to use 10.0.0.0 anywhere.

I assume that:

  • You’ve not modified the plugin code in ANY way, other than the IP address/port in the file.

If telnet is working reliably, then there’s probably nothing wrong with your network. Run the .hrp with “Start” on your PC (in The Home Remote Designer) and see if any useful info is recorded to log. You can add logging commands to the plugin itself to aid in debugging.

Hey Kryten,
Got me pretty confused. Sometimes it seems to work for an action or two and sometimes it wont work for any action. When it fails I have to reset the IP configuration to Auto then back to manual.

I just tried START and the status for the input, volume level etc reads OK from the socket.
Then when I clicked info I get:

AVM onChangeRequest-MediaCommand:Info
Z1SIM0012
command: Z1SIM0012
Unable to read data from the transport connection: An existing connection was forcibly closed by the remote host…
Anthem onDisconnect
Anthem onConnect
No connection could be made because the target machine actively refused it.
Anthem onDisconnect

It’s like the receiver just decides to lock the connection - even though I’ve never had an issue with a command from the command prompt using telnet.
I have tried a few variations of port forwarding (thinking there may be some issue between the telnet normal port 23 and the Anthem port of 14999 but none seem to make any difference. Have you had to setup something like that?

Thanks again,

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;
    }
}

Kryten,
So I found my issue! I have been working on a quickapp for the Fibaro HC3 which I have at home. During the development of that I experienced the same issues. After some discussion with Anthem and others - I worked out that the receiver has two MAC addresses and IP addresses - one for the receiver and the other for Play-fi. After I just ensured the reserved IP for the receiver had the right MAC address and everything seems really stable now.

So now I have a new issue. I prefer to have my home automation setup have the quickapp rather than the home remote but for some reason the quickapp is not imported when I synchronize my devices. I’m not sure how to setup the quickapp to be compatible with the Fibaro device declaration. I assume that I need to make the device type and capabilities the same as the home remote options. I’ll keep working on it but if you have any further ideas I’d appreciate it.
Thanks
Anthony

So you’re trying to pull the Anthem into THR through Fibaro and not use this plugin? Sorry its a little confusing.

If there is a direct (local control not cloud based) method to control your Anthem like this plug-in, that’s the way to go! Fibaro and THR can both communicate to the Anthem at the same time. I do this with HomeAssistant and THR… HA for automations and THR for user inputs (like volume control).

Hey Jdamore,
So I try to have all my devices in my house centralised in the Fibaro HC3. It’s not cloud based - just local network.
I wasn’t aware though that you can have two sessions at the same time - using the same IP and Port? I’ll give it a try.
Thanks.
Anth