Initial commit.

This commit is contained in:
mfm 2018-01-23 15:01:58 +01:00
commit 8449b1c68e
15 changed files with 534 additions and 0 deletions

17
.editorconfig Normal file
View File

@ -0,0 +1,17 @@
# This file is for unifying the coding style for different editors and IDEs
# editorconfig.org
root = true
[*]
end_of_line = lf
charset = utf-8
insert_final_newline = true
trim_trailing_whitespace = true
[**.py]
indent_style = tab
[**.js]
indent_style = space
indent_size = 4

9
.gitignore vendored Normal file
View File

@ -0,0 +1,9 @@
*.pyc
*.swp
.idea
*.iml
build
dist
*.egg*
.DS_Store
*.zip

4
MANIFEST.in Normal file
View File

@ -0,0 +1,4 @@
include README.md
recursive-include octoprint_klipper/templates *
recursive-include octoprint_klipper/translations *
recursive-include octoprint_klipper/static *

19
README.md Normal file
View File

@ -0,0 +1,19 @@
# Klipper
This plugin assists in managing and monitoring the [Klipper](https://github.com/KevinOConnor/klipper) 3D printer firmware.
Besides that it provides some usefull tools.
**STILL UNDER DEVELOPMENT**
## Setup
Install via the bundled [Plugin Manager](https://github.com/foosel/OctoPrint/wiki/Plugin:-Plugin-Manager)
or manually using this URL:
https://github.com/mmone/OctoPrintKlipper/archive/master.zip
## Configuration
Click on the wrench icon in the titlebar and select "Klipper" to get to the available settings for this plugin.

6
babel.cfg Normal file
View File

@ -0,0 +1,6 @@
[python: */**.py]
[jinja2: */**.jinja2]
extensions=jinja2.ext.autoescape, jinja2.ext.with_
[javascript: */**.js]
extract_messages = gettext, ngettext

View File

@ -0,0 +1,74 @@
# coding=utf-8
from __future__ import absolute_import
import logging
import octoprint.plugin
import octoprint.plugin.core
class KlipperPlugin(
octoprint.plugin.StartupPlugin,
octoprint.plugin.TemplatePlugin,
octoprint.plugin.SettingsPlugin,
octoprint.plugin.AssetPlugin):
def on_after_startup(self):
#self._settings.set(["appearance"]["components"]["order"]["sidebar"]["test"]);
self._logger.info("startup hook ---------- {value} ----------".format(value=self._settings.get(["replace_connection_panel"])) )
def get_settings_defaults(self):
return dict(
serialport="/tmp/printer",
replace_connection_panel=True,
macros=[],
probePoints=[])
def get_template_configs(self):
return [
dict(type="navbar", custom_bindings=False),
dict(type="settings", custom_bindings=True),
dict(type="sidebar",
custom_bindings=True,
replaces= "connection" if self._settings.get(["replace_connection_panel"]) else "")
]
def get_assets(self):
return dict(
js=["js/klipper.js"],
css=["css/klipper.css"],
less=["css/klipper.less"]
)
def on_parse_gcode(self, comm, line, *args, **kwargs):
if "ok" not in line:
return line
self._plugin_manager.send_plugin_message(self._identifier, dict(message=line))
#from octoprint.util.comm import parse_firmware_line
# Create a dict with all the keys/values returned by the M115 request
#printer_data = parse_firmware_line(line)
self._logger.info("Machine type detected {line}.".format(line=line))
#self._logger.info("Machine type detected: {machine}.".format(machine=printer_data["MACHINE_TYPE"]))
return line
def on_printer_action(self, comm, line, action, *args, **kwargs):
#if not action == "custom":
# return
self._logger.info("action recieved:".action)
__plugin_name__ = "Klipper"
def __plugin_load__():
global __plugin_implementation__
global __plugin_hooks__
__plugin_implementation__ = KlipperPlugin()
__plugin_hooks__ = {
"octoprint.comm.protocol.gcode.received": __plugin_implementation__.on_parse_gcode,
"octoprint.comm.protocol.action": __plugin_implementation__.on_printer_action
}
#__plugin_name__ = "Klipper"
#__plugin_implementation__ = KlipperPlugin()

View File

@ -0,0 +1,10 @@
#tab_plugin_klipper iframe {
width: 100%;
height: 600px;
border: 1px solid #808080;
}
.plugin_klipper_msg {
overflow-y: scroll;
height: 100%;
}

View File

@ -0,0 +1,147 @@
$(function() {
function KlipperViewModel(parameters) {
var self = this;
// injection settingsViewModel
self.settings = parameters[0];
// injection loginStateViewModel
self.loginState = parameters[1];
// injection connectionViewModel
self.connectionState = parameters[2];
self.shortStatus = ko.observable();
self.logMessages = ko.observableArray();
/*
self.onConnectToHost = function() {
console.log("Connecting");
self.shortStatus("Connecting to Host");
OctoPrint.connection.connect({"port" : "VIRTUAL"});
self.connectButtonText("Disconnect");
console.log(self.loginState);
}*/
self.onGetStatus = function() {
self.shortStatus("Update Status")
}
self.onRestartFirmware = function() {
//OctoPrint.control.sendGcode("FIRMWARE_RESTART")
self.shortStatus("Restarting Firmware");
console.log("Restart firmware");
};
self.onRestartHost = function() {
self.shortStatus("Restarting Host");
console.log("Restart Host");
self.logMessage("Restarted Host");
//OctoPrint.control.sendGcode("RESTART")
};
self.onBeforeBinding = function() {
self.connectionState.selectedPort("VIRTUAL");
}
self.onAfterBinding = function() {
self.connectionState.selectedPort("VIRTUAL");
console.log(self.connectionState.selectedPort());
self.shortStatus("Idle");
}
self.onDataUpdaterPluginMessage = function(plugin, message) {
//console.log(message);
self.logMessage("plugin: " +plugin+ " message recieved: " + message["message"]);
}
self.logMessage = function(message) {
self.logMessages.push({time: Date.now(), msg: message});
}
self.onClearLog = function() {
self.logMessages.removeAll();
};
self.isActive = function() {
return self.connectionState.isOperational() && self.loginState.isUser();
}
// ------------ Settings ---------------- //
self.moveItemDown = function(list, item) {
var i = list().indexOf(item);
if (i < list().length - 1) {
var rawList = list();
list.splice(i, 2, rawList[i + 1], rawList[i]);
}
}
self.moveItemUp = function(list, item) {
var i = list().indexOf(item);
if (i > 0) {
var rawList = list();
list.splice(i-1, 2, rawList[i], rawList[i-1]);
}
}
self.executeMacro = function(macro) {
console.log(macro.name());
OctoPrint.control.sendGcode(macro.macro());
}
self.addMacro = function() {
self.settings.settings.plugins.klipper.macros.push({name: 'Macro', macro: ''});
}
self.removeMacro = function(macro) {
self.settings.settings.plugins.klipper.macros.remove(macro);
}
self.moveMacroUp = function(macro) {
self.moveItemUp(self.settings.settings.plugins.klipper.macros, macro)
}
self.moveMacroDown = function(macro) {
self.moveItemDown(self.settings.settings.plugins.klipper.macros, macro)
}
self.addProbePoint = function() {
self.settings.settings.plugins.klipper.probePoints.push({x: 0, y:0, z:0});
}
self.removeProbePoint = function(point) {
self.settings.settings.plugins.klipper.probePoints.remove(point);
}
self.moveProbePointUp = function(macro) {
self.moveItemUp(self.settings.settings.plugins.klipper.probePoints, macro)
}
self.moveProbePointDown = function(macro) {
self.moveItemDown(self.settings.settings.plugins.klipper.probePoints, macro)
}
}
/*
var KlipperMacroCollection = function(name, gcode) {
this.name = name;
this.macros = ko.observableArray(macros);
this.addMacro = function() {
this.push({name: 'macro 2', macro: 'G1 X1 Y2 Z3'});
}.bind(this);
}*/
// This is how our plugin registers itself with the application, by adding some configuration
// information to the global variable OCTOPRINT_VIEWMODELS
OCTOPRINT_VIEWMODELS.push({
// This is the constructor to call for instantiating the plugin
construct: KlipperViewModel,
// dependencies to inject into the plugin.
dependencies: ["settingsViewModel", "loginStateViewModel", "connectionViewModel"],
// elements this view model will be bound to.
elements: ["#tab_plugin_klipper", "#sidebar_plugin_klipper", "#settings_plugin_klipper"]
});
});

View File

@ -0,0 +1,5 @@
#tab_plugin_klipper iframe {
width: 100%;
height: 600px;
border: 1px solid #808080;
}

View File

@ -0,0 +1 @@
<a href="#" data-bind="attr: {href: settings.settings.plugins.klipper.url}">Status: Connected</a>

View File

@ -0,0 +1,90 @@
<form class="form-horizontal">
<div class="control-group">
<h4>{{ _('Settings') }}<h4>
</div>
<div class="control-group">
<label class="control-label">{{ _('Serial Port') }}</label>
<div class="controls">
<input type="text" class="input-block-level" data-bind="value: settings.settings.plugins.klipper.serialport">
</div>
</div>
<div class="control-group">
<label class="control-label">{{ _('Replace Connection Panel') }}</label>
<div class="controls">
<input type="checkbox" class="input-block-level" data-bind="checked: settings.settings.plugins.klipper.replace_connection_panel">
</div>
</div>
<div class="control-group">
<h4>{{ _('Macros') }}<h4>
</div>
<div data-bind="foreach: settings.settings.plugins.klipper.macros">
<div class="control-group">
<label class="control-label">{{ _('Name') }}</label>
<div class="controls">
<div class="row-fluid">
<div class="span9">
<input type="text" class="input-block-level" data-bind="value: name">
</div>
<div class="span2">
<a href='#' data-bind='click: $parent.moveMacroUp' class="fa fa-chevron-up"></a>
<a href='#' data-bind='click: $parent.moveMacroDown' class="fa fa-chevron-down"></a>
<a href='#' data-bind='click: $parent.removeMacro' class="fa fa-trash-o"></a>
</div>
</div>
</div>
<label class="control-label">{{ _('GCODE') }}</label>
<div class="controls">
<div class="row-fluid">
<div class="span9">
<textarea rows="4" class="block" data-bind="value: macro">
</textarea>
</div>
<div class="span2"></div>
</div>
</div>
</div>
</div>
<div class="control-group">
<div class="controls">
<a href='#' data-bind='click: addMacro' class="fa fa-plus-circle"></a>
</div>
</div>
<div class="control-group">
<h4>{{ _('Probe Points') }}</h4>
</div>
<div class="control-group">
<div class="controls">
<div class="row-fluid">
<div class="span3">X</div>
<div class="span3">Y</div>
<div class="span3">Z</div>
<div class="span3"> </div>
</div>
</div>
</div>
<div data-bind="foreach: settings.settings.plugins.klipper.probePoints" class="control-group">
<label class="control-label" data-bind="text: $index"></label>
<div class="controls">
<div class="row-fluid">
<div class="span3"><input type="text" class="input-block-level" data-bind="value: x"></div>
<div class="span3"><input type="text" class="input-block-level" data-bind="value: y"></div>
<div class="span3"><input type="text" class="input-block-level" data-bind="value: z"></div>
<div class="span3">
<a href='#' data-bind='click: $parent.moveProbePointUp' class="fa fa-sort-asc"></a>
<a href='#' data-bind='click: $parent.moveProbePointDown' class="fa fa-sort-desc"></a>
<a href='#' data-bind='click: $parent.removeMacro' class="fa fa-trash-o"></a>
</div>
</div>
</div>
</div>
<div class="control-group">
<div class="controls">
<a href='#' data-bind='click: addProbePoint' class="fa fa-plus-circle"></a>
</div>
</div>
</form>

View File

@ -0,0 +1,21 @@
<label><span class="label label-success" data-bind="text: shortStatus">Connected</span></label>
<label for="connection_printers" data-bind="css: {disabled: !connectionState.isErrorOrClosed()}, enable: connectionState.isErrorOrClosed() && loginState.isUser()">{{ _('Printer Profile') }}</label>
<select id="connection_printers" data-bind="options: connectionState.printerOptions, optionsText: 'name', optionsValue: 'id', value: connectionState.selectedPrinter, css: {disabled: !connectionState.isErrorOrClosed()}, enable: connectionState.isErrorOrClosed() && loginState.isUser()"></select>
<!--
<label class="checkbox">
<input type="checkbox" id="connection_save" data-bind="checked: connectionState.saveSettings, css: {disabled: !connectionState.isErrorOrClosed()}, enable: connectionState.isErrorOrClosed() && loginState.isUser()"> {{ _('Save connection settings') }}
</label>
<label class="checkbox">
<input type="checkbox" id="connection_autoconnect" data-bind="checked: settings.serial_autoconnect, css: {disabled: !connectionState.isErrorOrClosed()}, enable: connectionState.isErrorOrClosed() && loginState.isUser()"> {{ _('Auto-connect on server startup') }}
</label>
-->
<button class="btn btn-block" data-bind="click: connectionState.connect, text: connectionState.buttonText(), enable: loginState.isUser()">{{ _('Connect') }}</button>
<hr>
<div>
<label>Macros</label>
<div data-bind="foreach: settings.settings.plugins.klipper.macros">
<button class="btn btn-block" data-bind="text: name, click: $parent.executeMacro, enable: $parent.isActive()"></button>
</div>
</div>

View File

@ -0,0 +1,26 @@
<div class="row-fluid">
<div class="span8">
<label>Log <span class="label label-success" data-bind="text: shortStatus">Connected</span> </label>
<hr>
<div class="plugin_klipper_msg" data-bind="foreach: logMessages">
<div><i data-bind="text: formatDate(time)"></i> <span data-bind="text: msg"></span></div>
</div>
</div>
<div class="span4">
<div>
<label>Process Management</label>
<button class="btn btn-block" data-bind="click: onGetStatus, enable: isActive()">Status</button>
<label>Restart</label>
<button class="btn btn-block" data-bind="click: onRestartHost, enable: isActive()"><i class="fa fa-repeat"></i> Host</button>
<button class="btn btn-block" data-bind="click: onRestartFirmware, enable: isActive()"><i class="fa fa-repeat"></i> MCU</button>
<button class="btn btn-block" data-bind="click: onClearLog"><i class="fa fa-trash"></i> Clear Log</button>
</div>
<hr>
<div>
<label>Macros</label>
<div data-bind="foreach: settings.settings.plugins.klipper.macros">
<button class="btn btn-block" data-bind="text: name, click: $parent.executeMacro, enable: $parent.isActive()"></button>
</div>
</div>
</div>
</div>

9
requirements.txt Normal file
View File

@ -0,0 +1,9 @@
###
# This file is only here to make sure that something like
#
# pip install -e .
#
# works as expected. Requirements can be found in setup.py.
###
.

96
setup.py Normal file
View File

@ -0,0 +1,96 @@
# coding=utf-8
########################################################################################################################
### Do not forget to adjust the following variables to your own plugin.
# The plugin's identifier, has to be unique
plugin_identifier = "klipper"
# The plugin's python package, should be "octoprint_<plugin identifier>", has to be unique
plugin_package = "octoprint_klipper"
# The plugin's human readable name. Can be overwritten within OctoPrint's internal data via __plugin_name__ in the
# plugin module
plugin_name = "Klipper"
# The plugin's version. Can be overwritten within OctoPrint's internal data via __plugin_version__ in the plugin module
plugin_version = "0.1.0"
# The plugin's description. Can be overwritten within OctoPrint's internal data via __plugin_description__ in the plugin
# module
plugin_description = """A plugin for octoprint to control and monitor the Klipper 3D printer software."""
# The plugin's author. Can be overwritten within OctoPrint's internal data via __plugin_author__ in the plugin module
plugin_author = "Martin Mühlhäuser"
# The plugin's author's mail address.
plugin_author_email = "github@mmone.de"
# The plugin's homepage URL. Can be overwritten within OctoPrint's internal data via __plugin_url__ in the plugin module
plugin_url = "https://github.com/mmone/OctoPrintKlipper"
# The plugin's license. Can be overwritten within OctoPrint's internal data via __plugin_license__ in the plugin module
plugin_license = "AGPLv3"
# Any additional requirements besides OctoPrint should be listed here
plugin_requires = []
### --------------------------------------------------------------------------------------------------------------------
### More advanced options that you usually shouldn't have to touch follow after this point
### --------------------------------------------------------------------------------------------------------------------
# Additional package data to install for this plugin. The subfolders "templates", "static" and "translations" will
# already be installed automatically if they exist. Note that if you add something here you'll also need to update
# MANIFEST.in to match to ensure that python setup.py sdist produces a source distribution that contains all your
# files. This is sadly due to how python's setup.py works, see also http://stackoverflow.com/a/14159430/2028598
plugin_additional_data = []
# Any additional python packages you need to install with your plugin that are not contained in <plugin_package>.*
plugin_additional_packages = []
# Any python packages within <plugin_package>.* you do NOT want to install with your plugin
plugin_ignored_packages = []
# Additional parameters for the call to setuptools.setup. If your plugin wants to register additional entry points,
# define dependency links or other things like that, this is the place to go. Will be merged recursively with the
# default setup parameters as provided by octoprint_setuptools.create_plugin_setup_parameters using
# octoprint.util.dict_merge.
#
# Example:
# plugin_requires = ["someDependency==dev"]
# additional_setup_parameters = {"dependency_links": ["https://github.com/someUser/someRepo/archive/master.zip#egg=someDependency-dev"]}
additional_setup_parameters = {}
########################################################################################################################
from setuptools import setup
try:
import octoprint_setuptools
except:
print("Could not import OctoPrint's setuptools, are you sure you are running that under "
"the same python installation that OctoPrint is installed under?")
import sys
sys.exit(-1)
setup_parameters = octoprint_setuptools.create_plugin_setup_parameters(
identifier=plugin_identifier,
package=plugin_package,
name=plugin_name,
version=plugin_version,
description=plugin_description,
author=plugin_author,
mail=plugin_author_email,
url=plugin_url,
license=plugin_license,
requires=plugin_requires,
additional_packages=plugin_additional_packages,
ignored_packages=plugin_ignored_packages,
additional_data=plugin_additional_data
)
if len(additional_setup_parameters):
from octoprint.util import dict_merge
setup_parameters = dict_merge(setup_parameters, additional_setup_parameters)
setup(**setup_parameters)