I added the Anthem / Processor / AVM60 Zone 1 device to my global cache device.
I don’t see how to request an input source request. It has “AnthemReceiverPreamp.MediaCommand” listed when I attempt a binding via a Data Action, but I don’t know what the command would be for the value (how do I get a list of allowed values?). I was expecting that the “MediaInputSource” capability would be enabled, but it is not listed (is there a way to edit built-in device capabilities?).
thanks.
MediaInputSource requires a device that provides state information. The device you have likely does not provide status info. You will need to use the MediaCommand attribute to change the input.
Look at your codeset. The commands probably start with the word “Input” (see screenshot below)
Of course, it makes perfect sense now that it’s pointed out … the codeset! Thanks Bill.
I’m trying to implement a plugin for my AVM60, so this is a fallback if I can’t get that working.
Bill, I’ve got my plugin working reasonably well (just the basics: input, volume, mute) over a tcp socket.
One problem I’ve noticed is that the polling tcp commands appear to interfere on occasion with commands being issued on-demand (like a volume up command). Is it necessary to put some sort of manual serialization to prevent concurrent tcp socket access? I could try opening a socket on each call, but this seems like it would be inefficient. If I set the polling to 500 or lower the problem becomes really pronounced. Is this a common issue with a known workaround?
You shouldn’t have to poll tcp. Most of the time those events are automatically sent. All you need is something to read the event messages.
Take a look at what this Trinnov plugin is doing. It’s TCP based & probably similar to what you have. In “onPoll”, all it’s doing is waiting for & reading incoming messages. It only sends status requests in “onConnect” because after that it should never have to request status info. It should be sent automatically. I recommend you try & model your plugin after what I wrote in Trinnov.plugin.
Trinnov.plugin (5.1 KB)
plugin.Name = "TrinnovAltitude";
plugin.OnChangeRequest = onChangeRequest;
plugin.OnConnect = onConnect;
plugin.OnDisconnect = onDisconnect;
plugin.OnPoll = onPoll;
plugin.OnSynchronizeDevices = onSynchronizeDevices;
plugin.PollingInterval = 250;
plugin.DefaultSettings = { "Host": "192.168.1.100", "Port": "44100" };
var _tcp = new TCPClient();
var _readBuffer = "";
var _eol = "\n";
var _mediaCommands = ["Light", "ModeDown", "ModeUp", "Mute", "VolumeDown", "VolumeUp"];
var _deviceId = "1";
function onChangeRequest(device, attribute, value) {
var cmd = null;
switch (attribute) {
case "MediaCommand":
if (value == "Light") {
cmd = "fav_light";
}
else if (value == "ModeDown") {
cmd = "upmixer -";
}
else if (value == "ModeUp") {
cmd = "upmixer +";
}
else if (value == "Mute") {
cmd = "mute 2";
}
else if (value == "VolumeDown") {
cmd = "dvolume -0.5";
}
else if (value == "VolumeUp") {
cmd = "dvolume 0.5";
}
else {
throw "Not implemented!";
}
break;
case "Bypass":
cmd = "bypass " + value;
break;
case "Dim":
cmd = "dim " + value;
break;
case "InputSource":
cmd = "profile " + value;
break;
case "Preset":
cmd = "loadp " + value;
break;
case "Mute":
cmd = "mute " + ((value == "Muted") ? "1" : "0");
break;
case "SoundMode":
cmd = "upmixer " + value;
break;
case "VolumeDecibel":
cmd = "volume " + value;
break;
default:
throw "Not implemented!";
}
_tcp.send(cmd + "\r");
}
function onConnect() {
_tcp.connect(plugin.Settings["Host"], parseInt(plugin.Settings["Port"]));
var connectResponse = readMessage();
if (connectResponse.indexOf("Welcome on Trinnov Optimizer") == -1) {
throw "Unexpected connect response";
}
_tcp.send("id home_remote\r");
_tcp.send("send_volume\r");
_tcp.send("get_current_profile\r");
_tcp.send("get_current_preset\r");
_tcp.send("upmixer\r");
var device = plugin.Devices[_deviceId];
device.SupportedMediaCommands = _mediaCommands;
}
function onDisconnect() {
_tcp.close();
}
function onPoll() {
var device = plugin.Devices[_deviceId];
while (true) {
var message = readMessage();
var spaceIndex = message.indexOf(' ');
if (spaceIndex != -1) {
var messageId = message.substring(0, spaceIndex);
var messageValue = message.substring(spaceIndex + 1).trim();
switch (messageId) {
case "BYPASS":
device.Bypass = messageValue;
break;
case "CURRENT_PRESET":
device.Preset = messageValue;
break;
case "CURRENT_PROFILE": // For when we request status with get_current_profile.
case "META_PRESET_LOADED": // For when the active input/source/profile changes.
device.InputSource = messageValue;
break;
case "DECODER":
var upMixerIndex = message.indexOf("UPMIXER ");
if (upMixerIndex != -1) {
device.SoundMode = messageValue.substring(upMixerIndex);
}
break;
case "DIM":
device.Dim = messageValue;
break;
case "MUTE":
device.Mute = (messageValue == "1") ? "Muted" : "Unmuted";
break;
case "VOLUME":
device.VolumeDecibel = parseFloat(messageValue).toFixed(1);
break;
default:
break;
}
}
}
}
function readMessage() {
while (true) {
var eolIndex = _readBuffer.indexOf(_eol);
if (eolIndex != -1) {
var message = _readBuffer.substring(0, eolIndex + 1);
_readBuffer = _readBuffer.substring(eolIndex + 1);
return message;
}
else {
var data = _tcp.receive();
if (!data) {
throw "End of the stream";
}
_readBuffer = _readBuffer + data;
}
}
}
function onSynchronizeDevices() {
var device = new Device();
device.Id = _deviceId;
device.DisplayName = "Trinnov Altitude";
device.Icon = "Speaker";
device.Capabilities = ["AudioMute", "AudioSoundMode", "AudioVolumeDecibel", "MediaControl", "MediaInputSource"];
device.Attributes = ["Bypass", "Dim", "Preset"];
device.TileTemplate = "TrinnovTile.xaml";
device.DetailsTemplate = "TrinnovDetails.xaml";
plugin.Devices[device.Id] = device;
}
Interesting. A bit of a different paradigm than I’m used to – one function responsible for sending, and the poll handing responses. I’ll try that approach at some point…