Made what is registered for discovery more configurable

- Additional zeroconf services may now be added via config
- Exposed zeroconf_register method as helper for plugins
- Vendor specifics may be added to service registrations via config
This commit is contained in:
Gina Häußge 2014-09-11 12:19:10 +02:00
parent 5cd6ab9335
commit 65263a8b74
3 changed files with 102 additions and 39 deletions

View file

@ -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

View file

@ -8,19 +8,14 @@
<deviceType>urn:schemas-upnp-org:device:Basic:1</deviceType>
<friendlyName>{{ friendlyName }}</friendlyName>
<manufacturer>{{ manufacturer }}</manufacturer>
<manufacturerURL>{{ manufacturerUrl }}</manufacturerURL>
<modelDescription>{{ modelDescription }}</modelDescription>
{% if manufacturerUrl %}<manufacturerURL>{{ manufacturerUrl }}</manufacturerURL>{% endif %}
<modelName>{{ modelName }}</modelName>
{% if modelDescription %}<modelDescription>{{ modelDescription }}</modelDescription>{% endif %}
{% if modelNumber %}<modelNumber>{{ modelNumber }}</modelNumber>{% endif %}
{% if modelUrl %}<modelURL>{{ modelUrl }}</modelURL>{% endif %}
{% if serialNumber %}<serialNumber>{{ serialNumber }}</serialNumber>{% endif %}
<UDN>uuid:{{ uuid }}</UDN>
<serviceList>
<service>
<URLBase>{{ presentationUrl }}</URLBase>
<serviceType>urn:schemas-dummy-com:service:Dummy:1</serviceType>
<serviceId>urn:dummy-com:serviceId:dummy1</serviceId>
<controlURL>/dummy</controlURL>
<eventSubURL>/dummy</eventSubURL>
<SCPDURL>/dummy.xml</SCPDURL>
</service>
</serviceList>
<presentationURL>{{ presentationUrl }}</presentationURL>
</device>

View file

@ -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()