paneldue: execute commands synchronously
This prevents gcode requests from blocking temperature updates, accessing the file list, etc. Signed-off-by: Eric Callahan <arksine.code@gmail.com>
This commit is contained in:
parent
70501772c1
commit
eea2abd60e
|
@ -13,7 +13,6 @@ from collections import deque
|
||||||
from utils import ServerError
|
from utils import ServerError
|
||||||
from tornado import gen
|
from tornado import gen
|
||||||
from tornado.ioloop import IOLoop
|
from tornado.ioloop import IOLoop
|
||||||
from tornado.locks import Lock
|
|
||||||
|
|
||||||
MIN_EST_TIME = 10.
|
MIN_EST_TIME = 10.
|
||||||
INITIALIZE_TIMEOUT = 10.
|
INITIALIZE_TIMEOUT = 10.
|
||||||
|
@ -30,12 +29,11 @@ class SerialConnection:
|
||||||
self.paneldue = paneldue
|
self.paneldue = paneldue
|
||||||
self.port = config.get('serial')
|
self.port = config.get('serial')
|
||||||
self.baud = config.getint('baud', 57600)
|
self.baud = config.getint('baud', 57600)
|
||||||
self.sendlock = Lock()
|
|
||||||
self.partial_input = b""
|
self.partial_input = b""
|
||||||
self.ser = self.fd = None
|
self.ser = self.fd = None
|
||||||
self.connected = False
|
self.connected = False
|
||||||
self.busy = False
|
self.send_busy = False
|
||||||
self.pending_lines = []
|
self.send_buffer = b""
|
||||||
self.attempting_connect = True
|
self.attempting_connect = True
|
||||||
self.ioloop.spawn_callback(self._connect)
|
self.ioloop.spawn_callback(self._connect)
|
||||||
|
|
||||||
|
@ -48,6 +46,7 @@ class SerialConnection:
|
||||||
self.ser.close()
|
self.ser.close()
|
||||||
self.ser = None
|
self.ser = None
|
||||||
self.partial_input = b""
|
self.partial_input = b""
|
||||||
|
self.send_buffer = b""
|
||||||
self.paneldue.initialized = False
|
self.paneldue.initialized = False
|
||||||
logging.info("PanelDue Disconnected")
|
logging.info("PanelDue Disconnected")
|
||||||
if reconnect and not self.attempting_connect:
|
if reconnect and not self.attempting_connect:
|
||||||
|
@ -98,22 +97,14 @@ class SerialConnection:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Remove null bytes, separate into lines
|
# Remove null bytes, separate into lines
|
||||||
data = data.strip(b'\x00\xFF')
|
data = data.strip(b'\x00')
|
||||||
lines = data.split(b'\n')
|
lines = data.split(b'\n')
|
||||||
lines[0] = self.partial_input + lines[0]
|
lines[0] = self.partial_input + lines[0]
|
||||||
self.partial_input = lines.pop()
|
self.partial_input = lines.pop()
|
||||||
self.pending_lines.extend(lines)
|
for line in lines:
|
||||||
if self.busy:
|
|
||||||
return
|
|
||||||
self.busy = True
|
|
||||||
self.ioloop.spawn_callback(self._process_commands)
|
|
||||||
|
|
||||||
async def _process_commands(self):
|
|
||||||
while self.pending_lines:
|
|
||||||
line = self.pending_lines.pop(0)
|
|
||||||
line = line.strip().decode()
|
|
||||||
try:
|
try:
|
||||||
await self.paneldue.process_line(line)
|
line = line.strip().decode('utf-8', 'ignore')
|
||||||
|
self.paneldue.process_line(line)
|
||||||
except ServerError:
|
except ServerError:
|
||||||
logging.exception(
|
logging.exception(
|
||||||
f"GCode Processing Error: {line}")
|
f"GCode Processing Error: {line}")
|
||||||
|
@ -121,28 +112,31 @@ class SerialConnection:
|
||||||
f"!! GCode Processing Error: {line}")
|
f"!! GCode Processing Error: {line}")
|
||||||
except Exception:
|
except Exception:
|
||||||
logging.exception("Error during gcode processing")
|
logging.exception("Error during gcode processing")
|
||||||
self.busy = False
|
|
||||||
|
|
||||||
async def send(self, data):
|
def send(self, data):
|
||||||
if self.connected:
|
self.send_buffer += data
|
||||||
async with self.sendlock:
|
if not self.send_busy:
|
||||||
while data:
|
self.send_busy = True
|
||||||
try:
|
self.ioloop.spawn_callback(self._do_send)
|
||||||
sent = os.write(self.fd, data)
|
|
||||||
except os.error as e:
|
|
||||||
if e.errno == errno.EBADF or e.errno == errno.EPIPE:
|
|
||||||
sent = 0
|
|
||||||
else:
|
|
||||||
await gen.sleep(.001)
|
|
||||||
continue
|
|
||||||
if sent:
|
|
||||||
data = data[sent:]
|
|
||||||
else:
|
|
||||||
logging.exception(
|
|
||||||
"Error writing data, closing serial connection")
|
|
||||||
self.disconnect(reconnect=True)
|
|
||||||
return
|
|
||||||
|
|
||||||
|
async def _do_send(self):
|
||||||
|
while self.send_buffer:
|
||||||
|
try:
|
||||||
|
sent = os.write(self.fd, self.send_buffer)
|
||||||
|
except os.error as e:
|
||||||
|
if e.errno == errno.EBADF or e.errno == errno.EPIPE:
|
||||||
|
sent = 0
|
||||||
|
else:
|
||||||
|
await gen.sleep(.001)
|
||||||
|
continue
|
||||||
|
if sent:
|
||||||
|
self.send_buffer = self.send_buffer[sent:]
|
||||||
|
else:
|
||||||
|
logging.exception(
|
||||||
|
"Error writing data, closing serial connection")
|
||||||
|
self.disconnect(reconnect=True)
|
||||||
|
return
|
||||||
|
self.send_busy = False
|
||||||
|
|
||||||
class PanelDue:
|
class PanelDue:
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
|
@ -171,7 +165,7 @@ class PanelDue:
|
||||||
self.is_ready = False
|
self.is_ready = False
|
||||||
self.is_shutdown = False
|
self.is_shutdown = False
|
||||||
self.initialized = False
|
self.initialized = False
|
||||||
self.last_printer_state = 'C'
|
self.last_printer_state = 'O'
|
||||||
self.last_update_time = 0.
|
self.last_update_time = 0.
|
||||||
|
|
||||||
# Set up macros
|
# Set up macros
|
||||||
|
@ -296,31 +290,32 @@ class PanelDue:
|
||||||
self.is_shutdown = False
|
self.is_shutdown = False
|
||||||
self.is_ready = True
|
self.is_ready = True
|
||||||
|
|
||||||
async def _process_klippy_shutdown(self):
|
def _process_klippy_shutdown(self):
|
||||||
self.is_shutdown = True
|
self.is_shutdown = True
|
||||||
|
|
||||||
async def _process_klippy_disconnect(self):
|
def _process_klippy_disconnect(self):
|
||||||
# Tell the PD that we are shutting down
|
# Tell the PD that the printer is "off"
|
||||||
await self.write_response({'status': 'S'})
|
self.write_response({'status': 'O'})
|
||||||
self.is_ready = False
|
self.last_printer_state = 'O'
|
||||||
|
self.is_shutdown = self.is_shutdown = False
|
||||||
|
|
||||||
async def handle_status_update(self, status):
|
def handle_status_update(self, status):
|
||||||
for obj, items in status.items():
|
for obj, items in status.items():
|
||||||
if obj in self.printer_state:
|
if obj in self.printer_state:
|
||||||
self.printer_state[obj].update(items)
|
self.printer_state[obj].update(items)
|
||||||
else:
|
else:
|
||||||
self.printer_state[obj] = items
|
self.printer_state[obj] = items
|
||||||
|
|
||||||
async def paneldue_beep(self, frequency, duration):
|
def paneldue_beep(self, frequency, duration):
|
||||||
duration = int(duration * 1000.)
|
duration = int(duration * 1000.)
|
||||||
await self.write_response(
|
self.write_response(
|
||||||
{'beep_freq': frequency, 'beep_length': duration})
|
{'beep_freq': frequency, 'beep_length': duration})
|
||||||
|
|
||||||
async def process_line(self, line):
|
def process_line(self, line):
|
||||||
self.debug_queue.append(line)
|
self.debug_queue.append(line)
|
||||||
# If we find M112 in the line then skip verification
|
# If we find M112 in the line then skip verification
|
||||||
if "M112" in line.upper():
|
if "M112" in line.upper():
|
||||||
await self.klippy_apis.emergency_stop()
|
self.ioloop.spawn_callback(self.klippy_apis.emergency_stop)
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.enable_checksum:
|
if self.enable_checksum:
|
||||||
|
@ -356,11 +351,11 @@ class PanelDue:
|
||||||
logging.info("PanelDue: " + msg)
|
logging.info("PanelDue: " + msg)
|
||||||
raise PanelDueError(msg)
|
raise PanelDueError(msg)
|
||||||
|
|
||||||
await self._run_gcode(line[line_index+1:cs_index])
|
self._run_gcode(line[line_index+1:cs_index])
|
||||||
else:
|
else:
|
||||||
await self._run_gcode(line)
|
self._run_gcode(line)
|
||||||
|
|
||||||
async def _run_gcode(self, script):
|
def _run_gcode(self, script):
|
||||||
# Execute the gcode. Check for special RRF gcodes that
|
# Execute the gcode. Check for special RRF gcodes that
|
||||||
# require special handling
|
# require special handling
|
||||||
parts = script.split()
|
parts = script.split()
|
||||||
|
@ -381,7 +376,7 @@ class PanelDue:
|
||||||
return
|
return
|
||||||
params["arg_" + arg] = val
|
params["arg_" + arg] = val
|
||||||
func = self.direct_gcodes[cmd]
|
func = self.direct_gcodes[cmd]
|
||||||
await func(**params)
|
func(**params)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Prepare GCodes that require special handling
|
# Prepare GCodes that require special handling
|
||||||
|
@ -392,9 +387,11 @@ class PanelDue:
|
||||||
if not script:
|
if not script:
|
||||||
return
|
return
|
||||||
elif script in RESTART_GCODES:
|
elif script in RESTART_GCODES:
|
||||||
await self.klippy_apis.do_restart(script)
|
self.ioloop.spawn_callback(self.klippy_apis.do_restart, script)
|
||||||
return
|
return
|
||||||
|
self.ioloop.spawn_callback(self._send_klippy_gcode, script)
|
||||||
|
|
||||||
|
async def _send_klippy_gcode(self, script):
|
||||||
try:
|
try:
|
||||||
await self.klippy_apis.run_gcode(script)
|
await self.klippy_apis.run_gcode(script)
|
||||||
except self.server.error:
|
except self.server.error:
|
||||||
|
@ -466,7 +463,7 @@ class PanelDue:
|
||||||
mbox['msgBox.title'] = title
|
mbox['msgBox.title'] = title
|
||||||
mbox['msgBox.controls'] = 0
|
mbox['msgBox.controls'] = 0
|
||||||
mbox['msgBox.timeout'] = 0
|
mbox['msgBox.timeout'] = 0
|
||||||
self.ioloop.spawn_callback(self.write_response, mbox)
|
self.write_response(mbox)
|
||||||
|
|
||||||
def handle_gcode_response(self, response):
|
def handle_gcode_response(self, response):
|
||||||
# Only queue up "non-trivial" gcode responses. At the
|
# Only queue up "non-trivial" gcode responses. At the
|
||||||
|
@ -480,9 +477,9 @@ class PanelDue:
|
||||||
self.last_gcode_response = response
|
self.last_gcode_response = response
|
||||||
return
|
return
|
||||||
|
|
||||||
async def write_response(self, response):
|
def write_response(self, response):
|
||||||
byte_resp = json.dumps(response) + "\r\n"
|
byte_resp = json.dumps(response) + "\r\n"
|
||||||
await self.ser_conn.send(byte_resp.encode())
|
self.ser_conn.send(byte_resp.encode())
|
||||||
|
|
||||||
def _get_printer_status(self):
|
def _get_printer_status(self):
|
||||||
# PanelDue States applicable to Klipper:
|
# PanelDue States applicable to Klipper:
|
||||||
|
@ -516,7 +513,7 @@ class PanelDue:
|
||||||
|
|
||||||
return 'I'
|
return 'I'
|
||||||
|
|
||||||
async def _run_paneldue_M408(self, arg_r=None, arg_s=1):
|
def _run_paneldue_M408(self, arg_r=None, arg_s=1):
|
||||||
response = {}
|
response = {}
|
||||||
sequence = arg_r
|
sequence = arg_r
|
||||||
response_type = arg_s
|
response_type = arg_s
|
||||||
|
@ -530,19 +527,16 @@ class PanelDue:
|
||||||
response['dir'] = "/macros"
|
response['dir'] = "/macros"
|
||||||
response['files'] = list(self.available_macros.keys())
|
response['files'] = list(self.available_macros.keys())
|
||||||
self.initialized = True
|
self.initialized = True
|
||||||
|
|
||||||
if not self.is_ready:
|
if not self.is_ready:
|
||||||
self.last_printer_state = 'S' if self.is_shutdown else 'C'
|
self.last_printer_state = 'O'
|
||||||
response['status'] = self.last_printer_state
|
response['status'] = self.last_printer_state
|
||||||
await self.write_response(response)
|
self.write_response(response)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Send gcode responses
|
|
||||||
if sequence is not None and self.last_gcode_response:
|
if sequence is not None and self.last_gcode_response:
|
||||||
|
# Send gcode responses
|
||||||
response['seq'] = sequence + 1
|
response['seq'] = sequence + 1
|
||||||
response['resp'] = self.last_gcode_response
|
response['resp'] = self.last_gcode_response
|
||||||
self.last_gcode_response = None
|
self.last_gcode_response = None
|
||||||
|
|
||||||
if response_type == 1:
|
if response_type == 1:
|
||||||
# Extended response Request
|
# Extended response Request
|
||||||
response['myName'] = self.machine_name
|
response['myName'] = self.machine_name
|
||||||
|
@ -642,9 +636,9 @@ class PanelDue:
|
||||||
# is strange about this, and displays it as a full screen
|
# is strange about this, and displays it as a full screen
|
||||||
# notification
|
# notification
|
||||||
self.last_message = msg
|
self.last_message = msg
|
||||||
await self.write_response(response)
|
self.write_response(response)
|
||||||
|
|
||||||
async def _run_paneldue_M20(self, arg_p, arg_s=0):
|
def _run_paneldue_M20(self, arg_p, arg_s=0):
|
||||||
response_type = arg_s
|
response_type = arg_s
|
||||||
if response_type != 2:
|
if response_type != 2:
|
||||||
logging.info(
|
logging.info(
|
||||||
|
@ -682,9 +676,9 @@ class PanelDue:
|
||||||
flist = self.file_manager.list_dir(path, simple_format=True)
|
flist = self.file_manager.list_dir(path, simple_format=True)
|
||||||
if flist:
|
if flist:
|
||||||
response['files'] = flist
|
response['files'] = flist
|
||||||
await self.write_response(response)
|
self.write_response(response)
|
||||||
|
|
||||||
async def _run_paneldue_M30(self, arg_p=None):
|
def _run_paneldue_M30(self, arg_p=None):
|
||||||
# Delete a file. Clean up the file name and make sure
|
# Delete a file. Clean up the file name and make sure
|
||||||
# it is relative to the "gcodes" root.
|
# it is relative to the "gcodes" root.
|
||||||
path = arg_p
|
path = arg_p
|
||||||
|
@ -696,9 +690,9 @@ class PanelDue:
|
||||||
|
|
||||||
if not path.startswith("gcodes/"):
|
if not path.startswith("gcodes/"):
|
||||||
path = "gcodes/" + path
|
path = "gcodes/" + path
|
||||||
await self.file_manager.delete_file(path)
|
self.ioloop.spawn_callback(self.file_manager.delete_file, path)
|
||||||
|
|
||||||
async def _run_paneldue_M36(self, arg_p=None):
|
def _run_paneldue_M36(self, arg_p=None):
|
||||||
response = {}
|
response = {}
|
||||||
filename = arg_p
|
filename = arg_p
|
||||||
sd_status = self.printer_state.get('virtual_sdcard', {})
|
sd_status = self.printer_state.get('virtual_sdcard', {})
|
||||||
|
@ -713,12 +707,11 @@ class PanelDue:
|
||||||
if not filename or not active:
|
if not filename or not active:
|
||||||
# Either no file printing or no virtual_sdcard
|
# Either no file printing or no virtual_sdcard
|
||||||
response['err'] = 1
|
response['err'] = 1
|
||||||
await self.write_response(response)
|
self.write_response(response)
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
response['fileName'] = filename.split("/")[-1]
|
response['fileName'] = filename.split("/")[-1]
|
||||||
|
|
||||||
|
|
||||||
# For consistency make sure that the filename begins with the
|
# For consistency make sure that the filename begins with the
|
||||||
# "gcodes/" root. The M20 HACK should add this in some cases.
|
# "gcodes/" root. The M20 HACK should add this in some cases.
|
||||||
# Ideally we would add support to the PanelDue firmware that
|
# Ideally we would add support to the PanelDue firmware that
|
||||||
|
@ -749,9 +742,9 @@ class PanelDue:
|
||||||
response['printTime'] = int(est_time + .5)
|
response['printTime'] = int(est_time + .5)
|
||||||
else:
|
else:
|
||||||
response['err'] = 1
|
response['err'] = 1
|
||||||
await self.write_response(response)
|
self.write_response(response)
|
||||||
|
|
||||||
async def close(self):
|
def close(self):
|
||||||
self.ser_conn.disconnect()
|
self.ser_conn.disconnect()
|
||||||
msg = "\nPanelDue GCode Dump:"
|
msg = "\nPanelDue GCode Dump:"
|
||||||
for i, gc in enumerate(self.debug_queue):
|
for i, gc in enumerate(self.debug_queue):
|
||||||
|
|
Loading…
Reference in New Issue