aboutsummaryrefslogtreecommitdiffstats
path: root/klippy/extras/motion_report.py
diff options
context:
space:
mode:
authorKevin O'Connor <kevin@koconnor.net>2021-07-21 12:48:23 -0400
committerKevin O'Connor <kevin@koconnor.net>2021-08-22 11:10:19 -0400
commit2fdd8a420d6e313d6261642c0505126869d017d2 (patch)
treecc362f0640faa0e06da7e955a1656712d1950549 /klippy/extras/motion_report.py
parentcf2e941aec5bbe1c992fcfde6bd91db2389e7e94 (diff)
downloadkutter-2fdd8a420d6e313d6261642c0505126869d017d2.tar.gz
kutter-2fdd8a420d6e313d6261642c0505126869d017d2.tar.xz
kutter-2fdd8a420d6e313d6261642c0505126869d017d2.zip
motion_report: Add support for dumping steps/trapq via API server
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
Diffstat (limited to 'klippy/extras/motion_report.py')
-rw-r--r--klippy/extras/motion_report.py130
1 files changed, 125 insertions, 5 deletions
diff --git a/klippy/extras/motion_report.py b/klippy/extras/motion_report.py
index ce7486e4..439c30c6 100644
--- a/klippy/extras/motion_report.py
+++ b/klippy/extras/motion_report.py
@@ -6,11 +6,79 @@
import logging
import chelper
+API_UPDATE_INTERVAL = 0.500
+
+# Helper to periodically transmit data to a set of API clients
+class APIDumpHelper:
+ def __init__(self, printer, data_cb, startstop_cb=None,
+ update_interval=API_UPDATE_INTERVAL):
+ self.printer = printer
+ self.data_cb = data_cb
+ if startstop_cb is None:
+ startstop_cb = (lambda is_start: None)
+ self.startstop_cb = startstop_cb
+ self.update_interval = update_interval
+ self.update_timer = None
+ self.clients = {}
+ def _stop(self):
+ self.clients.clear()
+ if self.update_timer is None:
+ return
+ reactor = self.printer.get_reactor()
+ reactor.unregister_timer(self.update_timer)
+ self.update_timer = None
+ try:
+ self.startstop_cb(False)
+ except self.printer.command_error as e:
+ logging.exception("API Dump Helper stop callback error")
+ return reactor.NEVER
+ def _start(self):
+ if self.update_timer is not None:
+ return
+ try:
+ self.startstop_cb(True)
+ except self.printer.command_error as e:
+ logging.exception("API Dump Helper start callback error")
+ self._stop()
+ return
+ reactor = self.printer.get_reactor()
+ systime = reactor.monotonic()
+ waketime = systime + self.update_interval
+ self.update_timer = reactor.register_timer(self._update, waketime)
+ def add_client(self, web_request):
+ cconn = web_request.get_client_connection()
+ template = web_request.get_dict('response_template', {})
+ self.clients[cconn] = template
+ self._start()
+ def _update(self, eventtime):
+ try:
+ msg = self.data_cb(eventtime)
+ except self.printer.command_error as e:
+ logging.exception("API Dump Helper data callback error")
+ return self._stop()
+ if not msg:
+ return eventtime + self.update_interval
+ for cconn, template in list(self.clients.items()):
+ if cconn.is_closed():
+ del self.clients[cconn]
+ if not self.clients:
+ return self._stop()
+ continue
+ tmp = dict(template)
+ tmp['params'] = msg
+ cconn.send(tmp)
+ return eventtime + self.update_interval
+
# Extract stepper queue_step messages
class DumpStepper:
def __init__(self, printer, mcu_stepper):
self.printer = printer
self.mcu_stepper = mcu_stepper
+ self.last_api_clock = 0
+ self.api_dump = APIDumpHelper(printer, self._api_update)
+ wh = self.printer.lookup_object('webhooks')
+ wh.register_mux_endpoint("motion_report/dump_stepper", "name",
+ mcu_stepper.get_name(), self._add_api_client)
def get_step_queue(self, start_clock, end_clock):
mcu_stepper = self.mcu_stepper
res = []
@@ -36,6 +104,30 @@ class DumpStepper:
% (i, s.first_clock, s.start_position, s.interval,
s.step_count, s.add))
logging.info('\n'.join(out))
+ def _api_update(self, eventtime):
+ data, cdata = self.get_step_queue(self.last_api_clock, 1<<63)
+ if not data:
+ return {}
+ clock_to_print_time = self.mcu_stepper.get_mcu().clock_to_print_time
+ first = data[0]
+ first_clock = first.first_clock
+ first_time = clock_to_print_time(first_clock)
+ self.last_api_clock = last_clock = data[-1].last_clock
+ last_time = clock_to_print_time(last_clock)
+ mcu_pos = first.start_position
+ start_position = self.mcu_stepper.mcu_to_commanded_position(mcu_pos)
+ step_dist = self.mcu_stepper.get_step_dist()
+ if self.mcu_stepper.is_dir_inverted():
+ step_dist = -step_dist
+ d = [(s.interval, s.step_count, s.add) for s in data]
+ return {"data": d, "start_position": start_position,
+ "start_mcu_position": mcu_pos, "step_distance": step_dist,
+ "first_clock": first_clock, "first_step_time": first_time,
+ "last_clock": last_clock, "last_step_time": last_time}
+ def _add_api_client(self, web_request):
+ self.api_dump.add_client(web_request)
+ hdr = ('interval', 'count', 'add')
+ web_request.send({'header': hdr})
NEVER_TIME = 9999999999999999.
@@ -45,7 +137,12 @@ class DumpTrapQ:
self.printer = printer
self.name = name
self.trapq = trapq
- def get_trapq(self, start_time, end_time):
+ self.last_api_msg = (0., 0.)
+ self.api_dump = APIDumpHelper(printer, self._api_update)
+ wh = self.printer.lookup_object('webhooks')
+ wh.register_mux_endpoint("motion_report/dump_trapq", "name", name,
+ self._add_api_client)
+ def extract_trapq(self, start_time, end_time):
ffi_main, ffi_lib = chelper.get_ffi()
res = []
while 1:
@@ -83,6 +180,23 @@ class DumpTrapQ:
move.start_z + move.z_r * dist)
velocity = move.start_v + move.accel * move_time
return pos, velocity
+ def _api_update(self, eventtime):
+ qtime = self.last_api_msg[0] + min(self.last_api_msg[1], 0.100)
+ data, cdata = self.extract_trapq(qtime, NEVER_TIME)
+ d = [(m.print_time, m.move_t, m.start_v, m.accel,
+ (m.start_x, m.start_y, m.start_z), (m.x_r, m.y_r, m.z_r))
+ for m in data]
+ if d and d[0] == self.last_api_msg:
+ d.pop(0)
+ if not d:
+ return {}
+ self.last_api_msg = d[-1]
+ return {"data": d}
+ def _add_api_client(self, web_request):
+ self.api_dump.add_client(web_request)
+ hdr = ('time', 'duration', 'start_velocity', 'acceleration',
+ 'start_position', 'direction')
+ web_request.send({'header': hdr})
STATUS_REFRESH_TIME = 0.250
@@ -94,8 +208,11 @@ class PrinterMotionReport:
# get_status information
self.next_status_time = 0.
gcode = self.printer.lookup_object('gcode')
- self.last_status = {'live_position': gcode.Coord(0., 0., 0., 0.),
- 'live_velocity': 0., 'live_extruder_velocity': 0.}
+ self.last_status = {
+ 'live_position': gcode.Coord(0., 0., 0., 0.),
+ 'live_velocity': 0., 'live_extruder_velocity': 0.,
+ 'steppers': [], 'trapq': [],
+ }
# Register handlers
self.printer.register_event_handler("klippy:connect", self._connect)
self.printer.register_event_handler("klippy:shutdown", self._shutdown)
@@ -117,6 +234,9 @@ class PrinterMotionReport:
break
etrapq = extruder.get_trapq()
self.trapqs[ename] = DumpTrapQ(self.printer, ename, etrapq)
+ # Populate 'trapq' and 'steppers' in get_status result
+ self.last_status['steppers'] = list(sorted(self.steppers.keys()))
+ self.last_status['trapq'] = list(sorted(self.trapqs.keys()))
# Shutdown handling
def _dump_shutdown(self, eventtime):
# Log stepper queue_steps on mcu that started shutdown (if any)
@@ -136,8 +256,8 @@ class PrinterMotionReport:
return
# Log trapqs around time of shutdown
for dtrapq in self.trapqs.values():
- data, cdata = dtrapq.get_trapq(shutdown_time - .100,
- shutdown_time + .100)
+ data, cdata = dtrapq.extract_trapq(shutdown_time - .100,
+ shutdown_time + .100)
dtrapq.log_trapq(data)
# Log estimated toolhead position at time of shutdown
dtrapq = self.trapqs.get('toolhead')