motan: Pass dataset parameters in parenthesis
Replace names like "trapq:toolhead:x" with "trapq(toolhead,x)". Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
This commit is contained in:
parent
5fd1c9853d
commit
1e4041a96b
|
@ -122,7 +122,7 @@ Graphs can be generated with a command like the following:
|
||||||
One can use the `-g` option to specify the datasets to graph (it takes
|
One can use the `-g` option to specify the datasets to graph (it takes
|
||||||
a Python literal containing a list of lists). For example:
|
a Python literal containing a list of lists). For example:
|
||||||
```
|
```
|
||||||
~/klipper/scripts/motan/motan_graph.py mylog -g '[["trapq:toolhead:velocity"], ["trapq:toolhead:accel"]]'
|
~/klipper/scripts/motan/motan_graph.py mylog -g '[["trapq(toolhead,velocity)"], ["trapq(toolhead,accel)"]]'
|
||||||
```
|
```
|
||||||
|
|
||||||
The list of available datasets can be found using the `-l` option -
|
The list of available datasets can be found using the `-l` option -
|
||||||
|
@ -134,7 +134,7 @@ for example:
|
||||||
It is also possible to specify matplotlib plot options for each
|
It is also possible to specify matplotlib plot options for each
|
||||||
dataset:
|
dataset:
|
||||||
```
|
```
|
||||||
~/klipper/scripts/motan/motan_graph.py mylog -g '[["trapq:toolhead:velocity?color=red"]]'
|
~/klipper/scripts/motan/motan_graph.py mylog -g '[["trapq(toolhead,velocity)?color=red&alpha=0.4"]]'
|
||||||
```
|
```
|
||||||
Many matplotlib options are available; some examples are "color",
|
Many matplotlib options are available; some examples are "color",
|
||||||
"label", "alpha", and "linestyle".
|
"label", "alpha", and "linestyle".
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
#
|
#
|
||||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
import collections
|
import collections
|
||||||
|
import readlog
|
||||||
|
|
||||||
|
|
||||||
######################################################################
|
######################################################################
|
||||||
|
@ -15,12 +16,13 @@ AHandlers = {}
|
||||||
|
|
||||||
# Calculate a derivative (position to velocity, or velocity to accel)
|
# Calculate a derivative (position to velocity, or velocity to accel)
|
||||||
class GenDerivative:
|
class GenDerivative:
|
||||||
|
ParametersMin = ParametersMax = 1
|
||||||
DataSets = [
|
DataSets = [
|
||||||
('derivative:<dataset>', 'Derivative of the given dataset'),
|
('derivative(<dataset>)', 'Derivative of the given dataset'),
|
||||||
]
|
]
|
||||||
def __init__(self, amanager, params):
|
def __init__(self, amanager, name_parts):
|
||||||
self.amanager = amanager
|
self.amanager = amanager
|
||||||
self.source = params
|
self.source = name_parts[1]
|
||||||
amanager.setup_dataset(self.source)
|
amanager.setup_dataset(self.source)
|
||||||
def get_label(self):
|
def get_label(self):
|
||||||
label = self.amanager.get_label(self.source)
|
label = self.amanager.get_label(self.source)
|
||||||
|
@ -46,28 +48,30 @@ AHandlers["derivative"] = GenDerivative
|
||||||
|
|
||||||
# Calculate a kinematic stepper position from the toolhead requested position
|
# Calculate a kinematic stepper position from the toolhead requested position
|
||||||
class GenKinematicPosition:
|
class GenKinematicPosition:
|
||||||
|
ParametersMin = ParametersMax = 1
|
||||||
DataSets = [
|
DataSets = [
|
||||||
('kin:<stepper>', 'Stepper position derived from toolhead kinematics'),
|
('kin(<stepper>)', 'Stepper position derived from toolhead kinematics'),
|
||||||
]
|
]
|
||||||
def __init__(self, amanager, params):
|
def __init__(self, amanager, name_parts):
|
||||||
self.amanager = amanager
|
self.amanager = amanager
|
||||||
|
stepper = name_parts[1]
|
||||||
status = self.amanager.get_initial_status()
|
status = self.amanager.get_initial_status()
|
||||||
kin = status['configfile']['settings']['printer']['kinematics']
|
kin = status['configfile']['settings']['printer']['kinematics']
|
||||||
if kin not in ['cartesian', 'corexy']:
|
if kin not in ['cartesian', 'corexy']:
|
||||||
raise amanager.error("Unsupported kinematics '%s'" % (kin,))
|
raise amanager.error("Unsupported kinematics '%s'" % (kin,))
|
||||||
if params not in ['stepper_x', 'stepper_y', 'stepper_z']:
|
if stepper not in ['stepper_x', 'stepper_y', 'stepper_z']:
|
||||||
raise amanager.error("Unknown stepper '%s'" % (params,))
|
raise amanager.error("Unknown stepper '%s'" % (stepper,))
|
||||||
if kin == 'corexy' and params in ['stepper_x', 'stepper_y']:
|
if kin == 'corexy' and stepper in ['stepper_x', 'stepper_y']:
|
||||||
self.source1 = 'trapq:toolhead:x'
|
self.source1 = 'trapq(toolhead,x)'
|
||||||
self.source2 = 'trapq:toolhead:y'
|
self.source2 = 'trapq(toolhead,y)'
|
||||||
if params == 'stepper_x':
|
if stepper == 'stepper_x':
|
||||||
self.generate_data = self.generate_data_corexy_plus
|
self.generate_data = self.generate_data_corexy_plus
|
||||||
else:
|
else:
|
||||||
self.generate_data = self.generate_data_corexy_minus
|
self.generate_data = self.generate_data_corexy_minus
|
||||||
amanager.setup_dataset(self.source1)
|
amanager.setup_dataset(self.source1)
|
||||||
amanager.setup_dataset(self.source2)
|
amanager.setup_dataset(self.source2)
|
||||||
else:
|
else:
|
||||||
self.source1 = 'trapq:toolhead:' + params[-1:]
|
self.source1 = 'trapq(toolhead,%s)' % (stepper[-1:],)
|
||||||
self.source2 = None
|
self.source2 = None
|
||||||
self.generate_data = self.generate_data_passthrough
|
self.generate_data = self.generate_data_passthrough
|
||||||
amanager.setup_dataset(self.source1)
|
amanager.setup_dataset(self.source1)
|
||||||
|
@ -89,15 +93,13 @@ AHandlers["kin"] = GenKinematicPosition
|
||||||
|
|
||||||
# Calculate a position deviation
|
# Calculate a position deviation
|
||||||
class GenDeviation:
|
class GenDeviation:
|
||||||
|
ParametersMin = ParametersMax = 2
|
||||||
DataSets = [
|
DataSets = [
|
||||||
('deviation:<dataset1>-<dataset2>', 'Difference between datasets'),
|
('deviation(<dataset1>,<dataset2>)', 'Difference between datasets'),
|
||||||
]
|
]
|
||||||
def __init__(self, amanager, params):
|
def __init__(self, amanager, name_parts):
|
||||||
self.amanager = amanager
|
self.amanager = amanager
|
||||||
parts = params.split('-')
|
self.source1, self.source2 = name_parts[1:]
|
||||||
if len(parts) != 2:
|
|
||||||
raise amanager.error("Invalid deviation '%s'" % (params,))
|
|
||||||
self.source1, self.source2 = parts
|
|
||||||
amanager.setup_dataset(self.source1)
|
amanager.setup_dataset(self.source1)
|
||||||
amanager.setup_dataset(self.source2)
|
amanager.setup_dataset(self.source2)
|
||||||
def get_label(self):
|
def get_label(self):
|
||||||
|
@ -117,20 +119,16 @@ AHandlers["deviation"] = GenDeviation
|
||||||
|
|
||||||
|
|
||||||
######################################################################
|
######################################################################
|
||||||
# List datasets
|
# Analyzer management and data generation
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|
||||||
|
# Return a description of available analyzers
|
||||||
def list_datasets():
|
def list_datasets():
|
||||||
datasets = []
|
datasets = []
|
||||||
for ah in sorted(AHandlers.keys()):
|
for ah in sorted(AHandlers.keys()):
|
||||||
datasets += AHandlers[ah].DataSets
|
datasets += AHandlers[ah].DataSets
|
||||||
return datasets
|
return datasets
|
||||||
|
|
||||||
|
|
||||||
######################################################################
|
|
||||||
# Data generation
|
|
||||||
######################################################################
|
|
||||||
|
|
||||||
# Manage raw and generated data samples
|
# Manage raw and generated data samples
|
||||||
class AnalyzerManager:
|
class AnalyzerManager:
|
||||||
error = None
|
error = None
|
||||||
|
@ -159,15 +157,18 @@ class AnalyzerManager:
|
||||||
return self.raw_datasets[name]
|
return self.raw_datasets[name]
|
||||||
if name in self.gen_datasets:
|
if name in self.gen_datasets:
|
||||||
return self.gen_datasets[name]
|
return self.gen_datasets[name]
|
||||||
nparts = name.split(':')
|
name_parts = readlog.name_split(name)
|
||||||
if nparts[0] in self.lmanager.available_dataset_types():
|
if name_parts[0] in self.lmanager.available_dataset_types():
|
||||||
hdl = self.lmanager.setup_dataset(name)
|
hdl = self.lmanager.setup_dataset(name)
|
||||||
self.raw_datasets[name] = hdl
|
self.raw_datasets[name] = hdl
|
||||||
else:
|
else:
|
||||||
cls = AHandlers.get(nparts[0])
|
cls = AHandlers.get(name_parts[0])
|
||||||
if cls is None:
|
if cls is None:
|
||||||
raise self.error("Unknown dataset '%s'" % (name,))
|
raise self.error("Unknown dataset '%s'" % (name,))
|
||||||
hdl = cls(self, ':'.join(nparts[1:]))
|
num_param = len(name_parts) - 1
|
||||||
|
if num_param < cls.ParametersMin or num_param > cls.ParametersMax:
|
||||||
|
raise self.error("Invalid parameters to dataset '%s'" % (name,))
|
||||||
|
hdl = cls(self, name_parts)
|
||||||
self.gen_datasets[name] = hdl
|
self.gen_datasets[name] = hdl
|
||||||
self.datasets[name] = []
|
self.datasets[name] = []
|
||||||
return hdl
|
return hdl
|
||||||
|
|
|
@ -125,9 +125,9 @@ def main():
|
||||||
|
|
||||||
# Default graphs to draw
|
# Default graphs to draw
|
||||||
graph_descs = [
|
graph_descs = [
|
||||||
["trapq:toolhead:velocity?color=green"],
|
["trapq(toolhead,velocity)?color=green"],
|
||||||
["trapq:toolhead:accel?color=green"],
|
["trapq(toolhead,accel)?color=green"],
|
||||||
["deviation:stepq:stepper_x-kin:stepper_x?color=blue"],
|
["deviation(stepq(stepper_x),kin(stepper_x))?color=blue"],
|
||||||
]
|
]
|
||||||
if options.graph is not None:
|
if options.graph is not None:
|
||||||
graph_descs = ast.literal_eval(options.graph)
|
graph_descs = ast.literal_eval(options.graph)
|
||||||
|
|
|
@ -18,20 +18,21 @@ LogHandlers = {}
|
||||||
|
|
||||||
# Extract requested position, velocity, and accel from a trapq log
|
# Extract requested position, velocity, and accel from a trapq log
|
||||||
class HandleTrapQ:
|
class HandleTrapQ:
|
||||||
ParametersSubscriptionId = 2
|
SubscriptionIdParts = 2
|
||||||
ParametersMin = ParametersMax = 3
|
ParametersMin = ParametersMax = 2
|
||||||
DataSets = [
|
DataSets = [
|
||||||
('trapq:<name>:velocity', 'Requested velocity for the given trapq'),
|
('trapq(<name>,velocity)', 'Requested velocity for the given trapq'),
|
||||||
('trapq:<name>:<axis>', 'Requested axis (x, y, or z) position'),
|
('trapq(<name>,accel)', 'Requested acceleration for the given trapq'),
|
||||||
('trapq:<name>:<axis>_velocity', 'Requested axis velocity'),
|
('trapq(<name>,<axis>)', 'Requested axis (x, y, or z) position'),
|
||||||
('trapq:<name>:<axis>_accel', 'Requested axis acceleration'),
|
('trapq(<name>,<axis>_velocity)', 'Requested axis velocity'),
|
||||||
|
('trapq(<name>,<axis>_accel)', 'Requested axis acceleration'),
|
||||||
]
|
]
|
||||||
def __init__(self, lmanager, name):
|
def __init__(self, lmanager, name, name_parts):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.jdispatch = lmanager.get_jdispatch()
|
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.))]
|
||||||
self.data_pos = 0
|
self.data_pos = 0
|
||||||
tq, trapq_name, datasel = name.split(':')
|
tq, trapq_name, datasel = name_parts
|
||||||
ptypes = {}
|
ptypes = {}
|
||||||
ptypes['velocity'] = {
|
ptypes['velocity'] = {
|
||||||
'label': '%s velocity' % (trapq_name,),
|
'label': '%s velocity' % (trapq_name,),
|
||||||
|
@ -113,26 +114,27 @@ LogHandlers["trapq"] = HandleTrapQ
|
||||||
|
|
||||||
# Extract positions from queue_step log
|
# Extract positions from queue_step log
|
||||||
class HandleStepQ:
|
class HandleStepQ:
|
||||||
ParametersSubscriptionId = 2
|
SubscriptionIdParts = 2
|
||||||
ParametersMin = 2
|
ParametersMin = 1
|
||||||
ParametersMax = 3
|
ParametersMax = 2
|
||||||
DataSets = [
|
DataSets = [
|
||||||
('stepq:<stepper>', 'Commanded position of the given stepper'),
|
('stepq(<stepper>)', 'Commanded position of the given stepper'),
|
||||||
('stepq:<stepper>:raw', 'Commanded position without smoothing'),
|
('stepq(<stepper>,<time>)', 'Commanded position with smooth time'),
|
||||||
]
|
]
|
||||||
def __init__(self, lmanager, name):
|
def __init__(self, lmanager, name, name_parts):
|
||||||
self.name = name
|
self.name = name
|
||||||
|
self.stepper_name = name_parts[1]
|
||||||
self.jdispatch = lmanager.get_jdispatch()
|
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.)] # [(time, half_pos, pos)]
|
||||||
self.data_pos = 0
|
self.data_pos = 0
|
||||||
self.smooth_time = 0.010
|
self.smooth_time = 0.010
|
||||||
name_parts = name.split(':')
|
|
||||||
if len(name_parts) == 3:
|
if len(name_parts) == 3:
|
||||||
if name_parts[2] != 'raw':
|
try:
|
||||||
raise error("Unknown stepq data selection '%s'" % (name,))
|
self.smooth_time = float(name_parts[2])
|
||||||
self.smooth_time = 0.
|
except ValueError:
|
||||||
|
raise error("Invalid stepq smooth time '%s'" % (name_parts[2],))
|
||||||
def get_label(self):
|
def get_label(self):
|
||||||
label = '%s position' % (self.name.split(':')[1],)
|
label = '%s position' % (self.stepper_name,)
|
||||||
return {'label': label, 'units': 'Position\n(mm)'}
|
return {'label': label, 'units': 'Position\n(mm)'}
|
||||||
def pull_data(self, req_time):
|
def pull_data(self, req_time):
|
||||||
smooth_time = self.smooth_time
|
smooth_time = self.smooth_time
|
||||||
|
@ -204,17 +206,6 @@ class HandleStepQ:
|
||||||
LogHandlers["stepq"] = HandleStepQ
|
LogHandlers["stepq"] = HandleStepQ
|
||||||
|
|
||||||
|
|
||||||
######################################################################
|
|
||||||
# List datasets
|
|
||||||
######################################################################
|
|
||||||
|
|
||||||
def list_datasets():
|
|
||||||
datasets = []
|
|
||||||
for lh in sorted(LogHandlers.keys()):
|
|
||||||
datasets += LogHandlers[lh].DataSets
|
|
||||||
return datasets
|
|
||||||
|
|
||||||
|
|
||||||
######################################################################
|
######################################################################
|
||||||
# Log reading
|
# Log reading
|
||||||
######################################################################
|
######################################################################
|
||||||
|
@ -279,6 +270,40 @@ class JsonDispatcher:
|
||||||
for mq in self.queues.get(qid, []):
|
for mq in self.queues.get(qid, []):
|
||||||
mq.append(json_msg['params'])
|
mq.append(json_msg['params'])
|
||||||
|
|
||||||
|
|
||||||
|
######################################################################
|
||||||
|
# Dataset and log tracking
|
||||||
|
######################################################################
|
||||||
|
|
||||||
|
# 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 == ',':
|
||||||
|
out.append(line[prev:i])
|
||||||
|
prev = i+1
|
||||||
|
elif c == '(':
|
||||||
|
level += 1
|
||||||
|
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(')'):
|
||||||
|
raise error("Malformed dataset name '%s'" % (name,))
|
||||||
|
aname, aparams = name.split('(', 1)
|
||||||
|
return [aname] + param_split(aparams[:-1])
|
||||||
|
|
||||||
|
# Return a description of possible datasets
|
||||||
|
def list_datasets():
|
||||||
|
datasets = []
|
||||||
|
for lh in sorted(LogHandlers.keys()):
|
||||||
|
datasets += LogHandlers[lh].DataSets
|
||||||
|
return datasets
|
||||||
|
|
||||||
# Main log access management
|
# Main log access management
|
||||||
class LogManager:
|
class LogManager:
|
||||||
error = error
|
error = error
|
||||||
|
@ -325,15 +350,16 @@ class LogManager:
|
||||||
def setup_dataset(self, name):
|
def setup_dataset(self, name):
|
||||||
if name in self.datasets:
|
if name in self.datasets:
|
||||||
return self.datasets[name]
|
return self.datasets[name]
|
||||||
parts = name.split(':')
|
name_parts = name_split(name)
|
||||||
cls = LogHandlers.get(parts[0])
|
cls = LogHandlers.get(name_parts[0])
|
||||||
if cls is None:
|
if cls is None:
|
||||||
raise error("Unknown dataset '%s'" % (parts[0],))
|
raise error("Unknown dataset '%s'" % (name_parts[0],))
|
||||||
if len(parts) < cls.ParametersMin or len(parts) > cls.ParametersMax:
|
len_pp = len(name_parts) - 1
|
||||||
raise error("Invalid number of parameters for %s" % (parts[0],))
|
if len_pp < cls.ParametersMin or len_pp > cls.ParametersMax:
|
||||||
subscription_id = ":".join(parts[:cls.ParametersSubscriptionId])
|
raise error("Invalid number of parameters for '%s'" % (name,))
|
||||||
|
subscription_id = ":".join(name_parts[:cls.SubscriptionIdParts])
|
||||||
if subscription_id not in self.log_subscriptions:
|
if subscription_id not in self.log_subscriptions:
|
||||||
raise error("Dataset '%s' not in capture" % (subscription_id,))
|
raise error("Dataset '%s' not in capture" % (subscription_id,))
|
||||||
self.datasets[name] = hdl = cls(self, name)
|
self.datasets[name] = hdl = cls(self, name, name_parts)
|
||||||
self.jdispatch.add_handler(name, subscription_id)
|
self.jdispatch.add_handler(name, subscription_id)
|
||||||
return hdl
|
return hdl
|
||||||
|
|
Loading…
Reference in New Issue