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()
|
||||
}
|
||||
|
||||
currentData = self._printer.getCurrentData()
|
||||
currentData = self._printer.get_current_data()
|
||||
|
||||
if "currentZ" in currentData.keys() and currentData["currentZ"] is not None:
|
||||
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"))
|
||||
|
||||
# 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.standard import Printer
|
||||
from octoprint.settings import settings
|
||||
import octoprint.users as users
|
||||
import octoprint.events as events
|
||||
|
|
@ -673,7 +674,7 @@ class Server():
|
|||
if settings().getBoolean(["serial", "autoconnect"]):
|
||||
(port, baudrate) = settings().get(["serial", "port"]), settings().getInt(["serial", "baudrate"])
|
||||
printer_profile = printerProfileManager.get_default()
|
||||
connectionOptions = getConnectionOptions()
|
||||
connectionOptions = get_connection_options()
|
||||
if port in connectionOptions["ports"]:
|
||||
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 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.api import api
|
||||
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"])
|
||||
def connectionState():
|
||||
state, port, baudrate, printer_profile = printer.getCurrentConnection()
|
||||
state, port, baudrate, printer_profile = printer.get_current_connection()
|
||||
current = {
|
||||
"state": state,
|
||||
"port": port,
|
||||
|
|
@ -41,7 +41,7 @@ def connectionCommand():
|
|||
return response
|
||||
|
||||
if command == "connect":
|
||||
connection_options = getConnectionOptions()
|
||||
connection_options = get_connection_options()
|
||||
|
||||
port = None
|
||||
baudrate = None
|
||||
|
|
@ -72,7 +72,7 @@ def connectionCommand():
|
|||
return NO_CONTENT
|
||||
|
||||
def _get_options():
|
||||
connection_options = getConnectionOptions()
|
||||
connection_options = get_connection_options()
|
||||
profile_options = printerProfileManager.get_all()
|
||||
default_profile = printerProfileManager.get_default()
|
||||
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ def _getFileDetails(origin, filename):
|
|||
|
||||
def _getFileList(origin, filter=None):
|
||||
if origin == FileDestinations.SDCARD:
|
||||
sdFileList = printer.getSdFiles()
|
||||
sdFileList = printer.get_sd_files()
|
||||
|
||||
files = []
|
||||
if sdFileList is not None:
|
||||
|
|
@ -117,7 +117,7 @@ def _getFileList(origin, filter=None):
|
|||
|
||||
def _verifyFileExists(origin, filename):
|
||||
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:
|
||||
return fileManager.file_exists(origin, filename)
|
||||
|
||||
|
|
@ -150,15 +150,15 @@ def uploadGcodeFile(target):
|
|||
|
||||
if sd:
|
||||
# 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)
|
||||
if not printer.isSdReady():
|
||||
if not printer.is_sd_ready():
|
||||
return make_response("Can not upload to SD card, not yet initialized", 409)
|
||||
|
||||
# determine current job
|
||||
currentFilename = None
|
||||
currentOrigin = None
|
||||
currentJob = printer.getCurrentJob()
|
||||
currentJob = printer.get_current_job()
|
||||
if currentJob is not None and "file" in currentJob.keys():
|
||||
currentJobFile = currentJob["file"]
|
||||
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)
|
||||
|
||||
# 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)
|
||||
|
||||
def fileProcessingFinished(filename, absFilename, destination):
|
||||
|
|
@ -186,7 +186,7 @@ def uploadGcodeFile(target):
|
|||
"""
|
||||
|
||||
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:
|
||||
selectAndOrPrint(filename, absFilename, destination)
|
||||
return filename
|
||||
|
|
@ -201,7 +201,7 @@ def uploadGcodeFile(target):
|
|||
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)):
|
||||
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)
|
||||
if added_file is None:
|
||||
|
|
@ -284,7 +284,7 @@ def gcodeFileCommand(filename, target):
|
|||
# selects/loads a file
|
||||
printAfterLoading = False
|
||||
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)
|
||||
printAfterLoading = True
|
||||
|
||||
|
|
@ -294,7 +294,7 @@ def gcodeFileCommand(filename, target):
|
|||
sd = True
|
||||
else:
|
||||
filenameToSelect = fileManager.path_on_disk(target, filename)
|
||||
printer.selectFile(filenameToSelect, sd, printAfterLoading)
|
||||
printer.select_file(filenameToSelect, sd, printAfterLoading)
|
||||
|
||||
elif command == "slice":
|
||||
if "slicer" in data.keys():
|
||||
|
|
@ -312,7 +312,7 @@ def gcodeFileCommand(filename, target):
|
|||
if not octoprint.filemanager.valid_file_type(filename, type="stl"):
|
||||
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
|
||||
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
|
||||
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)
|
||||
|
||||
if "profile" in data.keys() and data["profile"]:
|
||||
|
|
@ -349,13 +349,13 @@ def gcodeFileCommand(filename, target):
|
|||
|
||||
select_after_slicing = False
|
||||
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)
|
||||
select_after_slicing = True
|
||||
|
||||
print_after_slicing = False
|
||||
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)
|
||||
select_after_slicing = print_after_slicing = True
|
||||
|
||||
|
|
@ -372,7 +372,7 @@ def gcodeFileCommand(filename, target):
|
|||
sd = True
|
||||
else:
|
||||
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,
|
||||
profile=profile,
|
||||
|
|
@ -414,7 +414,7 @@ def deleteGcodeFile(filename, target):
|
|||
|
||||
# prohibit deleting files that are currently in use
|
||||
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)
|
||||
|
||||
if (target, filename) in fileManager.get_busy_files():
|
||||
|
|
@ -422,18 +422,18 @@ def deleteGcodeFile(filename, target):
|
|||
|
||||
# deselect the file if it's currently selected
|
||||
if currentFilename is not None and filename == currentFilename:
|
||||
printer.unselectFile()
|
||||
printer.unselect_file()
|
||||
|
||||
# delete it
|
||||
if target == FileDestinations.SDCARD:
|
||||
printer.deleteSdFile(filename)
|
||||
printer.delete_sd_file(filename)
|
||||
else:
|
||||
fileManager.remove_file(target, filename)
|
||||
|
||||
return NO_CONTENT
|
||||
|
||||
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"]:
|
||||
return currentJob["file"]["origin"], currentJob["file"]["name"]
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import octoprint.util as util
|
|||
@api.route("/job", methods=["POST"])
|
||||
@restricted_access
|
||||
def controlJob():
|
||||
if not printer.isOperational():
|
||||
if not printer.is_operational():
|
||||
return make_response("Printer is not operational", 409)
|
||||
|
||||
valid_commands = {
|
||||
|
|
@ -30,30 +30,30 @@ def controlJob():
|
|||
if response is not None:
|
||||
return response
|
||||
|
||||
activePrintjob = printer.isPrinting() or printer.isPaused()
|
||||
activePrintjob = printer.is_printing() or printer.is_paused()
|
||||
|
||||
if command == "start":
|
||||
if activePrintjob:
|
||||
return make_response("Printer already has an active print job, did you mean 'restart'?", 409)
|
||||
printer.startPrint()
|
||||
printer.start_print()
|
||||
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)
|
||||
printer.startPrint()
|
||||
printer.start_print()
|
||||
elif command == "pause":
|
||||
if not activePrintjob:
|
||||
return make_response("Printer is neither printing nor paused, 'pause' command cannot be performed", 409)
|
||||
printer.togglePausePrint()
|
||||
printer.toggle_pause_print()
|
||||
elif command == "cancel":
|
||||
if not activePrintjob:
|
||||
return make_response("Printer is neither printing nor paused, 'cancel' command cannot be performed", 409)
|
||||
printer.cancelPrint()
|
||||
printer.cancel_print()
|
||||
return NO_CONTENT
|
||||
|
||||
|
||||
@api.route("/job", methods=["GET"])
|
||||
def jobState():
|
||||
currentData = printer.getCurrentData()
|
||||
currentData = printer.get_current_data()
|
||||
return jsonify({
|
||||
"job": currentData["job"],
|
||||
"progress": currentData["progress"],
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import octoprint.util as util
|
|||
|
||||
@api.route("/printer", methods=["GET"])
|
||||
def printerState():
|
||||
if not printer.isOperational():
|
||||
if not printer.is_operational():
|
||||
return make_response("Printer is not operational", 409)
|
||||
|
||||
# process excludes
|
||||
|
|
@ -38,11 +38,11 @@ def printerState():
|
|||
|
||||
# add sd information
|
||||
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
|
||||
if not "state" in excludes:
|
||||
state = printer.getCurrentData()["state"]
|
||||
state = printer.get_current_data()["state"]
|
||||
result.update({"state": state})
|
||||
|
||||
return jsonify(result)
|
||||
|
|
@ -54,7 +54,7 @@ def printerState():
|
|||
@api.route("/printer/tool", methods=["POST"])
|
||||
@restricted_access
|
||||
def printerToolCommand():
|
||||
if not printer.isOperational():
|
||||
if not printer.is_operational():
|
||||
return make_response("Printer is not operational", 409)
|
||||
|
||||
valid_commands = {
|
||||
|
|
@ -78,7 +78,7 @@ def printerToolCommand():
|
|||
if not tool.startswith("tool"):
|
||||
return make_response("Invalid tool for selection: %s" % tool, 400)
|
||||
|
||||
printer.changeTool(tool)
|
||||
printer.change_tool(tool)
|
||||
|
||||
##~~ temperature
|
||||
elif command == "target":
|
||||
|
|
@ -95,7 +95,7 @@ def printerToolCommand():
|
|||
|
||||
# perform the actual temperature commands
|
||||
for tool in validated_values.keys():
|
||||
printer.setTemperature(tool, validated_values[tool])
|
||||
printer.set_temperature(tool, validated_values[tool])
|
||||
|
||||
##~~ temperature offset
|
||||
elif command == "offset":
|
||||
|
|
@ -113,11 +113,11 @@ def printerToolCommand():
|
|||
validated_values[tool] = value
|
||||
|
||||
# set the offsets
|
||||
printer.setTemperatureOffset(validated_values)
|
||||
printer.set_temperature_offset(validated_values)
|
||||
|
||||
##~~ extrusion
|
||||
elif command == "extrude":
|
||||
if printer.isPrinting():
|
||||
if printer.is_printing():
|
||||
# do not extrude when a print job is running
|
||||
return make_response("Printer is currently printing", 409)
|
||||
|
||||
|
|
@ -131,7 +131,7 @@ def printerToolCommand():
|
|||
if not isinstance(factor, (int, long, float)):
|
||||
return make_response("Not a number for flow rate: %r" % factor, 400)
|
||||
try:
|
||||
printer.flowRate(factor)
|
||||
printer.flow_rate(factor)
|
||||
except ValueError as e:
|
||||
return make_response("Invalid value for flow rate: %s" % e.message, 400)
|
||||
|
||||
|
|
@ -140,7 +140,7 @@ def printerToolCommand():
|
|||
|
||||
@api.route("/printer/tool", methods=["GET"])
|
||||
def printerToolState():
|
||||
if not printer.isOperational():
|
||||
if not printer.is_operational():
|
||||
return make_response("Printer is not operational", 409)
|
||||
|
||||
def deleteBed(x):
|
||||
|
|
@ -159,7 +159,7 @@ def printerToolState():
|
|||
@api.route("/printer/bed", methods=["POST"])
|
||||
@restricted_access
|
||||
def printerBedCommand():
|
||||
if not printer.isOperational():
|
||||
if not printer.is_operational():
|
||||
return make_response("Printer is not operational", 409)
|
||||
|
||||
valid_commands = {
|
||||
|
|
@ -179,7 +179,7 @@ def printerBedCommand():
|
|||
return make_response("Not a number: %r" % target, 400)
|
||||
|
||||
# perform the actual temperature command
|
||||
printer.setTemperature("bed", target)
|
||||
printer.set_temperature("bed", target)
|
||||
|
||||
##~~ temperature offset
|
||||
elif command == "offset":
|
||||
|
|
@ -192,14 +192,14 @@ def printerBedCommand():
|
|||
return make_response("Offset not in range [-50, 50]: %f" % offset, 400)
|
||||
|
||||
# set the offsets
|
||||
printer.setTemperatureOffset({"bed": offset})
|
||||
printer.set_temperature_offset({"bed": offset})
|
||||
|
||||
return NO_CONTENT
|
||||
|
||||
|
||||
@api.route("/printer/bed", methods=["GET"])
|
||||
def printerBedState():
|
||||
if not printer.isOperational():
|
||||
if not printer.is_operational():
|
||||
return make_response("Printer is not operational", 409)
|
||||
|
||||
def deleteTools(x):
|
||||
|
|
@ -223,7 +223,7 @@ def printerBedState():
|
|||
@api.route("/printer/printhead", methods=["POST"])
|
||||
@restricted_access
|
||||
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
|
||||
return make_response("Printer is not operational or currently printing", 409)
|
||||
|
||||
|
|
@ -269,7 +269,7 @@ def printerPrintheadCommand():
|
|||
if not isinstance(factor, (int, long, float)):
|
||||
return make_response("Not a number for feed rate: %r" % factor, 400)
|
||||
try:
|
||||
printer.feedRate(factor)
|
||||
printer.feed_rate(factor)
|
||||
except ValueError as e:
|
||||
return make_response("Invalid value for feed rate: %s" % e.message, 400)
|
||||
|
||||
|
|
@ -285,7 +285,7 @@ def printerSdCommand():
|
|||
if not settings().getBoolean(["feature", "sdSupport"]):
|
||||
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)
|
||||
|
||||
valid_commands = {
|
||||
|
|
@ -298,11 +298,11 @@ def printerSdCommand():
|
|||
return response
|
||||
|
||||
if command == "init":
|
||||
printer.initSdCard()
|
||||
printer.init_sd_card()
|
||||
elif command == "refresh":
|
||||
printer.refreshSdFiles()
|
||||
printer.refresh_sd_files()
|
||||
elif command == "release":
|
||||
printer.releaseSdCard()
|
||||
printer.release_sd_card()
|
||||
|
||||
return NO_CONTENT
|
||||
|
||||
|
|
@ -312,7 +312,7 @@ def printerSdState():
|
|||
if not settings().getBoolean(["feature", "sdSupport"]):
|
||||
return make_response("SD support is disabled", 404)
|
||||
|
||||
return jsonify(ready=printer.isSdReady())
|
||||
return jsonify(ready=printer.is_sd_ready())
|
||||
|
||||
|
||||
##~~ Commands
|
||||
|
|
@ -321,7 +321,7 @@ def printerSdState():
|
|||
@api.route("/printer/command", methods=["POST"])
|
||||
@restricted_access
|
||||
def printerCommand():
|
||||
if not printer.isOperational():
|
||||
if not printer.is_operational():
|
||||
return make_response("Printer is not operational", 409)
|
||||
|
||||
if not "application/json" in request.headers["Content-Type"]:
|
||||
|
|
@ -364,13 +364,13 @@ def getCustomControls():
|
|||
|
||||
|
||||
def _getTemperatureData(filter):
|
||||
if not printer.isOperational():
|
||||
if not printer.is_operational():
|
||||
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:
|
||||
tempHistory = printer.getTemperatureHistory()
|
||||
tempHistory = printer.get_temperature_history()
|
||||
|
||||
limit = 300
|
||||
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.settings import settings
|
||||
from octoprint.printer import getConnectionOptions
|
||||
from octoprint.printer import get_connection_options
|
||||
|
||||
from octoprint.server import admin_permission
|
||||
from octoprint.server.api import api
|
||||
|
|
@ -28,7 +28,7 @@ import octoprint.util
|
|||
def getSettings():
|
||||
s = settings()
|
||||
|
||||
connectionOptions = getConnectionOptions()
|
||||
connectionOptions = get_connection_options()
|
||||
|
||||
data = {
|
||||
"api": {
|
||||
|
|
|
|||
|
|
@ -13,8 +13,10 @@ import octoprint.timelapse
|
|||
import octoprint.server
|
||||
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):
|
||||
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
|
||||
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)
|
||||
octoprint.timelapse.registerCallback(self)
|
||||
self._pluginManager.register_client(self)
|
||||
|
|
@ -62,7 +64,7 @@ class PrinterStateConnection(sockjs.tornado.SockJSConnection):
|
|||
|
||||
def on_close(self):
|
||||
self._logger.info("Client connection closed: %s" % self._remoteAddress)
|
||||
self._printer.unregisterCallback(self)
|
||||
self._printer.unregister_callback(self)
|
||||
self._fileManager.unregister_slicingprogress_callback(self)
|
||||
octoprint.timelapse.unregisterCallback(self)
|
||||
self._pluginManager.unregister_client(self)
|
||||
|
|
@ -74,7 +76,7 @@ class PrinterStateConnection(sockjs.tornado.SockJSConnection):
|
|||
def on_message(self, message):
|
||||
pass
|
||||
|
||||
def sendCurrentData(self, data):
|
||||
def on_printer_send_current_data(self, data):
|
||||
# add current temperature, log and message backlogs to sent data
|
||||
with self._temperatureBacklogMutex:
|
||||
temperatures = self._temperatureBacklog
|
||||
|
|
@ -92,7 +94,7 @@ class PrinterStateConnection(sockjs.tornado.SockJSConnection):
|
|||
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 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"]))
|
||||
|
||||
data.update({
|
||||
|
|
@ -103,13 +105,13 @@ class PrinterStateConnection(sockjs.tornado.SockJSConnection):
|
|||
})
|
||||
self._emit("current", data)
|
||||
|
||||
def sendHistoryData(self, data):
|
||||
def on_printer_send_initial_data(self, data):
|
||||
self._emit("history", data)
|
||||
|
||||
def sendEvent(self, type, payload=None):
|
||||
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})
|
||||
|
||||
def sendTimelapseConfig(self, timelapseConfig):
|
||||
|
|
@ -123,15 +125,15 @@ class PrinterStateConnection(sockjs.tornado.SockJSConnection):
|
|||
def sendPluginMessage(self, plugin, data):
|
||||
self._emit("plugin", dict(plugin=plugin, data=data))
|
||||
|
||||
def addLog(self, data):
|
||||
def on_printer_add_log(self, data):
|
||||
with self._logBacklogMutex:
|
||||
self._logBacklog.append(data)
|
||||
|
||||
def addMessage(self, data):
|
||||
def on_printer_add_message(self, data):
|
||||
with self._messageBacklogMutex:
|
||||
self._messageBacklog.append(data)
|
||||
|
||||
def addTemperature(self, data):
|
||||
def on_printer_add_temperature(self, data):
|
||||
with self._temperatureBacklogMutex:
|
||||
self._temperatureBacklog.append(data)
|
||||
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ class GcodeWatchdogHandler(watchdog.events.PatternMatchingEventHandler):
|
|||
# determine current job
|
||||
currentFilename = None
|
||||
currentOrigin = None
|
||||
currentJob = self._printer.getCurrentJob()
|
||||
currentJob = self._printer.get_current_job()
|
||||
if currentJob is not None and "file" in currentJob.keys():
|
||||
currentJobFile = currentJob["file"]
|
||||
if "name" in currentJobFile.keys() and "origin" in currentJobFile.keys():
|
||||
|
|
@ -59,7 +59,7 @@ class GcodeWatchdogHandler(watchdog.events.PatternMatchingEventHandler):
|
|||
return
|
||||
|
||||
# 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
|
||||
|
||||
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_wrapper">
|
||||
{% 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" 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>
|
||||
{% 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>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -223,15 +223,15 @@ class MachineCom(object):
|
|||
if settings().get(["feature", "sdSupport"]):
|
||||
self._sdFileList = False
|
||||
self._sdFiles = []
|
||||
self._callback.mcSdFiles([])
|
||||
self._callback.on_comm_sd_files([])
|
||||
|
||||
oldState = self.getStateString()
|
||||
self._state = newState
|
||||
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):
|
||||
self._callback.mcLog(message)
|
||||
self._callback.on_comm_log(message)
|
||||
self._serialLogger.debug(message)
|
||||
|
||||
def _addToLastLines(self, cmd):
|
||||
|
|
@ -494,7 +494,7 @@ class MachineCom(object):
|
|||
|
||||
self.sendCommand("M28 %s" % 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):
|
||||
if self.isBusy():
|
||||
|
|
@ -512,7 +512,7 @@ class MachineCom(object):
|
|||
"file": self._currentFile.getFilename(),
|
||||
"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):
|
||||
if self.isBusy():
|
||||
|
|
@ -520,7 +520,7 @@ class MachineCom(object):
|
|||
|
||||
self._currentFile = None
|
||||
eventManager().fire(Events.FILE_DESELECTED)
|
||||
self._callback.mcFileSelected(None, None, False)
|
||||
self._callback.on_comm_file_selected(None, None, False)
|
||||
|
||||
def cancelPrint(self):
|
||||
if not self.isOperational() or self.isStreaming():
|
||||
|
|
@ -625,7 +625,7 @@ class MachineCom(object):
|
|||
if settings().getBoolean(["feature", "sdAlwaysAvailable"]):
|
||||
self._sdAvailable = True
|
||||
self.refreshSdFiles()
|
||||
self._callback.mcSdStateChange(self._sdAvailable)
|
||||
self._callback.on_comm_sd_state_change(self._sdAvailable)
|
||||
|
||||
def releaseSdCard(self):
|
||||
if not self.isOperational() or (self.isBusy() and self.isSdFileSelected()):
|
||||
|
|
@ -636,8 +636,8 @@ class MachineCom(object):
|
|||
self._sdAvailable = False
|
||||
self._sdFiles = []
|
||||
|
||||
self._callback.mcSdStateChange(self._sdAvailable)
|
||||
self._callback.mcSdFiles(self._sdFiles)
|
||||
self._callback.on_comm_sd_state_change(self._sdAvailable)
|
||||
self._callback.on_comm_sd_files(self._sdFiles)
|
||||
|
||||
##~~ communication monitoring and handling
|
||||
|
||||
|
|
@ -761,7 +761,7 @@ class MachineCom(object):
|
|||
self.setPause(False)
|
||||
elif action_command == "disconnect":
|
||||
self._log("Disconnecting on request of the printer...")
|
||||
self._callback.mcForceDisconnect()
|
||||
self._callback.on_comm_force_disconnect()
|
||||
else:
|
||||
for hook in self._printer_action_hooks:
|
||||
self._printer_action_hooks[hook](self, line, action_command)
|
||||
|
|
@ -803,7 +803,7 @@ class MachineCom(object):
|
|||
##~~ Temperature processing
|
||||
if ' T:' in line or line.startswith('T:') or ' T0:' in line or line.startswith('T0:'):
|
||||
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):
|
||||
matchExtr = self._regex_repetierTempExtr.match(line)
|
||||
|
|
@ -818,7 +818,7 @@ class MachineCom(object):
|
|||
self._temp[toolNum] = (actual, target)
|
||||
else:
|
||||
self._temp[toolNum] = (None, target)
|
||||
self._callback.mcTempUpdate(self._temp, self._bedTemp)
|
||||
self._callback.on_comm_temperature_update(self._temp, self._bedTemp)
|
||||
except ValueError:
|
||||
pass
|
||||
elif matchBed is not None:
|
||||
|
|
@ -829,7 +829,7 @@ class MachineCom(object):
|
|||
self._bedTemp = (actual, target)
|
||||
else:
|
||||
self._bedTemp = (None, target)
|
||||
self._callback.mcTempUpdate(self._temp, self._bedTemp)
|
||||
self._callback.on_comm_temperature_update(self._temp, self._bedTemp)
|
||||
except ValueError:
|
||||
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:
|
||||
self._sdAvailable = False
|
||||
self._sdFiles = []
|
||||
self._callback.mcSdStateChange(self._sdAvailable)
|
||||
self._callback.on_comm_sd_state_change(self._sdAvailable)
|
||||
elif 'Not SD printing' in line:
|
||||
if self.isSdFileSelected() and self.isPrinting():
|
||||
# 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:
|
||||
self._sdAvailable = True
|
||||
self.refreshSdFiles()
|
||||
self._callback.mcSdStateChange(self._sdAvailable)
|
||||
self._callback.on_comm_sd_state_change(self._sdAvailable)
|
||||
elif 'Begin file list' in line:
|
||||
self._sdFiles = []
|
||||
self._sdFileList = True
|
||||
elif 'End file list' in line:
|
||||
self._sdFileList = False
|
||||
self._callback.mcSdFiles(self._sdFiles)
|
||||
self._callback.on_comm_sd_files(self._sdFiles)
|
||||
elif 'SD printing byte' in line:
|
||||
# answer to M27, at least on Marlin, Repetier and Sprinter: "SD printing byte %d/%d"
|
||||
match = self._regex_sdPrintingByte.search(line)
|
||||
self._currentFile.setFilepos(int(match.group(1)))
|
||||
self._callback.mcProgress()
|
||||
self._callback.on_comm_progress()
|
||||
elif 'File opened' in line:
|
||||
# answer to M23, at least on Marlin, Repetier and Sprinter: "File opened:%s Size:%d"
|
||||
match = self._regex_sdFileOpened.search(line)
|
||||
|
|
@ -875,7 +875,7 @@ class MachineCom(object):
|
|||
elif 'File selected' in line:
|
||||
# final answer to M23, at least on Marlin, Repetier and Sprinter: "File selected"
|
||||
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, {
|
||||
"file": self._currentFile.getFilename(),
|
||||
"origin": self._currentFile.getFileLocation()
|
||||
|
|
@ -887,7 +887,7 @@ class MachineCom(object):
|
|||
elif 'Done printing file' in line:
|
||||
# printer is reporting file finished printing
|
||||
self._sdFilePos = 0
|
||||
self._callback.mcPrintjobDone()
|
||||
self._callback.on_comm_print_job_done()
|
||||
self._changeState(self.STATE_OPERATIONAL)
|
||||
eventManager().fire(Events.PRINT_DONE, {
|
||||
"file": self._currentFile.getFilename(),
|
||||
|
|
@ -904,7 +904,7 @@ class MachineCom(object):
|
|||
and not line.startswith('Resend:') \
|
||||
and line != 'echo:Unknown command:""\n' \
|
||||
and self.isOperational():
|
||||
self._callback.mcMessage(line)
|
||||
self._callback.on_comm_message(line)
|
||||
|
||||
##~~ Parsing for feedback commands
|
||||
if feedbackControls:
|
||||
|
|
@ -922,7 +922,7 @@ class MachineCom(object):
|
|||
formatFunction = unicode.format
|
||||
|
||||
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:
|
||||
if not name in feedbackErrors:
|
||||
self._logger.info("Something went wrong with feedbackControl \"%s\": " % name, exc_info=True)
|
||||
|
|
@ -1171,7 +1171,7 @@ class MachineCom(object):
|
|||
|
||||
self._currentFile = None
|
||||
self._changeState(self.STATE_OPERATIONAL)
|
||||
self._callback.mcFileTransferDone(remote)
|
||||
self._callback.on_comm_file_transfer_done(remote)
|
||||
eventManager().fire(Events.TRANSFER_DONE, payload)
|
||||
self.refreshSdFiles()
|
||||
else:
|
||||
|
|
@ -1181,7 +1181,7 @@ class MachineCom(object):
|
|||
"origin": self._currentFile.getFileLocation(),
|
||||
"time": self.getPrintTime()
|
||||
}
|
||||
self._callback.mcPrintjobDone()
|
||||
self._callback.on_comm_print_job_done()
|
||||
self._changeState(self.STATE_OPERATIONAL)
|
||||
eventManager().fire(Events.PRINT_DONE, payload)
|
||||
|
||||
|
|
@ -1193,7 +1193,7 @@ class MachineCom(object):
|
|||
line = self._getNext()
|
||||
if line is not None:
|
||||
self._sendCommand(line, True)
|
||||
self._callback.mcProgress()
|
||||
self._callback.on_comm_progress()
|
||||
|
||||
def _handleResendRequest(self, line):
|
||||
lineToResend = None
|
||||
|
|
@ -1325,7 +1325,7 @@ class MachineCom(object):
|
|||
z = float(match.group(1))
|
||||
if self._currentZ != z:
|
||||
self._currentZ = z
|
||||
self._callback.mcZChange(z)
|
||||
self._callback.on_comm_z_change(z)
|
||||
except ValueError:
|
||||
pass
|
||||
return cmd
|
||||
|
|
@ -1419,43 +1419,46 @@ class MachineCom(object):
|
|||
### MachineCom callback ################################################################################################
|
||||
|
||||
class MachineComPrintCallback(object):
|
||||
def mcLog(self, message):
|
||||
def on_comm_log(self, message):
|
||||
pass
|
||||
|
||||
def mcTempUpdate(self, temp, bedTemp):
|
||||
def on_comm_temperature_update(self, temp, bedTemp):
|
||||
pass
|
||||
|
||||
def mcStateChange(self, state):
|
||||
def on_comm_state_change(self, state):
|
||||
pass
|
||||
|
||||
def mcMessage(self, message):
|
||||
def on_comm_message(self, message):
|
||||
pass
|
||||
|
||||
def mcProgress(self):
|
||||
def on_comm_progress(self):
|
||||
pass
|
||||
|
||||
def mcZChange(self, newZ):
|
||||
def on_comm_print_job_done(self):
|
||||
pass
|
||||
|
||||
def mcFileSelected(self, filename, filesize, sd):
|
||||
def on_comm_z_change(self, newZ):
|
||||
pass
|
||||
|
||||
def mcSdStateChange(self, sdReady):
|
||||
def on_comm_file_selected(self, filename, filesize, sd):
|
||||
pass
|
||||
|
||||
def mcSdFiles(self, files):
|
||||
def on_comm_sd_state_change(self, sdReady):
|
||||
pass
|
||||
|
||||
def mcSdPrintingDone(self):
|
||||
def on_comm_sd_files(self, files):
|
||||
pass
|
||||
|
||||
def mcFileTransferStarted(self, filename, filesize):
|
||||
def on_comm_file_transfer_started(self, filename, filesize):
|
||||
pass
|
||||
|
||||
def mcReceivedRegisteredMessage(self, command, message):
|
||||
def on_comm_file_transfer_done(self, filename):
|
||||
pass
|
||||
|
||||
def mcForceDisconnect(self):
|
||||
def on_comm_received_registered_message(self, command, message):
|
||||
pass
|
||||
|
||||
def on_comm_force_disconnect(self):
|
||||
pass
|
||||
|
||||
### Printing file information classes ##################################################################################
|
||||
|
|
|
|||
|
|
@ -263,6 +263,8 @@ class VirtualPrinter():
|
|||
self.outgoing.put("End file list")
|
||||
|
||||
def _selectSdFile(self, filename):
|
||||
if filename.startswith("/"):
|
||||
filename = filename[1:]
|
||||
file = os.path.join(self._virtualSd, filename).lower()
|
||||
if not os.path.exists(file) or not os.path.isfile(file):
|
||||
self.outgoing.put("open failed, File: %s." % filename)
|
||||
|
|
@ -448,6 +450,8 @@ class VirtualPrinter():
|
|||
pass
|
||||
|
||||
def _writeSdFile(self, filename):
|
||||
if filename.startswith("/"):
|
||||
filename = filename[1:]
|
||||
file = os.path.join(self._virtualSd, filename).lower()
|
||||
if os.path.exists(file):
|
||||
if os.path.isfile(file):
|
||||
|
|
@ -507,6 +511,8 @@ class VirtualPrinter():
|
|||
time.sleep(delay)
|
||||
|
||||
def _deleteSdFile(self, filename):
|
||||
if filename.startswith("/"):
|
||||
filename = filename[1:]
|
||||
f = os.path.join(self._virtualSd, filename)
|
||||
if os.path.exists(f) and os.path.isfile(f):
|
||||
os.remove(f)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
# coding=utf-8
|
||||
from __future__ import absolute_import
|
||||
from octoprint.printer.estimation import TimeEstimationHelper
|
||||
|
||||
__author__ = "Gina Häußge <osd@foosel.net>"
|
||||
__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
|
||||
|
|
@ -15,7 +16,7 @@ import octoprint.printer
|
|||
class EstimationTestCase(unittest.TestCase):
|
||||
|
||||
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_ROLLING_WINDOW': 3,
|
||||
'STABLE_COUNTDOWN': 1
|
||||
|
|
|
|||
Loading…
Reference in a new issue