added back the vibrations profile measurement
This commit is contained in:
@@ -1,205 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Classes to parse the Klipper log and parse the TMC dump to extract the relevant information
|
||||
# Written by Frix_x#0161 #
|
||||
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional, Union
|
||||
|
||||
|
||||
class Motor:
|
||||
def __init__(self, name: str):
|
||||
self._name: str = name
|
||||
self._registers: Dict[str, Dict[str, Any]] = {}
|
||||
self._properties: Dict[str, Any] = {}
|
||||
|
||||
def set_register(self, register: str, value: Any) -> None:
|
||||
# Special parsing for CHOPCONF to extract meaningful values
|
||||
if register == 'CHOPCONF':
|
||||
# Add intpol=0 if missing from the register dump
|
||||
if 'intpol=' not in value:
|
||||
value += ' intpol=0'
|
||||
# Simplify the microstep resolution format
|
||||
mres_match = re.search(r'mres=\d+\((\d+)usteps\)', value)
|
||||
if mres_match:
|
||||
value = re.sub(r'mres=\d+\(\d+usteps\)', f'mres={mres_match.group(1)}', value)
|
||||
|
||||
# Special parsing for CHOPCONF to avoid pwm_ before each values
|
||||
if register == 'PWMCONF':
|
||||
parts = value.split()
|
||||
new_parts = []
|
||||
for part in parts:
|
||||
key, val = part.split('=', 1)
|
||||
if key.startswith('pwm_'):
|
||||
key = key[4:]
|
||||
new_parts.append(f'{key}={val}')
|
||||
value = ' '.join(new_parts)
|
||||
|
||||
# General cleaning to remove extraneous labels and colons and parse the whole into Motor _registers
|
||||
cleaned_values = re.sub(r'\b\w+:\s+\S+\s+', '', value)
|
||||
|
||||
# Then fill the registers while merging all the thresholds into the same THRS virtual register
|
||||
if register in ['TPWMTHRS', 'TCOOLTHRS']:
|
||||
existing_thrs = self._registers.get('THRS', {})
|
||||
new_values = self._parse_register_values(cleaned_values)
|
||||
merged_values = {**existing_thrs, **new_values}
|
||||
self._registers['THRS'] = merged_values
|
||||
else:
|
||||
self._registers[register] = self._parse_register_values(cleaned_values)
|
||||
|
||||
def _parse_register_values(self, register_string: str) -> Dict[str, Any]:
|
||||
parsed = {}
|
||||
parts = register_string.split()
|
||||
for part in parts:
|
||||
if '=' in part:
|
||||
k, v = part.split('=', 1)
|
||||
parsed[k] = v
|
||||
return parsed
|
||||
|
||||
def get_register(self, register: str) -> Optional[Dict[str, Any]]:
|
||||
return self._registers.get(register)
|
||||
|
||||
def get_registers(self) -> Dict[str, Dict[str, Any]]:
|
||||
return self._registers
|
||||
|
||||
def set_property(self, property: str, value: Any) -> None:
|
||||
self._properties[property] = value
|
||||
|
||||
def get_property(self, property: str) -> Optional[Any]:
|
||||
return self._properties.get(property)
|
||||
|
||||
def __str__(self):
|
||||
return f'Stepper: {self._name}\nKlipper config: {self._properties}\nTMC Registers: {self._registers}'
|
||||
|
||||
# Return the other motor properties and registers that are different from the current motor
|
||||
def compare_to(self, other: 'Motor') -> Optional[Dict[str, Dict[str, Any]]]:
|
||||
differences = {'properties': {}, 'registers': {}}
|
||||
|
||||
# Compare properties
|
||||
all_keys = self._properties.keys() | other._properties.keys()
|
||||
for key in all_keys:
|
||||
val1 = self._properties.get(key)
|
||||
val2 = other._properties.get(key)
|
||||
if val1 != val2:
|
||||
differences['properties'][key] = val2
|
||||
|
||||
# Compare registers
|
||||
all_keys = self._registers.keys() | other._registers.keys()
|
||||
for key in all_keys:
|
||||
reg1 = self._registers.get(key, {})
|
||||
reg2 = other._registers.get(key, {})
|
||||
if reg1 != reg2:
|
||||
reg_diffs = {}
|
||||
sub_keys = reg1.keys() | reg2.keys()
|
||||
for sub_key in sub_keys:
|
||||
reg_val1 = reg1.get(sub_key)
|
||||
reg_val2 = reg2.get(sub_key)
|
||||
if reg_val1 != reg_val2:
|
||||
reg_diffs[sub_key] = reg_val2
|
||||
if reg_diffs:
|
||||
differences['registers'][key] = reg_diffs
|
||||
|
||||
# Clean up: remove empty sections if there are no differences
|
||||
if not differences['properties']:
|
||||
del differences['properties']
|
||||
if not differences['registers']:
|
||||
del differences['registers']
|
||||
|
||||
if not differences:
|
||||
return None
|
||||
|
||||
return differences
|
||||
|
||||
|
||||
class MotorLogParser:
|
||||
_section_pattern: str = r'DUMP_TMC stepper_(x|y)'
|
||||
_register_patterns: Dict[str, str] = {
|
||||
'CHOPCONF': r'CHOPCONF:\s+\S+\s+(.*)',
|
||||
'PWMCONF': r'PWMCONF:\s+\S+\s+(.*)',
|
||||
'COOLCONF': r'COOLCONF:\s+(.*)',
|
||||
'TPWMTHRS': r'TPWMTHRS:\s+\S+\s+(.*)',
|
||||
'TCOOLTHRS': r'TCOOLTHRS:\s+\S+\s+(.*)',
|
||||
}
|
||||
|
||||
def __init__(self, filepath: Path, config_string: Optional[str] = None):
|
||||
self._filepath = filepath
|
||||
|
||||
self._motors: List[Motor] = []
|
||||
self._config = self._parse_config(config_string) if config_string else {}
|
||||
|
||||
self._parse_registers()
|
||||
|
||||
def _parse_config(self, config_string: str) -> Dict[str, Any]:
|
||||
config = {}
|
||||
entries = config_string.split('|')
|
||||
for entry in entries:
|
||||
if entry:
|
||||
key, value = entry.split(':')
|
||||
config[key.strip()] = self._convert_value(value.strip())
|
||||
return config
|
||||
|
||||
def _convert_value(self, value: str) -> Union[int, float, bool, str]:
|
||||
if value.isdigit():
|
||||
return int(value)
|
||||
try:
|
||||
return float(value)
|
||||
except ValueError:
|
||||
if value.lower() in ['true', 'false']:
|
||||
return value.lower() == 'true'
|
||||
return value
|
||||
|
||||
def _parse_registers(self) -> None:
|
||||
with open(self._filepath, 'r') as file:
|
||||
log_content = file.read()
|
||||
|
||||
sections = re.split(self._section_pattern, log_content)
|
||||
|
||||
# Detect only the latest dumps from the log (to ignore potential previous and outdated dumps)
|
||||
last_sections: Dict[str, int] = {}
|
||||
for i in range(1, len(sections), 2):
|
||||
stepper_name = 'stepper_' + sections[i].strip()
|
||||
last_sections[stepper_name] = i
|
||||
|
||||
for stepper_name, index in last_sections.items():
|
||||
content = sections[index + 1]
|
||||
motor = Motor(stepper_name)
|
||||
|
||||
# Apply general properties from config string
|
||||
for key, value in self._config.items():
|
||||
if stepper_name in key:
|
||||
prop_key = key.replace(stepper_name + '_', '')
|
||||
motor.set_property(prop_key, value)
|
||||
elif 'autotune' in key:
|
||||
motor.set_property(key, value)
|
||||
|
||||
# Parse TMC registers
|
||||
for key, pattern in self._register_patterns.items():
|
||||
match = re.search(pattern, content)
|
||||
if match:
|
||||
values = match.group(1).strip()
|
||||
motor.set_register(key, values)
|
||||
|
||||
self._motors.append(motor)
|
||||
|
||||
# Find and return the motor by its name
|
||||
def get_motor(self, motor_name: str) -> Optional[Motor]:
|
||||
for motor in self._motors:
|
||||
if motor._name == motor_name:
|
||||
return motor
|
||||
return None
|
||||
|
||||
# Get all the motor list at once
|
||||
def get_motors(self) -> List[Motor]:
|
||||
return self._motors
|
||||
|
||||
|
||||
# # Usage example:
|
||||
# config_string = "stepper_x_tmc:tmc2240|stepper_x_run_current:0.9|stepper_x_hold_current:0.9|stepper_y_tmc:tmc2240|stepper_y_run_current:0.9|stepper_y_hold_current:0.9|autotune_enabled:True|stepper_x_motor:ldo-35sth48-1684ah|stepper_x_voltage:|stepper_y_motor:ldo-35sth48-1684ah|stepper_y_voltage:|"
|
||||
# parser = MotorLogParser('/path/to/your/logfile.log', config_string)
|
||||
|
||||
# stepper_x = parser.get_motor('stepper_x')
|
||||
# stepper_y = parser.get_motor('stepper_y')
|
||||
|
||||
# print(stepper_x)
|
||||
# print(stepper_y)
|
||||
Reference in New Issue
Block a user