Discover does not always work when you have multiple interfaces

104 views
Skip to first unread message

Sonosy

unread,
Mar 23, 2020, 6:27:39 AM3/23/20
to SoCo - Sonos Controller
Discover does not always find the correct interface.
There is a suggestion in discover.py to use netifaces but this only gives you a list of IP addresses to choose from.

The problem is exacerbated if you have multiple interfaces (like wireshark's ncap or vmware)

One solution I found at stackoverflow was:

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(('8.8.8.8', 1))  # connect() for UDP doesn't send packets
local_ip_address = s.getsockname()[0]
 
You can then call
speakers = soco.discover(local_ip_address)

This works (for me) in Windows and Debian

discover.py uses two methods to try to get the local interface's ip address

socket.gethostbyname(socket.gethostname())
socket.gethostbyname(socket.getfqdn())

For me these just returned the ncap device not my ethernet interface

Maybe in the future discover.py will have the code at the top too...

(Reported on Github)
Hope this helps someone

Sonosy

unread,
Mar 25, 2020, 6:01:31 AM3/25/20
to SoCo - Sonos Controller
I've discovered(!) that this really needs to be incorporated into discover.py rather than finding the correct interface BEFORE calling discover().

Without this in 'discover', discovery.by_name() and discovery.any_soco() won't work as they both call .discover() (without any args) and just get the default (wrong) interface.

I'm new to Github and haven't a clue how to go through the update process.

pointers?

Psychlist

unread,
Mar 25, 2020, 7:57:03 AM3/25/20
to SoCo - Sonos Controller
The best place to seek help with this is on the SoCo Gitter channel:


You'll need to start by raising a GitHub issue against SoCo. I recollect that there's been some prior discussion of discovery problems, so it's worth quickly browsing the history.

Cheers,
P.

Marco Quezada

unread,
Mar 25, 2020, 11:32:50 AM3/25/20
to SoCo - Sonos Controller
I ran into this a couple months ago and as Psychlist points out, there has already been an issue opened (and closed) on the topic over at the github issue tracker. If I remember correctly, the consensus was that native python did not seem to provide the functionality needed to perform a scan over the interfaces on the host computer. This required the use of third party libraries or code and as such it was decided not to include a new dependency into SoCo and the implementation of the solution is left to the implementer. Here is the code I use to scan through the interfaces and find those that are connected to Sonos devices. It will stop scanning on the first instance in which discover() works. It does rely on the psutil library to scan the system and get a list of interfaces to check.

import psutil 
def scanForZones(self):
      print("SCAN!")
      global Zones

      # Get the list of interfaces on the host.
      addressDict = psutil.net_if_addrs()
      ipList = []
      for key in addressDict:
         intf = addressDict[key]
         for l in intf:
            # Filter out the interfaces that are not of the correct type
            # and that are not on the network I care about. Change filter
            # as appropriate for your case.
            if l.family == socket.AddressFamily.AF_INET and l.address.startswith('192.168.'):
               # Add interface address to a list
               ipList.append(l.address)
      #zones = soco.discover(interface_addr='192.168.1.86')
      # Loop through the list of addresses, trying to discover
      # devices on a short timeout.
      for ip in ipList:
         zones = soco.discover(timeout=1interface_addr=ip)
         # If it finds zones then use this interface.
         # I also check for the case of a previous scan,
         # it has to do with how the rest of my code
         # works. You should modify to suit your code.
         if zones is not None and len(Zones) != len(zones):
            self.zoneStore.clear()
            if len(zones) > 0:
               self.titleLabel.set_label("Rooms (" + str(len(zones)) + ")")
               for zone in zones:
                  z = Zone(zone, self)
                  Zones.append(z)
                  self.zoneStore.append([self.testSymbol, zone.player_name, self.testSymbol2, zone.get_current_transport_info()["current_transport_state"], z])
#                  print(zone.get_current_transport_info())
               self.selected_row_iter = self.zoneStore.get_iter_first()
               self.select.select_iter(self.selected_row_iter)
               break
         elif zones is None:
            print("No zones found at " + ip)
         else:
            print("CACHED!")

Good luck!

George Shaw

unread,
Dec 1, 2020, 7:35:32 PM12/1/20
to SoCo - Sonos Controller
Wow, seems like lots of work, and as mentioned, does not deal with the internal calls to discover(). I ran into a similar problem because I have VirtualBox running, which creates a virtual network interface. So when discover() calls socket.gethostbyname(), guess which IP is returned first? Not the one I wanted. So I switched that line to use socket.gethostbyname_ex(), which returns a list of IPs, and then shoved the list into the set of addresses. Works fine for me.

addresses = set()
try:
    _,_,addressList = socket.gethostbyname_ex(socket.gethostname())
    addresses.update(addressList)
except socket.error:
    pass
try:
    _,_,addressList = socket.gethostbyname_ex(socket.getfqdn())
    addresses.update(addressList)
except socket.error:
    pass  

Reply all
Reply to author
Forward
0 new messages