diff --git a/eiscp/core.py b/eiscp/core.py
index 806cb6a..605d412 100644
--- a/eiscp/core.py
+++ b/eiscp/core.py
@@ -275,6 +275,9 @@ def filter_for_message(getter_func, msg):
# It seems ISCP commands are always three characters.
if candidate and candidate[:3] == msg[:3]:
return candidate
+ elif candidate and candidate[:3] == 'MDI' and msg[:3] == 'MGS':
+ # the MGS command for grouping multiroom audio, returns an MDI message, not MGS
+ return candidate
# exception for HDMI-CEC commands (CTV) since they don't provide any response/confirmation
if "CTV" in msg[:3]:
@@ -564,6 +567,92 @@ def power_off(self):
"""Turn the receiver power off."""
return self.command('power', 'off')
+ def group_with(self, otherIDs=[]):
+ """Create a multiroom audio / flareconnect group with the supplied device IDs.
+ Calling this without arguments or an empty list stops the multiroom audio / flareconnect group.
+ Calling this method twice with the same arguments does not generate a response from the receiver, thus causing a timeout on the message."""
+ if otherIDs:
+ # check if the supplied deviceIDs are all strings
+ for ID in otherIDs:
+ if type(ID) != str:
+ raise ValueError('group_with needs a list object, with each device identifier as a string')
+ # construct a MGS message with a list of the device IDs
+ message='MGS1500' + \
+ ''%(self.identifier) + \
+ ''.join([''%(ID) for ID in otherIDs]) + \
+ ''
+ else:
+ # No other devices specified. Create an empty group, which stops the multiroom audio / flareconnect
+ message='MGS0'
+ return self.raw(message)
+
+ def grouped_with(self, timeout=1):
+ """Return a list of receiver objects we are currently grouped with and their role"""
+ group_list = []
+ # get our own group info
+ mygroups = self.get_groups()
+ mygroupids = []
+ if not mygroups:
+ # we are not part of a group, no need to waste time on discovering other receivers on the network
+ return None
+ # we are part of a group. Add ourselve to the group dict
+ for group in mygroups:
+ group_list.append({ "identifier" : self.identifier,
+ "host" : self.host,
+ "model_name" : self.model_name,
+ "zoneid" : group["id"],
+ "groupid" : group["groupid"],
+ "role" : group["role"],
+ "powerstate" : group["powerstate"]
+ })
+ mygroupids.append(group["groupid"])
+ # now let's find our group friends
+ receivers = self.discover(timeout=timeout)
+ for receiver in receivers:
+ if receiver.identifier == self.identifier:
+ # no need to parse ourselves
+ continue
+ receivergroups = receiver.get_groups()
+ for theirgroup in receivergroups:
+ # check if their groupid matches any of our groupids
+ if theirgroup["groupid"] in mygroupids:
+ # we have a match, append it to the group_list
+ group_list.append({ "identifier" : receiver.identifier,
+ "host" : receiver.host,
+ "model_name" : receiver.model_name,
+ "zoneid" : theirgroup["id"],
+ "groupid" : theirgroup["groupid"],
+ "role" : theirgroup["role"],
+ "powerstate" : theirgroup["powerstate"]
+ })
+ return group_list
+
+ def get_groups(self):
+ """Show the current groups info for this receiver.
+ This returns a list of all zones in the receiver that are part of a multiroom audio group.
+ In most cases this will be an empty list (not grouped), or have a single entry.
+ In rare cases (e.g. receiver with both a main zone and a Zone2 that are participating in a group) you can get a multi-item list.
+ The items in the list are a dict with all the info returned by the receiver.
+ The interesting parts of this dict are (IMHO): "groupid", "role", "powerstate"
+ Determining which receivers are part of the group has to be done separately, by finding all receivers participating with the same groupid. One will have 'role' : 'src', all others will have 'role' : 'dst' (for source and destination)
+ """
+ message = 'MDIQSTN'
+ data = self.raw(message)
+ grouped_zones=[]
+ if data:
+ # strip the "MDI" from the start of the reply
+ data = data.replace('MDI','')
+ # turn it into a dict
+ data = xmltodict.parse(data, attr_prefix="")
+ # Cast OrderedDict to dict
+ data = json.loads(json.dumps(data))
+ # the interesting part here is the ["mdi"]["zonelist"]["zone"] part
+ zonelist = data["mdi"]["zonelist"]["zone"]
+ for zone in zonelist:
+ if zone["groupid"] != '0' and zone["role"] != 'none':
+ grouped_zones.append(zone)
+ return grouped_zones
+
def get_nri(self):
"""Return NRI info as dict."""
data = self.command("dock.receiver-information=query")[1]
diff --git a/eiscp/script.py b/eiscp/script.py
index f55ccf2..32fd51c 100644
--- a/eiscp/script.py
+++ b/eiscp/script.py
@@ -5,6 +5,8 @@
%(prog_n_space)s [--all] [--name ] [--id ]
%(prog_n_space)s [--verbose | -v]... [--quiet | -q]... ...
%(program_name)s --discover
+ %(program_name)s [--host ] [--group_with ...]
+ %(program_name)s [--host ] [--grouped_with]
%(program_name)s --help-commands [ ]
%(program_name)s -h | --help
@@ -31,6 +33,11 @@
Turn receiver on, select "PC" source, set volume to 75.
%(program_name)s zone2.power=standby
To execute a command for zone that isn't the main one.
+ %(program_name)s --host 192.168.1.15 --group_with "0009B0E4B723" "0009B0E4B724"
+ Setup multiroom audio using Flareconnect. The receiver is source, all receiver IDs supplied for join the in a Flareconnect group.
+ Use the --discover option to get the receiver IDs.
+ %(program_name)s --host 192.168.1.15 --group_with
+ Stop the multiroom audio group / flareconnect.
'''
import sys
@@ -112,6 +119,35 @@ def main(argv=sys.argv):
print('No receivers found.')
return 1
+ if options['--group_with']:
+ receivers[0].group_with(options[''])
+
+ if options['--grouped_with']:
+ groupdata = receivers[0].grouped_with()
+ if groupdata:
+ # get a list of the active groups and their groupid. Normally this is only a single item
+ groups = set([dev["groupid"] for dev in groupdata])
+ for group in groups:
+ # find the source
+ source = [ dev for dev in groupdata if dev["groupid"] == group and dev["role"] == "src" ]
+ if source:
+ source = source[0]
+ else:
+ print("failed to detect the source for groupid %s" %(group))
+ print(groupdata)
+ return
+ destinations = [ dev for dev in groupdata if dev["groupid"] == group and dev["role"] == "dst" ]
+ if destinations:
+ pass
+ else:
+ print("failed to detect the destinations for groupid %s" %(group))
+ print(groupdata)
+ return
+ print("Multiroom audio group active with groupid %s" %(group))
+ print("source: %s on address: %s" %(source["model_name"], source["host"]))
+ for destination in destinations:
+ print("destination: %s on address: %s" %(destination["model_name"], destination["host"]))
+
# List of commands to execute - deal with special shortcut case
to_execute = options['']