diff --git a/src/octoprint/plugins/netconnectd/__init__.py b/src/octoprint/plugins/netconnectd/__init__.py index b3b4f641..a7c473c2 100644 --- a/src/octoprint/plugins/netconnectd/__init__.py +++ b/src/octoprint/plugins/netconnectd/__init__.py @@ -21,10 +21,14 @@ default_settings = { s = octoprint.plugin.plugin_settings("netconnectd", defaults=default_settings) -class NetconnectdSettingsPlugin(octoprint.plugin.SettingsPlugin, octoprint.plugin.SimpleApiPlugin, octoprint.plugin.AssetPlugin): +class NetconnectdSettingsPlugin(octoprint.plugin.SettingsPlugin, + octoprint.plugin.TemplatePlugin, + octoprint.plugin.SimpleApiPlugin, + octoprint.plugin.AssetPlugin): def __init__(self): - self.logger = logging.getLogger(__name__) + self.logger = logging.getLogger("plugins.netconnectd." + __name__) + self.address = s.get(["socket"]) ##~~ SettingsPlugin @@ -37,6 +41,8 @@ class NetconnectdSettingsPlugin(octoprint.plugin.SettingsPlugin, octoprint.plugi if "socket" in data and data["socket"]: s.set(["socket"], data["socket"]) + self.address = s.get(["socket"]) + ##~~ TemplatePlugin API (part of SettingsPlugin) def get_template_vars(self): @@ -79,6 +85,8 @@ class NetconnectdSettingsPlugin(octoprint.plugin.SettingsPlugin, octoprint.plugi else: self.logger.info("Configuring wifi {ssid}...".format(**data)) + self._configure_and_select_wifi(data["ssid"], data["psk"], force=data["force"] if "force" in data else False) + elif command == "start_ap": self.logger.info("Starting ap...") @@ -100,20 +108,92 @@ class NetconnectdSettingsPlugin(octoprint.plugin.SettingsPlugin, octoprint.plugi ##~~ Private helpers def _get_wifi_list(self, force=False): + payload = dict() if force: self.logger.info("Forcing wifi refresh...") - return [ - {"name": "A Test Wifi", "quality": 59, "encrypted": True}, - {"name": "TyrionDiesOnPage24", "quality": 90, "encrypted": True}, - {"name": "Giraffenhaus", "quality": 78, "encrypted": False}, - ] + payload["force"] = True + + flag, content = self._send_message("list_wifi", payload) + if not flag: + raise RuntimeError("Error while listing wifi: " + content) + + result = [] + for wifi in content: + result.append(dict(ssid=wifi["ssid"], address=wifi["address"], quality=wifi["signal"], encrypted=wifi["encrypted"])) + return result def _get_status(self): - return { - "ap": False, - "connectedToWifi": True - } + payload = dict() + flag, content = self._send_message("status", payload) + if not flag: + raise RuntimeError("Error while querying status: " + content) + + return dict( + ap=content["ap"], + link=content["link"], + wifi_available=content["wifi_available"] + ) + + def _configure_and_select_wifi(self, ssid, psk, force=False): + payload = dict( + ssid=ssid, + psk=psk, + force=force + ) + + flag, content = self._send_message("config_wifi", payload) + if not flag: + raise RuntimeError("Error while configuring wifi: " + content) + + flag, content = self._send_message("start_wifi", dict()) + if not flag: + raise RuntimeError("Error while selecting wifi: " + content) + + def _send_message(self, message, data): + obj = dict() + obj[message] = data + + import json + js = json.dumps(obj, encoding="utf8", separators=(",", ":")) + + import socket + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + try: + sock.connect(self.address) + sock.sendall(js + '\x00') + + buffer = [] + while True: + chunk = sock.recv(16) + if chunk: + buffer.append(chunk) + if chunk.endswith('\x00'): + break + + data = ''.join(buffer).strip()[:-1] + + response = json.loads(data.strip()) + if "result" in response: + return True, response["result"] + + elif "error" in response: + # something went wrong + self.logger.warn("Request to netconnectd went wrong: " + response["error"]) + return False, response["error"] + + else: + output = "Unknown response from netconnectd: {response!r}".format(response=response) + self.logger.warn(output) + return False, output + + except Exception as e: + output = "Error while talking to netconnectd: {}".format(e.message) + self.logger.warn(output) + return False, output + + finally: + sock.close() __plugin_name__ = "netconnectd client" __plugin_version__ = "0.1" diff --git a/src/octoprint/plugins/netconnectd/static/js/netconnectd.js b/src/octoprint/plugins/netconnectd/static/js/netconnectd.js index 93bcdab7..3ec652bf 100644 --- a/src/octoprint/plugins/netconnectd/static/js/netconnectd.js +++ b/src/octoprint/plugins/netconnectd/static/js/netconnectd.js @@ -5,42 +5,49 @@ $(function() { self.loginState = parameters[0]; self.settingsViewModel = parameters[1]; - self.settings = undefined; + self.pollingEnabled = false; + self.pollingTimeoutId = undefined; - self.data = { - wifis: ko.observableArray([]), - status: { - ap: ko.observable(), - connectedToWifi: ko.observable() - } - }; + self.enableQualitySorting = ko.observable(false); + self.statusAp = ko.observable(); + self.statusLink = ko.observable(); + self.statusWifiAvailable = ko.observable(); + + self.editorWifi = undefined; self.editorWifiSsid = ko.observable(); self.editorWifiPassphrase1 = ko.observable(); self.editorWifiPassphrase2 = ko.observable(); + self.working = ko.observable(false); + self.editorWifiPassphraseMismatch = ko.computed(function() { return self.editorWifiPassphrase1() != self.editorWifiPassphrase2(); }); + self.error = ko.observable(false); self.connectionStateText = ko.computed(function() { - if (self.data.status.ap()) { - return gettext("Access Point is active"); - } else if (self.data.status.connectedToWifi()) { - return gettext("Connected to configured Wifi"); - } else { - return gettext("Connected") + if (self.error()) { + return gettext("Error while talking to netconnectd, is the service running?") + } else if (self.statusAp() && !self.statusWifiAvailable()) { + return gettext("Access Point is active, wifi configured but not available"); + } else if (self.statusAp() && self.statusWifiAvailable()) { + return gettext("Access Point is active, wifi configured"); + } else if (self.statusLink()) { + return gettext("Connected"); } + + return gettext("Unknown connection state"); }); // initialize list helper self.listHelper = new ItemListHelper( "wifis", { - "name": function (a, b) { + "ssid": function (a, b) { // sorts ascending - if (a["name"].toLocaleLowerCase() < b["name"].toLocaleLowerCase()) return -1; - if (a["name"].toLocaleLowerCase() > b["name"].toLocaleLowerCase()) return 1; + if (a["ssid"].toLocaleLowerCase() < b["ssid"].toLocaleLowerCase()) return -1; + if (a["ssid"].toLocaleLowerCase() > b["ssid"].toLocaleLowerCase()) return 1; return 0; }, "quality": function (a, b) { @@ -58,19 +65,52 @@ $(function() { 10 ); + self.getEntryId = function(ssid) { + return "settings_plugin_netconnectd_connectbutton_" + md5(data.ssid); + }; + + self.refresh = function() { + self.requestData(); + }; + self.fromResponse = function (response) { if (response.error !== undefined) { + self.error(true); return; + } else { + self.error(false); } - ko.mapping.fromJS(response, self.data); + + self.statusAp(response.status.ap); + self.statusLink(response.status.link); + self.statusWifiAvailable(response.status.wifi_available); + + var enableQualitySorting = false; + _.each(response.wifis, function(wifi) { + if (wifi.quality != undefined) { + enableQualitySorting = true; + } + }); + self.enableQualitySorting(enableQualitySorting); + self.listHelper.updateItems(response.wifis); + if (!enableQualitySorting) { + self.listHelper.changeSorting("ssid"); + } + + if (self.pollingEnabled) { + self.pollingTimeoutId = setTimeout(function() { + self.requestData(); + }, 30000) + } }; self.configureWifi = function(data) { + self.editorWifi = data; + self.editorWifiSsid(data.ssid); + self.editorWifiPassphrase1(undefined); + self.editorWifiPassphrase2(undefined); if (data.encrypted) { - self.editorWifiSsid(data.ssid); - self.editorWifiPassphrase1(undefined); - self.editorWifiPassphrase2(undefined); $("#settings_plugin_netconnectd_wificonfig").modal("show"); } else { self.confirmWifiConfiguration(); @@ -79,11 +119,12 @@ $(function() { self.confirmWifiConfiguration = function() { self.sendWifiConfig(self.editorWifiSsid(), self.editorWifiPassphrase1(), function() { + self.editorWifi = undefined; self.editorWifiSsid(undefined); self.editorWifiPassphrase1(undefined); self.editorWifiPassphrase2(undefined); $("#settings_plugin_netconnectd_wificonfig").modal("hide"); - }) + }); }; self.sendStartAp = function() { @@ -101,28 +142,40 @@ $(function() { }); }; - self.sendWifiConfig = function(ssid, psk, callback) { - self._postCommand("configure_wifi", {ssid: ssid, psk: psk}); + self.sendWifiConfig = function(ssid, psk, successCallback, failureCallback) { + self.working(true); + self._postCommand("configure_wifi", {ssid: ssid, psk: psk}, successCallback, failureCallback, function() { + self.working(false); + }); }; - self._postCommand = function (command, data, successCallback, failureCallback) { + self._postCommand = function (command, data, successCallback, failureCallback, alwaysCallback) { var payload = _.extend(data, {command: command}); $.ajax({ url: API_BASEURL + "plugin/netconnectd", type: "POST", dataType: "json", - data: payload, + data: JSON.stringify(payload), + contentType: "application/json; charset=UTF-8", success: function(response) { if (successCallback) successCallback(response); }, - fail: function() { + error: function() { if (failureCallback) failureCallback(); + }, + complete: function() { + if (alwaysCallback) alwaysCallback(); } }); }; self.requestData = function () { + if (self.pollingTimeoutId != undefined) { + clearTimeout(self.pollingTimeoutId); + self.pollingTimeoutId = undefined; + } + $.ajax({ url: API_BASEURL + "plugin/netconnectd", type: "GET", @@ -133,11 +186,25 @@ $(function() { self.onBeforeBinding = function() { self.settings = self.settingsViewModel.settings; - }; - - self.onStartup = function() { self.requestData(); }; + + self.onDataUpdaterReconnect = function() { + self.requestData(); + }; + + self.onSettingsShown = function() { + self.pollingEnabled = true; + self.requestData(); + }; + + self.onSettingsHidden = function() { + if (self.pollingTimeoutId != undefined) { + self.pollingTimeoutId = undefined; + } + self.pollingEnabled = false; + } + } // view model class, parameters for constructor, container to bind to diff --git a/src/octoprint/plugins/netconnectd/templates/netconnectd_settings_dialog.jinja2 b/src/octoprint/plugins/netconnectd/templates/netconnectd_settings_dialog.jinja2 index 3c61f393..3d30315b 100644 --- a/src/octoprint/plugins/netconnectd/templates/netconnectd_settings_dialog.jinja2 +++ b/src/octoprint/plugins/netconnectd/templates/netconnectd_settings_dialog.jinja2 @@ -3,27 +3,27 @@ {{ _('Connection state') }}:

-
+
- {{ _('Sort by') }}: {{ _('Name') }} ({{ _('ascending') }}) | {{ _('Quality') }} ({{ _('descending') }}) + {{ _('Sort by') }}: {{ _('Name') }} ({{ _('ascending') }}) | {{ _('Quality') }} ({{ _('descending') }})
- - + + - - - - - + + + + +
{{ _('Name') }}{{ _('Quality') }}{{ _('SSID') }}{{ _('Quality') }} {{ _('Action') }}
- -
+ +
+ +
{{ _('netconnectd socket') }}:
@@ -71,8 +73,8 @@
diff --git a/src/octoprint/server/api/settings.py b/src/octoprint/server/api/settings.py index 93c9b567..e01df927 100644 --- a/src/octoprint/server/api/settings.py +++ b/src/octoprint/server/api/settings.py @@ -220,9 +220,8 @@ def setSettings(): enabled = cura.get("enabled") s.setBoolean(["cura", "enabled"], enabled) - octoprint.plugin.call_plugin(octoprint.plugin.SettingsPlugin, - "on_settings_save", - args=(data["plugins"][name])) + for name, plugin in octoprint.plugin.plugin_manager().get_implementations(octoprint.plugin.SettingsPlugin).items(): + plugin.on_settings_save(data["plugins"][name]) s.save() diff --git a/src/octoprint/static/js/app/dataupdater.js b/src/octoprint/static/js/app/dataupdater.js index cd637261..177cef78 100644 --- a/src/octoprint/static/js/app/dataupdater.js +++ b/src/octoprint/static/js/app/dataupdater.js @@ -108,8 +108,18 @@ function DataUpdater(allViewModels) { var gcodeUploadProgress = $("#gcode_upload_progress"); var gcodeUploadProgressBar = $(".bar", gcodeUploadProgress); - if ((type == "UpdatedFiles" && payload.type == "gcode") || type == "MetadataAnalysisFinished") { - gcodeFilesViewModel.requestData(); + if (type == "UpdatedFiles") { + _.each(self.allViewModels, function(viewModel) { + if (viewModel.hasOwnProperty("onUpdatedFiles")) { + viewModel.onUpdatedFiles(payload); + } + }); + } else if (type == "MetadataAnalysisFinished") { + _.each(self.allViewModels, function(viewModel) { + if (viewModel.hasOwnProperty("onMetadataAnalysisFinished")) { + viewModel.onMetadataAnalysisFinished(payload); + } + }); } else if (type == "MovieRendering") { new PNotify({title: gettext("Rendering timelapse"), text: _.sprintf(gettext("Now rendering timelapse %(movie_basename)s"), payload)}); } else if (type == "MovieDone") { diff --git a/src/octoprint/static/js/app/main.js b/src/octoprint/static/js/app/main.js index 5e0d6172..d81cd623 100644 --- a/src/octoprint/static/js/app/main.js +++ b/src/octoprint/static/js/app/main.js @@ -40,12 +40,28 @@ $(function() { }); //~~ Show settings - to ensure centered + var settingsDialog = $('#settings_dialog'); + settingsDialog.on('show', function() { + _.each(allViewModels, function(viewModel) { + if (viewModel.hasOwnProperty("onSettingsShown")) { + viewModel.onSettingsShown(); + } + }); + }); + settingsDialog.on('hidden', function() { + _.each(allViewModels, function(viewModel) { + if (viewModel.hasOwnProperty("onSettingsHidden")) { + viewModel.onSettingsHidden(); + } + }); + }); $('#navbar_show_settings').click(function() { - $('#settings_dialog').modal() + settingsDialog.modal() .css({ width: 'auto', 'margin-left': function() { return -($(this).width() /2); } }); + return false; }); diff --git a/src/octoprint/static/js/app/viewmodels/files.js b/src/octoprint/static/js/app/viewmodels/files.js index 217e96f0..ed28e9c7 100644 --- a/src/octoprint/static/js/app/viewmodels/files.js +++ b/src/octoprint/static/js/app/viewmodels/files.js @@ -311,5 +311,15 @@ function GcodeFilesViewModel(printerStateViewModel, loginStateViewModel) { self.onStartup = function() { self.requestData(); }; + + self.onUpdatedFiles = function(payload) { + if (payload.type == "gcode") { + self.requestData(); + } + }; + + self.onMetadataAnalysisFinished = function(payload) { + self.requestData(); + } }