diff options
Diffstat (limited to 'netdisco/discoverables')
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'}) |