diff options
Diffstat (limited to 'scripts/motan/readlog.py')
-rw-r--r-- | scripts/motan/readlog.py | 413 |
1 files changed, 253 insertions, 160 deletions
diff --git a/scripts/motan/readlog.py b/scripts/motan/readlog.py index 43c01619..8f86d4e2 100644 --- a/scripts/motan/readlog.py +++ b/scripts/motan/readlog.py @@ -5,6 +5,7 @@ # This file may be distributed under the terms of the GNU GPLv3 license. import json, zlib + class error(Exception): pass @@ -16,81 +17,98 @@ class error(Exception): # Log data handlers: {name: class, ...} LogHandlers = {} + # Extract status fields from log class HandleStatusField: SubscriptionIdParts = 0 ParametersMin = ParametersMax = 1 DataSets = [ - ('status(<field>)', 'A get_status field name (separate by periods)'), + ("status(<field>)", "A get_status field name (separate by periods)"), ] + def __init__(self, lmanager, name, name_parts): self.status_tracker = lmanager.get_status_tracker() self.field_name = name_parts[1] - self.field_parts = name_parts[1].split('.') - self.next_update_time = 0. + self.field_parts = name_parts[1].split(".") + self.next_update_time = 0.0 self.result = None + def get_label(self): - label = '%s field' % (self.field_name,) - return {'label': label, 'units': 'Unknown'} + label = "%s field" % (self.field_name,) + return {"label": label, "units": "Unknown"} + def pull_data(self, req_time): if req_time < self.next_update_time: return self.result db, next_update_time = self.status_tracker.pull_status(req_time) for fp in self.field_parts[:-1]: db = db.get(fp, {}) - self.result = db.get(self.field_parts[-1], 0.) + self.result = db.get(self.field_parts[-1], 0.0) self.next_update_time = next_update_time return self.result + + LogHandlers["status"] = HandleStatusField + # Extract requested position, velocity, and accel from a trapq log class HandleTrapQ: SubscriptionIdParts = 2 ParametersMin = ParametersMax = 2 DataSets = [ - ('trapq(<name>,velocity)', 'Requested velocity for the given trapq'), - ('trapq(<name>,accel)', 'Requested acceleration for the given trapq'), - ('trapq(<name>,<axis>)', 'Requested axis (x, y, or z) position'), - ('trapq(<name>,<axis>_velocity)', 'Requested axis velocity'), - ('trapq(<name>,<axis>_accel)', 'Requested axis acceleration'), + ("trapq(<name>,velocity)", "Requested velocity for the given trapq"), + ("trapq(<name>,accel)", "Requested acceleration for the given trapq"), + ("trapq(<name>,<axis>)", "Requested axis (x, y, or z) position"), + ("trapq(<name>,<axis>_velocity)", "Requested axis velocity"), + ("trapq(<name>,<axis>_accel)", "Requested axis acceleration"), ] + def __init__(self, lmanager, name, name_parts): self.name = name self.jdispatch = lmanager.get_jdispatch() - self.cur_data = [(0., 0., 0., 0., (0., 0., 0.), (0., 0., 0.))] + self.cur_data = [(0.0, 0.0, 0.0, 0.0, (0.0, 0.0, 0.0), (0.0, 0.0, 0.0))] self.data_pos = 0 tq, trapq_name, datasel = name_parts ptypes = {} - ptypes['velocity'] = { - 'label': '%s velocity' % (trapq_name,), - 'units': 'Velocity\n(mm/s)', 'func': self._pull_velocity + ptypes["velocity"] = { + "label": "%s velocity" % (trapq_name,), + "units": "Velocity\n(mm/s)", + "func": self._pull_velocity, } - ptypes['accel'] = { - 'label': '%s acceleration' % (trapq_name,), - 'units': 'Acceleration\n(mm/s^2)', 'func': self._pull_accel + ptypes["accel"] = { + "label": "%s acceleration" % (trapq_name,), + "units": "Acceleration\n(mm/s^2)", + "func": self._pull_accel, } for axis, name in enumerate("xyz"): - ptypes['%s' % (name,)] = { - 'label': '%s %s position' % (trapq_name, name), 'axis': axis, - 'units': 'Position\n(mm)', 'func': self._pull_axis_position + ptypes["%s" % (name,)] = { + "label": "%s %s position" % (trapq_name, name), + "axis": axis, + "units": "Position\n(mm)", + "func": self._pull_axis_position, } - ptypes['%s_velocity' % (name,)] = { - 'label': '%s %s velocity' % (trapq_name, name), 'axis': axis, - 'units': 'Velocity\n(mm/s)', 'func': self._pull_axis_velocity + ptypes["%s_velocity" % (name,)] = { + "label": "%s %s velocity" % (trapq_name, name), + "axis": axis, + "units": "Velocity\n(mm/s)", + "func": self._pull_axis_velocity, } - ptypes['%s_accel' % (name,)] = { - 'label': '%s %s acceleration' % (trapq_name, name), - 'axis': axis, 'units': 'Acceleration\n(mm/s^2)', - 'func': self._pull_axis_accel + ptypes["%s_accel" % (name,)] = { + "label": "%s %s acceleration" % (trapq_name, name), + "axis": axis, + "units": "Acceleration\n(mm/s^2)", + "func": self._pull_axis_accel, } pinfo = ptypes.get(datasel) if pinfo is None: raise error("Unknown trapq data selection '%s'" % (datasel,)) - self.label = {'label': pinfo['label'], 'units': pinfo['units']} - self.axis = pinfo.get('axis') - self.pull_data = pinfo['func'] + self.label = {"label": pinfo["label"], "units": pinfo["units"]} + self.axis = pinfo.get("axis") + self.pull_data = pinfo["func"] + def get_label(self): return self.label + def _find_move(self, req_time): data_pos = self.data_pos while 1: @@ -105,54 +123,63 @@ class HandleTrapQ: jmsg = self.jdispatch.pull_msg(req_time, self.name) if jmsg is None: return move, False - self.cur_data = jmsg['data'] + self.cur_data = jmsg["data"] self.data_pos = data_pos = 0 + def _pull_axis_position(self, req_time): move, in_range = self._find_move(req_time) print_time, move_t, start_v, accel, start_pos, axes_r = move - mtime = max(0., min(move_t, req_time - print_time)) - dist = (start_v + .5 * accel * mtime) * mtime; + mtime = max(0.0, min(move_t, req_time - print_time)) + dist = (start_v + 0.5 * accel * mtime) * mtime return start_pos[self.axis] + axes_r[self.axis] * dist + def _pull_axis_velocity(self, req_time): move, in_range = self._find_move(req_time) if not in_range: - return 0. + return 0.0 print_time, move_t, start_v, accel, start_pos, axes_r = move return (start_v + accel * (req_time - print_time)) * axes_r[self.axis] + def _pull_axis_accel(self, req_time): move, in_range = self._find_move(req_time) if not in_range: - return 0. + return 0.0 print_time, move_t, start_v, accel, start_pos, axes_r = move return accel * axes_r[self.axis] + def _pull_velocity(self, req_time): move, in_range = self._find_move(req_time) if not in_range: - return 0. + return 0.0 print_time, move_t, start_v, accel, start_pos, axes_r = move return start_v + accel * (req_time - print_time) + def _pull_accel(self, req_time): move, in_range = self._find_move(req_time) if not in_range: - return 0. + return 0.0 print_time, move_t, start_v, accel, start_pos, axes_r = move return accel + + LogHandlers["trapq"] = HandleTrapQ + # Extract positions from queue_step log class HandleStepQ: SubscriptionIdParts = 2 ParametersMin = 1 ParametersMax = 2 DataSets = [ - ('stepq(<stepper>)', 'Commanded position of the given stepper'), - ('stepq(<stepper>,<time>)', 'Commanded position with smooth time'), + ("stepq(<stepper>)", "Commanded position of the given stepper"), + ("stepq(<stepper>,<time>)", "Commanded position with smooth time"), ] + def __init__(self, lmanager, name, name_parts): self.name = name self.stepper_name = name_parts[1] self.jdispatch = lmanager.get_jdispatch() - self.step_data = [(0., 0., 0.), (0., 0., 0.)] # [(time, half_pos, pos)] + self.step_data = [(0.0, 0.0, 0.0), (0.0, 0.0, 0.0)] # [(time, half_pos, pos)] self.data_pos = 0 self.smooth_time = 0.010 if len(name_parts) == 3: @@ -160,9 +187,11 @@ class HandleStepQ: self.smooth_time = float(name_parts[2]) except ValueError: raise error("Invalid stepq smooth time '%s'" % (name_parts[2],)) + def get_label(self): - label = '%s position' % (self.stepper_name,) - return {'label': label, 'units': 'Position\n(mm)'} + label = "%s position" % (self.stepper_name,) + return {"label": label, "units": "Position\n(mm)"} + def pull_data(self, req_time): smooth_time = self.smooth_time while 1: @@ -183,7 +212,7 @@ class HandleStepQ: if stime <= smooth_time: pdiff = next_halfpos - last_halfpos return last_halfpos + rtdiff * pdiff / stime - stime = .5 * smooth_time + stime = 0.5 * smooth_time if rtdiff < stime: pdiff = last_pos - last_halfpos return last_halfpos + rtdiff * pdiff / stime @@ -192,6 +221,7 @@ class HandleStepQ: pdiff = last_pos - next_halfpos return next_halfpos + rtdiff * pdiff / stime return last_pos + def _pull_block(self, req_time): step_data = self.step_data del step_data[:-1] @@ -201,25 +231,25 @@ class HandleStepQ: jmsg = self.jdispatch.pull_msg(req_time, self.name) if jmsg is None: last_time, last_halfpos, last_pos = step_data[0] - self.step_data.append((req_time + .1, last_pos, last_pos)) + self.step_data.append((req_time + 0.1, last_pos, last_pos)) return - last_time = jmsg['last_step_time'] + last_time = jmsg["last_step_time"] if req_time <= last_time: break # Process block into (time, half_position, position) 3-tuples - first_time = step_time = jmsg['first_step_time'] - first_clock = jmsg['first_clock'] - step_clock = first_clock - jmsg['data'][0][0] - cdiff = jmsg['last_clock'] - first_clock + first_time = step_time = jmsg["first_step_time"] + first_clock = jmsg["first_clock"] + step_clock = first_clock - jmsg["data"][0][0] + cdiff = jmsg["last_clock"] - first_clock tdiff = last_time - first_time - inv_freq = 0. + inv_freq = 0.0 if cdiff: inv_freq = tdiff / cdiff - step_dist = jmsg['step_distance'] - step_pos = jmsg['start_position'] + step_dist = jmsg["step_distance"] + step_pos = jmsg["start_position"] if not step_data[0][0]: - step_data[0] = (0., step_pos, step_pos) - for interval, raw_count, add in jmsg['data']: + step_data[0] = (0.0, step_pos, step_pos) + for interval, raw_count, add in jmsg["data"]: qs_dist = step_dist count = raw_count if count < 0: @@ -229,22 +259,30 @@ class HandleStepQ: step_clock += interval interval += add step_time = first_time + (step_clock - first_clock) * inv_freq - step_halfpos = step_pos + .5 * qs_dist + step_halfpos = step_pos + 0.5 * qs_dist step_pos += qs_dist step_data.append((step_time, step_halfpos, step_pos)) + + LogHandlers["stepq"] = HandleStepQ + # Extract tmc current and stallguard data from the log class HandleStallguard: SubscriptionIdParts = 2 ParametersMin = 2 ParametersMax = 2 DataSets = [ - ('stallguard(<stepper>,sg_result)', - 'Stallguard result of the given stepper driver'), - ('stallguard(<stepper>,cs_actual)', - 'Current level result of the given stepper driver'), + ( + "stallguard(<stepper>,sg_result)", + "Stallguard result of the given stepper driver", + ), + ( + "stallguard(<stepper>,cs_actual)", + "Current level result of the given stepper driver", + ), ] + def __init__(self, lmanager, name, name_parts): self.name = name self.stepper_name = name_parts[1] @@ -253,7 +291,7 @@ class HandleStallguard: self.data = [] self.ret = None self.driver_name = "" - for k in lmanager.get_initial_status()['configfile']['settings']: + for k in lmanager.get_initial_status()["configfile"]["settings"]: if not k.startswith("tmc"): continue if k.endswith(self.stepper_name): @@ -261,15 +299,16 @@ class HandleStallguard: break # Current decode self.status_tracker = lmanager.get_status_tracker() - self.next_status_time = 0. + self.next_status_time = 0.0 self.irun = 0 + def get_label(self): - label = '%s %s %s' % (self.driver_name, self.stepper_name, - self.filter) + label = "%s %s %s" % (self.driver_name, self.stepper_name, self.filter) if self.filter == "sg_result": - return {'label': label, 'units': 'Stallguard'} + return {"label": label, "units": "Stallguard"} elif self.filter == "cs_actual": - return {'label': label, 'units': 'CS Actual'} + return {"label": label, "units": "CS Actual"} + # Search datapoint in dataset extrapolate in between def pull_data(self, req_time): while 1: @@ -290,25 +329,30 @@ class HandleStallguard: if req_time <= time: return self.ret[self.filter] self.ret = None + + LogHandlers["stallguard"] = HandleStallguard + # Extract stepper motor phase position class HandleStepPhase: SubscriptionIdParts = 0 ParametersMin = 1 ParametersMax = 2 DataSets = [ - ('step_phase(<driver>)', 'Stepper motor phase of the given stepper'), - ('step_phase(<driver>,microstep)', 'Microstep position for stepper'), + ("step_phase(<driver>)", "Stepper motor phase of the given stepper"), + ("step_phase(<driver>,microstep)", "Microstep position for stepper"), ] + def __init__(self, lmanager, name, name_parts): self.name = name self.driver_name = name_parts[1] self.stepper_name = " ".join(self.driver_name.split()[1:]) - config = lmanager.get_initial_status()['configfile']['settings'] + config = lmanager.get_initial_status()["configfile"]["settings"] if self.driver_name not in config or self.stepper_name not in config: - raise error("Unable to find stepper driver '%s' config" - % (self.driver_name,)) + raise error( + "Unable to find stepper driver '%s' config" % (self.driver_name,) + ) if len(name_parts) == 3 and name_parts[2] != "microstep": raise error("Unknown step_phase selection '%s'" % (name_parts[2],)) self.report_microsteps = len(name_parts) == 3 @@ -319,23 +363,28 @@ class HandleStepPhase: self.jdispatch = lmanager.get_jdispatch() self.jdispatch.add_handler(name, "stepq:" + self.stepper_name) # stepq tracking - self.step_data = [(0., 0), (0., 0)] # [(time, mcu_pos)] + self.step_data = [(0.0, 0), (0.0, 0)] # [(time, mcu_pos)] self.data_pos = 0 # driver phase tracking self.status_tracker = lmanager.get_status_tracker() - self.next_status_time = 0. + self.next_status_time = 0.0 self.mcu_phase_offset = 0 + def get_label(self): if self.report_microsteps: - return {'label': '%s microstep' % (self.stepper_name,), - 'units': 'Microstep'} - return {'label': '%s phase' % (self.stepper_name,), 'units': 'Phase'} + return { + "label": "%s microstep" % (self.stepper_name,), + "units": "Microstep", + } + return {"label": "%s phase" % (self.stepper_name,), "units": "Phase"} + def _pull_phase_offset(self, req_time): db, self.next_status_time = self.status_tracker.pull_status(req_time) - mcu_phase_offset = db.get(self.driver_name, {}).get('mcu_phase_offset') + mcu_phase_offset = db.get(self.driver_name, {}).get("mcu_phase_offset") if mcu_phase_offset is None: mcu_phase_offset = 0 self.mcu_phase_offset = mcu_phase_offset + def pull_data(self, req_time): if req_time >= self.next_status_time: self._pull_phase_offset(req_time) @@ -352,6 +401,7 @@ class HandleStepPhase: continue step_pos = step_data[data_pos][1] return (step_pos + self.mcu_phase_offset) % self.phases + def _pull_block(self, req_time): step_data = self.step_data del step_data[:-1] @@ -361,24 +411,24 @@ class HandleStepPhase: jmsg = self.jdispatch.pull_msg(req_time, self.name) if jmsg is None: last_time, last_pos = step_data[0] - self.step_data.append((req_time + .1, last_pos)) + self.step_data.append((req_time + 0.1, last_pos)) return - last_time = jmsg['last_step_time'] + last_time = jmsg["last_step_time"] if req_time <= last_time: break # Process block into (time, position) 2-tuples - first_time = step_time = jmsg['first_step_time'] - first_clock = jmsg['first_clock'] - step_clock = first_clock - jmsg['data'][0][0] - cdiff = jmsg['last_clock'] - first_clock + first_time = step_time = jmsg["first_step_time"] + first_clock = jmsg["first_clock"] + step_clock = first_clock - jmsg["data"][0][0] + cdiff = jmsg["last_clock"] - first_clock tdiff = last_time - first_time - inv_freq = 0. + inv_freq = 0.0 if cdiff: inv_freq = tdiff / cdiff - step_pos = jmsg['start_mcu_position'] + step_pos = jmsg["start_mcu_position"] if not step_data[0][0]: - step_data[0] = (0., step_pos) - for interval, raw_count, add in jmsg['data']: + step_data[0] = (0.0, step_pos) + for interval, raw_count, add in jmsg["data"]: qs_dist = 1 count = raw_count if count < 0: @@ -390,29 +440,35 @@ class HandleStepPhase: step_time = first_time + (step_clock - first_clock) * inv_freq step_pos += qs_dist step_data.append((step_time, step_pos)) + + LogHandlers["step_phase"] = HandleStepPhase + # Extract accelerometer data class HandleADXL345: SubscriptionIdParts = 2 ParametersMin = ParametersMax = 2 DataSets = [ - ('adxl345(<name>,<axis>)', 'Accelerometer for given axis (x, y, or z)'), + ("adxl345(<name>,<axis>)", "Accelerometer for given axis (x, y, or z)"), ] + def __init__(self, lmanager, name, name_parts): self.name = name self.adxl_name = name_parts[1] self.jdispatch = lmanager.get_jdispatch() - self.next_accel_time = self.last_accel_time = 0. - self.next_accel = self.last_accel = (0., 0., 0.) + self.next_accel_time = self.last_accel_time = 0.0 + self.next_accel = self.last_accel = (0.0, 0.0, 0.0) self.cur_data = [] self.data_pos = 0 - if name_parts[2] not in 'xyz': + if name_parts[2] not in "xyz": raise error("Unknown adxl345 data selection '%s'" % (name,)) - self.axis = 'xyz'.index(name_parts[2]) + self.axis = "xyz".index(name_parts[2]) + def get_label(self): - label = '%s %s acceleration' % (self.adxl_name, 'xyz'[self.axis]) - return {'label': label, 'units': 'Acceleration\n(mm/s^2)'} + label = "%s %s acceleration" % (self.adxl_name, "xyz"[self.axis]) + return {"label": label, "units": "Acceleration\n(mm/s^2)"} + def pull_data(self, req_time): axis = self.axis while 1: @@ -425,8 +481,8 @@ class HandleADXL345: # Read next data block jmsg = self.jdispatch.pull_msg(req_time, self.name) if jmsg is None: - return 0. - self.cur_data = jmsg['data'] + return 0.0 + self.cur_data = jmsg["data"] self.data_pos = 0 continue self.last_accel = self.next_accel @@ -434,42 +490,50 @@ class HandleADXL345: self.next_accel_time, x, y, z = self.cur_data[self.data_pos] self.next_accel = (x, y, z) self.data_pos += 1 + + LogHandlers["adxl345"] = HandleADXL345 + # Extract positions from magnetic angle sensor class HandleAngle: SubscriptionIdParts = 2 ParametersMin = ParametersMax = 1 DataSets = [ - ('angle(<name>)', 'Angle sensor position'), + ("angle(<name>)", "Angle sensor position"), ] + def __init__(self, lmanager, name, name_parts): self.name = name self.angle_name = name_parts[1] self.jdispatch = lmanager.get_jdispatch() - self.next_angle_time = self.last_angle_time = 0. - self.next_angle = self.last_angle = 0. + self.next_angle_time = self.last_angle_time = 0.0 + self.next_angle = self.last_angle = 0.0 self.cur_data = [] self.data_pos = 0 - self.position_offset = 0. - self.angle_dist = 1. + self.position_offset = 0.0 + self.angle_dist = 1.0 # Determine angle distance from associated stepper's rotation_distance - config = lmanager.get_initial_status()['configfile']['settings'] - aname = 'angle %s' % (self.angle_name,) - stepper_name = config.get(aname, {}).get('stepper') + config = lmanager.get_initial_status()["configfile"]["settings"] + aname = "angle %s" % (self.angle_name,) + stepper_name = config.get(aname, {}).get("stepper") if stepper_name is not None: sconfig = config.get(stepper_name, {}) - rotation_distance = sconfig.get('rotation_distance', 1.) - gear_ratio = sconfig.get('gear_ratio', ()) - if type(gear_ratio) == str: # XXX - gear_ratio = [[float(v.strip()) for v in gr.split(':')] - for gr in gear_ratio.split(',')] + rotation_distance = sconfig.get("rotation_distance", 1.0) + gear_ratio = sconfig.get("gear_ratio", ()) + if type(gear_ratio) == str: # XXX + gear_ratio = [ + [float(v.strip()) for v in gr.split(":")] + for gr in gear_ratio.split(",") + ] for n, d in gear_ratio: rotation_distance *= d / n - self.angle_dist = rotation_distance / 65536. + self.angle_dist = rotation_distance / 65536.0 + def get_label(self): - label = '%s position' % (self.angle_name,) - return {'label': label, 'units': 'Position\n(mm)'} + label = "%s position" % (self.angle_name,) + return {"label": label, "units": "Position\n(mm)"} + def pull_data(self, req_time): while 1: if req_time <= self.next_angle_time: @@ -477,16 +541,14 @@ class HandleAngle: tdiff = self.next_angle_time - self.last_angle_time rtdiff = req_time - self.last_angle_time po = rtdiff * pdiff / tdiff - return ((self.last_angle + po) * self.angle_dist - + self.position_offset) + return (self.last_angle + po) * self.angle_dist + self.position_offset if self.data_pos >= len(self.cur_data): # Read next data block jmsg = self.jdispatch.pull_msg(req_time, self.name) if jmsg is None: - return (self.next_angle * self.angle_dist - + self.position_offset) - self.cur_data = jmsg['data'] - position_offset = jmsg.get('position_offset') + return self.next_angle * self.angle_dist + self.position_offset + self.cur_data = jmsg["data"] + position_offset = jmsg.get("position_offset") if position_offset is not None: self.position_offset = position_offset self.data_pos = 0 @@ -495,24 +557,29 @@ class HandleAngle: self.last_angle_time = self.next_angle_time self.next_angle_time, self.next_angle = self.cur_data[self.data_pos] self.data_pos += 1 + + LogHandlers["angle"] = HandleAngle + def interpolate(next_val, prev_val, next_time, prev_time, req_time): vdiff = next_val - prev_val tdiff = next_time - prev_time rtdiff = req_time - prev_time return prev_val + rtdiff * vdiff / tdiff + # Extract eddy current data class HandleEddyCurrent: SubscriptionIdParts = 2 ParametersMin = 1 ParametersMax = 2 DataSets = [ - ('ldc1612(<name>)', 'Coil resonant frequency'), - ('ldc1612(<name>,period)', 'Coil resonant period'), - ('ldc1612(<name>,z)', 'Estimated Z height'), + ("ldc1612(<name>)", "Coil resonant frequency"), + ("ldc1612(<name>,period)", "Coil resonant period"), + ("ldc1612(<name>,z)", "Estimated Z height"), ] + def __init__(self, lmanager, name, name_parts): self.name = name self.sensor_name = name_parts[1] @@ -521,18 +588,20 @@ class HandleEddyCurrent: self.report_frequency = len(name_parts) == 2 self.report_z = len(name_parts) == 3 and name_parts[2] == "z" self.jdispatch = lmanager.get_jdispatch() - self.next_samp = self.prev_samp = [0., 0., 0.] + self.next_samp = self.prev_samp = [0.0, 0.0, 0.0] self.cur_data = [] self.data_pos = 0 + def get_label(self): if self.report_frequency: - label = '%s frequency' % (self.sensor_name,) - return {'label': label, 'units': 'Frequency\n(Hz)'} + label = "%s frequency" % (self.sensor_name,) + return {"label": label, "units": "Frequency\n(Hz)"} if self.report_z: - label = '%s height' % (self.sensor_name,) - return {'label': label, 'units': 'Position\n(mm)'} - label = '%s period' % (self.sensor_name,) - return {'label': label, 'units': 'Period\n(s)'} + label = "%s height" % (self.sensor_name,) + return {"label": label, "units": "Position\n(mm)"} + label = "%s period" % (self.sensor_name,) + return {"label": label, "units": "Period\n(s)"} + def pull_data(self, req_time): while 1: next_time, next_freq, next_z = self.next_samp @@ -545,21 +614,22 @@ class HandleEddyCurrent: next_val = next_z prev_val = prev_z else: - next_val = 1. / next_freq - prev_val = 1. / prev_freq - return interpolate(next_val, prev_val, next_time, prev_time, - req_time) + next_val = 1.0 / next_freq + prev_val = 1.0 / prev_freq + return interpolate(next_val, prev_val, next_time, prev_time, req_time) if self.data_pos >= len(self.cur_data): # Read next data block jmsg = self.jdispatch.pull_msg(req_time, self.name) if jmsg is None: - return 0. - self.cur_data = jmsg['data'] + return 0.0 + self.cur_data = jmsg["data"] self.data_pos = 0 continue self.prev_samp = self.next_samp self.next_samp = self.cur_data[self.data_pos] self.data_pos += 1 + + LogHandlers["ldc1612"] = HandleEddyCurrent @@ -567,15 +637,18 @@ LogHandlers["ldc1612"] = HandleEddyCurrent # Log reading ###################################################################### + # Read, uncompress, and parse messages in a log built by data_logger.py class JsonLogReader: def __init__(self, filename): self.file = open(filename, "rb") self.comp = zlib.decompressobj(31) self.msgs = [b""] + def seek(self, pos): self.file.seek(pos) self.comp = zlib.decompressobj(-15) + def pull_msg(self): msgs = self.msgs while 1: @@ -591,55 +664,61 @@ class JsonLogReader: if not raw_data: return None data = self.comp.decompress(raw_data) - parts = data.split(b'\x03') + parts = data.split(b"\x03") parts[0] = msgs[0] + parts[0] self.msgs = msgs = parts + # Store messages in per-subscription queues until handlers are ready for them class JsonDispatcher: def __init__(self, log_prefix): self.names = {} self.queues = {} - self.last_read_time = 0. + self.last_read_time = 0.0 self.log_reader = JsonLogReader(log_prefix + ".json.gz") self.is_eof = False + def check_end_of_data(self): return self.is_eof and not any(self.queues.values()) + def add_handler(self, name, subscription_id): self.names[name] = q = [] self.queues.setdefault(subscription_id, []).append(q) + def pull_msg(self, req_time, name): q = self.names[name] while 1: if q: return q.pop(0) - if req_time + 1. < self.last_read_time: + if req_time + 1.0 < self.last_read_time: return None json_msg = self.log_reader.pull_msg() if json_msg is None: self.is_eof = True return None - qid = json_msg.get('q') - if qid == 'status': - pt = json_msg.get('toolhead', {}).get('estimated_print_time') + qid = json_msg.get("q") + if qid == "status": + pt = json_msg.get("toolhead", {}).get("estimated_print_time") if pt is not None: self.last_read_time = pt for mq in self.queues.get(qid, []): - mq.append(json_msg['params']) + mq.append(json_msg["params"]) ###################################################################### # Dataset and log tracking ###################################################################### + # Tracking of get_status messages class TrackStatus: def __init__(self, lmanager, name, start_status): self.name = name self.jdispatch = lmanager.get_jdispatch() - self.next_status_time = 0. + self.next_status_time = 0.0 self.status = dict(start_status) self.next_update = {} + def pull_status(self, req_time): status = self.status while 1: @@ -652,32 +731,35 @@ class TrackStatus: self.next_status_time = req_time + 0.100 self.next_update = {} return status, self.next_status_time - self.next_update = jmsg['status'] - th = self.next_update.get('toolhead', {}) - self.next_status_time = th.get('estimated_print_time', 0.) + self.next_update = jmsg["status"] + th = self.next_update.get("toolhead", {}) + self.next_status_time = th.get("estimated_print_time", 0.0) + # Split a string by commas while keeping parenthesis intact def param_split(line): out = [] level = prev = 0 for i, c in enumerate(line): - if not level and c == ',': + if not level and c == ",": out.append(line[prev:i]) - prev = i+1 - elif c == '(': + prev = i + 1 + elif c == "(": level += 1 - elif level and c== ')': + elif level and c == ")": level -= 1 out.append(line[prev:]) return out + # Split a dataset name (eg, "abc(def,ghi)") into parts def name_split(name): - if '(' not in name or not name.endswith(')'): + if "(" not in name or not name.endswith(")"): raise error("Malformed dataset name '%s'" % (name,)) - aname, aparams = name.split('(', 1) + aname, aparams = name.split("(", 1) return [aname] + param_split(aparams[:-1]) + # Return a description of possible datasets def list_datasets(): datasets = [] @@ -685,58 +767,69 @@ def list_datasets(): datasets += LogHandlers[lh].DataSets return datasets + # Main log access management class LogManager: error = error + def __init__(self, log_prefix): self.index_reader = JsonLogReader(log_prefix + ".index.gz") self.jdispatch = JsonDispatcher(log_prefix) - self.initial_start_time = self.start_time = 0. + self.initial_start_time = self.start_time = 0.0 self.datasets = {} self.initial_status = {} self.start_status = {} self.log_subscriptions = {} self.status_tracker = None + def setup_index(self): fmsg = self.index_reader.pull_msg() - self.initial_status = status = fmsg['status'] + self.initial_status = status = fmsg["status"] self.start_status = dict(status) - start_time = status['toolhead']['estimated_print_time'] + start_time = status["toolhead"]["estimated_print_time"] self.initial_start_time = self.start_time = start_time - self.log_subscriptions = fmsg.get('subscriptions', {}) + self.log_subscriptions = fmsg.get("subscriptions", {}) + def get_initial_status(self): return self.initial_status + def available_dataset_types(self): return {name: None for name in LogHandlers} + def get_jdispatch(self): return self.jdispatch + def seek_time(self, req_time): self.start_time = req_start_time = self.initial_start_time + req_time start_status = self.start_status - seek_time = max(self.initial_start_time, req_start_time - 1.) + seek_time = max(self.initial_start_time, req_start_time - 1.0) file_position = 0 while 1: fmsg = self.index_reader.pull_msg() if fmsg is None: break - th = fmsg['status']['toolhead'] - ptime = max(th['estimated_print_time'], th.get('print_time', 0.)) + th = fmsg["status"]["toolhead"] + ptime = max(th["estimated_print_time"], th.get("print_time", 0.0)) if ptime > seek_time: break for k, v in fmsg["status"].items(): start_status.setdefault(k, {}).update(v) - file_position = fmsg['file_position'] + file_position = fmsg["file_position"] if file_position: self.jdispatch.log_reader.seek(file_position) + def get_initial_start_time(self): return self.initial_start_time + def get_start_time(self): return self.start_time + def get_status_tracker(self): if self.status_tracker is None: self.status_tracker = TrackStatus(self, "status", self.start_status) self.jdispatch.add_handler("status", "status") return self.status_tracker + def setup_dataset(self, name): if name in self.datasets: return self.datasets[name] @@ -748,7 +841,7 @@ class LogManager: if len_pp < cls.ParametersMin or len_pp > cls.ParametersMax: raise error("Invalid number of parameters for '%s'" % (name,)) if cls.SubscriptionIdParts: - subscription_id = ":".join(name_parts[:cls.SubscriptionIdParts]) + subscription_id = ":".join(name_parts[: cls.SubscriptionIdParts]) if subscription_id not in self.log_subscriptions: raise error("Dataset '%s' not in capture" % (subscription_id,)) self.jdispatch.add_handler(name, subscription_id) |