Allow multi line commands in sending phase
(cherry picked from commit 1018946)
This commit is contained in:
parent
a50e8bbbb5
commit
35c45df86c
3 changed files with 94 additions and 39 deletions
|
|
@ -451,18 +451,21 @@ This describes actually four hooks:
|
||||||
should use this option.
|
should use this option.
|
||||||
* A 2-tuple consisting of a rewritten version of the ``cmd`` and the ``cmd_type``, e.g. ``return "M105", "temperature_poll"``.
|
* A 2-tuple consisting of a rewritten version of the ``cmd`` and the ``cmd_type``, e.g. ``return "M105", "temperature_poll"``.
|
||||||
Handlers which wish to rewrite both the command and the command type should use this option.
|
Handlers which wish to rewrite both the command and the command type should use this option.
|
||||||
* **``queuing`` phase only**: A list of any of the above to allow for expanding one command into
|
* A list of any of the above to allow for expanding one command into
|
||||||
many. The following example shows how any queued command could be turned into a sequence of a temperature query,
|
many. The following example shows how any queued command could be turned into a sequence of a temperature query,
|
||||||
line number reset, display of the ``gcode`` on the printer's display and finally the actual command (this example
|
line number reset, display of the ``gcode`` on the printer's display and finally the actual command (this example
|
||||||
does not make a lot of sense to be quiet honest):
|
does not make a lot of sense to be quite honest):
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
def multi_expansion(*args, **kwargs):
|
def rewrite_foo(self, comm_instance, phase, cmd, cmd_type, gcode, *args, **kwargs):
|
||||||
return [("M105", "temperature_poll"),
|
if gcode or not cmd.startswith("@foo"):
|
||||||
("M110",),
|
return
|
||||||
"M117 GCODE: {}".format(gcode),
|
|
||||||
(command, command_type)]
|
return [("M105", "temperature_poll"),
|
||||||
|
("M110",),
|
||||||
|
"M117 echo foo: {}".format(cmd)]
|
||||||
|
|
||||||
|
|
||||||
Note: Only one command of a given ``cmd_type`` (other than None) may be queued at a time. Trying to rewrite the ``cmd_type``
|
Note: Only one command of a given ``cmd_type`` (other than None) may be queued at a time. Trying to rewrite the ``cmd_type``
|
||||||
to one already in the queue will give an error.
|
to one already in the queue will give an error.
|
||||||
|
|
|
||||||
|
|
@ -1046,19 +1046,58 @@ class InvariantContainer(object):
|
||||||
return self._data.__iter__()
|
return self._data.__iter__()
|
||||||
|
|
||||||
|
|
||||||
class TypedQueue(queue.Queue):
|
class PrependQueue(queue.Queue):
|
||||||
|
|
||||||
def __init__(self, maxsize=0):
|
def __init__(self, maxsize=0):
|
||||||
queue.Queue.__init__(self, maxsize=maxsize)
|
queue.Queue.__init__(self, maxsize=maxsize)
|
||||||
|
|
||||||
|
def prepend(self, item, block=True, timeout=True):
|
||||||
|
from time import time as _time
|
||||||
|
|
||||||
|
self.not_full.acquire()
|
||||||
|
try:
|
||||||
|
if self.maxsize > 0:
|
||||||
|
if not block:
|
||||||
|
if self._qsize() == self.maxsize:
|
||||||
|
raise queue.Full
|
||||||
|
elif timeout is None:
|
||||||
|
while self._qsize() >= self.maxsize:
|
||||||
|
self.not_full.wait()
|
||||||
|
elif timeout < 0:
|
||||||
|
raise ValueError("'timeout' must be a non-negative number")
|
||||||
|
else:
|
||||||
|
endtime = _time() + timeout
|
||||||
|
while self._qsize() == self.maxsize:
|
||||||
|
remaining = endtime - _time()
|
||||||
|
if remaining <= 0.0:
|
||||||
|
raise queue.Full
|
||||||
|
self.not_full.wait(remaining)
|
||||||
|
self._prepend(item)
|
||||||
|
self.unfinished_tasks += 1
|
||||||
|
self.not_empty.notify()
|
||||||
|
finally:
|
||||||
|
self.not_full.release()
|
||||||
|
|
||||||
|
def _prepend(self, item):
|
||||||
|
self.queue.appendleft(item)
|
||||||
|
|
||||||
|
|
||||||
|
class TypedQueue(PrependQueue):
|
||||||
|
|
||||||
|
def __init__(self, maxsize=0):
|
||||||
|
PrependQueue.__init__(self, maxsize=maxsize)
|
||||||
self._lookup = set()
|
self._lookup = set()
|
||||||
|
|
||||||
def put(self, item, item_type=None, *args, **kwargs):
|
def put(self, item, item_type=None, *args, **kwargs):
|
||||||
queue.Queue.put(self, (item, item_type), *args, **kwargs)
|
PrependQueue.put(self, (item, item_type), *args, **kwargs)
|
||||||
|
|
||||||
def get(self, *args, **kwargs):
|
def get(self, *args, **kwargs):
|
||||||
item, _ = queue.Queue.get(self, *args, **kwargs)
|
item, _ = PrependQueue.get(self, *args, **kwargs)
|
||||||
return item
|
return item
|
||||||
|
|
||||||
|
def prepend(self, item, item_type=None, *args, **kwargs):
|
||||||
|
PrependQueue.prepend(self, (item, item_type), *args, **kwargs)
|
||||||
|
|
||||||
def _put(self, item):
|
def _put(self, item):
|
||||||
_, item_type = item
|
_, item_type = item
|
||||||
if item_type is not None:
|
if item_type is not None:
|
||||||
|
|
@ -1070,7 +1109,7 @@ class TypedQueue(queue.Queue):
|
||||||
queue.Queue._put(self, item)
|
queue.Queue._put(self, item)
|
||||||
|
|
||||||
def _get(self):
|
def _get(self):
|
||||||
item = queue.Queue._get(self)
|
item = PrependQueue._get(self)
|
||||||
_, item_type = item
|
_, item_type = item
|
||||||
|
|
||||||
if item_type is not None:
|
if item_type is not None:
|
||||||
|
|
@ -1078,6 +1117,15 @@ class TypedQueue(queue.Queue):
|
||||||
|
|
||||||
return item
|
return item
|
||||||
|
|
||||||
|
def _prepend(self, item):
|
||||||
|
_, item_type = item
|
||||||
|
if item_type is not None:
|
||||||
|
if item_type in self._lookup:
|
||||||
|
raise TypeAlreadyInQueue(item_type, "Type {} is already in queue".format(item_type))
|
||||||
|
else:
|
||||||
|
self._lookup.add(item_type)
|
||||||
|
|
||||||
|
PrependQueue._prepend(self, item)
|
||||||
|
|
||||||
class TypeAlreadyInQueue(Exception):
|
class TypeAlreadyInQueue(Exception):
|
||||||
def __init__(self, t, *args, **kwargs):
|
def __init__(self, t, *args, **kwargs):
|
||||||
|
|
|
||||||
|
|
@ -2167,16 +2167,19 @@ class MachineCom(object):
|
||||||
|
|
||||||
def _enqueue_for_sending(self, command, linenumber=None, command_type=None, on_sent=None):
|
def _enqueue_for_sending(self, command, linenumber=None, command_type=None, on_sent=None):
|
||||||
"""
|
"""
|
||||||
Enqueues a command an optional linenumber to use for it in the send queue.
|
Enqueues a command and optional linenumber to use for it in the send queue.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
command (str): The command to send.
|
command (str): The command to send.
|
||||||
linenumber (int): The line number with which to send the command. May be ``None`` in which case the command
|
linenumber (int): The line number with which to send the command. May be ``None`` in which case the command
|
||||||
will be sent without a line number and checksum.
|
will be sent without a line number and checksum.
|
||||||
|
command_type (str): Optional command type, if set and command type is already in the queue the
|
||||||
|
command won't be enqueued
|
||||||
|
on_sent (callable): Optional callable to call after command has been sent to printer.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self._send_queue.put((command, linenumber, command_type, on_sent), item_type=command_type)
|
self._send_queue.put((command, linenumber, command_type, on_sent, False), item_type=command_type)
|
||||||
return True
|
return True
|
||||||
except TypeAlreadyInQueue as e:
|
except TypeAlreadyInQueue as e:
|
||||||
self._logger.debug("Type already in send queue: " + e.type)
|
self._logger.debug("Type already in send queue: " + e.type)
|
||||||
|
|
@ -2207,7 +2210,7 @@ class MachineCom(object):
|
||||||
self._dwelling_until = False
|
self._dwelling_until = False
|
||||||
|
|
||||||
# fetch command, command type and optional linenumber and sent callback from queue
|
# fetch command, command type and optional linenumber and sent callback from queue
|
||||||
command, linenumber, command_type, on_sent = entry
|
command, linenumber, command_type, on_sent, processed = entry
|
||||||
|
|
||||||
# some firmwares (e.g. Smoothie) might support additional in-band communication that will not
|
# some firmwares (e.g. Smoothie) might support additional in-band communication that will not
|
||||||
# stick to the acknowledgement behaviour of GCODE, so we check here if we have a GCODE command
|
# stick to the acknowledgement behaviour of GCODE, so we check here if we have a GCODE command
|
||||||
|
|
@ -2220,28 +2223,35 @@ class MachineCom(object):
|
||||||
self._do_send_with_checksum(command, linenumber)
|
self._do_send_with_checksum(command, linenumber)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# trigger "sending" phase
|
if not processed:
|
||||||
results = self._process_command_phase("sending", command, command_type, gcode=gcode)
|
# trigger "sending" phase if we didn't so far
|
||||||
|
results = self._process_command_phase("sending", command, command_type, gcode=gcode)
|
||||||
|
|
||||||
if not results:
|
if not results:
|
||||||
# No, we are not going to send this, that was a last-minute bail.
|
# No, we are not going to send this, that was a last-minute bail.
|
||||||
# However, since we already are in the send queue, our _monitor
|
# However, since we already are in the send queue, our _monitor
|
||||||
# loop won't be triggered with the reply from this unsent command
|
# loop won't be triggered with the reply from this unsent command
|
||||||
# now, so we try to tickle the processing of any active
|
# now, so we try to tickle the processing of any active
|
||||||
# command queues manually
|
# command queues manually
|
||||||
self._continue_sending()
|
self._continue_sending()
|
||||||
|
|
||||||
# and now let's fetch the next item from the queue
|
# and now let's fetch the next item from the queue
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# we explicitly throw away plugin hook results that try
|
if len(results) > 1:
|
||||||
# to perform command expansion in the sending/sent phase,
|
# last command gets on_sent attached
|
||||||
# so "results" really should only have more than one entry
|
last = results[-1]
|
||||||
# at this point if our core code contains a bug
|
self._send_queue.prepend((last[0], None, None, on_sent, True))
|
||||||
assert len(results) == 1
|
on_sent = None
|
||||||
|
|
||||||
# we only use the first (and only!) entry here
|
# middle gets prepended reversed (so order gets restored)
|
||||||
command, _, gcode = results[0]
|
if len(results) > 2:
|
||||||
|
to_prepend = reversed(results[1:-1])
|
||||||
|
for m in to_prepend:
|
||||||
|
self._send_queue.prepend((m[0], None, None, None, True))
|
||||||
|
|
||||||
|
# we only actually send the first entry here
|
||||||
|
command, _, gcode = results[0]
|
||||||
|
|
||||||
if command.strip() == "":
|
if command.strip() == "":
|
||||||
self._logger.info("Refusing to send an empty line to the printer")
|
self._logger.info("Refusing to send an empty line to the printer")
|
||||||
|
|
@ -2315,13 +2325,7 @@ class MachineCom(object):
|
||||||
self._logger.exception("Error while processing hook {name} for phase {phase} and command {command}:".format(**locals()))
|
self._logger.exception("Error while processing hook {name} for phase {phase} and command {command}:".format(**locals()))
|
||||||
else:
|
else:
|
||||||
normalized = _normalize_command_handler_result(command, command_type, gcode, hook_results)
|
normalized = _normalize_command_handler_result(command, command_type, gcode, hook_results)
|
||||||
|
new_results += normalized
|
||||||
# make sure we don't allow multi entry results in anything but the queuing phase
|
|
||||||
if not phase in ("queuing",) and len(normalized) > 1:
|
|
||||||
self._logger.error("Error while processing hook {name} for phase {phase} and command {command}: Hook returned multi-entry result for phase {phase} and command {command}. That's not supported, if you need to do multi expansion of commands you need to do this in the queuing phase. Ignoring hook result and sending command as-is.".format(**locals()))
|
|
||||||
new_results.append((command, command_type, gcode))
|
|
||||||
else:
|
|
||||||
new_results += normalized
|
|
||||||
if not new_results:
|
if not new_results:
|
||||||
# hook handler returned None or empty list for all commands, so we'll stop here and return a full out empty result
|
# hook handler returned None or empty list for all commands, so we'll stop here and return a full out empty result
|
||||||
return []
|
return []
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue