feat: Fingerprint-Attribute für Discovery-Scoring
Ergänzt DRIVER_NAME, HTTP_FINGERPRINT, SNMP_FINGERPRINT, SSH_FINGERPRINT, PORT_SPECS und SNMP_OBJECT_ID_PREFIX gemäß docs/DISCOVERY_FINGERPRINTING.md. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
6d8f01fe27
commit
f6bd38a9fe
@@ -2,11 +2,11 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import re
|
import re
|
||||||
import socket
|
import socket
|
||||||
from typing import Any, Dict, List, Optional
|
from typing import Any
|
||||||
|
|
||||||
from napalm.base import NetworkDriver
|
|
||||||
from napalm.base.exceptions import ConnectionException
|
from napalm.base.exceptions import ConnectionException
|
||||||
|
|
||||||
|
from napalm_device_types import AccessPointDriver, FingerprintRule, PortSpec
|
||||||
from napalm_device_types.models import DeviceActionResultDict
|
from napalm_device_types.models import DeviceActionResultDict
|
||||||
|
|
||||||
"""NAPALM driver for Sonos speakers.
|
"""NAPALM driver for Sonos speakers.
|
||||||
@@ -19,7 +19,7 @@ media-playback controls.
|
|||||||
_VOLUME_RE = re.compile(r"^\d+$")
|
_VOLUME_RE = re.compile(r"^\d+$")
|
||||||
|
|
||||||
|
|
||||||
class SonosDriver(NetworkDriver):
|
class SonosDriver(AccessPointDriver):
|
||||||
"""NAPALM driver for controlling Sonos speakers.
|
"""NAPALM driver for controlling Sonos speakers.
|
||||||
|
|
||||||
Implements the standard NAPALM interface plus media-playback methods
|
Implements the standard NAPALM interface plus media-playback methods
|
||||||
@@ -27,6 +27,15 @@ class SonosDriver(NetworkDriver):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
VENDOR = "Sonos"
|
VENDOR = "Sonos"
|
||||||
|
DRIVER_NAME = "sonos"
|
||||||
|
PORT_SPECS = [
|
||||||
|
PortSpec("http", 1400, ("/xml/device_description.xml",), weight=9.0, mandatory=True),
|
||||||
|
PortSpec("https", 1443, weight=4.0),
|
||||||
|
]
|
||||||
|
HTTP_FINGERPRINT = [
|
||||||
|
FingerprintRule("sonos", weight=8.0, mandatory=True),
|
||||||
|
FingerprintRule("rincon", weight=5.0),
|
||||||
|
]
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@@ -34,7 +43,7 @@ class SonosDriver(NetworkDriver):
|
|||||||
username: str = "",
|
username: str = "",
|
||||||
password: str = "",
|
password: str = "",
|
||||||
timeout: int = 60,
|
timeout: int = 60,
|
||||||
optional_args: Optional[Dict[str, Any]] = None,
|
optional_args: dict[str, Any] | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialise the driver with connection details."""
|
"""Initialise the driver with connection details."""
|
||||||
self.hostname = hostname
|
self.hostname = hostname
|
||||||
@@ -44,7 +53,7 @@ class SonosDriver(NetworkDriver):
|
|||||||
self.optional_args = optional_args or {}
|
self.optional_args = optional_args or {}
|
||||||
|
|
||||||
self._device: Any = None
|
self._device: Any = None
|
||||||
self._speaker_info: Optional[Dict[str, Any]] = None
|
self._speaker_info: dict[str, Any] | None = None
|
||||||
|
|
||||||
def open(self) -> None:
|
def open(self) -> None:
|
||||||
"""Open a connection to the Sonos speaker."""
|
"""Open a connection to the Sonos speaker."""
|
||||||
@@ -62,7 +71,7 @@ class SonosDriver(NetworkDriver):
|
|||||||
self._device = None
|
self._device = None
|
||||||
self._speaker_info = None
|
self._speaker_info = None
|
||||||
|
|
||||||
def is_alive(self) -> Dict[str, bool]:
|
def is_alive(self) -> dict[str, bool]:
|
||||||
"""Check whether the speaker is reachable on the network."""
|
"""Check whether the speaker is reachable on the network."""
|
||||||
if self._device is None:
|
if self._device is None:
|
||||||
return {"is_alive": False}
|
return {"is_alive": False}
|
||||||
@@ -75,7 +84,7 @@ class SonosDriver(NetworkDriver):
|
|||||||
except OSError:
|
except OSError:
|
||||||
return {"is_alive": False}
|
return {"is_alive": False}
|
||||||
|
|
||||||
def get_facts(self) -> Dict[str, Any]:
|
def get_facts(self) -> dict[str, Any]:
|
||||||
"""Return device facts gathered during connection."""
|
"""Return device facts gathered during connection."""
|
||||||
if not self._speaker_info:
|
if not self._speaker_info:
|
||||||
return {}
|
return {}
|
||||||
@@ -91,8 +100,8 @@ class SonosDriver(NetworkDriver):
|
|||||||
"interface_list": self._detect_interfaces(info),
|
"interface_list": self._detect_interfaces(info),
|
||||||
}
|
}
|
||||||
|
|
||||||
def _detect_interfaces(self, info: Dict[str, Any]) -> List[str]:
|
def _detect_interfaces(self, info: dict[str, Any]) -> list[str]:
|
||||||
ifaces: List[str] = []
|
ifaces: list[str] = []
|
||||||
has_eth = self._check_wired_connection()
|
has_eth = self._check_wired_connection()
|
||||||
if has_eth:
|
if has_eth:
|
||||||
ifaces.append("eth0")
|
ifaces.append("eth0")
|
||||||
@@ -112,7 +121,7 @@ class SonosDriver(NetworkDriver):
|
|||||||
pass
|
pass
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def get_interfaces(self) -> Dict[str, Dict[str, Any]]:
|
def get_interfaces(self) -> dict[str, dict[str, Any]]:
|
||||||
"""Return interface information for this speaker."""
|
"""Return interface information for this speaker."""
|
||||||
if not self._speaker_info:
|
if not self._speaker_info:
|
||||||
return {}
|
return {}
|
||||||
@@ -121,7 +130,7 @@ class SonosDriver(NetworkDriver):
|
|||||||
is_up = self.is_alive().get("is_alive", False)
|
is_up = self.is_alive().get("is_alive", False)
|
||||||
has_eth = self._check_wired_connection()
|
has_eth = self._check_wired_connection()
|
||||||
|
|
||||||
interfaces: Dict[str, Dict[str, Any]] = {}
|
interfaces: dict[str, dict[str, Any]] = {}
|
||||||
if has_eth:
|
if has_eth:
|
||||||
interfaces["eth0"] = {
|
interfaces["eth0"] = {
|
||||||
"is_up": is_up,
|
"is_up": is_up,
|
||||||
@@ -143,7 +152,7 @@ class SonosDriver(NetworkDriver):
|
|||||||
}
|
}
|
||||||
return interfaces
|
return interfaces
|
||||||
|
|
||||||
def get_interfaces_ip(self) -> Dict[str, Dict[str, Any]]:
|
def get_interfaces_ip(self) -> dict[str, dict[str, Any]]:
|
||||||
"""Return IP address information for the speaker interfaces."""
|
"""Return IP address information for the speaker interfaces."""
|
||||||
if not self._speaker_info:
|
if not self._speaker_info:
|
||||||
return {}
|
return {}
|
||||||
@@ -156,45 +165,45 @@ class SonosDriver(NetworkDriver):
|
|||||||
"prefix_length": 24,
|
"prefix_length": 24,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
interfaces: Dict[str, Any] = {}
|
interfaces: dict[str, Any] = {}
|
||||||
if has_eth:
|
if has_eth:
|
||||||
interfaces["eth0"] = {"ipv4": ipv4_entry, "ipv6": {}}
|
interfaces["eth0"] = {"ipv4": ipv4_entry, "ipv6": {}}
|
||||||
interfaces["wlan0"] = {"ipv4": ipv4_entry, "ipv6": {}}
|
interfaces["wlan0"] = {"ipv4": ipv4_entry, "ipv6": {}}
|
||||||
return interfaces
|
return interfaces
|
||||||
|
|
||||||
def get_arp_table(self) -> List[Dict[str, Any]]:
|
def get_arp_table(self) -> list[dict[str, Any]]:
|
||||||
"""Return the ARP table (not supported on Sonos)."""
|
"""Return the ARP table (not supported on Sonos)."""
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def get_lldp_neighbors(self) -> Dict[str, Any]:
|
def get_lldp_neighbors(self) -> dict[str, Any]:
|
||||||
"""Return LLDP neighbors (not supported on Sonos)."""
|
"""Return LLDP neighbors (not supported on Sonos)."""
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def get_lldp_neighbors_detail(self, interface: str = "") -> Dict[str, Any]:
|
def get_lldp_neighbors_detail(self, interface: str = "") -> dict[str, Any]:
|
||||||
"""Return detailed LLDP neighbors (not supported on Sonos)."""
|
"""Return detailed LLDP neighbors (not supported on Sonos)."""
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def get_config(self) -> Dict[str, Any]:
|
def get_config(self) -> dict[str, Any]:
|
||||||
"""Return the device config (not supported on Sonos)."""
|
"""Return the device config (not supported on Sonos)."""
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def get_environment(self) -> Dict[str, Any]:
|
def get_environment(self) -> dict[str, Any]:
|
||||||
"""Return environment/power info (not supported on Sonos)."""
|
"""Return environment/power info (not supported on Sonos)."""
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def get_optics(self) -> Dict[str, Any]:
|
def get_optics(self) -> dict[str, Any]:
|
||||||
"""Return optics info (not supported on Sonos)."""
|
"""Return optics info (not supported on Sonos)."""
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def get_ntp_peers(self) -> Dict[str, Optional[str]]:
|
def get_ntp_peers(self) -> dict[str, str | None]:
|
||||||
"""Return NTP peers (not supported on Sonos)."""
|
"""Return NTP peers (not supported on Sonos)."""
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def get_ntp_servers(self) -> Dict[str, Optional[str]]:
|
def get_ntp_servers(self) -> dict[str, str | None]:
|
||||||
"""Return NTP servers (not supported on Sonos)."""
|
"""Return NTP servers (not supported on Sonos)."""
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def get_ntp_stats(self) -> List[Dict[str, Any]]:
|
def get_ntp_stats(self) -> list[dict[str, Any]]:
|
||||||
"""Return NTP stats (not supported on Sonos)."""
|
"""Return NTP stats (not supported on Sonos)."""
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@@ -207,7 +216,7 @@ class SonosDriver(NetworkDriver):
|
|||||||
size: int = 64,
|
size: int = 64,
|
||||||
count: int = 5,
|
count: int = 5,
|
||||||
vrf: str = "",
|
vrf: str = "",
|
||||||
) -> Dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""Ping a destination (not supported on Sonos)."""
|
"""Ping a destination (not supported on Sonos)."""
|
||||||
return {
|
return {
|
||||||
"success": {},
|
"success": {},
|
||||||
@@ -221,58 +230,58 @@ class SonosDriver(NetworkDriver):
|
|||||||
ttl: int = 255,
|
ttl: int = 255,
|
||||||
timeout: int = 2,
|
timeout: int = 2,
|
||||||
vrf: str = "",
|
vrf: str = "",
|
||||||
) -> Dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""Run traceroute (not supported on Sonos)."""
|
"""Run traceroute (not supported on Sonos)."""
|
||||||
return {
|
return {
|
||||||
"success": {},
|
"success": {},
|
||||||
"error": "Not supported on Sonos",
|
"error": "Not supported on Sonos",
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_route_to(self, destination: str) -> List[Dict[str, Any]]:
|
def get_route_to(self, destination: str) -> list[dict[str, Any]]:
|
||||||
"""Return routing table (not supported on Sonos)."""
|
"""Return routing table (not supported on Sonos)."""
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def get_snmp_information(self) -> Dict[str, Any]:
|
def get_snmp_information(self) -> dict[str, Any]:
|
||||||
"""Return SNMP info (not supported on Sonos)."""
|
"""Return SNMP info (not supported on Sonos)."""
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def get_users(self) -> Dict[str, Any]:
|
def get_users(self) -> dict[str, Any]:
|
||||||
"""Return user accounts (not supported on Sonos)."""
|
"""Return user accounts (not supported on Sonos)."""
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def get_network_instances(self, name: str = "") -> Dict[str, Any]:
|
def get_network_instances(self, name: str = "") -> dict[str, Any]:
|
||||||
"""Return network instances / VRFs (not supported on Sonos)."""
|
"""Return network instances / VRFs (not supported on Sonos)."""
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def get_bgp_config(self, group: str = "") -> Dict[str, Any]:
|
def get_bgp_config(self, group: str = "") -> dict[str, Any]:
|
||||||
"""Return BGP config (not supported on Sonos)."""
|
"""Return BGP config (not supported on Sonos)."""
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def get_bgp_neighbors(self) -> Dict[str, Any]:
|
def get_bgp_neighbors(self) -> dict[str, Any]:
|
||||||
"""Return BGP neighbors (not supported on Sonos)."""
|
"""Return BGP neighbors (not supported on Sonos)."""
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def get_bgp_neighbors_detail(self, neighbor_address: str = "") -> Dict[str, Any]:
|
def get_bgp_neighbors_detail(self, neighbor_address: str = "") -> dict[str, Any]:
|
||||||
"""Return detailed BGP neighbor info (not supported on Sonos)."""
|
"""Return detailed BGP neighbor info (not supported on Sonos)."""
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def get_probes_config(self) -> Dict[str, Any]:
|
def get_probes_config(self) -> dict[str, Any]:
|
||||||
"""Return probe config (not supported on Sonos)."""
|
"""Return probe config (not supported on Sonos)."""
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def get_probes_results(self) -> Dict[str, Any]:
|
def get_probes_results(self) -> dict[str, Any]:
|
||||||
"""Return probe results (not supported on Sonos)."""
|
"""Return probe results (not supported on Sonos)."""
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def get_mac_address_table(self) -> List[Dict[str, Any]]:
|
def get_mac_address_table(self) -> list[dict[str, Any]]:
|
||||||
"""Return MAC address table (not supported on Sonos)."""
|
"""Return MAC address table (not supported on Sonos)."""
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def get_firewall_policies(self) -> Dict[str, Any]:
|
def get_firewall_policies(self) -> dict[str, Any]:
|
||||||
"""Return firewall policies (not supported on Sonos)."""
|
"""Return firewall policies (not supported on Sonos)."""
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def cli(self, commands: List[str]) -> List[Dict[str, Any]]:
|
def cli(self, commands: list[str]) -> list[dict[str, Any]]:
|
||||||
"""Run CLI commands (not supported on Sonos)."""
|
"""Run CLI commands (not supported on Sonos)."""
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@@ -310,7 +319,7 @@ class SonosDriver(NetworkDriver):
|
|||||||
# Broad catch: SoCo transport errors are unpredictable
|
# Broad catch: SoCo transport errors are unpredictable
|
||||||
return "STOPPED"
|
return "STOPPED"
|
||||||
|
|
||||||
def get_current_track(self) -> Dict[str, Any]:
|
def get_current_track(self) -> dict[str, Any]:
|
||||||
"""Return information about the currently playing track."""
|
"""Return information about the currently playing track."""
|
||||||
if self._device is None:
|
if self._device is None:
|
||||||
return {}
|
return {}
|
||||||
@@ -383,7 +392,7 @@ class SonosDriver(NetworkDriver):
|
|||||||
return
|
return
|
||||||
self._device.previous()
|
self._device.previous()
|
||||||
|
|
||||||
def get_group_info(self) -> Dict[str, Any]:
|
def get_group_info(self) -> dict[str, Any]:
|
||||||
"""Return information about the Sonos group this speaker belongs to."""
|
"""Return information about the Sonos group this speaker belongs to."""
|
||||||
if self._device is None:
|
if self._device is None:
|
||||||
return {}
|
return {}
|
||||||
@@ -407,7 +416,7 @@ class SonosDriver(NetworkDriver):
|
|||||||
# Broad catch: SoCo group/network operations can fail in many ways
|
# Broad catch: SoCo group/network operations can fail in many ways
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def get_zone_info(self) -> Dict[str, Any]:
|
def get_zone_info(self) -> dict[str, Any]:
|
||||||
"""Return detailed zone/speaker information."""
|
"""Return detailed zone/speaker information."""
|
||||||
if self._device is None:
|
if self._device is None:
|
||||||
return {}
|
return {}
|
||||||
|
|||||||
Reference in New Issue
Block a user