commit
8f710befbe
10
README.md
10
README.md
@ -8,11 +8,15 @@ Add a slider to control the speed of a parts cooling fan.
|
|||||||
|
|
||||||
Slide the slider, click the button. There really isn't much else to do :)
|
Slide the slider, click the button. There really isn't much else to do :)
|
||||||
|
|
||||||
The default value of the slider is user configurable, this is the value that the slider will be set to upon loading OctoPrint's UI, and any time you refresh the page.
|
## Settings
|
||||||
|
|
||||||
The minimum fan speed setting will limit how slow the fan runs, this is useful since some fans don't work below a certain speed.
|
* The default value of the slider is user configurable, this is the value that the slider will be set to upon loading OctoPrint's UI, and any time you refresh the page.
|
||||||
|
|
||||||
The maximum fan speed setting will limit how fast the fan runs, this is useful if your fan is too strong, or you wish to limit the speed post-slice without having to re-slice your file.
|
* The minimum fan speed setting will limit how slow the fan runs, this is useful since some fans don't work below a certain speed.
|
||||||
|
|
||||||
|
* The maximum fan speed setting will limit how fast the fan runs, this is useful if your fan is too strong, or you wish to limit the speed post-slice without having to re-slice your file.
|
||||||
|
|
||||||
|
* Notification autohide delay controls how long any notifications will remain on the screen for. If the user manually sets a speed outside of the set range, a notification will be displayed informing the user the fan speed has been modified. Print jobs shouldn't trigger these notifications, and so popup spam shouldn't occur, however if a user wishes not to receive notifications when setting fan speeds outside of the set range, this value can be set to 0 (zero) to disable notifications. (this setting won't / shouldn't affect OctoPrint's global notifications, it only applies to info popups generated by this plugin).
|
||||||
|
|
||||||
*Note: Slider does __not__ follow the speed of the fan. If the fan speed is set via gcode or an LCD panel on the printer, the slider will not respond to the change. It is a __setting__, not an indicator, and functions the same way the feedrate and flowrate sliders do.*
|
*Note: Slider does __not__ follow the speed of the fan. If the fan speed is set via gcode or an LCD panel on the printer, the slider will not respond to the change. It is a __setting__, not an indicator, and functions the same way the feedrate and flowrate sliders do.*
|
||||||
|
|
||||||
|
@ -17,12 +17,49 @@ class FanSliderPlugin(octoprint.plugin.StartupPlugin,
|
|||||||
return dict(
|
return dict(
|
||||||
defaultFanSpeed=100,
|
defaultFanSpeed=100,
|
||||||
minSpeed=0,
|
minSpeed=0,
|
||||||
maxSpeed=100
|
maxSpeed=100,
|
||||||
|
notifyDelay=4000
|
||||||
)
|
)
|
||||||
|
|
||||||
def on_settings_save(self, data):
|
def on_settings_save(self, data):
|
||||||
octoprint.plugin.SettingsPlugin.on_settings_save(self, data)
|
s = self._settings
|
||||||
|
if "defaultFanSpeed" in data.keys():
|
||||||
|
s.setInt(["defaultFanSpeed"], data["defaultFanSpeed"])
|
||||||
|
if "minSpeed" in data.keys():
|
||||||
|
s.setInt(["minSpeed"], data["minSpeed"])
|
||||||
|
if "maxSpeed" in data.keys():
|
||||||
|
s.setInt(["maxSpeed"], data["maxSpeed"])
|
||||||
|
if "notifyDelay" in data.keys():
|
||||||
|
s.setInt(["notifyDelay"], data["notifyDelay"])
|
||||||
self.get_settings_updates()
|
self.get_settings_updates()
|
||||||
|
#clean up settings if everything's default
|
||||||
|
self.on_settings_cleanup()
|
||||||
|
s.save()
|
||||||
|
|
||||||
|
#function stolen...err borrowed :D from types.py @ 1663
|
||||||
|
def on_settings_cleanup(self):
|
||||||
|
import octoprint.util
|
||||||
|
from octoprint.settings import NoSuchSettingsPath
|
||||||
|
|
||||||
|
try:
|
||||||
|
config = self._settings.get_all_data(merged=False, incl_defaults=False, error_on_path=True)
|
||||||
|
except NoSuchSettingsPath:
|
||||||
|
return
|
||||||
|
|
||||||
|
if config is None:
|
||||||
|
self._settings.clean_all_data()
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.config_version_key in config and config[self.config_version_key] is None:
|
||||||
|
del config[self.config_version_key]
|
||||||
|
|
||||||
|
defaults = self.get_settings_defaults()
|
||||||
|
diff = octoprint.util.dict_minimal_mergediff(defaults, config)
|
||||||
|
|
||||||
|
if not diff:
|
||||||
|
self._settings.clean_all_data()
|
||||||
|
else:
|
||||||
|
self._settings.set([], diff)
|
||||||
|
|
||||||
def get_assets(self):
|
def get_assets(self):
|
||||||
return dict(
|
return dict(
|
||||||
@ -36,9 +73,9 @@ class FanSliderPlugin(octoprint.plugin.StartupPlugin,
|
|||||||
]
|
]
|
||||||
|
|
||||||
def get_settings_updates(self):
|
def get_settings_updates(self):
|
||||||
self.defaultFanSpeed = self._settings.get(["defaultFanSpeed"])
|
self.defaultFanSpeed = self._settings.getInt(["defaultFanSpeed"])
|
||||||
self.minSpeed = self._settings.get(["minSpeed"])
|
self.minSpeed = self._settings.getInt(["minSpeed"])
|
||||||
self.maxSpeed = self._settings.get(["maxSpeed"])
|
self.maxSpeed = self._settings.getInt(["maxSpeed"])
|
||||||
|
|
||||||
getcontext().prec=5 #sets precision for "Decimal" not sure if this'll cause conflicts, ideas?
|
getcontext().prec=5 #sets precision for "Decimal" not sure if this'll cause conflicts, ideas?
|
||||||
self.minPWM = round( Decimal(self.minSpeed) * Decimal(2.55), 2 )
|
self.minPWM = round( Decimal(self.minSpeed) * Decimal(2.55), 2 )
|
||||||
@ -50,11 +87,11 @@ class FanSliderPlugin(octoprint.plugin.StartupPlugin,
|
|||||||
if fanPwm and fanPwm.group(1):
|
if fanPwm and fanPwm.group(1):
|
||||||
fanPwm = fanPwm.group(1)
|
fanPwm = fanPwm.group(1)
|
||||||
if Decimal(fanPwm) < self.minPWM and Decimal(fanPwm) != 0:
|
if Decimal(fanPwm) < self.minPWM and Decimal(fanPwm) != 0:
|
||||||
self._logger.info("fan pwm value " + str(fanPwm) + " is below threshold, increasing to " + str(self.minPWM))
|
self._logger.info("fan pwm value " + str(fanPwm) + " is below threshold, increasing to " + str(self.minPWM) + " (" + str(self.minSpeed) + "%)")
|
||||||
cmd = "M106 S" + str(self.minPWM)
|
cmd = "M106 S" + str(self.minPWM)
|
||||||
return cmd,
|
return cmd,
|
||||||
elif Decimal(fanPwm) > self.maxPWM:
|
elif Decimal(fanPwm) > self.maxPWM:
|
||||||
self._logger.info("fan pwm value " + str(fanPwm) + " is above threshold, decreasing to " + str(self.maxPWM))
|
self._logger.info("fan pwm value " + str(fanPwm) + " is above threshold, decreasing to " + str(self.maxPWM) + " (" + str(self.maxSpeed) + "%)")
|
||||||
cmd = "M106 S" + str(self.maxPWM)
|
cmd = "M106 S" + str(self.maxPWM)
|
||||||
return cmd,
|
return cmd,
|
||||||
|
|
||||||
|
@ -5,60 +5,140 @@
|
|||||||
$(function () {
|
$(function () {
|
||||||
|
|
||||||
function FanSliderPluginViewModel(parameters) {
|
function FanSliderPluginViewModel(parameters) {
|
||||||
|
//'use strict';
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
self.settings = parameters[0];
|
self.settings = parameters[0];
|
||||||
self.control = parameters[1];
|
self.control = parameters[1];
|
||||||
self.loginState = parameters[2];
|
self.loginState = parameters[2];
|
||||||
|
|
||||||
fanSpeed = ko.observable(undefined);
|
self.settings.defaultFanSpeed = new ko.observable(100); //this,
|
||||||
|
self.control.fanSpeed = new ko.observable(100); //this,
|
||||||
|
self.settings.minFanSpeed = new ko.observable(0); //this,
|
||||||
|
self.settings.maxFanSpeed = new ko.observable(100); //and this are percents 0 - 100%
|
||||||
|
self.settings.notifyDelay = new ko.observable(4000); //time in milliseconds
|
||||||
|
|
||||||
//convert percentage into PWM
|
self.showNotify = function (self, options) {
|
||||||
self.fanPWM = ko.pureComputed(function () {
|
options.hide = true;
|
||||||
self.speed = fanSpeed() * 255 / 100 //don't forget to limit this to 2 decimal places at some point.
|
options.title = "Fan Speed Control";
|
||||||
|
options.delay = self.settings.notifyDelay();
|
||||||
|
options.type = "info";
|
||||||
|
if (options.delay != "0") {
|
||||||
|
new PNotify(options);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
self.control.fanSpeedToPwm = ko.pureComputed(function () {
|
||||||
|
self.speed = self.control.fanSpeed() * 255 / 100 //don't forget to limit this to 2 decimal places at some point.
|
||||||
return self.speed;
|
return self.speed;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
self.control.checkSliderValue = ko.pureComputed(function () {
|
||||||
|
if (self.control.fanSpeed() < self.settings.minFanSpeed() && self.control.fanSpeed() != "0") {
|
||||||
|
console.log("Fan Speed Control Plugin: " + self.control.fanSpeed() + "% is less than the minimum speed (" + self.settings.minFanSpeed() + "%), increasing.");
|
||||||
|
self.control.fanSpeed(self.settings.minFanSpeed());
|
||||||
|
var options = {
|
||||||
|
text: gettext('Fan speed increased to meet minimum speed requirement.'),
|
||||||
|
addclass: 'fan_speed_notice_low',
|
||||||
|
}
|
||||||
|
if ($(".fan_speed_notice_low").length <1) {
|
||||||
|
self.showNotify(self, options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (self.control.fanSpeed() > self.settings.maxFanSpeed()) {
|
||||||
|
console.log("Fan Speed Control Plugin: " + self.control.fanSpeed() + "% is more than the maximum speed (" + self.settings.maxFanSpeed() + "%), decreasing.");
|
||||||
|
self.control.fanSpeed(self.settings.maxFanSpeed());
|
||||||
|
var options = {
|
||||||
|
text: gettext('Fan speed decreased to meet maximum speed requirement.'),
|
||||||
|
addclass: 'fan_speed_notice_high',
|
||||||
|
}
|
||||||
|
if ($(".fan_speed_notice_high").length <1) {
|
||||||
|
self.showNotify(self, options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
//send gcode to set fan speed
|
//send gcode to set fan speed
|
||||||
sendFanSpeed = function () {
|
self.control.sendFanSpeed = function () {
|
||||||
self.control.sendCustomCommand({ command: "M106 S" + self.fanPWM() });
|
self.control.checkSliderValue();
|
||||||
|
self.control.sendCustomCommand({ command: "M106 S" + self.control.fanSpeedToPwm() });
|
||||||
};
|
};
|
||||||
//extra classes
|
|
||||||
$("#control > div.jog-panel").eq(0).addClass("controls");
|
//ph34r
|
||||||
$("#control > div.jog-panel").eq(1).addClass("tools");
|
try {
|
||||||
$("#control > div.jog-panel").eq(2).addClass("general");
|
//for some reason touchui uses "jog general" for the fan controls? Oh well, makes my job easier
|
||||||
//If !TouchUI then remove standard buttons + add slider + new buttons
|
$("#control-jog-general").find("button").eq(0).attr("id", "motors-off");
|
||||||
|
$("#control-jog-general").find("button").eq(1).attr("id", "fan-on");
|
||||||
|
$("#control-jog-general").find("button").eq(2).attr("id", "fan-off");
|
||||||
|
//If not TouchUI then remove standard buttons + add slider + new buttons
|
||||||
if ($("#touch body").length == 0) {
|
if ($("#touch body").length == 0) {
|
||||||
//add ID to buttons
|
|
||||||
$("#control > div.general").find("button").eq(0).attr("id", "motors-off");
|
|
||||||
$("#control > div.general").find("button").eq(1).attr("id", "fan-on");
|
|
||||||
$("#control > div.general").find("button").eq(2).attr("id", "fan-off");
|
|
||||||
//remove original fan on/off buttons
|
//remove original fan on/off buttons
|
||||||
$("#fan-on").remove();
|
$("#fan-on").remove();
|
||||||
$("#fan-off").remove();
|
$("#fan-off").remove();
|
||||||
//add new fan controls
|
//add new fan controls
|
||||||
$("#control > div.jog-panel.general").find("button").eq(0).before("\
|
$("#control-jog-general").find("button").eq(0).before("\
|
||||||
<input type=\"number\" style=\"width: 90px\" data-bind=\"slider: {min: 00, max: 100, step: 1, value: fanSpeed, tooltip: 'hide'}\">\
|
<input type=\"number\" style=\"width: 95px\" data-bind=\"slider: {min: 00, max: 100, step: 1, value: fanSpeed, tooltip: 'hide'}\">\
|
||||||
<button class=\"btn btn-block control-box\" data-bind=\"enable: isOperational() && loginState.isUser(), click: function() { sendFanSpeed() }\">" + gettext("Fan on") + ":<span data-bind=\"text: fanSpeed() + '%'\"></span></button>\
|
<button class=\"btn btn-block control-box\" id=\"fan-on\" data-bind=\"enable: isOperational() && loginState.isUser(), click: function() { $root.sendFanSpeed() }\">" + gettext("Fan speed") + ":<span data-bind=\"text: fanSpeed() + '%'\"></span></button>\
|
||||||
<button class=\"btn btn-block control-box\" data-bind=\"enable: isOperational() && loginState.isUser(), click: function() { $root.sendCustomCommand({ type: 'command', commands: ['M106 S0'] }) }\">" + gettext("Fan off") + "</button>\
|
<button class=\"btn btn-block control-box\" id=\"fan-off\" data-bind=\"enable: isOperational() && loginState.isUser(), click: function() { $root.sendCustomCommand({ type: 'command', commands: ['M106 S0'] }) }\">" + gettext("Fan off") + "</button>\
|
||||||
");
|
");
|
||||||
} else { //if TouchUI is active we only add the speed input + fan on button in a new section.
|
} else {
|
||||||
console.log("Fan Speed Slider: NOTICE! TouchUI is active, adding simplified control.");
|
//replace touch UI's fan on button with one that sends whatever speed is set in this plugin
|
||||||
$("#control > div.jog-panel.general").after("\
|
$("#fan-on").remove();
|
||||||
|
$("#control-jog-general").find("button").eq(0).after("\
|
||||||
|
<button class=\"btn btn-block control-box\" id=\"fan-on\" data-bind=\"enable: isOperational() && loginState.isUser(), click: function() { $root.sendFanSpeed() }\">" + gettext("Fan on") + "</button>\
|
||||||
|
");
|
||||||
|
//also add spin box + button below in its own section, button is redundant but convenient
|
||||||
|
$("#control-jog-extrusion").after("\
|
||||||
<div id=\"control-fan-slider\" class=\"jog-panel filament\" data-bind=\"visible: loginState.isUser\">\
|
<div id=\"control-fan-slider\" class=\"jog-panel filament\" data-bind=\"visible: loginState.isUser\">\
|
||||||
<div>\
|
<div>\
|
||||||
<input type=\"number\" style=\"width: 150px\" data-bind=\"slider: {min: 00, max: 255, step: 1, value: fanSpeed, tooltip: 'hide'}\">\
|
<input type=\"number\" style=\"width: 150px\" data-bind=\"slider: {min: 00, max: 100, step: 1, value: fanSpeed, tooltip: 'hide'}\">\
|
||||||
<button class=\"btn btn-block control-box\" data-bind=\"enable: isOperational() && loginState.isUser(), click: function() { sendFanSpeed() }\">" + gettext("Fan Speed(%)") + "</button>\
|
<button class=\"btn btn-block\" style=\"width: 169px\" data-bind=\"enable: isOperational() && loginState.isUser(), click: function() { $root.sendFanSpeed() }\">" + gettext("Fan speed:") + "<span data-bind=\"text: fanSpeed() + '%'\"></span></button>\
|
||||||
</div>\
|
</div>\
|
||||||
</div>\
|
</div>\
|
||||||
");
|
");
|
||||||
}
|
}
|
||||||
//retrieve settings
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.updateSettings = function () {
|
||||||
|
try {
|
||||||
|
self.settings.minFanSpeed(parseInt(self.settings.settings.plugins.fanspeedslider.minSpeed()));
|
||||||
|
self.settings.maxFanSpeed(parseInt(self.settings.settings.plugins.fanspeedslider.maxSpeed()));
|
||||||
|
self.settings.notifyDelay(parseInt(self.settings.settings.plugins.fanspeedslider.notifyDelay()));
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.onBeforeBinding = function () {
|
self.onBeforeBinding = function () {
|
||||||
fanSpeed(self.settings.settings.plugins.fanspeedslider.defaultFanSpeed());
|
self.settings.defaultFanSpeed(parseInt(self.settings.settings.plugins.fanspeedslider.defaultFanSpeed()));
|
||||||
|
self.updateSettings();
|
||||||
|
//if the default fan speed is above or below max/min then set to either max or min
|
||||||
|
if (self.settings.defaultFanSpeed() < self.settings.minFanSpeed()) {
|
||||||
|
self.control.fanSpeed(self.settings.minFanSpeed());
|
||||||
|
}
|
||||||
|
else if (self.settings.defaultFanSpeed() > self.settings.maxFanSpeed()) {
|
||||||
|
self.control.fanSpeed(self.settings.maxFanSpeed());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
self.control.fanSpeed(self.settings.defaultFanSpeed());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
OCTOPRINT_VIEWMODELS.push([
|
|
||||||
FanSliderPluginViewModel,
|
//update settings in case user changes them, otherwise a refresh of the UI is required
|
||||||
["settingsViewModel", "controlViewModel", "loginStateViewModel"]
|
self.onSettingsHidden = function () {
|
||||||
]);
|
self.updateSettings();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OCTOPRINT_VIEWMODELS.push({
|
||||||
|
construct: FanSliderPluginViewModel,
|
||||||
|
additionalNames: [],
|
||||||
|
dependencies: ["settingsViewModel", "controlViewModel", "loginStateViewModel"],
|
||||||
|
optional: [],
|
||||||
|
elements: []
|
||||||
|
});
|
||||||
});
|
});
|
@ -1,12 +1,11 @@
|
|||||||
<h3>{{ _('Fan Speed Control') }}</h3>
|
<h3>{{ _('Fan Speed Control') }}</h3>
|
||||||
<div>
|
<div>
|
||||||
<form class="form-horizontal">
|
<form class="form-horizontal">
|
||||||
<p>{{ _('Set the default value for the speed slider.') }}</p>
|
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<label class="control-label">{{ _('Default Value') }}</label>
|
<label class="control-label">{{ _('Default Value') }}</label>
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
<div class="input-append">
|
<div class="input-append">
|
||||||
<input type="number" min="0" max="100" class="input-mini" data-bind="value: settings.plugins.fanspeedslider.defaultFanSpeed">
|
<input type="number" min="0" max="100" class="input-mini" data-bind="attr: {title: 'This is the value the slider will default to when the UI is loaded / refreshed.' }, value: settings.plugins.fanspeedslider.defaultFanSpeed">
|
||||||
<span class="add-on">%</span>
|
<span class="add-on">%</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="help-block">{{ _('The default value the slider will be set to when opening OctoPrint\'s UI') }}</span>
|
<span class="help-block">{{ _('The default value the slider will be set to when opening OctoPrint\'s UI') }}</span>
|
||||||
@ -34,5 +33,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p><i>{{ _('NOTE: The min/max setting has no effect when you are printing from an SD card that is attached directly to the printer as the gcode does not pass through OctoPrint.') }}</i></p>
|
<p><i>{{ _('NOTE: The min/max setting has no effect when you are printing from an SD card that is attached directly to the printer as the gcode does not pass through OctoPrint.') }}</i></p>
|
||||||
|
<h3>{{ _('Notification Auto Hide Delay') }}</h3>
|
||||||
|
<div class="control-group">
|
||||||
|
<label class="control-label">{{ _('Notification Autohide Delay') }}</label>
|
||||||
|
<div class="controls">
|
||||||
|
<div class="input-append">
|
||||||
|
<input type="number" min="0" class="input-mini" data-bind="attr: {title: 'The plugin will notify a user when the fan speed is automatically adjusted, this only applies to speeds set via the button in the UI. \n\nFan speeds sent via the terminal (and therefore any print job) won\'t trigger notification spam.' }, value: settings.plugins.fanspeedslider.notifyDelay">
|
||||||
|
<span class="add-on">ms</span>
|
||||||
|
</div>
|
||||||
|
<span class="help-block">{{ _('Delay (in milliseconds) before notifications are auto-hidden. Set to 0 to disable notifications.') }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
2
setup.py
2
setup.py
@ -14,7 +14,7 @@ plugin_package = "octoprint_fanspeedslider"
|
|||||||
plugin_name = "OctoPrint-FanSpeedSlider"
|
plugin_name = "OctoPrint-FanSpeedSlider"
|
||||||
|
|
||||||
# The plugin's version. Can be overwritten within OctoPrint's internal data via __plugin_version__ in the plugin module
|
# The plugin's version. Can be overwritten within OctoPrint's internal data via __plugin_version__ in the plugin module
|
||||||
plugin_version = "0.1.5"
|
plugin_version = "0.1.6.dev1"
|
||||||
|
|
||||||
# The plugin's description. Can be overwritten within OctoPrint's internal data via __plugin_description__ in the plugin
|
# The plugin's description. Can be overwritten within OctoPrint's internal data via __plugin_description__ in the plugin
|
||||||
# module
|
# module
|
||||||
|
Loading…
Reference in New Issue
Block a user