diff --git a/src/octoprint/plugins/discovery/__init__.py b/src/octoprint/plugins/discovery/__init__.py
index 67ac0e82..ad05f6bb 100644
--- a/src/octoprint/plugins/discovery/__init__.py
+++ b/src/octoprint/plugins/discovery/__init__.py
@@ -6,7 +6,6 @@ __license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agp
__copyright__ = "Copyright (C) 2014 The OctoPrint Project - Released under terms of the AGPLv3 License"
-import collections
import logging
import os
import flask
@@ -26,7 +25,16 @@ default_settings = {
"httpUsername": None,
"httpPassword": None,
"upnpUuid": None,
- "zeroConf": []
+ "zeroConf": [],
+ "model": {
+ "name": None,
+ "description": None,
+ "number": None,
+ "url": None,
+ "serial": None,
+ "vendor": None,
+ "vendorUrl": None
+ }
}
s = octoprint.plugin.plugin_settings("discovery", defaults=default_settings)
@@ -56,12 +64,28 @@ blueprint = flask.Blueprint("plugin.discovery", __name__, template_folder=os.pat
@blueprint.route("/discovery.xml")
def discovery():
- logging.getLogger("octoprint.plugins." + __name__).info("Rendering discovery.xml")
+ logging.getLogger("octoprint.plugins." + __name__).debug("Rendering discovery.xml")
+
+ modelName = s.get(["model", "name"])
+ if not modelName:
+ import octoprint.server
+ modelName = octoprint.server.DISPLAY_VERSION
+
+ vendor = s.get(["model", "vendor"])
+ vendorUrl = s.get(["model", "vendorUrl"])
+ if not vendor:
+ vendor = "The OctoPrint Project"
+ vendorUrl = "http://www.octoprint.org/"
+
response = flask.make_response(flask.render_template("discovery.jinja2",
friendlyName=get_instance_name(),
- manufacturer="The OctoPrint project",
- manufacturerUrl="http://www.octoprint.org",
- modelName="OctoPrint",
+ manufacturer=vendor,
+ manufacturerUrl=vendorUrl,
+ modelName=modelName,
+ modelDescription=s.get(["model", "description"]),
+ modelNumber=s.get(["model", "number"]),
+ modelUrl=s.get(["model", "url"]),
+ serialNumber=s.get(["model", "serial"]),
uuid=UUID,
presentationUrl=flask.url_for("index", _external=True)))
response.headers['Content-Type'] = 'application/xml'
@@ -71,6 +95,12 @@ class DiscoveryPlugin(octoprint.plugin.types.StartupPlugin,
octoprint.plugin.types.ShutdownPlugin,
octoprint.plugin.types.BlueprintPlugin,
octoprint.plugin.types.SettingsPlugin):
+
+ ssdp_multicast_addr = "239.255.255.250"
+
+ ssdp_multicast_port = 1900
+
+
def __init__(self):
self.logger = logging.getLogger("octoprint.plugins." + __name__)
@@ -78,7 +108,7 @@ class DiscoveryPlugin(octoprint.plugin.types.StartupPlugin,
self.port = None
# zeroconf
- self._sd_refs = {}
+ self._sd_refs = dict()
# upnp/ssdp
self._ssdp_monitor_active = False
@@ -132,6 +162,10 @@ class DiscoveryPlugin(octoprint.plugin.types.StartupPlugin,
#~~ ShutdownPlugin API
def on_shutdown(self):
+ for key in self._sd_refs:
+ reg_type, port = key
+ self.zeroconf_unregister(reg_type, port)
+
self._ssdp_unregister()
#~~ SettingsPlugin API
@@ -161,27 +195,38 @@ class DiscoveryPlugin(octoprint.plugin.types.StartupPlugin,
# ZeroConf
- def zeroconf_register(self, service_type, name, port=None, txt_record=None):
+ def zeroconf_register(self, reg_type, name, port=None, txt_record=None, timeout=5):
if not pybonjour:
return
- def register_callback(sd_ref, flags, error_code, name, reg_type, domain):
- if error_code == pybonjour.kDNSServiceErr_NoError:
- self.logger.info("Registered {name} for {reg_type} with domain {domain}".format(**locals()))
+ if not port:
+ port = self.port
params = dict(
name=name,
- regtype=service_type,
- port=port if port else self.port,
- callBack=register_callback
+ regtype=reg_type,
+ port=port
)
if txt_record:
params["txtRecord"] = pybonjour.TXTRecord(txt_record)
- self._sd_refs[service_type] = pybonjour.DNSServiceRegister(**params)
- pybonjour.DNSServiceProcessResult(self._sd_refs[service_type])
+ key = (reg_type, port)
+ self._sd_refs[key] = pybonjour.DNSServiceRegister(**params)
+ self.logger.info("Registered {name} for {reg_type}".format(**locals()))
- def zeroconf_browse(self, service_type, block=False, callback=None, timeout=5):
+ def zeroconf_unregister(self, reg_type, port):
+ key = (reg_type, port)
+ if not key in self._sd_refs:
+ return
+
+ sd_ref = self._sd_refs[key]
+ try:
+ sd_ref.close()
+ self.logger.debug("Unregistered {reg_type} on port {port}".format(reg_type=reg_type, port=port))
+ except:
+ self.logger.exception("Could not unregister {reg_type} on port {port}".format(reg_type=reg_type, port=port))
+
+ def zeroconf_browse(self, service_type, block=False, callback=None, timeout=5, resolve_timeout=5):
if not pybonjour:
return None
@@ -223,7 +268,13 @@ class DiscoveryPlugin(octoprint.plugin.types.StartupPlugin,
self.logger.debug("Got a browsing result for Zeroconf resolution of {service_type}, resolving...".format(service_type=service_type))
resolve_ref = pybonjour.DNSServiceResolve(0, interface_index, service_name, regtype, reply_domain, resolve_callback)
try:
- pybonjour.DNSServiceProcessResult(resolve_ref)
+ while True:
+ ready = select.select([resolve_ref], [], [], resolve_timeout)
+ if resolve_ref in ready[0]:
+ pybonjour.DNSServiceProcessResult(resolve_ref)
+ else:
+ self.logger.warn("Timeout while trying to resolve a service")
+ break
finally:
resolve_ref.close()
@@ -263,6 +314,13 @@ class DiscoveryPlugin(octoprint.plugin.types.StartupPlugin,
api=octoprint.server.api.VERSION,
))
+ modelName = s.get(["model", "name"])
+ if modelName:
+ entries.update(dict(model=modelName))
+ vendor = s.get(["model", "vendor"])
+ if vendor:
+ entries.update(dict(vendor=vendor))
+
return entries
def _create_base_txt_record_dict(self):
@@ -290,6 +348,8 @@ class DiscoveryPlugin(octoprint.plugin.types.StartupPlugin,
# SSDP/UPNP
+ ## The SSDP/UPNP implementations has been largely inspired by https://gist.github.com/schlamar/2428250
+
def ssdp_browse(self, query, block=False, callback=None, timeout=1, retries=5):
import threading
@@ -318,7 +378,7 @@ class DiscoveryPlugin(octoprint.plugin.types.StartupPlugin,
"ST: {query}\r\n",
"MX: 3\r\n",
"MAN: \"ssdp:discovery\"\r\n",
- "HOST: 239.255.255.250:1900\r\n\r\n"
+ "HOST: {mcast_addr}:{mcast_port}\r\n\r\n"
])
for _ in xrange(retries):
@@ -329,9 +389,11 @@ class DiscoveryPlugin(octoprint.plugin.types.StartupPlugin,
sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 2)
sock.bind((addr, 0))
- message = search_message.format(query=query)
+ message = search_message.format(query=query,
+ mcast_addr=self.__class__.ssdp_multicast_addr,
+ mcast_port=self.__class__.ssdp_multicast_port)
for _ in xrange(2):
- sock.sendto(message, ("239.255.255.250", 1900))
+ sock.sendto(message, (self.__class__.ssdp_multicast_addr, self.__class__.ssdp_multicast_port))
try:
data = sock.recv(1024)
@@ -342,7 +404,6 @@ class DiscoveryPlugin(octoprint.plugin.types.StartupPlugin,
result.append(response.getheader("Location"))
except Exception as e:
- self.logger.exception("oops with {addr}".format(addr=addr))
pass
if callback:
@@ -401,11 +462,15 @@ class DiscoveryPlugin(octoprint.plugin.types.StartupPlugin,
"NTS: {nts}\r\n",
"NT: upnp:rootdevice\r\n",
"USN: uuid:{uuid}::upnp:rootdevice\r\n",
- "Host: 239.255.255.250:1900\r\n\r\n"
+ "HOST: {mcast_addr}:{mcast_port}\r\n\r\n"
])
- message = notify_message.format(uuid=UUID, location=location, nts="ssdp:alive" if alive else "ssdp:byebye")
+ message = notify_message.format(uuid=UUID,
+ location=location,
+ nts="ssdp:alive" if alive else "ssdp:byebye",
+ mcast_addr=self.__class__.ssdp_multicast_addr,
+ mcast_port=self.__class__.ssdp_multicast_port)
for _ in xrange(2):
- sock.sendto(message, ("239.255.255.250", 1900))
+ sock.sendto(message, (self.__class__.ssdp_multicast_addr, self.__class__.ssdp_multicast_port))
except Exception as e:
pass
@@ -442,9 +507,9 @@ class DiscoveryPlugin(octoprint.plugin.types.StartupPlugin,
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 2)
- sock.bind(('', 1900))
+ sock.bind(('', self.__class__.ssdp_multicast_port))
- sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, socket.inet_aton('239.255.255.250') + socket.inet_aton('0.0.0.0'))
+ sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, socket.inet_aton(self.__class__.ssdp_multicast_addr) + socket.inet_aton('0.0.0.0'))
self.logger.info("Registered {} for SSDP".format(get_instance_name()))
@@ -494,6 +559,7 @@ def __plugin_check__():
)
if pybonjour:
__plugin_helpers__["zeroconf_browse"] = discovery_plugin.zeroconf_browse
+ __plugin_helpers__["zeroconf_register"] = discovery_plugin.zeroconf_register
return True
diff --git a/src/octoprint/plugins/discovery/templates/discovery.jinja2 b/src/octoprint/plugins/discovery/templates/discovery.jinja2
index c0f06fde..413c84f7 100644
--- a/src/octoprint/plugins/discovery/templates/discovery.jinja2
+++ b/src/octoprint/plugins/discovery/templates/discovery.jinja2
@@ -8,19 +8,14 @@
urn:schemas-upnp-org:device:Basic:1
{{ friendlyName }}
{{ manufacturer }}
- {{ manufacturerUrl }}
- {{ modelDescription }}
+ {% if manufacturerUrl %}{{ manufacturerUrl }}{% endif %}
{{ modelName }}
+ {% if modelDescription %}{{ modelDescription }}{% endif %}
+ {% if modelNumber %}{{ modelNumber }}{% endif %}
+ {% if modelUrl %}{{ modelUrl }}{% endif %}
+ {% if serialNumber %}{{ serialNumber }}{% endif %}
uuid:{{ uuid }}
-
- {{ presentationUrl }}
- urn:schemas-dummy-com:service:Dummy:1
- urn:dummy-com:serviceId:dummy1
- /dummy
- /dummy
- /dummy.xml
-
{{ presentationUrl }}
diff --git a/src/octoprint/server/api/settings.py b/src/octoprint/server/api/settings.py
index e01df927..05f6dcec 100644
--- a/src/octoprint/server/api/settings.py
+++ b/src/octoprint/server/api/settings.py
@@ -220,8 +220,10 @@ def setSettings():
enabled = cura.get("enabled")
s.setBoolean(["cura", "enabled"], enabled)
- for name, plugin in octoprint.plugin.plugin_manager().get_implementations(octoprint.plugin.SettingsPlugin).items():
- plugin.on_settings_save(data["plugins"][name])
+ if "plugins" in data:
+ for name, plugin in octoprint.plugin.plugin_manager().get_implementations(octoprint.plugin.SettingsPlugin).items():
+ if name in data["plugins"]:
+ plugin.on_settings_save(data["plugins"][name])
s.save()