Refactoring of "printer" modules
- renamed methods from camelCase to snake_case - renamed callback mathods from comm module from camelCase to snake_case - extracted and documented public interface to be used by plugins - extracted callback interface to be implemented by subscribed callbacks to printer - moved standard implementation to custom package - moved time estimation classes to custom package
This commit is contained in:
parent
af0cad90a8
commit
333c9ba205
16 changed files with 1447 additions and 977 deletions
|
|
@ -340,7 +340,7 @@ class CommandTrigger(GenericEventListener):
|
||||||
"__now": datetime.datetime.now().isoformat()
|
"__now": datetime.datetime.now().isoformat()
|
||||||
}
|
}
|
||||||
|
|
||||||
currentData = self._printer.getCurrentData()
|
currentData = self._printer.get_current_data()
|
||||||
|
|
||||||
if "currentZ" in currentData.keys() and currentData["currentZ"] is not None:
|
if "currentZ" in currentData.keys() and currentData["currentZ"] is not None:
|
||||||
params["__currentZ"] = str(currentData["currentZ"])
|
params["__currentZ"] = str(currentData["currentZ"])
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
63
src/octoprint/printer/estimation.py
Normal file
63
src/octoprint/printer/estimation.py
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
# coding=utf-8
|
||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
__author__ = "Gina Häußge <osd@foosel.net>"
|
||||||
|
__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
|
||||||
|
__copyright__ = "Copyright (C) 2014 The OctoPrint Project - Released under terms of the AGPLv3 License"
|
||||||
|
|
||||||
|
|
||||||
|
class TimeEstimationHelper(object):
|
||||||
|
|
||||||
|
STABLE_THRESHOLD = 0.1
|
||||||
|
STABLE_COUNTDOWN = 250
|
||||||
|
STABLE_ROLLING_WINDOW = 250
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
import collections
|
||||||
|
self._distances = collections.deque([], self.__class__.STABLE_ROLLING_WINDOW)
|
||||||
|
self._totals = collections.deque([], self.__class__.STABLE_ROLLING_WINDOW)
|
||||||
|
self._sum_total = 0
|
||||||
|
self._count = 0
|
||||||
|
self._stable_counter = None
|
||||||
|
|
||||||
|
def is_stable(self):
|
||||||
|
return self._stable_counter is not None and self._stable_counter >= self.__class__.STABLE_COUNTDOWN
|
||||||
|
|
||||||
|
def update(self, newEstimate):
|
||||||
|
old_average_total = self.average_total
|
||||||
|
|
||||||
|
self._sum_total += newEstimate
|
||||||
|
self._totals.append(newEstimate)
|
||||||
|
self._count += 1
|
||||||
|
|
||||||
|
if old_average_total:
|
||||||
|
self._distances.append(abs(self.average_total - old_average_total))
|
||||||
|
|
||||||
|
if -1.0 * self.__class__.STABLE_THRESHOLD < self.average_distance < self.__class__.STABLE_THRESHOLD:
|
||||||
|
if self._stable_counter is None:
|
||||||
|
self._stable_counter = 0
|
||||||
|
else:
|
||||||
|
self._stable_counter += 1
|
||||||
|
else:
|
||||||
|
self._stable_counter = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def average_total(self):
|
||||||
|
if not self._count:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return self._sum_total / self._count
|
||||||
|
|
||||||
|
@property
|
||||||
|
def average_total_rolling(self):
|
||||||
|
if not self._count or self._count < self.__class__.STABLE_ROLLING_WINDOW:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return sum(self._totals) / len(self._totals)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def average_distance(self):
|
||||||
|
if not self._count or self._count < self.__class__.STABLE_ROLLING_WINDOW + 1:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return sum(self._distances) / len(self._distances)
|
||||||
924
src/octoprint/printer/standard.py
Normal file
924
src/octoprint/printer/standard.py
Normal file
|
|
@ -0,0 +1,924 @@
|
||||||
|
# coding=utf-8
|
||||||
|
"""
|
||||||
|
This module holds the standard implementation of the :class:`PrinterInterface` and it helpers.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
__author__ = "Gina Häußge <osd@foosel.net>"
|
||||||
|
__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
|
||||||
|
__copyright__ = "Copyright (C) 2014 The OctoPrint Project - Released under terms of the AGPLv3 License"
|
||||||
|
|
||||||
|
import copy
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
|
||||||
|
from octoprint import util as util
|
||||||
|
from octoprint.events import eventManager, Events
|
||||||
|
from octoprint.filemanager import FileDestinations
|
||||||
|
from octoprint.plugin import plugin_manager, ProgressPlugin
|
||||||
|
from octoprint.printer import PrinterInterface, PrinterCallback
|
||||||
|
from octoprint.printer.estimation import TimeEstimationHelper
|
||||||
|
from octoprint.settings import settings
|
||||||
|
from octoprint.util import comm as comm
|
||||||
|
|
||||||
|
|
||||||
|
class Printer(PrinterInterface, comm.MachineComPrintCallback):
|
||||||
|
"""
|
||||||
|
Default implementation of the :class:`PrinterInterface`. Manages the communication layer object and registers
|
||||||
|
itself with it as a callback to react to changes on the communication layer.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, fileManager, analysisQueue, printerProfileManager):
|
||||||
|
from collections import deque
|
||||||
|
|
||||||
|
self._logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
self._analysisQueue = analysisQueue
|
||||||
|
self._fileManager = fileManager
|
||||||
|
self._printerProfileManager = printerProfileManager
|
||||||
|
|
||||||
|
# state
|
||||||
|
# TODO do we really need to hold the temperature here?
|
||||||
|
self._temp = None
|
||||||
|
self._bedTemp = None
|
||||||
|
self._targetTemp = None
|
||||||
|
self._targetBedTemp = None
|
||||||
|
self._temps = deque([], 300)
|
||||||
|
self._tempBacklog = []
|
||||||
|
|
||||||
|
self._latestMessage = None
|
||||||
|
self._messages = deque([], 300)
|
||||||
|
self._messageBacklog = []
|
||||||
|
|
||||||
|
self._latestLog = None
|
||||||
|
self._log = deque([], 300)
|
||||||
|
self._logBacklog = []
|
||||||
|
|
||||||
|
self._state = None
|
||||||
|
|
||||||
|
self._currentZ = None
|
||||||
|
|
||||||
|
self._progress = None
|
||||||
|
self._printTime = None
|
||||||
|
self._printTimeLeft = None
|
||||||
|
|
||||||
|
self._printAfterSelect = False
|
||||||
|
|
||||||
|
# sd handling
|
||||||
|
self._sdPrinting = False
|
||||||
|
self._sdStreaming = False
|
||||||
|
self._sdFilelistAvailable = threading.Event()
|
||||||
|
self._streamingFinishedCallback = None
|
||||||
|
|
||||||
|
self._selectedFile = None
|
||||||
|
self._timeEstimationData = None
|
||||||
|
|
||||||
|
# comm
|
||||||
|
self._comm = None
|
||||||
|
|
||||||
|
# callbacks
|
||||||
|
self._callbacks = []
|
||||||
|
|
||||||
|
# progress plugins
|
||||||
|
self._lastProgressReport = None
|
||||||
|
self._progressPlugins = plugin_manager().get_implementations(ProgressPlugin)
|
||||||
|
|
||||||
|
self._stateMonitor = StateMonitor(
|
||||||
|
interval=0.5,
|
||||||
|
on_update=self._sendCurrentDataCallbacks,
|
||||||
|
on_add_temperature=self._sendAddTemperatureCallbacks,
|
||||||
|
on_add_log=self._sendAddLogCallbacks,
|
||||||
|
on_add_message=self._sendAddMessageCallbacks
|
||||||
|
)
|
||||||
|
self._stateMonitor.reset(
|
||||||
|
state={"text": self.get_state_string(), "flags": self._getStateFlags()},
|
||||||
|
job_data={
|
||||||
|
"file": {
|
||||||
|
"name": None,
|
||||||
|
"size": None,
|
||||||
|
"origin": None,
|
||||||
|
"date": None
|
||||||
|
},
|
||||||
|
"estimatedPrintTime": None,
|
||||||
|
"lastPrintTime": None,
|
||||||
|
"filament": {
|
||||||
|
"length": None,
|
||||||
|
"volume": None
|
||||||
|
}
|
||||||
|
},
|
||||||
|
progress={"completion": None, "filepos": None, "printTime": None, "printTimeLeft": None},
|
||||||
|
current_z=None
|
||||||
|
)
|
||||||
|
|
||||||
|
eventManager().subscribe(Events.METADATA_ANALYSIS_FINISHED, self._on_event_MetadataAnalysisFinished)
|
||||||
|
eventManager().subscribe(Events.METADATA_STATISTICS_UPDATED, self._on_event_MetadataStatisticsUpdated)
|
||||||
|
|
||||||
|
#~~ handling of PrinterCallbacks
|
||||||
|
|
||||||
|
def register_callback(self, callback):
|
||||||
|
if not isinstance(callback, PrinterCallback):
|
||||||
|
self._logger.warn("Registering an object as printer callback which doesn't implement the PrinterCallback interface")
|
||||||
|
|
||||||
|
self._callbacks.append(callback)
|
||||||
|
self._sendInitialStateUpdate(callback)
|
||||||
|
|
||||||
|
def unregister_callback(self, callback):
|
||||||
|
if callback in self._callbacks:
|
||||||
|
self._callbacks.remove(callback)
|
||||||
|
|
||||||
|
def _sendAddTemperatureCallbacks(self, data):
|
||||||
|
for callback in self._callbacks:
|
||||||
|
try: callback.on_printer_add_temperature(data)
|
||||||
|
except: self._logger.exception("Exception while adding temperature data point")
|
||||||
|
|
||||||
|
def _sendAddLogCallbacks(self, data):
|
||||||
|
for callback in self._callbacks:
|
||||||
|
try: callback.on_printer_add_log(data)
|
||||||
|
except: self._logger.exception("Exception while adding communication log entry")
|
||||||
|
|
||||||
|
def _sendAddMessageCallbacks(self, data):
|
||||||
|
for callback in self._callbacks:
|
||||||
|
try: callback.on_printer_add_message(data)
|
||||||
|
except: self._logger.exception("Exception while adding printer message")
|
||||||
|
|
||||||
|
def _sendCurrentDataCallbacks(self, data):
|
||||||
|
for callback in self._callbacks:
|
||||||
|
try: callback.on_printer_send_current_data(copy.deepcopy(data))
|
||||||
|
except: self._logger.exception("Exception while pushing current data")
|
||||||
|
|
||||||
|
def _sendFeedbackCommandOutput(self, name, output):
|
||||||
|
for callback in self._callbacks:
|
||||||
|
try: callback.on_printer_received_registered_message(name, output)
|
||||||
|
except: self._logger.exception("Exception while pushing feedback command output")
|
||||||
|
|
||||||
|
#~~ callback from metadata analysis event
|
||||||
|
|
||||||
|
def _on_event_MetadataAnalysisFinished(self, event, data):
|
||||||
|
if self._selectedFile:
|
||||||
|
self._setJobData(self._selectedFile["filename"],
|
||||||
|
self._selectedFile["filesize"],
|
||||||
|
self._selectedFile["sd"])
|
||||||
|
|
||||||
|
def _on_event_MetadataStatisticsUpdated(self, event, data):
|
||||||
|
self._setJobData(self._selectedFile["filename"],
|
||||||
|
self._selectedFile["filesize"],
|
||||||
|
self._selectedFile["sd"])
|
||||||
|
|
||||||
|
#~~ progress plugin reporting
|
||||||
|
|
||||||
|
def _reportPrintProgressToPlugins(self, progress):
|
||||||
|
if not progress or not self._selectedFile or not "sd" in self._selectedFile or not "filename" in self._selectedFile:
|
||||||
|
return
|
||||||
|
|
||||||
|
storage = "sdcard" if self._selectedFile["sd"] else "local"
|
||||||
|
filename = self._selectedFile["filename"]
|
||||||
|
|
||||||
|
def call_plugins(storage, filename, progress):
|
||||||
|
for name, plugin in self._progressPlugins.items():
|
||||||
|
try:
|
||||||
|
plugin.on_print_progress(storage, filename, progress)
|
||||||
|
except:
|
||||||
|
self._logger.exception("Exception while sending print progress to plugin %s" % name)
|
||||||
|
|
||||||
|
thread = threading.Thread(target=call_plugins, args=(storage, filename, progress))
|
||||||
|
thread.daemon = False
|
||||||
|
thread.start()
|
||||||
|
|
||||||
|
#~~ PrinterInterface implementation
|
||||||
|
|
||||||
|
def connect(self, port=None, baudrate=None, profile=None):
|
||||||
|
"""
|
||||||
|
Connects to the printer. If port and/or baudrate is provided, uses these settings, otherwise autodetection
|
||||||
|
will be attempted.
|
||||||
|
"""
|
||||||
|
if self._comm is not None:
|
||||||
|
self._comm.close()
|
||||||
|
self._printerProfileManager.select(profile)
|
||||||
|
self._comm = comm.MachineCom(port, baudrate, callbackObject=self, printerProfileManager=self._printerProfileManager)
|
||||||
|
|
||||||
|
def disconnect(self):
|
||||||
|
"""
|
||||||
|
Closes the connection to the printer.
|
||||||
|
"""
|
||||||
|
if self._comm is not None:
|
||||||
|
self._comm.close()
|
||||||
|
self._comm = None
|
||||||
|
self._printerProfileManager.deselect()
|
||||||
|
eventManager().fire(Events.DISCONNECTED)
|
||||||
|
|
||||||
|
def get_transport(self):
|
||||||
|
|
||||||
|
if self._comm is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return self._comm.getTransport()
|
||||||
|
getTransport = util.deprecated("getTransport has been renamed to get_transport", since="1.2.0-dev-590", includedoc="Replaced by :func:`get_transport`")
|
||||||
|
|
||||||
|
def commands(self, commands):
|
||||||
|
"""
|
||||||
|
Sends one or more gcode commands to the printer.
|
||||||
|
"""
|
||||||
|
if self._comm is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
if not isinstance(commands, (list, tuple)):
|
||||||
|
commands = [commands]
|
||||||
|
|
||||||
|
for command in commands:
|
||||||
|
self._comm.sendCommand(command)
|
||||||
|
|
||||||
|
def jog(self, axis, amount):
|
||||||
|
if not isinstance(axis, (str, unicode)):
|
||||||
|
raise ValueError("axis must be a string: {axis}".format(axis=axis))
|
||||||
|
|
||||||
|
axis = axis.lower()
|
||||||
|
if not axis in PrinterInterface.valid_axes:
|
||||||
|
raise ValueError("axis must be any of {axes}: {axis}".format(axes=", ".join(PrinterInterface.valid_axes), axis=axis))
|
||||||
|
if not isinstance(amount, (int, long, float)):
|
||||||
|
raise ValueError("amount must be a valid number: {amount}".format(amount=amount))
|
||||||
|
|
||||||
|
printer_profile = self._printerProfileManager.get_current_or_default()
|
||||||
|
movement_speed = printer_profile["axes"][axis]["speed"]
|
||||||
|
self.commands(["G91", "G1 %s%.4f F%d" % (axis.upper(), amount, movement_speed), "G90"])
|
||||||
|
|
||||||
|
def home(self, axes):
|
||||||
|
if not isinstance(axes, (list, tuple)):
|
||||||
|
if isinstance(axes, (str, unicode)):
|
||||||
|
axes = [axes]
|
||||||
|
else:
|
||||||
|
raise ValueError("axes is neither a list nor a string: {axes}".format(axes=axes))
|
||||||
|
|
||||||
|
validated_axes = filter(lambda x: x in PrinterInterface.valid_axes, map(lambda x: x.lower(), axes))
|
||||||
|
if len(axes) != len(validated_axes):
|
||||||
|
raise ValueError("axes contains invalid axes: {axes}".format(axes=axes))
|
||||||
|
|
||||||
|
self.commands(["G91", "G28 %s" % " ".join(map(lambda x: "%s0" % x.upper(), validated_axes)), "G90"])
|
||||||
|
|
||||||
|
def extrude(self, amount):
|
||||||
|
if not isinstance(amount, (int, long, float)):
|
||||||
|
raise ValueError("amount must be a valid number: {amount}".format(amount=amount))
|
||||||
|
|
||||||
|
printer_profile = self._printerProfileManager.get_current_or_default()
|
||||||
|
extrusion_speed = printer_profile["axes"]["e"]["speed"]
|
||||||
|
self.commands(["G91", "G1 E%s F%d" % (amount, extrusion_speed), "G90"])
|
||||||
|
|
||||||
|
def change_tool(self, tool):
|
||||||
|
if not isinstance(tool, int) or tool < 0:
|
||||||
|
raise ValueError("tool must be an integer >= 0: {tool}".format(tool, tool))
|
||||||
|
|
||||||
|
toolNum = int(tool[len("tool"):])
|
||||||
|
self.commands("T%d" % toolNum)
|
||||||
|
|
||||||
|
def set_temperature(self, heater, value):
|
||||||
|
if not PrinterInterface.valid_tool_regex.match(heater):
|
||||||
|
raise ValueError("heater must match \"tool[0-9]+\" or \"bed\": {heater}".format(type=heater))
|
||||||
|
|
||||||
|
if not isinstance(value, (int, long, float)) or value < 0:
|
||||||
|
raise ValueError("value must be a valid number >= 0: {value}".format(value=value))
|
||||||
|
|
||||||
|
if heater.startswith("tool"):
|
||||||
|
printer_profile = self._printerProfileManager.get_current_or_default()
|
||||||
|
extruder_count = printer_profile["extruder"]["count"]
|
||||||
|
if extruder_count > 1:
|
||||||
|
toolNum = int(heater[len("tool"):])
|
||||||
|
self.commands("M104 T%d S%f" % (toolNum, value))
|
||||||
|
else:
|
||||||
|
self.commands("M104 S%f" % value)
|
||||||
|
|
||||||
|
elif heater == "bed":
|
||||||
|
self.commands("M140 S%f" % value)
|
||||||
|
|
||||||
|
def set_temperature_offset(self, offsets=None):
|
||||||
|
if offsets is None:
|
||||||
|
offsets = dict()
|
||||||
|
|
||||||
|
if not isinstance(offsets, dict):
|
||||||
|
raise ValueError("offsets must be a dict")
|
||||||
|
|
||||||
|
validated_keys = filter(lambda x: PrinterInterface.valid_tool_regex.match(x), offsets.keys())
|
||||||
|
validated_values = filter(lambda x: isinstance(value, (int, long, float)), offsets.values())
|
||||||
|
|
||||||
|
if len(validated_keys) != len(offsets):
|
||||||
|
raise ValueError("offsets contains invalid keys: {offsets}".format(offsets=offsets))
|
||||||
|
if len(validated_values) != len(offsets):
|
||||||
|
raise ValueError("offsets contains invalid values: {offsets}".format(offsets=offsets))
|
||||||
|
|
||||||
|
if self._comm is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
tool, bed = self._comm.getOffsets()
|
||||||
|
|
||||||
|
validatedOffsets = dict()
|
||||||
|
|
||||||
|
for key in offsets:
|
||||||
|
value = offsets[key]
|
||||||
|
if key == "bed":
|
||||||
|
bed = value
|
||||||
|
validatedOffsets[key] = value
|
||||||
|
elif key.startswith("tool"):
|
||||||
|
toolNum = int(key[len("tool"):])
|
||||||
|
tool[toolNum] = value
|
||||||
|
validatedOffsets[key] = value
|
||||||
|
|
||||||
|
self._comm.setTemperatureOffset(tool, bed)
|
||||||
|
self._stateMonitor.set_temp_offsets(validatedOffsets)
|
||||||
|
|
||||||
|
def _convert_rate_value(self, factor, min=0, max=200):
|
||||||
|
if not isinstance(factor, (int, float, long)):
|
||||||
|
raise ValueError("factor is not a number")
|
||||||
|
|
||||||
|
if isinstance(factor, float):
|
||||||
|
factor = int(factor * 100.0)
|
||||||
|
|
||||||
|
if factor < min or factor > max:
|
||||||
|
raise ValueError("factor must be a value between %f and %f" % (min, max))
|
||||||
|
|
||||||
|
return factor
|
||||||
|
|
||||||
|
def feed_rate(self, factor):
|
||||||
|
factor = self._convert_rate_value(factor, min=50, max=200)
|
||||||
|
self.commands("M220 S%d" % factor)
|
||||||
|
|
||||||
|
def flow_rate(self, factor):
|
||||||
|
factor = self._convert_rate_value(factor, min=75, max=125)
|
||||||
|
self.commands("M221 S%d" % factor)
|
||||||
|
|
||||||
|
def select_file(self, path, sd, printAfterSelect=False):
|
||||||
|
if self._comm is None or (self._comm.isBusy() or self._comm.isStreaming()):
|
||||||
|
self._logger.info("Cannot load file: printer not connected or currently busy")
|
||||||
|
return
|
||||||
|
|
||||||
|
self._printAfterSelect = printAfterSelect
|
||||||
|
self._comm.selectFile("/" + path if sd else path, sd)
|
||||||
|
self._setProgressData(0, None, None, None)
|
||||||
|
self._setCurrentZ(None)
|
||||||
|
|
||||||
|
def unselect_file(self):
|
||||||
|
if self._comm is not None and (self._comm.isBusy() or self._comm.isStreaming()):
|
||||||
|
return
|
||||||
|
|
||||||
|
self._comm.unselectFile()
|
||||||
|
self._setProgressData(0, None, None, None)
|
||||||
|
self._setCurrentZ(None)
|
||||||
|
|
||||||
|
def start_print(self):
|
||||||
|
"""
|
||||||
|
Starts the currently loaded print job.
|
||||||
|
Only starts if the printer is connected and operational, not currently printing and a printjob is loaded
|
||||||
|
"""
|
||||||
|
if self._comm is None or not self._comm.isOperational() or self._comm.isPrinting():
|
||||||
|
return
|
||||||
|
if self._selectedFile is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
self._timeEstimationData = TimeEstimationHelper()
|
||||||
|
self._lastProgressReport = None
|
||||||
|
self._setCurrentZ(None)
|
||||||
|
self._comm.startPrint()
|
||||||
|
|
||||||
|
def toggle_pause_print(self):
|
||||||
|
"""
|
||||||
|
Pause the current printjob.
|
||||||
|
"""
|
||||||
|
if self._comm is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
self._comm.setPause(not self._comm.isPaused())
|
||||||
|
|
||||||
|
def cancel_print(self):
|
||||||
|
"""
|
||||||
|
Cancel the current printjob.
|
||||||
|
"""
|
||||||
|
if self._comm is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
self._comm.cancelPrint()
|
||||||
|
|
||||||
|
# reset progress, height, print time
|
||||||
|
self._setCurrentZ(None)
|
||||||
|
self._setProgressData(None, None, None, None)
|
||||||
|
|
||||||
|
# mark print as failure
|
||||||
|
if self._selectedFile is not None:
|
||||||
|
self._fileManager.log_print(FileDestinations.SDCARD if self._selectedFile["sd"] else FileDestinations.LOCAL, self._selectedFile["filename"], time.time(), self._comm.getPrintTime(), False, self._printerProfileManager.get_current_or_default()["id"])
|
||||||
|
payload = {
|
||||||
|
"file": self._selectedFile["filename"],
|
||||||
|
"origin": FileDestinations.LOCAL
|
||||||
|
}
|
||||||
|
if self._selectedFile["sd"]:
|
||||||
|
payload["origin"] = FileDestinations.SDCARD
|
||||||
|
eventManager().fire(Events.PRINT_FAILED, payload)
|
||||||
|
|
||||||
|
def get_state_string(self):
|
||||||
|
"""
|
||||||
|
Returns a human readable string corresponding to the current communication state.
|
||||||
|
"""
|
||||||
|
if self._comm is None:
|
||||||
|
return "Offline"
|
||||||
|
else:
|
||||||
|
return self._comm.getStateString()
|
||||||
|
|
||||||
|
def get_current_data(self):
|
||||||
|
return self._stateMonitor.get_current_data()
|
||||||
|
|
||||||
|
def get_current_job(self):
|
||||||
|
currentData = self._stateMonitor.get_current_data()
|
||||||
|
return currentData["job"]
|
||||||
|
|
||||||
|
def get_current_temperatures(self):
|
||||||
|
if self._comm is not None:
|
||||||
|
tempOffset, bedTempOffset = self._comm.getOffsets()
|
||||||
|
else:
|
||||||
|
tempOffset = {}
|
||||||
|
bedTempOffset = None
|
||||||
|
|
||||||
|
result = {}
|
||||||
|
if self._temp is not None:
|
||||||
|
for tool in self._temp.keys():
|
||||||
|
result["tool%d" % tool] = {
|
||||||
|
"actual": self._temp[tool][0],
|
||||||
|
"target": self._temp[tool][1],
|
||||||
|
"offset": tempOffset[tool] if tool in tempOffset.keys() and tempOffset[tool] is not None else 0
|
||||||
|
}
|
||||||
|
if self._bedTemp is not None:
|
||||||
|
result["bed"] = {
|
||||||
|
"actual": self._bedTemp[0],
|
||||||
|
"target": self._bedTemp[1],
|
||||||
|
"offset": bedTempOffset
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def get_temperature_history(self):
|
||||||
|
return self._temps
|
||||||
|
|
||||||
|
def get_current_connection(self):
|
||||||
|
if self._comm is None:
|
||||||
|
return "Closed", None, None, None
|
||||||
|
|
||||||
|
port, baudrate = self._comm.getConnection()
|
||||||
|
printer_profile = self._printerProfileManager.get_current_or_default()
|
||||||
|
return self._comm.getStateString(), port, baudrate, printer_profile
|
||||||
|
|
||||||
|
def is_closed_or_error(self):
|
||||||
|
return self._comm is None or self._comm.isClosedOrError()
|
||||||
|
|
||||||
|
def is_operational(self):
|
||||||
|
return self._comm is not None and self._comm.isOperational()
|
||||||
|
|
||||||
|
def is_printing(self):
|
||||||
|
return self._comm is not None and self._comm.isPrinting()
|
||||||
|
|
||||||
|
def is_paused(self):
|
||||||
|
return self._comm is not None and self._comm.isPaused()
|
||||||
|
|
||||||
|
def is_error(self):
|
||||||
|
return self._comm is not None and self._comm.isError()
|
||||||
|
|
||||||
|
def is_ready(self):
|
||||||
|
return self.is_operational() and not self._comm.isStreaming()
|
||||||
|
|
||||||
|
def is_sd_ready(self):
|
||||||
|
if not settings().getBoolean(["feature", "sdSupport"]) or self._comm is None:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return self._comm.isSdReady()
|
||||||
|
|
||||||
|
#~~ sd file handling
|
||||||
|
|
||||||
|
def get_sd_files(self):
|
||||||
|
if self._comm is None or not self._comm.isSdReady():
|
||||||
|
return []
|
||||||
|
return map(lambda x: (x[0][1:], x[1]), self._comm.getSdFiles())
|
||||||
|
|
||||||
|
def add_sd_file(self, filename, absolutePath, streamingFinishedCallback):
|
||||||
|
if not self._comm or self._comm.isBusy() or not self._comm.isSdReady():
|
||||||
|
self._logger.error("No connection to printer or printer is busy")
|
||||||
|
return
|
||||||
|
|
||||||
|
self._streamingFinishedCallback = streamingFinishedCallback
|
||||||
|
|
||||||
|
self.refresh_sd_files(blocking=True)
|
||||||
|
existingSdFiles = map(lambda x: x[0], self._comm.getSdFiles())
|
||||||
|
|
||||||
|
remoteName = util.get_dos_filename(filename, existing_filenames=existingSdFiles, extension="gco")
|
||||||
|
self._timeEstimationData = TimeEstimationHelper()
|
||||||
|
self._comm.startFileTransfer(absolutePath, filename, "/" + remoteName)
|
||||||
|
|
||||||
|
return remoteName
|
||||||
|
|
||||||
|
def delete_sd_file(self, filename):
|
||||||
|
if not self._comm or not self._comm.isSdReady():
|
||||||
|
return
|
||||||
|
self._comm.deleteSdFile("/" + filename)
|
||||||
|
|
||||||
|
def init_sd_card(self):
|
||||||
|
if not self._comm or self._comm.isSdReady():
|
||||||
|
return
|
||||||
|
self._comm.initSdCard()
|
||||||
|
|
||||||
|
def release_sd_card(self):
|
||||||
|
if not self._comm or not self._comm.isSdReady():
|
||||||
|
return
|
||||||
|
self._comm.releaseSdCard()
|
||||||
|
|
||||||
|
def refresh_sd_files(self, blocking=False):
|
||||||
|
"""
|
||||||
|
Refreshs the list of file stored on the SD card attached to printer (if available and printer communication
|
||||||
|
available). Optional blocking parameter allows making the method block (max 10s) until the file list has been
|
||||||
|
received (and can be accessed via self._comm.getSdFiles()). Defaults to an asynchronous operation.
|
||||||
|
"""
|
||||||
|
if not self._comm or not self._comm.isSdReady():
|
||||||
|
return
|
||||||
|
self._sdFilelistAvailable.clear()
|
||||||
|
self._comm.refreshSdFiles()
|
||||||
|
if blocking:
|
||||||
|
self._sdFilelistAvailable.wait(10000)
|
||||||
|
|
||||||
|
#~~ state monitoring
|
||||||
|
|
||||||
|
def _setCurrentZ(self, currentZ):
|
||||||
|
self._currentZ = currentZ
|
||||||
|
self._stateMonitor.set_current_z(self._currentZ)
|
||||||
|
|
||||||
|
def _setState(self, state):
|
||||||
|
self._state = state
|
||||||
|
self._stateMonitor.set_state({"text": self.get_state_string(), "flags": self._getStateFlags()})
|
||||||
|
|
||||||
|
def _addLog(self, log):
|
||||||
|
self._log.append(log)
|
||||||
|
self._stateMonitor.add_log(log)
|
||||||
|
|
||||||
|
def _addMessage(self, message):
|
||||||
|
self._messages.append(message)
|
||||||
|
self._stateMonitor.add_message(message)
|
||||||
|
|
||||||
|
def _estimateTotalPrintTime(self, progress, printTime):
|
||||||
|
if not progress or not printTime or not self._timeEstimationData:
|
||||||
|
return None
|
||||||
|
|
||||||
|
else:
|
||||||
|
newEstimate = printTime / progress
|
||||||
|
self._timeEstimationData.update(newEstimate)
|
||||||
|
|
||||||
|
result = None
|
||||||
|
if self._timeEstimationData.is_stable():
|
||||||
|
result = self._timeEstimationData.average_total_rolling
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def _setProgressData(self, progress, filepos, printTime, cleanedPrintTime):
|
||||||
|
estimatedTotalPrintTime = self._estimateTotalPrintTime(progress, cleanedPrintTime)
|
||||||
|
totalPrintTime = estimatedTotalPrintTime
|
||||||
|
|
||||||
|
if self._selectedFile and "estimatedPrintTime" in self._selectedFile and self._selectedFile["estimatedPrintTime"]:
|
||||||
|
statisticalTotalPrintTime = self._selectedFile["estimatedPrintTime"]
|
||||||
|
if progress and cleanedPrintTime:
|
||||||
|
if estimatedTotalPrintTime is None:
|
||||||
|
totalPrintTime = statisticalTotalPrintTime
|
||||||
|
else:
|
||||||
|
if progress < 0.5:
|
||||||
|
sub_progress = progress * 2
|
||||||
|
else:
|
||||||
|
sub_progress = 1.0
|
||||||
|
totalPrintTime = (1 - sub_progress) * statisticalTotalPrintTime + sub_progress * estimatedTotalPrintTime
|
||||||
|
|
||||||
|
self._progress = progress
|
||||||
|
self._printTime = printTime
|
||||||
|
self._printTimeLeft = totalPrintTime - cleanedPrintTime if (totalPrintTime is not None and cleanedPrintTime is not None) else None
|
||||||
|
|
||||||
|
self._stateMonitor.set_progress({
|
||||||
|
"completion": self._progress * 100 if self._progress is not None else None,
|
||||||
|
"filepos": filepos,
|
||||||
|
"printTime": int(self._printTime) if self._printTime is not None else None,
|
||||||
|
"printTimeLeft": int(self._printTimeLeft) if self._printTimeLeft is not None else None
|
||||||
|
})
|
||||||
|
|
||||||
|
if progress:
|
||||||
|
progress_int = int(progress * 100)
|
||||||
|
if self._lastProgressReport != progress_int:
|
||||||
|
self._lastProgressReport = progress_int
|
||||||
|
self._reportPrintProgressToPlugins(progress_int)
|
||||||
|
|
||||||
|
|
||||||
|
def _addTemperatureData(self, temp, bedTemp):
|
||||||
|
currentTimeUtc = int(time.time())
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"time": currentTimeUtc
|
||||||
|
}
|
||||||
|
for tool in temp.keys():
|
||||||
|
data["tool%d" % tool] = {
|
||||||
|
"actual": temp[tool][0],
|
||||||
|
"target": temp[tool][1]
|
||||||
|
}
|
||||||
|
if bedTemp is not None and isinstance(bedTemp, tuple):
|
||||||
|
data["bed"] = {
|
||||||
|
"actual": bedTemp[0],
|
||||||
|
"target": bedTemp[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
self._temps.append(data)
|
||||||
|
|
||||||
|
self._temp = temp
|
||||||
|
self._bedTemp = bedTemp
|
||||||
|
|
||||||
|
self._stateMonitor.add_temperature(data)
|
||||||
|
|
||||||
|
def _setJobData(self, filename, filesize, sd):
|
||||||
|
if filename is not None:
|
||||||
|
if sd:
|
||||||
|
path_in_storage = filename[1:]
|
||||||
|
path_on_disk = None
|
||||||
|
else:
|
||||||
|
path_in_storage = self._fileManager.path_in_storage(FileDestinations.LOCAL, filename)
|
||||||
|
path_on_disk = self._fileManager.path_on_disk(FileDestinations.LOCAL, filename)
|
||||||
|
self._selectedFile = {
|
||||||
|
"filename": path_in_storage,
|
||||||
|
"filesize": filesize,
|
||||||
|
"sd": sd,
|
||||||
|
"estimatedPrintTime": None
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
self._selectedFile = None
|
||||||
|
self._stateMonitor.set_job_data({
|
||||||
|
"file": {
|
||||||
|
"name": None,
|
||||||
|
"origin": None,
|
||||||
|
"size": None,
|
||||||
|
"date": None
|
||||||
|
},
|
||||||
|
"estimatedPrintTime": None,
|
||||||
|
"averagePrintTime": None,
|
||||||
|
"lastPrintTime": None,
|
||||||
|
"filament": None,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
|
||||||
|
estimatedPrintTime = None
|
||||||
|
lastPrintTime = None
|
||||||
|
averagePrintTime = None
|
||||||
|
date = None
|
||||||
|
filament = None
|
||||||
|
if path_on_disk:
|
||||||
|
# Use a string for mtime because it could be float and the
|
||||||
|
# javascript needs to exact match
|
||||||
|
if not sd:
|
||||||
|
date = int(os.stat(path_on_disk).st_ctime)
|
||||||
|
|
||||||
|
try:
|
||||||
|
fileData = self._fileManager.get_metadata(FileDestinations.SDCARD if sd else FileDestinations.LOCAL, path_on_disk)
|
||||||
|
except:
|
||||||
|
fileData = None
|
||||||
|
if fileData is not None:
|
||||||
|
if "analysis" in fileData:
|
||||||
|
if estimatedPrintTime is None and "estimatedPrintTime" in fileData["analysis"]:
|
||||||
|
estimatedPrintTime = fileData["analysis"]["estimatedPrintTime"]
|
||||||
|
if "filament" in fileData["analysis"].keys():
|
||||||
|
filament = fileData["analysis"]["filament"]
|
||||||
|
if "statistics" in fileData:
|
||||||
|
printer_profile = self._printerProfileManager.get_current_or_default()["id"]
|
||||||
|
if "averagePrintTime" in fileData["statistics"] and printer_profile in fileData["statistics"]["averagePrintTime"]:
|
||||||
|
averagePrintTime = fileData["statistics"]["averagePrintTime"][printer_profile]
|
||||||
|
if "lastPrintTime" in fileData["statistics"] and printer_profile in fileData["statistics"]["lastPrintTime"]:
|
||||||
|
lastPrintTime = fileData["statistics"]["lastPrintTime"][printer_profile]
|
||||||
|
|
||||||
|
if averagePrintTime is not None:
|
||||||
|
self._selectedFile["estimatedPrintTime"] = averagePrintTime
|
||||||
|
elif estimatedPrintTime is not None:
|
||||||
|
# TODO apply factor which first needs to be tracked!
|
||||||
|
self._selectedFile["estimatedPrintTime"] = estimatedPrintTime
|
||||||
|
|
||||||
|
self._stateMonitor.set_job_data({
|
||||||
|
"file": {
|
||||||
|
"name": path_in_storage,
|
||||||
|
"origin": FileDestinations.SDCARD if sd else FileDestinations.LOCAL,
|
||||||
|
"size": filesize,
|
||||||
|
"date": date
|
||||||
|
},
|
||||||
|
"estimatedPrintTime": estimatedPrintTime,
|
||||||
|
"averagePrintTime": averagePrintTime,
|
||||||
|
"lastPrintTime": lastPrintTime,
|
||||||
|
"filament": filament,
|
||||||
|
})
|
||||||
|
|
||||||
|
def _sendInitialStateUpdate(self, callback):
|
||||||
|
try:
|
||||||
|
data = self._stateMonitor.get_current_data()
|
||||||
|
data.update({
|
||||||
|
"temps": list(self._temps),
|
||||||
|
"logs": list(self._log),
|
||||||
|
"messages": list(self._messages)
|
||||||
|
})
|
||||||
|
callback.on_printer_send_initial_data(data)
|
||||||
|
except Exception, err:
|
||||||
|
import sys
|
||||||
|
sys.stderr.write("ERROR: %s\n" % str(err))
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _getStateFlags(self):
|
||||||
|
return {
|
||||||
|
"operational": self.is_operational(),
|
||||||
|
"printing": self.is_printing(),
|
||||||
|
"closedOrError": self.is_closed_or_error(),
|
||||||
|
"error": self.is_error(),
|
||||||
|
"paused": self.is_paused(),
|
||||||
|
"ready": self.is_ready(),
|
||||||
|
"sdReady": self.is_sd_ready()
|
||||||
|
}
|
||||||
|
|
||||||
|
#~~ comm.MachineComPrintCallback implementation
|
||||||
|
|
||||||
|
def on_comm_log(self, message):
|
||||||
|
"""
|
||||||
|
Callback method for the comm object, called upon log output.
|
||||||
|
"""
|
||||||
|
self._addLog(message)
|
||||||
|
|
||||||
|
def on_comm_temperature_update(self, temp, bedTemp):
|
||||||
|
self._addTemperatureData(temp, bedTemp)
|
||||||
|
|
||||||
|
def on_comm_state_change(self, state):
|
||||||
|
"""
|
||||||
|
Callback method for the comm object, called if the connection state changes.
|
||||||
|
"""
|
||||||
|
oldState = self._state
|
||||||
|
|
||||||
|
# forward relevant state changes to gcode manager
|
||||||
|
if self._comm is not None and oldState == self._comm.STATE_PRINTING:
|
||||||
|
if self._selectedFile is not None:
|
||||||
|
if state == self._comm.STATE_OPERATIONAL:
|
||||||
|
self._fileManager.log_print(FileDestinations.SDCARD if self._selectedFile["sd"] else FileDestinations.LOCAL, self._selectedFile["filename"], time.time(), self._comm.getPrintTime(), True, self._printerProfileManager.get_current_or_default()["id"])
|
||||||
|
elif state == self._comm.STATE_CLOSED or state == self._comm.STATE_ERROR or state == self._comm.STATE_CLOSED_WITH_ERROR:
|
||||||
|
self._fileManager.log_print(FileDestinations.SDCARD if self._selectedFile["sd"] else FileDestinations.LOCAL, self._selectedFile["filename"], time.time(), self._comm.getPrintTime(), False, self._printerProfileManager.get_current_or_default()["id"])
|
||||||
|
self._analysisQueue.resume() # printing done, put those cpu cycles to good use
|
||||||
|
elif self._comm is not None and state == self._comm.STATE_PRINTING:
|
||||||
|
self._analysisQueue.pause() # do not analyse files while printing
|
||||||
|
|
||||||
|
self._setState(state)
|
||||||
|
|
||||||
|
def on_comm_message(self, message):
|
||||||
|
"""
|
||||||
|
Callback method for the comm object, called upon message exchanges via serial.
|
||||||
|
Stores the message in the message buffer, truncates buffer to the last 300 lines.
|
||||||
|
"""
|
||||||
|
self._addMessage(message)
|
||||||
|
|
||||||
|
def on_comm_progress(self):
|
||||||
|
"""
|
||||||
|
Callback method for the comm object, called upon any change in progress of the printjob.
|
||||||
|
Triggers storage of new values for printTime, printTimeLeft and the current progress.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self._setProgressData(self._comm.getPrintProgress(), self._comm.getPrintFilepos(), self._comm.getPrintTime(), self._comm.getCleanedPrintTime())
|
||||||
|
|
||||||
|
def on_comm_z_change(self, newZ):
|
||||||
|
"""
|
||||||
|
Callback method for the comm object, called upon change of the z-layer.
|
||||||
|
"""
|
||||||
|
oldZ = self._currentZ
|
||||||
|
if newZ != oldZ:
|
||||||
|
# we have to react to all z-changes, even those that might "go backward" due to a slicer's retraction or
|
||||||
|
# anti-backlash-routines. Event subscribes should individually take care to filter out "wrong" z-changes
|
||||||
|
eventManager().fire(Events.Z_CHANGE, {"new": newZ, "old": oldZ})
|
||||||
|
|
||||||
|
self._setCurrentZ(newZ)
|
||||||
|
|
||||||
|
def on_comm_sd_state_change(self, sdReady):
|
||||||
|
self._stateMonitor.set_state({"text": self.get_state_string(), "flags": self._getStateFlags()})
|
||||||
|
|
||||||
|
def on_comm_sd_files(self, files):
|
||||||
|
eventManager().fire(Events.UPDATED_FILES, {"type": "gcode"})
|
||||||
|
self._sdFilelistAvailable.set()
|
||||||
|
|
||||||
|
def on_comm_file_selected(self, filename, filesize, sd):
|
||||||
|
self._setJobData(filename, filesize, sd)
|
||||||
|
self._stateMonitor.set_state({"text": self.get_state_string(), "flags": self._getStateFlags()})
|
||||||
|
|
||||||
|
if self._printAfterSelect:
|
||||||
|
self.start_print()
|
||||||
|
|
||||||
|
def on_comm_print_job_done(self):
|
||||||
|
self._setProgressData(1.0, self._selectedFile["filesize"], self._comm.getPrintTime(), 0)
|
||||||
|
self._stateMonitor.set_state({"text": self.get_state_string(), "flags": self._getStateFlags()})
|
||||||
|
|
||||||
|
def on_comm_file_transfer_started(self, filename, filesize):
|
||||||
|
self._sdStreaming = True
|
||||||
|
|
||||||
|
self._setJobData(filename, filesize, True)
|
||||||
|
self._setProgressData(0.0, 0, 0, None)
|
||||||
|
self._stateMonitor.set_state({"text": self.get_state_string(), "flags": self._getStateFlags()})
|
||||||
|
|
||||||
|
def on_comm_file_transfer_done(self, filename):
|
||||||
|
self._sdStreaming = False
|
||||||
|
|
||||||
|
if self._streamingFinishedCallback is not None:
|
||||||
|
# in case of SD files, both filename and absolutePath are the same, so we set the (remote) filename for
|
||||||
|
# both parameters
|
||||||
|
self._streamingFinishedCallback(filename, filename, FileDestinations.SDCARD)
|
||||||
|
|
||||||
|
self._setCurrentZ(None)
|
||||||
|
self._setJobData(None, None, None)
|
||||||
|
self._setProgressData(None, None, None, None)
|
||||||
|
self._stateMonitor.set_state({"text": self.get_state_string(), "flags": self._getStateFlags()})
|
||||||
|
|
||||||
|
def on_comm_received_registered_message(self, command, output):
|
||||||
|
self._sendFeedbackCommandOutput(command, output)
|
||||||
|
|
||||||
|
def on_comm_force_disconnect(self):
|
||||||
|
self.disconnect()
|
||||||
|
|
||||||
|
|
||||||
|
class StateMonitor(object):
|
||||||
|
def __init__(self, interval=0.5, on_update=None, on_add_temperature=None, on_add_log=None, on_add_message=None):
|
||||||
|
self._interval = interval
|
||||||
|
self._update_callback = on_update
|
||||||
|
self._on_add_temperature = on_add_temperature
|
||||||
|
self._on_add_log = on_add_log
|
||||||
|
self._on_add_message = on_add_message
|
||||||
|
|
||||||
|
self._state = None
|
||||||
|
self._job_data = None
|
||||||
|
self._gcode_data = None
|
||||||
|
self._sd_upload_data = None
|
||||||
|
self._current_z = None
|
||||||
|
self._progress = None
|
||||||
|
|
||||||
|
self._offsets = {}
|
||||||
|
|
||||||
|
self._change_event = threading.Event()
|
||||||
|
self._state_lock = threading.Lock()
|
||||||
|
|
||||||
|
self._last_update = time.time()
|
||||||
|
self._worker = threading.Thread(target=self._work)
|
||||||
|
self._worker.daemon = True
|
||||||
|
self._worker.start()
|
||||||
|
|
||||||
|
def reset(self, state=None, job_data=None, progress=None, current_z=None):
|
||||||
|
self.set_state(state)
|
||||||
|
self.set_job_data(job_data)
|
||||||
|
self.set_progress(progress)
|
||||||
|
self.set_current_z(current_z)
|
||||||
|
|
||||||
|
def add_temperature(self, temperature):
|
||||||
|
self._on_add_temperature(temperature)
|
||||||
|
self._change_event.set()
|
||||||
|
|
||||||
|
def add_log(self, log):
|
||||||
|
self._on_add_log(log)
|
||||||
|
self._change_event.set()
|
||||||
|
|
||||||
|
def add_message(self, message):
|
||||||
|
self._on_add_message(message)
|
||||||
|
self._change_event.set()
|
||||||
|
|
||||||
|
def set_current_z(self, current_z):
|
||||||
|
self._current_z = current_z
|
||||||
|
self._change_event.set()
|
||||||
|
|
||||||
|
def set_state(self, state):
|
||||||
|
with self._state_lock:
|
||||||
|
self._state = state
|
||||||
|
self._change_event.set()
|
||||||
|
|
||||||
|
def set_job_data(self, job_data):
|
||||||
|
self._job_data = job_data
|
||||||
|
self._change_event.set()
|
||||||
|
|
||||||
|
def set_progress(self, progress):
|
||||||
|
self._progress = progress
|
||||||
|
self._change_event.set()
|
||||||
|
|
||||||
|
def set_temp_offsets(self, offsets):
|
||||||
|
self._offsets = offsets
|
||||||
|
self._change_event.set()
|
||||||
|
|
||||||
|
def _work(self):
|
||||||
|
while True:
|
||||||
|
self._change_event.wait()
|
||||||
|
|
||||||
|
with self._state_lock:
|
||||||
|
now = time.time()
|
||||||
|
delta = now - self._last_update
|
||||||
|
additional_wait_time = self._interval - delta
|
||||||
|
if additional_wait_time > 0:
|
||||||
|
time.sleep(additional_wait_time)
|
||||||
|
|
||||||
|
data = self.get_current_data()
|
||||||
|
self._update_callback(data)
|
||||||
|
self._last_update = time.time()
|
||||||
|
self._change_event.clear()
|
||||||
|
|
||||||
|
def get_current_data(self):
|
||||||
|
return {
|
||||||
|
"state": self._state,
|
||||||
|
"job": self._job_data,
|
||||||
|
"currentZ": self._current_z,
|
||||||
|
"progress": self._progress,
|
||||||
|
"offsets": self._offsets
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -43,8 +43,9 @@ admin_permission = Permission(RoleNeed("admin"))
|
||||||
user_permission = Permission(RoleNeed("user"))
|
user_permission = Permission(RoleNeed("user"))
|
||||||
|
|
||||||
# only import the octoprint stuff down here, as it might depend on things defined above to be initialized already
|
# only import the octoprint stuff down here, as it might depend on things defined above to be initialized already
|
||||||
from octoprint.printer import Printer, getConnectionOptions
|
from octoprint.printer import get_connection_options
|
||||||
from octoprint.printer.profile import PrinterProfileManager
|
from octoprint.printer.profile import PrinterProfileManager
|
||||||
|
from octoprint.printer.standard import Printer
|
||||||
from octoprint.settings import settings
|
from octoprint.settings import settings
|
||||||
import octoprint.users as users
|
import octoprint.users as users
|
||||||
import octoprint.events as events
|
import octoprint.events as events
|
||||||
|
|
@ -673,7 +674,7 @@ class Server():
|
||||||
if settings().getBoolean(["serial", "autoconnect"]):
|
if settings().getBoolean(["serial", "autoconnect"]):
|
||||||
(port, baudrate) = settings().get(["serial", "port"]), settings().getInt(["serial", "baudrate"])
|
(port, baudrate) = settings().get(["serial", "port"]), settings().getInt(["serial", "baudrate"])
|
||||||
printer_profile = printerProfileManager.get_default()
|
printer_profile = printerProfileManager.get_default()
|
||||||
connectionOptions = getConnectionOptions()
|
connectionOptions = get_connection_options()
|
||||||
if port in connectionOptions["ports"]:
|
if port in connectionOptions["ports"]:
|
||||||
printer.connect(port=port, baudrate=baudrate, profile=printer_profile["id"] if "id" in printer_profile else "_default")
|
printer.connect(port=port, baudrate=baudrate, profile=printer_profile["id"] if "id" in printer_profile else "_default")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ __copyright__ = "Copyright (C) 2014 The OctoPrint Project - Released under terms
|
||||||
from flask import request, jsonify, make_response
|
from flask import request, jsonify, make_response
|
||||||
|
|
||||||
from octoprint.settings import settings
|
from octoprint.settings import settings
|
||||||
from octoprint.printer import getConnectionOptions
|
from octoprint.printer import get_connection_options
|
||||||
from octoprint.server import printer, printerProfileManager, NO_CONTENT
|
from octoprint.server import printer, printerProfileManager, NO_CONTENT
|
||||||
from octoprint.server.api import api
|
from octoprint.server.api import api
|
||||||
from octoprint.server.util.flask import restricted_access, get_json_command_from_request
|
from octoprint.server.util.flask import restricted_access, get_json_command_from_request
|
||||||
|
|
@ -17,7 +17,7 @@ import octoprint.util as util
|
||||||
|
|
||||||
@api.route("/connection", methods=["GET"])
|
@api.route("/connection", methods=["GET"])
|
||||||
def connectionState():
|
def connectionState():
|
||||||
state, port, baudrate, printer_profile = printer.getCurrentConnection()
|
state, port, baudrate, printer_profile = printer.get_current_connection()
|
||||||
current = {
|
current = {
|
||||||
"state": state,
|
"state": state,
|
||||||
"port": port,
|
"port": port,
|
||||||
|
|
@ -41,7 +41,7 @@ def connectionCommand():
|
||||||
return response
|
return response
|
||||||
|
|
||||||
if command == "connect":
|
if command == "connect":
|
||||||
connection_options = getConnectionOptions()
|
connection_options = get_connection_options()
|
||||||
|
|
||||||
port = None
|
port = None
|
||||||
baudrate = None
|
baudrate = None
|
||||||
|
|
@ -72,7 +72,7 @@ def connectionCommand():
|
||||||
return NO_CONTENT
|
return NO_CONTENT
|
||||||
|
|
||||||
def _get_options():
|
def _get_options():
|
||||||
connection_options = getConnectionOptions()
|
connection_options = get_connection_options()
|
||||||
profile_options = printerProfileManager.get_all()
|
profile_options = printerProfileManager.get_all()
|
||||||
default_profile = printerProfileManager.get_default()
|
default_profile = printerProfileManager.get_default()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ def _getFileDetails(origin, filename):
|
||||||
|
|
||||||
def _getFileList(origin, filter=None):
|
def _getFileList(origin, filter=None):
|
||||||
if origin == FileDestinations.SDCARD:
|
if origin == FileDestinations.SDCARD:
|
||||||
sdFileList = printer.getSdFiles()
|
sdFileList = printer.get_sd_files()
|
||||||
|
|
||||||
files = []
|
files = []
|
||||||
if sdFileList is not None:
|
if sdFileList is not None:
|
||||||
|
|
@ -117,7 +117,7 @@ def _getFileList(origin, filter=None):
|
||||||
|
|
||||||
def _verifyFileExists(origin, filename):
|
def _verifyFileExists(origin, filename):
|
||||||
if origin == FileDestinations.SDCARD:
|
if origin == FileDestinations.SDCARD:
|
||||||
return filename in map(lambda x: x[0], printer.getSdFiles())
|
return filename in map(lambda x: x[0], printer.get_sd_files())
|
||||||
else:
|
else:
|
||||||
return fileManager.file_exists(origin, filename)
|
return fileManager.file_exists(origin, filename)
|
||||||
|
|
||||||
|
|
@ -150,15 +150,15 @@ def uploadGcodeFile(target):
|
||||||
|
|
||||||
if sd:
|
if sd:
|
||||||
# validate that all preconditions for SD upload are met before attempting it
|
# validate that all preconditions for SD upload are met before attempting it
|
||||||
if not (printer.isOperational() and not (printer.isPrinting() or printer.isPaused())):
|
if not (printer.is_operational() and not (printer.is_printing() or printer.is_paused())):
|
||||||
return make_response("Can not upload to SD card, printer is either not operational or already busy", 409)
|
return make_response("Can not upload to SD card, printer is either not operational or already busy", 409)
|
||||||
if not printer.isSdReady():
|
if not printer.is_sd_ready():
|
||||||
return make_response("Can not upload to SD card, not yet initialized", 409)
|
return make_response("Can not upload to SD card, not yet initialized", 409)
|
||||||
|
|
||||||
# determine current job
|
# determine current job
|
||||||
currentFilename = None
|
currentFilename = None
|
||||||
currentOrigin = None
|
currentOrigin = None
|
||||||
currentJob = printer.getCurrentJob()
|
currentJob = printer.get_current_job()
|
||||||
if currentJob is not None and "file" in currentJob.keys():
|
if currentJob is not None and "file" in currentJob.keys():
|
||||||
currentJobFile = currentJob["file"]
|
currentJobFile = currentJob["file"]
|
||||||
if "name" in currentJobFile.keys() and "origin" in currentJobFile.keys():
|
if "name" in currentJobFile.keys() and "origin" in currentJobFile.keys():
|
||||||
|
|
@ -174,7 +174,7 @@ def uploadGcodeFile(target):
|
||||||
return make_response("Can not upload file %s, wrong format?" % upload.filename, 415)
|
return make_response("Can not upload file %s, wrong format?" % upload.filename, 415)
|
||||||
|
|
||||||
# prohibit overwriting currently selected file while it's being printed
|
# prohibit overwriting currently selected file while it's being printed
|
||||||
if futureFilename == currentFilename and target == currentOrigin and printer.isPrinting() or printer.isPaused():
|
if futureFilename == currentFilename and target == currentOrigin and printer.is_printing() or printer.is_paused():
|
||||||
return make_response("Trying to overwrite file that is currently being printed: %s" % currentFilename, 409)
|
return make_response("Trying to overwrite file that is currently being printed: %s" % currentFilename, 409)
|
||||||
|
|
||||||
def fileProcessingFinished(filename, absFilename, destination):
|
def fileProcessingFinished(filename, absFilename, destination):
|
||||||
|
|
@ -186,7 +186,7 @@ def uploadGcodeFile(target):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if destination == FileDestinations.SDCARD and octoprint.filemanager.valid_file_type(filename, "gcode"):
|
if destination == FileDestinations.SDCARD and octoprint.filemanager.valid_file_type(filename, "gcode"):
|
||||||
return filename, printer.addSdFile(filename, absFilename, selectAndOrPrint)
|
return filename, printer.add_sd_file(filename, absFilename, selectAndOrPrint)
|
||||||
else:
|
else:
|
||||||
selectAndOrPrint(filename, absFilename, destination)
|
selectAndOrPrint(filename, absFilename, destination)
|
||||||
return filename
|
return filename
|
||||||
|
|
@ -201,7 +201,7 @@ def uploadGcodeFile(target):
|
||||||
exact file is already selected, such reloading it.
|
exact file is already selected, such reloading it.
|
||||||
"""
|
"""
|
||||||
if octoprint.filemanager.valid_file_type(added_file, "gcode") and (selectAfterUpload or printAfterSelect or (currentFilename == filename and currentOrigin == destination)):
|
if octoprint.filemanager.valid_file_type(added_file, "gcode") and (selectAfterUpload or printAfterSelect or (currentFilename == filename and currentOrigin == destination)):
|
||||||
printer.selectFile(absFilename, destination == FileDestinations.SDCARD, printAfterSelect)
|
printer.select_file(absFilename, destination == FileDestinations.SDCARD, printAfterSelect)
|
||||||
|
|
||||||
added_file = fileManager.add_file(FileDestinations.LOCAL, upload.filename, upload, allow_overwrite=True)
|
added_file = fileManager.add_file(FileDestinations.LOCAL, upload.filename, upload, allow_overwrite=True)
|
||||||
if added_file is None:
|
if added_file is None:
|
||||||
|
|
@ -284,7 +284,7 @@ def gcodeFileCommand(filename, target):
|
||||||
# selects/loads a file
|
# selects/loads a file
|
||||||
printAfterLoading = False
|
printAfterLoading = False
|
||||||
if "print" in data.keys() and data["print"] in valid_boolean_trues:
|
if "print" in data.keys() and data["print"] in valid_boolean_trues:
|
||||||
if not printer.isOperational():
|
if not printer.is_operational():
|
||||||
return make_response("Printer is not operational, cannot directly start printing", 409)
|
return make_response("Printer is not operational, cannot directly start printing", 409)
|
||||||
printAfterLoading = True
|
printAfterLoading = True
|
||||||
|
|
||||||
|
|
@ -294,7 +294,7 @@ def gcodeFileCommand(filename, target):
|
||||||
sd = True
|
sd = True
|
||||||
else:
|
else:
|
||||||
filenameToSelect = fileManager.path_on_disk(target, filename)
|
filenameToSelect = fileManager.path_on_disk(target, filename)
|
||||||
printer.selectFile(filenameToSelect, sd, printAfterLoading)
|
printer.select_file(filenameToSelect, sd, printAfterLoading)
|
||||||
|
|
||||||
elif command == "slice":
|
elif command == "slice":
|
||||||
if "slicer" in data.keys():
|
if "slicer" in data.keys():
|
||||||
|
|
@ -312,7 +312,7 @@ def gcodeFileCommand(filename, target):
|
||||||
if not octoprint.filemanager.valid_file_type(filename, type="stl"):
|
if not octoprint.filemanager.valid_file_type(filename, type="stl"):
|
||||||
return make_response("Cannot slice {filename}, not an STL file".format(**locals()), 415)
|
return make_response("Cannot slice {filename}, not an STL file".format(**locals()), 415)
|
||||||
|
|
||||||
if slicer_instance.get_slicer_properties()["same_device"] and (printer.isPrinting() or printer.isPaused()):
|
if slicer_instance.get_slicer_properties()["same_device"] and (printer.is_printing() or printer.is_paused()):
|
||||||
# slicer runs on same device as OctoPrint, slicing while printing is hence disabled
|
# slicer runs on same device as OctoPrint, slicing while printing is hence disabled
|
||||||
return make_response("Cannot slice on {slicer} while printing due to performance reasons".format(**locals()), 409)
|
return make_response("Cannot slice on {slicer} while printing due to performance reasons".format(**locals()), 409)
|
||||||
|
|
||||||
|
|
@ -326,7 +326,7 @@ def gcodeFileCommand(filename, target):
|
||||||
|
|
||||||
# prohibit overwriting the file that is currently being printed
|
# prohibit overwriting the file that is currently being printed
|
||||||
currentOrigin, currentFilename = _getCurrentFile()
|
currentOrigin, currentFilename = _getCurrentFile()
|
||||||
if currentFilename == gcode_name and currentOrigin == target and (printer.isPrinting() or printer.isPaused()):
|
if currentFilename == gcode_name and currentOrigin == target and (printer.is_printing() or printer.is_paused()):
|
||||||
make_response("Trying to slice into file that is currently being printed: %s" % gcode_name, 409)
|
make_response("Trying to slice into file that is currently being printed: %s" % gcode_name, 409)
|
||||||
|
|
||||||
if "profile" in data.keys() and data["profile"]:
|
if "profile" in data.keys() and data["profile"]:
|
||||||
|
|
@ -349,13 +349,13 @@ def gcodeFileCommand(filename, target):
|
||||||
|
|
||||||
select_after_slicing = False
|
select_after_slicing = False
|
||||||
if "select" in data.keys() and data["select"] in valid_boolean_trues:
|
if "select" in data.keys() and data["select"] in valid_boolean_trues:
|
||||||
if not printer.isOperational():
|
if not printer.is_operational():
|
||||||
return make_response("Printer is not operational, cannot directly select for printing", 409)
|
return make_response("Printer is not operational, cannot directly select for printing", 409)
|
||||||
select_after_slicing = True
|
select_after_slicing = True
|
||||||
|
|
||||||
print_after_slicing = False
|
print_after_slicing = False
|
||||||
if "print" in data.keys() and data["print"] in valid_boolean_trues:
|
if "print" in data.keys() and data["print"] in valid_boolean_trues:
|
||||||
if not printer.isOperational():
|
if not printer.is_operational():
|
||||||
return make_response("Printer is not operational, cannot directly start printing", 409)
|
return make_response("Printer is not operational, cannot directly start printing", 409)
|
||||||
select_after_slicing = print_after_slicing = True
|
select_after_slicing = print_after_slicing = True
|
||||||
|
|
||||||
|
|
@ -372,7 +372,7 @@ def gcodeFileCommand(filename, target):
|
||||||
sd = True
|
sd = True
|
||||||
else:
|
else:
|
||||||
filenameToSelect = fileManager.path_on_disk(target, gcode_name)
|
filenameToSelect = fileManager.path_on_disk(target, gcode_name)
|
||||||
printer.selectFile(filenameToSelect, sd, print_after_slicing)
|
printer.select_file(filenameToSelect, sd, print_after_slicing)
|
||||||
|
|
||||||
ok, result = fileManager.slice(slicer, target, filename, target, gcode_name,
|
ok, result = fileManager.slice(slicer, target, filename, target, gcode_name,
|
||||||
profile=profile,
|
profile=profile,
|
||||||
|
|
@ -414,7 +414,7 @@ def deleteGcodeFile(filename, target):
|
||||||
|
|
||||||
# prohibit deleting files that are currently in use
|
# prohibit deleting files that are currently in use
|
||||||
currentOrigin, currentFilename = _getCurrentFile()
|
currentOrigin, currentFilename = _getCurrentFile()
|
||||||
if currentFilename == filename and currentOrigin == target and (printer.isPrinting() or printer.isPaused()):
|
if currentFilename == filename and currentOrigin == target and (printer.is_printing() or printer.is_paused()):
|
||||||
make_response("Trying to delete file that is currently being printed: %s" % filename, 409)
|
make_response("Trying to delete file that is currently being printed: %s" % filename, 409)
|
||||||
|
|
||||||
if (target, filename) in fileManager.get_busy_files():
|
if (target, filename) in fileManager.get_busy_files():
|
||||||
|
|
@ -422,18 +422,18 @@ def deleteGcodeFile(filename, target):
|
||||||
|
|
||||||
# deselect the file if it's currently selected
|
# deselect the file if it's currently selected
|
||||||
if currentFilename is not None and filename == currentFilename:
|
if currentFilename is not None and filename == currentFilename:
|
||||||
printer.unselectFile()
|
printer.unselect_file()
|
||||||
|
|
||||||
# delete it
|
# delete it
|
||||||
if target == FileDestinations.SDCARD:
|
if target == FileDestinations.SDCARD:
|
||||||
printer.deleteSdFile(filename)
|
printer.delete_sd_file(filename)
|
||||||
else:
|
else:
|
||||||
fileManager.remove_file(target, filename)
|
fileManager.remove_file(target, filename)
|
||||||
|
|
||||||
return NO_CONTENT
|
return NO_CONTENT
|
||||||
|
|
||||||
def _getCurrentFile():
|
def _getCurrentFile():
|
||||||
currentJob = printer.getCurrentJob()
|
currentJob = printer.get_current_job()
|
||||||
if currentJob is not None and "file" in currentJob.keys() and "name" in currentJob["file"] and "origin" in currentJob["file"]:
|
if currentJob is not None and "file" in currentJob.keys() and "name" in currentJob["file"] and "origin" in currentJob["file"]:
|
||||||
return currentJob["file"]["origin"], currentJob["file"]["name"]
|
return currentJob["file"]["origin"], currentJob["file"]["name"]
|
||||||
else:
|
else:
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ import octoprint.util as util
|
||||||
@api.route("/job", methods=["POST"])
|
@api.route("/job", methods=["POST"])
|
||||||
@restricted_access
|
@restricted_access
|
||||||
def controlJob():
|
def controlJob():
|
||||||
if not printer.isOperational():
|
if not printer.is_operational():
|
||||||
return make_response("Printer is not operational", 409)
|
return make_response("Printer is not operational", 409)
|
||||||
|
|
||||||
valid_commands = {
|
valid_commands = {
|
||||||
|
|
@ -30,30 +30,30 @@ def controlJob():
|
||||||
if response is not None:
|
if response is not None:
|
||||||
return response
|
return response
|
||||||
|
|
||||||
activePrintjob = printer.isPrinting() or printer.isPaused()
|
activePrintjob = printer.is_printing() or printer.is_paused()
|
||||||
|
|
||||||
if command == "start":
|
if command == "start":
|
||||||
if activePrintjob:
|
if activePrintjob:
|
||||||
return make_response("Printer already has an active print job, did you mean 'restart'?", 409)
|
return make_response("Printer already has an active print job, did you mean 'restart'?", 409)
|
||||||
printer.startPrint()
|
printer.start_print()
|
||||||
elif command == "restart":
|
elif command == "restart":
|
||||||
if not printer.isPaused():
|
if not printer.is_paused():
|
||||||
return make_response("Printer does not have an active print job or is not paused", 409)
|
return make_response("Printer does not have an active print job or is not paused", 409)
|
||||||
printer.startPrint()
|
printer.start_print()
|
||||||
elif command == "pause":
|
elif command == "pause":
|
||||||
if not activePrintjob:
|
if not activePrintjob:
|
||||||
return make_response("Printer is neither printing nor paused, 'pause' command cannot be performed", 409)
|
return make_response("Printer is neither printing nor paused, 'pause' command cannot be performed", 409)
|
||||||
printer.togglePausePrint()
|
printer.toggle_pause_print()
|
||||||
elif command == "cancel":
|
elif command == "cancel":
|
||||||
if not activePrintjob:
|
if not activePrintjob:
|
||||||
return make_response("Printer is neither printing nor paused, 'cancel' command cannot be performed", 409)
|
return make_response("Printer is neither printing nor paused, 'cancel' command cannot be performed", 409)
|
||||||
printer.cancelPrint()
|
printer.cancel_print()
|
||||||
return NO_CONTENT
|
return NO_CONTENT
|
||||||
|
|
||||||
|
|
||||||
@api.route("/job", methods=["GET"])
|
@api.route("/job", methods=["GET"])
|
||||||
def jobState():
|
def jobState():
|
||||||
currentData = printer.getCurrentData()
|
currentData = printer.get_current_data()
|
||||||
return jsonify({
|
return jsonify({
|
||||||
"job": currentData["job"],
|
"job": currentData["job"],
|
||||||
"progress": currentData["progress"],
|
"progress": currentData["progress"],
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ import octoprint.util as util
|
||||||
|
|
||||||
@api.route("/printer", methods=["GET"])
|
@api.route("/printer", methods=["GET"])
|
||||||
def printerState():
|
def printerState():
|
||||||
if not printer.isOperational():
|
if not printer.is_operational():
|
||||||
return make_response("Printer is not operational", 409)
|
return make_response("Printer is not operational", 409)
|
||||||
|
|
||||||
# process excludes
|
# process excludes
|
||||||
|
|
@ -38,11 +38,11 @@ def printerState():
|
||||||
|
|
||||||
# add sd information
|
# add sd information
|
||||||
if not "sd" in excludes and settings().getBoolean(["feature", "sdSupport"]):
|
if not "sd" in excludes and settings().getBoolean(["feature", "sdSupport"]):
|
||||||
result.update({"sd": {"ready": printer.isSdReady()}})
|
result.update({"sd": {"ready": printer.is_sd_ready()}})
|
||||||
|
|
||||||
# add state information
|
# add state information
|
||||||
if not "state" in excludes:
|
if not "state" in excludes:
|
||||||
state = printer.getCurrentData()["state"]
|
state = printer.get_current_data()["state"]
|
||||||
result.update({"state": state})
|
result.update({"state": state})
|
||||||
|
|
||||||
return jsonify(result)
|
return jsonify(result)
|
||||||
|
|
@ -54,7 +54,7 @@ def printerState():
|
||||||
@api.route("/printer/tool", methods=["POST"])
|
@api.route("/printer/tool", methods=["POST"])
|
||||||
@restricted_access
|
@restricted_access
|
||||||
def printerToolCommand():
|
def printerToolCommand():
|
||||||
if not printer.isOperational():
|
if not printer.is_operational():
|
||||||
return make_response("Printer is not operational", 409)
|
return make_response("Printer is not operational", 409)
|
||||||
|
|
||||||
valid_commands = {
|
valid_commands = {
|
||||||
|
|
@ -78,7 +78,7 @@ def printerToolCommand():
|
||||||
if not tool.startswith("tool"):
|
if not tool.startswith("tool"):
|
||||||
return make_response("Invalid tool for selection: %s" % tool, 400)
|
return make_response("Invalid tool for selection: %s" % tool, 400)
|
||||||
|
|
||||||
printer.changeTool(tool)
|
printer.change_tool(tool)
|
||||||
|
|
||||||
##~~ temperature
|
##~~ temperature
|
||||||
elif command == "target":
|
elif command == "target":
|
||||||
|
|
@ -95,7 +95,7 @@ def printerToolCommand():
|
||||||
|
|
||||||
# perform the actual temperature commands
|
# perform the actual temperature commands
|
||||||
for tool in validated_values.keys():
|
for tool in validated_values.keys():
|
||||||
printer.setTemperature(tool, validated_values[tool])
|
printer.set_temperature(tool, validated_values[tool])
|
||||||
|
|
||||||
##~~ temperature offset
|
##~~ temperature offset
|
||||||
elif command == "offset":
|
elif command == "offset":
|
||||||
|
|
@ -113,11 +113,11 @@ def printerToolCommand():
|
||||||
validated_values[tool] = value
|
validated_values[tool] = value
|
||||||
|
|
||||||
# set the offsets
|
# set the offsets
|
||||||
printer.setTemperatureOffset(validated_values)
|
printer.set_temperature_offset(validated_values)
|
||||||
|
|
||||||
##~~ extrusion
|
##~~ extrusion
|
||||||
elif command == "extrude":
|
elif command == "extrude":
|
||||||
if printer.isPrinting():
|
if printer.is_printing():
|
||||||
# do not extrude when a print job is running
|
# do not extrude when a print job is running
|
||||||
return make_response("Printer is currently printing", 409)
|
return make_response("Printer is currently printing", 409)
|
||||||
|
|
||||||
|
|
@ -131,7 +131,7 @@ def printerToolCommand():
|
||||||
if not isinstance(factor, (int, long, float)):
|
if not isinstance(factor, (int, long, float)):
|
||||||
return make_response("Not a number for flow rate: %r" % factor, 400)
|
return make_response("Not a number for flow rate: %r" % factor, 400)
|
||||||
try:
|
try:
|
||||||
printer.flowRate(factor)
|
printer.flow_rate(factor)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
return make_response("Invalid value for flow rate: %s" % e.message, 400)
|
return make_response("Invalid value for flow rate: %s" % e.message, 400)
|
||||||
|
|
||||||
|
|
@ -140,7 +140,7 @@ def printerToolCommand():
|
||||||
|
|
||||||
@api.route("/printer/tool", methods=["GET"])
|
@api.route("/printer/tool", methods=["GET"])
|
||||||
def printerToolState():
|
def printerToolState():
|
||||||
if not printer.isOperational():
|
if not printer.is_operational():
|
||||||
return make_response("Printer is not operational", 409)
|
return make_response("Printer is not operational", 409)
|
||||||
|
|
||||||
def deleteBed(x):
|
def deleteBed(x):
|
||||||
|
|
@ -159,7 +159,7 @@ def printerToolState():
|
||||||
@api.route("/printer/bed", methods=["POST"])
|
@api.route("/printer/bed", methods=["POST"])
|
||||||
@restricted_access
|
@restricted_access
|
||||||
def printerBedCommand():
|
def printerBedCommand():
|
||||||
if not printer.isOperational():
|
if not printer.is_operational():
|
||||||
return make_response("Printer is not operational", 409)
|
return make_response("Printer is not operational", 409)
|
||||||
|
|
||||||
valid_commands = {
|
valid_commands = {
|
||||||
|
|
@ -179,7 +179,7 @@ def printerBedCommand():
|
||||||
return make_response("Not a number: %r" % target, 400)
|
return make_response("Not a number: %r" % target, 400)
|
||||||
|
|
||||||
# perform the actual temperature command
|
# perform the actual temperature command
|
||||||
printer.setTemperature("bed", target)
|
printer.set_temperature("bed", target)
|
||||||
|
|
||||||
##~~ temperature offset
|
##~~ temperature offset
|
||||||
elif command == "offset":
|
elif command == "offset":
|
||||||
|
|
@ -192,14 +192,14 @@ def printerBedCommand():
|
||||||
return make_response("Offset not in range [-50, 50]: %f" % offset, 400)
|
return make_response("Offset not in range [-50, 50]: %f" % offset, 400)
|
||||||
|
|
||||||
# set the offsets
|
# set the offsets
|
||||||
printer.setTemperatureOffset({"bed": offset})
|
printer.set_temperature_offset({"bed": offset})
|
||||||
|
|
||||||
return NO_CONTENT
|
return NO_CONTENT
|
||||||
|
|
||||||
|
|
||||||
@api.route("/printer/bed", methods=["GET"])
|
@api.route("/printer/bed", methods=["GET"])
|
||||||
def printerBedState():
|
def printerBedState():
|
||||||
if not printer.isOperational():
|
if not printer.is_operational():
|
||||||
return make_response("Printer is not operational", 409)
|
return make_response("Printer is not operational", 409)
|
||||||
|
|
||||||
def deleteTools(x):
|
def deleteTools(x):
|
||||||
|
|
@ -223,7 +223,7 @@ def printerBedState():
|
||||||
@api.route("/printer/printhead", methods=["POST"])
|
@api.route("/printer/printhead", methods=["POST"])
|
||||||
@restricted_access
|
@restricted_access
|
||||||
def printerPrintheadCommand():
|
def printerPrintheadCommand():
|
||||||
if not printer.isOperational() or printer.isPrinting():
|
if not printer.is_operational() or printer.is_printing():
|
||||||
# do not jog when a print job is running or we don't have a connection
|
# do not jog when a print job is running or we don't have a connection
|
||||||
return make_response("Printer is not operational or currently printing", 409)
|
return make_response("Printer is not operational or currently printing", 409)
|
||||||
|
|
||||||
|
|
@ -269,7 +269,7 @@ def printerPrintheadCommand():
|
||||||
if not isinstance(factor, (int, long, float)):
|
if not isinstance(factor, (int, long, float)):
|
||||||
return make_response("Not a number for feed rate: %r" % factor, 400)
|
return make_response("Not a number for feed rate: %r" % factor, 400)
|
||||||
try:
|
try:
|
||||||
printer.feedRate(factor)
|
printer.feed_rate(factor)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
return make_response("Invalid value for feed rate: %s" % e.message, 400)
|
return make_response("Invalid value for feed rate: %s" % e.message, 400)
|
||||||
|
|
||||||
|
|
@ -285,7 +285,7 @@ def printerSdCommand():
|
||||||
if not settings().getBoolean(["feature", "sdSupport"]):
|
if not settings().getBoolean(["feature", "sdSupport"]):
|
||||||
return make_response("SD support is disabled", 404)
|
return make_response("SD support is disabled", 404)
|
||||||
|
|
||||||
if not printer.isOperational() or printer.isPrinting() or printer.isPaused():
|
if not printer.is_operational() or printer.is_printing() or printer.is_paused():
|
||||||
return make_response("Printer is not operational or currently busy", 409)
|
return make_response("Printer is not operational or currently busy", 409)
|
||||||
|
|
||||||
valid_commands = {
|
valid_commands = {
|
||||||
|
|
@ -298,11 +298,11 @@ def printerSdCommand():
|
||||||
return response
|
return response
|
||||||
|
|
||||||
if command == "init":
|
if command == "init":
|
||||||
printer.initSdCard()
|
printer.init_sd_card()
|
||||||
elif command == "refresh":
|
elif command == "refresh":
|
||||||
printer.refreshSdFiles()
|
printer.refresh_sd_files()
|
||||||
elif command == "release":
|
elif command == "release":
|
||||||
printer.releaseSdCard()
|
printer.release_sd_card()
|
||||||
|
|
||||||
return NO_CONTENT
|
return NO_CONTENT
|
||||||
|
|
||||||
|
|
@ -312,7 +312,7 @@ def printerSdState():
|
||||||
if not settings().getBoolean(["feature", "sdSupport"]):
|
if not settings().getBoolean(["feature", "sdSupport"]):
|
||||||
return make_response("SD support is disabled", 404)
|
return make_response("SD support is disabled", 404)
|
||||||
|
|
||||||
return jsonify(ready=printer.isSdReady())
|
return jsonify(ready=printer.is_sd_ready())
|
||||||
|
|
||||||
|
|
||||||
##~~ Commands
|
##~~ Commands
|
||||||
|
|
@ -321,7 +321,7 @@ def printerSdState():
|
||||||
@api.route("/printer/command", methods=["POST"])
|
@api.route("/printer/command", methods=["POST"])
|
||||||
@restricted_access
|
@restricted_access
|
||||||
def printerCommand():
|
def printerCommand():
|
||||||
if not printer.isOperational():
|
if not printer.is_operational():
|
||||||
return make_response("Printer is not operational", 409)
|
return make_response("Printer is not operational", 409)
|
||||||
|
|
||||||
if not "application/json" in request.headers["Content-Type"]:
|
if not "application/json" in request.headers["Content-Type"]:
|
||||||
|
|
@ -364,13 +364,13 @@ def getCustomControls():
|
||||||
|
|
||||||
|
|
||||||
def _getTemperatureData(filter):
|
def _getTemperatureData(filter):
|
||||||
if not printer.isOperational():
|
if not printer.is_operational():
|
||||||
return make_response("Printer is not operational", 409)
|
return make_response("Printer is not operational", 409)
|
||||||
|
|
||||||
tempData = printer.getCurrentTemperatures()
|
tempData = printer.get_current_temperatures()
|
||||||
|
|
||||||
if "history" in request.values.keys() and request.values["history"] in valid_boolean_trues:
|
if "history" in request.values.keys() and request.values["history"] in valid_boolean_trues:
|
||||||
tempHistory = printer.getTemperatureHistory()
|
tempHistory = printer.get_temperature_history()
|
||||||
|
|
||||||
limit = 300
|
limit = 300
|
||||||
if "limit" in request.values.keys() and unicode(request.values["limit"]).isnumeric():
|
if "limit" in request.values.keys() and unicode(request.values["limit"]).isnumeric():
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ from flask.exceptions import JSONBadRequest
|
||||||
|
|
||||||
from octoprint.events import eventManager, Events
|
from octoprint.events import eventManager, Events
|
||||||
from octoprint.settings import settings
|
from octoprint.settings import settings
|
||||||
from octoprint.printer import getConnectionOptions
|
from octoprint.printer import get_connection_options
|
||||||
|
|
||||||
from octoprint.server import admin_permission
|
from octoprint.server import admin_permission
|
||||||
from octoprint.server.api import api
|
from octoprint.server.api import api
|
||||||
|
|
@ -28,7 +28,7 @@ import octoprint.util
|
||||||
def getSettings():
|
def getSettings():
|
||||||
s = settings()
|
s = settings()
|
||||||
|
|
||||||
connectionOptions = getConnectionOptions()
|
connectionOptions = get_connection_options()
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
"api": {
|
"api": {
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,10 @@ import octoprint.timelapse
|
||||||
import octoprint.server
|
import octoprint.server
|
||||||
from octoprint.events import Events
|
from octoprint.events import Events
|
||||||
|
|
||||||
|
import octoprint.printer
|
||||||
|
|
||||||
class PrinterStateConnection(sockjs.tornado.SockJSConnection):
|
|
||||||
|
class PrinterStateConnection(sockjs.tornado.SockJSConnection, octoprint.printer.PrinterCallback):
|
||||||
def __init__(self, printer, fileManager, analysisQueue, userManager, eventManager, pluginManager, session):
|
def __init__(self, printer, fileManager, analysisQueue, userManager, eventManager, pluginManager, session):
|
||||||
sockjs.tornado.SockJSConnection.__init__(self, session)
|
sockjs.tornado.SockJSConnection.__init__(self, session)
|
||||||
|
|
||||||
|
|
@ -49,7 +51,7 @@ class PrinterStateConnection(sockjs.tornado.SockJSConnection):
|
||||||
# connected => update the API key, might be necessary if the client was left open while the server restarted
|
# connected => update the API key, might be necessary if the client was left open while the server restarted
|
||||||
self._emit("connected", {"apikey": octoprint.server.UI_API_KEY, "version": octoprint.server.VERSION, "display_version": octoprint.server.DISPLAY_VERSION})
|
self._emit("connected", {"apikey": octoprint.server.UI_API_KEY, "version": octoprint.server.VERSION, "display_version": octoprint.server.DISPLAY_VERSION})
|
||||||
|
|
||||||
self._printer.registerCallback(self)
|
self._printer.register_callback(self)
|
||||||
self._fileManager.register_slicingprogress_callback(self)
|
self._fileManager.register_slicingprogress_callback(self)
|
||||||
octoprint.timelapse.registerCallback(self)
|
octoprint.timelapse.registerCallback(self)
|
||||||
self._pluginManager.register_client(self)
|
self._pluginManager.register_client(self)
|
||||||
|
|
@ -62,7 +64,7 @@ class PrinterStateConnection(sockjs.tornado.SockJSConnection):
|
||||||
|
|
||||||
def on_close(self):
|
def on_close(self):
|
||||||
self._logger.info("Client connection closed: %s" % self._remoteAddress)
|
self._logger.info("Client connection closed: %s" % self._remoteAddress)
|
||||||
self._printer.unregisterCallback(self)
|
self._printer.unregister_callback(self)
|
||||||
self._fileManager.unregister_slicingprogress_callback(self)
|
self._fileManager.unregister_slicingprogress_callback(self)
|
||||||
octoprint.timelapse.unregisterCallback(self)
|
octoprint.timelapse.unregisterCallback(self)
|
||||||
self._pluginManager.unregister_client(self)
|
self._pluginManager.unregister_client(self)
|
||||||
|
|
@ -74,7 +76,7 @@ class PrinterStateConnection(sockjs.tornado.SockJSConnection):
|
||||||
def on_message(self, message):
|
def on_message(self, message):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def sendCurrentData(self, data):
|
def on_printer_send_current_data(self, data):
|
||||||
# add current temperature, log and message backlogs to sent data
|
# add current temperature, log and message backlogs to sent data
|
||||||
with self._temperatureBacklogMutex:
|
with self._temperatureBacklogMutex:
|
||||||
temperatures = self._temperatureBacklog
|
temperatures = self._temperatureBacklog
|
||||||
|
|
@ -92,7 +94,7 @@ class PrinterStateConnection(sockjs.tornado.SockJSConnection):
|
||||||
if "job" in data and data["job"] is not None \
|
if "job" in data and data["job"] is not None \
|
||||||
and "file" in data["job"] and "name" in data["job"]["file"] and "origin" in data["job"]["file"] \
|
and "file" in data["job"] and "name" in data["job"]["file"] and "origin" in data["job"]["file"] \
|
||||||
and data["job"]["file"]["name"] is not None and data["job"]["file"]["origin"] is not None \
|
and data["job"]["file"]["name"] is not None and data["job"]["file"]["origin"] is not None \
|
||||||
and (self._printer.isPrinting() or self._printer.isPaused()):
|
and (self._printer.is_printing() or self._printer.is_paused()):
|
||||||
busy_files.append(dict(origin=data["job"]["file"]["origin"], name=data["job"]["file"]["name"]))
|
busy_files.append(dict(origin=data["job"]["file"]["origin"], name=data["job"]["file"]["name"]))
|
||||||
|
|
||||||
data.update({
|
data.update({
|
||||||
|
|
@ -103,13 +105,13 @@ class PrinterStateConnection(sockjs.tornado.SockJSConnection):
|
||||||
})
|
})
|
||||||
self._emit("current", data)
|
self._emit("current", data)
|
||||||
|
|
||||||
def sendHistoryData(self, data):
|
def on_printer_send_initial_data(self, data):
|
||||||
self._emit("history", data)
|
self._emit("history", data)
|
||||||
|
|
||||||
def sendEvent(self, type, payload=None):
|
def sendEvent(self, type, payload=None):
|
||||||
self._emit("event", {"type": type, "payload": payload})
|
self._emit("event", {"type": type, "payload": payload})
|
||||||
|
|
||||||
def sendFeedbackCommandOutput(self, name, output):
|
def on_printer_received_registered_message(self, name, output):
|
||||||
self._emit("feedbackCommandOutput", {"name": name, "output": output})
|
self._emit("feedbackCommandOutput", {"name": name, "output": output})
|
||||||
|
|
||||||
def sendTimelapseConfig(self, timelapseConfig):
|
def sendTimelapseConfig(self, timelapseConfig):
|
||||||
|
|
@ -123,15 +125,15 @@ class PrinterStateConnection(sockjs.tornado.SockJSConnection):
|
||||||
def sendPluginMessage(self, plugin, data):
|
def sendPluginMessage(self, plugin, data):
|
||||||
self._emit("plugin", dict(plugin=plugin, data=data))
|
self._emit("plugin", dict(plugin=plugin, data=data))
|
||||||
|
|
||||||
def addLog(self, data):
|
def on_printer_add_log(self, data):
|
||||||
with self._logBacklogMutex:
|
with self._logBacklogMutex:
|
||||||
self._logBacklog.append(data)
|
self._logBacklog.append(data)
|
||||||
|
|
||||||
def addMessage(self, data):
|
def on_printer_add_message(self, data):
|
||||||
with self._messageBacklogMutex:
|
with self._messageBacklogMutex:
|
||||||
self._messageBacklog.append(data)
|
self._messageBacklog.append(data)
|
||||||
|
|
||||||
def addTemperature(self, data):
|
def on_printer_add_temperature(self, data):
|
||||||
with self._temperatureBacklogMutex:
|
with self._temperatureBacklogMutex:
|
||||||
self._temperatureBacklog.append(data)
|
self._temperatureBacklog.append(data)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ class GcodeWatchdogHandler(watchdog.events.PatternMatchingEventHandler):
|
||||||
# determine current job
|
# determine current job
|
||||||
currentFilename = None
|
currentFilename = None
|
||||||
currentOrigin = None
|
currentOrigin = None
|
||||||
currentJob = self._printer.getCurrentJob()
|
currentJob = self._printer.get_current_job()
|
||||||
if currentJob is not None and "file" in currentJob.keys():
|
if currentJob is not None and "file" in currentJob.keys():
|
||||||
currentJobFile = currentJob["file"]
|
currentJobFile = currentJob["file"]
|
||||||
if "name" in currentJobFile.keys() and "origin" in currentJobFile.keys():
|
if "name" in currentJobFile.keys() and "origin" in currentJobFile.keys():
|
||||||
|
|
@ -59,7 +59,7 @@ class GcodeWatchdogHandler(watchdog.events.PatternMatchingEventHandler):
|
||||||
return
|
return
|
||||||
|
|
||||||
# prohibit overwriting currently selected file while it's being printed
|
# prohibit overwriting currently selected file while it's being printed
|
||||||
if futureFilename == currentFilename and currentOrigin == octoprint.filemanager.FileDestinations.LOCAL and self._printer.isPrinting() or self._printer.isPaused():
|
if futureFilename == currentFilename and currentOrigin == octoprint.filemanager.FileDestinations.LOCAL and self._printer.is_printing() or self._printer.is_paused():
|
||||||
return
|
return
|
||||||
|
|
||||||
added_file = self._file_manager.add_file(octoprint.filemanager.FileDestinations.LOCAL,
|
added_file = self._file_manager.add_file(octoprint.filemanager.FileDestinations.LOCAL,
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,12 @@
|
||||||
<div id="drop_overlay_background"></div>
|
<div id="drop_overlay_background"></div>
|
||||||
<div id="drop_overlay_wrapper">
|
<div id="drop_overlay_wrapper">
|
||||||
{% if enableSdSupport %}
|
{% if enableSdSupport %}
|
||||||
<div class="dropzone" id="drop_locally"><span class="centered"><i class="icon-upload-alt"></i><br>{{ _('Upload locally') }}</span></div>
|
<div class="dropzone" id="drop_locally"><span class="text"><i class="icon-upload-alt"></i><br>{{ _('Upload locally') }}</span></div>
|
||||||
<div class="dropzone_background" id="drop_locally_background"></div>
|
<div class="dropzone_background" id="drop_locally_background"></div>
|
||||||
<div class="dropzone" id="drop_sd"><span class="centered"><i class="icon-upload-alt"></i><br>{{ _('Upload to SD') }}<br><small data-bind="visible: !isSdReady()">({{ _('SD not initialized') }})</small></span></div>
|
<div class="dropzone" id="drop_sd"><span class="text"><i class="icon-upload-alt"></i><br>{{ _('Upload to SD') }}<br><small data-bind="visible: !isSdReady()">({{ _('SD not initialized') }})</small></span></div>
|
||||||
<div class="dropzone_background" id="drop_sd_background"></div>
|
<div class="dropzone_background" id="drop_sd_background"></div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="dropzone" id="drop"><span class="centered"><i class="icon-upload-alt"></i><br>{{ _('Upload') }}</span></div>
|
<div class="dropzone" id="drop"><span class="text"><i class="icon-upload-alt"></i><br>{{ _('Upload') }}</span></div>
|
||||||
<div class="dropzone_background" id="drop_background"></div>
|
<div class="dropzone_background" id="drop_background"></div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -223,15 +223,15 @@ class MachineCom(object):
|
||||||
if settings().get(["feature", "sdSupport"]):
|
if settings().get(["feature", "sdSupport"]):
|
||||||
self._sdFileList = False
|
self._sdFileList = False
|
||||||
self._sdFiles = []
|
self._sdFiles = []
|
||||||
self._callback.mcSdFiles([])
|
self._callback.on_comm_sd_files([])
|
||||||
|
|
||||||
oldState = self.getStateString()
|
oldState = self.getStateString()
|
||||||
self._state = newState
|
self._state = newState
|
||||||
self._log('Changing monitoring state from \'%s\' to \'%s\'' % (oldState, self.getStateString()))
|
self._log('Changing monitoring state from \'%s\' to \'%s\'' % (oldState, self.getStateString()))
|
||||||
self._callback.mcStateChange(newState)
|
self._callback.on_comm_state_change(newState)
|
||||||
|
|
||||||
def _log(self, message):
|
def _log(self, message):
|
||||||
self._callback.mcLog(message)
|
self._callback.on_comm_log(message)
|
||||||
self._serialLogger.debug(message)
|
self._serialLogger.debug(message)
|
||||||
|
|
||||||
def _addToLastLines(self, cmd):
|
def _addToLastLines(self, cmd):
|
||||||
|
|
@ -494,7 +494,7 @@ class MachineCom(object):
|
||||||
|
|
||||||
self.sendCommand("M28 %s" % remoteFilename)
|
self.sendCommand("M28 %s" % remoteFilename)
|
||||||
eventManager().fire(Events.TRANSFER_STARTED, {"local": localFilename, "remote": remoteFilename})
|
eventManager().fire(Events.TRANSFER_STARTED, {"local": localFilename, "remote": remoteFilename})
|
||||||
self._callback.mcFileTransferStarted(remoteFilename, self._currentFile.getFilesize())
|
self._callback.on_comm_file_transfer_started(remoteFilename, self._currentFile.getFilesize())
|
||||||
|
|
||||||
def selectFile(self, filename, sd):
|
def selectFile(self, filename, sd):
|
||||||
if self.isBusy():
|
if self.isBusy():
|
||||||
|
|
@ -512,7 +512,7 @@ class MachineCom(object):
|
||||||
"file": self._currentFile.getFilename(),
|
"file": self._currentFile.getFilename(),
|
||||||
"origin": self._currentFile.getFileLocation()
|
"origin": self._currentFile.getFileLocation()
|
||||||
})
|
})
|
||||||
self._callback.mcFileSelected(filename, self._currentFile.getFilesize(), False)
|
self._callback.on_comm_file_selected(filename, self._currentFile.getFilesize(), False)
|
||||||
|
|
||||||
def unselectFile(self):
|
def unselectFile(self):
|
||||||
if self.isBusy():
|
if self.isBusy():
|
||||||
|
|
@ -520,7 +520,7 @@ class MachineCom(object):
|
||||||
|
|
||||||
self._currentFile = None
|
self._currentFile = None
|
||||||
eventManager().fire(Events.FILE_DESELECTED)
|
eventManager().fire(Events.FILE_DESELECTED)
|
||||||
self._callback.mcFileSelected(None, None, False)
|
self._callback.on_comm_file_selected(None, None, False)
|
||||||
|
|
||||||
def cancelPrint(self):
|
def cancelPrint(self):
|
||||||
if not self.isOperational() or self.isStreaming():
|
if not self.isOperational() or self.isStreaming():
|
||||||
|
|
@ -625,7 +625,7 @@ class MachineCom(object):
|
||||||
if settings().getBoolean(["feature", "sdAlwaysAvailable"]):
|
if settings().getBoolean(["feature", "sdAlwaysAvailable"]):
|
||||||
self._sdAvailable = True
|
self._sdAvailable = True
|
||||||
self.refreshSdFiles()
|
self.refreshSdFiles()
|
||||||
self._callback.mcSdStateChange(self._sdAvailable)
|
self._callback.on_comm_sd_state_change(self._sdAvailable)
|
||||||
|
|
||||||
def releaseSdCard(self):
|
def releaseSdCard(self):
|
||||||
if not self.isOperational() or (self.isBusy() and self.isSdFileSelected()):
|
if not self.isOperational() or (self.isBusy() and self.isSdFileSelected()):
|
||||||
|
|
@ -636,8 +636,8 @@ class MachineCom(object):
|
||||||
self._sdAvailable = False
|
self._sdAvailable = False
|
||||||
self._sdFiles = []
|
self._sdFiles = []
|
||||||
|
|
||||||
self._callback.mcSdStateChange(self._sdAvailable)
|
self._callback.on_comm_sd_state_change(self._sdAvailable)
|
||||||
self._callback.mcSdFiles(self._sdFiles)
|
self._callback.on_comm_sd_files(self._sdFiles)
|
||||||
|
|
||||||
##~~ communication monitoring and handling
|
##~~ communication monitoring and handling
|
||||||
|
|
||||||
|
|
@ -761,7 +761,7 @@ class MachineCom(object):
|
||||||
self.setPause(False)
|
self.setPause(False)
|
||||||
elif action_command == "disconnect":
|
elif action_command == "disconnect":
|
||||||
self._log("Disconnecting on request of the printer...")
|
self._log("Disconnecting on request of the printer...")
|
||||||
self._callback.mcForceDisconnect()
|
self._callback.on_comm_force_disconnect()
|
||||||
else:
|
else:
|
||||||
for hook in self._printer_action_hooks:
|
for hook in self._printer_action_hooks:
|
||||||
self._printer_action_hooks[hook](self, line, action_command)
|
self._printer_action_hooks[hook](self, line, action_command)
|
||||||
|
|
@ -803,7 +803,7 @@ class MachineCom(object):
|
||||||
##~~ Temperature processing
|
##~~ Temperature processing
|
||||||
if ' T:' in line or line.startswith('T:') or ' T0:' in line or line.startswith('T0:'):
|
if ' T:' in line or line.startswith('T:') or ' T0:' in line or line.startswith('T0:'):
|
||||||
self._processTemperatures(line)
|
self._processTemperatures(line)
|
||||||
self._callback.mcTempUpdate(self._temp, self._bedTemp)
|
self._callback.on_comm_temperature_update(self._temp, self._bedTemp)
|
||||||
|
|
||||||
elif supportRepetierTargetTemp and ('TargetExtr' in line or 'TargetBed' in line):
|
elif supportRepetierTargetTemp and ('TargetExtr' in line or 'TargetBed' in line):
|
||||||
matchExtr = self._regex_repetierTempExtr.match(line)
|
matchExtr = self._regex_repetierTempExtr.match(line)
|
||||||
|
|
@ -818,7 +818,7 @@ class MachineCom(object):
|
||||||
self._temp[toolNum] = (actual, target)
|
self._temp[toolNum] = (actual, target)
|
||||||
else:
|
else:
|
||||||
self._temp[toolNum] = (None, target)
|
self._temp[toolNum] = (None, target)
|
||||||
self._callback.mcTempUpdate(self._temp, self._bedTemp)
|
self._callback.on_comm_temperature_update(self._temp, self._bedTemp)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
elif matchBed is not None:
|
elif matchBed is not None:
|
||||||
|
|
@ -829,7 +829,7 @@ class MachineCom(object):
|
||||||
self._bedTemp = (actual, target)
|
self._bedTemp = (actual, target)
|
||||||
else:
|
else:
|
||||||
self._bedTemp = (None, target)
|
self._bedTemp = (None, target)
|
||||||
self._callback.mcTempUpdate(self._temp, self._bedTemp)
|
self._callback.on_comm_temperature_update(self._temp, self._bedTemp)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
@ -842,7 +842,7 @@ class MachineCom(object):
|
||||||
elif 'SD init fail' in line or 'volume.init failed' in line or 'openRoot failed' in line:
|
elif 'SD init fail' in line or 'volume.init failed' in line or 'openRoot failed' in line:
|
||||||
self._sdAvailable = False
|
self._sdAvailable = False
|
||||||
self._sdFiles = []
|
self._sdFiles = []
|
||||||
self._callback.mcSdStateChange(self._sdAvailable)
|
self._callback.on_comm_sd_state_change(self._sdAvailable)
|
||||||
elif 'Not SD printing' in line:
|
elif 'Not SD printing' in line:
|
||||||
if self.isSdFileSelected() and self.isPrinting():
|
if self.isSdFileSelected() and self.isPrinting():
|
||||||
# something went wrong, printer is reporting that we actually are not printing right now...
|
# something went wrong, printer is reporting that we actually are not printing right now...
|
||||||
|
|
@ -851,18 +851,18 @@ class MachineCom(object):
|
||||||
elif 'SD card ok' in line and not self._sdAvailable:
|
elif 'SD card ok' in line and not self._sdAvailable:
|
||||||
self._sdAvailable = True
|
self._sdAvailable = True
|
||||||
self.refreshSdFiles()
|
self.refreshSdFiles()
|
||||||
self._callback.mcSdStateChange(self._sdAvailable)
|
self._callback.on_comm_sd_state_change(self._sdAvailable)
|
||||||
elif 'Begin file list' in line:
|
elif 'Begin file list' in line:
|
||||||
self._sdFiles = []
|
self._sdFiles = []
|
||||||
self._sdFileList = True
|
self._sdFileList = True
|
||||||
elif 'End file list' in line:
|
elif 'End file list' in line:
|
||||||
self._sdFileList = False
|
self._sdFileList = False
|
||||||
self._callback.mcSdFiles(self._sdFiles)
|
self._callback.on_comm_sd_files(self._sdFiles)
|
||||||
elif 'SD printing byte' in line:
|
elif 'SD printing byte' in line:
|
||||||
# answer to M27, at least on Marlin, Repetier and Sprinter: "SD printing byte %d/%d"
|
# answer to M27, at least on Marlin, Repetier and Sprinter: "SD printing byte %d/%d"
|
||||||
match = self._regex_sdPrintingByte.search(line)
|
match = self._regex_sdPrintingByte.search(line)
|
||||||
self._currentFile.setFilepos(int(match.group(1)))
|
self._currentFile.setFilepos(int(match.group(1)))
|
||||||
self._callback.mcProgress()
|
self._callback.on_comm_progress()
|
||||||
elif 'File opened' in line:
|
elif 'File opened' in line:
|
||||||
# answer to M23, at least on Marlin, Repetier and Sprinter: "File opened:%s Size:%d"
|
# answer to M23, at least on Marlin, Repetier and Sprinter: "File opened:%s Size:%d"
|
||||||
match = self._regex_sdFileOpened.search(line)
|
match = self._regex_sdFileOpened.search(line)
|
||||||
|
|
@ -875,7 +875,7 @@ class MachineCom(object):
|
||||||
elif 'File selected' in line:
|
elif 'File selected' in line:
|
||||||
# final answer to M23, at least on Marlin, Repetier and Sprinter: "File selected"
|
# final answer to M23, at least on Marlin, Repetier and Sprinter: "File selected"
|
||||||
if self._currentFile is not None:
|
if self._currentFile is not None:
|
||||||
self._callback.mcFileSelected(self._currentFile.getFilename(), self._currentFile.getFilesize(), True)
|
self._callback.on_comm_file_selected(self._currentFile.getFilename(), self._currentFile.getFilesize(), True)
|
||||||
eventManager().fire(Events.FILE_SELECTED, {
|
eventManager().fire(Events.FILE_SELECTED, {
|
||||||
"file": self._currentFile.getFilename(),
|
"file": self._currentFile.getFilename(),
|
||||||
"origin": self._currentFile.getFileLocation()
|
"origin": self._currentFile.getFileLocation()
|
||||||
|
|
@ -887,7 +887,7 @@ class MachineCom(object):
|
||||||
elif 'Done printing file' in line:
|
elif 'Done printing file' in line:
|
||||||
# printer is reporting file finished printing
|
# printer is reporting file finished printing
|
||||||
self._sdFilePos = 0
|
self._sdFilePos = 0
|
||||||
self._callback.mcPrintjobDone()
|
self._callback.on_comm_print_job_done()
|
||||||
self._changeState(self.STATE_OPERATIONAL)
|
self._changeState(self.STATE_OPERATIONAL)
|
||||||
eventManager().fire(Events.PRINT_DONE, {
|
eventManager().fire(Events.PRINT_DONE, {
|
||||||
"file": self._currentFile.getFilename(),
|
"file": self._currentFile.getFilename(),
|
||||||
|
|
@ -904,7 +904,7 @@ class MachineCom(object):
|
||||||
and not line.startswith('Resend:') \
|
and not line.startswith('Resend:') \
|
||||||
and line != 'echo:Unknown command:""\n' \
|
and line != 'echo:Unknown command:""\n' \
|
||||||
and self.isOperational():
|
and self.isOperational():
|
||||||
self._callback.mcMessage(line)
|
self._callback.on_comm_message(line)
|
||||||
|
|
||||||
##~~ Parsing for feedback commands
|
##~~ Parsing for feedback commands
|
||||||
if feedbackControls:
|
if feedbackControls:
|
||||||
|
|
@ -922,7 +922,7 @@ class MachineCom(object):
|
||||||
formatFunction = unicode.format
|
formatFunction = unicode.format
|
||||||
|
|
||||||
if formatFunction is not None:
|
if formatFunction is not None:
|
||||||
self._callback.mcReceivedRegisteredMessage(name, formatFunction(template, *(match.groups("n/a"))))
|
self._callback.on_comm_received_registered_message(name, formatFunction(template, *(match.groups("n/a"))))
|
||||||
except:
|
except:
|
||||||
if not name in feedbackErrors:
|
if not name in feedbackErrors:
|
||||||
self._logger.info("Something went wrong with feedbackControl \"%s\": " % name, exc_info=True)
|
self._logger.info("Something went wrong with feedbackControl \"%s\": " % name, exc_info=True)
|
||||||
|
|
@ -1171,7 +1171,7 @@ class MachineCom(object):
|
||||||
|
|
||||||
self._currentFile = None
|
self._currentFile = None
|
||||||
self._changeState(self.STATE_OPERATIONAL)
|
self._changeState(self.STATE_OPERATIONAL)
|
||||||
self._callback.mcFileTransferDone(remote)
|
self._callback.on_comm_file_transfer_done(remote)
|
||||||
eventManager().fire(Events.TRANSFER_DONE, payload)
|
eventManager().fire(Events.TRANSFER_DONE, payload)
|
||||||
self.refreshSdFiles()
|
self.refreshSdFiles()
|
||||||
else:
|
else:
|
||||||
|
|
@ -1181,7 +1181,7 @@ class MachineCom(object):
|
||||||
"origin": self._currentFile.getFileLocation(),
|
"origin": self._currentFile.getFileLocation(),
|
||||||
"time": self.getPrintTime()
|
"time": self.getPrintTime()
|
||||||
}
|
}
|
||||||
self._callback.mcPrintjobDone()
|
self._callback.on_comm_print_job_done()
|
||||||
self._changeState(self.STATE_OPERATIONAL)
|
self._changeState(self.STATE_OPERATIONAL)
|
||||||
eventManager().fire(Events.PRINT_DONE, payload)
|
eventManager().fire(Events.PRINT_DONE, payload)
|
||||||
|
|
||||||
|
|
@ -1193,7 +1193,7 @@ class MachineCom(object):
|
||||||
line = self._getNext()
|
line = self._getNext()
|
||||||
if line is not None:
|
if line is not None:
|
||||||
self._sendCommand(line, True)
|
self._sendCommand(line, True)
|
||||||
self._callback.mcProgress()
|
self._callback.on_comm_progress()
|
||||||
|
|
||||||
def _handleResendRequest(self, line):
|
def _handleResendRequest(self, line):
|
||||||
lineToResend = None
|
lineToResend = None
|
||||||
|
|
@ -1325,7 +1325,7 @@ class MachineCom(object):
|
||||||
z = float(match.group(1))
|
z = float(match.group(1))
|
||||||
if self._currentZ != z:
|
if self._currentZ != z:
|
||||||
self._currentZ = z
|
self._currentZ = z
|
||||||
self._callback.mcZChange(z)
|
self._callback.on_comm_z_change(z)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
return cmd
|
return cmd
|
||||||
|
|
@ -1419,43 +1419,46 @@ class MachineCom(object):
|
||||||
### MachineCom callback ################################################################################################
|
### MachineCom callback ################################################################################################
|
||||||
|
|
||||||
class MachineComPrintCallback(object):
|
class MachineComPrintCallback(object):
|
||||||
def mcLog(self, message):
|
def on_comm_log(self, message):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def mcTempUpdate(self, temp, bedTemp):
|
def on_comm_temperature_update(self, temp, bedTemp):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def mcStateChange(self, state):
|
def on_comm_state_change(self, state):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def mcMessage(self, message):
|
def on_comm_message(self, message):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def mcProgress(self):
|
def on_comm_progress(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def mcZChange(self, newZ):
|
def on_comm_print_job_done(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def mcFileSelected(self, filename, filesize, sd):
|
def on_comm_z_change(self, newZ):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def mcSdStateChange(self, sdReady):
|
def on_comm_file_selected(self, filename, filesize, sd):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def mcSdFiles(self, files):
|
def on_comm_sd_state_change(self, sdReady):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def mcSdPrintingDone(self):
|
def on_comm_sd_files(self, files):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def mcFileTransferStarted(self, filename, filesize):
|
def on_comm_file_transfer_started(self, filename, filesize):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def mcReceivedRegisteredMessage(self, command, message):
|
def on_comm_file_transfer_done(self, filename):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def mcForceDisconnect(self):
|
def on_comm_received_registered_message(self, command, message):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def on_comm_force_disconnect(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
### Printing file information classes ##################################################################################
|
### Printing file information classes ##################################################################################
|
||||||
|
|
|
||||||
|
|
@ -263,6 +263,8 @@ class VirtualPrinter():
|
||||||
self.outgoing.put("End file list")
|
self.outgoing.put("End file list")
|
||||||
|
|
||||||
def _selectSdFile(self, filename):
|
def _selectSdFile(self, filename):
|
||||||
|
if filename.startswith("/"):
|
||||||
|
filename = filename[1:]
|
||||||
file = os.path.join(self._virtualSd, filename).lower()
|
file = os.path.join(self._virtualSd, filename).lower()
|
||||||
if not os.path.exists(file) or not os.path.isfile(file):
|
if not os.path.exists(file) or not os.path.isfile(file):
|
||||||
self.outgoing.put("open failed, File: %s." % filename)
|
self.outgoing.put("open failed, File: %s." % filename)
|
||||||
|
|
@ -448,6 +450,8 @@ class VirtualPrinter():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def _writeSdFile(self, filename):
|
def _writeSdFile(self, filename):
|
||||||
|
if filename.startswith("/"):
|
||||||
|
filename = filename[1:]
|
||||||
file = os.path.join(self._virtualSd, filename).lower()
|
file = os.path.join(self._virtualSd, filename).lower()
|
||||||
if os.path.exists(file):
|
if os.path.exists(file):
|
||||||
if os.path.isfile(file):
|
if os.path.isfile(file):
|
||||||
|
|
@ -507,6 +511,8 @@ class VirtualPrinter():
|
||||||
time.sleep(delay)
|
time.sleep(delay)
|
||||||
|
|
||||||
def _deleteSdFile(self, filename):
|
def _deleteSdFile(self, filename):
|
||||||
|
if filename.startswith("/"):
|
||||||
|
filename = filename[1:]
|
||||||
f = os.path.join(self._virtualSd, filename)
|
f = os.path.join(self._virtualSd, filename)
|
||||||
if os.path.exists(f) and os.path.isfile(f):
|
if os.path.exists(f) and os.path.isfile(f):
|
||||||
os.remove(f)
|
os.remove(f)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
# coding=utf-8
|
# coding=utf-8
|
||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
|
from octoprint.printer.estimation import TimeEstimationHelper
|
||||||
|
|
||||||
__author__ = "Gina Häußge <osd@foosel.net>"
|
__author__ = "Gina Häußge <osd@foosel.net>"
|
||||||
__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
|
__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
|
||||||
|
|
@ -15,7 +16,7 @@ import octoprint.printer
|
||||||
class EstimationTestCase(unittest.TestCase):
|
class EstimationTestCase(unittest.TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.estimation_helper = type(octoprint.printer.TimeEstimationHelper)(octoprint.printer.TimeEstimationHelper.__name__, (octoprint.printer.TimeEstimationHelper,), {
|
self.estimation_helper = type(TimeEstimationHelper)(TimeEstimationHelper.__name__, (TimeEstimationHelper,), {
|
||||||
'STABLE_THRESHOLD': 0.1,
|
'STABLE_THRESHOLD': 0.1,
|
||||||
'STABLE_ROLLING_WINDOW': 3,
|
'STABLE_ROLLING_WINDOW': 3,
|
||||||
'STABLE_COUNTDOWN': 1
|
'STABLE_COUNTDOWN': 1
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue