summaryrefslogtreecommitdiff
path: root/netdisco/discoverables
diff options
context:
space:
mode:
Diffstat (limited to 'netdisco/discoverables')
-rw-r--r--netdisco/discoverables/__init__.py151
-rw-r--r--netdisco/discoverables/apple_tv.py16
-rw-r--r--netdisco/discoverables/arduino.py9
-rw-r--r--netdisco/discoverables/asus_router.py13
-rw-r--r--netdisco/discoverables/axis.py41
-rw-r--r--netdisco/discoverables/belkin_wemo.py19
-rw-r--r--netdisco/discoverables/bluesound.py10
-rw-r--r--netdisco/discoverables/bose_soundtouch.py10
-rw-r--r--netdisco/discoverables/cambridgeaudio.py13
-rw-r--r--netdisco/discoverables/canon_printer.py13
-rw-r--r--netdisco/discoverables/daikin.py14
-rw-r--r--netdisco/discoverables/deconz.py13
-rw-r--r--netdisco/discoverables/denonavr.py23
-rw-r--r--netdisco/discoverables/directv.py13
-rw-r--r--netdisco/discoverables/dlna_dmr.py13
-rw-r--r--netdisco/discoverables/dlna_dms.py13
-rw-r--r--netdisco/discoverables/freebox.py10
-rw-r--r--netdisco/discoverables/fritzbox.py10
-rw-r--r--netdisco/discoverables/frontier_silicon.py12
-rw-r--r--netdisco/discoverables/google_cast.py10
-rw-r--r--netdisco/discoverables/harmony.py13
-rw-r--r--netdisco/discoverables/hass_ios.py9
-rw-r--r--netdisco/discoverables/home_assistant.py9
-rw-r--r--netdisco/discoverables/homekit.py18
-rw-r--r--netdisco/discoverables/hp_printer.py13
-rw-r--r--netdisco/discoverables/huawei_router.py13
-rw-r--r--netdisco/discoverables/igd.py14
-rw-r--r--netdisco/discoverables/ikea_tradfri.py10
-rw-r--r--netdisco/discoverables/kodi.py13
-rw-r--r--netdisco/discoverables/konnected.py10
-rw-r--r--netdisco/discoverables/lg_smart_device.py10
-rw-r--r--netdisco/discoverables/logitech_mediaserver.py14
-rw-r--r--netdisco/discoverables/lutron.py11
-rw-r--r--netdisco/discoverables/nanoleaf_aurora.py9
-rw-r--r--netdisco/discoverables/netgear_router.py13
-rw-r--r--netdisco/discoverables/octoprint.py12
-rw-r--r--netdisco/discoverables/openhome.py10
-rw-r--r--netdisco/discoverables/panasonic_viera.py10
-rw-r--r--netdisco/discoverables/philips_hue.py14
-rw-r--r--netdisco/discoverables/plex_mediaserver.py21
-rw-r--r--netdisco/discoverables/roku.py10
-rw-r--r--netdisco/discoverables/sabnzbd.py13
-rw-r--r--netdisco/discoverables/samsung_printer.py13
-rw-r--r--netdisco/discoverables/samsung_tv.py25
-rw-r--r--netdisco/discoverables/songpal.py50
-rw-r--r--netdisco/discoverables/sonos.py10
-rw-r--r--netdisco/discoverables/spotify_connect.py10
-rw-r--r--netdisco/discoverables/tellstick.py14
-rw-r--r--netdisco/discoverables/tivo_dvr.py14
-rw-r--r--netdisco/discoverables/volumio.py10
-rw-r--r--netdisco/discoverables/webos_tv.py15
-rw-r--r--netdisco/discoverables/wink.py14
-rw-r--r--netdisco/discoverables/xbox_smartglass.py14
-rw-r--r--netdisco/discoverables/xiaomi_gw.py33
-rw-r--r--netdisco/discoverables/yamaha.py42
-rw-r--r--netdisco/discoverables/yeelight.py27
-rw-r--r--netdisco/discoverables/ziggo_mediabox_xl.py12
57 files changed, 998 insertions, 0 deletions
diff --git a/netdisco/discoverables/__init__.py b/netdisco/discoverables/__init__.py
new file mode 100644
index 0000000..dd39785
--- /dev/null
+++ b/netdisco/discoverables/__init__.py
@@ -0,0 +1,151 @@
+"""Provides helpful stuff for discoverables."""
+# pylint: disable=abstract-method
+import ipaddress
+from urllib.parse import urlparse
+
+from ..const import (
+ ATTR_NAME, ATTR_MODEL_NAME, ATTR_HOST, ATTR_PORT, ATTR_SSDP_DESCRIPTION,
+ ATTR_SERIAL, ATTR_MODEL_NUMBER, ATTR_HOSTNAME, ATTR_MAC_ADDRESS,
+ ATTR_PROPERTIES, ATTR_MANUFACTURER, ATTR_UDN, ATTR_UPNP_DEVICE_TYPE)
+
+
+class BaseDiscoverable:
+ """Base class for discoverable services or device types."""
+
+ def is_discovered(self):
+ """Return True if it is discovered."""
+ return len(self.get_entries()) > 0
+
+ def get_info(self):
+ """Return a list with the important info for each item.
+
+ Uses self.info_from_entry internally.
+ """
+ return [self.info_from_entry(entry) for entry in self.get_entries()]
+
+ # pylint: disable=no-self-use
+ def info_from_entry(self, entry):
+ """Return an object with important info from the entry."""
+ return entry
+
+ def get_entries(self):
+ """Return all the discovered entries."""
+ raise NotImplementedError()
+
+
+class SSDPDiscoverable(BaseDiscoverable):
+ """uPnP discoverable base class."""
+
+ def __init__(self, netdis):
+ """Initialize SSDPDiscoverable."""
+ self.netdis = netdis
+
+ def info_from_entry(self, entry):
+ """Get most important info."""
+ url = urlparse(entry.location)
+ info = {
+ ATTR_HOST: url.hostname,
+ ATTR_PORT: url.port,
+ ATTR_SSDP_DESCRIPTION: entry.location
+ }
+ device = entry.description.get('device')
+
+ if device:
+ info[ATTR_NAME] = device.get('friendlyName')
+ info[ATTR_MODEL_NAME] = device.get('modelName')
+ info[ATTR_MODEL_NUMBER] = device.get('modelNumber')
+ info[ATTR_SERIAL] = device.get('serialNumber')
+ info[ATTR_MANUFACTURER] = device.get('manufacturer')
+ info[ATTR_UDN] = device.get('UDN')
+ info[ATTR_UPNP_DEVICE_TYPE] = device.get('deviceType')
+
+ return info
+
+ # Helper functions
+
+ # pylint: disable=invalid-name
+ def find_by_st(self, st):
+ """Find entries by ST (the device identifier)."""
+ return self.netdis.ssdp.find_by_st(st)
+
+ def find_by_device_description(self, values):
+ """Find entries based on values from their description."""
+ return self.netdis.ssdp.find_by_device_description(values)
+
+
+class MDNSDiscoverable(BaseDiscoverable):
+ """mDNS Discoverable base class."""
+
+ def __init__(self, netdis, typ):
+ """Initialize MDNSDiscoverable."""
+ self.netdis = netdis
+ self.typ = typ
+ self.services = {}
+
+ netdis.mdns.register_service(self)
+
+ def reset(self):
+ """Reset found services."""
+ self.services.clear()
+
+ # pylint: disable=unused-argument
+ def remove_service(self, zconf, typ, name):
+ """Callback when a service is removed."""
+ self.services.pop(name, None)
+
+ def add_service(self, zconf, typ, name):
+ """Callback when a service is found."""
+ service = None
+ tries = 0
+ while service is None and tries < 3:
+ service = zconf.get_service_info(typ, name)
+ tries += 1
+
+ if service is not None:
+ self.services[name] = service
+
+ def get_entries(self):
+ """Return all found services."""
+ return self.services.values()
+
+ def info_from_entry(self, entry):
+ """Return most important info from mDNS entries."""
+ properties = {}
+
+ for key, value in entry.properties.items():
+ if isinstance(value, bytes):
+ value = value.decode('utf-8')
+ properties[key.decode('utf-8')] = value
+
+ info = {
+ ATTR_HOST: str(ipaddress.ip_address(entry.address)),
+ ATTR_PORT: entry.port,
+ ATTR_HOSTNAME: entry.server,
+ ATTR_PROPERTIES: properties,
+ }
+
+ if "mac" in properties:
+ info[ATTR_MAC_ADDRESS] = properties["mac"]
+
+ return info
+
+ def find_by_device_name(self, name):
+ """Find entries based on the beginning of their entry names."""
+ return [entry for entry in self.services.values()
+ if entry.name.startswith(name)]
+
+
+class GDMDiscoverable(BaseDiscoverable):
+ """GDM discoverable base class."""
+
+ def __init__(self, netdis):
+ """Initialize GDMDiscoverable."""
+ self.netdis = netdis
+
+ def find_by_content_type(self, value):
+ """Find entries based on values from their content_type."""
+ return self.netdis.gdm.find_by_content_type(value)
+
+ def find_by_data(self, values):
+ """Find entries based on values from any returned field."""
+ return self.netdis.gdm.find_by_data(values)
diff --git a/netdisco/discoverables/apple_tv.py b/netdisco/discoverables/apple_tv.py
new file mode 100644
index 0000000..02cb642
--- /dev/null
+++ b/netdisco/discoverables/apple_tv.py
@@ -0,0 +1,16 @@
+"""Discover Apple TV media players."""
+from . import MDNSDiscoverable
+from ..const import ATTR_NAME, ATTR_PROPERTIES
+
+
+class Discoverable(MDNSDiscoverable):
+ """Add support for Apple TV devices."""
+
+ def __init__(self, nd):
+ super(Discoverable, self).__init__(nd, '_appletv-v2._tcp.local.')
+
+ def info_from_entry(self, entry):
+ """Returns most important info from mDNS entries."""
+ info = super().info_from_entry(entry)
+ info[ATTR_NAME] = info[ATTR_PROPERTIES]['Name'].replace('\xa0', ' ')
+ return info
diff --git a/netdisco/discoverables/arduino.py b/netdisco/discoverables/arduino.py
new file mode 100644
index 0000000..013b6cc
--- /dev/null
+++ b/netdisco/discoverables/arduino.py
@@ -0,0 +1,9 @@
+"""Discover Arduino devices."""
+from . import MDNSDiscoverable
+
+
+class Discoverable(MDNSDiscoverable):
+ """Add support for discovering Arduino devices."""
+
+ def __init__(self, nd):
+ super(Discoverable, self).__init__(nd, '_arduino._tcp.local.')
diff --git a/netdisco/discoverables/asus_router.py b/netdisco/discoverables/asus_router.py
new file mode 100644
index 0000000..20de3a8
--- /dev/null
+++ b/netdisco/discoverables/asus_router.py
@@ -0,0 +1,13 @@
+"""Discover ASUS routers."""
+from . import SSDPDiscoverable
+
+
+class Discoverable(SSDPDiscoverable):
+ """Add support for discovering ASUS routers."""
+
+ def get_entries(self):
+ """Get all the ASUS uPnP entries."""
+ return self.find_by_device_description({
+ "manufacturer": "ASUSTeK Computer Inc.",
+ "deviceType": "urn:schemas-upnp-org:device:InternetGatewayDevice:1"
+ })
diff --git a/netdisco/discoverables/axis.py b/netdisco/discoverables/axis.py
new file mode 100644
index 0000000..c4278b1
--- /dev/null
+++ b/netdisco/discoverables/axis.py
@@ -0,0 +1,41 @@
+"""Discover Axis devices."""
+from . import MDNSDiscoverable
+
+from ..const import (
+ ATTR_HOST, ATTR_PORT, ATTR_HOSTNAME, ATTR_PROPERTIES)
+
+
+class Discoverable(MDNSDiscoverable):
+ """Add support for discovering Axis devices."""
+
+ def info_from_entry(self, entry):
+ """Return most important info from mDNS entries."""
+ properties = {}
+
+ for key, value in entry.properties.items():
+ if isinstance(value, bytes):
+ value = value.decode('utf-8')
+ properties[key.decode('utf-8')] = value
+
+ return {
+ ATTR_HOST: self.ip_from_host(entry.server),
+ ATTR_PORT: entry.port,
+ ATTR_HOSTNAME: entry.server,
+ ATTR_PROPERTIES: properties,
+ }
+
+ def __init__(self, nd):
+ """Initialize the Axis discovery."""
+ super(Discoverable, self).__init__(nd, '_axis-video._tcp.local.')
+
+ def ip_from_host(self, host):
+ """Attempt to return the ip address from an mDNS host.
+
+ Return host if failed.
+ """
+ ips = self.netdis.mdns.zeroconf.cache.entries_with_name(host.lower())
+
+ try:
+ return repr(ips[0]) if ips else host
+ except TypeError:
+ return host
diff --git a/netdisco/discoverables/belkin_wemo.py b/netdisco/discoverables/belkin_wemo.py
new file mode 100644
index 0000000..8f3a26e
--- /dev/null
+++ b/netdisco/discoverables/belkin_wemo.py
@@ -0,0 +1,19 @@
+"""Discover Belkin Wemo devices."""
+from . import SSDPDiscoverable
+from ..const import ATTR_MAC_ADDRESS
+
+
+class Discoverable(SSDPDiscoverable):
+ """Add support for discovering Belkin WeMo platform devices."""
+
+ def info_from_entry(self, entry):
+ """Return most important info from a uPnP entry."""
+ info = super().info_from_entry(entry)
+ device = entry.description['device']
+ info[ATTR_MAC_ADDRESS] = device.get('macAddress', '')
+ return info
+
+ def get_entries(self):
+ """Return all Belkin Wemo entries."""
+ return self.find_by_device_description(
+ {'manufacturer': 'Belkin International Inc.'})
diff --git a/netdisco/discoverables/bluesound.py b/netdisco/discoverables/bluesound.py
new file mode 100644
index 0000000..4603d83
--- /dev/null
+++ b/netdisco/discoverables/bluesound.py
@@ -0,0 +1,10 @@
+"""Discover devices that implement the Bluesound platform."""
+from . import MDNSDiscoverable
+
+
+class Discoverable(MDNSDiscoverable):
+ """Add support for discovering Bluesound service."""
+
+ def __init__(self, nd):
+ """Initialize the Bluesound discovery."""
+ super(Discoverable, self).__init__(nd, '_musc._tcp.local.')
diff --git a/netdisco/discoverables/bose_soundtouch.py b/netdisco/discoverables/bose_soundtouch.py
new file mode 100644
index 0000000..e9f819a
--- /dev/null
+++ b/netdisco/discoverables/bose_soundtouch.py
@@ -0,0 +1,10 @@
+"""Discover Bose SoundTouch devices."""
+from . import MDNSDiscoverable
+
+
+class Discoverable(MDNSDiscoverable):
+ """Add support for discovering Bose SoundTouch devices."""
+
+ def __init__(self, nd):
+ """Initialize the Bose SoundTouch discovery."""
+ super(Discoverable, self).__init__(nd, '_soundtouch._tcp.local.')
diff --git a/netdisco/discoverables/cambridgeaudio.py b/netdisco/discoverables/cambridgeaudio.py
new file mode 100644
index 0000000..3538e48
--- /dev/null
+++ b/netdisco/discoverables/cambridgeaudio.py
@@ -0,0 +1,13 @@
+""" Discover Cambridge Audio StreamMagic devices. """
+from . import SSDPDiscoverable
+
+
+class Discoverable(SSDPDiscoverable):
+ """Add support for discovering Cambridge Audio StreamMagic devices."""
+
+ def get_entries(self):
+ """Get all Cambridge Audio MediaRenderer uPnP entries."""
+ return self.find_by_device_description({
+ "manufacturer": "Cambridge Audio",
+ "deviceType": "urn:schemas-upnp-org:device:MediaRenderer:1"
+ })
diff --git a/netdisco/discoverables/canon_printer.py b/netdisco/discoverables/canon_printer.py
new file mode 100644
index 0000000..b005f61
--- /dev/null
+++ b/netdisco/discoverables/canon_printer.py
@@ -0,0 +1,13 @@
+"""Discover Canon Printers"""
+from . import SSDPDiscoverable
+
+
+class Discoverable(SSDPDiscoverable):
+ """Support for the discovery of Canon Printers"""
+
+ def get_entries(self):
+ """Get all the Canon Printer uPnP entries."""
+ return self.find_by_device_description({
+ "manufacturer": "CANON INC.",
+ "deviceType": "urn:schemas-cipa-jp:device:DPSPrinter:1"
+ })
diff --git a/netdisco/discoverables/daikin.py b/netdisco/discoverables/daikin.py
new file mode 100644
index 0000000..b260fd4
--- /dev/null
+++ b/netdisco/discoverables/daikin.py
@@ -0,0 +1,14 @@
+"""Discover Daikin devices."""
+from . import BaseDiscoverable
+
+
+class Discoverable(BaseDiscoverable):
+ """Add support for discovering a Daikin device."""
+
+ def __init__(self, netdis):
+ """Initialize the Daikin discovery."""
+ self._netdis = netdis
+
+ def get_entries(self):
+ """Get all the Daikin details."""
+ return self._netdis.daikin.entries
diff --git a/netdisco/discoverables/deconz.py b/netdisco/discoverables/deconz.py
new file mode 100644
index 0000000..0863cda
--- /dev/null
+++ b/netdisco/discoverables/deconz.py
@@ -0,0 +1,13 @@
+"""Discover deCONZ gateways."""
+from . import SSDPDiscoverable
+
+
+class Discoverable(SSDPDiscoverable):
+ """Add support for discovering deCONZ Wireless Light Control gateways."""
+
+ def get_entries(self):
+ """Get all the deCONZ uPnP entries."""
+ return self.find_by_device_description({
+ "manufacturerURL": "http://www.dresden-elektronik.de",
+ "modelDescription": "dresden elektronik Wireless Light Control"
+ })
diff --git a/netdisco/discoverables/denonavr.py b/netdisco/discoverables/denonavr.py
new file mode 100644
index 0000000..d830aa6
--- /dev/null
+++ b/netdisco/discoverables/denonavr.py
@@ -0,0 +1,23 @@
+"""Discover Denon AVR devices."""
+from urllib.parse import urlparse
+
+from . import SSDPDiscoverable
+from ..const import ATTR_HOST
+
+
+class Discoverable(SSDPDiscoverable):
+ """Add support for discovering Denon AVR devices."""
+
+ def get_entries(self):
+ """Get all Denon AVR uPnP entries."""
+ return self.find_by_device_description({
+ "manufacturer": "Denon",
+ "deviceType": "urn:schemas-upnp-org:device:MediaRenderer:1"
+ })
+
+ def info_from_entry(self, entry):
+ """Get most important info, which is name, model and host."""
+ info = super().info_from_entry(entry)
+ info[ATTR_HOST] = urlparse(
+ entry.description['device']['presentationURL']).hostname
+ return info
diff --git a/netdisco/discoverables/directv.py b/netdisco/discoverables/directv.py
new file mode 100644
index 0000000..7babd9c
--- /dev/null
+++ b/netdisco/discoverables/directv.py
@@ -0,0 +1,13 @@
+"""Discover DirecTV Receivers."""
+from . import SSDPDiscoverable
+
+
+class Discoverable(SSDPDiscoverable):
+ """Add support for discovering DirecTV Receivers."""
+
+ def get_entries(self):
+ """Get all the DirecTV uPnP entries."""
+ return self.find_by_device_description({
+ "manufacturer": "DIRECTV",
+ "deviceType": "urn:schemas-upnp-org:device:MediaServer:1"
+ })
diff --git a/netdisco/discoverables/dlna_dmr.py b/netdisco/discoverables/dlna_dmr.py
new file mode 100644
index 0000000..bbc7fe6
--- /dev/null
+++ b/netdisco/discoverables/dlna_dmr.py
@@ -0,0 +1,13 @@
+"""Discover DLNA services."""
+from . import SSDPDiscoverable
+
+
+class Discoverable(SSDPDiscoverable):
+ """Add support for discovering DLNA services."""
+
+ def get_entries(self):
+ """Get all the DLNA service uPnP entries."""
+ return \
+ self.find_by_st("urn:schemas-upnp-org:device:MediaRenderer:1") + \
+ self.find_by_st("urn:schemas-upnp-org:device:MediaRenderer:2") + \
+ self.find_by_st("urn:schemas-upnp-org:device:MediaRenderer:3")
diff --git a/netdisco/discoverables/dlna_dms.py b/netdisco/discoverables/dlna_dms.py
new file mode 100644
index 0000000..eac38ec
--- /dev/null
+++ b/netdisco/discoverables/dlna_dms.py
@@ -0,0 +1,13 @@
+"""Discover DLNA services."""
+from . import SSDPDiscoverable
+
+
+class Discoverable(SSDPDiscoverable):
+ """Add support for discovering DLNA services."""
+
+ def get_entries(self):
+ """Get all the DLNA service uPnP entries."""
+ return self.find_by_st("urn:schemas-upnp-org:device:MediaServer:1") + \
+ self.find_by_st("urn:schemas-upnp-org:device:MediaServer:2") + \
+ self.find_by_st("urn:schemas-upnp-org:device:MediaServer:3") + \
+ self.find_by_st("urn:schemas-upnp-org:device:MediaServer:4")
diff --git a/netdisco/discoverables/freebox.py b/netdisco/discoverables/freebox.py
new file mode 100644
index 0000000..11bc5f8
--- /dev/null
+++ b/netdisco/discoverables/freebox.py
@@ -0,0 +1,10 @@
+"""Discover Freebox routers."""
+from . import MDNSDiscoverable
+
+
+class Discoverable(MDNSDiscoverable):
+ """Add support for discovering Freebox routers."""
+
+ def __init__(self, nd):
+ """Initialize the Freebox discovery."""
+ super(Discoverable, self).__init__(nd, '_fbx-api._tcp.local.')
diff --git a/netdisco/discoverables/fritzbox.py b/netdisco/discoverables/fritzbox.py
new file mode 100644
index 0000000..4b729f5
--- /dev/null
+++ b/netdisco/discoverables/fritzbox.py
@@ -0,0 +1,10 @@
+"""Discover AVM FRITZ devices."""
+from . import SSDPDiscoverable
+
+
+class Discoverable(SSDPDiscoverable):
+ """Add support for discovering AVM FRITZ devices."""
+
+ def get_entries(self):
+ """Get all AVM FRITZ entries."""
+ return self.find_by_st("urn:schemas-upnp-org:device:fritzbox:1")
diff --git a/netdisco/discoverables/frontier_silicon.py b/netdisco/discoverables/frontier_silicon.py
new file mode 100644
index 0000000..6cb5fe5
--- /dev/null
+++ b/netdisco/discoverables/frontier_silicon.py
@@ -0,0 +1,12 @@
+"""Discover frontier silicon devices."""
+from . import SSDPDiscoverable
+
+
+class Discoverable(SSDPDiscoverable):
+ """Add support for discovering frontier silicon devices."""
+
+ def get_entries(self):
+ """Get all the frontier silicon uPnP entries."""
+ return [entry for entry in self.netdis.ssdp.all()
+ if entry.st and 'fsapi' in entry.st and
+ 'urn:schemas-frontier-silicon-com' in entry.st]
diff --git a/netdisco/discoverables/google_cast.py b/netdisco/discoverables/google_cast.py
new file mode 100644
index 0000000..fb7d373
--- /dev/null
+++ b/netdisco/discoverables/google_cast.py
@@ -0,0 +1,10 @@
+"""Discover devices that implement the Google Cast platform."""
+from . import MDNSDiscoverable
+
+
+class Discoverable(MDNSDiscoverable):
+ """Add support for discovering Google Cast platform devices."""
+
+ def __init__(self, nd):
+ """Initialize the Cast discovery."""
+ super(Discoverable, self).__init__(nd, '_googlecast._tcp.local.')
diff --git a/netdisco/discoverables/harmony.py b/netdisco/discoverables/harmony.py
new file mode 100644
index 0000000..19d97eb
--- /dev/null
+++ b/netdisco/discoverables/harmony.py
@@ -0,0 +1,13 @@
+"""Discover Harmony Hub remotes."""
+from . import SSDPDiscoverable
+
+
+class Discoverable(SSDPDiscoverable):
+ """Add support for discovering Harmony Hub remotes"""
+
+ def get_entries(self):
+ """Get all the Harmony uPnP entries."""
+ return self.find_by_device_description({
+ "manufacturer": "Logitech",
+ "deviceType": "urn:myharmony-com:device:harmony:1"
+ })
diff --git a/netdisco/discoverables/hass_ios.py b/netdisco/discoverables/hass_ios.py
new file mode 100644
index 0000000..84c1eaa
--- /dev/null
+++ b/netdisco/discoverables/hass_ios.py
@@ -0,0 +1,9 @@
+"""Discover Home Assistant iOS app."""
+from . import MDNSDiscoverable
+
+
+class Discoverable(MDNSDiscoverable):
+ """Add support for discovering the Home Assistant iOS app."""
+
+ def __init__(self, nd):
+ super(Discoverable, self).__init__(nd, '_hass-ios._tcp.local.')
diff --git a/netdisco/discoverables/home_assistant.py b/netdisco/discoverables/home_assistant.py
new file mode 100644
index 0000000..2b7828e
--- /dev/null
+++ b/netdisco/discoverables/home_assistant.py
@@ -0,0 +1,9 @@
+"""Discover Home Assistant servers."""
+from . import MDNSDiscoverable
+
+
+class Discoverable(MDNSDiscoverable):
+ """Add support for discovering Home Assistant instances."""
+
+ def __init__(self, nd):
+ super(Discoverable, self).__init__(nd, '_home-assistant._tcp.local.')
diff --git a/netdisco/discoverables/homekit.py b/netdisco/discoverables/homekit.py
new file mode 100644
index 0000000..690cb63
--- /dev/null
+++ b/netdisco/discoverables/homekit.py
@@ -0,0 +1,18 @@
+"""Discover Homekit devices."""
+from . import MDNSDiscoverable
+
+from ..const import ATTR_NAME
+
+
+class Discoverable(MDNSDiscoverable):
+ """Add support for discovering HomeKit devices."""
+
+ def __init__(self, nd):
+ super(Discoverable, self).__init__(nd, '_hap._tcp.local.')
+
+ def info_from_entry(self, entry):
+ info = super(Discoverable, self).info_from_entry(entry)
+ name = entry.name
+ name = name.replace('._hap._tcp.local.', '')
+ info[ATTR_NAME] = name
+ return info
diff --git a/netdisco/discoverables/hp_printer.py b/netdisco/discoverables/hp_printer.py
new file mode 100644
index 0000000..b4c6131
--- /dev/null
+++ b/netdisco/discoverables/hp_printer.py
@@ -0,0 +1,13 @@
+"""Discover HP Printers"""
+from . import MDNSDiscoverable
+
+
+class Discoverable(MDNSDiscoverable):
+ """Support for the discovery of HP Printers"""
+
+ def __init__(self, nd):
+ """Initialize the HP Printer discovery"""
+ super(Discoverable, self).__init__(nd, '_printer._tcp.local.')
+
+ def get_entries(self):
+ return self.find_by_device_name('HP ')
diff --git a/netdisco/discoverables/huawei_router.py b/netdisco/discoverables/huawei_router.py
new file mode 100644
index 0000000..7f1bb3d
--- /dev/null
+++ b/netdisco/discoverables/huawei_router.py
@@ -0,0 +1,13 @@
+"""Discover Huawei routers."""
+from . import SSDPDiscoverable
+
+
+class Discoverable(SSDPDiscoverable):
+ """Add support for discovering Huawei routers."""
+
+ def get_entries(self):
+ """Get all the Huawei uPnP entries."""
+ return self.find_by_device_description({
+ "manufacturer": "Huawei Technologies Co., Ltd.",
+ "deviceType": "urn:schemas-upnp-org:device:InternetGatewayDevice:1"
+ })
diff --git a/netdisco/discoverables/igd.py b/netdisco/discoverables/igd.py
new file mode 100644
index 0000000..ba92c90
--- /dev/null
+++ b/netdisco/discoverables/igd.py
@@ -0,0 +1,14 @@
+"""Discover IGD services."""
+from . import SSDPDiscoverable
+
+
+class Discoverable(SSDPDiscoverable):
+ """Add support for discovering IGD services."""
+
+ def get_entries(self):
+ """Get all the IGD service uPnP entries."""
+ return \
+ self.find_by_st(
+ "urn:schemas-upnp-org:device:InternetGatewayDevice:1") + \
+ self.find_by_st(
+ "urn:schemas-upnp-org:device:InternetGatewayDevice:2")
diff --git a/netdisco/discoverables/ikea_tradfri.py b/netdisco/discoverables/ikea_tradfri.py
new file mode 100644
index 0000000..9fa7c57
--- /dev/null
+++ b/netdisco/discoverables/ikea_tradfri.py
@@ -0,0 +1,10 @@
+"""Discover devices that implement the Ikea Tradfri platform."""
+from . import MDNSDiscoverable
+
+
+class Discoverable(MDNSDiscoverable):
+ """Add support for discovering Ikea Tradfri devices."""
+
+ def __init__(self, nd):
+ """Initialize the Cast discovery."""
+ super(Discoverable, self).__init__(nd, '_coap._udp.local.')
diff --git a/netdisco/discoverables/kodi.py b/netdisco/discoverables/kodi.py
new file mode 100644
index 0000000..4de74dd
--- /dev/null
+++ b/netdisco/discoverables/kodi.py
@@ -0,0 +1,13 @@
+"""Discover Kodi servers."""
+from . import MDNSDiscoverable
+
+
+class Discoverable(MDNSDiscoverable):
+ """Add support for discovering Kodi."""
+
+ def __init__(self, nd):
+ """Initialize the Kodi discovery."""
+ super(Discoverable, self).__init__(nd, '_http._tcp.local.')
+
+ def get_entries(self):
+ return self.find_by_device_name('Kodi ')
diff --git a/netdisco/discoverables/konnected.py b/netdisco/discoverables/konnected.py
new file mode 100644
index 0000000..7057b17
--- /dev/null
+++ b/netdisco/discoverables/konnected.py
@@ -0,0 +1,10 @@
+"""Discover Konnected Security devices."""
+from . import SSDPDiscoverable
+
+
+class Discoverable(SSDPDiscoverable):
+ """Add support for discovering Konnected Security devices."""
+
+ def get_entries(self):
+ """Return all Konnected entries."""
+ return self.find_by_st('urn:schemas-konnected-io:device:Security:1')
diff --git a/netdisco/discoverables/lg_smart_device.py b/netdisco/discoverables/lg_smart_device.py
new file mode 100644
index 0000000..49a6fe9
--- /dev/null
+++ b/netdisco/discoverables/lg_smart_device.py
@@ -0,0 +1,10 @@
+"""Discover LG smart devices."""
+from . import MDNSDiscoverable
+
+
+# pylint: disable=too-few-public-methods
+class Discoverable(MDNSDiscoverable):
+ """Add support for discovering LG smart devices."""
+
+ def __init__(self, nd):
+ super(Discoverable, self).__init__(nd, '_lg-smart-device._tcp.local.')
diff --git a/netdisco/discoverables/logitech_mediaserver.py b/netdisco/discoverables/logitech_mediaserver.py
new file mode 100644
index 0000000..d03b8fd
--- /dev/null
+++ b/netdisco/discoverables/logitech_mediaserver.py
@@ -0,0 +1,14 @@
+"""Discover Logitech Media Server."""
+from . import BaseDiscoverable
+
+
+class Discoverable(BaseDiscoverable):
+ """Add support for discovering Logitech Media Server."""
+
+ def __init__(self, netdis):
+ """Initialize Logitech Media Server discovery."""
+ self.netdis = netdis
+
+ def get_entries(self):
+ """Get all the Logitech Media Server details."""
+ return self.netdis.lms.entries
diff --git a/netdisco/discoverables/lutron.py b/netdisco/discoverables/lutron.py
new file mode 100644
index 0000000..b0c51d0
--- /dev/null
+++ b/netdisco/discoverables/lutron.py
@@ -0,0 +1,11 @@
+"""Discover Lutron Caseta Smart Bridge and Smart Bridge Pro devices."""
+from . import MDNSDiscoverable
+
+
+class Discoverable(MDNSDiscoverable):
+ """Add support for discovering Lutron Caseta Smart Bridge
+ and Smart Bridge Pro devices."""
+
+ def __init__(self, nd):
+ """Initialize the Lutron Smart Bridge discovery."""
+ super(Discoverable, self).__init__(nd, '_lutron._tcp.local.')
diff --git a/netdisco/discoverables/nanoleaf_aurora.py b/netdisco/discoverables/nanoleaf_aurora.py
new file mode 100644
index 0000000..135d785
--- /dev/null
+++ b/netdisco/discoverables/nanoleaf_aurora.py
@@ -0,0 +1,9 @@
+"""Discover Nanoleaf Aurora devices."""
+from . import MDNSDiscoverable
+
+
+class Discoverable(MDNSDiscoverable):
+ """Add support for discovering Nanoleaf Aurora devices."""
+
+ def __init__(self, nd):
+ super(Discoverable, self).__init__(nd, '_nanoleafapi._tcp.local.')
diff --git a/netdisco/discoverables/netgear_router.py b/netdisco/discoverables/netgear_router.py
new file mode 100644
index 0000000..d4409bd
--- /dev/null
+++ b/netdisco/discoverables/netgear_router.py
@@ -0,0 +1,13 @@
+"""Discover Netgear routers."""
+from . import SSDPDiscoverable
+
+
+class Discoverable(SSDPDiscoverable):
+ """Add support for discovering Netgear routers."""
+
+ def get_entries(self):
+ """Get all the Netgear uPnP entries."""
+ return self.find_by_device_description({
+ "manufacturer": "NETGEAR, Inc.",
+ "deviceType": "urn:schemas-upnp-org:device:InternetGatewayDevice:1"
+ })
diff --git a/netdisco/discoverables/octoprint.py b/netdisco/discoverables/octoprint.py
new file mode 100644
index 0000000..f6d10e0
--- /dev/null
+++ b/netdisco/discoverables/octoprint.py
@@ -0,0 +1,12 @@
+"""Discover OctoPrint Servers."""
+from . import SSDPDiscoverable
+
+
+class Discoverable(SSDPDiscoverable):
+ """Add support for discovering OctoPrint servers."""
+
+ def get_entries(self):
+ """Get all the OctoPrint uPnP entries."""
+ return self.find_by_device_description({
+ "manufacturer": "The OctoPrint Project"
+ })
diff --git a/netdisco/discoverables/openhome.py b/netdisco/discoverables/openhome.py
new file mode 100644
index 0000000..3cc4314
--- /dev/null
+++ b/netdisco/discoverables/openhome.py
@@ -0,0 +1,10 @@
+"""Discover Openhome devices."""
+from . import SSDPDiscoverable
+
+
+class Discoverable(SSDPDiscoverable):
+ """Add support for discovering Openhome compliant devices."""
+
+ def get_entries(self):
+ """Get all the Openhome compliant device uPnP entries."""
+ return self.find_by_st("urn:av-openhome-org:service:Product:2")
diff --git a/netdisco/discoverables/panasonic_viera.py b/netdisco/discoverables/panasonic_viera.py
new file mode 100644
index 0000000..3f90271
--- /dev/null
+++ b/netdisco/discoverables/panasonic_viera.py
@@ -0,0 +1,10 @@
+"""Discover Panasonic Viera TV devices."""
+from . import SSDPDiscoverable
+
+
+class Discoverable(SSDPDiscoverable):
+ """Add support for discovering Viera TV devices."""
+
+ def get_entries(self):
+ """Get all the Viera TV device uPnP entries."""
+ return self.find_by_st("urn:panasonic-com:service:p00NetworkControl:1")
diff --git a/netdisco/discoverables/philips_hue.py b/netdisco/discoverables/philips_hue.py
new file mode 100644
index 0000000..582fbc1
--- /dev/null
+++ b/netdisco/discoverables/philips_hue.py
@@ -0,0 +1,14 @@
+"""Discover Philips Hue bridges."""
+from . import SSDPDiscoverable
+
+
+class Discoverable(SSDPDiscoverable):
+ """Add support for discovering Philips Hue bridges."""
+
+ def get_entries(self):
+ """Get all the Hue bridge uPnP entries."""
+ return self.find_by_device_description({
+ "manufacturer": "Royal Philips Electronics",
+ "manufacturerURL": "http://www.philips.com",
+ "modelNumber": ["929000226503", "BSB002"]
+ })
diff --git a/netdisco/discoverables/plex_mediaserver.py b/netdisco/discoverables/plex_mediaserver.py
new file mode 100644
index 0000000..9eb427a
--- /dev/null
+++ b/netdisco/discoverables/plex_mediaserver.py
@@ -0,0 +1,21 @@
+"""Discover PlexMediaServer."""
+from . import GDMDiscoverable
+from ..const import ATTR_NAME, ATTR_HOST, ATTR_PORT, ATTR_URLBASE
+
+
+class Discoverable(GDMDiscoverable):
+ """Add support for discovering Plex Media Server."""
+
+ def info_from_entry(self, entry):
+ """Return most important info from a GDM entry."""
+ return {
+ ATTR_NAME: entry['data']['Name'],
+ ATTR_HOST: entry['from'][0],
+ ATTR_PORT: entry['data']['Port'],
+ ATTR_URLBASE: 'https://%s:%s' % (entry['from'][0],
+ entry['data']['Port'])
+ }
+
+ def get_entries(self):
+ """Return all PMS entries."""
+ return self.find_by_data({'Content-Type': 'plex/media-server'})
diff --git a/netdisco/discoverables/roku.py b/netdisco/discoverables/roku.py
new file mode 100644
index 0000000..2cf7c4e
--- /dev/null
+++ b/netdisco/discoverables/roku.py
@@ -0,0 +1,10 @@
+"""Discover Roku players."""
+from . import SSDPDiscoverable
+
+
+class Discoverable(SSDPDiscoverable):
+ """Add support for discovering Roku media players."""
+
+ def get_entries(self):
+ """Get all the Roku entries."""
+ return self.find_by_st("roku:ecp")
diff --git a/netdisco/discoverables/sabnzbd.py b/netdisco/discoverables/sabnzbd.py
new file mode 100644
index 0000000..b2cb9e7
--- /dev/null
+++ b/netdisco/discoverables/sabnzbd.py
@@ -0,0 +1,13 @@
+"""Discover SABnzbd servers."""
+from . import MDNSDiscoverable
+
+
+class Discoverable(MDNSDiscoverable):
+ """Add support for discovering SABnzbd."""
+
+ def __init__(self, nd):
+ """Initialize the SABnzbd discovery."""
+ super(Discoverable, self).__init__(nd, '_http._tcp.local.')
+
+ def get_entries(self):
+ return self.find_by_device_name('SABnzbd on')
diff --git a/netdisco/discoverables/samsung_printer.py b/netdisco/discoverables/samsung_printer.py
new file mode 100644
index 0000000..04eb2c1
--- /dev/null
+++ b/netdisco/discoverables/samsung_printer.py
@@ -0,0 +1,13 @@
+"""Discover Samsung Printers"""
+from . import SSDPDiscoverable
+
+
+class Discoverable(SSDPDiscoverable):
+ """Support for the discovery of Samsung Printers"""
+
+ def get_entries(self):
+ """Get all the Samsung Printer uPnP entries."""
+ return self.find_by_device_description({
+ "manufacturer": "Samsung Electronics",
+ "deviceType": "urn:schemas-upnp-org:device:Printer:1"
+ })
diff --git a/netdisco/discoverables/samsung_tv.py b/netdisco/discoverables/samsung_tv.py
new file mode 100644
index 0000000..4ad31bb
--- /dev/null
+++ b/netdisco/discoverables/samsung_tv.py
@@ -0,0 +1,25 @@
+"""Discover Samsung Smart TV services."""
+from . import SSDPDiscoverable
+from ..const import ATTR_NAME
+
+# For some models, Samsung forces a [TV] prefix to the user-specified name.
+FORCED_NAME_PREFIX = '[TV]'
+
+
+class Discoverable(SSDPDiscoverable):
+ """Add support for discovering Samsung Smart TV services."""
+
+ def get_entries(self):
+ """Get all the Samsung RemoteControlReceiver entries."""
+ return self.find_by_st(
+ "urn:samsung.com:device:RemoteControlReceiver:1")
+
+ def info_from_entry(self, entry):
+ """Get most important info, by default the description location."""
+ info = super().info_from_entry(entry)
+
+ # Strip the forced prefix, if present
+ if info[ATTR_NAME].startswith(FORCED_NAME_PREFIX):
+ info[ATTR_NAME] = info[ATTR_NAME][len(FORCED_NAME_PREFIX):].strip()
+
+ return info
diff --git a/netdisco/discoverables/songpal.py b/netdisco/discoverables/songpal.py
new file mode 100644
index 0000000..94d09d6
--- /dev/null
+++ b/netdisco/discoverables/songpal.py
@@ -0,0 +1,50 @@
+"""Discover Songpal devices."""
+import logging
+from . import SSDPDiscoverable
+from . import ATTR_PROPERTIES
+
+
+class Discoverable(SSDPDiscoverable):
+ """Support for Songpal devices.
+ Supported devices: http://vssupport.sony.net/en_ww/device.html."""
+
+ def get_entries(self):
+ """Get all the Songpal devices."""
+ devs = self.find_by_st(
+ "urn:schemas-sony-com:service:ScalarWebAPI:1")
+
+ # At least some Bravia televisions use this API for communication.
+ # Based on some examples they always seem to lack modelNumber,
+ # so we use it here to keep them undiscovered for now.
+ non_bravias = []
+ for dev in devs:
+ if 'device' in dev.description:
+ device = dev.description['device']
+ if 'modelNumber' in device:
+ non_bravias.append(dev)
+
+ return non_bravias
+
+ def info_from_entry(self, entry):
+ """Get information for a device.."""
+ info = super().info_from_entry(entry)
+
+ cached_descs = entry.DESCRIPTION_CACHE[entry.location]
+
+ device_info_element = "X_ScalarWebAPI_DeviceInfo"
+ baseurl_element = "X_ScalarWebAPI_BaseURL"
+ device_element = "device"
+ if device_element in cached_descs and \
+ device_info_element in cached_descs[device_element]:
+ scalarweb = cached_descs[device_element][device_info_element]
+
+ properties = {"scalarwebapi": scalarweb}
+ if baseurl_element in scalarweb:
+ properties["endpoint"] = scalarweb[baseurl_element]
+ else:
+ logging.warning("Unable to find %s", baseurl_element)
+ info[ATTR_PROPERTIES] = properties
+ else:
+ logging.warning("Unable to find ScalarWeb element from desc.")
+
+ return info
diff --git a/netdisco/discoverables/sonos.py b/netdisco/discoverables/sonos.py
new file mode 100644
index 0000000..29c96c0
--- /dev/null
+++ b/netdisco/discoverables/sonos.py
@@ -0,0 +1,10 @@
+"""Discover Sonos devices."""
+from . import SSDPDiscoverable
+
+
+class Discoverable(SSDPDiscoverable):
+ """Add support for discovering Sonos devices."""
+
+ def get_entries(self):
+ """Get all the Sonos device uPnP entries."""
+ return self.find_by_st("urn:schemas-upnp-org:device:ZonePlayer:1")
diff --git a/netdisco/discoverables/spotify_connect.py b/netdisco/discoverables/spotify_connect.py
new file mode 100644
index 0000000..6bdb062
--- /dev/null
+++ b/netdisco/discoverables/spotify_connect.py
@@ -0,0 +1,10 @@
+"""Discover devices that implement the Spotify Connect platform."""
+from . import MDNSDiscoverable
+
+
+class Discoverable(MDNSDiscoverable):
+ """Add support for discovering Spotify Connect service."""
+
+ def __init__(self, nd):
+ """Initialize the Cast discovery."""
+ super(Discoverable, self).__init__(nd, '_spotify-connect._tcp.local.')
diff --git a/netdisco/discoverables/tellstick.py b/netdisco/discoverables/tellstick.py
new file mode 100644
index 0000000..727aa76
--- /dev/null
+++ b/netdisco/discoverables/tellstick.py
@@ -0,0 +1,14 @@
+"""Discover Tellstick devices."""
+from . import BaseDiscoverable
+
+
+class Discoverable(BaseDiscoverable):
+ """Add support for discovering a Tellstick device."""
+
+ def __init__(self, netdis):
+ """Initialize the Tellstick discovery."""
+ self._netdis = netdis
+
+ def get_entries(self):
+ """Get all the Tellstick details."""
+ return self._netdis.tellstick.entries
diff --git a/netdisco/discoverables/tivo_dvr.py b/netdisco/discoverables/tivo_dvr.py
new file mode 100644
index 0000000..454cfe9
--- /dev/null
+++ b/netdisco/discoverables/tivo_dvr.py
@@ -0,0 +1,14 @@
+"""Discover TiVo DVR devices providing the TCP Remote Protocol."""
+from . import MDNSDiscoverable
+
+
+class Discoverable(MDNSDiscoverable):
+ """Add support for discovering TiVo Remote Protocol service."""
+
+ def __init__(self, nd):
+ """Initialize the discovery.
+
+ Yields a dictionary with hostname, host and port along with a
+ properties sub-dictionary with some device specific ids.
+ """
+ super(Discoverable, self).__init__(nd, '_tivo-remote._tcp.local.')
diff --git a/netdisco/discoverables/volumio.py b/netdisco/discoverables/volumio.py
new file mode 100644
index 0000000..a08b902
--- /dev/null
+++ b/netdisco/discoverables/volumio.py
@@ -0,0 +1,10 @@
+"""Discover Volumio servers."""
+from . import MDNSDiscoverable
+
+
+class Discoverable(MDNSDiscoverable):
+ """Add support for discovering Volumio."""
+
+ def __init__(self, nd):
+ """Initialize the Volumio discovery."""
+ super(Discoverable, self).__init__(nd, '_Volumio._tcp.local.')
diff --git a/netdisco/discoverables/webos_tv.py b/netdisco/discoverables/webos_tv.py
new file mode 100644
index 0000000..4328c60
--- /dev/null
+++ b/netdisco/discoverables/webos_tv.py
@@ -0,0 +1,15 @@
+"""Discover LG WebOS TV devices."""
+from . import SSDPDiscoverable
+
+
+class Discoverable(SSDPDiscoverable):
+ """Add support for discovering LG WebOS TV devices."""
+
+ def get_entries(self):
+ """Get all the LG WebOS TV device uPnP entries."""
+ return self.find_by_device_description(
+ {
+ "deviceType": "urn:schemas-upnp-org:device:Basic:1",
+ "modelName": "LG Smart TV"
+ }
+ )
diff --git a/netdisco/discoverables/wink.py b/netdisco/discoverables/wink.py
new file mode 100644
index 0000000..9e6833f
--- /dev/null
+++ b/netdisco/discoverables/wink.py
@@ -0,0 +1,14 @@
+"""Discover Wink hub devices."""
+from . import SSDPDiscoverable
+
+
+class Discoverable(SSDPDiscoverable):
+ """Add support for discovering Wink hub devices."""
+
+ def get_entries(self):
+ """Return all Wink entries."""
+ results = []
+ results.extend(self.find_by_st('urn:wink-com:device:hub2:2'))
+ results.extend(self.find_by_st('urn:wink-com:device:hub:2'))
+ results.extend(self.find_by_st('urn:wink-com:device:relay:2'))
+ return results
diff --git a/netdisco/discoverables/xbox_smartglass.py b/netdisco/discoverables/xbox_smartglass.py
new file mode 100644
index 0000000..191ee7b
--- /dev/null
+++ b/netdisco/discoverables/xbox_smartglass.py
@@ -0,0 +1,14 @@
+"""Discover Xbox SmartGlass devices."""
+from . import BaseDiscoverable
+
+
+class Discoverable(BaseDiscoverable):
+ """Add support for discovering a Xbox SmartGlass device."""
+
+ def __init__(self, netdis):
+ """Initialize the Xbox SmartGlass discovery."""
+ self._netdis = netdis
+
+ def get_entries(self):
+ """Get all the Xbox SmartGlass details."""
+ return self._netdis.xbox_smartglass.entries
diff --git a/netdisco/discoverables/xiaomi_gw.py b/netdisco/discoverables/xiaomi_gw.py
new file mode 100644
index 0000000..5c9ab0d
--- /dev/null
+++ b/netdisco/discoverables/xiaomi_gw.py
@@ -0,0 +1,33 @@
+"""Discover Xiaomi Mi Home (aka Lumi) Gateways."""
+from . import MDNSDiscoverable
+from ..const import ATTR_MAC_ADDRESS, ATTR_PROPERTIES
+
+
+class Discoverable(MDNSDiscoverable):
+ """Add support for discovering Xiaomi Gateway"""
+
+ def __init__(self, nd):
+ """Initialize the discovery."""
+ super(Discoverable, self).__init__(nd, '_miio._udp.local.')
+
+ def info_from_entry(self, entry):
+ """Return most important info from mDNS entries."""
+ info = super().info_from_entry(entry)
+
+ # Workaround of misparsing of mDNS properties. It's unclear
+ # whether it's bug in zeroconf module or in the Gateway, but
+ # returned properties look like:
+ # {b'poch': b'0:mac=286c07aaaaaa\x00'} instead of expected:
+ # {b'epoch': b'0', b'mac': '286c07aaaaaa'}
+ if "poch" in info[ATTR_PROPERTIES]:
+ misparsed = info[ATTR_PROPERTIES]["poch"]
+ misparsed = misparsed.rstrip("\0")
+ for val in misparsed.split(":"):
+ if val.startswith("mac="):
+ info[ATTR_MAC_ADDRESS] = val[len("mac="):]
+
+ return info
+
+ def get_entries(self):
+ """Return Xiaomi Gateway devices."""
+ return self.find_by_device_name('lumi-gateway-')
diff --git a/netdisco/discoverables/yamaha.py b/netdisco/discoverables/yamaha.py
new file mode 100644
index 0000000..7361818
--- /dev/null
+++ b/netdisco/discoverables/yamaha.py
@@ -0,0 +1,42 @@
+"""Discover Yamaha Receivers."""
+from . import SSDPDiscoverable
+
+
+class Discoverable(SSDPDiscoverable):
+ """Add support for discovering Yamaha Receivers."""
+
+ INCOMPATIBLE_MODELS = set('N301')
+
+ REMOTE_CONTROL_SPEC_TYPE =\
+ 'urn:schemas-yamaha-com:service:X_YamahaRemoteControl:1'
+
+ def info_from_entry(self, entry):
+ """Return the most important info from a uPnP entry."""
+ info = super().info_from_entry(entry)
+
+ yam = entry.description['X_device']
+ services = yam['X_serviceList']['X_service']
+ if isinstance(services, list):
+ service = next(
+ (s for s in services
+ if s['X_specType'] == self.REMOTE_CONTROL_SPEC_TYPE),
+ services[0])
+ else:
+ service = services
+ # do a slice of the second element so we don't have double /
+ info['control_url'] = yam['X_URLBase'] + service['X_controlURL'][1:]
+ info['description_url'] = (yam['X_URLBase'] +
+ service['X_unitDescURL'][1:])
+
+ return info
+
+ def get_entries(self):
+ """Get all the Yamaha uPnP entries."""
+ devices = self.find_by_device_description({
+ "manufacturer": "Yamaha Corporation",
+ "deviceType": "urn:schemas-upnp-org:device:MediaRenderer:1"
+ })
+
+ return [device for device in devices if
+ device.description['device'].get('modelNumber', '') not in
+ self.INCOMPATIBLE_MODELS]
diff --git a/netdisco/discoverables/yeelight.py b/netdisco/discoverables/yeelight.py
new file mode 100644
index 0000000..9a42c1b
--- /dev/null
+++ b/netdisco/discoverables/yeelight.py
@@ -0,0 +1,27 @@
+"""Discover Yeelight bulbs, based on Kodi discoverable."""
+from . import MDNSDiscoverable
+from ..const import ATTR_DEVICE_TYPE
+
+DEVICE_NAME_PREFIX = 'yeelink-light-'
+
+
+class Discoverable(MDNSDiscoverable):
+ """Add support for discovering Yeelight."""
+
+ def __init__(self, nd):
+ """Initialize the Yeelight discovery."""
+ super(Discoverable, self).__init__(nd, '_miio._udp.local.')
+
+ def info_from_entry(self, entry):
+ """Return most important info from mDNS entries."""
+ info = super().info_from_entry(entry)
+
+ # Example name: yeelink-light-ceiling4_mibt72799069._miio._udp.local.
+ info[ATTR_DEVICE_TYPE] = \
+ entry.name.replace(DEVICE_NAME_PREFIX, '').split('_', 1)[0]
+
+ return info
+
+ def get_entries(self):
+ """ Return yeelight devices. """
+ return self.find_by_device_name(DEVICE_NAME_PREFIX)
diff --git a/netdisco/discoverables/ziggo_mediabox_xl.py b/netdisco/discoverables/ziggo_mediabox_xl.py
new file mode 100644
index 0000000..57fcd50
--- /dev/null
+++ b/netdisco/discoverables/ziggo_mediabox_xl.py
@@ -0,0 +1,12 @@
+"""Discover Ziggo Mediabox XL devices."""
+from . import SSDPDiscoverable
+
+
+class Discoverable(SSDPDiscoverable):
+ """Add support for discovering Ziggo Mediabox XL devices."""
+
+ def get_entries(self):
+ """Return all Ziggo (UPC) Mediabox XL entries."""
+ return self.find_by_device_description(
+ {'modelDescription': 'UPC Hzn Gateway',
+ 'deviceType': 'urn:schemas-upnp-org:device:RemoteUIServer:2'})