From 8ac375fc9bab4574d4dbf0c15c669af6974efcf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Thu, 16 Apr 2015 11:43:30 +0200 Subject: [PATCH] Some minor changes before merging the PR * moved virtual printer into plugin * made default serial factory use supplied parameters instead of directly utilizing self._port and similar * documented new hook --- docs/plugins/hooks.rst | 82 ++++++++++++++++++- .../plugins/virtual_printer/__init__.py | 38 +++++++++ .../virtual_printer}/virtual.py | 0 src/octoprint/util/comm.py | 19 ++--- 4 files changed, 126 insertions(+), 13 deletions(-) create mode 100644 src/octoprint/plugins/virtual_printer/__init__.py rename src/octoprint/{util => plugins/virtual_printer}/virtual.py (100%) diff --git a/docs/plugins/hooks.rst b/docs/plugins/hooks.rst index 2a72a0af..37d6911c 100644 --- a/docs/plugins/hooks.rst +++ b/docs/plugins/hooks.rst @@ -3,6 +3,9 @@ Available plugin hooks ====================== +.. contents:: + :local: + .. _sec-plugins-hook-comm-protocol-action: octoprint.comm.protocol.action @@ -111,4 +114,81 @@ octoprint.comm.protocol.scripts :param str script_type: The type of the script for which the hook was called, currently only "gcode" is supported here. :param str script_name: The name of the script for which the hook was called. :return: A 2-tuple in the form ``(prefix, postfix)`` or None - :rtype: tuple or None \ No newline at end of file + :rtype: tuple or None + +.. _sec-plugins-hook-comm-transport-serial-factory: + +octoprint.comm.transport.serial.factory +--------------------------------------- + +.. py:function:: hook(comm_instance, port, baudrate, read_timeout, *args, **kwargs) + + Return a serial object to use as serial connection to the printer. If a handler cannot create a serial object + for the specified ``port`` (and ``baudrate``), it should just return ``None``. + + If the hook handler needs to perform state switches (e.g. for autodetection) or other operations on the + :class:`~octoprint.util.comm.MachineCom` instance, it can use the supplied ``comm_instance`` to do so. Plugin + authors should keep in mind however that due to a pending change in the communication layer of + OctoPrint, that interface will change in the future. Authors are advised to follow OctoPrint's development + closely if directly utilizing :class:`~octoprint.util.comm.MachineCom` functionality. + + A valid serial instance is expected to provide the following methods, analogue to PySerial's + `serial.Serial `_: + + readline(size=None, eol='\n') + Reads a line from the serial connection, compare `serial.Filelike.readline `_. + write(data) + Writes data to the serial connection, compare `serial.Filelike.write `_. + close() + Closes the serial connection, compare `serial.Serial.close `_. + + Additionally setting the following attributes need to be supported if baudrate detection is supposed to work: + + baudrate + An integer describing the baudrate to use for the serial connection, compare `serial.Serial.baudrate `_. + timeout + An integer describing the read timeout on the serial connection, compare `serial.Serial.timeout `_. + + **Example:** + + Serial factory similar to the default one which performs auto detection of the serial port if ``port`` is ``None`` + or ``AUTO``. + + .. code-block:: python + :linenos: + + def default(comm_instance, port, baudrate, connection_timeout): + if port is None or port == 'AUTO': + # no known port, try auto detection + comm_instance._changeState(comm_instance.STATE_DETECT_SERIAL) + serial_obj = comm_instance._detectPort(False) + if serial_obj is None: + comm_instance._log("Failed to autodetect serial port") + comm_instance._errorValue = 'Failed to autodetect serial port.' + comm_instance._changeState(comm_instance.STATE_ERROR) + eventManager().fire(Events.ERROR, {"error": comm_instance.getErrorString()}) + return None + + else: + # connect to regular serial port + comm_instance._log("Connecting to: %s" % port) + if baudrate == 0: + serial_obj = serial.Serial(str(port), 115200, timeout=connection_timeout, writeTimeout=10000, parity=serial.PARITY_ODD) + else: + serial_obj = serial.Serial(str(port), baudrate, timeout=connection_timeout, writeTimeout=10000, parity=serial.PARITY_ODD) + serial_obj.close() + serial_obj.parity = serial.PARITY_NONE + serial_obj.open() + + return serial_obj + + :param MachineCom comm_instance: The :class:`~octoprint.util.comm.MachineCom` instance which triggered the hook. + :param port str: The port for which to construct a serial instance. May be ``None`` or ``AUTO`` in which case port + auto detection is to be performed. + :param baudrate int: The baudrate for which to construct a serial instance. May be 0 in which case baudrate auto + detection is to be performed. + :param read_timeout int: The read timeout to set on the serial port. + :return: The constructed serial object ready for use, or ``None`` if the handler could not construct the object. + :rtype: A serial instance implementing implementing the methods ``readline(...)``, ``write(...)``, ``close()`` and + optionally ``baudrate`` and ``timeout`` attributes as described above. + diff --git a/src/octoprint/plugins/virtual_printer/__init__.py b/src/octoprint/plugins/virtual_printer/__init__.py new file mode 100644 index 00000000..e3173ff4 --- /dev/null +++ b/src/octoprint/plugins/virtual_printer/__init__.py @@ -0,0 +1,38 @@ +# coding=utf-8 +from __future__ import absolute_import + +__author__ = "Gina Häußge " +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' +__copyright__ = "Copyright (C) 2015 The OctoPrint Project - Released under terms of the AGPLv3 License" + +import octoprint.plugin + +class VirtualPrinterPlugin(octoprint.plugin.SettingsPlugin): + + def virtual_printer_factory(self, comm_instance, port, baudrate, read_timeout): + if not port == "VIRTUAL": + return None + + if not self._settings.global_get_boolean(["devel", "virtualPrinter", "enabled"]): + return None + + from . import virtual + serial_obj = virtual.VirtualPrinter(read_timeout=float(read_timeout)) + return serial_obj + +__plugin_name__ = "Virtual Printer" +__plugin_author__ = "Gina Häußge, based on work by Daid Braam" +__plugin_homepage__ = "https://github.com/foosel/OctoPrint/wiki/Plugin:-Virtual-Printer" +__plugin_license__ = "AGPLv3" +__plugin_description__ = "Provides a virtual printer via a virtual serial port for development and testing purposes" + +def __plugin_load__(): + plugin = VirtualPrinterPlugin() + + global __plugin_implementation__ + __plugin_implementation__ = plugin + + global __plugin_hooks__ + __plugin_hooks__ = { + "octoprint.comm.transport.serial.factory": plugin.virtual_printer_factory + } diff --git a/src/octoprint/util/virtual.py b/src/octoprint/plugins/virtual_printer/virtual.py similarity index 100% rename from src/octoprint/util/virtual.py rename to src/octoprint/plugins/virtual_printer/virtual.py diff --git a/src/octoprint/util/comm.py b/src/octoprint/util/comm.py index 7d147c40..8147b39f 100644 --- a/src/octoprint/util/comm.py +++ b/src/octoprint/util/comm.py @@ -25,7 +25,6 @@ from octoprint.events import eventManager, Events from octoprint.filemanager import valid_file_type from octoprint.filemanager.destinations import FileDestinations from octoprint.util import get_exception_string, sanitize_ascii, filter_non_ascii, CountedEvent, RepeatedTimer -from octoprint.util.virtual import VirtualPrinter try: import _winreg @@ -174,7 +173,7 @@ class MachineCom(object): self._gcode_hooks = self._pluginManager.get_hooks("octoprint.comm.protocol.gcode") self._printer_action_hooks = self._pluginManager.get_hooks("octoprint.comm.protocol.action") self._gcodescript_hooks = self._pluginManager.get_hooks("octoprint.comm.protocol.scripts") - self._serial_hooks = self._pluginManager.get_hooks("octoprint.comm.protocol.serial") + self._serial_factory_hooks = self._pluginManager.get_hooks("octoprint.comm.transport.serial.factory") # SD status data self._sdAvailable = False @@ -1205,8 +1204,8 @@ class MachineCom(object): return None def _openSerial(self): - def default(_, port, baudrate, connection_timeout): - if self._port is None or self._port == 'AUTO': + def default(_, port, baudrate, read_timeout): + if port is None or port == 'AUTO': # no known port, try auto detection self._changeState(self.STATE_DETECT_SERIAL) serial_obj = self._detectPort(False) @@ -1217,24 +1216,20 @@ class MachineCom(object): eventManager().fire(Events.ERROR, {"error": self.getErrorString()}) return None - elif port == "VIRTUAL": - # virtual printer, use that - serial_obj = VirtualPrinter() - else: # connect to regular serial port - self._log("Connecting to: %s" % self._port) + self._log("Connecting to: %s" % port) if baudrate == 0: - serial_obj = serial.Serial(str(port), 115200, timeout=connection_timeout, writeTimeout=10000, parity=serial.PARITY_ODD) + serial_obj = serial.Serial(str(port), 115200, timeout=read_timeout, writeTimeout=10000, parity=serial.PARITY_ODD) else: - serial_obj = serial.Serial(str(port), baudrate, timeout=connection_timeout, writeTimeout=10000, parity=serial.PARITY_ODD) + serial_obj = serial.Serial(str(port), baudrate, timeout=read_timeout, writeTimeout=10000, parity=serial.PARITY_ODD) serial_obj.close() serial_obj.parity = serial.PARITY_NONE serial_obj.open() return serial_obj - serial_factories = self._serial_hooks.items() + [("default", default)] + serial_factories = self._serial_factory_hooks.items() + [("default", default)] for name, factory in serial_factories: try: serial_obj = factory(self, self._port, self._baudrate, settings().getFloat(["serial", "timeout", "connection"]))