Try to reconnect to backend when switching from ap to wifi

netconnectd now regularly pings hostname it got from backend (defaults to <systemname>.local) while switching to wifi from ap mode and reloads page to that if host comes up.

This requires the browser the client is currently running in to be able to resolve local domains as broadcasted via avahi. Windows systems will need to install the Bonjour for Windows client for this to work.
This commit is contained in:
Gina Häußge 2014-09-11 15:40:04 +02:00
parent 65263a8b74
commit d1c34a9c88
8 changed files with 171 additions and 29 deletions

View file

@ -91,10 +91,10 @@ def discovery():
response.headers['Content-Type'] = 'application/xml'
return response
class DiscoveryPlugin(octoprint.plugin.types.StartupPlugin,
octoprint.plugin.types.ShutdownPlugin,
octoprint.plugin.types.BlueprintPlugin,
octoprint.plugin.types.SettingsPlugin):
class DiscoveryPlugin(octoprint.plugin.StartupPlugin,
octoprint.plugin.ShutdownPlugin,
octoprint.plugin.BlueprintPlugin,
octoprint.plugin.SettingsPlugin):
ssdp_multicast_addr = "239.255.255.250"
@ -109,6 +109,7 @@ class DiscoveryPlugin(octoprint.plugin.types.StartupPlugin,
# zeroconf
self._sd_refs = dict()
self._cnames = dict()
# upnp/ssdp
self._ssdp_monitor_active = False
@ -215,6 +216,9 @@ class DiscoveryPlugin(octoprint.plugin.types.StartupPlugin,
self.logger.info("Registered {name} for {reg_type}".format(**locals()))
def zeroconf_unregister(self, reg_type, port):
if not pybonjour:
return
key = (reg_type, port)
if not key in self._sd_refs:
return

View file

@ -14,7 +14,8 @@ import octoprint.plugin
default_settings = {
"socket": "/var/run/netconnectd.sock"
"socket": "/var/run/netconnectd.sock",
"hostname": None
}
s = octoprint.plugin.plugin_settings("netconnectd", defaults=default_settings)
@ -28,20 +29,31 @@ class NetconnectdSettingsPlugin(octoprint.plugin.SettingsPlugin,
self.logger = logging.getLogger("plugins.netconnectd." + __name__)
self.address = s.get(["socket"])
@property
def hostname(self):
if s.get(["hostname"]):
return s.get(["hostname"])
else:
import socket
return socket.gethostname() + ".local"
##~~ SettingsPlugin
def on_settings_load(self):
return {
"socket": s.get(["socket"])
"socket": s.get(["socket"]),
"hostname": s.get(["hostname"])
}
def on_settings_save(self, data):
if "socket" in data and data["socket"]:
s.set(["socket"], data["socket"])
if "hostname" in data and data["hostname"]:
s.set(["hostname"], data["hostname"])
self.address = s.get(["socket"])
##~~ TemplatePlugin API (part of SettingsPlugin)
##~~ TemplatePlugin API
def get_template_vars(self):
return dict(
@ -70,7 +82,8 @@ class NetconnectdSettingsPlugin(octoprint.plugin.SettingsPlugin,
return jsonify({
"wifis": wifis,
"status": status
"status": status,
"hostname": self.hostname
})
def on_api_command(self, command, data):
@ -153,6 +166,7 @@ class NetconnectdSettingsPlugin(octoprint.plugin.SettingsPlugin,
import socket
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
sock.settimeout(10)
try:
sock.connect(self.address)
sock.sendall(js + '\x00')

View file

@ -8,8 +8,12 @@ $(function() {
self.pollingEnabled = false;
self.pollingTimeoutId = undefined;
self.reconnectInProgress = false;
self.reconnectTimeout = undefined;
self.enableQualitySorting = ko.observable(false);
self.hostname = ko.observable();
self.status = {
link: ko.observable(),
connections: {
@ -32,17 +36,16 @@ $(function() {
return self.editorWifiPassphrase1() != self.editorWifiPassphrase2();
});
self.working = ko.observable(false);
self.error = ko.observable(false);
self.connectionStateText = ko.computed(function() {
if (self.error()) {
return gettext("Error while talking to netconnectd, is the service running?");
} else if (self.status.connections.ap()) {
return gettext("Acting as access point");
} else if (self.status.link()) {
if (self.status.connections.ap()) {
return gettext("Acting as access point");
} else if (self.status.connections.wired()) {
if (self.status.connections.wired()) {
return gettext("Connected via wire");
} else if (self.status.connections.wifi()) {
if (self.status.wifi.current_ssid()) {
@ -84,7 +87,7 @@ $(function() {
);
self.getEntryId = function(data) {
return "settings_plugin_netconnectd_connectbutton_" + md5(data.ssid);
return "settings_plugin_netconnectd_wifi_" + md5(data.ssid);
};
self.refresh = function() {
@ -99,6 +102,8 @@ $(function() {
self.error(false);
}
self.hostname(response.hostname);
self.status.link(response.status.link);
self.status.connections.ap(response.status.connections.ap);
self.status.connections.wifi(response.status.connections.wifi);
@ -106,7 +111,6 @@ $(function() {
self.status.wifi.current_ssid(response.status.wifi.current_ssid);
self.status.wifi.current_address(response.status.wifi.current_address);
self.statusCurrentWifi(undefined);
if (response.status.wifi.current_ssid && response.status.wifi.current_address) {
_.each(response.wifis, function(wifi) {
@ -195,15 +199,52 @@ $(function() {
self.sendWifiConfig = function(ssid, psk, successCallback, failureCallback) {
self.working(true);
if (self.status.connections.ap()) {
self.reconnectInProgress = true;
var reconnectText = gettext("OctoPrint is now switching to your configured Wifi connection and therefore shutting down the Access Point. I'm continuously trying to reach it at <strong>%(hostname)s</strong> but it might take a while. If you are not reconnected over the next couple of minutes, please try to reconnect to OctoPrint manually because then I was unable to find it myself.");
showOfflineOverlay(
gettext("Reconnecting..."),
_.sprintf(reconnectText, {hostname: self.hostname()}),
self.tryReconnect
);
}
self._postCommand("configure_wifi", {ssid: ssid, psk: psk}, successCallback, failureCallback, function() {
self.working(false);
});
if (self.reconnectInProgress) {
self.tryReconnect();
}
}, 5000);
};
self._postCommand = function (command, data, successCallback, failureCallback, alwaysCallback) {
self.tryReconnect = function() {
var hostname = self.hostname();
var location = window.location.href
location = location.replace(location.match("https?\\://([^:@]+(:[^@]+)?@)?([^:/]+)")[3], hostname);
var pingCallback = function(result) {
if (!result) {
return;
}
if (self.reconnectTimeout != undefined) {
clearTimeout(self.reconnectTimeout);
window.location.replace(location);
}
hideOfflineOverlay();
self.reconnectInProgress = false;
};
ping(location, pingCallback);
self.reconnectTimeout = setTimeout(self.tryReconnect, 1000);
};
self._postCommand = function (command, data, successCallback, failureCallback, alwaysCallback, timeout) {
var payload = _.extend(data, {command: command});
$.ajax({
var params = {
url: API_BASEURL + "plugin/netconnectd",
type: "POST",
dataType: "json",
@ -218,7 +259,13 @@ $(function() {
complete: function() {
if (alwaysCallback) alwaysCallback();
}
});
};
if (timeout != undefined) {
params.timeout = timeout;
}
$.ajax(params);
};
self.requestData = function () {
@ -254,6 +301,10 @@ $(function() {
self.pollingTimeoutId = undefined;
}
self.pollingEnabled = false;
};
self.onServerDisconnect = function() {
return !self.reconnectInProgress;
}
}

View file

@ -114,7 +114,7 @@ def afterApiRequests(resp):
def pluginData(name):
api_plugins = octoprint.plugin.plugin_manager().get_implementations(octoprint.plugin.SimpleApiPlugin)
if not name in api_plugins:
make_response(404)
return make_response("Not found", 404)
api_plugin = api_plugins[name]
response = api_plugin.on_api_get(request)
@ -130,12 +130,12 @@ def pluginData(name):
def pluginCommand(name):
api_plugins = octoprint.plugin.plugin_manager().get_implementations(octoprint.plugin.SimpleApiPlugin)
if not name in api_plugins:
return make_response(404)
return make_response("Not found", 404)
api_plugin = api_plugins[name]
valid_commands = api_plugin.get_api_commands()
if valid_commands is None:
return make_response(405)
return make_response("Method not allowed", 405)
command, data, response = util.getJsonCommandFromRequest(request, valid_commands)
if response is not None:

View file

@ -31,9 +31,28 @@ function DataUpdater(allViewModels) {
};
self._onclose = function() {
$("#offline_overlay_message").html(gettext("The server appears to be offline, at least I'm not getting any response from it. I'll try to reconnect automatically <strong>over the next couple of minutes</strong>, however you are welcome to try a manual reconnect anytime using the button below."));
if (!$("#offline_overlay").is(":visible"))
$("#offline_overlay").show();
var handled = false;
_.each(self.allViewModels, function(viewModel) {
if (handled == true) {
return;
}
if (viewModel.hasattr("onServerDisconnect")) {
if (!viewModel.onServerDisconnect()) {
handled = true;
}
}
});
if (handled) {
return;
}
showOfflineOverlay(
gettext("Server is offline"),
gettext("The server appears to be offline, at least I'm not getting any response from it. I'll try to reconnect automatically <strong>over the next couple of minutes</strong>, however you are welcome to try a manual reconnect anytime using the button below."),
self.reconnect
);
if (self._autoReconnectTrial < self._autoReconnectTimeouts.length) {
var timeout = self._autoReconnectTimeouts[self._autoReconnectTrial];
@ -46,6 +65,24 @@ function DataUpdater(allViewModels) {
};
self._onreconnectfailed = function() {
var handled = false;
_.each(self.allViewModels, function(viewModel) {
if (handled == true) {
return;
}
if (viewModel.hasattr("onServerDisconnect")) {
if (!viewModel.onServerDisconnect()) {
handled = true;
}
}
});
if (handled) {
return;
}
$("#offline_overlay_title").text(gettext("Server is offline"));
$("#offline_overlay_message").html(gettext("The server appears to be offline, at least I'm not getting any response from it. I <strong>could not reconnect automatically</strong>, but you may try a manual reconnect using the button below."));
};
@ -70,7 +107,7 @@ function DataUpdater(allViewModels) {
$("span.version").text(DISPLAY_VERSION);
if ($("#offline_overlay").is(":visible")) {
$("#offline_overlay").hide();
hideOfflineOverlay();
_.each(self.allViewModels, function(viewModel) {
if (viewModel.hasOwnProperty("onDataUpdaterReconnect")) {
viewModel.onDataUpdaterReconnect();

View file

@ -359,3 +359,42 @@ function pnotifyAdditionalInfo(inner) {
+ '<div class="pnotify_more_container hide">' + inner + '</div>'
+ '</div>';
}
function ping(url, callback) {
var img = new Image();
var calledBack = false;
img.onload = function() {
callback(true);
calledBack = true;
};
img.onerror = function() {
if (!calledBack) {
callback(true);
calledBack = true;
}
};
img.src = url;
setTimeout(function() {
if (!calledBack) {
callback(false);
calledBack = true;
}
}, 1500);
}
function showOfflineOverlay(title, message, reconnectCallback) {
if (title == undefined) {
title = gettext("Server is offline");
}
$("#offline_overlay_title").text(title);
$("#offline_overlay_message").html(message);
$("#offline_overlay_reconnect").click(reconnectCallback);
if (!$("#offline_overlay").is(":visible"))
$("#offline_overlay").show();
}
function hideOfflineOverlay() {
$("#offline_overlay").hide();
}

View file

@ -331,9 +331,6 @@ $(function() {
}, 100);
});
//~~ Offline overlay
$("#offline_overlay_reconnect").click(function() {dataUpdater.reconnect()});
//~~ Underscore setup
_.mixin(_.str.exports());

View file

@ -3,7 +3,7 @@
<div id="offline_overlay_wrapper">
<div class="container">
<div class="hero-unit">
<h1>{{ _('Server is offline') }}</h1>
<h1 id="offline_overlay_title">{{ _('Server is offline') }}</h1>
<p id="offline_overlay_message"></p>
<p>
<a class="btn btn-primary btn-large" id="offline_overlay_reconnect">{{ _('Attempt to reconnect') }}</a>