Last active
May 17, 2024 19:16
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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}') |
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
Sonos Device Discovery and Port Mapping Script
This script discovers Sonos devices on your network using SSDP (Simple Service Discovery Protocol), retrieves their MAC addresses, and then connects to a UniFi switch to find which ports the MAC addresses are connected to using Telnet commands.
Requirements
requests
libraryparamiko
libraryYou can install the required libraries using pip:
How It Works
LOCATION
URLs from the SSDP responses.LOCATION
URL and extracts theMACAddress
.show mac-addr-table
command to find the port each MAC address is connected to.Usage
Update Script Configuration: Replace the placeholder values in the script with your actual network details.
switch_ip
: IP address of your UniFi switch.username
: SSH username for your UniFi switch.password
: SSH password for your UniFi switch. paramiko support ssh public key auth, at least it does on macOS so you can leave this as is if you have that setup in unifiRun the Script: Execute the script using Python.
Notes
License
This project is licensed under the MIT License.