Integrated netconnectd plugin with netconnectd service
This commit is contained in:
parent
80486d644d
commit
75017d7ac6
7 changed files with 244 additions and 60 deletions
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -3,27 +3,27 @@
|
|||
<strong>{{ _('Connection state') }}:</strong> <span data-bind="text: connectionStateText"></span>
|
||||
</p>
|
||||
|
||||
<div class="pull-right">
|
||||
<div class="pull-right" data-bind="visible: enableQualitySorting">
|
||||
<small>
|
||||
{{ _('Sort by') }}: <a href="#" data-bind="click: function() { listHelper.changeSorting('name'); }">{{ _('Name') }} ({{ _('ascending') }})</a> | <a href="#" data-bind="click: function() { listHelper.changeSorting('quality'); }">{{ _('Quality') }} ({{ _('descending') }})</a></a>
|
||||
{{ _('Sort by') }}: <a href="#" data-bind="click: function() { listHelper.changeSorting('name'); }">{{ _('Name') }} ({{ _('ascending') }})</a> | <a href="#" data-bind="click: function() { listHelper.changeSorting('quality'); }">{{ _('Quality') }} ({{ _('descending') }})</a>
|
||||
</small>
|
||||
</div>
|
||||
<table class="table table-striped table-hover table-condensed table-hover" id="log_files">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="settings_plugin_netconnectd_wifis_name">{{ _('Name') }}</th>
|
||||
<th class="settings_plugin_netconnectd_wifis_quality">{{ _('Quality') }}</th>
|
||||
<th class="settings_plugin_netconnectd_wifis_ssid">{{ _('SSID') }}</th>
|
||||
<th class="settings_plugin_netconnectd_wifis_quality" data-bind="visible: enableQualitySorting">{{ _('Quality') }}</th>
|
||||
<th class="settings_plugin_netconnectd_wifis_action">{{ _('Action') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody data-bind="foreach: listHelper.paginatedItems">
|
||||
<tr data-bind="attr: {title: name}">
|
||||
<td class="settings_plugin_netconnectd_wifis_name"><span class="icon-lock" data-bind="invisible: !encrypted"></span> <span data-bind="text: name"></span></td>
|
||||
<td class="settings_plugin_netconnectd_wifis_quality" data-bind="text: quality"></td>
|
||||
<td class="settings_plugin_netconnectd_wifis_action">
|
||||
<button class="button button-small" data-bind="click: function() { if ($root.loginState.isUser()) { $parent.configureWifi($data); } else { return; } }, css: {disabled: !$root.loginState.isUser()}">{{ _('Connect') }}</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr data-bind="attr: {title: name}">
|
||||
<td class="settings_plugin_netconnectd_wifis_ssid"><span class="icon-lock" data-bind="invisible: !encrypted"></span> <span data-bind="text: ssid"></span></td>
|
||||
<td class="settings_plugin_netconnectd_wifis_quality" data-bind="visible: $root.enableQualitySorting, text: quality"></td>
|
||||
<td class="settings_plugin_netconnectd_wifis_action">
|
||||
<button class="btn btn-small" data-bind="click: function() { $parent.configureWifi($data); }, css: {disabled: !$root.loginState.isUser() || $root.working() || $root.error()}"><i class="icon-spinner icon-spin" data-bind="visible: $root.working"></i> {{ _('Connect') }}</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="pagination pagination-mini pagination-centered">
|
||||
|
|
@ -44,6 +44,8 @@
|
|||
</ul>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-primary btn-block" data-bind="click: function() { $root.refresh() }">{{ _('Refresh') }}</button>
|
||||
|
||||
<div>
|
||||
<small class="muted">{{ _('netconnectd socket') }}: <span data-bind="text: settings.plugins.netconnectd.socket"></span></small>
|
||||
</div>
|
||||
|
|
@ -71,8 +73,8 @@
|
|||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn" data-dismiss="modal" aria-hidden="true">{{ _('Abort') }}</button>
|
||||
<button class="btn btn-primary" data-bind="click: function() { $root.confirmConfigureWifi(); }, enable: !$root.editorWifiPassphraseMismatch()">{{ _('Confirm') }}</button>
|
||||
<button class="btn" data-dismiss="modal" data-bind="enable: !$root.working() && !$root.error()" aria-hidden="true">{{ _('Abort') }}</button>
|
||||
<button class="btn btn-primary" data-bind="click: function() { $root.confirmWifiConfiguration(); }, enable: !$root.editorWifiPassphraseMismatch() && !$root.working() && !$root.error()"><i class="icon-spinner icon-spin" data-bind="visible: working"></i> {{ _('Confirm') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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") {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue