Skip to content

Instantly share code, notes, and snippets.

@billchurch
Last active May 17, 2024 19:16
Show Gist options
  • Save billchurch/8dc2c8835dedf140075dbc60ed483b05 to your computer and use it in GitHub Desktop.
Save billchurch/8dc2c8835dedf140075dbc60ed483b05 to your computer and use it in GitHub Desktop.
Python script to find all sonos units on your network with uPNP and then connect to a unifi switch to tell you what switch port they're on
import socket
import requests
import paramiko
import time
import xml.etree.ElementTree as ET
def discover_sonos_devices():
MCAST_GRP = '239.255.255.250'
MCAST_PORT = 1900
msg = (
'M-SEARCH * HTTP/1.1\r\n'
'HOST: 239.255.255.250:1900\r\n'
'MAN: "ssdp:discover"\r\n'
'MX: 1\r\n'
'ST: urn:schemas-upnp-org:device:ZonePlayer:1\r\n'
'\r\n'
)
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 2)
sock.settimeout(2)
sock.sendto(msg.encode('utf-8'), (MCAST_GRP, MCAST_PORT))
devices = {}
try:
while True:
data, addr = sock.recvfrom(1024)
response = data.decode('utf-8')
if 'Sonos' in response:
for line in response.split('\r\n'):
if line.startswith('LOCATION:'):
location = line.split(' ')[1].strip()
devices[addr[0]] = location
except socket.timeout:
pass
return devices
def get_mac_address_from_description(url):
try:
response = requests.get(url)
if response.status_code == 200:
root = ET.fromstring(response.content)
ns = {'upnp': 'urn:schemas-upnp-org:device-1-0'}
mac_address = root.find('.//upnp:MACAddress', ns).text
return mac_address
else:
print(f'Failed to get device description from {url}')
return None
except requests.RequestException as e:
print(f'Error getting device description from {url}: {e}')
return None
def ssh_to_telnet(host, username, password, telnet_command):
try:
# Establish SSH connection
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(host, username=username, password=password)
# Start an interactive session
channel = ssh.invoke_shell()
# Wait for the SSH prompt
time.sleep(1)
channel.recv(9999)
# Enter telnet mode
channel.send('telnet localhost\n')
time.sleep(1)
# Wait for the Telnet prompt
channel.recv(9999)
# Send Telnet command
channel.send(telnet_command + '\n')
time.sleep(2)
# Receive output
output = channel.recv(9999).decode('ascii')
# Close the Telnet session
channel.send('exit\n')
time.sleep(1)
# Close the SSH session
channel.send('exit\n')
ssh.close()
return output
except Exception as e:
print(f'Error executing SSH to Telnet command: {e}')
return None
def get_mac_address_table_via_telnet(host, username, password, mac_address):
telnet_command = f'show mac-addr-table {mac_address}'
result = ssh_to_telnet(host, username, password, telnet_command)
return result
def parse_mac_table_output(output):
mac_port_mapping = {}
lines = output.split('\n')
for line in lines:
parts = line.split()
if len(parts) > 4 and parts[1].count(':') == 5: # Ensuring it's a MAC address line
mac = parts[1].strip()
port = parts[2].strip()
mac_port_mapping[mac] = port
return mac_port_mapping
if __name__ == '__main__':
# Discover Sonos devices
sonos_devices = discover_sonos_devices()
mac_addresses = []
if sonos_devices:
print(f'Discovered Sonos devices: {list(sonos_devices.keys())}')
for ip, location_url in sonos_devices.items():
mac_address = get_mac_address_from_description(location_url)
if mac_address:
mac_addresses.append(mac_address)
print(f'MAC: {mac_address}')
else:
print('No Sonos devices found on the network.')
# Connect to UniFi switch and find ports for MAC addresses
switch_ip = '192.168.0.30' # Replace with your switch IP address
username = 'your-username' # Replace with your SSH username
password = 'your-password' # Replace with your SSH password
for mac in mac_addresses:
output = get_mac_address_table_via_telnet(switch_ip, username, password, mac)
if output:
mac_port_mapping = parse_mac_table_output(output)
for mac, port in mac_port_mapping.items():
print(f'MAC address {mac} is connected to port {port}')
else:
print(f'No output for MAC address {mac}')
@billchurch
Copy link
Author

billchurch commented May 17, 2024

Sample output

% python3 sonos_unifi_discovery.py
Discovered Sonos devices: ['192.168.0.94', '192.168.0.90', '192.168.0.92', '192.168.0.98', '192.168.0.93', '192.168.0.97', '192.168.0.95', '192.168.0.91', '192.168.0.96']
MAC: 34:7E:5C:72:1D:65
MAC: 78:28:CA:A9:2D:2C
MAC: 34:7E:5C:36:E5:8C
MAC: 5C:AA:FD:6E:F2:41
MAC: 94:9F:3E:F8:7E:19
MAC: 5C:AA:FD:94:16:31
MAC: 34:7E:5C:B2:1B:BD
MAC: 78:28:CA:24:14:AB
MAC: 94:9F:3E:F6:03:A2
MAC address 34:7E:5C:72:1D:65 is connected to port 0/19
MAC address 78:28:CA:A9:2D:2C is connected to port 0/19
MAC address 34:7E:5C:36:E5:8C is connected to port 0/19
MAC address 5C:AA:FD:6E:F2:41 is connected to port 0/40
MAC address 94:9F:3E:F8:7E:19 is connected to port 0/19
MAC address 5C:AA:FD:94:16:31 is connected to port 0/19
MAC address 34:7E:5C:B2:1B:BD is connected to port 0/19
MAC address 78:28:CA:24:14:AB is connected to port 0/19
MAC address 94:9F:3E:F6:03:A2 is connected to port 0/16

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment