aboutsummaryrefslogtreecommitdiffstats
path: root/klippy/extras
diff options
context:
space:
mode:
authorTomasz Kramkowski <tomasz@kramkow.ski>2025-08-06 17:50:53 +0100
committerTomasz Kramkowski <tomasz@kramkow.ski>2025-08-06 17:50:53 +0100
commit581208b2ffeeb2a2128aee0741fa3fd9e46358e2 (patch)
treef0d7e0e5cb574b235a5495b0f40f79e3ded7aa4d /klippy/extras
parente1176e4dfb9018e712d4fa86daf41e9e762a1698 (diff)
downloadkutter-581208b2ffeeb2a2128aee0741fa3fd9e46358e2.tar.gz
kutter-581208b2ffeeb2a2128aee0741fa3fd9e46358e2.tar.xz
kutter-581208b2ffeeb2a2128aee0741fa3fd9e46358e2.zip
Run black on all first party python code
Diffstat (limited to 'klippy/extras')
-rw-r--r--klippy/extras/ad5206.py15
-rw-r--r--klippy/extras/adc_scaled.py45
-rw-r--r--klippy/extras/adc_temperature.py565
-rw-r--r--klippy/extras/ads1220.py169
-rw-r--r--klippy/extras/ads1x1x.py291
-rw-r--r--klippy/extras/adxl345.py204
-rw-r--r--klippy/extras/aht10.py78
-rw-r--r--klippy/extras/angle.py470
-rw-r--r--klippy/extras/axis_twist_compensation.py256
-rw-r--r--klippy/extras/bed_mesh.py934
-rw-r--r--klippy/extras/bed_screws.py81
-rw-r--r--klippy/extras/bed_tilt.py97
-rw-r--r--klippy/extras/bltouch.py212
-rw-r--r--klippy/extras/bme280.py609
-rw-r--r--klippy/extras/board_pins.py11
-rw-r--r--klippy/extras/bulk_sensor.py104
-rw-r--r--klippy/extras/bus.py245
-rw-r--r--klippy/extras/buttons.py222
-rw-r--r--klippy/extras/canbus_ids.py7
-rw-r--r--klippy/extras/canbus_stats.py91
-rw-r--r--klippy/extras/controller_fan.py36
-rw-r--r--klippy/extras/dac084S085.py19
-rw-r--r--klippy/extras/delayed_gcode.py28
-rw-r--r--klippy/extras/delta_calibrate.py209
-rw-r--r--klippy/extras/display/__init__.py10
-rw-r--r--klippy/extras/display/aip31068_spi.py151
-rw-r--r--klippy/extras/display/display.py176
-rw-r--r--klippy/extras/display/font8x14.py512
-rw-r--r--klippy/extras/display/hd44780.py109
-rw-r--r--klippy/extras/display/hd44780_spi.py80
-rw-r--r--klippy/extras/display/menu.py344
-rw-r--r--klippy/extras/display/menu_keys.py73
-rw-r--r--klippy/extras/display/st7920.py165
-rw-r--r--klippy/extras/display/uc1701.py180
-rw-r--r--klippy/extras/display_status.py41
-rw-r--r--klippy/extras/dotstar.py46
-rw-r--r--klippy/extras/ds18b20.py43
-rw-r--r--klippy/extras/duplicate_pin_override.py6
-rw-r--r--klippy/extras/endstop_phase.py143
-rw-r--r--klippy/extras/error_mcu.py84
-rw-r--r--klippy/extras/exclude_object.py156
-rw-r--r--klippy/extras/extruder_stepper.py9
-rw-r--r--klippy/extras/fan.py99
-rw-r--r--klippy/extras/fan_generic.py24
-rw-r--r--klippy/extras/filament_motion_sensor.py61
-rw-r--r--klippy/extras/filament_switch_sensor.py73
-rw-r--r--klippy/extras/firmware_retraction.py79
-rw-r--r--klippy/extras/force_move.py141
-rw-r--r--klippy/extras/garbage_collection.py12
-rw-r--r--klippy/extras/gcode_arcs.py79
-rw-r--r--klippy/extras/gcode_button.py45
-rw-r--r--klippy/extras/gcode_macro.py123
-rw-r--r--klippy/extras/gcode_move.py284
-rw-r--r--klippy/extras/hall_filament_width_sensor.py181
-rw-r--r--klippy/extras/heater_bed.py14
-rw-r--r--klippy/extras/heater_fan.py21
-rw-r--r--klippy/extras/heater_generic.py3
-rw-r--r--klippy/extras/heaters.py275
-rw-r--r--klippy/extras/homing.py181
-rw-r--r--klippy/extras/homing_heaters.py37
-rw-r--r--klippy/extras/homing_override.py24
-rw-r--r--klippy/extras/htu21d.py226
-rw-r--r--klippy/extras/hx71x.py128
-rw-r--r--klippy/extras/icm20948.py125
-rw-r--r--klippy/extras/idle_timeout.py66
-rw-r--r--klippy/extras/input_shaper.py137
-rw-r--r--klippy/extras/ldc1612.py163
-rw-r--r--klippy/extras/led.py104
-rw-r--r--klippy/extras/lis2dw.py101
-rw-r--r--klippy/extras/lis3dh.py2
-rw-r--r--klippy/extras/lm75.py37
-rw-r--r--klippy/extras/load_cell.py294
-rw-r--r--klippy/extras/load_cell_probe.py394
-rw-r--r--klippy/extras/manual_probe.py228
-rw-r--r--klippy/extras/manual_stepper.py205
-rw-r--r--klippy/extras/mcp4018.py40
-rw-r--r--klippy/extras/mcp4451.py14
-rw-r--r--klippy/extras/mcp4728.py19
-rw-r--r--klippy/extras/motion_report.py205
-rw-r--r--klippy/extras/mpu9250.py124
-rw-r--r--klippy/extras/multi_pin.py28
-rw-r--r--klippy/extras/neopixel.py71
-rw-r--r--klippy/extras/output_pin.py101
-rw-r--r--klippy/extras/palette2.py212
-rw-r--r--klippy/extras/pause_resume.py75
-rw-r--r--klippy/extras/pca9533.py23
-rw-r--r--klippy/extras/pca9632.py29
-rw-r--r--klippy/extras/pid_calibrate.py97
-rw-r--r--klippy/extras/print_stats.py87
-rw-r--r--klippy/extras/probe.py445
-rw-r--r--klippy/extras/probe_eddy_current.py240
-rw-r--r--klippy/extras/pulse_counter.py35
-rw-r--r--klippy/extras/pwm_cycle_time.py130
-rw-r--r--klippy/extras/pwm_tool.py160
-rw-r--r--klippy/extras/quad_gantry_level.py101
-rw-r--r--klippy/extras/query_adc.py25
-rw-r--r--klippy/extras/query_endstops.py52
-rw-r--r--klippy/extras/replicape.py197
-rw-r--r--klippy/extras/resonance_tester.py394
-rw-r--r--klippy/extras/respond.py46
-rw-r--r--klippy/extras/safe_z_home.py49
-rw-r--r--klippy/extras/samd_sercom.py33
-rw-r--r--klippy/extras/save_variables.py31
-rw-r--r--klippy/extras/screws_tilt_adjust.py120
-rw-r--r--klippy/extras/sdcard_loop.py35
-rw-r--r--klippy/extras/servo.py70
-rw-r--r--klippy/extras/shaper_calibrate.py288
-rw-r--r--klippy/extras/shaper_defs.py95
-rw-r--r--klippy/extras/sht3x.py108
-rw-r--r--klippy/extras/skew_correction.py160
-rw-r--r--klippy/extras/smart_effector.py110
-rw-r--r--klippy/extras/sos_filter.py128
-rw-r--r--klippy/extras/spi_temperature.py274
-rw-r--r--klippy/extras/static_digital_output.py14
-rw-r--r--klippy/extras/statistics.py44
-rw-r--r--klippy/extras/stepper_enable.py72
-rw-r--r--klippy/extras/sx1509.py151
-rw-r--r--klippy/extras/temperature_combined.py73
-rw-r--r--klippy/extras/temperature_fan.py153
-rw-r--r--klippy/extras/temperature_host.py30
-rw-r--r--klippy/extras/temperature_mcu.py199
-rw-r--r--klippy/extras/temperature_probe.py210
-rw-r--r--klippy/extras/temperature_sensor.py32
-rw-r--r--klippy/extras/thermistor.py79
-rw-r--r--klippy/extras/tmc.py321
-rw-r--r--klippy/extras/tmc2130.py320
-rw-r--r--klippy/extras/tmc2208.py278
-rw-r--r--klippy/extras/tmc2209.py63
-rw-r--r--klippy/extras/tmc2240.py462
-rw-r--r--klippy/extras/tmc2660.py120
-rw-r--r--klippy/extras/tmc5160.py457
-rw-r--r--klippy/extras/tmc_uart.py205
-rw-r--r--klippy/extras/tsl1401cl_filament_width_sensor.py83
-rw-r--r--klippy/extras/tuning_tower.py63
-rw-r--r--klippy/extras/verify_heater.py56
-rw-r--r--klippy/extras/virtual_sdcard.py138
-rw-r--r--klippy/extras/z_thermal_adjust.py134
-rw-r--r--klippy/extras/z_tilt.py142
138 files changed, 11752 insertions, 7890 deletions
diff --git a/klippy/extras/ad5206.py b/klippy/extras/ad5206.py
index d4725801..b9e2745c 100644
--- a/klippy/extras/ad5206.py
+++ b/klippy/extras/ad5206.py
@@ -5,18 +5,23 @@
# This file may be distributed under the terms of the GNU GPLv3 license.
from . import bus
+
class ad5206:
def __init__(self, config):
self.spi = bus.MCU_SPI_from_config(
- config, 0, pin_option="enable_pin", default_speed=25000000)
- scale = config.getfloat('scale', 1., above=0.)
+ config, 0, pin_option="enable_pin", default_speed=25000000
+ )
+ scale = config.getfloat("scale", 1.0, above=0.0)
for i in range(6):
- val = config.getfloat('channel_%d' % (i+1,), None,
- minval=0., maxval=scale)
+ val = config.getfloat(
+ "channel_%d" % (i + 1,), None, minval=0.0, maxval=scale
+ )
if val is not None:
- self.set_register(i, int(val * 256. / scale + .5))
+ self.set_register(i, int(val * 256.0 / scale + 0.5))
+
def set_register(self, reg, value):
self.spi.spi_send([reg, value])
+
def load_config_prefix(config):
return ad5206(config)
diff --git a/klippy/extras/adc_scaled.py b/klippy/extras/adc_scaled.py
index 80ea452f..454e592e 100644
--- a/klippy/extras/adc_scaled.py
+++ b/klippy/extras/adc_scaled.py
@@ -8,70 +8,81 @@ SAMPLE_TIME = 0.001
SAMPLE_COUNT = 8
REPORT_TIME = 0.300
+
class MCU_scaled_adc:
def __init__(self, main, pin_params):
self._main = main
- self._last_state = (0., 0.)
- self._mcu_adc = main.mcu.setup_pin('adc', pin_params)
- query_adc = main.printer.lookup_object('query_adc')
- qname = main.name + ":" + pin_params['pin']
+ self._last_state = (0.0, 0.0)
+ self._mcu_adc = main.mcu.setup_pin("adc", pin_params)
+ query_adc = main.printer.lookup_object("query_adc")
+ qname = main.name + ":" + pin_params["pin"]
query_adc.register_adc(qname, self._mcu_adc)
self._callback = None
self.setup_adc_sample = self._mcu_adc.setup_adc_sample
self.get_mcu = self._mcu_adc.get_mcu
+
def _handle_callback(self, read_time, read_value):
max_adc = self._main.last_vref[1]
min_adc = self._main.last_vssa[1]
scaled_val = (read_value - min_adc) / (max_adc - min_adc)
self._last_state = (scaled_val, read_time)
self._callback(read_time, scaled_val)
+
def setup_adc_callback(self, report_time, callback):
self._callback = callback
self._mcu_adc.setup_adc_callback(report_time, self._handle_callback)
+
def get_last_value(self):
return self._last_state
+
class PrinterADCScaled:
def __init__(self, config):
self.printer = config.get_printer()
self.name = config.get_name().split()[1]
- self.last_vref = (0., 0.)
- self.last_vssa = (0., 0.)
+ self.last_vref = (0.0, 0.0)
+ self.last_vssa = (0.0, 0.0)
# Configure vref and vssa pins
- self.mcu_vref = self._config_pin(config, 'vref', self.vref_callback)
- self.mcu_vssa = self._config_pin(config, 'vssa', self.vssa_callback)
- smooth_time = config.getfloat('smooth_time', 2., above=0.)
- self.inv_smooth_time = 1. / smooth_time
+ self.mcu_vref = self._config_pin(config, "vref", self.vref_callback)
+ self.mcu_vssa = self._config_pin(config, "vssa", self.vssa_callback)
+ smooth_time = config.getfloat("smooth_time", 2.0, above=0.0)
+ self.inv_smooth_time = 1.0 / smooth_time
self.mcu = self.mcu_vref.get_mcu()
if self.mcu is not self.mcu_vssa.get_mcu():
raise config.error("vref and vssa must be on same mcu")
# Register setup_pin
- ppins = self.printer.lookup_object('pins')
+ ppins = self.printer.lookup_object("pins")
ppins.register_chip(self.name, self)
+
def _config_pin(self, config, name, callback):
- pin_name = config.get(name + '_pin')
- ppins = self.printer.lookup_object('pins')
- mcu_adc = ppins.setup_pin('adc', pin_name)
+ pin_name = config.get(name + "_pin")
+ ppins = self.printer.lookup_object("pins")
+ mcu_adc = ppins.setup_pin("adc", pin_name)
mcu_adc.setup_adc_callback(REPORT_TIME, callback)
mcu_adc.setup_adc_sample(SAMPLE_TIME, SAMPLE_COUNT)
- query_adc = config.get_printer().load_object(config, 'query_adc')
+ query_adc = config.get_printer().load_object(config, "query_adc")
query_adc.register_adc(self.name + ":" + name, mcu_adc)
return mcu_adc
+
def setup_pin(self, pin_type, pin_params):
- if pin_type != 'adc':
+ if pin_type != "adc":
raise self.printer.config_error("adc_scaled only supports adc pins")
return MCU_scaled_adc(self, pin_params)
+
def calc_smooth(self, read_time, read_value, last):
last_time, last_value = last
time_diff = read_time - last_time
value_diff = read_value - last_value
- adj_time = min(time_diff * self.inv_smooth_time, 1.)
+ adj_time = min(time_diff * self.inv_smooth_time, 1.0)
smoothed_value = last_value + value_diff * adj_time
return (read_time, smoothed_value)
+
def vref_callback(self, read_time, read_value):
self.last_vref = self.calc_smooth(read_time, read_value, self.last_vref)
+
def vssa_callback(self, read_time, read_value):
self.last_vssa = self.calc_smooth(read_time, read_value, self.last_vssa)
+
def load_config_prefix(config):
return PrinterADCScaled(config)
diff --git a/klippy/extras/adc_temperature.py b/klippy/extras/adc_temperature.py
index c53ae705..ef05c52b 100644
--- a/klippy/extras/adc_temperature.py
+++ b/klippy/extras/adc_temperature.py
@@ -15,30 +15,41 @@ SAMPLE_COUNT = 8
REPORT_TIME = 0.300
RANGE_CHECK_COUNT = 4
+
# Interface between ADC and heater temperature callbacks
class PrinterADCtoTemperature:
def __init__(self, config, adc_convert):
self.adc_convert = adc_convert
- ppins = config.get_printer().lookup_object('pins')
- self.mcu_adc = ppins.setup_pin('adc', config.get('sensor_pin'))
+ ppins = config.get_printer().lookup_object("pins")
+ self.mcu_adc = ppins.setup_pin("adc", config.get("sensor_pin"))
self.mcu_adc.setup_adc_callback(REPORT_TIME, self.adc_callback)
self.diag_helper = HelperTemperatureDiagnostics(
- config, self.mcu_adc, adc_convert.calc_temp)
+ config, self.mcu_adc, adc_convert.calc_temp
+ )
+
def setup_callback(self, temperature_callback):
self.temperature_callback = temperature_callback
+
def get_report_time_delta(self):
return REPORT_TIME
+
def adc_callback(self, read_time, read_value):
temp = self.adc_convert.calc_temp(read_value)
self.temperature_callback(read_time + SAMPLE_COUNT * SAMPLE_TIME, temp)
+
def setup_minmax(self, min_temp, max_temp):
arange = [self.adc_convert.calc_adc(t) for t in [min_temp, max_temp]]
min_adc, max_adc = sorted(arange)
- self.mcu_adc.setup_adc_sample(SAMPLE_TIME, SAMPLE_COUNT,
- minval=min_adc, maxval=max_adc,
- range_check_count=RANGE_CHECK_COUNT)
+ self.mcu_adc.setup_adc_sample(
+ SAMPLE_TIME,
+ SAMPLE_COUNT,
+ minval=min_adc,
+ maxval=max_adc,
+ range_check_count=RANGE_CHECK_COUNT,
+ )
self.diag_helper.setup_diag_minmax(min_temp, max_temp, min_adc, max_adc)
+
# Tool to register with query_adc and report extra info on ADC range errors
class HelperTemperatureDiagnostics:
def __init__(self, config, mcu_adc, calc_temp_cb):
@@ -47,13 +58,15 @@ class HelperTemperatureDiagnostics:
self.mcu_adc = mcu_adc
self.calc_temp_cb = calc_temp_cb
self.min_temp = self.max_temp = self.min_adc = self.max_adc = None
- query_adc = self.printer.load_object(config, 'query_adc')
+ query_adc = self.printer.load_object(config, "query_adc")
query_adc.register_adc(self.name, self.mcu_adc)
- error_mcu = self.printer.load_object(config, 'error_mcu')
+ error_mcu = self.printer.load_object(config, "error_mcu")
error_mcu.add_clarify("ADC out of range", self._clarify_adc_range)
+
def setup_diag_minmax(self, min_temp, max_temp, min_adc, max_adc):
self.min_temp, self.max_temp = min_temp, max_temp
self.min_adc, self.max_adc = min_adc, max_adc
+
def _clarify_adc_range(self, msg, details):
if self.min_temp is None:
return None
@@ -68,14 +81,19 @@ class HelperTemperatureDiagnostics:
tempstr = "%.3f" % (last_temp,)
except e:
logging.exception("Error in calc_temp callback")
- return ("Sensor '%s' temperature %s not in range %.3f:%.3f"
- % (self.name, tempstr, self.min_temp, self.max_temp))
+ return "Sensor '%s' temperature %s not in range %.3f:%.3f" % (
+ self.name,
+ tempstr,
+ self.min_temp,
+ self.max_temp,
+ )
######################################################################
# Linear interpolation
######################################################################
+
# Helper code to perform linear interpolation
class LinearInterpolate:
def __init__(self, samples):
@@ -99,15 +117,18 @@ class LinearInterpolate:
self.slopes.append((gain, offset))
if not self.keys:
raise ValueError("need at least two samples")
- self.keys.append(9999999999999.)
+ self.keys.append(9999999999999.0)
self.slopes.append(self.slopes[-1])
+
def interpolate(self, key):
pos = bisect.bisect(self.keys, key)
gain, offset = self.slopes[pos]
return key * gain + offset
+
def reverse_interpolate(self, value):
- values = [key * gain + offset for key, (gain, offset) in zip(
- self.keys, self.slopes)]
+ values = [
+ key * gain + offset for key, (gain, offset) in zip(self.keys, self.slopes)
+ ]
if values[0] < values[-2]:
valid = [i for i in range(len(values)) if values[i] >= value]
else:
@@ -120,27 +141,34 @@ class LinearInterpolate:
# Linear voltage to temperature converter
######################################################################
+
# Linear style conversion chips calibrated from temperature measurements
class LinearVoltage:
def __init__(self, config, params):
- adc_voltage = config.getfloat('adc_voltage', 5., above=0.)
- voltage_offset = config.getfloat('voltage_offset', 0.0)
+ adc_voltage = config.getfloat("adc_voltage", 5.0, above=0.0)
+ voltage_offset = config.getfloat("voltage_offset", 0.0)
samples = []
for temp, volt in params:
adc = (volt - voltage_offset) / adc_voltage
- if adc < 0. or adc > 1.:
- logging.warning("Ignoring adc sample %.3f/%.3f in heater %s",
- temp, volt, config.get_name())
+ if adc < 0.0 or adc > 1.0:
+ logging.warning(
+ "Ignoring adc sample %.3f/%.3f in heater %s",
+ temp,
+ volt,
+ config.get_name(),
+ )
continue
samples.append((adc, temp))
try:
li = LinearInterpolate(samples)
except ValueError as e:
- raise config.error("adc_temperature %s in heater %s" % (
- str(e), config.get_name()))
+ raise config.error(
+ "adc_temperature %s in heater %s" % (str(e), config.get_name())
+ )
self.calc_temp = li.interpolate
self.calc_adc = li.reverse_interpolate
+
# Custom defined sensors from the config file
class CustomLinearVoltage:
def __init__(self, config):
@@ -152,6 +180,7 @@ class CustomLinearVoltage:
break
v = config.getfloat("voltage%d" % (i,))
self.params.append((t, v))
+
def create(self, config):
lv = LinearVoltage(config, self.params)
return PrinterADCtoTemperature(config, lv)
@@ -161,25 +190,30 @@ class CustomLinearVoltage:
# Linear resistance to temperature converter
######################################################################
+
# Linear resistance calibrated from temperature measurements
class LinearResistance:
def __init__(self, config, samples):
- self.pullup = config.getfloat('pullup_resistor', 4700., above=0.)
+ self.pullup = config.getfloat("pullup_resistor", 4700.0, above=0.0)
try:
self.li = LinearInterpolate([(r, t) for t, r in samples])
except ValueError as e:
- raise config.error("adc_temperature %s in heater %s" % (
- str(e), config.get_name()))
+ raise config.error(
+ "adc_temperature %s in heater %s" % (str(e), config.get_name())
+ )
+
def calc_temp(self, adc):
# Calculate temperature from adc
- adc = max(.00001, min(.99999, adc))
+ adc = max(0.00001, min(0.99999, adc))
r = self.pullup * adc / (1.0 - adc)
return self.li.interpolate(r)
+
def calc_adc(self, temp):
# Calculate adc reading from a temperature
r = self.li.reverse_interpolate(temp)
return r / (self.pullup + r)
+
# Custom defined sensors from the config file
class CustomLinearResistance:
def __init__(self, config):
@@ -191,6 +225,7 @@ class CustomLinearResistance:
break
r = config.getfloat("resistance%d" % (i,))
self.samples.append((t, r))
+
def create(self, config):
lr = LinearResistance(config, self.samples)
return PrinterADCtoTemperature(config, lr)
@@ -201,132 +236,432 @@ class CustomLinearResistance:
######################################################################
AD595 = [
- (0., .0027), (10., .101), (20., .200), (25., .250), (30., .300),
- (40., .401), (50., .503), (60., .605), (80., .810), (100., 1.015),
- (120., 1.219), (140., 1.420), (160., 1.620), (180., 1.817), (200., 2.015),
- (220., 2.213), (240., 2.413), (260., 2.614), (280., 2.817), (300., 3.022),
- (320., 3.227), (340., 3.434), (360., 3.641), (380., 3.849), (400., 4.057),
- (420., 4.266), (440., 4.476), (460., 4.686), (480., 4.896)
+ (0.0, 0.0027),
+ (10.0, 0.101),
+ (20.0, 0.200),
+ (25.0, 0.250),
+ (30.0, 0.300),
+ (40.0, 0.401),
+ (50.0, 0.503),
+ (60.0, 0.605),
+ (80.0, 0.810),
+ (100.0, 1.015),
+ (120.0, 1.219),
+ (140.0, 1.420),
+ (160.0, 1.620),
+ (180.0, 1.817),
+ (200.0, 2.015),
+ (220.0, 2.213),
+ (240.0, 2.413),
+ (260.0, 2.614),
+ (280.0, 2.817),
+ (300.0, 3.022),
+ (320.0, 3.227),
+ (340.0, 3.434),
+ (360.0, 3.641),
+ (380.0, 3.849),
+ (400.0, 4.057),
+ (420.0, 4.266),
+ (440.0, 4.476),
+ (460.0, 4.686),
+ (480.0, 4.896),
]
AD597 = [
- (0., 0.), (10., .097), (20., .196), (25., .245), (30., .295),
- (40., 0.395), (50., 0.496), (60., 0.598), (80., 0.802), (100., 1.005),
- (120., 1.207), (140., 1.407), (160., 1.605), (180., 1.801), (200., 1.997),
- (220., 2.194), (240., 2.392), (260., 2.592), (280., 2.794), (300., 2.996),
- (320., 3.201), (340., 3.406), (360., 3.611), (380., 3.817), (400., 4.024),
- (420., 4.232), (440., 4.440), (460., 4.649), (480., 4.857), (500., 5.066)
+ (0.0, 0.0),
+ (10.0, 0.097),
+ (20.0, 0.196),
+ (25.0, 0.245),
+ (30.0, 0.295),
+ (40.0, 0.395),
+ (50.0, 0.496),
+ (60.0, 0.598),
+ (80.0, 0.802),
+ (100.0, 1.005),
+ (120.0, 1.207),
+ (140.0, 1.407),
+ (160.0, 1.605),
+ (180.0, 1.801),
+ (200.0, 1.997),
+ (220.0, 2.194),
+ (240.0, 2.392),
+ (260.0, 2.592),
+ (280.0, 2.794),
+ (300.0, 2.996),
+ (320.0, 3.201),
+ (340.0, 3.406),
+ (360.0, 3.611),
+ (380.0, 3.817),
+ (400.0, 4.024),
+ (420.0, 4.232),
+ (440.0, 4.440),
+ (460.0, 4.649),
+ (480.0, 4.857),
+ (500.0, 5.066),
]
AD8494 = [
- (-180, -0.714), (-160, -0.658), (-140, -0.594), (-120, -0.523),
- (-100, -0.446), (-80, -0.365), (-60, -0.278), (-40, -0.188),
- (-20, -0.095), (0, 0.002), (20, 0.1), (25, 0.125), (40, 0.201),
- (60, 0.303), (80, 0.406), (100, 0.511), (120, 0.617), (140, 0.723),
- (160, 0.829), (180, 0.937), (200, 1.044), (220, 1.151), (240, 1.259),
- (260, 1.366), (280, 1.473), (300, 1.58), (320, 1.687), (340, 1.794),
- (360, 1.901), (380, 2.008), (400, 2.114), (420, 2.221), (440, 2.328),
- (460, 2.435), (480, 2.542), (500, 2.65), (520, 2.759), (540, 2.868),
- (560, 2.979), (580, 3.09), (600, 3.203), (620, 3.316), (640, 3.431),
- (660, 3.548), (680, 3.666), (700, 3.786), (720, 3.906), (740, 4.029),
- (760, 4.152), (780, 4.276), (800, 4.401), (820, 4.526), (840, 4.65),
- (860, 4.774), (880, 4.897), (900, 5.018), (920, 5.138), (940, 5.257),
- (960, 5.374), (980, 5.49), (1000, 5.606), (1020, 5.72), (1040, 5.833),
- (1060, 5.946), (1080, 6.058), (1100, 6.17), (1120, 6.282), (1140, 6.394),
- (1160, 6.505), (1180, 6.616), (1200, 6.727)
+ (-180, -0.714),
+ (-160, -0.658),
+ (-140, -0.594),
+ (-120, -0.523),
+ (-100, -0.446),
+ (-80, -0.365),
+ (-60, -0.278),
+ (-40, -0.188),
+ (-20, -0.095),
+ (0, 0.002),
+ (20, 0.1),
+ (25, 0.125),
+ (40, 0.201),
+ (60, 0.303),
+ (80, 0.406),
+ (100, 0.511),
+ (120, 0.617),
+ (140, 0.723),
+ (160, 0.829),
+ (180, 0.937),
+ (200, 1.044),
+ (220, 1.151),
+ (240, 1.259),
+ (260, 1.366),
+ (280, 1.473),
+ (300, 1.58),
+ (320, 1.687),
+ (340, 1.794),
+ (360, 1.901),
+ (380, 2.008),
+ (400, 2.114),
+ (420, 2.221),
+ (440, 2.328),
+ (460, 2.435),
+ (480, 2.542),
+ (500, 2.65),
+ (520, 2.759),
+ (540, 2.868),
+ (560, 2.979),
+ (580, 3.09),
+ (600, 3.203),
+ (620, 3.316),
+ (640, 3.431),
+ (660, 3.548),
+ (680, 3.666),
+ (700, 3.786),
+ (720, 3.906),
+ (740, 4.029),
+ (760, 4.152),
+ (780, 4.276),
+ (800, 4.401),
+ (820, 4.526),
+ (840, 4.65),
+ (860, 4.774),
+ (880, 4.897),
+ (900, 5.018),
+ (920, 5.138),
+ (940, 5.257),
+ (960, 5.374),
+ (980, 5.49),
+ (1000, 5.606),
+ (1020, 5.72),
+ (1040, 5.833),
+ (1060, 5.946),
+ (1080, 6.058),
+ (1100, 6.17),
+ (1120, 6.282),
+ (1140, 6.394),
+ (1160, 6.505),
+ (1180, 6.616),
+ (1200, 6.727),
]
AD8495 = [
- (-260, -0.786), (-240, -0.774), (-220, -0.751), (-200, -0.719),
- (-180, -0.677), (-160, -0.627), (-140, -0.569), (-120, -0.504),
- (-100, -0.432), (-80, -0.355), (-60, -0.272), (-40, -0.184), (-20, -0.093),
- (0, 0.003), (20, 0.1), (25, 0.125), (40, 0.2), (60, 0.301), (80, 0.402),
- (100, 0.504), (120, 0.605), (140, 0.705), (160, 0.803), (180, 0.901),
- (200, 0.999), (220, 1.097), (240, 1.196), (260, 1.295), (280, 1.396),
- (300, 1.497), (320, 1.599), (340, 1.701), (360, 1.803), (380, 1.906),
- (400, 2.01), (420, 2.113), (440, 2.217), (460, 2.321), (480, 2.425),
- (500, 2.529), (520, 2.634), (540, 2.738), (560, 2.843), (580, 2.947),
- (600, 3.051), (620, 3.155), (640, 3.259), (660, 3.362), (680, 3.465),
- (700, 3.568), (720, 3.67), (740, 3.772), (760, 3.874), (780, 3.975),
- (800, 4.076), (820, 4.176), (840, 4.275), (860, 4.374), (880, 4.473),
- (900, 4.571), (920, 4.669), (940, 4.766), (960, 4.863), (980, 4.959),
- (1000, 5.055), (1020, 5.15), (1040, 5.245), (1060, 5.339), (1080, 5.432),
- (1100, 5.525), (1120, 5.617), (1140, 5.709), (1160, 5.8), (1180, 5.891),
- (1200, 5.98), (1220, 6.069), (1240, 6.158), (1260, 6.245), (1280, 6.332),
- (1300, 6.418), (1320, 6.503), (1340, 6.587), (1360, 6.671), (1380, 6.754)
+ (-260, -0.786),
+ (-240, -0.774),
+ (-220, -0.751),
+ (-200, -0.719),
+ (-180, -0.677),
+ (-160, -0.627),
+ (-140, -0.569),
+ (-120, -0.504),
+ (-100, -0.432),
+ (-80, -0.355),
+ (-60, -0.272),
+ (-40, -0.184),
+ (-20, -0.093),
+ (0, 0.003),
+ (20, 0.1),
+ (25, 0.125),
+ (40, 0.2),
+ (60, 0.301),
+ (80, 0.402),
+ (100, 0.504),
+ (120, 0.605),
+ (140, 0.705),
+ (160, 0.803),
+ (180, 0.901),
+ (200, 0.999),
+ (220, 1.097),
+ (240, 1.196),
+ (260, 1.295),
+ (280, 1.396),
+ (300, 1.497),
+ (320, 1.599),
+ (340, 1.701),
+ (360, 1.803),
+ (380, 1.906),
+ (400, 2.01),
+ (420, 2.113),
+ (440, 2.217),
+ (460, 2.321),
+ (480, 2.425),
+ (500, 2.529),
+ (520, 2.634),
+ (540, 2.738),
+ (560, 2.843),
+ (580, 2.947),
+ (600, 3.051),
+ (620, 3.155),
+ (640, 3.259),
+ (660, 3.362),
+ (680, 3.465),
+ (700, 3.568),
+ (720, 3.67),
+ (740, 3.772),
+ (760, 3.874),
+ (780, 3.975),
+ (800, 4.076),
+ (820, 4.176),
+ (840, 4.275),
+ (860, 4.374),
+ (880, 4.473),
+ (900, 4.571),
+ (920, 4.669),
+ (940, 4.766),
+ (960, 4.863),
+ (980, 4.959),
+ (1000, 5.055),
+ (1020, 5.15),
+ (1040, 5.245),
+ (1060, 5.339),
+ (1080, 5.432),
+ (1100, 5.525),
+ (1120, 5.617),
+ (1140, 5.709),
+ (1160, 5.8),
+ (1180, 5.891),
+ (1200, 5.98),
+ (1220, 6.069),
+ (1240, 6.158),
+ (1260, 6.245),
+ (1280, 6.332),
+ (1300, 6.418),
+ (1320, 6.503),
+ (1340, 6.587),
+ (1360, 6.671),
+ (1380, 6.754),
]
AD8496 = [
- (-180, -0.642), (-160, -0.59), (-140, -0.53), (-120, -0.464),
- (-100, -0.392), (-80, -0.315), (-60, -0.235), (-40, -0.15), (-20, -0.063),
- (0, 0.027), (20, 0.119), (25, 0.142), (40, 0.213), (60, 0.308),
- (80, 0.405), (100, 0.503), (120, 0.601), (140, 0.701), (160, 0.8),
- (180, 0.9), (200, 1.001), (220, 1.101), (240, 1.201), (260, 1.302),
- (280, 1.402), (300, 1.502), (320, 1.602), (340, 1.702), (360, 1.801),
- (380, 1.901), (400, 2.001), (420, 2.1), (440, 2.2), (460, 2.3),
- (480, 2.401), (500, 2.502), (520, 2.603), (540, 2.705), (560, 2.808),
- (580, 2.912), (600, 3.017), (620, 3.124), (640, 3.231), (660, 3.34),
- (680, 3.451), (700, 3.562), (720, 3.675), (740, 3.789), (760, 3.904),
- (780, 4.02), (800, 4.137), (820, 4.254), (840, 4.37), (860, 4.486),
- (880, 4.6), (900, 4.714), (920, 4.826), (940, 4.937), (960, 5.047),
- (980, 5.155), (1000, 5.263), (1020, 5.369), (1040, 5.475), (1060, 5.581),
- (1080, 5.686), (1100, 5.79), (1120, 5.895), (1140, 5.999), (1160, 6.103),
- (1180, 6.207), (1200, 6.311)
+ (-180, -0.642),
+ (-160, -0.59),
+ (-140, -0.53),
+ (-120, -0.464),
+ (-100, -0.392),
+ (-80, -0.315),
+ (-60, -0.235),
+ (-40, -0.15),
+ (-20, -0.063),
+ (0, 0.027),
+ (20, 0.119),
+ (25, 0.142),
+ (40, 0.213),
+ (60, 0.308),
+ (80, 0.405),
+ (100, 0.503),
+ (120, 0.601),
+ (140, 0.701),
+ (160, 0.8),
+ (180, 0.9),
+ (200, 1.001),
+ (220, 1.101),
+ (240, 1.201),
+ (260, 1.302),
+ (280, 1.402),
+ (300, 1.502),
+ (320, 1.602),
+ (340, 1.702),
+ (360, 1.801),
+ (380, 1.901),
+ (400, 2.001),
+ (420, 2.1),
+ (440, 2.2),
+ (460, 2.3),
+ (480, 2.401),
+ (500, 2.502),
+ (520, 2.603),
+ (540, 2.705),
+ (560, 2.808),
+ (580, 2.912),
+ (600, 3.017),
+ (620, 3.124),
+ (640, 3.231),
+ (660, 3.34),
+ (680, 3.451),
+ (700, 3.562),
+ (720, 3.675),
+ (740, 3.789),
+ (760, 3.904),
+ (780, 4.02),
+ (800, 4.137),
+ (820, 4.254),
+ (840, 4.37),
+ (860, 4.486),
+ (880, 4.6),
+ (900, 4.714),
+ (920, 4.826),
+ (940, 4.937),
+ (960, 5.047),
+ (980, 5.155),
+ (1000, 5.263),
+ (1020, 5.369),
+ (1040, 5.475),
+ (1060, 5.581),
+ (1080, 5.686),
+ (1100, 5.79),
+ (1120, 5.895),
+ (1140, 5.999),
+ (1160, 6.103),
+ (1180, 6.207),
+ (1200, 6.311),
]
AD8497 = [
- (-260, -0.785), (-240, -0.773), (-220, -0.751), (-200, -0.718),
- (-180, -0.676), (-160, -0.626), (-140, -0.568), (-120, -0.503),
- (-100, -0.432), (-80, -0.354), (-60, -0.271), (-40, -0.184),
- (-20, -0.092), (0, 0.003), (20, 0.101), (25, 0.126), (40, 0.2),
- (60, 0.301), (80, 0.403), (100, 0.505), (120, 0.605), (140, 0.705),
- (160, 0.804), (180, 0.902), (200, 0.999), (220, 1.097), (240, 1.196),
- (260, 1.296), (280, 1.396), (300, 1.498), (320, 1.599), (340, 1.701),
- (360, 1.804), (380, 1.907), (400, 2.01), (420, 2.114), (440, 2.218),
- (460, 2.322), (480, 2.426), (500, 2.53), (520, 2.634), (540, 2.739),
- (560, 2.843), (580, 2.948), (600, 3.052), (620, 3.156), (640, 3.259),
- (660, 3.363), (680, 3.466), (700, 3.569), (720, 3.671), (740, 3.773),
- (760, 3.874), (780, 3.976), (800, 4.076), (820, 4.176), (840, 4.276),
- (860, 4.375), (880, 4.474), (900, 4.572), (920, 4.67), (940, 4.767),
- (960, 4.863), (980, 4.96), (1000, 5.055), (1020, 5.151), (1040, 5.245),
- (1060, 5.339), (1080, 5.433), (1100, 5.526), (1120, 5.618), (1140, 5.71),
- (1160, 5.801), (1180, 5.891), (1200, 5.981), (1220, 6.07), (1240, 6.158),
- (1260, 6.246), (1280, 6.332), (1300, 6.418), (1320, 6.503), (1340, 6.588),
- (1360, 6.671), (1380, 6.754)
+ (-260, -0.785),
+ (-240, -0.773),
+ (-220, -0.751),
+ (-200, -0.718),
+ (-180, -0.676),
+ (-160, -0.626),
+ (-140, -0.568),
+ (-120, -0.503),
+ (-100, -0.432),
+ (-80, -0.354),
+ (-60, -0.271),
+ (-40, -0.184),
+ (-20, -0.092),
+ (0, 0.003),
+ (20, 0.101),
+ (25, 0.126),
+ (40, 0.2),
+ (60, 0.301),
+ (80, 0.403),
+ (100, 0.505),
+ (120, 0.605),
+ (140, 0.705),
+ (160, 0.804),
+ (180, 0.902),
+ (200, 0.999),
+ (220, 1.097),
+ (240, 1.196),
+ (260, 1.296),
+ (280, 1.396),
+ (300, 1.498),
+ (320, 1.599),
+ (340, 1.701),
+ (360, 1.804),
+ (380, 1.907),
+ (400, 2.01),
+ (420, 2.114),
+ (440, 2.218),
+ (460, 2.322),
+ (480, 2.426),
+ (500, 2.53),
+ (520, 2.634),
+ (540, 2.739),
+ (560, 2.843),
+ (580, 2.948),
+ (600, 3.052),
+ (620, 3.156),
+ (640, 3.259),
+ (660, 3.363),
+ (680, 3.466),
+ (700, 3.569),
+ (720, 3.671),
+ (740, 3.773),
+ (760, 3.874),
+ (780, 3.976),
+ (800, 4.076),
+ (820, 4.176),
+ (840, 4.276),
+ (860, 4.375),
+ (880, 4.474),
+ (900, 4.572),
+ (920, 4.67),
+ (940, 4.767),
+ (960, 4.863),
+ (980, 4.96),
+ (1000, 5.055),
+ (1020, 5.151),
+ (1040, 5.245),
+ (1060, 5.339),
+ (1080, 5.433),
+ (1100, 5.526),
+ (1120, 5.618),
+ (1140, 5.71),
+ (1160, 5.801),
+ (1180, 5.891),
+ (1200, 5.981),
+ (1220, 6.07),
+ (1240, 6.158),
+ (1260, 6.246),
+ (1280, 6.332),
+ (1300, 6.418),
+ (1320, 6.503),
+ (1340, 6.588),
+ (1360, 6.671),
+ (1380, 6.754),
]
-def calc_pt100(base=100.):
+
+def calc_pt100(base=100.0):
# Calc PT100/PT1000 resistances using Callendar-Van Dusen formula
A, B = (3.9083e-3, -5.775e-7)
- return [(float(t), base * (1. + A*t + B*t*t)) for t in range(0, 500, 10)]
+ return [(float(t), base * (1.0 + A * t + B * t * t)) for t in range(0, 500, 10)]
+
def calc_ina826_pt100():
# Standard circuit is 4400ohm pullup with 10x gain to 5V
- return [(t, 10. * 5. * r / (4400. + r)) for t, r in calc_pt100()]
+ return [(t, 10.0 * 5.0 * r / (4400.0 + r)) for t, r in calc_pt100()]
+
DefaultVoltageSensors = [
- ("AD595", AD595), ("AD597", AD597), ("AD8494", AD8494), ("AD8495", AD8495),
- ("AD8496", AD8496), ("AD8497", AD8497),
- ("PT100 INA826", calc_ina826_pt100())
+ ("AD595", AD595),
+ ("AD597", AD597),
+ ("AD8494", AD8494),
+ ("AD8495", AD8495),
+ ("AD8496", AD8496),
+ ("AD8497", AD8497),
+ ("PT100 INA826", calc_ina826_pt100()),
]
-DefaultResistanceSensors = [
- ("PT1000", calc_pt100(1000.))
-]
+DefaultResistanceSensors = [("PT1000", calc_pt100(1000.0))]
+
def load_config(config):
# Register default sensors
pheaters = config.get_printer().load_object(config, "heaters")
for sensor_type, params in DefaultVoltageSensors:
- func = (lambda config, params=params:
- PrinterADCtoTemperature(config, LinearVoltage(config, params)))
+ func = lambda config, params=params: PrinterADCtoTemperature(
+ config, LinearVoltage(config, params)
+ )
pheaters.add_sensor_factory(sensor_type, func)
for sensor_type, params in DefaultResistanceSensors:
- func = (lambda config, params=params:
- PrinterADCtoTemperature(config,
- LinearResistance(config, params)))
+ func = lambda config, params=params: PrinterADCtoTemperature(
+ config, LinearResistance(config, params)
+ )
pheaters.add_sensor_factory(sensor_type, func)
+
def load_config_prefix(config):
if config.get("resistance1", None) is None:
custom_sensor = CustomLinearVoltage(config)
diff --git a/klippy/extras/ads1220.py b/klippy/extras/ads1220.py
index 16080dc7..d1ccc9c9 100644
--- a/klippy/extras/ads1220.py
+++ b/klippy/extras/ads1220.py
@@ -19,6 +19,7 @@ WREG_CMD = 0x40
NOOP_CMD = 0x0
RESET_STATE = bytearray([0x0, 0x0, 0x0, 0x0])
+
# turn bytearrays into pretty hex strings: [0xff, 0x1]
def hexify(byte_array):
return "[%s]" % (", ".join([hex(b) for b in byte_array]))
@@ -32,59 +33,103 @@ class ADS1220:
self.consecutive_fails = 0
# Chip options
# Gain
- self.gain_options = {'1': 0x0, '2': 0x1, '4': 0x2, '8': 0x3, '16': 0x4,
- '32': 0x5, '64': 0x6, '128': 0x7}
- self.gain = config.getchoice('gain', self.gain_options, default='128')
+ self.gain_options = {
+ "1": 0x0,
+ "2": 0x1,
+ "4": 0x2,
+ "8": 0x3,
+ "16": 0x4,
+ "32": 0x5,
+ "64": 0x6,
+ "128": 0x7,
+ }
+ self.gain = config.getchoice("gain", self.gain_options, default="128")
# Sample rate
- self.sps_normal = {'20': 20, '45': 45, '90': 90, '175': 175,
- '330': 330, '600': 600, '1000': 1000}
- self.sps_turbo = {'40': 40, '90': 90, '180': 180, '350': 350,
- '660': 660, '1200': 1200, '2000': 2000}
+ self.sps_normal = {
+ "20": 20,
+ "45": 45,
+ "90": 90,
+ "175": 175,
+ "330": 330,
+ "600": 600,
+ "1000": 1000,
+ }
+ self.sps_turbo = {
+ "40": 40,
+ "90": 90,
+ "180": 180,
+ "350": 350,
+ "660": 660,
+ "1200": 1200,
+ "2000": 2000,
+ }
self.sps_options = self.sps_normal.copy()
self.sps_options.update(self.sps_turbo)
- self.sps = config.getchoice('sample_rate', self.sps_options,
- default='660')
+ self.sps = config.getchoice("sample_rate", self.sps_options, default="660")
self.is_turbo = str(self.sps) in self.sps_turbo
# Input multiplexer: AINP and AINN
- mux_options = {'AIN0_AIN1': 0b0000, 'AIN0_AIN2': 0b0001,
- 'AIN0_AIN3': 0b0010, 'AIN1_AIN2': 0b0011,
- 'AIN1_AIN3': 0b0100, 'AIN2_AIN3': 0b0101,
- 'AIN1_AIN0': 0b0110, 'AIN3_AIN2': 0b0111,
- 'AIN0_AVSS': 0b1000, 'AIN1_AVSS': 0b1001,
- 'AIN2_AVSS': 0b1010, 'AIN3_AVSS': 0b1011}
- self.mux = config.getchoice('input_mux', mux_options,
- default='AIN0_AIN1')
+ mux_options = {
+ "AIN0_AIN1": 0b0000,
+ "AIN0_AIN2": 0b0001,
+ "AIN0_AIN3": 0b0010,
+ "AIN1_AIN2": 0b0011,
+ "AIN1_AIN3": 0b0100,
+ "AIN2_AIN3": 0b0101,
+ "AIN1_AIN0": 0b0110,
+ "AIN3_AIN2": 0b0111,
+ "AIN0_AVSS": 0b1000,
+ "AIN1_AVSS": 0b1001,
+ "AIN2_AVSS": 0b1010,
+ "AIN3_AVSS": 0b1011,
+ }
+ self.mux = config.getchoice("input_mux", mux_options, default="AIN0_AIN1")
# PGA Bypass
- self.pga_bypass = config.getboolean('pga_bypass', default=False)
+ self.pga_bypass = config.getboolean("pga_bypass", default=False)
# bypass PGA when AVSS is the negative input
force_pga_bypass = self.mux >= 0b1000
self.pga_bypass = force_pga_bypass or self.pga_bypass
# Voltage Reference
- self.vref_options = {'internal': 0b0, 'REF0': 0b01, 'REF1': 0b10,
- 'analog_supply': 0b11}
- self.vref = config.getchoice('vref', self.vref_options,
- default='internal')
+ self.vref_options = {
+ "internal": 0b0,
+ "REF0": 0b01,
+ "REF1": 0b10,
+ "analog_supply": 0b11,
+ }
+ self.vref = config.getchoice("vref", self.vref_options, default="internal")
# check for conflict between REF1 and AIN0/AIN3
- mux_conflict = [0b0000, 0b0001, 0b0010, 0b0100, 0b0101, 0b0110, 0b0111,
- 0b1000, 0b1011]
+ mux_conflict = [
+ 0b0000,
+ 0b0001,
+ 0b0010,
+ 0b0100,
+ 0b0101,
+ 0b0110,
+ 0b0111,
+ 0b1000,
+ 0b1011,
+ ]
if self.vref == 0b10 and self.mux in mux_conflict:
- raise config.error("ADS1220 config error: AIN0/REFP1 and AIN3/REFN1"
- " cant be used as a voltage reference and"
- " an input at the same time")
+ raise config.error(
+ "ADS1220 config error: AIN0/REFP1 and AIN3/REFN1"
+ " cant be used as a voltage reference and"
+ " an input at the same time"
+ )
# SPI Setup
spi_speed = 512000 if self.is_turbo else 256000
self.spi = bus.MCU_SPI_from_config(config, 1, default_speed=spi_speed)
self.mcu = mcu = self.spi.get_mcu()
self.oid = mcu.create_oid()
# Data Ready (DRDY) Pin
- drdy_pin = config.get('data_ready_pin')
- ppins = printer.lookup_object('pins')
+ drdy_pin = config.get("data_ready_pin")
+ ppins = printer.lookup_object("pins")
drdy_ppin = ppins.lookup_pin(drdy_pin)
- self.data_ready_pin = drdy_ppin['pin']
- drdy_pin_mcu = drdy_ppin['chip']
+ self.data_ready_pin = drdy_ppin["pin"]
+ drdy_pin_mcu = drdy_ppin["chip"]
if drdy_pin_mcu != self.mcu:
- raise config.error("ADS1220 config error: SPI communication and"
- " data_ready_pin must be on the same MCU")
+ raise config.error(
+ "ADS1220 config error: SPI communication and"
+ " data_ready_pin must be on the same MCU"
+ )
# Bulk Sensor Setup
self.bulk_queue = bulk_sensor.BulkDataQueue(self.mcu, oid=self.oid)
# Clock tracking
@@ -93,26 +138,35 @@ class ADS1220:
self.ffreader = bulk_sensor.FixedFreqReader(mcu, chip_smooth, "<i")
# Process messages in batches
self.batch_bulk = bulk_sensor.BatchBulkHelper(
- self.printer, self._process_batch, self._start_measurements,
- self._finish_measurements, UPDATE_INTERVAL)
+ self.printer,
+ self._process_batch,
+ self._start_measurements,
+ self._finish_measurements,
+ UPDATE_INTERVAL,
+ )
# Command Configuration
self.attach_probe_cmd = None
mcu.add_config_cmd(
"config_ads1220 oid=%d spi_oid=%d data_ready_pin=%s"
- % (self.oid, self.spi.get_oid(), self.data_ready_pin))
- mcu.add_config_cmd("query_ads1220 oid=%d rest_ticks=0"
- % (self.oid,), on_restart=True)
+ % (self.oid, self.spi.get_oid(), self.data_ready_pin)
+ )
+ mcu.add_config_cmd(
+ "query_ads1220 oid=%d rest_ticks=0" % (self.oid,), on_restart=True
+ )
mcu.register_config_callback(self._build_config)
self.query_ads1220_cmd = None
def _build_config(self):
cmdqueue = self.spi.get_command_queue()
self.query_ads1220_cmd = self.mcu.lookup_command(
- "query_ads1220 oid=%c rest_ticks=%u", cq=cmdqueue)
+ "query_ads1220 oid=%c rest_ticks=%u", cq=cmdqueue
+ )
self.attach_probe_cmd = self.mcu.lookup_command(
- "ads1220_attach_load_cell_probe oid=%c load_cell_probe_oid=%c")
- self.ffreader.setup_query_command("query_ads1220_status oid=%c",
- oid=self.oid, cq=cmdqueue)
+ "ads1220_attach_load_cell_probe oid=%c load_cell_probe_oid=%c"
+ )
+ self.ffreader.setup_query_command(
+ "query_ads1220_status oid=%c", oid=self.oid, cq=cmdqueue
+ )
def get_mcu(self):
return self.mcu
@@ -134,7 +188,7 @@ class ADS1220:
# Measurement decoding
def _convert_samples(self, samples):
- adc_factor = 1. / (1 << 23)
+ adc_factor = 1.0 / (1 << 23)
count = 0
for ptime, val in samples:
samples[count] = (round(ptime, 6), val, round(val * adc_factor, 9))
@@ -148,7 +202,7 @@ class ADS1220:
# Start bulk reading
self.reset_chip()
self.setup_chip()
- rest_ticks = self.mcu.seconds_to_clock(1. / (10. * self.sps))
+ rest_ticks = self.mcu.seconds_to_clock(1.0 / (10.0 * self.sps))
self.query_ads1220_cmd.send([self.oid, rest_ticks])
logging.info("ADS1220 starting '%s' measurements", self.name)
# Initialize clock tracking
@@ -166,8 +220,11 @@ class ADS1220:
def _process_batch(self, eventtime):
samples = self.ffreader.pull_samples()
self._convert_samples(samples)
- return {'data': samples, 'errors': self.last_error_count,
- 'overflows': self.ffreader.get_last_overflows()}
+ return {
+ "data": samples,
+ "errors": self.last_error_count,
+ "overflows": self.ffreader.get_last_overflows(),
+ }
def reset_chip(self):
# the reset command takes 50us to complete
@@ -179,17 +236,20 @@ class ADS1220:
"Invalid ads1220 reset state (got %s vs %s).\n"
"This is generally indicative of connection problems\n"
"(e.g. faulty wiring) or a faulty ADS1220 chip."
- % (hexify(val), hexify(RESET_STATE)))
+ % (hexify(val), hexify(RESET_STATE))
+ )
def setup_chip(self):
continuous = 0x1 # enable continuous conversions
mode = 0x2 if self.is_turbo else 0x0 # turbo mode
sps_list = self.sps_turbo if self.is_turbo else self.sps_normal
data_rate = list(sps_list.keys()).index(str(self.sps))
- reg_values = [(self.mux << 4) | (self.gain << 1) | int(self.pga_bypass),
- (data_rate << 5) | (mode << 3) | (continuous << 2),
- (self.vref << 6),
- 0x0]
+ reg_values = [
+ (self.mux << 4) | (self.gain << 1) | int(self.pga_bypass),
+ (data_rate << 5) | (mode << 3) | (continuous << 2),
+ (self.vref << 6),
+ 0x0,
+ ]
self.write_reg(0x0, reg_values)
# start measurements immediately
self.send_command(START_SYNC_CMD)
@@ -198,7 +258,7 @@ class ADS1220:
read_command = [RREG_CMD | (reg << 2) | (byte_count - 1)]
read_command += [NOOP_CMD] * byte_count
params = self.spi.spi_transfer(read_command)
- return bytearray(params['response'][1:])
+ return bytearray(params["response"][1:])
def send_command(self, cmd):
self.spi.spi_send([cmd])
@@ -211,8 +271,9 @@ class ADS1220:
if bytearray(register_bytes) != stored_val:
raise self.printer.command_error(
"Failed to set ADS1220 register [0x%x] to %s: got %s. "
- "This may be a connection problem (e.g. faulty wiring)" % (
- reg, hexify(register_bytes), hexify(stored_val)))
+ "This may be a connection problem (e.g. faulty wiring)"
+ % (reg, hexify(register_bytes), hexify(stored_val))
+ )
ADS1220_SENSOR_TYPE = {"ads1220": ADS1220}
diff --git a/klippy/extras/ads1x1x.py b/klippy/extras/ads1x1x.py
index 9290b3f1..43bdea25 100644
--- a/klippy/extras/ads1x1x.py
+++ b/klippy/extras/ads1x1x.py
@@ -9,81 +9,83 @@ from . import bus
# Supported chip types
ADS1X1X_CHIP_TYPE = {
- 'ADS1013': 3,
- 'ADS1014': 4,
- 'ADS1015': 5,
- 'ADS1113': 13,
- 'ADS1114': 14,
- 'ADS1115': 15
+ "ADS1013": 3,
+ "ADS1014": 4,
+ "ADS1015": 5,
+ "ADS1113": 13,
+ "ADS1114": 14,
+ "ADS1115": 15,
}
+
def isADS101X(chip):
- return (chip == ADS1X1X_CHIP_TYPE['ADS1013'] \
- or chip == ADS1X1X_CHIP_TYPE['ADS1014'] \
- or chip == ADS1X1X_CHIP_TYPE['ADS1015'])
+ return (
+ chip == ADS1X1X_CHIP_TYPE["ADS1013"]
+ or chip == ADS1X1X_CHIP_TYPE["ADS1014"]
+ or chip == ADS1X1X_CHIP_TYPE["ADS1015"]
+ )
+
def isADS111X(chip):
- return (chip == ADS1X1X_CHIP_TYPE['ADS1113'] \
- or chip == ADS1X1X_CHIP_TYPE['ADS1114'] \
- or chip == ADS1X1X_CHIP_TYPE['ADS1115'])
+ return (
+ chip == ADS1X1X_CHIP_TYPE["ADS1113"]
+ or chip == ADS1X1X_CHIP_TYPE["ADS1114"]
+ or chip == ADS1X1X_CHIP_TYPE["ADS1115"]
+ )
+
# Address is defined by how the address pin is wired
-ADS1X1X_CHIP_ADDR = {
- 'GND': 0x48,
- 'VCC': 0x49,
- 'SDA': 0x4a,
- 'SCL': 0x4b
-}
+ADS1X1X_CHIP_ADDR = {"GND": 0x48, "VCC": 0x49, "SDA": 0x4A, "SCL": 0x4B}
# Chip "pointer" registers
ADS1X1X_REG_POINTER_MASK = 0x03
ADS1X1X_REG_POINTER = {
- 'CONVERSION': 0x00,
- 'CONFIG': 0x01,
- 'LO_THRESH': 0x02,
- 'HI_THRESH': 0x03
+ "CONVERSION": 0x00,
+ "CONFIG": 0x01,
+ "LO_THRESH": 0x02,
+ "HI_THRESH": 0x03,
}
# Config register masks
ADS1X1X_REG_CONFIG = {
- 'OS_MASK': 0x8000,
- 'MULTIPLEXER_MASK': 0x7000,
- 'PGA_MASK': 0x0E00,
- 'MODE_MASK': 0x0100,
- 'DATA_RATE_MASK': 0x00E0,
- 'COMPARATOR_MODE_MASK': 0x0010,
- 'COMPARATOR_POLARITY_MASK': 0x0008,
+ "OS_MASK": 0x8000,
+ "MULTIPLEXER_MASK": 0x7000,
+ "PGA_MASK": 0x0E00,
+ "MODE_MASK": 0x0100,
+ "DATA_RATE_MASK": 0x00E0,
+ "COMPARATOR_MODE_MASK": 0x0010,
+ "COMPARATOR_POLARITY_MASK": 0x0008,
# Determines if ALERT/RDY pin latches once asserted
- 'COMPARATOR_LATCHING_MASK': 0x0004,
- 'COMPARATOR_QUEUE_MASK': 0x0003
+ "COMPARATOR_LATCHING_MASK": 0x0004,
+ "COMPARATOR_QUEUE_MASK": 0x0003,
}
#
# The following enums are to be used with the configuration functions.
#
ADS1X1X_OS = {
- 'OS_IDLE': 0x8000, # Device is not performing a conversion
- 'OS_SINGLE': 0x8000 # Single-conversion
+ "OS_IDLE": 0x8000, # Device is not performing a conversion
+ "OS_SINGLE": 0x8000, # Single-conversion
}
ADS1X1X_MUX = {
- 'DIFF01': 0x0000, # Differential P = AIN0, N = AIN1 0
- 'DIFF03': 0x1000, # Differential P = AIN0, N = AIN3 4096
- 'DIFF13': 0x2000, # Differential P = AIN1, N = AIN3 8192
- 'DIFF23': 0x3000, # Differential P = AIN2, N = AIN3 12288
- 'AIN0': 0x4000, # Single-ended (ADS1015: AIN0 16384)
- 'AIN1': 0x5000, # Single-ended (ADS1015: AIN1 20480)
- 'AIN2': 0x6000, # Single-ended (ADS1015: AIN2 24576)
- 'AIN3': 0x7000 # Single-ended (ADS1015: AIN3 28672)
+ "DIFF01": 0x0000, # Differential P = AIN0, N = AIN1 0
+ "DIFF03": 0x1000, # Differential P = AIN0, N = AIN3 4096
+ "DIFF13": 0x2000, # Differential P = AIN1, N = AIN3 8192
+ "DIFF23": 0x3000, # Differential P = AIN2, N = AIN3 12288
+ "AIN0": 0x4000, # Single-ended (ADS1015: AIN0 16384)
+ "AIN1": 0x5000, # Single-ended (ADS1015: AIN1 20480)
+ "AIN2": 0x6000, # Single-ended (ADS1015: AIN2 24576)
+ "AIN3": 0x7000, # Single-ended (ADS1015: AIN3 28672)
}
ADS1X1X_PGA = {
- '6.144V': 0x0000, # +/-6.144V range = Gain 2/3
- '4.096V': 0x0200, # +/-4.096V range = Gain 1
- '2.048V': 0x0400, # +/-2.048V range = Gain 2
- '1.024V': 0x0600, # +/-1.024V range = Gain 4
- '0.512V': 0x0800, # +/-0.512V range = Gain 8
- '0.256V': 0x0A00 # +/-0.256V range = Gain 16
+ "6.144V": 0x0000, # +/-6.144V range = Gain 2/3
+ "4.096V": 0x0200, # +/-4.096V range = Gain 1
+ "2.048V": 0x0400, # +/-2.048V range = Gain 2
+ "1.024V": 0x0600, # +/-1.024V range = Gain 4
+ "0.512V": 0x0800, # +/-0.512V range = Gain 8
+ "0.256V": 0x0A00, # +/-0.256V range = Gain 16
}
ADS1X1X_PGA_VALUE = {
0x0000: 6.144,
@@ -100,7 +102,7 @@ ADS111X_PGA_SCALAR = {
0x0400: 2.048 / ADS111X_RESOLUTION, # +/-2.048V range = Gain 2
0x0600: 1.024 / ADS111X_RESOLUTION, # +/-1.024V range = Gain 4
0x0800: 0.512 / ADS111X_RESOLUTION, # +/-0.512V range = Gain 8
- 0x0A00: 0.256 / ADS111X_RESOLUTION # +/-0.256V range = Gain 16
+ 0x0A00: 0.256 / ADS111X_RESOLUTION, # +/-0.256V range = Gain 16
}
ADS101X_RESOLUTION = 2047.0
ADS101X_PGA_SCALAR = {
@@ -109,63 +111,61 @@ ADS101X_PGA_SCALAR = {
0x0400: 2.048 / ADS101X_RESOLUTION, # +/-2.048V range = Gain 2
0x0600: 1.024 / ADS101X_RESOLUTION, # +/-1.024V range = Gain 4
0x0800: 0.512 / ADS101X_RESOLUTION, # +/-0.512V range = Gain 8
- 0x0A00: 0.256 / ADS101X_RESOLUTION # +/-0.256V range = Gain 16
+ 0x0A00: 0.256 / ADS101X_RESOLUTION, # +/-0.256V range = Gain 16
}
ADS1X1X_MODE = {
- 'continuous': 0x0000, # Continuous conversion mode
- 'single': 0x0100 # Power-down single-shot mode
+ "continuous": 0x0000, # Continuous conversion mode
+ "single": 0x0100, # Power-down single-shot mode
}
# Lesser samples per second means it takes and averages more samples before
# returning a result.
ADS101X_SAMPLES_PER_SECOND = {
- '128': 0x0000, # 128 samples per second
- '250': 0x0020, # 250 samples per second
- '490': 0x0040, # 490 samples per second
- '920': 0x0060, # 920 samples per second
- '1600': 0x0080, # 1600 samples per second
- '2400': 0x00a0, # 2400 samples per second
- '3300': 0x00c0, # 3300 samples per second
+ "128": 0x0000, # 128 samples per second
+ "250": 0x0020, # 250 samples per second
+ "490": 0x0040, # 490 samples per second
+ "920": 0x0060, # 920 samples per second
+ "1600": 0x0080, # 1600 samples per second
+ "2400": 0x00A0, # 2400 samples per second
+ "3300": 0x00C0, # 3300 samples per second
}
ADS111X_SAMPLES_PER_SECOND = {
- '8': 0x0000, # 8 samples per second
- '16': 0x0020, # 16 samples per second
- '32': 0x0040, # 32 samples per second
- '64': 0x0060, # 64 samples per second
- '128': 0x0080, # 128 samples per second
- '250': 0x00a0, # 250 samples per second
- '475': 0x00c0, # 475 samples per second
- '860': 0x00e0 # 860 samples per second
+ "8": 0x0000, # 8 samples per second
+ "16": 0x0020, # 16 samples per second
+ "32": 0x0040, # 32 samples per second
+ "64": 0x0060, # 64 samples per second
+ "128": 0x0080, # 128 samples per second
+ "250": 0x00A0, # 250 samples per second
+ "475": 0x00C0, # 475 samples per second
+ "860": 0x00E0, # 860 samples per second
}
ADS1X1X_COMPARATOR_MODE = {
- 'TRADITIONAL': 0x0000, # Traditional comparator with hysteresis
- 'WINDOW': 0x0010 # Window comparator
+ "TRADITIONAL": 0x0000, # Traditional comparator with hysteresis
+ "WINDOW": 0x0010, # Window comparator
}
ADS1X1X_COMPARATOR_POLARITY = {
- 'ACTIVE_LO': 0x0000, # ALERT/RDY pin is low when active
- 'ACTIVE_HI': 0x0008 # ALERT/RDY pin is high when active
+ "ACTIVE_LO": 0x0000, # ALERT/RDY pin is low when active
+ "ACTIVE_HI": 0x0008, # ALERT/RDY pin is high when active
}
ADS1X1X_COMPARATOR_LATCHING = {
- 'NON_LATCHING': 0x0000, # Non-latching comparator
- 'LATCHING': 0x0004 # Latching comparator
+ "NON_LATCHING": 0x0000, # Non-latching comparator
+ "LATCHING": 0x0004, # Latching comparator
}
ADS1X1X_COMPARATOR_QUEUE = {
- 'QUEUE_1': 0x0000, # Assert ALERT/RDY after one conversions
- 'QUEUE_2': 0x0001, # Assert ALERT/RDY after two conversions
- 'QUEUE_4': 0x0002, # Assert ALERT/RDY after four conversions
- 'QUEUE_NONE': 0x0003 # Disable the comparator and put ALERT/RDY
- # in high state
+ "QUEUE_1": 0x0000, # Assert ALERT/RDY after one conversions
+ "QUEUE_2": 0x0001, # Assert ALERT/RDY after two conversions
+ "QUEUE_4": 0x0002, # Assert ALERT/RDY after four conversions
+ "QUEUE_NONE": 0x0003, # Disable the comparator and put ALERT/RDY
+ # in high state
}
-ADS1X1_OPERATIONS = {
- 'SET_MUX': 0,
- 'READ_CONVERSION': 1
-}
+ADS1X1_OPERATIONS = {"SET_MUX": 0, "READ_CONVERSION": 1}
+
class ADS1X1X_chip:
@@ -174,73 +174,67 @@ class ADS1X1X_chip:
self._reactor = self._printer.get_reactor()
self.name = config.get_name().split()[-1]
- self.chip = config.getchoice('chip', ADS1X1X_CHIP_TYPE)
- address = ADS1X1X_CHIP_ADDR['GND']
+ self.chip = config.getchoice("chip", ADS1X1X_CHIP_TYPE)
+ address = ADS1X1X_CHIP_ADDR["GND"]
# If none is specified, i2c_address can be used for a specific address
- if config.get('address_pin', None) is not None:
- address = config.getchoice('address_pin', ADS1X1X_CHIP_ADDR)
+ if config.get("address_pin", None) is not None:
+ address = config.getchoice("address_pin", ADS1X1X_CHIP_ADDR)
self._ppins = self._printer.lookup_object("pins")
self._ppins.register_chip(self.name, self)
- self.pga = config.getchoice('pga', ADS1X1X_PGA, '4.096V')
- self.adc_voltage = config.getfloat('adc_voltage', above=0., default=3.3)
+ self.pga = config.getchoice("pga", ADS1X1X_PGA, "4.096V")
+ self.adc_voltage = config.getfloat("adc_voltage", above=0.0, default=3.3)
# Comparators are not implemented, they would only be useful if the
# alert pin is used, which we haven't made configurable.
# But that wouldn't be useful for a normal temperature sensor anyway.
- self.comp_mode = ADS1X1X_COMPARATOR_MODE['TRADITIONAL']
- self.comp_polarity = ADS1X1X_COMPARATOR_POLARITY['ACTIVE_LO']
- self.comp_latching = ADS1X1X_COMPARATOR_LATCHING['NON_LATCHING']
- self.comp_queue = ADS1X1X_COMPARATOR_QUEUE['QUEUE_NONE']
+ self.comp_mode = ADS1X1X_COMPARATOR_MODE["TRADITIONAL"]
+ self.comp_polarity = ADS1X1X_COMPARATOR_POLARITY["ACTIVE_LO"]
+ self.comp_latching = ADS1X1X_COMPARATOR_LATCHING["NON_LATCHING"]
+ self.comp_queue = ADS1X1X_COMPARATOR_QUEUE["QUEUE_NONE"]
self._i2c = bus.MCU_I2C_from_config(config, address)
self.mcu = self._i2c.get_mcu()
self._printer.add_object("ads1x1x " + self.name, self)
- self._printer.register_event_handler("klippy:connect", \
- self._handle_connect)
+ self._printer.register_event_handler("klippy:connect", self._handle_connect)
self._pins = {}
self._mutex = self._reactor.mutex()
def setup_pin(self, pin_type, pin_params):
- pin = pin_params['pin']
- if pin_type == 'adc':
- if (pin not in ADS1X1X_MUX):
- raise pins.error('ADS1x1x pin %s is not valid' % \
- pin_params['pin'])
+ pin = pin_params["pin"]
+ if pin_type == "adc":
+ if pin not in ADS1X1X_MUX:
+ raise pins.error("ADS1x1x pin %s is not valid" % pin_params["pin"])
pcfg = 0
- pcfg |= (ADS1X1X_OS['OS_SINGLE'] & \
- ADS1X1X_REG_CONFIG['OS_MASK'])
- pcfg |= (ADS1X1X_MUX[pin_params['pin']] & \
- ADS1X1X_REG_CONFIG['MULTIPLEXER_MASK'])
- pcfg |= (self.pga & ADS1X1X_REG_CONFIG['PGA_MASK'])
+ pcfg |= ADS1X1X_OS["OS_SINGLE"] & ADS1X1X_REG_CONFIG["OS_MASK"]
+ pcfg |= (
+ ADS1X1X_MUX[pin_params["pin"]] & ADS1X1X_REG_CONFIG["MULTIPLEXER_MASK"]
+ )
+ pcfg |= self.pga & ADS1X1X_REG_CONFIG["PGA_MASK"]
# Have to use single mode, because in continuous, it never reaches
# idle state, which we use to determine if the sampling is done.
- pcfg |= (ADS1X1X_MODE['single'] & \
- ADS1X1X_REG_CONFIG['MODE_MASK'])
+ pcfg |= ADS1X1X_MODE["single"] & ADS1X1X_REG_CONFIG["MODE_MASK"]
# lowest sample rate per default, until report time has been set in
# setup_adc_sample
- pcfg |= (self.comp_mode \
- & ADS1X1X_REG_CONFIG['COMPARATOR_MODE_MASK'])
- pcfg |= (self.comp_polarity \
- & ADS1X1X_REG_CONFIG['COMPARATOR_POLARITY_MASK'])
- pcfg |= (self.comp_latching \
- & ADS1X1X_REG_CONFIG['COMPARATOR_LATCHING_MASK'])
- pcfg |= (self.comp_queue \
- & ADS1X1X_REG_CONFIG['COMPARATOR_QUEUE_MASK'])
+ pcfg |= self.comp_mode & ADS1X1X_REG_CONFIG["COMPARATOR_MODE_MASK"]
+ pcfg |= self.comp_polarity & ADS1X1X_REG_CONFIG["COMPARATOR_POLARITY_MASK"]
+ pcfg |= self.comp_latching & ADS1X1X_REG_CONFIG["COMPARATOR_LATCHING_MASK"]
+ pcfg |= self.comp_queue & ADS1X1X_REG_CONFIG["COMPARATOR_QUEUE_MASK"]
pin_obj = ADS1X1X_pin(self, pcfg)
if pin in self._pins:
raise pins.error(
- 'pin %s for chip %s is used multiple times' \
- % (pin, self.name))
+ "pin %s for chip %s is used multiple times" % (pin, self.name)
+ )
self._pins[pin] = pin_obj
return pin_obj
- raise pins.error('Wrong pin or incompatible type: %s with type %s! ' % (
- pin, pin_type))
+ raise pins.error(
+ "Wrong pin or incompatible type: %s with type %s! " % (pin, pin_type)
+ )
def _handle_connect(self):
try:
@@ -250,9 +244,8 @@ class ADS1X1X_chip:
logging.exception("ADS1X1X: error while resetting device")
def is_ready(self):
- cfg = self._read_register(ADS1X1X_REG_POINTER['CONFIG'])
- return bool((cfg & ADS1X1X_REG_CONFIG['OS_MASK']) == \
- ADS1X1X_OS['OS_IDLE'])
+ cfg = self._read_register(ADS1X1X_REG_POINTER["CONFIG"])
+ return bool((cfg & ADS1X1X_REG_CONFIG["OS_MASK"]) == ADS1X1X_OS["OS_IDLE"])
def calculate_sample_rate(self):
pin_count = len(self._pins)
@@ -266,30 +259,31 @@ class ADS1X1X_chip:
samples_per_second = ADS101X_SAMPLES_PER_SECOND
# make sure the samples list is sorted correctly by number.
- samples_per_second = sorted(samples_per_second.items(), \
- key=lambda t: int(t[0]))
+ samples_per_second = sorted(samples_per_second.items(), key=lambda t: int(t[0]))
for rate, bits in samples_per_second:
rate_number = int(rate)
if sample_rate <= rate_number:
return (rate_number, bits)
logging.warning(
- "ADS1X1X: requested sample rate %s is higher than supported by %s."\
- % (sample_rate, self.name))
+ "ADS1X1X: requested sample rate %s is higher than supported by %s."
+ % (sample_rate, self.name)
+ )
return (rate_number, bits)
def handle_report_time_update(self):
(sample_rate, sample_rate_bits) = self.calculate_sample_rate()
for pin in self._pins.values():
- pin.pcfg = (pin.pcfg & ~ADS1X1X_REG_CONFIG['DATA_RATE_MASK']) \
- | (sample_rate_bits & ADS1X1X_REG_CONFIG['DATA_RATE_MASK'])
+ pin.pcfg = (pin.pcfg & ~ADS1X1X_REG_CONFIG["DATA_RATE_MASK"]) | (
+ sample_rate_bits & ADS1X1X_REG_CONFIG["DATA_RATE_MASK"]
+ )
self.delay = 1 / float(sample_rate)
def sample(self, pin):
with self._mutex:
try:
- self._write_register(ADS1X1X_REG_POINTER['CONFIG'], pin.pcfg)
+ self._write_register(ADS1X1X_REG_POINTER["CONFIG"], pin.pcfg)
self._reactor.pause(self._reactor.monotonic() + self.delay)
start_time = self._reactor.monotonic()
while not self.is_ready():
@@ -298,7 +292,7 @@ class ADS1X1X_chip:
if start_time + self.delay < self._reactor.monotonic():
logging.warning("ADS1X1X: timeout during sampling")
return None
- return self._read_register(ADS1X1X_REG_POINTER['CONVERSION'])
+ return self._read_register(ADS1X1X_REG_POINTER["CONVERSION"])
except Exception as e:
logging.exception("ADS1X1X: error while sampling: %s" % str(e))
return None
@@ -306,17 +300,18 @@ class ADS1X1X_chip:
def _read_register(self, reg):
# read a single register
params = self._i2c.i2c_read([reg], 2)
- buff = bytearray(params['response'])
- return (buff[0]<<8 | buff[1])
+ buff = bytearray(params["response"])
+ return buff[0] << 8 | buff[1]
def _write_register(self, reg, data):
data = [
- (reg & 0xFF), # Control register
- ((data>>8) & 0xFF), # High byte
- (data & 0xFF), # Lo byte
+ (reg & 0xFF), # Control register
+ ((data >> 8) & 0xFF), # High byte
+ (data & 0xFF), # Lo byte
]
self._i2c.i2c_write(data)
+
class ADS1X1X_pin:
def __init__(self, chip, pcfg):
self.mcu = chip.mcu
@@ -325,14 +320,15 @@ class ADS1X1X_pin:
self.invalid_count = 0
- self.chip._printer.register_event_handler("klippy:connect", \
- self._handle_connect)
+ self.chip._printer.register_event_handler(
+ "klippy:connect", self._handle_connect
+ )
def _handle_connect(self):
self._reactor = self.chip._printer.get_reactor()
- self._sample_timer = \
- self._reactor.register_timer(self._process_sample, \
- self._reactor.NOW)
+ self._sample_timer = self._reactor.register_timer(
+ self._process_sample, self._reactor.NOW
+ )
def _process_sample(self, eventtime):
sample = self.chip.sample(self)
@@ -350,8 +346,9 @@ class ADS1X1X_pin:
# voltage divider is only 3.3V, not 4.096V. So we remap the range
# from what the PGA allows as range to end up between 0 and 1 for
# the thermistor logic to work as expected.
- target_value = target_value * (ADS1X1X_PGA_VALUE[self.chip.pga] / \
- self.chip.adc_voltage)
+ target_value = target_value * (
+ ADS1X1X_PGA_VALUE[self.chip.pga] / self.chip.adc_voltage
+ )
if target_value > self.maxval or target_value < self.minval:
self.invalid_count = self.invalid_count + 1
@@ -362,8 +359,9 @@ class ADS1X1X_pin:
# Publish result
measured_time = self._reactor.monotonic()
- self.callback(self.chip.mcu.estimated_print_time(measured_time),
- target_value)
+ self.callback(
+ self.chip.mcu.estimated_print_time(measured_time), target_value
+ )
else:
self.invalid_count = self.invalid_count + 1
self.check_invalid()
@@ -372,8 +370,7 @@ class ADS1X1X_pin:
def check_invalid(self):
if self.invalid_count > self.range_check_count:
- self.chip._printer.invoke_shutdown(
- "ADS1X1X temperature check failed")
+ self.chip._printer.invoke_shutdown("ADS1X1X temperature check failed")
def get_mcu(self):
return self.mcu
@@ -383,11 +380,13 @@ class ADS1X1X_pin:
self.callback = callback
self.chip.handle_report_time_update()
- def setup_adc_sample(self, sample_time, sample_count,
- minval=0., maxval=1., range_check_count=0):
+ def setup_adc_sample(
+ self, sample_time, sample_count, minval=0.0, maxval=1.0, range_check_count=0
+ ):
self.minval = minval
self.maxval = maxval
self.range_check_count = range_check_count
+
def load_config_prefix(config):
return ADS1X1X_chip(config)
diff --git a/klippy/extras/adxl345.py b/klippy/extras/adxl345.py
index c2d64a41..7870bd4a 100644
--- a/klippy/extras/adxl345.py
+++ b/klippy/extras/adxl345.py
@@ -16,34 +16,44 @@ REG_MOD_READ = 0x80
REG_MOD_MULTI = 0x40
QUERY_RATES = {
- 25: 0x8, 50: 0x9, 100: 0xa, 200: 0xb, 400: 0xc,
- 800: 0xd, 1600: 0xe, 3200: 0xf,
+ 25: 0x8,
+ 50: 0x9,
+ 100: 0xA,
+ 200: 0xB,
+ 400: 0xC,
+ 800: 0xD,
+ 1600: 0xE,
+ 3200: 0xF,
}
-ADXL345_DEV_ID = 0xe5
+ADXL345_DEV_ID = 0xE5
SET_FIFO_CTL = 0x90
-FREEFALL_ACCEL = 9.80665 * 1000.
-SCALE_XY = 0.003774 * FREEFALL_ACCEL # 1 / 265 (at 3.3V) mg/LSB
-SCALE_Z = 0.003906 * FREEFALL_ACCEL # 1 / 256 (at 3.3V) mg/LSB
+FREEFALL_ACCEL = 9.80665 * 1000.0
+SCALE_XY = 0.003774 * FREEFALL_ACCEL # 1 / 265 (at 3.3V) mg/LSB
+SCALE_Z = 0.003906 * FREEFALL_ACCEL # 1 / 256 (at 3.3V) mg/LSB
Accel_Measurement = collections.namedtuple(
- 'Accel_Measurement', ('time', 'accel_x', 'accel_y', 'accel_z'))
+ "Accel_Measurement", ("time", "accel_x", "accel_y", "accel_z")
+)
+
# Helper class to obtain measurements
class AccelQueryHelper:
def __init__(self, printer):
self.printer = printer
self.is_finished = False
- print_time = printer.lookup_object('toolhead').get_last_move_time()
+ print_time = printer.lookup_object("toolhead").get_last_move_time()
self.request_start_time = self.request_end_time = print_time
self.msgs = []
self.samples = []
+
def finish_measurements(self):
- toolhead = self.printer.lookup_object('toolhead')
+ toolhead = self.printer.lookup_object("toolhead")
self.request_end_time = toolhead.get_last_move_time()
toolhead.wait_moves()
self.is_finished = True
+
def handle_batch(self, msg):
if self.is_finished:
return False
@@ -52,13 +62,16 @@ class AccelQueryHelper:
return False
self.msgs.append(msg)
return True
+
def has_valid_samples(self):
for msg in self.msgs:
- data = msg['data']
+ data = msg["data"]
first_sample_time = data[0][0]
last_sample_time = data[-1][0]
- if (first_sample_time > self.request_end_time
- or last_sample_time < self.request_start_time):
+ if (
+ first_sample_time > self.request_end_time
+ or last_sample_time < self.request_start_time
+ ):
continue
# The time intervals [first_sample_time, last_sample_time]
# and [request_start_time, request_end_time] have non-zero
@@ -69,14 +82,15 @@ class AccelQueryHelper:
# is at least 1 second, so this possibility is negligible.
return True
return False
+
def get_samples(self):
if not self.msgs:
return self.samples
- total = sum([len(m['data']) for m in self.msgs])
+ total = sum([len(m["data"]) for m in self.msgs])
count = 0
self.samples = samples = [None] * total
for msg in self.msgs:
- for samp_time, x, y, z in msg['data']:
+ for samp_time, x, y, z in msg["data"]:
if samp_time < self.request_start_time:
continue
if samp_time > self.request_end_time:
@@ -85,6 +99,7 @@ class AccelQueryHelper:
count += 1
del samples[count:]
return self.samples
+
def write_to_file(self, filename):
def write_impl():
try:
@@ -96,13 +111,14 @@ class AccelQueryHelper:
f.write("#time,accel_x,accel_y,accel_z\n")
samples = self.samples or self.get_samples()
for t, accel_x, accel_y, accel_z in samples:
- f.write("%.6f,%.6f,%.6f,%.6f\n" % (
- t, accel_x, accel_y, accel_z))
+ f.write("%.6f,%.6f,%.6f,%.6f\n" % (t, accel_x, accel_y, accel_z))
f.close()
+
write_proc = multiprocessing.Process(target=write_impl)
write_proc.daemon = True
write_proc.start()
+
# Helper class for G-Code commands
class AccelCommandHelper:
def __init__(self, config, chip):
@@ -116,22 +132,41 @@ class AccelCommandHelper:
if len(name_parts) == 1:
if self.name == "adxl345" or not config.has_section("adxl345"):
self.register_commands(None)
+
def register_commands(self, name):
# Register commands
- gcode = self.printer.lookup_object('gcode')
- gcode.register_mux_command("ACCELEROMETER_MEASURE", "CHIP", name,
- self.cmd_ACCELEROMETER_MEASURE,
- desc=self.cmd_ACCELEROMETER_MEASURE_help)
- gcode.register_mux_command("ACCELEROMETER_QUERY", "CHIP", name,
- self.cmd_ACCELEROMETER_QUERY,
- desc=self.cmd_ACCELEROMETER_QUERY_help)
- gcode.register_mux_command("ACCELEROMETER_DEBUG_READ", "CHIP", name,
- self.cmd_ACCELEROMETER_DEBUG_READ,
- desc=self.cmd_ACCELEROMETER_DEBUG_READ_help)
- gcode.register_mux_command("ACCELEROMETER_DEBUG_WRITE", "CHIP", name,
- self.cmd_ACCELEROMETER_DEBUG_WRITE,
- desc=self.cmd_ACCELEROMETER_DEBUG_WRITE_help)
+ gcode = self.printer.lookup_object("gcode")
+ gcode.register_mux_command(
+ "ACCELEROMETER_MEASURE",
+ "CHIP",
+ name,
+ self.cmd_ACCELEROMETER_MEASURE,
+ desc=self.cmd_ACCELEROMETER_MEASURE_help,
+ )
+ gcode.register_mux_command(
+ "ACCELEROMETER_QUERY",
+ "CHIP",
+ name,
+ self.cmd_ACCELEROMETER_QUERY,
+ desc=self.cmd_ACCELEROMETER_QUERY_help,
+ )
+ gcode.register_mux_command(
+ "ACCELEROMETER_DEBUG_READ",
+ "CHIP",
+ name,
+ self.cmd_ACCELEROMETER_DEBUG_READ,
+ desc=self.cmd_ACCELEROMETER_DEBUG_READ_help,
+ )
+ gcode.register_mux_command(
+ "ACCELEROMETER_DEBUG_WRITE",
+ "CHIP",
+ name,
+ self.cmd_ACCELEROMETER_DEBUG_WRITE,
+ desc=self.cmd_ACCELEROMETER_DEBUG_WRITE_help,
+ )
+
cmd_ACCELEROMETER_MEASURE_help = "Start/stop accelerometer"
+
def cmd_ACCELEROMETER_MEASURE(self, gcmd):
if self.bg_client is None:
# Start measurements
@@ -140,7 +175,7 @@ class AccelCommandHelper:
return
# End measurements
name = gcmd.get("NAME", time.strftime("%Y%m%d_%H%M%S"))
- if not name.replace('-', '').replace('_', '').isalnum():
+ if not name.replace("-", "").replace("_", "").isalnum():
raise gcmd.error("Invalid NAME parameter")
bg_client = self.bg_client
self.bg_client = None
@@ -151,48 +186,64 @@ class AccelCommandHelper:
else:
filename = "/tmp/%s-%s-%s.csv" % (self.base_name, self.name, name)
bg_client.write_to_file(filename)
- gcmd.respond_info("Writing raw accelerometer data to %s file"
- % (filename,))
+ gcmd.respond_info("Writing raw accelerometer data to %s file" % (filename,))
+
cmd_ACCELEROMETER_QUERY_help = "Query accelerometer for the current values"
+
def cmd_ACCELEROMETER_QUERY(self, gcmd):
aclient = self.chip.start_internal_client()
- self.printer.lookup_object('toolhead').dwell(1.)
+ self.printer.lookup_object("toolhead").dwell(1.0)
aclient.finish_measurements()
values = aclient.get_samples()
if not values:
raise gcmd.error("No accelerometer measurements found")
_, accel_x, accel_y, accel_z = values[-1]
- gcmd.respond_info("accelerometer values (x, y, z): %.6f, %.6f, %.6f"
- % (accel_x, accel_y, accel_z))
+ gcmd.respond_info(
+ "accelerometer values (x, y, z): %.6f, %.6f, %.6f"
+ % (accel_x, accel_y, accel_z)
+ )
+
cmd_ACCELEROMETER_DEBUG_READ_help = "Query register (for debugging)"
+
def cmd_ACCELEROMETER_DEBUG_READ(self, gcmd):
reg = gcmd.get("REG", minval=0, maxval=127, parser=lambda x: int(x, 0))
val = self.chip.read_reg(reg)
gcmd.respond_info("Accelerometer REG[0x%x] = 0x%x" % (reg, val))
+
cmd_ACCELEROMETER_DEBUG_WRITE_help = "Set register (for debugging)"
+
def cmd_ACCELEROMETER_DEBUG_WRITE(self, gcmd):
reg = gcmd.get("REG", minval=0, maxval=127, parser=lambda x: int(x, 0))
val = gcmd.get("VAL", minval=0, maxval=255, parser=lambda x: int(x, 0))
self.chip.set_reg(reg, val)
+
# Helper to read the axes_map parameter from the config
def read_axes_map(config, scale_x, scale_y, scale_z):
- am = {'x': (0, scale_x), 'y': (1, scale_y), 'z': (2, scale_z),
- '-x': (0, -scale_x), '-y': (1, -scale_y), '-z': (2, -scale_z)}
- axes_map = config.getlist('axes_map', ('x','y','z'), count=3)
+ am = {
+ "x": (0, scale_x),
+ "y": (1, scale_y),
+ "z": (2, scale_z),
+ "-x": (0, -scale_x),
+ "-y": (1, -scale_y),
+ "-z": (2, -scale_z),
+ }
+ axes_map = config.getlist("axes_map", ("x", "y", "z"), count=3)
if any([a not in am for a in axes_map]):
raise config.error("Invalid axes_map parameter")
return [am[a.strip()] for a in axes_map]
+
BATCH_UPDATES = 0.100
+
# Printer class that controls ADXL345 chip
class ADXL345:
def __init__(self, config):
self.printer = config.get_printer()
AccelCommandHelper(config, self)
self.axes_map = read_axes_map(config, SCALE_XY, SCALE_XY, SCALE_Z)
- self.data_rate = config.getint('rate', 3200)
+ self.data_rate = config.getint("rate", 3200)
if self.data_rate not in QUERY_RATES:
raise config.error("Invalid rate parameter: %d" % (self.data_rate,))
# Setup mcu sensor_adxl345 bulk query code
@@ -200,10 +251,12 @@ class ADXL345:
self.mcu = mcu = self.spi.get_mcu()
self.oid = oid = mcu.create_oid()
self.query_adxl345_cmd = None
- mcu.add_config_cmd("config_adxl345 oid=%d spi_oid=%d"
- % (oid, self.spi.get_oid()))
- mcu.add_config_cmd("query_adxl345 oid=%d rest_ticks=0"
- % (oid,), on_restart=True)
+ mcu.add_config_cmd(
+ "config_adxl345 oid=%d spi_oid=%d" % (oid, self.spi.get_oid())
+ )
+ mcu.add_config_cmd(
+ "query_adxl345 oid=%d rest_ticks=0" % (oid,), on_restart=True
+ )
mcu.register_config_callback(self._build_config)
# Bulk sample message reading
chip_smooth = self.data_rate * BATCH_UPDATES * 2
@@ -211,35 +264,48 @@ class ADXL345:
self.last_error_count = 0
# Process messages in batches
self.batch_bulk = bulk_sensor.BatchBulkHelper(
- self.printer, self._process_batch,
- self._start_measurements, self._finish_measurements, BATCH_UPDATES)
+ self.printer,
+ self._process_batch,
+ self._start_measurements,
+ self._finish_measurements,
+ BATCH_UPDATES,
+ )
self.name = config.get_name().split()[-1]
- hdr = ('time', 'x_acceleration', 'y_acceleration', 'z_acceleration')
- self.batch_bulk.add_mux_endpoint("adxl345/dump_adxl345", "sensor",
- self.name, {'header': hdr})
+ hdr = ("time", "x_acceleration", "y_acceleration", "z_acceleration")
+ self.batch_bulk.add_mux_endpoint(
+ "adxl345/dump_adxl345", "sensor", self.name, {"header": hdr}
+ )
+
def _build_config(self):
cmdqueue = self.spi.get_command_queue()
self.query_adxl345_cmd = self.mcu.lookup_command(
- "query_adxl345 oid=%c rest_ticks=%u", cq=cmdqueue)
- self.ffreader.setup_query_command("query_adxl345_status oid=%c",
- oid=self.oid, cq=cmdqueue)
+ "query_adxl345 oid=%c rest_ticks=%u", cq=cmdqueue
+ )
+ self.ffreader.setup_query_command(
+ "query_adxl345_status oid=%c", oid=self.oid, cq=cmdqueue
+ )
+
def read_reg(self, reg):
params = self.spi.spi_transfer([reg | REG_MOD_READ, 0x00])
- response = bytearray(params['response'])
+ response = bytearray(params["response"])
return response[1]
+
def set_reg(self, reg, val, minclock=0):
self.spi.spi_send([reg, val & 0xFF], minclock=minclock)
stored_val = self.read_reg(reg)
if stored_val != val:
raise self.printer.command_error(
- "Failed to set ADXL345 register [0x%x] to 0x%x: got 0x%x. "
- "This is generally indicative of connection problems "
- "(e.g. faulty wiring) or a faulty adxl345 chip." % (
- reg, val, stored_val))
+ "Failed to set ADXL345 register [0x%x] to 0x%x: got 0x%x. "
+ "This is generally indicative of connection problems "
+ "(e.g. faulty wiring) or a faulty adxl345 chip."
+ % (reg, val, stored_val)
+ )
+
def start_internal_client(self):
aqh = AccelQueryHelper(self.printer)
self.batch_bulk.add_client(aqh.handle_batch)
return aqh
+
# Measurement decoding
def _convert_samples(self, samples):
(x_pos, x_scale), (y_pos, y_scale), (z_pos, z_scale) = self.axes_map
@@ -248,10 +314,11 @@ class ADXL345:
if yzhigh & 0x80:
self.last_error_count += 1
continue
- rx = (xlow | ((xzhigh & 0x1f) << 8)) - ((xzhigh & 0x10) << 9)
- ry = (ylow | ((yzhigh & 0x1f) << 8)) - ((yzhigh & 0x10) << 9)
- rz = ((zlow | ((xzhigh & 0xe0) << 3) | ((yzhigh & 0xe0) << 6))
- - ((yzhigh & 0x40) << 7))
+ rx = (xlow | ((xzhigh & 0x1F) << 8)) - ((xzhigh & 0x10) << 9)
+ ry = (ylow | ((yzhigh & 0x1F) << 8)) - ((yzhigh & 0x10) << 9)
+ rz = (zlow | ((xzhigh & 0xE0) << 3) | ((yzhigh & 0xE0) << 6)) - (
+ (yzhigh & 0x40) << 7
+ )
raw_xyz = (rx, ry, rz)
x = round(raw_xyz[x_pos] * x_scale, 6)
y = round(raw_xyz[y_pos] * y_scale, 6)
@@ -259,6 +326,7 @@ class ADXL345:
samples[count] = (round(ptime, 6), x, y, z)
count += 1
del samples[count:]
+
# Start, stop, and process message batches
def _start_measurements(self):
# In case of miswiring, testing ADXL345 device ID prevents treating
@@ -269,7 +337,8 @@ class ADXL345:
"Invalid adxl345 id (got %x vs %x).\n"
"This is generally indicative of connection problems\n"
"(e.g. faulty wiring) or a faulty adxl345 chip."
- % (dev_id, ADXL345_DEV_ID))
+ % (dev_id, ADXL345_DEV_ID)
+ )
# Setup chip in requested query rate
self.set_reg(REG_POWER_CTL, 0x00)
self.set_reg(REG_DATA_FORMAT, 0x0B)
@@ -277,29 +346,36 @@ class ADXL345:
self.set_reg(REG_BW_RATE, QUERY_RATES[self.data_rate])
self.set_reg(REG_FIFO_CTL, SET_FIFO_CTL)
# Start bulk reading
- rest_ticks = self.mcu.seconds_to_clock(4. / self.data_rate)
+ rest_ticks = self.mcu.seconds_to_clock(4.0 / self.data_rate)
self.query_adxl345_cmd.send([self.oid, rest_ticks])
self.set_reg(REG_POWER_CTL, 0x08)
logging.info("ADXL345 starting '%s' measurements", self.name)
# Initialize clock tracking
self.ffreader.note_start()
self.last_error_count = 0
+
def _finish_measurements(self):
# Halt bulk reading
self.set_reg(REG_POWER_CTL, 0x00)
self.query_adxl345_cmd.send_wait_ack([self.oid, 0])
self.ffreader.note_end()
logging.info("ADXL345 finished '%s' measurements", self.name)
+
def _process_batch(self, eventtime):
samples = self.ffreader.pull_samples()
self._convert_samples(samples)
if not samples:
return {}
- return {'data': samples, 'errors': self.last_error_count,
- 'overflows': self.ffreader.get_last_overflows()}
+ return {
+ "data": samples,
+ "errors": self.last_error_count,
+ "overflows": self.ffreader.get_last_overflows(),
+ }
+
def load_config(config):
return ADXL345(config)
+
def load_config_prefix(config):
return ADXL345(config)
diff --git a/klippy/extras/aht10.py b/klippy/extras/aht10.py
index 001f7e54..f680004d 100644
--- a/klippy/extras/aht10.py
+++ b/klippy/extras/aht10.py
@@ -13,15 +13,16 @@ from . import bus
# AHT21 - Tested w/ BTT GTR 1.0 MCU on i2c3
######################################################################
-AHT10_I2C_ADDR= 0x38
+AHT10_I2C_ADDR = 0x38
AHT10_COMMANDS = {
- 'INIT' :[0xE1, 0x08, 0x00],
- 'MEASURE' :[0xAC, 0x33, 0x00],
- 'RESET' :[0xBA, 0x08, 0x00]
+ "INIT": [0xE1, 0x08, 0x00],
+ "MEASURE": [0xAC, 0x33, 0x00],
+ "RESET": [0xBA, 0x08, 0x00],
}
-AHT10_MAX_BUSY_CYCLES= 5
+AHT10_MAX_BUSY_CYCLES = 5
+
class AHT10:
def __init__(self, config):
@@ -29,14 +30,14 @@ class AHT10:
self.name = config.get_name().split()[-1]
self.reactor = self.printer.get_reactor()
self.i2c = bus.MCU_I2C_from_config(
- config, default_addr=AHT10_I2C_ADDR, default_speed=100000)
- self.report_time = config.getint('aht10_report_time',30,minval=5)
- self.temp = self.min_temp = self.max_temp = self.humidity = 0.
+ config, default_addr=AHT10_I2C_ADDR, default_speed=100000
+ )
+ self.report_time = config.getint("aht10_report_time", 30, minval=5)
+ self.temp = self.min_temp = self.max_temp = self.humidity = 0.0
self.sample_timer = self.reactor.register_timer(self._sample_aht10)
self.printer.add_object("aht10 " + self.name, self)
- self.printer.register_event_handler("klippy:connect",
- self.handle_connect)
- self.is_calibrated = False
+ self.printer.register_event_handler("klippy:connect", self.handle_connect)
+ self.is_calibrated = False
self.init_sent = False
def handle_connect(self):
@@ -67,28 +68,31 @@ class AHT10:
# Check if we're constantly busy. If so, send soft-reset
# and issue warning.
if is_busy and cycles > AHT10_MAX_BUSY_CYCLES:
- logging.warning("aht10: device reported busy after " +
- "%d cycles, resetting device"% AHT10_MAX_BUSY_CYCLES)
+ logging.warning(
+ "aht10: device reported busy after "
+ + "%d cycles, resetting device" % AHT10_MAX_BUSY_CYCLES
+ )
self._reset_device()
data = None
break
cycles += 1
# Write command for updating temperature+status bit
- self.i2c.i2c_write(AHT10_COMMANDS['MEASURE'])
+ self.i2c.i2c_write(AHT10_COMMANDS["MEASURE"])
# Wait 110ms after first read, 75ms minimum
- self.reactor.pause(self.reactor.monotonic() + .110)
+ self.reactor.pause(self.reactor.monotonic() + 0.110)
# Read data
read = self.i2c.i2c_read([], 6)
if read is None:
- logging.warning("aht10: received data from" +
- " i2c_read is None")
+ logging.warning("aht10: received data from" + " i2c_read is None")
continue
- data = bytearray(read['response'])
+ data = bytearray(read["response"])
if len(data) < 6:
- logging.warning("aht10: received bytes less than" +
- " expected 6 [%d]"%len(data))
+ logging.warning(
+ "aht10: received bytes less than"
+ + " expected 6 [%d]" % len(data)
+ )
continue
self.is_calibrated = True if (data[0] & 0b00000100) else False
@@ -97,19 +101,20 @@ class AHT10:
if is_busy:
return False
except Exception as e:
- logging.exception("aht10: exception encountered" +
- " reading data: %s"%str(e))
+ logging.exception(
+ "aht10: exception encountered" + " reading data: %s" % str(e)
+ )
return False
temp = ((data[3] & 0x0F) << 16) | (data[4] << 8) | data[5]
- self.temp = ((temp*200) / 1048576) - 50
+ self.temp = ((temp * 200) / 1048576) - 50
hum = ((data[1] << 16) | (data[2] << 8) | data[3]) >> 4
self.humidity = int(hum * 100 / 1048576)
# Clamp humidity
- if (self.humidity > 100):
+ if self.humidity > 100:
self.humidity = 100
- elif (self.humidity < 0):
+ elif self.humidity < 0:
self.humidity = 0
return True
@@ -119,30 +124,33 @@ class AHT10:
return
# Reset device
- self.i2c.i2c_write(AHT10_COMMANDS['RESET'])
+ self.i2c.i2c_write(AHT10_COMMANDS["RESET"])
# Wait 100ms after reset
- self.reactor.pause(self.reactor.monotonic() + .10)
+ self.reactor.pause(self.reactor.monotonic() + 0.10)
def _init_aht10(self):
# Init device
- self.i2c.i2c_write(AHT10_COMMANDS['INIT'])
+ self.i2c.i2c_write(AHT10_COMMANDS["INIT"])
# Wait 100ms after init
- self.reactor.pause(self.reactor.monotonic() + .10)
+ self.reactor.pause(self.reactor.monotonic() + 0.10)
self.init_sent = True
if self._make_measurement():
- logging.info("aht10: successfully initialized, initial temp: " +
- "%.3f, humidity: %.3f"%(self.temp, self.humidity))
+ logging.info(
+ "aht10: successfully initialized, initial temp: "
+ + "%.3f, humidity: %.3f" % (self.temp, self.humidity)
+ )
def _sample_aht10(self, eventtime):
if not self._make_measurement():
- self.temp = self.humidity = .0
+ self.temp = self.humidity = 0.0
return self.reactor.NEVER
if self.temp < self.min_temp or self.temp > self.max_temp:
self.printer.invoke_shutdown(
"AHT10 temperature %0.1f outside range of %0.1f:%.01f"
- % (self.temp, self.min_temp, self.max_temp))
+ % (self.temp, self.min_temp, self.max_temp)
+ )
measured_time = self.reactor.monotonic()
print_time = self.i2c.get_mcu().estimated_print_time(measured_time)
@@ -151,8 +159,8 @@ class AHT10:
def get_status(self, eventtime):
return {
- 'temperature': round(self.temp, 2),
- 'humidity': self.humidity,
+ "temperature": round(self.temp, 2),
+ "humidity": self.humidity,
}
diff --git a/klippy/extras/angle.py b/klippy/extras/angle.py
index 73af67ca..f2799543 100644
--- a/klippy/extras/angle.py
+++ b/klippy/extras/angle.py
@@ -7,19 +7,19 @@ import logging, math
from . import bus, bulk_sensor
MIN_MSG_TIME = 0.100
-TCODE_ERROR = 0xff
+TCODE_ERROR = 0xFF
-TRINAMIC_DRIVERS = ["tmc2130", "tmc2208", "tmc2209", "tmc2240", "tmc2660",
- "tmc5160"]
+TRINAMIC_DRIVERS = ["tmc2130", "tmc2208", "tmc2209", "tmc2240", "tmc2660", "tmc5160"]
+
+CALIBRATION_BITS = 6 # 64 entries
+ANGLE_BITS = 16 # angles range from 0..65535
-CALIBRATION_BITS = 6 # 64 entries
-ANGLE_BITS = 16 # angles range from 0..65535
class AngleCalibration:
def __init__(self, config):
self.printer = config.get_printer()
self.name = config.get_name()
- self.stepper_name = config.get('stepper', None)
+ self.stepper_name = config.get("stepper", None)
if self.stepper_name is None:
# No calibration
return
@@ -28,30 +28,37 @@ class AngleCalibration:
except:
raise config.error("Angle calibration requires numpy module")
sconfig = config.getsection(self.stepper_name)
- sconfig.getint('microsteps', note_valid=False)
+ sconfig.getint("microsteps", note_valid=False)
self.tmc_module = self.mcu_stepper = None
# Current calibration data
self.mcu_pos_offset = None
- self.angle_phase_offset = 0.
+ self.angle_phase_offset = 0.0
self.calibration_reversed = False
self.calibration = []
- cal = config.get('calibrate', None)
+ cal = config.get("calibrate", None)
if cal is not None:
- data = [d.strip() for d in cal.split(',')]
+ data = [d.strip() for d in cal.split(",")]
angles = [float(d) for d in data if d]
self.load_calibration(angles)
# Register commands
- self.printer.register_event_handler("stepper:sync_mcu_position",
- self.handle_sync_mcu_pos)
+ self.printer.register_event_handler(
+ "stepper:sync_mcu_position", self.handle_sync_mcu_pos
+ )
self.printer.register_event_handler("klippy:connect", self.connect)
cname = self.name.split()[-1]
- gcode = self.printer.lookup_object('gcode')
- gcode.register_mux_command("ANGLE_CALIBRATE", "CHIP",
- cname, self.cmd_ANGLE_CALIBRATE,
- desc=self.cmd_ANGLE_CALIBRATE_help)
+ gcode = self.printer.lookup_object("gcode")
+ gcode.register_mux_command(
+ "ANGLE_CALIBRATE",
+ "CHIP",
+ cname,
+ self.cmd_ANGLE_CALIBRATE,
+ desc=self.cmd_ANGLE_CALIBRATE_help,
+ )
+
def handle_sync_mcu_pos(self, mcu_stepper):
if mcu_stepper.get_name() == self.stepper_name:
self.mcu_pos_offset = None
+
def calc_mcu_pos_offset(self, sample):
# Lookup phase information
mcu_phase_offset, phases = self.tmc_module.get_phase_offset()
@@ -62,15 +69,18 @@ class AngleCalibration:
mcu_pos = self.mcu_stepper.get_past_mcu_position(angle_time)
# Convert angle_pos to mcu_pos units
microsteps, full_steps = self.get_microsteps()
- angle_to_mcu_pos = full_steps * microsteps / float(1<<ANGLE_BITS)
+ angle_to_mcu_pos = full_steps * microsteps / float(1 << ANGLE_BITS)
angle_mpos = angle_pos * angle_to_mcu_pos
# Calculate adjustment for stepper phases
- phase_diff = ((angle_mpos + self.angle_phase_offset * angle_to_mcu_pos)
- - (mcu_pos + mcu_phase_offset)) % phases
- if phase_diff > phases//2:
+ phase_diff = (
+ (angle_mpos + self.angle_phase_offset * angle_to_mcu_pos)
+ - (mcu_pos + mcu_phase_offset)
+ ) % phases
+ if phase_diff > phases // 2:
phase_diff -= phases
# Store final offset
self.mcu_pos_offset = mcu_pos - (angle_mpos - phase_diff)
+
def apply_calibration(self, samples):
calibration = self.calibration
if not calibration:
@@ -80,12 +90,12 @@ class AngleCalibration:
interp_mask = (1 << interp_bits) - 1
interp_round = 1 << (interp_bits - 1)
for i, (samp_time, angle) in enumerate(samples):
- bucket = (angle & 0xffff) >> interp_bits
+ bucket = (angle & 0xFFFF) >> interp_bits
cal1 = calibration[bucket]
cal2 = calibration[bucket + 1]
adj = (angle & interp_mask) * (cal2 - cal1)
adj = cal1 + ((adj + interp_round) >> interp_bits)
- angle_diff = (adj - angle) & 0xffff
+ angle_diff = (adj - angle) & 0xFFFF
angle_diff -= (angle_diff & 0x8000) << 1
new_angle = angle + angle_diff
if calibration_reversed:
@@ -96,6 +106,7 @@ class AngleCalibration:
if self.mcu_pos_offset is None:
return None
return self.mcu_stepper.mcu_to_commanded_position(self.mcu_pos_offset)
+
def load_calibration(self, angles):
# Calculate linear interpolation calibration buckets by solving
# linear equations
@@ -111,59 +122,69 @@ class AngleCalibration:
first_step = angles.index(min(angles))
angles = angles[first_step:] + angles[:first_step]
import numpy
+
eqs = numpy.zeros((full_steps, calibration_count))
ans = numpy.zeros((full_steps,))
for step, angle in enumerate(angles):
- int_angle = int(angle + .5) % angle_max
+ int_angle = int(angle + 0.5) % angle_max
bucket = int(int_angle / bucket_size)
bucket_start = bucket * bucket_size
ang_diff = angle - bucket_start
ang_diff_per = ang_diff / bucket_size
eq = eqs[step]
- eq[bucket] = 1. - ang_diff_per
+ eq[bucket] = 1.0 - ang_diff_per
eq[(bucket + 1) % calibration_count] = ang_diff_per
ans[step] = float(step * nominal_step)
if bucket + 1 >= calibration_count:
ans[step] -= ang_diff_per * angle_max
sol = numpy.linalg.lstsq(eqs, ans, rcond=None)[0]
- isol = [int(s + .5) for s in sol]
+ isol = [int(s + 0.5) for s in sol]
self.calibration = isol + [isol[0] + angle_max]
+
def lookup_tmc(self):
for driver in TRINAMIC_DRIVERS:
driver_name = "%s %s" % (driver, self.stepper_name)
module = self.printer.lookup_object(driver_name, None)
if module is not None:
return module
- raise self.printer.command_error("Unable to find TMC driver for %s"
- % (self.stepper_name,))
+ raise self.printer.command_error(
+ "Unable to find TMC driver for %s" % (self.stepper_name,)
+ )
+
def connect(self):
self.tmc_module = self.lookup_tmc()
- fmove = self.printer.lookup_object('force_move')
+ fmove = self.printer.lookup_object("force_move")
self.mcu_stepper = fmove.lookup_stepper(self.stepper_name)
+
def get_microsteps(self):
- configfile = self.printer.lookup_object('configfile')
- sconfig = configfile.get_status(None)['settings']
+ configfile = self.printer.lookup_object("configfile")
+ sconfig = configfile.get_status(None)["settings"]
stconfig = sconfig.get(self.stepper_name, {})
- microsteps = stconfig['microsteps']
- full_steps = stconfig['full_steps_per_rotation']
+ microsteps = stconfig["microsteps"]
+ full_steps = stconfig["full_steps_per_rotation"]
return microsteps, full_steps
+
def get_stepper_phase(self):
mcu_phase_offset, phases = self.tmc_module.get_phase_offset()
if mcu_phase_offset is None:
- raise self.printer.command_error("Driver phase not known for %s"
- % (self.stepper_name,))
+ raise self.printer.command_error(
+ "Driver phase not known for %s" % (self.stepper_name,)
+ )
mcu_pos = self.mcu_stepper.get_mcu_position()
return (mcu_pos + mcu_phase_offset) % phases
+
def do_calibration_moves(self):
- move = self.printer.lookup_object('force_move').manual_move
+ move = self.printer.lookup_object("force_move").manual_move
# Start data collection
msgs = []
is_finished = False
+
def handle_batch(msg):
if is_finished:
return False
msgs.append(msg)
return True
+
self.printer.lookup_object(self.name).add_client(handle_batch)
# Move stepper several turns (to allow internal sensor calibration)
microsteps, full_steps = self.get_microsteps()
@@ -174,12 +195,12 @@ class AngleCalibration:
align_dist = step_dist * self.get_stepper_phase()
move_time = 0.010
move_speed = full_step_dist / move_time
- move(mcu_stepper, -(rotation_dist+align_dist), move_speed)
- move(mcu_stepper, 2. * rotation_dist, move_speed)
- move(mcu_stepper, -2. * rotation_dist, move_speed)
- move(mcu_stepper, .5 * rotation_dist - full_step_dist, move_speed)
+ move(mcu_stepper, -(rotation_dist + align_dist), move_speed)
+ move(mcu_stepper, 2.0 * rotation_dist, move_speed)
+ move(mcu_stepper, -2.0 * rotation_dist, move_speed)
+ move(mcu_stepper, 0.5 * rotation_dist - full_step_dist, move_speed)
# Move to each full step position
- toolhead = self.printer.lookup_object('toolhead')
+ toolhead = self.printer.lookup_object("toolhead")
times = []
samp_dist = full_step_dist
for i in range(2 * full_steps):
@@ -188,12 +209,12 @@ class AngleCalibration:
end_query_time = start_query_time + 0.050
times.append((start_query_time, end_query_time))
toolhead.dwell(0.150)
- if i == full_steps-1:
+ if i == full_steps - 1:
# Reverse direction and test each full step again
- move(mcu_stepper, .5 * rotation_dist, move_speed)
- move(mcu_stepper, -.5 * rotation_dist + samp_dist, move_speed)
+ move(mcu_stepper, 0.5 * rotation_dist, move_speed)
+ move(mcu_stepper, -0.5 * rotation_dist + samp_dist, move_speed)
samp_dist = -samp_dist
- move(mcu_stepper, .5*rotation_dist + align_dist, move_speed)
+ move(mcu_stepper, 0.5 * rotation_dist + align_dist, move_speed)
toolhead.wait_moves()
# Finish data collection
is_finished = True
@@ -201,7 +222,7 @@ class AngleCalibration:
cal = {}
step = 0
for msg in msgs:
- for query_time, pos in msg['data']:
+ for query_time, pos in msg["data"]:
# Add to step tracking
while step < len(times) and query_time > times[step][1]:
step += 1
@@ -209,10 +230,12 @@ class AngleCalibration:
cal.setdefault(step, []).append(pos)
if len(cal) != len(times):
raise self.printer.command_error(
- "Failed calibration - incomplete sensor data")
- fcal = { i: cal[i] for i in range(full_steps) }
- rcal = { full_steps-i-1: cal[i+full_steps] for i in range(full_steps) }
+ "Failed calibration - incomplete sensor data"
+ )
+ fcal = {i: cal[i] for i in range(full_steps)}
+ rcal = {full_steps - i - 1: cal[i + full_steps] for i in range(full_steps)}
return fcal, rcal
+
def calc_angles(self, meas):
total_count = total_variance = 0
angles = {}
@@ -221,9 +244,11 @@ class AngleCalibration:
angle_avg = float(sum(data)) / count
angles[step] = angle_avg
total_count += count
- total_variance += sum([(d - angle_avg)**2 for d in data])
+ total_variance += sum([(d - angle_avg) ** 2 for d in data])
return angles, math.sqrt(total_variance / total_count), total_count
+
cmd_ANGLE_CALIBRATE_help = "Calibrate angle sensor to stepper motor"
+
def cmd_ANGLE_CALIBRATE(self, gcmd):
# Perform calibration movement and capture
old_calibration = self.calibration
@@ -236,16 +261,20 @@ class AngleCalibration:
microsteps, full_steps = self.get_microsteps()
fangles, fstd, ftotal = self.calc_angles(fcal)
rangles, rstd, rtotal = self.calc_angles(rcal)
- if (len({a: i for i, a in fangles.items()}) != len(fangles)
- or len({a: i for i, a in rangles.items()}) != len(rangles)):
+ if len({a: i for i, a in fangles.items()}) != len(fangles) or len(
+ {a: i for i, a in rangles.items()}
+ ) != len(rangles):
raise self.printer.command_error(
- "Failed calibration - sensor not updating for each step")
- merged = { i: fcal[i] + rcal[i] for i in range(full_steps) }
+ "Failed calibration - sensor not updating for each step"
+ )
+ merged = {i: fcal[i] + rcal[i] for i in range(full_steps)}
angles, std, total = self.calc_angles(merged)
- gcmd.respond_info("angle: stddev=%.3f (%.3f forward / %.3f reverse)"
- " in %d queries" % (std, fstd, rstd, total))
+ gcmd.respond_info(
+ "angle: stddev=%.3f (%.3f forward / %.3f reverse)"
+ " in %d queries" % (std, fstd, rstd, total)
+ )
# Order data with lowest/highest magnet position first
- anglist = [angles[i] % 0xffff for i in range(full_steps)]
+ anglist = [angles[i] % 0xFFFF for i in range(full_steps)]
if angles[0] > angles[1]:
first_ang = max(anglist)
else:
@@ -256,45 +285,55 @@ class AngleCalibration:
cal_contents = []
for i, angle in enumerate(anglist):
if not i % 8:
- cal_contents.append('\n')
+ cal_contents.append("\n")
cal_contents.append("%.1f" % (angle,))
- cal_contents.append(',')
+ cal_contents.append(",")
cal_contents.pop()
- configfile = self.printer.lookup_object('configfile')
+ configfile = self.printer.lookup_object("configfile")
configfile.remove_section(self.name)
- configfile.set(self.name, 'calibrate', ''.join(cal_contents))
+ configfile.set(self.name, "calibrate", "".join(cal_contents))
+
class HelperA1333:
SPI_MODE = 3
SPI_SPEED = 10000000
+
def __init__(self, config, spi, oid):
self.spi = spi
self.is_tcode_absolute = False
self.last_temperature = None
+
def get_static_delay(self):
- return .000001
+ return 0.000001
+
def start(self):
# Setup for angle query
self.spi.spi_transfer([0x32, 0x00])
+
class HelperAS5047D:
SPI_MODE = 1
- SPI_SPEED = int(1. / .000000350)
+ SPI_SPEED = int(1.0 / 0.000000350)
+
def __init__(self, config, spi, oid):
self.spi = spi
self.is_tcode_absolute = False
self.last_temperature = None
+
def get_static_delay(self):
- return .000100
+ return 0.000100
+
def start(self):
# Clear any errors from device
- self.spi.spi_transfer([0xff, 0xfc]) # Read DIAAGC
- self.spi.spi_transfer([0x40, 0x01]) # Read ERRFL
- self.spi.spi_transfer([0xc0, 0x00]) # Read NOP
+ self.spi.spi_transfer([0xFF, 0xFC]) # Read DIAAGC
+ self.spi.spi_transfer([0x40, 0x01]) # Read ERRFL
+ self.spi.spi_transfer([0xC0, 0x00]) # Read NOP
+
class HelperTLE5012B:
SPI_MODE = 1
SPI_SPEED = 4000000
+
def __init__(self, config, spi, oid):
self.printer = config.get_printer()
self.spi = spi
@@ -305,115 +344,140 @@ class HelperTLE5012B:
self.mcu.register_config_callback(self._build_config)
self.spi_angle_transfer_cmd = None
self.last_chip_mcu_clock = self.last_chip_clock = 0
- self.chip_freq = 0.
+ self.chip_freq = 0.0
name = config.get_name().split()[-1]
gcode = self.printer.lookup_object("gcode")
- gcode.register_mux_command("ANGLE_DEBUG_READ", "CHIP", name,
- self.cmd_ANGLE_DEBUG_READ,
- desc=self.cmd_ANGLE_DEBUG_READ_help)
- gcode.register_mux_command("ANGLE_DEBUG_WRITE", "CHIP", name,
- self.cmd_ANGLE_DEBUG_WRITE,
- desc=self.cmd_ANGLE_DEBUG_WRITE_help)
+ gcode.register_mux_command(
+ "ANGLE_DEBUG_READ",
+ "CHIP",
+ name,
+ self.cmd_ANGLE_DEBUG_READ,
+ desc=self.cmd_ANGLE_DEBUG_READ_help,
+ )
+ gcode.register_mux_command(
+ "ANGLE_DEBUG_WRITE",
+ "CHIP",
+ name,
+ self.cmd_ANGLE_DEBUG_WRITE,
+ desc=self.cmd_ANGLE_DEBUG_WRITE_help,
+ )
+
def _build_config(self):
cmdqueue = self.spi.get_command_queue()
self.spi_angle_transfer_cmd = self.mcu.lookup_query_command(
"spi_angle_transfer oid=%c data=%*s",
"spi_angle_transfer_response oid=%c clock=%u response=%*s",
- oid=self.oid, cq=cmdqueue)
+ oid=self.oid,
+ cq=cmdqueue,
+ )
+
def get_tcode_params(self):
return self.last_chip_mcu_clock, self.last_chip_clock, self.chip_freq
+
def _calc_crc(self, data):
- crc = 0xff
+ crc = 0xFF
for d in data:
crc ^= d
for i in range(8):
if crc & 0x80:
- crc = (crc << 1) ^ 0x1d
+ crc = (crc << 1) ^ 0x1D
else:
crc <<= 1
- return (~crc) & 0xff
+ return (~crc) & 0xFF
+
def _send_spi(self, msg):
for retry in range(5):
if msg[0] & 0x04:
params = self.spi_angle_transfer_cmd.send([self.oid, msg])
else:
params = self.spi.spi_transfer(msg)
- resp = bytearray(params['response'])
+ resp = bytearray(params["response"])
crc = self._calc_crc(bytearray(msg[:2]) + resp[2:-2])
if crc == resp[-1]:
return params
raise self.printer.command_error("Unable to query tle5012b chip")
+
def _read_reg(self, reg):
- cw = 0x8000 | ((reg & 0x3f) << 4) | 0x01
+ cw = 0x8000 | ((reg & 0x3F) << 4) | 0x01
if reg >= 0x05 and reg <= 0x11:
cw |= 0x5000
- msg = [cw >> 8, cw & 0xff, 0, 0, 0, 0]
+ msg = [cw >> 8, cw & 0xFF, 0, 0, 0, 0]
params = self._send_spi(msg)
- resp = bytearray(params['response'])
+ resp = bytearray(params["response"])
return (resp[2] << 8) | resp[3]
+
def _write_reg(self, reg, val):
- cw = ((reg & 0x3f) << 4) | 0x01
+ cw = ((reg & 0x3F) << 4) | 0x01
if reg >= 0x05 and reg <= 0x11:
cw |= 0x5000
- msg = [cw >> 8, cw & 0xff, (val >> 8) & 0xff, val & 0xff, 0, 0]
+ msg = [cw >> 8, cw & 0xFF, (val >> 8) & 0xFF, val & 0xFF, 0, 0]
for retry in range(5):
self._send_spi(msg)
rval = self._read_reg(reg)
if rval == val:
return
raise self.printer.command_error("Unable to write to tle5012b chip")
+
def _mask_reg(self, reg, off, on):
rval = self._read_reg(reg)
self._write_reg(reg, (rval & ~off) | on)
+
def _query_clock(self):
# Read frame counter (and normalize to a 16bit counter)
- msg = [0x84, 0x42, 0, 0, 0, 0, 0, 0] # Read with latch, AREV and FSYNC
+ msg = [0x84, 0x42, 0, 0, 0, 0, 0, 0] # Read with latch, AREV and FSYNC
params = self._send_spi(msg)
- resp = bytearray(params['response'])
- mcu_clock = self.mcu.clock32_to_clock64(params['clock'])
- chip_clock = ((resp[2] & 0x7e) << 9) | ((resp[4] & 0x3e) << 4)
+ resp = bytearray(params["response"])
+ mcu_clock = self.mcu.clock32_to_clock64(params["clock"])
+ chip_clock = ((resp[2] & 0x7E) << 9) | ((resp[4] & 0x3E) << 4)
# Calculate temperature
temper = resp[5] - ((resp[4] & 0x01) << 8)
self.last_temperature = (temper + 152) / 2.776
return mcu_clock, chip_clock
+
def update_clock(self):
mcu_clock, chip_clock = self._query_clock()
mdiff = mcu_clock - self.last_chip_mcu_clock
- chip_mclock = self.last_chip_clock + int(mdiff * self.chip_freq + .5)
- cdiff = (chip_clock - chip_mclock) & 0xffff
+ chip_mclock = self.last_chip_clock + int(mdiff * self.chip_freq + 0.5)
+ cdiff = (chip_clock - chip_mclock) & 0xFFFF
cdiff -= (cdiff & 0x8000) << 1
new_chip_clock = chip_mclock + cdiff
self.chip_freq = float(new_chip_clock - self.last_chip_clock) / mdiff
self.last_chip_clock = new_chip_clock
self.last_chip_mcu_clock = mcu_clock
+
def start(self):
# Clear any errors from device
- self._read_reg(0x00) # Read STAT
+ self._read_reg(0x00) # Read STAT
# Initialize chip (so different chip variants work the same way)
- self._mask_reg(0x06, 0xc003, 0x4000) # MOD1: 42.7us, IIF disable
- self._mask_reg(0x08, 0x0007, 0x0001) # MOD2: Predict off, autocal=1
- self._mask_reg(0x0e, 0x0003, 0x0000) # MOD4: IIF mode
+ self._mask_reg(0x06, 0xC003, 0x4000) # MOD1: 42.7us, IIF disable
+ self._mask_reg(0x08, 0x0007, 0x0001) # MOD2: Predict off, autocal=1
+ self._mask_reg(0x0E, 0x0003, 0x0000) # MOD4: IIF mode
# Setup starting clock values
mcu_clock, chip_clock = self._query_clock()
self.last_chip_clock = chip_clock
self.last_chip_mcu_clock = mcu_clock
- self.chip_freq = float(1<<5) / self.mcu.seconds_to_clock(1. / 750000.)
+ self.chip_freq = float(1 << 5) / self.mcu.seconds_to_clock(1.0 / 750000.0)
self.update_clock()
+
cmd_ANGLE_DEBUG_READ_help = "Query low-level angle sensor register"
+
def cmd_ANGLE_DEBUG_READ(self, gcmd):
reg = gcmd.get("REG", minval=0, maxval=0x30, parser=lambda x: int(x, 0))
val = self._read_reg(reg)
gcmd.respond_info("ANGLE REG[0x%02x] = 0x%04x" % (reg, val))
+
cmd_ANGLE_DEBUG_WRITE_help = "Set low-level angle sensor register"
+
def cmd_ANGLE_DEBUG_WRITE(self, gcmd):
reg = gcmd.get("REG", minval=0, maxval=0x30, parser=lambda x: int(x, 0))
- val = gcmd.get("VAL", minval=0, maxval=0xffff,
- parser=lambda x: int(x, 0))
+ val = gcmd.get("VAL", minval=0, maxval=0xFFFF, parser=lambda x: int(x, 0))
self._write_reg(reg, val)
+
class HelperMT6816:
SPI_MODE = 3
SPI_SPEED = 10000000
+
def __init__(self, config, spi, oid):
self.printer = config.get_printer()
self.spi = spi
@@ -425,28 +489,41 @@ class HelperMT6816:
self.last_temperature = None
name = config.get_name().split()[-1]
gcode = self.printer.lookup_object("gcode")
- gcode.register_mux_command("ANGLE_DEBUG_READ", "CHIP", name,
- self.cmd_ANGLE_DEBUG_READ,
- desc=self.cmd_ANGLE_DEBUG_READ_help)
+ gcode.register_mux_command(
+ "ANGLE_DEBUG_READ",
+ "CHIP",
+ name,
+ self.cmd_ANGLE_DEBUG_READ,
+ desc=self.cmd_ANGLE_DEBUG_READ_help,
+ )
+
def _build_config(self):
cmdqueue = self.spi.get_command_queue()
self.spi_angle_transfer_cmd = self.mcu.lookup_query_command(
"spi_angle_transfer oid=%c data=%*s",
"spi_angle_transfer_response oid=%c clock=%u response=%*s",
- oid=self.oid, cq=cmdqueue)
+ oid=self.oid,
+ cq=cmdqueue,
+ )
+
def _send_spi(self, msg):
return self.spi.spi_transfer(msg)
+
def get_static_delay(self):
- return .000001
+ return 0.000001
+
def _read_reg(self, reg):
msg = [reg, 0, 0]
params = self._send_spi(msg)
- resp = bytearray(params['response'])
- val = (resp[1] << 8) | resp[2]
+ resp = bytearray(params["response"])
+ val = (resp[1] << 8) | resp[2]
return val
+
def start(self):
pass
+
cmd_ANGLE_DEBUG_READ_help = "Query low-level angle sensor register"
+
def cmd_ANGLE_DEBUG_READ(self, gcmd):
reg = 0x83
val = self._read_reg(reg)
@@ -457,12 +534,14 @@ class HelperMT6816:
gcmd.respond_info("No Mag: %i" % (val >> 1 & 0x1))
gcmd.respond_info("Parity: %i == %i" % (parity, val & 0x1))
+
class HelperMT6826S:
SPI_MODE = 3
SPI_SPEED = 10000000
+
def __init__(self, config, spi, oid):
self.printer = config.get_printer()
- self.stepper_name = config.get('stepper', None)
+ self.stepper_name = config.get("stepper", None)
self.spi = spi
self.oid = oid
self.mcu = spi.get_mcu()
@@ -472,39 +551,55 @@ class HelperMT6826S:
self.last_temperature = None
name = config.get_name().split()[-1]
gcode = self.printer.lookup_object("gcode")
- gcode.register_mux_command("ANGLE_DEBUG_READ", "CHIP", name,
- self.cmd_ANGLE_DEBUG_READ,
- desc=self.cmd_ANGLE_DEBUG_READ_help)
- gcode.register_mux_command("ANGLE_CHIP_CALIBRATE", "CHIP", name,
- self.cmd_ANGLE_CHIP_CALIBRATE,
- desc=self.cmd_ANGLE_CHIP_CALIBRATE_help)
+ gcode.register_mux_command(
+ "ANGLE_DEBUG_READ",
+ "CHIP",
+ name,
+ self.cmd_ANGLE_DEBUG_READ,
+ desc=self.cmd_ANGLE_DEBUG_READ_help,
+ )
+ gcode.register_mux_command(
+ "ANGLE_CHIP_CALIBRATE",
+ "CHIP",
+ name,
+ self.cmd_ANGLE_CHIP_CALIBRATE,
+ desc=self.cmd_ANGLE_CHIP_CALIBRATE_help,
+ )
self.status_map = {
0: "No Calibration",
1: "Running Calibration",
2: "Calibration Failed",
- 3: "Calibration Successful"
+ 3: "Calibration Successful",
}
+
def _build_config(self):
cmdqueue = self.spi.get_command_queue()
self.spi_angle_transfer_cmd = self.mcu.lookup_query_command(
"spi_angle_transfer oid=%c data=%*s",
"spi_angle_transfer_response oid=%c clock=%u response=%*s",
- oid=self.oid, cq=cmdqueue)
+ oid=self.oid,
+ cq=cmdqueue,
+ )
+
def _send_spi(self, msg):
params = self.spi.spi_transfer(msg)
return params
+
def get_static_delay(self):
- return .00001
+ return 0.00001
+
def _read_reg(self, reg):
reg = 0x3000 | reg
- msg = [reg >> 8, reg & 0xff, 0]
+ msg = [reg >> 8, reg & 0xFF, 0]
params = self._send_spi(msg)
- resp = bytearray(params['response'])
+ resp = bytearray(params["response"])
return resp[2]
+
def _write_reg(self, reg, data):
reg = 0x6000 | reg
- msg = [reg >> 8, reg & 0xff, data]
+ msg = [reg >> 8, reg & 0xFF, data]
self._send_spi(msg)
+
def crc8(self, data):
polynomial = 0x07
crc = 0x00
@@ -517,39 +612,45 @@ class HelperMT6826S:
crc <<= 1
crc &= 0xFF
return crc
+
def _read_angle(self, reg):
reg = 0x3000 | reg
- msg = [reg >> 8, reg & 0xff, 0, 0, 0, 0]
+ msg = [reg >> 8, reg & 0xFF, 0, 0, 0, 0]
params = self._send_spi(msg)
- resp = bytearray(params['response'])
+ resp = bytearray(params["response"])
angle = (resp[2] << 7) | (resp[3] >> 1)
status = resp[4]
crc_computed = self.crc8([resp[2], resp[3], resp[4]])
crc = resp[5]
return angle, status, crc, crc_computed
+
def start(self):
- val = self._read_reg(0x00d)
+ val = self._read_reg(0x00D)
# Set histeresis to 0.003 degree
- self._write_reg(0x00d, (val & 0xf8) | 0x5)
+ self._write_reg(0x00D, (val & 0xF8) | 0x5)
+
def get_microsteps(self):
- configfile = self.printer.lookup_object('configfile')
- sconfig = configfile.get_status(None)['settings']
+ configfile = self.printer.lookup_object("configfile")
+ sconfig = configfile.get_status(None)["settings"]
stconfig = sconfig.get(self.stepper_name, {})
- microsteps = stconfig['microsteps']
- full_steps = stconfig['full_steps_per_rotation']
+ microsteps = stconfig["microsteps"]
+ full_steps = stconfig["full_steps_per_rotation"]
return microsteps, full_steps
+
cmd_ANGLE_CHIP_CALIBRATE_help = "Run MT6826s calibration sequence"
+
def cmd_ANGLE_CHIP_CALIBRATE(self, gcmd):
- fmove = self.printer.lookup_object('force_move')
+ fmove = self.printer.lookup_object("force_move")
mcu_stepper = fmove.lookup_stepper(self.stepper_name)
if self.stepper_name is None:
gcmd.respond_info("stepper not defined")
return
gcmd.respond_info("MT6826S Run calibration sequence")
- gcmd.respond_info("Motor will do 18+ rotations -" +
- " ensure pulley is disconnected")
- req_freq = self._read_reg(0x00e) >> 4 & 0x7
+ gcmd.respond_info(
+ "Motor will do 18+ rotations -" + " ensure pulley is disconnected"
+ )
+ req_freq = self._read_reg(0x00E) >> 4 & 0x7
# Minimal calibration speed
rpm = (3200 >> req_freq) + 1
rps = rpm / 60
@@ -560,7 +661,7 @@ class HelperMT6826S:
full_step_dist = step_dist * microsteps
rotation_dist = full_steps * full_step_dist
move(mcu_stepper, 2 * rotation_dist, rps * rotation_dist)
- self._write_reg(0x155, 0x5e)
+ self._write_reg(0x155, 0x5E)
move(mcu_stepper, 20 * rotation_dist, rps * rotation_dist)
val = self._read_reg(0x113)
code = val >> 6
@@ -574,22 +675,20 @@ class HelperMT6826S:
gcmd.respond_info("Calibration failed")
if code == 3:
gcmd.respond_info("Calibration success, please poweroff sensor")
+
cmd_ANGLE_DEBUG_READ_help = "Query low-level angle sensor register"
+
def cmd_ANGLE_DEBUG_READ(self, gcmd):
- reg = gcmd.get("REG", minval=0, maxval=0x155,
- parser=lambda x: int(x, 0))
+ reg = gcmd.get("REG", minval=0, maxval=0x155, parser=lambda x: int(x, 0))
if reg == 0x003:
angle, status, crc1, crc2 = self._read_angle(reg)
- gcmd.respond_info("ANGLE REG[0x003] = 0x%02x" %
- (angle >> 7))
- gcmd.respond_info("ANGLE REG[0x004] = 0x%02x" %
- ((angle << 1) & 0xff))
- gcmd.respond_info("Angle %i ~ %.2f" % (angle,
- angle * 360 / (1 << 15)))
+ gcmd.respond_info("ANGLE REG[0x003] = 0x%02x" % (angle >> 7))
+ gcmd.respond_info("ANGLE REG[0x004] = 0x%02x" % ((angle << 1) & 0xFF))
+ gcmd.respond_info("Angle %i ~ %.2f" % (angle, angle * 360 / (1 << 15)))
gcmd.respond_info("Weak Mag: %i" % (status >> 1 & 0x1))
gcmd.respond_info("Under Voltage: %i" % (status >> 2 & 0x1))
gcmd.respond_info("CRC: 0x%02x == 0x%02x" % (crc1, crc2))
- elif reg == 0x00e:
+ elif reg == 0x00E:
val = self._read_reg(reg)
gcmd.respond_info("GPIO_DS = %i" % (val >> 7))
gcmd.respond_info("AUTOCAL_FREQ = %i" % (val >> 4 & 0x7))
@@ -607,25 +706,28 @@ SAMPLES_PER_BLOCK = bulk_sensor.MAX_BULK_MSG_SIZE // BYTES_PER_SAMPLE
SAMPLE_PERIOD = 0.000400
BATCH_UPDATES = 0.100
+
class Angle:
def __init__(self, config):
self.printer = config.get_printer()
- self.sample_period = config.getfloat('sample_period', SAMPLE_PERIOD,
- above=0.)
+ self.sample_period = config.getfloat("sample_period", SAMPLE_PERIOD, above=0.0)
self.calibration = AngleCalibration(config)
# Measurement conversion
self.start_clock = self.time_shift = self.sample_ticks = 0
self.last_sequence = self.last_angle = 0
# Sensor type
- sensors = { "a1333": HelperA1333,
- "as5047d": HelperAS5047D,
- "tle5012b": HelperTLE5012B,
- "mt6816": HelperMT6816,
- "mt6826s": HelperMT6826S }
- sensor_type = config.getchoice('sensor_type', {s: s for s in sensors})
+ sensors = {
+ "a1333": HelperA1333,
+ "as5047d": HelperAS5047D,
+ "tle5012b": HelperTLE5012B,
+ "mt6816": HelperMT6816,
+ "mt6826s": HelperMT6826S,
+ }
+ sensor_type = config.getchoice("sensor_type", {s: s for s in sensors})
sensor_class = sensors[sensor_type]
- self.spi = bus.MCU_SPI_from_config(config, sensor_class.SPI_MODE,
- default_speed=sensor_class.SPI_SPEED)
+ self.spi = bus.MCU_SPI_from_config(
+ config, sensor_class.SPI_MODE, default_speed=sensor_class.SPI_SPEED
+ )
self.mcu = mcu = self.spi.get_mcu()
self.oid = oid = mcu.create_oid()
self.sensor_helper = sensor_class(config, self.spi, oid)
@@ -633,32 +735,43 @@ class Angle:
self.query_spi_angle_cmd = None
mcu.add_config_cmd(
"config_spi_angle oid=%d spi_oid=%d spi_angle_type=%s"
- % (oid, self.spi.get_oid(), sensor_type))
+ % (oid, self.spi.get_oid(), sensor_type)
+ )
mcu.add_config_cmd(
- "query_spi_angle oid=%d clock=0 rest_ticks=0 time_shift=0"
- % (oid,), on_restart=True)
+ "query_spi_angle oid=%d clock=0 rest_ticks=0 time_shift=0" % (oid,),
+ on_restart=True,
+ )
mcu.register_config_callback(self._build_config)
self.bulk_queue = bulk_sensor.BulkDataQueue(mcu, oid=oid)
# Process messages in batches
self.batch_bulk = bulk_sensor.BatchBulkHelper(
- self.printer, self._process_batch,
- self._start_measurements, self._finish_measurements, BATCH_UPDATES)
+ self.printer,
+ self._process_batch,
+ self._start_measurements,
+ self._finish_measurements,
+ BATCH_UPDATES,
+ )
self.name = config.get_name().split()[1]
- api_resp = {'header': ('time', 'angle')}
- self.batch_bulk.add_mux_endpoint("angle/dump_angle",
- "sensor", self.name, api_resp)
+ api_resp = {"header": ("time", "angle")}
+ self.batch_bulk.add_mux_endpoint(
+ "angle/dump_angle", "sensor", self.name, api_resp
+ )
+
def _build_config(self):
- freq = self.mcu.seconds_to_clock(1.)
+ freq = self.mcu.seconds_to_clock(1.0)
while float(TCODE_ERROR << self.time_shift) / freq < 0.002:
self.time_shift += 1
cmdqueue = self.spi.get_command_queue()
self.query_spi_angle_cmd = self.mcu.lookup_command(
- "query_spi_angle oid=%c clock=%u rest_ticks=%u time_shift=%c",
- cq=cmdqueue)
+ "query_spi_angle oid=%c clock=%u rest_ticks=%u time_shift=%c", cq=cmdqueue
+ )
+
def get_status(self, eventtime=None):
- return {'temperature': self.sensor_helper.last_temperature}
+ return {"temperature": self.sensor_helper.last_temperature}
+
def add_client(self, client_cb):
self.batch_bulk.add_client(client_cb)
+
# Measurement decoding
def _extract_samples(self, raw_samples):
# Load variables to optimize inner loop below
@@ -668,13 +781,13 @@ class Angle:
last_sequence = self.last_sequence
last_angle = self.last_angle
time_shift = 0
- static_delay = 0.
- last_chip_mcu_clock = last_chip_clock = chip_freq = inv_chip_freq = 0.
+ static_delay = 0.0
+ last_chip_mcu_clock = last_chip_clock = chip_freq = inv_chip_freq = 0.0
is_tcode_absolute = self.sensor_helper.is_tcode_absolute
if is_tcode_absolute:
tparams = self.sensor_helper.get_tcode_params()
last_chip_mcu_clock, last_chip_clock, chip_freq = tparams
- inv_chip_freq = 1. / chip_freq
+ inv_chip_freq = 1.0 / chip_freq
else:
time_shift = self.time_shift
static_delay = self.sensor_helper.get_static_delay()
@@ -682,32 +795,32 @@ class Angle:
count = error_count = 0
samples = [None] * (len(raw_samples) * SAMPLES_PER_BLOCK)
for params in raw_samples:
- seq_diff = (params['sequence'] - last_sequence) & 0xffff
+ seq_diff = (params["sequence"] - last_sequence) & 0xFFFF
last_sequence += seq_diff
samp_count = last_sequence * SAMPLES_PER_BLOCK
- msg_mclock = start_clock + samp_count*sample_ticks
- d = bytearray(params['data'])
+ msg_mclock = start_clock + samp_count * sample_ticks
+ d = bytearray(params["data"])
for i in range(len(d) // BYTES_PER_SAMPLE):
- d_ta = d[i*BYTES_PER_SAMPLE:(i+1)*BYTES_PER_SAMPLE]
+ d_ta = d[i * BYTES_PER_SAMPLE : (i + 1) * BYTES_PER_SAMPLE]
tcode = d_ta[0]
if tcode == TCODE_ERROR:
error_count += 1
continue
raw_angle = d_ta[1] | (d_ta[2] << 8)
- angle_diff = (raw_angle - last_angle) & 0xffff
+ angle_diff = (raw_angle - last_angle) & 0xFFFF
angle_diff -= (angle_diff & 0x8000) << 1
last_angle += angle_diff
- mclock = msg_mclock + i*sample_ticks
+ mclock = msg_mclock + i * sample_ticks
if is_tcode_absolute:
# tcode is tle5012b frame counter
mdiff = mclock - last_chip_mcu_clock
- chip_mclock = last_chip_clock + int(mdiff * chip_freq + .5)
- cdiff = ((tcode << 10) - chip_mclock) & 0xffff
+ chip_mclock = last_chip_clock + int(mdiff * chip_freq + 0.5)
+ cdiff = ((tcode << 10) - chip_mclock) & 0xFFFF
cdiff -= (cdiff & 0x8000) << 1
sclock = mclock + (cdiff - 0x800) * inv_chip_freq
else:
# tcode is mcu clock offset shifted by time_shift
- sclock = mclock + (tcode<<time_shift)
+ sclock = mclock + (tcode << time_shift)
ptime = round(clock_to_print_time(sclock) - static_delay, 6)
samples[count] = (ptime, last_angle)
count += 1
@@ -715,9 +828,11 @@ class Angle:
self.last_angle = last_angle
del samples[count:]
return samples, error_count
+
# Start, stop, and process message batches
def _is_measuring(self):
return self.start_clock != 0
+
def _start_measurements(self):
logging.info("Starting angle '%s' measurements", self.name)
self.sensor_helper.start()
@@ -729,14 +844,17 @@ class Angle:
self.start_clock = reqclock = self.mcu.print_time_to_clock(print_time)
rest_ticks = self.mcu.seconds_to_clock(self.sample_period)
self.sample_ticks = rest_ticks
- self.query_spi_angle_cmd.send([self.oid, reqclock, rest_ticks,
- self.time_shift], reqclock=reqclock)
+ self.query_spi_angle_cmd.send(
+ [self.oid, reqclock, rest_ticks, self.time_shift], reqclock=reqclock
+ )
+
def _finish_measurements(self):
# Halt bulk reading
self.query_spi_angle_cmd.send_wait_ack([self.oid, 0, 0, 0])
self.bulk_queue.clear_queue()
self.sensor_helper.last_temperature = None
logging.info("Stopped angle '%s' measurements", self.name)
+
def _process_batch(self, eventtime):
if self.sensor_helper.is_tcode_absolute:
self.sensor_helper.update_clock()
@@ -747,8 +865,8 @@ class Angle:
if not samples:
return {}
offset = self.calibration.apply_calibration(samples)
- return {'data': samples, 'errors': error_count,
- 'position_offset': offset}
+ return {"data": samples, "errors": error_count, "position_offset": offset}
+
def load_config_prefix(config):
return Angle(config)
diff --git a/klippy/extras/axis_twist_compensation.py b/klippy/extras/axis_twist_compensation.py
index 908ac4da..224f0a6c 100644
--- a/klippy/extras/axis_twist_compensation.py
+++ b/klippy/extras/axis_twist_compensation.py
@@ -9,112 +9,115 @@ from . import manual_probe, bed_mesh, probe
DEFAULT_SAMPLE_COUNT = 3
-DEFAULT_SPEED = 50.
-DEFAULT_HORIZONTAL_MOVE_Z = 5.
+DEFAULT_SPEED = 50.0
+DEFAULT_HORIZONTAL_MOVE_Z = 5.0
class AxisTwistCompensation:
def __init__(self, config):
# get printer
self.printer = config.get_printer()
- self.gcode = self.printer.lookup_object('gcode')
+ self.gcode = self.printer.lookup_object("gcode")
# get values from [axis_twist_compensation] section in printer .cfg
- self.horizontal_move_z = config.getfloat('horizontal_move_z',
- DEFAULT_HORIZONTAL_MOVE_Z)
- self.speed = config.getfloat('speed', DEFAULT_SPEED)
- self.calibrate_start_x = config.getfloat('calibrate_start_x',
- default=None)
- self.calibrate_end_x = config.getfloat('calibrate_end_x', default=None)
- self.calibrate_y = config.getfloat('calibrate_y', default=None)
- self.z_compensations = config.getlists('z_compensations',
- default=[], parser=float)
- self.compensation_start_x = config.getfloat('compensation_start_x',
- default=None)
- self.compensation_end_x = config.getfloat('compensation_end_x',
- default=None)
+ self.horizontal_move_z = config.getfloat(
+ "horizontal_move_z", DEFAULT_HORIZONTAL_MOVE_Z
+ )
+ self.speed = config.getfloat("speed", DEFAULT_SPEED)
+ self.calibrate_start_x = config.getfloat("calibrate_start_x", default=None)
+ self.calibrate_end_x = config.getfloat("calibrate_end_x", default=None)
+ self.calibrate_y = config.getfloat("calibrate_y", default=None)
+ self.z_compensations = config.getlists(
+ "z_compensations", default=[], parser=float
+ )
+ self.compensation_start_x = config.getfloat(
+ "compensation_start_x", default=None
+ )
+ self.compensation_end_x = config.getfloat("compensation_end_x", default=None)
- self.calibrate_start_y = config.getfloat('calibrate_start_y',
- default=None)
- self.calibrate_end_y = config.getfloat('calibrate_end_y', default=None)
- self.calibrate_x = config.getfloat('calibrate_x', default=None)
- self.compensation_start_y = config.getfloat('compensation_start_y',
- default=None)
- self.compensation_end_y = config.getfloat('compensation_end_y',
- default=None)
- self.zy_compensations = config.getlists('zy_compensations',
- default=[], parser=float)
+ self.calibrate_start_y = config.getfloat("calibrate_start_y", default=None)
+ self.calibrate_end_y = config.getfloat("calibrate_end_y", default=None)
+ self.calibrate_x = config.getfloat("calibrate_x", default=None)
+ self.compensation_start_y = config.getfloat(
+ "compensation_start_y", default=None
+ )
+ self.compensation_end_y = config.getfloat("compensation_end_y", default=None)
+ self.zy_compensations = config.getlists(
+ "zy_compensations", default=[], parser=float
+ )
# setup calibrater
self.calibrater = Calibrater(self, config)
# register events
- self.printer.register_event_handler("probe:update_results",
- self._update_z_compensation_value)
+ self.printer.register_event_handler(
+ "probe:update_results", self._update_z_compensation_value
+ )
def _update_z_compensation_value(self, pos):
if self.z_compensations:
pos[2] += self._get_interpolated_z_compensation(
- pos[0], self.z_compensations,
+ pos[0],
+ self.z_compensations,
self.compensation_start_x,
- self.compensation_end_x
- )
+ self.compensation_end_x,
+ )
if self.zy_compensations:
pos[2] += self._get_interpolated_z_compensation(
- pos[1], self.zy_compensations,
+ pos[1],
+ self.zy_compensations,
self.compensation_start_y,
- self.compensation_end_y
- )
+ self.compensation_end_y,
+ )
def _get_interpolated_z_compensation(
- self, coord, z_compensations,
- comp_start,
- comp_end
- ):
+ self, coord, z_compensations, comp_start, comp_end
+ ):
sample_count = len(z_compensations)
- spacing = ((comp_end - comp_start)
- / (sample_count - 1))
+ spacing = (comp_end - comp_start) / (sample_count - 1)
interpolate_t = (coord - comp_start) / spacing
interpolate_i = int(math.floor(interpolate_t))
interpolate_i = bed_mesh.constrain(interpolate_i, 0, sample_count - 2)
interpolate_t -= interpolate_i
interpolated_z_compensation = bed_mesh.lerp(
- interpolate_t, z_compensations[interpolate_i],
- z_compensations[interpolate_i + 1])
+ interpolate_t,
+ z_compensations[interpolate_i],
+ z_compensations[interpolate_i + 1],
+ )
return interpolated_z_compensation
def clear_compensations(self, axis=None):
if axis is None:
self.z_compensations = []
self.zy_compensations = []
- elif axis == 'X':
+ elif axis == "X":
self.z_compensations = []
- elif axis == 'Y':
+ elif axis == "Y":
self.zy_compensations = []
+
class Calibrater:
def __init__(self, compensation, config):
# setup self attributes
self.compensation = compensation
self.printer = compensation.printer
- self.gcode = self.printer.lookup_object('gcode')
+ self.gcode = self.printer.lookup_object("gcode")
self.probe = None
# probe settings are set to none, until they are available
- self.lift_speed, self.probe_x_offset, self.probe_y_offset, _ = \
- None, None, None, None
- self.printer.register_event_handler("klippy:connect",
- self._handle_connect)
+ self.lift_speed, self.probe_x_offset, self.probe_y_offset, _ = (
+ None,
+ None,
+ None,
+ None,
+ )
+ self.printer.register_event_handler("klippy:connect", self._handle_connect)
self.speed = compensation.speed
self.horizontal_move_z = compensation.horizontal_move_z
- self.x_start_point = (compensation.calibrate_start_x,
- compensation.calibrate_y)
- self.x_end_point = (compensation.calibrate_end_x,
- compensation.calibrate_y)
- self.y_start_point = (compensation.calibrate_x,
- compensation.calibrate_start_y)
- self.y_end_point = (compensation.calibrate_x,
- compensation.calibrate_end_y)
+ self.x_start_point = (compensation.calibrate_start_x, compensation.calibrate_y)
+ self.x_end_point = (compensation.calibrate_end_x, compensation.calibrate_y)
+ self.y_start_point = (compensation.calibrate_x, compensation.calibrate_start_y)
+ self.y_end_point = (compensation.calibrate_x, compensation.calibrate_end_y)
self.results = None
self.current_point_index = None
self.gcmd = None
@@ -124,21 +127,22 @@ class Calibrater:
self._register_gcode_handlers()
def _handle_connect(self):
- self.probe = self.printer.lookup_object('probe', None)
+ self.probe = self.printer.lookup_object("probe", None)
if self.probe is None:
raise self.printer.config_error(
- "AXIS_TWIST_COMPENSATION requires [probe] to be defined")
- self.lift_speed = self.probe.get_probe_params()['lift_speed']
- self.probe_x_offset, self.probe_y_offset, _ = \
- self.probe.get_offsets()
+ "AXIS_TWIST_COMPENSATION requires [probe] to be defined"
+ )
+ self.lift_speed = self.probe.get_probe_params()["lift_speed"]
+ self.probe_x_offset, self.probe_y_offset, _ = self.probe.get_offsets()
def _register_gcode_handlers(self):
# register gcode handlers
- self.gcode = self.printer.lookup_object('gcode')
+ self.gcode = self.printer.lookup_object("gcode")
self.gcode.register_command(
- 'AXIS_TWIST_COMPENSATION_CALIBRATE',
+ "AXIS_TWIST_COMPENSATION_CALIBRATE",
self.cmd_AXIS_TWIST_COMPENSATION_CALIBRATE,
- desc=self.cmd_AXIS_TWIST_COMPENSATION_CALIBRATE_help)
+ desc=self.cmd_AXIS_TWIST_COMPENSATION_CALIBRATE_help,
+ )
cmd_AXIS_TWIST_COMPENSATION_CALIBRATE_help = """
Performs the x twist calibration wizard
@@ -148,32 +152,29 @@ class Calibrater:
def cmd_AXIS_TWIST_COMPENSATION_CALIBRATE(self, gcmd):
self.gcmd = gcmd
- sample_count = gcmd.get_int('SAMPLE_COUNT', DEFAULT_SAMPLE_COUNT)
- axis = gcmd.get('AXIS', 'X')
+ sample_count = gcmd.get_int("SAMPLE_COUNT", DEFAULT_SAMPLE_COUNT)
+ axis = gcmd.get("AXIS", "X")
# check for valid sample_count
if sample_count < 2:
- raise self.gcmd.error(
- "SAMPLE_COUNT to probe must be at least 2")
+ raise self.gcmd.error("SAMPLE_COUNT to probe must be at least 2")
# calculate the points to put the probe at, returned as a list of tuples
nozzle_points = []
- if axis == 'X':
+ if axis == "X":
- self.compensation.clear_compensations('X')
+ self.compensation.clear_compensations("X")
- if not all([
- self.x_start_point[0],
- self.x_end_point[0],
- self.x_start_point[1]
- ]):
+ if not all(
+ [self.x_start_point[0], self.x_end_point[0], self.x_start_point[1]]
+ ):
raise self.gcmd.error(
"""AXIS_TWIST_COMPENSATION for X axis requires
calibrate_start_x, calibrate_end_x and calibrate_y
to be defined
"""
- )
+ )
start_point = self.x_start_point
end_point = self.x_end_point
@@ -186,21 +187,19 @@ class Calibrater:
y = start_point[1]
nozzle_points.append((x, y))
- elif axis == 'Y':
+ elif axis == "Y":
- self.compensation.clear_compensations('Y')
+ self.compensation.clear_compensations("Y")
- if not all([
- self.y_start_point[0],
- self.y_end_point[0],
- self.y_start_point[1]
- ]):
+ if not all(
+ [self.y_start_point[0], self.y_end_point[0], self.y_start_point[1]]
+ ):
raise self.gcmd.error(
"""AXIS_TWIST_COMPENSATION for Y axis requires
calibrate_start_y, calibrate_end_y and calibrate_x
to be defined
"""
- )
+ )
start_point = self.y_start_point
end_point = self.y_end_point
@@ -214,12 +213,11 @@ class Calibrater:
nozzle_points.append((x, y))
else:
- raise self.gcmd.error(
- "AXIS_TWIST_COMPENSATION_CALIBRATE: "
- "Invalid axis.")
+ raise self.gcmd.error("AXIS_TWIST_COMPENSATION_CALIBRATE: " "Invalid axis.")
probe_points = self._calculate_probe_points(
- nozzle_points, self.probe_x_offset, self.probe_y_offset)
+ nozzle_points, self.probe_x_offset, self.probe_y_offset
+ )
# verify no other manual probe is in progress
manual_probe.verify_no_manual_probe(self.printer)
@@ -230,8 +228,7 @@ class Calibrater:
self.current_axis = axis
self._calibration(probe_points, nozzle_points, interval_dist)
- def _calculate_probe_points(self, nozzle_points,
- probe_x_offset, probe_y_offset):
+ def _calculate_probe_points(self, nozzle_points, probe_x_offset, probe_y_offset):
# calculate the points to put the nozzle at
# returned as a list of tuples
probe_points = []
@@ -243,27 +240,34 @@ class Calibrater:
def _move_helper(self, target_coordinates, override_speed=None):
# pad target coordinates
- target_coordinates = \
- (target_coordinates[0], target_coordinates[1], None) \
- if len(target_coordinates) == 2 else target_coordinates
- toolhead = self.printer.lookup_object('toolhead')
+ target_coordinates = (
+ (target_coordinates[0], target_coordinates[1], None)
+ if len(target_coordinates) == 2
+ else target_coordinates
+ )
+ toolhead = self.printer.lookup_object("toolhead")
speed = self.speed if target_coordinates[2] == None else self.lift_speed
speed = override_speed if override_speed is not None else speed
toolhead.manual_move(target_coordinates, speed)
def _calibration(self, probe_points, nozzle_points, interval):
# begin the calibration process
- self.gcmd.respond_info("AXIS_TWIST_COMPENSATION_CALIBRATE: "
- "Probing point %d of %d" % (
- self.current_point_index + 1,
- len(probe_points)))
+ self.gcmd.respond_info(
+ "AXIS_TWIST_COMPENSATION_CALIBRATE: "
+ "Probing point %d of %d" % (self.current_point_index + 1, len(probe_points))
+ )
# horizontal_move_z (to prevent probe trigger or hitting bed)
self._move_helper((None, None, self.horizontal_move_z))
# move to point to probe
- self._move_helper((probe_points[self.current_point_index][0],
- probe_points[self.current_point_index][1], None))
+ self._move_helper(
+ (
+ probe_points[self.current_point_index][0],
+ probe_points[self.current_point_index][1],
+ None,
+ )
+ )
# probe the point
pos = probe.run_single_probe(self.probe, self.gcmd)
@@ -277,12 +281,12 @@ class Calibrater:
# start the manual (nozzle) probe
manual_probe.ManualProbeHelper(
- self.printer, self.gcmd,
- self._manual_probe_callback_factory(
- probe_points, nozzle_points, interval))
+ self.printer,
+ self.gcmd,
+ self._manual_probe_callback_factory(probe_points, nozzle_points, interval),
+ )
- def _manual_probe_callback_factory(self, probe_points,
- nozzle_points, interval):
+ def _manual_probe_callback_factory(self, probe_points, nozzle_points, interval):
# returns a callback function for the manual probe
is_end = self.current_point_index == len(probe_points) - 1
@@ -291,7 +295,8 @@ class Calibrater:
# probe was cancelled
self.gcmd.respond_info(
"AXIS_TWIST_COMPENSATION_CALIBRATE: Probe cancelled, "
- "calibration aborted")
+ "calibration aborted"
+ )
return
z_offset = self.current_measured_z - kin_pos[2]
self.results.append(z_offset)
@@ -302,6 +307,7 @@ class Calibrater:
# move to next point
self.current_point_index += 1
self._calibration(probe_points, nozzle_points, interval)
+
return callback
def _finalize_calibration(self):
@@ -312,29 +318,28 @@ class Calibrater:
# so that they are independent of z_offset
self.results = [avg - x for x in self.results]
# save the config
- configfile = self.printer.lookup_object('configfile')
- values_as_str = ', '.join(["{:.6f}".format(x)
- for x in self.results])
+ configfile = self.printer.lookup_object("configfile")
+ values_as_str = ", ".join(["{:.6f}".format(x) for x in self.results])
- if(self.current_axis == 'X'):
+ if self.current_axis == "X":
- configfile.set(self.configname, 'z_compensations', values_as_str)
- configfile.set(self.configname, 'compensation_start_x',
- self.x_start_point[0])
- configfile.set(self.configname, 'compensation_end_x',
- self.x_end_point[0])
+ configfile.set(self.configname, "z_compensations", values_as_str)
+ configfile.set(
+ self.configname, "compensation_start_x", self.x_start_point[0]
+ )
+ configfile.set(self.configname, "compensation_end_x", self.x_end_point[0])
self.compensation.z_compensations = self.results
self.compensation.compensation_start_x = self.x_start_point[0]
self.compensation.compensation_end_x = self.x_end_point[0]
- elif(self.current_axis == 'Y'):
+ elif self.current_axis == "Y":
- configfile.set(self.configname, 'zy_compensations', values_as_str)
- configfile.set(self.configname, 'compensation_start_y',
- self.y_start_point[1])
- configfile.set(self.configname, 'compensation_end_y',
- self.y_end_point[1])
+ configfile.set(self.configname, "zy_compensations", values_as_str)
+ configfile.set(
+ self.configname, "compensation_start_y", self.y_start_point[1]
+ )
+ configfile.set(self.configname, "compensation_end_y", self.y_end_point[1])
self.compensation.zy_compensations = self.results
self.compensation.compensation_start_y = self.y_start_point[1]
@@ -343,12 +348,13 @@ class Calibrater:
self.gcode.respond_info(
"AXIS_TWIST_COMPENSATION state has been saved "
"for the current session. The SAVE_CONFIG command will "
- "update the printer config file and restart the printer.")
+ "update the printer config file and restart the printer."
+ )
# output result
self.gcmd.respond_info(
"AXIS_TWIST_COMPENSATION_CALIBRATE: Calibration complete, "
- "offsets: %s, mean z_offset: %f"
- % (self.results, avg))
+ "offsets: %s, mean z_offset: %f" % (self.results, avg)
+ )
# klipper's entry point using [axis_twist_compensation] section in printer.cfg
diff --git a/klippy/extras/bed_mesh.py b/klippy/extras/bed_mesh.py
index a8e5764c..921b6296 100644
--- a/klippy/extras/bed_mesh.py
+++ b/klippy/extras/bed_mesh.py
@@ -8,56 +8,74 @@ from . import probe
PROFILE_VERSION = 1
PROFILE_OPTIONS = {
- 'min_x': float, 'max_x': float, 'min_y': float, 'max_y': float,
- 'x_count': int, 'y_count': int, 'mesh_x_pps': int, 'mesh_y_pps': int,
- 'algo': str, 'tension': float
+ "min_x": float,
+ "max_x": float,
+ "min_y": float,
+ "max_y": float,
+ "x_count": int,
+ "y_count": int,
+ "mesh_x_pps": int,
+ "mesh_y_pps": int,
+ "algo": str,
+ "tension": float,
}
+
class BedMeshError(Exception):
pass
+
# PEP 485 isclose()
def isclose(a, b, rel_tol=1e-09, abs_tol=0.0):
- return abs(a-b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol)
+ return abs(a - b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol)
+
# return true if a coordinate is within the region
# specified by min_c and max_c
def within(coord, min_c, max_c, tol=0.0):
- return (max_c[0] + tol) >= coord[0] >= (min_c[0] - tol) and \
- (max_c[1] + tol) >= coord[1] >= (min_c[1] - tol)
+ return (max_c[0] + tol) >= coord[0] >= (min_c[0] - tol) and (
+ max_c[1] + tol
+ ) >= coord[1] >= (min_c[1] - tol)
+
# Constrain value between min and max
def constrain(val, min_val, max_val):
return min(max_val, max(min_val, val))
+
# Linear interpolation between two values
def lerp(t, v0, v1):
- return (1. - t) * v0 + t * v1
+ return (1.0 - t) * v0 + t * v1
+
# retrieve comma separated pair from config
def parse_config_pair(config, option, default, minval=None, maxval=None):
pair = config.getintlist(option, (default, default))
if len(pair) != 2:
if len(pair) != 1:
- raise config.error("bed_mesh: malformed '%s' value: %s"
- % (option, config.get(option)))
+ raise config.error(
+ "bed_mesh: malformed '%s' value: %s" % (option, config.get(option))
+ )
pair = (pair[0], pair[0])
if minval is not None:
if pair[0] < minval or pair[1] < minval:
raise config.error(
"Option '%s' in section bed_mesh must have a minimum of %s"
- % (option, str(minval)))
+ % (option, str(minval))
+ )
if maxval is not None:
if pair[0] > maxval or pair[1] > maxval:
raise config.error(
"Option '%s' in section bed_mesh must have a maximum of %s"
- % (option, str(maxval)))
+ % (option, str(maxval))
+ )
return pair
+
# retrieve comma separated pair from a g-code command
def parse_gcmd_pair(gcmd, name, minval=None, maxval=None):
try:
- pair = [int(v.strip()) for v in gcmd.get(name).split(',')]
+ pair = [int(v.strip()) for v in gcmd.get(name).split(",")]
except:
raise gcmd.error("Unable to parse parameter '%s'" % (name,))
if len(pair) != 2:
@@ -66,18 +84,21 @@ def parse_gcmd_pair(gcmd, name, minval=None, maxval=None):
pair = (pair[0], pair[0])
if minval is not None:
if pair[0] < minval or pair[1] < minval:
- raise gcmd.error("Parameter '%s' must have a minimum of %d"
- % (name, minval))
+ raise gcmd.error(
+ "Parameter '%s' must have a minimum of %d" % (name, minval)
+ )
if maxval is not None:
if pair[0] > maxval or pair[1] > maxval:
- raise gcmd.error("Parameter '%s' must have a maximum of %d"
- % (name, maxval))
+ raise gcmd.error(
+ "Parameter '%s' must have a maximum of %d" % (name, maxval)
+ )
return pair
+
# retrieve comma separated coordinate from a g-code command
def parse_gcmd_coord(gcmd, name):
try:
- v1, v2 = [float(v.strip()) for v in gcmd.get(name).split(',')]
+ v1, v2 = [float(v.strip()) for v in gcmd.get(name).split(",")]
except:
raise gcmd.error("Unable to parse parameter '%s'" % (name,))
return v1, v2
@@ -85,55 +106,59 @@ def parse_gcmd_coord(gcmd, name):
class BedMesh:
FADE_DISABLE = 0x7FFFFFFF
+
def __init__(self, config):
self.printer = config.get_printer()
- self.printer.register_event_handler("klippy:connect",
- self.handle_connect)
- self.last_position = [0., 0., 0., 0.]
+ self.printer.register_event_handler("klippy:connect", self.handle_connect)
+ self.last_position = [0.0, 0.0, 0.0, 0.0]
self.bmc = BedMeshCalibrate(config, self)
self.z_mesh = None
self.toolhead = None
- self.horizontal_move_z = config.getfloat('horizontal_move_z', 5.)
- self.fade_start = config.getfloat('fade_start', 1.)
- self.fade_end = config.getfloat('fade_end', 0.)
+ self.horizontal_move_z = config.getfloat("horizontal_move_z", 5.0)
+ self.fade_start = config.getfloat("fade_start", 1.0)
+ self.fade_end = config.getfloat("fade_end", 0.0)
self.fade_dist = self.fade_end - self.fade_start
- if self.fade_dist <= 0.:
+ if self.fade_dist <= 0.0:
self.fade_start = self.fade_end = self.FADE_DISABLE
self.log_fade_complete = False
- self.base_fade_target = config.getfloat('fade_target', None)
- self.fade_target = 0.
- self.tool_offset = 0.
- self.gcode = self.printer.lookup_object('gcode')
+ self.base_fade_target = config.getfloat("fade_target", None)
+ self.fade_target = 0.0
+ self.tool_offset = 0.0
+ self.gcode = self.printer.lookup_object("gcode")
self.splitter = MoveSplitter(config, self.gcode)
# setup persistent storage
self.pmgr = ProfileManager(config, self)
self.save_profile = self.pmgr.save_profile
# register gcodes
self.gcode.register_command(
- 'BED_MESH_OUTPUT', self.cmd_BED_MESH_OUTPUT,
- desc=self.cmd_BED_MESH_OUTPUT_help)
+ "BED_MESH_OUTPUT",
+ self.cmd_BED_MESH_OUTPUT,
+ desc=self.cmd_BED_MESH_OUTPUT_help,
+ )
self.gcode.register_command(
- 'BED_MESH_MAP', self.cmd_BED_MESH_MAP,
- desc=self.cmd_BED_MESH_MAP_help)
+ "BED_MESH_MAP", self.cmd_BED_MESH_MAP, desc=self.cmd_BED_MESH_MAP_help
+ )
self.gcode.register_command(
- 'BED_MESH_CLEAR', self.cmd_BED_MESH_CLEAR,
- desc=self.cmd_BED_MESH_CLEAR_help)
+ "BED_MESH_CLEAR", self.cmd_BED_MESH_CLEAR, desc=self.cmd_BED_MESH_CLEAR_help
+ )
self.gcode.register_command(
- 'BED_MESH_OFFSET', self.cmd_BED_MESH_OFFSET,
- desc=self.cmd_BED_MESH_OFFSET_help)
- # Register dump webhooks
- webhooks = self.printer.lookup_object('webhooks')
- webhooks.register_endpoint(
- "bed_mesh/dump_mesh", self._handle_dump_request
+ "BED_MESH_OFFSET",
+ self.cmd_BED_MESH_OFFSET,
+ desc=self.cmd_BED_MESH_OFFSET_help,
)
+ # Register dump webhooks
+ webhooks = self.printer.lookup_object("webhooks")
+ webhooks.register_endpoint("bed_mesh/dump_mesh", self._handle_dump_request)
# Register transform
- gcode_move = self.printer.load_object(config, 'gcode_move')
+ gcode_move = self.printer.load_object(config, "gcode_move")
gcode_move.set_move_transform(self)
# initialize status dict
self.update_status()
+
def handle_connect(self):
- self.toolhead = self.printer.lookup_object('toolhead')
+ self.toolhead = self.printer.lookup_object("toolhead")
self.bmc.print_generated_points(logging.info, truncate=True)
+
def set_mesh(self, mesh):
if mesh is not None and self.fade_end != self.FADE_DISABLE:
self.log_fade_complete = True
@@ -142,42 +167,45 @@ class BedMesh:
else:
self.fade_target = self.base_fade_target
min_z, max_z = mesh.get_z_range()
- if (not min_z <= self.fade_target <= max_z and
- self.fade_target != 0.):
+ if not min_z <= self.fade_target <= max_z and self.fade_target != 0.0:
# fade target is non-zero, out of mesh range
err_target = self.fade_target
self.z_mesh = None
- self.fade_target = 0.
+ self.fade_target = 0.0
raise self.gcode.error(
"bed_mesh: ERROR, fade_target lies outside of mesh z "
"range\nmin: %.4f, max: %.4f, fade_target: %.4f"
- % (min_z, max_z, err_target))
+ % (min_z, max_z, err_target)
+ )
min_z, max_z = mesh.get_z_range()
if self.fade_dist <= max(abs(min_z), abs(max_z)):
self.z_mesh = None
- self.fade_target = 0.
+ self.fade_target = 0.0
raise self.gcode.error(
"bed_mesh: Mesh extends outside of the fade range, "
"please see the fade_start and fade_end options in"
"example-extras.cfg. fade distance: %.2f mesh min: %.4f"
- "mesh max: %.4f" % (self.fade_dist, min_z, max_z))
+ "mesh max: %.4f" % (self.fade_dist, min_z, max_z)
+ )
else:
- self.fade_target = 0.
- self.tool_offset = 0.
+ self.fade_target = 0.0
+ self.tool_offset = 0.0
self.z_mesh = mesh
self.splitter.initialize(mesh, self.fade_target)
# cache the current position before a transform takes place
- gcode_move = self.printer.lookup_object('gcode_move')
+ gcode_move = self.printer.lookup_object("gcode_move")
gcode_move.reset_last_position()
self.update_status()
+
def get_z_factor(self, z_pos):
z_pos += self.tool_offset
if z_pos >= self.fade_end:
- return 0.
+ return 0.0
elif z_pos >= self.fade_start:
return (self.fade_end - z_pos) / self.fade_dist
else:
- return 1.
+ return 1.0
+
def get_position(self):
# Return last, non-transformed position
if self.z_mesh is None:
@@ -189,22 +217,24 @@ class BedMesh:
cur_pos = self.toolhead.get_position()
x, y, z = cur_pos[:3]
max_adj = self.z_mesh.calc_z(x, y)
- factor = 1.
+ factor = 1.0
z_adj = max_adj - self.fade_target
fade_z_pos = z + self.tool_offset
if min(fade_z_pos, (fade_z_pos - max_adj)) >= self.fade_end:
# Fade out is complete, no factor
- factor = 0.
+ factor = 0.0
elif max(fade_z_pos, (fade_z_pos - max_adj)) >= self.fade_start:
# Likely in the process of fading out adjustment.
# Because we don't yet know the gcode z position, use
# algebra to calculate the factor from the toolhead pos
- factor = ((self.fade_end + self.fade_target - fade_z_pos) /
- (self.fade_dist - z_adj))
- factor = constrain(factor, 0., 1.)
+ factor = (self.fade_end + self.fade_target - fade_z_pos) / (
+ self.fade_dist - z_adj
+ )
+ factor = constrain(factor, 0.0, 1.0)
final_z_adj = factor * z_adj + self.fade_target
self.last_position[:] = [x, y, z - final_z_adj] + cur_pos[3:]
return list(self.last_position)
+
def move(self, newpos, speed):
factor = self.get_z_factor(newpos[2])
if self.z_mesh is None or not factor:
@@ -214,7 +244,8 @@ class BedMesh:
self.log_fade_complete = False
logging.info(
"bed_mesh fade complete: Current Z: %.4f fade_target: %.4f "
- % (z, self.fade_target))
+ % (z, self.fade_target)
+ )
self.toolhead.move([x, y, z + self.fade_target] + newpos[3:], speed)
else:
self.splitter.build_move(self.last_position, newpos, factor)
@@ -223,36 +254,40 @@ class BedMesh:
if split_move:
self.toolhead.move(split_move, speed)
else:
- raise self.gcode.error(
- "Mesh Leveling: Error splitting move ")
+ raise self.gcode.error("Mesh Leveling: Error splitting move ")
self.last_position[:] = newpos
+
def get_status(self, eventtime=None):
return self.status
+
def update_status(self):
self.status = {
"profile_name": "",
- "mesh_min": (0., 0.),
- "mesh_max": (0., 0.),
+ "mesh_min": (0.0, 0.0),
+ "mesh_max": (0.0, 0.0),
"probed_matrix": [[]],
"mesh_matrix": [[]],
- "profiles": self.pmgr.get_profiles()
+ "profiles": self.pmgr.get_profiles(),
}
if self.z_mesh is not None:
params = self.z_mesh.get_mesh_params()
- mesh_min = (params['min_x'], params['min_y'])
- mesh_max = (params['max_x'], params['max_y'])
+ mesh_min = (params["min_x"], params["min_y"])
+ mesh_max = (params["max_x"], params["max_y"])
probed_matrix = self.z_mesh.get_probed_matrix()
mesh_matrix = self.z_mesh.get_mesh_matrix()
- self.status['profile_name'] = self.z_mesh.get_profile_name()
- self.status['mesh_min'] = mesh_min
- self.status['mesh_max'] = mesh_max
- self.status['probed_matrix'] = probed_matrix
- self.status['mesh_matrix'] = mesh_matrix
+ self.status["profile_name"] = self.z_mesh.get_profile_name()
+ self.status["mesh_min"] = mesh_min
+ self.status["mesh_max"] = mesh_max
+ self.status["probed_matrix"] = probed_matrix
+ self.status["mesh_matrix"] = mesh_matrix
+
def get_mesh(self):
return self.z_mesh
+
cmd_BED_MESH_OUTPUT_help = "Retrieve interpolated grid of probed z-points"
+
def cmd_BED_MESH_OUTPUT(self, gcmd):
- if gcmd.get_int('PGP', 0):
+ if gcmd.get_int("PGP", 0):
# Print Generated Points instead of mesh
self.bmc.print_generated_points(gcmd.respond_info)
elif self.z_mesh is None:
@@ -260,34 +295,42 @@ class BedMesh:
else:
self.z_mesh.print_probed_matrix(gcmd.respond_info)
self.z_mesh.print_mesh(gcmd.respond_raw, self.horizontal_move_z)
+
cmd_BED_MESH_MAP_help = "Serialize mesh and output to terminal"
+
def cmd_BED_MESH_MAP(self, gcmd):
if self.z_mesh is not None:
params = self.z_mesh.get_mesh_params()
outdict = {
- 'mesh_min': (params['min_x'], params['min_y']),
- 'mesh_max': (params['max_x'], params['max_y']),
- 'z_positions': self.z_mesh.get_probed_matrix()}
+ "mesh_min": (params["min_x"], params["min_y"]),
+ "mesh_max": (params["max_x"], params["max_y"]),
+ "z_positions": self.z_mesh.get_probed_matrix(),
+ }
gcmd.respond_raw("mesh_map_output " + json.dumps(outdict))
else:
gcmd.respond_info("Bed has not been probed")
+
cmd_BED_MESH_CLEAR_help = "Clear the Mesh so no z-adjustment is made"
+
def cmd_BED_MESH_CLEAR(self, gcmd):
self.set_mesh(None)
+
cmd_BED_MESH_OFFSET_help = "Add X/Y offsets to the mesh lookup"
+
def cmd_BED_MESH_OFFSET(self, gcmd):
if self.z_mesh is not None:
offsets = [None, None]
- for i, axis in enumerate(['X', 'Y']):
+ for i, axis in enumerate(["X", "Y"]):
offsets[i] = gcmd.get_float(axis, None)
self.z_mesh.set_mesh_offsets(offsets)
tool_offset = gcmd.get_float("ZFADE", None)
if tool_offset is not None:
self.tool_offset = tool_offset
- gcode_move = self.printer.lookup_object('gcode_move')
+ gcode_move = self.printer.lookup_object("gcode_move")
gcode_move.reset_last_position()
else:
gcmd.respond_info("No mesh loaded to offset")
+
def _handle_dump_request(self, web_request):
eventtime = self.printer.get_reactor().monotonic()
prb = self.printer.lookup_object("probe", None)
@@ -298,7 +341,7 @@ class BedMesh:
"name": self.z_mesh.get_profile_name(),
"probed_matrix": self.z_mesh.get_probed_matrix(),
"mesh_matrix": self.z_mesh.get_mesh_matrix(),
- "mesh_params": self.z_mesh.get_mesh_params()
+ "mesh_params": self.z_mesh.get_mesh_params(),
}
mesh_args = web_request.get_dict("mesh_args", {})
gcmd = None
@@ -317,43 +360,43 @@ class BedMesh:
class ZrefMode:
DISABLED = 0 # Zero reference disabled
- IN_MESH = 1 # Zero reference position within mesh
- PROBE = 2 # Zero refrennce position outside of mesh, probe needed
+ IN_MESH = 1 # Zero reference position within mesh
+ PROBE = 2 # Zero refrennce position outside of mesh, probe needed
class BedMeshCalibrate:
- ALGOS = ['lagrange', 'bicubic']
+ ALGOS = ["lagrange", "bicubic"]
+
def __init__(self, config, bedmesh):
self.printer = config.get_printer()
- self.orig_config = {'radius': None, 'origin': None}
+ self.orig_config = {"radius": None, "origin": None}
self.radius = self.origin = None
- self.mesh_min = self.mesh_max = (0., 0.)
- self.adaptive_margin = config.getfloat('adaptive_margin', 0.0)
+ self.mesh_min = self.mesh_max = (0.0, 0.0)
+ self.adaptive_margin = config.getfloat("adaptive_margin", 0.0)
self.bedmesh = bedmesh
self.mesh_config = collections.OrderedDict()
self._init_mesh_config(config)
- self.probe_mgr = ProbeManager(
- config, self.orig_config, self.probe_finalize
- )
+ self.probe_mgr = ProbeManager(config, self.orig_config, self.probe_finalize)
try:
self.probe_mgr.generate_points(
- self.mesh_config, self.mesh_min, self.mesh_max,
- self.radius, self.origin
+ self.mesh_config, self.mesh_min, self.mesh_max, self.radius, self.origin
)
except BedMeshError as e:
raise config.error(str(e))
self._profile_name = "default"
- self.gcode = self.printer.lookup_object('gcode')
+ self.gcode = self.printer.lookup_object("gcode")
self.gcode.register_command(
- 'BED_MESH_CALIBRATE', self.cmd_BED_MESH_CALIBRATE,
- desc=self.cmd_BED_MESH_CALIBRATE_help)
+ "BED_MESH_CALIBRATE",
+ self.cmd_BED_MESH_CALIBRATE,
+ desc=self.cmd_BED_MESH_CALIBRATE_help,
+ )
+
def print_generated_points(self, print_func, truncate=False):
- x_offset = y_offset = 0.
- probe = self.printer.lookup_object('probe', None)
+ x_offset = y_offset = 0.0
+ probe = self.printer.lookup_object("probe", None)
if probe is not None:
x_offset, y_offset = probe.get_offsets()[:2]
- print_func("bed_mesh: generated points\nIndex"
- " | Tool Adjusted | Probe")
+ print_func("bed_mesh: generated points\nIndex" " | Tool Adjusted | Probe")
points = self.probe_mgr.get_base_points()
for i, (x, y) in enumerate(points):
if i >= 50 and truncate:
@@ -362,8 +405,7 @@ class BedMeshCalibrate:
break
adj_pt = "(%.1f, %.1f)" % (x - x_offset, y - y_offset)
mesh_pt = "(%.1f, %.1f)" % (x, y)
- print_func(
- " %-4d| %-16s| %s" % (i, adj_pt, mesh_pt))
+ print_func(" %-4d| %-16s| %s" % (i, adj_pt, mesh_pt))
zero_ref_pos = self.probe_mgr.get_zero_ref_pos()
if zero_ref_pos is not None:
print_func(
@@ -375,83 +417,90 @@ class BedMeshCalibrate:
print_func("bed_mesh: faulty region points")
for i, v in substitutes.items():
pt = points[i]
- print_func("%d (%.2f, %.2f), substituted points: %s"
- % (i, pt[0], pt[1], repr(v)))
+ print_func(
+ "%d (%.2f, %.2f), substituted points: %s"
+ % (i, pt[0], pt[1], repr(v))
+ )
+
def _init_mesh_config(self, config):
mesh_cfg = self.mesh_config
orig_cfg = self.orig_config
- self.radius = config.getfloat('mesh_radius', None, above=0.)
+ self.radius = config.getfloat("mesh_radius", None, above=0.0)
if self.radius is not None:
- self.origin = config.getfloatlist('mesh_origin', (0., 0.), count=2)
- x_cnt = y_cnt = config.getint('round_probe_count', 5, minval=3)
+ self.origin = config.getfloatlist("mesh_origin", (0.0, 0.0), count=2)
+ x_cnt = y_cnt = config.getint("round_probe_count", 5, minval=3)
# round beds must have an odd number of points along each axis
if not x_cnt & 1:
- raise config.error(
- "bed_mesh: probe_count must be odd for round beds")
+ raise config.error("bed_mesh: probe_count must be odd for round beds")
# radius may have precision to .1mm
self.radius = math.floor(self.radius * 10) / 10
- orig_cfg['radius'] = self.radius
- orig_cfg['origin'] = self.origin
+ orig_cfg["radius"] = self.radius
+ orig_cfg["origin"] = self.origin
min_x = min_y = -self.radius
max_x = max_y = self.radius
else:
# rectangular
- x_cnt, y_cnt = parse_config_pair(config, 'probe_count', 3, minval=3)
- min_x, min_y = config.getfloatlist('mesh_min', count=2)
- max_x, max_y = config.getfloatlist('mesh_max', count=2)
+ x_cnt, y_cnt = parse_config_pair(config, "probe_count", 3, minval=3)
+ min_x, min_y = config.getfloatlist("mesh_min", count=2)
+ max_x, max_y = config.getfloatlist("mesh_max", count=2)
if max_x <= min_x or max_y <= min_y:
- raise config.error('bed_mesh: invalid min/max points')
- orig_cfg['x_count'] = mesh_cfg['x_count'] = x_cnt
- orig_cfg['y_count'] = mesh_cfg['y_count'] = y_cnt
- orig_cfg['mesh_min'] = self.mesh_min = (min_x, min_y)
- orig_cfg['mesh_max'] = self.mesh_max = (max_x, max_y)
+ raise config.error("bed_mesh: invalid min/max points")
+ orig_cfg["x_count"] = mesh_cfg["x_count"] = x_cnt
+ orig_cfg["y_count"] = mesh_cfg["y_count"] = y_cnt
+ orig_cfg["mesh_min"] = self.mesh_min = (min_x, min_y)
+ orig_cfg["mesh_max"] = self.mesh_max = (max_x, max_y)
- pps = parse_config_pair(config, 'mesh_pps', 2, minval=0)
- orig_cfg['mesh_x_pps'] = mesh_cfg['mesh_x_pps'] = pps[0]
- orig_cfg['mesh_y_pps'] = mesh_cfg['mesh_y_pps'] = pps[1]
- orig_cfg['algo'] = mesh_cfg['algo'] = \
- config.get('algorithm', 'lagrange').strip().lower()
- orig_cfg['tension'] = mesh_cfg['tension'] = config.getfloat(
- 'bicubic_tension', .2, minval=0., maxval=2.)
+ pps = parse_config_pair(config, "mesh_pps", 2, minval=0)
+ orig_cfg["mesh_x_pps"] = mesh_cfg["mesh_x_pps"] = pps[0]
+ orig_cfg["mesh_y_pps"] = mesh_cfg["mesh_y_pps"] = pps[1]
+ orig_cfg["algo"] = mesh_cfg["algo"] = (
+ config.get("algorithm", "lagrange").strip().lower()
+ )
+ orig_cfg["tension"] = mesh_cfg["tension"] = config.getfloat(
+ "bicubic_tension", 0.2, minval=0.0, maxval=2.0
+ )
self._verify_algorithm(config.error)
+
def _verify_algorithm(self, error):
params = self.mesh_config
- x_pps = params['mesh_x_pps']
- y_pps = params['mesh_y_pps']
- if params['algo'] not in self.ALGOS:
- raise error(
- "bed_mesh: Unknown algorithm <%s>"
- % (self.mesh_config['algo']))
+ x_pps = params["mesh_x_pps"]
+ y_pps = params["mesh_y_pps"]
+ if params["algo"] not in self.ALGOS:
+ raise error("bed_mesh: Unknown algorithm <%s>" % (self.mesh_config["algo"]))
# Check the algorithm against the current configuration
- max_probe_cnt = max(params['x_count'], params['y_count'])
- min_probe_cnt = min(params['x_count'], params['y_count'])
+ max_probe_cnt = max(params["x_count"], params["y_count"])
+ min_probe_cnt = min(params["x_count"], params["y_count"])
if max(x_pps, y_pps) == 0:
# Interpolation disabled
- self.mesh_config['algo'] = 'direct'
- elif params['algo'] == 'lagrange' and max_probe_cnt > 6:
+ self.mesh_config["algo"] = "direct"
+ elif params["algo"] == "lagrange" and max_probe_cnt > 6:
# Lagrange interpolation tends to oscillate when using more
# than 6 samples
raise error(
"bed_mesh: cannot exceed a probe_count of 6 when using "
- "lagrange interpolation. Configured Probe Count: %d, %d" %
- (self.mesh_config['x_count'], self.mesh_config['y_count']))
- elif params['algo'] == 'bicubic' and min_probe_cnt < 4:
+ "lagrange interpolation. Configured Probe Count: %d, %d"
+ % (self.mesh_config["x_count"], self.mesh_config["y_count"])
+ )
+ elif params["algo"] == "bicubic" and min_probe_cnt < 4:
if max_probe_cnt > 6:
raise error(
"bed_mesh: invalid probe_count option when using bicubic "
"interpolation. Combination of 3 points on one axis with "
"more than 6 on another is not permitted. "
- "Configured Probe Count: %d, %d" %
- (self.mesh_config['x_count'], self.mesh_config['y_count']))
+ "Configured Probe Count: %d, %d"
+ % (self.mesh_config["x_count"], self.mesh_config["y_count"])
+ )
else:
logging.info(
"bed_mesh: bicubic interpolation with a probe_count of "
"less than 4 points detected. Forcing lagrange "
- "interpolation. Configured Probe Count: %d, %d" %
- (self.mesh_config['x_count'], self.mesh_config['y_count']))
- params['algo'] = 'lagrange'
+ "interpolation. Configured Probe Count: %d, %d"
+ % (self.mesh_config["x_count"], self.mesh_config["y_count"])
+ )
+ params["algo"] = "lagrange"
+
def set_adaptive_mesh(self, gcmd):
- if not gcmd.get_int('ADAPTIVE', 0):
+ if not gcmd.get_int("ADAPTIVE", 0):
return False
exclude_objects = self.printer.lookup_object("exclude_object", None)
if exclude_objects is None:
@@ -460,7 +509,7 @@ class BedMeshCalibrate:
objects = exclude_objects.get_status().get("objects", [])
if not objects:
return False
- margin = gcmd.get_float('ADAPTIVE_MARGIN', self.adaptive_margin)
+ margin = gcmd.get_float("ADAPTIVE_MARGIN", self.adaptive_margin)
# List all exclude_object points by axis and iterate over
# all polygon points, and pick the min and max or each axis
@@ -479,68 +528,83 @@ class BedMeshCalibrate:
adjusted_mesh_max = [x + margin for x in mesh_max]
# Force margin to respect original mesh bounds
- adjusted_mesh_min[0] = max(adjusted_mesh_min[0],
- self.orig_config["mesh_min"][0])
- adjusted_mesh_min[1] = max(adjusted_mesh_min[1],
- self.orig_config["mesh_min"][1])
- adjusted_mesh_max[0] = min(adjusted_mesh_max[0],
- self.orig_config["mesh_max"][0])
- adjusted_mesh_max[1] = min(adjusted_mesh_max[1],
- self.orig_config["mesh_max"][1])
+ adjusted_mesh_min[0] = max(
+ adjusted_mesh_min[0], self.orig_config["mesh_min"][0]
+ )
+ adjusted_mesh_min[1] = max(
+ adjusted_mesh_min[1], self.orig_config["mesh_min"][1]
+ )
+ adjusted_mesh_max[0] = min(
+ adjusted_mesh_max[0], self.orig_config["mesh_max"][0]
+ )
+ adjusted_mesh_max[1] = min(
+ adjusted_mesh_max[1], self.orig_config["mesh_max"][1]
+ )
- adjusted_mesh_size = (adjusted_mesh_max[0] - adjusted_mesh_min[0],
- adjusted_mesh_max[1] - adjusted_mesh_min[1])
+ adjusted_mesh_size = (
+ adjusted_mesh_max[0] - adjusted_mesh_min[0],
+ adjusted_mesh_max[1] - adjusted_mesh_min[1],
+ )
# Compute a ratio between the adapted and original sizes
- ratio = (adjusted_mesh_size[0] /
- (self.orig_config["mesh_max"][0] -
- self.orig_config["mesh_min"][0]),
- adjusted_mesh_size[1] /
- (self.orig_config["mesh_max"][1] -
- self.orig_config["mesh_min"][1]))
+ ratio = (
+ adjusted_mesh_size[0]
+ / (self.orig_config["mesh_max"][0] - self.orig_config["mesh_min"][0]),
+ adjusted_mesh_size[1]
+ / (self.orig_config["mesh_max"][1] - self.orig_config["mesh_min"][1]),
+ )
- gcmd.respond_info("Original mesh bounds: (%s,%s)" %
- (self.orig_config["mesh_min"],
- self.orig_config["mesh_max"]))
- gcmd.respond_info("Original probe count: (%s,%s)" %
- (self.mesh_config["x_count"],
- self.mesh_config["y_count"]))
- gcmd.respond_info("Adapted mesh bounds: (%s,%s)" %
- (adjusted_mesh_min, adjusted_mesh_max))
+ gcmd.respond_info(
+ "Original mesh bounds: (%s,%s)"
+ % (self.orig_config["mesh_min"], self.orig_config["mesh_max"])
+ )
+ gcmd.respond_info(
+ "Original probe count: (%s,%s)"
+ % (self.mesh_config["x_count"], self.mesh_config["y_count"])
+ )
+ gcmd.respond_info(
+ "Adapted mesh bounds: (%s,%s)" % (adjusted_mesh_min, adjusted_mesh_max)
+ )
gcmd.respond_info("Ratio: (%s, %s)" % ratio)
- new_x_probe_count = int(
- math.ceil(self.mesh_config["x_count"] * ratio[0]))
- new_y_probe_count = int(
- math.ceil(self.mesh_config["y_count"] * ratio[1]))
+ new_x_probe_count = int(math.ceil(self.mesh_config["x_count"] * ratio[0]))
+ new_y_probe_count = int(math.ceil(self.mesh_config["y_count"] * ratio[1]))
# There is one case, where we may have to adjust the probe counts:
# axis0 < 4 and axis1 > 6 (see _verify_algorithm).
min_num_of_probes = 3
- if max(new_x_probe_count, new_y_probe_count) > 6 and \
- min(new_x_probe_count, new_y_probe_count) < 4:
+ if (
+ max(new_x_probe_count, new_y_probe_count) > 6
+ and min(new_x_probe_count, new_y_probe_count) < 4
+ ):
min_num_of_probes = 4
new_x_probe_count = max(min_num_of_probes, new_x_probe_count)
new_y_probe_count = max(min_num_of_probes, new_y_probe_count)
- gcmd.respond_info("Adapted probe count: (%s,%s)" %
- (new_x_probe_count, new_y_probe_count))
+ gcmd.respond_info(
+ "Adapted probe count: (%s,%s)" % (new_x_probe_count, new_y_probe_count)
+ )
# If the adapted mesh size is too small, adjust it to something
# useful.
- adjusted_mesh_size = (max(adjusted_mesh_size[0], new_x_probe_count),
- max(adjusted_mesh_size[1], new_y_probe_count))
+ adjusted_mesh_size = (
+ max(adjusted_mesh_size[0], new_x_probe_count),
+ max(adjusted_mesh_size[1], new_y_probe_count),
+ )
if self.radius is not None:
- adapted_radius = math.sqrt((adjusted_mesh_size[0] ** 2) +
- (adjusted_mesh_size[1] ** 2)) / 2
- adapted_origin = (adjusted_mesh_min[0] +
- (adjusted_mesh_size[0] / 2),
- adjusted_mesh_min[1] +
- (adjusted_mesh_size[1] / 2))
- to_adapted_origin = math.sqrt(adapted_origin[0]**2 +
- adapted_origin[1]**2)
+ adapted_radius = (
+ math.sqrt((adjusted_mesh_size[0] ** 2) + (adjusted_mesh_size[1] ** 2))
+ / 2
+ )
+ adapted_origin = (
+ adjusted_mesh_min[0] + (adjusted_mesh_size[0] / 2),
+ adjusted_mesh_min[1] + (adjusted_mesh_size[1] / 2),
+ )
+ to_adapted_origin = math.sqrt(
+ adapted_origin[0] ** 2 + adapted_origin[1] ** 2
+ )
# If the adapted mesh size is smaller than the default/full
# mesh, adjust the parameters. Otherwise, just do the full mesh.
if adapted_radius + to_adapted_origin < self.radius:
@@ -551,8 +615,9 @@ class BedMeshCalibrate:
new_probe_count = max(new_x_probe_count, new_y_probe_count)
# Adaptive meshes require odd number of points
new_probe_count += 1 - (new_probe_count % 2)
- self.mesh_config["x_count"] = self.mesh_config["y_count"] = \
- new_probe_count
+ self.mesh_config["x_count"] = self.mesh_config["y_count"] = (
+ new_probe_count
+ )
else:
self.mesh_min = adjusted_mesh_min
self.mesh_max = adjusted_mesh_max
@@ -560,12 +625,13 @@ class BedMeshCalibrate:
self.mesh_config["y_count"] = new_y_probe_count
self._profile_name = None
return True
+
def update_config(self, gcmd):
# reset default configuration
- self.radius = self.orig_config['radius']
- self.origin = self.orig_config['origin']
- self.mesh_min = self.orig_config['mesh_min']
- self.mesh_max = self.orig_config['mesh_max']
+ self.radius = self.orig_config["radius"]
+ self.origin = self.orig_config["origin"]
+ self.mesh_min = self.orig_config["mesh_min"]
+ self.mesh_max = self.orig_config["mesh_max"]
for key in list(self.mesh_config.keys()):
self.mesh_config[key] = self.orig_config[key]
@@ -579,34 +645,34 @@ class BedMeshCalibrate:
self.mesh_max = (self.radius, self.radius)
need_cfg_update = True
if "MESH_ORIGIN" in params:
- self.origin = parse_gcmd_coord(gcmd, 'MESH_ORIGIN')
+ self.origin = parse_gcmd_coord(gcmd, "MESH_ORIGIN")
need_cfg_update = True
if "ROUND_PROBE_COUNT" in params:
- cnt = gcmd.get_int('ROUND_PROBE_COUNT', minval=3)
- self.mesh_config['x_count'] = cnt
- self.mesh_config['y_count'] = cnt
+ cnt = gcmd.get_int("ROUND_PROBE_COUNT", minval=3)
+ self.mesh_config["x_count"] = cnt
+ self.mesh_config["y_count"] = cnt
need_cfg_update = True
else:
if "MESH_MIN" in params:
- self.mesh_min = parse_gcmd_coord(gcmd, 'MESH_MIN')
+ self.mesh_min = parse_gcmd_coord(gcmd, "MESH_MIN")
need_cfg_update = True
if "MESH_MAX" in params:
- self.mesh_max = parse_gcmd_coord(gcmd, 'MESH_MAX')
+ self.mesh_max = parse_gcmd_coord(gcmd, "MESH_MAX")
need_cfg_update = True
if "PROBE_COUNT" in params:
- x_cnt, y_cnt = parse_gcmd_pair(gcmd, 'PROBE_COUNT', minval=3)
- self.mesh_config['x_count'] = x_cnt
- self.mesh_config['y_count'] = y_cnt
+ x_cnt, y_cnt = parse_gcmd_pair(gcmd, "PROBE_COUNT", minval=3)
+ self.mesh_config["x_count"] = x_cnt
+ self.mesh_config["y_count"] = y_cnt
need_cfg_update = True
if "MESH_PPS" in params:
- xpps, ypps = parse_gcmd_pair(gcmd, 'MESH_PPS', minval=0)
- self.mesh_config['mesh_x_pps'] = xpps
- self.mesh_config['mesh_y_pps'] = ypps
+ xpps, ypps = parse_gcmd_pair(gcmd, "MESH_PPS", minval=0)
+ self.mesh_config["mesh_x_pps"] = xpps
+ self.mesh_config["mesh_y_pps"] = ypps
need_cfg_update = True
if "ALGORITHM" in params:
- self.mesh_config['algo'] = gcmd.get('ALGORITHM').strip().lower()
+ self.mesh_config["algo"] = gcmd.get("ALGORITHM").strip().lower()
need_cfg_update = True
need_cfg_update |= self.set_adaptive_mesh(gcmd)
@@ -615,17 +681,25 @@ class BedMeshCalibrate:
if need_cfg_update:
self._verify_algorithm(gcmd.error)
self.probe_mgr.generate_points(
- self.mesh_config, self.mesh_min, self.mesh_max,
- self.radius, self.origin, probe_method
+ self.mesh_config,
+ self.mesh_min,
+ self.mesh_max,
+ self.radius,
+ self.origin,
+ probe_method,
)
- msg = "\n".join(["%s: %s" % (k, v)
- for k, v in self.mesh_config.items()])
+ msg = "\n".join(["%s: %s" % (k, v) for k, v in self.mesh_config.items()])
logging.info("Updated Mesh Configuration:\n" + msg)
else:
self.probe_mgr.generate_points(
- self.mesh_config, self.mesh_min, self.mesh_max,
- self.radius, self.origin, probe_method
+ self.mesh_config,
+ self.mesh_min,
+ self.mesh_max,
+ self.radius,
+ self.origin,
+ probe_method,
)
+
def dump_calibration(self, gcmd=None):
if gcmd is not None and gcmd.get_command_parameters():
self.update_config(gcmd)
@@ -638,11 +712,13 @@ class BedMeshCalibrate:
"points": self.probe_mgr.get_base_points(),
"config": cfg,
"probe_path": self.probe_mgr.get_std_path(),
- "rapid_path": list(self.probe_mgr.iter_rapid_path())
+ "rapid_path": list(self.probe_mgr.iter_rapid_path()),
}
+
cmd_BED_MESH_CALIBRATE_help = "Perform Mesh Bed Leveling"
+
def cmd_BED_MESH_CALIBRATE(self, gcmd):
- self._profile_name = gcmd.get('PROFILE', "default")
+ self._profile_name = gcmd.get("PROFILE", "default")
if not self._profile_name.strip():
raise gcmd.error("Value for parameter 'PROFILE' must be specified")
self.bedmesh.set_mesh(None)
@@ -651,26 +727,25 @@ class BedMeshCalibrate:
except BedMeshError as e:
raise gcmd.error(str(e))
self.probe_mgr.start_probe(gcmd)
+
def probe_finalize(self, offsets, positions):
z_offset = offsets[2]
- positions = [[round(p[0], 2), round(p[1], 2), p[2]]
- for p in positions]
+ positions = [[round(p[0], 2), round(p[1], 2), p[2]] for p in positions]
if self.probe_mgr.get_zero_ref_mode() == ZrefMode.PROBE:
ref_pos = positions.pop()
logging.info(
"bed_mesh: z-offset replaced with probed z value at "
- "position (%.2f, %.2f, %.6f)"
- % (ref_pos[0], ref_pos[1], ref_pos[2])
+ "position (%.2f, %.2f, %.6f)" % (ref_pos[0], ref_pos[1], ref_pos[2])
)
z_offset = ref_pos[2]
base_points = self.probe_mgr.get_base_points()
params = dict(self.mesh_config)
- params['min_x'] = min(base_points, key=lambda p: p[0])[0]
- params['max_x'] = max(base_points, key=lambda p: p[0])[0]
- params['min_y'] = min(base_points, key=lambda p: p[1])[1]
- params['max_y'] = max(base_points, key=lambda p: p[1])[1]
- x_cnt = params['x_count']
- y_cnt = params['y_count']
+ params["min_x"] = min(base_points, key=lambda p: p[0])[0]
+ params["max_x"] = max(base_points, key=lambda p: p[0])[0]
+ params["min_y"] = min(base_points, key=lambda p: p[1])[1]
+ params["max_y"] = max(base_points, key=lambda p: p[1])[1]
+ x_cnt = params["x_count"]
+ y_cnt = params["y_count"]
substitutes = self.probe_mgr.get_substitutes()
probed_pts = positions
@@ -687,15 +762,15 @@ class BedMeshCalibrate:
idx = i + idx_offset
# Add "normal" points
corrected_pts.extend(positions[start_idx:idx])
- avg_z = sum([p[2] for p in positions[idx:idx+len(pts)]]) \
- / len(pts)
+ avg_z = sum([p[2] for p in positions[idx : idx + len(pts)]]) / len(pts)
idx_offset += len(pts) - 1
start_idx = idx + len(pts)
fpt.append(avg_z)
logging.info(
"bed_mesh: Replacing value at faulty index %d"
" (%.4f, %.4f): avg value = %.6f, avg w/ z_offset = %.6f"
- % (i, fpt[0], fpt[1], avg_z, avg_z - z_offset))
+ % (i, fpt[0], fpt[1], avg_z, avg_z - z_offset)
+ )
corrected_pts.append(fpt)
corrected_pts.extend(positions[start_idx:])
positions = corrected_pts
@@ -714,9 +789,8 @@ class BedMeshCalibrate:
prev_pos = base_points[0]
for pos, result in zip(base_points, positions):
offset_pos = [p - o for p, o in zip(pos, offsets[:2])]
- if (
- not isclose(offset_pos[0], result[0], abs_tol=.5) or
- not isclose(offset_pos[1], result[1], abs_tol=.5)
+ if not isclose(offset_pos[0], result[0], abs_tol=0.5) or not isclose(
+ offset_pos[1], result[1], abs_tol=0.5
):
logging.info(
"bed_mesh: point deviation > .5mm: orig pt = (%.2f, %.2f)"
@@ -724,7 +798,7 @@ class BedMeshCalibrate:
% (offset_pos[0], offset_pos[1], result[0], result[1])
)
z_pos = result[2] - z_offset
- if not isclose(pos[1], prev_pos[1], abs_tol=.1):
+ if not isclose(pos[1], prev_pos[1], abs_tol=0.1):
# y has changed, append row and start new
probed_matrix.append(row)
row = []
@@ -741,9 +815,12 @@ class BedMeshCalibrate:
# make sure the y-axis is the correct length
if len(probed_matrix) != y_cnt:
raise self.gcode.error(
- ("bed_mesh: Invalid y-axis table length\n"
- "Probed table length: %d Probed Table:\n%s") %
- (len(probed_matrix), str(probed_matrix)))
+ (
+ "bed_mesh: Invalid y-axis table length\n"
+ "Probed table length: %d Probed Table:\n%s"
+ )
+ % (len(probed_matrix), str(probed_matrix))
+ )
if self.radius is not None:
# round bed, extrapolate probed values to create a square mesh
@@ -759,7 +836,7 @@ class BedMeshCalibrate:
if buf_cnt == 0:
continue
left_buffer = [row[0]] * buf_cnt
- right_buffer = [row[row_size-1]] * buf_cnt
+ right_buffer = [row[row_size - 1]] * buf_cnt
row[0:0] = left_buffer
row.extend(right_buffer)
@@ -767,9 +844,12 @@ class BedMeshCalibrate:
for row in probed_matrix:
if len(row) != x_cnt:
raise self.gcode.error(
- ("bed_mesh: invalid x-axis table length\n"
- "Probed table length: %d Probed Table:\n%s") %
- (len(probed_matrix), str(probed_matrix)))
+ (
+ "bed_mesh: invalid x-axis table length\n"
+ "Probed table length: %d Probed Table:\n%s"
+ )
+ % (len(probed_matrix), str(probed_matrix))
+ )
z_mesh = ZMesh(params, self._profile_name)
try:
@@ -786,6 +866,7 @@ class BedMeshCalibrate:
self.gcode.respond_info("Mesh Bed Leveling Complete")
if self._profile_name is not None:
self.bedmesh.save_profile(self._profile_name)
+
def _dump_points(self, probed_pts, corrected_pts, offsets):
# logs generated points with offset applied, points received
# from the finalize callback, and the list of corrected points
@@ -793,7 +874,8 @@ class BedMeshCalibrate:
max_len = max([len(points), len(probed_pts), len(corrected_pts)])
logging.info(
"bed_mesh: calibration point dump\nIndex | %-17s| %-25s|"
- " Corrected Point" % ("Generated Point", "Probed Point"))
+ " Corrected Point" % ("Generated Point", "Probed Point")
+ )
for i in list(range(max_len)):
gen_pt = probed_pt = corr_pt = ""
if i < len(points):
@@ -803,13 +885,13 @@ class BedMeshCalibrate:
probed_pt = "(%.2f, %.2f, %.4f)" % tuple(probed_pts[i])
if i < len(corrected_pts):
corr_pt = "(%.2f, %.2f, %.4f)" % tuple(corrected_pts[i])
- logging.info(
- " %-4d| %-17s| %-25s| %s" % (i, gen_pt, probed_pt, corr_pt))
+ logging.info(" %-4d| %-17s| %-25s| %s" % (i, gen_pt, probed_pt, corr_pt))
+
class ProbeManager:
def __init__(self, config, orig_config, finalize_cb):
self.printer = config.get_printer()
- self.cfg_overshoot = config.getfloat("scan_overshoot", 0, minval=1.)
+ self.cfg_overshoot = config.getfloat("scan_overshoot", 0, minval=1.0)
self.orig_config = orig_config
self.faulty_regions = []
self.overshoot = self.cfg_overshoot
@@ -827,8 +909,7 @@ class ProbeManager:
def _init_faulty_regions(self, config):
for i in list(range(1, 100, 1)):
- start = config.getfloatlist("faulty_region_%d_min" % (i,), None,
- count=2)
+ start = config.getfloatlist("faulty_region_%d_min" % (i,), None, count=2)
if start is None:
break
end = config.getfloatlist("faulty_region_%d_max" % (i,), count=2)
@@ -851,16 +932,16 @@ class ProbeManager:
raise config.error(
"bed_mesh: Existing faulty_region_%d %s overlaps "
"added faulty_region_%d %s"
- % (j+1, repr([prev_c1, prev_c3]),
- i, repr([c1, c3])))
+ % (j + 1, repr([prev_c1, prev_c3]), i, repr([c1, c3]))
+ )
# Validate that no new corner is within an existing region
for coord in [c1, c2, c3, c4]:
if within(coord, prev_c1, prev_c3):
raise config.error(
"bed_mesh: Added faulty_region_%d %s overlaps "
"existing faulty_region_%d %s"
- % (i, repr([c1, c3]),
- j+1, repr([prev_c1, prev_c3])))
+ % (i, repr([c1, c3]), j + 1, repr([prev_c1, prev_c3]))
+ )
self.faulty_regions.append((c1, c3))
def start_probe(self, gcmd):
@@ -885,11 +966,10 @@ class ProbeManager:
return self.substitutes
def generate_points(
- self, mesh_config, mesh_min, mesh_max, radius, origin,
- probe_method="automatic"
+ self, mesh_config, mesh_min, mesh_max, radius, origin, probe_method="automatic"
):
- x_cnt = mesh_config['x_count']
- y_cnt = mesh_config['y_count']
+ x_cnt = mesh_config["x_count"]
+ y_cnt = mesh_config["y_count"]
min_x, min_y = mesh_min
max_x, max_y = mesh_max
x_dist = (max_x - min_x) / (x_cnt - 1)
@@ -897,7 +977,7 @@ class ProbeManager:
# floor distances down to next hundredth
x_dist = math.floor(x_dist * 100) / 100
y_dist = math.floor(y_dist * 100) / 100
- if x_dist < 1. or y_dist < 1.:
+ if x_dist < 1.0 or y_dist < 1.0:
raise BedMeshError("bed_mesh: min/max points too close together")
if radius is not None:
@@ -924,10 +1004,9 @@ class ProbeManager:
points.append((pos_x, pos_y))
else:
# round bed, check distance from origin
- dist_from_origin = math.sqrt(pos_x*pos_x + pos_y*pos_y)
+ dist_from_origin = math.sqrt(pos_x * pos_x + pos_y * pos_y)
if dist_from_origin <= radius:
- points.append(
- (origin[0] + pos_x, origin[1] + pos_y))
+ points.append((origin[0] + pos_x, origin[1] + pos_y))
pos_y += y_dist
if self.zero_ref_pos is None or probe_method == "manual":
# Zero Reference Disabled
@@ -961,7 +1040,11 @@ class ProbeManager:
"bed_mesh: Cannot probe zero reference position at "
"(%.2f, %.2f) as it is located within a faulty region."
" Check the value for option '%s'"
- % (self.zero_ref_pos[0], self.zero_ref_pos[1], opt,)
+ % (
+ self.zero_ref_pos[0],
+ self.zero_ref_pos[1],
+ opt,
+ )
)
# Check to see if any points fall within faulty regions
last_y = self.base_points[0][1]
@@ -972,11 +1055,14 @@ class ProbeManager:
last_y = coord[1]
adj_coords = []
for min_c, max_c in self.faulty_regions:
- if within(coord, min_c, max_c, tol=.00001):
+ if within(coord, min_c, max_c, tol=0.00001):
# Point lies within a faulty region
adj_coords = [
- (min_c[0], coord[1]), (coord[0], min_c[1]),
- (coord[0], max_c[1]), (max_c[0], coord[1])]
+ (min_c[0], coord[1]),
+ (coord[0], min_c[1]),
+ (coord[0], max_c[1]),
+ (max_c[0], coord[1]),
+ ]
if is_reversed:
# Swap first and last points for zig-zag pattern
first = adj_coords[0]
@@ -990,10 +1076,10 @@ class ProbeManager:
for ac in adj_coords:
# make sure that coordinates are within the mesh boundary
if radius is None:
- if within(ac, min_pt, max_pt, .000001):
+ if within(ac, min_pt, max_pt, 0.000001):
valid_coords.append(ac)
else:
- dist_from_origin = math.sqrt(ac[0]*ac[0] + ac[1]*ac[1])
+ dist_from_origin = math.sqrt(ac[0] * ac[0] + ac[1] * ac[1])
if dist_from_origin <= radius:
valid_coords.append(ac)
if not valid_coords:
@@ -1030,9 +1116,7 @@ class ProbeManager:
# increasing Y indicates direction change
dir_change = not isclose(pt[1], last_base_pt[1], abs_tol=1e-6)
if idx in self.substitutes:
- fp_gen = self._gen_faulty_path(
- last_mv_pt, idx, ascnd_x, dir_change
- )
+ fp_gen = self._gen_faulty_path(last_mv_pt, idx, ascnd_x, dir_change)
for sub_pt, is_smp in fp_gen:
yield sub_pt, is_smp
last_mv_pt = sub_pt
@@ -1113,10 +1197,10 @@ class ProbeManager:
return
# overshoot X beyond the outer point
xdir = 1 if ascnd_x else -1
- overshoot = 2. if self.overshoot >= 3. else self.overshoot
+ overshoot = 2.0 if self.overshoot >= 3.0 else self.overshoot
ovr_pt = (last_pt[0] + overshoot * xdir, last_pt[1])
yield ovr_pt
- if self.overshoot < 3.:
+ if self.overshoot < 3.0:
# No room to generate an arc, move up to next y
yield (next_pt[0] + overshoot * xdir, next_pt[1])
else:
@@ -1130,17 +1214,16 @@ class ProbeManager:
origin = [ovr_pt[0], last_pt[1] + radius]
next_origin_y = next_pt[1] - radius
# determine angle
- if xdiff < .01:
+ if xdiff < 0.01:
# Move is aligned on the x-axis
angle = 90
- if next_origin_y - origin[1] < .05:
+ if next_origin_y - origin[1] < 0.05:
# The move can be completed in a single arc
angle = 180
else:
angle = int(math.degrees(math.atan(ydiff / xdiff)))
- if (
- (ascnd_x and next_pt[0] < last_pt[0]) or
- (not ascnd_x and next_pt[0] > last_pt[0])
+ if (ascnd_x and next_pt[0] < last_pt[0]) or (
+ not ascnd_x and next_pt[0] > last_pt[0]
):
angle = 180 - angle
count = int(angle // STEP_ANGLE)
@@ -1172,15 +1255,16 @@ class ProbeManager:
yield (origin[0] + adj, origin[1] + opp)
-MAX_HIT_DIST = 2.
+MAX_HIT_DIST = 2.0
MM_WIN_SPEED = 125
+
class RapidScanHelper:
def __init__(self, config, probe_mgr, finalize_cb):
self.printer = config.get_printer()
self.probe_manager = probe_mgr
- self.speed = config.getfloat("speed", 50., above=0.)
- self.scan_height = config.getfloat("horizontal_move_z", 5.)
+ self.speed = config.getfloat("speed", 50.0, above=0.0)
+ self.scan_height = config.getfloat("horizontal_move_z", 5.0)
self.finalize_callback = finalize_cb
def perform_rapid_scan(self, gcmd):
@@ -1233,7 +1317,7 @@ class RapidScanHelper:
return
pparams = pprobe.get_probe_params(gcmd)
lift_speed = pparams["lift_speed"]
- cur_pos[2] = self.scan_height + .5
+ cur_pos[2] = self.scan_height + 0.5
toolhead.manual_move(cur_pos, lift_speed)
def _move_to_scan_height(self, gcmd, scan_height):
@@ -1244,11 +1328,11 @@ class RapidScanHelper:
pparams = pprobe.get_probe_params(gcmd)
lift_speed = pparams["lift_speed"]
probe_speed = pparams["probe_speed"]
- cur_pos[2] = scan_height + .5
+ cur_pos[2] = scan_height + 0.5
toolhead.manual_move(cur_pos, lift_speed)
cur_pos[2] = scan_height
toolhead.manual_move(cur_pos, probe_speed)
- toolhead.dwell(time_window / 2 + .01)
+ toolhead.dwell(time_window / 2 + 0.01)
def _apply_offsets(self, point, offsets):
return [(pos - ofs) for pos, ofs in zip(point, offsets)]
@@ -1256,16 +1340,18 @@ class RapidScanHelper:
class MoveSplitter:
def __init__(self, config, gcode):
- self.split_delta_z = config.getfloat(
- 'split_delta_z', .025, minval=0.01)
+ self.split_delta_z = config.getfloat("split_delta_z", 0.025, minval=0.01)
self.move_check_distance = config.getfloat(
- 'move_check_distance', 5., minval=3.)
+ "move_check_distance", 5.0, minval=3.0
+ )
self.z_mesh = None
- self.fade_offset = 0.
+ self.fade_offset = 0.0
self.gcode = gcode
+
def initialize(self, mesh, fade_offset):
self.z_mesh = mesh
self.fade_offset = fade_offset
+
def build_move(self, prev_pos, next_pos, factor):
self.prev_pos = tuple(prev_pos)
self.next_pos = tuple(next_pos)
@@ -1273,30 +1359,35 @@ class MoveSplitter:
self.z_factor = factor
self.z_offset = self._calc_z_offset(prev_pos)
self.traverse_complete = False
- self.distance_checked = 0.
+ self.distance_checked = 0.0
axes_d = [np - pp for np, pp in zip(self.next_pos, self.prev_pos)]
- self.total_move_length = math.sqrt(sum([d*d for d in axes_d[:3]]))
- self.axis_move = [not isclose(d, 0., abs_tol=1e-10) for d in axes_d]
+ self.total_move_length = math.sqrt(sum([d * d for d in axes_d[:3]]))
+ self.axis_move = [not isclose(d, 0.0, abs_tol=1e-10) for d in axes_d]
+
def _calc_z_offset(self, pos):
z = self.z_mesh.calc_z(pos[0], pos[1])
offset = self.fade_offset
return self.z_factor * (z - offset) + offset
+
def _set_next_move(self, distance_from_prev):
t = distance_from_prev / self.total_move_length
- if t > 1. or t < 0.:
+ if t > 1.0 or t < 0.0:
raise self.gcode.error(
"bed_mesh: Slice distance is negative "
- "or greater than entire move length")
+ "or greater than entire move length"
+ )
for i in range(len(self.next_pos)):
if self.axis_move[i]:
- self.current_pos[i] = lerp(
- t, self.prev_pos[i], self.next_pos[i])
+ self.current_pos[i] = lerp(t, self.prev_pos[i], self.next_pos[i])
+
def split(self):
if not self.traverse_complete:
if self.axis_move[0] or self.axis_move[1]:
# X and/or Y axis move, traverse if necessary
- while self.distance_checked + self.move_check_distance \
- < self.total_move_length:
+ while (
+ self.distance_checked + self.move_check_distance
+ < self.total_move_length
+ ):
self.distance_checked += self.move_check_distance
self._set_next_move(self.distance_checked)
next_z = self._calc_z_offset(self.current_pos)
@@ -1323,54 +1414,57 @@ class ZMesh:
self.profile_name = name or "adaptive-%X" % (id(self),)
self.probed_matrix = self.mesh_matrix = None
self.mesh_params = params
- self.mesh_offsets = [0., 0.]
- logging.debug('bed_mesh: probe/mesh parameters:')
+ self.mesh_offsets = [0.0, 0.0]
+ logging.debug("bed_mesh: probe/mesh parameters:")
for key, value in self.mesh_params.items():
logging.debug("%s : %s" % (key, value))
- self.mesh_x_min = params['min_x']
- self.mesh_x_max = params['max_x']
- self.mesh_y_min = params['min_y']
- self.mesh_y_max = params['max_y']
+ self.mesh_x_min = params["min_x"]
+ self.mesh_x_max = params["max_x"]
+ self.mesh_y_min = params["min_y"]
+ self.mesh_y_max = params["max_y"]
logging.debug(
"bed_mesh: Mesh Min: (%.2f,%.2f) Mesh Max: (%.2f,%.2f)"
- % (self.mesh_x_min, self.mesh_y_min,
- self.mesh_x_max, self.mesh_y_max))
+ % (self.mesh_x_min, self.mesh_y_min, self.mesh_x_max, self.mesh_y_max)
+ )
# Set the interpolation algorithm
interpolation_algos = {
- 'lagrange': self._sample_lagrange,
- 'bicubic': self._sample_bicubic,
- 'direct': self._sample_direct
+ "lagrange": self._sample_lagrange,
+ "bicubic": self._sample_bicubic,
+ "direct": self._sample_direct,
}
- self._sample = interpolation_algos.get(params['algo'])
+ self._sample = interpolation_algos.get(params["algo"])
# Number of points to interpolate per segment
- mesh_x_pps = params['mesh_x_pps']
- mesh_y_pps = params['mesh_y_pps']
- px_cnt = params['x_count']
- py_cnt = params['y_count']
+ mesh_x_pps = params["mesh_x_pps"]
+ mesh_y_pps = params["mesh_y_pps"]
+ px_cnt = params["x_count"]
+ py_cnt = params["y_count"]
self.mesh_x_count = (px_cnt - 1) * mesh_x_pps + px_cnt
self.mesh_y_count = (py_cnt - 1) * mesh_y_pps + py_cnt
self.x_mult = mesh_x_pps + 1
self.y_mult = mesh_y_pps + 1
- logging.debug("bed_mesh: Mesh grid size - X:%d, Y:%d"
- % (self.mesh_x_count, self.mesh_y_count))
- self.mesh_x_dist = (self.mesh_x_max - self.mesh_x_min) / \
- (self.mesh_x_count - 1)
- self.mesh_y_dist = (self.mesh_y_max - self.mesh_y_min) / \
- (self.mesh_y_count - 1)
+ logging.debug(
+ "bed_mesh: Mesh grid size - X:%d, Y:%d"
+ % (self.mesh_x_count, self.mesh_y_count)
+ )
+ self.mesh_x_dist = (self.mesh_x_max - self.mesh_x_min) / (self.mesh_x_count - 1)
+ self.mesh_y_dist = (self.mesh_y_max - self.mesh_y_min) / (self.mesh_y_count - 1)
+
def get_mesh_matrix(self):
if self.mesh_matrix is not None:
- return [[round(z, 6) for z in line]
- for line in self.mesh_matrix]
+ return [[round(z, 6) for z in line] for line in self.mesh_matrix]
return [[]]
+
def get_probed_matrix(self):
if self.probed_matrix is not None:
- return [[round(z, 6) for z in line]
- for line in self.probed_matrix]
+ return [[round(z, 6) for z in line] for line in self.probed_matrix]
return [[]]
+
def get_mesh_params(self):
return self.mesh_params
+
def get_profile_name(self):
return self.profile_name
+
def print_probed_matrix(self, print_func):
if self.probed_matrix is not None:
msg = "Mesh Leveling Probed Z positions:\n"
@@ -1381,6 +1475,7 @@ class ZMesh:
print_func(msg)
else:
print_func("bed_mesh: bed has not been probed")
+
def print_mesh(self, print_func, move_z=None):
matrix = self.get_mesh_matrix()
if matrix is not None:
@@ -1388,12 +1483,13 @@ class ZMesh:
if move_z is not None:
msg += "Search Height: %d\n" % (move_z)
msg += "Mesh Offsets: X=%.4f, Y=%.4f\n" % (
- self.mesh_offsets[0], self.mesh_offsets[1])
+ self.mesh_offsets[0],
+ self.mesh_offsets[1],
+ )
msg += "Mesh Average: %.2f\n" % (self.get_z_average())
rng = self.get_z_range()
msg += "Mesh Range: min=%.4f max=%.4f\n" % (rng[0], rng[1])
- msg += "Interpolation Algorithm: %s\n" \
- % (self.mesh_params['algo'])
+ msg += "Interpolation Algorithm: %s\n" % (self.mesh_params["algo"])
msg += "Measured points:\n"
for y_line in range(self.mesh_y_count - 1, -1, -1):
for z in matrix[y_line]:
@@ -1402,10 +1498,12 @@ class ZMesh:
print_func(msg)
else:
print_func("bed_mesh: Z Mesh not generated")
+
def build_mesh(self, z_matrix):
self.probed_matrix = z_matrix
self._sample(z_matrix)
self.print_mesh(logging.debug)
+
def set_zero_reference(self, xpos, ypos):
offset = self.calc_z(xpos, ypos)
logging.info(
@@ -1416,42 +1514,50 @@ class ZMesh:
for yidx in range(len(matrix)):
for xidx in range(len(matrix[yidx])):
matrix[yidx][xidx] -= offset
+
def set_mesh_offsets(self, offsets):
for i, o in enumerate(offsets):
if o is not None:
self.mesh_offsets[i] = o
+
def get_x_coordinate(self, index):
return self.mesh_x_min + self.mesh_x_dist * index
+
def get_y_coordinate(self, index):
return self.mesh_y_min + self.mesh_y_dist * index
+
def calc_z(self, x, y):
if self.mesh_matrix is not None:
tbl = self.mesh_matrix
tx, xidx = self._get_linear_index(x + self.mesh_offsets[0], 0)
ty, yidx = self._get_linear_index(y + self.mesh_offsets[1], 1)
- z0 = lerp(tx, tbl[yidx][xidx], tbl[yidx][xidx+1])
- z1 = lerp(tx, tbl[yidx+1][xidx], tbl[yidx+1][xidx+1])
+ z0 = lerp(tx, tbl[yidx][xidx], tbl[yidx][xidx + 1])
+ z1 = lerp(tx, tbl[yidx + 1][xidx], tbl[yidx + 1][xidx + 1])
return lerp(ty, z0, z1)
else:
# No mesh table generated, no z-adjustment
- return 0.
+ return 0.0
+
def get_z_range(self):
if self.mesh_matrix is not None:
mesh_min = min([min(x) for x in self.mesh_matrix])
mesh_max = max([max(x) for x in self.mesh_matrix])
return mesh_min, mesh_max
else:
- return 0., 0.
+ return 0.0, 0.0
+
def get_z_average(self):
if self.mesh_matrix is not None:
- avg_z = (sum([sum(x) for x in self.mesh_matrix]) /
- sum([len(x) for x in self.mesh_matrix]))
+ avg_z = sum([sum(x) for x in self.mesh_matrix]) / sum(
+ [len(x) for x in self.mesh_matrix]
+ )
# Round average to the nearest 100th. This
# should produce an offset that is divisible by common
# z step distances
return round(avg_z, 2)
else:
- return 0.
+ return 0.0
+
def _get_linear_index(self, coord, axis):
if axis == 0:
# X-axis
@@ -1465,21 +1571,29 @@ class ZMesh:
mesh_cnt = self.mesh_y_count
mesh_dist = self.mesh_y_dist
cfunc = self.get_y_coordinate
- t = 0.
+ t = 0.0
idx = int(math.floor((coord - mesh_min) / mesh_dist))
idx = constrain(idx, 0, mesh_cnt - 2)
t = (coord - cfunc(idx)) / mesh_dist
- return constrain(t, 0., 1.), idx
+ return constrain(t, 0.0, 1.0), idx
+
def _sample_direct(self, z_matrix):
self.mesh_matrix = z_matrix
+
def _sample_lagrange(self, z_matrix):
x_mult = self.x_mult
y_mult = self.y_mult
- self.mesh_matrix = \
- [[0. if ((i % x_mult) or (j % y_mult))
- else z_matrix[j//y_mult][i//x_mult]
- for i in range(self.mesh_x_count)]
- for j in range(self.mesh_y_count)]
+ self.mesh_matrix = [
+ [
+ (
+ 0.0
+ if ((i % x_mult) or (j % y_mult))
+ else z_matrix[j // y_mult][i // x_mult]
+ )
+ for i in range(self.mesh_x_count)
+ ]
+ for j in range(self.mesh_y_count)
+ ]
xpts, ypts = self._get_lagrange_coords()
# Interpolate X coordinates
for i in range(self.mesh_y_count):
@@ -1498,43 +1612,52 @@ class ZMesh:
continue
y = self.get_y_coordinate(j)
self.mesh_matrix[j][i] = self._calc_lagrange(ypts, y, i, 1)
+
def _get_lagrange_coords(self):
xpts = []
ypts = []
- for i in range(self.mesh_params['x_count']):
+ for i in range(self.mesh_params["x_count"]):
xpts.append(self.get_x_coordinate(i * self.x_mult))
- for j in range(self.mesh_params['y_count']):
+ for j in range(self.mesh_params["y_count"]):
ypts.append(self.get_y_coordinate(j * self.y_mult))
return xpts, ypts
+
def _calc_lagrange(self, lpts, c, vec, axis=0):
pt_cnt = len(lpts)
- total = 0.
+ total = 0.0
for i in range(pt_cnt):
- n = 1.
- d = 1.
+ n = 1.0
+ d = 1.0
for j in range(pt_cnt):
if j == i:
continue
- n *= (c - lpts[j])
- d *= (lpts[i] - lpts[j])
+ n *= c - lpts[j]
+ d *= lpts[i] - lpts[j]
if axis == 0:
# Calc X-Axis
- z = self.mesh_matrix[vec][i*self.x_mult]
+ z = self.mesh_matrix[vec][i * self.x_mult]
else:
# Calc Y-Axis
- z = self.mesh_matrix[i*self.y_mult][vec]
+ z = self.mesh_matrix[i * self.y_mult][vec]
total += z * n / d
return total
+
def _sample_bicubic(self, z_matrix):
# should work for any number of probe points above 3x3
x_mult = self.x_mult
y_mult = self.y_mult
- c = self.mesh_params['tension']
- self.mesh_matrix = \
- [[0. if ((i % x_mult) or (j % y_mult))
- else z_matrix[j//y_mult][i//x_mult]
- for i in range(self.mesh_x_count)]
- for j in range(self.mesh_y_count)]
+ c = self.mesh_params["tension"]
+ self.mesh_matrix = [
+ [
+ (
+ 0.0
+ if ((i % x_mult) or (j % y_mult))
+ else z_matrix[j // y_mult][i // x_mult]
+ )
+ for i in range(self.mesh_x_count)
+ ]
+ for j in range(self.mesh_y_count)
+ ]
# Interpolate X values
for y in range(self.mesh_y_count):
if y % y_mult != 0:
@@ -1551,6 +1674,7 @@ class ZMesh:
continue
pts = self._get_y_ctl_pts(x, y)
self.mesh_matrix[y][x] = self._cardinal_spline(pts, c)
+
def _get_x_ctl_pts(self, x, y):
# Fetch control points and t for a X value in the mesh
x_mult = self.x_mult
@@ -1559,7 +1683,7 @@ class ZMesh:
if x < x_mult:
p0 = p1 = x_row[0]
p2 = x_row[x_mult]
- p3 = x_row[2*x_mult]
+ p3 = x_row[2 * x_mult]
t = x / float(x_mult)
elif x > last_pt:
p0 = x_row[last_pt - x_mult]
@@ -1573,14 +1697,14 @@ class ZMesh:
p0 = x_row[i - x_mult]
p1 = x_row[i]
p2 = x_row[i + x_mult]
- p3 = x_row[i + 2*x_mult]
+ p3 = x_row[i + 2 * x_mult]
t = (x - i) / float(x_mult)
found = True
break
if not found:
- raise BedMeshError(
- "bed_mesh: Error finding x control points")
+ raise BedMeshError("bed_mesh: Error finding x control points")
return p0, p1, p2, p3, t
+
def _get_y_ctl_pts(self, x, y):
# Fetch control points and t for a Y value in the mesh
y_mult = self.y_mult
@@ -1589,7 +1713,7 @@ class ZMesh:
if y < y_mult:
p0 = p1 = y_col[0][x]
p2 = y_col[y_mult][x]
- p3 = y_col[2*y_mult][x]
+ p3 = y_col[2 * y_mult][x]
t = y / float(y_mult)
elif y > last_pt:
p0 = y_col[last_pt - y_mult][x]
@@ -1603,23 +1727,23 @@ class ZMesh:
p0 = y_col[i - y_mult][x]
p1 = y_col[i][x]
p2 = y_col[i + y_mult][x]
- p3 = y_col[i + 2*y_mult][x]
+ p3 = y_col[i + 2 * y_mult][x]
t = (y - i) / float(y_mult)
found = True
break
if not found:
- raise BedMeshError(
- "bed_mesh: Error finding y control points")
+ raise BedMeshError("bed_mesh: Error finding y control points")
return p0, p1, p2, p3, t
+
def _cardinal_spline(self, p, tension):
t = p[4]
- t2 = t*t
- t3 = t2*t
+ t2 = t * t
+ t3 = t2 * t
m1 = tension * (p[2] - p[0])
m2 = tension * (p[3] - p[1])
- a = p[1] * (2*t3 - 3*t2 + 1)
- b = p[2] * (-2*t3 + 3*t2)
- c = m1 * (t3 - 2*t2 + t)
+ a = p[1] * (2 * t3 - 3 * t2 + 1)
+ b = p[2] * (-2 * t3 + 3 * t2)
+ c = m1 * (t3 - 2 * t2 + t)
d = m2 * (t3 - t2)
return a + b + c + d
@@ -1628,29 +1752,28 @@ class ProfileManager:
def __init__(self, config, bedmesh):
self.name = config.get_name()
self.printer = config.get_printer()
- self.gcode = self.printer.lookup_object('gcode')
+ self.gcode = self.printer.lookup_object("gcode")
self.bedmesh = bedmesh
self.profiles = {}
self.incompatible_profiles = []
# Fetch stored profiles from Config
stored_profs = config.get_prefix_sections(self.name)
- stored_profs = [s for s in stored_profs
- if s.get_name() != self.name]
+ stored_profs = [s for s in stored_profs if s.get_name() != self.name]
for profile in stored_profs:
- name = profile.get_name().split(' ', 1)[1]
- version = profile.getint('version', 0)
+ name = profile.get_name().split(" ", 1)[1]
+ version = profile.getint("version", 0)
if version != PROFILE_VERSION:
logging.info(
"bed_mesh: Profile [%s] not compatible with this version\n"
"of bed_mesh. Profile Version: %d Current Version: %d "
- % (name, version, PROFILE_VERSION))
+ % (name, version, PROFILE_VERSION)
+ )
self.incompatible_profiles.append(name)
continue
self.profiles[name] = {}
- zvals = profile.getlists('points', seps=(',', '\n'), parser=float)
- self.profiles[name]['points'] = zvals
- self.profiles[name]['mesh_params'] = params = \
- collections.OrderedDict()
+ zvals = profile.getlists("points", seps=(",", "\n"), parser=float)
+ self.profiles[name]["points"] = zvals
+ self.profiles[name]["mesh_params"] = params = collections.OrderedDict()
for key, t in PROFILE_OPTIONS.items():
if t is int:
params[key] = profile.getint(key)
@@ -1660,31 +1783,38 @@ class ProfileManager:
params[key] = profile.get(key)
# Register GCode
self.gcode.register_command(
- 'BED_MESH_PROFILE', self.cmd_BED_MESH_PROFILE,
- desc=self.cmd_BED_MESH_PROFILE_help)
+ "BED_MESH_PROFILE",
+ self.cmd_BED_MESH_PROFILE,
+ desc=self.cmd_BED_MESH_PROFILE_help,
+ )
+
def get_profiles(self):
return self.profiles
+
def _check_incompatible_profiles(self):
if self.incompatible_profiles:
- configfile = self.printer.lookup_object('configfile')
+ configfile = self.printer.lookup_object("configfile")
for profile in self.incompatible_profiles:
- configfile.remove_section('bed_mesh ' + profile)
+ configfile.remove_section("bed_mesh " + profile)
self.gcode.respond_info(
"The following incompatible profiles have been detected\n"
"and are scheduled for removal:\n%s\n"
"The SAVE_CONFIG command will update the printer config\n"
- "file and restart the printer" %
- (('\n').join(self.incompatible_profiles)))
+ "file and restart the printer"
+ % (("\n").join(self.incompatible_profiles))
+ )
+
def save_profile(self, prof_name):
z_mesh = self.bedmesh.get_mesh()
if z_mesh is None:
self.gcode.respond_info(
"Unable to save to profile [%s], the bed has not been probed"
- % (prof_name))
+ % (prof_name)
+ )
return
probed_matrix = z_mesh.get_probed_matrix()
mesh_params = z_mesh.get_mesh_params()
- configfile = self.printer.lookup_object('configfile')
+ configfile = self.printer.lookup_object("configfile")
cfg_name = self.name + " " + prof_name
# set params
z_values = ""
@@ -1693,40 +1823,41 @@ class ProfileManager:
for p in line:
z_values += "%.6f, " % p
z_values = z_values[:-2]
- configfile.set(cfg_name, 'version', PROFILE_VERSION)
- configfile.set(cfg_name, 'points', z_values)
+ configfile.set(cfg_name, "version", PROFILE_VERSION)
+ configfile.set(cfg_name, "points", z_values)
for key, value in mesh_params.items():
configfile.set(cfg_name, key, value)
# save copy in local storage
# ensure any self.profiles returned as status remains immutable
profiles = dict(self.profiles)
profiles[prof_name] = profile = {}
- profile['points'] = probed_matrix
- profile['mesh_params'] = collections.OrderedDict(mesh_params)
+ profile["points"] = probed_matrix
+ profile["mesh_params"] = collections.OrderedDict(mesh_params)
self.profiles = profiles
self.bedmesh.update_status()
self.gcode.respond_info(
"Bed Mesh state has been saved to profile [%s]\n"
"for the current session. The SAVE_CONFIG command will\n"
- "update the printer config file and restart the printer."
- % (prof_name))
+ "update the printer config file and restart the printer." % (prof_name)
+ )
+
def load_profile(self, prof_name):
profile = self.profiles.get(prof_name, None)
if profile is None:
- raise self.gcode.error(
- "bed_mesh: Unknown profile [%s]" % prof_name)
- probed_matrix = profile['points']
- mesh_params = profile['mesh_params']
+ raise self.gcode.error("bed_mesh: Unknown profile [%s]" % prof_name)
+ probed_matrix = profile["points"]
+ mesh_params = profile["mesh_params"]
z_mesh = ZMesh(mesh_params, prof_name)
try:
z_mesh.build_mesh(probed_matrix)
except BedMeshError as e:
raise self.gcode.error(str(e))
self.bedmesh.set_mesh(z_mesh)
+
def remove_profile(self, prof_name):
if prof_name in self.profiles:
- configfile = self.printer.lookup_object('configfile')
- configfile.remove_section('bed_mesh ' + prof_name)
+ configfile = self.printer.lookup_object("configfile")
+ configfile.remove_section("bed_mesh " + prof_name)
profiles = dict(self.profiles)
del profiles[prof_name]
self.profiles = profiles
@@ -1734,17 +1865,21 @@ class ProfileManager:
self.gcode.respond_info(
"Profile [%s] removed from storage for this session.\n"
"The SAVE_CONFIG command will update the printer\n"
- "configuration and restart the printer" % (prof_name))
+ "configuration and restart the printer" % (prof_name)
+ )
else:
- self.gcode.respond_info(
- "No profile named [%s] to remove" % (prof_name))
+ self.gcode.respond_info("No profile named [%s] to remove" % (prof_name))
+
cmd_BED_MESH_PROFILE_help = "Bed Mesh Persistent Storage management"
+
def cmd_BED_MESH_PROFILE(self, gcmd):
- options = collections.OrderedDict({
- 'LOAD': self.load_profile,
- 'SAVE': self.save_profile,
- 'REMOVE': self.remove_profile
- })
+ options = collections.OrderedDict(
+ {
+ "LOAD": self.load_profile,
+ "SAVE": self.save_profile,
+ "REMOVE": self.remove_profile,
+ }
+ )
for key in options:
name = gcmd.get(key, None)
if name is not None:
@@ -1752,10 +1887,11 @@ class ProfileManager:
raise gcmd.error(
"Value for parameter '%s' must be specified" % (key)
)
- if name == "default" and key == 'SAVE':
+ if name == "default" and key == "SAVE":
gcmd.respond_info(
"Profile 'default' is reserved, please choose"
- " another profile name.")
+ " another profile name."
+ )
else:
options[key](name)
return
diff --git a/klippy/extras/bed_screws.py b/klippy/extras/bed_screws.py
index c3446191..28be0e16 100644
--- a/klippy/extras/bed_screws.py
+++ b/klippy/extras/bed_screws.py
@@ -4,6 +4,7 @@
#
# This file may be distributed under the terms of the GNU GPLv3 license.
+
class BedScrews:
def __init__(self, config):
self.printer = config.get_printer()
@@ -27,27 +28,32 @@ class BedScrews:
if len(screws) < 3:
raise config.error("bed_screws: Must have at least three screws")
self.number_of_screws = len(screws)
- self.states = {'adjust': screws, 'fine': fine_adjust}
- self.speed = config.getfloat('speed', 50., above=0.)
- self.lift_speed = config.getfloat('probe_speed', 5., above=0.)
- self.horizontal_move_z = config.getfloat('horizontal_move_z', 5.)
- self.probe_z = config.getfloat('probe_height', 0.)
+ self.states = {"adjust": screws, "fine": fine_adjust}
+ self.speed = config.getfloat("speed", 50.0, above=0.0)
+ self.lift_speed = config.getfloat("probe_speed", 5.0, above=0.0)
+ self.horizontal_move_z = config.getfloat("horizontal_move_z", 5.0)
+ self.probe_z = config.getfloat("probe_height", 0.0)
# Register command
- self.gcode = self.printer.lookup_object('gcode')
- self.gcode.register_command("BED_SCREWS_ADJUST",
- self.cmd_BED_SCREWS_ADJUST,
- desc=self.cmd_BED_SCREWS_ADJUST_help)
+ self.gcode = self.printer.lookup_object("gcode")
+ self.gcode.register_command(
+ "BED_SCREWS_ADJUST",
+ self.cmd_BED_SCREWS_ADJUST,
+ desc=self.cmd_BED_SCREWS_ADJUST_help,
+ )
+
def reset(self):
self.state = None
self.current_screw = 0
self.accepted_screws = 0
+
def move(self, coord, speed):
try:
- self.printer.lookup_object('toolhead').manual_move(coord, speed)
+ self.printer.lookup_object("toolhead").manual_move(coord, speed)
except self.printer.command_error as e:
self.unregister_commands()
self.reset()
raise
+
def move_to_screw(self, state, screw):
# Move up, over, and then down
self.move((None, None, self.horizontal_move_z), self.lift_speed)
@@ -60,64 +66,79 @@ class BedScrews:
# Register commands
self.gcode.respond_info(
"Adjust %s. Then run ACCEPT, ADJUSTED, or ABORT\n"
- "Use ADJUSTED if a significant screw adjustment is made" % (name,))
- self.gcode.register_command('ACCEPT', self.cmd_ACCEPT,
- desc=self.cmd_ACCEPT_help)
- self.gcode.register_command('ADJUSTED', self.cmd_ADJUSTED,
- desc=self.cmd_ADJUSTED_help)
- self.gcode.register_command('ABORT', self.cmd_ABORT,
- desc=self.cmd_ABORT_help)
+ "Use ADJUSTED if a significant screw adjustment is made" % (name,)
+ )
+ self.gcode.register_command(
+ "ACCEPT", self.cmd_ACCEPT, desc=self.cmd_ACCEPT_help
+ )
+ self.gcode.register_command(
+ "ADJUSTED", self.cmd_ADJUSTED, desc=self.cmd_ADJUSTED_help
+ )
+ self.gcode.register_command("ABORT", self.cmd_ABORT, desc=self.cmd_ABORT_help)
+
def unregister_commands(self):
- self.gcode.register_command('ACCEPT', None)
- self.gcode.register_command('ADJUSTED', None)
- self.gcode.register_command('ABORT', None)
+ self.gcode.register_command("ACCEPT", None)
+ self.gcode.register_command("ADJUSTED", None)
+ self.gcode.register_command("ABORT", None)
+
def get_status(self, eventtime):
return {
- 'is_active': self.state is not None,
- 'state': self.state,
- 'current_screw': self.current_screw,
- 'accepted_screws': self.accepted_screws
+ "is_active": self.state is not None,
+ "state": self.state,
+ "current_screw": self.current_screw,
+ "accepted_screws": self.accepted_screws,
}
+
cmd_BED_SCREWS_ADJUST_help = "Tool to help adjust bed leveling screws"
+
def cmd_BED_SCREWS_ADJUST(self, gcmd):
if self.state is not None:
raise gcmd.error("Already in bed_screws helper; use ABORT to exit")
# reset accepted screws
self.accepted_screws = 0
self.move((None, None, self.horizontal_move_z), self.speed)
- self.move_to_screw('adjust', 0)
+ self.move_to_screw("adjust", 0)
+
cmd_ACCEPT_help = "Accept bed screw position"
+
def cmd_ACCEPT(self, gcmd):
self.unregister_commands()
self.accepted_screws = self.accepted_screws + 1
- if self.current_screw + 1 < len(self.states[self.state]) \
- and self.accepted_screws < self.number_of_screws:
+ if (
+ self.current_screw + 1 < len(self.states[self.state])
+ and self.accepted_screws < self.number_of_screws
+ ):
# Continue with next screw
self.move_to_screw(self.state, self.current_screw + 1)
return
if self.accepted_screws < self.number_of_screws:
# Retry coarse adjustments
- self.move_to_screw('adjust', 0)
+ self.move_to_screw("adjust", 0)
return
- if self.state == 'adjust' and self.states['fine']:
+ if self.state == "adjust" and self.states["fine"]:
# Reset accepted screws for fine adjustment
self.accepted_screws = 0
# Perform fine screw adjustments
- self.move_to_screw('fine', 0)
+ self.move_to_screw("fine", 0)
return
# Done
self.reset()
self.move((None, None, self.horizontal_move_z), self.lift_speed)
gcmd.respond_info("Bed screws tool completed successfully")
+
cmd_ADJUSTED_help = "Accept bed screw position after notable adjustment"
+
def cmd_ADJUSTED(self, gcmd):
self.unregister_commands()
self.accepted_screws = -1
self.cmd_ACCEPT(gcmd)
+
cmd_ABORT_help = "Abort bed screws tool"
+
def cmd_ABORT(self, gcmd):
self.unregister_commands()
self.reset()
+
def load_config(config):
return BedScrews(config)
diff --git a/klippy/extras/bed_tilt.py b/klippy/extras/bed_tilt.py
index e5686cbe..54bdce56 100644
--- a/klippy/extras/bed_tilt.py
+++ b/klippy/extras/bed_tilt.py
@@ -7,41 +7,46 @@ import logging
import mathutil
from . import probe
+
class BedTilt:
def __init__(self, config):
self.printer = config.get_printer()
- self.printer.register_event_handler("klippy:connect",
- self.handle_connect)
- self.x_adjust = config.getfloat('x_adjust', 0.)
- self.y_adjust = config.getfloat('y_adjust', 0.)
- self.z_adjust = config.getfloat('z_adjust', 0.)
- if config.get('points', None) is not None:
+ self.printer.register_event_handler("klippy:connect", self.handle_connect)
+ self.x_adjust = config.getfloat("x_adjust", 0.0)
+ self.y_adjust = config.getfloat("y_adjust", 0.0)
+ self.z_adjust = config.getfloat("z_adjust", 0.0)
+ if config.get("points", None) is not None:
BedTiltCalibrate(config, self)
self.toolhead = None
# Register move transform with g-code class
- gcode_move = self.printer.load_object(config, 'gcode_move')
+ gcode_move = self.printer.load_object(config, "gcode_move")
gcode_move.set_move_transform(self)
+
def handle_connect(self):
- self.toolhead = self.printer.lookup_object('toolhead')
+ self.toolhead = self.printer.lookup_object("toolhead")
+
def get_position(self):
pos = self.toolhead.get_position()
x, y, z = pos[:3]
- z -= x*self.x_adjust + y*self.y_adjust + self.z_adjust
+ z -= x * self.x_adjust + y * self.y_adjust + self.z_adjust
return [x, y, z] + pos[3:]
+
def move(self, newpos, speed):
x, y, z = newpos[:3]
- z += x*self.x_adjust + y*self.y_adjust + self.z_adjust
+ z += x * self.x_adjust + y * self.y_adjust + self.z_adjust
self.toolhead.move([x, y, z] + newpos[3:], speed)
+
def update_adjust(self, x_adjust, y_adjust, z_adjust):
self.x_adjust = x_adjust
self.y_adjust = y_adjust
self.z_adjust = z_adjust
- gcode_move = self.printer.lookup_object('gcode_move')
+ gcode_move = self.printer.lookup_object("gcode_move")
gcode_move.reset_last_position()
- configfile = self.printer.lookup_object('configfile')
- configfile.set('bed_tilt', 'x_adjust', "%.6f" % (x_adjust,))
- configfile.set('bed_tilt', 'y_adjust', "%.6f" % (y_adjust,))
- configfile.set('bed_tilt', 'z_adjust', "%.6f" % (z_adjust,))
+ configfile = self.printer.lookup_object("configfile")
+ configfile.set("bed_tilt", "x_adjust", "%.6f" % (x_adjust,))
+ configfile.set("bed_tilt", "y_adjust", "%.6f" % (y_adjust,))
+ configfile.set("bed_tilt", "z_adjust", "%.6f" % (z_adjust,))
+
# Helper script to calibrate the bed tilt
class BedTiltCalibrate:
@@ -51,51 +56,73 @@ class BedTiltCalibrate:
self.probe_helper = probe.ProbePointsHelper(config, self.probe_finalize)
self.probe_helper.minimum_points(3)
# Register BED_TILT_CALIBRATE command
- self.gcode = self.printer.lookup_object('gcode')
+ self.gcode = self.printer.lookup_object("gcode")
self.gcode.register_command(
- 'BED_TILT_CALIBRATE', self.cmd_BED_TILT_CALIBRATE,
- desc=self.cmd_BED_TILT_CALIBRATE_help)
+ "BED_TILT_CALIBRATE",
+ self.cmd_BED_TILT_CALIBRATE,
+ desc=self.cmd_BED_TILT_CALIBRATE_help,
+ )
+
cmd_BED_TILT_CALIBRATE_help = "Bed tilt calibration script"
+
def cmd_BED_TILT_CALIBRATE(self, gcmd):
self.probe_helper.start_probe(gcmd)
+
def probe_finalize(self, offsets, positions):
# Setup for coordinate descent analysis
z_offset = offsets[2]
logging.info("Calculating bed_tilt with: %s", positions)
- params = { 'x_adjust': self.bedtilt.x_adjust,
- 'y_adjust': self.bedtilt.y_adjust,
- 'z_adjust': z_offset }
+ params = {
+ "x_adjust": self.bedtilt.x_adjust,
+ "y_adjust": self.bedtilt.y_adjust,
+ "z_adjust": z_offset,
+ }
logging.info("Initial bed_tilt parameters: %s", params)
+
# Perform coordinate descent
def adjusted_height(pos, params):
x, y, z = pos
- return (z - x*params['x_adjust'] - y*params['y_adjust']
- - params['z_adjust'])
+ return (
+ z - x * params["x_adjust"] - y * params["y_adjust"] - params["z_adjust"]
+ )
+
def errorfunc(params):
- total_error = 0.
+ total_error = 0.0
for pos in positions:
- total_error += adjusted_height(pos, params)**2
+ total_error += adjusted_height(pos, params) ** 2
return total_error
- new_params = mathutil.coordinate_descent(
- params.keys(), params, errorfunc)
+
+ new_params = mathutil.coordinate_descent(params.keys(), params, errorfunc)
# Update current bed_tilt calculations
- x_adjust = new_params['x_adjust']
- y_adjust = new_params['y_adjust']
- z_adjust = (new_params['z_adjust'] - z_offset
- - x_adjust * offsets[0] - y_adjust * offsets[1])
+ x_adjust = new_params["x_adjust"]
+ y_adjust = new_params["y_adjust"]
+ z_adjust = (
+ new_params["z_adjust"]
+ - z_offset
+ - x_adjust * offsets[0]
+ - y_adjust * offsets[1]
+ )
self.bedtilt.update_adjust(x_adjust, y_adjust, z_adjust)
# Log and report results
logging.info("Calculated bed_tilt parameters: %s", new_params)
for pos in positions:
- logging.info("orig: %s new: %s", adjusted_height(pos, params),
- adjusted_height(pos, new_params))
+ logging.info(
+ "orig: %s new: %s",
+ adjusted_height(pos, params),
+ adjusted_height(pos, new_params),
+ )
msg = "x_adjust: %.6f y_adjust: %.6f z_adjust: %.6f" % (
- x_adjust, y_adjust, z_adjust)
+ x_adjust,
+ y_adjust,
+ z_adjust,
+ )
self.printer.set_rollover_info("bed_tilt", "bed_tilt: %s" % (msg,))
self.gcode.respond_info(
"%s\nThe above parameters have been applied to the current\n"
"session. The SAVE_CONFIG command will update the printer\n"
- "config file and restart the printer." % (msg,))
+ "config file and restart the printer." % (msg,)
+ )
+
def load_config(config):
return BedTilt(config)
diff --git a/klippy/extras/bltouch.py b/klippy/extras/bltouch.py
index 2bcb9cc1..76823a25 100644
--- a/klippy/extras/bltouch.py
+++ b/klippy/extras/bltouch.py
@@ -9,49 +9,54 @@ from . import probe
SIGNAL_PERIOD = 0.020
MIN_CMD_TIME = 5 * SIGNAL_PERIOD
-TEST_TIME = 5 * 60.
-RETRY_RESET_TIME = 1.
-ENDSTOP_REST_TIME = .001
-ENDSTOP_SAMPLE_TIME = .000015
+TEST_TIME = 5 * 60.0
+RETRY_RESET_TIME = 1.0
+ENDSTOP_REST_TIME = 0.001
+ENDSTOP_SAMPLE_TIME = 0.000015
ENDSTOP_SAMPLE_COUNT = 4
Commands = {
- 'pin_down': 0.000650, 'touch_mode': 0.001165,
- 'pin_up': 0.001475, 'self_test': 0.001780, 'reset': 0.002190,
- 'set_5V_output_mode' : 0.001988, 'set_OD_output_mode' : 0.002091,
- 'output_mode_store' : 0.001884,
+ "pin_down": 0.000650,
+ "touch_mode": 0.001165,
+ "pin_up": 0.001475,
+ "self_test": 0.001780,
+ "reset": 0.002190,
+ "set_5V_output_mode": 0.001988,
+ "set_OD_output_mode": 0.002091,
+ "output_mode_store": 0.001884,
}
+
# BLTouch "endstop" wrapper
class BLTouchProbe:
def __init__(self, config):
self.printer = config.get_printer()
- self.position_endstop = config.getfloat('z_offset', minval=0.)
- self.stow_on_each_sample = config.getboolean('stow_on_each_sample',
- True)
- self.probe_touch_mode = config.getboolean('probe_with_touch_mode',
- False)
+ self.position_endstop = config.getfloat("z_offset", minval=0.0)
+ self.stow_on_each_sample = config.getboolean("stow_on_each_sample", True)
+ self.probe_touch_mode = config.getboolean("probe_with_touch_mode", False)
# Create a pwm object to handle the control pin
- ppins = self.printer.lookup_object('pins')
- self.mcu_pwm = ppins.setup_pin('pwm', config.get('control_pin'))
- self.mcu_pwm.setup_max_duration(0.)
+ ppins = self.printer.lookup_object("pins")
+ self.mcu_pwm = ppins.setup_pin("pwm", config.get("control_pin"))
+ self.mcu_pwm.setup_max_duration(0.0)
self.mcu_pwm.setup_cycle_time(SIGNAL_PERIOD)
# Command timing
- self.next_cmd_time = self.action_end_time = 0.
+ self.next_cmd_time = self.action_end_time = 0.0
self.finish_home_complete = self.wait_trigger_complete = None
# Create an "endstop" object to handle the sensor pin
- self.mcu_endstop = ppins.setup_pin('endstop', config.get('sensor_pin'))
+ self.mcu_endstop = ppins.setup_pin("endstop", config.get("sensor_pin"))
# output mode
- omodes = ['5V', 'OD', None]
- self.output_mode = config.getchoice('set_output_mode', omodes, None)
+ omodes = ["5V", "OD", None]
+ self.output_mode = config.getchoice("set_output_mode", omodes, None)
# Setup for sensor test
- self.next_test_time = 0.
+ self.next_test_time = 0.0
self.pin_up_not_triggered = config.getboolean(
- 'pin_up_reports_not_triggered', True)
+ "pin_up_reports_not_triggered", True
+ )
self.pin_up_touch_triggered = config.getboolean(
- 'pin_up_touch_mode_reports_triggered', True)
+ "pin_up_touch_mode_reports_triggered", True
+ )
# Calculate pin move time
- self.pin_move_time = config.getfloat('pin_move_time', 0.680, above=0.)
+ self.pin_move_time = config.getfloat("pin_move_time", 0.680, above=0.0)
# Wrappers
self.get_mcu = self.mcu_endstop.get_mcu
self.add_stepper = self.mcu_endstop.add_stepper
@@ -59,33 +64,40 @@ class BLTouchProbe:
self.home_wait = self.mcu_endstop.home_wait
self.query_endstop = self.mcu_endstop.query_endstop
# multi probes state
- self.multi = 'OFF'
+ self.multi = "OFF"
# Common probe implementation helpers
self.cmd_helper = probe.ProbeCommandHelper(
- config, self, self.mcu_endstop.query_endstop)
+ config, self, self.mcu_endstop.query_endstop
+ )
self.probe_offsets = probe.ProbeOffsetsHelper(config)
self.param_helper = probe.ProbeParameterHelper(config)
- self.homing_helper = probe.HomingViaProbeHelper(config, self,
- self.param_helper)
+ self.homing_helper = probe.HomingViaProbeHelper(config, self, self.param_helper)
self.probe_session = probe.ProbeSessionHelper(
- config, self.param_helper, self.homing_helper.start_probe_session)
+ config, self.param_helper, self.homing_helper.start_probe_session
+ )
# Register BLTOUCH_DEBUG command
- self.gcode = self.printer.lookup_object('gcode')
- self.gcode.register_command("BLTOUCH_DEBUG", self.cmd_BLTOUCH_DEBUG,
- desc=self.cmd_BLTOUCH_DEBUG_help)
- self.gcode.register_command("BLTOUCH_STORE", self.cmd_BLTOUCH_STORE,
- desc=self.cmd_BLTOUCH_STORE_help)
+ self.gcode = self.printer.lookup_object("gcode")
+ self.gcode.register_command(
+ "BLTOUCH_DEBUG", self.cmd_BLTOUCH_DEBUG, desc=self.cmd_BLTOUCH_DEBUG_help
+ )
+ self.gcode.register_command(
+ "BLTOUCH_STORE", self.cmd_BLTOUCH_STORE, desc=self.cmd_BLTOUCH_STORE_help
+ )
# Register events
- self.printer.register_event_handler("klippy:connect",
- self.handle_connect)
+ self.printer.register_event_handler("klippy:connect", self.handle_connect)
+
def get_probe_params(self, gcmd=None):
return self.param_helper.get_probe_params(gcmd)
+
def get_offsets(self):
return self.probe_offsets.get_offsets()
+
def get_status(self, eventtime):
return self.cmd_helper.get_status(eventtime)
+
def start_probe_session(self, gcmd):
return self.probe_session.start_probe_session(gcmd)
+
def handle_connect(self):
self.sync_mcu_print_time()
self.next_cmd_time += 0.200
@@ -95,17 +107,20 @@ class BLTouchProbe:
self.verify_raise_probe()
except self.printer.command_error as e:
logging.warning("BLTouch raise probe error: %s", str(e))
+
def sync_mcu_print_time(self):
curtime = self.printer.get_reactor().monotonic()
est_time = self.mcu_pwm.get_mcu().estimated_print_time(curtime)
self.next_cmd_time = max(self.next_cmd_time, est_time + MIN_CMD_TIME)
+
def sync_print_time(self):
- toolhead = self.printer.lookup_object('toolhead')
+ toolhead = self.printer.lookup_object("toolhead")
print_time = toolhead.get_last_move_time()
if self.next_cmd_time > print_time:
toolhead.dwell(self.next_cmd_time - print_time)
else:
self.next_cmd_time = print_time
+
def send_cmd(self, cmd, duration=MIN_CMD_TIME):
# Translate duration to ticks to avoid any secondary mcu clock skew
mcu = self.mcu_pwm.get_mcu()
@@ -115,26 +130,32 @@ class BLTouchProbe:
end_time = mcu.clock_to_print_time(cmd_clock)
# Schedule command followed by PWM disable
self.mcu_pwm.set_pwm(self.next_cmd_time, Commands[cmd] / SIGNAL_PERIOD)
- self.mcu_pwm.set_pwm(end_time, 0.)
+ self.mcu_pwm.set_pwm(end_time, 0.0)
# Update time tracking
self.action_end_time = self.next_cmd_time + duration
self.next_cmd_time = max(self.action_end_time, end_time + MIN_CMD_TIME)
+
def verify_state(self, triggered):
# Perform endstop check to verify bltouch reports desired state
- self.mcu_endstop.home_start(self.action_end_time, ENDSTOP_SAMPLE_TIME,
- ENDSTOP_SAMPLE_COUNT, ENDSTOP_REST_TIME,
- triggered=triggered)
+ self.mcu_endstop.home_start(
+ self.action_end_time,
+ ENDSTOP_SAMPLE_TIME,
+ ENDSTOP_SAMPLE_COUNT,
+ ENDSTOP_REST_TIME,
+ triggered=triggered,
+ )
try:
- trigger_time = self.mcu_endstop.home_wait(
- self.action_end_time + 0.100)
+ trigger_time = self.mcu_endstop.home_wait(self.action_end_time + 0.100)
except self.printer.command_error as e:
return False
- return trigger_time > 0.
+ return trigger_time > 0.0
+
def raise_probe(self):
self.sync_mcu_print_time()
if not self.pin_up_not_triggered:
- self.send_cmd('reset')
- self.send_cmd('pin_up', duration=self.pin_move_time)
+ self.send_cmd("reset")
+ self.send_cmd("pin_up", duration=self.pin_move_time)
+
def verify_raise_probe(self):
if not self.pin_up_not_triggered:
# No way to verify raise attempt
@@ -145,24 +166,25 @@ class BLTouchProbe:
# The "probe raised" test completed successfully
break
if retry >= 2:
- raise self.printer.command_error(
- "BLTouch failed to raise probe")
+ raise self.printer.command_error("BLTouch failed to raise probe")
msg = "Failed to verify BLTouch probe is raised; retrying."
self.gcode.respond_info(msg)
self.sync_mcu_print_time()
- self.send_cmd('reset', duration=RETRY_RESET_TIME)
- self.send_cmd('pin_up', duration=self.pin_move_time)
+ self.send_cmd("reset", duration=RETRY_RESET_TIME)
+ self.send_cmd("pin_up", duration=self.pin_move_time)
+
def lower_probe(self):
self.test_sensor()
self.sync_print_time()
- self.send_cmd('pin_down', duration=self.pin_move_time)
+ self.send_cmd("pin_down", duration=self.pin_move_time)
if self.probe_touch_mode:
- self.send_cmd('touch_mode')
+ self.send_cmd("touch_mode")
+
def test_sensor(self):
if not self.pin_up_touch_triggered:
# Nothing to test
return
- toolhead = self.printer.lookup_object('toolhead')
+ toolhead = self.printer.lookup_object("toolhead")
print_time = toolhead.get_last_move_time()
if print_time < self.next_test_time:
self.next_test_time = print_time + TEST_TIME
@@ -170,8 +192,8 @@ class BLTouchProbe:
# Raise the bltouch probe and test if probe is raised
self.sync_print_time()
for retry in range(3):
- self.send_cmd('pin_up', duration=self.pin_move_time)
- self.send_cmd('touch_mode')
+ self.send_cmd("pin_up", duration=self.pin_move_time)
+ self.send_cmd("touch_mode")
success = self.verify_state(True)
self.sync_print_time()
if success:
@@ -181,12 +203,14 @@ class BLTouchProbe:
msg = "BLTouch failed to verify sensor state"
if retry >= 2:
raise self.printer.command_error(msg)
- self.gcode.respond_info(msg + '; retrying.')
- self.send_cmd('reset', duration=RETRY_RESET_TIME)
+ self.gcode.respond_info(msg + "; retrying.")
+ self.send_cmd("reset", duration=RETRY_RESET_TIME)
+
def multi_probe_begin(self):
if self.stow_on_each_sample:
return
- self.multi = 'FIRST'
+ self.multi = "FIRST"
+
def multi_probe_end(self):
if self.stow_on_each_sample:
return
@@ -194,35 +218,43 @@ class BLTouchProbe:
self.raise_probe()
self.verify_raise_probe()
self.sync_print_time()
- self.multi = 'OFF'
+ self.multi = "OFF"
+
def probe_prepare(self, hmove):
- if self.multi == 'OFF' or self.multi == 'FIRST':
+ if self.multi == "OFF" or self.multi == "FIRST":
self.lower_probe()
- if self.multi == 'FIRST':
- self.multi = 'ON'
+ if self.multi == "FIRST":
+ self.multi = "ON"
self.sync_print_time()
- def home_start(self, print_time, sample_time, sample_count, rest_time,
- triggered=True):
+
+ def home_start(
+ self, print_time, sample_time, sample_count, rest_time, triggered=True
+ ):
rest_time = min(rest_time, ENDSTOP_REST_TIME)
self.finish_home_complete = self.mcu_endstop.home_start(
- print_time, sample_time, sample_count, rest_time, triggered)
+ print_time, sample_time, sample_count, rest_time, triggered
+ )
# Schedule wait_for_trigger callback
r = self.printer.get_reactor()
self.wait_trigger_complete = r.register_callback(self.wait_for_trigger)
return self.finish_home_complete
+
def wait_for_trigger(self, eventtime):
self.finish_home_complete.wait()
- if self.multi == 'OFF':
+ if self.multi == "OFF":
self.raise_probe()
+
def probe_finish(self, hmove):
self.wait_trigger_complete.wait()
- if self.multi == 'OFF':
+ if self.multi == "OFF":
self.verify_raise_probe()
self.sync_print_time()
if hmove.check_no_movement() is not None:
raise self.printer.command_error("BLTouch failed to deploy")
+
def get_position_endstop(self):
return self.position_endstop
+
def set_output_mode(self, mode):
# If this is inadvertently/purposely issued for a
# BLTOUCH pre V3.0 and clones:
@@ -233,10 +265,11 @@ class BLTouchProbe:
return
logging.info("BLTouch set output mode: %s", mode)
self.sync_mcu_print_time()
- if mode == '5V':
- self.send_cmd('set_5V_output_mode')
- if mode == 'OD':
- self.send_cmd('set_OD_output_mode')
+ if mode == "5V":
+ self.send_cmd("set_5V_output_mode")
+ if mode == "OD":
+ self.send_cmd("set_OD_output_mode")
+
def store_output_mode(self, mode):
# If this command is inadvertently/purposely issued for a
# BLTOUCH pre V3.0 and clones:
@@ -249,32 +282,38 @@ class BLTouchProbe:
# The pin-up is not needed but does not hurt
logging.info("BLTouch store output mode: %s", mode)
self.sync_print_time()
- self.send_cmd('pin_down')
- if mode == '5V':
- self.send_cmd('set_5V_output_mode')
+ self.send_cmd("pin_down")
+ if mode == "5V":
+ self.send_cmd("set_5V_output_mode")
else:
- self.send_cmd('set_OD_output_mode')
- self.send_cmd('output_mode_store')
- if mode == '5V':
- self.send_cmd('set_5V_output_mode')
+ self.send_cmd("set_OD_output_mode")
+ self.send_cmd("output_mode_store")
+ if mode == "5V":
+ self.send_cmd("set_5V_output_mode")
else:
- self.send_cmd('set_OD_output_mode')
- self.send_cmd('pin_up')
+ self.send_cmd("set_OD_output_mode")
+ self.send_cmd("pin_up")
+
cmd_BLTOUCH_DEBUG_help = "Send a command to the bltouch for debugging"
+
def cmd_BLTOUCH_DEBUG(self, gcmd):
- cmd = gcmd.get('COMMAND', None)
+ cmd = gcmd.get("COMMAND", None)
if cmd is None or cmd not in Commands:
- gcmd.respond_info("BLTouch commands: %s" % (
- ", ".join(sorted([c for c in Commands if c is not None]))))
+ gcmd.respond_info(
+ "BLTouch commands: %s"
+ % (", ".join(sorted([c for c in Commands if c is not None])))
+ )
return
gcmd.respond_info("Sending BLTOUCH_DEBUG COMMAND=%s" % (cmd,))
self.sync_print_time()
self.send_cmd(cmd, duration=self.pin_move_time)
self.sync_print_time()
+
cmd_BLTOUCH_STORE_help = "Store an output mode in the BLTouch EEPROM"
+
def cmd_BLTOUCH_STORE(self, gcmd):
- cmd = gcmd.get('MODE', None)
- if cmd is None or cmd not in ['5V', 'OD']:
+ cmd = gcmd.get("MODE", None)
+ if cmd is None or cmd not in ["5V", "OD"]:
gcmd.respond_info("BLTouch output modes: 5V, OD")
return
gcmd.respond_info("Storing BLTouch output mode: %s" % (cmd,))
@@ -282,7 +321,8 @@ class BLTouchProbe:
self.store_output_mode(cmd)
self.sync_print_time()
+
def load_config(config):
blt = BLTouchProbe(config)
- config.get_printer().add_object('probe', blt)
+ config.get_printer().add_object("probe", blt)
return blt
diff --git a/klippy/extras/bme280.py b/klippy/extras/bme280.py
index 1c26bbee..44de9de3 100644
--- a/klippy/extras/bme280.py
+++ b/klippy/extras/bme280.py
@@ -6,15 +6,25 @@
import logging
from . import bus
-REPORT_TIME = .8
+REPORT_TIME = 0.8
BME280_CHIP_ADDR = 0x76
BME280_REGS = {
- 'RESET': 0xE0, 'CTRL_HUM': 0xF2,
- 'STATUS': 0xF3, 'CTRL_MEAS': 0xF4, 'CONFIG': 0xF5,
- 'PRESSURE_MSB': 0xF7, 'PRESSURE_LSB': 0xF8, 'PRESSURE_XLSB': 0xF9,
- 'TEMP_MSB': 0xFA, 'TEMP_LSB': 0xFB, 'TEMP_XLSB': 0xFC,
- 'HUM_MSB': 0xFD, 'HUM_LSB': 0xFE, 'CAL_1': 0x88, 'CAL_2': 0xE1
+ "RESET": 0xE0,
+ "CTRL_HUM": 0xF2,
+ "STATUS": 0xF3,
+ "CTRL_MEAS": 0xF4,
+ "CONFIG": 0xF5,
+ "PRESSURE_MSB": 0xF7,
+ "PRESSURE_LSB": 0xF8,
+ "PRESSURE_XLSB": 0xF9,
+ "TEMP_MSB": 0xFA,
+ "TEMP_LSB": 0xFB,
+ "TEMP_XLSB": 0xFC,
+ "HUM_MSB": 0xFD,
+ "HUM_LSB": 0xFE,
+ "CAL_1": 0x88,
+ "CAL_2": 0xE1,
}
BMP388_REGS = {
@@ -41,43 +51,61 @@ BMP388_REG_VAL_DRDY_EN = 0b100000
BMP388_REG_VAL_NORMAL_MODE = 0x30
BME680_REGS = {
- 'RESET': 0xE0, 'CTRL_HUM': 0x72, 'CTRL_GAS_1': 0x71, 'CTRL_GAS_0': 0x70,
- 'GAS_WAIT_0': 0x64, 'RES_HEAT_0': 0x5A, 'IDAC_HEAT_0': 0x50,
- 'STATUS': 0x73, 'EAS_STATUS_0': 0x1D, 'CTRL_MEAS': 0x74, 'CONFIG': 0x75,
- 'GAS_R_LSB': 0x2B, 'GAS_R_MSB': 0x2A,
- 'PRESSURE_MSB': 0x1F, 'PRESSURE_LSB': 0x20, 'PRESSURE_XLSB': 0x21,
- 'TEMP_MSB': 0x22, 'TEMP_LSB': 0x23, 'TEMP_XLSB': 0x24,
- 'HUM_MSB': 0x25, 'HUM_LSB': 0x26, 'CAL_1': 0x88, 'CAL_2': 0xE1,
- 'RES_HEAT_VAL': 0x00, 'RES_HEAT_RANGE': 0x02, 'RANGE_SWITCHING_ERROR': 0x04
+ "RESET": 0xE0,
+ "CTRL_HUM": 0x72,
+ "CTRL_GAS_1": 0x71,
+ "CTRL_GAS_0": 0x70,
+ "GAS_WAIT_0": 0x64,
+ "RES_HEAT_0": 0x5A,
+ "IDAC_HEAT_0": 0x50,
+ "STATUS": 0x73,
+ "EAS_STATUS_0": 0x1D,
+ "CTRL_MEAS": 0x74,
+ "CONFIG": 0x75,
+ "GAS_R_LSB": 0x2B,
+ "GAS_R_MSB": 0x2A,
+ "PRESSURE_MSB": 0x1F,
+ "PRESSURE_LSB": 0x20,
+ "PRESSURE_XLSB": 0x21,
+ "TEMP_MSB": 0x22,
+ "TEMP_LSB": 0x23,
+ "TEMP_XLSB": 0x24,
+ "HUM_MSB": 0x25,
+ "HUM_LSB": 0x26,
+ "CAL_1": 0x88,
+ "CAL_2": 0xE1,
+ "RES_HEAT_VAL": 0x00,
+ "RES_HEAT_RANGE": 0x02,
+ "RANGE_SWITCHING_ERROR": 0x04,
}
BME680_GAS_CONSTANTS = {
- 0: (1., 8000000.),
- 1: (1., 4000000.),
- 2: (1., 2000000.),
- 3: (1., 1000000.),
- 4: (1., 499500.4995),
+ 0: (1.0, 8000000.0),
+ 1: (1.0, 4000000.0),
+ 2: (1.0, 2000000.0),
+ 3: (1.0, 1000000.0),
+ 4: (1.0, 499500.4995),
5: (0.99, 248262.1648),
- 6: (1., 125000.),
+ 6: (1.0, 125000.0),
7: (0.992, 63004.03226),
- 8: (1., 31281.28128),
- 9: (1., 15625.),
+ 8: (1.0, 31281.28128),
+ 9: (1.0, 15625.0),
10: (0.998, 7812.5),
11: (0.995, 3906.25),
- 12: (1., 1953.125),
+ 12: (1.0, 1953.125),
13: (0.99, 976.5625),
- 14: (1., 488.28125),
- 15: (1., 244.140625)
+ 14: (1.0, 488.28125),
+ 15: (1.0, 244.140625),
}
BMP180_REGS = {
- 'RESET': 0xE0,
- 'CAL_1': 0xAA,
- 'CTRL_MEAS': 0xF4,
- 'REG_MSB': 0xF6,
- 'REG_LSB': 0xF7,
- 'CRV_TEMP': 0x2E,
- 'CRV_PRES': 0x34
+ "RESET": 0xE0,
+ "CAL_1": 0xAA,
+ "CTRL_MEAS": 0xF4,
+ "REG_MSB": 0xF6,
+ "REG_LSB": 0xF7,
+ "CRV_TEMP": 0x2E,
+ "CRV_PRES": 0x34,
}
STATUS_MEASURING = 1 << 3
@@ -92,8 +120,11 @@ MEASURE_DONE = 1 << 5
RESET_CHIP_VALUE = 0xB6
BME_CHIPS = {
- 0x58: 'BMP280', 0x60: 'BME280', 0x61: 'BME680', 0x55: 'BMP180',
- 0x50: 'BMP388'
+ 0x58: "BMP280",
+ 0x60: "BME280",
+ 0x61: "BME680",
+ 0x55: "BMP180",
+ 0x50: "BMP388",
}
BME_CHIP_ID_REG = 0xD0
BMP3_CHIP_ID_REG = 0x00
@@ -101,7 +132,7 @@ BMP3_CHIP_ID_REG = 0x00
def get_twos_complement(val, bit_size):
if val & (1 << (bit_size - 1)):
- val -= (1 << bit_size)
+ val -= 1 << bit_size
return val
@@ -126,37 +157,43 @@ def get_signed_short_msb(bits):
val = get_unsigned_short_msb(bits)
return get_twos_complement(val, 16)
+
class BME280:
def __init__(self, config):
self.printer = config.get_printer()
self.name = config.get_name().split()[-1]
self.reactor = self.printer.get_reactor()
self.i2c = bus.MCU_I2C_from_config(
- config, default_addr=BME280_CHIP_ADDR, default_speed=100000)
+ config, default_addr=BME280_CHIP_ADDR, default_speed=100000
+ )
self.mcu = self.i2c.get_mcu()
- self.iir_filter = config.getint('bme280_iir_filter', 1)
- self.os_temp = config.getint('bme280_oversample_temp', 2)
- self.os_hum = config.getint('bme280_oversample_hum', 2)
- self.os_pres = config.getint('bme280_oversample_pressure', 2)
- self.gas_heat_temp = config.getint('bme280_gas_target_temp', 320)
- self.gas_heat_duration = config.getint('bme280_gas_heat_duration', 150)
- logging.info("BMxx80: Oversampling: Temp %dx Humid %dx Pressure %dx" % (
- pow(2, self.os_temp - 1), pow(2, self.os_hum - 1),
- pow(2, self.os_pres - 1)))
+ self.iir_filter = config.getint("bme280_iir_filter", 1)
+ self.os_temp = config.getint("bme280_oversample_temp", 2)
+ self.os_hum = config.getint("bme280_oversample_hum", 2)
+ self.os_pres = config.getint("bme280_oversample_pressure", 2)
+ self.gas_heat_temp = config.getint("bme280_gas_target_temp", 320)
+ self.gas_heat_duration = config.getint("bme280_gas_heat_duration", 150)
+ logging.info(
+ "BMxx80: Oversampling: Temp %dx Humid %dx Pressure %dx"
+ % (
+ pow(2, self.os_temp - 1),
+ pow(2, self.os_hum - 1),
+ pow(2, self.os_pres - 1),
+ )
+ )
logging.info("BMxx80: IIR: %dx" % (pow(2, self.iir_filter) - 1))
self.iir_filter = self.iir_filter & 0x07
- self.temp = self.pressure = self.humidity = self.gas = self.t_fine = 0.
- self.min_temp = self.max_temp = self.range_switching_error = 0.
+ self.temp = self.pressure = self.humidity = self.gas = self.t_fine = 0.0
+ self.min_temp = self.max_temp = self.range_switching_error = 0.0
self.max_sample_time = None
self.dig = self.sample_timer = None
- self.chip_type = 'BMP280'
+ self.chip_type = "BMP280"
self.chip_registers = BME280_REGS
self.printer.add_object("bme280 " + self.name, self)
- if self.printer.get_start_args().get('debugoutput') is not None:
+ if self.printer.get_start_args().get("debugoutput") is not None:
return
- self.printer.register_event_handler("klippy:connect",
- self.handle_connect)
+ self.printer.register_event_handler("klippy:connect", self.handle_connect)
self.last_gas_time = 0
def handle_connect(self):
@@ -176,19 +213,19 @@ class BME280:
def _init_bmxx80(self):
def read_calibration_data_bmp280(calib_data_1):
dig = {}
- dig['T1'] = get_unsigned_short(calib_data_1[0:2])
- dig['T2'] = get_signed_short(calib_data_1[2:4])
- dig['T3'] = get_signed_short(calib_data_1[4:6])
+ dig["T1"] = get_unsigned_short(calib_data_1[0:2])
+ dig["T2"] = get_signed_short(calib_data_1[2:4])
+ dig["T3"] = get_signed_short(calib_data_1[4:6])
- dig['P1'] = get_unsigned_short(calib_data_1[6:8])
- dig['P2'] = get_signed_short(calib_data_1[8:10])
- dig['P3'] = get_signed_short(calib_data_1[10:12])
- dig['P4'] = get_signed_short(calib_data_1[12:14])
- dig['P5'] = get_signed_short(calib_data_1[14:16])
- dig['P6'] = get_signed_short(calib_data_1[16:18])
- dig['P7'] = get_signed_short(calib_data_1[18:20])
- dig['P8'] = get_signed_short(calib_data_1[20:22])
- dig['P9'] = get_signed_short(calib_data_1[22:24])
+ dig["P1"] = get_unsigned_short(calib_data_1[6:8])
+ dig["P2"] = get_signed_short(calib_data_1[8:10])
+ dig["P3"] = get_signed_short(calib_data_1[10:12])
+ dig["P4"] = get_signed_short(calib_data_1[12:14])
+ dig["P5"] = get_signed_short(calib_data_1[14:16])
+ dig["P6"] = get_signed_short(calib_data_1[16:18])
+ dig["P7"] = get_signed_short(calib_data_1[18:20])
+ dig["P8"] = get_signed_short(calib_data_1[20:22])
+ dig["P9"] = get_signed_short(calib_data_1[22:24])
return dig
def read_calibration_data_bmp388(calib_data_1):
@@ -216,63 +253,67 @@ class BME280:
def read_calibration_data_bme280(calib_data_1, calib_data_2):
dig = read_calibration_data_bmp280(calib_data_1)
- dig['H1'] = calib_data_1[25] & 0xFF
- dig['H2'] = get_signed_short(calib_data_2[0:2])
- dig['H3'] = calib_data_2[2] & 0xFF
- dig['H4'] = get_twos_complement(
- (calib_data_2[3] << 4) | (calib_data_2[4] & 0x0F), 12)
- dig['H5'] = get_twos_complement(
- (calib_data_2[5] << 4) | ((calib_data_2[4] & 0xF0) >> 4), 12)
- dig['H6'] = get_twos_complement(calib_data_2[6], 8)
+ dig["H1"] = calib_data_1[25] & 0xFF
+ dig["H2"] = get_signed_short(calib_data_2[0:2])
+ dig["H3"] = calib_data_2[2] & 0xFF
+ dig["H4"] = get_twos_complement(
+ (calib_data_2[3] << 4) | (calib_data_2[4] & 0x0F), 12
+ )
+ dig["H5"] = get_twos_complement(
+ (calib_data_2[5] << 4) | ((calib_data_2[4] & 0xF0) >> 4), 12
+ )
+ dig["H6"] = get_twos_complement(calib_data_2[6], 8)
return dig
def read_calibration_data_bme680(calib_data_1, calib_data_2):
dig = {}
- dig['T1'] = get_unsigned_short(calib_data_2[8:10])
- dig['T2'] = get_signed_short(calib_data_1[2:4])
- dig['T3'] = get_signed_byte(calib_data_1[4])
+ dig["T1"] = get_unsigned_short(calib_data_2[8:10])
+ dig["T2"] = get_signed_short(calib_data_1[2:4])
+ dig["T3"] = get_signed_byte(calib_data_1[4])
- dig['P1'] = get_unsigned_short(calib_data_1[6:8])
- dig['P2'] = get_signed_short(calib_data_1[8:10])
- dig['P3'] = calib_data_1[10]
- dig['P4'] = get_signed_short(calib_data_1[12:14])
- dig['P5'] = get_signed_short(calib_data_1[14:16])
- dig['P6'] = get_signed_byte(calib_data_1[17])
- dig['P7'] = get_signed_byte(calib_data_1[16])
- dig['P8'] = get_signed_short(calib_data_1[20:22])
- dig['P9'] = get_signed_short(calib_data_1[22:24])
- dig['P10'] = calib_data_1[24]
+ dig["P1"] = get_unsigned_short(calib_data_1[6:8])
+ dig["P2"] = get_signed_short(calib_data_1[8:10])
+ dig["P3"] = calib_data_1[10]
+ dig["P4"] = get_signed_short(calib_data_1[12:14])
+ dig["P5"] = get_signed_short(calib_data_1[14:16])
+ dig["P6"] = get_signed_byte(calib_data_1[17])
+ dig["P7"] = get_signed_byte(calib_data_1[16])
+ dig["P8"] = get_signed_short(calib_data_1[20:22])
+ dig["P9"] = get_signed_short(calib_data_1[22:24])
+ dig["P10"] = calib_data_1[24]
- dig['H1'] = get_twos_complement(
- (calib_data_2[2] << 4) | (calib_data_2[1] & 0x0F), 12)
- dig['H2'] = get_twos_complement(
- (calib_data_2[0] << 4) | ((calib_data_2[1] & 0xF0) >> 4), 12)
- dig['H3'] = get_signed_byte(calib_data_2[3])
- dig['H4'] = get_signed_byte(calib_data_2[4])
- dig['H5'] = get_signed_byte(calib_data_2[5])
- dig['H6'] = calib_data_2[6]
- dig['H7'] = get_signed_byte(calib_data_2[7])
+ dig["H1"] = get_twos_complement(
+ (calib_data_2[2] << 4) | (calib_data_2[1] & 0x0F), 12
+ )
+ dig["H2"] = get_twos_complement(
+ (calib_data_2[0] << 4) | ((calib_data_2[1] & 0xF0) >> 4), 12
+ )
+ dig["H3"] = get_signed_byte(calib_data_2[3])
+ dig["H4"] = get_signed_byte(calib_data_2[4])
+ dig["H5"] = get_signed_byte(calib_data_2[5])
+ dig["H6"] = calib_data_2[6]
+ dig["H7"] = get_signed_byte(calib_data_2[7])
- dig['G1'] = get_signed_byte(calib_data_2[12])
- dig['G2'] = get_signed_short(calib_data_2[10:12])
- dig['G3'] = get_signed_byte(calib_data_2[13])
+ dig["G1"] = get_signed_byte(calib_data_2[12])
+ dig["G2"] = get_signed_short(calib_data_2[10:12])
+ dig["G3"] = get_signed_byte(calib_data_2[13])
return dig
def read_calibration_data_bmp180(calib_data_1):
dig = {}
- dig['AC1'] = get_signed_short_msb(calib_data_1[0:2])
- dig['AC2'] = get_signed_short_msb(calib_data_1[2:4])
- dig['AC3'] = get_signed_short_msb(calib_data_1[4:6])
- dig['AC4'] = get_unsigned_short_msb(calib_data_1[6:8])
- dig['AC5'] = get_unsigned_short_msb(calib_data_1[8:10])
- dig['AC6'] = get_unsigned_short_msb(calib_data_1[10:12])
+ dig["AC1"] = get_signed_short_msb(calib_data_1[0:2])
+ dig["AC2"] = get_signed_short_msb(calib_data_1[2:4])
+ dig["AC3"] = get_signed_short_msb(calib_data_1[4:6])
+ dig["AC4"] = get_unsigned_short_msb(calib_data_1[6:8])
+ dig["AC5"] = get_unsigned_short_msb(calib_data_1[8:10])
+ dig["AC6"] = get_unsigned_short_msb(calib_data_1[10:12])
- dig['B1'] = get_signed_short_msb(calib_data_1[12:14])
- dig['B2'] = get_signed_short_msb(calib_data_1[14:16])
+ dig["B1"] = get_signed_short_msb(calib_data_1[12:14])
+ dig["B2"] = get_signed_short_msb(calib_data_1[14:16])
- dig['MB'] = get_signed_short_msb(calib_data_1[16:18])
- dig['MC'] = get_signed_short_msb(calib_data_1[18:20])
- dig['MD'] = get_signed_short_msb(calib_data_1[20:22])
+ dig["MB"] = get_signed_short_msb(calib_data_1[16:18])
+ dig["MC"] = get_signed_short_msb(calib_data_1[18:20])
+ dig["MD"] = get_signed_short_msb(calib_data_1[20:22])
return dig
chip_id = self.read_id() or self.read_bmp3_id()
@@ -280,31 +321,35 @@ class BME280:
logging.info("bme280: Unknown Chip ID received %#x" % chip_id)
else:
self.chip_type = BME_CHIPS[chip_id]
- logging.info("bme280: Found Chip %s at %#x" % (
- self.chip_type, self.i2c.i2c_address))
+ logging.info(
+ "bme280: Found Chip %s at %#x" % (self.chip_type, self.i2c.i2c_address)
+ )
# Reset chip
- self.write_register('RESET', [RESET_CHIP_VALUE], wait=True)
- self.reactor.pause(self.reactor.monotonic() + .5)
+ self.write_register("RESET", [RESET_CHIP_VALUE], wait=True)
+ self.reactor.pause(self.reactor.monotonic() + 0.5)
# Make sure non-volatile memory has been copied to registers
- if self.chip_type != 'BMP180':
+ if self.chip_type != "BMP180":
# BMP180 has no status register available
- status = self.read_register('STATUS', 1)[0]
+ status = self.read_register("STATUS", 1)[0]
while status & STATUS_IM_UPDATE:
- self.reactor.pause(self.reactor.monotonic() + .01)
- status = self.read_register('STATUS', 1)[0]
+ self.reactor.pause(self.reactor.monotonic() + 0.01)
+ status = self.read_register("STATUS", 1)[0]
- if self.chip_type == 'BME680':
- self.max_sample_time = \
- (1.25 + (2.3 * self.os_temp) + ((2.3 * self.os_pres) + .575)
- + ((2.3 * self.os_hum) + .575)) / 1000
+ if self.chip_type == "BME680":
+ self.max_sample_time = (
+ 1.25
+ + (2.3 * self.os_temp)
+ + ((2.3 * self.os_pres) + 0.575)
+ + ((2.3 * self.os_hum) + 0.575)
+ ) / 1000
self.sample_timer = self.reactor.register_timer(self._sample_bme680)
self.chip_registers = BME680_REGS
- elif self.chip_type == 'BMP180':
+ elif self.chip_type == "BMP180":
self.sample_timer = self.reactor.register_timer(self._sample_bmp180)
self.chip_registers = BMP180_REGS
- elif self.chip_type == 'BMP388':
+ elif self.chip_type == "BMP388":
self.chip_registers = BMP388_REGS
self.write_register(
"PWR_CTRL",
@@ -321,43 +366,46 @@ class BME280:
self.write_register("INT_CTRL", [BMP388_REG_VAL_DRDY_EN])
self.sample_timer = self.reactor.register_timer(self._sample_bmp388)
- elif self.chip_type == 'BME280':
- self.max_sample_time = \
- (1.25 + (2.3 * self.os_temp) + ((2.3 * self.os_pres) + .575)
- + ((2.3 * self.os_hum) + .575)) / 1000
+ elif self.chip_type == "BME280":
+ self.max_sample_time = (
+ 1.25
+ + (2.3 * self.os_temp)
+ + ((2.3 * self.os_pres) + 0.575)
+ + ((2.3 * self.os_hum) + 0.575)
+ ) / 1000
self.sample_timer = self.reactor.register_timer(self._sample_bme280)
self.chip_registers = BME280_REGS
else:
- self.max_sample_time = \
- (1.25 + (2.3 * self.os_temp)
- + ((2.3 * self.os_pres) + .575)) / 1000
+ self.max_sample_time = (
+ 1.25 + (2.3 * self.os_temp) + ((2.3 * self.os_pres) + 0.575)
+ ) / 1000
self.sample_timer = self.reactor.register_timer(self._sample_bme280)
self.chip_registers = BME280_REGS
# Read out and calculate the trimming parameters
- if self.chip_type == 'BMP180':
- cal_1 = self.read_register('CAL_1', 22)
- elif self.chip_type == 'BMP388':
- cal_1 = self.read_register('CAL_1', 21)
+ if self.chip_type == "BMP180":
+ cal_1 = self.read_register("CAL_1", 22)
+ elif self.chip_type == "BMP388":
+ cal_1 = self.read_register("CAL_1", 21)
else:
- cal_1 = self.read_register('CAL_1', 26)
- cal_2 = self.read_register('CAL_2', 16)
- if self.chip_type == 'BME280':
+ cal_1 = self.read_register("CAL_1", 26)
+ cal_2 = self.read_register("CAL_2", 16)
+ if self.chip_type == "BME280":
self.dig = read_calibration_data_bme280(cal_1, cal_2)
- elif self.chip_type == 'BMP280':
+ elif self.chip_type == "BMP280":
self.dig = read_calibration_data_bmp280(cal_1)
- elif self.chip_type == 'BME680':
+ elif self.chip_type == "BME680":
self.dig = read_calibration_data_bme680(cal_1, cal_2)
- elif self.chip_type == 'BMP180':
+ elif self.chip_type == "BMP180":
self.dig = read_calibration_data_bmp180(cal_1)
- elif self.chip_type == 'BMP388':
+ elif self.chip_type == "BMP388":
self.dig = read_calibration_data_bmp388(cal_1)
- if self.chip_type in ('BME280', 'BMP280'):
+ if self.chip_type in ("BME280", "BMP280"):
max_standby_time = REPORT_TIME - self.max_sample_time
# 0.5 ms
t_sb = 0
- if self.chip_type == 'BME280':
+ if self.chip_type == "BME280":
if max_standby_time > 1:
t_sb = 5
elif max_standby_time > 0.5:
@@ -389,50 +437,51 @@ class BME280:
t_sb = 1
cfg = t_sb << 5 | self.iir_filter << 2
- self.write_register('CONFIG', cfg)
- if self.chip_type == 'BME280':
- self.write_register('CTRL_HUM', self.os_hum)
+ self.write_register("CONFIG", cfg)
+ if self.chip_type == "BME280":
+ self.write_register("CTRL_HUM", self.os_hum)
# Enter normal (periodic) mode
meas = self.os_temp << 5 | self.os_pres << 2 | MODE_PERIODIC
- self.write_register('CTRL_MEAS', meas, wait=True)
+ self.write_register("CTRL_MEAS", meas, wait=True)
- if self.chip_type == 'BME680':
- self.write_register('CONFIG', self.iir_filter << 2)
+ if self.chip_type == "BME680":
+ self.write_register("CONFIG", self.iir_filter << 2)
# Should be set once and reused on every mode register write
- self.write_register('CTRL_HUM', self.os_hum & 0x07)
+ self.write_register("CTRL_HUM", self.os_hum & 0x07)
gas_wait_0 = self._calc_gas_heater_duration(self.gas_heat_duration)
- self.write_register('GAS_WAIT_0', [gas_wait_0])
+ self.write_register("GAS_WAIT_0", [gas_wait_0])
res_heat_0 = self._calc_gas_heater_resistance(self.gas_heat_temp)
- self.write_register('RES_HEAT_0', [res_heat_0])
+ self.write_register("RES_HEAT_0", [res_heat_0])
# Set initial heater current to reach Gas heater target on start
- self.write_register('IDAC_HEAT_0', 96)
+ self.write_register("IDAC_HEAT_0", 96)
def _sample_bme280(self, eventtime):
# In normal mode data shadowing is performed
# So reading can be done while measurements are in process
try:
- if self.chip_type == 'BME280':
- data = self.read_register('PRESSURE_MSB', 8)
- elif self.chip_type == 'BMP280':
- data = self.read_register('PRESSURE_MSB', 6)
+ if self.chip_type == "BME280":
+ data = self.read_register("PRESSURE_MSB", 8)
+ elif self.chip_type == "BMP280":
+ data = self.read_register("PRESSURE_MSB", 6)
else:
return self.reactor.NEVER
except Exception:
logging.exception("BME280: Error reading data")
- self.temp = self.pressure = self.humidity = .0
+ self.temp = self.pressure = self.humidity = 0.0
return self.reactor.NEVER
temp_raw = (data[3] << 12) | (data[4] << 4) | (data[5] >> 4)
self.temp = self._compensate_temp(temp_raw)
pressure_raw = (data[0] << 12) | (data[1] << 4) | (data[2] >> 4)
- self.pressure = self._compensate_pressure_bme280(pressure_raw) / 100.
- if self.chip_type == 'BME280':
+ self.pressure = self._compensate_pressure_bme280(pressure_raw) / 100.0
+ if self.chip_type == "BME280":
humid_raw = (data[6] << 8) | data[7]
self.humidity = self._compensate_humidity_bme280(humid_raw)
if self.temp < self.min_temp or self.temp > self.max_temp:
self.printer.invoke_shutdown(
"BME280 temperature %0.1f outside range of %0.1f:%.01f"
- % (self.temp, self.min_temp, self.max_temp))
+ % (self.temp, self.min_temp, self.max_temp)
+ )
measured_time = self.reactor.monotonic()
self._callback(self.mcu.estimated_print_time(measured_time), self.temp)
return measured_time + REPORT_TIME
@@ -490,7 +539,7 @@ class BME280:
partial_data1 = self.dig["P2"] * self.t_fine
partial_data2 = self.dig["P3"] * (self.t_fine * self.t_fine)
partial_data3 = self.dig["P4"]
- partial_data3 *= (self.t_fine * self.t_fine * self.t_fine)
+ partial_data3 *= self.t_fine * self.t_fine * self.t_fine
partial_out2 = adc_P * (
self.dig["P1"] + partial_data1 + partial_data2 + partial_data3
)
@@ -512,7 +561,7 @@ class BME280:
def _sample_bme680(self, eventtime):
def data_ready(stat, run_gas):
- new_data = (stat & EAS_NEW_DATA)
+ new_data = stat & EAS_NEW_DATA
gas_done = not (stat & GAS_DONE)
meas_done = not (stat & MEASURE_DONE)
if not run_gas:
@@ -523,31 +572,30 @@ class BME280:
# Check VOC once a while
if self.reactor.monotonic() - self.last_gas_time > 3:
gas_config = RUN_GAS | NB_CONV_0
- self.write_register('CTRL_GAS_1', [gas_config])
+ self.write_register("CTRL_GAS_1", [gas_config])
run_gas = True
# Enter forced mode
meas = self.os_temp << 5 | self.os_pres << 2 | MODE
- self.write_register('CTRL_MEAS', meas, wait=True)
+ self.write_register("CTRL_MEAS", meas, wait=True)
max_sample_time = self.max_sample_time
if run_gas:
max_sample_time += self.gas_heat_duration / 1000
self.reactor.pause(self.reactor.monotonic() + max_sample_time)
try:
# wait until results are ready
- status = self.read_register('EAS_STATUS_0', 1)[0]
+ status = self.read_register("EAS_STATUS_0", 1)[0]
while not data_ready(status, run_gas):
- self.reactor.pause(
- self.reactor.monotonic() + self.max_sample_time)
- status = self.read_register('EAS_STATUS_0', 1)[0]
+ self.reactor.pause(self.reactor.monotonic() + self.max_sample_time)
+ status = self.read_register("EAS_STATUS_0", 1)[0]
- data = self.read_register('PRESSURE_MSB', 8)
+ data = self.read_register("PRESSURE_MSB", 8)
gas_data = [0, 0]
if run_gas:
- gas_data = self.read_register('GAS_R_MSB', 2)
+ gas_data = self.read_register("GAS_R_MSB", 2)
except Exception:
logging.exception("BME680: Error reading data")
- self.temp = self.pressure = self.humidity = self.gas = .0
+ self.temp = self.pressure = self.humidity = self.gas = 0.0
return self.reactor.NEVER
temp_raw = (data[3] << 12) | (data[4] << 4) | (data[5] >> 4)
@@ -555,169 +603,191 @@ class BME280:
self.temp = self._compensate_temp(temp_raw)
pressure_raw = (data[0] << 12) | (data[1] << 4) | (data[2] >> 4)
if pressure_raw != 0x80000:
- self.pressure = self._compensate_pressure_bme680(
- pressure_raw) / 100.
+ self.pressure = self._compensate_pressure_bme680(pressure_raw) / 100.0
humid_raw = (data[6] << 8) | data[7]
self.humidity = self._compensate_humidity_bme680(humid_raw)
- gas_valid = ((gas_data[1] & 0x20) == 0x20)
+ gas_valid = (gas_data[1] & 0x20) == 0x20
if gas_valid:
- gas_heater_stable = ((gas_data[1] & 0x10) == 0x10)
+ gas_heater_stable = (gas_data[1] & 0x10) == 0x10
if not gas_heater_stable:
logging.warning("BME680: Gas heater didn't reach target")
gas_raw = (gas_data[0] << 2) | ((gas_data[1] & 0xC0) >> 6)
- gas_range = (gas_data[1] & 0x0F)
+ gas_range = gas_data[1] & 0x0F
self.gas = self._compensate_gas(gas_raw, gas_range)
# Disable gas measurement on success
gas_config = NB_CONV_0
- self.write_register('CTRL_GAS_1', [gas_config])
+ self.write_register("CTRL_GAS_1", [gas_config])
self.last_gas_time = self.reactor.monotonic()
if self.temp < self.min_temp or self.temp > self.max_temp:
self.printer.invoke_shutdown(
"BME680 temperature %0.1f outside range of %0.1f:%.01f"
- % (self.temp, self.min_temp, self.max_temp))
+ % (self.temp, self.min_temp, self.max_temp)
+ )
measured_time = self.reactor.monotonic()
self._callback(self.mcu.estimated_print_time(measured_time), self.temp)
return measured_time + REPORT_TIME
def _sample_bmp180(self, eventtime):
- meas = self.chip_registers['CRV_TEMP']
- self.write_register('CTRL_MEAS', meas)
+ meas = self.chip_registers["CRV_TEMP"]
+ self.write_register("CTRL_MEAS", meas)
try:
- self.reactor.pause(self.reactor.monotonic() + .01)
- data = self.read_register('REG_MSB', 2)
+ self.reactor.pause(self.reactor.monotonic() + 0.01)
+ data = self.read_register("REG_MSB", 2)
temp_raw = (data[0] << 8) | data[1]
except Exception:
logging.exception("BMP180: Error reading temperature")
- self.temp = self.pressure = .0
+ self.temp = self.pressure = 0.0
return self.reactor.NEVER
- meas = self.chip_registers['CRV_PRES'] | (self.os_pres << 6)
- self.write_register('CTRL_MEAS', meas)
+ meas = self.chip_registers["CRV_PRES"] | (self.os_pres << 6)
+ self.write_register("CTRL_MEAS", meas)
try:
- self.reactor.pause(self.reactor.monotonic() + .01)
- data = self.read_register('REG_MSB', 3)
- pressure_raw = \
- ((data[0] << 16)|(data[1] << 8)|data[2]) >> (8 - self.os_pres)
+ self.reactor.pause(self.reactor.monotonic() + 0.01)
+ data = self.read_register("REG_MSB", 3)
+ pressure_raw = ((data[0] << 16) | (data[1] << 8) | data[2]) >> (
+ 8 - self.os_pres
+ )
except Exception:
logging.exception("BMP180: Error reading pressure")
- self.temp = self.pressure = .0
+ self.temp = self.pressure = 0.0
return self.reactor.NEVER
self.temp = self._compensate_temp_bmp180(temp_raw)
- self.pressure = self._compensate_pressure_bmp180(pressure_raw) / 100.
+ self.pressure = self._compensate_pressure_bmp180(pressure_raw) / 100.0
if self.temp < self.min_temp or self.temp > self.max_temp:
self.printer.invoke_shutdown(
"BMP180 temperature %0.1f outside range of %0.1f:%.01f"
- % (self.temp, self.min_temp, self.max_temp))
+ % (self.temp, self.min_temp, self.max_temp)
+ )
measured_time = self.reactor.monotonic()
self._callback(self.mcu.estimated_print_time(measured_time), self.temp)
return measured_time + REPORT_TIME
-
def _compensate_temp(self, raw_temp):
dig = self.dig
- var1 = ((raw_temp / 16384. - (dig['T1'] / 1024.)) * dig['T2'])
+ var1 = (raw_temp / 16384.0 - (dig["T1"] / 1024.0)) * dig["T2"]
var2 = (
- ((raw_temp / 131072.) - (dig['T1'] / 8192.)) *
- ((raw_temp / 131072.) - (dig['T1'] / 8192.)) * dig['T3'])
+ ((raw_temp / 131072.0) - (dig["T1"] / 8192.0))
+ * ((raw_temp / 131072.0) - (dig["T1"] / 8192.0))
+ * dig["T3"]
+ )
self.t_fine = var1 + var2
return self.t_fine / 5120.0
def _compensate_pressure_bme280(self, raw_pressure):
dig = self.dig
t_fine = self.t_fine
- var1 = t_fine / 2. - 64000.
- var2 = var1 * var1 * dig['P6'] / 32768.
- var2 = var2 + var1 * dig['P5'] * 2.
- var2 = var2 / 4. + (dig['P4'] * 65536.)
- var1 = (dig['P3'] * var1 * var1 / 524288. + dig['P2'] * var1) / 524288.
- var1 = (1. + var1 / 32768.) * dig['P1']
+ var1 = t_fine / 2.0 - 64000.0
+ var2 = var1 * var1 * dig["P6"] / 32768.0
+ var2 = var2 + var1 * dig["P5"] * 2.0
+ var2 = var2 / 4.0 + (dig["P4"] * 65536.0)
+ var1 = (dig["P3"] * var1 * var1 / 524288.0 + dig["P2"] * var1) / 524288.0
+ var1 = (1.0 + var1 / 32768.0) * dig["P1"]
if var1 == 0:
- return 0.
+ return 0.0
else:
pressure = 1048576.0 - raw_pressure
- pressure = ((pressure - var2 / 4096.) * 6250.) / var1
- var1 = dig['P9'] * pressure * pressure / 2147483648.
- var2 = pressure * dig['P8'] / 32768.
- return pressure + (var1 + var2 + dig['P7']) / 16.
+ pressure = ((pressure - var2 / 4096.0) * 6250.0) / var1
+ var1 = dig["P9"] * pressure * pressure / 2147483648.0
+ var2 = pressure * dig["P8"] / 32768.0
+ return pressure + (var1 + var2 + dig["P7"]) / 16.0
def _compensate_pressure_bme680(self, raw_pressure):
dig = self.dig
t_fine = self.t_fine
- var1 = t_fine / 2. - 64000.
- var2 = var1 * var1 * dig['P6'] / 131072.
- var2 = var2 + var1 * dig['P5'] * 2.
- var2 = var2 / 4. + (dig['P4'] * 65536.)
- var1 = (dig['P3'] * var1 * var1 / 16384. + dig['P2'] * var1) / 524288.
- var1 = (1. + var1 / 32768.) * dig['P1']
+ var1 = t_fine / 2.0 - 64000.0
+ var2 = var1 * var1 * dig["P6"] / 131072.0
+ var2 = var2 + var1 * dig["P5"] * 2.0
+ var2 = var2 / 4.0 + (dig["P4"] * 65536.0)
+ var1 = (dig["P3"] * var1 * var1 / 16384.0 + dig["P2"] * var1) / 524288.0
+ var1 = (1.0 + var1 / 32768.0) * dig["P1"]
if var1 == 0:
- return 0.
+ return 0.0
else:
pressure = 1048576.0 - raw_pressure
- pressure = ((pressure - var2 / 4096.) * 6250.) / var1
- var1 = dig['P9'] * pressure * pressure / 2147483648.
- var2 = pressure * dig['P8'] / 32768.
- var3 = (pressure / 256.) * (pressure / 256.) * (pressure / 256.) * (
- dig['P10'] / 131072.)
- return pressure + (var1 + var2 + var3 + (dig['P7'] * 128.)) / 16.
+ pressure = ((pressure - var2 / 4096.0) * 6250.0) / var1
+ var1 = dig["P9"] * pressure * pressure / 2147483648.0
+ var2 = pressure * dig["P8"] / 32768.0
+ var3 = (
+ (pressure / 256.0)
+ * (pressure / 256.0)
+ * (pressure / 256.0)
+ * (dig["P10"] / 131072.0)
+ )
+ return pressure + (var1 + var2 + var3 + (dig["P7"] * 128.0)) / 16.0
def _compensate_humidity_bme280(self, raw_humidity):
dig = self.dig
t_fine = self.t_fine
- humidity = t_fine - 76800.
- h1 = (
- raw_humidity - (
- dig['H4'] * 64. + dig['H5'] / 16384. * humidity))
- h2 = (dig['H2'] / 65536. * (1. + dig['H6'] / 67108864. * humidity *
- (1. + dig['H3'] / 67108864. * humidity)))
+ humidity = t_fine - 76800.0
+ h1 = raw_humidity - (dig["H4"] * 64.0 + dig["H5"] / 16384.0 * humidity)
+ h2 = (
+ dig["H2"]
+ / 65536.0
+ * (
+ 1.0
+ + dig["H6"]
+ / 67108864.0
+ * humidity
+ * (1.0 + dig["H3"] / 67108864.0 * humidity)
+ )
+ )
humidity = h1 * h2
- humidity = humidity * (1. - dig['H1'] * humidity / 524288.)
- return min(100., max(0., humidity))
+ humidity = humidity * (1.0 - dig["H1"] * humidity / 524288.0)
+ return min(100.0, max(0.0, humidity))
def _compensate_humidity_bme680(self, raw_humidity):
dig = self.dig
temp_comp = self.temp
- var1 = raw_humidity - (
- (dig['H1'] * 16.) + ((dig['H3'] / 2.) * temp_comp))
- var2 = var1 * ((dig['H2'] / 262144.) *
- (1. + ((dig['H4'] / 16384.) * temp_comp) +
- ((dig['H5'] / 1048576.) * temp_comp * temp_comp)))
- var3 = dig['H6'] / 16384.
- var4 = dig['H7'] / 2097152.
+ var1 = raw_humidity - ((dig["H1"] * 16.0) + ((dig["H3"] / 2.0) * temp_comp))
+ var2 = var1 * (
+ (dig["H2"] / 262144.0)
+ * (
+ 1.0
+ + ((dig["H4"] / 16384.0) * temp_comp)
+ + ((dig["H5"] / 1048576.0) * temp_comp * temp_comp)
+ )
+ )
+ var3 = dig["H6"] / 16384.0
+ var4 = dig["H7"] / 2097152.0
humidity = var2 + ((var3 + (var4 * temp_comp)) * var2 * var2)
- return min(100., max(0., humidity))
+ return min(100.0, max(0.0, humidity))
def _compensate_gas(self, gas_raw, gas_range):
- gas_switching_error = self.read_register('RANGE_SWITCHING_ERROR', 1)[0]
- var1 = (1340. + 5. * gas_switching_error) * \
- BME680_GAS_CONSTANTS[gas_range][0]
- gas = var1 * BME680_GAS_CONSTANTS[gas_range][1] / (
- gas_raw - 512. + var1)
+ gas_switching_error = self.read_register("RANGE_SWITCHING_ERROR", 1)[0]
+ var1 = (1340.0 + 5.0 * gas_switching_error) * BME680_GAS_CONSTANTS[gas_range][0]
+ gas = var1 * BME680_GAS_CONSTANTS[gas_range][1] / (gas_raw - 512.0 + var1)
return gas
def _calc_gas_heater_resistance(self, target_temp):
amb_temp = self.temp
- heater_data = self.read_register('RES_HEAT_VAL', 3)
+ heater_data = self.read_register("RES_HEAT_VAL", 3)
res_heat_val = get_signed_byte(heater_data[0])
res_heat_range = (heater_data[2] & 0x30) >> 4
dig = self.dig
- var1 = (dig['G1'] / 16.) + 49.
- var2 = ((dig['G2'] / 32768.) * 0.0005) + 0.00235
- var3 = dig['G3'] / 1024.
- var4 = var1 * (1. + (var2 * target_temp))
+ var1 = (dig["G1"] / 16.0) + 49.0
+ var2 = ((dig["G2"] / 32768.0) * 0.0005) + 0.00235
+ var3 = dig["G3"] / 1024.0
+ var4 = var1 * (1.0 + (var2 * target_temp))
var5 = var4 + (var3 * amb_temp)
- res_heat = (3.4 * ((var5 * (4. / (4. + res_heat_range))
- * (1. / (1. + (res_heat_val * 0.002)))) - 25))
+ res_heat = 3.4 * (
+ (
+ var5
+ * (4.0 / (4.0 + res_heat_range))
+ * (1.0 / (1.0 + (res_heat_val * 0.002)))
+ )
+ - 25
+ )
return int(res_heat)
def _calc_gas_heater_duration(self, duration_ms):
if duration_ms >= 4032:
- duration_reg = 0xff
+ duration_reg = 0xFF
else:
factor = 0
while duration_ms > 0x3F:
@@ -729,54 +799,54 @@ class BME280:
def _compensate_temp_bmp180(self, raw_temp):
dig = self.dig
- x1 = (raw_temp - dig['AC6']) * dig['AC5'] / 32768.
- x2 = dig['MC'] * 2048 / (x1 + dig['MD'])
+ x1 = (raw_temp - dig["AC6"]) * dig["AC5"] / 32768.0
+ x2 = dig["MC"] * 2048 / (x1 + dig["MD"])
b5 = x1 + x2
self.t_fine = b5
- return (b5 + 8)/16./10.
+ return (b5 + 8) / 16.0 / 10.0
def _compensate_pressure_bmp180(self, raw_pressure):
dig = self.dig
b5 = self.t_fine
b6 = b5 - 4000
- x1 = (dig['B2'] * (b6 * b6 / 4096)) / 2048
- x2 = dig['AC2'] * b6 / 2048
+ x1 = (dig["B2"] * (b6 * b6 / 4096)) / 2048
+ x2 = dig["AC2"] * b6 / 2048
x3 = x1 + x2
- b3 = ((int(dig['AC1'] * 4 + x3) << self.os_pres) + 2) / 4
- x1 = dig['AC3'] * b6 / 8192
- x2 = (dig['B1'] * (b6 * b6 / 4096)) / 65536
+ b3 = ((int(dig["AC1"] * 4 + x3) << self.os_pres) + 2) / 4
+ x1 = dig["AC3"] * b6 / 8192
+ x2 = (dig["B1"] * (b6 * b6 / 4096)) / 65536
x3 = ((x1 + x2) + 2) / 4
- b4 = dig['AC4'] * (x3 + 32768) / 32768
+ b4 = dig["AC4"] * (x3 + 32768) / 32768
b7 = (raw_pressure - b3) * (50000 >> self.os_pres)
- if (b7 < 0x80000000):
+ if b7 < 0x80000000:
p = (b7 * 2) / b4
else:
p = (b7 / b4) * 2
x1 = (p / 256) * (p / 256)
x1 = (x1 * 3038) / 65536
x2 = (-7357 * p) / 65536
- p = p + (x1 + x2 + 3791) / 16.
+ p = p + (x1 + x2 + 3791) / 16.0
return p
def read_id(self):
# read chip id register
regs = [BME_CHIP_ID_REG]
params = self.i2c.i2c_read(regs, 1)
- return bytearray(params['response'])[0]
+ return bytearray(params["response"])[0]
def read_bmp3_id(self):
# read chip id register
regs = [BMP3_CHIP_ID_REG]
params = self.i2c.i2c_read(regs, 1)
- return bytearray(params['response'])[0]
+ return bytearray(params["response"])[0]
def read_register(self, reg_name, read_len):
# read a single register
regs = [self.chip_registers[reg_name]]
params = self.i2c.i2c_read(regs, read_len)
- return bytearray(params['response'])
+ return bytearray(params["response"])
- def write_register(self, reg_name, data, wait = False):
+ def write_register(self, reg_name, data, wait=False):
if type(data) is not list:
data = [data]
reg = self.chip_registers[reg_name]
@@ -787,14 +857,11 @@ class BME280:
self.i2c.i2c_write_wait_ack(data)
def get_status(self, eventtime):
- data = {
- 'temperature': round(self.temp, 2),
- 'pressure': self.pressure
- }
- if self.chip_type in ('BME280', 'BME680'):
- data['humidity'] = self.humidity
- if self.chip_type == 'BME680':
- data['gas'] = self.gas
+ data = {"temperature": round(self.temp, 2), "pressure": self.pressure}
+ if self.chip_type in ("BME280", "BME680"):
+ data["humidity"] = self.humidity
+ if self.chip_type == "BME680":
+ data["gas"] = self.gas
return data
diff --git a/klippy/extras/board_pins.py b/klippy/extras/board_pins.py
index 11244e4a..3c95e0a3 100644
--- a/klippy/extras/board_pins.py
+++ b/klippy/extras/board_pins.py
@@ -4,24 +4,27 @@
#
# This file may be distributed under the terms of the GNU GPLv3 license.
+
class PrinterBoardAliases:
def __init__(self, config):
- ppins = config.get_printer().lookup_object('pins')
- mcu_names = config.getlist('mcu', ('mcu',))
+ ppins = config.get_printer().lookup_object("pins")
+ mcu_names = config.getlist("mcu", ("mcu",))
pin_resolvers = [ppins.get_pin_resolver(n) for n in mcu_names]
options = ["aliases"] + config.get_prefix_options("aliases_")
for opt in options:
- aliases = config.getlists(opt, seps=('=', ','), count=2)
+ aliases = config.getlists(opt, seps=("=", ","), count=2)
for name, value in aliases:
- if value.startswith('<') and value.endswith('>'):
+ if value.startswith("<") and value.endswith(">"):
for pin_resolver in pin_resolvers:
pin_resolver.reserve_pin(name, value)
else:
for pin_resolver in pin_resolvers:
pin_resolver.alias_pin(name, value)
+
def load_config(config):
return PrinterBoardAliases(config)
+
def load_config_prefix(config):
return PrinterBoardAliases(config)
diff --git a/klippy/extras/bulk_sensor.py b/klippy/extras/bulk_sensor.py
index b0aa320d..cd112e1a 100644
--- a/klippy/extras/bulk_sensor.py
+++ b/klippy/extras/bulk_sensor.py
@@ -19,23 +19,31 @@ import logging, threading, struct
BATCH_INTERVAL = 0.500
+
# Helper to process accumulated messages in periodic batches
class BatchBulkHelper:
- def __init__(self, printer, batch_cb, start_cb=None, stop_cb=None,
- batch_interval=BATCH_INTERVAL):
+ def __init__(
+ self,
+ printer,
+ batch_cb,
+ start_cb=None,
+ stop_cb=None,
+ batch_interval=BATCH_INTERVAL,
+ ):
self.printer = printer
self.batch_cb = batch_cb
if start_cb is None:
- start_cb = (lambda: None)
+ start_cb = lambda: None
self.start_cb = start_cb
if stop_cb is None:
- stop_cb = (lambda: None)
+ stop_cb = lambda: None
self.stop_cb = stop_cb
self.is_started = False
self.batch_interval = batch_interval
self.batch_timer = None
self.client_cbs = []
self.webhooks_start_resp = {}
+
# Periodic batch processing
def _start(self):
if self.is_started:
@@ -52,6 +60,7 @@ class BatchBulkHelper:
systime = reactor.monotonic()
waketime = systime + self.batch_interval
self.batch_timer = reactor.register_timer(self._proc_batch, waketime)
+
def _stop(self):
del self.client_cbs[:]
self.printer.get_reactor().unregister_timer(self.batch_timer)
@@ -67,6 +76,7 @@ class BatchBulkHelper:
if self.client_cbs:
# New client started while in process of stopping
self._start()
+
def _proc_batch(self, eventtime):
try:
msg = self.batch_cb(eventtime)
@@ -85,33 +95,39 @@ class BatchBulkHelper:
self._stop()
return self.printer.get_reactor().NEVER
return eventtime + self.batch_interval
+
# Client registration
def add_client(self, client_cb):
self.client_cbs.append(client_cb)
self._start()
+
# Webhooks registration
def _add_api_client(self, web_request):
whbatch = BatchWebhooksClient(web_request)
self.add_client(whbatch.handle_batch)
web_request.send(self.webhooks_start_resp)
+
def add_mux_endpoint(self, path, key, value, webhooks_start_resp):
self.webhooks_start_resp = webhooks_start_resp
- wh = self.printer.lookup_object('webhooks')
+ wh = self.printer.lookup_object("webhooks")
wh.register_mux_endpoint(path, key, value, self._add_api_client)
+
# A webhooks wrapper for use by BatchBulkHelper
class BatchWebhooksClient:
def __init__(self, web_request):
self.cconn = web_request.get_client_connection()
- self.template = web_request.get_dict('response_template', {})
+ self.template = web_request.get_dict("response_template", {})
+
def handle_batch(self, msg):
if self.cconn.is_closed():
return False
tmp = dict(self.template)
- tmp['params'] = msg
+ tmp["params"] = msg
self.cconn.send(tmp)
return True
+
# Helper class to store incoming messages in a queue
class BulkDataQueue:
def __init__(self, mcu, msg_name="sensor_bulk_data", oid=None):
@@ -120,14 +136,17 @@ class BulkDataQueue:
self.raw_samples = []
# Register callback with mcu
mcu.register_response(self._handle_data, msg_name, oid)
+
def _handle_data(self, params):
with self.lock:
self.raw_samples.append(params)
+
def pull_queue(self):
with self.lock:
raw_samples = self.raw_samples
self.raw_samples = []
return raw_samples
+
def clear_queue(self):
self.pull_queue()
@@ -150,35 +169,42 @@ class BulkDataQueue:
# estimate of measurement frequency, which can then also be utilized
# to determine the time of the first measurement.
+
# Helper class for chip clock synchronization via linear regression
class ClockSyncRegression:
- def __init__(self, mcu, chip_clock_smooth, decay = 1. / 20.):
+ def __init__(self, mcu, chip_clock_smooth, decay=1.0 / 20.0):
self.mcu = mcu
self.chip_clock_smooth = chip_clock_smooth
self.decay = decay
- self.last_chip_clock = self.last_exp_mcu_clock = 0.
- self.mcu_clock_avg = self.mcu_clock_variance = 0.
- self.chip_clock_avg = self.chip_clock_covariance = 0.
+ self.last_chip_clock = self.last_exp_mcu_clock = 0.0
+ self.mcu_clock_avg = self.mcu_clock_variance = 0.0
+ self.chip_clock_avg = self.chip_clock_covariance = 0.0
+
def reset(self, mcu_clock, chip_clock):
self.mcu_clock_avg = self.last_mcu_clock = mcu_clock
self.chip_clock_avg = chip_clock
- self.mcu_clock_variance = self.chip_clock_covariance = 0.
- self.last_chip_clock = self.last_exp_mcu_clock = 0.
+ self.mcu_clock_variance = self.chip_clock_covariance = 0.0
+ self.last_chip_clock = self.last_exp_mcu_clock = 0.0
+
def update(self, mcu_clock, chip_clock):
# Update linear regression
decay = self.decay
diff_mcu_clock = mcu_clock - self.mcu_clock_avg
self.mcu_clock_avg += decay * diff_mcu_clock
- self.mcu_clock_variance = (1. - decay) * (
- self.mcu_clock_variance + diff_mcu_clock**2 * decay)
+ self.mcu_clock_variance = (1.0 - decay) * (
+ self.mcu_clock_variance + diff_mcu_clock**2 * decay
+ )
diff_chip_clock = chip_clock - self.chip_clock_avg
self.chip_clock_avg += decay * diff_chip_clock
- self.chip_clock_covariance = (1. - decay) * (
- self.chip_clock_covariance + diff_mcu_clock*diff_chip_clock*decay)
+ self.chip_clock_covariance = (1.0 - decay) * (
+ self.chip_clock_covariance + diff_mcu_clock * diff_chip_clock * decay
+ )
+
def set_last_chip_clock(self, chip_clock):
base_mcu, base_chip, inv_cfreq = self.get_clock_translation()
self.last_chip_clock = chip_clock
- self.last_exp_mcu_clock = base_mcu + (chip_clock-base_chip) * inv_cfreq
+ self.last_exp_mcu_clock = base_mcu + (chip_clock - base_chip) * inv_cfreq
+
def get_clock_translation(self):
inv_chip_freq = self.mcu_clock_variance / self.chip_clock_covariance
if not self.last_chip_clock:
@@ -191,6 +217,7 @@ class ClockSyncRegression:
mdiff = s_mcu_clock - self.last_exp_mcu_clock
s_inv_chip_freq = mdiff / self.chip_clock_smooth
return self.last_exp_mcu_clock, self.last_chip_clock, s_inv_chip_freq
+
def get_time_translation(self):
base_mcu, base_chip, inv_cfreq = self.get_clock_translation()
clock_to_print_time = self.mcu.clock_to_print_time
@@ -198,8 +225,10 @@ class ClockSyncRegression:
inv_freq = clock_to_print_time(base_mcu + inv_cfreq) - base_time
return base_time, base_chip, inv_freq
+
MAX_BULK_MSG_SIZE = 51
+
# Read sensor_bulk_data and calculate timestamps for devices that take
# samples at a fixed frequency (and produce fixed data size samples).
class FixedFreqReader:
@@ -213,19 +242,26 @@ class FixedFreqReader:
self.last_sequence = self.max_query_duration = 0
self.last_overflows = 0
self.bulk_queue = self.oid = self.query_status_cmd = None
+
def setup_query_command(self, msgformat, oid, cq):
# Lookup sensor query command (that responds with sensor_bulk_status)
self.oid = oid
self.query_status_cmd = self.mcu.lookup_query_command(
- msgformat, "sensor_bulk_status oid=%c clock=%u query_ticks=%u"
+ msgformat,
+ "sensor_bulk_status oid=%c clock=%u query_ticks=%u"
" next_sequence=%hu buffered=%u possible_overflows=%hu",
- oid=oid, cq=cq)
+ oid=oid,
+ cq=cq,
+ )
# Read sensor_bulk_data messages and store in a queue
self.bulk_queue = BulkDataQueue(self.mcu, oid=oid)
+
def get_last_overflows(self):
return self.last_overflows
+
def _clear_duration_filter(self):
self.max_query_duration = 1 << 31
+
def note_start(self):
self.last_sequence = 0
self.last_overflows = 0
@@ -235,26 +271,31 @@ class FixedFreqReader:
self._clear_duration_filter()
self._update_clock(is_reset=True)
self._clear_duration_filter()
+
def note_end(self):
# Clear local queue (free no longer needed memory)
self.bulk_queue.clear_queue()
+
def _update_clock(self, is_reset=False):
params = self.query_status_cmd.send([self.oid])
- mcu_clock = self.mcu.clock32_to_clock64(params['clock'])
- seq_diff = (params['next_sequence'] - self.last_sequence) & 0xffff
+ mcu_clock = self.mcu.clock32_to_clock64(params["clock"])
+ seq_diff = (params["next_sequence"] - self.last_sequence) & 0xFFFF
self.last_sequence += seq_diff
- buffered = params['buffered']
- po_diff = (params['possible_overflows'] - self.last_overflows) & 0xffff
+ buffered = params["buffered"]
+ po_diff = (params["possible_overflows"] - self.last_overflows) & 0xFFFF
self.last_overflows += po_diff
- duration = params['query_ticks']
+ duration = params["query_ticks"]
if duration > self.max_query_duration:
# Skip measurement as a high query time could skew clock tracking
- self.max_query_duration = max(2 * self.max_query_duration,
- self.mcu.seconds_to_clock(.000005))
+ self.max_query_duration = max(
+ 2 * self.max_query_duration, self.mcu.seconds_to_clock(0.000005)
+ )
return
self.max_query_duration = 2 * duration
- msg_count = (self.last_sequence * self.samples_per_block
- + buffered // self.bytes_per_sample)
+ msg_count = (
+ self.last_sequence * self.samples_per_block
+ + buffered // self.bytes_per_sample
+ )
# The "chip clock" is the message counter plus .5 for average
# inaccuracy of query responses and plus .5 for assumed offset
# of hardware processing time.
@@ -264,6 +305,7 @@ class FixedFreqReader:
self.clock_sync.reset(avg_mcu_clock, chip_clock)
else:
self.clock_sync.update(avg_mcu_clock, chip_clock)
+
# Convert sensor_bulk_data responses into list of samples
def pull_samples(self):
# Query MCU for sample timing and update clock synchronization
@@ -282,11 +324,11 @@ class FixedFreqReader:
count = seq = 0
samples = [None] * (len(raw_samples) * samples_per_block)
for params in raw_samples:
- seq_diff = (params['sequence'] - last_sequence) & 0xffff
+ seq_diff = (params["sequence"] - last_sequence) & 0xFFFF
seq_diff -= (seq_diff & 0x8000) << 1
seq = last_sequence + seq_diff
msg_cdiff = seq * samples_per_block - chip_base
- data = params['data']
+ data = params["data"]
for i in range(len(data) // bytes_per_sample):
ptime = time_base + (msg_cdiff + i) * inv_freq
udata = unpack_from(data, i * bytes_per_sample)
diff --git a/klippy/extras/bus.py b/klippy/extras/bus.py
index 4121c1c8..6e87b2e1 100644
--- a/klippy/extras/bus.py
+++ b/klippy/extras/bus.py
@@ -5,10 +5,11 @@
# This file may be distributed under the terms of the GNU GPLv3 license.
import mcu
+
def resolve_bus_name(mcu, param, bus):
# Find enumerations for the given bus
enumerations = mcu.get_enumerations()
- enums = enumerations.get(param, enumerations.get('bus'))
+ enums = enumerations.get(param, enumerations.get("bus"))
if enums is None:
if bus is None:
return 0
@@ -25,10 +26,10 @@ def resolve_bus_name(mcu, param, bus):
raise ppins.error("Unknown %s '%s'" % (param, bus))
# Check for reserved bus pins
constants = mcu.get_constants()
- reserve_pins = constants.get('BUS_PINS_%s' % (bus,), None)
+ reserve_pins = constants.get("BUS_PINS_%s" % (bus,), None)
pin_resolver = ppins.get_pin_resolver(mcu_name)
if reserve_pins is not None:
- for pin in reserve_pins.split(','):
+ for pin in reserve_pins.split(","):
pin_resolver.reserve_pin(pin, bus)
return bus
@@ -37,10 +38,10 @@ def resolve_bus_name(mcu, param, bus):
# SPI
######################################################################
+
# Helper code for working with devices connected to an MCU via an SPI bus
class MCU_SPI:
- def __init__(self, mcu, bus, pin, mode, speed, sw_pins=None,
- cs_active_high=False):
+ def __init__(self, mcu, bus, pin, mode, speed, sw_pins=None, cs_active_high=False):
self.mcu = mcu
self.bus = bus
self.speed = speed
@@ -49,101 +50,133 @@ class MCU_SPI:
if pin is None:
mcu.add_config_cmd("config_spi_without_cs oid=%d" % (self.oid,))
else:
- mcu.add_config_cmd("config_spi oid=%d pin=%s cs_active_high=%d"
- % (self.oid, pin, cs_active_high))
+ mcu.add_config_cmd(
+ "config_spi oid=%d pin=%s cs_active_high=%d"
+ % (self.oid, pin, cs_active_high)
+ )
# Generate SPI bus config message
self.config_fmt_ticks = None
if sw_pins is not None:
self.config_fmt = (
"spi_set_software_bus oid=%d"
" miso_pin=%s mosi_pin=%s sclk_pin=%s mode=%d rate=%d"
- % (self.oid, sw_pins[0], sw_pins[1], sw_pins[2], mode, speed))
+ % (self.oid, sw_pins[0], sw_pins[1], sw_pins[2], mode, speed)
+ )
self.config_fmt_ticks = (
"spi_set_sw_bus oid=%d"
" miso_pin=%s mosi_pin=%s sclk_pin=%s mode=%d pulse_ticks=%%d"
- % (self.oid, sw_pins[0], sw_pins[1],
- sw_pins[2], mode))
+ % (self.oid, sw_pins[0], sw_pins[1], sw_pins[2], mode)
+ )
else:
- self.config_fmt = (
- "spi_set_bus oid=%d spi_bus=%%s mode=%d rate=%d"
- % (self.oid, mode, speed))
+ self.config_fmt = "spi_set_bus oid=%d spi_bus=%%s mode=%d rate=%d" % (
+ self.oid,
+ mode,
+ speed,
+ )
self.cmd_queue = mcu.alloc_command_queue()
mcu.register_config_callback(self.build_config)
self.spi_send_cmd = self.spi_transfer_cmd = None
+
def setup_shutdown_msg(self, shutdown_seq):
shutdown_msg = "".join(["%02x" % (x,) for x in shutdown_seq])
self.mcu.add_config_cmd(
"config_spi_shutdown oid=%d spi_oid=%d shutdown_msg=%s"
- % (self.mcu.create_oid(), self.oid, shutdown_msg))
+ % (self.mcu.create_oid(), self.oid, shutdown_msg)
+ )
+
def get_oid(self):
return self.oid
+
def get_mcu(self):
return self.mcu
+
def get_command_queue(self):
return self.cmd_queue
+
def build_config(self):
- if '%' in self.config_fmt:
+ if "%" in self.config_fmt:
bus = resolve_bus_name(self.mcu, "spi_bus", self.bus)
self.config_fmt = self.config_fmt % (bus,)
if self.config_fmt_ticks:
- if self.mcu.try_lookup_command("spi_set_sw_bus oid=%c miso_pin=%u "
- "mosi_pin=%u sclk_pin=%u "
- "mode=%u pulse_ticks=%u"):
- pulse_ticks = self.mcu.seconds_to_clock(1./self.speed)
+ if self.mcu.try_lookup_command(
+ "spi_set_sw_bus oid=%c miso_pin=%u "
+ "mosi_pin=%u sclk_pin=%u "
+ "mode=%u pulse_ticks=%u"
+ ):
+ pulse_ticks = self.mcu.seconds_to_clock(1.0 / self.speed)
self.config_fmt = self.config_fmt_ticks % (pulse_ticks,)
self.mcu.add_config_cmd(self.config_fmt)
self.spi_send_cmd = self.mcu.lookup_command(
- "spi_send oid=%c data=%*s", cq=self.cmd_queue)
+ "spi_send oid=%c data=%*s", cq=self.cmd_queue
+ )
self.spi_transfer_cmd = self.mcu.lookup_query_command(
"spi_transfer oid=%c data=%*s",
- "spi_transfer_response oid=%c response=%*s", oid=self.oid,
- cq=self.cmd_queue)
+ "spi_transfer_response oid=%c response=%*s",
+ oid=self.oid,
+ cq=self.cmd_queue,
+ )
+
def spi_send(self, data, minclock=0, reqclock=0):
if self.spi_send_cmd is None:
# Send setup message via mcu initialization
data_msg = "".join(["%02x" % (x,) for x in data])
- self.mcu.add_config_cmd("spi_send oid=%d data=%s" % (
- self.oid, data_msg), is_init=True)
+ self.mcu.add_config_cmd(
+ "spi_send oid=%d data=%s" % (self.oid, data_msg), is_init=True
+ )
return
- self.spi_send_cmd.send([self.oid, data],
- minclock=minclock, reqclock=reqclock)
+ self.spi_send_cmd.send([self.oid, data], minclock=minclock, reqclock=reqclock)
+
def spi_transfer(self, data, minclock=0, reqclock=0):
- return self.spi_transfer_cmd.send([self.oid, data],
- minclock=minclock, reqclock=reqclock)
- def spi_transfer_with_preface(self, preface_data, data,
- minclock=0, reqclock=0):
+ return self.spi_transfer_cmd.send(
+ [self.oid, data], minclock=minclock, reqclock=reqclock
+ )
+
+ def spi_transfer_with_preface(self, preface_data, data, minclock=0, reqclock=0):
return self.spi_transfer_cmd.send_with_preface(
- self.spi_send_cmd, [self.oid, preface_data], [self.oid, data],
- minclock=minclock, reqclock=reqclock)
+ self.spi_send_cmd,
+ [self.oid, preface_data],
+ [self.oid, data],
+ minclock=minclock,
+ reqclock=reqclock,
+ )
+
# Helper to setup an spi bus from settings in a config section
-def MCU_SPI_from_config(config, mode, pin_option="cs_pin",
- default_speed=100000, share_type=None,
- cs_active_high=False):
+def MCU_SPI_from_config(
+ config,
+ mode,
+ pin_option="cs_pin",
+ default_speed=100000,
+ share_type=None,
+ cs_active_high=False,
+):
# Determine pin from config
ppins = config.get_printer().lookup_object("pins")
cs_pin = config.get(pin_option)
cs_pin_params = ppins.lookup_pin(cs_pin, share_type=share_type)
- pin = cs_pin_params['pin']
- if pin == 'None':
+ pin = cs_pin_params["pin"]
+ if pin == "None":
ppins.reset_pin_sharing(cs_pin_params)
pin = None
# Load bus parameters
- mcu = cs_pin_params['chip']
- speed = config.getint('spi_speed', default_speed, minval=100000)
- if config.get('spi_software_sclk_pin', None) is not None:
- sw_pin_names = ['spi_software_%s_pin' % (name,)
- for name in ['miso', 'mosi', 'sclk']]
- sw_pin_params = [ppins.lookup_pin(config.get(name), share_type=name)
- for name in sw_pin_names]
+ mcu = cs_pin_params["chip"]
+ speed = config.getint("spi_speed", default_speed, minval=100000)
+ if config.get("spi_software_sclk_pin", None) is not None:
+ sw_pin_names = [
+ "spi_software_%s_pin" % (name,) for name in ["miso", "mosi", "sclk"]
+ ]
+ sw_pin_params = [
+ ppins.lookup_pin(config.get(name), share_type=name) for name in sw_pin_names
+ ]
for pin_params in sw_pin_params:
- if pin_params['chip'] != mcu:
- raise ppins.error("%s: spi pins must be on same mcu" % (
- config.get_name(),))
- sw_pins = tuple([pin_params['pin'] for pin_params in sw_pin_params])
+ if pin_params["chip"] != mcu:
+ raise ppins.error(
+ "%s: spi pins must be on same mcu" % (config.get_name(),)
+ )
+ sw_pins = tuple([pin_params["pin"] for pin_params in sw_pin_params])
bus = None
else:
- bus = config.get('spi_bus', None)
+ bus = config.get("spi_bus", None)
sw_pins = None
# Create MCU_SPI object
return MCU_SPI(mcu, bus, pin, mode, speed, sw_pins, cs_active_high)
@@ -153,6 +186,7 @@ def MCU_SPI_from_config(config, mode, pin_option="cs_pin",
# I2C
######################################################################
+
# Helper code for working with devices connected to an MCU via an I2C bus
class MCU_I2C:
def __init__(self, mcu, bus, addr, speed, sw_pins=None):
@@ -168,78 +202,97 @@ class MCU_I2C:
self.config_fmt = (
"i2c_set_software_bus oid=%d"
" scl_pin=%s sda_pin=%s rate=%d address=%d"
- % (self.oid, sw_pins[0], sw_pins[1], speed, addr))
+ % (self.oid, sw_pins[0], sw_pins[1], speed, addr)
+ )
self.config_fmt_ticks = (
"i2c_set_sw_bus oid=%d"
" scl_pin=%s sda_pin=%s pulse_ticks=%%d address=%d"
- % (self.oid, sw_pins[0], sw_pins[1], addr))
+ % (self.oid, sw_pins[0], sw_pins[1], addr)
+ )
else:
- self.config_fmt = (
- "i2c_set_bus oid=%d i2c_bus=%%s rate=%d address=%d"
- % (self.oid, speed, addr))
+ self.config_fmt = "i2c_set_bus oid=%d i2c_bus=%%s rate=%d address=%d" % (
+ self.oid,
+ speed,
+ addr,
+ )
self.cmd_queue = self.mcu.alloc_command_queue()
self.mcu.register_config_callback(self.build_config)
self.i2c_write_cmd = self.i2c_read_cmd = None
+
def get_oid(self):
return self.oid
+
def get_mcu(self):
return self.mcu
+
def get_i2c_address(self):
return self.i2c_address
+
def get_command_queue(self):
return self.cmd_queue
+
def build_config(self):
- if '%' in self.config_fmt:
+ if "%" in self.config_fmt:
bus = resolve_bus_name(self.mcu, "i2c_bus", self.bus)
self.config_fmt = self.config_fmt % (bus,)
if self.config_fmt_ticks:
- if self.mcu.try_lookup_command("i2c_set_sw_bus oid=%c"
- " scl_pin=%u sda_pin=%u"
- " pulse_ticks=%u address=%u"):
- pulse_ticks = self.mcu.seconds_to_clock(1./self.speed/2)
+ if self.mcu.try_lookup_command(
+ "i2c_set_sw_bus oid=%c"
+ " scl_pin=%u sda_pin=%u"
+ " pulse_ticks=%u address=%u"
+ ):
+ pulse_ticks = self.mcu.seconds_to_clock(1.0 / self.speed / 2)
self.config_fmt = self.config_fmt_ticks % (pulse_ticks,)
self.mcu.add_config_cmd(self.config_fmt)
self.i2c_write_cmd = self.mcu.lookup_command(
- "i2c_write oid=%c data=%*s", cq=self.cmd_queue)
+ "i2c_write oid=%c data=%*s", cq=self.cmd_queue
+ )
self.i2c_read_cmd = self.mcu.lookup_query_command(
"i2c_read oid=%c reg=%*s read_len=%u",
- "i2c_read_response oid=%c response=%*s", oid=self.oid,
- cq=self.cmd_queue)
+ "i2c_read_response oid=%c response=%*s",
+ oid=self.oid,
+ cq=self.cmd_queue,
+ )
+
def i2c_write(self, data, minclock=0, reqclock=0):
if self.i2c_write_cmd is None:
# Send setup message via mcu initialization
data_msg = "".join(["%02x" % (x,) for x in data])
- self.mcu.add_config_cmd("i2c_write oid=%d data=%s" % (
- self.oid, data_msg), is_init=True)
+ self.mcu.add_config_cmd(
+ "i2c_write oid=%d data=%s" % (self.oid, data_msg), is_init=True
+ )
return
- self.i2c_write_cmd.send([self.oid, data],
- minclock=minclock, reqclock=reqclock)
+ self.i2c_write_cmd.send([self.oid, data], minclock=minclock, reqclock=reqclock)
+
def i2c_write_wait_ack(self, data, minclock=0, reqclock=0):
- self.i2c_write_cmd.send_wait_ack([self.oid, data],
- minclock=minclock, reqclock=reqclock)
+ self.i2c_write_cmd.send_wait_ack(
+ [self.oid, data], minclock=minclock, reqclock=reqclock
+ )
+
def i2c_read(self, write, read_len, retry=True):
return self.i2c_read_cmd.send([self.oid, write, read_len], retry)
+
def MCU_I2C_from_config(config, default_addr=None, default_speed=100000):
# Load bus parameters
printer = config.get_printer()
- i2c_mcu = mcu.get_printer_mcu(printer, config.get('i2c_mcu', 'mcu'))
- speed = config.getint('i2c_speed', default_speed, minval=100000)
+ i2c_mcu = mcu.get_printer_mcu(printer, config.get("i2c_mcu", "mcu"))
+ speed = config.getint("i2c_speed", default_speed, minval=100000)
if default_addr is None:
- addr = config.getint('i2c_address', minval=0, maxval=127)
+ addr = config.getint("i2c_address", minval=0, maxval=127)
else:
- addr = config.getint('i2c_address', default_addr, minval=0, maxval=127)
+ addr = config.getint("i2c_address", default_addr, minval=0, maxval=127)
# Determine pin from config
ppins = config.get_printer().lookup_object("pins")
- if config.get('i2c_software_scl_pin', None) is not None:
- sw_pin_names = ['i2c_software_%s_pin' % (name,)
- for name in ['scl', 'sda']]
- sw_pin_params = [ppins.lookup_pin(config.get(name), share_type=name)
- for name in sw_pin_names]
- sw_pins = tuple([pin_params['pin'] for pin_params in sw_pin_params])
+ if config.get("i2c_software_scl_pin", None) is not None:
+ sw_pin_names = ["i2c_software_%s_pin" % (name,) for name in ["scl", "sda"]]
+ sw_pin_params = [
+ ppins.lookup_pin(config.get(name), share_type=name) for name in sw_pin_names
+ ]
+ sw_pins = tuple([pin_params["pin"] for pin_params in sw_pin_params])
bus = None
else:
- bus = config.get('i2c_bus', None)
+ bus = config.get("i2c_bus", None)
sw_pins = None
# Create MCU_I2C object
return MCU_I2C(i2c_mcu, bus, addr, speed, sw_pins)
@@ -249,38 +302,48 @@ def MCU_I2C_from_config(config, default_addr=None, default_speed=100000):
# Bus synchronized digital outputs
######################################################################
+
# Helper code for a gpio that updates on a cmd_queue
class MCU_bus_digital_out:
def __init__(self, mcu, pin_desc, cmd_queue=None, value=0):
self.mcu = mcu
self.oid = mcu.create_oid()
- ppins = mcu.get_printer().lookup_object('pins')
+ ppins = mcu.get_printer().lookup_object("pins")
pin_params = ppins.lookup_pin(pin_desc)
- if pin_params['chip'] is not mcu:
- raise ppins.error("Pin %s must be on mcu %s" % (
- pin_desc, mcu.get_name()))
- mcu.add_config_cmd("config_digital_out oid=%d pin=%s value=%d"
- " default_value=%d max_duration=%d"
- % (self.oid, pin_params['pin'], value, value, 0))
+ if pin_params["chip"] is not mcu:
+ raise ppins.error("Pin %s must be on mcu %s" % (pin_desc, mcu.get_name()))
+ mcu.add_config_cmd(
+ "config_digital_out oid=%d pin=%s value=%d"
+ " default_value=%d max_duration=%d"
+ % (self.oid, pin_params["pin"], value, value, 0)
+ )
mcu.register_config_callback(self.build_config)
if cmd_queue is None:
cmd_queue = mcu.alloc_command_queue()
self.cmd_queue = cmd_queue
self.update_pin_cmd = None
+
def get_oid(self):
return self.oid
+
def get_mcu(self):
return self.mcu
+
def get_command_queue(self):
return self.cmd_queue
+
def build_config(self):
self.update_pin_cmd = self.mcu.lookup_command(
- "update_digital_out oid=%c value=%c", cq=self.cmd_queue)
+ "update_digital_out oid=%c value=%c", cq=self.cmd_queue
+ )
+
def update_digital_out(self, value, minclock=0, reqclock=0):
if self.update_pin_cmd is None:
# Send setup message via mcu initialization
- self.mcu.add_config_cmd("update_digital_out oid=%c value=%c"
- % (self.oid, not not value))
+ self.mcu.add_config_cmd(
+ "update_digital_out oid=%c value=%c" % (self.oid, not not value)
+ )
return
- self.update_pin_cmd.send([self.oid, not not value],
- minclock=minclock, reqclock=reqclock)
+ self.update_pin_cmd.send(
+ [self.oid, not not value], minclock=minclock, reqclock=reqclock
+ )
diff --git a/klippy/extras/buttons.py b/klippy/extras/buttons.py
index f83dd5ed..0b90f1a3 100644
--- a/klippy/extras/buttons.py
+++ b/klippy/extras/buttons.py
@@ -10,9 +10,10 @@ import logging
# Button state tracking
######################################################################
-QUERY_TIME = .002
+QUERY_TIME = 0.002
RETRANSMIT_COUNT = 50
+
class MCU_buttons:
def __init__(self, printer, mcu):
self.reactor = printer.get_reactor()
@@ -23,45 +24,52 @@ class MCU_buttons:
self.invert = self.last_button = 0
self.ack_cmd = None
self.ack_count = 0
+
def setup_buttons(self, pins, callback):
mask = 0
shift = len(self.pin_list)
for pin_params in pins:
- if pin_params['invert']:
+ if pin_params["invert"]:
self.invert |= 1 << len(self.pin_list)
mask |= 1 << len(self.pin_list)
- self.pin_list.append((pin_params['pin'], pin_params['pullup']))
+ self.pin_list.append((pin_params["pin"], pin_params["pullup"]))
self.callbacks.append((mask, shift, callback))
+
def build_config(self):
if not self.pin_list:
return
self.oid = self.mcu.create_oid()
- self.mcu.add_config_cmd("config_buttons oid=%d button_count=%d" % (
- self.oid, len(self.pin_list)))
+ self.mcu.add_config_cmd(
+ "config_buttons oid=%d button_count=%d" % (self.oid, len(self.pin_list))
+ )
for i, (pin, pull_up) in enumerate(self.pin_list):
self.mcu.add_config_cmd(
- "buttons_add oid=%d pos=%d pin=%s pull_up=%d" % (
- self.oid, i, pin, pull_up), is_init=True)
+ "buttons_add oid=%d pos=%d pin=%s pull_up=%d"
+ % (self.oid, i, pin, pull_up),
+ is_init=True,
+ )
cmd_queue = self.mcu.alloc_command_queue()
self.ack_cmd = self.mcu.lookup_command(
- "buttons_ack oid=%c count=%c", cq=cmd_queue)
+ "buttons_ack oid=%c count=%c", cq=cmd_queue
+ )
clock = self.mcu.get_query_slot(self.oid)
rest_ticks = self.mcu.seconds_to_clock(QUERY_TIME)
self.mcu.add_config_cmd(
"buttons_query oid=%d clock=%d"
- " rest_ticks=%d retransmit_count=%d invert=%d" % (
- self.oid, clock, rest_ticks, RETRANSMIT_COUNT,
- self.invert), is_init=True)
- self.mcu.register_response(self.handle_buttons_state,
- "buttons_state", self.oid)
+ " rest_ticks=%d retransmit_count=%d invert=%d"
+ % (self.oid, clock, rest_ticks, RETRANSMIT_COUNT, self.invert),
+ is_init=True,
+ )
+ self.mcu.register_response(self.handle_buttons_state, "buttons_state", self.oid)
+
def handle_buttons_state(self, params):
# Expand the message ack_count from 8-bit
ack_count = self.ack_count
- ack_diff = (params['ack_count'] - ack_count) & 0xff
+ ack_diff = (params["ack_count"] - ack_count) & 0xFF
ack_diff -= (ack_diff & 0x80) << 1
msg_ack_count = ack_count + ack_diff
# Determine new buttons
- buttons = bytearray(params['state'])
+ buttons = bytearray(params["state"])
new_count = msg_ack_count + len(buttons) - self.ack_count
if new_count <= 0:
return
@@ -70,7 +78,7 @@ class MCU_buttons:
self.ack_cmd.send([self.oid, new_count])
self.ack_count += new_count
# Invoke callbacks with this event in main thread
- btime = params['#receive_time']
+ btime = params["#receive_time"]
for button in new_buttons:
button ^= self.invert
changed = button ^ self.last_button
@@ -79,7 +87,8 @@ class MCU_buttons:
if changed & mask:
state = (button & mask) >> shift
self.reactor.register_async_callback(
- (lambda et, c=callback, bt=btime, s=state: c(bt, s)))
+ (lambda et, c=callback, bt=btime, s=state: c(bt, s))
+ )
######################################################################
@@ -91,6 +100,7 @@ ADC_DEBOUNCE_TIME = 0.025
ADC_SAMPLE_TIME = 0.001
ADC_SAMPLE_COUNT = 6
+
class MCU_ADC_buttons:
def __init__(self, printer, pin, pullup):
self.reactor = printer.get_reactor()
@@ -101,13 +111,13 @@ class MCU_ADC_buttons:
self.pullup = pullup
self.pin = pin
self.min_value = 999999999999.9
- self.max_value = 0.
- ppins = printer.lookup_object('pins')
- self.mcu_adc = ppins.setup_pin('adc', self.pin)
+ self.max_value = 0.0
+ ppins = printer.lookup_object("pins")
+ self.mcu_adc = ppins.setup_pin("adc", self.pin)
self.mcu_adc.setup_adc_sample(ADC_SAMPLE_TIME, ADC_SAMPLE_COUNT)
self.mcu_adc.setup_adc_callback(ADC_REPORT_TIME, self.adc_callback)
- query_adc = printer.lookup_object('query_adc')
- query_adc.register_adc('adc_button:' + pin.strip(), self.mcu_adc)
+ query_adc = printer.lookup_object("query_adc")
+ query_adc.register_adc("adc_button:" + pin.strip(), self.mcu_adc)
def setup_button(self, min_value, max_value, callback):
self.min_value = min(self.min_value, min_value)
@@ -115,7 +125,7 @@ class MCU_ADC_buttons:
self.buttons.append((min_value, max_value, callback))
def adc_callback(self, read_time, read_value):
- adc = max(.00001, min(.99999, read_value))
+ adc = max(0.00001, min(0.99999, read_value))
value = self.pullup * adc / (1.0 - adc)
# Determine button pressed
@@ -132,8 +142,11 @@ class MCU_ADC_buttons:
self.last_debouncetime = read_time
# button debounce check & new button pressed
- if ((read_time - self.last_debouncetime) >= ADC_DEBOUNCE_TIME
- and self.last_button == btn and self.last_pressed != btn):
+ if (
+ (read_time - self.last_debouncetime) >= ADC_DEBOUNCE_TIME
+ and self.last_button == btn
+ and self.last_pressed != btn
+ ):
# release last_pressed
if self.last_pressed is not None:
self.call_button(self.last_pressed, False)
@@ -146,113 +159,128 @@ class MCU_ADC_buttons:
def call_button(self, button, state):
minval, maxval, callback = self.buttons[button]
- self.reactor.register_async_callback(
- (lambda e, cb=callback, s=state: cb(e, s)))
+ self.reactor.register_async_callback((lambda e, cb=callback, s=state: cb(e, s)))
######################################################################
# Rotary Encoders
######################################################################
+
# Rotary encoder handler https://github.com/brianlow/Rotary
# Copyright 2011 Ben Buxton (bb@cactii.net).
# Licenced under the GNU GPL Version 3.
class BaseRotaryEncoder:
- R_START = 0x0
- R_DIR_CW = 0x10
- R_DIR_CCW = 0x20
- R_DIR_MSK = 0x30
+ R_START = 0x0
+ R_DIR_CW = 0x10
+ R_DIR_CCW = 0x20
+ R_DIR_MSK = 0x30
def __init__(self, cw_callback, ccw_callback):
self.cw_callback = cw_callback
self.ccw_callback = ccw_callback
self.encoder_state = self.R_START
+
def encoder_callback(self, eventtime, state):
- es = self.ENCODER_STATES[self.encoder_state & 0xf][state & 0x3]
+ es = self.ENCODER_STATES[self.encoder_state & 0xF][state & 0x3]
self.encoder_state = es
if es & self.R_DIR_MSK == self.R_DIR_CW:
self.cw_callback(eventtime)
elif es & self.R_DIR_MSK == self.R_DIR_CCW:
self.ccw_callback(eventtime)
+
class FullStepRotaryEncoder(BaseRotaryEncoder):
- R_CW_FINAL = 0x1
- R_CW_BEGIN = 0x2
- R_CW_NEXT = 0x3
+ R_CW_FINAL = 0x1
+ R_CW_BEGIN = 0x2
+ R_CW_NEXT = 0x3
R_CCW_BEGIN = 0x4
R_CCW_FINAL = 0x5
- R_CCW_NEXT = 0x6
+ R_CCW_NEXT = 0x6
# Use the full-step state table (emits a code at 00 only)
ENCODER_STATES = (
# R_START
- (BaseRotaryEncoder.R_START, R_CW_BEGIN, R_CCW_BEGIN,
- BaseRotaryEncoder.R_START),
-
+ (BaseRotaryEncoder.R_START, R_CW_BEGIN, R_CCW_BEGIN, BaseRotaryEncoder.R_START),
# R_CW_FINAL
- (R_CW_NEXT, BaseRotaryEncoder.R_START, R_CW_FINAL,
- BaseRotaryEncoder.R_START | BaseRotaryEncoder.R_DIR_CW),
-
+ (
+ R_CW_NEXT,
+ BaseRotaryEncoder.R_START,
+ R_CW_FINAL,
+ BaseRotaryEncoder.R_START | BaseRotaryEncoder.R_DIR_CW,
+ ),
# R_CW_BEGIN
- (R_CW_NEXT, R_CW_BEGIN, BaseRotaryEncoder.R_START,
- BaseRotaryEncoder.R_START),
-
+ (R_CW_NEXT, R_CW_BEGIN, BaseRotaryEncoder.R_START, BaseRotaryEncoder.R_START),
# R_CW_NEXT
(R_CW_NEXT, R_CW_BEGIN, R_CW_FINAL, BaseRotaryEncoder.R_START),
-
# R_CCW_BEGIN
- (R_CCW_NEXT, BaseRotaryEncoder.R_START, R_CCW_BEGIN,
- BaseRotaryEncoder.R_START),
-
+ (R_CCW_NEXT, BaseRotaryEncoder.R_START, R_CCW_BEGIN, BaseRotaryEncoder.R_START),
# R_CCW_FINAL
- (R_CCW_NEXT, R_CCW_FINAL, BaseRotaryEncoder.R_START,
- BaseRotaryEncoder.R_START | BaseRotaryEncoder.R_DIR_CCW),
-
+ (
+ R_CCW_NEXT,
+ R_CCW_FINAL,
+ BaseRotaryEncoder.R_START,
+ BaseRotaryEncoder.R_START | BaseRotaryEncoder.R_DIR_CCW,
+ ),
# R_CCW_NEXT
- (R_CCW_NEXT, R_CCW_FINAL, R_CCW_BEGIN, BaseRotaryEncoder.R_START)
+ (R_CCW_NEXT, R_CCW_FINAL, R_CCW_BEGIN, BaseRotaryEncoder.R_START),
)
+
class HalfStepRotaryEncoder(BaseRotaryEncoder):
# Use the half-step state table (emits a code at 00 and 11)
- R_CCW_BEGIN = 0x1
- R_CW_BEGIN = 0x2
- R_START_M = 0x3
- R_CW_BEGIN_M = 0x4
+ R_CCW_BEGIN = 0x1
+ R_CW_BEGIN = 0x2
+ R_START_M = 0x3
+ R_CW_BEGIN_M = 0x4
R_CCW_BEGIN_M = 0x5
ENCODER_STATES = (
# R_START (00)
(R_START_M, R_CW_BEGIN, R_CCW_BEGIN, BaseRotaryEncoder.R_START),
-
# R_CCW_BEGIN
- (R_START_M | BaseRotaryEncoder.R_DIR_CCW, BaseRotaryEncoder.R_START,
- R_CCW_BEGIN, BaseRotaryEncoder.R_START),
-
+ (
+ R_START_M | BaseRotaryEncoder.R_DIR_CCW,
+ BaseRotaryEncoder.R_START,
+ R_CCW_BEGIN,
+ BaseRotaryEncoder.R_START,
+ ),
# R_CW_BEGIN
- (R_START_M | BaseRotaryEncoder.R_DIR_CW, R_CW_BEGIN,
- BaseRotaryEncoder.R_START, BaseRotaryEncoder.R_START),
-
+ (
+ R_START_M | BaseRotaryEncoder.R_DIR_CW,
+ R_CW_BEGIN,
+ BaseRotaryEncoder.R_START,
+ BaseRotaryEncoder.R_START,
+ ),
# R_START_M (11)
- (R_START_M, R_CCW_BEGIN_M, R_CW_BEGIN_M, BaseRotaryEncoder.R_START),
-
+ (R_START_M, R_CCW_BEGIN_M, R_CW_BEGIN_M, BaseRotaryEncoder.R_START),
# R_CW_BEGIN_M
- (R_START_M, R_START_M, R_CW_BEGIN_M,
- BaseRotaryEncoder.R_START | BaseRotaryEncoder.R_DIR_CW),
-
+ (
+ R_START_M,
+ R_START_M,
+ R_CW_BEGIN_M,
+ BaseRotaryEncoder.R_START | BaseRotaryEncoder.R_DIR_CW,
+ ),
# R_CCW_BEGIN_M
- (R_START_M, R_CCW_BEGIN_M, R_START_M,
- BaseRotaryEncoder.R_START | BaseRotaryEncoder.R_DIR_CCW),
+ (
+ R_START_M,
+ R_CCW_BEGIN_M,
+ R_START_M,
+ BaseRotaryEncoder.R_START | BaseRotaryEncoder.R_DIR_CCW,
+ ),
)
+
class DebounceButton:
def __init__(self, config, button_action):
self.printer = config.get_printer()
self.reactor = self.printer.get_reactor()
self.button_action = button_action
- self.debounce_delay = config.getfloat('debounce_delay', 0., minval=0.)
+ self.debounce_delay = config.getfloat("debounce_delay", 0.0, minval=0.0)
self.logical_state = None
self.physical_state = None
self.latest_eventtime = None
+
def button_handler(self, eventtime, state):
self.physical_state = state
self.latest_eventtime = eventtime
@@ -261,6 +289,7 @@ class DebounceButton:
return
trigger_time = eventtime + self.debounce_delay
self.reactor.register_callback(self._debounce_event, trigger_time)
+
def _debounce_event(self, eventtime):
# if there would be no state transition, ignore the event:
if self.logical_state == self.physical_state:
@@ -272,69 +301,84 @@ class DebounceButton:
self.logical_state = self.physical_state
self.button_action(self.latest_eventtime, self.logical_state)
+
######################################################################
# Button registration code
######################################################################
+
class PrinterButtons:
def __init__(self, config):
self.printer = config.get_printer()
- self.printer.load_object(config, 'query_adc')
+ self.printer.load_object(config, "query_adc")
self.mcu_buttons = {}
self.adc_buttons = {}
+
def register_adc_button(self, pin, min_val, max_val, pullup, callback):
adc_buttons = self.adc_buttons.get(pin)
if adc_buttons is None:
self.adc_buttons[pin] = adc_buttons = MCU_ADC_buttons(
- self.printer, pin, pullup)
+ self.printer, pin, pullup
+ )
adc_buttons.setup_button(min_val, max_val, callback)
+
def register_debounce_button(self, pin, callback, config):
debounce = DebounceButton(config, callback)
return self.register_buttons([pin], debounce.button_handler)
- def register_debounce_adc_button(self, pin, min_val, max_val, pullup
- , callback, config):
+
+ def register_debounce_adc_button(
+ self, pin, min_val, max_val, pullup, callback, config
+ ):
debounce = DebounceButton(config, callback)
- return self.register_adc_button(pin, min_val, max_val, pullup
- , debounce.button_handler)
+ return self.register_adc_button(
+ pin, min_val, max_val, pullup, debounce.button_handler
+ )
+
def register_adc_button_push(self, pin, min_val, max_val, pullup, callback):
def helper(eventtime, state, callback=callback):
if state:
callback(eventtime)
+
self.register_adc_button(pin, min_val, max_val, pullup, helper)
+
def register_buttons(self, pins, callback):
# Parse pins
- ppins = self.printer.lookup_object('pins')
+ ppins = self.printer.lookup_object("pins")
mcu = mcu_name = None
pin_params_list = []
for pin in pins:
pin_params = ppins.lookup_pin(pin, can_invert=True, can_pullup=True)
- if mcu is not None and pin_params['chip'] != mcu:
+ if mcu is not None and pin_params["chip"] != mcu:
raise ppins.error("button pins must be on same mcu")
- mcu = pin_params['chip']
- mcu_name = pin_params['chip_name']
+ mcu = pin_params["chip"]
+ mcu_name = pin_params["chip_name"]
pin_params_list.append(pin_params)
# Register pins and callback with the appropriate MCU
mcu_buttons = self.mcu_buttons.get(mcu_name)
- if (mcu_buttons is None
- or len(mcu_buttons.pin_list) + len(pin_params_list) > 8):
- self.mcu_buttons[mcu_name] = mcu_buttons = MCU_buttons(
- self.printer, mcu)
+ if mcu_buttons is None or len(mcu_buttons.pin_list) + len(pin_params_list) > 8:
+ self.mcu_buttons[mcu_name] = mcu_buttons = MCU_buttons(self.printer, mcu)
mcu_buttons.setup_buttons(pin_params_list, callback)
- def register_rotary_encoder(self, pin1, pin2, cw_callback, ccw_callback,
- steps_per_detent):
+
+ def register_rotary_encoder(
+ self, pin1, pin2, cw_callback, ccw_callback, steps_per_detent
+ ):
if steps_per_detent == 2:
re = HalfStepRotaryEncoder(cw_callback, ccw_callback)
elif steps_per_detent == 4:
re = FullStepRotaryEncoder(cw_callback, ccw_callback)
else:
raise self.printer.config_error(
- "%d steps per detent not supported" % steps_per_detent)
+ "%d steps per detent not supported" % steps_per_detent
+ )
self.register_buttons([pin1, pin2], re.encoder_callback)
+
def register_button_push(self, pin, callback):
def helper(eventtime, state, callback=callback):
if state:
callback(eventtime)
+
self.register_buttons([pin], helper)
+
def load_config(config):
return PrinterButtons(config)
diff --git a/klippy/extras/canbus_ids.py b/klippy/extras/canbus_ids.py
index f96510fa..f0c96016 100644
--- a/klippy/extras/canbus_ids.py
+++ b/klippy/extras/canbus_ids.py
@@ -6,21 +6,24 @@
NODEID_FIRST = 4
+
class PrinterCANBus:
def __init__(self, config):
self.printer = config.get_printer()
self.ids = {}
+
def add_uuid(self, config, canbus_uuid, canbus_iface):
if canbus_uuid in self.ids:
raise config.error("Duplicate canbus_uuid")
new_id = len(self.ids) + NODEID_FIRST
self.ids[canbus_uuid] = new_id
return new_id
+
def get_nodeid(self, canbus_uuid):
if canbus_uuid not in self.ids:
- raise self.printer.config_error("Unknown canbus_uuid %s"
- % (canbus_uuid,))
+ raise self.printer.config_error("Unknown canbus_uuid %s" % (canbus_uuid,))
return self.ids[canbus_uuid]
+
def load_config(config):
return PrinterCANBus(config)
diff --git a/klippy/extras/canbus_stats.py b/klippy/extras/canbus_stats.py
index 0ddafaf3..13d480ac 100644
--- a/klippy/extras/canbus_stats.py
+++ b/klippy/extras/canbus_stats.py
@@ -5,6 +5,7 @@
# This file may be distributed under the terms of the GNU GPLv3 license.
import logging
+
class PrinterCANBusStats:
def __init__(self, config):
self.printer = config.get_printer()
@@ -12,23 +13,27 @@ class PrinterCANBusStats:
self.name = config.get_name().split()[-1]
self.mcu = None
self.get_canbus_status_cmd = None
- self.status = {'rx_error': None, 'tx_error': None, 'tx_retries': None,
- 'bus_state': None}
- self.printer.register_event_handler("klippy:connect",
- self.handle_connect)
- self.printer.register_event_handler("klippy:shutdown",
- self.handle_shutdown)
+ self.status = {
+ "rx_error": None,
+ "tx_error": None,
+ "tx_retries": None,
+ "bus_state": None,
+ }
+ self.printer.register_event_handler("klippy:connect", self.handle_connect)
+ self.printer.register_event_handler("klippy:shutdown", self.handle_shutdown)
+
def handle_shutdown(self):
status = self.status.copy()
- if status['bus_state'] is not None:
+ if status["bus_state"] is not None:
# Clear bus_state on shutdown to note that the values may be stale
- status['bus_state'] = 'unknown'
+ status["bus_state"] = "unknown"
self.status = status
+
def handle_connect(self):
# Lookup mcu
mcu_name = self.name
- if mcu_name != 'mcu':
- mcu_name = 'mcu ' + mcu_name
+ if mcu_name != "mcu":
+ mcu_name = "mcu " + mcu_name
self.mcu = self.printer.lookup_object(mcu_name)
# Lookup status query command
if self.mcu.try_lookup_command("get_canbus_status") is None:
@@ -36,45 +41,61 @@ class PrinterCANBusStats:
self.get_canbus_status_cmd = self.mcu.lookup_query_command(
"get_canbus_status",
"canbus_status rx_error=%u tx_error=%u tx_retries=%u"
- " canbus_bus_state=%u")
+ " canbus_bus_state=%u",
+ )
# Register usb_canbus_state message handling (for usb to canbus bridge)
- self.mcu.register_response(self.handle_usb_canbus_state,
- "usb_canbus_state")
+ self.mcu.register_response(self.handle_usb_canbus_state, "usb_canbus_state")
# Register periodic query timer
self.reactor.register_timer(self.query_event, self.reactor.NOW)
+
def handle_usb_canbus_state(self, params):
- discard = params['discard']
+ discard = params["discard"]
if discard:
- logging.warning("USB CANBUS bridge '%s' is discarding!"
- % (self.name,))
+ logging.warning("USB CANBUS bridge '%s' is discarding!" % (self.name,))
else:
- logging.warning("USB CANBUS bridge '%s' is no longer discarding."
- % (self.name,))
+ logging.warning(
+ "USB CANBUS bridge '%s' is no longer discarding." % (self.name,)
+ )
+
def query_event(self, eventtime):
- prev_rx = self.status['rx_error']
- prev_tx = self.status['tx_error']
- prev_retries = self.status['tx_retries']
+ prev_rx = self.status["rx_error"]
+ prev_tx = self.status["tx_error"]
+ prev_retries = self.status["tx_retries"]
if prev_rx is None:
prev_rx = prev_tx = prev_retries = 0
params = self.get_canbus_status_cmd.send()
- rx = prev_rx + ((params['rx_error'] - prev_rx) & 0xffffffff)
- tx = prev_tx + ((params['tx_error'] - prev_tx) & 0xffffffff)
- retries = prev_retries + ((params['tx_retries'] - prev_retries)
- & 0xffffffff)
- state = params['canbus_bus_state']
- self.status = {'rx_error': rx, 'tx_error': tx, 'tx_retries': retries,
- 'bus_state': state}
- return self.reactor.monotonic() + 1.
+ rx = prev_rx + ((params["rx_error"] - prev_rx) & 0xFFFFFFFF)
+ tx = prev_tx + ((params["tx_error"] - prev_tx) & 0xFFFFFFFF)
+ retries = prev_retries + ((params["tx_retries"] - prev_retries) & 0xFFFFFFFF)
+ state = params["canbus_bus_state"]
+ self.status = {
+ "rx_error": rx,
+ "tx_error": tx,
+ "tx_retries": retries,
+ "bus_state": state,
+ }
+ return self.reactor.monotonic() + 1.0
+
def stats(self, eventtime):
status = self.status
- if status['rx_error'] is None:
- return (False, '')
- return (False, 'canstat_%s: bus_state=%s rx_error=%d'
- ' tx_error=%d tx_retries=%d'
- % (self.name, status['bus_state'], status['rx_error'],
- status['tx_error'], status['tx_retries']))
+ if status["rx_error"] is None:
+ return (False, "")
+ return (
+ False,
+ "canstat_%s: bus_state=%s rx_error=%d"
+ " tx_error=%d tx_retries=%d"
+ % (
+ self.name,
+ status["bus_state"],
+ status["rx_error"],
+ status["tx_error"],
+ status["tx_retries"],
+ ),
+ )
+
def get_status(self, eventtime):
return self.status
+
def load_config_prefix(config):
return PrinterCANBusStats(config)
diff --git a/klippy/extras/controller_fan.py b/klippy/extras/controller_fan.py
index b1286b59..9f148d9f 100644
--- a/klippy/extras/controller_fan.py
+++ b/klippy/extras/controller_fan.py
@@ -7,28 +7,31 @@ from . import fan
PIN_MIN_TIME = 0.100
+
class ControllerFan:
def __init__(self, config):
self.printer = config.get_printer()
self.printer.register_event_handler("klippy:ready", self.handle_ready)
- self.printer.register_event_handler("klippy:connect",
- self.handle_connect)
+ self.printer.register_event_handler("klippy:connect", self.handle_connect)
self.stepper_names = config.getlist("stepper", None)
- self.stepper_enable = self.printer.load_object(config, 'stepper_enable')
- self.printer.load_object(config, 'heaters')
+ self.stepper_enable = self.printer.load_object(config, "stepper_enable")
+ self.printer.load_object(config, "heaters")
self.heaters = []
self.fan = fan.Fan(config)
- self.fan_speed = config.getfloat('fan_speed', default=1.,
- minval=0., maxval=1.)
+ self.fan_speed = config.getfloat(
+ "fan_speed", default=1.0, minval=0.0, maxval=1.0
+ )
self.idle_speed = config.getfloat(
- 'idle_speed', default=self.fan_speed, minval=0., maxval=1.)
+ "idle_speed", default=self.fan_speed, minval=0.0, maxval=1.0
+ )
self.idle_timeout = config.getint("idle_timeout", default=30, minval=0)
self.heater_names = config.getlist("heater", ("extruder",))
self.last_on = self.idle_timeout
- self.last_speed = 0.
+ self.last_speed = 0.0
+
def handle_connect(self):
# Heater lookup
- pheaters = self.printer.lookup_object('heaters')
+ pheaters = self.printer.lookup_object("heaters")
self.heaters = [pheaters.lookup_heater(n) for n in self.heater_names]
# Stepper lookup
all_steppers = self.stepper_enable.get_steppers()
@@ -38,15 +41,19 @@ class ControllerFan:
if not all(x in all_steppers for x in self.stepper_names):
raise self.printer.config_error(
"One or more of these steppers are unknown: "
- "%s (valid steppers are: %s)"
- % (self.stepper_names, ", ".join(all_steppers)))
+ "%s (valid steppers are: %s)"
+ % (self.stepper_names, ", ".join(all_steppers))
+ )
+
def handle_ready(self):
reactor = self.printer.get_reactor()
- reactor.register_timer(self.callback, reactor.monotonic()+PIN_MIN_TIME)
+ reactor.register_timer(self.callback, reactor.monotonic() + PIN_MIN_TIME)
+
def get_status(self, eventtime):
return self.fan.get_status(eventtime)
+
def callback(self, eventtime):
- speed = 0.
+ speed = 0.0
active = False
for name in self.stepper_names:
active |= self.stepper_enable.lookup_enable(name).is_motor_enabled()
@@ -63,7 +70,8 @@ class ControllerFan:
if speed != self.last_speed:
self.last_speed = speed
self.fan.set_speed(speed)
- return eventtime + 1.
+ return eventtime + 1.0
+
def load_config_prefix(config):
return ControllerFan(config)
diff --git a/klippy/extras/dac084S085.py b/klippy/extras/dac084S085.py
index bc015dd4..ad67c40e 100644
--- a/klippy/extras/dac084S085.py
+++ b/klippy/extras/dac084S085.py
@@ -6,20 +6,25 @@
from . import bus
+
class dac084S085:
def __init__(self, config):
self.spi = bus.MCU_SPI_from_config(
- config, 1, pin_option="enable_pin", default_speed=10000000)
- scale = config.getfloat('scale', 1., above=0.)
+ config, 1, pin_option="enable_pin", default_speed=10000000
+ )
+ scale = config.getfloat("scale", 1.0, above=0.0)
for chan, name in enumerate("ABCD"):
- val = config.getfloat('channel_%s' % (name,), None,
- minval=0., maxval=scale)
+ val = config.getfloat(
+ "channel_%s" % (name,), None, minval=0.0, maxval=scale
+ )
if val is not None:
- self.set_register(chan, int(val * 255. / scale))
+ self.set_register(chan, int(val * 255.0 / scale))
+
def set_register(self, chan, value):
- b1 = (chan << 6) | (1 << 4) | ((value >> 4) & 0x0f)
- b2 = (value << 4) & 0xf0
+ b1 = (chan << 6) | (1 << 4) | ((value >> 4) & 0x0F)
+ b2 = (value << 4) & 0xF0
self.spi.spi_send([b1, b2])
+
def load_config_prefix(config):
return dac084S085(config)
diff --git a/klippy/extras/delayed_gcode.py b/klippy/extras/delayed_gcode.py
index c15f231e..ddc8f697 100644
--- a/klippy/extras/delayed_gcode.py
+++ b/klippy/extras/delayed_gcode.py
@@ -6,28 +6,35 @@
import logging
+
class DelayedGcode:
def __init__(self, config):
self.printer = config.get_printer()
self.reactor = self.printer.get_reactor()
self.name = config.get_name().split()[1]
- self.gcode = self.printer.lookup_object('gcode')
- gcode_macro = self.printer.load_object(config, 'gcode_macro')
- self.timer_gcode = gcode_macro.load_template(config, 'gcode')
- self.duration = config.getfloat('initial_duration', 0., minval=0.)
+ self.gcode = self.printer.lookup_object("gcode")
+ gcode_macro = self.printer.load_object(config, "gcode_macro")
+ self.timer_gcode = gcode_macro.load_template(config, "gcode")
+ self.duration = config.getfloat("initial_duration", 0.0, minval=0.0)
self.timer_handler = None
self.inside_timer = self.repeat = False
self.printer.register_event_handler("klippy:ready", self._handle_ready)
self.gcode.register_mux_command(
- "UPDATE_DELAYED_GCODE", "ID", self.name,
+ "UPDATE_DELAYED_GCODE",
+ "ID",
+ self.name,
self.cmd_UPDATE_DELAYED_GCODE,
- desc=self.cmd_UPDATE_DELAYED_GCODE_help)
+ desc=self.cmd_UPDATE_DELAYED_GCODE_help,
+ )
+
def _handle_ready(self):
waketime = self.reactor.NEVER
if self.duration:
waketime = self.reactor.monotonic() + self.duration
self.timer_handler = self.reactor.register_timer(
- self._gcode_timer_event, waketime)
+ self._gcode_timer_event, waketime
+ )
+
def _gcode_timer_event(self, eventtime):
self.inside_timer = True
try:
@@ -39,16 +46,19 @@ class DelayedGcode:
nextwake = eventtime + self.duration
self.inside_timer = self.repeat = False
return nextwake
+
cmd_UPDATE_DELAYED_GCODE_help = "Update the duration of a delayed_gcode"
+
def cmd_UPDATE_DELAYED_GCODE(self, gcmd):
- self.duration = gcmd.get_float('DURATION', minval=0.)
+ self.duration = gcmd.get_float("DURATION", minval=0.0)
if self.inside_timer:
- self.repeat = (self.duration != 0.)
+ self.repeat = self.duration != 0.0
else:
waketime = self.reactor.NEVER
if self.duration:
waketime = self.reactor.monotonic() + self.duration
self.reactor.update_timer(self.timer_handler, waketime)
+
def load_config_prefix(config):
return DelayedGcode(config)
diff --git a/klippy/extras/delta_calibrate.py b/klippy/extras/delta_calibrate.py
index 4054e231..342aa110 100644
--- a/klippy/extras/delta_calibrate.py
+++ b/klippy/extras/delta_calibrate.py
@@ -12,6 +12,7 @@ from . import probe
# calibration uses this coordinate system because it allows a position
# to be described independent of the software parameters.
+
# Load a stable position from a config entry
def load_config_stable(config, option):
return config.getfloatlist(option, count=3)
@@ -23,51 +24,55 @@ def load_config_stable(config, option):
# The angles and distances of the calibration object found in
# docs/prints/calibrate_size.stl
-MeasureAngles = [210., 270., 330., 30., 90., 150.]
+MeasureAngles = [210.0, 270.0, 330.0, 30.0, 90.0, 150.0]
MeasureOuterRadius = 65
-MeasureRidgeRadius = 5. - .5
+MeasureRidgeRadius = 5.0 - 0.5
# How much to prefer a distance measurement over a height measurement
MEASURE_WEIGHT = 0.5
+
# Convert distance measurements made on the calibration object to
# 3-tuples of (actual_distance, stable_position1, stable_position2)
def measurements_to_distances(measured_params, delta_params):
# Extract params
mp = measured_params
dp = delta_params
- scale = mp['SCALE'][0]
- cpw = mp['CENTER_PILLAR_WIDTHS']
+ scale = mp["SCALE"][0]
+ cpw = mp["CENTER_PILLAR_WIDTHS"]
center_widths = [cpw[0], cpw[2], cpw[1], cpw[0], cpw[2], cpw[1]]
- center_dists = [od - cw
- for od, cw in zip(mp['CENTER_DISTS'], center_widths)]
+ center_dists = [od - cw for od, cw in zip(mp["CENTER_DISTS"], center_widths)]
outer_dists = [
- od - opw
- for od, opw in zip(mp['OUTER_DISTS'], mp['OUTER_PILLAR_WIDTHS']) ]
+ od - opw for od, opw in zip(mp["OUTER_DISTS"], mp["OUTER_PILLAR_WIDTHS"])
+ ]
# Convert angles in degrees to an XY multiplier
obj_angles = list(map(math.radians, MeasureAngles))
xy_angles = list(zip(map(math.cos, obj_angles), map(math.sin, obj_angles)))
# Calculate stable positions for center measurements
inner_ridge = MeasureRidgeRadius * scale
- inner_pos = [(ax * inner_ridge, ay * inner_ridge, 0.)
- for ax, ay in xy_angles]
+ inner_pos = [(ax * inner_ridge, ay * inner_ridge, 0.0) for ax, ay in xy_angles]
outer_ridge = (MeasureOuterRadius + MeasureRidgeRadius) * scale
- outer_pos = [(ax * outer_ridge, ay * outer_ridge, 0.)
- for ax, ay in xy_angles]
+ outer_pos = [(ax * outer_ridge, ay * outer_ridge, 0.0) for ax, ay in xy_angles]
center_positions = [
(cd, dp.calc_stable_position(ip), dp.calc_stable_position(op))
- for cd, ip, op in zip(center_dists, inner_pos, outer_pos)]
+ for cd, ip, op in zip(center_dists, inner_pos, outer_pos)
+ ]
# Calculate positions of outer measurements
outer_center = MeasureOuterRadius * scale
start_pos = [(ax * outer_center, ay * outer_center) for ax, ay in xy_angles]
shifted_angles = xy_angles[2:] + xy_angles[:2]
- first_pos = [(ax * inner_ridge + spx, ay * inner_ridge + spy, 0.)
- for (ax, ay), (spx, spy) in zip(shifted_angles, start_pos)]
- second_pos = [(ax * outer_ridge + spx, ay * outer_ridge + spy, 0.)
- for (ax, ay), (spx, spy) in zip(shifted_angles, start_pos)]
+ first_pos = [
+ (ax * inner_ridge + spx, ay * inner_ridge + spy, 0.0)
+ for (ax, ay), (spx, spy) in zip(shifted_angles, start_pos)
+ ]
+ second_pos = [
+ (ax * outer_ridge + spx, ay * outer_ridge + spy, 0.0)
+ for (ax, ay), (spx, spy) in zip(shifted_angles, start_pos)
+ ]
outer_positions = [
(od, dp.calc_stable_position(fp), dp.calc_stable_position(sp))
- for od, fp, sp in zip(outer_dists, first_pos, second_pos)]
+ for od, fp, sp in zip(outer_dists, first_pos, second_pos)
+ ]
return center_positions + outer_positions
@@ -75,21 +80,22 @@ def measurements_to_distances(measured_params, delta_params):
# Delta Calibrate class
######################################################################
+
class DeltaCalibrate:
def __init__(self, config):
self.printer = config.get_printer()
- self.printer.register_event_handler("klippy:connect",
- self.handle_connect)
+ self.printer.register_event_handler("klippy:connect", self.handle_connect)
# Calculate default probing points
- radius = config.getfloat('radius', above=0.)
- points = [(0., 0.)]
- scatter = [.95, .90, .85, .70, .75, .80]
+ radius = config.getfloat("radius", above=0.0)
+ points = [(0.0, 0.0)]
+ scatter = [0.95, 0.90, 0.85, 0.70, 0.75, 0.80]
for i in range(6):
- r = math.radians(90. + 60. * i)
+ r = math.radians(90.0 + 60.0 * i)
dist = radius * scatter[i]
points.append((math.cos(r) * dist, math.sin(r) * dist))
self.probe_helper = probe.ProbePointsHelper(
- config, self.probe_finalize, default_points=points)
+ config, self.probe_finalize, default_points=points
+ )
self.probe_helper.minimum_points(3)
# Restore probe stable positions
self.last_probe_positions = []
@@ -105,11 +111,10 @@ class DeltaCalibrate:
height = config.getfloat("manual_height%d" % (i,), None)
if height is None:
break
- height_pos = load_config_stable(config, "manual_height%d_pos"
- % (i,))
+ height_pos = load_config_stable(config, "manual_height%d_pos" % (i,))
self.manual_heights.append((height, height_pos))
# Restore distance measurements
- self.delta_analyze_entry = {'SCALE': (1.,)}
+ self.delta_analyze_entry = {"SCALE": (1.0,)}
self.last_distances = []
for i in range(999):
dist = config.getfloat("distance%d" % (i,), None)
@@ -119,60 +124,79 @@ class DeltaCalibrate:
distance_pos2 = load_config_stable(config, "distance%d_pos2" % (i,))
self.last_distances.append((dist, distance_pos1, distance_pos2))
# Register gcode commands
- self.gcode = self.printer.lookup_object('gcode')
- self.gcode.register_command('DELTA_CALIBRATE', self.cmd_DELTA_CALIBRATE,
- desc=self.cmd_DELTA_CALIBRATE_help)
- self.gcode.register_command('DELTA_ANALYZE', self.cmd_DELTA_ANALYZE,
- desc=self.cmd_DELTA_ANALYZE_help)
+ self.gcode = self.printer.lookup_object("gcode")
+ self.gcode.register_command(
+ "DELTA_CALIBRATE",
+ self.cmd_DELTA_CALIBRATE,
+ desc=self.cmd_DELTA_CALIBRATE_help,
+ )
+ self.gcode.register_command(
+ "DELTA_ANALYZE", self.cmd_DELTA_ANALYZE, desc=self.cmd_DELTA_ANALYZE_help
+ )
+
def handle_connect(self):
- kin = self.printer.lookup_object('toolhead').get_kinematics()
+ kin = self.printer.lookup_object("toolhead").get_kinematics()
if not hasattr(kin, "get_calibration"):
raise self.printer.config_error(
- "Delta calibrate is only for delta printers")
+ "Delta calibrate is only for delta printers"
+ )
+
def save_state(self, probe_positions, distances, delta_params):
# Save main delta parameters
- configfile = self.printer.lookup_object('configfile')
+ configfile = self.printer.lookup_object("configfile")
delta_params.save_state(configfile)
# Save probe stable positions
- section = 'delta_calibrate'
+ section = "delta_calibrate"
configfile.remove_section(section)
for i, (z_offset, spos) in enumerate(probe_positions):
configfile.set(section, "height%d" % (i,), z_offset)
- configfile.set(section, "height%d_pos" % (i,),
- "%.3f,%.3f,%.3f" % tuple(spos))
+ configfile.set(
+ section, "height%d_pos" % (i,), "%.3f,%.3f,%.3f" % tuple(spos)
+ )
# Save manually entered heights
for i, (z_offset, spos) in enumerate(self.manual_heights):
configfile.set(section, "manual_height%d" % (i,), z_offset)
- configfile.set(section, "manual_height%d_pos" % (i,),
- "%.3f,%.3f,%.3f" % tuple(spos))
+ configfile.set(
+ section, "manual_height%d_pos" % (i,), "%.3f,%.3f,%.3f" % tuple(spos)
+ )
# Save distance measurements
for i, (dist, spos1, spos2) in enumerate(distances):
configfile.set(section, "distance%d" % (i,), dist)
- configfile.set(section, "distance%d_pos1" % (i,),
- "%.3f,%.3f,%.3f" % tuple(spos1))
- configfile.set(section, "distance%d_pos2" % (i,),
- "%.3f,%.3f,%.3f" % tuple(spos2))
+ configfile.set(
+ section, "distance%d_pos1" % (i,), "%.3f,%.3f,%.3f" % tuple(spos1)
+ )
+ configfile.set(
+ section, "distance%d_pos2" % (i,), "%.3f,%.3f,%.3f" % tuple(spos2)
+ )
+
def probe_finalize(self, offsets, positions):
# Convert positions into (z_offset, stable_position) pairs
z_offset = offsets[2]
- kin = self.printer.lookup_object('toolhead').get_kinematics()
+ kin = self.printer.lookup_object("toolhead").get_kinematics()
delta_params = kin.get_calibration()
- probe_positions = [(z_offset, delta_params.calc_stable_position(p))
- for p in positions]
+ probe_positions = [
+ (z_offset, delta_params.calc_stable_position(p)) for p in positions
+ ]
# Perform analysis
self.calculate_params(probe_positions, self.last_distances)
+
def calculate_params(self, probe_positions, distances):
height_positions = self.manual_heights + probe_positions
# Setup for coordinate descent analysis
- kin = self.printer.lookup_object('toolhead').get_kinematics()
+ kin = self.printer.lookup_object("toolhead").get_kinematics()
orig_delta_params = odp = kin.get_calibration()
adj_params, params = odp.coordinate_descent_params(distances)
- logging.info("Calculating delta_calibrate with:\n%s\n%s\n"
- "Initial delta_calibrate parameters: %s",
- height_positions, distances, params)
- z_weight = 1.
+ logging.info(
+ "Calculating delta_calibrate with:\n%s\n%s\n"
+ "Initial delta_calibrate parameters: %s",
+ height_positions,
+ distances,
+ params,
+ )
+ z_weight = 1.0
if distances:
z_weight = len(distances) / (MEASURE_WEIGHT * len(probe_positions))
+
# Perform coordinate descent
def delta_errorfunc(params):
try:
@@ -180,54 +204,64 @@ class DeltaCalibrate:
delta_params = orig_delta_params.new_calibration(params)
getpos = delta_params.get_position_from_stable
# Calculate z height errors
- total_error = 0.
+ total_error = 0.0
for z_offset, stable_pos in height_positions:
x, y, z = getpos(stable_pos)
- total_error += (z - z_offset)**2
+ total_error += (z - z_offset) ** 2
total_error *= z_weight
# Calculate distance errors
for dist, stable_pos1, stable_pos2 in distances:
x1, y1, z1 = getpos(stable_pos1)
x2, y2, z2 = getpos(stable_pos2)
- d = math.sqrt((x1-x2)**2 + (y1-y2)**2 + (z1-z2)**2)
- total_error += (d - dist)**2
+ d = math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2 + (z1 - z2) ** 2)
+ total_error += (d - dist) ** 2
return total_error
except ValueError:
return 9999999999999.9
+
new_params = mathutil.background_coordinate_descent(
- self.printer, adj_params, params, delta_errorfunc)
+ self.printer, adj_params, params, delta_errorfunc
+ )
# Log and report results
logging.info("Calculated delta_calibrate parameters: %s", new_params)
new_delta_params = orig_delta_params.new_calibration(new_params)
for z_offset, spos in height_positions:
- logging.info("height orig: %.6f new: %.6f goal: %.6f",
- orig_delta_params.get_position_from_stable(spos)[2],
- new_delta_params.get_position_from_stable(spos)[2],
- z_offset)
+ logging.info(
+ "height orig: %.6f new: %.6f goal: %.6f",
+ orig_delta_params.get_position_from_stable(spos)[2],
+ new_delta_params.get_position_from_stable(spos)[2],
+ z_offset,
+ )
for dist, spos1, spos2 in distances:
x1, y1, z1 = orig_delta_params.get_position_from_stable(spos1)
x2, y2, z2 = orig_delta_params.get_position_from_stable(spos2)
- orig_dist = math.sqrt((x1-x2)**2 + (y1-y2)**2 + (z1-z2)**2)
+ orig_dist = math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2 + (z1 - z2) ** 2)
x1, y1, z1 = new_delta_params.get_position_from_stable(spos1)
x2, y2, z2 = new_delta_params.get_position_from_stable(spos2)
- new_dist = math.sqrt((x1-x2)**2 + (y1-y2)**2 + (z1-z2)**2)
- logging.info("distance orig: %.6f new: %.6f goal: %.6f",
- orig_dist, new_dist, dist)
+ new_dist = math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2 + (z1 - z2) ** 2)
+ logging.info(
+ "distance orig: %.6f new: %.6f goal: %.6f", orig_dist, new_dist, dist
+ )
# Store results for SAVE_CONFIG
self.save_state(probe_positions, distances, new_delta_params)
self.gcode.respond_info(
"The SAVE_CONFIG command will update the printer config file\n"
- "with these parameters and restart the printer.")
+ "with these parameters and restart the printer."
+ )
+
cmd_DELTA_CALIBRATE_help = "Delta calibration script"
+
def cmd_DELTA_CALIBRATE(self, gcmd):
self.probe_helper.start_probe(gcmd)
+
def add_manual_height(self, height):
# Determine current location of toolhead
- toolhead = self.printer.lookup_object('toolhead')
+ toolhead = self.printer.lookup_object("toolhead")
toolhead.flush_step_generation()
kin = toolhead.get_kinematics()
- kin_spos = {s.get_name(): s.get_commanded_position()
- for s in kin.get_steppers()}
+ kin_spos = {
+ s.get_name(): s.get_commanded_position() for s in kin.get_steppers()
+ }
kin_pos = kin.calc_position(kin_spos)
# Convert location to a stable position
delta_params = kin.get_calibration()
@@ -236,7 +270,9 @@ class DeltaCalibrate:
self.manual_heights.append((height, stable_pos))
self.gcode.respond_info(
"Adding manual height: %.3f,%.3f,%.3f is actually z=%.3f"
- % (kin_pos[0], kin_pos[1], kin_pos[2], height))
+ % (kin_pos[0], kin_pos[1], kin_pos[2], height)
+ )
+
def do_extended_calibration(self):
# Extract distance positions
if len(self.delta_analyze_entry) <= 1:
@@ -244,44 +280,53 @@ class DeltaCalibrate:
elif len(self.delta_analyze_entry) < 5:
raise self.gcode.error("Not all measurements provided")
else:
- kin = self.printer.lookup_object('toolhead').get_kinematics()
+ kin = self.printer.lookup_object("toolhead").get_kinematics()
delta_params = kin.get_calibration()
distances = measurements_to_distances(
- self.delta_analyze_entry, delta_params)
+ self.delta_analyze_entry, delta_params
+ )
if not self.last_probe_positions:
raise self.gcode.error(
- "Must run basic calibration with DELTA_CALIBRATE first")
+ "Must run basic calibration with DELTA_CALIBRATE first"
+ )
# Perform analysis
self.calculate_params(self.last_probe_positions, distances)
+
cmd_DELTA_ANALYZE_help = "Extended delta calibration tool"
+
def cmd_DELTA_ANALYZE(self, gcmd):
# Check for manual height entry
- mheight = gcmd.get_float('MANUAL_HEIGHT', None)
+ mheight = gcmd.get_float("MANUAL_HEIGHT", None)
if mheight is not None:
self.add_manual_height(mheight)
return
# Parse distance measurements
- args = {'CENTER_DISTS': 6, 'CENTER_PILLAR_WIDTHS': 3,
- 'OUTER_DISTS': 6, 'OUTER_PILLAR_WIDTHS': 6, 'SCALE': 1}
+ args = {
+ "CENTER_DISTS": 6,
+ "CENTER_PILLAR_WIDTHS": 3,
+ "OUTER_DISTS": 6,
+ "OUTER_PILLAR_WIDTHS": 6,
+ "SCALE": 1,
+ }
for name, count in args.items():
data = gcmd.get(name, None)
if data is None:
continue
try:
- parts = list(map(float, data.split(',')))
+ parts = list(map(float, data.split(",")))
except:
raise gcmd.error("Unable to parse parameter '%s'" % (name,))
if len(parts) != count:
- raise gcmd.error("Parameter '%s' must have %d values"
- % (name, count))
+ raise gcmd.error("Parameter '%s' must have %d values" % (name, count))
self.delta_analyze_entry[name] = parts
logging.info("DELTA_ANALYZE %s = %s", name, parts)
# Perform analysis if requested
- action = gcmd.get('CALIBRATE', None)
+ action = gcmd.get("CALIBRATE", None)
if action is not None:
- if action != 'extended':
+ if action != "extended":
raise gcmd.error("Unknown calibrate action")
self.do_extended_calibration()
+
def load_config(config):
return DeltaCalibrate(config)
diff --git a/klippy/extras/display/__init__.py b/klippy/extras/display/__init__.py
index da0e861c..b4650180 100644
--- a/klippy/extras/display/__init__.py
+++ b/klippy/extras/display/__init__.py
@@ -5,17 +5,21 @@
# This file may be distributed under the terms of the GNU GPLv3 license.
from . import display
+
def load_config(config):
return display.load_config(config)
+
def load_config_prefix(config):
- if not config.has_section('display'):
+ if not config.has_section("display"):
raise config.error(
"A primary [display] section must be defined in printer.cfg "
- "to use auxiliary displays")
+ "to use auxiliary displays"
+ )
name = config.get_name().split()[-1]
if name == "display":
raise config.error(
"Section name [display display] is not valid. "
- "Please choose a different postfix.")
+ "Please choose a different postfix."
+ )
return display.load_config(config)
diff --git a/klippy/extras/display/aip31068_spi.py b/klippy/extras/display/aip31068_spi.py
index 75f4978f..9bbbbdaa 100644
--- a/klippy/extras/display/aip31068_spi.py
+++ b/klippy/extras/display/aip31068_spi.py
@@ -16,10 +16,11 @@
from .. import bus
-LINE_LENGTH_DEFAULT=20
-LINE_LENGTH_OPTIONS={16:16, 20:20}
+LINE_LENGTH_DEFAULT = 20
+LINE_LENGTH_OPTIONS = {16: 16, 20: 20}
+
+TextGlyphs = {"right_arrow": b"\x7e"}
-TextGlyphs = { 'right_arrow': b'\x7e' }
# Each command is 9 bits long:
# 1 bit for RS (Register Select) - 0 for command, 1 for data
@@ -30,66 +31,79 @@ TextGlyphs = { 'right_arrow': b'\x7e' }
# or just by OR with enabled flags:
# cmd = CMND | flg_CMND.param1
class CMND:
- CLR = 1 # Clear display
- HOME = 2 # Return home
+ CLR = 1 # Clear display
+ HOME = 2 # Return home
ENTERY_MODE = 2**2 # Entry mode set
- DISPLAY = 2**3 # Display on/off control
- SHIFT = 2**4 # Cursor or display shift
- FUNCTION = 2**5 # Function set
- CGRAM = 2**6 # Character Generator RAM
- DDRAM = 2**7 # Display Data RAM
- WRITE_RAM = 2**8 # Write to RAM
+ DISPLAY = 2**3 # Display on/off control
+ SHIFT = 2**4 # Cursor or display shift
+ FUNCTION = 2**5 # Function set
+ CGRAM = 2**6 # Character Generator RAM
+ DDRAM = 2**7 # Display Data RAM
+ WRITE_RAM = 2**8 # Write to RAM
+
# Define flags for all commands:
class flg_ENTERY_MODE:
- INC = 2**1 # Increment
- SHIFT = 2**0 # Shift display
+ INC = 2**1 # Increment
+ SHIFT = 2**0 # Shift display
+
class flg_DISPLAY:
- ON = 2**2 # Display ON
- CURSOR = 2**1 # Cursor ON
- BLINK = 2**0 # Blink ON
+ ON = 2**2 # Display ON
+ CURSOR = 2**1 # Cursor ON
+ BLINK = 2**0 # Blink ON
+
class flg_SHIFT:
- WHOLE_DISPLAY = 2**3 # Shift whole display
- RIGHT = 2**2 # Shift right
+ WHOLE_DISPLAY = 2**3 # Shift whole display
+ RIGHT = 2**2 # Shift right
+
class flg_FUNCTION:
- TWO_LINES = 2**3 # 2-line display mode
- FIVE_BY_ELEVEN = 2**2 # 5x11 dot character font
+ TWO_LINES = 2**3 # 2-line display mode
+ FIVE_BY_ELEVEN = 2**2 # 5x11 dot character font
+
class flg_CGRAM:
- MASK = 0b00111111 # CGRAM address mask
+ MASK = 0b00111111 # CGRAM address mask
+
class flg_DDRAM:
- MASK = 0b01111111 # DDRAM address mask
+ MASK = 0b01111111 # DDRAM address mask
+
class flg_WRITE_RAM:
- MASK = 0b11111111 # Write RAM mask
+ MASK = 0b11111111 # Write RAM mask
-DISPLAY_INIT_CMNDS= [
+
+DISPLAY_INIT_CMNDS = [
# CMND.CLR - no need as framebuffer will rewrite all
- CMND.HOME, # move cursor to home (0x00)
- CMND.ENTERY_MODE | flg_ENTERY_MODE.INC, # increment cursor and no shift
- CMND.DISPLAY | flg_DISPLAY.ON, # keep cursor and blinking off
- CMND.SHIFT | flg_SHIFT.RIGHT, # shift right cursor only
- CMND.FUNCTION | flg_FUNCTION.TWO_LINES, # 2-line display mode, 5x8 dots
+ CMND.HOME, # move cursor to home (0x00)
+ CMND.ENTERY_MODE | flg_ENTERY_MODE.INC, # increment cursor and no shift
+ CMND.DISPLAY | flg_DISPLAY.ON, # keep cursor and blinking off
+ CMND.SHIFT | flg_SHIFT.RIGHT, # shift right cursor only
+ CMND.FUNCTION | flg_FUNCTION.TWO_LINES, # 2-line display mode, 5x8 dots
]
+
class aip31068_spi:
def __init__(self, config):
self.printer = config.get_printer()
# spi config
self.spi = bus.MCU_SPI_from_config(
- config, 0x00, pin_option="latch_pin") # (config, mode, cs_name)
+ config, 0x00, pin_option="latch_pin"
+ ) # (config, mode, cs_name)
self.mcu = self.spi.get_mcu()
self.icons = {}
- self.line_length = config.getchoice('line_length', LINE_LENGTH_OPTIONS,
- LINE_LENGTH_DEFAULT)
+ self.line_length = config.getchoice(
+ "line_length", LINE_LENGTH_OPTIONS, LINE_LENGTH_DEFAULT
+ )
# each controller's line is 2 lines on the display and hence twice
# line length
- self.text_framebuffers = [bytearray(b' '*2*self.line_length),
- bytearray(b' '*2*self.line_length)]
+ self.text_framebuffers = [
+ bytearray(b" " * 2 * self.line_length),
+ bytearray(b" " * 2 * self.line_length),
+ ]
self.glyph_framebuffer = bytearray(64)
# all_framebuffers - list of tuples per buffer type.
# Each tuple contains:
@@ -102,23 +116,33 @@ class aip31068_spi:
# (immutable tuple is allowed to store mutable bytearray)
self.all_framebuffers = [
# Text framebuffers
- (self.text_framebuffers[0], bytearray(b'~'*2*self.line_length),
- CMND.DDRAM | (flg_DDRAM.MASK & 0x00) ),
- (self.text_framebuffers[1], bytearray(b'~'*2*self.line_length),
- CMND.DDRAM | (flg_DDRAM.MASK & 0x40) ),
+ (
+ self.text_framebuffers[0],
+ bytearray(b"~" * 2 * self.line_length),
+ CMND.DDRAM | (flg_DDRAM.MASK & 0x00),
+ ),
+ (
+ self.text_framebuffers[1],
+ bytearray(b"~" * 2 * self.line_length),
+ CMND.DDRAM | (flg_DDRAM.MASK & 0x40),
+ ),
# Glyph framebuffer
- (self.glyph_framebuffer, bytearray(b'~'*64),
- CMND.CGRAM | (flg_CGRAM.MASK & 0x00) ) ]
+ (
+ self.glyph_framebuffer,
+ bytearray(b"~" * 64),
+ CMND.CGRAM | (flg_CGRAM.MASK & 0x00),
+ ),
+ ]
+
@staticmethod
- def encode(data, width = 9):
+ def encode(data, width=9):
encoded_bytes = []
accumulator = 0 # To accumulate bits
acc_bits = 0 # Count of bits in the accumulator
for num in data:
# check that num will fit in width bits
if num >= (1 << width):
- raise ValueError("Number {} does not fit in {} bits".
- format(num, width))
+ raise ValueError("Number {} does not fit in {} bits".format(num, width))
# Shift the current number into the accumulator from the right
accumulator = (accumulator << width) | num
acc_bits += width # Update the count of bits in the accumulator
@@ -135,6 +159,7 @@ class aip31068_spi:
last_byte = accumulator << (8 - acc_bits)
encoded_bytes.append(last_byte)
return encoded_bytes
+
def send(self, data, minclock=0):
# different commands have different processing time
# to avoid timing violation pad with some fast command, e.g. ENTRY_MODE
@@ -142,56 +167,63 @@ class aip31068_spi:
pad = CMND.ENTERY_MODE | flg_ENTERY_MODE.INC
for i in range(0, len(data), 8):
# Take a slice of 8 numbers
- group = data[i:i+8]
+ group = data[i : i + 8]
# Pad the group if it has fewer than 8 elements
if len(group) < 8:
group.extend([pad] * (8 - len(group)))
self.spi.spi_send(self.encode(group), minclock)
+
def flush(self):
# Find all differences in the framebuffers and send them to the chip
for new_data, old_data, fb_cmnd in self.all_framebuffers:
if new_data == old_data:
continue
# Find the position of all changed bytes in this framebuffer
- diffs = [[i, 1] for i, (n, o) in enumerate(zip(new_data, old_data))
- if n != o]
+ diffs = [
+ [i, 1] for i, (n, o) in enumerate(zip(new_data, old_data)) if n != o
+ ]
# Batch together changes that are close to each other
- for i in range(len(diffs)-2, -1, -1):
+ for i in range(len(diffs) - 2, -1, -1):
pos, count = diffs[i]
- nextpos, nextcount = diffs[i+1]
+ nextpos, nextcount = diffs[i + 1]
if pos + 4 >= nextpos and nextcount < 16:
diffs[i][1] = nextcount + (nextpos - pos)
- del diffs[i+1]
+ del diffs[i + 1]
# Transmit changes
for pos, count in diffs:
chip_pos = pos
self.send([fb_cmnd + chip_pos])
- self.send([CMND.WRITE_RAM | byte for byte in
- new_data[pos:pos+count]])
+ self.send(
+ [CMND.WRITE_RAM | byte for byte in new_data[pos : pos + count]]
+ )
old_data[:] = new_data
+
def init(self):
curtime = self.printer.get_reactor().monotonic()
print_time = self.mcu.estimated_print_time(curtime)
for i, cmds in enumerate(DISPLAY_INIT_CMNDS):
- minclock = self.mcu.print_time_to_clock(print_time + i * .100)
+ minclock = self.mcu.print_time_to_clock(print_time + i * 0.100)
self.send([cmds], minclock=minclock)
self.flush()
+
def write_text(self, x, y, data):
if x + len(data) > self.line_length:
- data = data[:self.line_length - min(x, self.line_length)]
+ data = data[: self.line_length - min(x, self.line_length)]
pos = x + ((y & 0x02) >> 1) * self.line_length
- self.text_framebuffers[y & 1][pos:pos+len(data)] = data
+ self.text_framebuffers[y & 1][pos : pos + len(data)] = data
+
def set_glyphs(self, glyphs):
for glyph_name, glyph_data in glyphs.items():
- data = glyph_data.get('icon5x8')
+ data = glyph_data.get("icon5x8")
if data is not None:
self.icons[glyph_name] = data
+
def write_glyph(self, x, y, glyph_name):
data = self.icons.get(glyph_name)
if data is not None:
slot, bits = data
self.write_text(x, y, [slot])
- self.glyph_framebuffer[slot * 8:(slot + 1) * 8] = bits
+ self.glyph_framebuffer[slot * 8 : (slot + 1) * 8] = bits
return 1
char = TextGlyphs.get(glyph_name)
if char is not None:
@@ -199,11 +231,14 @@ class aip31068_spi:
self.write_text(x, y, char)
return 1
return 0
+
def write_graphics(self, x, y, data):
- pass # this display supports only hardcoded or 8 user defined glyphs
+ pass # this display supports only hardcoded or 8 user defined glyphs
+
def clear(self):
- spaces = b' ' * 2*self.line_length
+ spaces = b" " * 2 * self.line_length
self.text_framebuffers[0][:] = spaces
self.text_framebuffers[1][:] = spaces
+
def get_dimensions(self):
return (self.line_length, 4)
diff --git a/klippy/extras/display/display.py b/klippy/extras/display/display.py
index e9ba31d6..c381cf09 100644
--- a/klippy/extras/display/display.py
+++ b/klippy/extras/display/display.py
@@ -14,77 +14,90 @@ REDRAW_TIME = 0.500
REDRAW_MIN_TIME = 0.100
LCD_chips = {
- 'st7920': st7920.ST7920, 'emulated_st7920': st7920.EmulatedST7920,
- 'hd44780': hd44780.HD44780, 'uc1701': uc1701.UC1701,
- 'ssd1306': uc1701.SSD1306, 'sh1106': uc1701.SH1106,
- 'hd44780_spi': hd44780_spi.hd44780_spi,
- 'aip31068_spi':aip31068_spi.aip31068_spi
+ "st7920": st7920.ST7920,
+ "emulated_st7920": st7920.EmulatedST7920,
+ "hd44780": hd44780.HD44780,
+ "uc1701": uc1701.UC1701,
+ "ssd1306": uc1701.SSD1306,
+ "sh1106": uc1701.SH1106,
+ "hd44780_spi": hd44780_spi.hd44780_spi,
+ "aip31068_spi": aip31068_spi.aip31068_spi,
}
+
# Storage of [display_template my_template] config sections
class DisplayTemplate:
def __init__(self, config):
self.printer = config.get_printer()
name_parts = config.get_name().split()
if len(name_parts) != 2:
- raise config.error("Section name '%s' is not valid"
- % (config.get_name(),))
+ raise config.error("Section name '%s' is not valid" % (config.get_name(),))
self.name = name_parts[1]
self.params = {}
- for option in config.get_prefix_options('param_'):
+ for option in config.get_prefix_options("param_"):
try:
self.params[option] = ast.literal_eval(config.get(option))
except ValueError as e:
raise config.error(
- "Option '%s' in section '%s' is not a valid literal" % (
- option, config.get_name()))
- gcode_macro = self.printer.load_object(config, 'gcode_macro')
- self.template = gcode_macro.load_template(config, 'text')
+ "Option '%s' in section '%s' is not a valid literal"
+ % (option, config.get_name())
+ )
+ gcode_macro = self.printer.load_object(config, "gcode_macro")
+ self.template = gcode_macro.load_template(config, "text")
+
def get_params(self):
return self.params
+
def render(self, context, **kwargs):
params = dict(self.params)
params.update(**kwargs)
if len(params) != len(self.params):
raise self.printer.command_error(
- "Invalid parameter to display_template %s" % (self.name,))
+ "Invalid parameter to display_template %s" % (self.name,)
+ )
context = dict(context)
context.update(params)
return self.template.render(context)
+
# Store [display_data my_group my_item] sections (one instance per group name)
class DisplayGroup:
def __init__(self, config, name, data_configs):
# Load and parse the position of display_data items
items = []
for c in data_configs:
- pos = c.get('position')
+ pos = c.get("position")
try:
- row, col = [int(v.strip()) for v in pos.split(',')]
+ row, col = [int(v.strip()) for v in pos.split(",")]
except:
- raise config.error("Unable to parse 'position' in section '%s'"
- % (c.get_name(),))
+ raise config.error(
+ "Unable to parse 'position' in section '%s'" % (c.get_name(),)
+ )
items.append((row, col, c.get_name()))
# Load all templates and store sorted by display position
configs_by_name = {c.get_name(): c for c in data_configs}
printer = config.get_printer()
- gcode_macro = printer.load_object(config, 'gcode_macro')
+ gcode_macro = printer.load_object(config, "gcode_macro")
self.data_items = []
for row, col, name in sorted(items):
c = configs_by_name[name]
- if c.get('text'):
- template = gcode_macro.load_template(c, 'text')
+ if c.get("text"):
+ template = gcode_macro.load_template(c, "text")
self.data_items.append((row, col, template))
+
def show(self, display, templates, eventtime):
context = self.data_items[0][2].create_template_context(eventtime)
- context['draw_progress_bar'] = display.draw_progress_bar
+ context["draw_progress_bar"] = display.draw_progress_bar
+
def render(name, **kwargs):
return templates[name].render(context, **kwargs)
- context['render'] = render
+
+ context["render"] = render
for row, col, template in self.data_items:
text = template.render(context)
- display.draw_text(row, col, text.replace('\n', ''), eventtime)
- context.clear() # Remove circular references for better gc
+ display.draw_text(row, col, text.replace("\n", ""), eventtime)
+ context.clear() # Remove circular references for better gc
+
# Global cache of DisplayTemplate, DisplayGroup, and glyphs
class PrinterDisplayTemplate:
@@ -94,76 +107,89 @@ class PrinterDisplayTemplate:
self.display_data_groups = {}
self.display_glyphs = {}
self.load_config(config)
+
def get_display_templates(self):
return self.display_templates
+
def get_display_data_groups(self):
return self.display_data_groups
+
def get_display_glyphs(self):
return self.display_glyphs
+
def _parse_glyph(self, config, glyph_name, data, width, height):
glyph_data = []
- for line in data.split('\n'):
- line = line.strip().replace('.', '0').replace('*', '1')
+ for line in data.split("\n"):
+ line = line.strip().replace(".", "0").replace("*", "1")
if not line:
continue
- if len(line) != width or line.replace('0', '').replace('1', ''):
+ if len(line) != width or line.replace("0", "").replace("1", ""):
raise config.error("Invalid glyph line in %s" % (glyph_name,))
glyph_data.append(int(line, 2))
if len(glyph_data) != height:
raise config.error("Glyph %s incorrect lines" % (glyph_name,))
return glyph_data
+
def load_config(self, config):
# Load default display config file
- pconfig = self.printer.lookup_object('configfile')
- filename = os.path.join(os.path.dirname(__file__), 'display.cfg')
+ pconfig = self.printer.lookup_object("configfile")
+ filename = os.path.join(os.path.dirname(__file__), "display.cfg")
try:
dconfig = pconfig.read_config(filename)
except Exception:
- raise self.printer.config_error("Cannot load config '%s'"
- % (filename,))
+ raise self.printer.config_error("Cannot load config '%s'" % (filename,))
# Load display_template sections
- dt_main = config.get_prefix_sections('display_template ')
- dt_main_names = { c.get_name(): 1 for c in dt_main }
- dt_def = [c for c in dconfig.get_prefix_sections('display_template ')
- if c.get_name() not in dt_main_names]
+ dt_main = config.get_prefix_sections("display_template ")
+ dt_main_names = {c.get_name(): 1 for c in dt_main}
+ dt_def = [
+ c
+ for c in dconfig.get_prefix_sections("display_template ")
+ if c.get_name() not in dt_main_names
+ ]
for c in dt_main + dt_def:
dt = DisplayTemplate(c)
self.display_templates[dt.name] = dt
# Load display_data sections
- dd_main = config.get_prefix_sections('display_data ')
- dd_main_names = { c.get_name(): 1 for c in dd_main }
- dd_def = [c for c in dconfig.get_prefix_sections('display_data ')
- if c.get_name() not in dd_main_names]
+ dd_main = config.get_prefix_sections("display_data ")
+ dd_main_names = {c.get_name(): 1 for c in dd_main}
+ dd_def = [
+ c
+ for c in dconfig.get_prefix_sections("display_data ")
+ if c.get_name() not in dd_main_names
+ ]
groups = {}
for c in dd_main + dd_def:
name_parts = c.get_name().split()
if len(name_parts) != 3:
- raise config.error("Section name '%s' is not valid"
- % (c.get_name(),))
+ raise config.error("Section name '%s' is not valid" % (c.get_name(),))
groups.setdefault(name_parts[1], []).append(c)
for group_name, data_configs in groups.items():
dg = DisplayGroup(config, group_name, data_configs)
self.display_data_groups[group_name] = dg
# Load display glyphs
- dg_prefix = 'display_glyph '
+ dg_prefix = "display_glyph "
self.display_glyphs = icons = {}
dg_main = config.get_prefix_sections(dg_prefix)
dg_main_names = {c.get_name(): 1 for c in dg_main}
- dg_def = [c for c in dconfig.get_prefix_sections(dg_prefix)
- if c.get_name() not in dg_main_names]
+ dg_def = [
+ c
+ for c in dconfig.get_prefix_sections(dg_prefix)
+ if c.get_name() not in dg_main_names
+ ]
for dg in dg_main + dg_def:
- glyph_name = dg.get_name()[len(dg_prefix):]
- data = dg.get('data', None)
+ glyph_name = dg.get_name()[len(dg_prefix) :]
+ data = dg.get("data", None)
if data is not None:
idata = self._parse_glyph(config, glyph_name, data, 16, 16)
- icon1 = [(bits >> 8) & 0xff for bits in idata]
- icon2 = [bits & 0xff for bits in idata]
- icons.setdefault(glyph_name, {})['icon16x16'] = (icon1, icon2)
- data = dg.get('hd44780_data', None)
+ icon1 = [(bits >> 8) & 0xFF for bits in idata]
+ icon2 = [bits & 0xFF for bits in idata]
+ icons.setdefault(glyph_name, {})["icon16x16"] = (icon1, icon2)
+ data = dg.get("hd44780_data", None)
if data is not None:
- slot = dg.getint('hd44780_slot', minval=0, maxval=7)
+ slot = dg.getint("hd44780_slot", minval=0, maxval=7)
idata = self._parse_glyph(config, glyph_name, data, 5, 8)
- icons.setdefault(glyph_name, {})['icon5x8'] = (slot, idata)
+ icons.setdefault(glyph_name, {})["icon5x8"] = (slot, idata)
+
def lookup_display_templates(config):
printer = config.get_printer()
@@ -173,16 +199,17 @@ def lookup_display_templates(config):
printer.add_object("display_template", dt)
return dt
+
class PrinterLCD:
def __init__(self, config):
self.printer = config.get_printer()
self.reactor = self.printer.get_reactor()
# Load low-level lcd handler
- self.lcd_chip = config.getchoice('lcd_type', LCD_chips)(config)
+ self.lcd_chip = config.getchoice("lcd_type", LCD_chips)(config)
# Load menu and display_status
self.menu = None
name = config.get_name()
- if name == 'display':
+ if name == "display":
# only load menu for primary display
self.menu = menu.MenuManager(config, self)
self.printer.load_object(config, "display_status")
@@ -194,30 +221,37 @@ class PrinterLCD:
dgroup = "_default_16x4"
if self.lcd_chip.get_dimensions()[0] == 20:
dgroup = "_default_20x4"
- dgroup = config.get('display_group', dgroup)
+ dgroup = config.get("display_group", dgroup)
self.show_data_group = self.display_data_groups.get(dgroup)
if self.show_data_group is None:
raise config.error("Unknown display_data group '%s'" % (dgroup,))
# Screen updating
self.printer.register_event_handler("klippy:ready", self.handle_ready)
- self.screen_update_timer = self.reactor.register_timer(
- self.screen_update_event)
+ self.screen_update_timer = self.reactor.register_timer(self.screen_update_event)
self.redraw_request_pending = False
- self.redraw_time = 0.
+ self.redraw_time = 0.0
# Register g-code commands
gcode = self.printer.lookup_object("gcode")
- gcode.register_mux_command('SET_DISPLAY_GROUP', 'DISPLAY', name,
- self.cmd_SET_DISPLAY_GROUP,
- desc=self.cmd_SET_DISPLAY_GROUP_help)
- if name == 'display':
- gcode.register_mux_command('SET_DISPLAY_GROUP', 'DISPLAY', None,
- self.cmd_SET_DISPLAY_GROUP)
+ gcode.register_mux_command(
+ "SET_DISPLAY_GROUP",
+ "DISPLAY",
+ name,
+ self.cmd_SET_DISPLAY_GROUP,
+ desc=self.cmd_SET_DISPLAY_GROUP_help,
+ )
+ if name == "display":
+ gcode.register_mux_command(
+ "SET_DISPLAY_GROUP", "DISPLAY", None, self.cmd_SET_DISPLAY_GROUP
+ )
+
def get_dimensions(self):
return self.lcd_chip.get_dimensions()
+
def handle_ready(self):
self.lcd_chip.init()
# Start screen update timer
self.reactor.update_timer(self.screen_update_timer, self.reactor.NOW)
+
# Screen updating
def screen_update_event(self, eventtime):
if self.redraw_request_pending:
@@ -237,14 +271,16 @@ class PrinterLCD:
logging.exception("Error during display screen update")
self.lcd_chip.flush()
return eventtime + REDRAW_TIME
+
def request_redraw(self):
if self.redraw_request_pending:
return
self.redraw_request_pending = True
self.reactor.update_timer(self.screen_update_timer, self.redraw_time)
+
def draw_text(self, row, col, mixed_text, eventtime):
pos = col
- for i, text in enumerate(mixed_text.split('~')):
+ for i, text in enumerate(mixed_text.split("~")):
if i & 1 == 0:
# write text
self.lcd_chip.write_text(pos, row, text.encode())
@@ -253,20 +289,24 @@ class PrinterLCD:
# write glyph
pos += self.lcd_chip.write_glyph(pos, row, text)
return pos
+
def draw_progress_bar(self, row, col, width, value):
- pixels = -1 << int(width * 8 * (1. - value) + .5)
+ pixels = -1 << int(width * 8 * (1.0 - value) + 0.5)
pixels |= (1 << (width * 8 - 1)) | 1
for i in range(width):
- data = [0xff] + [(pixels >> (i * 8)) & 0xff] * 14 + [0xff]
+ data = [0xFF] + [(pixels >> (i * 8)) & 0xFF] * 14 + [0xFF]
self.lcd_chip.write_graphics(col + width - 1 - i, row, data)
return ""
+
cmd_SET_DISPLAY_GROUP_help = "Set the active display group"
+
def cmd_SET_DISPLAY_GROUP(self, gcmd):
- group = gcmd.get('GROUP')
+ group = gcmd.get("GROUP")
new_dg = self.display_data_groups.get(group)
if new_dg is None:
raise gcmd.error("Unknown display_data group '%s'" % (group,))
self.show_data_group = new_dg
+
def load_config(config):
return PrinterLCD(config)
diff --git a/klippy/extras/display/font8x14.py b/klippy/extras/display/font8x14.py
index 66592edb..b79d177d 100644
--- a/klippy/extras/display/font8x14.py
+++ b/klippy/extras/display/font8x14.py
@@ -17,260 +17,260 @@
######################################################################
VGA_FONT = [
- b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
- b'\x00\x00\x00\x7e\x81\xa5\x81\x81\xbd\x99\x81\x7e\x00\x00\x00\x00',
- b'\x00\x00\x00\x7e\xff\xdb\xff\xff\xc3\xe7\xff\x7e\x00\x00\x00\x00',
- b'\x00\x00\x00\x00\x6c\xfe\xfe\xfe\xfe\x7c\x38\x10\x00\x00\x00\x00',
- b'\x00\x00\x00\x00\x10\x38\x7c\xfe\x7c\x38\x10\x00\x00\x00\x00\x00',
- b'\x00\x00\x00\x18\x3c\x3c\xe7\xe7\xe7\x18\x18\x3c\x00\x00\x00\x00',
- b'\x00\x00\x00\x18\x3c\x7e\xff\xff\x7e\x18\x18\x3c\x00\x00\x00\x00',
- b'\x00\x00\x00\x00\x00\x00\x18\x3c\x3c\x18\x00\x00\x00\x00\x00\x00',
- b'\x00\xff\xff\xff\xff\xff\xe7\xc3\xc3\xe7\xff\xff\xff\xff\xff\x00',
- b'\x00\x00\x00\x00\x00\x3c\x66\x42\x42\x66\x3c\x00\x00\x00\x00\x00',
- b'\x00\xff\xff\xff\xff\xc3\x99\xbd\xbd\x99\xc3\xff\xff\xff\xff\x00',
- b'\x00\x00\x00\x1e\x0e\x1a\x32\x78\xcc\xcc\xcc\x78\x00\x00\x00\x00',
- b'\x00\x00\x00\x3c\x66\x66\x66\x3c\x18\x7e\x18\x18\x00\x00\x00\x00',
- b'\x00\x00\x00\x3f\x33\x3f\x30\x30\x30\x70\xf0\xe0\x00\x00\x00\x00',
- b'\x00\x00\x00\x7f\x63\x7f\x63\x63\x63\x67\xe7\xe6\xc0\x00\x00\x00',
- b'\x00\x00\x00\x18\x18\xdb\x3c\xe7\x3c\xdb\x18\x18\x00\x00\x00\x00',
- b'\x00\x00\x00\x80\xc0\xe0\xf8\xfe\xf8\xe0\xc0\x80\x00\x00\x00\x00',
- b'\x00\x00\x00\x02\x06\x0e\x3e\xfe\x3e\x0e\x06\x02\x00\x00\x00\x00',
- b'\x00\x00\x00\x18\x3c\x7e\x18\x18\x18\x7e\x3c\x18\x00\x00\x00\x00',
- b'\x00\x00\x00\x66\x66\x66\x66\x66\x66\x00\x66\x66\x00\x00\x00\x00',
- b'\x00\x00\x00\x7f\xdb\xdb\xdb\x7b\x1b\x1b\x1b\x1b\x00\x00\x00\x00',
- b'\x00\x00\x7c\xc6\x60\x38\x6c\xc6\xc6\x6c\x38\x0c\xc6\x7c\x00\x00',
- b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\xfe\xfe\xfe\x00\x00\x00\x00',
- b'\x00\x00\x00\x18\x3c\x7e\x18\x18\x18\x7e\x3c\x18\x7e\x00\x00\x00',
- b'\x00\x00\x00\x18\x3c\x7e\x18\x18\x18\x18\x18\x18\x00\x00\x00\x00',
- b'\x00\x00\x00\x18\x18\x18\x18\x18\x18\x7e\x3c\x18\x00\x00\x00\x00',
- b'\x00\x00\x00\x00\x00\x18\x0c\xfe\x0c\x18\x00\x00\x00\x00\x00\x00',
- b'\x00\x00\x00\x00\x00\x30\x60\xfe\x60\x30\x00\x00\x00\x00\x00\x00',
- b'\x00\x00\x00\x00\x00\x00\xc0\xc0\xc0\xfe\x00\x00\x00\x00\x00\x00',
- b'\x00\x00\x00\x00\x00\x28\x6c\xfe\x6c\x28\x00\x00\x00\x00\x00\x00',
- b'\x00\x00\x00\x00\x10\x38\x38\x7c\x7c\xfe\xfe\x00\x00\x00\x00\x00',
- b'\x00\x00\x00\x00\xfe\xfe\x7c\x7c\x38\x38\x10\x00\x00\x00\x00\x00',
- b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
- b'\x00\x00\x00\x18\x3c\x3c\x3c\x18\x18\x00\x18\x18\x00\x00\x00\x00',
- b'\x00\x00\x66\x66\x66\x24\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
- b'\x00\x00\x00\x6c\x6c\xfe\x6c\x6c\x6c\xfe\x6c\x6c\x00\x00\x00\x00',
- b'\x00\x18\x18\x7c\xc6\xc2\xc0\x7c\x06\x86\xc6\x7c\x18\x18\x00\x00',
- b'\x00\x00\x00\x00\x00\xc2\xc6\x0c\x18\x30\x66\xc6\x00\x00\x00\x00',
- b'\x00\x00\x00\x38\x6c\x6c\x38\x76\xdc\xcc\xcc\x76\x00\x00\x00\x00',
- b'\x00\x00\x30\x30\x30\x60\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
- b'\x00\x00\x00\x0c\x18\x30\x30\x30\x30\x30\x18\x0c\x00\x00\x00\x00',
- b'\x00\x00\x00\x30\x18\x0c\x0c\x0c\x0c\x0c\x18\x30\x00\x00\x00\x00',
- b'\x00\x00\x00\x00\x00\x66\x3c\xff\x3c\x66\x00\x00\x00\x00\x00\x00',
- b'\x00\x00\x00\x00\x00\x18\x18\x7e\x18\x18\x00\x00\x00\x00\x00\x00',
- b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x18\x18\x18\x30\x00\x00\x00',
- b'\x00\x00\x00\x00\x00\x00\x00\xfe\x00\x00\x00\x00\x00\x00\x00\x00',
- b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x18\x18\x00\x00\x00\x00',
- b'\x00\x00\x00\x02\x06\x0c\x18\x30\x60\xc0\x80\x00\x00\x00\x00\x00',
- b'\x00\x00\x00\x7c\xc6\xce\xde\xf6\xe6\xc6\xc6\x7c\x00\x00\x00\x00',
- b'\x00\x00\x00\x18\x38\x78\x18\x18\x18\x18\x18\x7e\x00\x00\x00\x00',
- b'\x00\x00\x00\x7c\xc6\x06\x0c\x18\x30\x60\xc6\xfe\x00\x00\x00\x00',
- b'\x00\x00\x00\x7c\xc6\x06\x06\x3c\x06\x06\xc6\x7c\x00\x00\x00\x00',
- b'\x00\x00\x00\x0c\x1c\x3c\x6c\xcc\xfe\x0c\x0c\x1e\x00\x00\x00\x00',
- b'\x00\x00\x00\xfe\xc0\xc0\xc0\xfc\x06\x06\xc6\x7c\x00\x00\x00\x00',
- b'\x00\x00\x00\x38\x60\xc0\xc0\xfc\xc6\xc6\xc6\x7c\x00\x00\x00\x00',
- b'\x00\x00\x00\xfe\xc6\x06\x0c\x18\x30\x30\x30\x30\x00\x00\x00\x00',
- b'\x00\x00\x00\x7c\xc6\xc6\xc6\x7c\xc6\xc6\xc6\x7c\x00\x00\x00\x00',
- b'\x00\x00\x00\x7c\xc6\xc6\xc6\x7e\x06\x06\x0c\x78\x00\x00\x00\x00',
- b'\x00\x00\x00\x00\x18\x18\x00\x00\x00\x18\x18\x00\x00\x00\x00\x00',
- b'\x00\x00\x00\x00\x18\x18\x00\x00\x00\x18\x18\x30\x00\x00\x00\x00',
- b'\x00\x00\x00\x06\x0c\x18\x30\x60\x30\x18\x0c\x06\x00\x00\x00\x00',
- b'\x00\x00\x00\x00\x00\x00\x7e\x00\x00\x7e\x00\x00\x00\x00\x00\x00',
- b'\x00\x00\x00\x60\x30\x18\x0c\x06\x0c\x18\x30\x60\x00\x00\x00\x00',
- b'\x00\x00\x00\x7c\xc6\xc6\x0c\x18\x18\x00\x18\x18\x00\x00\x00\x00',
- b'\x00\x00\x00\x7c\xc6\xc6\xde\xde\xde\xdc\xc0\x7c\x00\x00\x00\x00',
- b'\x00\x00\x00\x10\x38\x6c\xc6\xc6\xfe\xc6\xc6\xc6\x00\x00\x00\x00',
- b'\x00\x00\x00\xfc\x66\x66\x66\x7c\x66\x66\x66\xfc\x00\x00\x00\x00',
- b'\x00\x00\x00\x3c\x66\xc2\xc0\xc0\xc0\xc2\x66\x3c\x00\x00\x00\x00',
- b'\x00\x00\x00\xf8\x6c\x66\x66\x66\x66\x66\x6c\xf8\x00\x00\x00\x00',
- b'\x00\x00\x00\xfe\x66\x62\x68\x78\x68\x62\x66\xfe\x00\x00\x00\x00',
- b'\x00\x00\x00\xfe\x66\x62\x68\x78\x68\x60\x60\xf0\x00\x00\x00\x00',
- b'\x00\x00\x00\x3c\x66\xc2\xc0\xc0\xde\xc6\x66\x3a\x00\x00\x00\x00',
- b'\x00\x00\x00\xc6\xc6\xc6\xc6\xfe\xc6\xc6\xc6\xc6\x00\x00\x00\x00',
- b'\x00\x00\x00\x3c\x18\x18\x18\x18\x18\x18\x18\x3c\x00\x00\x00\x00',
- b'\x00\x00\x00\x1e\x0c\x0c\x0c\x0c\x0c\xcc\xcc\x78\x00\x00\x00\x00',
- b'\x00\x00\x00\xe6\x66\x6c\x6c\x78\x6c\x6c\x66\xe6\x00\x00\x00\x00',
- b'\x00\x00\x00\xf0\x60\x60\x60\x60\x60\x62\x66\xfe\x00\x00\x00\x00',
- b'\x00\x00\x00\xc6\xee\xfe\xfe\xd6\xc6\xc6\xc6\xc6\x00\x00\x00\x00',
- b'\x00\x00\x00\xc6\xe6\xf6\xfe\xde\xce\xc6\xc6\xc6\x00\x00\x00\x00',
- b'\x00\x00\x00\x38\x6c\xc6\xc6\xc6\xc6\xc6\x6c\x38\x00\x00\x00\x00',
- b'\x00\x00\x00\xfc\x66\x66\x66\x7c\x60\x60\x60\xf0\x00\x00\x00\x00',
- b'\x00\x00\x00\x7c\xc6\xc6\xc6\xc6\xd6\xde\x7c\x0c\x0e\x00\x00\x00',
- b'\x00\x00\x00\xfc\x66\x66\x66\x7c\x6c\x66\x66\xe6\x00\x00\x00\x00',
- b'\x00\x00\x00\x7c\xc6\xc6\x60\x38\x0c\xc6\xc6\x7c\x00\x00\x00\x00',
- b'\x00\x00\x00\x7e\x7e\x5a\x18\x18\x18\x18\x18\x3c\x00\x00\x00\x00',
- b'\x00\x00\x00\xc6\xc6\xc6\xc6\xc6\xc6\xc6\xc6\x7c\x00\x00\x00\x00',
- b'\x00\x00\x00\xc6\xc6\xc6\xc6\xc6\xc6\x6c\x38\x10\x00\x00\x00\x00',
- b'\x00\x00\x00\xc6\xc6\xc6\xc6\xd6\xd6\xfe\x7c\x6c\x00\x00\x00\x00',
- b'\x00\x00\x00\xc6\xc6\x6c\x38\x38\x38\x6c\xc6\xc6\x00\x00\x00\x00',
- b'\x00\x00\x00\x66\x66\x66\x66\x3c\x18\x18\x18\x3c\x00\x00\x00\x00',
- b'\x00\x00\x00\xfe\xc6\x8c\x18\x30\x60\xc2\xc6\xfe\x00\x00\x00\x00',
- b'\x00\x00\x00\x3c\x30\x30\x30\x30\x30\x30\x30\x3c\x00\x00\x00\x00',
- b'\x00\x00\x00\x80\xc0\xe0\x70\x38\x1c\x0e\x06\x02\x00\x00\x00\x00',
- b'\x00\x00\x00\x3c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x3c\x00\x00\x00\x00',
- b'\x00\x10\x38\x6c\xc6\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
- b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x00\x00',
- b'\x00\x30\x30\x18\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
- b'\x00\x00\x00\x00\x00\x00\x78\x0c\x7c\xcc\xcc\x76\x00\x00\x00\x00',
- b'\x00\x00\x00\xe0\x60\x60\x78\x6c\x66\x66\x66\x7c\x00\x00\x00\x00',
- b'\x00\x00\x00\x00\x00\x00\x7c\xc6\xc0\xc0\xc6\x7c\x00\x00\x00\x00',
- b'\x00\x00\x00\x1c\x0c\x0c\x3c\x6c\xcc\xcc\xcc\x76\x00\x00\x00\x00',
- b'\x00\x00\x00\x00\x00\x00\x7c\xc6\xfe\xc0\xc6\x7c\x00\x00\x00\x00',
- b'\x00\x00\x00\x38\x6c\x64\x60\xf0\x60\x60\x60\xf0\x00\x00\x00\x00',
- b'\x00\x00\x00\x00\x00\x00\x76\xcc\xcc\xcc\x7c\x0c\xcc\x78\x00\x00',
- b'\x00\x00\x00\xe0\x60\x60\x6c\x76\x66\x66\x66\xe6\x00\x00\x00\x00',
- b'\x00\x00\x00\x18\x18\x00\x38\x18\x18\x18\x18\x3c\x00\x00\x00\x00',
- b'\x00\x00\x00\x06\x06\x00\x0e\x06\x06\x06\x06\x66\x66\x3c\x00\x00',
- b'\x00\x00\x00\xe0\x60\x60\x66\x6c\x78\x6c\x66\xe6\x00\x00\x00\x00',
- b'\x00\x00\x00\x38\x18\x18\x18\x18\x18\x18\x18\x3c\x00\x00\x00\x00',
- b'\x00\x00\x00\x00\x00\x00\xec\xfe\xd6\xd6\xd6\xc6\x00\x00\x00\x00',
- b'\x00\x00\x00\x00\x00\x00\xdc\x66\x66\x66\x66\x66\x00\x00\x00\x00',
- b'\x00\x00\x00\x00\x00\x00\x7c\xc6\xc6\xc6\xc6\x7c\x00\x00\x00\x00',
- b'\x00\x00\x00\x00\x00\x00\xdc\x66\x66\x66\x7c\x60\x60\xf0\x00\x00',
- b'\x00\x00\x00\x00\x00\x00\x76\xcc\xcc\xcc\x7c\x0c\x0c\x1e\x00\x00',
- b'\x00\x00\x00\x00\x00\x00\xdc\x76\x66\x60\x60\xf0\x00\x00\x00\x00',
- b'\x00\x00\x00\x00\x00\x00\x7c\xc6\x70\x1c\xc6\x7c\x00\x00\x00\x00',
- b'\x00\x00\x00\x10\x30\x30\xfc\x30\x30\x30\x36\x1c\x00\x00\x00\x00',
- b'\x00\x00\x00\x00\x00\x00\xcc\xcc\xcc\xcc\xcc\x76\x00\x00\x00\x00',
- b'\x00\x00\x00\x00\x00\x00\x66\x66\x66\x66\x3c\x18\x00\x00\x00\x00',
- b'\x00\x00\x00\x00\x00\x00\xc6\xc6\xd6\xd6\xfe\x6c\x00\x00\x00\x00',
- b'\x00\x00\x00\x00\x00\x00\xc6\x6c\x38\x38\x6c\xc6\x00\x00\x00\x00',
- b'\x00\x00\x00\x00\x00\x00\xc6\xc6\xc6\xc6\x7e\x06\x0c\xf8\x00\x00',
- b'\x00\x00\x00\x00\x00\x00\xfe\xcc\x18\x30\x66\xfe\x00\x00\x00\x00',
- b'\x00\x00\x00\x0e\x18\x18\x18\x70\x18\x18\x18\x0e\x00\x00\x00\x00',
- b'\x00\x00\x00\x18\x18\x18\x18\x00\x18\x18\x18\x18\x00\x00\x00\x00',
- b'\x00\x00\x00\x70\x18\x18\x18\x0e\x18\x18\x18\x70\x00\x00\x00\x00',
- b'\x00\x00\x00\x76\xdc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
- b'\x00\x00\x00\x00\x00\x10\x38\x6c\xc6\xc6\xfe\x00\x00\x00\x00\x00',
- b'\x00\x00\x00\x3c\x66\xc2\xc0\xc0\xc2\x66\x3c\x0c\x06\x7c\x00\x00',
- b'\x00\x00\x00\xcc\xcc\x00\xcc\xcc\xcc\xcc\xcc\x76\x00\x00\x00\x00',
- b'\x00\x00\x0c\x18\x30\x00\x7c\xc6\xfe\xc0\xc6\x7c\x00\x00\x00\x00',
- b'\x00\x00\x10\x38\x6c\x00\x78\x0c\x7c\xcc\xcc\x76\x00\x00\x00\x00',
- b'\x00\x00\x00\xcc\xcc\x00\x78\x0c\x7c\xcc\xcc\x76\x00\x00\x00\x00',
- b'\x00\x00\x60\x30\x18\x00\x78\x0c\x7c\xcc\xcc\x76\x00\x00\x00\x00',
- b'\x00\x00\x38\x6c\x38\x00\x78\x0c\x7c\xcc\xcc\x76\x00\x00\x00\x00',
- b'\x00\x00\x00\x00\x00\x3c\x66\x60\x66\x3c\x0c\x06\x3c\x00\x00\x00',
- b'\x00\x00\x10\x38\x6c\x00\x7c\xc6\xfe\xc0\xc6\x7c\x00\x00\x00\x00',
- b'\x00\x00\x00\xcc\xcc\x00\x7c\xc6\xfe\xc0\xc6\x7c\x00\x00\x00\x00',
- b'\x00\x00\x60\x30\x18\x00\x7c\xc6\xfe\xc0\xc6\x7c\x00\x00\x00\x00',
- b'\x00\x00\x00\x66\x66\x00\x38\x18\x18\x18\x18\x3c\x00\x00\x00\x00',
- b'\x00\x00\x18\x3c\x66\x00\x38\x18\x18\x18\x18\x3c\x00\x00\x00\x00',
- b'\x00\x00\x60\x30\x18\x00\x38\x18\x18\x18\x18\x3c\x00\x00\x00\x00',
- b'\x00\x00\xc6\xc6\x10\x38\x6c\xc6\xc6\xfe\xc6\xc6\x00\x00\x00\x00',
- b'\x00\x38\x6c\x38\x00\x38\x6c\xc6\xc6\xfe\xc6\xc6\x00\x00\x00\x00',
- b'\x00\x18\x30\x60\x00\xfe\x66\x60\x7c\x60\x66\xfe\x00\x00\x00\x00',
- b'\x00\x00\x00\x00\x00\xcc\x76\x36\x7e\xd8\xd8\x6e\x00\x00\x00\x00',
- b'\x00\x00\x00\x3e\x6c\xcc\xcc\xfe\xcc\xcc\xcc\xce\x00\x00\x00\x00',
- b'\x00\x00\x10\x38\x6c\x00\x7c\xc6\xc6\xc6\xc6\x7c\x00\x00\x00\x00',
- b'\x00\x00\x00\xc6\xc6\x00\x7c\xc6\xc6\xc6\xc6\x7c\x00\x00\x00\x00',
- b'\x00\x00\x60\x30\x18\x00\x7c\xc6\xc6\xc6\xc6\x7c\x00\x00\x00\x00',
- b'\x00\x00\x30\x78\xcc\x00\xcc\xcc\xcc\xcc\xcc\x76\x00\x00\x00\x00',
- b'\x00\x00\x60\x30\x18\x00\xcc\xcc\xcc\xcc\xcc\x76\x00\x00\x00\x00',
- b'\x00\x00\x00\xc6\xc6\x00\xc6\xc6\xc6\xc6\x7e\x06\x0c\x78\x00\x00',
- b'\x00\x00\xc6\xc6\x38\x6c\xc6\xc6\xc6\xc6\x6c\x38\x00\x00\x00\x00',
- b'\x00\x00\xc6\xc6\x00\xc6\xc6\xc6\xc6\xc6\xc6\x7c\x00\x00\x00\x00',
- b'\x00\x00\x18\x18\x3c\x66\x60\x60\x66\x3c\x18\x18\x00\x00\x00\x00',
- b'\x00\x00\x38\x6c\x64\x60\xf0\x60\x60\x60\xe6\xfc\x00\x00\x00\x00',
- b'\x00\x00\x00\x66\x66\x3c\x18\x7e\x18\x7e\x18\x18\x00\x00\x00\x00',
- b'\x00\x00\xf8\xcc\xcc\xf8\xc4\xcc\xde\xcc\xcc\xc6\x00\x00\x00\x00',
- b'\x00\x00\x0e\x1b\x18\x18\x18\x7e\x18\x18\x18\x18\xd8\x70\x00\x00',
- b'\x00\x00\x18\x30\x60\x00\x78\x0c\x7c\xcc\xcc\x76\x00\x00\x00\x00',
- b'\x00\x00\x0c\x18\x30\x00\x38\x18\x18\x18\x18\x3c\x00\x00\x00\x00',
- b'\x00\x00\x18\x30\x60\x00\x7c\xc6\xc6\xc6\xc6\x7c\x00\x00\x00\x00',
- b'\x00\x00\x18\x30\x60\x00\xcc\xcc\xcc\xcc\xcc\x76\x00\x00\x00\x00',
- b'\x00\x00\x00\x76\xdc\x00\xdc\x66\x66\x66\x66\x66\x00\x00\x00\x00',
- b'\x00\x76\xdc\x00\xc6\xe6\xf6\xfe\xde\xce\xc6\xc6\x00\x00\x00\x00',
- b'\x00\x00\x3c\x6c\x6c\x3e\x00\x7e\x00\x00\x00\x00\x00\x00\x00\x00',
- b'\x00\x00\x38\x6c\x6c\x38\x00\x7c\x00\x00\x00\x00\x00\x00\x00\x00',
- b'\x00\x00\x00\x30\x30\x00\x30\x30\x60\xc6\xc6\x7c\x00\x00\x00\x00',
- b'\x00\x00\x00\x00\x00\x00\x00\xfe\xc0\xc0\xc0\x00\x00\x00\x00\x00',
- b'\x00\x00\x00\x00\x00\x00\x00\xfe\x06\x06\x06\x00\x00\x00\x00\x00',
- b'\x00\x00\xc0\xc0\xc6\xcc\xd8\x30\x60\xdc\x86\x0c\x18\x3e\x00\x00',
- b'\x00\x00\xc0\xc0\xc6\xcc\xd8\x30\x66\xce\x9e\x3e\x06\x06\x00\x00',
- b'\x00\x00\x00\x18\x18\x00\x18\x18\x3c\x3c\x3c\x18\x00\x00\x00\x00',
- b'\x00\x00\x00\x00\x00\x36\x6c\xd8\x6c\x36\x00\x00\x00\x00\x00\x00',
- b'\x00\x00\x00\x00\x00\xd8\x6c\x36\x6c\xd8\x00\x00\x00\x00\x00\x00',
- b'\x00\x11\x44\x11\x44\x11\x44\x11\x44\x11\x44\x11\x44\x11\x44\x00',
- b'\x00\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x00',
- b'\x00\xdd\x77\xdd\x77\xdd\x77\xdd\x77\xdd\x77\xdd\x77\xdd\x77\x00',
- b'\x00\x18\x18\x18\x18\x18\x18\x18\x18\x18\x18\x18\x18\x18\x18\x00',
- b'\x00\x18\x18\x18\x18\x18\x18\x18\xf8\x18\x18\x18\x18\x18\x18\x00',
- b'\x00\x18\x18\x18\x18\x18\xf8\x18\xf8\x18\x18\x18\x18\x18\x18\x00',
- b'\x00\x36\x36\x36\x36\x36\x36\x36\xf6\x36\x36\x36\x36\x36\x36\x00',
- b'\x00\x00\x00\x00\x00\x00\x00\x00\xfe\x36\x36\x36\x36\x36\x36\x00',
- b'\x00\x00\x00\x00\x00\x00\xf8\x18\xf8\x18\x18\x18\x18\x18\x18\x00',
- b'\x00\x36\x36\x36\x36\x36\xf6\x06\xf6\x36\x36\x36\x36\x36\x36\x00',
- b'\x00\x36\x36\x36\x36\x36\x36\x36\x36\x36\x36\x36\x36\x36\x36\x00',
- b'\x00\x00\x00\x00\x00\x00\xfe\x06\xf6\x36\x36\x36\x36\x36\x36\x00',
- b'\x00\x36\x36\x36\x36\x36\xf6\x06\xfe\x00\x00\x00\x00\x00\x00\x00',
- b'\x00\x36\x36\x36\x36\x36\x36\x36\xfe\x00\x00\x00\x00\x00\x00\x00',
- b'\x00\x18\x18\x18\x18\x18\xf8\x18\xf8\x00\x00\x00\x00\x00\x00\x00',
- b'\x00\x00\x00\x00\x00\x00\x00\x00\xf8\x18\x18\x18\x18\x18\x18\x00',
- b'\x00\x18\x18\x18\x18\x18\x18\x18\x1f\x00\x00\x00\x00\x00\x00\x00',
- b'\x00\x18\x18\x18\x18\x18\x18\x18\xff\x00\x00\x00\x00\x00\x00\x00',
- b'\x00\x00\x00\x00\x00\x00\x00\x00\xff\x18\x18\x18\x18\x18\x18\x00',
- b'\x00\x18\x18\x18\x18\x18\x18\x18\x1f\x18\x18\x18\x18\x18\x18\x00',
- b'\x00\x00\x00\x00\x00\x00\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00',
- b'\x00\x18\x18\x18\x18\x18\x18\x18\xff\x18\x18\x18\x18\x18\x18\x00',
- b'\x00\x18\x18\x18\x18\x18\x1f\x18\x1f\x18\x18\x18\x18\x18\x18\x00',
- b'\x00\x36\x36\x36\x36\x36\x36\x36\x37\x36\x36\x36\x36\x36\x36\x00',
- b'\x00\x36\x36\x36\x36\x36\x37\x30\x3f\x00\x00\x00\x00\x00\x00\x00',
- b'\x00\x00\x00\x00\x00\x00\x3f\x30\x37\x36\x36\x36\x36\x36\x36\x00',
- b'\x00\x36\x36\x36\x36\x36\xf7\x00\xff\x00\x00\x00\x00\x00\x00\x00',
- b'\x00\x00\x00\x00\x00\x00\xff\x00\xf7\x36\x36\x36\x36\x36\x36\x00',
- b'\x00\x36\x36\x36\x36\x36\x37\x30\x37\x36\x36\x36\x36\x36\x36\x00',
- b'\x00\x00\x00\x00\x00\x00\xff\x00\xff\x00\x00\x00\x00\x00\x00\x00',
- b'\x00\x36\x36\x36\x36\x36\xf7\x00\xf7\x36\x36\x36\x36\x36\x36\x00',
- b'\x00\x18\x18\x18\x18\x18\xff\x00\xff\x00\x00\x00\x00\x00\x00\x00',
- b'\x00\x36\x36\x36\x36\x36\x36\x36\xff\x00\x00\x00\x00\x00\x00\x00',
- b'\x00\x00\x00\x00\x00\x00\xff\x00\xff\x18\x18\x18\x18\x18\x18\x00',
- b'\x00\x00\x00\x00\x00\x00\x00\x00\xff\x36\x36\x36\x36\x36\x36\x00',
- b'\x00\x36\x36\x36\x36\x36\x36\x36\x3f\x00\x00\x00\x00\x00\x00\x00',
- b'\x00\x18\x18\x18\x18\x18\x1f\x18\x1f\x00\x00\x00\x00\x00\x00\x00',
- b'\x00\x00\x00\x00\x00\x00\x1f\x18\x1f\x18\x18\x18\x18\x18\x18\x00',
- b'\x00\x00\x00\x00\x00\x00\x00\x00\x3f\x36\x36\x36\x36\x36\x36\x00',
- b'\x00\x36\x36\x36\x36\x36\x36\x36\xff\x36\x36\x36\x36\x36\x36\x00',
- b'\x00\x18\x18\x18\x18\x18\xff\x18\xff\x18\x18\x18\x18\x18\x18\x00',
- b'\x00\x18\x18\x18\x18\x18\x18\x18\xf8\x00\x00\x00\x00\x00\x00\x00',
- b'\x00\x00\x00\x00\x00\x00\x00\x00\x1f\x18\x18\x18\x18\x18\x18\x00',
- b'\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00',
- b'\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\x00',
- b'\x00\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0\x00',
- b'\x00\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x00',
- b'\x00\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00',
- b'\x00\x00\x00\x00\x00\x00\x76\xdc\xd8\xd8\xdc\x76\x00\x00\x00\x00',
- b'\x00\x00\x00\x00\x00\x7c\xc6\xfc\xc6\xc6\xfc\xc0\xc0\x40\x00\x00',
- b'\x00\x00\x00\xfe\xc6\xc6\xc0\xc0\xc0\xc0\xc0\xc0\x00\x00\x00\x00',
- b'\x00\x00\x00\x00\x00\xfe\x6c\x6c\x6c\x6c\x6c\x6c\x00\x00\x00\x00',
- b'\x00\x00\x00\xfe\xc6\x60\x30\x18\x30\x60\xc6\xfe\x00\x00\x00\x00',
- b'\x00\x00\x00\x00\x00\x00\x7e\xd8\xd8\xd8\xd8\x70\x00\x00\x00\x00',
- b'\x00\x00\x00\x00\x00\x66\x66\x66\x66\x7c\x60\x60\xc0\x00\x00\x00',
- b'\x00\x00\x00\x00\x00\x76\xdc\x18\x18\x18\x18\x18\x00\x00\x00\x00',
- b'\x00\x00\x00\x7e\x18\x3c\x66\x66\x66\x3c\x18\x7e\x00\x00\x00\x00',
- b'\x00\x00\x00\x38\x6c\xc6\xc6\xfe\xc6\xc6\x6c\x38\x00\x00\x00\x00',
- b'\x00\x00\x00\x38\x6c\xc6\xc6\xc6\x6c\x6c\x6c\xee\x00\x00\x00\x00',
- b'\x00\x00\x00\x1e\x30\x18\x0c\x3e\x66\x66\x66\x3c\x00\x00\x00\x00',
- b'\x00\x00\x00\x00\x00\x00\x7e\xdb\xdb\x7e\x00\x00\x00\x00\x00\x00',
- b'\x00\x00\x00\x03\x06\x7e\xdb\xdb\xf3\x7e\x60\xc0\x00\x00\x00\x00',
- b'\x00\x00\x00\x1c\x30\x60\x60\x7c\x60\x60\x30\x1c\x00\x00\x00\x00',
- b'\x00\x00\x00\x00\x7c\xc6\xc6\xc6\xc6\xc6\xc6\xc6\x00\x00\x00\x00',
- b'\x00\x00\x00\x00\xfe\x00\x00\xfe\x00\x00\xfe\x00\x00\x00\x00\x00',
- b'\x00\x00\x00\x00\x18\x18\x7e\x18\x18\x00\x00\xff\x00\x00\x00\x00',
- b'\x00\x00\x00\x30\x18\x0c\x06\x0c\x18\x30\x00\x7e\x00\x00\x00\x00',
- b'\x00\x00\x00\x0c\x18\x30\x60\x30\x18\x0c\x00\x7e\x00\x00\x00\x00',
- b'\x00\x00\x00\x0e\x1b\x1b\x18\x18\x18\x18\x18\x18\x18\x18\x18\x00',
- b'\x00\x18\x18\x18\x18\x18\x18\x18\x18\xd8\xd8\x70\x00\x00\x00\x00',
- b'\x00\x00\x00\x00\x18\x18\x00\x7e\x00\x18\x18\x00\x00\x00\x00\x00',
- b'\x00\x00\x00\x00\x00\x76\xdc\x00\x76\xdc\x00\x00\x00\x00\x00\x00',
- b'\x00\x00\x38\x6c\x6c\x38\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
- b'\x00\x00\x00\x00\x00\x00\x00\x18\x18\x00\x00\x00\x00\x00\x00\x00',
- b'\x00\x00\x00\x00\x00\x00\x00\x00\x18\x00\x00\x00\x00\x00\x00\x00',
- b'\x00\x00\x0f\x0c\x0c\x0c\x0c\x0c\xec\x6c\x3c\x1c\x00\x00\x00\x00',
- b'\x00\x00\xd8\x6c\x6c\x6c\x6c\x6c\x00\x00\x00\x00\x00\x00\x00\x00',
- b'\x00\x00\x70\xd8\x30\x60\xc8\xf8\x00\x00\x00\x00\x00\x00\x00\x00',
- b'\x00\x00\x00\x00\x00\x7c\x7c\x7c\x7c\x7c\x7c\x00\x00\x00\x00\x00',
- b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
+ b"\x00\x00\x00\x7e\x81\xa5\x81\x81\xbd\x99\x81\x7e\x00\x00\x00\x00",
+ b"\x00\x00\x00\x7e\xff\xdb\xff\xff\xc3\xe7\xff\x7e\x00\x00\x00\x00",
+ b"\x00\x00\x00\x00\x6c\xfe\xfe\xfe\xfe\x7c\x38\x10\x00\x00\x00\x00",
+ b"\x00\x00\x00\x00\x10\x38\x7c\xfe\x7c\x38\x10\x00\x00\x00\x00\x00",
+ b"\x00\x00\x00\x18\x3c\x3c\xe7\xe7\xe7\x18\x18\x3c\x00\x00\x00\x00",
+ b"\x00\x00\x00\x18\x3c\x7e\xff\xff\x7e\x18\x18\x3c\x00\x00\x00\x00",
+ b"\x00\x00\x00\x00\x00\x00\x18\x3c\x3c\x18\x00\x00\x00\x00\x00\x00",
+ b"\x00\xff\xff\xff\xff\xff\xe7\xc3\xc3\xe7\xff\xff\xff\xff\xff\x00",
+ b"\x00\x00\x00\x00\x00\x3c\x66\x42\x42\x66\x3c\x00\x00\x00\x00\x00",
+ b"\x00\xff\xff\xff\xff\xc3\x99\xbd\xbd\x99\xc3\xff\xff\xff\xff\x00",
+ b"\x00\x00\x00\x1e\x0e\x1a\x32\x78\xcc\xcc\xcc\x78\x00\x00\x00\x00",
+ b"\x00\x00\x00\x3c\x66\x66\x66\x3c\x18\x7e\x18\x18\x00\x00\x00\x00",
+ b"\x00\x00\x00\x3f\x33\x3f\x30\x30\x30\x70\xf0\xe0\x00\x00\x00\x00",
+ b"\x00\x00\x00\x7f\x63\x7f\x63\x63\x63\x67\xe7\xe6\xc0\x00\x00\x00",
+ b"\x00\x00\x00\x18\x18\xdb\x3c\xe7\x3c\xdb\x18\x18\x00\x00\x00\x00",
+ b"\x00\x00\x00\x80\xc0\xe0\xf8\xfe\xf8\xe0\xc0\x80\x00\x00\x00\x00",
+ b"\x00\x00\x00\x02\x06\x0e\x3e\xfe\x3e\x0e\x06\x02\x00\x00\x00\x00",
+ b"\x00\x00\x00\x18\x3c\x7e\x18\x18\x18\x7e\x3c\x18\x00\x00\x00\x00",
+ b"\x00\x00\x00\x66\x66\x66\x66\x66\x66\x00\x66\x66\x00\x00\x00\x00",
+ b"\x00\x00\x00\x7f\xdb\xdb\xdb\x7b\x1b\x1b\x1b\x1b\x00\x00\x00\x00",
+ b"\x00\x00\x7c\xc6\x60\x38\x6c\xc6\xc6\x6c\x38\x0c\xc6\x7c\x00\x00",
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\xfe\xfe\xfe\x00\x00\x00\x00",
+ b"\x00\x00\x00\x18\x3c\x7e\x18\x18\x18\x7e\x3c\x18\x7e\x00\x00\x00",
+ b"\x00\x00\x00\x18\x3c\x7e\x18\x18\x18\x18\x18\x18\x00\x00\x00\x00",
+ b"\x00\x00\x00\x18\x18\x18\x18\x18\x18\x7e\x3c\x18\x00\x00\x00\x00",
+ b"\x00\x00\x00\x00\x00\x18\x0c\xfe\x0c\x18\x00\x00\x00\x00\x00\x00",
+ b"\x00\x00\x00\x00\x00\x30\x60\xfe\x60\x30\x00\x00\x00\x00\x00\x00",
+ b"\x00\x00\x00\x00\x00\x00\xc0\xc0\xc0\xfe\x00\x00\x00\x00\x00\x00",
+ b"\x00\x00\x00\x00\x00\x28\x6c\xfe\x6c\x28\x00\x00\x00\x00\x00\x00",
+ b"\x00\x00\x00\x00\x10\x38\x38\x7c\x7c\xfe\xfe\x00\x00\x00\x00\x00",
+ b"\x00\x00\x00\x00\xfe\xfe\x7c\x7c\x38\x38\x10\x00\x00\x00\x00\x00",
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
+ b"\x00\x00\x00\x18\x3c\x3c\x3c\x18\x18\x00\x18\x18\x00\x00\x00\x00",
+ b"\x00\x00\x66\x66\x66\x24\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
+ b"\x00\x00\x00\x6c\x6c\xfe\x6c\x6c\x6c\xfe\x6c\x6c\x00\x00\x00\x00",
+ b"\x00\x18\x18\x7c\xc6\xc2\xc0\x7c\x06\x86\xc6\x7c\x18\x18\x00\x00",
+ b"\x00\x00\x00\x00\x00\xc2\xc6\x0c\x18\x30\x66\xc6\x00\x00\x00\x00",
+ b"\x00\x00\x00\x38\x6c\x6c\x38\x76\xdc\xcc\xcc\x76\x00\x00\x00\x00",
+ b"\x00\x00\x30\x30\x30\x60\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
+ b"\x00\x00\x00\x0c\x18\x30\x30\x30\x30\x30\x18\x0c\x00\x00\x00\x00",
+ b"\x00\x00\x00\x30\x18\x0c\x0c\x0c\x0c\x0c\x18\x30\x00\x00\x00\x00",
+ b"\x00\x00\x00\x00\x00\x66\x3c\xff\x3c\x66\x00\x00\x00\x00\x00\x00",
+ b"\x00\x00\x00\x00\x00\x18\x18\x7e\x18\x18\x00\x00\x00\x00\x00\x00",
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x18\x18\x18\x30\x00\x00\x00",
+ b"\x00\x00\x00\x00\x00\x00\x00\xfe\x00\x00\x00\x00\x00\x00\x00\x00",
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x18\x18\x00\x00\x00\x00",
+ b"\x00\x00\x00\x02\x06\x0c\x18\x30\x60\xc0\x80\x00\x00\x00\x00\x00",
+ b"\x00\x00\x00\x7c\xc6\xce\xde\xf6\xe6\xc6\xc6\x7c\x00\x00\x00\x00",
+ b"\x00\x00\x00\x18\x38\x78\x18\x18\x18\x18\x18\x7e\x00\x00\x00\x00",
+ b"\x00\x00\x00\x7c\xc6\x06\x0c\x18\x30\x60\xc6\xfe\x00\x00\x00\x00",
+ b"\x00\x00\x00\x7c\xc6\x06\x06\x3c\x06\x06\xc6\x7c\x00\x00\x00\x00",
+ b"\x00\x00\x00\x0c\x1c\x3c\x6c\xcc\xfe\x0c\x0c\x1e\x00\x00\x00\x00",
+ b"\x00\x00\x00\xfe\xc0\xc0\xc0\xfc\x06\x06\xc6\x7c\x00\x00\x00\x00",
+ b"\x00\x00\x00\x38\x60\xc0\xc0\xfc\xc6\xc6\xc6\x7c\x00\x00\x00\x00",
+ b"\x00\x00\x00\xfe\xc6\x06\x0c\x18\x30\x30\x30\x30\x00\x00\x00\x00",
+ b"\x00\x00\x00\x7c\xc6\xc6\xc6\x7c\xc6\xc6\xc6\x7c\x00\x00\x00\x00",
+ b"\x00\x00\x00\x7c\xc6\xc6\xc6\x7e\x06\x06\x0c\x78\x00\x00\x00\x00",
+ b"\x00\x00\x00\x00\x18\x18\x00\x00\x00\x18\x18\x00\x00\x00\x00\x00",
+ b"\x00\x00\x00\x00\x18\x18\x00\x00\x00\x18\x18\x30\x00\x00\x00\x00",
+ b"\x00\x00\x00\x06\x0c\x18\x30\x60\x30\x18\x0c\x06\x00\x00\x00\x00",
+ b"\x00\x00\x00\x00\x00\x00\x7e\x00\x00\x7e\x00\x00\x00\x00\x00\x00",
+ b"\x00\x00\x00\x60\x30\x18\x0c\x06\x0c\x18\x30\x60\x00\x00\x00\x00",
+ b"\x00\x00\x00\x7c\xc6\xc6\x0c\x18\x18\x00\x18\x18\x00\x00\x00\x00",
+ b"\x00\x00\x00\x7c\xc6\xc6\xde\xde\xde\xdc\xc0\x7c\x00\x00\x00\x00",
+ b"\x00\x00\x00\x10\x38\x6c\xc6\xc6\xfe\xc6\xc6\xc6\x00\x00\x00\x00",
+ b"\x00\x00\x00\xfc\x66\x66\x66\x7c\x66\x66\x66\xfc\x00\x00\x00\x00",
+ b"\x00\x00\x00\x3c\x66\xc2\xc0\xc0\xc0\xc2\x66\x3c\x00\x00\x00\x00",
+ b"\x00\x00\x00\xf8\x6c\x66\x66\x66\x66\x66\x6c\xf8\x00\x00\x00\x00",
+ b"\x00\x00\x00\xfe\x66\x62\x68\x78\x68\x62\x66\xfe\x00\x00\x00\x00",
+ b"\x00\x00\x00\xfe\x66\x62\x68\x78\x68\x60\x60\xf0\x00\x00\x00\x00",
+ b"\x00\x00\x00\x3c\x66\xc2\xc0\xc0\xde\xc6\x66\x3a\x00\x00\x00\x00",
+ b"\x00\x00\x00\xc6\xc6\xc6\xc6\xfe\xc6\xc6\xc6\xc6\x00\x00\x00\x00",
+ b"\x00\x00\x00\x3c\x18\x18\x18\x18\x18\x18\x18\x3c\x00\x00\x00\x00",
+ b"\x00\x00\x00\x1e\x0c\x0c\x0c\x0c\x0c\xcc\xcc\x78\x00\x00\x00\x00",
+ b"\x00\x00\x00\xe6\x66\x6c\x6c\x78\x6c\x6c\x66\xe6\x00\x00\x00\x00",
+ b"\x00\x00\x00\xf0\x60\x60\x60\x60\x60\x62\x66\xfe\x00\x00\x00\x00",
+ b"\x00\x00\x00\xc6\xee\xfe\xfe\xd6\xc6\xc6\xc6\xc6\x00\x00\x00\x00",
+ b"\x00\x00\x00\xc6\xe6\xf6\xfe\xde\xce\xc6\xc6\xc6\x00\x00\x00\x00",
+ b"\x00\x00\x00\x38\x6c\xc6\xc6\xc6\xc6\xc6\x6c\x38\x00\x00\x00\x00",
+ b"\x00\x00\x00\xfc\x66\x66\x66\x7c\x60\x60\x60\xf0\x00\x00\x00\x00",
+ b"\x00\x00\x00\x7c\xc6\xc6\xc6\xc6\xd6\xde\x7c\x0c\x0e\x00\x00\x00",
+ b"\x00\x00\x00\xfc\x66\x66\x66\x7c\x6c\x66\x66\xe6\x00\x00\x00\x00",
+ b"\x00\x00\x00\x7c\xc6\xc6\x60\x38\x0c\xc6\xc6\x7c\x00\x00\x00\x00",
+ b"\x00\x00\x00\x7e\x7e\x5a\x18\x18\x18\x18\x18\x3c\x00\x00\x00\x00",
+ b"\x00\x00\x00\xc6\xc6\xc6\xc6\xc6\xc6\xc6\xc6\x7c\x00\x00\x00\x00",
+ b"\x00\x00\x00\xc6\xc6\xc6\xc6\xc6\xc6\x6c\x38\x10\x00\x00\x00\x00",
+ b"\x00\x00\x00\xc6\xc6\xc6\xc6\xd6\xd6\xfe\x7c\x6c\x00\x00\x00\x00",
+ b"\x00\x00\x00\xc6\xc6\x6c\x38\x38\x38\x6c\xc6\xc6\x00\x00\x00\x00",
+ b"\x00\x00\x00\x66\x66\x66\x66\x3c\x18\x18\x18\x3c\x00\x00\x00\x00",
+ b"\x00\x00\x00\xfe\xc6\x8c\x18\x30\x60\xc2\xc6\xfe\x00\x00\x00\x00",
+ b"\x00\x00\x00\x3c\x30\x30\x30\x30\x30\x30\x30\x3c\x00\x00\x00\x00",
+ b"\x00\x00\x00\x80\xc0\xe0\x70\x38\x1c\x0e\x06\x02\x00\x00\x00\x00",
+ b"\x00\x00\x00\x3c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x3c\x00\x00\x00\x00",
+ b"\x00\x10\x38\x6c\xc6\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x00\x00",
+ b"\x00\x30\x30\x18\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
+ b"\x00\x00\x00\x00\x00\x00\x78\x0c\x7c\xcc\xcc\x76\x00\x00\x00\x00",
+ b"\x00\x00\x00\xe0\x60\x60\x78\x6c\x66\x66\x66\x7c\x00\x00\x00\x00",
+ b"\x00\x00\x00\x00\x00\x00\x7c\xc6\xc0\xc0\xc6\x7c\x00\x00\x00\x00",
+ b"\x00\x00\x00\x1c\x0c\x0c\x3c\x6c\xcc\xcc\xcc\x76\x00\x00\x00\x00",
+ b"\x00\x00\x00\x00\x00\x00\x7c\xc6\xfe\xc0\xc6\x7c\x00\x00\x00\x00",
+ b"\x00\x00\x00\x38\x6c\x64\x60\xf0\x60\x60\x60\xf0\x00\x00\x00\x00",
+ b"\x00\x00\x00\x00\x00\x00\x76\xcc\xcc\xcc\x7c\x0c\xcc\x78\x00\x00",
+ b"\x00\x00\x00\xe0\x60\x60\x6c\x76\x66\x66\x66\xe6\x00\x00\x00\x00",
+ b"\x00\x00\x00\x18\x18\x00\x38\x18\x18\x18\x18\x3c\x00\x00\x00\x00",
+ b"\x00\x00\x00\x06\x06\x00\x0e\x06\x06\x06\x06\x66\x66\x3c\x00\x00",
+ b"\x00\x00\x00\xe0\x60\x60\x66\x6c\x78\x6c\x66\xe6\x00\x00\x00\x00",
+ b"\x00\x00\x00\x38\x18\x18\x18\x18\x18\x18\x18\x3c\x00\x00\x00\x00",
+ b"\x00\x00\x00\x00\x00\x00\xec\xfe\xd6\xd6\xd6\xc6\x00\x00\x00\x00",
+ b"\x00\x00\x00\x00\x00\x00\xdc\x66\x66\x66\x66\x66\x00\x00\x00\x00",
+ b"\x00\x00\x00\x00\x00\x00\x7c\xc6\xc6\xc6\xc6\x7c\x00\x00\x00\x00",
+ b"\x00\x00\x00\x00\x00\x00\xdc\x66\x66\x66\x7c\x60\x60\xf0\x00\x00",
+ b"\x00\x00\x00\x00\x00\x00\x76\xcc\xcc\xcc\x7c\x0c\x0c\x1e\x00\x00",
+ b"\x00\x00\x00\x00\x00\x00\xdc\x76\x66\x60\x60\xf0\x00\x00\x00\x00",
+ b"\x00\x00\x00\x00\x00\x00\x7c\xc6\x70\x1c\xc6\x7c\x00\x00\x00\x00",
+ b"\x00\x00\x00\x10\x30\x30\xfc\x30\x30\x30\x36\x1c\x00\x00\x00\x00",
+ b"\x00\x00\x00\x00\x00\x00\xcc\xcc\xcc\xcc\xcc\x76\x00\x00\x00\x00",
+ b"\x00\x00\x00\x00\x00\x00\x66\x66\x66\x66\x3c\x18\x00\x00\x00\x00",
+ b"\x00\x00\x00\x00\x00\x00\xc6\xc6\xd6\xd6\xfe\x6c\x00\x00\x00\x00",
+ b"\x00\x00\x00\x00\x00\x00\xc6\x6c\x38\x38\x6c\xc6\x00\x00\x00\x00",
+ b"\x00\x00\x00\x00\x00\x00\xc6\xc6\xc6\xc6\x7e\x06\x0c\xf8\x00\x00",
+ b"\x00\x00\x00\x00\x00\x00\xfe\xcc\x18\x30\x66\xfe\x00\x00\x00\x00",
+ b"\x00\x00\x00\x0e\x18\x18\x18\x70\x18\x18\x18\x0e\x00\x00\x00\x00",
+ b"\x00\x00\x00\x18\x18\x18\x18\x00\x18\x18\x18\x18\x00\x00\x00\x00",
+ b"\x00\x00\x00\x70\x18\x18\x18\x0e\x18\x18\x18\x70\x00\x00\x00\x00",
+ b"\x00\x00\x00\x76\xdc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
+ b"\x00\x00\x00\x00\x00\x10\x38\x6c\xc6\xc6\xfe\x00\x00\x00\x00\x00",
+ b"\x00\x00\x00\x3c\x66\xc2\xc0\xc0\xc2\x66\x3c\x0c\x06\x7c\x00\x00",
+ b"\x00\x00\x00\xcc\xcc\x00\xcc\xcc\xcc\xcc\xcc\x76\x00\x00\x00\x00",
+ b"\x00\x00\x0c\x18\x30\x00\x7c\xc6\xfe\xc0\xc6\x7c\x00\x00\x00\x00",
+ b"\x00\x00\x10\x38\x6c\x00\x78\x0c\x7c\xcc\xcc\x76\x00\x00\x00\x00",
+ b"\x00\x00\x00\xcc\xcc\x00\x78\x0c\x7c\xcc\xcc\x76\x00\x00\x00\x00",
+ b"\x00\x00\x60\x30\x18\x00\x78\x0c\x7c\xcc\xcc\x76\x00\x00\x00\x00",
+ b"\x00\x00\x38\x6c\x38\x00\x78\x0c\x7c\xcc\xcc\x76\x00\x00\x00\x00",
+ b"\x00\x00\x00\x00\x00\x3c\x66\x60\x66\x3c\x0c\x06\x3c\x00\x00\x00",
+ b"\x00\x00\x10\x38\x6c\x00\x7c\xc6\xfe\xc0\xc6\x7c\x00\x00\x00\x00",
+ b"\x00\x00\x00\xcc\xcc\x00\x7c\xc6\xfe\xc0\xc6\x7c\x00\x00\x00\x00",
+ b"\x00\x00\x60\x30\x18\x00\x7c\xc6\xfe\xc0\xc6\x7c\x00\x00\x00\x00",
+ b"\x00\x00\x00\x66\x66\x00\x38\x18\x18\x18\x18\x3c\x00\x00\x00\x00",
+ b"\x00\x00\x18\x3c\x66\x00\x38\x18\x18\x18\x18\x3c\x00\x00\x00\x00",
+ b"\x00\x00\x60\x30\x18\x00\x38\x18\x18\x18\x18\x3c\x00\x00\x00\x00",
+ b"\x00\x00\xc6\xc6\x10\x38\x6c\xc6\xc6\xfe\xc6\xc6\x00\x00\x00\x00",
+ b"\x00\x38\x6c\x38\x00\x38\x6c\xc6\xc6\xfe\xc6\xc6\x00\x00\x00\x00",
+ b"\x00\x18\x30\x60\x00\xfe\x66\x60\x7c\x60\x66\xfe\x00\x00\x00\x00",
+ b"\x00\x00\x00\x00\x00\xcc\x76\x36\x7e\xd8\xd8\x6e\x00\x00\x00\x00",
+ b"\x00\x00\x00\x3e\x6c\xcc\xcc\xfe\xcc\xcc\xcc\xce\x00\x00\x00\x00",
+ b"\x00\x00\x10\x38\x6c\x00\x7c\xc6\xc6\xc6\xc6\x7c\x00\x00\x00\x00",
+ b"\x00\x00\x00\xc6\xc6\x00\x7c\xc6\xc6\xc6\xc6\x7c\x00\x00\x00\x00",
+ b"\x00\x00\x60\x30\x18\x00\x7c\xc6\xc6\xc6\xc6\x7c\x00\x00\x00\x00",
+ b"\x00\x00\x30\x78\xcc\x00\xcc\xcc\xcc\xcc\xcc\x76\x00\x00\x00\x00",
+ b"\x00\x00\x60\x30\x18\x00\xcc\xcc\xcc\xcc\xcc\x76\x00\x00\x00\x00",
+ b"\x00\x00\x00\xc6\xc6\x00\xc6\xc6\xc6\xc6\x7e\x06\x0c\x78\x00\x00",
+ b"\x00\x00\xc6\xc6\x38\x6c\xc6\xc6\xc6\xc6\x6c\x38\x00\x00\x00\x00",
+ b"\x00\x00\xc6\xc6\x00\xc6\xc6\xc6\xc6\xc6\xc6\x7c\x00\x00\x00\x00",
+ b"\x00\x00\x18\x18\x3c\x66\x60\x60\x66\x3c\x18\x18\x00\x00\x00\x00",
+ b"\x00\x00\x38\x6c\x64\x60\xf0\x60\x60\x60\xe6\xfc\x00\x00\x00\x00",
+ b"\x00\x00\x00\x66\x66\x3c\x18\x7e\x18\x7e\x18\x18\x00\x00\x00\x00",
+ b"\x00\x00\xf8\xcc\xcc\xf8\xc4\xcc\xde\xcc\xcc\xc6\x00\x00\x00\x00",
+ b"\x00\x00\x0e\x1b\x18\x18\x18\x7e\x18\x18\x18\x18\xd8\x70\x00\x00",
+ b"\x00\x00\x18\x30\x60\x00\x78\x0c\x7c\xcc\xcc\x76\x00\x00\x00\x00",
+ b"\x00\x00\x0c\x18\x30\x00\x38\x18\x18\x18\x18\x3c\x00\x00\x00\x00",
+ b"\x00\x00\x18\x30\x60\x00\x7c\xc6\xc6\xc6\xc6\x7c\x00\x00\x00\x00",
+ b"\x00\x00\x18\x30\x60\x00\xcc\xcc\xcc\xcc\xcc\x76\x00\x00\x00\x00",
+ b"\x00\x00\x00\x76\xdc\x00\xdc\x66\x66\x66\x66\x66\x00\x00\x00\x00",
+ b"\x00\x76\xdc\x00\xc6\xe6\xf6\xfe\xde\xce\xc6\xc6\x00\x00\x00\x00",
+ b"\x00\x00\x3c\x6c\x6c\x3e\x00\x7e\x00\x00\x00\x00\x00\x00\x00\x00",
+ b"\x00\x00\x38\x6c\x6c\x38\x00\x7c\x00\x00\x00\x00\x00\x00\x00\x00",
+ b"\x00\x00\x00\x30\x30\x00\x30\x30\x60\xc6\xc6\x7c\x00\x00\x00\x00",
+ b"\x00\x00\x00\x00\x00\x00\x00\xfe\xc0\xc0\xc0\x00\x00\x00\x00\x00",
+ b"\x00\x00\x00\x00\x00\x00\x00\xfe\x06\x06\x06\x00\x00\x00\x00\x00",
+ b"\x00\x00\xc0\xc0\xc6\xcc\xd8\x30\x60\xdc\x86\x0c\x18\x3e\x00\x00",
+ b"\x00\x00\xc0\xc0\xc6\xcc\xd8\x30\x66\xce\x9e\x3e\x06\x06\x00\x00",
+ b"\x00\x00\x00\x18\x18\x00\x18\x18\x3c\x3c\x3c\x18\x00\x00\x00\x00",
+ b"\x00\x00\x00\x00\x00\x36\x6c\xd8\x6c\x36\x00\x00\x00\x00\x00\x00",
+ b"\x00\x00\x00\x00\x00\xd8\x6c\x36\x6c\xd8\x00\x00\x00\x00\x00\x00",
+ b"\x00\x11\x44\x11\x44\x11\x44\x11\x44\x11\x44\x11\x44\x11\x44\x00",
+ b"\x00\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x00",
+ b"\x00\xdd\x77\xdd\x77\xdd\x77\xdd\x77\xdd\x77\xdd\x77\xdd\x77\x00",
+ b"\x00\x18\x18\x18\x18\x18\x18\x18\x18\x18\x18\x18\x18\x18\x18\x00",
+ b"\x00\x18\x18\x18\x18\x18\x18\x18\xf8\x18\x18\x18\x18\x18\x18\x00",
+ b"\x00\x18\x18\x18\x18\x18\xf8\x18\xf8\x18\x18\x18\x18\x18\x18\x00",
+ b"\x00\x36\x36\x36\x36\x36\x36\x36\xf6\x36\x36\x36\x36\x36\x36\x00",
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\xfe\x36\x36\x36\x36\x36\x36\x00",
+ b"\x00\x00\x00\x00\x00\x00\xf8\x18\xf8\x18\x18\x18\x18\x18\x18\x00",
+ b"\x00\x36\x36\x36\x36\x36\xf6\x06\xf6\x36\x36\x36\x36\x36\x36\x00",
+ b"\x00\x36\x36\x36\x36\x36\x36\x36\x36\x36\x36\x36\x36\x36\x36\x00",
+ b"\x00\x00\x00\x00\x00\x00\xfe\x06\xf6\x36\x36\x36\x36\x36\x36\x00",
+ b"\x00\x36\x36\x36\x36\x36\xf6\x06\xfe\x00\x00\x00\x00\x00\x00\x00",
+ b"\x00\x36\x36\x36\x36\x36\x36\x36\xfe\x00\x00\x00\x00\x00\x00\x00",
+ b"\x00\x18\x18\x18\x18\x18\xf8\x18\xf8\x00\x00\x00\x00\x00\x00\x00",
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\xf8\x18\x18\x18\x18\x18\x18\x00",
+ b"\x00\x18\x18\x18\x18\x18\x18\x18\x1f\x00\x00\x00\x00\x00\x00\x00",
+ b"\x00\x18\x18\x18\x18\x18\x18\x18\xff\x00\x00\x00\x00\x00\x00\x00",
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\xff\x18\x18\x18\x18\x18\x18\x00",
+ b"\x00\x18\x18\x18\x18\x18\x18\x18\x1f\x18\x18\x18\x18\x18\x18\x00",
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00",
+ b"\x00\x18\x18\x18\x18\x18\x18\x18\xff\x18\x18\x18\x18\x18\x18\x00",
+ b"\x00\x18\x18\x18\x18\x18\x1f\x18\x1f\x18\x18\x18\x18\x18\x18\x00",
+ b"\x00\x36\x36\x36\x36\x36\x36\x36\x37\x36\x36\x36\x36\x36\x36\x00",
+ b"\x00\x36\x36\x36\x36\x36\x37\x30\x3f\x00\x00\x00\x00\x00\x00\x00",
+ b"\x00\x00\x00\x00\x00\x00\x3f\x30\x37\x36\x36\x36\x36\x36\x36\x00",
+ b"\x00\x36\x36\x36\x36\x36\xf7\x00\xff\x00\x00\x00\x00\x00\x00\x00",
+ b"\x00\x00\x00\x00\x00\x00\xff\x00\xf7\x36\x36\x36\x36\x36\x36\x00",
+ b"\x00\x36\x36\x36\x36\x36\x37\x30\x37\x36\x36\x36\x36\x36\x36\x00",
+ b"\x00\x00\x00\x00\x00\x00\xff\x00\xff\x00\x00\x00\x00\x00\x00\x00",
+ b"\x00\x36\x36\x36\x36\x36\xf7\x00\xf7\x36\x36\x36\x36\x36\x36\x00",
+ b"\x00\x18\x18\x18\x18\x18\xff\x00\xff\x00\x00\x00\x00\x00\x00\x00",
+ b"\x00\x36\x36\x36\x36\x36\x36\x36\xff\x00\x00\x00\x00\x00\x00\x00",
+ b"\x00\x00\x00\x00\x00\x00\xff\x00\xff\x18\x18\x18\x18\x18\x18\x00",
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\xff\x36\x36\x36\x36\x36\x36\x00",
+ b"\x00\x36\x36\x36\x36\x36\x36\x36\x3f\x00\x00\x00\x00\x00\x00\x00",
+ b"\x00\x18\x18\x18\x18\x18\x1f\x18\x1f\x00\x00\x00\x00\x00\x00\x00",
+ b"\x00\x00\x00\x00\x00\x00\x1f\x18\x1f\x18\x18\x18\x18\x18\x18\x00",
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x3f\x36\x36\x36\x36\x36\x36\x00",
+ b"\x00\x36\x36\x36\x36\x36\x36\x36\xff\x36\x36\x36\x36\x36\x36\x00",
+ b"\x00\x18\x18\x18\x18\x18\xff\x18\xff\x18\x18\x18\x18\x18\x18\x00",
+ b"\x00\x18\x18\x18\x18\x18\x18\x18\xf8\x00\x00\x00\x00\x00\x00\x00",
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x1f\x18\x18\x18\x18\x18\x18\x00",
+ b"\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00",
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\x00",
+ b"\x00\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0\x00",
+ b"\x00\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x00",
+ b"\x00\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00",
+ b"\x00\x00\x00\x00\x00\x00\x76\xdc\xd8\xd8\xdc\x76\x00\x00\x00\x00",
+ b"\x00\x00\x00\x00\x00\x7c\xc6\xfc\xc6\xc6\xfc\xc0\xc0\x40\x00\x00",
+ b"\x00\x00\x00\xfe\xc6\xc6\xc0\xc0\xc0\xc0\xc0\xc0\x00\x00\x00\x00",
+ b"\x00\x00\x00\x00\x00\xfe\x6c\x6c\x6c\x6c\x6c\x6c\x00\x00\x00\x00",
+ b"\x00\x00\x00\xfe\xc6\x60\x30\x18\x30\x60\xc6\xfe\x00\x00\x00\x00",
+ b"\x00\x00\x00\x00\x00\x00\x7e\xd8\xd8\xd8\xd8\x70\x00\x00\x00\x00",
+ b"\x00\x00\x00\x00\x00\x66\x66\x66\x66\x7c\x60\x60\xc0\x00\x00\x00",
+ b"\x00\x00\x00\x00\x00\x76\xdc\x18\x18\x18\x18\x18\x00\x00\x00\x00",
+ b"\x00\x00\x00\x7e\x18\x3c\x66\x66\x66\x3c\x18\x7e\x00\x00\x00\x00",
+ b"\x00\x00\x00\x38\x6c\xc6\xc6\xfe\xc6\xc6\x6c\x38\x00\x00\x00\x00",
+ b"\x00\x00\x00\x38\x6c\xc6\xc6\xc6\x6c\x6c\x6c\xee\x00\x00\x00\x00",
+ b"\x00\x00\x00\x1e\x30\x18\x0c\x3e\x66\x66\x66\x3c\x00\x00\x00\x00",
+ b"\x00\x00\x00\x00\x00\x00\x7e\xdb\xdb\x7e\x00\x00\x00\x00\x00\x00",
+ b"\x00\x00\x00\x03\x06\x7e\xdb\xdb\xf3\x7e\x60\xc0\x00\x00\x00\x00",
+ b"\x00\x00\x00\x1c\x30\x60\x60\x7c\x60\x60\x30\x1c\x00\x00\x00\x00",
+ b"\x00\x00\x00\x00\x7c\xc6\xc6\xc6\xc6\xc6\xc6\xc6\x00\x00\x00\x00",
+ b"\x00\x00\x00\x00\xfe\x00\x00\xfe\x00\x00\xfe\x00\x00\x00\x00\x00",
+ b"\x00\x00\x00\x00\x18\x18\x7e\x18\x18\x00\x00\xff\x00\x00\x00\x00",
+ b"\x00\x00\x00\x30\x18\x0c\x06\x0c\x18\x30\x00\x7e\x00\x00\x00\x00",
+ b"\x00\x00\x00\x0c\x18\x30\x60\x30\x18\x0c\x00\x7e\x00\x00\x00\x00",
+ b"\x00\x00\x00\x0e\x1b\x1b\x18\x18\x18\x18\x18\x18\x18\x18\x18\x00",
+ b"\x00\x18\x18\x18\x18\x18\x18\x18\x18\xd8\xd8\x70\x00\x00\x00\x00",
+ b"\x00\x00\x00\x00\x18\x18\x00\x7e\x00\x18\x18\x00\x00\x00\x00\x00",
+ b"\x00\x00\x00\x00\x00\x76\xdc\x00\x76\xdc\x00\x00\x00\x00\x00\x00",
+ b"\x00\x00\x38\x6c\x6c\x38\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
+ b"\x00\x00\x00\x00\x00\x00\x00\x18\x18\x00\x00\x00\x00\x00\x00\x00",
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x18\x00\x00\x00\x00\x00\x00\x00",
+ b"\x00\x00\x0f\x0c\x0c\x0c\x0c\x0c\xec\x6c\x3c\x1c\x00\x00\x00\x00",
+ b"\x00\x00\xd8\x6c\x6c\x6c\x6c\x6c\x00\x00\x00\x00\x00\x00\x00\x00",
+ b"\x00\x00\x70\xd8\x30\x60\xc8\xf8\x00\x00\x00\x00\x00\x00\x00\x00",
+ b"\x00\x00\x00\x00\x00\x7c\x7c\x7c\x7c\x7c\x7c\x00\x00\x00\x00\x00",
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
]
diff --git a/klippy/extras/display/hd44780.py b/klippy/extras/display/hd44780.py
index 2da49c51..8cde1d6d 100644
--- a/klippy/extras/display/hd44780.py
+++ b/klippy/extras/display/hd44780.py
@@ -6,87 +6,106 @@
# This file may be distributed under the terms of the GNU GPLv3 license.
import logging
-BACKGROUND_PRIORITY_CLOCK = 0x7fffffff00000000
-LINE_LENGTH_DEFAULT=20
-LINE_LENGTH_OPTIONS=[16, 20]
+BACKGROUND_PRIORITY_CLOCK = 0x7FFFFFFF00000000
+LINE_LENGTH_DEFAULT = 20
+LINE_LENGTH_OPTIONS = [16, 20]
-TextGlyphs = { 'right_arrow': b'\x7e' }
+TextGlyphs = {"right_arrow": b"\x7e"}
+
+HD44780_DELAY = 0.000040
-HD44780_DELAY = .000040
class HD44780:
def __init__(self, config):
self.printer = config.get_printer()
# pin config
- ppins = self.printer.lookup_object('pins')
- pins = [ppins.lookup_pin(config.get(name + '_pin'))
- for name in ['rs', 'e', 'd4', 'd5', 'd6', 'd7']]
- self.hd44780_protocol_init = config.getboolean('hd44780_protocol_init',
- True)
- self.line_length = config.getchoice('line_length', LINE_LENGTH_OPTIONS,
- LINE_LENGTH_DEFAULT)
+ ppins = self.printer.lookup_object("pins")
+ pins = [
+ ppins.lookup_pin(config.get(name + "_pin"))
+ for name in ["rs", "e", "d4", "d5", "d6", "d7"]
+ ]
+ self.hd44780_protocol_init = config.getboolean("hd44780_protocol_init", True)
+ self.line_length = config.getchoice(
+ "line_length", LINE_LENGTH_OPTIONS, LINE_LENGTH_DEFAULT
+ )
mcu = None
for pin_params in pins:
- if mcu is not None and pin_params['chip'] != mcu:
+ if mcu is not None and pin_params["chip"] != mcu:
raise ppins.error("hd44780 all pins must be on same mcu")
- mcu = pin_params['chip']
- self.pins = [pin_params['pin'] for pin_params in pins]
+ mcu = pin_params["chip"]
+ self.pins = [pin_params["pin"] for pin_params in pins]
self.mcu = mcu
self.oid = self.mcu.create_oid()
self.mcu.register_config_callback(self.build_config)
self.send_data_cmd = self.send_cmds_cmd = None
self.icons = {}
# framebuffers
- self.text_framebuffers = [bytearray(b' '*2*self.line_length),
- bytearray(b' '*2*self.line_length)]
+ self.text_framebuffers = [
+ bytearray(b" " * 2 * self.line_length),
+ bytearray(b" " * 2 * self.line_length),
+ ]
self.glyph_framebuffer = bytearray(64)
self.all_framebuffers = [
# Text framebuffers
- (self.text_framebuffers[0], bytearray(b'~'*2*self.line_length),
- 0x80),
- (self.text_framebuffers[1], bytearray(b'~'*2*self.line_length),
- 0xc0),
+ (self.text_framebuffers[0], bytearray(b"~" * 2 * self.line_length), 0x80),
+ (self.text_framebuffers[1], bytearray(b"~" * 2 * self.line_length), 0xC0),
# Glyph framebuffer
- (self.glyph_framebuffer, bytearray(b'~'*64), 0x40) ]
+ (self.glyph_framebuffer, bytearray(b"~" * 64), 0x40),
+ ]
+
def build_config(self):
self.mcu.add_config_cmd(
"config_hd44780 oid=%d rs_pin=%s e_pin=%s"
- " d4_pin=%s d5_pin=%s d6_pin=%s d7_pin=%s delay_ticks=%d" % (
- self.oid, self.pins[0], self.pins[1],
- self.pins[2], self.pins[3], self.pins[4], self.pins[5],
- self.mcu.seconds_to_clock(HD44780_DELAY)))
+ " d4_pin=%s d5_pin=%s d6_pin=%s d7_pin=%s delay_ticks=%d"
+ % (
+ self.oid,
+ self.pins[0],
+ self.pins[1],
+ self.pins[2],
+ self.pins[3],
+ self.pins[4],
+ self.pins[5],
+ self.mcu.seconds_to_clock(HD44780_DELAY),
+ )
+ )
cmd_queue = self.mcu.alloc_command_queue()
self.send_cmds_cmd = self.mcu.lookup_command(
- "hd44780_send_cmds oid=%c cmds=%*s", cq=cmd_queue)
+ "hd44780_send_cmds oid=%c cmds=%*s", cq=cmd_queue
+ )
self.send_data_cmd = self.mcu.lookup_command(
- "hd44780_send_data oid=%c data=%*s", cq=cmd_queue)
+ "hd44780_send_data oid=%c data=%*s", cq=cmd_queue
+ )
+
def send(self, cmds, is_data=False):
cmd_type = self.send_cmds_cmd
if is_data:
cmd_type = self.send_data_cmd
cmd_type.send([self.oid, cmds], reqclock=BACKGROUND_PRIORITY_CLOCK)
- #logging.debug("hd44780 %d %s", is_data, repr(cmds))
+ # logging.debug("hd44780 %d %s", is_data, repr(cmds))
+
def flush(self):
# Find all differences in the framebuffers and send them to the chip
for new_data, old_data, fb_id in self.all_framebuffers:
if new_data == old_data:
continue
# Find the position of all changed bytes in this framebuffer
- diffs = [[i, 1] for i, (n, o) in enumerate(zip(new_data, old_data))
- if n != o]
+ diffs = [
+ [i, 1] for i, (n, o) in enumerate(zip(new_data, old_data)) if n != o
+ ]
# Batch together changes that are close to each other
- for i in range(len(diffs)-2, -1, -1):
+ for i in range(len(diffs) - 2, -1, -1):
pos, count = diffs[i]
- nextpos, nextcount = diffs[i+1]
+ nextpos, nextcount = diffs[i + 1]
if pos + 4 >= nextpos and nextcount < 16:
diffs[i][1] = nextcount + (nextpos - pos)
- del diffs[i+1]
+ del diffs[i + 1]
# Transmit changes
for pos, count in diffs:
chip_pos = pos
self.send([fb_id + chip_pos])
- self.send(new_data[pos:pos+count], is_data=True)
+ self.send(new_data[pos : pos + count], is_data=True)
old_data[:] = new_data
+
def init(self):
curtime = self.printer.get_reactor().monotonic()
print_time = self.mcu.estimated_print_time(curtime)
@@ -96,27 +115,30 @@ class HD44780:
else:
init = [[0x02]]
# Reset (set positive direction ; enable display and hide cursor)
- init.append([0x06, 0x0c])
+ init.append([0x06, 0x0C])
for i, cmds in enumerate(init):
- minclock = self.mcu.print_time_to_clock(print_time + i * .100)
+ minclock = self.mcu.print_time_to_clock(print_time + i * 0.100)
self.send_cmds_cmd.send([self.oid, cmds], minclock=minclock)
self.flush()
+
def write_text(self, x, y, data):
if x + len(data) > self.line_length:
- data = data[:self.line_length - min(x, self.line_length)]
+ data = data[: self.line_length - min(x, self.line_length)]
pos = x + ((y & 0x02) >> 1) * self.line_length
- self.text_framebuffers[y & 1][pos:pos+len(data)] = data
+ self.text_framebuffers[y & 1][pos : pos + len(data)] = data
+
def set_glyphs(self, glyphs):
for glyph_name, glyph_data in glyphs.items():
- data = glyph_data.get('icon5x8')
+ data = glyph_data.get("icon5x8")
if data is not None:
self.icons[glyph_name] = data
+
def write_glyph(self, x, y, glyph_name):
data = self.icons.get(glyph_name)
if data is not None:
slot, bits = data
self.write_text(x, y, [slot])
- self.glyph_framebuffer[slot * 8:(slot + 1) * 8] = bits
+ self.glyph_framebuffer[slot * 8 : (slot + 1) * 8] = bits
return 1
char = TextGlyphs.get(glyph_name)
if char is not None:
@@ -124,11 +146,14 @@ class HD44780:
self.write_text(x, y, char)
return 1
return 0
+
def write_graphics(self, x, y, data):
pass
+
def clear(self):
- spaces = b' ' * 2*self.line_length
+ spaces = b" " * 2 * self.line_length
self.text_framebuffers[0][:] = spaces
self.text_framebuffers[1][:] = spaces
+
def get_dimensions(self):
return (self.line_length, 4)
diff --git a/klippy/extras/display/hd44780_spi.py b/klippy/extras/display/hd44780_spi.py
index f21accbb..dd46acd3 100644
--- a/klippy/extras/display/hd44780_spi.py
+++ b/klippy/extras/display/hd44780_spi.py
@@ -8,43 +8,43 @@
import logging
from .. import bus
-LINE_LENGTH_DEFAULT=20
-LINE_LENGTH_OPTIONS=[16, 20]
-
-TextGlyphs = { 'right_arrow': b'\x7e' }
+LINE_LENGTH_DEFAULT = 20
+LINE_LENGTH_OPTIONS = [16, 20]
+TextGlyphs = {"right_arrow": b"\x7e"}
class hd44780_spi:
def __init__(self, config):
self.printer = config.get_printer()
- self.hd44780_protocol_init = config.getboolean('hd44780_protocol_init',
- True)
+ self.hd44780_protocol_init = config.getboolean("hd44780_protocol_init", True)
# spi config
- self.spi = bus.MCU_SPI_from_config(
- config, 0x00, pin_option="latch_pin")
+ self.spi = bus.MCU_SPI_from_config(config, 0x00, pin_option="latch_pin")
self.mcu = self.spi.get_mcu()
- #self.spi.spi_send([0x01,0xa0])
- self.data_mask = (1<<1)
+ # self.spi.spi_send([0x01,0xa0])
+ self.data_mask = 1 << 1
self.command_mask = 0
- self.enable_mask = (1<<3)
+ self.enable_mask = 1 << 3
self.icons = {}
- self.line_length = config.getchoice('line_length', LINE_LENGTH_OPTIONS,
- LINE_LENGTH_DEFAULT)
+ self.line_length = config.getchoice(
+ "line_length", LINE_LENGTH_OPTIONS, LINE_LENGTH_DEFAULT
+ )
# framebuffers
- self.text_framebuffers = [bytearray(b' '*2*self.line_length),
- bytearray(b' '*2*self.line_length)]
+ self.text_framebuffers = [
+ bytearray(b" " * 2 * self.line_length),
+ bytearray(b" " * 2 * self.line_length),
+ ]
self.glyph_framebuffer = bytearray(64)
self.all_framebuffers = [
# Text framebuffers
- (self.text_framebuffers[0], bytearray(b'~'*2*self.line_length),
- 0x80),
- (self.text_framebuffers[1], bytearray(b'~'*2*self.line_length),
- 0xc0),
+ (self.text_framebuffers[0], bytearray(b"~" * 2 * self.line_length), 0x80),
+ (self.text_framebuffers[1], bytearray(b"~" * 2 * self.line_length), 0xC0),
# Glyph framebuffer
- (self.glyph_framebuffer, bytearray(b'~'*64), 0x40) ]
+ (self.glyph_framebuffer, bytearray(b"~" * 64), 0x40),
+ ]
+
def send_4_bits(self, cmd, is_data, minclock):
if is_data:
mask = self.data_mask
@@ -53,31 +53,35 @@ class hd44780_spi:
self.spi.spi_send([(cmd & 0xF0) | mask], minclock)
self.spi.spi_send([(cmd & 0xF0) | mask | self.enable_mask], minclock)
self.spi.spi_send([(cmd & 0xF0) | mask], minclock)
+
def send(self, cmds, is_data=False, minclock=0):
for data in cmds:
- self.send_4_bits(data,is_data,minclock)
- self.send_4_bits(data<<4,is_data,minclock)
+ self.send_4_bits(data, is_data, minclock)
+ self.send_4_bits(data << 4, is_data, minclock)
+
def flush(self):
# Find all differences in the framebuffers and send them to the chip
for new_data, old_data, fb_id in self.all_framebuffers:
if new_data == old_data:
continue
# Find the position of all changed bytes in this framebuffer
- diffs = [[i, 1] for i, (n, o) in enumerate(zip(new_data, old_data))
- if n != o]
+ diffs = [
+ [i, 1] for i, (n, o) in enumerate(zip(new_data, old_data)) if n != o
+ ]
# Batch together changes that are close to each other
- for i in range(len(diffs)-2, -1, -1):
+ for i in range(len(diffs) - 2, -1, -1):
pos, count = diffs[i]
- nextpos, nextcount = diffs[i+1]
+ nextpos, nextcount = diffs[i + 1]
if pos + 4 >= nextpos and nextcount < 16:
diffs[i][1] = nextcount + (nextpos - pos)
- del diffs[i+1]
+ del diffs[i + 1]
# Transmit changes
for pos, count in diffs:
chip_pos = pos
self.send([fb_id + chip_pos])
- self.send(new_data[pos:pos+count], is_data=True)
+ self.send(new_data[pos : pos + count], is_data=True)
old_data[:] = new_data
+
def init(self):
curtime = self.printer.get_reactor().monotonic()
print_time = self.mcu.estimated_print_time(curtime)
@@ -87,27 +91,30 @@ class hd44780_spi:
else:
init = [[0x02]]
# Reset (set positive direction ; enable display and hide cursor)
- init.append([0x06, 0x0c])
+ init.append([0x06, 0x0C])
for i, cmds in enumerate(init):
- minclock = self.mcu.print_time_to_clock(print_time + i * .100)
+ minclock = self.mcu.print_time_to_clock(print_time + i * 0.100)
self.send(cmds, minclock=minclock)
self.flush()
+
def write_text(self, x, y, data):
if x + len(data) > self.line_length:
- data = data[:self.line_length - min(x, self.line_length)]
+ data = data[: self.line_length - min(x, self.line_length)]
pos = x + ((y & 0x02) >> 1) * self.line_length
- self.text_framebuffers[y & 1][pos:pos+len(data)] = data
+ self.text_framebuffers[y & 1][pos : pos + len(data)] = data
+
def set_glyphs(self, glyphs):
for glyph_name, glyph_data in glyphs.items():
- data = glyph_data.get('icon5x8')
+ data = glyph_data.get("icon5x8")
if data is not None:
self.icons[glyph_name] = data
+
def write_glyph(self, x, y, glyph_name):
data = self.icons.get(glyph_name)
if data is not None:
slot, bits = data
self.write_text(x, y, [slot])
- self.glyph_framebuffer[slot * 8:(slot + 1) * 8] = bits
+ self.glyph_framebuffer[slot * 8 : (slot + 1) * 8] = bits
return 1
char = TextGlyphs.get(glyph_name)
if char is not None:
@@ -115,11 +122,14 @@ class hd44780_spi:
self.write_text(x, y, char)
return 1
return 0
+
def write_graphics(self, x, y, data):
pass
+
def clear(self):
- spaces = b' ' * 2*self.line_length
+ spaces = b" " * 2 * self.line_length
self.text_framebuffers[0][:] = spaces
self.text_framebuffers[1][:] = spaces
+
def get_dimensions(self):
return (self.line_length, 4)
diff --git a/klippy/extras/display/menu.py b/klippy/extras/display/menu.py
index e7723a7e..717b6084 100644
--- a/klippy/extras/display/menu.py
+++ b/klippy/extras/display/menu.py
@@ -21,33 +21,33 @@ class error(Exception):
class MenuElement(object):
def __init__(self, manager, config, **kwargs):
if type(self) is MenuElement:
- raise error(
- 'Abstract MenuElement cannot be instantiated directly')
+ raise error("Abstract MenuElement cannot be instantiated directly")
self._manager = manager
- self._cursor = '>'
+ self._cursor = ">"
# set class defaults and attributes from arguments
- self._index = kwargs.get('index', None)
- self._enable = kwargs.get('enable', True)
- self._name = kwargs.get('name', None)
+ self._index = kwargs.get("index", None)
+ self._enable = kwargs.get("enable", True)
+ self._name = kwargs.get("name", None)
self._enable_tpl = self._name_tpl = None
if config is not None:
# overwrite class attributes from config
- self._index = config.getint('index', self._index)
+ self._index = config.getint("index", self._index)
self._name_tpl = manager.gcode_macro.load_template(
- config, 'name', self._name)
+ config, "name", self._name
+ )
try:
- self._enable = config.getboolean('enable', self._enable)
+ self._enable = config.getboolean("enable", self._enable)
except config.error:
- self._enable_tpl = manager.gcode_macro.load_template(
- config, 'enable')
+ self._enable_tpl = manager.gcode_macro.load_template(config, "enable")
# item namespace - used in relative paths
- self._ns = str(" ".join(config.get_name().split(' ')[1:])).strip()
+ self._ns = str(" ".join(config.get_name().split(" ")[1:])).strip()
else:
# ns - item namespace key, used in item relative paths
# $__id - generated id text variable
- __id = '__menu_' + hex(id(self)).lstrip("0x").rstrip("L")
- self._ns = Template(
- 'menu ' + kwargs.get('ns', __id)).safe_substitute(__id=__id)
+ __id = "__menu_" + hex(id(self)).lstrip("0x").rstrip("L")
+ self._ns = Template("menu " + kwargs.get("ns", __id)).safe_substitute(
+ __id=__id
+ )
self._last_heartbeat = None
self.__scroll_pos = None
self.__scroll_request_pending = False
@@ -71,14 +71,15 @@ class MenuElement(object):
"""Load script template from config or callback from dict"""
if name in self._scripts:
logging.info(
- "Declaration of '%s' hides "
- "previous script declaration" % (name,))
+ "Declaration of '%s' hides " "previous script declaration" % (name,)
+ )
option = option or name
if isinstance(config, dict):
self._scripts[name] = config.get(option, None)
else:
self._scripts[name] = self.manager.gcode_macro.load_template(
- config, option, '')
+ config, option, ""
+ )
# override
def is_editing(self):
@@ -105,9 +106,7 @@ class MenuElement(object):
def get_context(self, cxt=None):
# get default menu context
context = self.manager.get_context(cxt)
- context['menu'].update({
- 'ns': self.get_ns()
- })
+ context["menu"].update({"ns": self.get_ns()})
return context
def eval_enable(self, context):
@@ -153,7 +152,7 @@ class MenuElement(object):
def __slice_name(self, name, index):
chunks = []
- for i, text in enumerate(re.split(r'(\~.*?\~)', name)):
+ for i, text in enumerate(re.split(r"(\~.*?\~)", name)):
if i & 1 == 0: # text
chunks += text
else: # glyph placeholder
@@ -168,18 +167,16 @@ class MenuElement(object):
self.__reset_scroller()
return name
- def get_ns(self, name='.'):
+ def get_ns(self, name="."):
name = str(name).strip()
- if name.startswith('..'):
- name = ' '.join(
- [(' '.join(str(self._ns).split(' ')[:-1])), name[2:]])
- elif name.startswith('.'):
- name = ' '.join([str(self._ns), name[1:]])
+ if name.startswith(".."):
+ name = " ".join([(" ".join(str(self._ns).split(" ")[:-1])), name[2:]])
+ elif name.startswith("."):
+ name = " ".join([str(self._ns), name[1:]])
return name.strip()
def send_event(self, event, *args):
- return self.manager.send_event(
- "%s:%s" % (self.get_ns(), str(event)), *args)
+ return self.manager.send_event("%s:%s" % (self.get_ns(), str(event)), *args)
def get_script(self, name):
if name in self._scripts:
@@ -187,7 +184,7 @@ class MenuElement(object):
return None
def _run_script(self, name, context):
- _render = getattr(self._scripts[name], 'render', None)
+ _render = getattr(self._scripts[name], "render", None)
# check template
if _render is not None and callable(_render):
return _render(context)
@@ -199,16 +196,14 @@ class MenuElement(object):
return self._scripts[name]
def run_script(self, name, **kwargs):
- event = kwargs.get('event', None)
- context = kwargs.get('context', None)
- render_only = kwargs.get('render_only', False)
+ event = kwargs.get("event", None)
+ context = kwargs.get("context", None)
+ render_only = kwargs.get("render_only", False)
result = ""
# init context
if name in self._scripts:
context = self.get_context(context)
- context['menu'].update({
- 'event': event or name
- })
+ context["menu"].update({"event": event or name})
result = self._run_script(name, context)
if not render_only:
# run result as gcode
@@ -234,13 +229,13 @@ class MenuElement(object):
class MenuContainer(MenuElement):
"""Menu container abstract class"""
+
def __init__(self, manager, config, **kwargs):
if type(self) is MenuContainer:
- raise error(
- 'Abstract MenuContainer cannot be instantiated directly')
+ raise error("Abstract MenuContainer cannot be instantiated directly")
super(MenuContainer, self).__init__(manager, config, **kwargs)
- self._populate_cb = kwargs.get('populate', None)
- self._cursor = '>'
+ self._populate_cb = kwargs.get("populate", None)
+ self._cursor = ">"
self.__selected = None
self._allitems = []
self._names = []
@@ -307,8 +302,9 @@ class MenuContainer(MenuElement):
self._parents.append(parents)
def assert_recursive_relation(self, parents=None):
- assert self not in (parents or self._parents), \
- "Recursive relation of '%s' container" % (self.get_ns(),)
+ assert self not in (
+ parents or self._parents
+ ), "Recursive relation of '%s' container" % (self.get_ns(),)
def insert_item(self, s, index=None):
self._insert_item(s, index)
@@ -343,11 +339,10 @@ class MenuContainer(MenuElement):
if self._populate_cb is not None and callable(self._populate_cb):
self._populate_cb(self)
# send populate event
- self.send_event('populate', self)
+ self.send_event("populate", self)
def update_items(self):
- _a = [(item, name) for item, name in self._allitems
- if item.is_enabled()]
+ _a = [(item, name) for item, name in self._allitems if item.is_enabled()]
self._items, self._names = zip(*_a) or ([], [])
# select methods
@@ -416,7 +411,7 @@ class MenuContainer(MenuElement):
class MenuDisabled(MenuElement):
def __init__(self, manager, config, **kwargs):
- super(MenuDisabled, self).__init__(manager, config, name='')
+ super(MenuDisabled, self).__init__(manager, config, name="")
def is_enabled(self):
return False
@@ -425,30 +420,32 @@ class MenuDisabled(MenuElement):
class MenuCommand(MenuElement):
def __init__(self, manager, config, **kwargs):
super(MenuCommand, self).__init__(manager, config, **kwargs)
- self._load_script(config or kwargs, 'gcode')
+ self._load_script(config or kwargs, "gcode")
class MenuInput(MenuCommand):
def __init__(self, manager, config, **kwargs):
super(MenuInput, self).__init__(manager, config, **kwargs)
# set class defaults and attributes from arguments
- self._input = kwargs.get('input', None)
- self._input_min = kwargs.get('input_min', -999999.0)
- self._input_max = kwargs.get('input_max', 999999.0)
- self._input_step = kwargs.get('input_step', 1.0)
- self._realtime = kwargs.get('realtime', False)
+ self._input = kwargs.get("input", None)
+ self._input_min = kwargs.get("input_min", -999999.0)
+ self._input_max = kwargs.get("input_max", 999999.0)
+ self._input_step = kwargs.get("input_step", 1.0)
+ self._realtime = kwargs.get("realtime", False)
self._input_tpl = self._input_min_tpl = self._input_max_tpl = None
if config is not None:
# overwrite class attributes from config
- self._realtime = config.getboolean('realtime', self._realtime)
- self._input_tpl = manager.gcode_macro.load_template(
- config, 'input')
+ self._realtime = config.getboolean("realtime", self._realtime)
+ self._input_tpl = manager.gcode_macro.load_template(config, "input")
self._input_min_tpl = manager.gcode_macro.load_template(
- config, 'input_min', str(self._input_min))
+ config, "input_min", str(self._input_min)
+ )
self._input_max_tpl = manager.gcode_macro.load_template(
- config, 'input_max', str(self._input_max))
+ config, "input_max", str(self._input_max)
+ )
self._input_step = config.getfloat(
- 'input_step', self._input_step, above=0.)
+ "input_step", self._input_step, above=0.0
+ )
def init(self):
super(MenuInput, self).init()
@@ -474,22 +471,25 @@ class MenuInput(MenuCommand):
def heartbeat(self, eventtime):
super(MenuInput, self).heartbeat(eventtime)
- if (self._is_dirty is True
- and self.__last_change is not None
- and self._input_value is not None
- and (eventtime - self.__last_change) > 0.250):
+ if (
+ self._is_dirty is True
+ and self.__last_change is not None
+ and self._input_value is not None
+ and (eventtime - self.__last_change) > 0.250
+ ):
if self._realtime is True:
- self.run_script('gcode', event='change')
- self.run_script('change')
+ self.run_script("gcode", event="change")
+ self.run_script("change")
self._is_dirty = False
def get_context(self, cxt=None):
context = super(MenuInput, self).get_context(cxt)
- value = (self._eval_value(context) if self._input_value is None
- else self._input_value)
- context['menu'].update({
- 'input': value
- })
+ value = (
+ self._eval_value(context)
+ if self._input_value is None
+ else self._input_value
+ )
+ context["menu"].update({"input": value})
return context
def is_enabled(self):
@@ -499,8 +499,7 @@ class MenuInput(MenuCommand):
def _eval_min(self, context):
try:
if self._input_min_tpl is not None:
- return float(ast.literal_eval(
- self._input_min_tpl.render(context)))
+ return float(ast.literal_eval(self._input_min_tpl.render(context)))
return float(self._input_min)
except ValueError:
logging.exception("Input min value evaluation error")
@@ -508,8 +507,7 @@ class MenuInput(MenuCommand):
def _eval_max(self, context):
try:
if self._input_max_tpl is not None:
- return float(ast.literal_eval(
- self._input_max_tpl.render(context)))
+ return float(ast.literal_eval(self._input_max_tpl.render(context)))
return float(self._input_max)
except ValueError:
logging.exception("Input max value evaluation error")
@@ -517,8 +515,7 @@ class MenuInput(MenuCommand):
def _eval_value(self, context):
try:
if self._input_tpl is not None:
- return float(ast.literal_eval(
- self._input_tpl.render(context)))
+ return float(ast.literal_eval(self._input_tpl.render(context)))
return float(self._input)
except ValueError:
logging.exception("Input value evaluation error")
@@ -532,17 +529,21 @@ class MenuInput(MenuCommand):
self._input_value = None
self._input_min = self._eval_min(context)
self._input_max = self._eval_max(context)
- self._input_value = min(self._input_max, max(
- self._input_min, self._eval_value(context)))
+ self._input_value = min(
+ self._input_max, max(self._input_min, self._eval_value(context))
+ )
self._value_changed()
def _reset_value(self):
self._input_value = None
def _get_input_step(self, fast_rate=False):
- return ((10.0 * self._input_step) if fast_rate and (
- (self._input_max - self._input_min) / self._input_step > 100.0)
- else self._input_step)
+ return (
+ (10.0 * self._input_step)
+ if fast_rate
+ and ((self._input_max - self._input_min) / self._input_step > 100.0)
+ else self._input_step
+ )
def inc_value(self, fast_rate=False):
last_value = self._input_value
@@ -551,8 +552,9 @@ class MenuInput(MenuCommand):
input_step = self._get_input_step(fast_rate)
self._input_value += abs(input_step)
- self._input_value = min(self._input_max, max(
- self._input_min, self._input_value))
+ self._input_value = min(
+ self._input_max, max(self._input_min, self._input_value)
+ )
if last_value != self._input_value:
self._value_changed()
@@ -564,8 +566,9 @@ class MenuInput(MenuCommand):
input_step = self._get_input_step(fast_rate)
self._input_value -= abs(input_step)
- self._input_value = min(self._input_max, max(
- self._input_min, self._input_value))
+ self._input_value = min(
+ self._input_max, max(self._input_min, self._input_value)
+ )
if last_value != self._input_value:
self._value_changed()
@@ -585,9 +588,9 @@ class MenuList(MenuContainer):
def _cb(el, context):
el.manager.back()
+
# create back item
- self._itemBack = self.manager.menuitem_from(
- 'command', name='..', gcode=_cb)
+ self._itemBack = self.manager.menuitem_from("command", name="..", gcode=_cb)
def _names_aslist(self):
return self.manager.lookup_children(self.get_ns())
@@ -619,7 +622,7 @@ class MenuList(MenuContainer):
suffix = ""
if row < len(self):
current = self[row]
- selected = (row == selected_row)
+ selected = row == selected_row
if selected:
current.heartbeat(eventtime)
text = current.render_name(selected)
@@ -627,12 +630,12 @@ class MenuList(MenuContainer):
if selected and not current.is_editing():
prefix = current.cursor
elif selected and current.is_editing():
- prefix = '*'
+ prefix = "*"
else:
- prefix = ' '
+ prefix = " "
# add suffix (folder indicator)
if isinstance(current, MenuList):
- suffix += '>'
+ suffix += ">"
# draw to display
plen = len(prefix)
slen = len(suffix)
@@ -642,8 +645,7 @@ class MenuList(MenuContainer):
# draw item name
tpos = display.draw_text(y, ppos, text.ljust(width), eventtime)
# check scroller
- if (selected and tpos > self.manager.cols
- and current.is_scrollable()):
+ if selected and tpos > self.manager.cols and current.is_scrollable():
# scroll next
current.need_scroller(True)
else:
@@ -651,12 +653,11 @@ class MenuList(MenuContainer):
current.need_scroller(None)
# draw item suffix
if suffix:
- display.draw_text(
- y, self.manager.cols - slen, suffix, eventtime)
+ display.draw_text(y, self.manager.cols - slen, suffix, eventtime)
# next display row
y += 1
except Exception:
- logging.exception('List drawing error')
+ logging.exception("List drawing error")
class MenuVSDList(MenuList):
@@ -665,20 +666,23 @@ class MenuVSDList(MenuList):
def _populate(self):
super(MenuVSDList, self)._populate()
- sdcard = self.manager.printer.lookup_object('virtual_sdcard', None)
+ sdcard = self.manager.printer.lookup_object("virtual_sdcard", None)
if sdcard is not None:
files = sdcard.get_file_list()
for fname, fsize in files:
- self.insert_item(self.manager.menuitem_from(
- 'command', name=repr(fname), gcode='M23 /%s' % str(fname)))
+ self.insert_item(
+ self.manager.menuitem_from(
+ "command", name=repr(fname), gcode="M23 /%s" % str(fname)
+ )
+ )
menu_items = {
- 'disabled': MenuDisabled,
- 'command': MenuCommand,
- 'input': MenuInput,
- 'list': MenuList,
- 'vsdlist': MenuVSDList
+ "disabled": MenuDisabled,
+ "command": MenuCommand,
+ "input": MenuInput,
+ "list": MenuList,
+ "vsdlist": MenuVSDList,
}
@@ -693,33 +697,32 @@ class MenuManager:
self.children = {}
self.display = display
self.printer = config.get_printer()
- self.pconfig = self.printer.lookup_object('configfile')
- self.gcode = self.printer.lookup_object('gcode')
+ self.pconfig = self.printer.lookup_object("configfile")
+ self.gcode = self.printer.lookup_object("gcode")
self.gcode_queue = []
self.context = {}
self.root = None
- self._root = config.get('menu_root', '__main')
+ self._root = config.get("menu_root", "__main")
self.cols, self.rows = self.display.get_dimensions()
- self.timeout = config.getint('menu_timeout', 0)
+ self.timeout = config.getint("menu_timeout", 0)
self.timer = 0
# reverse container navigation
- self._reverse_navigation = config.getboolean(
- 'menu_reverse_navigation', False)
+ self._reverse_navigation = config.getboolean("menu_reverse_navigation", False)
# load printer objects
- self.gcode_macro = self.printer.load_object(config, 'gcode_macro')
+ self.gcode_macro = self.printer.load_object(config, "gcode_macro")
# register itself for printer callbacks
- self.printer.add_object('menu', self)
+ self.printer.add_object("menu", self)
self.printer.register_event_handler("klippy:ready", self.handle_ready)
# register for key events
menu_keys.MenuKeys(config, self.key_event)
# Load local config file in same directory as current module
- self.load_config(os.path.dirname(__file__), 'menu.cfg')
+ self.load_config(os.path.dirname(__file__), "menu.cfg")
# Load items from main config
self.load_menuitems(config)
# Load menu root
self.root = self.lookup_menuitem(self._root)
# send init event
- self.send_event('init', self)
+ self.send_event("init", self)
def handle_ready(self):
# start timer
@@ -731,8 +734,11 @@ class MenuManager:
return eventtime + TIMER_DELAY
def timeout_check(self, eventtime):
- if (self.is_running() and self.timeout > 0
- and isinstance(self.root, MenuContainer)):
+ if (
+ self.is_running()
+ and self.timeout > 0
+ and isinstance(self.root, MenuContainer)
+ ):
if self.timer >= self.timeout:
self.exit()
else:
@@ -751,7 +757,7 @@ class MenuManager:
self.timer = 0
if isinstance(self.root, MenuContainer):
# send begin event
- self.send_event('begin', self)
+ self.send_event("begin", self)
self.update_context(eventtime)
if isinstance(self.root, MenuContainer):
self.root.init_selection()
@@ -764,10 +770,10 @@ class MenuManager:
def get_status(self, eventtime):
return {
- 'timeout': self.timeout,
- 'running': self.running,
- 'rows': self.rows,
- 'cols': self.cols
+ "timeout": self.timeout,
+ "running": self.running,
+ "rows": self.rows,
+ "cols": self.cols,
}
def _action_back(self, force=False, update=True):
@@ -787,10 +793,10 @@ class MenuManager:
def update_context(self, eventtime):
# menu default jinja2 context
self.context = self.gcode_macro.create_template_context(eventtime)
- self.context['menu'] = {
- 'eventtime': eventtime,
- 'back': self._action_back,
- 'exit': self._action_exit
+ self.context["menu"] = {
+ "eventtime": eventtime,
+ "back": self._action_back,
+ "exit": self._action_exit,
}
def stack_push(self, container):
@@ -800,9 +806,9 @@ class MenuManager:
top = self.stack_peek()
if top is not None:
if isinstance(top, MenuList):
- top.run_script('leave')
+ top.run_script("leave")
if isinstance(container, MenuList):
- container.run_script('enter')
+ container.run_script("enter")
if not container.is_editing():
container.update_items()
container.init_selection()
@@ -822,12 +828,12 @@ class MenuManager:
top.update_items()
top.init_selection()
if isinstance(container, MenuList):
- container.run_script('leave')
+ container.run_script("leave")
if isinstance(top, MenuList):
- top.run_script('enter')
+ top.run_script("enter")
else:
if isinstance(container, MenuList):
- container.run_script('leave')
+ container.run_script("leave")
return container
def stack_size(self):
@@ -905,25 +911,26 @@ class MenuManager:
if self.running and isinstance(container, MenuContainer):
self.timer = 0
current = container.selected_item()
- if (not force and isinstance(current, MenuInput)
- and current.is_editing()):
+ if not force and isinstance(current, MenuInput) and current.is_editing():
return
if isinstance(container, MenuList):
- container.run_script('leave')
- self.send_event('exit', self)
+ container.run_script("leave")
+ self.send_event("exit", self)
self.running = False
def push_container(self, menu):
container = self.stack_peek()
if self.running and isinstance(container, MenuContainer):
- if (isinstance(menu, MenuContainer)
- and not container.is_editing()
- and menu is not container):
+ if (
+ isinstance(menu, MenuContainer)
+ and not container.is_editing()
+ and menu is not container
+ ):
self.stack_push(menu)
return True
return False
- def press(self, event='click'):
+ def press(self, event="click"):
container = self.stack_peek()
if self.running and isinstance(container, MenuContainer):
self.timer = 0
@@ -932,10 +939,10 @@ class MenuManager:
self.stack_push(current)
elif isinstance(current, MenuInput):
if current.is_editing():
- current.run_script('gcode', event=event)
+ current.run_script("gcode", event=event)
current.run_script(event)
elif isinstance(current, MenuCommand):
- current.run_script('gcode', event=event)
+ current.run_script("gcode", event=event)
current.run_script(event)
else:
# current is None, no selection. passthru to container
@@ -960,8 +967,10 @@ class MenuManager:
def menuitem_from(self, type, **kwargs):
if type not in menu_items:
- raise error("Choice '%s' for option '%s'"
- " is not a valid choice" % (type, menu_items))
+ raise error(
+ "Choice '%s' for option '%s'"
+ " is not a valid choice" % (type, menu_items)
+ )
return menu_items[type](self, None, **kwargs)
def add_menuitem(self, name, item):
@@ -969,18 +978,18 @@ class MenuManager:
if name in self.menuitems:
existing_item = True
logging.info(
- "Declaration of '%s' hides "
- "previous menuitem declaration" % (name,))
+ "Declaration of '%s' hides " "previous menuitem declaration" % (name,)
+ )
self.menuitems[name] = item
if isinstance(item, MenuElement):
- parent = item.get_ns('..')
+ parent = item.get_ns("..")
if parent and not existing_item:
if item.index is not None:
self.children.setdefault(parent, []).insert(
- item.index, item.get_ns())
+ item.index, item.get_ns()
+ )
else:
- self.children.setdefault(parent, []).append(
- item.get_ns())
+ self.children.setdefault(parent, []).append(item.get_ns())
def lookup_menuitem(self, name, default=sentinel):
if name is None:
@@ -988,8 +997,7 @@ class MenuManager:
if name in self.menuitems:
return self.menuitems[name]
if default is sentinel:
- raise self.printer.config_error(
- "Unknown menuitem '%s'" % (name,))
+ raise self.printer.config_error("Unknown menuitem '%s'" % (name,))
return default
def lookup_children(self, ns):
@@ -1003,18 +1011,19 @@ class MenuManager:
try:
cfg = self.pconfig.read_config(filename)
except Exception:
- raise self.printer.config_error(
- "Cannot load config '%s'" % (filename,))
+ raise self.printer.config_error("Cannot load config '%s'" % (filename,))
if cfg:
self.load_menuitems(cfg)
return cfg
def load_menuitems(self, config):
- for cfg in config.get_prefix_sections('menu '):
- type = cfg.get('type')
+ for cfg in config.get_prefix_sections("menu "):
+ type = cfg.get("type")
if type not in menu_items:
- raise error("Choice '%s' for option '%s'"
- " is not a valid choice" % (type, menu_items))
+ raise error(
+ "Choice '%s' for option '%s'"
+ " is not a valid choice" % (type, menu_items)
+ )
item = menu_items[type](self, cfg)
self.add_menuitem(item.get_ns(), item)
@@ -1026,19 +1035,19 @@ class MenuManager:
self.begin(eventtime)
def key_event(self, key, eventtime):
- if key == 'click':
+ if key == "click":
self._click_callback(eventtime, key)
- elif key == 'long_click':
+ elif key == "long_click":
self._click_callback(eventtime, key)
- elif key == 'up':
+ elif key == "up":
self.up(False)
- elif key == 'fast_up':
+ elif key == "fast_up":
self.up(True)
- elif key == 'down':
+ elif key == "down":
self.down(False)
- elif key == 'fast_down':
+ elif key == "fast_down":
self.down(True)
- elif key == 'back':
+ elif key == "back":
self.back()
self.display.request_redraw()
@@ -1048,8 +1057,9 @@ class MenuManager:
def stripliterals(cls, s):
"""Literals are beginning or ending by the double or single quotes"""
s = str(s)
- if (s.startswith('"') and s.endswith('"')) or \
- (s.startswith("'") and s.endswith("'")):
+ if (s.startswith('"') and s.endswith('"')) or (
+ s.startswith("'") and s.endswith("'")
+ ):
s = s[1:-1]
return s
@@ -1058,10 +1068,10 @@ class MenuManager:
if isinstance(s, str):
return s
elif isinstance(s, unicode):
- return unicode(s).encode('latin-1', 'ignore')
+ return unicode(s).encode("latin-1", "ignore")
else:
return str(s)
@classmethod
def asflat(cls, s):
- return cls.stripliterals(''.join(cls.aslatin(s).splitlines()))
+ return cls.stripliterals("".join(cls.aslatin(s).splitlines()))
diff --git a/klippy/extras/display/menu_keys.py b/klippy/extras/display/menu_keys.py
index 8094c996..5da9b05c 100644
--- a/klippy/extras/display/menu_keys.py
+++ b/klippy/extras/display/menu_keys.py
@@ -7,7 +7,8 @@
# This file may be distributed under the terms of the GNU GPLv3 license.
LONG_PRESS_DURATION = 0.800
-TIMER_DELAY = .200
+TIMER_DELAY = 0.200
+
class MenuKeys:
def __init__(self, config, callback):
@@ -16,45 +17,48 @@ class MenuKeys:
self.callback = callback
buttons = self.printer.load_object(config, "buttons")
# Register rotary encoder
- encoder_pins = config.get('encoder_pins', None)
- encoder_steps_per_detent = config.getchoice('encoder_steps_per_detent',
- [2, 4], 4)
+ encoder_pins = config.get("encoder_pins", None)
+ encoder_steps_per_detent = config.getchoice(
+ "encoder_steps_per_detent", [2, 4], 4
+ )
if encoder_pins is not None:
try:
- pin1, pin2 = encoder_pins.split(',')
+ pin1, pin2 = encoder_pins.split(",")
except:
raise config.error("Unable to parse encoder_pins")
- buttons.register_rotary_encoder(pin1.strip(), pin2.strip(),
- self.encoder_cw_callback,
- self.encoder_ccw_callback,
- encoder_steps_per_detent)
- self.encoder_fast_rate = config.getfloat('encoder_fast_rate',
- .030, above=0.)
+ buttons.register_rotary_encoder(
+ pin1.strip(),
+ pin2.strip(),
+ self.encoder_cw_callback,
+ self.encoder_ccw_callback,
+ encoder_steps_per_detent,
+ )
+ self.encoder_fast_rate = config.getfloat("encoder_fast_rate", 0.030, above=0.0)
self.last_encoder_cw_eventtime = 0
self.last_encoder_ccw_eventtime = 0
# Register click button
self.is_short_click = False
self.click_timer = self.reactor.register_timer(self.long_click_event)
- self.register_button(config, 'click_pin', self.click_callback, False)
+ self.register_button(config, "click_pin", self.click_callback, False)
# Register other buttons
- self.register_button(config, 'back_pin', self.back_callback)
- self.register_button(config, 'up_pin', self.up_callback)
- self.register_button(config, 'down_pin', self.down_callback)
- self.register_button(config, 'kill_pin', self.kill_callback)
+ self.register_button(config, "back_pin", self.back_callback)
+ self.register_button(config, "up_pin", self.up_callback)
+ self.register_button(config, "down_pin", self.down_callback)
+ self.register_button(config, "kill_pin", self.kill_callback)
def register_button(self, config, name, callback, push_only=True):
pin = config.get(name, None)
if pin is None:
return
buttons = self.printer.lookup_object("buttons")
- if config.get('analog_range_' + name, None) is None:
+ if config.get("analog_range_" + name, None) is None:
if push_only:
buttons.register_button_push(pin, callback)
else:
buttons.register_buttons([pin], callback)
return
- amin, amax = config.getfloatlist('analog_range_' + name, count=2)
- pullup = config.getfloat('analog_pullup_resistor', 4700., above=0.)
+ amin, amax = config.getfloatlist("analog_range_" + name, count=2)
+ pullup = config.getfloat("analog_pullup_resistor", 4700.0, above=0.0)
if push_only:
buttons.register_adc_button_push(pin, amin, amax, pullup, callback)
else:
@@ -62,47 +66,48 @@ class MenuKeys:
# Rotary encoder callbacks
def encoder_cw_callback(self, eventtime):
- fast_rate = ((eventtime - self.last_encoder_cw_eventtime)
- <= self.encoder_fast_rate)
+ fast_rate = (
+ eventtime - self.last_encoder_cw_eventtime
+ ) <= self.encoder_fast_rate
self.last_encoder_cw_eventtime = eventtime
if fast_rate:
- self.callback('fast_up', eventtime)
+ self.callback("fast_up", eventtime)
else:
- self.callback('up', eventtime)
+ self.callback("up", eventtime)
def encoder_ccw_callback(self, eventtime):
- fast_rate = ((eventtime - self.last_encoder_ccw_eventtime)
- <= self.encoder_fast_rate)
+ fast_rate = (
+ eventtime - self.last_encoder_ccw_eventtime
+ ) <= self.encoder_fast_rate
self.last_encoder_ccw_eventtime = eventtime
if fast_rate:
- self.callback('fast_down', eventtime)
+ self.callback("fast_down", eventtime)
else:
- self.callback('down', eventtime)
+ self.callback("down", eventtime)
# Click handling
def long_click_event(self, eventtime):
self.is_short_click = False
- self.callback('long_click', eventtime)
+ self.callback("long_click", eventtime)
return self.reactor.NEVER
def click_callback(self, eventtime, state):
if state:
self.is_short_click = True
- self.reactor.update_timer(self.click_timer,
- eventtime + LONG_PRESS_DURATION)
+ self.reactor.update_timer(self.click_timer, eventtime + LONG_PRESS_DURATION)
elif self.is_short_click:
self.reactor.update_timer(self.click_timer, self.reactor.NEVER)
- self.callback('click', eventtime)
+ self.callback("click", eventtime)
# Other button callbacks
def back_callback(self, eventtime):
- self.callback('back', eventtime)
+ self.callback("back", eventtime)
def up_callback(self, eventtime):
- self.callback('up', eventtime)
+ self.callback("up", eventtime)
def down_callback(self, eventtime):
- self.callback('down', eventtime)
+ self.callback("down", eventtime)
def kill_callback(self, eventtime):
self.printer.invoke_shutdown("Shutdown due to kill button!")
diff --git a/klippy/extras/display/st7920.py b/klippy/extras/display/st7920.py
index 5b2a943c..9411a064 100644
--- a/klippy/extras/display/st7920.py
+++ b/klippy/extras/display/st7920.py
@@ -7,46 +7,50 @@ import logging
from .. import bus
from . import font8x14
-BACKGROUND_PRIORITY_CLOCK = 0x7fffffff00000000
+BACKGROUND_PRIORITY_CLOCK = 0x7FFFFFFF00000000
# Spec says 72us, but faster is possible in practice
-ST7920_CMD_DELAY = .000020
-ST7920_SYNC_DELAY = .000045
+ST7920_CMD_DELAY = 0.000020
+ST7920_SYNC_DELAY = 0.000045
+
+TextGlyphs = {"right_arrow": b"\x1a"}
+CharGlyphs = {"degrees": bytearray(font8x14.VGA_FONT[0xF8])}
-TextGlyphs = { 'right_arrow': b'\x1a' }
-CharGlyphs = { 'degrees': bytearray(font8x14.VGA_FONT[0xf8]) }
class DisplayBase:
def __init__(self):
# framebuffers
- self.text_framebuffer = bytearray(b' '*64)
+ self.text_framebuffer = bytearray(b" " * 64)
self.glyph_framebuffer = bytearray(128)
self.graphics_framebuffers = [bytearray(32) for i in range(32)]
self.all_framebuffers = [
# Text framebuffer
- (self.text_framebuffer, bytearray(b'~'*64), 0x80),
+ (self.text_framebuffer, bytearray(b"~" * 64), 0x80),
# Glyph framebuffer
- (self.glyph_framebuffer, bytearray(b'~'*128), 0x40),
+ (self.glyph_framebuffer, bytearray(b"~" * 128), 0x40),
# Graphics framebuffers
- ] + [(self.graphics_framebuffers[i], bytearray(b'~'*32), i)
- for i in range(32)]
+ ] + [
+ (self.graphics_framebuffers[i], bytearray(b"~" * 32), i) for i in range(32)
+ ]
self.cached_glyphs = {}
self.icons = {}
+
def flush(self):
# Find all differences in the framebuffers and send them to the chip
for new_data, old_data, fb_id in self.all_framebuffers:
if new_data == old_data:
continue
# Find the position of all changed bytes in this framebuffer
- diffs = [[i, 1] for i, (n, o) in enumerate(zip(new_data, old_data))
- if n != o]
+ diffs = [
+ [i, 1] for i, (n, o) in enumerate(zip(new_data, old_data)) if n != o
+ ]
# Batch together changes that are close to each other
- for i in range(len(diffs)-2, -1, -1):
+ for i in range(len(diffs) - 2, -1, -1):
pos, count = diffs[i]
- nextpos, nextcount = diffs[i+1]
+ nextpos, nextcount = diffs[i + 1]
if pos + 5 >= nextpos and nextcount < 16:
diffs[i][1] = nextcount + (nextpos - pos)
- del diffs[i+1]
+ del diffs[i + 1]
# Transmit changes
for pos, count in diffs:
count += pos & 0x01
@@ -58,19 +62,23 @@ class DisplayBase:
self.send([0x80 + fb_id, 0x80 + chip_pos], is_extended=True)
else:
self.send([fb_id + chip_pos])
- self.send(new_data[pos:pos+count], is_data=True)
+ self.send(new_data[pos : pos + count], is_data=True)
old_data[:] = new_data
+
def init(self):
- cmds = [0x24, # Enter extended mode
- 0x40, # Clear vertical scroll address
- 0x02, # Enable CGRAM access
- 0x26, # Enable graphics
- 0x22, # Leave extended mode
- 0x02, # Home the display
- 0x06, # Set positive update direction
- 0x0c] # Enable display and hide cursor
+ cmds = [
+ 0x24, # Enter extended mode
+ 0x40, # Clear vertical scroll address
+ 0x02, # Enable CGRAM access
+ 0x26, # Enable graphics
+ 0x22, # Leave extended mode
+ 0x02, # Home the display
+ 0x06, # Set positive update direction
+ 0x0C,
+ ] # Enable display and hide cursor
self.send(cmds)
self.flush()
+
def cache_glyph(self, glyph_name, base_glyph_name, glyph_id):
icon = self.icons.get(glyph_name)
base_icon = self.icons.get(base_glyph_name)
@@ -79,23 +87,26 @@ class DisplayBase:
all_bits = zip(icon[0], icon[1], base_icon[0], base_icon[1])
for i, (ic1, ic2, b1, b2) in enumerate(all_bits):
x1, x2 = ic1 ^ b1, ic2 ^ b2
- pos = glyph_id*32 + i*2
- self.glyph_framebuffer[pos:pos+2] = [x1, x2]
- self.all_framebuffers[1][1][pos:pos+2] = [x1 ^ 1, x2 ^ 1]
- self.cached_glyphs[glyph_name] = (base_glyph_name, (0, glyph_id*2))
+ pos = glyph_id * 32 + i * 2
+ self.glyph_framebuffer[pos : pos + 2] = [x1, x2]
+ self.all_framebuffers[1][1][pos : pos + 2] = [x1 ^ 1, x2 ^ 1]
+ self.cached_glyphs[glyph_name] = (base_glyph_name, (0, glyph_id * 2))
+
def set_glyphs(self, glyphs):
for glyph_name, glyph_data in glyphs.items():
- icon = glyph_data.get('icon16x16')
+ icon = glyph_data.get("icon16x16")
if icon is not None:
self.icons[glyph_name] = icon
# Setup animated glyphs
- self.cache_glyph('fan2', 'fan1', 0)
- self.cache_glyph('bed_heat2', 'bed_heat1', 1)
+ self.cache_glyph("fan2", "fan1", 0)
+ self.cache_glyph("bed_heat2", "bed_heat1", 1)
+
def write_text(self, x, y, data):
if x + len(data) > 16:
- data = data[:16 - min(x, 16)]
+ data = data[: 16 - min(x, 16)]
pos = [0, 32, 16, 48][y] + x
- self.text_framebuffer[pos:pos+len(data)] = data
+ self.text_framebuffer[pos : pos + len(data)] = data
+
def write_graphics(self, x, y, data):
if x >= 16 or y >= 4 or len(data) != 16:
return
@@ -105,6 +116,7 @@ class DisplayBase:
x += 16
for i, bits in enumerate(data):
self.graphics_framebuffers[gfx_fb + i][x] = bits
+
def write_glyph(self, x, y, glyph_name):
glyph_id = self.cached_glyphs.get(glyph_name)
if glyph_id is not None and x & 1 == 0:
@@ -128,28 +140,33 @@ class DisplayBase:
self.write_graphics(x, y, font)
return 1
return 0
+
def clear(self):
- self.text_framebuffer[:] = b' '*64
+ self.text_framebuffer[:] = b" " * 64
zeros = bytearray(32)
for gfb in self.graphics_framebuffers:
gfb[:] = zeros
+
def get_dimensions(self):
return (16, 4)
+
# Display driver for stock ST7920 displays
class ST7920(DisplayBase):
def __init__(self, config):
printer = config.get_printer()
# pin config
- ppins = printer.lookup_object('pins')
- pins = [ppins.lookup_pin(config.get(name + '_pin'))
- for name in ['cs', 'sclk', 'sid']]
+ ppins = printer.lookup_object("pins")
+ pins = [
+ ppins.lookup_pin(config.get(name + "_pin"))
+ for name in ["cs", "sclk", "sid"]
+ ]
mcu = None
for pin_params in pins:
- if mcu is not None and pin_params['chip'] != mcu:
+ if mcu is not None and pin_params["chip"] != mcu:
raise ppins.error("st7920 all pins must be on same mcu")
- mcu = pin_params['chip']
- self.pins = [pin_params['pin'] for pin_params in pins]
+ mcu = pin_params["chip"]
+ self.pins = [pin_params["pin"] for pin_params in pins]
# prepare send functions
self.mcu = mcu
self.oid = self.mcu.create_oid()
@@ -158,19 +175,29 @@ class ST7920(DisplayBase):
self.is_extended = False
# init display base
DisplayBase.__init__(self)
+
def build_config(self):
# configure send functions
self.mcu.add_config_cmd(
"config_st7920 oid=%u cs_pin=%s sclk_pin=%s sid_pin=%s"
- " sync_delay_ticks=%d cmd_delay_ticks=%d" % (
- self.oid, self.pins[0], self.pins[1], self.pins[2],
+ " sync_delay_ticks=%d cmd_delay_ticks=%d"
+ % (
+ self.oid,
+ self.pins[0],
+ self.pins[1],
+ self.pins[2],
self.mcu.seconds_to_clock(ST7920_SYNC_DELAY),
- self.mcu.seconds_to_clock(ST7920_CMD_DELAY)))
+ self.mcu.seconds_to_clock(ST7920_CMD_DELAY),
+ )
+ )
cmd_queue = self.mcu.alloc_command_queue()
self.send_cmds_cmd = self.mcu.lookup_command(
- "st7920_send_cmds oid=%c cmds=%*s", cq=cmd_queue)
+ "st7920_send_cmds oid=%c cmds=%*s", cq=cmd_queue
+ )
self.send_data_cmd = self.mcu.lookup_command(
- "st7920_send_data oid=%c data=%*s", cq=cmd_queue)
+ "st7920_send_data oid=%c data=%*s", cq=cmd_queue
+ )
+
def send(self, cmds, is_data=False, is_extended=False):
cmd_type = self.send_cmds_cmd
if is_data:
@@ -182,26 +209,30 @@ class ST7920(DisplayBase):
cmds = [add_cmd] + cmds
self.is_extended = is_extended
cmd_type.send([self.oid, cmds], reqclock=BACKGROUND_PRIORITY_CLOCK)
- #logging.debug("st7920 %d %s", is_data, repr(cmds))
+ # logging.debug("st7920 %d %s", is_data, repr(cmds))
+
# Helper code for toggling the en pin on startup
class EnableHelper:
def __init__(self, pin_desc, spi):
- self.en_pin = bus.MCU_bus_digital_out(spi.get_mcu(), pin_desc,
- spi.get_command_queue())
+ self.en_pin = bus.MCU_bus_digital_out(
+ spi.get_mcu(), pin_desc, spi.get_command_queue()
+ )
+
def init(self):
mcu = self.en_pin.get_mcu()
curtime = mcu.get_printer().get_reactor().monotonic()
print_time = mcu.estimated_print_time(curtime)
# Toggle enable pin
- minclock = mcu.print_time_to_clock(print_time + .100)
+ minclock = mcu.print_time_to_clock(print_time + 0.100)
self.en_pin.update_digital_out(0, minclock=minclock)
- minclock = mcu.print_time_to_clock(print_time + .200)
+ minclock = mcu.print_time_to_clock(print_time + 0.200)
self.en_pin.update_digital_out(1, minclock=minclock)
# Force a delay to any subsequent commands on the command queue
- minclock = mcu.print_time_to_clock(print_time + .300)
+ minclock = mcu.print_time_to_clock(print_time + 0.300)
self.en_pin.update_digital_out(1, minclock=minclock)
+
# Display driver for displays that emulate the ST7920 in software.
# These displays rely on the CS pin to be toggled in order to initialize the
# SPI correctly. This display driver uses a software SPI with an unused pin
@@ -209,19 +240,22 @@ class EnableHelper:
class EmulatedST7920(DisplayBase):
def __init__(self, config):
# create software spi
- ppins = config.get_printer().lookup_object('pins')
- sw_pin_names = ['spi_software_%s_pin' % (name,)
- for name in ['miso', 'mosi', 'sclk']]
- sw_pin_params = [ppins.lookup_pin(config.get(name), share_type=name)
- for name in sw_pin_names]
+ ppins = config.get_printer().lookup_object("pins")
+ sw_pin_names = [
+ "spi_software_%s_pin" % (name,) for name in ["miso", "mosi", "sclk"]
+ ]
+ sw_pin_params = [
+ ppins.lookup_pin(config.get(name), share_type=name) for name in sw_pin_names
+ ]
mcu = None
for pin_params in sw_pin_params:
- if mcu is not None and pin_params['chip'] != mcu:
- raise ppins.error("%s: spi pins must be on same mcu" % (
- config.get_name(),))
- mcu = pin_params['chip']
- sw_pins = tuple([pin_params['pin'] for pin_params in sw_pin_params])
- speed = config.getint('spi_speed', 1000000, minval=100000)
+ if mcu is not None and pin_params["chip"] != mcu:
+ raise ppins.error(
+ "%s: spi pins must be on same mcu" % (config.get_name(),)
+ )
+ mcu = pin_params["chip"]
+ sw_pins = tuple([pin_params["pin"] for pin_params in sw_pin_params])
+ speed = config.getint("spi_speed", 1000000, minval=100000)
self.spi = bus.MCU_SPI(mcu, None, None, 0, speed, sw_pins)
# create enable helper
self.en_helper = EnableHelper(config.get("en_pin"), self.spi)
@@ -229,11 +263,12 @@ class EmulatedST7920(DisplayBase):
# init display base
self.is_extended = False
DisplayBase.__init__(self)
+
def send(self, cmds, is_data=False, is_extended=False):
# setup sync byte and check for exten mode switch
- sync_byte = 0xfa
+ sync_byte = 0xFA
if not is_data:
- sync_byte = 0xf8
+ sync_byte = 0xF8
if self.is_extended != is_extended:
add_cmd = 0x22
if is_extended:
@@ -254,4 +289,4 @@ class EmulatedST7920(DisplayBase):
self.en_set = True
# send data
self.spi.spi_send(spi_data, reqclock=BACKGROUND_PRIORITY_CLOCK)
- #logging.debug("st7920 %s", repr(spi_data))
+ # logging.debug("st7920 %s", repr(spi_data))
diff --git a/klippy/extras/display/uc1701.py b/klippy/extras/display/uc1701.py
index 8e877cf2..7d4e4683 100644
--- a/klippy/extras/display/uc1701.py
+++ b/klippy/extras/display/uc1701.py
@@ -8,9 +8,10 @@ import logging
from .. import bus
from . import font8x14
-BACKGROUND_PRIORITY_CLOCK = 0x7fffffff00000000
+BACKGROUND_PRIORITY_CLOCK = 0x7FFFFFFF00000000
+
+TextGlyphs = {"right_arrow": b"\x1a", "degrees": b"\xf8"}
-TextGlyphs = { 'right_arrow': b'\x1a', 'degrees': b'\xf8' }
class DisplayBase:
def __init__(self, io, columns=128, x_offset=0):
@@ -19,37 +20,40 @@ class DisplayBase:
self.columns = columns
self.x_offset = x_offset
self.vram = [bytearray(self.columns) for i in range(8)]
- self.all_framebuffers = [(self.vram[i], bytearray(b'~'*self.columns), i)
- for i in range(8)]
+ self.all_framebuffers = [
+ (self.vram[i], bytearray(b"~" * self.columns), i) for i in range(8)
+ ]
# Cache fonts and icons in display byte order
- self.font = [self._swizzle_bits(bytearray(c))
- for c in font8x14.VGA_FONT]
+ self.font = [self._swizzle_bits(bytearray(c)) for c in font8x14.VGA_FONT]
self.icons = {}
+
def flush(self):
# Find all differences in the framebuffers and send them to the chip
for new_data, old_data, page in self.all_framebuffers:
if new_data == old_data:
continue
# Find the position of all changed bytes in this framebuffer
- diffs = [[i, 1] for i, (n, o) in enumerate(zip(new_data, old_data))
- if n != o]
+ diffs = [
+ [i, 1] for i, (n, o) in enumerate(zip(new_data, old_data)) if n != o
+ ]
# Batch together changes that are close to each other
- for i in range(len(diffs)-2, -1, -1):
+ for i in range(len(diffs) - 2, -1, -1):
pos, count = diffs[i]
- nextpos, nextcount = diffs[i+1]
+ nextpos, nextcount = diffs[i + 1]
if pos + 5 >= nextpos and nextcount < 16:
diffs[i][1] = nextcount + (nextpos - pos)
- del diffs[i+1]
+ del diffs[i + 1]
# Transmit changes
for col_pos, count in diffs:
# Set Position registers
- ra = 0xb0 | (page & 0x0F)
+ ra = 0xB0 | (page & 0x0F)
ca_msb = 0x10 | ((col_pos >> 4) & 0x0F)
ca_lsb = col_pos & 0x0F
self.send([ra, ca_msb, ca_lsb])
# Send Data
- self.send(new_data[col_pos:col_pos+count], is_data=True)
+ self.send(new_data[col_pos : col_pos + count], is_data=True)
old_data[:] = new_data
+
def _swizzle_bits(self, data):
# Convert from "rows of pixels" format to "columns of pixels"
top = bot = 0
@@ -58,28 +62,31 @@ class DisplayBase:
top |= spaced >> (7 - row)
spaced = (data[row + 8] * 0x8040201008040201) & 0x8080808080808080
bot |= spaced >> (7 - row)
- bits_top = [(top >> s) & 0xff for s in range(0, 64, 8)]
- bits_bot = [(bot >> s) & 0xff for s in range(0, 64, 8)]
+ bits_top = [(top >> s) & 0xFF for s in range(0, 64, 8)]
+ bits_bot = [(bot >> s) & 0xFF for s in range(0, 64, 8)]
return (bytearray(bits_top), bytearray(bits_bot))
+
def set_glyphs(self, glyphs):
for glyph_name, glyph_data in glyphs.items():
- icon = glyph_data.get('icon16x16')
+ icon = glyph_data.get("icon16x16")
if icon is not None:
top1, bot1 = self._swizzle_bits(icon[0])
top2, bot2 = self._swizzle_bits(icon[1])
self.icons[glyph_name] = (top1 + top2, bot1 + bot2)
+
def write_text(self, x, y, data):
if x + len(data) > 16:
- data = data[:16 - min(x, 16)]
+ data = data[: 16 - min(x, 16)]
pix_x = x * 8
pix_x += self.x_offset
page_top = self.vram[y * 2]
page_bot = self.vram[y * 2 + 1]
for c in bytearray(data):
bits_top, bits_bot = self.font[c]
- page_top[pix_x:pix_x+8] = bits_top
- page_bot[pix_x:pix_x+8] = bits_bot
+ page_top[pix_x : pix_x + 8] = bits_top
+ page_bot[pix_x : pix_x + 8] = bits_bot
pix_x += 8
+
def write_graphics(self, x, y, data):
if x >= 16 or y >= 4 or len(data) != 16:
return
@@ -91,6 +98,7 @@ class DisplayBase:
for i in range(8):
page_top[pix_x + i] ^= bits_top[i]
page_bot[pix_x + i] ^= bits_bot[i]
+
def write_glyph(self, x, y, glyph_name):
icon = self.icons.get(glyph_name)
if icon is not None and x < 15:
@@ -98,8 +106,8 @@ class DisplayBase:
pix_x = x * 8
pix_x += self.x_offset
page_idx = y * 2
- self.vram[page_idx][pix_x:pix_x+16] = icon[0]
- self.vram[page_idx + 1][pix_x:pix_x+16] = icon[1]
+ self.vram[page_idx][pix_x : pix_x + 16] = icon[0]
+ self.vram[page_idx + 1][pix_x : pix_x + 16] = icon[1]
return 2
char = TextGlyphs.get(glyph_name)
if char is not None:
@@ -107,30 +115,37 @@ class DisplayBase:
self.write_text(x, y, char)
return 1
return 0
+
def clear(self):
zeros = bytearray(self.columns)
for page in self.vram:
page[:] = zeros
+
def get_dimensions(self):
return (16, 4)
+
# IO wrapper for "4 wire" spi bus (spi bus with an extra data/control line)
class SPI4wire:
def __init__(self, config, data_pin_name):
self.spi = bus.MCU_SPI_from_config(config, 0, default_speed=10000000)
dc_pin = config.get(data_pin_name)
- self.mcu_dc = bus.MCU_bus_digital_out(self.spi.get_mcu(), dc_pin,
- self.spi.get_command_queue())
+ self.mcu_dc = bus.MCU_bus_digital_out(
+ self.spi.get_mcu(), dc_pin, self.spi.get_command_queue()
+ )
+
def send(self, cmds, is_data=False):
- self.mcu_dc.update_digital_out(is_data,
- reqclock=BACKGROUND_PRIORITY_CLOCK)
+ self.mcu_dc.update_digital_out(is_data, reqclock=BACKGROUND_PRIORITY_CLOCK)
self.spi.spi_send(cmds, reqclock=BACKGROUND_PRIORITY_CLOCK)
+
# IO wrapper for i2c bus
class I2C:
def __init__(self, config, default_addr):
- self.i2c = bus.MCU_I2C_from_config(config, default_addr=default_addr,
- default_speed=400000)
+ self.i2c = bus.MCU_I2C_from_config(
+ config, default_addr=default_addr, default_speed=400000
+ )
+
def send(self, cmds, is_data=False):
if is_data:
hdr = 0x40
@@ -140,14 +155,17 @@ class I2C:
cmds.insert(0, hdr)
self.i2c.i2c_write(cmds, reqclock=BACKGROUND_PRIORITY_CLOCK)
+
# Helper code for toggling a reset pin on startup
class ResetHelper:
def __init__(self, pin_desc, io_bus):
self.mcu_reset = None
if pin_desc is None:
return
- self.mcu_reset = bus.MCU_bus_digital_out(io_bus.get_mcu(), pin_desc,
- io_bus.get_command_queue())
+ self.mcu_reset = bus.MCU_bus_digital_out(
+ io_bus.get_mcu(), pin_desc, io_bus.get_command_queue()
+ )
+
def init(self):
if self.mcu_reset is None:
return
@@ -155,45 +173,50 @@ class ResetHelper:
curtime = mcu.get_printer().get_reactor().monotonic()
print_time = mcu.estimated_print_time(curtime)
# Toggle reset
- minclock = mcu.print_time_to_clock(print_time + .100)
+ minclock = mcu.print_time_to_clock(print_time + 0.100)
self.mcu_reset.update_digital_out(0, minclock=minclock)
- minclock = mcu.print_time_to_clock(print_time + .200)
+ minclock = mcu.print_time_to_clock(print_time + 0.200)
self.mcu_reset.update_digital_out(1, minclock=minclock)
# Force a delay to any subsequent commands on the command queue
- minclock = mcu.print_time_to_clock(print_time + .300)
+ minclock = mcu.print_time_to_clock(print_time + 0.300)
self.mcu_reset.update_digital_out(1, minclock=minclock)
+
# The UC1701 is a "4-wire" SPI display device
class UC1701(DisplayBase):
def __init__(self, config):
io = SPI4wire(config, "a0_pin")
DisplayBase.__init__(self, io)
- self.contrast = config.getint('contrast', 40, minval=0, maxval=63)
+ self.contrast = config.getint("contrast", 40, minval=0, maxval=63)
self.reset = ResetHelper(config.get("rst_pin", None), io.spi)
+
def init(self):
self.reset.init()
- init_cmds = [0xE2, # System reset
- 0x40, # Set display to start at line 0
- 0xA0, # Set SEG direction
- 0xC8, # Set COM Direction
- 0xA2, # Set Bias = 1/9
- 0x2C, # Boost ON
- 0x2E, # Voltage regulator on
- 0x2F, # Voltage follower on
- 0xF8, # Set booster ratio
- 0x00, # Booster ratio value (4x)
- 0x23, # Set resistor ratio (3)
- 0x81, # Set Electronic Volume
- self.contrast, # Electronic Volume value
- 0xAC, # Set static indicator off
- 0x00, # NOP
- 0xA6, # Disable Inverse
- 0xAF] # Set display enable
+ init_cmds = [
+ 0xE2, # System reset
+ 0x40, # Set display to start at line 0
+ 0xA0, # Set SEG direction
+ 0xC8, # Set COM Direction
+ 0xA2, # Set Bias = 1/9
+ 0x2C, # Boost ON
+ 0x2E, # Voltage regulator on
+ 0x2F, # Voltage follower on
+ 0xF8, # Set booster ratio
+ 0x00, # Booster ratio value (4x)
+ 0x23, # Set resistor ratio (3)
+ 0x81, # Set Electronic Volume
+ self.contrast, # Electronic Volume value
+ 0xAC, # Set static indicator off
+ 0x00, # NOP
+ 0xA6, # Disable Inverse
+ 0xAF,
+ ] # Set display enable
self.send(init_cmds)
- self.send([0xA5]) # display all
- self.send([0xA4]) # normal display
+ self.send([0xA5]) # display all
+ self.send([0xA4]) # normal display
self.flush()
+
# The SSD1306 supports both i2c and "4-wire" spi
class SSD1306(DisplayBase):
def __init__(self, config, columns=128, x_offset=0):
@@ -206,35 +229,46 @@ class SSD1306(DisplayBase):
io_bus = io.spi
self.reset = ResetHelper(config.get("reset_pin", None), io_bus)
DisplayBase.__init__(self, io, columns, x_offset)
- self.contrast = config.getint('contrast', 239, minval=0, maxval=255)
- self.vcomh = config.getint('vcomh', 0, minval=0, maxval=63)
- self.invert = config.getboolean('invert', False)
+ self.contrast = config.getint("contrast", 239, minval=0, maxval=255)
+ self.vcomh = config.getint("vcomh", 0, minval=0, maxval=63)
+ self.invert = config.getboolean("invert", False)
+
def init(self):
self.reset.init()
init_cmds = [
- 0xAE, # Display off
- 0xD5, 0x80, # Set oscillator frequency
- 0xA8, 0x3f, # Set multiplex ratio
- 0xD3, 0x00, # Set display offset
- 0x40, # Set display start line
- 0x8D, 0x14, # Charge pump setting
- 0x20, 0x02, # Set Memory addressing mode
- 0xA1, # Set Segment re-map
- 0xC8, # Set COM output scan direction
- 0xDA, 0x12, # Set COM pins hardware configuration
- 0x81, self.contrast, # Set contrast control
- 0xD9, 0xA1, # Set pre-charge period
- 0xDB, self.vcomh, # Set VCOMH deselect level
- 0x2E, # Deactivate scroll
- 0xA4, # Output ram to display
- 0xA7 if self.invert else 0xA6, # Set normal/invert
- 0xAF, # Display on
+ 0xAE, # Display off
+ 0xD5,
+ 0x80, # Set oscillator frequency
+ 0xA8,
+ 0x3F, # Set multiplex ratio
+ 0xD3,
+ 0x00, # Set display offset
+ 0x40, # Set display start line
+ 0x8D,
+ 0x14, # Charge pump setting
+ 0x20,
+ 0x02, # Set Memory addressing mode
+ 0xA1, # Set Segment re-map
+ 0xC8, # Set COM output scan direction
+ 0xDA,
+ 0x12, # Set COM pins hardware configuration
+ 0x81,
+ self.contrast, # Set contrast control
+ 0xD9,
+ 0xA1, # Set pre-charge period
+ 0xDB,
+ self.vcomh, # Set VCOMH deselect level
+ 0x2E, # Deactivate scroll
+ 0xA4, # Output ram to display
+ 0xA7 if self.invert else 0xA6, # Set normal/invert
+ 0xAF, # Display on
]
self.send(init_cmds)
self.flush()
+
# the SH1106 is SSD1306 compatible with up to 132 columns
class SH1106(SSD1306):
def __init__(self, config):
- x_offset = config.getint('x_offset', 0, minval=0, maxval=3)
+ x_offset = config.getint("x_offset", 0, minval=0, maxval=3)
SSD1306.__init__(self, config, 132, x_offset=x_offset)
diff --git a/klippy/extras/display_status.py b/klippy/extras/display_status.py
index 5b2b6bec..769dcf7a 100644
--- a/klippy/extras/display_status.py
+++ b/klippy/extras/display_status.py
@@ -5,46 +5,55 @@
#
# This file may be distributed under the terms of the GNU GPLv3 license.
-M73_TIMEOUT = 5.
+M73_TIMEOUT = 5.0
+
class DisplayStatus:
def __init__(self, config):
self.printer = config.get_printer()
- self.expire_progress = 0.
+ self.expire_progress = 0.0
self.progress = self.message = None
# Register commands
- gcode = self.printer.lookup_object('gcode')
- gcode.register_command('M73', self.cmd_M73)
- gcode.register_command('M117', self.cmd_M117)
+ gcode = self.printer.lookup_object("gcode")
+ gcode.register_command("M73", self.cmd_M73)
+ gcode.register_command("M117", self.cmd_M117)
gcode.register_command(
- 'SET_DISPLAY_TEXT', self.cmd_SET_DISPLAY_TEXT,
- desc=self.cmd_SET_DISPLAY_TEXT_help)
+ "SET_DISPLAY_TEXT",
+ self.cmd_SET_DISPLAY_TEXT,
+ desc=self.cmd_SET_DISPLAY_TEXT_help,
+ )
+
def get_status(self, eventtime):
progress = self.progress
if progress is not None and eventtime > self.expire_progress:
- idle_timeout = self.printer.lookup_object('idle_timeout')
+ idle_timeout = self.printer.lookup_object("idle_timeout")
idle_timeout_info = idle_timeout.get_status(eventtime)
- if idle_timeout_info['state'] != "Printing":
+ if idle_timeout_info["state"] != "Printing":
self.progress = progress = None
if progress is None:
- progress = 0.
- sdcard = self.printer.lookup_object('virtual_sdcard', None)
+ progress = 0.0
+ sdcard = self.printer.lookup_object("virtual_sdcard", None)
if sdcard is not None:
- progress = sdcard.get_status(eventtime)['progress']
- return { 'progress': progress, 'message': self.message }
+ progress = sdcard.get_status(eventtime)["progress"]
+ return {"progress": progress, "message": self.message}
+
def cmd_M73(self, gcmd):
- progress = gcmd.get_float('P', None)
+ progress = gcmd.get_float("P", None)
if progress is not None:
- progress = progress / 100.
- self.progress = min(1., max(0., progress))
+ progress = progress / 100.0
+ self.progress = min(1.0, max(0.0, progress))
curtime = self.printer.get_reactor().monotonic()
self.expire_progress = curtime + M73_TIMEOUT
+
def cmd_M117(self, gcmd):
msg = gcmd.get_raw_command_parameters() or None
self.message = msg
+
cmd_SET_DISPLAY_TEXT_help = "Set or clear the display message"
+
def cmd_SET_DISPLAY_TEXT(self, gcmd):
self.message = gcmd.get("MSG", None)
+
def load_config(config):
return DisplayStatus(config)
diff --git a/klippy/extras/dotstar.py b/klippy/extras/dotstar.py
index 0262c13d..6270fe0c 100644
--- a/klippy/extras/dotstar.py
+++ b/klippy/extras/dotstar.py
@@ -5,30 +5,35 @@
# This file may be distributed under the terms of the GNU GPLv3 license.
from . import bus, led
-BACKGROUND_PRIORITY_CLOCK = 0x7fffffff00000000
+BACKGROUND_PRIORITY_CLOCK = 0x7FFFFFFF00000000
+
class PrinterDotstar:
def __init__(self, config):
self.printer = printer = config.get_printer()
# Configure a software spi bus
- ppins = printer.lookup_object('pins')
- data_pin_params = ppins.lookup_pin(config.get('data_pin'))
- clock_pin_params = ppins.lookup_pin(config.get('clock_pin'))
- mcu = data_pin_params['chip']
- if mcu is not clock_pin_params['chip']:
+ ppins = printer.lookup_object("pins")
+ data_pin_params = ppins.lookup_pin(config.get("data_pin"))
+ clock_pin_params = ppins.lookup_pin(config.get("clock_pin"))
+ mcu = data_pin_params["chip"]
+ if mcu is not clock_pin_params["chip"]:
raise config.error("Dotstar pins must be on same mcu")
- sw_spi_pins = (data_pin_params['pin'], data_pin_params['pin'],
- clock_pin_params['pin'])
+ sw_spi_pins = (
+ data_pin_params["pin"],
+ data_pin_params["pin"],
+ clock_pin_params["pin"],
+ )
self.spi = bus.MCU_SPI(mcu, None, None, 0, 500000, sw_spi_pins)
# Initialize color data
- self.chain_count = config.getint('chain_count', 1, minval=1)
- self.led_helper = led.LEDHelper(config, self.update_leds,
- self.chain_count)
+ self.chain_count = config.getint("chain_count", 1, minval=1)
+ self.led_helper = led.LEDHelper(config, self.update_leds, self.chain_count)
self.prev_data = None
# Register commands
printer.register_event_handler("klippy:connect", self.handle_connect)
+
def handle_connect(self):
- self.update_leds(self.led_helper.get_status()['color_data'], None)
+ self.update_leds(self.led_helper.get_status()["color_data"], None)
+
def update_leds(self, led_state, print_time):
if led_state == self.prev_data:
return
@@ -37,20 +42,21 @@ class PrinterDotstar:
data = [0] * ((len(led_state) + 2) * 4)
for i, (red, green, blue, white) in enumerate(led_state):
idx = (i + 1) * 4
- data[idx] = 0xff
- data[idx+1] = int(blue * 255. + .5)
- data[idx+2] = int(green * 255. + .5)
- data[idx+3] = int(red * 255. + .5)
- data[-4] = data[-3] = data[-2] = data[-1] = 0xff
+ data[idx] = 0xFF
+ data[idx + 1] = int(blue * 255.0 + 0.5)
+ data[idx + 2] = int(green * 255.0 + 0.5)
+ data[idx + 3] = int(red * 255.0 + 0.5)
+ data[-4] = data[-3] = data[-2] = data[-1] = 0xFF
# Transmit update
minclock = 0
if print_time is not None:
minclock = self.spi.get_mcu().print_time_to_clock(print_time)
- for d in [data[i:i+20] for i in range(0, len(data), 20)]:
- self.spi.spi_send(d, minclock=minclock,
- reqclock=BACKGROUND_PRIORITY_CLOCK)
+ for d in [data[i : i + 20] for i in range(0, len(data), 20)]:
+ self.spi.spi_send(d, minclock=minclock, reqclock=BACKGROUND_PRIORITY_CLOCK)
+
def get_status(self, eventtime):
return self.led_helper.get_status(eventtime)
+
def load_config_prefix(config):
return PrinterDotstar(config)
diff --git a/klippy/extras/ds18b20.py b/klippy/extras/ds18b20.py
index 37b30104..e9c52fde 100644
--- a/klippy/extras/ds18b20.py
+++ b/klippy/extras/ds18b20.py
@@ -12,6 +12,7 @@ DS18_REPORT_TIME = 3.0
DS18_MIN_REPORT_TIME = 1.0
DS18_MAX_CONSECUTIVE_ERRORS = 4
+
class DS18B20:
def __init__(self, config):
self.printer = config.get_printer()
@@ -20,40 +21,47 @@ class DS18B20:
self.temp = self.min_temp = self.max_temp = 0.0
self._report_clock = 0
self.report_time = config.getfloat(
- 'ds18_report_time',
- DS18_REPORT_TIME,
- minval=DS18_MIN_REPORT_TIME
+ "ds18_report_time", DS18_REPORT_TIME, minval=DS18_MIN_REPORT_TIME
)
- self._mcu = mcu.get_printer_mcu(self.printer, config.get('sensor_mcu'))
+ self._mcu = mcu.get_printer_mcu(self.printer, config.get("sensor_mcu"))
self.oid = self._mcu.create_oid()
- self._mcu.register_response(self._handle_ds18b20_response,
- "ds18b20_result", self.oid)
+ self._mcu.register_response(
+ self._handle_ds18b20_response, "ds18b20_result", self.oid
+ )
self._mcu.register_config_callback(self._build_config)
def _build_config(self):
sid = "".join(["%02x" % (x,) for x in self.sensor_id])
self._mcu.add_config_cmd(
"config_ds18b20 oid=%d serial=%s max_error_count=%d"
- % (self.oid, sid, DS18_MAX_CONSECUTIVE_ERRORS))
+ % (self.oid, sid, DS18_MAX_CONSECUTIVE_ERRORS)
+ )
clock = self._mcu.get_query_slot(self.oid)
self._report_clock = self._mcu.seconds_to_clock(self.report_time)
- self._mcu.add_config_cmd("query_ds18b20 oid=%d clock=%u rest_ticks=%u"
- " min_value=%d max_value=%d" % (
- self.oid, clock, self._report_clock,
- self.min_temp * 1000, self.max_temp * 1000), is_init=True)
+ self._mcu.add_config_cmd(
+ "query_ds18b20 oid=%d clock=%u rest_ticks=%u"
+ " min_value=%d max_value=%d"
+ % (
+ self.oid,
+ clock,
+ self._report_clock,
+ self.min_temp * 1000,
+ self.max_temp * 1000,
+ ),
+ is_init=True,
+ )
def _handle_ds18b20_response(self, params):
- temp = params['value'] / 1000.0
+ temp = params["value"] / 1000.0
if params["fault"]:
- logging.info("ds18b20 reports fault %d (temp=%0.1f)",
- params["fault"], temp)
+ logging.info("ds18b20 reports fault %d (temp=%0.1f)", params["fault"], temp)
return
- next_clock = self._mcu.clock32_to_clock64(params['next_clock'])
+ next_clock = self._mcu.clock32_to_clock64(params["next_clock"])
last_read_clock = next_clock - self._report_clock
- last_read_time = self._mcu.clock_to_print_time(last_read_clock)
+ last_read_time = self._mcu.clock_to_print_time(last_read_clock)
self._callback(last_read_time, temp)
def setup_minmax(self, min_temp, max_temp):
@@ -71,9 +79,10 @@ class DS18B20:
def get_status(self, eventtime):
return {
- 'temperature': round(self.temp, 2),
+ "temperature": round(self.temp, 2),
}
+
def load_config(config):
# Register sensor
pheaters = config.get_printer().load_object(config, "heaters")
diff --git a/klippy/extras/duplicate_pin_override.py b/klippy/extras/duplicate_pin_override.py
index bc61c3bf..2cd63363 100644
--- a/klippy/extras/duplicate_pin_override.py
+++ b/klippy/extras/duplicate_pin_override.py
@@ -4,12 +4,14 @@
#
# This file may be distributed under the terms of the GNU GPLv3 license.
+
class PrinterDupPinOverride:
def __init__(self, config):
printer = config.get_printer()
- ppins = printer.lookup_object('pins')
- for pin_desc in config.getlist('pins'):
+ ppins = printer.lookup_object("pins")
+ for pin_desc in config.getlist("pins"):
ppins.allow_multi_use_pin(pin_desc)
+
def load_config(config):
return PrinterDupPinOverride(config)
diff --git a/klippy/extras/endstop_phase.py b/klippy/extras/endstop_phase.py
index 689ec4fa..52d88681 100644
--- a/klippy/extras/endstop_phase.py
+++ b/klippy/extras/endstop_phase.py
@@ -6,8 +6,8 @@
import math, logging
import stepper
-TRINAMIC_DRIVERS = ["tmc2130", "tmc2208", "tmc2209", "tmc2240", "tmc2660",
- "tmc5160"]
+TRINAMIC_DRIVERS = ["tmc2130", "tmc2208", "tmc2209", "tmc2240", "tmc2660", "tmc5160"]
+
# Calculate the trigger phase of a stepper motor
class PhaseCalc:
@@ -19,6 +19,7 @@ class PhaseCalc:
# Statistics tracking for ENDSTOP_PHASE_CALIBRATE
self.phase_history = self.last_phase = self.last_mcu_position = None
self.is_primary = self.stats_only = False
+
def lookup_tmc(self):
for driver in TRINAMIC_DRIVERS:
driver_name = "%s %s" % (driver, self.name)
@@ -30,17 +31,20 @@ class PhaseCalc:
break
if self.phases is not None:
self.phase_history = [0] * self.phases
+
def convert_phase(self, driver_phase, driver_phases):
phases = self.phases
- return (int(float(driver_phase) / driver_phases * phases + .5) % phases)
+ return int(float(driver_phase) / driver_phases * phases + 0.5) % phases
+
def calc_phase(self, stepper, trig_mcu_pos):
mcu_phase_offset = 0
if self.tmc_module is not None:
mcu_phase_offset, phases = self.tmc_module.get_phase_offset()
if mcu_phase_offset is None:
- if self.printer.get_start_args().get('debugoutput') is None:
- raise self.printer.command_error("Stepper %s phase unknown"
- % (self.name,))
+ if self.printer.get_start_args().get("debugoutput") is None:
+ raise self.printer.command_error(
+ "Stepper %s phase unknown" % (self.name,)
+ )
mcu_phase_offset = 0
phase = (trig_mcu_pos + mcu_phase_offset) % self.phases
self.phase_history[phase] += 1
@@ -48,6 +52,7 @@ class PhaseCalc:
self.last_mcu_position = trig_mcu_pos
return phase
+
# Adjusted endstop trigger positions
class EndstopPhase:
def __init__(self, config):
@@ -60,62 +65,71 @@ class EndstopPhase:
self.phases = sconfig.getint("microsteps", note_valid=False) * 4
self.phase_calc = PhaseCalc(self.printer, self.name, self.phases)
# Register event handlers
- self.printer.register_event_handler("klippy:connect",
- self.phase_calc.lookup_tmc)
- self.printer.register_event_handler("homing:home_rails_end",
- self.handle_home_rails_end)
+ self.printer.register_event_handler(
+ "klippy:connect", self.phase_calc.lookup_tmc
+ )
+ self.printer.register_event_handler(
+ "homing:home_rails_end", self.handle_home_rails_end
+ )
self.printer.load_object(config, "endstop_phase")
# Read config
self.endstop_phase = None
- trigger_phase = config.get('trigger_phase', None)
+ trigger_phase = config.get("trigger_phase", None)
if trigger_phase is not None:
- p, ps = config.getintlist('trigger_phase', sep='/', count=2)
+ p, ps = config.getintlist("trigger_phase", sep="/", count=2)
if p >= ps:
- raise config.error("Invalid trigger_phase '%s'"
- % (trigger_phase,))
+ raise config.error("Invalid trigger_phase '%s'" % (trigger_phase,))
self.endstop_phase = self.phase_calc.convert_phase(p, ps)
- self.endstop_align_zero = config.getboolean('endstop_align_zero', False)
- self.endstop_accuracy = config.getfloat('endstop_accuracy', None,
- above=0.)
+ self.endstop_align_zero = config.getboolean("endstop_align_zero", False)
+ self.endstop_accuracy = config.getfloat("endstop_accuracy", None, above=0.0)
# Determine endstop accuracy
if self.endstop_accuracy is None:
- self.endstop_phase_accuracy = self.phases//2 - 1
+ self.endstop_phase_accuracy = self.phases // 2 - 1
elif self.endstop_phase is not None:
self.endstop_phase_accuracy = int(
- math.ceil(self.endstop_accuracy * .5 / self.step_dist))
+ math.ceil(self.endstop_accuracy * 0.5 / self.step_dist)
+ )
else:
self.endstop_phase_accuracy = int(
- math.ceil(self.endstop_accuracy / self.step_dist))
+ math.ceil(self.endstop_accuracy / self.step_dist)
+ )
if self.endstop_phase_accuracy >= self.phases // 2:
- raise config.error("Endstop for %s is not accurate enough for"
- " stepper phase adjustment" % (self.name,))
- if self.printer.get_start_args().get('debugoutput') is not None:
+ raise config.error(
+ "Endstop for %s is not accurate enough for"
+ " stepper phase adjustment" % (self.name,)
+ )
+ if self.printer.get_start_args().get("debugoutput") is not None:
self.endstop_phase_accuracy = self.phases
+
def align_endstop(self, rail):
if not self.endstop_align_zero or self.endstop_phase is None:
- return 0.
+ return 0.0
# Adjust the endstop position so 0.0 is always at a full step
microsteps = self.phases // 4
half_microsteps = microsteps // 2
- phase_offset = (((self.endstop_phase + half_microsteps) % microsteps)
- - half_microsteps) * self.step_dist
+ phase_offset = (
+ ((self.endstop_phase + half_microsteps) % microsteps) - half_microsteps
+ ) * self.step_dist
full_step = microsteps * self.step_dist
pe = rail.get_homing_info().position_endstop
- return int(pe / full_step + .5) * full_step - pe + phase_offset
+ return int(pe / full_step + 0.5) * full_step - pe + phase_offset
+
def get_homed_offset(self, stepper, trig_mcu_pos):
phase = self.phase_calc.calc_phase(stepper, trig_mcu_pos)
if self.endstop_phase is None:
logging.info("Setting %s endstop phase to %d", self.name, phase)
self.endstop_phase = phase
- return 0.
+ return 0.0
delta = (phase - self.endstop_phase) % self.phases
if delta >= self.phases - self.endstop_phase_accuracy:
delta -= self.phases
elif delta > self.endstop_phase_accuracy:
raise self.printer.command_error(
- "Endstop %s incorrect phase (got %d vs %d)" % (
- self.name, phase, self.endstop_phase))
+ "Endstop %s incorrect phase (got %d vs %d)"
+ % (self.name, phase, self.endstop_phase)
+ )
return delta * self.step_dist
+
def handle_home_rails_end(self, homing_state, rails):
for rail in rails:
stepper = rail.get_endstops()[0][0].get_steppers()[0]
@@ -126,18 +140,23 @@ class EndstopPhase:
homing_state.set_stepper_adjustment(self.name, align + offset)
return
+
# Support for ENDSTOP_PHASE_CALIBRATE command
class EndstopPhases:
def __init__(self, config):
self.printer = config.get_printer()
self.tracking = {}
# Register handlers
- self.printer.register_event_handler("homing:home_rails_end",
- self.handle_home_rails_end)
- self.gcode = self.printer.lookup_object('gcode')
- self.gcode.register_command("ENDSTOP_PHASE_CALIBRATE",
- self.cmd_ENDSTOP_PHASE_CALIBRATE,
- desc=self.cmd_ENDSTOP_PHASE_CALIBRATE_help)
+ self.printer.register_event_handler(
+ "homing:home_rails_end", self.handle_home_rails_end
+ )
+ self.gcode = self.printer.lookup_object("gcode")
+ self.gcode.register_command(
+ "ENDSTOP_PHASE_CALIBRATE",
+ self.cmd_ENDSTOP_PHASE_CALIBRATE,
+ desc=self.cmd_ENDSTOP_PHASE_CALIBRATE_help,
+ )
+
def update_stepper(self, stepper, trig_mcu_pos, is_primary):
stepper_name = stepper.get_name()
phase_calc = self.tracking.get(stepper_name)
@@ -159,6 +178,7 @@ class EndstopPhases:
phase_calc.is_primary = True
if phase_calc.stats_only:
phase_calc.calc_phase(stepper, trig_mcu_pos)
+
def handle_home_rails_end(self, homing_state, rails):
for rail in rails:
is_primary = True
@@ -167,27 +187,29 @@ class EndstopPhases:
trig_mcu_pos = homing_state.get_trigger_position(sname)
self.update_stepper(stepper, trig_mcu_pos, is_primary)
is_primary = False
+
cmd_ENDSTOP_PHASE_CALIBRATE_help = "Calibrate stepper phase"
+
def cmd_ENDSTOP_PHASE_CALIBRATE(self, gcmd):
- stepper_name = gcmd.get('STEPPER', None)
+ stepper_name = gcmd.get("STEPPER", None)
if stepper_name is None:
self.report_stats()
return
phase_calc = self.tracking.get(stepper_name)
if phase_calc is None or phase_calc.phase_history is None:
- raise gcmd.error("Stats not available for stepper %s"
- % (stepper_name,))
+ raise gcmd.error("Stats not available for stepper %s" % (stepper_name,))
endstop_phase, phases = self.generate_stats(stepper_name, phase_calc)
if not phase_calc.is_primary:
return
- configfile = self.printer.lookup_object('configfile')
- section = 'endstop_phase %s' % (stepper_name,)
+ configfile = self.printer.lookup_object("configfile")
+ section = "endstop_phase %s" % (stepper_name,)
configfile.remove_section(section)
- configfile.set(section, "trigger_phase",
- "%s/%s" % (endstop_phase, phases))
+ configfile.set(section, "trigger_phase", "%s/%s" % (endstop_phase, phases))
gcmd.respond_info(
"The SAVE_CONFIG command will update the printer config\n"
- "file with these parameters and restart the printer.")
+ "file with these parameters and restart the printer."
+ )
+
def generate_stats(self, stepper_name, phase_calc):
phase_history = phase_calc.phase_history
wph = phase_history + phase_history
@@ -196,36 +218,47 @@ class EndstopPhases:
res = []
for i in range(phases):
phase = i + half_phases
- cost = sum([wph[j] * abs(j-phase) for j in range(i, i+phases)])
+ cost = sum([wph[j] * abs(j - phase) for j in range(i, i + phases)])
res.append((cost, phase))
res.sort()
best = res[0][1]
- found = [j for j in range(best - half_phases, best + half_phases)
- if wph[j]]
+ found = [j for j in range(best - half_phases, best + half_phases) if wph[j]]
best_phase = best % phases
lo, hi = found[0] % phases, found[-1] % phases
- self.gcode.respond_info("%s: trigger_phase=%d/%d (range %d to %d)"
- % (stepper_name, best_phase, phases, lo, hi))
+ self.gcode.respond_info(
+ "%s: trigger_phase=%d/%d (range %d to %d)"
+ % (stepper_name, best_phase, phases, lo, hi)
+ )
return best_phase, phases
+
def report_stats(self):
if not self.tracking:
self.gcode.respond_info(
- "No steppers found. (Be sure to home at least once.)")
+ "No steppers found. (Be sure to home at least once.)"
+ )
return
for stepper_name in sorted(self.tracking.keys()):
phase_calc = self.tracking[stepper_name]
if phase_calc is None or not phase_calc.is_primary:
continue
self.generate_stats(stepper_name, phase_calc)
+
def get_status(self, eventtime):
- lh = { name: {'phase': pc.last_phase, 'phases': pc.phases,
- 'mcu_position': pc.last_mcu_position}
- for name, pc in self.tracking.items()
- if pc.phase_history is not None }
- return { 'last_home': lh }
+ lh = {
+ name: {
+ "phase": pc.last_phase,
+ "phases": pc.phases,
+ "mcu_position": pc.last_mcu_position,
+ }
+ for name, pc in self.tracking.items()
+ if pc.phase_history is not None
+ }
+ return {"last_home": lh}
+
def load_config_prefix(config):
return EndstopPhase(config)
+
def load_config(config):
return EndstopPhases(config)
diff --git a/klippy/extras/error_mcu.py b/klippy/extras/error_mcu.py
index dc91c33a..7e0610e9 100644
--- a/klippy/extras/error_mcu.py
+++ b/klippy/extras/error_mcu.py
@@ -31,26 +31,38 @@ Error configuring printer
"""
Common_MCU_errors = {
- ("Timer too close",): """
+ (
+ "Timer too close",
+ ): """
This often indicates the host computer is overloaded. Check
for other processes consuming excessive CPU time, high swap
usage, disk errors, overheating, unstable voltage, or
similar system problems on the host computer.""",
- ("Missed scheduling of next ",): """
+ (
+ "Missed scheduling of next ",
+ ): """
This is generally indicative of an intermittent
communication failure between micro-controller and host.""",
- ("ADC out of range",): """
+ (
+ "ADC out of range",
+ ): """
This generally occurs when a heater temperature exceeds
its configured min_temp or max_temp.""",
- ("Rescheduled timer in the past", "Stepper too far in past"): """
+ (
+ "Rescheduled timer in the past",
+ "Stepper too far in past",
+ ): """
This generally occurs when the micro-controller has been
requested to step at a rate higher than it is capable of
obtaining.""",
- ("Command request",): """
+ (
+ "Command request",
+ ): """
This generally occurs in response to an M112 G-Code command
or in response to an internal error in the host software.""",
}
+
def error_hint(msg):
for prefixes, help_msg in Common_MCU_errors.items():
for prefix in prefixes:
@@ -58,76 +70,88 @@ def error_hint(msg):
return help_msg
return ""
+
class PrinterMCUError:
def __init__(self, config):
self.printer = config.get_printer()
self.clarify_callbacks = {}
- self.printer.register_event_handler("klippy:notify_mcu_shutdown",
- self._handle_notify_mcu_shutdown)
- self.printer.register_event_handler("klippy:notify_mcu_error",
- self._handle_notify_mcu_error)
+ self.printer.register_event_handler(
+ "klippy:notify_mcu_shutdown", self._handle_notify_mcu_shutdown
+ )
+ self.printer.register_event_handler(
+ "klippy:notify_mcu_error", self._handle_notify_mcu_error
+ )
+
def add_clarify(self, msg, callback):
self.clarify_callbacks.setdefault(msg, []).append(callback)
+
def _check_mcu_shutdown(self, msg, details):
- mcu_name = details['mcu']
- mcu_msg = details['reason']
- event_type = details['event_type']
+ mcu_name = details["mcu"]
+ mcu_msg = details["reason"]
+ event_type = details["event_type"]
prefix = "MCU '%s' shutdown: " % (mcu_name,)
- if event_type == 'is_shutdown':
+ if event_type == "is_shutdown":
prefix = "Previous MCU '%s' shutdown: " % (mcu_name,)
# Lookup generic hint
hint = error_hint(mcu_msg)
# Add per instance help
- clarify = [cb(msg, details)
- for cb in self.clarify_callbacks.get(mcu_msg, [])]
+ clarify = [cb(msg, details) for cb in self.clarify_callbacks.get(mcu_msg, [])]
clarify = [cm for cm in clarify if cm is not None]
clarify_msg = ""
if clarify:
clarify_msg = "\n".join(["", ""] + clarify + [""])
# Update error message
- newmsg = "%s%s%s%s%s" % (prefix, mcu_msg, clarify_msg,
- hint, message_shutdown)
+ newmsg = "%s%s%s%s%s" % (prefix, mcu_msg, clarify_msg, hint, message_shutdown)
self.printer.update_error_msg(msg, newmsg)
+
def _handle_notify_mcu_shutdown(self, msg, details):
if msg == "MCU shutdown":
self._check_mcu_shutdown(msg, details)
else:
self.printer.update_error_msg(msg, "%s%s" % (msg, message_shutdown))
+
def _check_protocol_error(self, msg, details):
- host_version = self.printer.start_args['software_version']
+ host_version = self.printer.start_args["software_version"]
msg_update = []
msg_updated = []
- for mcu_name, mcu in self.printer.lookup_objects('mcu'):
+ for mcu_name, mcu in self.printer.lookup_objects("mcu"):
try:
- mcu_version = mcu.get_status()['mcu_version']
+ mcu_version = mcu.get_status()["mcu_version"]
except:
logging.exception("Unable to retrieve mcu_version from mcu")
continue
if mcu_version != host_version:
- msg_update.append("%s: Current version %s"
- % (mcu_name.split()[-1], mcu_version))
+ msg_update.append(
+ "%s: Current version %s" % (mcu_name.split()[-1], mcu_version)
+ )
else:
- msg_updated.append("%s: Current version %s"
- % (mcu_name.split()[-1], mcu_version))
+ msg_updated.append(
+ "%s: Current version %s" % (mcu_name.split()[-1], mcu_version)
+ )
if not msg_update:
msg_update.append("<none>")
if not msg_updated:
msg_updated.append("<none>")
- newmsg = ["MCU Protocol error",
- message_protocol_error1,
- "Your Klipper version is: %s" % (host_version,),
- "MCU(s) which should be updated:"]
+ newmsg = [
+ "MCU Protocol error",
+ message_protocol_error1,
+ "Your Klipper version is: %s" % (host_version,),
+ "MCU(s) which should be updated:",
+ ]
newmsg += msg_update + ["Up-to-date MCU(s):"] + msg_updated
- newmsg += [message_protocol_error2, details['error']]
+ newmsg += [message_protocol_error2, details["error"]]
self.printer.update_error_msg(msg, "\n".join(newmsg))
+
def _check_mcu_connect_error(self, msg, details):
- newmsg = "%s%s" % (details['error'], message_mcu_connect_error)
+ newmsg = "%s%s" % (details["error"], message_mcu_connect_error)
self.printer.update_error_msg(msg, newmsg)
+
def _handle_notify_mcu_error(self, msg, details):
if msg == "Protocol error":
self._check_protocol_error(msg, details)
elif msg == "MCU error during connect":
self._check_mcu_connect_error(msg, details)
+
def load_config(config):
return PrinterMCUError(config)
diff --git a/klippy/extras/exclude_object.py b/klippy/extras/exclude_object.py
index c80dd000..3043e8dd 100644
--- a/klippy/extras/exclude_object.py
+++ b/klippy/extras/exclude_object.py
@@ -8,64 +8,74 @@
import logging
import json
+
class ExcludeObject:
def __init__(self, config):
self.printer = config.get_printer()
- self.gcode = self.printer.lookup_object('gcode')
- self.gcode_move = self.printer.load_object(config, 'gcode_move')
- self.printer.register_event_handler('klippy:connect',
- self._handle_connect)
- self.printer.register_event_handler("virtual_sdcard:reset_file",
- self._reset_file)
+ self.gcode = self.printer.lookup_object("gcode")
+ self.gcode_move = self.printer.load_object(config, "gcode_move")
+ self.printer.register_event_handler("klippy:connect", self._handle_connect)
+ self.printer.register_event_handler(
+ "virtual_sdcard:reset_file", self._reset_file
+ )
self.next_transform = None
- self.last_position_extruded = [0., 0., 0., 0.]
- self.last_position_excluded = [0., 0., 0., 0.]
+ self.last_position_extruded = [0.0, 0.0, 0.0, 0.0]
+ self.last_position_excluded = [0.0, 0.0, 0.0, 0.0]
self._reset_state()
self.gcode.register_command(
- 'EXCLUDE_OBJECT_START', self.cmd_EXCLUDE_OBJECT_START,
- desc=self.cmd_EXCLUDE_OBJECT_START_help)
+ "EXCLUDE_OBJECT_START",
+ self.cmd_EXCLUDE_OBJECT_START,
+ desc=self.cmd_EXCLUDE_OBJECT_START_help,
+ )
self.gcode.register_command(
- 'EXCLUDE_OBJECT_END', self.cmd_EXCLUDE_OBJECT_END,
- desc=self.cmd_EXCLUDE_OBJECT_END_help)
+ "EXCLUDE_OBJECT_END",
+ self.cmd_EXCLUDE_OBJECT_END,
+ desc=self.cmd_EXCLUDE_OBJECT_END_help,
+ )
self.gcode.register_command(
- 'EXCLUDE_OBJECT', self.cmd_EXCLUDE_OBJECT,
- desc=self.cmd_EXCLUDE_OBJECT_help)
+ "EXCLUDE_OBJECT", self.cmd_EXCLUDE_OBJECT, desc=self.cmd_EXCLUDE_OBJECT_help
+ )
self.gcode.register_command(
- 'EXCLUDE_OBJECT_DEFINE', self.cmd_EXCLUDE_OBJECT_DEFINE,
- desc=self.cmd_EXCLUDE_OBJECT_DEFINE_help)
+ "EXCLUDE_OBJECT_DEFINE",
+ self.cmd_EXCLUDE_OBJECT_DEFINE,
+ desc=self.cmd_EXCLUDE_OBJECT_DEFINE_help,
+ )
def _register_transform(self):
if self.next_transform is None:
- tuning_tower = self.printer.lookup_object('tuning_tower')
+ tuning_tower = self.printer.lookup_object("tuning_tower")
if tuning_tower.is_active():
- logging.info('The ExcludeObject move transform is not being '
- 'loaded due to Tuning tower being Active')
+ logging.info(
+ "The ExcludeObject move transform is not being "
+ "loaded due to Tuning tower being Active"
+ )
return
- self.next_transform = self.gcode_move.set_move_transform(self,
- force=True)
+ self.next_transform = self.gcode_move.set_move_transform(self, force=True)
self.extrusion_offsets = {}
self.max_position_extruded = 0
self.max_position_excluded = 0
self.extruder_adj = 0
self.initial_extrusion_moves = 5
- self.last_position = [0., 0., 0., 0.]
+ self.last_position = [0.0, 0.0, 0.0, 0.0]
self.get_position()
self.last_position_extruded[:] = self.last_position
self.last_position_excluded[:] = self.last_position
def _handle_connect(self):
- self.toolhead = self.printer.lookup_object('toolhead')
+ self.toolhead = self.printer.lookup_object("toolhead")
def _unregister_transform(self):
if self.next_transform:
- tuning_tower = self.printer.lookup_object('tuning_tower')
+ tuning_tower = self.printer.lookup_object("tuning_tower")
if tuning_tower.is_active():
- logging.error('The Exclude Object move transform was not '
- 'unregistered because it is not at the head of the '
- 'transform chain.')
+ logging.error(
+ "The Exclude Object move transform was not "
+ "unregistered because it is not at the head of the "
+ "transform chain."
+ )
return
self.gcode_move.set_move_transform(self.next_transform, force=True)
@@ -86,10 +96,10 @@ class ExcludeObject:
ename = self.toolhead.get_extruder().get_name()
offset = self.extrusion_offsets.get(ename)
if offset is None:
- offset = [0.] * num_coord
+ offset = [0.0] * num_coord
self.extrusion_offsets[ename] = offset
if len(offset) < num_coord:
- offset.extend([0.] * (len(num_coord) - len(offset)))
+ offset.extend([0.0] * (len(num_coord) - len(offset)))
return offset
def get_position(self):
@@ -102,8 +112,7 @@ class ExcludeObject:
def _normal_move(self, newpos, speed):
offset = self._get_extrusion_offsets(len(newpos))
- if self.initial_extrusion_moves > 0 and \
- self.last_position[3] != newpos[3]:
+ if self.initial_extrusion_moves > 0 and self.last_position[3] != newpos[3]:
# Since the transform is not loaded until there is a request to
# exclude an object, the transform needs to track a few extrusions
# to get the state of the extruder
@@ -120,9 +129,10 @@ class ExcludeObject:
# Ideally, there will be Z and E moves right away to adjust any offsets
# before moving away from the last position. Any remaining corrections
# will be made on the firs XY move.
- if (offset[0] != 0 or offset[1] != 0) and \
- (newpos[0] != self.last_position_excluded[0] or \
- newpos[1] != self.last_position_excluded[1]):
+ if (offset[0] != 0 or offset[1] != 0) and (
+ newpos[0] != self.last_position_excluded[0]
+ or newpos[1] != self.last_position_excluded[1]
+ ):
for i in range(len(newpos)):
if i != 3:
offset[i] = 0
@@ -132,8 +142,7 @@ class ExcludeObject:
if offset[2] != 0 and newpos[2] != self.last_position_excluded[2]:
offset[2] = 0
- if self.extruder_adj != 0 and \
- newpos[3] != self.last_position_excluded[3]:
+ if self.extruder_adj != 0 and newpos[3] != self.last_position_excluded[3]:
offset[3] += self.extruder_adj
self.extruder_adj = 0
@@ -161,21 +170,25 @@ class ExcludeObject:
# This adjustment value is used to compensate for any retraction
# differences between the last object printed and excluded one.
- self.extruder_adj = self.max_position_excluded \
- - self.last_position_excluded[3] \
+ self.extruder_adj = (
+ self.max_position_excluded
+ - self.last_position_excluded[3]
- (self.max_position_extruded - self.last_position_extruded[3])
+ )
self._normal_move(newpos, speed)
def _test_in_excluded_region(self):
# Inside cancelled object
- return self.current_object in self.excluded_objects \
+ return (
+ self.current_object in self.excluded_objects
and self.initial_extrusion_moves == 0
+ )
def get_status(self, eventtime=None):
status = {
"objects": self.objects,
"excluded_objects": self.excluded_objects,
- "current_object": self.current_object
+ "current_object": self.current_object,
}
return status
@@ -194,34 +207,40 @@ class ExcludeObject:
else:
self._normal_move(newpos, speed)
- cmd_EXCLUDE_OBJECT_START_help = "Marks the beginning the current object" \
- " as labeled"
+ cmd_EXCLUDE_OBJECT_START_help = (
+ "Marks the beginning the current object" " as labeled"
+ )
+
def cmd_EXCLUDE_OBJECT_START(self, gcmd):
- name = gcmd.get('NAME').upper()
+ name = gcmd.get("NAME").upper()
if not any(obj["name"] == name for obj in self.objects):
self._add_object_definition({"name": name})
self.current_object = name
self.was_excluded_at_start = self._test_in_excluded_region()
cmd_EXCLUDE_OBJECT_END_help = "Marks the end the current object"
+
def cmd_EXCLUDE_OBJECT_END(self, gcmd):
if self.current_object == None and self.next_transform:
- gcmd.respond_info("EXCLUDE_OBJECT_END called, but no object is"
- " currently active")
+ gcmd.respond_info(
+ "EXCLUDE_OBJECT_END called, but no object is" " currently active"
+ )
return
- name = gcmd.get('NAME', default=None)
+ name = gcmd.get("NAME", default=None)
if name != None and name.upper() != self.current_object:
- gcmd.respond_info("EXCLUDE_OBJECT_END NAME=%s does not match the"
- " current object NAME=%s" %
- (name.upper(), self.current_object))
+ gcmd.respond_info(
+ "EXCLUDE_OBJECT_END NAME=%s does not match the"
+ " current object NAME=%s" % (name.upper(), self.current_object)
+ )
self.current_object = None
cmd_EXCLUDE_OBJECT_help = "Cancel moves inside a specified objects"
+
def cmd_EXCLUDE_OBJECT(self, gcmd):
- reset = gcmd.get('RESET', None)
- current = gcmd.get('CURRENT', None)
- name = gcmd.get('NAME', '').upper()
+ reset = gcmd.get("RESET", None)
+ current = gcmd.get("CURRENT", None)
+ name = gcmd.get("NAME", "").upper()
if reset:
if name:
@@ -236,7 +255,7 @@ class ExcludeObject:
elif current:
if not self.current_object:
- raise self.gcode.error('There is no current object to cancel')
+ raise self.gcode.error("There is no current object to cancel")
else:
self._exclude_object(self.current_object)
@@ -245,27 +264,28 @@ class ExcludeObject:
self._list_excluded_objects(gcmd)
cmd_EXCLUDE_OBJECT_DEFINE_help = "Provides a summary of an object"
+
def cmd_EXCLUDE_OBJECT_DEFINE(self, gcmd):
- reset = gcmd.get('RESET', None)
- name = gcmd.get('NAME', '').upper()
+ reset = gcmd.get("RESET", None)
+ name = gcmd.get("NAME", "").upper()
if reset:
self._reset_file()
elif name:
parameters = gcmd.get_command_parameters().copy()
- parameters.pop('NAME')
- center = parameters.pop('CENTER', None)
- polygon = parameters.pop('POLYGON', None)
+ parameters.pop("NAME")
+ center = parameters.pop("CENTER", None)
+ polygon = parameters.pop("POLYGON", None)
obj = {"name": name.upper()}
obj.update(parameters)
if center != None:
- obj['center'] = json.loads('[%s]' % center)
+ obj["center"] = json.loads("[%s]" % center)
if polygon != None:
- obj['polygon'] = json.loads(polygon)
+ obj["polygon"] = json.loads(polygon)
self._add_object_definition(obj)
@@ -273,32 +293,32 @@ class ExcludeObject:
self._list_objects(gcmd)
def _add_object_definition(self, definition):
- self.objects = sorted(self.objects + [definition],
- key=lambda o: o["name"])
+ self.objects = sorted(self.objects + [definition], key=lambda o: o["name"])
def _exclude_object(self, name):
self._register_transform()
- self.gcode.respond_info('Excluding object {}'.format(name.upper()))
+ self.gcode.respond_info("Excluding object {}".format(name.upper()))
if name not in self.excluded_objects:
self.excluded_objects = sorted(self.excluded_objects + [name])
def _unexclude_object(self, name):
- self.gcode.respond_info('Unexcluding object {}'.format(name.upper()))
+ self.gcode.respond_info("Unexcluding object {}".format(name.upper()))
if name in self.excluded_objects:
excluded_objects = list(self.excluded_objects)
excluded_objects.remove(name)
self.excluded_objects = sorted(excluded_objects)
def _list_objects(self, gcmd):
- if gcmd.get('JSON', None) is not None:
+ if gcmd.get("JSON", None) is not None:
object_list = json.dumps(self.objects)
else:
- object_list = " ".join(obj['name'] for obj in self.objects)
- gcmd.respond_info('Known objects: {}'.format(object_list))
+ object_list = " ".join(obj["name"] for obj in self.objects)
+ gcmd.respond_info("Known objects: {}".format(object_list))
def _list_excluded_objects(self, gcmd):
object_list = " ".join(self.excluded_objects)
- gcmd.respond_info('Excluded objects: {}'.format(object_list))
+ gcmd.respond_info("Excluded objects: {}".format(object_list))
+
def load_config(config):
return ExcludeObject(config)
diff --git a/klippy/extras/extruder_stepper.py b/klippy/extras/extruder_stepper.py
index 4ac5289f..b5feddf1 100644
--- a/klippy/extras/extruder_stepper.py
+++ b/klippy/extras/extruder_stepper.py
@@ -6,17 +6,20 @@
import logging
from kinematics import extruder
+
class PrinterExtruderStepper:
def __init__(self, config):
self.printer = config.get_printer()
self.extruder_stepper = extruder.ExtruderStepper(config)
- self.extruder_name = config.get('extruder')
- self.printer.register_event_handler("klippy:connect",
- self.handle_connect)
+ self.extruder_name = config.get("extruder")
+ self.printer.register_event_handler("klippy:connect", self.handle_connect)
+
def handle_connect(self):
self.extruder_stepper.sync_to_extruder(self.extruder_name)
+
def get_status(self, eventtime):
return self.extruder_stepper.get_status(eventtime)
+
def load_config_prefix(config):
return PrinterExtruderStepper(config)
diff --git a/klippy/extras/fan.py b/klippy/extras/fan.py
index c5677ba0..081db642 100644
--- a/klippy/extras/fan.py
+++ b/klippy/extras/fan.py
@@ -5,60 +5,68 @@
# This file may be distributed under the terms of the GNU GPLv3 license.
from . import pulse_counter, output_pin
+
class Fan:
- def __init__(self, config, default_shutdown_speed=0.):
+ def __init__(self, config, default_shutdown_speed=0.0):
self.printer = config.get_printer()
- self.last_fan_value = self.last_req_value = 0.
+ self.last_fan_value = self.last_req_value = 0.0
# Read config
- self.max_power = config.getfloat('max_power', 1., above=0., maxval=1.)
- self.kick_start_time = config.getfloat('kick_start_time', 0.1,
- minval=0.)
- self.off_below = config.getfloat('off_below', default=0.,
- minval=0., maxval=1.)
- cycle_time = config.getfloat('cycle_time', 0.010, above=0.)
- hardware_pwm = config.getboolean('hardware_pwm', False)
+ self.max_power = config.getfloat("max_power", 1.0, above=0.0, maxval=1.0)
+ self.kick_start_time = config.getfloat("kick_start_time", 0.1, minval=0.0)
+ self.off_below = config.getfloat(
+ "off_below", default=0.0, minval=0.0, maxval=1.0
+ )
+ cycle_time = config.getfloat("cycle_time", 0.010, above=0.0)
+ hardware_pwm = config.getboolean("hardware_pwm", False)
shutdown_speed = config.getfloat(
- 'shutdown_speed', default_shutdown_speed, minval=0., maxval=1.)
+ "shutdown_speed", default_shutdown_speed, minval=0.0, maxval=1.0
+ )
# Setup pwm object
- ppins = self.printer.lookup_object('pins')
- self.mcu_fan = ppins.setup_pin('pwm', config.get('pin'))
- self.mcu_fan.setup_max_duration(0.)
+ ppins = self.printer.lookup_object("pins")
+ self.mcu_fan = ppins.setup_pin("pwm", config.get("pin"))
+ self.mcu_fan.setup_max_duration(0.0)
self.mcu_fan.setup_cycle_time(cycle_time, hardware_pwm)
- shutdown_power = max(0., min(self.max_power, shutdown_speed))
- self.mcu_fan.setup_start_value(0., shutdown_power)
+ shutdown_power = max(0.0, min(self.max_power, shutdown_speed))
+ self.mcu_fan.setup_start_value(0.0, shutdown_power)
self.enable_pin = None
- enable_pin = config.get('enable_pin', None)
+ enable_pin = config.get("enable_pin", None)
if enable_pin is not None:
- self.enable_pin = ppins.setup_pin('digital_out', enable_pin)
- self.enable_pin.setup_max_duration(0.)
+ self.enable_pin = ppins.setup_pin("digital_out", enable_pin)
+ self.enable_pin.setup_max_duration(0.0)
# Create gcode request queue
- self.gcrq = output_pin.GCodeRequestQueue(config, self.mcu_fan.get_mcu(),
- self._apply_speed)
+ self.gcrq = output_pin.GCodeRequestQueue(
+ config, self.mcu_fan.get_mcu(), self._apply_speed
+ )
# Setup tachometer
self.tachometer = FanTachometer(config)
# Register callbacks
- self.printer.register_event_handler("gcode:request_restart",
- self._handle_request_restart)
+ self.printer.register_event_handler(
+ "gcode:request_restart", self._handle_request_restart
+ )
def get_mcu(self):
return self.mcu_fan.get_mcu()
+
def _apply_speed(self, print_time, value):
if value < self.off_below:
- value = 0.
- value = max(0., min(self.max_power, value * self.max_power))
+ value = 0.0
+ value = max(0.0, min(self.max_power, value * self.max_power))
if value == self.last_fan_value:
- return "discard", 0.
+ return "discard", 0.0
if self.enable_pin:
if value > 0 and self.last_fan_value == 0:
self.enable_pin.set_digital(print_time, 1)
elif value == 0 and self.last_fan_value > 0:
self.enable_pin.set_digital(print_time, 0)
- if (value and self.kick_start_time
- and (not self.last_fan_value or value - self.last_fan_value > .5)):
+ if (
+ value
+ and self.kick_start_time
+ and (not self.last_fan_value or value - self.last_fan_value > 0.5)
+ ):
# Run fan at full speed for specified kick_start_time
self.last_req_value = value
self.last_fan_value = self.max_power
@@ -66,57 +74,66 @@ class Fan:
return "delay", self.kick_start_time
self.last_fan_value = self.last_req_value = value
self.mcu_fan.set_pwm(print_time, value)
+
def set_speed(self, value, print_time=None):
self.gcrq.send_async_request(value, print_time)
+
def set_speed_from_command(self, value):
self.gcrq.queue_gcode_request(value)
+
def _handle_request_restart(self, print_time):
- self.set_speed(0., print_time)
+ self.set_speed(0.0, print_time)
def get_status(self, eventtime):
tachometer_status = self.tachometer.get_status(eventtime)
return {
- 'speed': self.last_req_value,
- 'rpm': tachometer_status['rpm'],
+ "speed": self.last_req_value,
+ "rpm": tachometer_status["rpm"],
}
+
class FanTachometer:
def __init__(self, config):
printer = config.get_printer()
self._freq_counter = None
- pin = config.get('tachometer_pin', None)
+ pin = config.get("tachometer_pin", None)
if pin is not None:
- self.ppr = config.getint('tachometer_ppr', 2, minval=1)
- poll_time = config.getfloat('tachometer_poll_interval',
- 0.0015, above=0.)
- sample_time = 1.
+ self.ppr = config.getint("tachometer_ppr", 2, minval=1)
+ poll_time = config.getfloat("tachometer_poll_interval", 0.0015, above=0.0)
+ sample_time = 1.0
self._freq_counter = pulse_counter.FrequencyCounter(
- printer, pin, sample_time, poll_time)
+ printer, pin, sample_time, poll_time
+ )
def get_status(self, eventtime):
if self._freq_counter is not None:
- rpm = self._freq_counter.get_frequency() * 30. / self.ppr
+ rpm = self._freq_counter.get_frequency() * 30.0 / self.ppr
else:
rpm = None
- return {'rpm': rpm}
+ return {"rpm": rpm}
+
class PrinterFan:
def __init__(self, config):
self.fan = Fan(config)
# Register commands
- gcode = config.get_printer().lookup_object('gcode')
+ gcode = config.get_printer().lookup_object("gcode")
gcode.register_command("M106", self.cmd_M106)
gcode.register_command("M107", self.cmd_M107)
+
def get_status(self, eventtime):
return self.fan.get_status(eventtime)
+
def cmd_M106(self, gcmd):
# Set fan speed
- value = gcmd.get_float('S', 255., minval=0.) / 255.
+ value = gcmd.get_float("S", 255.0, minval=0.0) / 255.0
self.fan.set_speed_from_command(value)
+
def cmd_M107(self, gcmd):
# Turn fan off
- self.fan.set_speed_from_command(0.)
+ self.fan.set_speed_from_command(0.0)
+
def load_config(config):
return PrinterFan(config)
diff --git a/klippy/extras/fan_generic.py b/klippy/extras/fan_generic.py
index f8ad996f..3a9b3791 100644
--- a/klippy/extras/fan_generic.py
+++ b/klippy/extras/fan_generic.py
@@ -6,34 +6,41 @@
import logging
from . import fan, output_pin
+
class PrinterFanGeneric:
cmd_SET_FAN_SPEED_help = "Sets the speed of a fan"
+
def __init__(self, config):
self.printer = config.get_printer()
- self.fan = fan.Fan(config, default_shutdown_speed=0.)
+ self.fan = fan.Fan(config, default_shutdown_speed=0.0)
self.fan_name = config.get_name().split()[-1]
# Template handling
self.template_eval = output_pin.lookup_template_eval(config)
gcode = self.printer.lookup_object("gcode")
- gcode.register_mux_command("SET_FAN_SPEED", "FAN",
- self.fan_name,
- self.cmd_SET_FAN_SPEED,
- desc=self.cmd_SET_FAN_SPEED_help)
+ gcode.register_mux_command(
+ "SET_FAN_SPEED",
+ "FAN",
+ self.fan_name,
+ self.cmd_SET_FAN_SPEED,
+ desc=self.cmd_SET_FAN_SPEED_help,
+ )
def get_status(self, eventtime):
return self.fan.get_status(eventtime)
+
def _template_update(self, text):
try:
value = float(text)
except ValueError as e:
logging.exception("fan_generic template render error")
- value = 0.
+ value = 0.0
self.fan.set_speed(value)
+
def cmd_SET_FAN_SPEED(self, gcmd):
- speed = gcmd.get_float('SPEED', None, 0.)
- template = gcmd.get('TEMPLATE', None)
+ speed = gcmd.get_float("SPEED", None, 0.0)
+ template = gcmd.get("TEMPLATE", None)
if (speed is None) == (template is None):
raise gcmd.error("SET_FAN_SPEED must specify SPEED or TEMPLATE")
# Check for template setting
@@ -42,5 +49,6 @@ class PrinterFanGeneric:
return
self.fan.set_speed_from_command(speed)
+
def load_config_prefix(config):
return PrinterFanGeneric(config)
diff --git a/klippy/extras/filament_motion_sensor.py b/klippy/extras/filament_motion_sensor.py
index dd476d28..71a01949 100644
--- a/klippy/extras/filament_motion_sensor.py
+++ b/klippy/extras/filament_motion_sensor.py
@@ -6,18 +6,18 @@
import logging
from . import filament_switch_sensor
-CHECK_RUNOUT_TIMEOUT = .250
+CHECK_RUNOUT_TIMEOUT = 0.250
+
class EncoderSensor:
def __init__(self, config):
# Read config
self.printer = config.get_printer()
- switch_pin = config.get('switch_pin')
- self.extruder_name = config.get('extruder')
- self.detection_length = config.getfloat(
- 'detection_length', 7., above=0.)
+ switch_pin = config.get("switch_pin")
+ self.extruder_name = config.get("extruder")
+ self.detection_length = config.getfloat("detection_length", 7.0, above=0.0)
# Configure pins
- buttons = self.printer.load_object(config, 'buttons')
+ buttons = self.printer.load_object(config, "buttons")
buttons.register_buttons([switch_pin], self.encoder_event)
# Get printer objects
self.reactor = self.printer.get_reactor()
@@ -28,44 +28,54 @@ class EncoderSensor:
# Initialise internal state
self.filament_runout_pos = None
# Register commands and event handlers
- self.printer.register_event_handler('klippy:ready',
- self._handle_ready)
- self.printer.register_event_handler('idle_timeout:printing',
- self._handle_printing)
- self.printer.register_event_handler('idle_timeout:ready',
- self._handle_not_printing)
- self.printer.register_event_handler('idle_timeout:idle',
- self._handle_not_printing)
+ self.printer.register_event_handler("klippy:ready", self._handle_ready)
+ self.printer.register_event_handler(
+ "idle_timeout:printing", self._handle_printing
+ )
+ self.printer.register_event_handler(
+ "idle_timeout:ready", self._handle_not_printing
+ )
+ self.printer.register_event_handler(
+ "idle_timeout:idle", self._handle_not_printing
+ )
+
def _update_filament_runout_pos(self, eventtime=None):
if eventtime is None:
eventtime = self.reactor.monotonic()
self.filament_runout_pos = (
- self._get_extruder_pos(eventtime) +
- self.detection_length)
+ self._get_extruder_pos(eventtime) + self.detection_length
+ )
+
def _handle_ready(self):
self.extruder = self.printer.lookup_object(self.extruder_name)
- self.estimated_print_time = (
- self.printer.lookup_object('mcu').estimated_print_time)
+ self.estimated_print_time = self.printer.lookup_object(
+ "mcu"
+ ).estimated_print_time
self._update_filament_runout_pos()
self._extruder_pos_update_timer = self.reactor.register_timer(
- self._extruder_pos_update_event)
+ self._extruder_pos_update_event
+ )
+
def _handle_printing(self, print_time):
- self.reactor.update_timer(self._extruder_pos_update_timer,
- self.reactor.NOW)
+ self.reactor.update_timer(self._extruder_pos_update_timer, self.reactor.NOW)
+
def _handle_not_printing(self, print_time):
- self.reactor.update_timer(self._extruder_pos_update_timer,
- self.reactor.NEVER)
+ self.reactor.update_timer(self._extruder_pos_update_timer, self.reactor.NEVER)
+
def _get_extruder_pos(self, eventtime=None):
if eventtime is None:
eventtime = self.reactor.monotonic()
print_time = self.estimated_print_time(eventtime)
return self.extruder.find_past_position(print_time)
+
def _extruder_pos_update_event(self, eventtime):
extruder_pos = self._get_extruder_pos(eventtime)
# Check for filament runout
- self.runout_helper.note_filament_present(eventtime,
- extruder_pos < self.filament_runout_pos)
+ self.runout_helper.note_filament_present(
+ eventtime, extruder_pos < self.filament_runout_pos
+ )
return eventtime + CHECK_RUNOUT_TIMEOUT
+
def encoder_event(self, eventtime, state):
if self.extruder is not None:
self._update_filament_runout_pos(eventtime)
@@ -73,5 +83,6 @@ class EncoderSensor:
# Filament is always assumed to be present on an encoder event
self.runout_helper.note_filament_present(eventtime, True)
+
def load_config_prefix(config):
return EncoderSensor(config)
diff --git a/klippy/extras/filament_switch_sensor.py b/klippy/extras/filament_switch_sensor.py
index d1acadd5..e045764d 100644
--- a/klippy/extras/filament_switch_sensor.py
+++ b/klippy/extras/filament_switch_sensor.py
@@ -11,21 +11,19 @@ class RunoutHelper:
self.name = config.get_name().split()[-1]
self.printer = config.get_printer()
self.reactor = self.printer.get_reactor()
- self.gcode = self.printer.lookup_object('gcode')
+ self.gcode = self.printer.lookup_object("gcode")
# Read config
- self.runout_pause = config.getboolean('pause_on_runout', True)
+ self.runout_pause = config.getboolean("pause_on_runout", True)
if self.runout_pause:
- self.printer.load_object(config, 'pause_resume')
+ self.printer.load_object(config, "pause_resume")
self.runout_gcode = self.insert_gcode = None
- gcode_macro = self.printer.load_object(config, 'gcode_macro')
- if self.runout_pause or config.get('runout_gcode', None) is not None:
- self.runout_gcode = gcode_macro.load_template(
- config, 'runout_gcode', '')
- if config.get('insert_gcode', None) is not None:
- self.insert_gcode = gcode_macro.load_template(
- config, 'insert_gcode')
- self.pause_delay = config.getfloat('pause_delay', .5, above=.0)
- self.event_delay = config.getfloat('event_delay', 3., minval=.0)
+ gcode_macro = self.printer.load_object(config, "gcode_macro")
+ if self.runout_pause or config.get("runout_gcode", None) is not None:
+ self.runout_gcode = gcode_macro.load_template(config, "runout_gcode", "")
+ if config.get("insert_gcode", None) is not None:
+ self.insert_gcode = gcode_macro.load_template(config, "insert_gcode")
+ self.pause_delay = config.getfloat("pause_delay", 0.5, above=0.0)
+ self.event_delay = config.getfloat("event_delay", 3.0, minval=0.0)
# Internal state
self.min_event_systime = self.reactor.NEVER
self.filament_present = False
@@ -33,33 +31,44 @@ class RunoutHelper:
# Register commands and event handlers
self.printer.register_event_handler("klippy:ready", self._handle_ready)
self.gcode.register_mux_command(
- "QUERY_FILAMENT_SENSOR", "SENSOR", self.name,
+ "QUERY_FILAMENT_SENSOR",
+ "SENSOR",
+ self.name,
self.cmd_QUERY_FILAMENT_SENSOR,
- desc=self.cmd_QUERY_FILAMENT_SENSOR_help)
+ desc=self.cmd_QUERY_FILAMENT_SENSOR_help,
+ )
self.gcode.register_mux_command(
- "SET_FILAMENT_SENSOR", "SENSOR", self.name,
+ "SET_FILAMENT_SENSOR",
+ "SENSOR",
+ self.name,
self.cmd_SET_FILAMENT_SENSOR,
- desc=self.cmd_SET_FILAMENT_SENSOR_help)
+ desc=self.cmd_SET_FILAMENT_SENSOR_help,
+ )
+
def _handle_ready(self):
- self.min_event_systime = self.reactor.monotonic() + 2.
+ self.min_event_systime = self.reactor.monotonic() + 2.0
+
def _runout_event_handler(self, eventtime):
# Pausing from inside an event requires that the pause portion
# of pause_resume execute immediately.
pause_prefix = ""
if self.runout_pause:
- pause_resume = self.printer.lookup_object('pause_resume')
+ pause_resume = self.printer.lookup_object("pause_resume")
pause_resume.send_pause_command()
pause_prefix = "PAUSE\n"
self.printer.get_reactor().pause(eventtime + self.pause_delay)
self._exec_gcode(pause_prefix, self.runout_gcode)
+
def _insert_event_handler(self, eventtime):
self._exec_gcode("", self.insert_gcode)
+
def _exec_gcode(self, prefix, template):
try:
self.gcode.run_script(prefix + template.render() + "\nM400")
except Exception:
logging.exception("Script running error")
self.min_event_systime = self.reactor.monotonic() + self.event_delay
+
def note_filament_present(self, eventtime, is_filament_present):
if is_filament_present == self.filament_present:
return
@@ -80,42 +89,52 @@ class RunoutHelper:
# insert detected
self.min_event_systime = self.reactor.NEVER
logging.info(
- "Filament Sensor %s: insert event detected, Time %.2f" %
- (self.name, now))
+ "Filament Sensor %s: insert event detected, Time %.2f"
+ % (self.name, now)
+ )
self.reactor.register_callback(self._insert_event_handler)
elif is_printing and self.runout_gcode is not None:
# runout detected
self.min_event_systime = self.reactor.NEVER
logging.info(
- "Filament Sensor %s: runout event detected, Time %.2f" %
- (self.name, now))
+ "Filament Sensor %s: runout event detected, Time %.2f"
+ % (self.name, now)
+ )
self.reactor.register_callback(self._runout_event_handler)
+
def get_status(self, eventtime):
return {
"filament_detected": bool(self.filament_present),
- "enabled": bool(self.sensor_enabled)}
+ "enabled": bool(self.sensor_enabled),
+ }
+
cmd_QUERY_FILAMENT_SENSOR_help = "Query the status of the Filament Sensor"
+
def cmd_QUERY_FILAMENT_SENSOR(self, gcmd):
if self.filament_present:
msg = "Filament Sensor %s: filament detected" % (self.name)
else:
msg = "Filament Sensor %s: filament not detected" % (self.name)
gcmd.respond_info(msg)
+
cmd_SET_FILAMENT_SENSOR_help = "Sets the filament sensor on/off"
+
def cmd_SET_FILAMENT_SENSOR(self, gcmd):
self.sensor_enabled = gcmd.get_int("ENABLE", 1)
+
class SwitchSensor:
def __init__(self, config):
printer = config.get_printer()
- buttons = printer.load_object(config, 'buttons')
- switch_pin = config.get('switch_pin')
- buttons.register_debounce_button(switch_pin, self._button_handler
- , config)
+ buttons = printer.load_object(config, "buttons")
+ switch_pin = config.get("switch_pin")
+ buttons.register_debounce_button(switch_pin, self._button_handler, config)
self.runout_helper = RunoutHelper(config)
self.get_status = self.runout_helper.get_status
+
def _button_handler(self, eventtime, state):
self.runout_helper.note_filament_present(eventtime, state)
+
def load_config_prefix(config):
return SwitchSensor(config)
diff --git a/klippy/extras/firmware_retraction.py b/klippy/extras/firmware_retraction.py
index da0d67af..697d77e7 100644
--- a/klippy/extras/firmware_retraction.py
+++ b/klippy/extras/firmware_retraction.py
@@ -4,24 +4,27 @@
#
# This file may be distributed under the terms of the GNU GPLv3 license.
+
class FirmwareRetraction:
def __init__(self, config):
self.printer = config.get_printer()
- self.retract_length = config.getfloat('retract_length', 0., minval=0.)
- self.retract_speed = config.getfloat('retract_speed', 20., minval=1)
+ self.retract_length = config.getfloat("retract_length", 0.0, minval=0.0)
+ self.retract_speed = config.getfloat("retract_speed", 20.0, minval=1)
self.unretract_extra_length = config.getfloat(
- 'unretract_extra_length', 0., minval=0.)
- self.unretract_speed = config.getfloat('unretract_speed', 10., minval=1)
- self.unretract_length = (self.retract_length
- + self.unretract_extra_length)
+ "unretract_extra_length", 0.0, minval=0.0
+ )
+ self.unretract_speed = config.getfloat("unretract_speed", 10.0, minval=1)
+ self.unretract_length = self.retract_length + self.unretract_extra_length
self.is_retracted = False
- self.gcode = self.printer.lookup_object('gcode')
- self.gcode.register_command('SET_RETRACTION', self.cmd_SET_RETRACTION,
- desc=self.cmd_SET_RETRACTION_help)
- self.gcode.register_command('GET_RETRACTION', self.cmd_GET_RETRACTION,
- desc=self.cmd_GET_RETRACTION_help)
- self.gcode.register_command('G10', self.cmd_G10)
- self.gcode.register_command('G11', self.cmd_G11)
+ self.gcode = self.printer.lookup_object("gcode")
+ self.gcode.register_command(
+ "SET_RETRACTION", self.cmd_SET_RETRACTION, desc=self.cmd_SET_RETRACTION_help
+ )
+ self.gcode.register_command(
+ "GET_RETRACTION", self.cmd_GET_RETRACTION, desc=self.cmd_GET_RETRACTION_help
+ )
+ self.gcode.register_command("G10", self.cmd_G10)
+ self.gcode.register_command("G11", self.cmd_G11)
def get_status(self, eventtime):
return {
@@ -30,25 +33,38 @@ class FirmwareRetraction:
"unretract_extra_length": self.unretract_extra_length,
"unretract_speed": self.unretract_speed,
}
- cmd_SET_RETRACTION_help = ("Set firmware retraction parameters")
+
+ cmd_SET_RETRACTION_help = "Set firmware retraction parameters"
+
def cmd_SET_RETRACTION(self, gcmd):
- self.retract_length = gcmd.get_float('RETRACT_LENGTH',
- self.retract_length, minval=0.)
- self.retract_speed = gcmd.get_float('RETRACT_SPEED',
- self.retract_speed, minval=1)
+ self.retract_length = gcmd.get_float(
+ "RETRACT_LENGTH", self.retract_length, minval=0.0
+ )
+ self.retract_speed = gcmd.get_float(
+ "RETRACT_SPEED", self.retract_speed, minval=1
+ )
self.unretract_extra_length = gcmd.get_float(
- 'UNRETRACT_EXTRA_LENGTH', self.unretract_extra_length, minval=0.)
- self.unretract_speed = gcmd.get_float('UNRETRACT_SPEED',
- self.unretract_speed, minval=1)
- self.unretract_length = (self.retract_length
- + self.unretract_extra_length)
+ "UNRETRACT_EXTRA_LENGTH", self.unretract_extra_length, minval=0.0
+ )
+ self.unretract_speed = gcmd.get_float(
+ "UNRETRACT_SPEED", self.unretract_speed, minval=1
+ )
+ self.unretract_length = self.retract_length + self.unretract_extra_length
self.is_retracted = False
- cmd_GET_RETRACTION_help = ("Report firmware retraction parameters")
+
+ cmd_GET_RETRACTION_help = "Report firmware retraction parameters"
+
def cmd_GET_RETRACTION(self, gcmd):
- gcmd.respond_info("RETRACT_LENGTH=%.5f RETRACT_SPEED=%.5f"
- " UNRETRACT_EXTRA_LENGTH=%.5f UNRETRACT_SPEED=%.5f"
- % (self.retract_length, self.retract_speed,
- self.unretract_extra_length, self.unretract_speed))
+ gcmd.respond_info(
+ "RETRACT_LENGTH=%.5f RETRACT_SPEED=%.5f"
+ " UNRETRACT_EXTRA_LENGTH=%.5f UNRETRACT_SPEED=%.5f"
+ % (
+ self.retract_length,
+ self.retract_speed,
+ self.unretract_extra_length,
+ self.unretract_speed,
+ )
+ )
def cmd_G10(self, gcmd):
if not self.is_retracted:
@@ -57,7 +73,8 @@ class FirmwareRetraction:
"G91\n"
"G1 E-%.5f F%d\n"
"RESTORE_GCODE_STATE NAME=_retract_state"
- % (self.retract_length, self.retract_speed*60))
+ % (self.retract_length, self.retract_speed * 60)
+ )
self.is_retracted = True
def cmd_G11(self, gcmd):
@@ -67,8 +84,10 @@ class FirmwareRetraction:
"G91\n"
"G1 E%.5f F%d\n"
"RESTORE_GCODE_STATE NAME=_retract_state"
- % (self.unretract_length, self.unretract_speed*60))
+ % (self.unretract_length, self.unretract_speed * 60)
+ )
self.is_retracted = False
+
def load_config(config):
return FirmwareRetraction(config)
diff --git a/klippy/extras/force_move.py b/klippy/extras/force_move.py
index 0be0eb9b..dfdc26f2 100644
--- a/klippy/extras/force_move.py
+++ b/klippy/extras/force_move.py
@@ -6,20 +6,21 @@
import math, logging
import chelper
-BUZZ_DISTANCE = 1.
-BUZZ_VELOCITY = BUZZ_DISTANCE / .250
-BUZZ_RADIANS_DISTANCE = math.radians(1.)
-BUZZ_RADIANS_VELOCITY = BUZZ_RADIANS_DISTANCE / .250
+BUZZ_DISTANCE = 1.0
+BUZZ_VELOCITY = BUZZ_DISTANCE / 0.250
+BUZZ_RADIANS_DISTANCE = math.radians(1.0)
+BUZZ_RADIANS_VELOCITY = BUZZ_RADIANS_DISTANCE / 0.250
STALL_TIME = 0.100
+
# Calculate a move's accel_t, cruise_t, and cruise_v
def calc_move_time(dist, speed, accel):
- axis_r = 1.
- if dist < 0.:
- axis_r = -1.
+ axis_r = 1.0
+ if dist < 0.0:
+ axis_r = -1.0
dist = -dist
if not accel or not dist:
- return axis_r, 0., dist / speed, speed
+ return axis_r, 0.0, dist / speed, speed
max_cruise_v2 = dist * accel
if max_cruise_v2 < speed**2:
speed = math.sqrt(max_cruise_v2)
@@ -28,6 +29,7 @@ def calc_move_time(dist, speed, accel):
cruise_t = (dist - accel_decel_d) / speed
return axis_r, accel_t, cruise_t, speed
+
class ForceMove:
def __init__(self, config):
self.printer = config.get_printer()
@@ -38,112 +40,155 @@ class ForceMove:
self.trapq_append = ffi_lib.trapq_append
self.trapq_finalize_moves = ffi_lib.trapq_finalize_moves
self.stepper_kinematics = ffi_main.gc(
- ffi_lib.cartesian_stepper_alloc(b'x'), ffi_lib.free)
+ ffi_lib.cartesian_stepper_alloc(b"x"), ffi_lib.free
+ )
# Register commands
- gcode = self.printer.lookup_object('gcode')
- gcode.register_command('STEPPER_BUZZ', self.cmd_STEPPER_BUZZ,
- desc=self.cmd_STEPPER_BUZZ_help)
+ gcode = self.printer.lookup_object("gcode")
+ gcode.register_command(
+ "STEPPER_BUZZ", self.cmd_STEPPER_BUZZ, desc=self.cmd_STEPPER_BUZZ_help
+ )
if config.getboolean("enable_force_move", False):
- gcode.register_command('FORCE_MOVE', self.cmd_FORCE_MOVE,
- desc=self.cmd_FORCE_MOVE_help)
- gcode.register_command('SET_KINEMATIC_POSITION',
- self.cmd_SET_KINEMATIC_POSITION,
- desc=self.cmd_SET_KINEMATIC_POSITION_help)
+ gcode.register_command(
+ "FORCE_MOVE", self.cmd_FORCE_MOVE, desc=self.cmd_FORCE_MOVE_help
+ )
+ gcode.register_command(
+ "SET_KINEMATIC_POSITION",
+ self.cmd_SET_KINEMATIC_POSITION,
+ desc=self.cmd_SET_KINEMATIC_POSITION_help,
+ )
+
def register_stepper(self, config, mcu_stepper):
self.steppers[mcu_stepper.get_name()] = mcu_stepper
+
def lookup_stepper(self, name):
if name not in self.steppers:
raise self.printer.config_error("Unknown stepper %s" % (name,))
return self.steppers[name]
+
def _force_enable(self, stepper):
- toolhead = self.printer.lookup_object('toolhead')
+ toolhead = self.printer.lookup_object("toolhead")
print_time = toolhead.get_last_move_time()
- stepper_enable = self.printer.lookup_object('stepper_enable')
+ stepper_enable = self.printer.lookup_object("stepper_enable")
enable = stepper_enable.lookup_enable(stepper.get_name())
was_enable = enable.is_motor_enabled()
if not was_enable:
enable.motor_enable(print_time)
toolhead.dwell(STALL_TIME)
return was_enable
+
def _restore_enable(self, stepper, was_enable):
if not was_enable:
- toolhead = self.printer.lookup_object('toolhead')
+ toolhead = self.printer.lookup_object("toolhead")
toolhead.dwell(STALL_TIME)
print_time = toolhead.get_last_move_time()
- stepper_enable = self.printer.lookup_object('stepper_enable')
+ stepper_enable = self.printer.lookup_object("stepper_enable")
enable = stepper_enable.lookup_enable(stepper.get_name())
enable.motor_disable(print_time)
toolhead.dwell(STALL_TIME)
- def manual_move(self, stepper, dist, speed, accel=0.):
- toolhead = self.printer.lookup_object('toolhead')
+
+ def manual_move(self, stepper, dist, speed, accel=0.0):
+ toolhead = self.printer.lookup_object("toolhead")
toolhead.flush_step_generation()
prev_sk = stepper.set_stepper_kinematics(self.stepper_kinematics)
prev_trapq = stepper.set_trapq(self.trapq)
- stepper.set_position((0., 0., 0.))
+ stepper.set_position((0.0, 0.0, 0.0))
axis_r, accel_t, cruise_t, cruise_v = calc_move_time(dist, speed, accel)
print_time = toolhead.get_last_move_time()
- self.trapq_append(self.trapq, print_time, accel_t, cruise_t, accel_t,
- 0., 0., 0., axis_r, 0., 0., 0., cruise_v, accel)
+ self.trapq_append(
+ self.trapq,
+ print_time,
+ accel_t,
+ cruise_t,
+ accel_t,
+ 0.0,
+ 0.0,
+ 0.0,
+ axis_r,
+ 0.0,
+ 0.0,
+ 0.0,
+ cruise_v,
+ accel,
+ )
print_time = print_time + accel_t + cruise_t + accel_t
stepper.generate_steps(print_time)
- self.trapq_finalize_moves(self.trapq, print_time + 99999.9,
- print_time + 99999.9)
+ self.trapq_finalize_moves(
+ self.trapq, print_time + 99999.9, print_time + 99999.9
+ )
stepper.set_trapq(prev_trapq)
stepper.set_stepper_kinematics(prev_sk)
toolhead.note_mcu_movequeue_activity(print_time)
toolhead.dwell(accel_t + cruise_t + accel_t)
toolhead.flush_step_generation()
+
def _lookup_stepper(self, gcmd):
- name = gcmd.get('STEPPER')
+ name = gcmd.get("STEPPER")
if name not in self.steppers:
raise gcmd.error("Unknown stepper %s" % (name,))
return self.steppers[name]
+
cmd_STEPPER_BUZZ_help = "Oscillate a given stepper to help id it"
+
def cmd_STEPPER_BUZZ(self, gcmd):
stepper = self._lookup_stepper(gcmd)
logging.info("Stepper buzz %s", stepper.get_name())
was_enable = self._force_enable(stepper)
- toolhead = self.printer.lookup_object('toolhead')
+ toolhead = self.printer.lookup_object("toolhead")
dist, speed = BUZZ_DISTANCE, BUZZ_VELOCITY
if stepper.units_in_radians():
dist, speed = BUZZ_RADIANS_DISTANCE, BUZZ_RADIANS_VELOCITY
for i in range(10):
self.manual_move(stepper, dist, speed)
- toolhead.dwell(.050)
+ toolhead.dwell(0.050)
self.manual_move(stepper, -dist, speed)
- toolhead.dwell(.450)
+ toolhead.dwell(0.450)
self._restore_enable(stepper, was_enable)
+
cmd_FORCE_MOVE_help = "Manually move a stepper; invalidates kinematics"
+
def cmd_FORCE_MOVE(self, gcmd):
stepper = self._lookup_stepper(gcmd)
- distance = gcmd.get_float('DISTANCE')
- speed = gcmd.get_float('VELOCITY', above=0.)
- accel = gcmd.get_float('ACCEL', 0., minval=0.)
- logging.info("FORCE_MOVE %s distance=%.3f velocity=%.3f accel=%.3f",
- stepper.get_name(), distance, speed, accel)
+ distance = gcmd.get_float("DISTANCE")
+ speed = gcmd.get_float("VELOCITY", above=0.0)
+ accel = gcmd.get_float("ACCEL", 0.0, minval=0.0)
+ logging.info(
+ "FORCE_MOVE %s distance=%.3f velocity=%.3f accel=%.3f",
+ stepper.get_name(),
+ distance,
+ speed,
+ accel,
+ )
self._force_enable(stepper)
self.manual_move(stepper, distance, speed, accel)
+
cmd_SET_KINEMATIC_POSITION_help = "Force a low-level kinematic position"
+
def cmd_SET_KINEMATIC_POSITION(self, gcmd):
- toolhead = self.printer.lookup_object('toolhead')
+ toolhead = self.printer.lookup_object("toolhead")
toolhead.get_last_move_time()
curpos = toolhead.get_position()
- x = gcmd.get_float('X', curpos[0])
- y = gcmd.get_float('Y', curpos[1])
- z = gcmd.get_float('Z', curpos[2])
- set_homed = gcmd.get('SET_HOMED', 'xyz').lower()
+ x = gcmd.get_float("X", curpos[0])
+ y = gcmd.get_float("Y", curpos[1])
+ z = gcmd.get_float("Z", curpos[2])
+ set_homed = gcmd.get("SET_HOMED", "xyz").lower()
set_homed_axes = "".join([a for a in "xyz" if a in set_homed])
- if gcmd.get('CLEAR_HOMED', None) is None:
+ if gcmd.get("CLEAR_HOMED", None) is None:
# "CLEAR" is an alias for "CLEAR_HOMED"; should deprecate
- clear_homed = gcmd.get('CLEAR', '').lower()
+ clear_homed = gcmd.get("CLEAR", "").lower()
else:
- clear_homed = gcmd.get('CLEAR_HOMED', '').lower()
+ clear_homed = gcmd.get("CLEAR_HOMED", "").lower()
clear_homed_axes = "".join([a for a in "xyz" if a in clear_homed])
- logging.info("SET_KINEMATIC_POSITION pos=%.3f,%.3f,%.3f"
- " set_homed=%s clear_homed=%s",
- x, y, z, set_homed_axes, clear_homed_axes)
+ logging.info(
+ "SET_KINEMATIC_POSITION pos=%.3f,%.3f,%.3f" " set_homed=%s clear_homed=%s",
+ x,
+ y,
+ z,
+ set_homed_axes,
+ clear_homed_axes,
+ )
toolhead.set_position([x, y, z], homing_axes=set_homed_axes)
toolhead.get_kinematics().clear_homing_state(clear_homed_axes)
+
def load_config(config):
return ForceMove(config)
diff --git a/klippy/extras/garbage_collection.py b/klippy/extras/garbage_collection.py
index da51df3b..8769251f 100644
--- a/klippy/extras/garbage_collection.py
+++ b/klippy/extras/garbage_collection.py
@@ -6,16 +6,17 @@
import gc
import logging
+
class GarbageCollection:
def __init__(self, config):
self.printer = config.get_printer()
# feature check ... freeze/unfreeze is only available in python 3.7+
- can_freeze = hasattr(gc, 'freeze') and hasattr(gc, 'unfreeze')
+ can_freeze = hasattr(gc, "freeze") and hasattr(gc, "unfreeze")
if can_freeze:
- self.printer.register_event_handler("klippy:ready",
- self._handle_ready)
- self.printer.register_event_handler("klippy:disconnect",
- self._handle_disconnect)
+ self.printer.register_event_handler("klippy:ready", self._handle_ready)
+ self.printer.register_event_handler(
+ "klippy:disconnect", self._handle_disconnect
+ )
def _handle_ready(self):
logging.debug("Running full garbage collection and freezing")
@@ -27,5 +28,6 @@ class GarbageCollection:
logging.debug("Unfreezing garbage collection")
gc.unfreeze()
+
def load_config(config):
return GarbageCollection(config)
diff --git a/klippy/extras/gcode_arcs.py b/klippy/extras/gcode_arcs.py
index 3917dac3..2e13e3af 100644
--- a/klippy/extras/gcode_arcs.py
+++ b/klippy/extras/gcode_arcs.py
@@ -28,10 +28,10 @@ class ArcSupport:
def __init__(self, config):
self.printer = config.get_printer()
- self.mm_per_arc_segment = config.getfloat('resolution', 1., above=0.0)
+ self.mm_per_arc_segment = config.getfloat("resolution", 1.0, above=0.0)
- self.gcode_move = self.printer.load_object(config, 'gcode_move')
- self.gcode = self.printer.lookup_object('gcode')
+ self.gcode_move = self.printer.load_object(config, "gcode_move")
+ self.gcode = self.printer.lookup_object("gcode")
self.gcode.register_command("G2", self.cmd_G2)
self.gcode.register_command("G3", self.cmd_G3)
@@ -59,30 +59,32 @@ class ArcSupport:
def _cmd_inner(self, gcmd, clockwise):
gcodestatus = self.gcode_move.get_status()
- if not gcodestatus['absolute_coordinates']:
+ if not gcodestatus["absolute_coordinates"]:
raise gcmd.error("G2/G3 does not support relative move mode")
- currentPos = gcodestatus['gcode_position']
- absolut_extrude = gcodestatus['absolute_extrude']
+ currentPos = gcodestatus["gcode_position"]
+ absolut_extrude = gcodestatus["absolute_extrude"]
# Parse parameters
- asTarget = [gcmd.get_float("X", currentPos[0]),
- gcmd.get_float("Y", currentPos[1]),
- gcmd.get_float("Z", currentPos[2])]
+ asTarget = [
+ gcmd.get_float("X", currentPos[0]),
+ gcmd.get_float("Y", currentPos[1]),
+ gcmd.get_float("Z", currentPos[2]),
+ ]
if gcmd.get_float("R", None) is not None:
raise gcmd.error("G2/G3 does not support R moves")
# determine the plane coordinates and the helical axis
- I = gcmd.get_float('I', 0.)
- J = gcmd.get_float('J', 0.)
+ I = gcmd.get_float("I", 0.0)
+ J = gcmd.get_float("J", 0.0)
asPlanar = (I, J)
axes = (X_AXIS, Y_AXIS, Z_AXIS)
if self.plane == ARC_PLANE_X_Z:
- K = gcmd.get_float('K', 0.)
+ K = gcmd.get_float("K", 0.0)
asPlanar = (I, K)
axes = (X_AXIS, Z_AXIS, Y_AXIS)
elif self.plane == ARC_PLANE_Y_Z:
- K = gcmd.get_float('K', 0.)
+ K = gcmd.get_float("K", 0.0)
asPlanar = (J, K)
axes = (Y_AXIS, Z_AXIS, X_AXIS)
@@ -90,8 +92,9 @@ class ArcSupport:
raise gcmd.error("G2/G3 requires IJ, IK or JK parameters")
# Build linear coordinates to move
- self.planArc(currentPos, asTarget, asPlanar, clockwise,
- gcmd, absolut_extrude, *axes)
+ self.planArc(
+ currentPos, asTarget, asPlanar, clockwise, gcmd, absolut_extrude, *axes
+ )
# function planArc() originates from marlin plan_arc()
# https://github.com/MarlinFirmware/Marlin
@@ -101,9 +104,18 @@ class ArcSupport:
# Arcs smaller then this value, will be a Line only
#
# alpha and beta axes are the current plane, helical axis is linear travel
- def planArc(self, currentPos, targetPos, offset, clockwise,
- gcmd, absolut_extrude,
- alpha_axis, beta_axis, helical_axis):
+ def planArc(
+ self,
+ currentPos,
+ targetPos,
+ offset,
+ clockwise,
+ gcmd,
+ absolut_extrude,
+ alpha_axis,
+ beta_axis,
+ helical_axis,
+ ):
# todo: sometimes produces full circles
# Radius vector from center to current location
@@ -115,19 +127,22 @@ class ArcSupport:
center_Q = currentPos[beta_axis] - r_Q
rt_Alpha = targetPos[alpha_axis] - center_P
rt_Beta = targetPos[beta_axis] - center_Q
- angular_travel = math.atan2(r_P * rt_Beta - r_Q * rt_Alpha,
- r_P * rt_Alpha + r_Q * rt_Beta)
- if angular_travel < 0.:
- angular_travel += 2. * math.pi
+ angular_travel = math.atan2(
+ r_P * rt_Beta - r_Q * rt_Alpha, r_P * rt_Alpha + r_Q * rt_Beta
+ )
+ if angular_travel < 0.0:
+ angular_travel += 2.0 * math.pi
if clockwise:
- angular_travel -= 2. * math.pi
+ angular_travel -= 2.0 * math.pi
- if (angular_travel == 0.
+ if (
+ angular_travel == 0.0
and currentPos[alpha_axis] == targetPos[alpha_axis]
- and currentPos[beta_axis] == targetPos[beta_axis]):
+ and currentPos[beta_axis] == targetPos[beta_axis]
+ ):
# Make a circle if the angular rotation is 0 and the
# target is current position
- angular_travel = 2. * math.pi
+ angular_travel = 2.0 * math.pi
# Determine number of segments
linear_travel = targetPos[helical_axis] - currentPos[helical_axis]
@@ -137,7 +152,7 @@ class ArcSupport:
mm_of_travel = math.hypot(flat_mm, linear_travel)
else:
mm_of_travel = math.fabs(flat_mm)
- segments = max(1., math.floor(mm_of_travel / self.mm_per_arc_segment))
+ segments = max(1.0, math.floor(mm_of_travel / self.mm_per_arc_segment))
# Generate coordinates
theta_per_segment = angular_travel / segments
@@ -146,7 +161,7 @@ class ArcSupport:
asE = gcmd.get_float("E", None)
asF = gcmd.get_float("F", None)
- e_per_move = e_base = 0.
+ e_per_move = e_base = 0.0
if asE is not None:
if absolut_extrude:
e_base = currentPos[3]
@@ -165,19 +180,19 @@ class ArcSupport:
c[beta_axis] = center_Q + r_Q
c[helical_axis] = currentPos[helical_axis] + dist_Helical
-
if i == segments:
c = targetPos
# Convert coords into G1 commands
- g1_params = {'X': c[0], 'Y': c[1], 'Z': c[2]}
+ g1_params = {"X": c[0], "Y": c[1], "Z": c[2]}
if e_per_move:
- g1_params['E'] = e_base + e_per_move
+ g1_params["E"] = e_base + e_per_move
if absolut_extrude:
e_base += e_per_move
if asF is not None:
- g1_params['F'] = asF
+ g1_params["F"] = asF
g1_gcmd = self.gcode.create_gcode_command("G1", "G1", g1_params)
self.gcode_move.cmd_G1(g1_gcmd)
+
def load_config(config):
return ArcSupport(config)
diff --git a/klippy/extras/gcode_button.py b/klippy/extras/gcode_button.py
index de280c98..7ab542c6 100644
--- a/klippy/extras/gcode_button.py
+++ b/klippy/extras/gcode_button.py
@@ -9,30 +9,34 @@ import logging
class GCodeButton:
def __init__(self, config):
self.printer = config.get_printer()
- self.name = config.get_name().split(' ')[-1]
- self.pin = config.get('pin')
+ self.name = config.get_name().split(" ")[-1]
+ self.pin = config.get("pin")
self.last_state = 0
buttons = self.printer.load_object(config, "buttons")
- if config.get('analog_range', None) is None:
- buttons.register_debounce_button(self.pin, self.button_callback
- , config)
+ if config.get("analog_range", None) is None:
+ buttons.register_debounce_button(self.pin, self.button_callback, config)
else:
- amin, amax = config.getfloatlist('analog_range', count=2)
- pullup = config.getfloat('analog_pullup_resistor', 4700., above=0.)
- buttons.register_debounce_adc_button(self.pin, amin, amax, pullup,
- self.button_callback, config)
- gcode_macro = self.printer.load_object(config, 'gcode_macro')
- self.press_template = gcode_macro.load_template(config, 'press_gcode')
- self.release_template = gcode_macro.load_template(config,
- 'release_gcode', '')
- self.gcode = self.printer.lookup_object('gcode')
- self.gcode.register_mux_command("QUERY_BUTTON", "BUTTON", self.name,
- self.cmd_QUERY_BUTTON,
- desc=self.cmd_QUERY_BUTTON_help)
+ amin, amax = config.getfloatlist("analog_range", count=2)
+ pullup = config.getfloat("analog_pullup_resistor", 4700.0, above=0.0)
+ buttons.register_debounce_adc_button(
+ self.pin, amin, amax, pullup, self.button_callback, config
+ )
+ gcode_macro = self.printer.load_object(config, "gcode_macro")
+ self.press_template = gcode_macro.load_template(config, "press_gcode")
+ self.release_template = gcode_macro.load_template(config, "release_gcode", "")
+ self.gcode = self.printer.lookup_object("gcode")
+ self.gcode.register_mux_command(
+ "QUERY_BUTTON",
+ "BUTTON",
+ self.name,
+ self.cmd_QUERY_BUTTON,
+ desc=self.cmd_QUERY_BUTTON_help,
+ )
cmd_QUERY_BUTTON_help = "Report on the state of a button"
+
def cmd_QUERY_BUTTON(self, gcmd):
- gcmd.respond_info(self.name + ": " + self.get_status()['state'])
+ gcmd.respond_info(self.name + ": " + self.get_status()["state"])
def button_callback(self, eventtime, state):
self.last_state = state
@@ -46,8 +50,9 @@ class GCodeButton:
def get_status(self, eventtime=None):
if self.last_state:
- return {'state': "PRESSED"}
- return {'state': "RELEASED"}
+ return {"state": "PRESSED"}
+ return {"state": "RELEASED"}
+
def load_config_prefix(config):
return GCodeButton(config)
diff --git a/klippy/extras/gcode_macro.py b/klippy/extras/gcode_macro.py
index 3aa5087b..ddcdc7c9 100644
--- a/klippy/extras/gcode_macro.py
+++ b/klippy/extras/gcode_macro.py
@@ -11,55 +11,67 @@ import jinja2
# Template handling
######################################################################
+
# Wrapper for access to printer object get_status() methods
class GetStatusWrapper:
def __init__(self, printer, eventtime=None):
self.printer = printer
self.eventtime = eventtime
self.cache = {}
+
def __getitem__(self, val):
sval = str(val).strip()
if sval in self.cache:
return self.cache[sval]
po = self.printer.lookup_object(sval, None)
- if po is None or not hasattr(po, 'get_status'):
+ if po is None or not hasattr(po, "get_status"):
raise KeyError(val)
if self.eventtime is None:
self.eventtime = self.printer.get_reactor().monotonic()
self.cache[sval] = res = copy.deepcopy(po.get_status(self.eventtime))
return res
+
def __contains__(self, val):
try:
self.__getitem__(val)
except KeyError as e:
return False
return True
+
def __iter__(self):
for name, obj in self.printer.lookup_objects():
if self.__contains__(name):
yield name
+
# Wrapper around a Jinja2 template
class TemplateWrapper:
def __init__(self, printer, env, name, script):
self.printer = printer
self.name = name
- self.gcode = self.printer.lookup_object('gcode')
- gcode_macro = self.printer.lookup_object('gcode_macro')
+ self.gcode = self.printer.lookup_object("gcode")
+ gcode_macro = self.printer.lookup_object("gcode_macro")
self.create_template_context = gcode_macro.create_template_context
try:
self.template = env.from_string(script)
except jinja2.exceptions.TemplateSyntaxError as e:
lines = script.splitlines()
msg = "Error loading template '%s'\nline %s: %s # %s" % (
- name, e.lineno, lines[e.lineno-1], e.message)
+ name,
+ e.lineno,
+ lines[e.lineno - 1],
+ e.message,
+ )
logging.exception(msg)
raise self.gcode.error(msg)
except Exception as e:
msg = "Error loading template '%s': %s" % (
- name, traceback.format_exception_only(type(e), e)[-1])
+ name,
+ traceback.format_exception_only(type(e), e)[-1],
+ )
logging.exception(msg)
raise printer.config_error(msg)
+
def render(self, context=None):
if context is None:
context = self.create_template_context()
@@ -67,17 +79,22 @@ class TemplateWrapper:
return str(self.template.render(context))
except Exception as e:
msg = "Error evaluating '%s': %s" % (
- self.name, traceback.format_exception_only(type(e), e)[-1])
+ self.name,
+ traceback.format_exception_only(type(e), e)[-1],
+ )
logging.exception(msg)
raise self.gcode.error(msg)
+
def run_gcode_from_command(self, context=None):
self.gcode.run_script_from_command(self.render(context))
+
# Main gcode macro template tracking
class PrinterGCodeMacro:
def __init__(self, config):
self.printer = config.get_printer()
- self.env = jinja2.Environment('{%', '%}', '{', '}')
+ self.env = jinja2.Environment("{%", "%}", "{", "}")
+
def load_template(self, config, option, default=None):
name = "%s:%s" % (config.get_name(), option)
if default is None:
@@ -85,30 +102,36 @@ class PrinterGCodeMacro:
else:
script = config.get(option, default)
return TemplateWrapper(self.printer, self.env, name, script)
+
def _action_emergency_stop(self, msg="action_emergency_stop"):
self.printer.invoke_shutdown("Shutdown due to %s" % (msg,))
return ""
+
def _action_respond_info(self, msg):
- self.printer.lookup_object('gcode').respond_info(msg)
+ self.printer.lookup_object("gcode").respond_info(msg)
return ""
+
def _action_raise_error(self, msg):
raise self.printer.command_error(msg)
+
def _action_call_remote_method(self, method, **kwargs):
- webhooks = self.printer.lookup_object('webhooks')
+ webhooks = self.printer.lookup_object("webhooks")
try:
webhooks.call_remote_method(method, **kwargs)
except self.printer.command_error:
logging.exception("Remote Call Error")
return ""
+
def create_template_context(self, eventtime=None):
return {
- 'printer': GetStatusWrapper(self.printer, eventtime),
- 'action_emergency_stop': self._action_emergency_stop,
- 'action_respond_info': self._action_respond_info,
- 'action_raise_error': self._action_raise_error,
- 'action_call_remote_method': self._action_call_remote_method,
+ "printer": GetStatusWrapper(self.printer, eventtime),
+ "action_emergency_stop": self._action_emergency_stop,
+ "action_respond_info": self._action_respond_info,
+ "action_raise_error": self._action_raise_error,
+ "action_call_remote_method": self._action_call_remote_method,
}
+
def load_config(config):
return PrinterGCodeMacro(config)
@@ -117,84 +140,98 @@ def load_config(config):
# GCode macro
######################################################################
+
class GCodeMacro:
def __init__(self, config):
if len(config.get_name().split()) > 2:
raise config.error(
- "Name of section '%s' contains illegal whitespace"
- % (config.get_name()))
+ "Name of section '%s' contains illegal whitespace" % (config.get_name())
+ )
name = config.get_name().split()[1]
self.alias = name.upper()
self.printer = printer = config.get_printer()
- gcode_macro = printer.load_object(config, 'gcode_macro')
- self.template = gcode_macro.load_template(config, 'gcode')
- self.gcode = printer.lookup_object('gcode')
+ gcode_macro = printer.load_object(config, "gcode_macro")
+ self.template = gcode_macro.load_template(config, "gcode")
+ self.gcode = printer.lookup_object("gcode")
self.rename_existing = config.get("rename_existing", None)
self.cmd_desc = config.get("description", "G-Code macro")
if self.rename_existing is not None:
- if (self.gcode.is_traditional_gcode(self.alias)
- != self.gcode.is_traditional_gcode(self.rename_existing)):
+ if self.gcode.is_traditional_gcode(
+ self.alias
+ ) != self.gcode.is_traditional_gcode(self.rename_existing):
raise config.error(
"G-Code macro rename of different types ('%s' vs '%s')"
- % (self.alias, self.rename_existing))
- printer.register_event_handler("klippy:connect",
- self.handle_connect)
+ % (self.alias, self.rename_existing)
+ )
+ printer.register_event_handler("klippy:connect", self.handle_connect)
else:
- self.gcode.register_command(self.alias, self.cmd,
- desc=self.cmd_desc)
- self.gcode.register_mux_command("SET_GCODE_VARIABLE", "MACRO",
- name, self.cmd_SET_GCODE_VARIABLE,
- desc=self.cmd_SET_GCODE_VARIABLE_help)
+ self.gcode.register_command(self.alias, self.cmd, desc=self.cmd_desc)
+ self.gcode.register_mux_command(
+ "SET_GCODE_VARIABLE",
+ "MACRO",
+ name,
+ self.cmd_SET_GCODE_VARIABLE,
+ desc=self.cmd_SET_GCODE_VARIABLE_help,
+ )
self.in_script = False
self.variables = {}
- prefix = 'variable_'
+ prefix = "variable_"
for option in config.get_prefix_options(prefix):
try:
literal = ast.literal_eval(config.get(option))
- json.dumps(literal, separators=(',', ':'))
- self.variables[option[len(prefix):]] = literal
+ json.dumps(literal, separators=(",", ":"))
+ self.variables[option[len(prefix) :]] = literal
except (SyntaxError, TypeError, ValueError) as e:
raise config.error(
- "Option '%s' in section '%s' is not a valid literal: %s" % (
- option, config.get_name(), e))
+ "Option '%s' in section '%s' is not a valid literal: %s"
+ % (option, config.get_name(), e)
+ )
+
def handle_connect(self):
prev_cmd = self.gcode.register_command(self.alias, None)
if prev_cmd is None:
raise self.printer.config_error(
- "Existing command '%s' not found in gcode_macro rename"
- % (self.alias,))
+ "Existing command '%s' not found in gcode_macro rename" % (self.alias,)
+ )
pdesc = "Renamed builtin of '%s'" % (self.alias,)
self.gcode.register_command(self.rename_existing, prev_cmd, desc=pdesc)
self.gcode.register_command(self.alias, self.cmd, desc=self.cmd_desc)
+
def get_status(self, eventtime):
return self.variables
+
cmd_SET_GCODE_VARIABLE_help = "Set the value of a G-Code macro variable"
+
def cmd_SET_GCODE_VARIABLE(self, gcmd):
- variable = gcmd.get('VARIABLE')
- value = gcmd.get('VALUE')
+ variable = gcmd.get("VARIABLE")
+ value = gcmd.get("VALUE")
if variable not in self.variables:
raise gcmd.error("Unknown gcode_macro variable '%s'" % (variable,))
try:
literal = ast.literal_eval(value)
- json.dumps(literal, separators=(',', ':'))
+ json.dumps(literal, separators=(",", ":"))
except (SyntaxError, TypeError, ValueError) as e:
- raise gcmd.error("Unable to parse '%s' as a literal: %s in '%s'" %
- (value, e, gcmd.get_commandline()))
+ raise gcmd.error(
+ "Unable to parse '%s' as a literal: %s in '%s'"
+ % (value, e, gcmd.get_commandline())
+ )
v = dict(self.variables)
v[variable] = literal
self.variables = v
+
def cmd(self, gcmd):
if self.in_script:
raise gcmd.error("Macro %s called recursively" % (self.alias,))
kwparams = dict(self.variables)
kwparams.update(self.template.create_template_context())
- kwparams['params'] = gcmd.get_command_parameters()
- kwparams['rawparams'] = gcmd.get_raw_command_parameters()
+ kwparams["params"] = gcmd.get_command_parameters()
+ kwparams["rawparams"] = gcmd.get_raw_command_parameters()
self.in_script = True
try:
self.template.run_gcode_from_command(kwparams)
finally:
self.in_script = False
+
def load_config_prefix(config):
return GCodeMacro(config)
diff --git a/klippy/extras/gcode_move.py b/klippy/extras/gcode_move.py
index 94a0ce42..8a49cc30 100644
--- a/klippy/extras/gcode_move.py
+++ b/klippy/extras/gcode_move.py
@@ -5,116 +5,146 @@
# This file may be distributed under the terms of the GNU GPLv3 license.
import logging
+
class GCodeMove:
def __init__(self, config):
self.printer = printer = config.get_printer()
printer.register_event_handler("klippy:ready", self._handle_ready)
printer.register_event_handler("klippy:shutdown", self._handle_shutdown)
- printer.register_event_handler("toolhead:set_position",
- self.reset_last_position)
- printer.register_event_handler("toolhead:manual_move",
- self.reset_last_position)
- printer.register_event_handler("toolhead:update_extra_axes",
- self._update_extra_axes)
- printer.register_event_handler("gcode:command_error",
- self.reset_last_position)
- printer.register_event_handler("extruder:activate_extruder",
- self._handle_activate_extruder)
- printer.register_event_handler("homing:home_rails_end",
- self._handle_home_rails_end)
+ printer.register_event_handler(
+ "toolhead:set_position", self.reset_last_position
+ )
+ printer.register_event_handler("toolhead:manual_move", self.reset_last_position)
+ printer.register_event_handler(
+ "toolhead:update_extra_axes", self._update_extra_axes
+ )
+ printer.register_event_handler("gcode:command_error", self.reset_last_position)
+ printer.register_event_handler(
+ "extruder:activate_extruder", self._handle_activate_extruder
+ )
+ printer.register_event_handler(
+ "homing:home_rails_end", self._handle_home_rails_end
+ )
self.is_printer_ready = False
# Register g-code commands
- gcode = printer.lookup_object('gcode')
+ gcode = printer.lookup_object("gcode")
handlers = [
- 'G1', 'G20', 'G21',
- 'M82', 'M83', 'G90', 'G91', 'G92', 'M220', 'M221',
- 'SET_GCODE_OFFSET', 'SAVE_GCODE_STATE', 'RESTORE_GCODE_STATE',
+ "G1",
+ "G20",
+ "G21",
+ "M82",
+ "M83",
+ "G90",
+ "G91",
+ "G92",
+ "M220",
+ "M221",
+ "SET_GCODE_OFFSET",
+ "SAVE_GCODE_STATE",
+ "RESTORE_GCODE_STATE",
]
for cmd in handlers:
- func = getattr(self, 'cmd_' + cmd)
- desc = getattr(self, 'cmd_' + cmd + '_help', None)
+ func = getattr(self, "cmd_" + cmd)
+ desc = getattr(self, "cmd_" + cmd + "_help", None)
gcode.register_command(cmd, func, False, desc)
- gcode.register_command('G0', self.cmd_G1)
- gcode.register_command('M114', self.cmd_M114, True)
- gcode.register_command('GET_POSITION', self.cmd_GET_POSITION, True,
- desc=self.cmd_GET_POSITION_help)
+ gcode.register_command("G0", self.cmd_G1)
+ gcode.register_command("M114", self.cmd_M114, True)
+ gcode.register_command(
+ "GET_POSITION", self.cmd_GET_POSITION, True, desc=self.cmd_GET_POSITION_help
+ )
self.Coord = gcode.Coord
# G-Code coordinate manipulation
self.absolute_coord = self.absolute_extrude = True
self.base_position = [0.0, 0.0, 0.0, 0.0]
self.last_position = [0.0, 0.0, 0.0, 0.0]
self.homing_position = [0.0, 0.0, 0.0, 0.0]
- self.axis_map = {'X':0, 'Y': 1, 'Z': 2, 'E': 3}
- self.speed = 25.
- self.speed_factor = 1. / 60.
- self.extrude_factor = 1.
+ self.axis_map = {"X": 0, "Y": 1, "Z": 2, "E": 3}
+ self.speed = 25.0
+ self.speed_factor = 1.0 / 60.0
+ self.extrude_factor = 1.0
# G-Code state
self.saved_states = {}
self.move_transform = self.move_with_transform = None
- self.position_with_transform = (lambda: [0., 0., 0., 0.])
+ self.position_with_transform = lambda: [0.0, 0.0, 0.0, 0.0]
+
def _handle_ready(self):
self.is_printer_ready = True
if self.move_transform is None:
- toolhead = self.printer.lookup_object('toolhead')
+ toolhead = self.printer.lookup_object("toolhead")
self.move_with_transform = toolhead.move
self.position_with_transform = toolhead.get_position
self.reset_last_position()
+
def _handle_shutdown(self):
if not self.is_printer_ready:
return
self.is_printer_ready = False
- logging.info("gcode state: absolute_coord=%s absolute_extrude=%s"
- " base_position=%s last_position=%s homing_position=%s"
- " speed_factor=%s extrude_factor=%s speed=%s",
- self.absolute_coord, self.absolute_extrude,
- self.base_position, self.last_position,
- self.homing_position, self.speed_factor,
- self.extrude_factor, self.speed)
+ logging.info(
+ "gcode state: absolute_coord=%s absolute_extrude=%s"
+ " base_position=%s last_position=%s homing_position=%s"
+ " speed_factor=%s extrude_factor=%s speed=%s",
+ self.absolute_coord,
+ self.absolute_extrude,
+ self.base_position,
+ self.last_position,
+ self.homing_position,
+ self.speed_factor,
+ self.extrude_factor,
+ self.speed,
+ )
+
def _handle_activate_extruder(self):
self.reset_last_position()
- self.extrude_factor = 1.
+ self.extrude_factor = 1.0
self.base_position[3] = self.last_position[3]
+
def _handle_home_rails_end(self, homing_state, rails):
self.reset_last_position()
for axis in homing_state.get_axes():
self.base_position[axis] = self.homing_position[axis]
+
def set_move_transform(self, transform, force=False):
if self.move_transform is not None and not force:
- raise self.printer.config_error(
- "G-Code move transform already specified")
+ raise self.printer.config_error("G-Code move transform already specified")
old_transform = self.move_transform
if old_transform is None:
- old_transform = self.printer.lookup_object('toolhead', None)
+ old_transform = self.printer.lookup_object("toolhead", None)
self.move_transform = transform
self.move_with_transform = transform.move
self.position_with_transform = transform.get_position
return old_transform
+
def _get_gcode_position(self):
p = [lp - bp for lp, bp in zip(self.last_position, self.base_position)]
p[3] /= self.extrude_factor
return p
+
def _get_gcode_speed(self):
return self.speed / self.speed_factor
+
def _get_gcode_speed_override(self):
- return self.speed_factor * 60.
+ return self.speed_factor * 60.0
+
def get_status(self, eventtime=None):
move_position = self._get_gcode_position()
return {
- 'speed_factor': self._get_gcode_speed_override(),
- 'speed': self._get_gcode_speed(),
- 'extrude_factor': self.extrude_factor,
- 'absolute_coordinates': self.absolute_coord,
- 'absolute_extrude': self.absolute_extrude,
- 'homing_origin': self.Coord(*self.homing_position[:4]),
- 'position': self.Coord(*self.last_position[:4]),
- 'gcode_position': self.Coord(*move_position[:4]),
+ "speed_factor": self._get_gcode_speed_override(),
+ "speed": self._get_gcode_speed(),
+ "extrude_factor": self.extrude_factor,
+ "absolute_coordinates": self.absolute_coord,
+ "absolute_extrude": self.absolute_extrude,
+ "homing_origin": self.Coord(*self.homing_position[:4]),
+ "position": self.Coord(*self.last_position[:4]),
+ "gcode_position": self.Coord(*move_position[:4]),
}
+
def reset_last_position(self):
if self.is_printer_ready:
self.last_position = self.position_with_transform()
+
def _update_extra_axes(self):
- toolhead = self.printer.lookup_object('toolhead')
- axis_map = {'X':0, 'Y': 1, 'Z': 2, 'E': 3}
+ toolhead = self.printer.lookup_object("toolhead")
+ axis_map = {"X": 0, "Y": 1, "Z": 2, "E": 3}
extra_axes = toolhead.get_extra_axes()
for index, ea in enumerate(extra_axes):
if ea is None:
@@ -124,8 +154,9 @@ class GCodeMove:
continue
axis_map[gcode_id] = index
self.axis_map = axis_map
- self.base_position[4:] = [0.] * (len(extra_axes) - 4)
+ self.base_position[4:] = [0.0] * (len(extra_axes) - 4)
self.reset_last_position()
+
# G-Code movement commands
def cmd_G1(self, gcmd):
# Move
@@ -135,7 +166,7 @@ class GCodeMove:
if axis in params:
v = float(params[axis])
absolute_coord = self.absolute_coord
- if axis == 'E':
+ if axis == "E":
v *= self.extrude_factor
if not self.absolute_extrude:
absolute_coord = False
@@ -145,38 +176,45 @@ class GCodeMove:
else:
# value relative to base coordinate position
self.last_position[pos] = v + self.base_position[pos]
- if 'F' in params:
- gcode_speed = float(params['F'])
- if gcode_speed <= 0.:
- raise gcmd.error("Invalid speed in '%s'"
- % (gcmd.get_commandline(),))
+ if "F" in params:
+ gcode_speed = float(params["F"])
+ if gcode_speed <= 0.0:
+ raise gcmd.error(
+ "Invalid speed in '%s'" % (gcmd.get_commandline(),)
+ )
self.speed = gcode_speed * self.speed_factor
except ValueError as e:
- raise gcmd.error("Unable to parse move '%s'"
- % (gcmd.get_commandline(),))
+ raise gcmd.error("Unable to parse move '%s'" % (gcmd.get_commandline(),))
self.move_with_transform(self.last_position, self.speed)
+
# G-Code coordinate manipulation
def cmd_G20(self, gcmd):
# Set units to inches
- raise gcmd.error('Machine does not support G20 (inches) command')
+ raise gcmd.error("Machine does not support G20 (inches) command")
+
def cmd_G21(self, gcmd):
# Set units to millimeters
pass
+
def cmd_M82(self, gcmd):
# Use absolute distances for extrusion
self.absolute_extrude = True
+
def cmd_M83(self, gcmd):
# Use relative distances for extrusion
self.absolute_extrude = False
+
def cmd_G90(self, gcmd):
# Use absolute coordinates
self.absolute_coord = True
+
def cmd_G91(self, gcmd):
# Use relative coordinates
self.absolute_coord = False
+
def cmd_G92(self, gcmd):
# Set position
- offsets = [ gcmd.get_float(a, None) for a in 'XYZE' ]
+ offsets = [gcmd.get_float(a, None) for a in "XYZE"]
for i, offset in enumerate(offsets):
if offset is not None:
if i == 3:
@@ -184,29 +222,34 @@ class GCodeMove:
self.base_position[i] = self.last_position[i] - offset
if offsets == [None, None, None, None]:
self.base_position[:4] = self.last_position[:4]
+
def cmd_M114(self, gcmd):
# Get Current Position
p = self._get_gcode_position()
gcmd.respond_raw("X:%.3f Y:%.3f Z:%.3f E:%.3f" % tuple(p))
+
def cmd_M220(self, gcmd):
# Set speed factor override percentage
- value = gcmd.get_float('S', 100., above=0.) / (60. * 100.)
+ value = gcmd.get_float("S", 100.0, above=0.0) / (60.0 * 100.0)
self.speed = self._get_gcode_speed() * value
self.speed_factor = value
+
def cmd_M221(self, gcmd):
# Set extrude factor override percentage
- new_extrude_factor = gcmd.get_float('S', 100., above=0.) / 100.
+ new_extrude_factor = gcmd.get_float("S", 100.0, above=0.0) / 100.0
last_e_pos = self.last_position[3]
e_value = (last_e_pos - self.base_position[3]) / self.extrude_factor
self.base_position[3] = last_e_pos - e_value * new_extrude_factor
self.extrude_factor = new_extrude_factor
+
cmd_SET_GCODE_OFFSET_help = "Set a virtual offset to g-code positions"
+
def cmd_SET_GCODE_OFFSET(self, gcmd):
- move_delta = [0., 0., 0., 0.]
- for pos, axis in enumerate('XYZE'):
+ move_delta = [0.0, 0.0, 0.0, 0.0]
+ for pos, axis in enumerate("XYZE"):
offset = gcmd.get_float(axis, None)
if offset is None:
- offset = gcmd.get_float(axis + '_ADJUST', None)
+ offset = gcmd.get_float(axis + "_ADJUST", None)
if offset is None:
continue
offset += self.homing_position[pos]
@@ -215,76 +258,97 @@ class GCodeMove:
self.base_position[pos] += delta
self.homing_position[pos] = offset
# Move the toolhead the given offset if requested
- if gcmd.get_int('MOVE', 0):
- speed = gcmd.get_float('MOVE_SPEED', self.speed, above=0.)
+ if gcmd.get_int("MOVE", 0):
+ speed = gcmd.get_float("MOVE_SPEED", self.speed, above=0.0)
for pos, delta in enumerate(move_delta):
self.last_position[pos] += delta
self.move_with_transform(self.last_position, speed)
+
cmd_SAVE_GCODE_STATE_help = "Save G-Code coordinate state"
+
def cmd_SAVE_GCODE_STATE(self, gcmd):
- state_name = gcmd.get('NAME', 'default')
+ state_name = gcmd.get("NAME", "default")
self.saved_states[state_name] = {
- 'absolute_coord': self.absolute_coord,
- 'absolute_extrude': self.absolute_extrude,
- 'base_position': list(self.base_position),
- 'last_position': list(self.last_position),
- 'homing_position': list(self.homing_position),
- 'speed': self.speed, 'speed_factor': self.speed_factor,
- 'extrude_factor': self.extrude_factor,
+ "absolute_coord": self.absolute_coord,
+ "absolute_extrude": self.absolute_extrude,
+ "base_position": list(self.base_position),
+ "last_position": list(self.last_position),
+ "homing_position": list(self.homing_position),
+ "speed": self.speed,
+ "speed_factor": self.speed_factor,
+ "extrude_factor": self.extrude_factor,
}
+
cmd_RESTORE_GCODE_STATE_help = "Restore a previously saved G-Code state"
+
def cmd_RESTORE_GCODE_STATE(self, gcmd):
- state_name = gcmd.get('NAME', 'default')
+ state_name = gcmd.get("NAME", "default")
state = self.saved_states.get(state_name)
if state is None:
raise gcmd.error("Unknown g-code state: %s" % (state_name,))
# Restore state
- self.absolute_coord = state['absolute_coord']
- self.absolute_extrude = state['absolute_extrude']
- self.base_position[:4] = state['base_position'][:4]
- self.homing_position = list(state['homing_position'])
- self.speed = state['speed']
- self.speed_factor = state['speed_factor']
- self.extrude_factor = state['extrude_factor']
+ self.absolute_coord = state["absolute_coord"]
+ self.absolute_extrude = state["absolute_extrude"]
+ self.base_position[:4] = state["base_position"][:4]
+ self.homing_position = list(state["homing_position"])
+ self.speed = state["speed"]
+ self.speed_factor = state["speed_factor"]
+ self.extrude_factor = state["extrude_factor"]
# Restore the relative E position
- e_diff = self.last_position[3] - state['last_position'][3]
+ e_diff = self.last_position[3] - state["last_position"][3]
self.base_position[3] += e_diff
# Move the toolhead back if requested
- if gcmd.get_int('MOVE', 0):
- speed = gcmd.get_float('MOVE_SPEED', self.speed, above=0.)
- self.last_position[:3] = state['last_position'][:3]
+ if gcmd.get_int("MOVE", 0):
+ speed = gcmd.get_float("MOVE_SPEED", self.speed, above=0.0)
+ self.last_position[:3] = state["last_position"][:3]
self.move_with_transform(self.last_position, speed)
- cmd_GET_POSITION_help = (
- "Return information on the current location of the toolhead")
+
+ cmd_GET_POSITION_help = "Return information on the current location of the toolhead"
+
def cmd_GET_POSITION(self, gcmd):
- toolhead = self.printer.lookup_object('toolhead', None)
+ toolhead = self.printer.lookup_object("toolhead", None)
if toolhead is None:
raise gcmd.error("Printer not ready")
kin = toolhead.get_kinematics()
steppers = kin.get_steppers()
- mcu_pos = " ".join(["%s:%d" % (s.get_name(), s.get_mcu_position())
- for s in steppers])
+ mcu_pos = " ".join(
+ ["%s:%d" % (s.get_name(), s.get_mcu_position()) for s in steppers]
+ )
cinfo = [(s.get_name(), s.get_commanded_position()) for s in steppers]
stepper_pos = " ".join(["%s:%.6f" % (a, v) for a, v in cinfo])
kinfo = zip("XYZ", kin.calc_position(dict(cinfo)))
kin_pos = " ".join(["%s:%.6f" % (a, v) for a, v in kinfo])
- toolhead_pos = " ".join(["%s:%.6f" % (a, v) for a, v in zip(
- "XYZE", toolhead.get_position()[:4])])
- gcode_pos = " ".join(["%s:%.6f" % (a, v)
- for a, v in zip("XYZE", self.last_position)])
- base_pos = " ".join(["%s:%.6f" % (a, v)
- for a, v in zip("XYZE", self.base_position)])
- homing_pos = " ".join(["%s:%.6f" % (a, v)
- for a, v in zip("XYZ", self.homing_position)])
- gcmd.respond_info("mcu: %s\n"
- "stepper: %s\n"
- "kinematic: %s\n"
- "toolhead: %s\n"
- "gcode: %s\n"
- "gcode base: %s\n"
- "gcode homing: %s"
- % (mcu_pos, stepper_pos, kin_pos, toolhead_pos,
- gcode_pos, base_pos, homing_pos))
+ toolhead_pos = " ".join(
+ ["%s:%.6f" % (a, v) for a, v in zip("XYZE", toolhead.get_position()[:4])]
+ )
+ gcode_pos = " ".join(
+ ["%s:%.6f" % (a, v) for a, v in zip("XYZE", self.last_position)]
+ )
+ base_pos = " ".join(
+ ["%s:%.6f" % (a, v) for a, v in zip("XYZE", self.base_position)]
+ )
+ homing_pos = " ".join(
+ ["%s:%.6f" % (a, v) for a, v in zip("XYZ", self.homing_position)]
+ )
+ gcmd.respond_info(
+ "mcu: %s\n"
+ "stepper: %s\n"
+ "kinematic: %s\n"
+ "toolhead: %s\n"
+ "gcode: %s\n"
+ "gcode base: %s\n"
+ "gcode homing: %s"
+ % (
+ mcu_pos,
+ stepper_pos,
+ kin_pos,
+ toolhead_pos,
+ gcode_pos,
+ base_pos,
+ homing_pos,
+ )
+ )
+
def load_config(config):
return GCodeMove(config)
diff --git a/klippy/extras/hall_filament_width_sensor.py b/klippy/extras/hall_filament_width_sensor.py
index 865e49af..75fea987 100644
--- a/klippy/extras/hall_filament_width_sensor.py
+++ b/klippy/extras/hall_filament_width_sensor.py
@@ -9,34 +9,35 @@ ADC_REPORT_TIME = 0.500
ADC_SAMPLE_TIME = 0.03
ADC_SAMPLE_COUNT = 15
+
class HallFilamentWidthSensor:
def __init__(self, config):
self.printer = config.get_printer()
self.reactor = self.printer.get_reactor()
- self.pin1 = config.get('adc1')
- self.pin2 = config.get('adc2')
- self.dia1=config.getfloat('Cal_dia1', 1.5)
- self.dia2=config.getfloat('Cal_dia2', 2.0)
- self.rawdia1=config.getint('Raw_dia1', 9500)
- self.rawdia2=config.getint('Raw_dia2', 10500)
- self.MEASUREMENT_INTERVAL_MM=config.getint('measurement_interval',10)
+ self.pin1 = config.get("adc1")
+ self.pin2 = config.get("adc2")
+ self.dia1 = config.getfloat("Cal_dia1", 1.5)
+ self.dia2 = config.getfloat("Cal_dia2", 2.0)
+ self.rawdia1 = config.getint("Raw_dia1", 9500)
+ self.rawdia2 = config.getint("Raw_dia2", 10500)
+ self.MEASUREMENT_INTERVAL_MM = config.getint("measurement_interval", 10)
self.nominal_filament_dia = config.getfloat(
- 'default_nominal_filament_diameter', above=1)
- self.measurement_delay = config.getfloat('measurement_delay', above=0.)
- self.measurement_max_difference = config.getfloat('max_difference', 0.2)
- self.max_diameter = (self.nominal_filament_dia
- + self.measurement_max_difference)
- self.min_diameter = (self.nominal_filament_dia
- - self.measurement_max_difference)
- self.diameter =self.nominal_filament_dia
- self.is_active =config.getboolean('enable', False)
- self.runout_dia_min=config.getfloat('min_diameter', 1.0)
- self.runout_dia_max=config.getfloat('max_diameter', self.max_diameter)
- self.is_log =config.getboolean('logging', False)
+ "default_nominal_filament_diameter", above=1
+ )
+ self.measurement_delay = config.getfloat("measurement_delay", above=0.0)
+ self.measurement_max_difference = config.getfloat("max_difference", 0.2)
+ self.max_diameter = self.nominal_filament_dia + self.measurement_max_difference
+ self.min_diameter = self.nominal_filament_dia - self.measurement_max_difference
+ self.diameter = self.nominal_filament_dia
+ self.is_active = config.getboolean("enable", False)
+ self.runout_dia_min = config.getfloat("min_diameter", 1.0)
+ self.runout_dia_max = config.getfloat("max_diameter", self.max_diameter)
+ self.is_log = config.getboolean("logging", False)
# Use the current diameter instead of nominal while the first
# measurement isn't in place
self.use_current_dia_while_delay = config.getboolean(
- 'use_current_dia_while_delay', False)
+ "use_current_dia_while_delay", False
+ )
# filament array [position, filamentWidth]
self.filament_array = []
self.lastFilamentWidthReading = 0
@@ -47,41 +48,38 @@ class HallFilamentWidthSensor:
self.toolhead = self.ppins = self.mcu_adc = None
self.printer.register_event_handler("klippy:ready", self.handle_ready)
# Start adc
- self.ppins = self.printer.lookup_object('pins')
- self.mcu_adc = self.ppins.setup_pin('adc', self.pin1)
+ self.ppins = self.printer.lookup_object("pins")
+ self.mcu_adc = self.ppins.setup_pin("adc", self.pin1)
self.mcu_adc.setup_adc_sample(ADC_SAMPLE_TIME, ADC_SAMPLE_COUNT)
self.mcu_adc.setup_adc_callback(ADC_REPORT_TIME, self.adc_callback)
- self.mcu_adc2 = self.ppins.setup_pin('adc', self.pin2)
+ self.mcu_adc2 = self.ppins.setup_pin("adc", self.pin2)
self.mcu_adc2.setup_adc_sample(ADC_SAMPLE_TIME, ADC_SAMPLE_COUNT)
self.mcu_adc2.setup_adc_callback(ADC_REPORT_TIME, self.adc2_callback)
# extrude factor updating
self.extrude_factor_update_timer = self.reactor.register_timer(
- self.extrude_factor_update_event)
+ self.extrude_factor_update_event
+ )
# Register commands
- self.gcode = self.printer.lookup_object('gcode')
- self.gcode.register_command('QUERY_FILAMENT_WIDTH', self.cmd_M407)
- self.gcode.register_command('RESET_FILAMENT_WIDTH_SENSOR',
- self.cmd_ClearFilamentArray)
- self.gcode.register_command('DISABLE_FILAMENT_WIDTH_SENSOR',
- self.cmd_M406)
- self.gcode.register_command('ENABLE_FILAMENT_WIDTH_SENSOR',
- self.cmd_M405)
- self.gcode.register_command('QUERY_RAW_FILAMENT_WIDTH',
- self.cmd_Get_Raw_Values)
- self.gcode.register_command('ENABLE_FILAMENT_WIDTH_LOG',
- self.cmd_log_enable)
- self.gcode.register_command('DISABLE_FILAMENT_WIDTH_LOG',
- self.cmd_log_disable)
+ self.gcode = self.printer.lookup_object("gcode")
+ self.gcode.register_command("QUERY_FILAMENT_WIDTH", self.cmd_M407)
+ self.gcode.register_command(
+ "RESET_FILAMENT_WIDTH_SENSOR", self.cmd_ClearFilamentArray
+ )
+ self.gcode.register_command("DISABLE_FILAMENT_WIDTH_SENSOR", self.cmd_M406)
+ self.gcode.register_command("ENABLE_FILAMENT_WIDTH_SENSOR", self.cmd_M405)
+ self.gcode.register_command("QUERY_RAW_FILAMENT_WIDTH", self.cmd_Get_Raw_Values)
+ self.gcode.register_command("ENABLE_FILAMENT_WIDTH_LOG", self.cmd_log_enable)
+ self.gcode.register_command("DISABLE_FILAMENT_WIDTH_LOG", self.cmd_log_disable)
self.runout_helper = filament_switch_sensor.RunoutHelper(config)
+
# Initialization
def handle_ready(self):
# Load printer objects
- self.toolhead = self.printer.lookup_object('toolhead')
+ self.toolhead = self.printer.lookup_object("toolhead")
# Start extrude factor update timer
- self.reactor.update_timer(self.extrude_factor_update_timer,
- self.reactor.NOW)
+ self.reactor.update_timer(self.extrude_factor_update_timer, self.reactor.NOW)
def adc_callback(self, read_time, read_value):
# read sensor value
@@ -91,32 +89,39 @@ class HallFilamentWidthSensor:
# read sensor value
self.lastFilamentWidthReading2 = round(read_value * 10000)
# calculate diameter
- diameter_new = round((self.dia2 - self.dia1)/
- (self.rawdia2-self.rawdia1)*
- ((self.lastFilamentWidthReading+self.lastFilamentWidthReading2)
- -self.rawdia1)+self.dia1,2)
- self.diameter=(5.0 * self.diameter + diameter_new)/6
+ diameter_new = round(
+ (self.dia2 - self.dia1)
+ / (self.rawdia2 - self.rawdia1)
+ * (
+ (self.lastFilamentWidthReading + self.lastFilamentWidthReading2)
+ - self.rawdia1
+ )
+ + self.dia1,
+ 2,
+ )
+ self.diameter = (5.0 * self.diameter + diameter_new) / 6
def update_filament_array(self, last_epos):
# Fill array
if len(self.filament_array) > 0:
# Get last reading position in array & calculate next
# reading position
- next_reading_position = (self.filament_array[-1][0] +
- self.MEASUREMENT_INTERVAL_MM)
- if next_reading_position <= (last_epos + self.measurement_delay):
- self.filament_array.append([last_epos + self.measurement_delay,
- self.diameter])
- if self.is_log:
- self.gcode.respond_info("Filament width:%.3f" %
- ( self.diameter ))
+ next_reading_position = (
+ self.filament_array[-1][0] + self.MEASUREMENT_INTERVAL_MM
+ )
+ if next_reading_position <= (last_epos + self.measurement_delay):
+ self.filament_array.append(
+ [last_epos + self.measurement_delay, self.diameter]
+ )
+ if self.is_log:
+ self.gcode.respond_info("Filament width:%.3f" % (self.diameter))
else:
# add first item to array
- self.filament_array.append([self.measurement_delay + last_epos,
- self.diameter])
- self.firstExtruderUpdatePosition = (self.measurement_delay
- + last_epos)
+ self.filament_array.append(
+ [self.measurement_delay + last_epos, self.diameter]
+ )
+ self.firstExtruderUpdatePosition = self.measurement_delay + last_epos
def extrude_factor_update_event(self, eventtime):
# Update extrude factor
@@ -125,8 +130,9 @@ class HallFilamentWidthSensor:
# Update filament array for lastFilamentWidthReading
self.update_filament_array(last_epos)
# Check runout
- self.runout_helper.note_filament_present(eventtime,
- self.runout_dia_min <= self.diameter <= self.runout_dia_max)
+ self.runout_helper.note_filament_present(
+ eventtime, self.runout_dia_min <= self.diameter <= self.runout_dia_max
+ )
# Does filament exists
if self.diameter > 0.5:
if len(self.filament_array) > 0:
@@ -137,16 +143,18 @@ class HallFilamentWidthSensor:
item = self.filament_array.pop(0)
self.filament_width = item[1]
else:
- if ((self.use_current_dia_while_delay)
- and (self.firstExtruderUpdatePosition
- == pending_position)):
+ if (self.use_current_dia_while_delay) and (
+ self.firstExtruderUpdatePosition == pending_position
+ ):
self.filament_width = self.diameter
- elif self.firstExtruderUpdatePosition == pending_position:
+ elif self.firstExtruderUpdatePosition == pending_position:
self.filament_width = self.nominal_filament_dia
- if ((self.filament_width <= self.max_diameter)
- and (self.filament_width >= self.min_diameter)):
- percentage = round(self.nominal_filament_dia**2
- / self.filament_width**2 * 100)
+ if (self.filament_width <= self.max_diameter) and (
+ self.filament_width >= self.min_diameter
+ ):
+ percentage = round(
+ self.nominal_filament_dia**2 / self.filament_width**2 * 100
+ )
self.gcode.run_script("M221 S" + str(percentage))
else:
self.gcode.run_script("M221 S100")
@@ -162,8 +170,7 @@ class HallFilamentWidthSensor:
def cmd_M407(self, gcmd):
response = ""
if self.diameter > 0:
- response += ("Filament dia (measured mm): "
- + str(self.diameter))
+ response += "Filament dia (measured mm): " + str(self.diameter)
else:
response += "Filament NOT present"
gcmd.respond_info(response)
@@ -181,8 +188,9 @@ class HallFilamentWidthSensor:
else:
self.is_active = True
# Start extrude factor update timer
- self.reactor.update_timer(self.extrude_factor_update_timer,
- self.reactor.NOW)
+ self.reactor.update_timer(
+ self.extrude_factor_update_timer, self.reactor.NOW
+ )
gcmd.respond_info(response)
def cmd_M406(self, gcmd):
@@ -192,8 +200,9 @@ class HallFilamentWidthSensor:
else:
self.is_active = False
# Stop extrude factor update timer
- self.reactor.update_timer(self.extrude_factor_update_timer,
- self.reactor.NEVER)
+ self.reactor.update_timer(
+ self.extrude_factor_update_timer, self.reactor.NEVER
+ )
# Clear filament array
self.filament_array = []
# Set extrude multiplier to 100%
@@ -202,19 +211,24 @@ class HallFilamentWidthSensor:
def cmd_Get_Raw_Values(self, gcmd):
response = "ADC1="
- response += (" "+str(self.lastFilamentWidthReading))
- response += (" ADC2="+str(self.lastFilamentWidthReading2))
- response += (" RAW="+
- str(self.lastFilamentWidthReading
- +self.lastFilamentWidthReading2))
+ response += " " + str(self.lastFilamentWidthReading)
+ response += " ADC2=" + str(self.lastFilamentWidthReading2)
+ response += " RAW=" + str(
+ self.lastFilamentWidthReading + self.lastFilamentWidthReading2
+ )
gcmd.respond_info(response)
+
def get_status(self, eventtime):
status = self.runout_helper.get_status(eventtime)
- status.update({'Diameter': self.diameter,
- 'Raw':(self.lastFilamentWidthReading+
- self.lastFilamentWidthReading2),
- 'is_active':self.is_active})
+ status.update(
+ {
+ "Diameter": self.diameter,
+ "Raw": (self.lastFilamentWidthReading + self.lastFilamentWidthReading2),
+ "is_active": self.is_active,
+ }
+ )
return status
+
def cmd_log_enable(self, gcmd):
self.is_log = True
gcmd.respond_info("Filament width logging Turned On")
@@ -223,5 +237,6 @@ class HallFilamentWidthSensor:
self.is_log = False
gcmd.respond_info("Filament width logging Turned Off")
+
def load_config(config):
return HallFilamentWidthSensor(config)
diff --git a/klippy/extras/heater_bed.py b/klippy/extras/heater_bed.py
index 96afb8fe..5ec52022 100644
--- a/klippy/extras/heater_bed.py
+++ b/klippy/extras/heater_bed.py
@@ -4,25 +4,29 @@
#
# This file may be distributed under the terms of the GNU GPLv3 license.
+
class PrinterHeaterBed:
def __init__(self, config):
self.printer = config.get_printer()
- pheaters = self.printer.load_object(config, 'heaters')
- self.heater = pheaters.setup_heater(config, 'B')
+ pheaters = self.printer.load_object(config, "heaters")
+ self.heater = pheaters.setup_heater(config, "B")
self.get_status = self.heater.get_status
self.stats = self.heater.stats
# Register commands
- gcode = self.printer.lookup_object('gcode')
+ gcode = self.printer.lookup_object("gcode")
gcode.register_command("M140", self.cmd_M140)
gcode.register_command("M190", self.cmd_M190)
+
def cmd_M140(self, gcmd, wait=False):
# Set Bed Temperature
- temp = gcmd.get_float('S', 0.)
- pheaters = self.printer.lookup_object('heaters')
+ temp = gcmd.get_float("S", 0.0)
+ pheaters = self.printer.lookup_object("heaters")
pheaters.set_temperature(self.heater, temp, wait)
+
def cmd_M190(self, gcmd):
# Set Bed Temperature and Wait
self.cmd_M140(gcmd, wait=True)
+
def load_config(config):
return PrinterHeaterBed(config)
diff --git a/klippy/extras/heater_fan.py b/klippy/extras/heater_fan.py
index 3630366e..6aa8ecc5 100644
--- a/klippy/extras/heater_fan.py
+++ b/klippy/extras/heater_fan.py
@@ -7,26 +7,30 @@ from . import fan
PIN_MIN_TIME = 0.100
+
class PrinterHeaterFan:
def __init__(self, config):
self.printer = config.get_printer()
- self.printer.load_object(config, 'heaters')
+ self.printer.load_object(config, "heaters")
self.printer.register_event_handler("klippy:ready", self.handle_ready)
self.heater_names = config.getlist("heater", ("extruder",))
self.heater_temp = config.getfloat("heater_temp", 50.0)
self.heaters = []
- self.fan = fan.Fan(config, default_shutdown_speed=1.)
- self.fan_speed = config.getfloat("fan_speed", 1., minval=0., maxval=1.)
- self.last_speed = 0.
+ self.fan = fan.Fan(config, default_shutdown_speed=1.0)
+ self.fan_speed = config.getfloat("fan_speed", 1.0, minval=0.0, maxval=1.0)
+ self.last_speed = 0.0
+
def handle_ready(self):
- pheaters = self.printer.lookup_object('heaters')
+ pheaters = self.printer.lookup_object("heaters")
self.heaters = [pheaters.lookup_heater(n) for n in self.heater_names]
reactor = self.printer.get_reactor()
- reactor.register_timer(self.callback, reactor.monotonic()+PIN_MIN_TIME)
+ reactor.register_timer(self.callback, reactor.monotonic() + PIN_MIN_TIME)
+
def get_status(self, eventtime):
return self.fan.get_status(eventtime)
+
def callback(self, eventtime):
- speed = 0.
+ speed = 0.0
for heater in self.heaters:
current_temp, target_temp = heater.get_temp(eventtime)
if target_temp or current_temp > self.heater_temp:
@@ -34,7 +38,8 @@ class PrinterHeaterFan:
if speed != self.last_speed:
self.last_speed = speed
self.fan.set_speed(speed)
- return eventtime + 1.
+ return eventtime + 1.0
+
def load_config_prefix(config):
return PrinterHeaterFan(config)
diff --git a/klippy/extras/heater_generic.py b/klippy/extras/heater_generic.py
index 46171c4f..2ed68e8e 100644
--- a/klippy/extras/heater_generic.py
+++ b/klippy/extras/heater_generic.py
@@ -4,6 +4,7 @@
#
# This file may be distributed under the terms of the GNU GPLv3 license.
+
def load_config_prefix(config):
- pheaters = config.get_printer().load_object(config, 'heaters')
+ pheaters = config.get_printer().load_object(config, "heaters")
return pheaters.setup_heater(config)
diff --git a/klippy/extras/heaters.py b/klippy/extras/heaters.py
index fce3c49a..ce06955e 100644
--- a/klippy/extras/heaters.py
+++ b/klippy/extras/heaters.py
@@ -12,11 +12,12 @@ import os, logging, threading
KELVIN_TO_CELSIUS = -273.15
MAX_HEAT_TIME = 3.0
-AMBIENT_TEMP = 25.
-PID_PARAM_BASE = 255.
+AMBIENT_TEMP = 25.0
+PID_PARAM_BASE = 255.0
MAX_MAINTHREAD_TIME = 5.0
QUELL_STALE_TIME = 7.0
+
class Heater:
def __init__(self, config, sensor):
self.printer = config.get_printer()
@@ -24,63 +25,69 @@ class Heater:
self.short_name = short_name = self.name.split()[-1]
# Setup sensor
self.sensor = sensor
- self.min_temp = config.getfloat('min_temp', minval=KELVIN_TO_CELSIUS)
- self.max_temp = config.getfloat('max_temp', above=self.min_temp)
+ self.min_temp = config.getfloat("min_temp", minval=KELVIN_TO_CELSIUS)
+ self.max_temp = config.getfloat("max_temp", above=self.min_temp)
self.sensor.setup_minmax(self.min_temp, self.max_temp)
self.sensor.setup_callback(self.temperature_callback)
self.pwm_delay = self.sensor.get_report_time_delta()
# Setup temperature checks
self.min_extrude_temp = config.getfloat(
- 'min_extrude_temp', 170.,
- minval=self.min_temp, maxval=self.max_temp)
- is_fileoutput = (self.printer.get_start_args().get('debugoutput')
- is not None)
- self.can_extrude = self.min_extrude_temp <= 0. or is_fileoutput
- self.max_power = config.getfloat('max_power', 1., above=0., maxval=1.)
- self.smooth_time = config.getfloat('smooth_time', 1., above=0.)
- self.inv_smooth_time = 1. / self.smooth_time
- self.verify_mainthread_time = -999.
+ "min_extrude_temp", 170.0, minval=self.min_temp, maxval=self.max_temp
+ )
+ is_fileoutput = self.printer.get_start_args().get("debugoutput") is not None
+ self.can_extrude = self.min_extrude_temp <= 0.0 or is_fileoutput
+ self.max_power = config.getfloat("max_power", 1.0, above=0.0, maxval=1.0)
+ self.smooth_time = config.getfloat("smooth_time", 1.0, above=0.0)
+ self.inv_smooth_time = 1.0 / self.smooth_time
+ self.verify_mainthread_time = -999.0
self.lock = threading.Lock()
- self.last_temp = self.smoothed_temp = self.target_temp = 0.
- self.last_temp_time = 0.
+ self.last_temp = self.smoothed_temp = self.target_temp = 0.0
+ self.last_temp_time = 0.0
# pwm caching
- self.next_pwm_time = 0.
- self.last_pwm_value = 0.
+ self.next_pwm_time = 0.0
+ self.last_pwm_value = 0.0
# Setup control algorithm sub-class
- algos = {'watermark': ControlBangBang, 'pid': ControlPID}
- algo = config.getchoice('control', algos)
+ algos = {"watermark": ControlBangBang, "pid": ControlPID}
+ algo = config.getchoice("control", algos)
self.control = algo(self, config)
# Setup output heater pin
- heater_pin = config.get('heater_pin')
- ppins = self.printer.lookup_object('pins')
- self.mcu_pwm = ppins.setup_pin('pwm', heater_pin)
- pwm_cycle_time = config.getfloat('pwm_cycle_time', 0.100, above=0.,
- maxval=self.pwm_delay)
+ heater_pin = config.get("heater_pin")
+ ppins = self.printer.lookup_object("pins")
+ self.mcu_pwm = ppins.setup_pin("pwm", heater_pin)
+ pwm_cycle_time = config.getfloat(
+ "pwm_cycle_time", 0.100, above=0.0, maxval=self.pwm_delay
+ )
self.mcu_pwm.setup_cycle_time(pwm_cycle_time)
self.mcu_pwm.setup_max_duration(MAX_HEAT_TIME)
# Load additional modules
self.printer.load_object(config, "verify_heater %s" % (short_name,))
self.printer.load_object(config, "pid_calibrate")
gcode = self.printer.lookup_object("gcode")
- gcode.register_mux_command("SET_HEATER_TEMPERATURE", "HEATER",
- short_name, self.cmd_SET_HEATER_TEMPERATURE,
- desc=self.cmd_SET_HEATER_TEMPERATURE_help)
- self.printer.register_event_handler("klippy:shutdown",
- self._handle_shutdown)
+ gcode.register_mux_command(
+ "SET_HEATER_TEMPERATURE",
+ "HEATER",
+ short_name,
+ self.cmd_SET_HEATER_TEMPERATURE,
+ desc=self.cmd_SET_HEATER_TEMPERATURE_help,
+ )
+ self.printer.register_event_handler("klippy:shutdown", self._handle_shutdown)
+
def set_pwm(self, read_time, value):
- if self.target_temp <= 0. or read_time > self.verify_mainthread_time:
- value = 0.
- if ((read_time < self.next_pwm_time or not self.last_pwm_value)
- and abs(value - self.last_pwm_value) < 0.05):
+ if self.target_temp <= 0.0 or read_time > self.verify_mainthread_time:
+ value = 0.0
+ if (read_time < self.next_pwm_time or not self.last_pwm_value) and abs(
+ value - self.last_pwm_value
+ ) < 0.05:
# No significant change in value - can suppress update
return
pwm_time = read_time + self.pwm_delay
self.next_pwm_time = pwm_time + 0.75 * MAX_HEAT_TIME
self.last_pwm_value = value
self.mcu_pwm.set_pwm(pwm_time, value)
- #logging.debug("%s: pwm=%.3f@%.3f (from %.3f@%.3f [%.3f])",
+ # logging.debug("%s: pwm=%.3f@%.3f (from %.3f@%.3f [%.3f])",
# self.name, value, pwm_time,
# self.last_temp, self.last_temp_time, self.target_temp)
+
def temperature_callback(self, read_time, temp):
with self.lock:
time_diff = read_time - self.last_temp_time
@@ -88,49 +95,62 @@ class Heater:
self.last_temp_time = read_time
self.control.temperature_update(read_time, temp, self.target_temp)
temp_diff = temp - self.smoothed_temp
- adj_time = min(time_diff * self.inv_smooth_time, 1.)
+ adj_time = min(time_diff * self.inv_smooth_time, 1.0)
self.smoothed_temp += temp_diff * adj_time
- self.can_extrude = (self.smoothed_temp >= self.min_extrude_temp)
- #logging.debug("temp: %.3f %f = %f", read_time, temp)
+ self.can_extrude = self.smoothed_temp >= self.min_extrude_temp
+ # logging.debug("temp: %.3f %f = %f", read_time, temp)
+
def _handle_shutdown(self):
- self.verify_mainthread_time = -999.
+ self.verify_mainthread_time = -999.0
+
# External commands
def get_name(self):
return self.name
+
def get_pwm_delay(self):
return self.pwm_delay
+
def get_max_power(self):
return self.max_power
+
def get_smooth_time(self):
return self.smooth_time
+
def set_temp(self, degrees):
if degrees and (degrees < self.min_temp or degrees > self.max_temp):
raise self.printer.command_error(
"Requested temperature (%.1f) out of range (%.1f:%.1f)"
- % (degrees, self.min_temp, self.max_temp))
+ % (degrees, self.min_temp, self.max_temp)
+ )
with self.lock:
self.target_temp = degrees
+
def get_temp(self, eventtime):
est_print_time = self.mcu_pwm.get_mcu().estimated_print_time(eventtime)
quell_time = est_print_time - QUELL_STALE_TIME
with self.lock:
if self.last_temp_time < quell_time:
- return 0., self.target_temp
+ return 0.0, self.target_temp
return self.smoothed_temp, self.target_temp
+
def check_busy(self, eventtime):
with self.lock:
return self.control.check_busy(
- eventtime, self.smoothed_temp, self.target_temp)
+ eventtime, self.smoothed_temp, self.target_temp
+ )
+
def set_control(self, control):
with self.lock:
old_control = self.control
self.control = control
- self.target_temp = 0.
+ self.target_temp = 0.0
return old_control
+
def alter_target(self, target_temp):
if target_temp:
target_temp = max(self.min_temp, min(self.max_temp, target_temp))
self.target_temp = target_temp
+
def stats(self, eventtime):
est_print_time = self.mcu_pwm.get_mcu().estimated_print_time(eventtime)
if not self.printer.is_shutdown():
@@ -139,20 +159,30 @@ class Heater:
target_temp = self.target_temp
last_temp = self.last_temp
last_pwm_value = self.last_pwm_value
- is_active = target_temp or last_temp > 50.
- return is_active, '%s: target=%.0f temp=%.1f pwm=%.3f' % (
- self.short_name, target_temp, last_temp, last_pwm_value)
+ is_active = target_temp or last_temp > 50.0
+ return is_active, "%s: target=%.0f temp=%.1f pwm=%.3f" % (
+ self.short_name,
+ target_temp,
+ last_temp,
+ last_pwm_value,
+ )
+
def get_status(self, eventtime):
with self.lock:
target_temp = self.target_temp
smoothed_temp = self.smoothed_temp
last_pwm_value = self.last_pwm_value
- return {'temperature': round(smoothed_temp, 2), 'target': target_temp,
- 'power': last_pwm_value}
+ return {
+ "temperature": round(smoothed_temp, 2),
+ "target": target_temp,
+ "power": last_pwm_value,
+ }
+
cmd_SET_HEATER_TEMPERATURE_help = "Sets a heater temperature"
+
def cmd_SET_HEATER_TEMPERATURE(self, gcmd):
- temp = gcmd.get_float('TARGET', 0.)
- pheaters = self.printer.lookup_object('heaters')
+ temp = gcmd.get_float("TARGET", 0.0)
+ pheaters = self.printer.lookup_object("heaters")
pheaters.set_temperature(self, temp)
@@ -160,47 +190,52 @@ class Heater:
# Bang-bang control algo
######################################################################
+
class ControlBangBang:
def __init__(self, heater, config):
self.heater = heater
self.heater_max_power = heater.get_max_power()
- self.max_delta = config.getfloat('max_delta', 2.0, above=0.)
+ self.max_delta = config.getfloat("max_delta", 2.0, above=0.0)
self.heating = False
+
def temperature_update(self, read_time, temp, target_temp):
- if self.heating and temp >= target_temp+self.max_delta:
+ if self.heating and temp >= target_temp + self.max_delta:
self.heating = False
- elif not self.heating and temp <= target_temp-self.max_delta:
+ elif not self.heating and temp <= target_temp - self.max_delta:
self.heating = True
if self.heating:
self.heater.set_pwm(read_time, self.heater_max_power)
else:
- self.heater.set_pwm(read_time, 0.)
+ self.heater.set_pwm(read_time, 0.0)
+
def check_busy(self, eventtime, smoothed_temp, target_temp):
- return smoothed_temp < target_temp-self.max_delta
+ return smoothed_temp < target_temp - self.max_delta
######################################################################
# Proportional Integral Derivative (PID) control algo
######################################################################
-PID_SETTLE_DELTA = 1.
-PID_SETTLE_SLOPE = .1
+PID_SETTLE_DELTA = 1.0
+PID_SETTLE_SLOPE = 0.1
+
class ControlPID:
def __init__(self, heater, config):
self.heater = heater
self.heater_max_power = heater.get_max_power()
- self.Kp = config.getfloat('pid_Kp') / PID_PARAM_BASE
- self.Ki = config.getfloat('pid_Ki') / PID_PARAM_BASE
- self.Kd = config.getfloat('pid_Kd') / PID_PARAM_BASE
+ self.Kp = config.getfloat("pid_Kp") / PID_PARAM_BASE
+ self.Ki = config.getfloat("pid_Ki") / PID_PARAM_BASE
+ self.Kd = config.getfloat("pid_Kd") / PID_PARAM_BASE
self.min_deriv_time = heater.get_smooth_time()
- self.temp_integ_max = 0.
+ self.temp_integ_max = 0.0
if self.Ki:
self.temp_integ_max = self.heater_max_power / self.Ki
self.prev_temp = AMBIENT_TEMP
- self.prev_temp_time = 0.
- self.prev_temp_deriv = 0.
- self.prev_temp_integ = 0.
+ self.prev_temp_time = 0.0
+ self.prev_temp_deriv = 0.0
+ self.prev_temp_integ = 0.0
+
def temperature_update(self, read_time, temp, target_temp):
time_diff = read_time - self.prev_temp_time
# Calculate change of temperature
@@ -208,17 +243,18 @@ class ControlPID:
if time_diff >= self.min_deriv_time:
temp_deriv = temp_diff / time_diff
else:
- temp_deriv = (self.prev_temp_deriv * (self.min_deriv_time-time_diff)
- + temp_diff) / self.min_deriv_time
+ temp_deriv = (
+ self.prev_temp_deriv * (self.min_deriv_time - time_diff) + temp_diff
+ ) / self.min_deriv_time
# Calculate accumulated temperature "error"
temp_err = target_temp - temp
temp_integ = self.prev_temp_integ + temp_err * time_diff
- temp_integ = max(0., min(self.temp_integ_max, temp_integ))
+ temp_integ = max(0.0, min(self.temp_integ_max, temp_integ))
# Calculate output
- co = self.Kp*temp_err + self.Ki*temp_integ - self.Kd*temp_deriv
- #logging.debug("pid: %f@%.3f -> diff=%f deriv=%f err=%f integ=%f co=%d",
+ co = self.Kp * temp_err + self.Ki * temp_integ - self.Kd * temp_deriv
+ # logging.debug("pid: %f@%.3f -> diff=%f deriv=%f err=%f integ=%f co=%d",
# temp, read_time, temp_diff, temp_deriv, temp_err, temp_integ, co)
- bounded_co = max(0., min(self.heater_max_power, co))
+ bounded_co = max(0.0, min(self.heater_max_power, co))
self.heater.set_pwm(read_time, bounded_co)
# Store state for next measurement
self.prev_temp = temp
@@ -226,16 +262,20 @@ class ControlPID:
self.prev_temp_deriv = temp_deriv
if co == bounded_co:
self.prev_temp_integ = temp_integ
+
def check_busy(self, eventtime, smoothed_temp, target_temp):
temp_diff = target_temp - smoothed_temp
- return (abs(temp_diff) > PID_SETTLE_DELTA
- or abs(self.prev_temp_deriv) > PID_SETTLE_SLOPE)
+ return (
+ abs(temp_diff) > PID_SETTLE_DELTA
+ or abs(self.prev_temp_deriv) > PID_SETTLE_SLOPE
+ )
######################################################################
# Sensor and heater lookup
######################################################################
+
class PrinterHeaters:
def __init__(self, config):
self.printer = config.get_printer()
@@ -247,30 +287,40 @@ class PrinterHeaters:
self.available_monitors = []
self.has_started = self.have_load_sensors = False
self.printer.register_event_handler("klippy:ready", self._handle_ready)
- self.printer.register_event_handler("gcode:request_restart",
- self.turn_off_all_heaters)
+ self.printer.register_event_handler(
+ "gcode:request_restart", self.turn_off_all_heaters
+ )
# Register commands
- gcode = self.printer.lookup_object('gcode')
- gcode.register_command("TURN_OFF_HEATERS", self.cmd_TURN_OFF_HEATERS,
- desc=self.cmd_TURN_OFF_HEATERS_help)
+ gcode = self.printer.lookup_object("gcode")
+ gcode.register_command(
+ "TURN_OFF_HEATERS",
+ self.cmd_TURN_OFF_HEATERS,
+ desc=self.cmd_TURN_OFF_HEATERS_help,
+ )
gcode.register_command("M105", self.cmd_M105, when_not_ready=True)
- gcode.register_command("TEMPERATURE_WAIT", self.cmd_TEMPERATURE_WAIT,
- desc=self.cmd_TEMPERATURE_WAIT_help)
+ gcode.register_command(
+ "TEMPERATURE_WAIT",
+ self.cmd_TEMPERATURE_WAIT,
+ desc=self.cmd_TEMPERATURE_WAIT_help,
+ )
+
def load_config(self, config):
self.have_load_sensors = True
# Load default temperature sensors
- pconfig = self.printer.lookup_object('configfile')
+ pconfig = self.printer.lookup_object("configfile")
dir_name = os.path.dirname(__file__)
- filename = os.path.join(dir_name, 'temperature_sensors.cfg')
+ filename = os.path.join(dir_name, "temperature_sensors.cfg")
try:
dconfig = pconfig.read_config(filename)
except Exception:
logging.exception("Unable to load temperature_sensors.cfg")
raise config.error("Cannot load config '%s'" % (filename,))
- for c in dconfig.get_prefix_sections(''):
+ for c in dconfig.get_prefix_sections(""):
self.printer.load_object(dconfig, c.get_name())
+
def add_sensor_factory(self, sensor_type, sensor_factory):
self.sensor_factories[sensor_type] = sensor_factory
+
def setup_heater(self, config, gcode_id=None):
heater_name = config.get_name().split()[-1]
if heater_name in self.heaters:
@@ -282,46 +332,60 @@ class PrinterHeaters:
self.register_sensor(config, heater, gcode_id)
self.available_heaters.append(config.get_name())
return heater
+
def get_all_heaters(self):
return self.available_heaters
+
def lookup_heater(self, heater_name):
if heater_name not in self.heaters:
- raise self.printer.config_error(
- "Unknown heater '%s'" % (heater_name,))
+ raise self.printer.config_error("Unknown heater '%s'" % (heater_name,))
return self.heaters[heater_name]
+
def setup_sensor(self, config):
if not self.have_load_sensors:
self.load_config(config)
- sensor_type = config.get('sensor_type')
+ sensor_type = config.get("sensor_type")
if sensor_type not in self.sensor_factories:
raise self.printer.config_error(
- "Unknown temperature sensor '%s'" % (sensor_type,))
+ "Unknown temperature sensor '%s'" % (sensor_type,)
+ )
return self.sensor_factories[sensor_type](config)
+
def register_sensor(self, config, psensor, gcode_id=None):
self.available_sensors.append(config.get_name())
if gcode_id is None:
- gcode_id = config.get('gcode_id', None)
+ gcode_id = config.get("gcode_id", None)
if gcode_id is None:
return
if gcode_id in self.gcode_id_to_sensor:
raise self.printer.config_error(
- "G-Code sensor id %s already registered" % (gcode_id,))
+ "G-Code sensor id %s already registered" % (gcode_id,)
+ )
self.gcode_id_to_sensor[gcode_id] = psensor
+
def register_monitor(self, config):
self.available_monitors.append(config.get_name())
+
def get_status(self, eventtime):
- return {'available_heaters': self.available_heaters,
- 'available_sensors': self.available_sensors,
- 'available_monitors': self.available_monitors}
- def turn_off_all_heaters(self, print_time=0.):
+ return {
+ "available_heaters": self.available_heaters,
+ "available_sensors": self.available_sensors,
+ "available_monitors": self.available_monitors,
+ }
+
+ def turn_off_all_heaters(self, print_time=0.0):
for heater in self.heaters.values():
- heater.set_temp(0.)
+ heater.set_temp(0.0)
+
cmd_TURN_OFF_HEATERS_help = "Turn off all heaters"
+
def cmd_TURN_OFF_HEATERS(self, gcmd):
self.turn_off_all_heaters()
+
# G-Code M105 temperature reporting
def _handle_ready(self):
self.has_started = True
+
def _get_temp(self, eventtime):
# Tn:XXX /YYY B:XXX /YYY
out = []
@@ -332,6 +396,7 @@ class PrinterHeaters:
if not out:
return "T:0"
return " ".join(out)
+
def cmd_M105(self, gcmd):
# Get Extruder Temperature
reactor = self.printer.get_reactor()
@@ -339,9 +404,10 @@ class PrinterHeaters:
did_ack = gcmd.ack(msg)
if not did_ack:
gcmd.respond_raw(msg)
+
def _wait_for_temperature(self, heater):
# Helper to wait on heater.check_busy() and report M105 temperatures
- if self.printer.get_start_args().get('debugoutput') is not None:
+ if self.printer.get_start_args().get("debugoutput") is not None:
return
toolhead = self.printer.lookup_object("toolhead")
gcode = self.printer.lookup_object("gcode")
@@ -350,24 +416,26 @@ class PrinterHeaters:
while not self.printer.is_shutdown() and heater.check_busy(eventtime):
print_time = toolhead.get_last_move_time()
gcode.respond_raw(self._get_temp(eventtime))
- eventtime = reactor.pause(eventtime + 1.)
+ eventtime = reactor.pause(eventtime + 1.0)
+
def set_temperature(self, heater, temp, wait=False):
- toolhead = self.printer.lookup_object('toolhead')
+ toolhead = self.printer.lookup_object("toolhead")
toolhead.register_lookahead_callback((lambda pt: None))
heater.set_temp(temp)
if wait and temp:
self._wait_for_temperature(heater)
+
cmd_TEMPERATURE_WAIT_help = "Wait for a temperature on a sensor"
+
def cmd_TEMPERATURE_WAIT(self, gcmd):
- sensor_name = gcmd.get('SENSOR')
+ sensor_name = gcmd.get("SENSOR")
if sensor_name not in self.available_sensors:
raise gcmd.error("Unknown sensor '%s'" % (sensor_name,))
- min_temp = gcmd.get_float('MINIMUM', float('-inf'))
- max_temp = gcmd.get_float('MAXIMUM', float('inf'), above=min_temp)
- if min_temp == float('-inf') and max_temp == float('inf'):
- raise gcmd.error(
- "Error on 'TEMPERATURE_WAIT': missing MINIMUM or MAXIMUM.")
- if self.printer.get_start_args().get('debugoutput') is not None:
+ min_temp = gcmd.get_float("MINIMUM", float("-inf"))
+ max_temp = gcmd.get_float("MAXIMUM", float("inf"), above=min_temp)
+ if min_temp == float("-inf") and max_temp == float("inf"):
+ raise gcmd.error("Error on 'TEMPERATURE_WAIT': missing MINIMUM or MAXIMUM.")
+ if self.printer.get_start_args().get("debugoutput") is not None:
return
if sensor_name in self.heaters:
sensor = self.heaters[sensor_name]
@@ -382,7 +450,8 @@ class PrinterHeaters:
return
print_time = toolhead.get_last_move_time()
gcmd.respond_raw(self._get_temp(eventtime))
- eventtime = reactor.pause(eventtime + 1.)
+ eventtime = reactor.pause(eventtime + 1.0)
+
def load_config(config):
return PrinterHeaters(config)
diff --git a/klippy/extras/homing.py b/klippy/extras/homing.py
index 723648c1..0a2b1f32 100644
--- a/klippy/extras/homing.py
+++ b/klippy/extras/homing.py
@@ -6,9 +6,10 @@
import logging, math
HOMING_START_DELAY = 0.001
-ENDSTOP_SAMPLE_TIME = .000015
+ENDSTOP_SAMPLE_TIME = 0.000015
ENDSTOP_SAMPLE_COUNT = 4
+
# Return a completion that completes when all completions in a list complete
def multi_complete(printer, completions):
if len(completions) == 1:
@@ -18,10 +19,10 @@ def multi_complete(printer, completions):
cp = reactor.register_callback(lambda e: [c.wait() for c in completions])
# If any completion indicates an error, then exit main completion early
for c in completions:
- reactor.register_callback(
- lambda e, c=c: cp.complete(1) if c.wait() else 0)
+ reactor.register_callback(lambda e, c=c: cp.complete(1) if c.wait() else 0)
return cp
+
# Tracking of stepper positions during a homing/probing move
class StepperPosition:
def __init__(self, stepper, endstop_name):
@@ -31,15 +32,21 @@ class StepperPosition:
self.start_pos = stepper.get_mcu_position()
self.start_cmd_pos = stepper.mcu_to_commanded_position(self.start_pos)
self.halt_pos = self.trig_pos = None
+
def note_home_end(self, trigger_time):
self.halt_pos = self.stepper.get_mcu_position()
self.trig_pos = self.stepper.get_past_mcu_position(trigger_time)
+
def verify_no_probe_skew(self, haltpos):
new_start_pos = self.stepper.get_mcu_position(self.start_cmd_pos)
if new_start_pos != self.start_pos:
logging.warning(
"Stepper '%s' position skew after probe: pos %d now %d",
- self.stepper.get_name(), self.start_pos, new_start_pos)
+ self.stepper.get_name(),
+ self.start_pos,
+ new_start_pos,
+ )
+
# Implementation of homing/probing moves
class HomingMove:
@@ -47,23 +54,34 @@ class HomingMove:
self.printer = printer
self.endstops = [es for es in endstops if es[0].get_steppers()]
if toolhead is None:
- toolhead = printer.lookup_object('toolhead')
+ toolhead = printer.lookup_object("toolhead")
self.toolhead = toolhead
self.stepper_positions = []
+
def get_mcu_endstops(self):
return [es for es, name in self.endstops]
+
def _calc_endstop_rate(self, mcu_endstop, movepos, speed):
startpos = self.toolhead.get_position()
axes_d = [mp - sp for mp, sp in zip(movepos, startpos)]
- move_d = math.sqrt(sum([d*d for d in axes_d[:3]]))
+ move_d = math.sqrt(sum([d * d for d in axes_d[:3]]))
move_t = move_d / speed
- max_steps = max([(abs(s.calc_position_from_coord(startpos)
- - s.calc_position_from_coord(movepos))
- / s.get_step_dist())
- for s in mcu_endstop.get_steppers()])
- if max_steps <= 0.:
- return .001
+ max_steps = max(
+ [
+ (
+ abs(
+ s.calc_position_from_coord(startpos)
+ - s.calc_position_from_coord(movepos)
+ )
+ / s.get_step_dist()
+ )
+ for s in mcu_endstop.get_steppers()
+ ]
+ )
+ if max_steps <= 0.0:
+ return 0.001
return move_t / max_steps
+
def calc_toolhead_pos(self, kin_spos, offsets):
kin_spos = dict(kin_spos)
kin = self.toolhead.get_kinematics()
@@ -72,28 +90,38 @@ class HomingMove:
kin_spos[sname] += offsets.get(sname, 0) * stepper.get_step_dist()
thpos = self.toolhead.get_position()
cpos = kin.calc_position(kin_spos)
- return [cp if cp is not None else tp
- for cp, tp in zip(cpos, thpos[:3])] + thpos[3:]
- def homing_move(self, movepos, speed, probe_pos=False,
- triggered=True, check_triggered=True):
+ return [
+ cp if cp is not None else tp for cp, tp in zip(cpos, thpos[:3])
+ ] + thpos[3:]
+
+ def homing_move(
+ self, movepos, speed, probe_pos=False, triggered=True, check_triggered=True
+ ):
# Notify start of homing/probing move
self.printer.send_event("homing:homing_move_begin", self)
# Note start location
self.toolhead.flush_step_generation()
kin = self.toolhead.get_kinematics()
- kin_spos = {s.get_name(): s.get_commanded_position()
- for s in kin.get_steppers()}
- self.stepper_positions = [ StepperPosition(s, name)
- for es, name in self.endstops
- for s in es.get_steppers() ]
+ kin_spos = {
+ s.get_name(): s.get_commanded_position() for s in kin.get_steppers()
+ }
+ self.stepper_positions = [
+ StepperPosition(s, name)
+ for es, name in self.endstops
+ for s in es.get_steppers()
+ ]
# Start endstop checking
print_time = self.toolhead.get_last_move_time()
endstop_triggers = []
for mcu_endstop, name in self.endstops:
rest_time = self._calc_endstop_rate(mcu_endstop, movepos, speed)
- wait = mcu_endstop.home_start(print_time, ENDSTOP_SAMPLE_TIME,
- ENDSTOP_SAMPLE_COUNT, rest_time,
- triggered=triggered)
+ wait = mcu_endstop.home_start(
+ print_time,
+ ENDSTOP_SAMPLE_TIME,
+ ENDSTOP_SAMPLE_COUNT,
+ rest_time,
+ triggered=triggered,
+ )
endstop_triggers.append(wait)
all_endstop_trigger = multi_complete(self.printer, endstop_triggers)
self.toolhead.dwell(HOMING_START_DELAY)
@@ -113,7 +141,7 @@ class HomingMove:
if error is None:
error = "Error during homing %s: %s" % (name, str(e))
continue
- if trigger_time > 0.:
+ if trigger_time > 0.0:
trigger_times[name] = trigger_time
elif check_triggered and error is None:
error = "No trigger on %s after full movement" % (name,)
@@ -123,10 +151,14 @@ class HomingMove:
tt = trigger_times.get(sp.endstop_name, move_end_print_time)
sp.note_home_end(tt)
if probe_pos:
- halt_steps = {sp.stepper_name: sp.halt_pos - sp.start_pos
- for sp in self.stepper_positions}
- trig_steps = {sp.stepper_name: sp.trig_pos - sp.start_pos
- for sp in self.stepper_positions}
+ halt_steps = {
+ sp.stepper_name: sp.halt_pos - sp.start_pos
+ for sp in self.stepper_positions
+ }
+ trig_steps = {
+ sp.stepper_name: sp.trig_pos - sp.start_pos
+ for sp in self.stepper_positions
+ }
haltpos = trigpos = self.calc_toolhead_pos(kin_spos, trig_steps)
if trig_steps != halt_steps:
haltpos = self.calc_toolhead_pos(kin_spos, halt_steps)
@@ -135,12 +167,15 @@ class HomingMove:
sp.verify_no_probe_skew(haltpos)
else:
haltpos = trigpos = movepos
- over_steps = {sp.stepper_name: sp.halt_pos - sp.trig_pos
- for sp in self.stepper_positions}
+ over_steps = {
+ sp.stepper_name: sp.halt_pos - sp.trig_pos
+ for sp in self.stepper_positions
+ }
if any(over_steps.values()):
self.toolhead.set_position(movepos)
- halt_kin_spos = {s.get_name(): s.get_commanded_position()
- for s in kin.get_steppers()}
+ halt_kin_spos = {
+ s.get_name(): s.get_commanded_position() for s in kin.get_steppers()
+ }
haltpos = self.calc_toolhead_pos(halt_kin_spos, over_steps)
self.toolhead.set_position(haltpos)
# Signal homing/probing move complete
@@ -152,30 +187,37 @@ class HomingMove:
if error is not None:
raise self.printer.command_error(error)
return trigpos
+
def check_no_movement(self):
- if self.printer.get_start_args().get('debuginput') is not None:
+ if self.printer.get_start_args().get("debuginput") is not None:
return None
for sp in self.stepper_positions:
if sp.start_pos == sp.trig_pos:
return sp.endstop_name
return None
+
# State tracking of homing requests
class Homing:
def __init__(self, printer):
self.printer = printer
- self.toolhead = printer.lookup_object('toolhead')
+ self.toolhead = printer.lookup_object("toolhead")
self.changed_axes = []
self.trigger_mcu_pos = {}
self.adjust_pos = {}
+
def set_axes(self, axes):
self.changed_axes = axes
+
def get_axes(self):
return self.changed_axes
+
def get_trigger_position(self, stepper_name):
return self.trigger_mcu_pos[stepper_name]
+
def set_stepper_adjustment(self, stepper_name, adjustment):
self.adjust_pos[stepper_name] = adjustment
+
def _fill_coord(self, coord):
# Fill in any None entries in 'coord' with current toolhead position
thcoord = list(self.toolhead.get_position())
@@ -183,8 +225,10 @@ class Homing:
if coord[i] is not None:
thcoord[i] = coord[i]
return thcoord
+
def set_homed_position(self, pos):
self.toolhead.set_position(self._fill_coord(pos))
+
def home_rails(self, rails, forcepos, movepos):
# Notify of upcoming homing operation
self.printer.send_event("homing:home_rails_begin", self, rails)
@@ -205,60 +249,68 @@ class Homing:
startpos = self._fill_coord(forcepos)
homepos = self._fill_coord(movepos)
axes_d = [hp - sp for hp, sp in zip(homepos, startpos)]
- move_d = math.sqrt(sum([d*d for d in axes_d[:3]]))
- retract_r = min(1., hi.retract_dist / move_d)
- retractpos = [hp - ad * retract_r
- for hp, ad in zip(homepos, axes_d)]
+ move_d = math.sqrt(sum([d * d for d in axes_d[:3]]))
+ retract_r = min(1.0, hi.retract_dist / move_d)
+ retractpos = [hp - ad * retract_r for hp, ad in zip(homepos, axes_d)]
self.toolhead.move(retractpos, hi.retract_speed)
# Home again
- startpos = [rp - ad * retract_r
- for rp, ad in zip(retractpos, axes_d)]
+ startpos = [rp - ad * retract_r for rp, ad in zip(retractpos, axes_d)]
self.toolhead.set_position(startpos)
hmove = HomingMove(self.printer, endstops)
hmove.homing_move(homepos, hi.second_homing_speed)
if hmove.check_no_movement() is not None:
raise self.printer.command_error(
"Endstop %s still triggered after retract"
- % (hmove.check_no_movement(),))
+ % (hmove.check_no_movement(),)
+ )
# Signal home operation complete
self.toolhead.flush_step_generation()
- self.trigger_mcu_pos = {sp.stepper_name: sp.trig_pos
- for sp in hmove.stepper_positions}
+ self.trigger_mcu_pos = {
+ sp.stepper_name: sp.trig_pos for sp in hmove.stepper_positions
+ }
self.adjust_pos = {}
self.printer.send_event("homing:home_rails_end", self, rails)
if any(self.adjust_pos.values()):
# Apply any homing offsets
kin = self.toolhead.get_kinematics()
homepos = self.toolhead.get_position()
- kin_spos = {s.get_name(): (s.get_commanded_position()
- + self.adjust_pos.get(s.get_name(), 0.))
- for s in kin.get_steppers()}
+ kin_spos = {
+ s.get_name(): (
+ s.get_commanded_position() + self.adjust_pos.get(s.get_name(), 0.0)
+ )
+ for s in kin.get_steppers()
+ }
newpos = kin.calc_position(kin_spos)
for axis in force_axes:
if newpos[axis] is None:
raise self.printer.command_error(
- "Cannot determine position of toolhead on "
- "axis %s after homing" % "xyz"[axis])
+ "Cannot determine position of toolhead on "
+ "axis %s after homing" % "xyz"[axis]
+ )
homepos[axis] = newpos[axis]
self.toolhead.set_position(homepos)
+
class PrinterHoming:
def __init__(self, config):
self.printer = config.get_printer()
# Register g-code commands
- gcode = self.printer.lookup_object('gcode')
- gcode.register_command('G28', self.cmd_G28)
- def manual_home(self, toolhead, endstops, pos, speed,
- triggered, check_triggered):
+ gcode = self.printer.lookup_object("gcode")
+ gcode.register_command("G28", self.cmd_G28)
+
+ def manual_home(self, toolhead, endstops, pos, speed, triggered, check_triggered):
hmove = HomingMove(self.printer, endstops, toolhead)
try:
- hmove.homing_move(pos, speed, triggered=triggered,
- check_triggered=check_triggered)
+ hmove.homing_move(
+ pos, speed, triggered=triggered, check_triggered=check_triggered
+ )
except self.printer.command_error:
if self.printer.is_shutdown():
raise self.printer.command_error(
- "Homing failed due to printer shutdown")
+ "Homing failed due to printer shutdown"
+ )
raise
+
def probing_move(self, mcu_probe, pos, speed):
endstops = [(mcu_probe, "probe")]
hmove = HomingMove(self.printer, endstops)
@@ -267,31 +319,34 @@ class PrinterHoming:
except self.printer.command_error:
if self.printer.is_shutdown():
raise self.printer.command_error(
- "Probing failed due to printer shutdown")
+ "Probing failed due to printer shutdown"
+ )
raise
if hmove.check_no_movement() is not None:
- raise self.printer.command_error(
- "Probe triggered prior to movement")
+ raise self.printer.command_error("Probe triggered prior to movement")
return epos
+
def cmd_G28(self, gcmd):
# Move to origin
axes = []
- for pos, axis in enumerate('XYZ'):
+ for pos, axis in enumerate("XYZ"):
if gcmd.get(axis, None) is not None:
axes.append(pos)
if not axes:
axes = [0, 1, 2]
homing_state = Homing(self.printer)
homing_state.set_axes(axes)
- kin = self.printer.lookup_object('toolhead').get_kinematics()
+ kin = self.printer.lookup_object("toolhead").get_kinematics()
try:
kin.home(homing_state)
except self.printer.command_error:
if self.printer.is_shutdown():
raise self.printer.command_error(
- "Homing failed due to printer shutdown")
- self.printer.lookup_object('stepper_enable').motor_off()
+ "Homing failed due to printer shutdown"
+ )
+ self.printer.lookup_object("stepper_enable").motor_off()
raise
+
def load_config(config):
return PrinterHoming(config)
diff --git a/klippy/extras/homing_heaters.py b/klippy/extras/homing_heaters.py
index 4bb9ba04..e9b37f99 100644
--- a/klippy/extras/homing_heaters.py
+++ b/klippy/extras/homing_heaters.py
@@ -6,18 +6,20 @@
import logging
+
class HomingHeaters:
def __init__(self, config):
self.printer = config.get_printer()
- self.printer.register_event_handler("klippy:connect",
- self.handle_connect)
- self.printer.register_event_handler("homing:homing_move_begin",
- self.handle_homing_move_begin)
- self.printer.register_event_handler("homing:homing_move_end",
- self.handle_homing_move_end)
+ self.printer.register_event_handler("klippy:connect", self.handle_connect)
+ self.printer.register_event_handler(
+ "homing:homing_move_begin", self.handle_homing_move_begin
+ )
+ self.printer.register_event_handler(
+ "homing:homing_move_end", self.handle_homing_move_end
+ )
self.disable_heaters = config.getlist("heaters", None)
self.flaky_steppers = config.getlist("steppers", None)
- self.pheaters = self.printer.load_object(config, 'heaters')
+ self.pheaters = self.printer.load_object(config, "heaters")
self.target_save = {}
def handle_connect(self):
@@ -29,30 +31,34 @@ class HomingHeaters:
if not all(x in all_heaters for x in self.disable_heaters):
raise self.printer.config_error(
"One or more of these heaters are unknown: %s"
- % (self.disable_heaters,))
+ % (self.disable_heaters,)
+ )
# steppers valid?
- kin = self.printer.lookup_object('toolhead').get_kinematics()
+ kin = self.printer.lookup_object("toolhead").get_kinematics()
all_steppers = [s.get_name() for s in kin.get_steppers()]
if self.flaky_steppers is None:
return
if not all(x in all_steppers for x in self.flaky_steppers):
raise self.printer.config_error(
- "One or more of these steppers are unknown: %s"
- % (self.flaky_steppers,))
+ "One or more of these steppers are unknown: %s" % (self.flaky_steppers,)
+ )
+
def check_eligible(self, endstops):
if self.flaky_steppers is None:
return True
- steppers_being_homed = [s.get_name()
- for es in endstops
- for s in es.get_steppers()]
+ steppers_being_homed = [
+ s.get_name() for es in endstops for s in es.get_steppers()
+ ]
return any(x in self.flaky_steppers for x in steppers_being_homed)
+
def handle_homing_move_begin(self, hmove):
if not self.check_eligible(hmove.get_mcu_endstops()):
return
for heater_name in self.disable_heaters:
heater = self.pheaters.lookup_heater(heater_name)
self.target_save[heater_name] = heater.get_temp(0)[1]
- heater.set_temp(0.)
+ heater.set_temp(0.0)
+
def handle_homing_move_end(self, hmove):
if not self.check_eligible(hmove.get_mcu_endstops()):
return
@@ -60,5 +66,6 @@ class HomingHeaters:
heater = self.pheaters.lookup_heater(heater_name)
heater.set_temp(self.target_save[heater_name])
+
def load_config(config):
return HomingHeaters(config)
diff --git a/klippy/extras/homing_override.py b/klippy/extras/homing_override.py
index 0a987d59..1d24223d 100644
--- a/klippy/extras/homing_override.py
+++ b/klippy/extras/homing_override.py
@@ -4,19 +4,20 @@
#
# This file may be distributed under the terms of the GNU GPLv3 license.
+
class HomingOverride:
def __init__(self, config):
self.printer = config.get_printer()
- self.start_pos = [config.getfloat('set_position_' + a, None)
- for a in 'xyz']
- self.axes = config.get('axes', 'XYZ').upper()
- gcode_macro = self.printer.load_object(config, 'gcode_macro')
- self.template = gcode_macro.load_template(config, 'gcode')
+ self.start_pos = [config.getfloat("set_position_" + a, None) for a in "xyz"]
+ self.axes = config.get("axes", "XYZ").upper()
+ gcode_macro = self.printer.load_object(config, "gcode_macro")
+ self.template = gcode_macro.load_template(config, "gcode")
self.in_script = False
- self.printer.load_object(config, 'homing')
- self.gcode = self.printer.lookup_object('gcode')
+ self.printer.load_object(config, "homing")
+ self.gcode = self.printer.lookup_object("gcode")
self.prev_G28 = self.gcode.register_command("G28", None)
self.gcode.register_command("G28", self.cmd_G28)
+
def cmd_G28(self, gcmd):
if self.in_script:
# Was called recursively - invoke the real G28 command
@@ -25,7 +26,7 @@ class HomingOverride:
# if no axis is given as parameter we assume the override
no_axis = True
- for axis in 'XYZ':
+ for axis in "XYZ":
if gcmd.get(axis, None) is not None:
no_axis = False
break
@@ -44,7 +45,7 @@ class HomingOverride:
return
# Calculate forced position (if configured)
- toolhead = self.printer.lookup_object('toolhead')
+ toolhead = self.printer.lookup_object("toolhead")
pos = toolhead.get_position()
homing_axes = ""
for axis, loc in enumerate(self.start_pos):
@@ -54,13 +55,14 @@ class HomingOverride:
toolhead.set_position(pos, homing_axes=homing_axes)
# Perform homing
context = self.template.create_template_context()
- context['params'] = gcmd.get_command_parameters()
- context['rawparams'] = gcmd.get_raw_command_parameters()
+ context["params"] = gcmd.get_command_parameters()
+ context["rawparams"] = gcmd.get_raw_command_parameters()
try:
self.in_script = True
self.template.run_gcode_from_command(context)
finally:
self.in_script = False
+
def load_config(config):
return HomingOverride(config)
diff --git a/klippy/extras/htu21d.py b/klippy/extras/htu21d.py
index 688338d9..d7104503 100644
--- a/klippy/extras/htu21d.py
+++ b/klippy/extras/htu21d.py
@@ -19,73 +19,78 @@ from . import bus
#
######################################################################
-HTU21D_I2C_ADDR= 0x40
+HTU21D_I2C_ADDR = 0x40
HTU21D_COMMANDS = {
- 'HTU21D_TEMP' :0xE3,
- 'HTU21D_HUMID' :0xE5,
- 'HTU21D_TEMP_NH' :0xF3,
- 'HTU21D_HUMID_NH' :0xF5,
- 'WRITE' :0xE6,
- 'READ' :0xE7,
- 'RESET' :0xFE,
- 'SERIAL' :[0xFA,0x0F,0xFC,0xC9],
- 'FIRMWARE_READ' :[0x84,0xB8]
-
+ "HTU21D_TEMP": 0xE3,
+ "HTU21D_HUMID": 0xE5,
+ "HTU21D_TEMP_NH": 0xF3,
+ "HTU21D_HUMID_NH": 0xF5,
+ "WRITE": 0xE6,
+ "READ": 0xE7,
+ "RESET": 0xFE,
+ "SERIAL": [0xFA, 0x0F, 0xFC, 0xC9],
+ "FIRMWARE_READ": [0x84, 0xB8],
}
HTU21D_RESOLUTION_MASK = 0x7E
HTU21D_RESOLUTIONS = {
- 'TEMP14_HUM12':int('00000000',2),
- 'TEMP13_HUM10':int('10000000',2),
- 'TEMP12_HUM08':int('00000001',2),
- 'TEMP11_HUM11':int('10000001',2)
+ "TEMP14_HUM12": int("00000000", 2),
+ "TEMP13_HUM10": int("10000000", 2),
+ "TEMP12_HUM08": int("00000001", 2),
+ "TEMP11_HUM11": int("10000001", 2),
}
ID_MAP = {
- 0x0D: 'SI7013',
- 0x14: 'SI7020',
- 0x15: 'SI7021',
- 0x31: 'SHT21',
- 0x01: 'SHT21',
- 0x32: 'HTU21D',
+ 0x0D: "SI7013",
+ 0x14: "SI7020",
+ 0x15: "SI7021",
+ 0x31: "SHT21",
+ 0x01: "SHT21",
+ 0x32: "HTU21D",
}
# Device with conversion time for tmp/resolution bit
# The format is:
# <CHIPNAME>:{id:<ID>, ..<RESOlUTION>:[<temp time>,<humidity time>].. }
HTU21D_DEVICES = {
- 'SI7013':{
- 'TEMP14_HUM12':[.11,.12],
- 'TEMP13_HUM10':[ .7, .5],
- 'TEMP12_HUM08':[ .4, .4],
- 'TEMP11_HUM11':[ .3, .7]},
- 'SI7020':{
- 'TEMP14_HUM12':[.11,.12],
- 'TEMP13_HUM10':[ .7, .5],
- 'TEMP12_HUM08':[ .4, .4],
- 'TEMP11_HUM11':[ .3, .7]},
- 'SI7021':{
- 'TEMP14_HUM12':[.11,.12],
- 'TEMP13_HUM10':[ .7, .5],
- 'TEMP12_HUM08':[ .4, .4],
- 'TEMP11_HUM11':[ .3, .7]},
- 'SHT21': {
- 'TEMP14_HUM12':[.85,.29],
- 'TEMP13_HUM10':[.43, .9],
- 'TEMP12_HUM08':[.22, .4],
- 'TEMP11_HUM11':[.11,.15]},
- 'HTU21D':{
- 'TEMP14_HUM12':[.50,.16],
- 'TEMP13_HUM10':[.25, .5],
- 'TEMP12_HUM08':[.13, .3],
- 'TEMP11_HUM11':[.12, .8]}
+ "SI7013": {
+ "TEMP14_HUM12": [0.11, 0.12],
+ "TEMP13_HUM10": [0.7, 0.5],
+ "TEMP12_HUM08": [0.4, 0.4],
+ "TEMP11_HUM11": [0.3, 0.7],
+ },
+ "SI7020": {
+ "TEMP14_HUM12": [0.11, 0.12],
+ "TEMP13_HUM10": [0.7, 0.5],
+ "TEMP12_HUM08": [0.4, 0.4],
+ "TEMP11_HUM11": [0.3, 0.7],
+ },
+ "SI7021": {
+ "TEMP14_HUM12": [0.11, 0.12],
+ "TEMP13_HUM10": [0.7, 0.5],
+ "TEMP12_HUM08": [0.4, 0.4],
+ "TEMP11_HUM11": [0.3, 0.7],
+ },
+ "SHT21": {
+ "TEMP14_HUM12": [0.85, 0.29],
+ "TEMP13_HUM10": [0.43, 0.9],
+ "TEMP12_HUM08": [0.22, 0.4],
+ "TEMP11_HUM11": [0.11, 0.15],
+ },
+ "HTU21D": {
+ "TEMP14_HUM12": [0.50, 0.16],
+ "TEMP13_HUM10": [0.25, 0.5],
+ "TEMP12_HUM08": [0.13, 0.3],
+ "TEMP11_HUM11": [0.12, 0.8],
+ },
}
-#temperature coefficient for RH compensation at range 0C..80C,
+# temperature coefficient for RH compensation at range 0C..80C,
# for HTU21D & SHT21 only
-HTU21D_TEMP_COEFFICIENT= -0.15
-#crc8 polynomial for 16bit value, CRC8 -> x^8 + x^5 + x^4 + 1
-HTU21D_CRC8_POLYNOMINAL= 0x13100
+HTU21D_TEMP_COEFFICIENT = -0.15
+# crc8 polynomial for 16bit value, CRC8 -> x^8 + x^5 + x^4 + 1
+HTU21D_CRC8_POLYNOMINAL = 0x13100
+
class HTU21D:
def __init__(self, config):
@@ -93,19 +98,21 @@ class HTU21D:
self.name = config.get_name().split()[-1]
self.reactor = self.printer.get_reactor()
self.i2c = bus.MCU_I2C_from_config(
- config, default_addr=HTU21D_I2C_ADDR, default_speed=100000)
- self.hold_master_mode = config.getboolean('htu21d_hold_master',False)
- self.resolution = config.get('htu21d_resolution','TEMP12_HUM08')
- self.report_time = config.getint('htu21d_report_time',30,minval=5)
+ config, default_addr=HTU21D_I2C_ADDR, default_speed=100000
+ )
+ self.hold_master_mode = config.getboolean("htu21d_hold_master", False)
+ self.resolution = config.get("htu21d_resolution", "TEMP12_HUM08")
+ self.report_time = config.getint("htu21d_report_time", 30, minval=5)
if self.resolution not in HTU21D_RESOLUTIONS:
- raise config.error("Invalid HTU21D Resolution. Valid are %s"
- % '|'.join(HTU21D_RESOLUTIONS.keys()))
- self.deviceId = config.get('sensor_type')
- self.temp = self.min_temp = self.max_temp = self.humidity = 0.
+ raise config.error(
+ "Invalid HTU21D Resolution. Valid are %s"
+ % "|".join(HTU21D_RESOLUTIONS.keys())
+ )
+ self.deviceId = config.get("sensor_type")
+ self.temp = self.min_temp = self.max_temp = self.humidity = 0.0
self.sample_timer = self.reactor.register_timer(self._sample_htu21d)
self.printer.add_object("htu21d " + self.name, self)
- self.printer.register_event_handler("klippy:connect",
- self.handle_connect)
+ self.printer.register_event_handler("klippy:connect", self.handle_connect)
def handle_connect(self):
self._init_htu21d()
@@ -123,14 +130,15 @@ class HTU21D:
def _init_htu21d(self):
# Device Soft Reset
- self.i2c.i2c_write([HTU21D_COMMANDS['RESET']])
+ self.i2c.i2c_write([HTU21D_COMMANDS["RESET"]])
# Wait 15ms after reset
- self.reactor.pause(self.reactor.monotonic() + .15)
+ self.reactor.pause(self.reactor.monotonic() + 0.15)
# Read ChipId
- params = self.i2c.i2c_read([HTU21D_COMMANDS['SERIAL'][2],
- HTU21D_COMMANDS['SERIAL'][3]], 3)
- response = bytearray(params['response'])
+ params = self.i2c.i2c_read(
+ [HTU21D_COMMANDS["SERIAL"][2], HTU21D_COMMANDS["SERIAL"][3]], 3
+ )
+ response = bytearray(params["response"])
rdevId = response[0] << 8
rdevId |= response[1]
checksum = response[2]
@@ -145,98 +153,104 @@ class HTU21D:
if self.deviceId != guess_dev:
logging.warning(
- "htu21d: Found device %s. Forcing to type %s as config." %
- (guess_dev, self.deviceId))
+ "htu21d: Found device %s. Forcing to type %s as config."
+ % (guess_dev, self.deviceId)
+ )
# Set Resolution
- params = self.i2c.i2c_read([HTU21D_COMMANDS['READ']], 1)
- response = bytearray(params['response'])
+ params = self.i2c.i2c_read([HTU21D_COMMANDS["READ"]], 1)
+ response = bytearray(params["response"])
registerData = response[0] & HTU21D_RESOLUTION_MASK
registerData |= HTU21D_RESOLUTIONS[self.resolution]
- self.i2c.i2c_write([HTU21D_COMMANDS['WRITE']],registerData)
+ self.i2c.i2c_write([HTU21D_COMMANDS["WRITE"]], registerData)
logging.info("htu21d: Setting resolution to %s " % self.resolution)
def _sample_htu21d(self, eventtime):
try:
# Read Temperature
if self.hold_master_mode:
- params = self.i2c.i2c_write([HTU21D_COMMANDS['HTU21D_TEMP']])
+ params = self.i2c.i2c_write([HTU21D_COMMANDS["HTU21D_TEMP"]])
else:
- params = self.i2c.i2c_write([HTU21D_COMMANDS['HTU21D_TEMP_NH']])
+ params = self.i2c.i2c_write([HTU21D_COMMANDS["HTU21D_TEMP_NH"]])
# Wait
- self.reactor.pause(self.reactor.monotonic()
- + HTU21D_DEVICES[self.deviceId][self.resolution][0])
-
+ self.reactor.pause(
+ self.reactor.monotonic()
+ + HTU21D_DEVICES[self.deviceId][self.resolution][0]
+ )
- params = self.i2c.i2c_read([],3)
+ params = self.i2c.i2c_read([], 3)
- response = bytearray(params['response'])
- rtemp = response[0] << 8
+ response = bytearray(params["response"])
+ rtemp = response[0] << 8
rtemp |= response[1]
if self._chekCRC8(rtemp) != response[2]:
- logging.warning(
- "htu21d: Checksum error on Temperature reading!"
- )
+ logging.warning("htu21d: Checksum error on Temperature reading!")
else:
- self.temp = (0.002681 * float(rtemp) - 46.85)
+ self.temp = 0.002681 * float(rtemp) - 46.85
logging.debug("htu21d: Temperature %.2f " % self.temp)
# Read Humidity
if self.hold_master_mode:
- self.i2c.i2c_write([HTU21D_COMMANDS['HTU21D_HUMID']])
+ self.i2c.i2c_write([HTU21D_COMMANDS["HTU21D_HUMID"]])
else:
- self.i2c.i2c_write([HTU21D_COMMANDS['HTU21D_HUMID_NH']])
+ self.i2c.i2c_write([HTU21D_COMMANDS["HTU21D_HUMID_NH"]])
# Wait
- self.reactor.pause(self.reactor.monotonic()
- + HTU21D_DEVICES[self.deviceId][self.resolution][1])
+ self.reactor.pause(
+ self.reactor.monotonic()
+ + HTU21D_DEVICES[self.deviceId][self.resolution][1]
+ )
- params = self.i2c.i2c_read([],3)
+ params = self.i2c.i2c_read([], 3)
- response = bytearray(params['response'])
+ response = bytearray(params["response"])
rhumid = response[0] << 8
- rhumid|= response[1]
+ rhumid |= response[1]
if self._chekCRC8(rhumid) != response[2]:
logging.warning("htu21d: Checksum error on Humidity reading!")
else:
- #clear status bits,
+ # clear status bits,
# humidity always returns xxxxxx10 in the LSB field
- rhumid ^= 0x02;
- self.humidity = (0.001907 * float(rhumid) - 6)
- if (self.humidity < 0):
- #due to RH accuracy, measured value might be
+ rhumid ^= 0x02
+ self.humidity = 0.001907 * float(rhumid) - 6
+ if self.humidity < 0:
+ # due to RH accuracy, measured value might be
# slightly less than 0 or more 100
self.humidity = 0
- elif (self.humidity > 100):
+ elif self.humidity > 100:
self.humidity = 100
# Only for HTU21D & SHT21.
# Calculates temperature compensated Humidity, %RH
- if( self.deviceId in ['SHT21','HTU21D']
- and self.temp > 0 and self.temp < 80):
+ if (
+ self.deviceId in ["SHT21", "HTU21D"]
+ and self.temp > 0
+ and self.temp < 80
+ ):
logging.debug("htu21d: Do temp compensation..")
self.humidity = self.humidity
- + (25.0 - self.temp) * HTU21D_TEMP_COEFFICIENT;
+ +(25.0 - self.temp) * HTU21D_TEMP_COEFFICIENT
logging.debug("htu21d: Humidity %.2f " % self.humidity)
except Exception:
logging.exception("htu21d: Error reading data")
- self.temp = self.humidity = .0
+ self.temp = self.humidity = 0.0
return self.reactor.NEVER
if self.temp < self.min_temp or self.temp > self.max_temp:
self.printer.invoke_shutdown(
"HTU21D temperature %0.1f outside range of %0.1f:%.01f"
- % (self.temp, self.min_temp, self.max_temp))
+ % (self.temp, self.min_temp, self.max_temp)
+ )
measured_time = self.reactor.monotonic()
print_time = self.i2c.get_mcu().estimated_print_time(measured_time)
self._callback(print_time, self.temp)
return measured_time + self.report_time
- def _chekCRC8(self,data):
- for bit in range(0,16):
- if (data & 0x8000):
- data = (data << 1) ^ HTU21D_CRC8_POLYNOMINAL;
+ def _chekCRC8(self, data):
+ for bit in range(0, 16):
+ if data & 0x8000:
+ data = (data << 1) ^ HTU21D_CRC8_POLYNOMINAL
else:
data <<= 1
data = data >> 8
@@ -244,8 +258,8 @@ class HTU21D:
def get_status(self, eventtime):
return {
- 'temperature': round(self.temp, 2),
- 'humidity': self.humidity,
+ "temperature": round(self.temp, 2),
+ "humidity": self.humidity,
}
diff --git a/klippy/extras/hx71x.py b/klippy/extras/hx71x.py
index a7f49f8a..ad3134f8 100644
--- a/klippy/extras/hx71x.py
+++ b/klippy/extras/hx71x.py
@@ -13,35 +13,46 @@ UPDATE_INTERVAL = 0.10
SAMPLE_ERROR_DESYNC = -0x80000000
SAMPLE_ERROR_LONG_READ = 0x40000000
+
# Implementation of HX711 and HX717
class HX71xBase:
- def __init__(self, config, sensor_type,
- sample_rate_options, default_sample_rate,
- gain_options, default_gain):
+ def __init__(
+ self,
+ config,
+ sensor_type,
+ sample_rate_options,
+ default_sample_rate,
+ gain_options,
+ default_gain,
+ ):
self.printer = printer = config.get_printer()
self.name = config.get_name().split()[-1]
self.last_error_count = 0
self.consecutive_fails = 0
self.sensor_type = sensor_type
# Chip options
- dout_pin_name = config.get('dout_pin')
- sclk_pin_name = config.get('sclk_pin')
- ppins = printer.lookup_object('pins')
+ dout_pin_name = config.get("dout_pin")
+ sclk_pin_name = config.get("sclk_pin")
+ ppins = printer.lookup_object("pins")
dout_ppin = ppins.lookup_pin(dout_pin_name)
sclk_ppin = ppins.lookup_pin(sclk_pin_name)
- self.mcu = mcu = dout_ppin['chip']
+ self.mcu = mcu = dout_ppin["chip"]
self.oid = mcu.create_oid()
- if sclk_ppin['chip'] is not mcu:
- raise config.error("%s config error: All pins must be "
- "connected to the same MCU" % (self.name,))
- self.dout_pin = dout_ppin['pin']
- self.sclk_pin = sclk_ppin['pin']
+ if sclk_ppin["chip"] is not mcu:
+ raise config.error(
+ "%s config error: All pins must be "
+ "connected to the same MCU" % (self.name,)
+ )
+ self.dout_pin = dout_ppin["pin"]
+ self.sclk_pin = sclk_ppin["pin"]
# Samples per second choices
- self.sps = config.getchoice('sample_rate', sample_rate_options,
- default=default_sample_rate)
+ self.sps = config.getchoice(
+ "sample_rate", sample_rate_options, default=default_sample_rate
+ )
# gain/channel choices
- self.gain_channel = int(config.getchoice('gain', gain_options,
- default=default_gain))
+ self.gain_channel = int(
+ config.getchoice("gain", gain_options, default=default_gain)
+ )
## Bulk Sensor Setup
self.bulk_queue = bulk_sensor.BulkDataQueue(mcu, oid=self.oid)
# Clock tracking
@@ -49,28 +60,35 @@ class HX71xBase:
self.ffreader = bulk_sensor.FixedFreqReader(mcu, chip_smooth, "<i")
# Process messages in batches
self.batch_bulk = bulk_sensor.BatchBulkHelper(
- self.printer, self._process_batch, self._start_measurements,
- self._finish_measurements, UPDATE_INTERVAL)
+ self.printer,
+ self._process_batch,
+ self._start_measurements,
+ self._finish_measurements,
+ UPDATE_INTERVAL,
+ )
# Command Configuration
self.query_hx71x_cmd = None
self.attach_probe_cmd = None
mcu.add_config_cmd(
"config_hx71x oid=%d gain_channel=%d dout_pin=%s sclk_pin=%s"
- % (self.oid, self.gain_channel, self.dout_pin, self.sclk_pin))
- mcu.add_config_cmd("query_hx71x oid=%d rest_ticks=0"
- % (self.oid,), on_restart=True)
+ % (self.oid, self.gain_channel, self.dout_pin, self.sclk_pin)
+ )
+ mcu.add_config_cmd(
+ "query_hx71x oid=%d rest_ticks=0" % (self.oid,), on_restart=True
+ )
mcu.register_config_callback(self._build_config)
def _build_config(self):
self.query_hx71x_cmd = self.mcu.lookup_command(
- "query_hx71x oid=%c rest_ticks=%u")
+ "query_hx71x oid=%c rest_ticks=%u"
+ )
self.attach_probe_cmd = self.mcu.lookup_command(
- "hx71x_attach_load_cell_probe oid=%c load_cell_probe_oid=%c")
- self.ffreader.setup_query_command("query_hx71x_status oid=%c",
- oid=self.oid,
- cq=self.mcu.alloc_command_queue())
-
+ "hx71x_attach_load_cell_probe oid=%c load_cell_probe_oid=%c"
+ )
+ self.ffreader.setup_query_command(
+ "query_hx71x_status oid=%c", oid=self.oid, cq=self.mcu.alloc_command_queue()
+ )
def get_mcu(self):
return self.mcu
@@ -92,7 +110,7 @@ class HX71xBase:
# Measurement decoding
def _convert_samples(self, samples):
- adc_factor = 1. / (1 << 23)
+ adc_factor = 1.0 / (1 << 23)
count = 0
for ptime, val in samples:
if val == SAMPLE_ERROR_DESYNC or val == SAMPLE_ERROR_LONG_READ:
@@ -107,10 +125,9 @@ class HX71xBase:
self.consecutive_fails = 0
self.last_error_count = 0
# Start bulk reading
- rest_ticks = self.mcu.seconds_to_clock(1. / (10. * self.sps))
+ rest_ticks = self.mcu.seconds_to_clock(1.0 / (10.0 * self.sps))
self.query_hx71x_cmd.send([self.oid, rest_ticks])
- logging.info("%s starting '%s' measurements",
- self.sensor_type, self.name)
+ logging.info("%s starting '%s' measurements", self.sensor_type, self.name)
# Initialize clock tracking
self.ffreader.note_start()
@@ -121,8 +138,7 @@ class HX71xBase:
# Halt bulk reading
self.query_hx71x_cmd.send_wait_ack([self.oid, 0])
self.ffreader.note_end()
- logging.info("%s finished '%s' measurements",
- self.sensor_type, self.name)
+ logging.info("%s finished '%s' measurements", self.sensor_type, self.name)
def _process_batch(self, eventtime):
prev_overflows = self.ffreader.get_last_overflows()
@@ -138,34 +154,42 @@ class HX71xBase:
elif overflows > 0:
self.consecutive_fails += 1
if self.consecutive_fails > 4:
- logging.error("%s: Forced sensor restart due to overflows",
- self.name)
+ logging.error("%s: Forced sensor restart due to overflows", self.name)
self._finish_measurements()
self._start_measurements()
else:
self.consecutive_fails = 0
- return {'data': samples, 'errors': self.last_error_count,
- 'overflows': self.ffreader.get_last_overflows()}
+ return {
+ "data": samples,
+ "errors": self.last_error_count,
+ "overflows": self.ffreader.get_last_overflows(),
+ }
def HX711(config):
- return HX71xBase(config, "hx711",
- # HX711 sps options
- {80: 80, 10: 10}, 80,
- # HX711 gain/channel options
- {'A-128': 1, 'B-32': 2, 'A-64': 3}, 'A-128')
+ return HX71xBase(
+ config,
+ "hx711",
+ # HX711 sps options
+ {80: 80, 10: 10},
+ 80,
+ # HX711 gain/channel options
+ {"A-128": 1, "B-32": 2, "A-64": 3},
+ "A-128",
+ )
def HX717(config):
- return HX71xBase(config, "hx717",
- # HX717 sps options
- {320: 320, 80: 80, 20: 20, 10: 10}, 320,
- # HX717 gain/channel options
- {'A-128': 1, 'B-64': 2, 'A-64': 3,
- 'B-8': 4}, 'A-128')
+ return HX71xBase(
+ config,
+ "hx717",
+ # HX717 sps options
+ {320: 320, 80: 80, 20: 20, 10: 10},
+ 320,
+ # HX717 gain/channel options
+ {"A-128": 1, "B-64": 2, "A-64": 3, "B-8": 4},
+ "A-128",
+ )
-HX71X_SENSOR_TYPES = {
- "hx711": HX711,
- "hx717": HX717
-}
+HX71X_SENSOR_TYPES = {"hx711": HX711, "hx717": HX717}
diff --git a/klippy/extras/icm20948.py b/klippy/extras/icm20948.py
index 92b89cc0..c99224fa 100644
--- a/klippy/extras/icm20948.py
+++ b/klippy/extras/icm20948.py
@@ -12,42 +12,42 @@
import logging
from . import bus, adxl345, bulk_sensor
-ICM20948_ADDR = 0x68
+ICM20948_ADDR = 0x68
ICM_DEV_IDS = {
0xEA: "icm-20948",
- #everything above are normal ICM IDs
+ # everything above are normal ICM IDs
}
# ICM20948 registers
-REG_DEVID = 0x00 # 0xEA
-REG_FIFO_EN = 0x67 # FIFO_EN_2
-REG_ACCEL_SMPLRT_DIV1 = 0x10 # MSB
-REG_ACCEL_SMPLRT_DIV2 = 0x11 # LSB
-REG_ACCEL_CONFIG = 0x14
-REG_USER_CTRL = 0x03
-REG_PWR_MGMT_1 = 0x06
-REG_PWR_MGMT_2 = 0x07
-REG_INT_STATUS = 0x19
-REG_BANK_SEL = 0x7F
+REG_DEVID = 0x00 # 0xEA
+REG_FIFO_EN = 0x67 # FIFO_EN_2
+REG_ACCEL_SMPLRT_DIV1 = 0x10 # MSB
+REG_ACCEL_SMPLRT_DIV2 = 0x11 # LSB
+REG_ACCEL_CONFIG = 0x14
+REG_USER_CTRL = 0x03
+REG_PWR_MGMT_1 = 0x06
+REG_PWR_MGMT_2 = 0x07
+REG_INT_STATUS = 0x19
+REG_BANK_SEL = 0x7F
-SAMPLE_RATE_DIVS = { 4500: 0x00 }
+SAMPLE_RATE_DIVS = {4500: 0x00}
-SET_BANK_0 = 0x00
-SET_BANK_1 = 0x10
-SET_BANK_2 = 0x20
-SET_BANK_3 = 0x30
-SET_ACCEL_CONFIG = 0x06 # 16g full scale, 1209Hz BW, 4.5kHz samp rate
-SET_PWR_MGMT_1_WAKE = 0x01
-SET_PWR_MGMT_1_SLEEP = 0x41
+SET_BANK_0 = 0x00
+SET_BANK_1 = 0x10
+SET_BANK_2 = 0x20
+SET_BANK_3 = 0x30
+SET_ACCEL_CONFIG = 0x06 # 16g full scale, 1209Hz BW, 4.5kHz samp rate
+SET_PWR_MGMT_1_WAKE = 0x01
+SET_PWR_MGMT_1_SLEEP = 0x41
SET_PWR_MGMT_2_ACCEL_ON = 0x07
-SET_PWR_MGMT_2_OFF = 0x3F
-SET_USER_FIFO_RESET = 0x0E
-SET_USER_FIFO_EN = 0x40
-SET_ENABLE_FIFO = 0x10
-SET_DISABLE_FIFO = 0x00
+SET_PWR_MGMT_2_OFF = 0x3F
+SET_USER_FIFO_RESET = 0x0E
+SET_USER_FIFO_EN = 0x40
+SET_ENABLE_FIFO = 0x10
+SET_DISABLE_FIFO = 0x00
-FREEFALL_ACCEL = 9.80665 * 1000.
+FREEFALL_ACCEL = 9.80665 * 1000.0
# SCALE = 1/2048 g/LSB @16g scale * Earth gravity in mm/s**2
SCALE = 0.00048828125 * FREEFALL_ACCEL
@@ -55,19 +55,20 @@ FIFO_SIZE = 512
BATCH_UPDATES = 0.100
+
# Printer class that controls ICM20948 chip
class ICM20948:
def __init__(self, config):
self.printer = config.get_printer()
adxl345.AccelCommandHelper(config, self)
self.axes_map = adxl345.read_axes_map(config, SCALE, SCALE, SCALE)
- self.data_rate = config.getint('rate', 4500)
+ self.data_rate = config.getint("rate", 4500)
if self.data_rate not in SAMPLE_RATE_DIVS:
raise config.error("Invalid rate parameter: %d" % (self.data_rate,))
# Setup mcu sensor_icm20948 bulk query code
- self.i2c = bus.MCU_I2C_from_config(config,
- default_addr=ICM20948_ADDR,
- default_speed=400000)
+ self.i2c = bus.MCU_I2C_from_config(
+ config, default_addr=ICM20948_ADDR, default_speed=400000
+ )
self.mcu = mcu = self.i2c.get_mcu()
self.oid = mcu.create_oid()
self.query_icm20948_cmd = None
@@ -78,31 +79,45 @@ class ICM20948:
self.last_error_count = 0
# Process messages in batches
self.batch_bulk = bulk_sensor.BatchBulkHelper(
- self.printer, self._process_batch,
- self._start_measurements, self._finish_measurements, BATCH_UPDATES)
+ self.printer,
+ self._process_batch,
+ self._start_measurements,
+ self._finish_measurements,
+ BATCH_UPDATES,
+ )
self.name = config.get_name().split()[-1]
- hdr = ('time', 'x_acceleration', 'y_acceleration', 'z_acceleration')
- self.batch_bulk.add_mux_endpoint("icm20948/dump_icm20948", "sensor",
- self.name, {'header': hdr})
+ hdr = ("time", "x_acceleration", "y_acceleration", "z_acceleration")
+ self.batch_bulk.add_mux_endpoint(
+ "icm20948/dump_icm20948", "sensor", self.name, {"header": hdr}
+ )
+
def _build_config(self):
cmdqueue = self.i2c.get_command_queue()
- self.mcu.add_config_cmd("config_icm20948 oid=%d i2c_oid=%d"
- % (self.oid, self.i2c.get_oid()))
- self.mcu.add_config_cmd("query_icm20948 oid=%d rest_ticks=0"
- % (self.oid,), on_restart=True)
+ self.mcu.add_config_cmd(
+ "config_icm20948 oid=%d i2c_oid=%d" % (self.oid, self.i2c.get_oid())
+ )
+ self.mcu.add_config_cmd(
+ "query_icm20948 oid=%d rest_ticks=0" % (self.oid,), on_restart=True
+ )
self.query_icm20948_cmd = self.mcu.lookup_command(
- "query_icm20948 oid=%c rest_ticks=%u", cq=cmdqueue)
- self.ffreader.setup_query_command("query_icm20948_status oid=%c",
- oid=self.oid, cq=cmdqueue)
+ "query_icm20948 oid=%c rest_ticks=%u", cq=cmdqueue
+ )
+ self.ffreader.setup_query_command(
+ "query_icm20948_status oid=%c", oid=self.oid, cq=cmdqueue
+ )
+
def read_reg(self, reg):
params = self.i2c.i2c_read([reg], 1)
- return bytearray(params['response'])[0]
+ return bytearray(params["response"])[0]
+
def set_reg(self, reg, val, minclock=0):
self.i2c.i2c_write([reg, val & 0xFF], minclock=minclock)
+
def start_internal_client(self):
aqh = adxl345.AccelQueryHelper(self.printer)
self.batch_bulk.add_client(aqh.handle_batch)
return aqh
+
# Measurement decoding
def _convert_samples(self, samples):
(x_pos, x_scale), (y_pos, y_scale), (z_pos, z_scale) = self.axes_map
@@ -114,6 +129,7 @@ class ICM20948:
z = round(raw_xyz[z_pos] * z_scale, 6)
samples[count] = (round(ptime, 6), x, y, z)
count += 1
+
# Start, stop, and process message batches
def _start_measurements(self):
# In case of miswiring, testing ICM20948 device ID prevents treating
@@ -123,15 +139,15 @@ class ICM20948:
raise self.printer.command_error(
"Invalid mpu id (got %x).\n"
"This is generally indicative of connection problems\n"
- "(e.g. faulty wiring) or a faulty chip."
- % (dev_id))
+ "(e.g. faulty wiring) or a faulty chip." % (dev_id)
+ )
else:
- logging.info("Found %s with id %x"% (ICM_DEV_IDS[dev_id], dev_id))
+ logging.info("Found %s with id %x" % (ICM_DEV_IDS[dev_id], dev_id))
# Setup chip in requested query rate
self.set_reg(REG_PWR_MGMT_1, SET_PWR_MGMT_1_WAKE)
self.set_reg(REG_PWR_MGMT_2, SET_PWR_MGMT_2_ACCEL_ON)
# Don't add 20ms pause for accelerometer chip wake up
- self.read_reg(REG_DEVID) # Dummy read to ensure queues flushed
+ self.read_reg(REG_DEVID) # Dummy read to ensure queues flushed
self.set_reg(REG_ACCEL_SMPLRT_DIV1, SAMPLE_RATE_DIVS[self.data_rate])
self.set_reg(REG_ACCEL_SMPLRT_DIV2, SAMPLE_RATE_DIVS[self.data_rate])
self.set_reg(REG_BANK_SEL, SET_BANK_2)
@@ -141,15 +157,16 @@ class ICM20948:
self.set_reg(REG_FIFO_EN, SET_DISABLE_FIFO)
self.set_reg(REG_USER_CTRL, SET_USER_FIFO_RESET)
self.set_reg(REG_USER_CTRL, SET_USER_FIFO_EN)
- self.read_reg(REG_INT_STATUS) # clear FIFO overflow flag
+ self.read_reg(REG_INT_STATUS) # clear FIFO overflow flag
# Start bulk reading
- rest_ticks = self.mcu.seconds_to_clock(4. / self.data_rate)
+ rest_ticks = self.mcu.seconds_to_clock(4.0 / self.data_rate)
self.query_icm20948_cmd.send([self.oid, rest_ticks])
self.set_reg(REG_FIFO_EN, SET_ENABLE_FIFO)
logging.info("ICM20948 starting '%s' measurements", self.name)
# Initialize clock tracking
self.ffreader.note_start()
self.last_error_count = 0
+
def _finish_measurements(self):
# Halt bulk reading
self.set_reg(REG_FIFO_EN, SET_DISABLE_FIFO)
@@ -158,16 +175,22 @@ class ICM20948:
logging.info("ICM20948 finished '%s' measurements", self.name)
self.set_reg(REG_PWR_MGMT_1, SET_PWR_MGMT_1_SLEEP)
self.set_reg(REG_PWR_MGMT_2, SET_PWR_MGMT_2_OFF)
+
def _process_batch(self, eventtime):
samples = self.ffreader.pull_samples()
self._convert_samples(samples)
if not samples:
return {}
- return {'data': samples, 'errors': self.last_error_count,
- 'overflows': self.ffreader.get_last_overflows()}
+ return {
+ "data": samples,
+ "errors": self.last_error_count,
+ "overflows": self.ffreader.get_last_overflows(),
+ }
+
def load_config(config):
return ICM20948(config)
+
def load_config_prefix(config):
return ICM20948(config)
diff --git a/klippy/extras/idle_timeout.py b/klippy/extras/idle_timeout.py
index 6ab2a34a..cc722b82 100644
--- a/klippy/extras/idle_timeout.py
+++ b/klippy/extras/idle_timeout.py
@@ -13,34 +13,40 @@ M84
"""
PIN_MIN_TIME = 0.100
-READY_TIMEOUT = .500
+READY_TIMEOUT = 0.500
+
class IdleTimeout:
def __init__(self, config):
self.printer = config.get_printer()
self.reactor = self.printer.get_reactor()
- self.gcode = self.printer.lookup_object('gcode')
+ self.gcode = self.printer.lookup_object("gcode")
self.toolhead = self.timeout_timer = None
self.printer.register_event_handler("klippy:ready", self.handle_ready)
- self.idle_timeout = config.getfloat('timeout', 600., above=0.)
- gcode_macro = self.printer.load_object(config, 'gcode_macro')
- self.idle_gcode = gcode_macro.load_template(config, 'gcode',
- DEFAULT_IDLE_GCODE)
- self.gcode.register_command('SET_IDLE_TIMEOUT',
- self.cmd_SET_IDLE_TIMEOUT,
- desc=self.cmd_SET_IDLE_TIMEOUT_help)
+ self.idle_timeout = config.getfloat("timeout", 600.0, above=0.0)
+ gcode_macro = self.printer.load_object(config, "gcode_macro")
+ self.idle_gcode = gcode_macro.load_template(config, "gcode", DEFAULT_IDLE_GCODE)
+ self.gcode.register_command(
+ "SET_IDLE_TIMEOUT",
+ self.cmd_SET_IDLE_TIMEOUT,
+ desc=self.cmd_SET_IDLE_TIMEOUT_help,
+ )
self.state = "Idle"
- self.last_print_start_systime = 0.
+ self.last_print_start_systime = 0.0
+
def get_status(self, eventtime):
- printing_time = 0.
+ printing_time = 0.0
if self.state == "Printing":
printing_time = eventtime - self.last_print_start_systime
- return { "state": self.state, "printing_time": printing_time }
+ return {"state": self.state, "printing_time": printing_time}
+
def handle_ready(self):
- self.toolhead = self.printer.lookup_object('toolhead')
+ self.toolhead = self.printer.lookup_object("toolhead")
self.timeout_timer = self.reactor.register_timer(self.timeout_handler)
- self.printer.register_event_handler("toolhead:sync_print_time",
- self.handle_sync_print_time)
+ self.printer.register_event_handler(
+ "toolhead:sync_print_time", self.handle_sync_print_time
+ )
+
def transition_idle_state(self, eventtime):
self.state = "Printing"
try:
@@ -49,17 +55,19 @@ class IdleTimeout:
except:
logging.exception("idle timeout gcode execution")
self.state = "Ready"
- return eventtime + 1.
+ return eventtime + 1.0
print_time = self.toolhead.get_last_move_time()
self.state = "Idle"
self.printer.send_event("idle_timeout:idle", print_time)
return self.reactor.NEVER
+
def check_idle_timeout(self, eventtime):
# Make sure toolhead class isn't busy
print_time, est_print_time, lookahead_empty = self.toolhead.check_busy(
- eventtime)
+ eventtime
+ )
idle_time = est_print_time - print_time
- if not lookahead_empty or idle_time < 1.:
+ if not lookahead_empty or idle_time < 1.0:
# Toolhead is busy
return eventtime + self.idle_timeout
if idle_time < self.idle_timeout:
@@ -67,9 +75,10 @@ class IdleTimeout:
return eventtime + self.idle_timeout - idle_time
if self.gcode.get_mutex().test():
# Gcode class busy
- return eventtime + 1.
+ return eventtime + 1.0
# Idle timeout has elapsed
return self.transition_idle_state(eventtime)
+
def timeout_handler(self, eventtime):
if self.printer.is_shutdown():
return self.reactor.NEVER
@@ -77,11 +86,12 @@ class IdleTimeout:
return self.check_idle_timeout(eventtime)
# Check if need to transition to "ready" state
print_time, est_print_time, lookahead_empty = self.toolhead.check_busy(
- eventtime)
- buffer_time = min(2., print_time - est_print_time)
+ eventtime
+ )
+ buffer_time = min(2.0, print_time - est_print_time)
if not lookahead_empty:
# Toolhead is busy
- return eventtime + READY_TIMEOUT + max(0., buffer_time)
+ return eventtime + READY_TIMEOUT + max(0.0, buffer_time)
if buffer_time > -READY_TIMEOUT:
# Wait for ready timeout
return eventtime + READY_TIMEOUT + buffer_time
@@ -90,9 +100,9 @@ class IdleTimeout:
return eventtime + READY_TIMEOUT
# Transition to "ready" state
self.state = "Ready"
- self.printer.send_event("idle_timeout:ready",
- est_print_time + PIN_MIN_TIME)
+ self.printer.send_event("idle_timeout:ready", est_print_time + PIN_MIN_TIME)
return eventtime + self.idle_timeout
+
def handle_sync_print_time(self, curtime, print_time, est_print_time):
if self.state == "Printing":
return
@@ -101,16 +111,18 @@ class IdleTimeout:
self.last_print_start_systime = curtime
check_time = READY_TIMEOUT + print_time - est_print_time
self.reactor.update_timer(self.timeout_timer, curtime + check_time)
- self.printer.send_event("idle_timeout:printing",
- est_print_time + PIN_MIN_TIME)
+ self.printer.send_event("idle_timeout:printing", est_print_time + PIN_MIN_TIME)
+
cmd_SET_IDLE_TIMEOUT_help = "Set the idle timeout in seconds"
+
def cmd_SET_IDLE_TIMEOUT(self, gcmd):
- timeout = gcmd.get_float('TIMEOUT', self.idle_timeout, above=0.)
+ timeout = gcmd.get_float("TIMEOUT", self.idle_timeout, above=0.0)
self.idle_timeout = timeout
gcmd.respond_info("idle_timeout: Timeout set to %.2f s" % (timeout,))
if self.state == "Ready":
checktime = self.reactor.monotonic() + timeout
self.reactor.update_timer(self.timeout_timer, checktime)
+
def load_config(config):
return IdleTimeout(config)
diff --git a/klippy/extras/input_shaper.py b/klippy/extras/input_shaper.py
index 67a287cd..11218d55 100644
--- a/klippy/extras/input_shaper.py
+++ b/klippy/extras/input_shaper.py
@@ -8,44 +8,54 @@ import collections
import chelper
from . import shaper_defs
+
class InputShaperParams:
def __init__(self, axis, config):
self.axis = axis
- self.shapers = {s.name : s.init_func for s in shaper_defs.INPUT_SHAPERS}
- shaper_type = config.get('shaper_type', 'mzv')
- self.shaper_type = config.get('shaper_type_' + axis, shaper_type)
+ self.shapers = {s.name: s.init_func for s in shaper_defs.INPUT_SHAPERS}
+ shaper_type = config.get("shaper_type", "mzv")
+ self.shaper_type = config.get("shaper_type_" + axis, shaper_type)
if self.shaper_type not in self.shapers:
- raise config.error(
- 'Unsupported shaper type: %s' % (self.shaper_type,))
- self.damping_ratio = config.getfloat('damping_ratio_' + axis,
- shaper_defs.DEFAULT_DAMPING_RATIO,
- minval=0., maxval=1.)
- self.shaper_freq = config.getfloat('shaper_freq_' + axis, 0., minval=0.)
+ raise config.error("Unsupported shaper type: %s" % (self.shaper_type,))
+ self.damping_ratio = config.getfloat(
+ "damping_ratio_" + axis,
+ shaper_defs.DEFAULT_DAMPING_RATIO,
+ minval=0.0,
+ maxval=1.0,
+ )
+ self.shaper_freq = config.getfloat("shaper_freq_" + axis, 0.0, minval=0.0)
+
def update(self, gcmd):
axis = self.axis.upper()
- self.damping_ratio = gcmd.get_float('DAMPING_RATIO_' + axis,
- self.damping_ratio,
- minval=0., maxval=1.)
- self.shaper_freq = gcmd.get_float('SHAPER_FREQ_' + axis,
- self.shaper_freq, minval=0.)
- shaper_type = gcmd.get('SHAPER_TYPE', None)
+ self.damping_ratio = gcmd.get_float(
+ "DAMPING_RATIO_" + axis, self.damping_ratio, minval=0.0, maxval=1.0
+ )
+ self.shaper_freq = gcmd.get_float(
+ "SHAPER_FREQ_" + axis, self.shaper_freq, minval=0.0
+ )
+ shaper_type = gcmd.get("SHAPER_TYPE", None)
if shaper_type is None:
- shaper_type = gcmd.get('SHAPER_TYPE_' + axis, self.shaper_type)
+ shaper_type = gcmd.get("SHAPER_TYPE_" + axis, self.shaper_type)
if shaper_type.lower() not in self.shapers:
- raise gcmd.error('Unsupported shaper type: %s' % (shaper_type,))
+ raise gcmd.error("Unsupported shaper type: %s" % (shaper_type,))
self.shaper_type = shaper_type.lower()
+
def get_shaper(self):
if not self.shaper_freq:
A, T = shaper_defs.get_none_shaper()
else:
- A, T = self.shapers[self.shaper_type](
- self.shaper_freq, self.damping_ratio)
+ A, T = self.shapers[self.shaper_type](self.shaper_freq, self.damping_ratio)
return len(A), A, T
+
def get_status(self):
- return collections.OrderedDict([
- ('shaper_type', self.shaper_type),
- ('shaper_freq', '%.3f' % (self.shaper_freq,)),
- ('damping_ratio', '%.6f' % (self.damping_ratio,))])
+ return collections.OrderedDict(
+ [
+ ("shaper_type", self.shaper_type),
+ ("shaper_freq", "%.3f" % (self.shaper_freq,)),
+ ("damping_ratio", "%.6f" % (self.damping_ratio,)),
+ ]
+ )
+
class AxisInputShaper:
def __init__(self, axis, config):
@@ -53,72 +63,96 @@ class AxisInputShaper:
self.params = InputShaperParams(axis, config)
self.n, self.A, self.T = self.params.get_shaper()
self.saved = None
+
def get_name(self):
- return 'shaper_' + self.axis
+ return "shaper_" + self.axis
+
def get_shaper(self):
return self.n, self.A, self.T
+
def update(self, gcmd):
self.params.update(gcmd)
self.n, self.A, self.T = self.params.get_shaper()
+
def set_shaper_kinematics(self, sk):
ffi_main, ffi_lib = chelper.get_ffi()
- success = ffi_lib.input_shaper_set_shaper_params(
- sk, self.axis.encode(), self.n, self.A, self.T) == 0
+ success = (
+ ffi_lib.input_shaper_set_shaper_params(
+ sk, self.axis.encode(), self.n, self.A, self.T
+ )
+ == 0
+ )
if not success:
self.disable_shaping()
ffi_lib.input_shaper_set_shaper_params(
- sk, self.axis.encode(), self.n, self.A, self.T)
+ sk, self.axis.encode(), self.n, self.A, self.T
+ )
return success
+
def is_enabled(self):
return self.n > 0
+
def disable_shaping(self):
if self.saved is None and self.n:
self.saved = (self.n, self.A, self.T)
A, T = shaper_defs.get_none_shaper()
self.n, self.A, self.T = len(A), A, T
+
def enable_shaping(self):
if self.saved is None:
# Input shaper was not disabled
return
self.n, self.A, self.T = self.saved
self.saved = None
+
def report(self, gcmd):
- info = ' '.join(["%s_%s:%s" % (key, self.axis, value)
- for (key, value) in self.params.get_status().items()])
+ info = " ".join(
+ [
+ "%s_%s:%s" % (key, self.axis, value)
+ for (key, value) in self.params.get_status().items()
+ ]
+ )
gcmd.respond_info(info)
+
class InputShaper:
def __init__(self, config):
self.printer = config.get_printer()
self.printer.register_event_handler("klippy:connect", self.connect)
- self.printer.register_event_handler("dual_carriage:update_kinematics",
- self._update_kinematics)
+ self.printer.register_event_handler(
+ "dual_carriage:update_kinematics", self._update_kinematics
+ )
self.toolhead = None
- self.shapers = [AxisInputShaper('x', config),
- AxisInputShaper('y', config)]
+ self.shapers = [AxisInputShaper("x", config), AxisInputShaper("y", config)]
self.input_shaper_stepper_kinematics = []
self.orig_stepper_kinematics = []
# Register gcode commands
- gcode = self.printer.lookup_object('gcode')
- gcode.register_command("SET_INPUT_SHAPER",
- self.cmd_SET_INPUT_SHAPER,
- desc=self.cmd_SET_INPUT_SHAPER_help)
+ gcode = self.printer.lookup_object("gcode")
+ gcode.register_command(
+ "SET_INPUT_SHAPER",
+ self.cmd_SET_INPUT_SHAPER,
+ desc=self.cmd_SET_INPUT_SHAPER_help,
+ )
+
def get_shapers(self):
return self.shapers
+
def connect(self):
self.toolhead = self.printer.lookup_object("toolhead")
- dual_carriage = self.printer.lookup_object('dual_carriage', None)
+ dual_carriage = self.printer.lookup_object("dual_carriage", None)
if dual_carriage is not None:
for shaper in self.shapers:
if shaper.is_enabled():
raise self.printer.config_error(
- 'Input shaper parameters cannot be configured via'
- ' [input_shaper] section with dual_carriage(s) '
- ' enabled. Refer to Klipper documentation on how '
- ' to configure input shaper for dual_carriage(s).')
+ "Input shaper parameters cannot be configured via"
+ " [input_shaper] section with dual_carriage(s) "
+ " enabled. Refer to Klipper documentation on how "
+ " to configure input shaper for dual_carriage(s)."
+ )
return
# Configure initial values
self._update_input_shaping(error=self.printer.config_error)
+
def _get_input_shaper_stepper_kinematics(self, stepper):
# Lookup stepper kinematics
sk = stepper.get_stepper_kinematics()
@@ -134,6 +168,7 @@ class InputShaper:
self.orig_stepper_kinematics.append(sk)
self.input_shaper_stepper_kinematics.append(is_sk)
return is_sk
+
def _update_kinematics(self):
if self.toolhead is None:
# Klipper initialization is not yet completed
@@ -150,8 +185,8 @@ class InputShaper:
ffi_lib.input_shaper_update_sk(is_sk)
new_delay = ffi_lib.input_shaper_get_step_generation_window(is_sk)
if old_delay != new_delay:
- self.toolhead.note_step_generation_scan_time(new_delay,
- old_delay)
+ self.toolhead.note_step_generation_scan_time(new_delay, old_delay)
+
def _update_input_shaping(self, error=None):
self.toolhead.flush_step_generation()
ffi_main, ffi_lib = chelper.get_ffi()
@@ -171,21 +206,26 @@ class InputShaper:
failed_shapers.append(shaper)
new_delay = ffi_lib.input_shaper_get_step_generation_window(is_sk)
if old_delay != new_delay:
- self.toolhead.note_step_generation_scan_time(new_delay,
- old_delay)
+ self.toolhead.note_step_generation_scan_time(new_delay, old_delay)
if failed_shapers:
error = error or self.printer.command_error
- raise error("Failed to configure shaper(s) %s with given parameters"
- % (', '.join([s.get_name() for s in failed_shapers])))
+ raise error(
+ "Failed to configure shaper(s) %s with given parameters"
+ % (", ".join([s.get_name() for s in failed_shapers]))
+ )
+
def disable_shaping(self):
for shaper in self.shapers:
shaper.disable_shaping()
self._update_input_shaping()
+
def enable_shaping(self):
for shaper in self.shapers:
shaper.enable_shaping()
self._update_input_shaping()
+
cmd_SET_INPUT_SHAPER_help = "Set cartesian parameters for input shaper"
+
def cmd_SET_INPUT_SHAPER(self, gcmd):
if gcmd.get_command_parameters():
for shaper in self.shapers:
@@ -194,5 +234,6 @@ class InputShaper:
for shaper in self.shapers:
shaper.report(gcmd)
+
def load_config(config):
return InputShaper(config)
diff --git a/klippy/extras/ldc1612.py b/klippy/extras/ldc1612.py
index dd41b43a..1bec5c35 100644
--- a/klippy/extras/ldc1612.py
+++ b/klippy/extras/ldc1612.py
@@ -10,53 +10,63 @@ MIN_MSG_TIME = 0.100
BATCH_UPDATES = 0.100
-LDC1612_ADDR = 0x2a
+LDC1612_ADDR = 0x2A
DEFAULT_LDC1612_FREQ = 12000000
SETTLETIME = 0.005
DRIVECUR = 15
-DEGLITCH = 0x05 # 10 Mhz
+DEGLITCH = 0x05 # 10 Mhz
LDC1612_MANUF_ID = 0x5449
LDC1612_DEV_ID = 0x3055
REG_RCOUNT0 = 0x08
-REG_OFFSET0 = 0x0c
+REG_OFFSET0 = 0x0C
REG_SETTLECOUNT0 = 0x10
REG_CLOCK_DIVIDERS0 = 0x14
REG_ERROR_CONFIG = 0x19
-REG_CONFIG = 0x1a
-REG_MUX_CONFIG = 0x1b
-REG_DRIVE_CURRENT0 = 0x1e
-REG_MANUFACTURER_ID = 0x7e
-REG_DEVICE_ID = 0x7f
+REG_CONFIG = 0x1A
+REG_MUX_CONFIG = 0x1B
+REG_DRIVE_CURRENT0 = 0x1E
+REG_MANUFACTURER_ID = 0x7E
+REG_DEVICE_ID = 0x7F
+
# Tool for determining appropriate DRIVE_CURRENT register
class DriveCurrentCalibrate:
def __init__(self, config, sensor):
self.printer = config.get_printer()
self.sensor = sensor
- self.drive_cur = config.getint("reg_drive_current", DRIVECUR,
- minval=0, maxval=31)
+ self.drive_cur = config.getint(
+ "reg_drive_current", DRIVECUR, minval=0, maxval=31
+ )
self.name = config.get_name()
- gcode = self.printer.lookup_object('gcode')
- gcode.register_mux_command("LDC_CALIBRATE_DRIVE_CURRENT",
- "CHIP", self.name.split()[-1],
- self.cmd_LDC_CALIBRATE,
- desc=self.cmd_LDC_CALIBRATE_help)
+ gcode = self.printer.lookup_object("gcode")
+ gcode.register_mux_command(
+ "LDC_CALIBRATE_DRIVE_CURRENT",
+ "CHIP",
+ self.name.split()[-1],
+ self.cmd_LDC_CALIBRATE,
+ desc=self.cmd_LDC_CALIBRATE_help,
+ )
+
def get_drive_current(self):
return self.drive_cur
+
cmd_LDC_CALIBRATE_help = "Calibrate LDC1612 DRIVE_CURRENT register"
+
def cmd_LDC_CALIBRATE(self, gcmd):
is_in_progress = True
+
def handle_batch(msg):
return is_in_progress
+
self.sensor.add_client(handle_batch)
toolhead = self.printer.lookup_object("toolhead")
toolhead.dwell(0.100)
toolhead.wait_moves()
old_config = self.sensor.read_reg(REG_CONFIG)
- self.sensor.set_reg(REG_CONFIG, 0x001 | (1<<9))
+ self.sensor.set_reg(REG_CONFIG, 0x001 | (1 << 9))
toolhead.wait_moves()
toolhead.dwell(0.100)
toolhead.wait_moves()
@@ -64,13 +74,15 @@ class DriveCurrentCalibrate:
self.sensor.set_reg(REG_CONFIG, old_config)
is_in_progress = False
# Report found value to user
- drive_cur = (reg_drive_current0 >> 6) & 0x1f
+ drive_cur = (reg_drive_current0 >> 6) & 0x1F
gcmd.respond_info(
"%s: reg_drive_current: %d\n"
"The SAVE_CONFIG command will update the printer config file\n"
- "with the above and restart the printer." % (self.name, drive_cur))
- configfile = self.printer.lookup_object('configfile')
- configfile.set(self.name, 'reg_drive_current', "%d" % (drive_cur,))
+ "with the above and restart the printer." % (self.name, drive_cur)
+ )
+ configfile = self.printer.lookup_object("configfile")
+ configfile.set(self.name, "reg_drive_current", "%d" % (drive_cur,))
+
# Interface class to LDC1612 mcu support
class LDC1612:
@@ -80,28 +92,32 @@ class LDC1612:
self.dccal = DriveCurrentCalibrate(config, self)
self.data_rate = 250
# Setup mcu sensor_ldc1612 bulk query code
- self.i2c = bus.MCU_I2C_from_config(config,
- default_addr=LDC1612_ADDR,
- default_speed=400000)
+ self.i2c = bus.MCU_I2C_from_config(
+ config, default_addr=LDC1612_ADDR, default_speed=400000
+ )
self.mcu = mcu = self.i2c.get_mcu()
self.oid = oid = mcu.create_oid()
self.query_ldc1612_cmd = None
self.ldc1612_setup_home_cmd = self.query_ldc1612_home_state_cmd = None
- self.frequency = config.getint("frequency", DEFAULT_LDC1612_FREQ,
- 2000000, 40000000)
- if config.get('intb_pin', None) is not None:
+ self.frequency = config.getint(
+ "frequency", DEFAULT_LDC1612_FREQ, 2000000, 40000000
+ )
+ if config.get("intb_pin", None) is not None:
ppins = config.get_printer().lookup_object("pins")
- pin_params = ppins.lookup_pin(config.get('intb_pin'))
- if pin_params['chip'] != mcu:
+ pin_params = ppins.lookup_pin(config.get("intb_pin"))
+ if pin_params["chip"] != mcu:
raise config.error("ldc1612 intb_pin must be on same mcu")
mcu.add_config_cmd(
"config_ldc1612_with_intb oid=%d i2c_oid=%d intb_pin=%s"
- % (oid, self.i2c.get_oid(), pin_params['pin']))
+ % (oid, self.i2c.get_oid(), pin_params["pin"])
+ )
else:
- mcu.add_config_cmd("config_ldc1612 oid=%d i2c_oid=%d"
- % (oid, self.i2c.get_oid()))
- mcu.add_config_cmd("query_ldc1612 oid=%d rest_ticks=0"
- % (oid,), on_restart=True)
+ mcu.add_config_cmd(
+ "config_ldc1612 oid=%d i2c_oid=%d" % (oid, self.i2c.get_oid())
+ )
+ mcu.add_config_cmd(
+ "query_ldc1612 oid=%d rest_ticks=0" % (oid,), on_restart=True
+ )
mcu.register_config_callback(self._build_config)
# Bulk sample message reading
chip_smooth = self.data_rate * BATCH_UPDATES * 2
@@ -109,60 +125,79 @@ class LDC1612:
self.last_error_count = 0
# Process messages in batches
self.batch_bulk = bulk_sensor.BatchBulkHelper(
- self.printer, self._process_batch,
- self._start_measurements, self._finish_measurements, BATCH_UPDATES)
+ self.printer,
+ self._process_batch,
+ self._start_measurements,
+ self._finish_measurements,
+ BATCH_UPDATES,
+ )
self.name = config.get_name().split()[-1]
- hdr = ('time', 'frequency', 'z')
- self.batch_bulk.add_mux_endpoint("ldc1612/dump_ldc1612", "sensor",
- self.name, {'header': hdr})
+ hdr = ("time", "frequency", "z")
+ self.batch_bulk.add_mux_endpoint(
+ "ldc1612/dump_ldc1612", "sensor", self.name, {"header": hdr}
+ )
+
def _build_config(self):
cmdqueue = self.i2c.get_command_queue()
self.query_ldc1612_cmd = self.mcu.lookup_command(
- "query_ldc1612 oid=%c rest_ticks=%u", cq=cmdqueue)
- self.ffreader.setup_query_command("query_status_ldc1612 oid=%c",
- oid=self.oid, cq=cmdqueue)
+ "query_ldc1612 oid=%c rest_ticks=%u", cq=cmdqueue
+ )
+ self.ffreader.setup_query_command(
+ "query_status_ldc1612 oid=%c", oid=self.oid, cq=cmdqueue
+ )
self.ldc1612_setup_home_cmd = self.mcu.lookup_command(
"ldc1612_setup_home oid=%c clock=%u threshold=%u"
- " trsync_oid=%c trigger_reason=%c error_reason=%c", cq=cmdqueue)
+ " trsync_oid=%c trigger_reason=%c error_reason=%c",
+ cq=cmdqueue,
+ )
self.query_ldc1612_home_state_cmd = self.mcu.lookup_query_command(
"query_ldc1612_home_state oid=%c",
"ldc1612_home_state oid=%c homing=%c trigger_clock=%u",
- oid=self.oid, cq=cmdqueue)
+ oid=self.oid,
+ cq=cmdqueue,
+ )
+
def get_mcu(self):
return self.i2c.get_mcu()
+
def read_reg(self, reg):
params = self.i2c.i2c_read([reg], 2)
- response = bytearray(params['response'])
+ response = bytearray(params["response"])
return (response[0] << 8) | response[1]
+
def set_reg(self, reg, val, minclock=0):
- self.i2c.i2c_write([reg, (val >> 8) & 0xff, val & 0xff],
- minclock=minclock)
+ self.i2c.i2c_write([reg, (val >> 8) & 0xFF, val & 0xFF], minclock=minclock)
+
def add_client(self, cb):
self.batch_bulk.add_client(cb)
+
# Homing
- def setup_home(self, print_time, trigger_freq,
- trsync_oid, hit_reason, err_reason):
+ def setup_home(self, print_time, trigger_freq, trsync_oid, hit_reason, err_reason):
clock = self.mcu.print_time_to_clock(print_time)
- tfreq = int(trigger_freq * (1<<28) / float(self.frequency) + 0.5)
+ tfreq = int(trigger_freq * (1 << 28) / float(self.frequency) + 0.5)
self.ldc1612_setup_home_cmd.send(
- [self.oid, clock, tfreq, trsync_oid, hit_reason, err_reason])
+ [self.oid, clock, tfreq, trsync_oid, hit_reason, err_reason]
+ )
+
def clear_home(self):
self.ldc1612_setup_home_cmd.send([self.oid, 0, 0, 0, 0, 0])
if self.mcu.is_fileoutput():
- return 0.
+ return 0.0
params = self.query_ldc1612_home_state_cmd.send([self.oid])
- tclock = self.mcu.clock32_to_clock64(params['trigger_clock'])
+ tclock = self.mcu.clock32_to_clock64(params["trigger_clock"])
return self.mcu.clock_to_print_time(tclock)
+
# Measurement decoding
def _convert_samples(self, samples):
- freq_conv = float(self.frequency) / (1<<28)
+ freq_conv = float(self.frequency) / (1 << 28)
count = 0
for ptime, val in samples:
- mv = val & 0x0fffffff
+ mv = val & 0x0FFFFFFF
if mv != val:
self.last_error_count += 1
samples[count] = (round(ptime, 6), round(freq_conv * mv, 3), 999.9)
count += 1
+
# Start, stop, and process message batches
def _start_measurements(self):
# In case of miswiring, testing LDC1612 device ID prevents treating
@@ -174,16 +209,17 @@ class LDC1612:
"Invalid ldc1612 id (got %x,%x vs %x,%x).\n"
"This is generally indicative of connection problems\n"
"(e.g. faulty wiring) or a faulty ldc1612 chip."
- % (manuf_id, dev_id, LDC1612_MANUF_ID, LDC1612_DEV_ID))
+ % (manuf_id, dev_id, LDC1612_MANUF_ID, LDC1612_DEV_ID)
+ )
# Setup chip in requested query rate
- rcount0 = self.frequency / (16. * (self.data_rate - 4))
+ rcount0 = self.frequency / (16.0 * (self.data_rate - 4))
self.set_reg(REG_RCOUNT0, int(rcount0 + 0.5))
self.set_reg(REG_OFFSET0, 0)
- self.set_reg(REG_SETTLECOUNT0, int(SETTLETIME*self.frequency/16. + .5))
+ self.set_reg(REG_SETTLECOUNT0, int(SETTLETIME * self.frequency / 16.0 + 0.5))
self.set_reg(REG_CLOCK_DIVIDERS0, (1 << 12) | 1)
- self.set_reg(REG_ERROR_CONFIG, (0x1f << 11) | 1)
+ self.set_reg(REG_ERROR_CONFIG, (0x1F << 11) | 1)
self.set_reg(REG_MUX_CONFIG, 0x0208 | DEGLITCH)
- self.set_reg(REG_CONFIG, 0x001 | (1<<12) | (1<<10) | (1<<9))
+ self.set_reg(REG_CONFIG, 0x001 | (1 << 12) | (1 << 10) | (1 << 9))
self.set_reg(REG_DRIVE_CURRENT0, self.dccal.get_drive_current() << 11)
# Start bulk reading
rest_ticks = self.mcu.seconds_to_clock(0.5 / self.data_rate)
@@ -192,11 +228,13 @@ class LDC1612:
# Initialize clock tracking
self.ffreader.note_start()
self.last_error_count = 0
+
def _finish_measurements(self):
# Halt bulk reading
self.query_ldc1612_cmd.send_wait_ack([self.oid, 0])
self.ffreader.note_end()
logging.info("LDC1612 finished '%s' measurements", self.name)
+
def _process_batch(self, eventtime):
samples = self.ffreader.pull_samples()
self._convert_samples(samples)
@@ -204,5 +242,8 @@ class LDC1612:
return {}
if self.calibration is not None:
self.calibration.apply_calibration(samples)
- return {'data': samples, 'errors': self.last_error_count,
- 'overflows': self.ffreader.get_last_overflows()}
+ return {
+ "data": samples,
+ "errors": self.last_error_count,
+ "overflows": self.ffreader.get_last_overflows(),
+ }
diff --git a/klippy/extras/led.py b/klippy/extras/led.py
index 37b58de3..b22ddf72 100644
--- a/klippy/extras/led.py
+++ b/klippy/extras/led.py
@@ -6,6 +6,7 @@
import logging
from . import output_pin
+
# Helper code for common LED initialization and control
class LEDHelper:
def __init__(self, config, update_func, led_count=1):
@@ -14,26 +15,34 @@ class LEDHelper:
self.led_count = led_count
self.need_transmit = False
# Initial color
- red = config.getfloat('initial_RED', 0., minval=0., maxval=1.)
- green = config.getfloat('initial_GREEN', 0., minval=0., maxval=1.)
- blue = config.getfloat('initial_BLUE', 0., minval=0., maxval=1.)
- white = config.getfloat('initial_WHITE', 0., minval=0., maxval=1.)
+ red = config.getfloat("initial_RED", 0.0, minval=0.0, maxval=1.0)
+ green = config.getfloat("initial_GREEN", 0.0, minval=0.0, maxval=1.0)
+ blue = config.getfloat("initial_BLUE", 0.0, minval=0.0, maxval=1.0)
+ white = config.getfloat("initial_WHITE", 0.0, minval=0.0, maxval=1.0)
self.led_state = [(red, green, blue, white)] * led_count
# Support setting an led template
self.template_eval = output_pin.lookup_template_eval(config)
- self.tcallbacks = [(lambda text, s=self, index=i+1:
- s._template_update(index, text))
- for i in range(led_count)]
+ self.tcallbacks = [
+ (lambda text, s=self, index=i + 1: s._template_update(index, text))
+ for i in range(led_count)
+ ]
# Register commands
name = config.get_name().split()[-1]
- gcode = self.printer.lookup_object('gcode')
- gcode.register_mux_command("SET_LED", "LED", name, self.cmd_SET_LED,
- desc=self.cmd_SET_LED_help)
- gcode.register_mux_command("SET_LED_TEMPLATE", "LED", name,
- self.cmd_SET_LED_TEMPLATE,
- desc=self.cmd_SET_LED_TEMPLATE_help)
+ gcode = self.printer.lookup_object("gcode")
+ gcode.register_mux_command(
+ "SET_LED", "LED", name, self.cmd_SET_LED, desc=self.cmd_SET_LED_help
+ )
+ gcode.register_mux_command(
+ "SET_LED_TEMPLATE",
+ "LED",
+ name,
+ self.cmd_SET_LED_TEMPLATE,
+ desc=self.cmd_SET_LED_TEMPLATE_help,
+ )
+
def get_status(self, eventtime=None):
- return {'color_data': self.led_state}
+ return {"color_data": self.led_state}
+
def _set_color(self, index, color):
if index is None:
new_led_state = [color] * self.led_count
@@ -46,16 +55,17 @@ class LEDHelper:
new_led_state[index - 1] = color
self.led_state = new_led_state
self.need_transmit = True
+
def _template_update(self, index, text):
try:
- parts = [max(0., min(1., float(f)))
- for f in text.split(',', 4)]
+ parts = [max(0.0, min(1.0, float(f))) for f in text.split(",", 4)]
except ValueError as e:
logging.exception("led template render error")
parts = []
if len(parts) < 4:
- parts += [0.] * (4 - len(parts))
+ parts += [0.0] * (4 - len(parts))
self._set_color(index, tuple(parts))
+
def _check_transmit(self, print_time=None):
if not self.need_transmit:
return
@@ -64,67 +74,77 @@ class LEDHelper:
self.update_func(self.led_state, print_time)
except self.printer.command_error as e:
logging.exception("led update transmit error")
+
cmd_SET_LED_help = "Set the color of an LED"
+
def cmd_SET_LED(self, gcmd):
# Parse parameters
- red = gcmd.get_float('RED', 0., minval=0., maxval=1.)
- green = gcmd.get_float('GREEN', 0., minval=0., maxval=1.)
- blue = gcmd.get_float('BLUE', 0., minval=0., maxval=1.)
- white = gcmd.get_float('WHITE', 0., minval=0., maxval=1.)
- index = gcmd.get_int('INDEX', None, minval=1, maxval=self.led_count)
- transmit = gcmd.get_int('TRANSMIT', 1)
- sync = gcmd.get_int('SYNC', 1)
+ red = gcmd.get_float("RED", 0.0, minval=0.0, maxval=1.0)
+ green = gcmd.get_float("GREEN", 0.0, minval=0.0, maxval=1.0)
+ blue = gcmd.get_float("BLUE", 0.0, minval=0.0, maxval=1.0)
+ white = gcmd.get_float("WHITE", 0.0, minval=0.0, maxval=1.0)
+ index = gcmd.get_int("INDEX", None, minval=1, maxval=self.led_count)
+ transmit = gcmd.get_int("TRANSMIT", 1)
+ sync = gcmd.get_int("SYNC", 1)
color = (red, green, blue, white)
+
# Update and transmit data
def lookahead_bgfunc(print_time):
self._set_color(index, color)
if transmit:
self._check_transmit(print_time)
+
if sync:
- #Sync LED Update with print time and send
- toolhead = self.printer.lookup_object('toolhead')
+ # Sync LED Update with print time and send
+ toolhead = self.printer.lookup_object("toolhead")
toolhead.register_lookahead_callback(lookahead_bgfunc)
else:
- #Send update now (so as not to wake toolhead and reset idle_timeout)
+ # Send update now (so as not to wake toolhead and reset idle_timeout)
lookahead_bgfunc(None)
+
cmd_SET_LED_TEMPLATE_help = "Assign a display_template to an LED"
+
def cmd_SET_LED_TEMPLATE(self, gcmd):
index = gcmd.get_int("INDEX", None, minval=1, maxval=self.led_count)
set_template = self.template_eval.set_template
if index is not None:
- set_template(gcmd, self.tcallbacks[index-1], self._check_transmit)
+ set_template(gcmd, self.tcallbacks[index - 1], self._check_transmit)
else:
for i in range(self.led_count):
set_template(gcmd, self.tcallbacks[i], self._check_transmit)
+
# Handler for PWM controlled LEDs
class PrinterPWMLED:
def __init__(self, config):
self.printer = printer = config.get_printer()
# Configure pwm pins
- ppins = printer.lookup_object('pins')
- max_duration = printer.lookup_object('mcu').max_nominal_duration()
- cycle_time = config.getfloat('cycle_time', 0.010, above=0.,
- maxval=max_duration)
- hardware_pwm = config.getboolean('hardware_pwm', False)
+ ppins = printer.lookup_object("pins")
+ max_duration = printer.lookup_object("mcu").max_nominal_duration()
+ cycle_time = config.getfloat(
+ "cycle_time", 0.010, above=0.0, maxval=max_duration
+ )
+ hardware_pwm = config.getboolean("hardware_pwm", False)
self.pins = []
for i, name in enumerate(("red", "green", "blue", "white")):
- pin_name = config.get(name + '_pin', None)
+ pin_name = config.get(name + "_pin", None)
if pin_name is None:
continue
- mcu_pin = ppins.setup_pin('pwm', pin_name)
- mcu_pin.setup_max_duration(0.)
+ mcu_pin = ppins.setup_pin("pwm", pin_name)
+ mcu_pin.setup_max_duration(0.0)
mcu_pin.setup_cycle_time(cycle_time, hardware_pwm)
self.pins.append((i, mcu_pin))
if not self.pins:
- raise config.error("No LED pin definitions found in '%s'"
- % (config.get_name(),))
- self.last_print_time = 0.
+ raise config.error(
+ "No LED pin definitions found in '%s'" % (config.get_name(),)
+ )
+ self.last_print_time = 0.0
# Initialize color data
self.led_helper = LEDHelper(config, self.update_leds, 1)
- self.prev_color = color = self.led_helper.get_status()['color_data'][0]
+ self.prev_color = color = self.led_helper.get_status()["color_data"][0]
for idx, mcu_pin in self.pins:
- mcu_pin.setup_start_value(color[idx], 0.)
+ mcu_pin.setup_start_value(color[idx], 0.0)
+
def update_leds(self, led_state, print_time):
mcu = self.pins[0][1].get_mcu()
min_sched_time = mcu.min_schedule_time()
@@ -138,8 +158,10 @@ class PrinterPWMLED:
mcu_pin.set_pwm(print_time, color[idx])
self.last_print_time = print_time
self.prev_color = color
+
def get_status(self, eventtime=None):
return self.led_helper.get_status(eventtime)
+
def load_config_prefix(config):
return PrinterPWMLED(config)
diff --git a/klippy/extras/lis2dw.py b/klippy/extras/lis2dw.py
index dba83d08..a0d582bb 100644
--- a/klippy/extras/lis2dw.py
+++ b/klippy/extras/lis2dw.py
@@ -22,7 +22,7 @@ REG_LIS2DW_OUT_YL_ADDR = 0x2A
REG_LIS2DW_OUT_YH_ADDR = 0x2B
REG_LIS2DW_OUT_ZL_ADDR = 0x2C
REG_LIS2DW_OUT_ZH_ADDR = 0x2D
-REG_LIS2DW_FIFO_CTRL = 0x2E
+REG_LIS2DW_FIFO_CTRL = 0x2E
REG_LIS2DW_FIFO_SAMPLES = 0x2F
REG_MOD_READ = 0x80
@@ -39,11 +39,12 @@ LIS3DH_SCALE = FREEFALL_ACCEL * 11.718 / 16
BATCH_UPDATES = 0.100
# "Enums" that should be compatible with all python versions
-LIS2DW_TYPE = 'LIS2DW'
-LIS3DH_TYPE = 'LIS3DH'
+LIS2DW_TYPE = "LIS2DW"
+LIS3DH_TYPE = "LIS3DH"
+
+SPI_SERIAL_TYPE = "spi"
+I2C_SERIAL_TYPE = "i2c"
-SPI_SERIAL_TYPE = 'spi'
-I2C_SERIAL_TYPE = 'i2c'
# Printer class that controls LIS2DW chip
class LIS2DW:
@@ -52,33 +53,35 @@ class LIS2DW:
adxl345.AccelCommandHelper(config, self)
self.lis_type = lis_type
if self.lis_type == LIS2DW_TYPE:
- self.axes_map = adxl345.read_axes_map(config, LIS2DW_SCALE,
- LIS2DW_SCALE, LIS2DW_SCALE)
+ self.axes_map = adxl345.read_axes_map(
+ config, LIS2DW_SCALE, LIS2DW_SCALE, LIS2DW_SCALE
+ )
self.data_rate = 1600
else:
- self.axes_map = adxl345.read_axes_map(config, LIS3DH_SCALE,
- LIS3DH_SCALE, LIS3DH_SCALE)
+ self.axes_map = adxl345.read_axes_map(
+ config, LIS3DH_SCALE, LIS3DH_SCALE, LIS3DH_SCALE
+ )
self.data_rate = 1344
# Check for spi or i2c
- if config.get('cs_pin', None) is not None:
+ if config.get("cs_pin", None) is not None:
self.bus_type = SPI_SERIAL_TYPE
else:
self.bus_type = I2C_SERIAL_TYPE
# Setup mcu sensor_lis2dw bulk query code
if self.bus_type == SPI_SERIAL_TYPE:
- self.bus = bus.MCU_SPI_from_config(config,
- 3, default_speed=5000000)
+ self.bus = bus.MCU_SPI_from_config(config, 3, default_speed=5000000)
else:
- self.bus = bus.MCU_I2C_from_config(config,
- default_addr=LIS_I2C_ADDR, default_speed=400000)
+ self.bus = bus.MCU_I2C_from_config(
+ config, default_addr=LIS_I2C_ADDR, default_speed=400000
+ )
self.mcu = mcu = self.bus.get_mcu()
self.oid = oid = mcu.create_oid()
self.query_lis2dw_cmd = None
- mcu.add_config_cmd("config_lis2dw oid=%d bus_oid=%d bus_oid_type=%s "
- "lis_chip_type=%s" % (oid, self.bus.get_oid(),
- self.bus_type, self.lis_type))
- mcu.add_config_cmd("query_lis2dw oid=%d rest_ticks=0"
- % (oid,), on_restart=True)
+ mcu.add_config_cmd(
+ "config_lis2dw oid=%d bus_oid=%d bus_oid_type=%s "
+ "lis_chip_type=%s" % (oid, self.bus.get_oid(), self.bus_type, self.lis_type)
+ )
+ mcu.add_config_cmd("query_lis2dw oid=%d rest_ticks=0" % (oid,), on_restart=True)
mcu.register_config_callback(self._build_config)
# Bulk sample message reading
chip_smooth = self.data_rate * BATCH_UPDATES * 2
@@ -86,25 +89,35 @@ class LIS2DW:
self.last_error_count = 0
# Process messages in batches
self.batch_bulk = bulk_sensor.BatchBulkHelper(
- self.printer, self._process_batch,
- self._start_measurements, self._finish_measurements, BATCH_UPDATES)
+ self.printer,
+ self._process_batch,
+ self._start_measurements,
+ self._finish_measurements,
+ BATCH_UPDATES,
+ )
self.name = config.get_name().split()[-1]
- hdr = ('time', 'x_acceleration', 'y_acceleration', 'z_acceleration')
- self.batch_bulk.add_mux_endpoint("lis2dw/dump_lis2dw", "sensor",
- self.name, {'header': hdr})
+ hdr = ("time", "x_acceleration", "y_acceleration", "z_acceleration")
+ self.batch_bulk.add_mux_endpoint(
+ "lis2dw/dump_lis2dw", "sensor", self.name, {"header": hdr}
+ )
+
def _build_config(self):
cmdqueue = self.bus.get_command_queue()
self.query_lis2dw_cmd = self.mcu.lookup_command(
- "query_lis2dw oid=%c rest_ticks=%u", cq=cmdqueue)
- self.ffreader.setup_query_command("query_lis2dw_status oid=%c",
- oid=self.oid, cq=cmdqueue)
+ "query_lis2dw oid=%c rest_ticks=%u", cq=cmdqueue
+ )
+ self.ffreader.setup_query_command(
+ "query_lis2dw_status oid=%c", oid=self.oid, cq=cmdqueue
+ )
+
def read_reg(self, reg):
if self.bus_type == SPI_SERIAL_TYPE:
params = self.bus.spi_transfer([reg | REG_MOD_READ, 0x00])
- response = bytearray(params['response'])
+ response = bytearray(params["response"])
return response[1]
params = self.bus.i2c_read([reg], 1)
- return bytearray(params['response'])[0]
+ return bytearray(params["response"])[0]
+
def set_reg(self, reg, val, minclock=0):
if self.bus_type == SPI_SERIAL_TYPE:
self.bus.spi_send([reg, val & 0xFF], minclock=minclock)
@@ -113,14 +126,16 @@ class LIS2DW:
stored_val = self.read_reg(reg)
if stored_val != val:
raise self.printer.command_error(
- "Failed to set LIS2DW register [0x%x] to 0x%x: got 0x%x. "
- "This is generally indicative of connection problems "
- "(e.g. faulty wiring) or a faulty lis2dw chip." % (
- reg, val, stored_val))
+ "Failed to set LIS2DW register [0x%x] to 0x%x: got 0x%x. "
+ "This is generally indicative of connection problems "
+ "(e.g. faulty wiring) or a faulty lis2dw chip." % (reg, val, stored_val)
+ )
+
def start_internal_client(self):
aqh = adxl345.AccelQueryHelper(self.printer)
self.batch_bulk.add_client(aqh.handle_batch)
return aqh
+
# Measurement decoding
def _convert_samples(self, samples):
(x_pos, x_scale), (y_pos, y_scale), (z_pos, z_scale) = self.axes_map
@@ -132,6 +147,7 @@ class LIS2DW:
z = round(raw_xyz[z_pos] * z_scale, 6)
samples[count] = (round(ptime, 6), x, y, z)
count += 1
+
# Start, stop, and process message batches
def _start_measurements(self):
# In case of miswiring, testing LIS2DW device ID prevents treating
@@ -144,7 +160,8 @@ class LIS2DW:
"Invalid lis2dw id (got %x vs %x).\n"
"This is generally indicative of connection problems\n"
"(e.g. faulty wiring) or a faulty lis2dw chip."
- % (dev_id, LIS2DW_DEV_ID))
+ % (dev_id, LIS2DW_DEV_ID)
+ )
if self.bus_type == SPI_SERIAL_TYPE:
# Disable I2C
self.set_reg(REG_LIS2DW_CTRL_REG2_ADDR, 0x06)
@@ -163,7 +180,8 @@ class LIS2DW:
"Invalid lis3dh id (got %x vs %x).\n"
"This is generally indicative of connection problems\n"
"(e.g. faulty wiring) or a faulty lis3dh chip."
- % (dev_id, LIS3DH_DEV_ID))
+ % (dev_id, LIS3DH_DEV_ID)
+ )
# High Resolution / Low Power mode 1344/5376 Hz
# High Resolution mode (12-bit resolution)
# Enable X Y Z axes
@@ -177,7 +195,7 @@ class LIS2DW:
# Stream mode
self.set_reg(REG_LIS2DW_FIFO_CTRL, 0x80)
# Start bulk reading
- rest_ticks = self.mcu.seconds_to_clock(4. / self.data_rate)
+ rest_ticks = self.mcu.seconds_to_clock(4.0 / self.data_rate)
self.query_lis2dw_cmd.send([self.oid, rest_ticks])
if self.lis_type == LIS2DW_TYPE:
self.set_reg(REG_LIS2DW_FIFO_CTRL, 0xC0)
@@ -187,6 +205,7 @@ class LIS2DW:
# Initialize clock tracking
self.ffreader.note_start()
self.last_error_count = 0
+
def _finish_measurements(self):
# Halt bulk reading
self.set_reg(REG_LIS2DW_FIFO_CTRL, 0x00)
@@ -194,16 +213,22 @@ class LIS2DW:
self.ffreader.note_end()
logging.info("LIS2DW finished '%s' measurements", self.name)
self.set_reg(REG_LIS2DW_FIFO_CTRL, 0x00)
+
def _process_batch(self, eventtime):
samples = self.ffreader.pull_samples()
self._convert_samples(samples)
if not samples:
return {}
- return {'data': samples, 'errors': self.last_error_count,
- 'overflows': self.ffreader.get_last_overflows()}
+ return {
+ "data": samples,
+ "errors": self.last_error_count,
+ "overflows": self.ffreader.get_last_overflows(),
+ }
+
def load_config(config):
return LIS2DW(config, LIS2DW_TYPE)
+
def load_config_prefix(config):
return LIS2DW(config, LIS2DW_TYPE)
diff --git a/klippy/extras/lis3dh.py b/klippy/extras/lis3dh.py
index b7e35186..bb5f0558 100644
--- a/klippy/extras/lis3dh.py
+++ b/klippy/extras/lis3dh.py
@@ -5,8 +5,10 @@
# This file may be distributed under the terms of the GNU GPLv3 license.
from . import lis2dw
+
def load_config(config):
return lis2dw.LIS2DW(config, lis2dw.LIS3DH_TYPE)
+
def load_config_prefix(config):
return lis2dw.LIS2DW(config, lis2dw.LIS3DH_TYPE)
diff --git a/klippy/extras/lm75.py b/klippy/extras/lm75.py
index 7571f270..10fbcb9f 100644
--- a/klippy/extras/lm75.py
+++ b/klippy/extras/lm75.py
@@ -9,33 +9,33 @@ from . import bus
LM75_CHIP_ADDR = 0x48
LM75_I2C_SPEED = 100000
LM75_REGS = {
- 'TEMP' : 0x00,
- 'CONF' : 0x01,
- 'THYST' : 0x02,
- 'TOS' : 0x03,
- 'PRODID' : 0x07 # TI LM75A chips only?
+ "TEMP": 0x00,
+ "CONF": 0x01,
+ "THYST": 0x02,
+ "TOS": 0x03,
+ "PRODID": 0x07, # TI LM75A chips only?
}
-LM75_REPORT_TIME = .8
+LM75_REPORT_TIME = 0.8
# Temperature can be sampled at any time but the read aborts
# the current conversion. Conversion time is 300ms so make
# sure not to read too often.
-LM75_MIN_REPORT_TIME = .5
+LM75_MIN_REPORT_TIME = 0.5
+
class LM75:
def __init__(self, config):
self.printer = config.get_printer()
self.name = config.get_name().split()[-1]
self.reactor = self.printer.get_reactor()
- self.i2c = bus.MCU_I2C_from_config(config, LM75_CHIP_ADDR,
- LM75_I2C_SPEED)
+ self.i2c = bus.MCU_I2C_from_config(config, LM75_CHIP_ADDR, LM75_I2C_SPEED)
self.mcu = self.i2c.get_mcu()
- self.report_time = config.getfloat('lm75_report_time', LM75_REPORT_TIME,
- minval=LM75_MIN_REPORT_TIME)
+ self.report_time = config.getfloat(
+ "lm75_report_time", LM75_REPORT_TIME, minval=LM75_MIN_REPORT_TIME
+ )
self.temp = self.min_temp = self.max_temp = 0.0
self.sample_timer = self.reactor.register_timer(self._sample_lm75)
self.printer.add_object("lm75 " + self.name, self)
- self.printer.register_event_handler("klippy:connect",
- self.handle_connect)
+ self.printer.register_event_handler("klippy:connect", self.handle_connect)
def handle_connect(self):
self._init_lm75()
@@ -60,14 +60,14 @@ class LM75:
# Check and report the chip ID but ignore errors since many
# chips don't have it
try:
- prodid = self.read_register('PRODID', 1)[0]
+ prodid = self.read_register("PRODID", 1)[0]
logging.info("lm75: Chip ID %#x" % prodid)
except:
pass
def _sample_lm75(self, eventtime):
try:
- sample = self.read_register('TEMP', 2)
+ sample = self.read_register("TEMP", 2)
self.temp = self.degrees_from_sample(sample)
except Exception:
logging.exception("lm75: Error reading data")
@@ -77,7 +77,8 @@ class LM75:
if self.temp < self.min_temp or self.temp > self.max_temp:
self.printer.invoke_shutdown(
"LM75 temperature %0.1f outside range of %0.1f:%.01f"
- % (self.temp, self.min_temp, self.max_temp))
+ % (self.temp, self.min_temp, self.max_temp)
+ )
measured_time = self.reactor.monotonic()
self._callback(self.mcu.estimated_print_time(measured_time), self.temp)
@@ -87,7 +88,7 @@ class LM75:
# read a single register
regs = [LM75_REGS[reg_name]]
params = self.i2c.i2c_read(regs, read_len)
- return bytearray(params['response'])
+ return bytearray(params["response"])
def write_register(self, reg_name, data):
if type(data) is not list:
@@ -98,7 +99,7 @@ class LM75:
def get_status(self, eventtime):
return {
- 'temperature': round(self.temp, 2),
+ "temperature": round(self.temp, 2),
}
diff --git a/klippy/extras/load_cell.py b/klippy/extras/load_cell.py
index 5ef2c5b7..3481c06e 100644
--- a/klippy/extras/load_cell.py
+++ b/klippy/extras/load_cell.py
@@ -8,20 +8,24 @@ from . import hx71x
from . import ads1220
from .bulk_sensor import BatchWebhooksClient
import collections, itertools
+
# We want either Python 3's zip() or Python 2's izip() but NOT 2's zip():
zip_impl = zip
try:
- from itertools import izip as zip_impl # python 2.x izip
-except ImportError: # will be Python 3.x
+ from itertools import izip as zip_impl # python 2.x izip
+except ImportError: # will be Python 3.x
pass
+
# alternative to numpy's column selection:
def select_column(data, column_idx):
return list(zip_impl(*data))[column_idx]
+
def avg(data):
return sum(data) / len(data)
+
# Helper for event driven webhooks and subscription based API clients
class ApiClientHelper(object):
def __init__(self, printer):
@@ -50,9 +54,10 @@ class ApiClientHelper(object):
# Set up a webhooks endpoint with a static header
def add_mux_endpoint(self, path, key, value, webhooks_start_resp):
self.webhooks_start_resp = webhooks_start_resp
- wh = self.printer.lookup_object('webhooks')
+ wh = self.printer.lookup_object("webhooks")
wh.register_mux_endpoint(path, key, value, self._add_webhooks_client)
+
# Class for handling commands related to load cells
class LoadCellCommandHelper:
def __init__(self, config, load_cell):
@@ -66,33 +71,53 @@ class LoadCellCommandHelper:
def register_commands(self, name):
# Register commands
- gcode = self.printer.lookup_object('gcode')
- gcode.register_mux_command("LOAD_CELL_TARE", "LOAD_CELL", name,
- self.cmd_LOAD_CELL_TARE,
- desc=self.cmd_LOAD_CELL_TARE_help)
- gcode.register_mux_command("LOAD_CELL_CALIBRATE", "LOAD_CELL", name,
- self.cmd_LOAD_CELL_CALIBRATE,
- desc=self.cmd_CALIBRATE_LOAD_CELL_help)
- gcode.register_mux_command("LOAD_CELL_READ", "LOAD_CELL", name,
- self.cmd_LOAD_CELL_READ,
- desc=self.cmd_LOAD_CELL_READ_help)
- gcode.register_mux_command("LOAD_CELL_DIAGNOSTIC", "LOAD_CELL", name,
- self.cmd_LOAD_CELL_DIAGNOSTIC,
- desc=self.cmd_LOAD_CELL_DIAGNOSTIC_help)
+ gcode = self.printer.lookup_object("gcode")
+ gcode.register_mux_command(
+ "LOAD_CELL_TARE",
+ "LOAD_CELL",
+ name,
+ self.cmd_LOAD_CELL_TARE,
+ desc=self.cmd_LOAD_CELL_TARE_help,
+ )
+ gcode.register_mux_command(
+ "LOAD_CELL_CALIBRATE",
+ "LOAD_CELL",
+ name,
+ self.cmd_LOAD_CELL_CALIBRATE,
+ desc=self.cmd_CALIBRATE_LOAD_CELL_help,
+ )
+ gcode.register_mux_command(
+ "LOAD_CELL_READ",
+ "LOAD_CELL",
+ name,
+ self.cmd_LOAD_CELL_READ,
+ desc=self.cmd_LOAD_CELL_READ_help,
+ )
+ gcode.register_mux_command(
+ "LOAD_CELL_DIAGNOSTIC",
+ "LOAD_CELL",
+ name,
+ self.cmd_LOAD_CELL_DIAGNOSTIC,
+ desc=self.cmd_LOAD_CELL_DIAGNOSTIC_help,
+ )
cmd_LOAD_CELL_TARE_help = "Set the Zero point of the load cell"
+
def cmd_LOAD_CELL_TARE(self, gcmd):
tare_counts = self.load_cell.avg_counts()
self.load_cell.tare(tare_counts)
tare_percent = self.load_cell.counts_to_percent(tare_counts)
- gcmd.respond_info("Load cell tare value: %.2f%% (%i)"
- % (tare_percent, tare_counts))
+ gcmd.respond_info(
+ "Load cell tare value: %.2f%% (%i)" % (tare_percent, tare_counts)
+ )
cmd_CALIBRATE_LOAD_CELL_help = "Start interactive calibration tool"
+
def cmd_LOAD_CELL_CALIBRATE(self, gcmd):
LoadCellGuidedCalibrationHelper(self.printer, self.load_cell)
cmd_LOAD_CELL_READ_help = "Take a reading from the load cell"
+
def cmd_LOAD_CELL_READ(self, gcmd):
counts = self.load_cell.avg_counts()
percent = self.load_cell.counts_to_percent(counts)
@@ -105,16 +130,19 @@ class LoadCellCommandHelper:
gcmd.respond_info("%.1fg (%.2f%%)" % (force, percent))
cmd_LOAD_CELL_DIAGNOSTIC_help = "Check the health of the load cell"
+
def cmd_LOAD_CELL_DIAGNOSTIC(self, gcmd):
gcmd.respond_info("Collecting load cell data for 10 seconds...")
collector = self.load_cell.get_collector()
reactor = self.printer.get_reactor()
collector.start_collecting()
- reactor.pause(reactor.monotonic() + 10.)
+ reactor.pause(reactor.monotonic() + 10.0)
samples, errors = collector.stop_collecting()
if errors:
- gcmd.respond_info("Sensor reported errors: %i errors,"
- " %i overflows" % (errors[0], errors[1]))
+ gcmd.respond_info(
+ "Sensor reported errors: %i errors,"
+ " %i overflows" % (errors[0], errors[1])
+ )
else:
gcmd.respond_info("Sensor reported no errors")
if not samples:
@@ -132,41 +160,49 @@ class LoadCellCommandHelper:
if len(samples) > 2:
sensor_sps = self.load_cell.sensor.get_samples_per_second()
sps = float(len(samples)) / (samples[-1][0] - samples[0][0])
- gcmd.respond_info("Measured samples per second: %.1f, "
- "configured: %.1f" % (sps, sensor_sps))
- gcmd.respond_info("Good samples: %i, Saturated samples: %i, Unique"
- " values: %i" % (good_count, saturation_count,
- len(set(counts))))
+ gcmd.respond_info(
+ "Measured samples per second: %.1f, "
+ "configured: %.1f" % (sps, sensor_sps)
+ )
+ gcmd.respond_info(
+ "Good samples: %i, Saturated samples: %i, Unique"
+ " values: %i" % (good_count, saturation_count, len(set(counts)))
+ )
max_pct = self.load_cell.counts_to_percent(max(counts))
min_pct = self.load_cell.counts_to_percent(min(counts))
- gcmd.respond_info("Sample range: [%.2f%% to %.2f%%]"
- % (min_pct, max_pct))
- gcmd.respond_info("Sample range / sensor capacity: %.5f%%"
- % ((max_pct - min_pct) / 2.))
+ gcmd.respond_info("Sample range: [%.2f%% to %.2f%%]" % (min_pct, max_pct))
+ gcmd.respond_info(
+ "Sample range / sensor capacity: %.5f%%" % ((max_pct - min_pct) / 2.0)
+ )
+
# Class to guide the user through calibrating a load cell
class LoadCellGuidedCalibrationHelper:
def __init__(self, printer, load_cell):
self.printer = printer
- self.gcode = printer.lookup_object('gcode')
+ self.gcode = printer.lookup_object("gcode")
self.load_cell = load_cell
self._tare_counts = self._counts_per_gram = None
- self.tare_percent = 0.
+ self.tare_percent = 0.0
self.register_commands()
self.gcode.respond_info(
"Starting load cell calibration. \n"
"1.) Remove all load and run TARE. \n"
"2.) Apply a known load, run CALIBRATE GRAMS=nnn. \n"
"Complete calibration with the ACCEPT command.\n"
- "Use the ABORT command to quit.")
+ "Use the ABORT command to quit."
+ )
- def verify_no_active_calibration(self,):
+ def verify_no_active_calibration(
+ self,
+ ):
try:
- self.gcode.register_command('TARE', 'dummy')
+ self.gcode.register_command("TARE", "dummy")
except self.printer.config_error as e:
raise self.gcode.error(
- "Already Calibrating a Load Cell. Use ABORT to quit.")
- self.gcode.register_command('TARE', None)
+ "Already Calibrating a Load Cell. Use ABORT to quit."
+ )
+ self.gcode.register_command("TARE", None)
def register_commands(self):
self.verify_no_active_calibration()
@@ -174,8 +210,7 @@ class LoadCellGuidedCalibrationHelper:
register_command("ABORT", self.cmd_ABORT, desc=self.cmd_ABORT_help)
register_command("ACCEPT", self.cmd_ACCEPT, desc=self.cmd_ACCEPT_help)
register_command("TARE", self.cmd_TARE, desc=self.cmd_TARE_help)
- register_command("CALIBRATE", self.cmd_CALIBRATE,
- desc=self.cmd_CALIBRATE_help)
+ register_command("CALIBRATE", self.cmd_CALIBRATE, desc=self.cmd_CALIBRATE_help)
# convert the delta of counts to a counts/gram metric
def counts_per_gram(self, grams, cal_counts):
@@ -185,85 +220,102 @@ class LoadCellGuidedCalibrationHelper:
# given tare bias, at saturation in kilograms
def capacity_kg(self, counts_per_gram):
range_min, range_max = self.load_cell.saturation_range()
- return (int((range_max - abs(self._tare_counts)) / counts_per_gram)
- / 1000.)
+ return int((range_max - abs(self._tare_counts)) / counts_per_gram) / 1000.0
def finalize(self, save_results=False):
- for name in ['ABORT', 'ACCEPT', 'TARE', 'CALIBRATE']:
+ for name in ["ABORT", "ACCEPT", "TARE", "CALIBRATE"]:
self.gcode.register_command(name, None)
if not save_results:
self.gcode.respond_info("Load cell calibration aborted")
return
if self._counts_per_gram is None or self._tare_counts is None:
- self.gcode.respond_info("Calibration process is incomplete, "
- "aborting")
+ self.gcode.respond_info("Calibration process is incomplete, " "aborting")
self.load_cell.set_calibration(self._counts_per_gram, self._tare_counts)
- self.gcode.respond_info("Load cell calibration settings:\n\n"
+ self.gcode.respond_info(
+ "Load cell calibration settings:\n\n"
"counts_per_gram: %.6f\n"
"reference_tare_counts: %i\n\n"
"The SAVE_CONFIG command will update the printer config file"
" with the above and restart the printer."
- % (self._counts_per_gram, self._tare_counts))
+ % (self._counts_per_gram, self._tare_counts)
+ )
self.load_cell.tare(self._tare_counts)
cmd_ABORT_help = "Abort load cell calibration tool"
+
def cmd_ABORT(self, gcmd):
self.finalize(False)
cmd_ACCEPT_help = "Accept calibration results and apply to load cell"
+
def cmd_ACCEPT(self, gcmd):
self.finalize(True)
cmd_TARE_help = "Tare the load cell"
+
def cmd_TARE(self, gcmd):
self._tare_counts = self.load_cell.avg_counts()
self._counts_per_gram = None # require re-calibration on tare
self.tare_percent = self.load_cell.counts_to_percent(self._tare_counts)
- gcmd.respond_info("Load cell tare value: %.2f%% (%i)"
- % (self.tare_percent, self._tare_counts))
- if self.tare_percent > 2.:
+ gcmd.respond_info(
+ "Load cell tare value: %.2f%% (%i)" % (self.tare_percent, self._tare_counts)
+ )
+ if self.tare_percent > 2.0:
gcmd.respond_info(
"WARNING: tare value is more than 2% away from 0!\n"
"The load cell's range will be impacted.\n"
- "Check for external force on the load cell.")
- gcmd.respond_info("Now apply a known force to the load cell and enter \
- the force value with:\n CALIBRATE GRAMS=nnn")
+ "Check for external force on the load cell."
+ )
+ gcmd.respond_info(
+ "Now apply a known force to the load cell and enter \
+ the force value with:\n CALIBRATE GRAMS=nnn"
+ )
cmd_CALIBRATE_help = "Enter the load cell value in grams"
+
def cmd_CALIBRATE(self, gcmd):
if self._tare_counts is None:
gcmd.respond_info("You must use TARE first.")
return
- grams = gcmd.get_float("GRAMS", minval=50., maxval=25000.)
+ grams = gcmd.get_float("GRAMS", minval=50.0, maxval=25000.0)
cal_counts = self.load_cell.avg_counts()
cal_percent = self.load_cell.counts_to_percent(cal_counts)
c_per_g = self.counts_per_gram(grams, cal_counts)
cap_kg = self.capacity_kg(c_per_g)
- gcmd.respond_info("Calibration value: %.2f%% (%i), Counts/gram: %.5f, \
+ gcmd.respond_info(
+ "Calibration value: %.2f%% (%i), Counts/gram: %.5f, \
Total capacity: +/- %0.2fKg"
- % (cal_percent, cal_counts, c_per_g, cap_kg))
+ % (cal_percent, cal_counts, c_per_g, cap_kg)
+ )
range_min, range_max = self.load_cell.saturation_range()
if cal_counts >= range_max or cal_counts <= range_min:
raise self.printer.command_error(
"ERROR: Sensor is saturated with too much load!\n"
- "Use less force to calibrate the load cell.")
+ "Use less force to calibrate the load cell."
+ )
if cal_counts == self._tare_counts:
raise self.printer.command_error(
"ERROR: Tare and Calibration readings are the same!\n"
- "Check wiring and validate sensor with READ_LOAD_CELL command.")
- if (abs(cal_percent - self.tare_percent)) < 1.:
+ "Check wiring and validate sensor with READ_LOAD_CELL command."
+ )
+ if (abs(cal_percent - self.tare_percent)) < 1.0:
raise self.printer.command_error(
"ERROR: Tare and Calibration readings are less than 1% "
"different!\n"
- "Use more force when calibrating or a higher sensor gain.")
+ "Use more force when calibrating or a higher sensor gain."
+ )
# only set _counts_per_gram after all errors are raised
self._counts_per_gram = c_per_g
- if cap_kg < 1.:
- gcmd.respond_info("WARNING: Load cell capacity is less than 1kg!\n"
- "Check wiring and consider using a lower sensor gain.")
- if cap_kg > 25.:
- gcmd.respond_info("WARNING: Load cell capacity is more than 25Kg!\n"
- "Check wiring and consider using a higher sensor gain.")
+ if cap_kg < 1.0:
+ gcmd.respond_info(
+ "WARNING: Load cell capacity is less than 1kg!\n"
+ "Check wiring and consider using a lower sensor gain."
+ )
+ if cap_kg > 25.0:
+ gcmd.respond_info(
+ "WARNING: Load cell capacity is more than 25Kg!\n"
+ "Check wiring and consider using a higher sensor gain."
+ )
gcmd.respond_info("Accept calibration with the ACCEPT command.")
@@ -272,13 +324,15 @@ class LoadCellGuidedCalibrationHelper:
# can collect a minimum n samples or collect until a specific print_time
# samples returned in [[time],[force],[counts]] arrays for easy processing
RETRY_DELAY = 0.05 # 20Hz
+
+
class LoadCellSampleCollector:
def __init__(self, printer, load_cell):
self._printer = printer
self._load_cell = load_cell
self._reactor = printer.get_reactor()
self._mcu = load_cell.sensor.get_mcu()
- self.min_time = 0.
+ self.min_time = 0.0
self.max_time = float("inf")
self.min_count = float("inf") # In Python 3.5 math.inf is better
self.is_started = False
@@ -289,9 +343,9 @@ class LoadCellSampleCollector:
def _on_samples(self, msg):
if not self.is_started:
return False # already stopped, ignore
- self._errors += msg['errors']
- self._overflows += msg['overflows']
- samples = msg['data']
+ self._errors += msg["errors"]
+ self._overflows += msg["overflows"]
+ samples = msg["data"]
for sample in samples:
time = sample[0]
if self.min_time <= time <= self.max_time:
@@ -304,7 +358,7 @@ class LoadCellSampleCollector:
def _finish_collecting(self):
self.is_started = False
- self.min_time = 0.
+ self.min_time = 0.0
self.max_time = float("inf")
self.min_count = float("inf") # In Python 3.5 math.inf is better
samples = self._samples
@@ -323,7 +377,8 @@ class LoadCellSampleCollector:
self._finish_collecting()
raise self._printer.command_error(
"LoadCellSampleCollector timed out! Errors: %i,"
- " Overflows: %i" % (self._errors, self._overflows))
+ " Overflows: %i" % (self._errors, self._overflows)
+ )
self._reactor.pause(now + RETRY_DELAY)
return self._finish_collecting()
@@ -348,38 +403,45 @@ class LoadCellSampleCollector:
print_time = self._mcu.estimated_print_time(self._reactor.monotonic())
start_time = max(print_time, self.min_time)
sps = self._load_cell.sensor.get_samples_per_second()
- return self._collect_until(start_time + 1. + (min_count / sps))
+ return self._collect_until(start_time + 1.0 + (min_count / sps))
# returns when a sample is collected with a timestamp after print_time
def collect_until(self, print_time=None):
self.max_time = print_time
if len(self._samples) and self._samples[-1][0] >= print_time:
return self._finish_collecting()
- return self._collect_until(self.max_time + 1.)
+ return self._collect_until(self.max_time + 1.0)
+
# Printer class that controls the load cell
-MIN_COUNTS_PER_GRAM = 1.
+MIN_COUNTS_PER_GRAM = 1.0
+
+
class LoadCell:
def __init__(self, config, sensor):
self.printer = printer = config.get_printer()
self.config_name = config.get_name()
self.name = config.get_name().split()[-1]
- self.sensor = sensor # must implement BulkSensorAdc
+ self.sensor = sensor # must implement BulkSensorAdc
buffer_size = sensor.get_samples_per_second() // 2
self._force_buffer = collections.deque(maxlen=buffer_size)
- self.reference_tare_counts = config.getint('reference_tare_counts',
- default=None)
+ self.reference_tare_counts = config.getint(
+ "reference_tare_counts", default=None
+ )
self.tare_counts = self.reference_tare_counts
- self.counts_per_gram = config.getfloat('counts_per_gram',
- minval=MIN_COUNTS_PER_GRAM, default=None)
- self.invert = config.getchoice('sensor_orientation',
- {'normal': 1., 'inverted': -1.}, default="normal")
+ self.counts_per_gram = config.getfloat(
+ "counts_per_gram", minval=MIN_COUNTS_PER_GRAM, default=None
+ )
+ self.invert = config.getchoice(
+ "sensor_orientation", {"normal": 1.0, "inverted": -1.0}, default="normal"
+ )
LoadCellCommandHelper(config, self)
# Client support:
self.clients = ApiClientHelper(printer)
header = {"header": ["time", "force (g)", "counts", "tare_counts"]}
- self.clients.add_mux_endpoint("load_cell/dump_force",
- "load_cell", self.name, header)
+ self.clients.add_mux_endpoint(
+ "load_cell/dump_force", "load_cell", self.name, header
+ )
# startup, when klippy is ready, start capturing data
printer.register_event_handler("klippy:ready", self._handle_ready)
@@ -402,9 +464,10 @@ class LoadCell:
samples = []
for row in data:
# [time, grams, counts, tare_counts]
- samples.append([row[0], self.counts_to_grams(row[1]), row[1],
- self.tare_counts])
- msg = {'data': samples, 'errors': errors, 'overflows': overflows}
+ samples.append(
+ [row[0], self.counts_to_grams(row[1]), row[1], self.tare_counts]
+ )
+ msg = {"data": samples, "errors": errors, "overflows": overflows}
self.clients.send(msg)
return True
@@ -417,18 +480,21 @@ class LoadCell:
self.printer.send_event("load_cell:tare", self)
def set_calibration(self, counts_per_gram, tare_counts):
- if (counts_per_gram is None
- or abs(counts_per_gram) < MIN_COUNTS_PER_GRAM):
+ if counts_per_gram is None or abs(counts_per_gram) < MIN_COUNTS_PER_GRAM:
raise self.printer.command_error("Invalid counts per gram value")
if tare_counts is None:
raise self.printer.command_error("Missing tare counts")
self.counts_per_gram = counts_per_gram
self.reference_tare_counts = int(tare_counts)
- configfile = self.printer.lookup_object('configfile')
- configfile.set(self.config_name, 'counts_per_gram',
- "%.5f" % (self.counts_per_gram,))
- configfile.set(self.config_name, 'reference_tare_counts',
- "%i" % (self.reference_tare_counts,))
+ configfile = self.printer.lookup_object("configfile")
+ configfile.set(
+ self.config_name, "counts_per_gram", "%.5f" % (self.counts_per_gram,)
+ )
+ configfile.set(
+ self.config_name,
+ "reference_tare_counts",
+ "%i" % (self.reference_tare_counts,),
+ )
self.printer.send_event("load_cell:calibrate", self)
def counts_to_grams(self, sample):
@@ -444,7 +510,7 @@ class LoadCell:
# convert raw counts to a +/- percentage of the sensors range
def counts_to_percent(self, counts):
range_min, range_max = self.saturation_range()
- return (float(counts) / float(range_max)) * 100.
+ return (float(counts) / float(range_max)) * 100.0
# read 1 second of load cell data and average it
# performs safety checks for saturation
@@ -454,40 +520,41 @@ class LoadCell:
samples, errors = self.get_collector().collect_min(num_samples)
if errors:
raise self.printer.command_error(
- "Sensor reported %i errors while sampling"
- % (errors[0] + errors[1]))
+ "Sensor reported %i errors while sampling" % (errors[0] + errors[1])
+ )
# check samples for saturated readings
range_min, range_max = self.saturation_range()
for sample in samples:
if sample[2] >= range_max or sample[2] <= range_min:
- raise self.printer.command_error(
- "Some samples are saturated (+/-100%)")
+ raise self.printer.command_error("Some samples are saturated (+/-100%)")
return avg(select_column(samples, 2))
# Provide ongoing force tracking/averaging for status updates
def _track_force(self, msg):
if not (self.is_calibrated() and self.is_tared()):
return True
- samples = msg['data']
+ samples = msg["data"]
# selectColumn unusable here because Python 2 lacks deque.extend
for sample in samples:
self._force_buffer.append(sample[1])
return True
def _force_g(self):
- if (self.is_calibrated() and self.is_tared()
- and len(self._force_buffer) > 0):
- return {"force_g": round(avg(self._force_buffer), 1),
- "min_force_g": round(min(self._force_buffer), 1),
- "max_force_g": round(max(self._force_buffer), 1)}
+ if self.is_calibrated() and self.is_tared() and len(self._force_buffer) > 0:
+ return {
+ "force_g": round(avg(self._force_buffer), 1),
+ "min_force_g": round(min(self._force_buffer), 1),
+ "max_force_g": round(max(self._force_buffer), 1),
+ }
return {}
def is_tared(self):
return self.tare_counts is not None
def is_calibrated(self):
- return (self.counts_per_gram is not None
- and self.reference_tare_counts is not None)
+ return (
+ self.counts_per_gram is not None and self.reference_tare_counts is not None
+ )
def get_sensor(self):
return self.sensor
@@ -506,10 +573,14 @@ class LoadCell:
def get_status(self, eventtime):
status = self._force_g()
- status.update({'is_calibrated': self.is_calibrated(),
- 'counts_per_gram': self.counts_per_gram,
- 'reference_tare_counts': self.reference_tare_counts,
- 'tare_counts': self.tare_counts})
+ status.update(
+ {
+ "is_calibrated": self.is_calibrated(),
+ "counts_per_gram": self.counts_per_gram,
+ "reference_tare_counts": self.reference_tare_counts,
+ "tare_counts": self.tare_counts,
+ }
+ )
return status
@@ -518,8 +589,9 @@ def load_config(config):
sensors = {}
sensors.update(hx71x.HX71X_SENSOR_TYPES)
sensors.update(ads1220.ADS1220_SENSOR_TYPE)
- sensor_class = config.getchoice('sensor_type', sensors)
+ sensor_class = config.getchoice("sensor_type", sensors)
return LoadCell(config, sensor_class(config))
+
def load_config_prefix(config):
return load_config(config)
diff --git a/klippy/extras/load_cell_probe.py b/klippy/extras/load_cell_probe.py
index db2f2a65..de54792f 100644
--- a/klippy/extras/load_cell_probe.py
+++ b/klippy/extras/load_cell_probe.py
@@ -11,9 +11,9 @@ np = None # delay NumPy import until configuration time
# constants for fixed point numbers
Q2_INT_BITS = 2
-Q2_FRAC_BITS = (32 - (1 + Q2_INT_BITS))
+Q2_FRAC_BITS = 32 - (1 + Q2_INT_BITS)
Q16_INT_BITS = 16
-Q16_FRAC_BITS = (32 - (1 + Q16_INT_BITS))
+Q16_FRAC_BITS = 32 - (1 + Q16_INT_BITS)
class TapAnalysis:
@@ -25,16 +25,27 @@ class TapAnalysis:
# convert to dictionary for JSON encoder
def to_dict(self):
return {
- 'time': self.time.tolist(), 'force': self.force.tolist(),
- 'is_valid': True,
+ "time": self.time.tolist(),
+ "force": self.force.tolist(),
+ "is_valid": True,
}
# Access a parameter from config or GCode command via a consistent interface
# stores name and constraints to keep things DRY
class ParamHelper:
- def __init__(self, config, name, type_name, default=None, minval=None,
- maxval=None, above=None, below=None, max_len=None):
+ def __init__(
+ self,
+ config,
+ name,
+ type_name,
+ default=None,
+ minval=None,
+ maxval=None,
+ above=None,
+ below=None,
+ max_len=None,
+ ):
self._config_section = config.get_name()
self._config_error = config.error
self.name = name
@@ -62,87 +73,120 @@ class ParamHelper:
# support for validating individual options in a list of floats
def _validate_float_list(self, gcmd, values, above, below):
if gcmd:
- description = ("Error on '%s': %s" % (
- gcmd.get_commandline(), self._get_name(gcmd)))
+ description = "Error on '%s': %s" % (
+ gcmd.get_commandline(),
+ self._get_name(gcmd),
+ )
error = gcmd.error
else:
- description = ("Option '%s' in section '%s'" % (
- self._get_name(gcmd), self._config_section))
+ description = "Option '%s' in section '%s'" % (
+ self._get_name(gcmd),
+ self._config_section,
+ )
error = self._config_error
if self.max_len is not None and len(values) > self.max_len:
- raise error(
- "%s has maximum length %s" % (description, self.max_len))
+ raise error("%s has maximum length %s" % (description, self.max_len))
for value in values:
self._validate_float(description, error, value, above, below)
def _get_int(self, config, gcmd, minval, maxval):
get = gcmd.get_int if gcmd else config.getint
- return get(self._get_name(gcmd), self.value, minval or self.minval,
- maxval or self.maxval)
+ return get(
+ self._get_name(gcmd),
+ self.value,
+ minval or self.minval,
+ maxval or self.maxval,
+ )
def _get_float(self, config, gcmd, minval, maxval, above, below):
get = gcmd.get_float if gcmd else config.getfloat
- return get(self._get_name(gcmd), self.value, minval or self.minval,
- maxval or self.maxval, above or self.above, below or self.below)
+ return get(
+ self._get_name(gcmd),
+ self.value,
+ minval or self.minval,
+ maxval or self.maxval,
+ above or self.above,
+ below or self.below,
+ )
def _get_float_list(self, config, gcmd, above, below):
# this code defaults to the empty list, never return None
- default = (self.value or [])
+ default = self.value or []
if gcmd:
# if the parameter isn't part of the command, return the default
if not self._get_name(gcmd) in gcmd.get_command_parameters():
return default
# parameter exists, always prefer whatever is in the command
- value = gcmd.get(self._get_name(gcmd), default='')
+ value = gcmd.get(self._get_name(gcmd), default="")
# Return an empty list for empty value
if len(value.strip()) == 0:
return []
try:
- float_list = [float(p.strip()) for p in value.split(',')]
+ float_list = [float(p.strip()) for p in value.split(",")]
except:
- raise gcmd.error("Error on '%s': unable to parse %s" % (
- gcmd.get_commandline(), value))
+ raise gcmd.error(
+ "Error on '%s': unable to parse %s"
+ % (gcmd.get_commandline(), value)
+ )
else:
- float_list = config.getfloatlist(self._get_name(gcmd),
- default=default)
+ float_list = config.getfloatlist(self._get_name(gcmd), default=default)
if float_list:
self._validate_float_list(gcmd, float_list, above, below)
return float_list
- def get(self, gcmd=None, minval=None, maxval=None, above=None, below=None,
- config=None):
+ def get(
+ self, gcmd=None, minval=None, maxval=None, above=None, below=None, config=None
+ ):
if config is None and gcmd is None:
return self.value
- if self._type_name == 'int':
+ if self._type_name == "int":
return self._get_int(config, gcmd, minval, maxval)
- elif self._type_name == 'float':
+ elif self._type_name == "float":
return self._get_float(config, gcmd, minval, maxval, above, below)
else:
return self._get_float_list(config, gcmd, above, below)
def intParamHelper(config, name, default=None, minval=None, maxval=None):
- return ParamHelper(config, name, 'int', default, minval=minval,
- maxval=maxval)
+ return ParamHelper(config, name, "int", default, minval=minval, maxval=maxval)
-def floatParamHelper(config, name, default=None, minval=None, maxval=None,
- above=None, below=None):
- return ParamHelper(config, name, 'float', default, minval=minval,
- maxval=maxval, above=above, below=below)
+def floatParamHelper(
+ config, name, default=None, minval=None, maxval=None, above=None, below=None
+):
+ return ParamHelper(
+ config,
+ name,
+ "float",
+ default,
+ minval=minval,
+ maxval=maxval,
+ above=above,
+ below=below,
+ )
-def floatListParamHelper(config, name, default=None, above=None, below=None,
- max_len=None):
- return ParamHelper(config, name, 'float_list', default, above=above,
- below=below, max_len=max_len)
+def floatListParamHelper(
+ config, name, default=None, above=None, below=None, max_len=None
+):
+ return ParamHelper(
+ config, name, "float_list", default, above=above, below=below, max_len=max_len
+ )
# container for filter parameters
# allows different filter configurations to be compared
class ContinuousTareFilter:
- def __init__(self, sps=None, drift=None, drift_delay=None, buzz=None,
- buzz_delay=None, notches=None, notch_quality=None):
+ def __init__(
+ self,
+ sps=None,
+ drift=None,
+ drift_delay=None,
+ buzz=None,
+ buzz_delay=None,
+ notches=None,
+ notch_quality=None,
+ ):
self.sps = sps
self.drift = drift
self.drift_delay = drift_delay
@@ -155,20 +199,33 @@ class ContinuousTareFilter:
if not isinstance(other, ContinuousTareFilter):
return False
return (
- self.sps == other.sps and self.drift == other.drift and
- self.drift_delay == other.drift_delay and self.buzz ==
- other.buzz and self.buzz_delay == other.buzz_delay and
- self.notches == other.notches and self.notch_quality ==
- other.notch_quality)
+ self.sps == other.sps
+ and self.drift == other.drift
+ and self.drift_delay == other.drift_delay
+ and self.buzz == other.buzz
+ and self.buzz_delay == other.buzz_delay
+ and self.notches == other.notches
+ and self.notch_quality == other.notch_quality
+ )
# create a filter design from the parameters
def design_filter(self, error_func):
- design = sos_filter.DigitalFilter(self.sps, error_func, self.drift,
- self.drift_delay, self.buzz, self.buzz_delay, self.notches,
- self.notch_quality)
+ design = sos_filter.DigitalFilter(
+ self.sps,
+ error_func,
+ self.drift,
+ self.drift_delay,
+ self.buzz,
+ self.buzz_delay,
+ self.notches,
+ self.notch_quality,
+ )
fixed_filter = sos_filter.FixedPointSosFilter(
- design.get_filter_sections(), design.get_initial_state(),
- Q2_INT_BITS, Q16_INT_BITS)
+ design.get_filter_sections(),
+ design.get_initial_state(),
+ Q2_INT_BITS,
+ Q16_INT_BITS,
+ )
return fixed_filter
@@ -177,31 +234,47 @@ class ContinuousTareFilterHelper:
def __init__(self, config, sensor, cmd_queue):
self._sensor = sensor
self._sps = self._sensor.get_samples_per_second()
- max_filter_frequency = math.floor(self._sps / 2.)
+ max_filter_frequency = math.floor(self._sps / 2.0)
# setup filter parameters
- self._drift_param = floatParamHelper(config,
- "drift_filter_cutoff_frequency", default=None, minval=0.1,
- maxval=20.0)
- self._drift_delay_param = intParamHelper(config, "drift_filter_delay",
- default=2, minval=1, maxval=2)
- self._buzz_param = floatParamHelper(config,
- "buzz_filter_cutoff_frequency", default=None,
+ self._drift_param = floatParamHelper(
+ config,
+ "drift_filter_cutoff_frequency",
+ default=None,
+ minval=0.1,
+ maxval=20.0,
+ )
+ self._drift_delay_param = intParamHelper(
+ config, "drift_filter_delay", default=2, minval=1, maxval=2
+ )
+ self._buzz_param = floatParamHelper(
+ config,
+ "buzz_filter_cutoff_frequency",
+ default=None,
above=min(80.0, max_filter_frequency - 1.0),
- below=max_filter_frequency)
- self._buzz_delay_param = intParamHelper(config, "buzz_filter_delay",
- default=2, minval=1, maxval=2)
- self._notches_param = floatListParamHelper(config,
- "notch_filter_frequencies", default=[], above=0.,
- below=max_filter_frequency, max_len=2)
- self._notch_quality_param = floatParamHelper(config,
- "notch_filter_quality", default=2.0, minval=0.5, maxval=6.0)
+ below=max_filter_frequency,
+ )
+ self._buzz_delay_param = intParamHelper(
+ config, "buzz_filter_delay", default=2, minval=1, maxval=2
+ )
+ self._notches_param = floatListParamHelper(
+ config,
+ "notch_filter_frequencies",
+ default=[],
+ above=0.0,
+ below=max_filter_frequency,
+ max_len=2,
+ )
+ self._notch_quality_param = floatParamHelper(
+ config, "notch_filter_quality", default=2.0, minval=0.5, maxval=6.0
+ )
# filter design specified in the config file, used for defaults
self._config_design = ContinuousTareFilter() # empty filter
self._config_design = self._build_filter()
# filter design currently inside the MCU
self._active_design = self._config_design
self._sos_filter = self._create_filter(
- self._active_design.design_filter(config.error), cmd_queue)
+ self._active_design.design_filter(config.error), cmd_queue
+ )
def _build_filter(self, gcmd=None):
drift = self._drift_param.get(gcmd)
@@ -211,12 +284,12 @@ class ContinuousTareFilterHelper:
# notches must be between drift and buzz:
notches = self._notches_param.get(gcmd, above=drift, below=buzz)
notch_quality = self._notch_quality_param.get(gcmd)
- return ContinuousTareFilter(self._sps, drift, drift_delay, buzz,
- buzz_delay, notches, notch_quality)
+ return ContinuousTareFilter(
+ self._sps, drift, drift_delay, buzz, buzz_delay, notches, notch_quality
+ )
def _create_filter(self, fixed_filter, cmd_queue):
- return sos_filter.SosFilter(self._sensor.get_mcu(), cmd_queue,
- fixed_filter, 4)
+ return sos_filter.SosFilter(self._sensor.get_mcu(), cmd_queue, fixed_filter, 4)
def update_from_command(self, gcmd, cq=None):
gcmd_filter = self._build_filter(gcmd)
@@ -224,8 +297,7 @@ class ContinuousTareFilterHelper:
if self._active_design == gcmd_filter:
return
# update MCU filter from GCode command
- self._sos_filter.change_filter(
- self._active_design.design_filter(gcmd.error))
+ self._sos_filter.change_filter(self._active_design.design_filter(gcmd.error))
def get_sos_filter(self):
return self._sos_filter
@@ -235,9 +307,10 @@ class ContinuousTareFilterHelper:
def check_sensor_errors(results, printer):
samples, errors = results
if errors:
- raise printer.command_error("Load cell sensor reported errors while"
- " probing: %i errors, %i overflows" % (
- errors[0], errors[1]))
+ raise printer.command_error(
+ "Load cell sensor reported errors while"
+ " probing: %i errors, %i overflows" % (errors[0], errors[1])
+ )
return samples
@@ -246,15 +319,18 @@ class LoadCellProbeConfigHelper:
self._printer = config.get_printer()
self._load_cell = load_cell_inst
self._sensor = load_cell_inst.get_sensor()
- self._rest_time = 1. / float(self._sensor.get_samples_per_second())
+ self._rest_time = 1.0 / float(self._sensor.get_samples_per_second())
# Collect 4 x 60hz power cycles of data to average across power noise
- self._tare_time_param = floatParamHelper(config, 'tare_time',
- default=4. / 60., minval=0.01, maxval=1.0)
+ self._tare_time_param = floatParamHelper(
+ config, "tare_time", default=4.0 / 60.0, minval=0.01, maxval=1.0
+ )
# triggering options
- self._trigger_force_param = intParamHelper(config, 'trigger_force',
- default=75, minval=10, maxval=250)
- self._force_safety_limit_param = intParamHelper(config,
- 'force_safety_limit', minval=100, maxval=5000, default=2000)
+ self._trigger_force_param = intParamHelper(
+ config, "trigger_force", default=75, minval=10, maxval=250
+ )
+ self._force_safety_limit_param = intParamHelper(
+ config, "force_safety_limit", minval=100, maxval=5000, default=2000
+ )
def get_tare_samples(self, gcmd=None):
tare_time = self._tare_time_param.get(gcmd)
@@ -292,7 +368,7 @@ class LoadCellProbeConfigHelper:
# few grams which seems very unlikely. Treat this as an error:
if counts_per_gram >= 2**Q2_FRAC_BITS:
raise OverflowError("counts_per_gram value is too large to filter")
- return sos_filter.to_fixed_32((1. / counts_per_gram), Q2_INT_BITS)
+ return sos_filter.to_fixed_32((1.0 / counts_per_gram), Q2_INT_BITS)
# McuLoadCellProbe is the interface to `load_cell_probe` on the MCU
@@ -303,8 +379,9 @@ class McuLoadCellProbe:
ERROR_OVERFLOW = mcu.MCU_trsync.REASON_COMMS_TIMEOUT + 2
ERROR_WATCHDOG = mcu.MCU_trsync.REASON_COMMS_TIMEOUT + 3
- def __init__(self, config, load_cell_inst, sos_filter_inst, config_helper,
- trigger_dispatch):
+ def __init__(
+ self, config, load_cell_inst, sos_filter_inst, config_helper, trigger_dispatch
+ ):
self._printer = config.get_printer()
self._load_cell = load_cell_inst
self._sos_filter = sos_filter_inst
@@ -325,23 +402,29 @@ class McuLoadCellProbe:
def _config_commands(self):
self._sos_filter.create_filter()
self._mcu.add_config_cmd(
- "config_load_cell_probe oid=%d sos_filter_oid=%d" % (
- self._oid, self._sos_filter.get_oid()))
+ "config_load_cell_probe oid=%d sos_filter_oid=%d"
+ % (self._oid, self._sos_filter.get_oid())
+ )
def _build_config(self):
# Lookup commands
self._query_cmd = self._mcu.lookup_query_command(
"load_cell_probe_query_state oid=%c",
- "load_cell_probe_state oid=%c is_homing_trigger=%c "
- "trigger_ticks=%u", oid=self._oid, cq=self._cmd_queue)
+ "load_cell_probe_state oid=%c is_homing_trigger=%c " "trigger_ticks=%u",
+ oid=self._oid,
+ cq=self._cmd_queue,
+ )
self._set_range_cmd = self._mcu.lookup_command(
"load_cell_probe_set_range"
" oid=%c safety_counts_min=%i safety_counts_max=%i tare_counts=%i"
- " trigger_grams=%u grams_per_count=%i", cq=self._cmd_queue)
+ " trigger_grams=%u grams_per_count=%i",
+ cq=self._cmd_queue,
+ )
self._home_cmd = self._mcu.lookup_command(
"load_cell_probe_home oid=%c trsync_oid=%c trigger_reason=%c"
" error_reason=%c clock=%u rest_ticks=%u timeout=%u",
- cq=self._cmd_queue)
+ cq=self._cmd_queue,
+ )
# the sensor data stream is connected on the MCU at the ready event
def _on_connect(self):
@@ -364,9 +447,14 @@ class McuLoadCellProbe:
self._load_cell.tare(tare_counts)
# update internal tare value
safety_min, safety_max = self._config_helper.get_safety_range(gcmd)
- args = [self._oid, safety_min, safety_max, int(tare_counts),
+ args = [
+ self._oid,
+ safety_min,
+ safety_max,
+ int(tare_counts),
self._config_helper.get_trigger_force_grams(gcmd),
- self._config_helper.get_grams_per_count()]
+ self._config_helper.get_grams_per_count(),
+ ]
self._set_range_cmd.send(args)
self._sos_filter.reset_filter()
@@ -374,14 +462,23 @@ class McuLoadCellProbe:
clock = self._mcu.print_time_to_clock(print_time)
rest_time = self._config_helper.get_rest_time()
rest_ticks = self._mcu.seconds_to_clock(rest_time)
- self._home_cmd.send([self._oid, self._dispatch.get_oid(),
- mcu.MCU_trsync.REASON_ENDSTOP_HIT, self.ERROR_SAFETY_RANGE, clock,
- rest_ticks, self.WATCHDOG_MAX], reqclock=clock)
+ self._home_cmd.send(
+ [
+ self._oid,
+ self._dispatch.get_oid(),
+ mcu.MCU_trsync.REASON_ENDSTOP_HIT,
+ self.ERROR_SAFETY_RANGE,
+ clock,
+ rest_ticks,
+ self.WATCHDOG_MAX,
+ ],
+ reqclock=clock,
+ )
def clear_home(self):
params = self._query_cmd.send([self._oid])
# The time of the first sample that triggered is in "trigger_ticks"
- trigger_ticks = self._mcu.clock32_to_clock64(params['trigger_ticks'])
+ trigger_ticks = self._mcu.clock32_to_clock64(params["trigger_ticks"])
# clear trsync from load_cell_endstop
self._home_cmd.send([self._oid, 0, 0, 0, 0, 0, 0, 0])
return self._mcu.clock_to_print_time(trigger_ticks)
@@ -390,18 +487,23 @@ class McuLoadCellProbe:
# Execute probing moves using the McuLoadCellProbe
class LoadCellProbingMove:
ERROR_MAP = {
- mcu.MCU_trsync.REASON_COMMS_TIMEOUT: "Communication timeout during "
- "homing",
+ mcu.MCU_trsync.REASON_COMMS_TIMEOUT: "Communication timeout during " "homing",
McuLoadCellProbe.ERROR_SAFETY_RANGE: "Load Cell Probe Error: load "
- "exceeds safety limit",
+ "exceeds safety limit",
McuLoadCellProbe.ERROR_OVERFLOW: "Load Cell Probe Error: fixed point "
- "math overflow",
+ "math overflow",
McuLoadCellProbe.ERROR_WATCHDOG: "Load Cell Probe Error: timed out "
- "waiting for sensor data"
+ "waiting for sensor data",
}
- def __init__(self, config, mcu_load_cell_probe, param_helper,
- continuous_tare_filter_helper, config_helper):
+ def __init__(
+ self,
+ config,
+ mcu_load_cell_probe,
+ param_helper,
+ continuous_tare_filter_helper,
+ config_helper,
+ ):
self._printer = config.get_printer()
self._mcu_load_cell_probe = mcu_load_cell_probe
self._param_helper = param_helper
@@ -417,7 +519,7 @@ class LoadCellProbingMove:
self._last_trigger_time = 0
def _start_collector(self):
- toolhead = self._printer.lookup_object('toolhead')
+ toolhead = self._printer.lookup_object("toolhead")
# homing uses the toolhead last move time which gets special handling
# to significantly buffer print_time if the move queue has drained
print_time = toolhead.get_last_move_time()
@@ -444,8 +546,9 @@ class LoadCellProbingMove:
self._mcu_load_cell_probe.home_start(print_time)
return trigger_completion
- def home_start(self, print_time, sample_time, sample_count, rest_time,
- triggered=True):
+ def home_start(
+ self, print_time, sample_time, sample_count, rest_time, triggered=True
+ ):
return self._home_start(print_time)
def home_wait(self, home_end_time):
@@ -460,7 +563,7 @@ class LoadCellProbingMove:
error = self.ERROR_MAP[res]
raise self._printer.command_error(error)
if res != mcu.MCU_trsync.REASON_ENDSTOP_HIT:
- return 0.
+ return 0.0
return self._last_trigger_time
def get_steppers(self):
@@ -474,11 +577,11 @@ class LoadCellProbingMove:
# tare the sensor just before probing
self._pause_and_tare(gcmd)
# get params for the homing move
- toolhead = self._printer.lookup_object('toolhead')
+ toolhead = self._printer.lookup_object("toolhead")
pos = toolhead.get_position()
pos[2] = self._z_min_position
- speed = self._param_helper.get_probe_params(gcmd)['probe_speed']
- phoming = self._printer.lookup_object('homing')
+ speed = self._param_helper.get_probe_params(gcmd)["probe_speed"]
+ phoming = self._printer.lookup_object("homing")
# start collector after tare samples are consumed
collector = self._start_collector()
# do homing move
@@ -487,15 +590,15 @@ class LoadCellProbingMove:
# Wait for the MCU to trigger with no movement
def probing_test(self, gcmd, timeout):
self._pause_and_tare(gcmd)
- toolhead = self._printer.lookup_object('toolhead')
+ toolhead = self._printer.lookup_object("toolhead")
print_time = toolhead.get_last_move_time()
self._home_start(print_time)
return self.home_wait(print_time + timeout)
def get_status(self, eventtime):
return {
- 'tare_counts': self._tare_counts,
- 'last_trigger_time': self._last_trigger_time,
+ "tare_counts": self._tare_counts,
+ "last_trigger_time": self._last_trigger_time,
}
@@ -512,15 +615,16 @@ class TappingMove:
self._clients = load_cell.ApiClientHelper(config.get_printer())
name = config.get_name()
header = {"header": ["probe_tap_event"]}
- self._clients.add_mux_endpoint("load_cell_probe/dump_taps",
- "load_cell_probe", name, header)
+ self._clients.add_mux_endpoint(
+ "load_cell_probe/dump_taps", "load_cell_probe", name, header
+ )
# perform a probing move and a pullback move
def run_tap(self, gcmd):
# do the descending move
epos, collector = self._load_cell_probing_move.probing_move(gcmd)
# collect samples from the tap
- toolhead = self._printer.lookup_object('toolhead')
+ toolhead = self._printer.lookup_object("toolhead")
toolhead.flush_step_generation()
move_end = toolhead.get_last_move_time()
results = collector.collect_until(move_end)
@@ -528,15 +632,15 @@ class TappingMove:
# Analyze the tap data
ppa = TapAnalysis(samples)
# broadcast tap event data:
- self._clients.send({'tap': ppa.to_dict()})
+ self._clients.send({"tap": ppa.to_dict()})
self._is_last_result_valid = True
self._last_result = epos[2]
return epos, self._is_last_result_valid
def get_status(self, eventtime):
return {
- 'last_z_result': self._last_result,
- 'is_last_tap_valid': self._is_last_result_valid
+ "last_z_result": self._last_result,
+ "is_last_tap_valid": self._is_last_result_valid,
}
@@ -574,20 +678,23 @@ class LoadCellProbeCommands:
def _register_commands(self):
# Register commands
- gcode = self._printer.lookup_object('gcode')
- gcode.register_command("LOAD_CELL_TEST_TAP",
- self.cmd_LOAD_CELL_TEST_TAP, desc=self.cmd_LOAD_CELL_TEST_TAP_help)
+ gcode = self._printer.lookup_object("gcode")
+ gcode.register_command(
+ "LOAD_CELL_TEST_TAP",
+ self.cmd_LOAD_CELL_TEST_TAP,
+ desc=self.cmd_LOAD_CELL_TEST_TAP_help,
+ )
cmd_LOAD_CELL_TEST_TAP_help = "Tap the load cell probe to verify operation"
def cmd_LOAD_CELL_TEST_TAP(self, gcmd):
taps = gcmd.get_int("TAPS", 3, minval=1, maxval=10)
- timeout = gcmd.get_float("TIMEOUT", 30., minval=1., maxval=120.)
+ timeout = gcmd.get_float("TIMEOUT", 30.0, minval=1.0, maxval=120.0)
gcmd.respond_info("Tap the load cell %s times:" % (taps,))
reactor = self._printer.get_reactor()
for i in range(0, taps):
result = self._load_cell_probing_move.probing_test(gcmd, timeout)
- if result == 0.:
+ if result == 0.0:
# notify of error, likely due to timeout
raise gcmd.error("Test timeout out")
gcmd.respond_info("Tap Detected!")
@@ -609,34 +716,43 @@ class LoadCellPrinterProbe:
sensors = {}
sensors.update(hx71x.HX71X_SENSOR_TYPES)
sensors.update(ads1220.ADS1220_SENSOR_TYPE)
- sensor_class = config.getchoice('sensor_type', sensors)
+ sensor_class = config.getchoice("sensor_type", sensors)
sensor = sensor_class(config)
self._load_cell = load_cell.LoadCell(config, sensor)
# Read all user configuration and build modules
config_helper = LoadCellProbeConfigHelper(config, self._load_cell)
self._mcu = self._load_cell.get_sensor().get_mcu()
trigger_dispatch = mcu.TriggerDispatch(self._mcu)
- continuous_tare_filter_helper = ContinuousTareFilterHelper(config,
- sensor, trigger_dispatch.get_command_queue())
+ continuous_tare_filter_helper = ContinuousTareFilterHelper(
+ config, sensor, trigger_dispatch.get_command_queue()
+ )
# Probe Interface
self._param_helper = probe.ProbeParameterHelper(config)
self._cmd_helper = probe.ProbeCommandHelper(config, self)
self._probe_offsets = probe.ProbeOffsetsHelper(config)
- self._mcu_load_cell_probe = McuLoadCellProbe(config, self._load_cell,
- continuous_tare_filter_helper.get_sos_filter(), config_helper,
- trigger_dispatch)
- load_cell_probing_move = LoadCellProbingMove(config,
- self._mcu_load_cell_probe, self._param_helper,
- continuous_tare_filter_helper, config_helper)
- self._tapping_move = TappingMove(config, load_cell_probing_move,
- config_helper)
+ self._mcu_load_cell_probe = McuLoadCellProbe(
+ config,
+ self._load_cell,
+ continuous_tare_filter_helper.get_sos_filter(),
+ config_helper,
+ trigger_dispatch,
+ )
+ load_cell_probing_move = LoadCellProbingMove(
+ config,
+ self._mcu_load_cell_probe,
+ self._param_helper,
+ continuous_tare_filter_helper,
+ config_helper,
+ )
+ self._tapping_move = TappingMove(config, load_cell_probing_move, config_helper)
tap_session = TapSession(config, self._tapping_move, self._param_helper)
- self._probe_session = probe.ProbeSessionHelper(config,
- self._param_helper, tap_session.start_probe_session)
+ self._probe_session = probe.ProbeSessionHelper(
+ config, self._param_helper, tap_session.start_probe_session
+ )
# printer integration
LoadCellProbeCommands(config, load_cell_probing_move)
probe.ProbeVirtualEndstopDeprecation(config)
- self._printer.add_object('probe', self)
+ self._printer.add_object("probe", self)
def get_probe_params(self, gcmd=None):
return self._param_helper.get_probe_params(gcmd)
diff --git a/klippy/extras/manual_probe.py b/klippy/extras/manual_probe.py
index 92f2d0f6..176a97e5 100644
--- a/klippy/extras/manual_probe.py
+++ b/klippy/extras/manual_probe.py
@@ -5,75 +5,89 @@
# This file may be distributed under the terms of the GNU GPLv3 license.
import logging, bisect
+
# Helper to lookup the Z stepper config section
def lookup_z_endstop_config(config):
- if config.has_section('stepper_z'):
- return config.getsection('stepper_z')
- elif config.has_section('carriage z'):
- return config.getsection('carriage z')
+ if config.has_section("stepper_z"):
+ return config.getsection("stepper_z")
+ elif config.has_section("carriage z"):
+ return config.getsection("carriage z")
return None
+
class ManualProbe:
def __init__(self, config):
self.printer = config.get_printer()
# Register commands
- self.gcode = self.printer.lookup_object('gcode')
+ self.gcode = self.printer.lookup_object("gcode")
self.gcode_move = self.printer.load_object(config, "gcode_move")
- self.gcode.register_command('MANUAL_PROBE', self.cmd_MANUAL_PROBE,
- desc=self.cmd_MANUAL_PROBE_help)
+ self.gcode.register_command(
+ "MANUAL_PROBE", self.cmd_MANUAL_PROBE, desc=self.cmd_MANUAL_PROBE_help
+ )
# Endstop value for cartesian printers with separate Z axis
zconfig = lookup_z_endstop_config(config)
if zconfig is not None:
- self.z_position_endstop = zconfig.getfloat('position_endstop', None,
- note_valid=False)
+ self.z_position_endstop = zconfig.getfloat(
+ "position_endstop", None, note_valid=False
+ )
self.z_endstop_config_name = zconfig.get_name()
else:
self.z_position_endstop = self.z_endstop_config_name = None
# Endstop values for linear delta printers with vertical A,B,C towers
- a_tower_config = config.getsection('stepper_a')
- self.a_position_endstop = a_tower_config.getfloat('position_endstop',
- None,
- note_valid=False)
- b_tower_config = config.getsection('stepper_b')
- self.b_position_endstop = b_tower_config.getfloat('position_endstop',
- None,
- note_valid=False)
- c_tower_config = config.getsection('stepper_c')
- self.c_position_endstop = c_tower_config.getfloat('position_endstop',
- None,
- note_valid=False)
+ a_tower_config = config.getsection("stepper_a")
+ self.a_position_endstop = a_tower_config.getfloat(
+ "position_endstop", None, note_valid=False
+ )
+ b_tower_config = config.getsection("stepper_b")
+ self.b_position_endstop = b_tower_config.getfloat(
+ "position_endstop", None, note_valid=False
+ )
+ c_tower_config = config.getsection("stepper_c")
+ self.c_position_endstop = c_tower_config.getfloat(
+ "position_endstop", None, note_valid=False
+ )
# Conditionally register appropriate commands depending on printer
# Cartestian printers with separate Z Axis
if self.z_position_endstop is not None:
self.gcode.register_command(
- 'Z_ENDSTOP_CALIBRATE', self.cmd_Z_ENDSTOP_CALIBRATE,
- desc=self.cmd_Z_ENDSTOP_CALIBRATE_help)
+ "Z_ENDSTOP_CALIBRATE",
+ self.cmd_Z_ENDSTOP_CALIBRATE,
+ desc=self.cmd_Z_ENDSTOP_CALIBRATE_help,
+ )
self.gcode.register_command(
- 'Z_OFFSET_APPLY_ENDSTOP',
+ "Z_OFFSET_APPLY_ENDSTOP",
self.cmd_Z_OFFSET_APPLY_ENDSTOP,
- desc=self.cmd_Z_OFFSET_APPLY_ENDSTOP_help)
+ desc=self.cmd_Z_OFFSET_APPLY_ENDSTOP_help,
+ )
# Linear delta printers with A,B,C towers
- if 'delta' == config.getsection('printer').get('kinematics'):
+ if "delta" == config.getsection("printer").get("kinematics"):
self.gcode.register_command(
- 'Z_OFFSET_APPLY_ENDSTOP',
+ "Z_OFFSET_APPLY_ENDSTOP",
self.cmd_Z_OFFSET_APPLY_DELTA_ENDSTOPS,
- desc=self.cmd_Z_OFFSET_APPLY_ENDSTOP_help)
+ desc=self.cmd_Z_OFFSET_APPLY_ENDSTOP_help,
+ )
self.reset_status()
+
def manual_probe_finalize(self, kin_pos):
if kin_pos is not None:
self.gcode.respond_info("Z position is %.3f" % (kin_pos[2],))
+
def reset_status(self):
self.status = {
- 'is_active': False,
- 'z_position': None,
- 'z_position_lower': None,
- 'z_position_upper': None
+ "is_active": False,
+ "z_position": None,
+ "z_position_lower": None,
+ "z_position_upper": None,
}
+
def get_status(self, eventtime):
return self.status
+
cmd_MANUAL_PROBE_help = "Start manual probe helper script"
+
def cmd_MANUAL_PROBE(self, gcmd):
ManualProbeHelper(self.printer, gcmd, self.manual_probe_finalize)
+
def z_endstop_finalize(self, kin_pos):
if kin_pos is None:
return
@@ -81,17 +95,25 @@ class ManualProbe:
self.gcode.respond_info(
"%s: position_endstop: %.3f\n"
"The SAVE_CONFIG command will update the printer config file\n"
- "with the above and restart the printer." % (
- self.z_endstop_config_name, z_pos,))
- configfile = self.printer.lookup_object('configfile')
- configfile.set(self.z_endstop_config_name, 'position_endstop',
- "%.3f" % (z_pos,))
+ "with the above and restart the printer."
+ % (
+ self.z_endstop_config_name,
+ z_pos,
+ )
+ )
+ configfile = self.printer.lookup_object("configfile")
+ configfile.set(
+ self.z_endstop_config_name, "position_endstop", "%.3f" % (z_pos,)
+ )
+
cmd_Z_ENDSTOP_CALIBRATE_help = "Calibrate a Z endstop"
+
def cmd_Z_ENDSTOP_CALIBRATE(self, gcmd):
ManualProbeHelper(self.printer, gcmd, self.z_endstop_finalize)
- def cmd_Z_OFFSET_APPLY_ENDSTOP(self,gcmd):
- offset = self.gcode_move.get_status()['homing_origin'].z
- configfile = self.printer.lookup_object('configfile')
+
+ def cmd_Z_OFFSET_APPLY_ENDSTOP(self, gcmd):
+ offset = self.gcode_move.get_status()["homing_origin"].z
+ configfile = self.printer.lookup_object("configfile")
if offset == 0:
self.gcode.respond_info("Nothing to do: Z Offset is 0")
else:
@@ -99,13 +121,18 @@ class ManualProbe:
self.gcode.respond_info(
"%s: position_endstop: %.3f\n"
"The SAVE_CONFIG command will update the printer config file\n"
- "with the above and restart the printer." % (
- self.z_endstop_config_name, new_calibrate))
- configfile.set(self.z_endstop_config_name, 'position_endstop',
- "%.3f" % (new_calibrate,))
- def cmd_Z_OFFSET_APPLY_DELTA_ENDSTOPS(self,gcmd):
- offset = self.gcode_move.get_status()['homing_origin'].z
- configfile = self.printer.lookup_object('configfile')
+ "with the above and restart the printer."
+ % (self.z_endstop_config_name, new_calibrate)
+ )
+ configfile.set(
+ self.z_endstop_config_name,
+ "position_endstop",
+ "%.3f" % (new_calibrate,),
+ )
+
+ def cmd_Z_OFFSET_APPLY_DELTA_ENDSTOPS(self, gcmd):
+ offset = self.gcode_move.get_status()["homing_origin"].z
+ configfile = self.printer.lookup_object("configfile")
if offset == 0:
self.gcode.respond_info("Nothing to do: Z Offset is 0")
else:
@@ -117,67 +144,70 @@ class ManualProbe:
"stepper_b: position_endstop: %.3f\n"
"stepper_c: position_endstop: %.3f\n"
"The SAVE_CONFIG command will update the printer config file\n"
- "with the above and restart the printer." % (new_a_calibrate,
- new_b_calibrate,
- new_c_calibrate))
- configfile.set('stepper_a', 'position_endstop',
- "%.3f" % (new_a_calibrate,))
- configfile.set('stepper_b', 'position_endstop',
- "%.3f" % (new_b_calibrate,))
- configfile.set('stepper_c', 'position_endstop',
- "%.3f" % (new_c_calibrate,))
+ "with the above and restart the printer."
+ % (new_a_calibrate, new_b_calibrate, new_c_calibrate)
+ )
+ configfile.set("stepper_a", "position_endstop", "%.3f" % (new_a_calibrate,))
+ configfile.set("stepper_b", "position_endstop", "%.3f" % (new_b_calibrate,))
+ configfile.set("stepper_c", "position_endstop", "%.3f" % (new_c_calibrate,))
+
cmd_Z_OFFSET_APPLY_ENDSTOP_help = "Adjust the z endstop_position"
+
# Verify that a manual probe isn't already in progress
def verify_no_manual_probe(printer):
- gcode = printer.lookup_object('gcode')
+ gcode = printer.lookup_object("gcode")
try:
- gcode.register_command('ACCEPT', 'dummy')
+ gcode.register_command("ACCEPT", "dummy")
except printer.config_error as e:
- raise gcode.error(
- "Already in a manual Z probe. Use ABORT to abort it.")
- gcode.register_command('ACCEPT', None)
+ raise gcode.error("Already in a manual Z probe. Use ABORT to abort it.")
+ gcode.register_command("ACCEPT", None)
+
Z_BOB_MINIMUM = 0.500
BISECT_MAX = 0.200
+
# Helper script to determine a Z height
class ManualProbeHelper:
def __init__(self, printer, gcmd, finalize_callback):
self.printer = printer
self.finalize_callback = finalize_callback
- self.gcode = self.printer.lookup_object('gcode')
- self.toolhead = self.printer.lookup_object('toolhead')
- self.manual_probe = self.printer.lookup_object('manual_probe')
- self.speed = gcmd.get_float("SPEED", 5.)
+ self.gcode = self.printer.lookup_object("gcode")
+ self.toolhead = self.printer.lookup_object("toolhead")
+ self.manual_probe = self.printer.lookup_object("manual_probe")
+ self.speed = gcmd.get_float("SPEED", 5.0)
self.past_positions = []
self.last_toolhead_pos = self.last_kinematics_pos = None
# Register commands
verify_no_manual_probe(printer)
- self.gcode.register_command('ACCEPT', self.cmd_ACCEPT,
- desc=self.cmd_ACCEPT_help)
- self.gcode.register_command('NEXT', self.cmd_ACCEPT)
- self.gcode.register_command('ABORT', self.cmd_ABORT,
- desc=self.cmd_ABORT_help)
- self.gcode.register_command('TESTZ', self.cmd_TESTZ,
- desc=self.cmd_TESTZ_help)
+ self.gcode.register_command(
+ "ACCEPT", self.cmd_ACCEPT, desc=self.cmd_ACCEPT_help
+ )
+ self.gcode.register_command("NEXT", self.cmd_ACCEPT)
+ self.gcode.register_command("ABORT", self.cmd_ABORT, desc=self.cmd_ABORT_help)
+ self.gcode.register_command("TESTZ", self.cmd_TESTZ, desc=self.cmd_TESTZ_help)
self.gcode.respond_info(
"Starting manual Z probe. Use TESTZ to adjust position.\n"
- "Finish with ACCEPT or ABORT command.")
+ "Finish with ACCEPT or ABORT command."
+ )
self.start_position = self.toolhead.get_position()
self.report_z_status()
+
def get_kinematics_pos(self):
toolhead_pos = self.toolhead.get_position()
if toolhead_pos == self.last_toolhead_pos:
return self.last_kinematics_pos
self.toolhead.flush_step_generation()
kin = self.toolhead.get_kinematics()
- kin_spos = {s.get_name(): s.get_commanded_position()
- for s in kin.get_steppers()}
+ kin_spos = {
+ s.get_name(): s.get_commanded_position() for s in kin.get_steppers()
+ }
kin_pos = kin.calc_position(kin_spos)
self.last_toolhead_pos = toolhead_pos
self.last_kinematics_pos = kin_pos
return kin_pos
+
def move_z(self, z_pos):
curpos = self.toolhead.get_position()
try:
@@ -188,13 +218,15 @@ class ManualProbeHelper:
except self.printer.command_error as e:
self.finalize(False)
raise
+
def report_z_status(self, warn_no_change=False, prev_pos=None):
# Get position
kin_pos = self.get_kinematics_pos()
z_pos = kin_pos[2]
if warn_no_change and z_pos == prev_pos:
self.gcode.respond_info(
- "WARNING: No change in position (reached stepper resolution)")
+ "WARNING: No change in position (reached stepper resolution)"
+ )
# Find recent positions that were tested
pp = self.past_positions
next_pos = bisect.bisect_left(pp, z_pos)
@@ -210,29 +242,37 @@ class ManualProbeHelper:
next_pos_val = pp[next_pos]
next_str = "%.3f" % (next_pos_val,)
self.manual_probe.status = {
- 'is_active': True,
- 'z_position': z_pos,
- 'z_position_lower': prev_pos_val,
- 'z_position_upper': next_pos_val,
+ "is_active": True,
+ "z_position": z_pos,
+ "z_position_lower": prev_pos_val,
+ "z_position_upper": next_pos_val,
}
# Find recent positions
- self.gcode.respond_info("Z position: %s --> %.3f <-- %s"
- % (prev_str, z_pos, next_str))
+ self.gcode.respond_info(
+ "Z position: %s --> %.3f <-- %s" % (prev_str, z_pos, next_str)
+ )
+
cmd_ACCEPT_help = "Accept the current Z position"
+
def cmd_ACCEPT(self, gcmd):
pos = self.toolhead.get_position()
start_pos = self.start_position
if pos[:2] != start_pos[:2] or pos[2] >= start_pos[2]:
gcmd.respond_info(
"Manual probe failed! Use TESTZ commands to position the\n"
- "nozzle prior to running ACCEPT.")
+ "nozzle prior to running ACCEPT."
+ )
self.finalize(False)
return
self.finalize(True)
+
cmd_ABORT_help = "Abort manual Z probing tool"
+
def cmd_ABORT(self, gcmd):
self.finalize(False)
+
cmd_TESTZ_help = "Move to new Z height"
+
def cmd_TESTZ(self, gcmd):
# Store current position for later reference
kin_pos = self.get_kinematics_pos()
@@ -243,35 +283,37 @@ class ManualProbeHelper:
pp.insert(insert_pos, z_pos)
# Determine next position to move to
req = gcmd.get("Z")
- if req in ('+', '++'):
+ if req in ("+", "++"):
check_z = 9999999999999.9
if insert_pos < len(self.past_positions) - 1:
check_z = self.past_positions[insert_pos + 1]
- if req == '+':
- check_z = (check_z + z_pos) / 2.
+ if req == "+":
+ check_z = (check_z + z_pos) / 2.0
next_z_pos = min(check_z, z_pos + BISECT_MAX)
- elif req in ('-', '--'):
+ elif req in ("-", "--"):
check_z = -9999999999999.9
if insert_pos > 0:
check_z = self.past_positions[insert_pos - 1]
- if req == '-':
- check_z = (check_z + z_pos) / 2.
+ if req == "-":
+ check_z = (check_z + z_pos) / 2.0
next_z_pos = max(check_z, z_pos - BISECT_MAX)
else:
next_z_pos = z_pos + gcmd.get_float("Z")
# Move to given position and report it
self.move_z(next_z_pos)
self.report_z_status(next_z_pos != z_pos, z_pos)
+
def finalize(self, success):
self.manual_probe.reset_status()
- self.gcode.register_command('ACCEPT', None)
- self.gcode.register_command('NEXT', None)
- self.gcode.register_command('ABORT', None)
- self.gcode.register_command('TESTZ', None)
+ self.gcode.register_command("ACCEPT", None)
+ self.gcode.register_command("NEXT", None)
+ self.gcode.register_command("ABORT", None)
+ self.gcode.register_command("TESTZ", None)
kin_pos = None
if success:
kin_pos = self.get_kinematics_pos()
self.finalize_callback(kin_pos)
+
def load_config(config):
return ManualProbe(config)
diff --git a/klippy/extras/manual_stepper.py b/klippy/extras/manual_stepper.py
index 5a8949b9..eeba2e93 100644
--- a/klippy/extras/manual_stepper.py
+++ b/klippy/extras/manual_stepper.py
@@ -7,50 +7,58 @@ import logging
import stepper, chelper
from . import force_move
+
class ManualStepper:
def __init__(self, config):
self.printer = config.get_printer()
- if config.get('endstop_pin', None) is not None:
+ if config.get("endstop_pin", None) is not None:
self.can_home = True
self.rail = stepper.LookupRail(
- config, need_position_minmax=False, default_position_endstop=0.)
+ config, need_position_minmax=False, default_position_endstop=0.0
+ )
self.steppers = self.rail.get_steppers()
else:
self.can_home = False
self.rail = stepper.PrinterStepper(config)
self.steppers = [self.rail]
- self.velocity = config.getfloat('velocity', 5., above=0.)
- self.accel = self.homing_accel = config.getfloat('accel', 0., minval=0.)
- self.next_cmd_time = 0.
- self.pos_min = config.getfloat('position_min', None)
- self.pos_max = config.getfloat('position_max', None)
+ self.velocity = config.getfloat("velocity", 5.0, above=0.0)
+ self.accel = self.homing_accel = config.getfloat("accel", 0.0, minval=0.0)
+ self.next_cmd_time = 0.0
+ self.pos_min = config.getfloat("position_min", None)
+ self.pos_max = config.getfloat("position_max", None)
# Setup iterative solver
ffi_main, ffi_lib = chelper.get_ffi()
self.trapq = ffi_main.gc(ffi_lib.trapq_alloc(), ffi_lib.trapq_free)
self.trapq_append = ffi_lib.trapq_append
self.trapq_finalize_moves = ffi_lib.trapq_finalize_moves
- self.rail.setup_itersolve('cartesian_stepper_alloc', b'x')
+ self.rail.setup_itersolve("cartesian_stepper_alloc", b"x")
self.rail.set_trapq(self.trapq)
# Registered with toolhead as an axtra axis
self.axis_gcode_id = None
- self.instant_corner_v = 0.
- self.gaxis_limit_velocity = self.gaxis_limit_accel = 0.
+ self.instant_corner_v = 0.0
+ self.gaxis_limit_velocity = self.gaxis_limit_accel = 0.0
# Register commands
stepper_name = config.get_name().split()[1]
- gcode = self.printer.lookup_object('gcode')
- gcode.register_mux_command('MANUAL_STEPPER', "STEPPER",
- stepper_name, self.cmd_MANUAL_STEPPER,
- desc=self.cmd_MANUAL_STEPPER_help)
+ gcode = self.printer.lookup_object("gcode")
+ gcode.register_mux_command(
+ "MANUAL_STEPPER",
+ "STEPPER",
+ stepper_name,
+ self.cmd_MANUAL_STEPPER,
+ desc=self.cmd_MANUAL_STEPPER_help,
+ )
+
def sync_print_time(self):
- toolhead = self.printer.lookup_object('toolhead')
+ toolhead = self.printer.lookup_object("toolhead")
print_time = toolhead.get_last_move_time()
if self.next_cmd_time > print_time:
toolhead.dwell(self.next_cmd_time - print_time)
else:
self.next_cmd_time = print_time
+
def do_enable(self, enable):
self.sync_print_time()
- stepper_enable = self.printer.lookup_object('stepper_enable')
+ stepper_enable = self.printer.lookup_object("stepper_enable")
if enable:
for s in self.steppers:
se = stepper_enable.lookup_enable(s.get_name())
@@ -60,79 +68,103 @@ class ManualStepper:
se = stepper_enable.lookup_enable(s.get_name())
se.motor_disable(self.next_cmd_time)
self.sync_print_time()
+
def do_set_position(self, setpos):
- self.rail.set_position([setpos, 0., 0.])
+ self.rail.set_position([setpos, 0.0, 0.0])
+
def _submit_move(self, movetime, movepos, speed, accel):
cp = self.rail.get_commanded_position()
dist = movepos - cp
axis_r, accel_t, cruise_t, cruise_v = force_move.calc_move_time(
- dist, speed, accel)
- self.trapq_append(self.trapq, movetime,
- accel_t, cruise_t, accel_t,
- cp, 0., 0., axis_r, 0., 0.,
- 0., cruise_v, accel)
+ dist, speed, accel
+ )
+ self.trapq_append(
+ self.trapq,
+ movetime,
+ accel_t,
+ cruise_t,
+ accel_t,
+ cp,
+ 0.0,
+ 0.0,
+ axis_r,
+ 0.0,
+ 0.0,
+ 0.0,
+ cruise_v,
+ accel,
+ )
return movetime + accel_t + cruise_t + accel_t
+
def do_move(self, movepos, speed, accel, sync=True):
self.sync_print_time()
- self.next_cmd_time = self._submit_move(self.next_cmd_time, movepos,
- speed, accel)
+ self.next_cmd_time = self._submit_move(
+ self.next_cmd_time, movepos, speed, accel
+ )
self.rail.generate_steps(self.next_cmd_time)
- self.trapq_finalize_moves(self.trapq, self.next_cmd_time + 99999.9,
- self.next_cmd_time + 99999.9)
- toolhead = self.printer.lookup_object('toolhead')
+ self.trapq_finalize_moves(
+ self.trapq, self.next_cmd_time + 99999.9, self.next_cmd_time + 99999.9
+ )
+ toolhead = self.printer.lookup_object("toolhead")
toolhead.note_mcu_movequeue_activity(self.next_cmd_time)
if sync:
self.sync_print_time()
+
def do_homing_move(self, movepos, speed, accel, triggered, check_trigger):
if not self.can_home:
- raise self.printer.command_error(
- "No endstop for this manual stepper")
+ raise self.printer.command_error("No endstop for this manual stepper")
self.homing_accel = accel
- pos = [movepos, 0., 0., 0.]
+ pos = [movepos, 0.0, 0.0, 0.0]
endstops = self.rail.get_endstops()
- phoming = self.printer.lookup_object('homing')
- phoming.manual_home(self, endstops, pos, speed,
- triggered, check_trigger)
+ phoming = self.printer.lookup_object("homing")
+ phoming.manual_home(self, endstops, pos, speed, triggered, check_trigger)
+
cmd_MANUAL_STEPPER_help = "Command a manually configured stepper"
+
def cmd_MANUAL_STEPPER(self, gcmd):
- if gcmd.get('GCODE_AXIS', None) is not None:
+ if gcmd.get("GCODE_AXIS", None) is not None:
return self.command_with_gcode_axis(gcmd)
if self.axis_gcode_id is not None:
raise gcmd.error("Must unregister from gcode axis first")
- enable = gcmd.get_int('ENABLE', None)
+ enable = gcmd.get_int("ENABLE", None)
if enable is not None:
self.do_enable(enable)
- setpos = gcmd.get_float('SET_POSITION', None)
+ setpos = gcmd.get_float("SET_POSITION", None)
if setpos is not None:
self.do_set_position(setpos)
- speed = gcmd.get_float('SPEED', self.velocity, above=0.)
- accel = gcmd.get_float('ACCEL', self.accel, minval=0.)
- homing_move = gcmd.get_int('STOP_ON_ENDSTOP', 0)
+ speed = gcmd.get_float("SPEED", self.velocity, above=0.0)
+ accel = gcmd.get_float("ACCEL", self.accel, minval=0.0)
+ homing_move = gcmd.get_int("STOP_ON_ENDSTOP", 0)
if homing_move:
- movepos = gcmd.get_float('MOVE')
- if ((self.pos_min is not None and movepos < self.pos_min)
- or (self.pos_max is not None and movepos > self.pos_max)):
+ movepos = gcmd.get_float("MOVE")
+ if (self.pos_min is not None and movepos < self.pos_min) or (
+ self.pos_max is not None and movepos > self.pos_max
+ ):
raise gcmd.error("Move out of range")
- self.do_homing_move(movepos, speed, accel,
- homing_move > 0, abs(homing_move) == 1)
- elif gcmd.get_float('MOVE', None) is not None:
- movepos = gcmd.get_float('MOVE')
- if ((self.pos_min is not None and movepos < self.pos_min)
- or (self.pos_max is not None and movepos > self.pos_max)):
+ self.do_homing_move(
+ movepos, speed, accel, homing_move > 0, abs(homing_move) == 1
+ )
+ elif gcmd.get_float("MOVE", None) is not None:
+ movepos = gcmd.get_float("MOVE")
+ if (self.pos_min is not None and movepos < self.pos_min) or (
+ self.pos_max is not None and movepos > self.pos_max
+ ):
raise gcmd.error("Move out of range")
- sync = gcmd.get_int('SYNC', 1)
+ sync = gcmd.get_int("SYNC", 1)
self.do_move(movepos, speed, accel, sync)
- elif gcmd.get_int('SYNC', 0):
+ elif gcmd.get_int("SYNC", 0):
self.sync_print_time()
+
# Register as a gcode axis
def command_with_gcode_axis(self, gcmd):
gcode_move = self.printer.lookup_object("gcode_move")
- toolhead = self.printer.lookup_object('toolhead')
- gcode_axis = gcmd.get('GCODE_AXIS').upper()
- instant_corner_v = gcmd.get_float('INSTANTANEOUS_CORNER_VELOCITY', 1.,
- minval=0.)
- limit_velocity = gcmd.get_float('LIMIT_VELOCITY', 999999.9, above=0.)
- limit_accel = gcmd.get_float('LIMIT_ACCEL', 999999.9, above=0.)
+ toolhead = self.printer.lookup_object("toolhead")
+ gcode_axis = gcmd.get("GCODE_AXIS").upper()
+ instant_corner_v = gcmd.get_float(
+ "INSTANTANEOUS_CORNER_VELOCITY", 1.0, minval=0.0
+ )
+ limit_velocity = gcmd.get_float("LIMIT_VELOCITY", 999999.9, above=0.0)
+ limit_accel = gcmd.get_float("LIMIT_ACCEL", 999999.9, above=0.0)
if self.axis_gcode_id is not None:
if gcode_axis:
raise gcmd.error("Must unregister axis first")
@@ -141,8 +173,7 @@ class ManualStepper:
toolhead.unregister_step_generator(self.rail.generate_steps)
self.axis_gcode_id = None
return
- if (len(gcode_axis) != 1 or not gcode_axis.isupper()
- or gcode_axis in "XYZEFN"):
+ if len(gcode_axis) != 1 or not gcode_axis.isupper() or gcode_axis in "XYZEFN":
if not gcode_axis:
# Request to unregister already unregistered axis
return
@@ -156,22 +187,36 @@ class ManualStepper:
self.gaxis_limit_accel = limit_accel
toolhead.add_extra_axis(self, self.get_position()[0])
toolhead.register_step_generator(self.rail.generate_steps)
+
def process_move(self, print_time, move, ea_index):
axis_r = move.axes_r[ea_index]
start_pos = move.start_pos[ea_index]
accel = move.accel * axis_r
start_v = move.start_v * axis_r
cruise_v = move.cruise_v * axis_r
- self.trapq_append(self.trapq, print_time,
- move.accel_t, move.cruise_t, move.decel_t,
- start_pos, 0., 0.,
- 1., 0., 0.,
- start_v, cruise_v, accel)
+ self.trapq_append(
+ self.trapq,
+ print_time,
+ move.accel_t,
+ move.cruise_t,
+ move.decel_t,
+ start_pos,
+ 0.0,
+ 0.0,
+ 1.0,
+ 0.0,
+ 0.0,
+ start_v,
+ cruise_v,
+ accel,
+ )
+
def check_move(self, move, ea_index):
# Check move is in bounds
movepos = move.end_pos[ea_index]
- if ((self.pos_min is not None and movepos < self.pos_min)
- or (self.pos_max is not None and movepos > self.pos_max)):
+ if (self.pos_min is not None and movepos < self.pos_min) or (
+ self.pos_max is not None and movepos > self.pos_max
+ ):
raise move.move_error()
# Check if need to limit maximum velocity and acceleration
axis_ratio = move.move_d / abs(move.axes_d[ea_index])
@@ -180,46 +225,60 @@ class ManualStepper:
if not move.is_kinematic_move and self.accel:
limit_accel = min(limit_accel, self.accel * axis_ratio)
move.limit_speed(limit_velocity, limit_accel)
+
def calc_junction(self, prev_move, move, ea_index):
diff_r = move.axes_r[ea_index] - prev_move.axes_r[ea_index]
if diff_r:
- return (self.instant_corner_v / abs(diff_r))**2
+ return (self.instant_corner_v / abs(diff_r)) ** 2
return move.max_cruise_v2
+
def get_axis_gcode_id(self):
return self.axis_gcode_id
+
def get_trapq(self):
return self.trapq
+
# Toolhead wrappers to support homing
def flush_step_generation(self):
self.sync_print_time()
+
def get_position(self):
- return [self.rail.get_commanded_position(), 0., 0., 0.]
+ return [self.rail.get_commanded_position(), 0.0, 0.0, 0.0]
+
def set_position(self, newpos, homing_axes=""):
self.do_set_position(newpos[0])
+
def get_last_move_time(self):
self.sync_print_time()
return self.next_cmd_time
+
def dwell(self, delay):
- self.next_cmd_time += max(0., delay)
+ self.next_cmd_time += max(0.0, delay)
+
def drip_move(self, newpos, speed, drip_completion):
# Submit move to trapq
self.sync_print_time()
- maxtime = self._submit_move(self.next_cmd_time, newpos[0],
- speed, self.homing_accel)
+ maxtime = self._submit_move(
+ self.next_cmd_time, newpos[0], speed, self.homing_accel
+ )
# Drip updates to motors
- toolhead = self.printer.lookup_object('toolhead')
+ toolhead = self.printer.lookup_object("toolhead")
toolhead.drip_update_time(maxtime, drip_completion, self.steppers)
# Clear trapq of any remaining parts of movement
reactor = self.printer.get_reactor()
self.trapq_finalize_moves(self.trapq, reactor.NEVER, 0)
- self.rail.set_position([newpos[0], 0., 0.])
+ self.rail.set_position([newpos[0], 0.0, 0.0])
self.sync_print_time()
+
def get_kinematics(self):
return self
+
def get_steppers(self):
return self.steppers
+
def calc_position(self, stepper_positions):
- return [stepper_positions[self.rail.get_name()], 0., 0.]
+ return [stepper_positions[self.rail.get_name()], 0.0, 0.0]
+
def load_config_prefix(config):
return ManualStepper(config)
diff --git a/klippy/extras/mcp4018.py b/klippy/extras/mcp4018.py
index 2f0f3989..4f070d2a 100644
--- a/klippy/extras/mcp4018.py
+++ b/klippy/extras/mcp4018.py
@@ -5,32 +5,44 @@
# This file may be distributed under the terms of the GNU GPLv3 license.
from . import bus
+
class mcp4018:
def __init__(self, config):
self.printer = config.get_printer()
- self.i2c = bus.MCU_I2C_from_config(config, default_addr=0x2f)
- self.scale = config.getfloat('scale', 1., above=0.)
- self.start_value = config.getfloat('wiper',
- minval=0., maxval=self.scale)
- config.get_printer().register_event_handler("klippy:connect",
- self.handle_connect)
+ self.i2c = bus.MCU_I2C_from_config(config, default_addr=0x2F)
+ self.scale = config.getfloat("scale", 1.0, above=0.0)
+ self.start_value = config.getfloat("wiper", minval=0.0, maxval=self.scale)
+ config.get_printer().register_event_handler(
+ "klippy:connect", self.handle_connect
+ )
# Register commands
self.name = config.get_name().split()[1]
- gcode = self.printer.lookup_object('gcode')
- gcode.register_mux_command("SET_DIGIPOT", "DIGIPOT", self.name,
- self.cmd_SET_DIGIPOT,
- desc=self.cmd_SET_DIGIPOT_help)
+ gcode = self.printer.lookup_object("gcode")
+ gcode.register_mux_command(
+ "SET_DIGIPOT",
+ "DIGIPOT",
+ self.name,
+ self.cmd_SET_DIGIPOT,
+ desc=self.cmd_SET_DIGIPOT_help,
+ )
+
def handle_connect(self):
self.set_dac(self.start_value)
+
def set_dac(self, value):
- val = int(value * 127. / self.scale + .5)
+ val = int(value * 127.0 / self.scale + 0.5)
self.i2c.i2c_write([val])
+
cmd_SET_DIGIPOT_help = "Set digipot value"
+
def cmd_SET_DIGIPOT(self, gcmd):
- wiper = gcmd.get_float('WIPER', minval=0., maxval=self.scale)
+ wiper = gcmd.get_float("WIPER", minval=0.0, maxval=self.scale)
if wiper is not None:
self.set_dac(wiper)
- gcmd.respond_info("New value for DIGIPOT = %s, wiper = %.2f"
- % (self.name, wiper))
+ gcmd.respond_info(
+ "New value for DIGIPOT = %s, wiper = %.2f" % (self.name, wiper)
+ )
+
+
def load_config_prefix(config):
return mcp4018(config)
diff --git a/klippy/extras/mcp4451.py b/klippy/extras/mcp4451.py
index 12d3b6a0..ab6e43ef 100644
--- a/klippy/extras/mcp4451.py
+++ b/klippy/extras/mcp4451.py
@@ -7,24 +7,26 @@ from . import bus
WiperRegisters = [0x00, 0x01, 0x06, 0x07]
+
class mcp4451:
def __init__(self, config):
self.i2c = bus.MCU_I2C_from_config(config)
i2c_addr = self.i2c.get_i2c_address()
if i2c_addr < 44 or i2c_addr > 47:
raise config.error("mcp4451 address must be between 44 and 47")
- scale = config.getfloat('scale', 1., above=0.)
+ scale = config.getfloat("scale", 1.0, above=0.0)
# Configure registers
- self.set_register(0x04, 0xff)
- self.set_register(0x0a, 0xff)
+ self.set_register(0x04, 0xFF)
+ self.set_register(0x0A, 0xFF)
for i in range(4):
- val = config.getfloat('wiper_%d' % (i,), None,
- minval=0., maxval=scale)
+ val = config.getfloat("wiper_%d" % (i,), None, minval=0.0, maxval=scale)
if val is not None:
- val = int(val * 255. / scale + .5)
+ val = int(val * 255.0 / scale + 0.5)
self.set_register(WiperRegisters[i], val)
+
def set_register(self, reg, value):
self.i2c.i2c_write([(reg << 4) | ((value >> 8) & 0x03), value])
+
def load_config_prefix(config):
return mcp4451(config)
diff --git a/klippy/extras/mcp4728.py b/klippy/extras/mcp4728.py
index 674ab18c..800c1082 100644
--- a/klippy/extras/mcp4728.py
+++ b/klippy/extras/mcp4728.py
@@ -5,19 +5,24 @@
# This file may be distributed under the terms of the GNU GPLv3 license.
from . import bus
+
class mcp4728:
def __init__(self, config):
self.i2c = bus.MCU_I2C_from_config(config, default_addr=0x60)
- scale = config.getfloat('scale', 1., above=0.)
+ scale = config.getfloat("scale", 1.0, above=0.0)
# Configure registers
- for i, name in enumerate('abcd'):
- val = config.getfloat('channel_%s' % (name,), None,
- minval=0., maxval=scale)
+ for i, name in enumerate("abcd"):
+ val = config.getfloat(
+ "channel_%s" % (name,), None, minval=0.0, maxval=scale
+ )
if val is not None:
- self.set_dac(i, int(val * 4095. / scale + .5))
+ self.set_dac(i, int(val * 4095.0 / scale + 0.5))
+
def set_dac(self, dac, value):
- self.i2c.i2c_write([0x40 | (dac << 1),
- ((value >> 8) & 0x0f) | 0x80, value & 0xff])
+ self.i2c.i2c_write(
+ [0x40 | (dac << 1), ((value >> 8) & 0x0F) | 0x80, value & 0xFF]
+ )
+
def load_config_prefix(config):
return mcp4728(config)
diff --git a/klippy/extras/motion_report.py b/klippy/extras/motion_report.py
index c142fb39..4b4debcb 100644
--- a/klippy/extras/motion_report.py
+++ b/klippy/extras/motion_report.py
@@ -7,17 +7,19 @@ import logging
import chelper
from . import bulk_sensor
+
# Extract stepper queue_step messages
class DumpStepper:
def __init__(self, printer, mcu_stepper):
self.printer = printer
self.mcu_stepper = mcu_stepper
self.last_batch_clock = 0
- self.batch_bulk = bulk_sensor.BatchBulkHelper(printer,
- self._process_batch)
- api_resp = {'header': ('interval', 'count', 'add')}
- self.batch_bulk.add_mux_endpoint("motion_report/dump_stepper", "name",
- mcu_stepper.get_name(), api_resp)
+ self.batch_bulk = bulk_sensor.BatchBulkHelper(printer, self._process_batch)
+ api_resp = {"header": ("interval", "count", "add")}
+ self.batch_bulk.add_mux_endpoint(
+ "motion_report/dump_stepper", "name", mcu_stepper.get_name(), api_resp
+ )
+
def get_step_queue(self, start_clock, end_clock):
mcu_stepper = self.mcu_stepper
res = []
@@ -28,23 +30,31 @@ class DumpStepper:
res.append((data, count))
if count < len(data):
break
- end_clock = data[count-1].first_clock
+ end_clock = data[count - 1].first_clock
res.reverse()
- return ([d[i] for d, cnt in res for i in range(cnt-1, -1, -1)], res)
+ return ([d[i] for d, cnt in res for i in range(cnt - 1, -1, -1)], res)
+
def log_steps(self, data):
if not data:
return
out = []
- out.append("Dumping stepper '%s' (%s) %d queue_step:"
- % (self.mcu_stepper.get_name(),
- self.mcu_stepper.get_mcu().get_name(), len(data)))
+ out.append(
+ "Dumping stepper '%s' (%s) %d queue_step:"
+ % (
+ self.mcu_stepper.get_name(),
+ self.mcu_stepper.get_mcu().get_name(),
+ len(data),
+ )
+ )
for i, s in enumerate(data):
- out.append("queue_step %d: t=%d p=%d i=%d c=%d a=%d"
- % (i, s.first_clock, s.start_position, s.interval,
- s.step_count, s.add))
- logging.info('\n'.join(out))
+ out.append(
+ "queue_step %d: t=%d p=%d i=%d c=%d a=%d"
+ % (i, s.first_clock, s.start_position, s.interval, s.step_count, s.add)
+ )
+ logging.info("\n".join(out))
+
def _process_batch(self, eventtime):
- data, cdata = self.get_step_queue(self.last_batch_clock, 1<<63)
+ data, cdata = self.get_step_queue(self.last_batch_clock, 1 << 63)
if not data:
return {}
clock_to_print_time = self.mcu_stepper.get_mcu().clock_to_print_time
@@ -57,12 +67,20 @@ class DumpStepper:
start_position = self.mcu_stepper.mcu_to_commanded_position(mcu_pos)
step_dist = self.mcu_stepper.get_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}
+ 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,
+ }
+
+
+NEVER_TIME = 9999999999999999.0
-NEVER_TIME = 9999999999999999.
# Extract trapezoidal motion queue (trapq)
class DumpTrapQ:
@@ -70,57 +88,94 @@ class DumpTrapQ:
self.printer = printer
self.name = name
self.trapq = trapq
- self.last_batch_msg = (0., 0.)
- self.batch_bulk = bulk_sensor.BatchBulkHelper(printer,
- self._process_batch)
- api_resp = {'header': ('time', 'duration', 'start_velocity',
- 'acceleration', 'start_position', 'direction')}
- self.batch_bulk.add_mux_endpoint("motion_report/dump_trapq",
- "name", name, api_resp)
+ self.last_batch_msg = (0.0, 0.0)
+ self.batch_bulk = bulk_sensor.BatchBulkHelper(printer, self._process_batch)
+ api_resp = {
+ "header": (
+ "time",
+ "duration",
+ "start_velocity",
+ "acceleration",
+ "start_position",
+ "direction",
+ )
+ }
+ self.batch_bulk.add_mux_endpoint(
+ "motion_report/dump_trapq", "name", name, api_resp
+ )
+
def extract_trapq(self, start_time, end_time):
ffi_main, ffi_lib = chelper.get_ffi()
res = []
while 1:
- data = ffi_main.new('struct pull_move[128]')
- count = ffi_lib.trapq_extract_old(self.trapq, data, len(data),
- start_time, end_time)
+ data = ffi_main.new("struct pull_move[128]")
+ count = ffi_lib.trapq_extract_old(
+ self.trapq, data, len(data), start_time, end_time
+ )
if not count:
break
res.append((data, count))
if count < len(data):
break
- end_time = data[count-1].print_time
+ end_time = data[count - 1].print_time
res.reverse()
- return ([d[i] for d, cnt in res for i in range(cnt-1, -1, -1)], res)
+ return ([d[i] for d, cnt in res for i in range(cnt - 1, -1, -1)], res)
+
def log_trapq(self, data):
if not data:
return
out = ["Dumping trapq '%s' %d moves:" % (self.name, len(data))]
for i, m in enumerate(data):
- out.append("move %d: pt=%.6f mt=%.6f sv=%.6f a=%.6f"
- " sp=(%.6f,%.6f,%.6f) ar=(%.6f,%.6f,%.6f)"
- % (i, 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))
- logging.info('\n'.join(out))
+ out.append(
+ "move %d: pt=%.6f mt=%.6f sv=%.6f a=%.6f"
+ " sp=(%.6f,%.6f,%.6f) ar=(%.6f,%.6f,%.6f)"
+ % (
+ i,
+ 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,
+ )
+ )
+ logging.info("\n".join(out))
+
def get_trapq_position(self, print_time):
ffi_main, ffi_lib = chelper.get_ffi()
- data = ffi_main.new('struct pull_move[1]')
- count = ffi_lib.trapq_extract_old(self.trapq, data, 1, 0., print_time)
+ data = ffi_main.new("struct pull_move[1]")
+ count = ffi_lib.trapq_extract_old(self.trapq, data, 1, 0.0, print_time)
if not count:
return None, None
move = data[0]
- move_time = max(0., min(move.move_t, print_time - move.print_time))
- dist = (move.start_v + .5 * move.accel * move_time) * move_time;
- pos = (move.start_x + move.x_r * dist, move.start_y + move.y_r * dist,
- move.start_z + move.z_r * dist)
+ move_time = max(0.0, min(move.move_t, print_time - move.print_time))
+ dist = (move.start_v + 0.5 * move.accel * move_time) * move_time
+ pos = (
+ move.start_x + move.x_r * dist,
+ move.start_y + move.y_r * dist,
+ move.start_z + move.z_r * dist,
+ )
velocity = move.start_v + move.accel * move_time
return pos, velocity
+
def _process_batch(self, eventtime):
qtime = self.last_batch_msg[0] + min(self.last_batch_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]
+ 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_batch_msg:
d.pop(0)
if not d:
@@ -128,32 +183,38 @@ class DumpTrapQ:
self.last_batch_msg = d[-1]
return {"data": d}
+
STATUS_REFRESH_TIME = 0.250
+
class PrinterMotionReport:
def __init__(self, config):
self.printer = config.get_printer()
self.steppers = {}
self.trapqs = {}
# get_status information
- self.next_status_time = 0.
- gcode = self.printer.lookup_object('gcode')
+ self.next_status_time = 0.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.,
- 'steppers': [], 'trapq': [],
+ "live_position": gcode.Coord(0.0, 0.0, 0.0, 0.0),
+ "live_velocity": 0.0,
+ "live_extruder_velocity": 0.0,
+ "steppers": [],
+ "trapq": [],
}
# Register handlers
self.printer.register_event_handler("klippy:connect", self._connect)
self.printer.register_event_handler("klippy:shutdown", self._shutdown)
+
def register_stepper(self, config, mcu_stepper):
ds = DumpStepper(self.printer, mcu_stepper)
self.steppers[mcu_stepper.get_name()] = ds
+
def _connect(self):
# Lookup toolhead trapq
toolhead = self.printer.lookup_object("toolhead")
trapq = toolhead.get_trapq()
- self.trapqs['toolhead'] = DumpTrapQ(self.printer, 'toolhead', trapq)
+ self.trapqs["toolhead"] = DumpTrapQ(self.printer, "toolhead", trapq)
# Lookup extruder trapqs
for i in range(99):
ename = "extruder%d" % (i,)
@@ -165,8 +226,9 @@ class PrinterMotionReport:
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()))
+ 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)
@@ -186,36 +248,42 @@ class PrinterMotionReport:
return
# Log trapqs around time of shutdown
for dtrapq in self.trapqs.values():
- data, cdata = dtrapq.extract_trapq(shutdown_time - .100,
- shutdown_time + .100)
+ data, cdata = dtrapq.extract_trapq(
+ shutdown_time - 0.100, shutdown_time + 0.100
+ )
dtrapq.log_trapq(data)
# Log estimated toolhead position at time of shutdown
- dtrapq = self.trapqs.get('toolhead')
+ dtrapq = self.trapqs.get("toolhead")
if dtrapq is None:
return
pos, velocity = dtrapq.get_trapq_position(shutdown_time)
if pos is not None:
- logging.info("Requested toolhead position at shutdown time %.6f: %s"
- , shutdown_time, pos)
+ logging.info(
+ "Requested toolhead position at shutdown time %.6f: %s",
+ shutdown_time,
+ pos,
+ )
+
def _shutdown(self):
self.printer.get_reactor().register_callback(self._dump_shutdown)
+
# Status reporting
def get_status(self, eventtime):
if eventtime < self.next_status_time or not self.trapqs:
return self.last_status
self.next_status_time = eventtime + STATUS_REFRESH_TIME
- xyzpos = (0., 0., 0.)
- epos = (0.,)
- xyzvelocity = evelocity = 0.
+ xyzpos = (0.0, 0.0, 0.0)
+ epos = (0.0,)
+ xyzvelocity = evelocity = 0.0
# Calculate current requested toolhead position
- mcu = self.printer.lookup_object('mcu')
+ mcu = self.printer.lookup_object("mcu")
print_time = mcu.estimated_print_time(eventtime)
- pos, velocity = self.trapqs['toolhead'].get_trapq_position(print_time)
+ pos, velocity = self.trapqs["toolhead"].get_trapq_position(print_time)
if pos is not None:
xyzpos = pos[:3]
xyzvelocity = velocity
# Calculate requested position of currently active extruder
- toolhead = self.printer.lookup_object('toolhead')
+ toolhead = self.printer.lookup_object("toolhead")
ehandler = self.trapqs.get(toolhead.get_extruder().get_name())
if ehandler is not None:
pos, velocity = ehandler.get_trapq_position(print_time)
@@ -224,10 +292,11 @@ class PrinterMotionReport:
evelocity = velocity
# Report status
self.last_status = dict(self.last_status)
- self.last_status['live_position'] = toolhead.Coord(*(xyzpos + epos))
- self.last_status['live_velocity'] = xyzvelocity
- self.last_status['live_extruder_velocity'] = evelocity
+ self.last_status["live_position"] = toolhead.Coord(*(xyzpos + epos))
+ self.last_status["live_velocity"] = xyzvelocity
+ self.last_status["live_extruder_velocity"] = evelocity
return self.last_status
+
def load_config(config):
return PrinterMotionReport(config)
diff --git a/klippy/extras/mpu9250.py b/klippy/extras/mpu9250.py
index ff15fed4..189f1f98 100644
--- a/klippy/extras/mpu9250.py
+++ b/klippy/extras/mpu9250.py
@@ -7,7 +7,7 @@
import logging
from . import bus, adxl345, bulk_sensor
-MPU9250_ADDR = 0x68
+MPU9250_ADDR = 0x68
MPU_DEV_IDS = {
0x74: "mpu-9515",
@@ -15,38 +15,38 @@ MPU_DEV_IDS = {
0x71: "mpu-9250",
0x70: "mpu-6500",
0x68: "mpu-6050",
- #everything above are normal MPU IDs
+ # everything above are normal MPU IDs
0x75: "mpu-unknown (DEFECTIVE! USE WITH CAUTION!)",
0x69: "mpu-unknown (DEFECTIVE! USE WITH CAUTION!)",
- }
+}
# MPU9250 registers
-REG_DEVID = 0x75
-REG_FIFO_EN = 0x23
-REG_SMPLRT_DIV = 0x19
-REG_CONFIG = 0x1A
-REG_ACCEL_CONFIG = 0x1C
+REG_DEVID = 0x75
+REG_FIFO_EN = 0x23
+REG_SMPLRT_DIV = 0x19
+REG_CONFIG = 0x1A
+REG_ACCEL_CONFIG = 0x1C
REG_ACCEL_CONFIG2 = 0x1D
-REG_USER_CTRL = 0x6A
-REG_PWR_MGMT_1 = 0x6B
-REG_PWR_MGMT_2 = 0x6C
-REG_INT_STATUS = 0x3A
+REG_USER_CTRL = 0x6A
+REG_PWR_MGMT_1 = 0x6B
+REG_PWR_MGMT_2 = 0x6C
+REG_INT_STATUS = 0x3A
-SAMPLE_RATE_DIVS = { 4000:0x00 }
+SAMPLE_RATE_DIVS = {4000: 0x00}
-SET_CONFIG = 0x01 # FIFO mode 'stream' style
-SET_ACCEL_CONFIG = 0x10 # 8g full scale
-SET_ACCEL_CONFIG2 = 0x08 # 1046Hz BW, 0.503ms delay 4kHz sample rate
-SET_PWR_MGMT_1_WAKE = 0x00
-SET_PWR_MGMT_1_SLEEP= 0x40
+SET_CONFIG = 0x01 # FIFO mode 'stream' style
+SET_ACCEL_CONFIG = 0x10 # 8g full scale
+SET_ACCEL_CONFIG2 = 0x08 # 1046Hz BW, 0.503ms delay 4kHz sample rate
+SET_PWR_MGMT_1_WAKE = 0x00
+SET_PWR_MGMT_1_SLEEP = 0x40
SET_PWR_MGMT_2_ACCEL_ON = 0x07
-SET_PWR_MGMT_2_OFF = 0x3F
+SET_PWR_MGMT_2_OFF = 0x3F
SET_USER_FIFO_RESET = 0x04
-SET_USER_FIFO_EN = 0x40
-SET_ENABLE_FIFO = 0x08
+SET_USER_FIFO_EN = 0x40
+SET_ENABLE_FIFO = 0x08
SET_DISABLE_FIFO = 0x00
-FREEFALL_ACCEL = 9.80665 * 1000.
+FREEFALL_ACCEL = 9.80665 * 1000.0
# SCALE = 1/4096 g/LSB @8g scale * Earth gravity in mm/s**2
SCALE = 0.000244140625 * FREEFALL_ACCEL
@@ -54,19 +54,20 @@ FIFO_SIZE = 512
BATCH_UPDATES = 0.100
+
# Printer class that controls MPU9250 chip
class MPU9250:
def __init__(self, config):
self.printer = config.get_printer()
adxl345.AccelCommandHelper(config, self)
self.axes_map = adxl345.read_axes_map(config, SCALE, SCALE, SCALE)
- self.data_rate = config.getint('rate', 4000)
+ self.data_rate = config.getint("rate", 4000)
if self.data_rate not in SAMPLE_RATE_DIVS:
raise config.error("Invalid rate parameter: %d" % (self.data_rate,))
# Setup mcu sensor_mpu9250 bulk query code
- self.i2c = bus.MCU_I2C_from_config(config,
- default_addr=MPU9250_ADDR,
- default_speed=400000)
+ self.i2c = bus.MCU_I2C_from_config(
+ config, default_addr=MPU9250_ADDR, default_speed=400000
+ )
self.mcu = mcu = self.i2c.get_mcu()
self.oid = oid = mcu.create_oid()
self.query_mpu9250_cmd = None
@@ -77,31 +78,45 @@ class MPU9250:
self.last_error_count = 0
# Process messages in batches
self.batch_bulk = bulk_sensor.BatchBulkHelper(
- self.printer, self._process_batch,
- self._start_measurements, self._finish_measurements, BATCH_UPDATES)
+ self.printer,
+ self._process_batch,
+ self._start_measurements,
+ self._finish_measurements,
+ BATCH_UPDATES,
+ )
self.name = config.get_name().split()[-1]
- hdr = ('time', 'x_acceleration', 'y_acceleration', 'z_acceleration')
- self.batch_bulk.add_mux_endpoint("mpu9250/dump_mpu9250", "sensor",
- self.name, {'header': hdr})
+ hdr = ("time", "x_acceleration", "y_acceleration", "z_acceleration")
+ self.batch_bulk.add_mux_endpoint(
+ "mpu9250/dump_mpu9250", "sensor", self.name, {"header": hdr}
+ )
+
def _build_config(self):
cmdqueue = self.i2c.get_command_queue()
- self.mcu.add_config_cmd("config_mpu9250 oid=%d i2c_oid=%d"
- % (self.oid, self.i2c.get_oid()))
- self.mcu.add_config_cmd("query_mpu9250 oid=%d rest_ticks=0"
- % (self.oid,), on_restart=True)
+ self.mcu.add_config_cmd(
+ "config_mpu9250 oid=%d i2c_oid=%d" % (self.oid, self.i2c.get_oid())
+ )
+ self.mcu.add_config_cmd(
+ "query_mpu9250 oid=%d rest_ticks=0" % (self.oid,), on_restart=True
+ )
self.query_mpu9250_cmd = self.mcu.lookup_command(
- "query_mpu9250 oid=%c rest_ticks=%u", cq=cmdqueue)
- self.ffreader.setup_query_command("query_mpu9250_status oid=%c",
- oid=self.oid, cq=cmdqueue)
+ "query_mpu9250 oid=%c rest_ticks=%u", cq=cmdqueue
+ )
+ self.ffreader.setup_query_command(
+ "query_mpu9250_status oid=%c", oid=self.oid, cq=cmdqueue
+ )
+
def read_reg(self, reg):
params = self.i2c.i2c_read([reg], 1)
- return bytearray(params['response'])[0]
+ return bytearray(params["response"])[0]
+
def set_reg(self, reg, val, minclock=0):
self.i2c.i2c_write([reg, val & 0xFF], minclock=minclock)
+
def start_internal_client(self):
aqh = adxl345.AccelQueryHelper(self.printer)
self.batch_bulk.add_client(aqh.handle_batch)
return aqh
+
# Measurement decoding
def _convert_samples(self, samples):
(x_pos, x_scale), (y_pos, y_scale), (z_pos, z_scale) = self.axes_map
@@ -113,6 +128,7 @@ class MPU9250:
z = round(raw_xyz[z_pos] * z_scale, 6)
samples[count] = (round(ptime, 6), x, y, z)
count += 1
+
# Start, stop, and process message batches
def _start_measurements(self):
# In case of miswiring, testing MPU9250 device ID prevents treating
@@ -122,19 +138,22 @@ class MPU9250:
raise self.printer.command_error(
"Invalid mpu id (got %x).\n"
"This is generally indicative of connection problems\n"
- "(e.g. faulty wiring) or a faulty chip."
- % (dev_id))
+ "(e.g. faulty wiring) or a faulty chip." % (dev_id)
+ )
else:
- logging.info("Found %s with id %x"% (MPU_DEV_IDS[dev_id], dev_id))
+ logging.info("Found %s with id %x" % (MPU_DEV_IDS[dev_id], dev_id))
# Setup chip in requested query rate
self.set_reg(REG_PWR_MGMT_1, SET_PWR_MGMT_1_WAKE)
self.set_reg(REG_PWR_MGMT_2, SET_PWR_MGMT_2_ACCEL_ON)
# Add 20ms pause for accelerometer chip wake up
- self.read_reg(REG_DEVID) # Dummy read to ensure queues flushed
+ self.read_reg(REG_DEVID) # Dummy read to ensure queues flushed
systime = self.printer.get_reactor().monotonic()
next_time = self.mcu.estimated_print_time(systime) + 0.020
- self.set_reg(REG_SMPLRT_DIV, SAMPLE_RATE_DIVS[self.data_rate],
- minclock=self.mcu.print_time_to_clock(next_time))
+ self.set_reg(
+ REG_SMPLRT_DIV,
+ SAMPLE_RATE_DIVS[self.data_rate],
+ minclock=self.mcu.print_time_to_clock(next_time),
+ )
self.set_reg(REG_CONFIG, SET_CONFIG)
self.set_reg(REG_ACCEL_CONFIG, SET_ACCEL_CONFIG)
self.set_reg(REG_ACCEL_CONFIG2, SET_ACCEL_CONFIG2)
@@ -142,16 +161,17 @@ class MPU9250:
self.set_reg(REG_FIFO_EN, SET_DISABLE_FIFO)
self.set_reg(REG_USER_CTRL, SET_USER_FIFO_RESET)
self.set_reg(REG_USER_CTRL, SET_USER_FIFO_EN)
- self.read_reg(REG_INT_STATUS) # clear FIFO overflow flag
+ self.read_reg(REG_INT_STATUS) # clear FIFO overflow flag
# Start bulk reading
- rest_ticks = self.mcu.seconds_to_clock(4. / self.data_rate)
+ rest_ticks = self.mcu.seconds_to_clock(4.0 / self.data_rate)
self.query_mpu9250_cmd.send([self.oid, rest_ticks])
self.set_reg(REG_FIFO_EN, SET_ENABLE_FIFO)
logging.info("MPU9250 starting '%s' measurements", self.name)
# Initialize clock tracking
self.ffreader.note_start()
self.last_error_count = 0
+
def _finish_measurements(self):
# Halt bulk reading
self.set_reg(REG_FIFO_EN, SET_DISABLE_FIFO)
@@ -160,16 +180,22 @@ class MPU9250:
logging.info("MPU9250 finished '%s' measurements", self.name)
self.set_reg(REG_PWR_MGMT_1, SET_PWR_MGMT_1_SLEEP)
self.set_reg(REG_PWR_MGMT_2, SET_PWR_MGMT_2_OFF)
+
def _process_batch(self, eventtime):
samples = self.ffreader.pull_samples()
self._convert_samples(samples)
if not samples:
return {}
- return {'data': samples, 'errors': self.last_error_count,
- 'overflows': self.ffreader.get_last_overflows()}
+ return {
+ "data": samples,
+ "errors": self.last_error_count,
+ "overflows": self.ffreader.get_last_overflows(),
+ }
+
def load_config(config):
return MPU9250(config)
+
def load_config_prefix(config):
return MPU9250(config)
diff --git a/klippy/extras/multi_pin.py b/klippy/extras/multi_pin.py
index c834ee07..d8d5bcb7 100644
--- a/klippy/extras/multi_pin.py
+++ b/klippy/extras/multi_pin.py
@@ -4,21 +4,23 @@
#
# This file may be distributed under the terms of the GNU GPLv3 license.
+
class PrinterMultiPin:
def __init__(self, config):
self.printer = config.get_printer()
- ppins = self.printer.lookup_object('pins')
+ ppins = self.printer.lookup_object("pins")
try:
- ppins.register_chip('multi_pin', self)
+ ppins.register_chip("multi_pin", self)
except ppins.error:
pass
self.pin_type = None
- self.pin_list = config.getlist('pins')
+ self.pin_list = config.getlist("pins")
self.mcu_pins = []
+
def setup_pin(self, pin_type, pin_params):
- ppins = self.printer.lookup_object('pins')
- pin_name = pin_params['pin']
- pin = self.printer.lookup_object('multi_pin ' + pin_name, None)
+ ppins = self.printer.lookup_object("pins")
+ pin_name = pin_params["pin"]
+ pin = self.printer.lookup_object("multi_pin " + pin_name, None)
if pin is not self:
if pin is None:
raise ppins.error("multi_pin %s not configured" % (pin_name,))
@@ -27,28 +29,36 @@ class PrinterMultiPin:
raise ppins.error("Can't setup multi_pin %s twice" % (pin_name,))
self.pin_type = pin_type
invert = ""
- if pin_params['invert']:
+ if pin_params["invert"]:
invert = "!"
- self.mcu_pins = [ppins.setup_pin(pin_type, invert + pin_desc)
- for pin_desc in self.pin_list]
+ self.mcu_pins = [
+ ppins.setup_pin(pin_type, invert + pin_desc) for pin_desc in self.pin_list
+ ]
return self
+
def get_mcu(self):
return self.mcu_pins[0].get_mcu()
+
def setup_max_duration(self, max_duration):
for mcu_pin in self.mcu_pins:
mcu_pin.setup_max_duration(max_duration)
+
def setup_start_value(self, start_value, shutdown_value):
for mcu_pin in self.mcu_pins:
mcu_pin.setup_start_value(start_value, shutdown_value)
+
def setup_cycle_time(self, cycle_time, hardware_pwm=False):
for mcu_pin in self.mcu_pins:
mcu_pin.setup_cycle_time(cycle_time, hardware_pwm)
+
def set_digital(self, print_time, value):
for mcu_pin in self.mcu_pins:
mcu_pin.set_digital(print_time, value)
+
def set_pwm(self, print_time, value):
for mcu_pin in self.mcu_pins:
mcu_pin.set_pwm(print_time, value)
+
def load_config_prefix(config):
return PrinterMultiPin(config)
diff --git a/klippy/extras/neopixel.py b/klippy/extras/neopixel.py
index e72b8a91..1bed21b4 100644
--- a/klippy/extras/neopixel.py
+++ b/klippy/extras/neopixel.py
@@ -6,27 +6,28 @@
import logging
from . import led
-BACKGROUND_PRIORITY_CLOCK = 0x7fffffff00000000
+BACKGROUND_PRIORITY_CLOCK = 0x7FFFFFFF00000000
-BIT_MAX_TIME=.000004
-RESET_MIN_TIME=.000050
+BIT_MAX_TIME = 0.000004
+RESET_MIN_TIME = 0.000050
MAX_MCU_SIZE = 500 # Sanity check on LED chain length
+
class PrinterNeoPixel:
def __init__(self, config):
self.printer = printer = config.get_printer()
self.mutex = printer.get_reactor().mutex()
# Configure neopixel
- ppins = printer.lookup_object('pins')
- pin_params = ppins.lookup_pin(config.get('pin'))
- self.mcu = pin_params['chip']
+ ppins = printer.lookup_object("pins")
+ pin_params = ppins.lookup_pin(config.get("pin"))
+ self.mcu = pin_params["chip"]
self.oid = self.mcu.create_oid()
- self.pin = pin_params['pin']
+ self.pin = pin_params["pin"]
self.mcu.register_config_callback(self.build_config)
self.neopixel_update_cmd = self.neopixel_send_cmd = None
# Build color map
- chain_count = config.getint('chain_count', 1, minval=1)
+ chain_count = config.getint("chain_count", 1, minval=1)
color_order = config.getlist("color_order", ["GRB"])
if len(color_order) == 1:
color_order = [color_order[0]] * chain_count
@@ -43,69 +44,83 @@ class PrinterNeoPixel:
# Initialize color data
self.led_helper = led.LEDHelper(config, self.update_leds, chain_count)
self.color_data = bytearray(len(self.color_map))
- self.update_color_data(self.led_helper.get_status()['color_data'])
+ self.update_color_data(self.led_helper.get_status()["color_data"])
self.old_color_data = bytearray([d ^ 1 for d in self.color_data])
# Register callbacks
printer.register_event_handler("klippy:connect", self.send_data)
+
def build_config(self):
bmt = self.mcu.seconds_to_clock(BIT_MAX_TIME)
rmt = self.mcu.seconds_to_clock(RESET_MIN_TIME)
- self.mcu.add_config_cmd("config_neopixel oid=%d pin=%s data_size=%d"
- " bit_max_ticks=%d reset_min_ticks=%d"
- % (self.oid, self.pin, len(self.color_data),
- bmt, rmt))
+ self.mcu.add_config_cmd(
+ "config_neopixel oid=%d pin=%s data_size=%d"
+ " bit_max_ticks=%d reset_min_ticks=%d"
+ % (self.oid, self.pin, len(self.color_data), bmt, rmt)
+ )
cmd_queue = self.mcu.alloc_command_queue()
self.neopixel_update_cmd = self.mcu.lookup_command(
- "neopixel_update oid=%c pos=%hu data=%*s", cq=cmd_queue)
+ "neopixel_update oid=%c pos=%hu data=%*s", cq=cmd_queue
+ )
self.neopixel_send_cmd = self.mcu.lookup_query_command(
- "neopixel_send oid=%c", "neopixel_result oid=%c success=%c",
- oid=self.oid, cq=cmd_queue)
+ "neopixel_send oid=%c",
+ "neopixel_result oid=%c success=%c",
+ oid=self.oid,
+ cq=cmd_queue,
+ )
+
def update_color_data(self, led_state):
color_data = self.color_data
for cdidx, (lidx, cidx) in self.color_map:
- color_data[cdidx] = int(led_state[lidx][cidx] * 255. + .5)
+ color_data[cdidx] = int(led_state[lidx][cidx] * 255.0 + 0.5)
+
def send_data(self, print_time=None):
old_data, new_data = self.old_color_data, self.color_data
if new_data == old_data:
return
# Find the position of all changed bytes in this framebuffer
- diffs = [[i, 1] for i, (n, o) in enumerate(zip(new_data, old_data))
- if n != o]
+ diffs = [[i, 1] for i, (n, o) in enumerate(zip(new_data, old_data)) if n != o]
# Batch together changes that are close to each other
- for i in range(len(diffs)-2, -1, -1):
+ for i in range(len(diffs) - 2, -1, -1):
pos, count = diffs[i]
- nextpos, nextcount = diffs[i+1]
+ nextpos, nextcount = diffs[i + 1]
if pos + 5 >= nextpos and nextcount < 16:
diffs[i][1] = nextcount + (nextpos - pos)
- del diffs[i+1]
+ del diffs[i + 1]
# Transmit changes
ucmd = self.neopixel_update_cmd.send
for pos, count in diffs:
- ucmd([self.oid, pos, new_data[pos:pos+count]],
- reqclock=BACKGROUND_PRIORITY_CLOCK)
+ ucmd(
+ [self.oid, pos, new_data[pos : pos + count]],
+ reqclock=BACKGROUND_PRIORITY_CLOCK,
+ )
old_data[:] = new_data
# Instruct mcu to update the LEDs
minclock = 0
if print_time is not None:
minclock = self.mcu.print_time_to_clock(print_time)
scmd = self.neopixel_send_cmd.send
- if self.printer.get_start_args().get('debugoutput') is not None:
+ if self.printer.get_start_args().get("debugoutput") is not None:
return
for i in range(8):
- params = scmd([self.oid], minclock=minclock,
- reqclock=BACKGROUND_PRIORITY_CLOCK)
- if params['success']:
+ params = scmd(
+ [self.oid], minclock=minclock, reqclock=BACKGROUND_PRIORITY_CLOCK
+ )
+ if params["success"]:
break
else:
logging.info("Neopixel update did not succeed")
+
def update_leds(self, led_state, print_time):
def reactor_bgfunc(eventtime):
with self.mutex:
self.update_color_data(led_state)
self.send_data(print_time)
+
self.printer.get_reactor().register_callback(reactor_bgfunc)
+
def get_status(self, eventtime=None):
return self.led_helper.get_status(eventtime)
+
def load_config_prefix(config):
return PrinterNeoPixel(config)
diff --git a/klippy/extras/output_pin.py b/klippy/extras/output_pin.py
index bde7ea69..85a9e91c 100644
--- a/klippy/extras/output_pin.py
+++ b/klippy/extras/output_pin.py
@@ -11,6 +11,7 @@ from .display import display
# G-Code request queuing helper
######################################################################
+
# Helper code to queue g-code requests
class GCodeRequestQueue:
def __init__(self, config, mcu, callback):
@@ -18,12 +19,14 @@ class GCodeRequestQueue:
self.mcu = mcu
self.callback = callback
self.rqueue = []
- self.next_min_flush_time = 0.
+ self.next_min_flush_time = 0.0
self.toolhead = None
mcu.register_flush_callback(self._flush_notification)
printer.register_event_handler("klippy:connect", self._handle_connect)
+
def _handle_connect(self):
- self.toolhead = self.printer.lookup_object('toolhead')
+ self.toolhead = self.printer.lookup_object("toolhead")
+
def _flush_notification(self, print_time, clock):
min_sched_time = self.mcu.min_schedule_time()
rqueue = self.rqueue
@@ -37,26 +40,30 @@ class GCodeRequestQueue:
pos += 1
req_pt, req_val = rqueue[pos]
# Invoke callback for the request
- min_wait = 0.
+ min_wait = 0.0
ret = self.callback(next_time, req_val)
if ret is not None:
# Handle special cases
action, min_wait = ret
if action == "discard":
- del rqueue[:pos+1]
+ del rqueue[: pos + 1]
continue
if action == "delay":
pos -= 1
- del rqueue[:pos+1]
+ del rqueue[: pos + 1]
self.next_min_flush_time = next_time + max(min_wait, min_sched_time)
# Ensure following queue items are flushed
self.toolhead.note_mcu_movequeue_activity(self.next_min_flush_time)
+
def _queue_request(self, print_time, value):
self.rqueue.append((print_time, value))
self.toolhead.note_mcu_movequeue_activity(print_time)
+
def queue_gcode_request(self, value):
self.toolhead.register_lookahead_callback(
- (lambda pt: self._queue_request(pt, value)))
+ (lambda pt: self._queue_request(pt, value))
+ )
+
def send_async_request(self, value, print_time=None):
min_sched_time = self.mcu.min_schedule_time()
if print_time is None:
@@ -65,7 +72,7 @@ class GCodeRequestQueue:
while 1:
next_time = max(print_time, self.next_min_flush_time)
# Invoke callback for the request
- action, min_wait = "normal", 0.
+ action, min_wait = "normal", 0.0
ret = self.callback(next_time, value)
if ret is not None:
# Handle special cases
@@ -84,6 +91,7 @@ class GCodeRequestQueue:
# Time between each template update
RENDER_TIME = 0.500
+
# Main template evaluation code
class PrinterTemplateEvaluator:
def __init__(self, config):
@@ -95,11 +103,13 @@ class PrinterTemplateEvaluator:
self.templates = dtemplates.get_display_templates()
gcode_macro = self.printer.load_object(config, "gcode_macro")
self.create_template_context = gcode_macro.create_template_context
+
def _activate_timer(self):
if self.render_timer is not None or not self.active_templates:
return
reactor = self.printer.get_reactor()
self.render_timer = reactor.register_timer(self._render, reactor.NOW)
+
def _activate_template(self, callback, template, lparams, flush_callback):
if template is not None:
# Build a unique id to make it possible to cache duplicate rendering
@@ -109,11 +119,11 @@ class PrinterTemplateEvaluator:
except TypeError as e:
# lparams is not static, so disable caching
uid = None
- self.active_templates[callback] = (
- uid, template, lparams, flush_callback)
+ self.active_templates[callback] = (uid, template, lparams, flush_callback)
return
if callback in self.active_templates:
del self.active_templates[callback]
+
def _render(self, eventtime):
if not self.active_templates:
# Nothing to do - unregister timer
@@ -123,9 +133,11 @@ class PrinterTemplateEvaluator:
return reactor.NEVER
# Setup gcode_macro template context
context = self.create_template_context(eventtime)
+
def render(name, **kwargs):
return self.templates[name].render(context, **kwargs)
- context['render'] = render
+
+ context["render"] = render
# Render all templates
flush_callbacks = {}
render_cache = {}
@@ -143,11 +155,12 @@ class PrinterTemplateEvaluator:
if flush_callback is not None:
flush_callbacks[flush_callback] = 1
callback(text)
- context.clear() # Remove circular references for better gc
+ context.clear() # Remove circular references for better gc
# Invoke optional flush callbacks
for flush_callback in flush_callbacks.keys():
flush_callback()
return eventtime + RENDER_TIME
+
def set_template(self, gcmd, callback, flush_callback=None):
template = None
lparams = {}
@@ -162,8 +175,7 @@ class PrinterTemplateEvaluator:
continue
p = p.lower()
if p not in tparams:
- raise gcmd.error("Invalid display_template parameter: %s"
- % (p,))
+ raise gcmd.error("Invalid display_template parameter: %s" % (p,))
try:
lparams[p] = ast.literal_eval(v)
except ValueError as e:
@@ -171,6 +183,7 @@ class PrinterTemplateEvaluator:
self._activate_template(callback, template, lparams, flush_callback)
self._activate_timer()
+
def lookup_template_eval(config):
printer = config.get_printer()
te = printer.lookup_object("template_evaluator", None)
@@ -184,62 +197,71 @@ def lookup_template_eval(config):
# Main output pin handling
######################################################################
+
class PrinterOutputPin:
def __init__(self, config):
self.printer = config.get_printer()
- ppins = self.printer.lookup_object('pins')
+ ppins = self.printer.lookup_object("pins")
# Determine pin type
- self.is_pwm = config.getboolean('pwm', False)
+ self.is_pwm = config.getboolean("pwm", False)
if self.is_pwm:
- self.mcu_pin = ppins.setup_pin('pwm', config.get('pin'))
+ self.mcu_pin = ppins.setup_pin("pwm", config.get("pin"))
max_duration = self.mcu_pin.get_mcu().max_nominal_duration()
- cycle_time = config.getfloat('cycle_time', 0.100, above=0.,
- maxval=max_duration)
- hardware_pwm = config.getboolean('hardware_pwm', False)
+ cycle_time = config.getfloat(
+ "cycle_time", 0.100, above=0.0, maxval=max_duration
+ )
+ hardware_pwm = config.getboolean("hardware_pwm", False)
self.mcu_pin.setup_cycle_time(cycle_time, hardware_pwm)
- self.scale = config.getfloat('scale', 1., above=0.)
+ self.scale = config.getfloat("scale", 1.0, above=0.0)
else:
- self.mcu_pin = ppins.setup_pin('digital_out', config.get('pin'))
- self.scale = 1.
- self.mcu_pin.setup_max_duration(0.)
+ self.mcu_pin = ppins.setup_pin("digital_out", config.get("pin"))
+ self.scale = 1.0
+ self.mcu_pin.setup_max_duration(0.0)
# Determine start and shutdown values
- self.last_value = config.getfloat(
- 'value', 0., minval=0., maxval=self.scale) / self.scale
- self.shutdown_value = config.getfloat(
- 'shutdown_value', 0., minval=0., maxval=self.scale) / self.scale
+ self.last_value = (
+ config.getfloat("value", 0.0, minval=0.0, maxval=self.scale) / self.scale
+ )
+ self.shutdown_value = (
+ config.getfloat("shutdown_value", 0.0, minval=0.0, maxval=self.scale)
+ / self.scale
+ )
self.mcu_pin.setup_start_value(self.last_value, self.shutdown_value)
# Create gcode request queue
- self.gcrq = GCodeRequestQueue(config, self.mcu_pin.get_mcu(),
- self._set_pin)
+ self.gcrq = GCodeRequestQueue(config, self.mcu_pin.get_mcu(), self._set_pin)
# Template handling
self.template_eval = lookup_template_eval(config)
# Register commands
pin_name = config.get_name().split()[1]
- gcode = self.printer.lookup_object('gcode')
- gcode.register_mux_command("SET_PIN", "PIN", pin_name,
- self.cmd_SET_PIN,
- desc=self.cmd_SET_PIN_help)
+ gcode = self.printer.lookup_object("gcode")
+ gcode.register_mux_command(
+ "SET_PIN", "PIN", pin_name, self.cmd_SET_PIN, desc=self.cmd_SET_PIN_help
+ )
+
def get_status(self, eventtime):
- return {'value': self.last_value}
+ return {"value": self.last_value}
+
def _set_pin(self, print_time, value):
if value == self.last_value:
- return "discard", 0.
+ return "discard", 0.0
self.last_value = value
if self.is_pwm:
self.mcu_pin.set_pwm(print_time, value)
else:
self.mcu_pin.set_digital(print_time, value)
+
def _template_update(self, text):
try:
value = float(text)
except ValueError as e:
logging.exception("output_pin template render error")
- value = 0.
+ value = 0.0
self.gcrq.send_async_request(value)
+
cmd_SET_PIN_help = "Set the value of an output pin"
+
def cmd_SET_PIN(self, gcmd):
- value = gcmd.get_float('VALUE', None, minval=0., maxval=self.scale)
- template = gcmd.get('TEMPLATE', None)
+ value = gcmd.get_float("VALUE", None, minval=0.0, maxval=self.scale)
+ template = gcmd.get("TEMPLATE", None)
if (value is None) == (template is None):
raise gcmd.error("SET_PIN command must specify VALUE or TEMPLATE")
# Check for template setting
@@ -248,10 +270,11 @@ class PrinterOutputPin:
return
# Read requested value
value /= self.scale
- if not self.is_pwm and value not in [0., 1.]:
+ if not self.is_pwm and value not in [0.0, 1.0]:
raise gcmd.error("Invalid pin value")
# Queue requested value
self.gcrq.queue_gcode_request(value)
+
def load_config_prefix(config):
return PrinterOutputPin(config)
diff --git a/klippy/extras/palette2.py b/klippy/extras/palette2.py
index 4fe72080..1679eec3 100644
--- a/klippy/extras/palette2.py
+++ b/klippy/extras/palette2.py
@@ -23,19 +23,20 @@ COMMAND_CLEAR = [
"O10 D1 D0 D0 DFFE1",
"O10 D2 D0 D0 DFFE1",
"O10 D3 D0 D0 DFFE1",
- "O10 D4 D0 D0 D0069"]
+ "O10 D4 D0 D0 D0069",
+]
COMMAND_FILENAME = "O51"
COMMAND_FILENAMES_DONE = "O52"
COMMAND_FIRMWARE = "O50"
COMMAND_PING = "O31"
COMMAND_SMART_LOAD_STOP = "O102 D1"
-HEARTBEAT_SEND = 5.
-HEARTBEAT_TIMEOUT = (HEARTBEAT_SEND * 2.) + 1.
+HEARTBEAT_SEND = 5.0
+HEARTBEAT_TIMEOUT = (HEARTBEAT_SEND * 2.0) + 1.0
SETUP_TIMEOUT = 10
SERIAL_TIMER = 0.1
-AUTOLOAD_TIMER = 5.
+AUTOLOAD_TIMER = 5.0
INFO_NOT_CONNECTED = "Palette 2 is not connected, connect first"
@@ -45,54 +46,56 @@ class Palette2:
self.printer = config.get_printer()
self.reactor = self.printer.get_reactor()
try:
- self.virtual_sdcard = self.printer.load_object(
- config, "virtual_sdcard")
+ self.virtual_sdcard = self.printer.load_object(config, "virtual_sdcard")
except config.error:
raise self.printer.config_error(
"Palette 2 requires [virtual_sdcard] to work,"
- " please add it to your config!")
+ " please add it to your config!"
+ )
try:
- self.pause_resume = self.printer.load_object(
- config, "pause_resume")
+ self.pause_resume = self.printer.load_object(config, "pause_resume")
except config.error:
raise self.printer.config_error(
"Palette 2 requires [pause_resume] to work,"
- " please add it to your config!")
- self.gcode_move = self.printer.load_object(config, 'gcode_move')
+ " please add it to your config!"
+ )
+ self.gcode_move = self.printer.load_object(config, "gcode_move")
self.gcode = self.printer.lookup_object("gcode")
self.gcode.register_command(
- "PALETTE_CONNECT", self.cmd_Connect, desc=self.cmd_Connect_Help)
+ "PALETTE_CONNECT", self.cmd_Connect, desc=self.cmd_Connect_Help
+ )
self.gcode.register_command(
- "PALETTE_DISCONNECT",
- self.cmd_Disconnect,
- desc=self.cmd_Disconnect_Help)
+ "PALETTE_DISCONNECT", self.cmd_Disconnect, desc=self.cmd_Disconnect_Help
+ )
self.gcode.register_command(
- "PALETTE_CLEAR", self.cmd_Clear, desc=self.cmd_Clear_Help)
+ "PALETTE_CLEAR", self.cmd_Clear, desc=self.cmd_Clear_Help
+ )
+ self.gcode.register_command("PALETTE_CUT", self.cmd_Cut, desc=self.cmd_Cut_Help)
self.gcode.register_command(
- "PALETTE_CUT", self.cmd_Cut, desc=self.cmd_Cut_Help)
- self.gcode.register_command(
- "PALETTE_SMART_LOAD",
- self.cmd_Smart_Load,
- desc=self.cmd_Smart_Load_Help)
+ "PALETTE_SMART_LOAD", self.cmd_Smart_Load, desc=self.cmd_Smart_Load_Help
+ )
self.serial = None
self.serial_port = config.get("serial")
if not self.serial_port:
raise config.error("Invalid serial port specific for Palette 2")
self.baud = config.getint("baud", default=115200)
self.feedrate_splice = config.getfloat(
- "feedrate_splice", default=0.8, minval=0., maxval=1.)
+ "feedrate_splice", default=0.8, minval=0.0, maxval=1.0
+ )
self.feedrate_normal = config.getfloat(
- "feedrate_normal", default=1.0, minval=0., maxval=1.)
+ "feedrate_normal", default=1.0, minval=0.0, maxval=1.0
+ )
self.auto_load_speed = config.getint("auto_load_speed", 2)
self.auto_cancel_variation = config.getfloat(
- "auto_cancel_variation", default=None, minval=0.01, maxval=0.2)
+ "auto_cancel_variation", default=None, minval=0.01, maxval=0.2
+ )
# Omega code matchers
self.omega_header = [None] * 9
omega_handlers = ["O" + str(i) for i in range(33)]
for cmd in omega_handlers:
- func = getattr(self, 'cmd_' + cmd, None)
- desc = getattr(self, 'cmd_' + cmd + '_help', None)
+ func = getattr(self, "cmd_" + cmd, None)
+ desc = getattr(self, "cmd_" + cmd + "_help", None)
if func:
self.gcode.register_command(cmd, func, desc=desc)
else:
@@ -137,20 +140,24 @@ class Palette2:
gcmd.respond_info(INFO_NOT_CONNECTED)
return False
- cmd_Connect_Help = ("Connect to the Palette 2")
+ cmd_Connect_Help = "Connect to the Palette 2"
def cmd_Connect(self, gcmd):
if self.serial:
gcmd.respond_info(
- "Palette 2 serial port is already active, disconnect first")
+ "Palette 2 serial port is already active, disconnect first"
+ )
return
self.signal_disconnect = False
- logging.info("Connecting to Palette 2 on port (%s) at (%s)" %
- (self.serial_port, self.baud))
+ logging.info(
+ "Connecting to Palette 2 on port (%s) at (%s)"
+ % (self.serial_port, self.baud)
+ )
try:
self.serial = serial.Serial(
- self.serial_port, self.baud, timeout=0, write_timeout=0)
+ self.serial_port, self.baud, timeout=0, write_timeout=0
+ )
except SerialException:
gcmd.respond_info("Unable to connect to the Palette 2")
return
@@ -160,19 +167,20 @@ class Palette2:
with self.read_queue.mutex:
self.read_queue.queue.clear()
- self.read_timer = self.reactor.register_timer(
- self._run_Read, self.reactor.NOW)
+ self.read_timer = self.reactor.register_timer(self._run_Read, self.reactor.NOW)
self.write_timer = self.reactor.register_timer(
- self._run_Write, self.reactor.NOW)
+ self._run_Write, self.reactor.NOW
+ )
self.heartbeat_timer = self.reactor.register_timer(
- self._run_Heartbeat, self.reactor.NOW)
+ self._run_Heartbeat, self.reactor.NOW
+ )
# Tell the device we're alive
self.write_queue.put("\n")
self.write_queue.put(COMMAND_FIRMWARE)
self._wait_for_heartbeat()
- cmd_Disconnect_Help = ("Disconnect from the Palette 2")
+ cmd_Disconnect_Help = "Disconnect from the Palette 2"
def cmd_Disconnect(self, gcmd=None):
self.gcode.respond_info("Disconnecting from Palette 2")
@@ -188,7 +196,7 @@ class Palette2:
self.heartbeat = None
self.is_printing = False
- cmd_Clear_Help = ("Clear the input and output of the Palette 2")
+ cmd_Clear_Help = "Clear the input and output of the Palette 2"
def cmd_Clear(self, gcmd):
logging.info("Clearing Palette 2 input and output")
@@ -196,20 +204,19 @@ class Palette2:
for l in COMMAND_CLEAR:
self.write_queue.put(l)
- cmd_Cut_Help = ("Cut the outgoing filament")
+ cmd_Cut_Help = "Cut the outgoing filament"
def cmd_Cut(self, gcmd):
logging.info("Cutting outgoing filament in Palette 2")
if self._check_P2(gcmd):
self.write_queue.put(COMMAND_CUT)
- cmd_Smart_Load_Help = ("Automatically load filament through the extruder")
+ cmd_Smart_Load_Help = "Automatically load filament through the extruder"
def cmd_Smart_Load(self, gcmd):
if self._check_P2(gcmd):
if not self.is_loading:
- gcmd.respond_info(
- "Cannot auto load when the Palette 2 is not ready")
+ gcmd.respond_info("Cannot auto load when the Palette 2 is not ready")
return
self.p2cmd_O102(params=None)
@@ -221,33 +228,32 @@ class Palette2:
def _wait_for_heartbeat(self):
startTs = self.reactor.monotonic()
currTs = startTs
- while self.heartbeat is None or (self.heartbeat < (
- currTs - SETUP_TIMEOUT) and startTs > (
- currTs - SETUP_TIMEOUT)):
- currTs = self.reactor.pause(currTs + 1.)
+ while self.heartbeat is None or (
+ self.heartbeat < (currTs - SETUP_TIMEOUT)
+ and startTs > (currTs - SETUP_TIMEOUT)
+ ):
+ currTs = self.reactor.pause(currTs + 1.0)
if self.heartbeat < (currTs - SETUP_TIMEOUT):
self.signal_disconnect = True
- raise self.printer.command_error(
- "No response from Palette 2")
+ raise self.printer.command_error("No response from Palette 2")
- cmd_O1_help = (
- "Initialize the print, and check connection with the Palette 2")
+ cmd_O1_help = "Initialize the print, and check connection with the Palette 2"
def cmd_O1(self, gcmd):
logging.info("Initializing print with Palette 2")
if not self._check_P2(gcmd):
raise self.printer.command_error(
- "Cannot initialize print, palette 2 is not connected")
+ "Cannot initialize print, palette 2 is not connected"
+ )
self.reactor.update_timer(self.heartbeat_timer, self.reactor.NOW)
self._wait_for_heartbeat()
self.write_queue.put(gcmd.get_commandline())
- self.gcode.respond_info(
- "Palette 2 waiting on user to complete setup")
+ self.gcode.respond_info("Palette 2 waiting on user to complete setup")
self.pause_resume.send_pause_command()
- cmd_O9_help = ("Reset print information")
+ cmd_O9_help = "Reset print information"
def cmd_O9(self, gcmd):
logging.info("Print finished, resetting Palette 2 state")
@@ -305,20 +311,19 @@ class Palette2:
param_drive = gcmd.get_commandline()[5:6]
param_distance = gcmd.get_commandline()[8:]
except IndexError:
- gcmd.respond_info(
- "Incorrect number of arguments for splice command")
+ gcmd.respond_info("Incorrect number of arguments for splice command")
try:
self.omega_splices.append((int(param_drive), param_distance))
except ValueError:
gcmd.respond_info("Incorrectly formatted splice command")
- logging.debug("Omega splice command drive %s distance %s" %
- (param_drive, param_distance))
+ logging.debug(
+ "Omega splice command drive %s distance %s" % (param_drive, param_distance)
+ )
def cmd_O31(self, gcmd):
if self._check_P2(gcmd):
self.omega_current_ping = gcmd.get_commandline()
- logging.debug("Omega ping command: %s" %
- (gcmd.get_commandline()))
+ logging.debug("Omega ping command: %s" % (gcmd.get_commandline()))
self.write_queue.put(COMMAND_PING)
self.gcode.create_gcode_command("G4", "G4", {"P": "10"})
@@ -352,14 +357,11 @@ class Palette2:
self.write_queue.put("O30 D%d D%s" % (splice[0], splice[1]))
self.omega_splices_counter = self.omega_splices_counter + 1
elif n == 2:
- logging.info("Sending current ping info %s" %
- self.omega_current_ping)
+ logging.info("Sending current ping info %s" % self.omega_current_ping)
self.write_queue.put(self.omega_current_ping)
elif n == 4:
- logging.info("Sending algorithm info %s" %
- self.omega_algorithms_counter)
- self.write_queue.put(
- self.omega_algorithms[self.omega_algorithms_counter])
+ logging.info("Sending algorithm info %s" % self.omega_algorithms_counter)
+ self.write_queue.put(self.omega_algorithms[self.omega_algorithms_counter])
self.omega_algorithms_counter = self.omega_algorithms_counter + 1
elif n == 8:
logging.info("Resending the last command to Palette 2")
@@ -371,11 +373,10 @@ class Palette2:
def check_ping_variation(last_ping):
if self.auto_cancel_variation is not None:
- ping_max = 100. + (self.auto_cancel_variation * 100.)
- ping_min = 100. - (self.auto_cancel_variation * 100.)
+ ping_max = 100.0 + (self.auto_cancel_variation * 100.0)
+ ping_min = 100.0 - (self.auto_cancel_variation * 100.0)
if last_ping < ping_min or last_ping > ping_max:
- logging.info("Ping variation is too high, "
- "cancelling print")
+ logging.info("Ping variation is too high, " "cancelling print")
self.gcode.run_script("CANCEL_PRINT")
if len(params) > 2:
@@ -400,21 +401,22 @@ class Palette2:
if len(params) > 1:
try:
fw = params[0][1:]
- logging.info(
- "Palette 2 firmware version %s detected" % fw)
+ logging.info("Palette 2 firmware version %s detected" % fw)
except (TypeError, IndexError):
logging.error("Unable to parse firmware version")
if fw < "9.0.9":
raise self.printer.command_error(
- "Palette 2 firmware version is too old, "
- "update to at least 9.0.9")
+ "Palette 2 firmware version is too old, " "update to at least 9.0.9"
+ )
else:
self.files = [
- file for (
- file,
- size) in self.virtual_sdcard.get_file_list(
- check_subdirs=True) if ".mcf.gcode" in file]
+ file
+ for (file, size) in self.virtual_sdcard.get_file_list(
+ check_subdirs=True
+ )
+ if ".mcf.gcode" in file
+ ]
for file in self.files:
self.write_queue.put("%s D%s" % (COMMAND_FILENAME, file))
self.write_queue.put(COMMAND_FILENAMES_DONE)
@@ -452,8 +454,7 @@ class Palette2:
def loadingOffset(params):
self.remaining_load_length = int(params[1][1:])
- logging.debug("Loading filamant remaining %d" %
- self.remaining_load_length)
+ logging.debug("Loading filamant remaining %d" % self.remaining_load_length)
if self.remaining_load_length >= 0 and self.smart_load_timer:
logging.info("Smart load filament is complete")
self.reactor.unregister_timer(self.smart_load_timer)
@@ -461,14 +462,12 @@ class Palette2:
self.is_loading = False
def feedrateStart(params):
- logging.info("Setting feedrate to %f for splice" %
- self.feedrate_splice)
+ logging.info("Setting feedrate to %f for splice" % self.feedrate_splice)
self.is_splicing = True
self.gcode.run_script("M220 S%d" % (self.feedrate_splice * 100))
def feedrateEnd(params):
- logging.info("Setting feedrate to %f splice done" %
- self.feedrate_normal)
+ logging.info("Setting feedrate to %f splice done" % self.feedrate_normal)
self.is_splicing = False
self.gcode.run_script("M220 S%d" % (self.feedrate_normal * 100))
@@ -495,13 +494,15 @@ class Palette2:
if not toolhead.get_extruder().get_heater().can_extrude:
self.write_queue.put(COMMAND_SMART_LOAD_STOP)
self.gcode.respond_info(
- "Unable to auto load filament, extruder is below minimum temp")
+ "Unable to auto load filament, extruder is below minimum temp"
+ )
return
if self.smart_load_timer is None:
logging.info("Smart load starting")
self.smart_load_timer = self.reactor.register_timer(
- self._run_Smart_Load, self.reactor.NOW)
+ self._run_Smart_Load, self.reactor.NOW
+ )
def p2cmd(self, line):
t = line.split()
@@ -514,7 +515,7 @@ class Palette2:
logging.error("Omega parameters are invalid")
return
- func = getattr(self, 'p2cmd_' + ocode, None)
+ func = getattr(self, "p2cmd_" + ocode, None)
if func is not None:
func(params)
@@ -523,8 +524,9 @@ class Palette2:
for matcher in matchers:
if len(params) >= matcher[1]:
match_params = matcher[2:]
- res = all([match_params[i] == params[i]
- for i in range(len(match_params))])
+ res = all(
+ [match_params[i] == params[i] for i in range(len(match_params))]
+ )
if res:
matcher[0](params)
return True
@@ -544,15 +546,14 @@ class Palette2:
self.cmd_Disconnect()
return self.reactor.NEVER
if len(raw_bytes):
- new_buffer = str(raw_bytes.decode(encoding='UTF-8',
- errors='ignore'))
+ new_buffer = str(raw_bytes.decode(encoding="UTF-8", errors="ignore"))
text_buffer = self.read_buffer + new_buffer
while True:
i = text_buffer.find("\n")
if i >= 0:
- line = text_buffer[0:i + 1]
+ line = text_buffer[0 : i + 1]
self.read_queue.put(line.strip())
- text_buffer = text_buffer[i + 1:]
+ text_buffer = text_buffer[i + 1 :]
else:
break
self.read_buffer = text_buffer
@@ -582,10 +583,8 @@ class Palette2:
def _run_Heartbeat(self, eventtime):
self.write_queue.put(COMMAND_HEARTBEAT)
eventtime = self.reactor.pause(eventtime + 5)
- if self.heartbeat and self.heartbeat < (
- eventtime - HEARTBEAT_TIMEOUT):
- logging.error(
- "P2 has not responded to heartbeat")
+ if self.heartbeat and self.heartbeat < (eventtime - HEARTBEAT_TIMEOUT):
+ logging.error("P2 has not responded to heartbeat")
if not self.is_printing or self.is_setup_complete:
self.cmd_Disconnect()
return self.reactor.NEVER
@@ -602,9 +601,7 @@ class Palette2:
self.omega_last_command = text_line
l = text_line.strip()
if COMMAND_HEARTBEAT not in l:
- logging.debug(
- "%s -> P2 : %s" %
- (self.reactor.monotonic(), l))
+ logging.debug("%s -> P2 : %s" % (self.reactor.monotonic(), l))
terminated_line = "%s\n" % (l)
try:
self.serial.write(terminated_line.encode())
@@ -619,23 +616,22 @@ class Palette2:
if not self.is_splicing and self.remaining_load_length < 0:
# Make sure toolhead class isn't busy
toolhead = self.printer.lookup_object("toolhead")
- print_time, est_print_time, lookahead_empty = toolhead.check_busy(
- eventtime)
+ print_time, est_print_time, lookahead_empty = toolhead.check_busy(eventtime)
idle_time = est_print_time - print_time
if not lookahead_empty or idle_time < 0.5:
- return eventtime + \
- max(0., min(1., print_time - est_print_time))
+ return eventtime + max(0.0, min(1.0, print_time - est_print_time))
extrude = abs(self.remaining_load_length)
extrude = min(50, extrude / 2)
if extrude <= 10:
extrude = 1
- logging.info("Smart loading %dmm filament with %dmm remaining" % (
- extrude, abs(self.remaining_load_length)))
+ logging.info(
+ "Smart loading %dmm filament with %dmm remaining"
+ % (extrude, abs(self.remaining_load_length))
+ )
self.gcode.run_script("G92 E0")
- self.gcode.run_script("G1 E%d F%d" % (
- extrude, self.auto_load_speed * 60))
+ self.gcode.run_script("G1 E%d F%d" % (extrude, self.auto_load_speed * 60))
return self.reactor.NOW
return eventtime + AUTOLOAD_TIMER
@@ -643,7 +639,7 @@ class Palette2:
status = {
"ping": None,
"remaining_load_length": self.remaining_load_length,
- "is_splicing": self.is_splicing
+ "is_splicing": self.is_splicing,
}
if self.omega_pings:
status["ping"] = self.omega_pings[-1]
diff --git a/klippy/extras/pause_resume.py b/klippy/extras/pause_resume.py
index 1f6b9752..ffd63e92 100644
--- a/klippy/extras/pause_resume.py
+++ b/klippy/extras/pause_resume.py
@@ -4,46 +4,50 @@
#
# This file may be distributed under the terms of the GNU GPLv3 license.
+
class PauseResume:
def __init__(self, config):
self.printer = config.get_printer()
- self.gcode = self.printer.lookup_object('gcode')
- self.recover_velocity = config.getfloat('recover_velocity', 50.)
+ self.gcode = self.printer.lookup_object("gcode")
+ self.recover_velocity = config.getfloat("recover_velocity", 50.0)
self.v_sd = None
self.is_paused = False
self.sd_paused = False
self.pause_command_sent = False
- self.printer.register_event_handler("klippy:connect",
- self.handle_connect)
- self.gcode.register_command("PAUSE", self.cmd_PAUSE,
- desc=self.cmd_PAUSE_help)
- self.gcode.register_command("RESUME", self.cmd_RESUME,
- desc=self.cmd_RESUME_help)
- self.gcode.register_command("CLEAR_PAUSE", self.cmd_CLEAR_PAUSE,
- desc=self.cmd_CLEAR_PAUSE_help)
- self.gcode.register_command("CANCEL_PRINT", self.cmd_CANCEL_PRINT,
- desc=self.cmd_CANCEL_PRINT_help)
- webhooks = self.printer.lookup_object('webhooks')
- webhooks.register_endpoint("pause_resume/cancel",
- self._handle_cancel_request)
- webhooks.register_endpoint("pause_resume/pause",
- self._handle_pause_request)
- webhooks.register_endpoint("pause_resume/resume",
- self._handle_resume_request)
+ self.printer.register_event_handler("klippy:connect", self.handle_connect)
+ self.gcode.register_command("PAUSE", self.cmd_PAUSE, desc=self.cmd_PAUSE_help)
+ self.gcode.register_command(
+ "RESUME", self.cmd_RESUME, desc=self.cmd_RESUME_help
+ )
+ self.gcode.register_command(
+ "CLEAR_PAUSE", self.cmd_CLEAR_PAUSE, desc=self.cmd_CLEAR_PAUSE_help
+ )
+ self.gcode.register_command(
+ "CANCEL_PRINT", self.cmd_CANCEL_PRINT, desc=self.cmd_CANCEL_PRINT_help
+ )
+ webhooks = self.printer.lookup_object("webhooks")
+ webhooks.register_endpoint("pause_resume/cancel", self._handle_cancel_request)
+ webhooks.register_endpoint("pause_resume/pause", self._handle_pause_request)
+ webhooks.register_endpoint("pause_resume/resume", self._handle_resume_request)
+
def handle_connect(self):
- self.v_sd = self.printer.lookup_object('virtual_sdcard', None)
+ self.v_sd = self.printer.lookup_object("virtual_sdcard", None)
+
def _handle_cancel_request(self, web_request):
self.gcode.run_script("CANCEL_PRINT")
+
def _handle_pause_request(self, web_request):
self.gcode.run_script("PAUSE")
+
def _handle_resume_request(self, web_request):
self.gcode.run_script("RESUME")
+
def get_status(self, eventtime):
- return {
- 'is_paused': self.is_paused
- }
+ return {"is_paused": self.is_paused}
+
def is_sd_active(self):
return self.v_sd is not None and self.v_sd.is_active()
+
def send_pause_command(self):
# This sends the appropriate pause command from an event. Note
# the difference between pause_command_sent and is_paused, the
@@ -57,7 +61,9 @@ class PauseResume:
self.sd_paused = False
self.gcode.respond_info("action:paused")
self.pause_command_sent = True
- cmd_PAUSE_help = ("Pauses the current print")
+
+ cmd_PAUSE_help = "Pauses the current print"
+
def cmd_PAUSE(self, gcmd):
if self.is_paused:
gcmd.respond_info("Print already paused")
@@ -65,6 +71,7 @@ class PauseResume:
self.send_pause_command()
self.gcode.run_script_from_command("SAVE_GCODE_STATE NAME=PAUSE_STATE")
self.is_paused = True
+
def send_resume_command(self):
if self.sd_paused:
# Printing from virtual sd, run pause command
@@ -73,22 +80,27 @@ class PauseResume:
else:
self.gcode.respond_info("action:resumed")
self.pause_command_sent = False
- cmd_RESUME_help = ("Resumes the print from a pause")
+
+ cmd_RESUME_help = "Resumes the print from a pause"
+
def cmd_RESUME(self, gcmd):
if not self.is_paused:
gcmd.respond_info("Print is not paused, resume aborted")
return
- velocity = gcmd.get_float('VELOCITY', self.recover_velocity)
+ velocity = gcmd.get_float("VELOCITY", self.recover_velocity)
self.gcode.run_script_from_command(
- "RESTORE_GCODE_STATE NAME=PAUSE_STATE MOVE=1 MOVE_SPEED=%.4f"
- % (velocity))
+ "RESTORE_GCODE_STATE NAME=PAUSE_STATE MOVE=1 MOVE_SPEED=%.4f" % (velocity)
+ )
self.send_resume_command()
self.is_paused = False
- cmd_CLEAR_PAUSE_help = (
- "Clears the current paused state without resuming the print")
+
+ cmd_CLEAR_PAUSE_help = "Clears the current paused state without resuming the print"
+
def cmd_CLEAR_PAUSE(self, gcmd):
self.is_paused = self.pause_command_sent = False
- cmd_CANCEL_PRINT_help = ("Cancel the current print")
+
+ cmd_CANCEL_PRINT_help = "Cancel the current print"
+
def cmd_CANCEL_PRINT(self, gcmd):
if self.is_sd_active() or self.sd_paused:
self.v_sd.do_cancel()
@@ -96,5 +108,6 @@ class PauseResume:
gcmd.respond_info("action:cancel")
self.cmd_CLEAR_PAUSE(gcmd)
+
def load_config(config):
return PauseResume(config)
diff --git a/klippy/extras/pca9533.py b/klippy/extras/pca9533.py
index a94e1334..c56ff71c 100644
--- a/klippy/extras/pca9533.py
+++ b/klippy/extras/pca9533.py
@@ -6,11 +6,12 @@
import logging
from . import bus, led
-BACKGROUND_PRIORITY_CLOCK = 0x7fffffff00000000
+BACKGROUND_PRIORITY_CLOCK = 0x7FFFFFFF00000000
+
+PCA9533_PWM0 = 0b010
+PCA9533_PWM1 = 0b100
+PCA9533_PLS0 = 0b101
-PCA9533_PWM0=0b010
-PCA9533_PWM1=0b100
-PCA9533_PLS0=0b101
class PCA9533:
def __init__(self, config):
@@ -19,18 +20,22 @@ class PCA9533:
self.led_helper = led.LEDHelper(config, self.update_leds, 1)
self.i2c.i2c_write([PCA9533_PWM0, 85])
self.i2c.i2c_write([PCA9533_PWM1, 170])
- self.update_leds(self.led_helper.get_status()['color_data'], None)
+ self.update_leds(self.led_helper.get_status()["color_data"], None)
+
def update_leds(self, led_state, print_time):
rmap = [0, 2, 3, 1, 1]
- red, green, blue, white = [rmap[int(v * 4.)] for v in led_state[0]]
- ls0 = (white<<6) | (blue<<4) | (green<<2) | red
+ red, green, blue, white = [rmap[int(v * 4.0)] for v in led_state[0]]
+ ls0 = (white << 6) | (blue << 4) | (green << 2) | red
minclock = 0
if print_time is not None:
minclock = self.i2c.get_mcu().print_time_to_clock(print_time)
- self.i2c.i2c_write([PCA9533_PLS0, ls0], minclock=minclock,
- reqclock=BACKGROUND_PRIORITY_CLOCK)
+ self.i2c.i2c_write(
+ [PCA9533_PLS0, ls0], minclock=minclock, reqclock=BACKGROUND_PRIORITY_CLOCK
+ )
+
def get_status(self, eventtime):
return self.led_helper.get_status(eventtime)
+
def load_config_prefix(config):
return PCA9533(config)
diff --git a/klippy/extras/pca9632.py b/klippy/extras/pca9632.py
index b8a813c3..9fb414af 100644
--- a/klippy/extras/pca9632.py
+++ b/klippy/extras/pca9632.py
@@ -5,7 +5,7 @@
# This file may be distributed under the terms of the GNU GPLv3 license.
from . import bus, led
-BACKGROUND_PRIORITY_CLOCK = 0x7fffffff00000000
+BACKGROUND_PRIORITY_CLOCK = 0x7FFFFFFF00000000
# Register addresses
PCA9632_MODE1 = 0x00
@@ -22,6 +22,7 @@ PCA9632_LED1 = 0x02
PCA9632_LED2 = 0x04
PCA9632_LED3 = 0x06
+
class PCA9632:
def __init__(self, config):
self.printer = printer = config.get_printer()
@@ -33,38 +34,44 @@ class PCA9632:
self.prev_regs = {}
self.led_helper = led.LEDHelper(config, self.update_leds, 1)
printer.register_event_handler("klippy:connect", self.handle_connect)
+
def reg_write(self, reg, val, minclock=0):
if self.prev_regs.get(reg) == val:
return
self.prev_regs[reg] = val
- self.i2c.i2c_write([reg, val], minclock=minclock,
- reqclock=BACKGROUND_PRIORITY_CLOCK)
+ self.i2c.i2c_write(
+ [reg, val], minclock=minclock, reqclock=BACKGROUND_PRIORITY_CLOCK
+ )
+
def handle_connect(self):
- #Configure MODE1
+ # Configure MODE1
self.reg_write(PCA9632_MODE1, 0x00)
- #Configure MODE2 (DIMMING, INVERT, CHANGE ON STOP,TOTEM)
+ # Configure MODE2 (DIMMING, INVERT, CHANGE ON STOP,TOTEM)
self.reg_write(PCA9632_MODE2, 0x15)
- self.update_leds(self.led_helper.get_status()['color_data'], None)
+ self.update_leds(self.led_helper.get_status()["color_data"], None)
+
def update_leds(self, led_state, print_time):
minclock = 0
if print_time is not None:
minclock = self.i2c.get_mcu().print_time_to_clock(print_time)
- color = [int(v * 255. + .5) for v in led_state[0]]
+ color = [int(v * 255.0 + 0.5) for v in led_state[0]]
led0, led1, led2, led3 = [color[idx] for idx in self.color_map]
self.reg_write(PCA9632_PWM0, led0, minclock=minclock)
self.reg_write(PCA9632_PWM1, led1, minclock=minclock)
self.reg_write(PCA9632_PWM2, led2, minclock=minclock)
self.reg_write(PCA9632_PWM3, led3, minclock=minclock)
- LEDOUT = (LED_PWM << PCA9632_LED0 if led0 else 0)
- LEDOUT |= (LED_PWM << PCA9632_LED1 if led1 else 0)
- LEDOUT |= (LED_PWM << PCA9632_LED2 if led2 else 0)
- LEDOUT |= (LED_PWM << PCA9632_LED3 if led3 else 0)
+ LEDOUT = LED_PWM << PCA9632_LED0 if led0 else 0
+ LEDOUT |= LED_PWM << PCA9632_LED1 if led1 else 0
+ LEDOUT |= LED_PWM << PCA9632_LED2 if led2 else 0
+ LEDOUT |= LED_PWM << PCA9632_LED3 if led3 else 0
self.reg_write(PCA9632_LEDOUT, LEDOUT, minclock=minclock)
+
def get_status(self, eventtime):
return self.led_helper.get_status(eventtime)
+
def load_config_prefix(config):
return PCA9632(config)
diff --git a/klippy/extras/pid_calibrate.py b/klippy/extras/pid_calibrate.py
index 20641167..a29f0b71 100644
--- a/klippy/extras/pid_calibrate.py
+++ b/klippy/extras/pid_calibrate.py
@@ -6,23 +6,27 @@
import math, logging
from . import heaters
+
class PIDCalibrate:
def __init__(self, config):
self.printer = config.get_printer()
- gcode = self.printer.lookup_object('gcode')
- gcode.register_command('PID_CALIBRATE', self.cmd_PID_CALIBRATE,
- desc=self.cmd_PID_CALIBRATE_help)
+ gcode = self.printer.lookup_object("gcode")
+ gcode.register_command(
+ "PID_CALIBRATE", self.cmd_PID_CALIBRATE, desc=self.cmd_PID_CALIBRATE_help
+ )
+
cmd_PID_CALIBRATE_help = "Run PID calibration test"
+
def cmd_PID_CALIBRATE(self, gcmd):
- heater_name = gcmd.get('HEATER')
- target = gcmd.get_float('TARGET')
- write_file = gcmd.get_int('WRITE_FILE', 0)
- pheaters = self.printer.lookup_object('heaters')
+ heater_name = gcmd.get("HEATER")
+ target = gcmd.get_float("TARGET")
+ write_file = gcmd.get_int("WRITE_FILE", 0)
+ pheaters = self.printer.lookup_object("heaters")
try:
heater = pheaters.lookup_heater(heater_name)
except self.printer.config_error as e:
raise gcmd.error(str(e))
- self.printer.lookup_object('toolhead').get_last_move_time()
+ self.printer.lookup_object("toolhead").get_last_move_time()
calibrate = ControlAutoTune(heater, target)
old_control = heater.set_control(calibrate)
try:
@@ -32,8 +36,8 @@ class PIDCalibrate:
raise
heater.set_control(old_control)
if write_file:
- calibrate.write_file('/tmp/heattest.txt')
- if calibrate.check_busy(0., 0., 0.):
+ calibrate.write_file("/tmp/heattest.txt")
+ if calibrate.check_busy(0.0, 0.0, 0.0):
raise gcmd.error("pid_calibrate interrupted")
# Log and report results
Kp, Ki, Kd = calibrate.calc_final_pid()
@@ -41,17 +45,20 @@ class PIDCalibrate:
gcmd.respond_info(
"PID parameters: pid_Kp=%.3f pid_Ki=%.3f pid_Kd=%.3f\n"
"The SAVE_CONFIG command will update the printer config file\n"
- "with these parameters and restart the printer." % (Kp, Ki, Kd))
+ "with these parameters and restart the printer." % (Kp, Ki, Kd)
+ )
# Store results for SAVE_CONFIG
cfgname = heater.get_name()
- configfile = self.printer.lookup_object('configfile')
- configfile.set(cfgname, 'control', 'pid')
- configfile.set(cfgname, 'pid_Kp', "%.3f" % (Kp,))
- configfile.set(cfgname, 'pid_Ki', "%.3f" % (Ki,))
- configfile.set(cfgname, 'pid_Kd', "%.3f" % (Kd,))
+ configfile = self.printer.lookup_object("configfile")
+ configfile.set(cfgname, "control", "pid")
+ configfile.set(cfgname, "pid_Kp", "%.3f" % (Kp,))
+ configfile.set(cfgname, "pid_Ki", "%.3f" % (Ki,))
+ configfile.set(cfgname, "pid_Kd", "%.3f" % (Kd,))
+
TUNE_PID_DELTA = 5.0
+
class ControlAutoTune:
def __init__(self, heater, target):
self.heater = heater
@@ -59,21 +66,22 @@ class ControlAutoTune:
self.calibrate_temp = target
# Heating control
self.heating = False
- self.peak = 0.
- self.peak_time = 0.
+ self.peak = 0.0
+ self.peak_time = 0.0
# Peak recording
self.peaks = []
# Sample recording
- self.last_pwm = 0.
+ self.last_pwm = 0.0
self.pwm_samples = []
self.temp_samples = []
+
# Heater control
def set_pwm(self, read_time, value):
if value != self.last_pwm:
- self.pwm_samples.append(
- (read_time + self.heater.get_pwm_delay(), value))
+ self.pwm_samples.append((read_time + self.heater.get_pwm_delay(), value))
self.last_pwm = value
self.heater.set_pwm(read_time, value)
+
def temperature_update(self, read_time, temp, target_temp):
self.temp_samples.append((read_time, temp))
# Check if the temperature has crossed the target and
@@ -93,30 +101,33 @@ class ControlAutoTune:
self.peak = temp
self.peak_time = read_time
else:
- self.set_pwm(read_time, 0.)
+ self.set_pwm(read_time, 0.0)
if temp > self.peak:
self.peak = temp
self.peak_time = read_time
+
def check_busy(self, eventtime, smoothed_temp, target_temp):
if self.heating or len(self.peaks) < 12:
return True
return False
+
# Analysis
def check_peaks(self):
self.peaks.append((self.peak, self.peak_time))
if self.heating:
- self.peak = 9999999.
+ self.peak = 9999999.0
else:
- self.peak = -9999999.
+ self.peak = -9999999.0
if len(self.peaks) < 4:
return
- self.calc_pid(len(self.peaks)-1)
+ self.calc_pid(len(self.peaks) - 1)
+
def calc_pid(self, pos):
- temp_diff = self.peaks[pos][0] - self.peaks[pos-1][0]
- time_diff = self.peaks[pos][1] - self.peaks[pos-2][1]
+ temp_diff = self.peaks[pos][0] - self.peaks[pos - 1][0]
+ time_diff = self.peaks[pos][1] - self.peaks[pos - 2][1]
# Use Astrom-Hagglund method to estimate Ku and Tu
- amplitude = .5 * abs(temp_diff)
- Ku = 4. * self.heater_max_power / (math.pi * amplitude)
+ amplitude = 0.5 * abs(temp_diff)
+ Ku = 4.0 * self.heater_max_power / (math.pi * amplitude)
Tu = time_diff
# Use Ziegler-Nichols method to generate PID parameters
Ti = 0.5 * Tu
@@ -124,22 +135,34 @@ class ControlAutoTune:
Kp = 0.6 * Ku * heaters.PID_PARAM_BASE
Ki = Kp / Ti
Kd = Kp * Td
- logging.info("Autotune: raw=%f/%f Ku=%f Tu=%f Kp=%f Ki=%f Kd=%f",
- temp_diff, self.heater_max_power, Ku, Tu, Kp, Ki, Kd)
+ logging.info(
+ "Autotune: raw=%f/%f Ku=%f Tu=%f Kp=%f Ki=%f Kd=%f",
+ temp_diff,
+ self.heater_max_power,
+ Ku,
+ Tu,
+ Kp,
+ Ki,
+ Kd,
+ )
return Kp, Ki, Kd
+
def calc_final_pid(self):
- cycle_times = [(self.peaks[pos][1] - self.peaks[pos-2][1], pos)
- for pos in range(4, len(self.peaks))]
- midpoint_pos = sorted(cycle_times)[len(cycle_times)//2][1]
+ cycle_times = [
+ (self.peaks[pos][1] - self.peaks[pos - 2][1], pos)
+ for pos in range(4, len(self.peaks))
+ ]
+ midpoint_pos = sorted(cycle_times)[len(cycle_times) // 2][1]
return self.calc_pid(midpoint_pos)
+
# Offline analysis helper
def write_file(self, filename):
- pwm = ["pwm: %.3f %.3f" % (time, value)
- for time, value in self.pwm_samples]
+ pwm = ["pwm: %.3f %.3f" % (time, value) for time, value in self.pwm_samples]
out = ["%.3f %.3f" % (time, temp) for time, temp in self.temp_samples]
f = open(filename, "w")
- f.write('\n'.join(pwm + out))
+ f.write("\n".join(pwm + out))
f.close()
+
def load_config(config):
return PIDCalibrate(config)
diff --git a/klippy/extras/print_stats.py b/klippy/extras/print_stats.py
index 2eb7a01e..e7b6e97e 100644
--- a/klippy/extras/print_stats.py
+++ b/klippy/extras/print_stats.py
@@ -4,31 +4,38 @@
#
# This file may be distributed under the terms of the GNU GPLv3 license.
+
class PrintStats:
def __init__(self, config):
printer = config.get_printer()
- self.gcode_move = printer.load_object(config, 'gcode_move')
+ self.gcode_move = printer.load_object(config, "gcode_move")
self.reactor = printer.get_reactor()
self.reset()
# Register commands
- self.gcode = printer.lookup_object('gcode')
+ self.gcode = printer.lookup_object("gcode")
self.gcode.register_command(
- "SET_PRINT_STATS_INFO", self.cmd_SET_PRINT_STATS_INFO,
- desc=self.cmd_SET_PRINT_STATS_INFO_help)
- printer.register_event_handler("extruder:activate_extruder",
- self._handle_activate_extruder)
+ "SET_PRINT_STATS_INFO",
+ self.cmd_SET_PRINT_STATS_INFO,
+ desc=self.cmd_SET_PRINT_STATS_INFO_help,
+ )
+ printer.register_event_handler(
+ "extruder:activate_extruder", self._handle_activate_extruder
+ )
+
def _handle_activate_extruder(self):
gc_status = self.gcode_move.get_status()
- self.last_epos = gc_status['position'].e
+ self.last_epos = gc_status["position"].e
+
def _update_filament_usage(self, eventtime):
gc_status = self.gcode_move.get_status(eventtime)
- cur_epos = gc_status['position'].e
- self.filament_used += (cur_epos - self.last_epos) \
- / gc_status['extrude_factor']
+ cur_epos = gc_status["position"].e
+ self.filament_used += (cur_epos - self.last_epos) / gc_status["extrude_factor"]
self.last_epos = cur_epos
+
def set_current_file(self, filename):
self.reset()
self.filename = filename
+
def note_start(self):
curtime = self.reactor.monotonic()
if self.print_start_time is None:
@@ -40,9 +47,10 @@ class PrintStats:
self.last_pause_time = None
# Reset last e-position
gc_status = self.gcode_move.get_status(curtime)
- self.last_epos = gc_status['position'].e
+ self.last_epos = gc_status["position"].e
self.state = "printing"
self.error_message = ""
+
def note_pause(self):
if self.last_pause_time is None:
curtime = self.reactor.monotonic()
@@ -51,13 +59,17 @@ class PrintStats:
self._update_filament_usage(curtime)
if self.state != "error":
self.state = "paused"
+
def note_complete(self):
self._note_finish("complete")
+
def note_error(self, message):
self._note_finish("error", message)
+
def note_cancel(self):
self._note_finish("cancelled")
- def _note_finish(self, state, error_message = ""):
+
+ def _note_finish(self, state, error_message=""):
if self.print_start_time is None:
return
self.state = state
@@ -66,16 +78,16 @@ class PrintStats:
self.total_duration = eventtime - self.print_start_time
if self.filament_used < 0.0000001:
# No positive extusion detected during print
- self.init_duration = self.total_duration - \
- self.prev_pause_duration
+ self.init_duration = self.total_duration - self.prev_pause_duration
self.print_start_time = None
- cmd_SET_PRINT_STATS_INFO_help = "Pass slicer info like layer act and " \
- "total to klipper"
+
+ cmd_SET_PRINT_STATS_INFO_help = (
+ "Pass slicer info like layer act and " "total to klipper"
+ )
+
def cmd_SET_PRINT_STATS_INFO(self, gcmd):
- total_layer = gcmd.get_int("TOTAL_LAYER", self.info_total_layer, \
- minval=0)
- current_layer = gcmd.get_int("CURRENT_LAYER", self.info_current_layer, \
- minval=0)
+ total_layer = gcmd.get_int("TOTAL_LAYER", self.info_total_layer, minval=0)
+ current_layer = gcmd.get_int("CURRENT_LAYER", self.info_current_layer, minval=0)
if total_layer == 0:
self.info_total_layer = None
self.info_current_layer = None
@@ -83,19 +95,23 @@ class PrintStats:
self.info_total_layer = total_layer
self.info_current_layer = 0
- if self.info_total_layer is not None and \
- current_layer is not None and \
- current_layer != self.info_current_layer:
+ if (
+ self.info_total_layer is not None
+ and current_layer is not None
+ and current_layer != self.info_current_layer
+ ):
self.info_current_layer = min(current_layer, self.info_total_layer)
+
def reset(self):
self.filename = self.error_message = ""
self.state = "standby"
- self.prev_pause_duration = self.last_epos = 0.
- self.filament_used = self.total_duration = 0.
+ self.prev_pause_duration = self.last_epos = 0.0
+ self.filament_used = self.total_duration = 0.0
self.print_start_time = self.last_pause_time = None
- self.init_duration = 0.
+ self.init_duration = 0.0
self.info_total_layer = None
self.info_current_layer = None
+
def get_status(self, eventtime):
time_paused = self.prev_pause_duration
if self.print_start_time is not None:
@@ -111,15 +127,18 @@ class PrintStats:
self.init_duration = self.total_duration - time_paused
print_duration = self.total_duration - self.init_duration - time_paused
return {
- 'filename': self.filename,
- 'total_duration': self.total_duration,
- 'print_duration': print_duration,
- 'filament_used': self.filament_used,
- 'state': self.state,
- 'message': self.error_message,
- 'info': {'total_layer': self.info_total_layer,
- 'current_layer': self.info_current_layer}
+ "filename": self.filename,
+ "total_duration": self.total_duration,
+ "print_duration": print_duration,
+ "filament_used": self.filament_used,
+ "state": self.state,
+ "message": self.error_message,
+ "info": {
+ "total_layer": self.info_total_layer,
+ "current_layer": self.info_current_layer,
+ },
}
+
def load_config(config):
return PrintStats(config)
diff --git a/klippy/extras/probe.py b/klippy/extras/probe.py
index bfeb33ce..3811f883 100644
--- a/klippy/extras/probe.py
+++ b/klippy/extras/probe.py
@@ -13,13 +13,13 @@ consider reducing the Z axis minimum position so the probe
can travel further (the Z minimum position can be negative).
"""
+
# Calculate the average Z from a set of positions
-def calc_probe_z_average(positions, method='average'):
- if method != 'median':
+def calc_probe_z_average(positions, method="average"):
+ if method != "median":
# Use mean average
count = float(len(positions))
- return [sum([pos[i] for pos in positions]) / count
- for i in range(3)]
+ return [sum([pos[i] for pos in positions]) / count for i in range(3)]
# Use median
z_sorted = sorted(positions, key=(lambda p: p[2]))
middle = len(positions) // 2
@@ -27,13 +27,14 @@ def calc_probe_z_average(positions, method='average'):
# odd number of samples
return z_sorted[middle]
# even number of samples
- return calc_probe_z_average(z_sorted[middle-1:middle+1], 'average')
+ return calc_probe_z_average(z_sorted[middle - 1 : middle + 1], "average")
######################################################################
# Probe device implementation helpers
######################################################################
+
# Helper to implement common probing commands
class ProbeCommandHelper:
def __init__(self, config, probe, query_endstop=None):
@@ -41,57 +42,75 @@ class ProbeCommandHelper:
self.probe = probe
self.query_endstop = query_endstop
self.name = config.get_name()
- gcode = self.printer.lookup_object('gcode')
+ gcode = self.printer.lookup_object("gcode")
# QUERY_PROBE command
self.last_state = False
- gcode.register_command('QUERY_PROBE', self.cmd_QUERY_PROBE,
- desc=self.cmd_QUERY_PROBE_help)
+ gcode.register_command(
+ "QUERY_PROBE", self.cmd_QUERY_PROBE, desc=self.cmd_QUERY_PROBE_help
+ )
# PROBE command
- self.last_z_result = 0.
- gcode.register_command('PROBE', self.cmd_PROBE,
- desc=self.cmd_PROBE_help)
+ self.last_z_result = 0.0
+ gcode.register_command("PROBE", self.cmd_PROBE, desc=self.cmd_PROBE_help)
# PROBE_CALIBRATE command
- self.probe_calibrate_z = 0.
- gcode.register_command('PROBE_CALIBRATE', self.cmd_PROBE_CALIBRATE,
- desc=self.cmd_PROBE_CALIBRATE_help)
+ self.probe_calibrate_z = 0.0
+ gcode.register_command(
+ "PROBE_CALIBRATE",
+ self.cmd_PROBE_CALIBRATE,
+ desc=self.cmd_PROBE_CALIBRATE_help,
+ )
# Other commands
- gcode.register_command('PROBE_ACCURACY', self.cmd_PROBE_ACCURACY,
- desc=self.cmd_PROBE_ACCURACY_help)
- gcode.register_command('Z_OFFSET_APPLY_PROBE',
- self.cmd_Z_OFFSET_APPLY_PROBE,
- desc=self.cmd_Z_OFFSET_APPLY_PROBE_help)
+ gcode.register_command(
+ "PROBE_ACCURACY", self.cmd_PROBE_ACCURACY, desc=self.cmd_PROBE_ACCURACY_help
+ )
+ gcode.register_command(
+ "Z_OFFSET_APPLY_PROBE",
+ self.cmd_Z_OFFSET_APPLY_PROBE,
+ desc=self.cmd_Z_OFFSET_APPLY_PROBE_help,
+ )
+
def _move(self, coord, speed):
- self.printer.lookup_object('toolhead').manual_move(coord, speed)
+ self.printer.lookup_object("toolhead").manual_move(coord, speed)
+
def get_status(self, eventtime):
- return {'name': self.name,
- 'last_query': self.last_state,
- 'last_z_result': self.last_z_result}
+ return {
+ "name": self.name,
+ "last_query": self.last_state,
+ "last_z_result": self.last_z_result,
+ }
+
cmd_QUERY_PROBE_help = "Return the status of the z-probe"
+
def cmd_QUERY_PROBE(self, gcmd):
if self.query_endstop is None:
raise gcmd.error("Probe does not support QUERY_PROBE")
- toolhead = self.printer.lookup_object('toolhead')
+ toolhead = self.printer.lookup_object("toolhead")
print_time = toolhead.get_last_move_time()
res = self.query_endstop(print_time)
self.last_state = res
gcmd.respond_info("probe: %s" % (["open", "TRIGGERED"][not not res],))
+
cmd_PROBE_help = "Probe Z-height at current XY position"
+
def cmd_PROBE(self, gcmd):
pos = run_single_probe(self.probe, gcmd)
gcmd.respond_info("Result is z=%.6f" % (pos[2],))
self.last_z_result = pos[2]
+
def probe_calibrate_finalize(self, kin_pos):
if kin_pos is None:
return
z_offset = self.probe_calibrate_z - kin_pos[2]
- gcode = self.printer.lookup_object('gcode')
+ gcode = self.printer.lookup_object("gcode")
gcode.respond_info(
"%s: z_offset: %.3f\n"
"The SAVE_CONFIG command will update the printer config file\n"
- "with the above and restart the printer." % (self.name, z_offset))
- configfile = self.printer.lookup_object('configfile')
- configfile.set(self.name, 'z_offset', "%.3f" % (z_offset,))
+ "with the above and restart the printer." % (self.name, z_offset)
+ )
+ configfile = self.printer.lookup_object("configfile")
+ configfile.set(self.name, "z_offset", "%.3f" % (z_offset,))
+
cmd_PROBE_CALIBRATE_help = "Calibrate the probe's z_offset"
+
def cmd_PROBE_CALIBRATE(self, gcmd):
manual_probe.verify_no_manual_probe(self.printer)
params = self.probe.get_probe_params(gcmd)
@@ -99,32 +118,43 @@ class ProbeCommandHelper:
curpos = run_single_probe(self.probe, gcmd)
# Move away from the bed
self.probe_calibrate_z = curpos[2]
- curpos[2] += 5.
- self._move(curpos, params['lift_speed'])
+ curpos[2] += 5.0
+ self._move(curpos, params["lift_speed"])
# Move the nozzle over the probe point
x_offset, y_offset, z_offset = self.probe.get_offsets()
curpos[0] += x_offset
curpos[1] += y_offset
- self._move(curpos, params['probe_speed'])
+ self._move(curpos, params["probe_speed"])
# Start manual probe
- manual_probe.ManualProbeHelper(self.printer, gcmd,
- self.probe_calibrate_finalize)
+ manual_probe.ManualProbeHelper(
+ self.printer, gcmd, self.probe_calibrate_finalize
+ )
+
cmd_PROBE_ACCURACY_help = "Probe Z-height accuracy at current XY position"
+
def cmd_PROBE_ACCURACY(self, gcmd):
params = self.probe.get_probe_params(gcmd)
sample_count = gcmd.get_int("SAMPLES", 10, minval=1)
- toolhead = self.printer.lookup_object('toolhead')
+ toolhead = self.printer.lookup_object("toolhead")
pos = toolhead.get_position()
- gcmd.respond_info("PROBE_ACCURACY at X:%.3f Y:%.3f Z:%.3f"
- " (samples=%d retract=%.3f"
- " speed=%.1f lift_speed=%.1f)\n"
- % (pos[0], pos[1], pos[2],
- sample_count, params['sample_retract_dist'],
- params['probe_speed'], params['lift_speed']))
+ gcmd.respond_info(
+ "PROBE_ACCURACY at X:%.3f Y:%.3f Z:%.3f"
+ " (samples=%d retract=%.3f"
+ " speed=%.1f lift_speed=%.1f)\n"
+ % (
+ pos[0],
+ pos[1],
+ pos[2],
+ sample_count,
+ params["sample_retract_dist"],
+ params["probe_speed"],
+ params["lift_speed"],
+ )
+ )
# Create dummy gcmd with SAMPLES=1
fo_params = dict(gcmd.get_command_parameters())
- fo_params['SAMPLES'] = '1'
- gcode = self.printer.lookup_object('gcode')
+ fo_params["SAMPLES"] = "1"
+ gcode = self.printer.lookup_object("gcode")
fo_gcmd = gcode.create_gcode_command("", "", fo_params)
# Probe bed sample_count times
probe_session = self.probe.start_probe_session(fo_gcmd)
@@ -135,30 +165,33 @@ class ProbeCommandHelper:
probe_num += 1
# Retract
pos = toolhead.get_position()
- liftpos = [None, None, pos[2] + params['sample_retract_dist']]
- self._move(liftpos, params['lift_speed'])
+ liftpos = [None, None, pos[2] + params["sample_retract_dist"]]
+ self._move(liftpos, params["lift_speed"])
positions = probe_session.pull_probed_results()
probe_session.end_probe_session()
# Calculate maximum, minimum and average values
max_value = max([p[2] for p in positions])
min_value = min([p[2] for p in positions])
range_value = max_value - min_value
- avg_value = calc_probe_z_average(positions, 'average')[2]
- median = calc_probe_z_average(positions, 'median')[2]
+ avg_value = calc_probe_z_average(positions, "average")[2]
+ median = calc_probe_z_average(positions, "median")[2]
# calculate the standard deviation
deviation_sum = 0
for i in range(len(positions)):
- deviation_sum += pow(positions[i][2] - avg_value, 2.)
+ deviation_sum += pow(positions[i][2] - avg_value, 2.0)
sigma = (deviation_sum / len(positions)) ** 0.5
# Show information
gcmd.respond_info(
"probe accuracy results: maximum %.6f, minimum %.6f, range %.6f, "
- "average %.6f, median %.6f, standard deviation %.6f" % (
- max_value, min_value, range_value, avg_value, median, sigma))
+ "average %.6f, median %.6f, standard deviation %.6f"
+ % (max_value, min_value, range_value, avg_value, median, sigma)
+ )
+
cmd_Z_OFFSET_APPLY_PROBE_help = "Adjust the probe's z_offset"
+
def cmd_Z_OFFSET_APPLY_PROBE(self, gcmd):
gcode_move = self.printer.lookup_object("gcode_move")
- offset = gcode_move.get_status()['homing_origin'].z
+ offset = gcode_move.get_status()["homing_origin"].z
if offset == 0:
gcmd.respond_info("Nothing to do: Z Offset is 0")
return
@@ -167,32 +200,37 @@ class ProbeCommandHelper:
gcmd.respond_info(
"%s: z_offset: %.3f\n"
"The SAVE_CONFIG command will update the printer config file\n"
- "with the above and restart the printer."
- % (self.name, new_calibrate))
- configfile = self.printer.lookup_object('configfile')
- configfile.set(self.name, 'z_offset', "%.3f" % (new_calibrate,))
+ "with the above and restart the printer." % (self.name, new_calibrate)
+ )
+ configfile = self.printer.lookup_object("configfile")
+ configfile.set(self.name, "z_offset", "%.3f" % (new_calibrate,))
+
# Helper to lookup the minimum Z position for the printer
def lookup_minimum_z(config):
zconfig = manual_probe.lookup_z_endstop_config(config)
if zconfig is not None:
- return zconfig.getfloat('position_min', 0., note_valid=False)
- pconfig = config.getsection('printer')
- return pconfig.getfloat('minimum_z_position', 0., note_valid=False)
+ return zconfig.getfloat("position_min", 0.0, note_valid=False)
+ pconfig = config.getsection("printer")
+ return pconfig.getfloat("minimum_z_position", 0.0, note_valid=False)
+
# Helper to lookup all the Z axis steppers
class LookupZSteppers:
def __init__(self, config, add_stepper_cb):
self.printer = config.get_printer()
self.add_stepper_cb = add_stepper_cb
- self.printer.register_event_handler('klippy:mcu_identify',
- self._handle_mcu_identify)
+ self.printer.register_event_handler(
+ "klippy:mcu_identify", self._handle_mcu_identify
+ )
+
def _handle_mcu_identify(self):
- kin = self.printer.lookup_object('toolhead').get_kinematics()
+ kin = self.printer.lookup_object("toolhead").get_kinematics()
for stepper in kin.get_steppers():
- if stepper.is_active_axis('z'):
+ if stepper.is_active_axis("z"):
self.add_stepper_cb(stepper)
+
# Homing via probe:z_virtual_endstop
class HomingViaProbeHelper:
def __init__(self, config, mcu_probe, param_helper):
@@ -204,34 +242,44 @@ class HomingViaProbeHelper:
self.results = []
LookupZSteppers(config, self.mcu_probe.add_stepper)
# Register z_virtual_endstop pin
- self.printer.lookup_object('pins').register_chip('probe', self)
+ self.printer.lookup_object("pins").register_chip("probe", self)
# Register event handlers
- self.printer.register_event_handler("homing:homing_move_begin",
- self._handle_homing_move_begin)
- self.printer.register_event_handler("homing:homing_move_end",
- self._handle_homing_move_end)
- self.printer.register_event_handler("homing:home_rails_begin",
- self._handle_home_rails_begin)
- self.printer.register_event_handler("homing:home_rails_end",
- self._handle_home_rails_end)
- self.printer.register_event_handler("gcode:command_error",
- self._handle_command_error)
+ self.printer.register_event_handler(
+ "homing:homing_move_begin", self._handle_homing_move_begin
+ )
+ self.printer.register_event_handler(
+ "homing:homing_move_end", self._handle_homing_move_end
+ )
+ self.printer.register_event_handler(
+ "homing:home_rails_begin", self._handle_home_rails_begin
+ )
+ self.printer.register_event_handler(
+ "homing:home_rails_end", self._handle_home_rails_end
+ )
+ self.printer.register_event_handler(
+ "gcode:command_error", self._handle_command_error
+ )
+
def _handle_homing_move_begin(self, hmove):
if self.mcu_probe in hmove.get_mcu_endstops():
self.mcu_probe.probe_prepare(hmove)
+
def _handle_homing_move_end(self, hmove):
if self.mcu_probe in hmove.get_mcu_endstops():
self.mcu_probe.probe_finish(hmove)
+
def _handle_home_rails_begin(self, homing_state, rails):
endstops = [es for rail in rails for es, name in rail.get_endstops()]
if self.mcu_probe in endstops:
self.mcu_probe.multi_probe_begin()
self.multi_probe_pending = True
+
def _handle_home_rails_end(self, homing_state, rails):
endstops = [es for rail in rails for es, name in rail.get_endstops()]
if self.multi_probe_pending and self.mcu_probe in endstops:
self.multi_probe_pending = False
self.mcu_probe.multi_probe_end()
+
def _handle_command_error(self):
if self.multi_probe_pending:
self.multi_probe_pending = False
@@ -239,82 +287,96 @@ class HomingViaProbeHelper:
self.mcu_probe.multi_probe_end()
except:
logging.exception("Homing multi-probe end")
+
def setup_pin(self, pin_type, pin_params):
- if pin_type != 'endstop' or pin_params['pin'] != 'z_virtual_endstop':
+ if pin_type != "endstop" or pin_params["pin"] != "z_virtual_endstop":
raise pins.error("Probe virtual endstop only useful as endstop pin")
- if pin_params['invert'] or pin_params['pullup']:
+ if pin_params["invert"] or pin_params["pullup"]:
raise pins.error("Can not pullup/invert probe virtual endstop")
return self.mcu_probe
+
# Helper to convert probe based commands to use homing module
def start_probe_session(self, gcmd):
self.mcu_probe.multi_probe_begin()
self.results = []
return self
+
def run_probe(self, gcmd):
- toolhead = self.printer.lookup_object('toolhead')
+ toolhead = self.printer.lookup_object("toolhead")
pos = toolhead.get_position()
pos[2] = self.z_min_position
- speed = self.param_helper.get_probe_params(gcmd)['probe_speed']
- phoming = self.printer.lookup_object('homing')
+ speed = self.param_helper.get_probe_params(gcmd)["probe_speed"]
+ phoming = self.printer.lookup_object("homing")
self.results.append(phoming.probing_move(self.mcu_probe, pos, speed))
+
def pull_probed_results(self):
res = self.results
self.results = []
return res
+
def end_probe_session(self):
self.results = []
self.mcu_probe.multi_probe_end()
+
class ProbeVirtualEndstopDeprecation:
def __init__(self, config):
self._name = config.get_name()
self._printer = config.get_printer()
# Register z_virtual_endstop pin
- self._printer.lookup_object('pins').register_chip('probe', self)
+ self._printer.lookup_object("pins").register_chip("probe", self)
+
def setup_pin(self, pin_type, pin_params):
raise self._printer.config_error(
"Module [%s] does not support `probe:z_virtual_endstop`"
- ", use a pin instead." % (self._name,))
+ ", use a pin instead." % (self._name,)
+ )
+
# Helper to read multi-sample parameters from config
class ProbeParameterHelper:
def __init__(self, config):
- gcode = config.get_printer().lookup_object('gcode')
+ gcode = config.get_printer().lookup_object("gcode")
self.dummy_gcode_cmd = gcode.create_gcode_command("", "", {})
# Configurable probing speeds
- self.speed = config.getfloat('speed', 5.0, above=0.)
- self.lift_speed = config.getfloat('lift_speed', self.speed, above=0.)
+ self.speed = config.getfloat("speed", 5.0, above=0.0)
+ self.lift_speed = config.getfloat("lift_speed", self.speed, above=0.0)
# Multi-sample support (for improved accuracy)
- self.sample_count = config.getint('samples', 1, minval=1)
- self.sample_retract_dist = config.getfloat('sample_retract_dist', 2.,
- above=0.)
- atypes = ['median', 'average']
- self.samples_result = config.getchoice('samples_result', atypes,
- 'average')
- self.samples_tolerance = config.getfloat('samples_tolerance', 0.100,
- minval=0.)
- self.samples_retries = config.getint('samples_tolerance_retries', 0,
- minval=0)
+ self.sample_count = config.getint("samples", 1, minval=1)
+ self.sample_retract_dist = config.getfloat(
+ "sample_retract_dist", 2.0, above=0.0
+ )
+ atypes = ["median", "average"]
+ self.samples_result = config.getchoice("samples_result", atypes, "average")
+ self.samples_tolerance = config.getfloat("samples_tolerance", 0.100, minval=0.0)
+ self.samples_retries = config.getint("samples_tolerance_retries", 0, minval=0)
+
def get_probe_params(self, gcmd=None):
if gcmd is None:
gcmd = self.dummy_gcode_cmd
- probe_speed = gcmd.get_float("PROBE_SPEED", self.speed, above=0.)
- lift_speed = gcmd.get_float("LIFT_SPEED", self.lift_speed, above=0.)
+ probe_speed = gcmd.get_float("PROBE_SPEED", self.speed, above=0.0)
+ lift_speed = gcmd.get_float("LIFT_SPEED", self.lift_speed, above=0.0)
samples = gcmd.get_int("SAMPLES", self.sample_count, minval=1)
- sample_retract_dist = gcmd.get_float("SAMPLE_RETRACT_DIST",
- self.sample_retract_dist, above=0.)
- samples_tolerance = gcmd.get_float("SAMPLES_TOLERANCE",
- self.samples_tolerance, minval=0.)
- samples_retries = gcmd.get_int("SAMPLES_TOLERANCE_RETRIES",
- self.samples_retries, minval=0)
+ sample_retract_dist = gcmd.get_float(
+ "SAMPLE_RETRACT_DIST", self.sample_retract_dist, above=0.0
+ )
+ samples_tolerance = gcmd.get_float(
+ "SAMPLES_TOLERANCE", self.samples_tolerance, minval=0.0
+ )
+ samples_retries = gcmd.get_int(
+ "SAMPLES_TOLERANCE_RETRIES", self.samples_retries, minval=0
+ )
samples_result = gcmd.get("SAMPLES_RESULT", self.samples_result)
- return {'probe_speed': probe_speed,
- 'lift_speed': lift_speed,
- 'samples': samples,
- 'sample_retract_dist': sample_retract_dist,
- 'samples_tolerance': samples_tolerance,
- 'samples_tolerance_retries': samples_retries,
- 'samples_result': samples_result}
+ return {
+ "probe_speed": probe_speed,
+ "lift_speed": lift_speed,
+ "samples": samples,
+ "sample_retract_dist": sample_retract_dist,
+ "samples_tolerance": samples_tolerance,
+ "samples_tolerance_retries": samples_retries,
+ "samples_result": samples_result,
+ }
+
# Helper to track multiple probe attempts in a single command
class ProbeSessionHelper:
@@ -326,23 +388,29 @@ class ProbeSessionHelper:
self.hw_probe_session = None
self.results = []
# Register event handlers
- self.printer.register_event_handler("gcode:command_error",
- self._handle_command_error)
+ self.printer.register_event_handler(
+ "gcode:command_error", self._handle_command_error
+ )
+
def _handle_command_error(self):
if self.hw_probe_session is not None:
try:
self.end_probe_session()
except:
logging.exception("Multi-probe end")
+
def _probe_state_error(self):
raise self.printer.command_error(
- "Internal probe error - start/end probe session mismatch")
+ "Internal probe error - start/end probe session mismatch"
+ )
+
def start_probe_session(self, gcmd):
if self.hw_probe_session is not None:
self._probe_state_error()
self.hw_probe_session = self.start_session_cb(gcmd)
self.results = []
return self
+
def end_probe_session(self):
hw_probe_session = self.hw_probe_session
if hw_probe_session is None:
@@ -350,10 +418,11 @@ class ProbeSessionHelper:
self.results = []
self.hw_probe_session = None
hw_probe_session.end_probe_session()
+
def _probe(self, gcmd):
- toolhead = self.printer.lookup_object('toolhead')
+ toolhead = self.printer.lookup_object("toolhead")
curtime = self.printer.get_reactor().monotonic()
- if 'z' not in toolhead.get_status(curtime)['homed_axes']:
+ if "z" not in toolhead.get_status(curtime)["homed_axes"]:
raise self.printer.command_error("Must home before probe")
try:
self.hw_probe_session.run_probe(gcmd)
@@ -366,27 +435,27 @@ class ProbeSessionHelper:
# Allow axis_twist_compensation to update results
self.printer.send_event("probe:update_results", epos)
# Report results
- gcode = self.printer.lookup_object('gcode')
- gcode.respond_info("probe at %.3f,%.3f is z=%.6f"
- % (epos[0], epos[1], epos[2]))
+ gcode = self.printer.lookup_object("gcode")
+ gcode.respond_info("probe at %.3f,%.3f is z=%.6f" % (epos[0], epos[1], epos[2]))
return epos[:3]
+
def run_probe(self, gcmd):
if self.hw_probe_session is None:
self._probe_state_error()
params = self.param_helper.get_probe_params(gcmd)
- toolhead = self.printer.lookup_object('toolhead')
+ toolhead = self.printer.lookup_object("toolhead")
probexy = toolhead.get_position()[:2]
retries = 0
positions = []
- sample_count = params['samples']
+ sample_count = params["samples"]
while len(positions) < sample_count:
# Probe position
pos = self._probe(gcmd)
positions.append(pos)
# Check samples tolerance
z_positions = [p[2] for p in positions]
- if max(z_positions)-min(z_positions) > params['samples_tolerance']:
- if retries >= params['samples_tolerance_retries']:
+ if max(z_positions) - min(z_positions) > params["samples_tolerance"]:
+ if retries >= params["samples_tolerance_retries"]:
raise gcmd.error("Probe samples exceed samples_tolerance")
gcmd.respond_info("Probe samples exceed tolerance. Retrying...")
retries += 1
@@ -394,22 +463,26 @@ class ProbeSessionHelper:
# Retract
if len(positions) < sample_count:
toolhead.manual_move(
- probexy + [pos[2] + params['sample_retract_dist']],
- params['lift_speed'])
+ probexy + [pos[2] + params["sample_retract_dist"]],
+ params["lift_speed"],
+ )
# Calculate result
- epos = calc_probe_z_average(positions, params['samples_result'])
+ epos = calc_probe_z_average(positions, params["samples_result"])
self.results.append(epos)
+
def pull_probed_results(self):
res = self.results
self.results = []
return res
+
# Helper to read the xyz probe offsets from the config
class ProbeOffsetsHelper:
def __init__(self, config):
- self.x_offset = config.getfloat('x_offset', 0.)
- self.y_offset = config.getfloat('y_offset', 0.)
- self.z_offset = config.getfloat('z_offset')
+ self.x_offset = config.getfloat("x_offset", 0.0)
+ self.y_offset = config.getfloat("y_offset", 0.0)
+ self.z_offset = config.getfloat("z_offset")
+
def get_offsets(self):
return self.x_offset, self.y_offset, self.z_offset
@@ -418,6 +491,7 @@ class ProbeOffsetsHelper:
# Tools for utilizing the probe
######################################################################
+
# Helper code that can probe a series of points and report the
# position at each point.
class ProbePointsHelper:
@@ -426,45 +500,55 @@ class ProbePointsHelper:
self.finalize_callback = finalize_callback
self.probe_points = default_points
self.name = config.get_name()
- self.gcode = self.printer.lookup_object('gcode')
+ self.gcode = self.printer.lookup_object("gcode")
# Read config settings
- if default_points is None or config.get('points', None) is not None:
- self.probe_points = config.getlists('points', seps=(',', '\n'),
- parser=float, count=2)
- def_move_z = config.getfloat('horizontal_move_z', 5.)
+ if default_points is None or config.get("points", None) is not None:
+ self.probe_points = config.getlists(
+ "points", seps=(",", "\n"), parser=float, count=2
+ )
+ def_move_z = config.getfloat("horizontal_move_z", 5.0)
self.default_horizontal_move_z = def_move_z
- self.speed = config.getfloat('speed', 50., above=0.)
+ self.speed = config.getfloat("speed", 50.0, above=0.0)
self.use_offsets = False
# Internal probing state
self.lift_speed = self.speed
- self.probe_offsets = (0., 0., 0.)
+ self.probe_offsets = (0.0, 0.0, 0.0)
self.manual_results = []
- def minimum_points(self,n):
+
+ def minimum_points(self, n):
if len(self.probe_points) < n:
raise self.printer.config_error(
- "Need at least %d probe points for %s" % (n, self.name))
+ "Need at least %d probe points for %s" % (n, self.name)
+ )
+
def update_probe_points(self, points, min_points):
self.probe_points = points
self.minimum_points(min_points)
+
def use_xy_offsets(self, use_offsets):
self.use_offsets = use_offsets
+
def get_lift_speed(self):
return self.lift_speed
+
def _move(self, coord, speed):
- self.printer.lookup_object('toolhead').manual_move(coord, speed)
+ self.printer.lookup_object("toolhead").manual_move(coord, speed)
+
def _raise_tool(self, is_first=False):
speed = self.lift_speed
if is_first:
# Use full speed to first probe position
speed = self.speed
self._move([None, None, self.horizontal_move_z], speed)
+
def _invoke_callback(self, results):
# Flush lookahead queue
- toolhead = self.printer.lookup_object('toolhead')
+ toolhead = self.printer.lookup_object("toolhead")
toolhead.get_last_move_time()
# Invoke callback
res = self.finalize_callback(self.probe_offsets, results)
return res != "retry"
+
def _move_next(self, probe_num):
# Move to next XY probe point
nextpos = list(self.probe_points[probe_num])
@@ -472,27 +556,26 @@ class ProbePointsHelper:
nextpos[0] -= self.probe_offsets[0]
nextpos[1] -= self.probe_offsets[1]
self._move(nextpos, self.speed)
+
def start_probe(self, gcmd):
manual_probe.verify_no_manual_probe(self.printer)
# Lookup objects
- probe = self.printer.lookup_object('probe', None)
- method = gcmd.get('METHOD', 'automatic').lower()
+ probe = self.printer.lookup_object("probe", None)
+ method = gcmd.get("METHOD", "automatic").lower()
def_move_z = self.default_horizontal_move_z
- self.horizontal_move_z = gcmd.get_float('HORIZONTAL_MOVE_Z',
- def_move_z)
- if probe is None or method == 'manual':
+ self.horizontal_move_z = gcmd.get_float("HORIZONTAL_MOVE_Z", def_move_z)
+ if probe is None or method == "manual":
# Manual probe
self.lift_speed = self.speed
- self.probe_offsets = (0., 0., 0.)
+ self.probe_offsets = (0.0, 0.0, 0.0)
self.manual_results = []
self._manual_probe_start()
return
# Perform automatic probing
- self.lift_speed = probe.get_probe_params(gcmd)['lift_speed']
+ self.lift_speed = probe.get_probe_params(gcmd)["lift_speed"]
self.probe_offsets = probe.get_offsets()
if self.horizontal_move_z < self.probe_offsets[2]:
- raise gcmd.error("horizontal_move_z can't be less than"
- " probe's z_offset")
+ raise gcmd.error("horizontal_move_z can't be less than" " probe's z_offset")
probe_session = probe.start_probe_session(gcmd)
probe_num = 0
while 1:
@@ -508,6 +591,7 @@ class ProbePointsHelper:
probe_session.run_probe(gcmd)
probe_num += 1
probe_session.end_probe_session()
+
def _manual_probe_start(self):
self._raise_tool(not self.manual_results)
if len(self.manual_results) >= len(self.probe_points):
@@ -518,14 +602,15 @@ class ProbePointsHelper:
self.manual_results = []
self._move_next(len(self.manual_results))
gcmd = self.gcode.create_gcode_command("", "", {})
- manual_probe.ManualProbeHelper(self.printer, gcmd,
- self._manual_probe_finalize)
+ manual_probe.ManualProbeHelper(self.printer, gcmd, self._manual_probe_finalize)
+
def _manual_probe_finalize(self, kin_pos):
if kin_pos is None:
return
self.manual_results.append(kin_pos)
self._manual_probe_start()
+
# Helper to obtain a single probe measurement
def run_single_probe(probe, gcmd):
probe_session = probe.start_probe_session(gcmd)
@@ -539,21 +624,21 @@ def run_single_probe(probe, gcmd):
# Handle [probe] config
######################################################################
+
# Endstop wrapper that enables probe specific features
class ProbeEndstopWrapper:
def __init__(self, config):
self.printer = config.get_printer()
- self.position_endstop = config.getfloat('z_offset')
- self.stow_on_each_sample = config.getboolean(
- 'deactivate_on_each_sample', True)
- gcode_macro = self.printer.load_object(config, 'gcode_macro')
- self.activate_gcode = gcode_macro.load_template(
- config, 'activate_gcode', '')
+ self.position_endstop = config.getfloat("z_offset")
+ self.stow_on_each_sample = config.getboolean("deactivate_on_each_sample", True)
+ gcode_macro = self.printer.load_object(config, "gcode_macro")
+ self.activate_gcode = gcode_macro.load_template(config, "activate_gcode", "")
self.deactivate_gcode = gcode_macro.load_template(
- config, 'deactivate_gcode', '')
+ config, "deactivate_gcode", ""
+ )
# Create an "endstop" object to handle the probe pin
- ppins = self.printer.lookup_object('pins')
- self.mcu_endstop = ppins.setup_pin('endstop', config.get('pin'))
+ ppins = self.printer.lookup_object("pins")
+ self.mcu_endstop = ppins.setup_pin("endstop", config.get("pin"))
# Wrappers
self.get_mcu = self.mcu_endstop.get_mcu
self.add_stepper = self.mcu_endstop.add_stepper
@@ -562,62 +647,78 @@ class ProbeEndstopWrapper:
self.home_wait = self.mcu_endstop.home_wait
self.query_endstop = self.mcu_endstop.query_endstop
# multi probes state
- self.multi = 'OFF'
+ self.multi = "OFF"
+
def _raise_probe(self):
- toolhead = self.printer.lookup_object('toolhead')
+ toolhead = self.printer.lookup_object("toolhead")
start_pos = toolhead.get_position()
self.deactivate_gcode.run_gcode_from_command()
if toolhead.get_position()[:3] != start_pos[:3]:
raise self.printer.command_error(
- "Toolhead moved during probe deactivate_gcode script")
+ "Toolhead moved during probe deactivate_gcode script"
+ )
+
def _lower_probe(self):
- toolhead = self.printer.lookup_object('toolhead')
+ toolhead = self.printer.lookup_object("toolhead")
start_pos = toolhead.get_position()
self.activate_gcode.run_gcode_from_command()
if toolhead.get_position()[:3] != start_pos[:3]:
raise self.printer.command_error(
- "Toolhead moved during probe activate_gcode script")
+ "Toolhead moved during probe activate_gcode script"
+ )
+
def multi_probe_begin(self):
if self.stow_on_each_sample:
return
- self.multi = 'FIRST'
+ self.multi = "FIRST"
+
def multi_probe_end(self):
if self.stow_on_each_sample:
return
self._raise_probe()
- self.multi = 'OFF'
+ self.multi = "OFF"
+
def probe_prepare(self, hmove):
- if self.multi == 'OFF' or self.multi == 'FIRST':
+ if self.multi == "OFF" or self.multi == "FIRST":
self._lower_probe()
- if self.multi == 'FIRST':
- self.multi = 'ON'
+ if self.multi == "FIRST":
+ self.multi = "ON"
+
def probe_finish(self, hmove):
- if self.multi == 'OFF':
+ if self.multi == "OFF":
self._raise_probe()
+
def get_position_endstop(self):
return self.position_endstop
+
# Main external probe interface
class PrinterProbe:
def __init__(self, config):
self.printer = config.get_printer()
self.mcu_probe = ProbeEndstopWrapper(config)
- self.cmd_helper = ProbeCommandHelper(config, self,
- self.mcu_probe.query_endstop)
+ self.cmd_helper = ProbeCommandHelper(config, self, self.mcu_probe.query_endstop)
self.probe_offsets = ProbeOffsetsHelper(config)
self.param_helper = ProbeParameterHelper(config)
- self.homing_helper = HomingViaProbeHelper(config, self.mcu_probe,
- self.param_helper)
+ self.homing_helper = HomingViaProbeHelper(
+ config, self.mcu_probe, self.param_helper
+ )
self.probe_session = ProbeSessionHelper(
- config, self.param_helper, self.homing_helper.start_probe_session)
+ config, self.param_helper, self.homing_helper.start_probe_session
+ )
+
def get_probe_params(self, gcmd=None):
return self.param_helper.get_probe_params(gcmd)
+
def get_offsets(self):
return self.probe_offsets.get_offsets()
+
def get_status(self, eventtime):
return self.cmd_helper.get_status(eventtime)
+
def start_probe_session(self, gcmd):
return self.probe_session.start_probe_session(gcmd)
+
def load_config(config):
return PrinterProbe(config)
diff --git a/klippy/extras/probe_eddy_current.py b/klippy/extras/probe_eddy_current.py
index 96c76670..529b097c 100644
--- a/klippy/extras/probe_eddy_current.py
+++ b/klippy/extras/probe_eddy_current.py
@@ -9,6 +9,7 @@ from . import ldc1612, probe, manual_probe
OUT_OF_RANGE = 99.9
+
# Tool for calibrating the sensor Z detection and applying that calibration
class EddyCalibration:
def __init__(self, config):
@@ -18,25 +19,31 @@ class EddyCalibration:
# Current calibration data
self.cal_freqs = []
self.cal_zpos = []
- cal = config.get('calibrate', None)
+ cal = config.get("calibrate", None)
if cal is not None:
- cal = [list(map(float, d.strip().split(':', 1)))
- for d in cal.split(',')]
+ cal = [list(map(float, d.strip().split(":", 1))) for d in cal.split(",")]
self.load_calibration(cal)
# Probe calibrate state
- self.probe_speed = 0.
+ self.probe_speed = 0.0
# Register commands
cname = self.name.split()[-1]
- gcode = self.printer.lookup_object('gcode')
- gcode.register_mux_command("PROBE_EDDY_CURRENT_CALIBRATE", "CHIP",
- cname, self.cmd_EDDY_CALIBRATE,
- desc=self.cmd_EDDY_CALIBRATE_help)
+ gcode = self.printer.lookup_object("gcode")
+ gcode.register_mux_command(
+ "PROBE_EDDY_CURRENT_CALIBRATE",
+ "CHIP",
+ cname,
+ self.cmd_EDDY_CALIBRATE,
+ desc=self.cmd_EDDY_CALIBRATE_help,
+ )
+
def is_calibrated(self):
return len(self.cal_freqs) > 2
+
def load_calibration(self, cal):
cal = sorted([(c[1], c[0]) for c in cal])
self.cal_freqs = [c[0] for c in cal]
self.cal_zpos = [c[1] for c in cal]
+
def apply_calibration(self, samples):
cur_temp = self.drift_comp.get_temperature()
for i, (samp_time, freq, dummy_z) in enumerate(samples):
@@ -56,18 +63,19 @@ class EddyCalibration:
offset = prev_zpos - prev_freq * gain
zpos = adj_freq * gain + offset
samples[i] = (samp_time, freq, round(zpos, 6))
+
def freq_to_height(self, freq):
- dummy_sample = [(0., freq, 0.)]
+ dummy_sample = [(0.0, freq, 0.0)]
self.apply_calibration(dummy_sample)
return dummy_sample[0][2]
+
def height_to_freq(self, height):
# XXX - could optimize lookup
rev_zpos = list(reversed(self.cal_zpos))
rev_freqs = list(reversed(self.cal_freqs))
pos = bisect.bisect(rev_zpos, height)
if pos == 0 or pos >= len(rev_zpos):
- raise self.printer.command_error(
- "Invalid probe_eddy_current height")
+ raise self.printer.command_error("Invalid probe_eddy_current height")
this_freq = rev_freqs[pos]
prev_freq = rev_freqs[pos - 1]
this_zpos = rev_zpos[pos]
@@ -76,25 +84,28 @@ class EddyCalibration:
offset = prev_freq - prev_zpos * gain
freq = height * gain + offset
return self.drift_comp.unadjust_freq(freq)
+
def do_calibration_moves(self, move_speed):
- toolhead = self.printer.lookup_object('toolhead')
+ toolhead = self.printer.lookup_object("toolhead")
kin = toolhead.get_kinematics()
move = toolhead.manual_move
# Start data collection
msgs = []
is_finished = False
+
def handle_batch(msg):
if is_finished:
return False
msgs.append(msg)
return True
+
self.printer.lookup_object(self.name).add_client(handle_batch)
- toolhead.dwell(1.)
+ toolhead.dwell(1.0)
self.drift_comp.note_z_calibration_start()
# Move to each 40um position
max_z = 4.0
samp_dist = 0.040
- req_zpos = [i*samp_dist for i in range(int(max_z / samp_dist) + 1)]
+ req_zpos = [i * samp_dist for i in range(int(max_z / samp_dist) + 1)]
start_pos = toolhead.get_position()
times = []
for zpos in req_zpos:
@@ -111,8 +122,9 @@ class EddyCalibration:
toolhead.dwell(0.200)
# Find Z position based on actual commanded stepper position
toolhead.flush_step_generation()
- kin_spos = {s.get_name(): s.get_commanded_position()
- for s in kin.get_steppers()}
+ kin_spos = {
+ s.get_name(): s.get_commanded_position() for s in kin.get_steppers()
+ }
kin_pos = kin.calc_position(kin_spos)
times.append((start_query_time, end_query_time, kin_pos[2]))
toolhead.dwell(1.0)
@@ -124,7 +136,7 @@ class EddyCalibration:
cal = {}
step = 0
for msg in msgs:
- for query_time, freq, old_z in msg['data']:
+ for query_time, freq, old_z in msg["data"]:
# Add to step tracking
while step < len(times) and query_time > times[step][1]:
step += 1
@@ -132,8 +144,10 @@ class EddyCalibration:
cal.setdefault(times[step][2], []).append(freq)
if len(cal) != len(times):
raise self.printer.command_error(
- "Failed calibration - incomplete sensor data")
+ "Failed calibration - incomplete sensor data"
+ )
return cal
+
def calc_freqs(self, meas):
total_count = total_variance = 0
positions = {}
@@ -142,17 +156,18 @@ class EddyCalibration:
freq_avg = float(sum(freqs)) / count
positions[pos] = freq_avg
total_count += count
- total_variance += sum([(f - freq_avg)**2 for f in freqs])
+ total_variance += sum([(f - freq_avg) ** 2 for f in freqs])
return positions, math.sqrt(total_variance / total_count), total_count
+
def post_manual_probe(self, kin_pos):
if kin_pos is None:
# Manual Probe was aborted
return
curpos = list(kin_pos)
- move = self.printer.lookup_object('toolhead').manual_move
+ move = self.printer.lookup_object("toolhead").manual_move
# Move away from the bed
probe_calibrate_z = curpos[2]
- curpos[2] += 5.
+ curpos[2] += 5.0
move(curpos, self.probe_speed)
# Move sensor over nozzle position
pprobe = self.printer.lookup_object("probe")
@@ -161,42 +176,47 @@ class EddyCalibration:
curpos[1] -= y_offset
move(curpos, self.probe_speed)
# Descend back to bed
- curpos[2] -= 5. - 0.050
+ curpos[2] -= 5.0 - 0.050
move(curpos, self.probe_speed)
# Perform calibration movement and capture
cal = self.do_calibration_moves(self.probe_speed)
# Calculate each sample position average and variance
positions, std, total = self.calc_freqs(cal)
- last_freq = 0.
+ last_freq = 0.0
for pos, freq in reversed(sorted(positions.items())):
if freq <= last_freq:
raise self.printer.command_error(
- "Failed calibration - frequency not increasing each step")
+ "Failed calibration - frequency not increasing each step"
+ )
last_freq = freq
gcode = self.printer.lookup_object("gcode")
gcode.respond_info(
"probe_eddy_current: stddev=%.3f in %d queries\n"
"The SAVE_CONFIG command will update the printer config file\n"
- "and restart the printer." % (std, total))
+ "and restart the printer." % (std, total)
+ )
# Save results
cal_contents = []
for i, (pos, freq) in enumerate(sorted(positions.items())):
if not i % 3:
- cal_contents.append('\n')
+ cal_contents.append("\n")
cal_contents.append("%.6f:%.3f" % (pos - probe_calibrate_z, freq))
- cal_contents.append(',')
+ cal_contents.append(",")
cal_contents.pop()
- configfile = self.printer.lookup_object('configfile')
- configfile.set(self.name, 'calibrate', ''.join(cal_contents))
+ configfile = self.printer.lookup_object("configfile")
+ configfile.set(self.name, "calibrate", "".join(cal_contents))
+
cmd_EDDY_CALIBRATE_help = "Calibrate eddy current probe"
+
def cmd_EDDY_CALIBRATE(self, gcmd):
- self.probe_speed = gcmd.get_float("PROBE_SPEED", 5., above=0.)
+ self.probe_speed = gcmd.get_float("PROBE_SPEED", 5.0, above=0.0)
# Start manual probe
- manual_probe.ManualProbeHelper(self.printer, gcmd,
- self.post_manual_probe)
+ manual_probe.ManualProbeHelper(self.printer, gcmd, self.post_manual_probe)
+
def register_drift_compensation(self, comp):
self.drift_comp = comp
+
# Tool to gather samples and convert them to probe positions
class EddyGatherSamples:
def __init__(self, printer, sensor_helper, calibration, z_offset):
@@ -211,9 +231,9 @@ class EddyGatherSamples:
self._need_stop = False
# Start samples
if not self._calibration.is_calibrated():
- raise self._printer.command_error(
- "Must calibrate probe_eddy_current first")
+ raise self._printer.command_error("Must calibrate probe_eddy_current first")
sensor_helper.add_client(self._add_measurement)
+
def _add_measurement(self, msg):
if self._need_stop:
del self._samples[:]
@@ -221,8 +241,10 @@ class EddyGatherSamples:
self._samples.append(msg)
self._check_samples()
return True
+
def finish(self):
self._need_stop = True
+
def _await_samples(self):
# Make sure enough samples have been collected
reactor = self._printer.get_reactor()
@@ -232,18 +254,18 @@ class EddyGatherSamples:
systime = reactor.monotonic()
est_print_time = mcu.estimated_print_time(systime)
if est_print_time > end_time + 1.0:
- raise self._printer.command_error(
- "probe_eddy_current sensor outage")
+ raise self._printer.command_error("probe_eddy_current sensor outage")
reactor.pause(systime + 0.010)
+
def _pull_freq(self, start_time, end_time):
# Find average sensor frequency between time range
msg_num = discard_msgs = 0
- samp_sum = 0.
+ samp_sum = 0.0
samp_count = 0
while msg_num < len(self._samples):
msg = self._samples[msg_num]
msg_num += 1
- data = msg['data']
+ data = msg["data"]
if data[0][0] > end_time:
break
if data[-1][0] < start_time:
@@ -256,19 +278,22 @@ class EddyGatherSamples:
del self._samples[:discard_msgs]
if not samp_count:
# No sensor readings - raise error in pull_probed()
- return 0.
+ return 0.0
return samp_sum / samp_count
+
def _lookup_toolhead_pos(self, pos_time):
- toolhead = self._printer.lookup_object('toolhead')
+ toolhead = self._printer.lookup_object("toolhead")
kin = toolhead.get_kinematics()
- kin_spos = {s.get_name(): s.mcu_to_commanded_position(
- s.get_past_mcu_position(pos_time))
- for s in kin.get_steppers()}
+ kin_spos = {
+ s.get_name(): s.mcu_to_commanded_position(s.get_past_mcu_position(pos_time))
+ for s in kin.get_steppers()
+ }
return kin.calc_position(kin_spos)
+
def _check_samples(self):
while self._samples and self._probe_times:
start_time, end_time, pos_time, toolhead_pos = self._probe_times[0]
- if self._samples[-1]['data'][-1][0] < end_time:
+ if self._samples[-1]["data"][-1][0] < end_time:
break
freq = self._pull_freq(start_time, end_time)
if pos_time is not None:
@@ -278,32 +303,39 @@ class EddyGatherSamples:
sensor_z = self._calibration.freq_to_height(freq)
self._probe_results.append((sensor_z, toolhead_pos))
self._probe_times.pop(0)
+
def pull_probed(self):
self._await_samples()
results = []
for sensor_z, toolhead_pos in self._probe_results:
if sensor_z is None:
raise self._printer.command_error(
- "Unable to obtain probe_eddy_current sensor readings")
+ "Unable to obtain probe_eddy_current sensor readings"
+ )
if sensor_z <= -OUT_OF_RANGE or sensor_z >= OUT_OF_RANGE:
raise self._printer.command_error(
- "probe_eddy_current sensor not in valid range")
+ "probe_eddy_current sensor not in valid range"
+ )
# Callers expect position relative to z_offset, so recalculate
bed_deviation = toolhead_pos[2] - sensor_z
toolhead_pos[2] = self._z_offset + bed_deviation
results.append(toolhead_pos)
del self._probe_results[:]
return results
+
def note_probe(self, start_time, end_time, toolhead_pos):
self._probe_times.append((start_time, end_time, None, toolhead_pos))
self._check_samples()
+
def note_probe_and_position(self, start_time, end_time, pos_time):
self._probe_times.append((start_time, end_time, pos_time, None))
self._check_samples()
+
# Helper for implementing PROBE style commands (descend until trigger)
class EddyDescend:
REASON_SENSOR_ERROR = mcu.MCU_trsync.REASON_COMMS_TIMEOUT + 1
+
def __init__(self, config, sensor_helper, calibration, param_helper):
self._printer = config.get_printer()
self._sensor_helper = sensor_helper
@@ -311,50 +343,60 @@ class EddyDescend:
self._calibration = calibration
self._param_helper = param_helper
self._z_min_position = probe.lookup_minimum_z(config)
- self._z_offset = config.getfloat('z_offset', minval=0.)
+ self._z_offset = config.getfloat("z_offset", minval=0.0)
self._dispatch = mcu.TriggerDispatch(self._mcu)
- self._trigger_time = 0.
+ self._trigger_time = 0.0
self._gather = None
probe.LookupZSteppers(config, self._dispatch.add_stepper)
+
# Interface for phoming.probing_move()
def get_steppers(self):
return self._dispatch.get_steppers()
- def home_start(self, print_time, sample_time, sample_count, rest_time,
- triggered=True):
- self._trigger_time = 0.
+
+ def home_start(
+ self, print_time, sample_time, sample_count, rest_time, triggered=True
+ ):
+ self._trigger_time = 0.0
trigger_freq = self._calibration.height_to_freq(self._z_offset)
trigger_completion = self._dispatch.start(print_time)
self._sensor_helper.setup_home(
- print_time, trigger_freq, self._dispatch.get_oid(),
- mcu.MCU_trsync.REASON_ENDSTOP_HIT, self.REASON_SENSOR_ERROR)
+ print_time,
+ trigger_freq,
+ self._dispatch.get_oid(),
+ mcu.MCU_trsync.REASON_ENDSTOP_HIT,
+ self.REASON_SENSOR_ERROR,
+ )
return trigger_completion
+
def home_wait(self, home_end_time):
self._dispatch.wait_end(home_end_time)
trigger_time = self._sensor_helper.clear_home()
res = self._dispatch.stop()
if res >= mcu.MCU_trsync.REASON_COMMS_TIMEOUT:
if res == mcu.MCU_trsync.REASON_COMMS_TIMEOUT:
- raise self._printer.command_error(
- "Communication timeout during homing")
+ raise self._printer.command_error("Communication timeout during homing")
raise self._printer.command_error("Eddy current sensor error")
if res != mcu.MCU_trsync.REASON_ENDSTOP_HIT:
- return 0.
+ return 0.0
if self._mcu.is_fileoutput():
return home_end_time
self._trigger_time = trigger_time
return trigger_time
+
# Probe session interface
def start_probe_session(self, gcmd):
- self._gather = EddyGatherSamples(self._printer, self._sensor_helper,
- self._calibration, self._z_offset)
+ self._gather = EddyGatherSamples(
+ self._printer, self._sensor_helper, self._calibration, self._z_offset
+ )
return self
+
def run_probe(self, gcmd):
- toolhead = self._printer.lookup_object('toolhead')
+ toolhead = self._printer.lookup_object("toolhead")
pos = toolhead.get_position()
pos[2] = self._z_min_position
- speed = self._param_helper.get_probe_params(gcmd)['probe_speed']
+ speed = self._param_helper.get_probe_params(gcmd)["probe_speed"]
# Perform probing move
- phoming = self._printer.lookup_object('homing')
+ phoming = self._printer.lookup_object("homing")
trig_pos = phoming.probing_move(self, pos, speed)
if not self._trigger_time:
return trig_pos
@@ -363,12 +405,15 @@ class EddyDescend:
end_time = start_time + 0.100
toolhead_pos = toolhead.get_position()
self._gather.note_probe(start_time, end_time, toolhead_pos)
+
def pull_probed_results(self):
return self._gather.pull_probed()
+
def end_probe_session(self):
self._gather.finish()
self._gather = None
+
# Wrapper to emulate mcu_endstop for probe:z_virtual_endstop
# Note that this does not provide accurate results
class EddyEndstopWrapper:
@@ -376,34 +421,48 @@ class EddyEndstopWrapper:
self._sensor_helper = sensor_helper
self._eddy_descend = eddy_descend
self._hw_probe_session = None
+
# Interface for MCU_endstop
def get_mcu(self):
return self._sensor_helper.get_mcu()
+
def add_stepper(self, stepper):
pass
+
def get_steppers(self):
return self._eddy_descend.get_steppers()
- def home_start(self, print_time, sample_time, sample_count, rest_time,
- triggered=True):
+
+ def home_start(
+ self, print_time, sample_time, sample_count, rest_time, triggered=True
+ ):
return self._eddy_descend.home_start(
- print_time, sample_time, sample_count, rest_time, triggered)
+ print_time, sample_time, sample_count, rest_time, triggered
+ )
+
def home_wait(self, home_end_time):
return self._eddy_descend.home_wait(home_end_time)
+
def query_endstop(self, print_time):
- return False # XXX
+ return False # XXX
+
# Interface for HomingViaProbeHelper
def multi_probe_begin(self):
self._hw_probe_session = self._eddy_descend.start_probe_session(None)
+
def multi_probe_end(self):
self._hw_probe_session.end_probe_session()
self._hw_probe_session = None
+
def probe_prepare(self, hmove):
pass
+
def probe_finish(self, hmove):
pass
+
def get_position_endstop(self):
return self._eddy_descend._z_offset
+
# Implementing probing with "METHOD=scan"
class EddyScanningProbe:
def __init__(self, printer, sensor_helper, calibration, z_offset, gcmd):
@@ -411,15 +470,17 @@ class EddyScanningProbe:
self._sensor_helper = sensor_helper
self._calibration = calibration
self._z_offset = z_offset
- self._gather = EddyGatherSamples(printer, sensor_helper,
- calibration, z_offset)
+ self._gather = EddyGatherSamples(printer, sensor_helper, calibration, z_offset)
self._sample_time_delay = 0.050
self._sample_time = gcmd.get_float("SAMPLE_TIME", 0.100, above=0.0)
- self._is_rapid = gcmd.get("METHOD", "scan") == 'rapid_scan'
+ self._is_rapid = gcmd.get("METHOD", "scan") == "rapid_scan"
+
def _rapid_lookahead_cb(self, printtime):
start_time = printtime - self._sample_time / 2
self._gather.note_probe_and_position(
- start_time, start_time + self._sample_time, printtime)
+ start_time, start_time + self._sample_time, printtime
+ )
+
def run_probe(self, gcmd):
toolhead = self._printer.lookup_object("toolhead")
if self._is_rapid:
@@ -429,7 +490,9 @@ class EddyScanningProbe:
toolhead.dwell(self._sample_time_delay + self._sample_time)
start_time = printtime + self._sample_time_delay
self._gather.note_probe_and_position(
- start_time, start_time + self._sample_time, start_time)
+ start_time, start_time + self._sample_time, start_time
+ )
+
def pull_probed_results(self):
if self._is_rapid:
# Flush lookahead (so all lookahead callbacks are invoked)
@@ -440,59 +503,76 @@ class EddyScanningProbe:
for epos in results:
self._printer.send_event("probe:update_results", epos)
return results
+
def end_probe_session(self):
self._gather.finish()
self._gather = None
+
# Main "printer object"
class PrinterEddyProbe:
def __init__(self, config):
self.printer = config.get_printer()
self.calibration = EddyCalibration(config)
# Sensor type
- sensors = { "ldc1612": ldc1612.LDC1612 }
- sensor_type = config.getchoice('sensor_type', {s: s for s in sensors})
+ sensors = {"ldc1612": ldc1612.LDC1612}
+ sensor_type = config.getchoice("sensor_type", {s: s for s in sensors})
self.sensor_helper = sensors[sensor_type](config, self.calibration)
# Probe interface
self.param_helper = probe.ProbeParameterHelper(config)
self.eddy_descend = EddyDescend(
- config, self.sensor_helper, self.calibration, self.param_helper)
+ config, self.sensor_helper, self.calibration, self.param_helper
+ )
self.cmd_helper = probe.ProbeCommandHelper(config, self)
self.probe_offsets = probe.ProbeOffsetsHelper(config)
self.probe_session = probe.ProbeSessionHelper(
- config, self.param_helper, self.eddy_descend.start_probe_session)
+ config, self.param_helper, self.eddy_descend.start_probe_session
+ )
mcu_probe = EddyEndstopWrapper(self.sensor_helper, self.eddy_descend)
probe.HomingViaProbeHelper(config, mcu_probe, self.param_helper)
- self.printer.add_object('probe', self)
+ self.printer.add_object("probe", self)
+
def add_client(self, cb):
self.sensor_helper.add_client(cb)
+
def get_probe_params(self, gcmd=None):
return self.param_helper.get_probe_params(gcmd)
+
def get_offsets(self):
return self.probe_offsets.get_offsets()
+
def get_status(self, eventtime):
return self.cmd_helper.get_status(eventtime)
+
def start_probe_session(self, gcmd):
- method = gcmd.get('METHOD', 'automatic').lower()
- if method in ('scan', 'rapid_scan'):
+ method = gcmd.get("METHOD", "automatic").lower()
+ if method in ("scan", "rapid_scan"):
z_offset = self.get_offsets()[2]
- return EddyScanningProbe(self.printer, self.sensor_helper,
- self.calibration, z_offset, gcmd)
+ return EddyScanningProbe(
+ self.printer, self.sensor_helper, self.calibration, z_offset, gcmd
+ )
return self.probe_session.start_probe_session(gcmd)
+
def register_drift_compensation(self, comp):
self.calibration.register_drift_compensation(comp)
+
class DummyDriftCompensation:
def get_temperature(self):
- return 0.
+ return 0.0
+
def note_z_calibration_start(self):
pass
+
def note_z_calibration_finish(self):
pass
+
def adjust_freq(self, freq, temp=None):
return freq
+
def unadjust_freq(self, freq, temp=None):
return freq
+
def load_config_prefix(config):
return PrinterEddyProbe(config)
diff --git a/klippy/extras/pulse_counter.py b/klippy/extras/pulse_counter.py
index a7587187..922398a6 100644
--- a/klippy/extras/pulse_counter.py
+++ b/klippy/extras/pulse_counter.py
@@ -4,14 +4,15 @@
#
# This file may be distributed under the terms of the GNU GPLv3 license.
+
class MCU_counter:
def __init__(self, printer, pin, sample_time, poll_time):
- ppins = printer.lookup_object('pins')
+ ppins = printer.lookup_object("pins")
pin_params = ppins.lookup_pin(pin, can_pullup=True)
- self._mcu = pin_params['chip']
+ self._mcu = pin_params["chip"]
self._oid = self._mcu.create_oid()
- self._pin = pin_params['pin']
- self._pullup = pin_params['pullup']
+ self._pin = pin_params["pin"]
+ self._pullup = pin_params["pullup"]
self._poll_time = poll_time
self._poll_ticks = 0
self._sample_time = sample_time
@@ -20,42 +21,48 @@ class MCU_counter:
self._mcu.register_config_callback(self.build_config)
def build_config(self):
- self._mcu.add_config_cmd("config_counter oid=%d pin=%s pull_up=%d"
- % (self._oid, self._pin, self._pullup))
+ self._mcu.add_config_cmd(
+ "config_counter oid=%d pin=%s pull_up=%d"
+ % (self._oid, self._pin, self._pullup)
+ )
clock = self._mcu.get_query_slot(self._oid)
self._poll_ticks = self._mcu.seconds_to_clock(self._poll_time)
sample_ticks = self._mcu.seconds_to_clock(self._sample_time)
self._mcu.add_config_cmd(
"query_counter oid=%d clock=%d poll_ticks=%d sample_ticks=%d"
- % (self._oid, clock, self._poll_ticks, sample_ticks), is_init=True)
- self._mcu.register_response(self._handle_counter_state,
- "counter_state", self._oid)
+ % (self._oid, clock, self._poll_ticks, sample_ticks),
+ is_init=True,
+ )
+ self._mcu.register_response(
+ self._handle_counter_state, "counter_state", self._oid
+ )
# Callback is called periodically every sample_time
def setup_callback(self, cb):
self._callback = cb
def _handle_counter_state(self, params):
- next_clock = self._mcu.clock32_to_clock64(params['next_clock'])
+ next_clock = self._mcu.clock32_to_clock64(params["next_clock"])
time = self._mcu.clock_to_print_time(next_clock - self._poll_ticks)
- count_clock = self._mcu.clock32_to_clock64(params['count_clock'])
+ count_clock = self._mcu.clock32_to_clock64(params["count_clock"])
count_time = self._mcu.clock_to_print_time(count_clock)
# handle 32-bit counter overflow
last_count = self._last_count
- delta_count = (params['count'] - last_count) & 0xffffffff
+ delta_count = (params["count"] - last_count) & 0xFFFFFFFF
count = last_count + delta_count
self._last_count = count
if self._callback is not None:
self._callback(time, count, count_time)
+
class FrequencyCounter:
def __init__(self, printer, pin, sample_time, poll_time):
self._callback = None
self._last_time = self._last_count = None
- self._freq = 0.
+ self._freq = 0.0
self._counter = MCU_counter(printer, pin, sample_time, poll_time)
self._counter.setup_callback(self._counter_callback)
@@ -70,7 +77,7 @@ class FrequencyCounter:
self._freq = delta_count / delta_time
else: # No counts since last sample
self._last_time = time
- self._freq = 0.
+ self._freq = 0.0
if self._callback is not None:
self._callback(time, self._freq)
self._last_count = count
diff --git a/klippy/extras/pwm_cycle_time.py b/klippy/extras/pwm_cycle_time.py
index 94f7bcf8..d2152316 100644
--- a/klippy/extras/pwm_cycle_time.py
+++ b/klippy/extras/pwm_cycle_time.py
@@ -4,101 +4,124 @@
#
# This file may be distributed under the terms of the GNU GPLv3 license.
+
class MCU_pwm_cycle:
def __init__(self, pin_params, cycle_time, start_value, shutdown_value):
- self._mcu = pin_params['chip']
+ self._mcu = pin_params["chip"]
self._cycle_time = cycle_time
self._oid = None
self._mcu.register_config_callback(self._build_config)
- self._pin = pin_params['pin']
- self._invert = pin_params['invert']
+ self._pin = pin_params["pin"]
+ self._invert = pin_params["invert"]
if self._invert:
- start_value = 1. - start_value
- shutdown_value = 1. - shutdown_value
- self._start_value = max(0., min(1., start_value))
- self._shutdown_value = max(0., min(1., shutdown_value))
+ start_value = 1.0 - start_value
+ shutdown_value = 1.0 - shutdown_value
+ self._start_value = max(0.0, min(1.0, start_value))
+ self._shutdown_value = max(0.0, min(1.0, shutdown_value))
self._last_clock = self._cycle_ticks = 0
self._set_cmd = self._set_cycle_ticks = None
+
def get_mcu(self):
return self._mcu
+
def _build_config(self):
cmd_queue = self._mcu.alloc_command_queue()
curtime = self._mcu.get_printer().get_reactor().monotonic()
printtime = self._mcu.estimated_print_time(curtime)
self._last_clock = self._mcu.print_time_to_clock(printtime + 0.200)
cycle_ticks = self._mcu.seconds_to_clock(self._cycle_time)
- if self._shutdown_value not in [0., 1.]:
+ if self._shutdown_value not in [0.0, 1.0]:
raise self._mcu.get_printer().config_error(
- "shutdown value must be 0.0 or 1.0 on soft pwm")
- if cycle_ticks >= 1<<31:
- raise self._mcu.get_printer().config_error(
- "PWM pin cycle time too large")
+ "shutdown value must be 0.0 or 1.0 on soft pwm"
+ )
+ if cycle_ticks >= 1 << 31:
+ raise self._mcu.get_printer().config_error("PWM pin cycle time too large")
self._mcu.request_move_queue_slot()
self._oid = self._mcu.create_oid()
self._mcu.add_config_cmd(
"config_digital_out oid=%d pin=%s value=%d"
" default_value=%d max_duration=%d"
- % (self._oid, self._pin, self._start_value >= 1.0,
- self._shutdown_value >= 0.5, 0))
+ % (
+ self._oid,
+ self._pin,
+ self._start_value >= 1.0,
+ self._shutdown_value >= 0.5,
+ 0,
+ )
+ )
self._mcu.add_config_cmd(
- "set_digital_out_pwm_cycle oid=%d cycle_ticks=%d"
- % (self._oid, cycle_ticks))
+ "set_digital_out_pwm_cycle oid=%d cycle_ticks=%d" % (self._oid, cycle_ticks)
+ )
self._cycle_ticks = cycle_ticks
svalue = int(self._start_value * cycle_ticks + 0.5)
self._mcu.add_config_cmd(
"queue_digital_out oid=%d clock=%d on_ticks=%d"
- % (self._oid, self._last_clock, svalue), is_init=True)
+ % (self._oid, self._last_clock, svalue),
+ is_init=True,
+ )
self._set_cmd = self._mcu.lookup_command(
- "queue_digital_out oid=%c clock=%u on_ticks=%u", cq=cmd_queue)
+ "queue_digital_out oid=%c clock=%u on_ticks=%u", cq=cmd_queue
+ )
self._set_cycle_ticks = self._mcu.lookup_command(
- "set_digital_out_pwm_cycle oid=%c cycle_ticks=%u", cq=cmd_queue)
+ "set_digital_out_pwm_cycle oid=%c cycle_ticks=%u", cq=cmd_queue
+ )
+
def set_pwm_cycle(self, print_time, value, cycle_time):
clock = self._mcu.print_time_to_clock(print_time)
minclock = self._last_clock
# Send updated cycle_time if necessary
cycle_ticks = self._mcu.seconds_to_clock(cycle_time)
if cycle_ticks != self._cycle_ticks:
- if cycle_ticks >= 1<<31:
- raise self._mcu.get_printer().command_error(
- "PWM cycle time too large")
- self._set_cycle_ticks.send([self._oid, cycle_ticks],
- minclock=minclock, reqclock=clock)
+ if cycle_ticks >= 1 << 31:
+ raise self._mcu.get_printer().command_error("PWM cycle time too large")
+ self._set_cycle_ticks.send(
+ [self._oid, cycle_ticks], minclock=minclock, reqclock=clock
+ )
self._cycle_ticks = cycle_ticks
# Send pwm update
if self._invert:
- value = 1. - value
- v = int(max(0., min(1., value)) * float(self._cycle_ticks) + 0.5)
- self._set_cmd.send([self._oid, clock, v],
- minclock=self._last_clock, reqclock=clock)
+ value = 1.0 - value
+ v = int(max(0.0, min(1.0, value)) * float(self._cycle_ticks) + 0.5)
+ self._set_cmd.send(
+ [self._oid, clock, v], minclock=self._last_clock, reqclock=clock
+ )
self._last_clock = clock
+
class PrinterOutputPWMCycle:
def __init__(self, config):
self.printer = config.get_printer()
- self.last_print_time = 0.
+ self.last_print_time = 0.0
# Determine start and shutdown values
- self.scale = config.getfloat('scale', 1., above=0.)
- self.last_value = config.getfloat(
- 'value', 0., minval=0., maxval=self.scale) / self.scale
- self.shutdown_value = config.getfloat(
- 'shutdown_value', 0., minval=0., maxval=self.scale) / self.scale
+ self.scale = config.getfloat("scale", 1.0, above=0.0)
+ self.last_value = (
+ config.getfloat("value", 0.0, minval=0.0, maxval=self.scale) / self.scale
+ )
+ self.shutdown_value = (
+ config.getfloat("shutdown_value", 0.0, minval=0.0, maxval=self.scale)
+ / self.scale
+ )
# Create pwm pin object
- ppins = self.printer.lookup_object('pins')
- pin_params = ppins.lookup_pin(config.get('pin'), can_invert=True)
- max_duration = pin_params['chip'].max_nominal_duration()
- cycle_time = config.getfloat('cycle_time', 0.100, above=0.,
- maxval=max_duration)
- self.mcu_pin = MCU_pwm_cycle(pin_params, cycle_time,
- self.last_value, self.shutdown_value)
+ ppins = self.printer.lookup_object("pins")
+ pin_params = ppins.lookup_pin(config.get("pin"), can_invert=True)
+ max_duration = pin_params["chip"].max_nominal_duration()
+ cycle_time = config.getfloat(
+ "cycle_time", 0.100, above=0.0, maxval=max_duration
+ )
+ self.mcu_pin = MCU_pwm_cycle(
+ pin_params, cycle_time, self.last_value, self.shutdown_value
+ )
self.last_cycle_time = self.default_cycle_time = cycle_time
# Register commands
pin_name = config.get_name().split()[1]
- gcode = self.printer.lookup_object('gcode')
- gcode.register_mux_command("SET_PIN", "PIN", pin_name,
- self.cmd_SET_PIN,
- desc=self.cmd_SET_PIN_help)
+ gcode = self.printer.lookup_object("gcode")
+ gcode.register_mux_command(
+ "SET_PIN", "PIN", pin_name, self.cmd_SET_PIN, desc=self.cmd_SET_PIN_help
+ )
+
def get_status(self, eventtime):
- return {'value': self.last_value}
+ return {"value": self.last_value}
+
def _set_pin(self, print_time, value, cycle_time):
if value == self.last_value and cycle_time == self.last_cycle_time:
return
@@ -108,18 +131,23 @@ class PrinterOutputPWMCycle:
self.last_value = value
self.last_cycle_time = cycle_time
self.last_print_time = print_time
+
cmd_SET_PIN_help = "Set the value of an output pin"
+
def cmd_SET_PIN(self, gcmd):
# Read requested value
- value = gcmd.get_float('VALUE', minval=0., maxval=self.scale)
+ value = gcmd.get_float("VALUE", minval=0.0, maxval=self.scale)
value /= self.scale
max_duration = self.mcu_pin.get_mcu().max_nominal_duration()
- cycle_time = gcmd.get_float('CYCLE_TIME', self.default_cycle_time,
- above=0., maxval=max_duration)
+ cycle_time = gcmd.get_float(
+ "CYCLE_TIME", self.default_cycle_time, above=0.0, maxval=max_duration
+ )
# Obtain print_time and apply requested settings
- toolhead = self.printer.lookup_object('toolhead')
+ toolhead = self.printer.lookup_object("toolhead")
toolhead.register_lookahead_callback(
- lambda print_time: self._set_pin(print_time, value, cycle_time))
+ lambda print_time: self._set_pin(print_time, value, cycle_time)
+ )
+
def load_config_prefix(config):
return PrinterOutputPWMCycle(config)
diff --git a/klippy/extras/pwm_tool.py b/klippy/extras/pwm_tool.py
index 46873203..b1c8f5ec 100644
--- a/klippy/extras/pwm_tool.py
+++ b/klippy/extras/pwm_tool.py
@@ -5,61 +5,71 @@
# This file may be distributed under the terms of the GNU GPLv3 license.
import chelper
+
class error(Exception):
pass
+
class MCU_queued_pwm:
def __init__(self, pin_params):
- self._mcu = pin_params['chip']
+ self._mcu = pin_params["chip"]
self._hardware_pwm = False
self._cycle_time = 0.100
- self._max_duration = 2.
+ self._max_duration = 2.0
self._oid = self._mcu.create_oid()
ffi_main, ffi_lib = chelper.get_ffi()
- self._stepqueue = ffi_main.gc(ffi_lib.stepcompress_alloc(self._oid),
- ffi_lib.stepcompress_free)
+ self._stepqueue = ffi_main.gc(
+ ffi_lib.stepcompress_alloc(self._oid), ffi_lib.stepcompress_free
+ )
self._mcu.register_stepqueue(self._stepqueue)
self._stepcompress_queue_mq_msg = ffi_lib.stepcompress_queue_mq_msg
self._mcu.register_config_callback(self._build_config)
- self._pin = pin_params['pin']
- self._invert = pin_params['invert']
+ self._pin = pin_params["pin"]
+ self._invert = pin_params["invert"]
self._start_value = self._shutdown_value = float(self._invert)
self._last_clock = self._last_value = self._default_value = 0
self._duration_ticks = 0
- self._pwm_max = 0.
+ self._pwm_max = 0.0
self._set_cmd_tag = None
self._toolhead = None
printer = self._mcu.get_printer()
printer.register_event_handler("klippy:connect", self._handle_connect)
+
def _handle_connect(self):
self._toolhead = self._mcu.get_printer().lookup_object("toolhead")
+
def get_mcu(self):
return self._mcu
+
def setup_max_duration(self, max_duration):
self._max_duration = max_duration
+
def setup_cycle_time(self, cycle_time, hardware_pwm=False):
self._cycle_time = cycle_time
self._hardware_pwm = hardware_pwm
+
def setup_start_value(self, start_value, shutdown_value):
if self._invert:
- start_value = 1. - start_value
- shutdown_value = 1. - shutdown_value
- self._start_value = max(0., min(1., start_value))
- self._shutdown_value = max(0., min(1., shutdown_value))
+ start_value = 1.0 - start_value
+ shutdown_value = 1.0 - shutdown_value
+ self._start_value = max(0.0, min(1.0, start_value))
+ self._shutdown_value = max(0.0, min(1.0, shutdown_value))
+
def _build_config(self):
config_error = self._mcu.get_printer().config_error
if self._max_duration and self._start_value != self._shutdown_value:
- raise config_error("Pin with max duration must have start"
- " value equal to shutdown value")
+ raise config_error(
+ "Pin with max duration must have start" " value equal to shutdown value"
+ )
cmd_queue = self._mcu.alloc_command_queue()
curtime = self._mcu.get_printer().get_reactor().monotonic()
printtime = self._mcu.estimated_print_time(curtime)
self._last_clock = self._mcu.print_time_to_clock(printtime + 0.200)
cycle_ticks = self._mcu.seconds_to_clock(self._cycle_time)
- if cycle_ticks >= 1<<31:
+ if cycle_ticks >= 1 << 31:
raise config_error("PWM pin cycle time too large")
self._duration_ticks = self._mcu.seconds_to_clock(self._max_duration)
- if self._duration_ticks >= 1<<31:
+ if self._duration_ticks >= 1 << 31:
raise config_error("PWM pin max duration too large")
if self._duration_ticks:
self._mcu.register_flush_callback(self._flush_notification)
@@ -69,44 +79,59 @@ class MCU_queued_pwm:
self._mcu.add_config_cmd(
"config_pwm_out oid=%d pin=%s cycle_ticks=%d value=%d"
" default_value=%d max_duration=%d"
- % (self._oid, self._pin, cycle_ticks,
- self._start_value * self._pwm_max,
- self._default_value, self._duration_ticks))
+ % (
+ self._oid,
+ self._pin,
+ cycle_ticks,
+ self._start_value * self._pwm_max,
+ self._default_value,
+ self._duration_ticks,
+ )
+ )
self._last_value = int(self._start_value * self._pwm_max + 0.5)
- self._mcu.add_config_cmd("queue_pwm_out oid=%d clock=%d value=%d"
- % (self._oid, self._last_clock,
- self._last_value),
- on_restart=True)
+ self._mcu.add_config_cmd(
+ "queue_pwm_out oid=%d clock=%d value=%d"
+ % (self._oid, self._last_clock, self._last_value),
+ on_restart=True,
+ )
self._set_cmd_tag = self._mcu.lookup_command(
- "queue_pwm_out oid=%c clock=%u value=%hu",
- cq=cmd_queue).get_command_tag()
+ "queue_pwm_out oid=%c clock=%u value=%hu", cq=cmd_queue
+ ).get_command_tag()
return
# Software PWM
- if self._shutdown_value not in [0., 1.]:
+ if self._shutdown_value not in [0.0, 1.0]:
raise config_error("shutdown value must be 0.0 or 1.0 on soft pwm")
self._mcu.add_config_cmd(
"config_digital_out oid=%d pin=%s value=%d"
" default_value=%d max_duration=%d"
- % (self._oid, self._pin, self._start_value >= 1.0,
- self._shutdown_value >= 0.5, self._duration_ticks))
+ % (
+ self._oid,
+ self._pin,
+ self._start_value >= 1.0,
+ self._shutdown_value >= 0.5,
+ self._duration_ticks,
+ )
+ )
self._default_value = int(self._shutdown_value >= 0.5) * cycle_ticks
self._mcu.add_config_cmd(
- "set_digital_out_pwm_cycle oid=%d cycle_ticks=%d"
- % (self._oid, cycle_ticks))
+ "set_digital_out_pwm_cycle oid=%d cycle_ticks=%d" % (self._oid, cycle_ticks)
+ )
self._pwm_max = float(cycle_ticks)
self._last_value = int(self._start_value * self._pwm_max + 0.5)
self._mcu.add_config_cmd(
"queue_digital_out oid=%d clock=%d on_ticks=%d"
- % (self._oid, self._last_clock, self._last_value), is_init=True)
+ % (self._oid, self._last_clock, self._last_value),
+ is_init=True,
+ )
self._set_cmd_tag = self._mcu.lookup_command(
- "queue_digital_out oid=%c clock=%u on_ticks=%u",
- cq=cmd_queue).get_command_tag()
+ "queue_digital_out oid=%c clock=%u on_ticks=%u", cq=cmd_queue
+ ).get_command_tag()
+
def _send_update(self, clock, val):
self._last_clock = clock = max(self._last_clock, clock)
self._last_value = val
- data = (self._set_cmd_tag, self._oid, clock & 0xffffffff, val)
- ret = self._stepcompress_queue_mq_msg(self._stepqueue, clock,
- data, len(data))
+ data = (self._set_cmd_tag, self._oid, clock & 0xFFFFFFFF, val)
+ ret = self._stepcompress_queue_mq_msg(self._stepqueue, clock, data, len(data))
if ret:
raise error("Internal error in stepcompress")
# Notify toolhead so that it will flush this update
@@ -116,50 +141,61 @@ class MCU_queued_pwm:
wakeclock += self._duration_ticks
wake_print_time = self._mcu.clock_to_print_time(wakeclock)
self._toolhead.note_mcu_movequeue_activity(wake_print_time)
+
def set_pwm(self, print_time, value):
clock = self._mcu.print_time_to_clock(print_time)
if self._invert:
- value = 1. - value
- v = int(max(0., min(1., value)) * self._pwm_max + 0.5)
+ value = 1.0 - value
+ v = int(max(0.0, min(1.0, value)) * self._pwm_max + 0.5)
self._send_update(clock, v)
+
def _flush_notification(self, print_time, clock):
if self._last_value != self._default_value:
while clock >= self._last_clock + self._duration_ticks:
- self._send_update(self._last_clock + self._duration_ticks,
- self._last_value)
+ self._send_update(
+ self._last_clock + self._duration_ticks, self._last_value
+ )
+
class PrinterOutputPin:
def __init__(self, config):
self.printer = config.get_printer()
- ppins = self.printer.lookup_object('pins')
+ ppins = self.printer.lookup_object("pins")
# Determine pin type
- pin_params = ppins.lookup_pin(config.get('pin'), can_invert=True)
+ pin_params = ppins.lookup_pin(config.get("pin"), can_invert=True)
self.mcu_pin = MCU_queued_pwm(pin_params)
max_duration = self.mcu_pin.get_mcu().max_nominal_duration()
- cycle_time = config.getfloat('cycle_time', 0.100, above=0.,
- maxval=max_duration)
- hardware_pwm = config.getboolean('hardware_pwm', False)
+ cycle_time = config.getfloat(
+ "cycle_time", 0.100, above=0.0, maxval=max_duration
+ )
+ hardware_pwm = config.getboolean("hardware_pwm", False)
self.mcu_pin.setup_cycle_time(cycle_time, hardware_pwm)
- self.scale = config.getfloat('scale', 1., above=0.)
- self.last_print_time = 0.
+ self.scale = config.getfloat("scale", 1.0, above=0.0)
+ self.last_print_time = 0.0
# Support mcu checking for maximum duration
- max_mcu_duration = config.getfloat('maximum_mcu_duration', 0.,
- minval=0.500, maxval=max_duration)
+ max_mcu_duration = config.getfloat(
+ "maximum_mcu_duration", 0.0, minval=0.500, maxval=max_duration
+ )
self.mcu_pin.setup_max_duration(max_mcu_duration)
# Determine start and shutdown values
- self.last_value = config.getfloat(
- 'value', 0., minval=0., maxval=self.scale) / self.scale
- self.shutdown_value = config.getfloat(
- 'shutdown_value', 0., minval=0., maxval=self.scale) / self.scale
+ self.last_value = (
+ config.getfloat("value", 0.0, minval=0.0, maxval=self.scale) / self.scale
+ )
+ self.shutdown_value = (
+ config.getfloat("shutdown_value", 0.0, minval=0.0, maxval=self.scale)
+ / self.scale
+ )
self.mcu_pin.setup_start_value(self.last_value, self.shutdown_value)
# Register commands
pin_name = config.get_name().split()[1]
- gcode = self.printer.lookup_object('gcode')
- gcode.register_mux_command("SET_PIN", "PIN", pin_name,
- self.cmd_SET_PIN,
- desc=self.cmd_SET_PIN_help)
+ gcode = self.printer.lookup_object("gcode")
+ gcode.register_mux_command(
+ "SET_PIN", "PIN", pin_name, self.cmd_SET_PIN, desc=self.cmd_SET_PIN_help
+ )
+
def get_status(self, eventtime):
- return {'value': self.last_value}
+ return {"value": self.last_value}
+
def _set_pin(self, print_time, value):
if value == self.last_value:
return
@@ -167,15 +203,19 @@ class PrinterOutputPin:
self.mcu_pin.set_pwm(print_time, value)
self.last_value = value
self.last_print_time = print_time
+
cmd_SET_PIN_help = "Set the value of an output pin"
+
def cmd_SET_PIN(self, gcmd):
# Read requested value
- value = gcmd.get_float('VALUE', minval=0., maxval=self.scale)
+ value = gcmd.get_float("VALUE", minval=0.0, maxval=self.scale)
value /= self.scale
# Obtain print_time and apply requested settings
- toolhead = self.printer.lookup_object('toolhead')
+ toolhead = self.printer.lookup_object("toolhead")
toolhead.register_lookahead_callback(
- lambda print_time: self._set_pin(print_time, value))
+ lambda print_time: self._set_pin(print_time, value)
+ )
+
def load_config_prefix(config):
return PrinterOutputPin(config)
diff --git a/klippy/extras/quad_gantry_level.py b/klippy/extras/quad_gantry_level.py
index 98cd53c5..4dbdc47e 100644
--- a/klippy/extras/quad_gantry_level.py
+++ b/klippy/extras/quad_gantry_level.py
@@ -22,42 +22,54 @@ from . import probe, z_tilt
# | * <-- probe0 probe3 --> * |
# Z stepper0 ----> O O <---- Z stepper3
+
class QuadGantryLevel:
def __init__(self, config):
self.printer = config.get_printer()
- self.retry_helper = z_tilt.RetryHelper(config,
- "Possibly Z motor numbering is wrong")
+ self.retry_helper = z_tilt.RetryHelper(
+ config, "Possibly Z motor numbering is wrong"
+ )
self.max_adjust = config.getfloat("max_adjust", 4, above=0)
self.horizontal_move_z = config.getfloat("horizontal_move_z", 5.0)
self.probe_helper = probe.ProbePointsHelper(config, self.probe_finalize)
if len(self.probe_helper.probe_points) != 4:
- raise config.error(
- "Need exactly 4 probe points for quad_gantry_level")
+ raise config.error("Need exactly 4 probe points for quad_gantry_level")
self.z_status = z_tilt.ZAdjustStatus(self.printer)
self.z_helper = z_tilt.ZAdjustHelper(config, 4)
- self.gantry_corners = config.getlists('gantry_corners', parser=float,
- seps=(',', '\n'), count=2)
+ self.gantry_corners = config.getlists(
+ "gantry_corners", parser=float, seps=(",", "\n"), count=2
+ )
if len(self.gantry_corners) < 2:
- raise config.error(
- "quad_gantry_level requires at least two gantry_corners")
+ raise config.error("quad_gantry_level requires at least two gantry_corners")
# Register QUAD_GANTRY_LEVEL command
- self.gcode = self.printer.lookup_object('gcode')
+ self.gcode = self.printer.lookup_object("gcode")
self.gcode.register_command(
- 'QUAD_GANTRY_LEVEL', self.cmd_QUAD_GANTRY_LEVEL,
- desc=self.cmd_QUAD_GANTRY_LEVEL_help)
+ "QUAD_GANTRY_LEVEL",
+ self.cmd_QUAD_GANTRY_LEVEL,
+ desc=self.cmd_QUAD_GANTRY_LEVEL_help,
+ )
+
cmd_QUAD_GANTRY_LEVEL_help = (
- "Conform a moving, twistable gantry to the shape of a stationary bed")
+ "Conform a moving, twistable gantry to the shape of a stationary bed"
+ )
+
def cmd_QUAD_GANTRY_LEVEL(self, gcmd):
self.z_status.reset()
self.retry_helper.start(gcmd)
self.probe_helper.start_probe(gcmd)
+
def probe_finalize(self, offsets, positions):
# Mirror our perspective so the adjustments make sense
# from the perspective of the gantry
z_positions = [self.horizontal_move_z - p[2] for p in positions]
points_message = "Gantry-relative probe points:\n%s\n" % (
- " ".join(["%s: %.6f" % (z_id, z_positions[z_id])
- for z_id in range(len(z_positions))]))
+ " ".join(
+ [
+ "%s: %.6f" % (z_id, z_positions[z_id])
+ for z_id in range(len(z_positions))
+ ]
+ )
+ )
self.gcode.respond_info(points_message)
# Calculate slope along X axis between probe point 0 and 3
ppx0 = [positions[0][0] + offsets[0], z_positions[0]]
@@ -67,30 +79,36 @@ class QuadGantryLevel:
ppx1 = [positions[1][0] + offsets[0], z_positions[1]]
ppx2 = [positions[2][0] + offsets[0], z_positions[2]]
slope_x_pp12 = self.linefit(ppx1, ppx2)
- logging.info("quad_gantry_level f1: %s, f2: %s"
- % (slope_x_pp03, slope_x_pp12))
+ logging.info("quad_gantry_level f1: %s, f2: %s" % (slope_x_pp03, slope_x_pp12))
# Calculate gantry slope along Y axis between stepper 0 and 1
- a1 = [positions[0][1] + offsets[1],
- self.plot(slope_x_pp03, self.gantry_corners[0][0])]
- a2 = [positions[1][1] + offsets[1],
- self.plot(slope_x_pp12, self.gantry_corners[0][0])]
+ a1 = [
+ positions[0][1] + offsets[1],
+ self.plot(slope_x_pp03, self.gantry_corners[0][0]),
+ ]
+ a2 = [
+ positions[1][1] + offsets[1],
+ self.plot(slope_x_pp12, self.gantry_corners[0][0]),
+ ]
slope_y_s01 = self.linefit(a1, a2)
# Calculate gantry slope along Y axis between stepper 2 and 3
- b1 = [positions[0][1] + offsets[1],
- self.plot(slope_x_pp03, self.gantry_corners[1][0])]
- b2 = [positions[1][1] + offsets[1],
- self.plot(slope_x_pp12, self.gantry_corners[1][0])]
+ b1 = [
+ positions[0][1] + offsets[1],
+ self.plot(slope_x_pp03, self.gantry_corners[1][0]),
+ ]
+ b2 = [
+ positions[1][1] + offsets[1],
+ self.plot(slope_x_pp12, self.gantry_corners[1][0]),
+ ]
slope_y_s23 = self.linefit(b1, b2)
- logging.info("quad_gantry_level af: %s, bf: %s"
- % (slope_y_s01, slope_y_s23))
+ logging.info("quad_gantry_level af: %s, bf: %s" % (slope_y_s01, slope_y_s23))
# Calculate z height of each stepper
- z_height = [0,0,0,0]
+ z_height = [0, 0, 0, 0]
z_height[0] = self.plot(slope_y_s01, self.gantry_corners[0][1])
z_height[1] = self.plot(slope_y_s01, self.gantry_corners[1][1])
z_height[2] = self.plot(slope_y_s23, self.gantry_corners[1][1])
z_height[3] = self.plot(slope_y_s23, self.gantry_corners[0][1])
- ainfo = zip(["z","z1","z2","z3"], z_height[0:4])
+ ainfo = zip(["z", "z1", "z2", "z3"], z_height[0:4])
apos = " ".join(["%s: %06f" % (x) for x in ainfo])
self.gcode.respond_info("Actuator Positions:\n" + apos)
@@ -102,27 +120,32 @@ class QuadGantryLevel:
adjust_max = max(z_adjust)
if adjust_max > self.max_adjust:
- raise self.gcode.error("Aborting quad_gantry_level"
- " required adjustment %0.6f"
- " is greater than max_adjust %0.6f"
- % (adjust_max, self.max_adjust))
+ raise self.gcode.error(
+ "Aborting quad_gantry_level"
+ " required adjustment %0.6f"
+ " is greater than max_adjust %0.6f" % (adjust_max, self.max_adjust)
+ )
speed = self.probe_helper.get_lift_speed()
self.z_helper.adjust_steppers(z_adjust, speed)
return self.z_status.check_retry_result(
- self.retry_helper.check_retry(z_positions))
+ self.retry_helper.check_retry(z_positions)
+ )
- def linefit(self,p1,p2):
+ def linefit(self, p1, p2):
if p1[1] == p2[1]:
# Straight line
- return 0,p1[1]
- m = (p2[1] - p1[1])/(p2[0] - p1[0])
+ return 0, p1[1]
+ m = (p2[1] - p1[1]) / (p2[0] - p1[0])
b = p1[1] - m * p1[0]
- return m,b
- def plot(self,f,x):
- return f[0]*x + f[1]
+ return m, b
+
+ def plot(self, f, x):
+ return f[0] * x + f[1]
+
def get_status(self, eventtime):
return self.z_status.get_status(eventtime)
+
def load_config(config):
return QuadGantryLevel(config)
diff --git a/klippy/extras/query_adc.py b/klippy/extras/query_adc.py
index 2102aaf8..0383a642 100644
--- a/klippy/extras/query_adc.py
+++ b/klippy/extras/query_adc.py
@@ -4,32 +4,41 @@
#
# This file may be distributed under the terms of the GNU GPLv3 license.
+
class QueryADC:
def __init__(self, config):
self.printer = config.get_printer()
self.adc = {}
- gcode = self.printer.lookup_object('gcode')
- gcode.register_command("QUERY_ADC", self.cmd_QUERY_ADC,
- desc=self.cmd_QUERY_ADC_help)
+ gcode = self.printer.lookup_object("gcode")
+ gcode.register_command(
+ "QUERY_ADC", self.cmd_QUERY_ADC, desc=self.cmd_QUERY_ADC_help
+ )
+
def register_adc(self, name, mcu_adc):
self.adc[name] = mcu_adc
+
cmd_QUERY_ADC_help = "Report the last value of an analog pin"
+
def cmd_QUERY_ADC(self, gcmd):
- name = gcmd.get('NAME', None)
+ name = gcmd.get("NAME", None)
if name not in self.adc:
objs = ['"%s"' % (n,) for n in sorted(self.adc.keys())]
- msg = "Available ADC objects: %s" % (', '.join(objs),)
+ msg = "Available ADC objects: %s" % (", ".join(objs),)
gcmd.respond_info(msg)
return
value, timestamp = self.adc[name].get_last_value()
msg = 'ADC object "%s" has value %.6f (timestamp %.3f)' % (
- name, value, timestamp)
- pullup = gcmd.get_float('PULLUP', None, above=0.)
+ name,
+ value,
+ timestamp,
+ )
+ pullup = gcmd.get_float("PULLUP", None, above=0.0)
if pullup is not None:
- v = max(.00001, min(.99999, value))
+ v = max(0.00001, min(0.99999, value))
r = pullup * v / (1.0 - v)
msg += "\n resistance %.3f (with %.0f pullup)" % (r, pullup)
gcmd.respond_info(msg)
+
def load_config(config):
return QueryADC(config)
diff --git a/klippy/extras/query_endstops.py b/klippy/extras/query_endstops.py
index 949e1585..34ef44e4 100644
--- a/klippy/extras/query_endstops.py
+++ b/klippy/extras/query_endstops.py
@@ -4,42 +4,58 @@
#
# This file may be distributed under the terms of the GNU GPLv3 license.
+
class QueryEndstops:
def __init__(self, config):
self.printer = config.get_printer()
self.endstops = []
self.last_state = []
# Register webhook if server is available
- webhooks = self.printer.lookup_object('webhooks')
- webhooks.register_endpoint(
- "query_endstops/status", self._handle_web_request)
- gcode = self.printer.lookup_object('gcode')
- gcode.register_command("QUERY_ENDSTOPS", self.cmd_QUERY_ENDSTOPS,
- desc=self.cmd_QUERY_ENDSTOPS_help)
+ webhooks = self.printer.lookup_object("webhooks")
+ webhooks.register_endpoint("query_endstops/status", self._handle_web_request)
+ gcode = self.printer.lookup_object("gcode")
+ gcode.register_command(
+ "QUERY_ENDSTOPS", self.cmd_QUERY_ENDSTOPS, desc=self.cmd_QUERY_ENDSTOPS_help
+ )
gcode.register_command("M119", self.cmd_QUERY_ENDSTOPS)
+
def register_endstop(self, mcu_endstop, name):
self.endstops.append((mcu_endstop, name))
+
def get_status(self, eventtime):
- return {'last_query': {name: value for name, value in self.last_state}}
+ return {"last_query": {name: value for name, value in self.last_state}}
+
def _handle_web_request(self, web_request):
- gc_mutex = self.printer.lookup_object('gcode').get_mutex()
- toolhead = self.printer.lookup_object('toolhead')
+ gc_mutex = self.printer.lookup_object("gcode").get_mutex()
+ toolhead = self.printer.lookup_object("toolhead")
with gc_mutex:
print_time = toolhead.get_last_move_time()
- self.last_state = [(name, mcu_endstop.query_endstop(print_time))
- for mcu_endstop, name in self.endstops]
- web_request.send({name: ["open", "TRIGGERED"][not not t]
- for name, t in self.last_state})
+ self.last_state = [
+ (name, mcu_endstop.query_endstop(print_time))
+ for mcu_endstop, name in self.endstops
+ ]
+ web_request.send(
+ {name: ["open", "TRIGGERED"][not not t] for name, t in self.last_state}
+ )
+
cmd_QUERY_ENDSTOPS_help = "Report on the status of each endstop"
+
def cmd_QUERY_ENDSTOPS(self, gcmd):
# Query the endstops
- print_time = self.printer.lookup_object('toolhead').get_last_move_time()
- self.last_state = [(name, mcu_endstop.query_endstop(print_time))
- for mcu_endstop, name in self.endstops]
+ print_time = self.printer.lookup_object("toolhead").get_last_move_time()
+ self.last_state = [
+ (name, mcu_endstop.query_endstop(print_time))
+ for mcu_endstop, name in self.endstops
+ ]
# Report results
- msg = " ".join(["%s:%s" % (name, ["open", "TRIGGERED"][not not t])
- for name, t in self.last_state])
+ msg = " ".join(
+ [
+ "%s:%s" % (name, ["open", "TRIGGERED"][not not t])
+ for name, t in self.last_state
+ ]
+ )
gcmd.respond_raw(msg)
+
def load_config(config):
return QueryEndstops(config)
diff --git a/klippy/extras/replicape.py b/klippy/extras/replicape.py
index f7f7bb64..8f62a959 100644
--- a/klippy/extras/replicape.py
+++ b/klippy/extras/replicape.py
@@ -10,14 +10,15 @@ from . import bus
REPLICAPE_MAX_CURRENT = 3.84
REPLICAPE_PCA9685_BUS = 2
REPLICAPE_PCA9685_ADDRESS = 0x70
-REPLICAPE_PCA9685_CYCLE_TIME = .001
+REPLICAPE_PCA9685_CYCLE_TIME = 0.001
PIN_MIN_TIME = 0.100
+
class pca9685_pwm:
def __init__(self, replicape, channel, pin_type, pin_params):
self._replicape = replicape
self._channel = channel
- if pin_type not in ['digital_out', 'pwm']:
+ if pin_type not in ["digital_out", "pwm"]:
raise pins.error("Pin type not supported on replicape")
self._mcu = replicape.host_mcu
self._mcu.register_config_callback(self._build_config)
@@ -25,33 +26,42 @@ class pca9685_pwm:
self._bus = REPLICAPE_PCA9685_BUS
self._address = REPLICAPE_PCA9685_ADDRESS
self._cycle_time = REPLICAPE_PCA9685_CYCLE_TIME
- self._max_duration = 2.
+ self._max_duration = 2.0
self._oid = None
- self._invert = pin_params['invert']
+ self._invert = pin_params["invert"]
self._start_value = self._shutdown_value = float(self._invert)
self._is_enable = not not self._start_value
self._last_clock = 0
- self._pwm_max = 0.
+ self._pwm_max = 0.0
self._set_cmd = None
+
def get_mcu(self):
return self._mcu
+
def setup_max_duration(self, max_duration):
self._max_duration = max_duration
+
def setup_cycle_time(self, cycle_time, hardware_pwm=False):
if hardware_pwm:
raise pins.error("pca9685 does not support hardware_pwm parameter")
if cycle_time != self._cycle_time:
- logging.info("Ignoring pca9685 cycle time of %.6f (using %.6f)",
- cycle_time, self._cycle_time)
+ logging.info(
+ "Ignoring pca9685 cycle time of %.6f (using %.6f)",
+ cycle_time,
+ self._cycle_time,
+ )
+
def setup_start_value(self, start_value, shutdown_value):
if self._invert:
- start_value = 1. - start_value
- shutdown_value = 1. - shutdown_value
- self._start_value = max(0., min(1., start_value))
- self._shutdown_value = max(0., min(1., shutdown_value))
+ start_value = 1.0 - start_value
+ shutdown_value = 1.0 - shutdown_value
+ self._start_value = max(0.0, min(1.0, start_value))
+ self._shutdown_value = max(0.0, min(1.0, shutdown_value))
self._replicape.note_pwm_start_value(
- self._channel, self._start_value, self._shutdown_value)
+ self._channel, self._start_value, self._shutdown_value
+ )
self._is_enable = not not self._start_value
+
def _build_config(self):
self._pwm_max = self._mcu.get_constant_float("PCA9685_MAX")
cycle_ticks = self._mcu.seconds_to_clock(self._cycle_time)
@@ -59,62 +69,83 @@ class pca9685_pwm:
self._oid = self._mcu.create_oid()
self._mcu.add_config_cmd(
"config_pca9685 oid=%d bus=%d addr=%d channel=%d cycle_ticks=%d"
- " value=%d default_value=%d max_duration=%d" % (
- self._oid, self._bus, self._address, self._channel, cycle_ticks,
+ " value=%d default_value=%d max_duration=%d"
+ % (
+ self._oid,
+ self._bus,
+ self._address,
+ self._channel,
+ cycle_ticks,
self._start_value * self._pwm_max,
self._shutdown_value * self._pwm_max,
- self._mcu.seconds_to_clock(self._max_duration)))
+ self._mcu.seconds_to_clock(self._max_duration),
+ )
+ )
cmd_queue = self._mcu.alloc_command_queue()
self._set_cmd = self._mcu.lookup_command(
- "queue_pca9685_out oid=%c clock=%u value=%hu", cq=cmd_queue)
+ "queue_pca9685_out oid=%c clock=%u value=%hu", cq=cmd_queue
+ )
+
def set_pwm(self, print_time, value):
clock = self._mcu.print_time_to_clock(print_time)
if self._invert:
- value = 1. - value
- value = int(max(0., min(1., value)) * self._pwm_max + 0.5)
+ value = 1.0 - value
+ value = int(max(0.0, min(1.0, value)) * self._pwm_max + 0.5)
is_enable = not not value
if is_enable != self._is_enable:
self._is_enable = is_enable
self._reactor.register_async_callback(
- (lambda e, s=self, pt=print_time, ie=is_enable:
- s._replicape.note_pwm_enable(pt, s._channel, ie)))
- self._set_cmd.send([self._oid, clock, value],
- minclock=self._last_clock, reqclock=clock)
+ (
+ lambda e, s=self, pt=print_time, ie=is_enable: s._replicape.note_pwm_enable(
+ pt, s._channel, ie
+ )
+ )
+ )
+ self._set_cmd.send(
+ [self._oid, clock, value], minclock=self._last_clock, reqclock=clock
+ )
self._last_clock = clock
+
def set_digital(self, print_time, value):
if value:
- self.set_pwm(print_time, 1.)
+ self.set_pwm(print_time, 1.0)
else:
- self.set_pwm(print_time, 0.)
+ self.set_pwm(print_time, 0.0)
+
class ReplicapeDACEnable:
def __init__(self, replicape, channel, pin_type, pin_params):
- if pin_type != 'digital_out':
+ if pin_type != "digital_out":
raise pins.error("Replicape virtual enable pin must be digital_out")
- if pin_params['invert']:
+ if pin_params["invert"]:
raise pins.error("Replicape virtual enable pin can not be inverted")
self.mcu = replicape.host_mcu
self.value = replicape.stepper_dacs[channel]
self.pwm = pca9685_pwm(replicape, channel, pin_type, pin_params)
+
def get_mcu(self):
return self.mcu
+
def setup_max_duration(self, max_duration):
self.pwm.setup_max_duration(max_duration)
+
def set_digital(self, print_time, value):
if value:
self.pwm.set_pwm(print_time, self.value)
else:
- self.pwm.set_pwm(print_time, 0.)
+ self.pwm.set_pwm(print_time, 0.0)
+
SERVO_PINS = {
- "servo0": ("/pwm0", "gpio0_30", "gpio1_18"), # P9_11, P9_14
- "servo1": ("/pwm1", "gpio3_17", "gpio1_19"), # P9_28, P9_16
+ "servo0": ("/pwm0", "gpio0_30", "gpio1_18"), # P9_11, P9_14
+ "servo1": ("/pwm1", "gpio3_17", "gpio1_19"), # P9_28, P9_16
}
+
class servo_pwm:
def __init__(self, replicape, pin_params):
- config_name = pin_params['pin']
- pwmchip = 'pwmchip0'
+ config_name = pin_params["pin"]
+ pwmchip = "pwmchip0"
if not replicape.host_mcu.is_fileoutput():
try:
# Determine the pwmchip number for the servo channels
@@ -124,13 +155,14 @@ class servo_pwm:
# /sys/class/pwm/ used by the Linux MCU should be a symlink
# to this directory.
pwmdev = os.listdir(
- '/sys/devices/platform/ocp/48302000.epwmss/48302200.pwm/pwm/')
- pwmchip = [pc for pc in pwmdev if pc.startswith('pwmchip')][0]
+ "/sys/devices/platform/ocp/48302000.epwmss/48302200.pwm/pwm/"
+ )
+ pwmchip = [pc for pc in pwmdev if pc.startswith("pwmchip")][0]
except:
raise pins.error("Replicape unable to determine pwmchip")
pwm_pin, resv1, resv2 = SERVO_PINS[config_name]
pin_params = dict(pin_params)
- pin_params['pin'] = pwmchip + pwm_pin
+ pin_params["pin"] = pwmchip + pwm_pin
# Setup actual pwm pin using linux hardware pwm on host
self.mcu_pwm = replicape.host_mcu.setup_pin("pwm", pin_params)
self.get_mcu = self.mcu_pwm.get_mcu
@@ -140,90 +172,106 @@ class servo_pwm:
# Reserve pins to warn user of conflicts
pru_mcu = replicape.mcu_pwm_enable.get_mcu()
printer = pru_mcu.get_printer()
- ppins = printer.lookup_object('pins')
+ ppins = printer.lookup_object("pins")
pin_resolver = ppins.get_pin_resolver(pru_mcu.get_name())
pin_resolver.reserve_pin(resv1, config_name)
pin_resolver.reserve_pin(resv2, config_name)
+
def setup_cycle_time(self, cycle_time, hardware_pwm=False):
self.mcu_pwm.setup_cycle_time(cycle_time, True)
+
ReplicapeStepConfig = {
- 'disable': None,
- '1': (1<<7)|(1<<5), '2': (1<<7)|(1<<5)|(1<<6), 'spread2': (1<<5),
- '4': (1<<7)|(1<<5)|(1<<4), '16': (1<<7)|(1<<5)|(1<<6)|(1<<4),
- 'spread4': (1<<5)|(1<<4), 'spread16': (1<<7), 'stealth4': (1<<7)|(1<<6),
- 'stealth16': 0
+ "disable": None,
+ "1": (1 << 7) | (1 << 5),
+ "2": (1 << 7) | (1 << 5) | (1 << 6),
+ "spread2": (1 << 5),
+ "4": (1 << 7) | (1 << 5) | (1 << 4),
+ "16": (1 << 7) | (1 << 5) | (1 << 6) | (1 << 4),
+ "spread4": (1 << 5) | (1 << 4),
+ "spread16": (1 << 7),
+ "stealth4": (1 << 7) | (1 << 6),
+ "stealth16": 0,
}
+
class Replicape:
def __init__(self, config):
printer = config.get_printer()
- ppins = printer.lookup_object('pins')
- ppins.register_chip('replicape', self)
- revisions = ['B3']
- config.getchoice('revision', revisions)
- self.host_mcu = mcu.get_printer_mcu(printer, config.get('host_mcu'))
+ ppins = printer.lookup_object("pins")
+ ppins.register_chip("replicape", self)
+ revisions = ["B3"]
+ config.getchoice("revision", revisions)
+ self.host_mcu = mcu.get_printer_mcu(printer, config.get("host_mcu"))
# Setup enable pin
- enable_pin = config.get('enable_pin', '!gpio0_20')
- self.mcu_pwm_enable = ppins.setup_pin('digital_out', enable_pin)
- self.mcu_pwm_enable.setup_max_duration(0.)
+ enable_pin = config.get("enable_pin", "!gpio0_20")
+ self.mcu_pwm_enable = ppins.setup_pin("digital_out", enable_pin)
+ self.mcu_pwm_enable.setup_max_duration(0.0)
self.mcu_pwm_start_value = self.mcu_pwm_shutdown_value = False
- self.last_pwm_enable_time = 0.
+ self.last_pwm_enable_time = 0.0
# Setup power pins
self.pins = {
- "power_e": (pca9685_pwm, 5), "power_h": (pca9685_pwm, 3),
+ "power_e": (pca9685_pwm, 5),
+ "power_h": (pca9685_pwm, 3),
"power_hotbed": (pca9685_pwm, 4),
- "power_fan0": (pca9685_pwm, 7), "power_fan1": (pca9685_pwm, 8),
- "power_fan2": (pca9685_pwm, 9), "power_fan3": (pca9685_pwm, 10) }
- self.servo_pins = {
- "servo0": 3, "servo1": 2 }
+ "power_fan0": (pca9685_pwm, 7),
+ "power_fan1": (pca9685_pwm, 8),
+ "power_fan2": (pca9685_pwm, 9),
+ "power_fan3": (pca9685_pwm, 10),
+ }
+ self.servo_pins = {"servo0": 3, "servo1": 2}
# Setup stepper config
self.stepper_dacs = {}
shift_registers = [1, 0, 0, 1, 1]
- for port, name in enumerate('xyzeh'):
- prefix = 'stepper_%s_' % (name,)
+ for port, name in enumerate("xyzeh"):
+ prefix = "stepper_%s_" % (name,)
sc = config.getchoice(
- prefix + 'microstep_mode', ReplicapeStepConfig, 'disable')
+ prefix + "microstep_mode", ReplicapeStepConfig, "disable"
+ )
if sc is None:
continue
sc |= shift_registers[port]
- if config.getboolean(prefix + 'chopper_off_time_high', False):
- sc |= 1<<3
- if config.getboolean(prefix + 'chopper_hysteresis_high', False):
- sc |= 1<<2
- if config.getboolean(prefix + 'chopper_blank_time_high', True):
- sc |= 1<<1
+ if config.getboolean(prefix + "chopper_off_time_high", False):
+ sc |= 1 << 3
+ if config.getboolean(prefix + "chopper_hysteresis_high", False):
+ sc |= 1 << 2
+ if config.getboolean(prefix + "chopper_blank_time_high", True):
+ sc |= 1 << 1
shift_registers[port] = sc
channel = port + 11
cur = config.getfloat(
- prefix + 'current', above=0., maxval=REPLICAPE_MAX_CURRENT)
+ prefix + "current", above=0.0, maxval=REPLICAPE_MAX_CURRENT
+ )
self.stepper_dacs[channel] = cur / REPLICAPE_MAX_CURRENT
- self.pins[prefix + 'enable'] = (ReplicapeDACEnable, channel)
+ self.pins[prefix + "enable"] = (ReplicapeDACEnable, channel)
self.enabled_channels = {ch: False for cl, ch in self.pins.values()}
self.sr_disabled = list(reversed(shift_registers))
- if [i for i in [0, 1, 2] if 11+i in self.stepper_dacs]:
+ if [i for i in [0, 1, 2] if 11 + i in self.stepper_dacs]:
# Enable xyz steppers
shift_registers[0] &= ~1
- if [i for i in [3, 4] if 11+i in self.stepper_dacs]:
+ if [i for i in [3, 4] if 11 + i in self.stepper_dacs]:
# Enable eh steppers
shift_registers[3] &= ~1
- if (config.getboolean('standstill_power_down', False)
- and self.stepper_dacs):
+ if config.getboolean("standstill_power_down", False) and self.stepper_dacs:
shift_registers[4] &= ~1
self.sr_enabled = list(reversed(shift_registers))
sr_spi_bus = "spidev1.1"
if not self.host_mcu.is_fileoutput() and os.path.exists(
- '/sys/devices/platform/ocp/481a0000.spi/spi_master/spi2'):
+ "/sys/devices/platform/ocp/481a0000.spi/spi_master/spi2"
+ ):
sr_spi_bus = "spidev2.1"
self.sr_spi = bus.MCU_SPI(self.host_mcu, sr_spi_bus, None, 0, 50000000)
self.sr_spi.setup_shutdown_msg(self.sr_disabled)
self.sr_spi.spi_send(self.sr_disabled)
+
def note_pwm_start_value(self, channel, start_value, shutdown_value):
self.mcu_pwm_start_value |= not not start_value
self.mcu_pwm_shutdown_value |= not not shutdown_value
self.mcu_pwm_enable.setup_start_value(
- self.mcu_pwm_start_value, self.mcu_pwm_shutdown_value)
+ self.mcu_pwm_start_value, self.mcu_pwm_shutdown_value
+ )
self.enabled_channels[channel] = not not start_value
+
def note_pwm_enable(self, print_time, channel, is_enable):
self.enabled_channels[channel] = is_enable
# Check if need to set the pca9685 enable pin
@@ -238,8 +286,7 @@ class Replicape:
# Check if need to set the stepper enable lines
if channel not in self.stepper_dacs:
return
- on_dacs = [1 for c in self.stepper_dacs.keys()
- if self.enabled_channels[c]]
+ on_dacs = [1 for c in self.stepper_dacs.keys() if self.enabled_channels[c]]
if not on_dacs:
sr = self.sr_disabled
elif is_enable and len(on_dacs) == 1:
@@ -248,8 +295,9 @@ class Replicape:
return
clock = self.host_mcu.print_time_to_clock(print_time)
self.sr_spi.spi_send(sr, minclock=clock, reqclock=clock)
+
def setup_pin(self, pin_type, pin_params):
- pin = pin_params['pin']
+ pin = pin_params["pin"]
if pin in self.pins:
pclass, channel = self.pins[pin]
return pclass(self, channel, pin_type, pin_params)
@@ -262,5 +310,6 @@ class Replicape:
return servo_pwm(self, pin_params)
raise pins.error("Unknown replicape pin %s" % (pin,))
+
def load_config(config):
return Replicape(config)
diff --git a/klippy/extras/resonance_tester.py b/klippy/extras/resonance_tester.py
index ff32dcac..85dac5ee 100644
--- a/klippy/extras/resonance_tester.py
+++ b/klippy/extras/resonance_tester.py
@@ -6,6 +6,7 @@
import logging, math, os, time
from . import shaper_calibrate
+
class TestAxis:
def __init__(self, axis=None, vib_dir=None):
if axis is None:
@@ -13,84 +14,98 @@ class TestAxis:
else:
self._name = axis
if vib_dir is None:
- self._vib_dir = (1., 0.) if axis == 'x' else (0., 1.)
+ self._vib_dir = (1.0, 0.0) if axis == "x" else (0.0, 1.0)
else:
- s = math.sqrt(sum([d*d for d in vib_dir]))
+ s = math.sqrt(sum([d * d for d in vib_dir]))
self._vib_dir = [d / s for d in vib_dir]
+
def matches(self, chip_axis):
- if self._vib_dir[0] and 'x' in chip_axis:
+ if self._vib_dir[0] and "x" in chip_axis:
return True
- if self._vib_dir[1] and 'y' in chip_axis:
+ if self._vib_dir[1] and "y" in chip_axis:
return True
return False
+
def get_name(self):
return self._name
+
def get_point(self, l):
return (self._vib_dir[0] * l, self._vib_dir[1] * l)
+
def _parse_axis(gcmd, raw_axis):
if raw_axis is None:
return None
raw_axis = raw_axis.lower()
- if raw_axis in ['x', 'y']:
+ if raw_axis in ["x", "y"]:
return TestAxis(axis=raw_axis)
- dirs = raw_axis.split(',')
+ dirs = raw_axis.split(",")
if len(dirs) != 2:
raise gcmd.error("Invalid format of axis '%s'" % (raw_axis,))
try:
dir_x = float(dirs[0].strip())
dir_y = float(dirs[1].strip())
except:
- raise gcmd.error(
- "Unable to parse axis direction '%s'" % (raw_axis,))
+ raise gcmd.error("Unable to parse axis direction '%s'" % (raw_axis,))
return TestAxis(vib_dir=(dir_x, dir_y))
+
class VibrationPulseTestGenerator:
def __init__(self, config):
- self.min_freq = config.getfloat('min_freq', 5., minval=1.)
- self.max_freq = config.getfloat('max_freq', 135.,
- minval=self.min_freq, maxval=300.)
- self.accel_per_hz = config.getfloat('accel_per_hz', 60., above=0.)
- self.hz_per_sec = config.getfloat('hz_per_sec', 1.,
- minval=0.1, maxval=2.)
+ self.min_freq = config.getfloat("min_freq", 5.0, minval=1.0)
+ self.max_freq = config.getfloat(
+ "max_freq", 135.0, minval=self.min_freq, maxval=300.0
+ )
+ self.accel_per_hz = config.getfloat("accel_per_hz", 60.0, above=0.0)
+ self.hz_per_sec = config.getfloat("hz_per_sec", 1.0, minval=0.1, maxval=2.0)
+
def prepare_test(self, gcmd):
- self.freq_start = gcmd.get_float("FREQ_START", self.min_freq, minval=1.)
- self.freq_end = gcmd.get_float("FREQ_END", self.max_freq,
- minval=self.freq_start, maxval=300.)
- self.test_accel_per_hz = gcmd.get_float("ACCEL_PER_HZ",
- self.accel_per_hz, above=0.)
- self.test_hz_per_sec = gcmd.get_float("HZ_PER_SEC", self.hz_per_sec,
- above=0., maxval=2.)
+ self.freq_start = gcmd.get_float("FREQ_START", self.min_freq, minval=1.0)
+ self.freq_end = gcmd.get_float(
+ "FREQ_END", self.max_freq, minval=self.freq_start, maxval=300.0
+ )
+ self.test_accel_per_hz = gcmd.get_float(
+ "ACCEL_PER_HZ", self.accel_per_hz, above=0.0
+ )
+ self.test_hz_per_sec = gcmd.get_float(
+ "HZ_PER_SEC", self.hz_per_sec, above=0.0, maxval=2.0
+ )
+
def gen_test(self):
freq = self.freq_start
res = []
- sign = 1.
- time = 0.
+ sign = 1.0
+ time = 0.0
while freq <= self.freq_end + 0.000001:
- t_seg = .25 / freq
+ t_seg = 0.25 / freq
accel = self.test_accel_per_hz * freq
time += t_seg
res.append((time, sign * accel, freq))
time += t_seg
res.append((time, -sign * accel, freq))
- freq += 2. * t_seg * self.test_hz_per_sec
+ freq += 2.0 * t_seg * self.test_hz_per_sec
sign = -sign
return res
+
def get_max_freq(self):
return self.freq_end
+
class SweepingVibrationsTestGenerator:
def __init__(self, config):
self.vibration_generator = VibrationPulseTestGenerator(config)
- self.sweeping_accel = config.getfloat('sweeping_accel', 400., above=0.)
- self.sweeping_period = config.getfloat('sweeping_period', 1.2,
- minval=0.)
+ self.sweeping_accel = config.getfloat("sweeping_accel", 400.0, above=0.0)
+ self.sweeping_period = config.getfloat("sweeping_period", 1.2, minval=0.0)
+
def prepare_test(self, gcmd):
self.vibration_generator.prepare_test(gcmd)
self.test_sweeping_accel = gcmd.get_float(
- "SWEEPING_ACCEL", self.sweeping_accel, above=0.)
+ "SWEEPING_ACCEL", self.sweeping_accel, above=0.0
+ )
self.test_sweeping_period = gcmd.get_float(
- "SWEEPING_PERIOD", self.sweeping_period, minval=0.)
+ "SWEEPING_PERIOD", self.sweeping_period, minval=0.0
+ )
+
def gen_test(self):
test_seq = self.vibration_generator.gen_test()
accel_fraction = math.sqrt(2.0) * 0.125
@@ -98,11 +113,11 @@ class SweepingVibrationsTestGenerator:
t_rem = self.test_sweeping_period * accel_fraction
sweeping_accel = self.test_sweeping_accel
else:
- t_rem = float('inf')
- sweeping_accel = 0.
+ t_rem = float("inf")
+ sweeping_accel = 0.0
res = []
- last_t = 0.
- sig = 1.
+ last_t = 0.0
+ sig = 1.0
accel_fraction += 0.25
for next_t, accel, freq in test_seq:
t_seg = next_t - last_t
@@ -117,47 +132,51 @@ class SweepingVibrationsTestGenerator:
res.append((next_t, accel + sweeping_accel * sig, freq))
last_t = next_t
return res
+
def get_max_freq(self):
return self.vibration_generator.get_max_freq()
+
class ResonanceTestExecutor:
def __init__(self, config):
self.printer = config.get_printer()
- self.gcode = self.printer.lookup_object('gcode')
+ self.gcode = self.printer.lookup_object("gcode")
+
def run_test(self, test_seq, axis, gcmd):
reactor = self.printer.get_reactor()
- toolhead = self.printer.lookup_object('toolhead')
+ toolhead = self.printer.lookup_object("toolhead")
tpos = toolhead.get_position()
X, Y = tpos[:2]
# Override maximum acceleration and acceleration to
# deceleration based on the maximum test frequency
systime = reactor.monotonic()
toolhead_info = toolhead.get_status(systime)
- old_max_accel = toolhead_info['max_accel']
- old_minimum_cruise_ratio = toolhead_info['minimum_cruise_ratio']
+ old_max_accel = toolhead_info["max_accel"]
+ old_minimum_cruise_ratio = toolhead_info["minimum_cruise_ratio"]
max_accel = max([abs(a) for _, a, _ in test_seq])
self.gcode.run_script_from_command(
- "SET_VELOCITY_LIMIT ACCEL=%.3f MINIMUM_CRUISE_RATIO=0"
- % (max_accel,))
- input_shaper = self.printer.lookup_object('input_shaper', None)
- if input_shaper is not None and not gcmd.get_int('INPUT_SHAPING', 0):
+ "SET_VELOCITY_LIMIT ACCEL=%.3f MINIMUM_CRUISE_RATIO=0" % (max_accel,)
+ )
+ input_shaper = self.printer.lookup_object("input_shaper", None)
+ if input_shaper is not None and not gcmd.get_int("INPUT_SHAPING", 0):
input_shaper.disable_shaping()
gcmd.respond_info("Disabled [input_shaper] for resonance testing")
else:
input_shaper = None
- last_v = last_t = last_accel = last_freq = 0.
+ last_v = last_t = last_accel = last_freq = 0.0
for next_t, accel, freq in test_seq:
t_seg = next_t - last_t
- toolhead.cmd_M204(self.gcode.create_gcode_command(
- "M204", "M204", {"S": abs(accel)}))
+ toolhead.cmd_M204(
+ self.gcode.create_gcode_command("M204", "M204", {"S": abs(accel)})
+ )
v = last_v + accel * t_seg
abs_v = abs(v)
if abs_v < 0.000001:
- v = abs_v = 0.
+ v = abs_v = 0.0
abs_last_v = abs(last_v)
v2 = v * v
last_v2 = last_v * last_v
- half_inv_accel = .5 / accel
+ half_inv_accel = 0.5 / accel
d = (v2 - last_v2) * half_inv_accel
dX, dY = axis.get_point(d)
nX = X + dX
@@ -180,58 +199,77 @@ class ResonanceTestExecutor:
last_accel = accel
last_freq = freq
if last_v:
- d_decel = -.5 * last_v2 / old_max_accel
+ d_decel = -0.5 * last_v2 / old_max_accel
decel_X, decel_Y = axis.get_point(d_decel)
- toolhead.cmd_M204(self.gcode.create_gcode_command(
- "M204", "M204", {"S": old_max_accel}))
+ toolhead.cmd_M204(
+ self.gcode.create_gcode_command("M204", "M204", {"S": old_max_accel})
+ )
toolhead.move([X + decel_X, Y + decel_Y] + tpos[2:], abs(last_v))
# Restore the original acceleration values
self.gcode.run_script_from_command(
"SET_VELOCITY_LIMIT ACCEL=%.3f MINIMUM_CRUISE_RATIO=%.3f"
- % (old_max_accel, old_minimum_cruise_ratio))
+ % (old_max_accel, old_minimum_cruise_ratio)
+ )
# Restore input shaper if it was disabled for resonance testing
if input_shaper is not None:
input_shaper.enable_shaping()
gcmd.respond_info("Re-enabled [input_shaper]")
+
class ResonanceTester:
def __init__(self, config):
self.printer = config.get_printer()
- self.move_speed = config.getfloat('move_speed', 50., above=0.)
+ self.move_speed = config.getfloat("move_speed", 50.0, above=0.0)
self.generator = SweepingVibrationsTestGenerator(config)
self.executor = ResonanceTestExecutor(config)
- if not config.get('accel_chip_x', None):
- self.accel_chip_names = [('xy', config.get('accel_chip').strip())]
+ if not config.get("accel_chip_x", None):
+ self.accel_chip_names = [("xy", config.get("accel_chip").strip())]
else:
self.accel_chip_names = [
- ('x', config.get('accel_chip_x').strip()),
- ('y', config.get('accel_chip_y').strip())]
+ ("x", config.get("accel_chip_x").strip()),
+ ("y", config.get("accel_chip_y").strip()),
+ ]
if self.accel_chip_names[0][1] == self.accel_chip_names[1][1]:
- self.accel_chip_names = [('xy', self.accel_chip_names[0][1])]
- self.max_smoothing = config.getfloat('max_smoothing', None, minval=0.05)
- self.probe_points = config.getlists('probe_points', seps=(',', '\n'),
- parser=float, count=3)
+ self.accel_chip_names = [("xy", self.accel_chip_names[0][1])]
+ self.max_smoothing = config.getfloat("max_smoothing", None, minval=0.05)
+ self.probe_points = config.getlists(
+ "probe_points", seps=(",", "\n"), parser=float, count=3
+ )
- self.gcode = self.printer.lookup_object('gcode')
- self.gcode.register_command("MEASURE_AXES_NOISE",
- self.cmd_MEASURE_AXES_NOISE,
- desc=self.cmd_MEASURE_AXES_NOISE_help)
- self.gcode.register_command("TEST_RESONANCES",
- self.cmd_TEST_RESONANCES,
- desc=self.cmd_TEST_RESONANCES_help)
- self.gcode.register_command("SHAPER_CALIBRATE",
- self.cmd_SHAPER_CALIBRATE,
- desc=self.cmd_SHAPER_CALIBRATE_help)
+ self.gcode = self.printer.lookup_object("gcode")
+ self.gcode.register_command(
+ "MEASURE_AXES_NOISE",
+ self.cmd_MEASURE_AXES_NOISE,
+ desc=self.cmd_MEASURE_AXES_NOISE_help,
+ )
+ self.gcode.register_command(
+ "TEST_RESONANCES",
+ self.cmd_TEST_RESONANCES,
+ desc=self.cmd_TEST_RESONANCES_help,
+ )
+ self.gcode.register_command(
+ "SHAPER_CALIBRATE",
+ self.cmd_SHAPER_CALIBRATE,
+ desc=self.cmd_SHAPER_CALIBRATE_help,
+ )
self.printer.register_event_handler("klippy:connect", self.connect)
def connect(self):
self.accel_chips = [
- (chip_axis, self.printer.lookup_object(chip_name))
- for chip_axis, chip_name in self.accel_chip_names]
+ (chip_axis, self.printer.lookup_object(chip_name))
+ for chip_axis, chip_name in self.accel_chip_names
+ ]
- def _run_test(self, gcmd, axes, helper, raw_name_suffix=None,
- accel_chips=None, test_point=None):
- toolhead = self.printer.lookup_object('toolhead')
+ def _run_test(
+ self,
+ gcmd,
+ axes,
+ helper,
+ raw_name_suffix=None,
+ accel_chips=None,
+ test_point=None,
+ ):
+ toolhead = self.printer.lookup_object("toolhead")
calibration_data = {axis: None for axis in axes}
self.generator.prepare_test(gcmd)
@@ -241,8 +279,7 @@ class ResonanceTester:
for point in test_points:
toolhead.manual_move(point, self.move_speed)
if len(test_points) > 1 or test_point is not None:
- gcmd.respond_info(
- "Probing point (%.3f, %.3f, %.3f)" % tuple(point))
+ gcmd.respond_info("Probing point (%.3f, %.3f, %.3f)" % tuple(point))
for axis in axes:
toolhead.wait_moves()
toolhead.dwell(0.500)
@@ -267,35 +304,42 @@ class ResonanceTester:
aclient.finish_measurements()
if raw_name_suffix is not None:
raw_name = self.get_filename(
- 'raw_data', raw_name_suffix, axis,
- point if len(test_points) > 1 else None,
- chip_name if accel_chips is not None else None,)
+ "raw_data",
+ raw_name_suffix,
+ axis,
+ point if len(test_points) > 1 else None,
+ chip_name if accel_chips is not None else None,
+ )
aclient.write_to_file(raw_name)
gcmd.respond_info(
- "Writing raw accelerometer data to "
- "%s file" % (raw_name,))
+ "Writing raw accelerometer data to " "%s file" % (raw_name,)
+ )
if helper is None:
continue
for chip_axis, aclient, chip_name in raw_values:
if not aclient.has_valid_samples():
raise gcmd.error(
- "accelerometer '%s' measured no data" % (
- chip_name,))
+ "accelerometer '%s' measured no data" % (chip_name,)
+ )
new_data = helper.process_accelerometer_data(aclient)
if calibration_data[axis] is None:
calibration_data[axis] = new_data
else:
calibration_data[axis].add_data(new_data)
return calibration_data
+
def _parse_chips(self, accel_chips):
parsed_chips = []
- for chip_name in accel_chips.split(','):
+ for chip_name in accel_chips.split(","):
chip = self.printer.lookup_object(chip_name.strip())
parsed_chips.append(chip)
return parsed_chips
+
def _get_max_calibration_freq(self):
return 1.5 * self.generator.get_max_freq()
- cmd_TEST_RESONANCES_help = ("Runs the resonance test for a specified axis")
+
+ cmd_TEST_RESONANCES_help = "Runs the resonance test for a specified axis"
+
def cmd_TEST_RESONANCES(self, gcmd):
# Parse parameters
axis = _parse_axis(gcmd, gcmd.get("AXIS").lower())
@@ -303,30 +347,36 @@ class ResonanceTester:
test_point = gcmd.get("POINT", None)
if test_point:
- test_coords = test_point.split(',')
+ test_coords = test_point.split(",")
if len(test_coords) != 3:
raise gcmd.error("Invalid POINT parameter, must be 'x,y,z'")
try:
test_point = [float(p.strip()) for p in test_coords]
except ValueError:
- raise gcmd.error("Invalid POINT parameter, must be 'x,y,z'"
- " where x, y and z are valid floating point numbers")
+ raise gcmd.error(
+ "Invalid POINT parameter, must be 'x,y,z'"
+ " where x, y and z are valid floating point numbers"
+ )
accel_chips = self._parse_chips(chips_str) if chips_str else None
- outputs = gcmd.get("OUTPUT", "resonances").lower().split(',')
+ outputs = gcmd.get("OUTPUT", "resonances").lower().split(",")
for output in outputs:
- if output not in ['resonances', 'raw_data']:
- raise gcmd.error("Unsupported output '%s', only 'resonances'"
- " and 'raw_data' are supported" % (output,))
+ if output not in ["resonances", "raw_data"]:
+ raise gcmd.error(
+ "Unsupported output '%s', only 'resonances'"
+ " and 'raw_data' are supported" % (output,)
+ )
if not outputs:
- raise gcmd.error("No output specified, at least one of 'resonances'"
- " or 'raw_data' must be set in OUTPUT parameter")
+ raise gcmd.error(
+ "No output specified, at least one of 'resonances'"
+ " or 'raw_data' must be set in OUTPUT parameter"
+ )
name_suffix = gcmd.get("NAME", time.strftime("%Y%m%d_%H%M%S"))
if not self.is_valid_name_suffix(name_suffix):
raise gcmd.error("Invalid NAME parameter")
- csv_output = 'resonances' in outputs
- raw_output = 'raw_data' in outputs
+ csv_output = "resonances" in outputs
+ raw_output = "raw_data" in outputs
# Setup calculation of resonances
if csv_output:
@@ -335,121 +385,161 @@ class ResonanceTester:
helper = None
data = self._run_test(
- gcmd, [axis], helper,
- raw_name_suffix=name_suffix if raw_output else None,
- accel_chips=accel_chips, test_point=test_point)[axis]
+ gcmd,
+ [axis],
+ helper,
+ raw_name_suffix=name_suffix if raw_output else None,
+ accel_chips=accel_chips,
+ test_point=test_point,
+ )[axis]
if csv_output:
csv_name = self.save_calibration_data(
- 'resonances', name_suffix, helper, axis, data,
- point=test_point, max_freq=self._get_max_calibration_freq())
- gcmd.respond_info(
- "Resonances data written to %s file" % (csv_name,))
+ "resonances",
+ name_suffix,
+ helper,
+ axis,
+ data,
+ point=test_point,
+ max_freq=self._get_max_calibration_freq(),
+ )
+ gcmd.respond_info("Resonances data written to %s file" % (csv_name,))
+
cmd_SHAPER_CALIBRATE_help = (
- "Similar to TEST_RESONANCES but suggest input shaper config")
+ "Similar to TEST_RESONANCES but suggest input shaper config"
+ )
+
def cmd_SHAPER_CALIBRATE(self, gcmd):
# Parse parameters
axis = gcmd.get("AXIS", None)
if not axis:
- calibrate_axes = [TestAxis('x'), TestAxis('y')]
- elif axis.lower() not in 'xy':
+ calibrate_axes = [TestAxis("x"), TestAxis("y")]
+ elif axis.lower() not in "xy":
raise gcmd.error("Unsupported axis '%s'" % (axis,))
else:
calibrate_axes = [TestAxis(axis.lower())]
chips_str = gcmd.get("CHIPS", None)
accel_chips = self._parse_chips(chips_str) if chips_str else None
- max_smoothing = gcmd.get_float(
- "MAX_SMOOTHING", self.max_smoothing, minval=0.05)
+ max_smoothing = gcmd.get_float("MAX_SMOOTHING", self.max_smoothing, minval=0.05)
name_suffix = gcmd.get("NAME", time.strftime("%Y%m%d_%H%M%S"))
if not self.is_valid_name_suffix(name_suffix):
raise gcmd.error("Invalid NAME parameter")
- input_shaper = self.printer.lookup_object('input_shaper', None)
+ input_shaper = self.printer.lookup_object("input_shaper", None)
# Setup shaper calibration
helper = shaper_calibrate.ShaperCalibrate(self.printer)
- calibration_data = self._run_test(gcmd, calibrate_axes, helper,
- accel_chips=accel_chips)
+ calibration_data = self._run_test(
+ gcmd, calibrate_axes, helper, accel_chips=accel_chips
+ )
- configfile = self.printer.lookup_object('configfile')
+ configfile = self.printer.lookup_object("configfile")
for axis in calibrate_axes:
axis_name = axis.get_name()
gcmd.respond_info(
- "Calculating the best input shaper parameters for %s axis"
- % (axis_name,))
+ "Calculating the best input shaper parameters for %s axis"
+ % (axis_name,)
+ )
calibration_data[axis].normalize_to_frequencies()
systime = self.printer.get_reactor().monotonic()
- toolhead = self.printer.lookup_object('toolhead')
+ toolhead = self.printer.lookup_object("toolhead")
toolhead_info = toolhead.get_status(systime)
- scv = toolhead_info['square_corner_velocity']
+ scv = toolhead_info["square_corner_velocity"]
max_freq = self._get_max_calibration_freq()
best_shaper, all_shapers = helper.find_best_shaper(
- calibration_data[axis], max_smoothing=max_smoothing,
- scv=scv, max_freq=max_freq, logger=gcmd.respond_info)
+ calibration_data[axis],
+ max_smoothing=max_smoothing,
+ scv=scv,
+ max_freq=max_freq,
+ logger=gcmd.respond_info,
+ )
gcmd.respond_info(
- "Recommended shaper_type_%s = %s, shaper_freq_%s = %.1f Hz"
- % (axis_name, best_shaper.name,
- axis_name, best_shaper.freq))
+ "Recommended shaper_type_%s = %s, shaper_freq_%s = %.1f Hz"
+ % (axis_name, best_shaper.name, axis_name, best_shaper.freq)
+ )
if input_shaper is not None:
- helper.apply_params(input_shaper, axis_name,
- best_shaper.name, best_shaper.freq)
- helper.save_params(configfile, axis_name,
- best_shaper.name, best_shaper.freq)
+ helper.apply_params(
+ input_shaper, axis_name, best_shaper.name, best_shaper.freq
+ )
+ helper.save_params(
+ configfile, axis_name, best_shaper.name, best_shaper.freq
+ )
csv_name = self.save_calibration_data(
- 'calibration_data', name_suffix, helper, axis,
- calibration_data[axis], all_shapers, max_freq=max_freq)
+ "calibration_data",
+ name_suffix,
+ helper,
+ axis,
+ calibration_data[axis],
+ all_shapers,
+ max_freq=max_freq,
+ )
gcmd.respond_info(
- "Shaper calibration data written to %s file" % (csv_name,))
+ "Shaper calibration data written to %s file" % (csv_name,)
+ )
gcmd.respond_info(
"The SAVE_CONFIG command will update the printer config file\n"
- "with these parameters and restart the printer.")
- cmd_MEASURE_AXES_NOISE_help = (
- "Measures noise of all enabled accelerometer chips")
+ "with these parameters and restart the printer."
+ )
+
+ cmd_MEASURE_AXES_NOISE_help = "Measures noise of all enabled accelerometer chips"
+
def cmd_MEASURE_AXES_NOISE(self, gcmd):
- meas_time = gcmd.get_float("MEAS_TIME", 2.)
- raw_values = [(chip_axis, chip.start_internal_client())
- for chip_axis, chip in self.accel_chips]
- self.printer.lookup_object('toolhead').dwell(meas_time)
+ meas_time = gcmd.get_float("MEAS_TIME", 2.0)
+ raw_values = [
+ (chip_axis, chip.start_internal_client())
+ for chip_axis, chip in self.accel_chips
+ ]
+ self.printer.lookup_object("toolhead").dwell(meas_time)
for chip_axis, aclient in raw_values:
aclient.finish_measurements()
helper = shaper_calibrate.ShaperCalibrate(self.printer)
for chip_axis, aclient in raw_values:
if not aclient.has_valid_samples():
raise gcmd.error(
- "%s-axis accelerometer measured no data" % (
- chip_axis,))
+ "%s-axis accelerometer measured no data" % (chip_axis,)
+ )
data = helper.process_accelerometer_data(aclient)
vx = data.psd_x.mean()
vy = data.psd_y.mean()
vz = data.psd_z.mean()
- gcmd.respond_info("Axes noise for %s-axis accelerometer: "
- "%.6f (x), %.6f (y), %.6f (z)" % (
- chip_axis, vx, vy, vz))
+ gcmd.respond_info(
+ "Axes noise for %s-axis accelerometer: "
+ "%.6f (x), %.6f (y), %.6f (z)" % (chip_axis, vx, vy, vz)
+ )
def is_valid_name_suffix(self, name_suffix):
- return name_suffix.replace('-', '').replace('_', '').isalnum()
+ return name_suffix.replace("-", "").replace("_", "").isalnum()
- def get_filename(self, base, name_suffix, axis=None,
- point=None, chip_name=None):
+ def get_filename(self, base, name_suffix, axis=None, point=None, chip_name=None):
name = base
if axis:
- name += '_' + axis.get_name()
+ name += "_" + axis.get_name()
if chip_name:
- name += '_' + chip_name.replace(" ", "_")
+ name += "_" + chip_name.replace(" ", "_")
if point:
name += "_%.3f_%.3f_%.3f" % (point[0], point[1], point[2])
- name += '_' + name_suffix
+ name += "_" + name_suffix
return os.path.join("/tmp", name + ".csv")
- def save_calibration_data(self, base_name, name_suffix, shaper_calibrate,
- axis, calibration_data,
- all_shapers=None, point=None, max_freq=None):
+ def save_calibration_data(
+ self,
+ base_name,
+ name_suffix,
+ shaper_calibrate,
+ axis,
+ calibration_data,
+ all_shapers=None,
+ point=None,
+ max_freq=None,
+ ):
output = self.get_filename(base_name, name_suffix, axis, point)
- shaper_calibrate.save_calibration_data(output, calibration_data,
- all_shapers, max_freq)
+ shaper_calibrate.save_calibration_data(
+ output, calibration_data, all_shapers, max_freq
+ )
return output
+
def load_config(config):
return ResonanceTester(config)
diff --git a/klippy/extras/respond.py b/klippy/extras/respond.py
index 047abb77..df2ffd0c 100644
--- a/klippy/extras/respond.py
+++ b/klippy/extras/respond.py
@@ -5,51 +5,57 @@
# This file may be distributed under the terms of the GNU GPLv3 license.
respond_types = {
- 'echo': 'echo:',
- 'command': '//',
- 'error' : '!!',
+ "echo": "echo:",
+ "command": "//",
+ "error": "!!",
}
respond_types_no_space = {
- 'echo_no_space': 'echo:',
+ "echo_no_space": "echo:",
}
+
class HostResponder:
def __init__(self, config):
self.printer = config.get_printer()
self.reactor = self.printer.get_reactor()
- self.default_prefix = config.getchoice('default_type', respond_types,
- 'echo')
- self.default_prefix = config.get('default_prefix', self.default_prefix)
- gcode = self.printer.lookup_object('gcode')
- gcode.register_command('M118', self.cmd_M118, True)
- gcode.register_command('RESPOND', self.cmd_RESPOND, True,
- desc=self.cmd_RESPOND_help)
+ self.default_prefix = config.getchoice("default_type", respond_types, "echo")
+ self.default_prefix = config.get("default_prefix", self.default_prefix)
+ gcode = self.printer.lookup_object("gcode")
+ gcode.register_command("M118", self.cmd_M118, True)
+ gcode.register_command(
+ "RESPOND", self.cmd_RESPOND, True, desc=self.cmd_RESPOND_help
+ )
+
def cmd_M118(self, gcmd):
msg = gcmd.get_raw_command_parameters()
gcmd.respond_raw("%s %s" % (self.default_prefix, msg))
- cmd_RESPOND_help = ("Echo the message prepended with a prefix")
+
+ cmd_RESPOND_help = "Echo the message prepended with a prefix"
+
def cmd_RESPOND(self, gcmd):
no_space = False
- respond_type = gcmd.get('TYPE', None)
+ respond_type = gcmd.get("TYPE", None)
prefix = self.default_prefix
- if(respond_type != None):
+ if respond_type != None:
respond_type = respond_type.lower()
- if(respond_type in respond_types):
+ if respond_type in respond_types:
prefix = respond_types[respond_type]
- elif(respond_type in respond_types_no_space):
+ elif respond_type in respond_types_no_space:
prefix = respond_types_no_space[respond_type]
no_space = True
else:
raise gcmd.error(
"RESPOND TYPE '%s' is invalid. Must be one"
- " of 'echo', 'command', or 'error'" % (respond_type,))
- prefix = gcmd.get('PREFIX', prefix)
- msg = gcmd.get('MSG', '')
- if(no_space):
+ " of 'echo', 'command', or 'error'" % (respond_type,)
+ )
+ prefix = gcmd.get("PREFIX", prefix)
+ msg = gcmd.get("MSG", "")
+ if no_space:
gcmd.respond_raw("%s%s" % (prefix, msg))
else:
gcmd.respond_raw("%s %s" % (prefix, msg))
+
def load_config(config):
return HostResponder(config)
diff --git a/klippy/extras/safe_z_home.py b/klippy/extras/safe_z_home.py
index 64b1ab00..3ee75760 100644
--- a/klippy/extras/safe_z_home.py
+++ b/klippy/extras/safe_z_home.py
@@ -5,30 +5,32 @@
# This file may be distributed under the terms of the GNU GPLv3 license.
from . import manual_probe
+
class SafeZHoming:
def __init__(self, config):
self.printer = config.get_printer()
x_pos, y_pos = config.getfloatlist("home_xy_position", count=2)
self.home_x_pos, self.home_y_pos = x_pos, y_pos
self.z_hop = config.getfloat("z_hop", default=0.0)
- self.z_hop_speed = config.getfloat('z_hop_speed', 15., above=0.)
+ self.z_hop_speed = config.getfloat("z_hop_speed", 15.0, above=0.0)
zconfig = manual_probe.lookup_z_endstop_config(config)
if zconfig is None:
- raise config.error('Missing Z endstop config for safe_z_homing')
- self.max_z = zconfig.getfloat('position_max', note_valid=False)
- self.speed = config.getfloat('speed', 50.0, above=0.)
- self.move_to_previous = config.getboolean('move_to_previous', False)
- self.printer.load_object(config, 'homing')
- self.gcode = self.printer.lookup_object('gcode')
+ raise config.error("Missing Z endstop config for safe_z_homing")
+ self.max_z = zconfig.getfloat("position_max", note_valid=False)
+ self.speed = config.getfloat("speed", 50.0, above=0.0)
+ self.move_to_previous = config.getboolean("move_to_previous", False)
+ self.printer.load_object(config, "homing")
+ self.gcode = self.printer.lookup_object("gcode")
self.prev_G28 = self.gcode.register_command("G28", None)
self.gcode.register_command("G28", self.cmd_G28)
if config.has_section("homing_override"):
- raise config.error("homing_override and safe_z_homing cannot"
- +" be used simultaneously")
+ raise config.error(
+ "homing_override and safe_z_homing cannot" + " be used simultaneously"
+ )
def cmd_G28(self, gcmd):
- toolhead = self.printer.lookup_object('toolhead')
+ toolhead = self.printer.lookup_object("toolhead")
# Perform Z Hop if necessary
if self.z_hop != 0.0:
@@ -37,30 +39,27 @@ class SafeZHoming:
kin_status = toolhead.get_kinematics().get_status(curtime)
pos = toolhead.get_position()
- if 'z' not in kin_status['homed_axes']:
+ if "z" not in kin_status["homed_axes"]:
# Always perform the z_hop if the Z axis is not homed
pos[2] = 0
toolhead.set_position(pos, homing_axes="z")
- toolhead.manual_move([None, None, self.z_hop],
- self.z_hop_speed)
+ toolhead.manual_move([None, None, self.z_hop], self.z_hop_speed)
toolhead.get_kinematics().clear_homing_state("z")
elif pos[2] < self.z_hop:
# If the Z axis is homed, and below z_hop, lift it to z_hop
- toolhead.manual_move([None, None, self.z_hop],
- self.z_hop_speed)
+ toolhead.manual_move([None, None, self.z_hop], self.z_hop_speed)
# Determine which axes we need to home
- need_x, need_y, need_z = [gcmd.get(axis, None) is not None
- for axis in "XYZ"]
+ need_x, need_y, need_z = [gcmd.get(axis, None) is not None for axis in "XYZ"]
if not need_x and not need_y and not need_z:
need_x = need_y = need_z = True
# Home XY axes if necessary
new_params = {}
if need_x:
- new_params['X'] = '0'
+ new_params["X"] = "0"
if need_y:
- new_params['Y'] = '0'
+ new_params["Y"] = "0"
if new_params:
g28_gcmd = self.gcode.create_gcode_command("G28", "G28", new_params)
self.prev_G28(g28_gcmd)
@@ -70,24 +69,26 @@ class SafeZHoming:
# Throw an error if X or Y are not homed
curtime = self.printer.get_reactor().monotonic()
kin_status = toolhead.get_kinematics().get_status(curtime)
- if ('x' not in kin_status['homed_axes'] or
- 'y' not in kin_status['homed_axes']):
+ if (
+ "x" not in kin_status["homed_axes"]
+ or "y" not in kin_status["homed_axes"]
+ ):
raise gcmd.error("Must home X and Y axes first")
# Move to safe XY homing position
prevpos = toolhead.get_position()
toolhead.manual_move([self.home_x_pos, self.home_y_pos], self.speed)
# Home Z
- g28_gcmd = self.gcode.create_gcode_command("G28", "G28", {'Z': '0'})
+ g28_gcmd = self.gcode.create_gcode_command("G28", "G28", {"Z": "0"})
self.prev_G28(g28_gcmd)
# Perform Z Hop again for pressure-based probes
if self.z_hop:
pos = toolhead.get_position()
if pos[2] < self.z_hop:
- toolhead.manual_move([None, None, self.z_hop],
- self.z_hop_speed)
+ toolhead.manual_move([None, None, self.z_hop], self.z_hop_speed)
# Move XY back to previous positions
if self.move_to_previous:
toolhead.manual_move(prevpos[:2], self.speed)
+
def load_config(config):
return SafeZHoming(config)
diff --git a/klippy/extras/samd_sercom.py b/klippy/extras/samd_sercom.py
index 44a9263c..0f17b78b 100644
--- a/klippy/extras/samd_sercom.py
+++ b/klippy/extras/samd_sercom.py
@@ -4,6 +4,7 @@
#
# This file may be distributed under the terms of the GNU GPLv3 license.
+
class SamdSERCOM:
def __init__(self, config):
self.printer = config.get_printer()
@@ -15,27 +16,33 @@ class SamdSERCOM:
ppins = self.printer.lookup_object("pins")
tx_pin_params = ppins.lookup_pin(self.tx_pin)
- self.mcu = tx_pin_params['chip']
+ self.mcu = tx_pin_params["chip"]
self.mcu.add_config_cmd(
- "set_sercom_pin bus=%s sercom_pin_type=tx pin=%s" % (
- self.sercom, tx_pin_params['pin']))
+ "set_sercom_pin bus=%s sercom_pin_type=tx pin=%s"
+ % (self.sercom, tx_pin_params["pin"])
+ )
clk_pin_params = ppins.lookup_pin(self.clk_pin)
- if self.mcu is not clk_pin_params['chip']:
- raise ppins.error("%s: SERCOM pins must be on same mcu" % (
- config.get_name(),))
+ if self.mcu is not clk_pin_params["chip"]:
+ raise ppins.error(
+ "%s: SERCOM pins must be on same mcu" % (config.get_name(),)
+ )
self.mcu.add_config_cmd(
- "set_sercom_pin bus=%s sercom_pin_type=clk pin=%s" % (
- self.sercom, clk_pin_params['pin']))
+ "set_sercom_pin bus=%s sercom_pin_type=clk pin=%s"
+ % (self.sercom, clk_pin_params["pin"])
+ )
if self.rx_pin:
rx_pin_params = ppins.lookup_pin(self.rx_pin)
- if self.mcu is not rx_pin_params['chip']:
- raise ppins.error("%s: SERCOM pins must be on same mcu" % (
- config.get_name(),))
+ if self.mcu is not rx_pin_params["chip"]:
+ raise ppins.error(
+ "%s: SERCOM pins must be on same mcu" % (config.get_name(),)
+ )
self.mcu.add_config_cmd(
- "set_sercom_pin bus=%s sercom_pin_type=rx pin=%s" % (
- self.sercom, rx_pin_params['pin']))
+ "set_sercom_pin bus=%s sercom_pin_type=rx pin=%s"
+ % (self.sercom, rx_pin_params["pin"])
+ )
+
def load_config_prefix(config):
return SamdSERCOM(config)
diff --git a/klippy/extras/save_variables.py b/klippy/extras/save_variables.py
index 2e405657..043f1c6d 100644
--- a/klippy/extras/save_variables.py
+++ b/klippy/extras/save_variables.py
@@ -6,10 +6,11 @@
# This file may be distributed under the terms of the GNU GPLv3 license.
import os, logging, ast, configparser
+
class SaveVariables:
def __init__(self, config):
self.printer = config.get_printer()
- self.filename = os.path.expanduser(config.get('filename'))
+ self.filename = os.path.expanduser(config.get("filename"))
self.allVariables = {}
try:
if not os.path.exists(self.filename):
@@ -17,28 +18,32 @@ class SaveVariables:
self.loadVariables()
except self.printer.command_error as e:
raise config.error(str(e))
- gcode = self.printer.lookup_object('gcode')
- gcode.register_command('SAVE_VARIABLE', self.cmd_SAVE_VARIABLE,
- desc=self.cmd_SAVE_VARIABLE_help)
+ gcode = self.printer.lookup_object("gcode")
+ gcode.register_command(
+ "SAVE_VARIABLE", self.cmd_SAVE_VARIABLE, desc=self.cmd_SAVE_VARIABLE_help
+ )
+
def loadVariables(self):
allvars = {}
varfile = configparser.ConfigParser()
try:
varfile.read(self.filename)
- if varfile.has_section('Variables'):
- for name, val in varfile.items('Variables'):
+ if varfile.has_section("Variables"):
+ for name, val in varfile.items("Variables"):
allvars[name] = ast.literal_eval(val)
except:
msg = "Unable to parse existing variable file"
logging.exception(msg)
raise self.printer.command_error(msg)
self.allVariables = allvars
+
cmd_SAVE_VARIABLE_help = "Save arbitrary variables to disk"
+
def cmd_SAVE_VARIABLE(self, gcmd):
- varname = gcmd.get('VARIABLE')
- if (varname.lower() != varname):
+ varname = gcmd.get("VARIABLE")
+ if varname.lower() != varname:
raise gcmd.error("VARIABLE must not contain upper case")
- value = gcmd.get('VALUE')
+ value = gcmd.get("VALUE")
try:
value = ast.literal_eval(value)
except ValueError as e:
@@ -47,9 +52,9 @@ class SaveVariables:
newvars[varname] = value
# Write file
varfile = configparser.ConfigParser()
- varfile.add_section('Variables')
+ varfile.add_section("Variables")
for name, val in sorted(newvars.items()):
- varfile.set('Variables', name, repr(val))
+ varfile.set("Variables", name, repr(val))
try:
f = open(self.filename, "w")
varfile.write(f)
@@ -59,8 +64,10 @@ class SaveVariables:
logging.exception(msg)
raise gcmd.error(msg)
self.loadVariables()
+
def get_status(self, eventtime):
- return {'variables': self.allVariables}
+ return {"variables": self.allVariables}
+
def load_config(config):
return SaveVariables(config)
diff --git a/klippy/extras/screws_tilt_adjust.py b/klippy/extras/screws_tilt_adjust.py
index b988c7ce..6dacde87 100644
--- a/klippy/extras/screws_tilt_adjust.py
+++ b/klippy/extras/screws_tilt_adjust.py
@@ -7,6 +7,7 @@
import math
from . import probe
+
class ScrewsTiltAdjust:
def __init__(self, config):
self.printer = config.get_printer()
@@ -24,26 +25,37 @@ class ScrewsTiltAdjust:
screw_name = config.get(prefix + "_name", screw_name)
self.screws.append((screw_coord, screw_name))
if len(self.screws) < 3:
- raise config.error("screws_tilt_adjust: Must have "
- "at least three screws")
- self.threads = {'CW-M3': 0, 'CCW-M3': 1, 'CW-M4': 2, 'CCW-M4': 3,
- 'CW-M5': 4, 'CCW-M5': 5, 'CW-M6': 6, 'CCW-M6': 7}
- self.thread = config.getchoice('screw_thread', self.threads,
- default='CW-M3')
+ raise config.error("screws_tilt_adjust: Must have " "at least three screws")
+ self.threads = {
+ "CW-M3": 0,
+ "CCW-M3": 1,
+ "CW-M4": 2,
+ "CCW-M4": 3,
+ "CW-M5": 4,
+ "CCW-M5": 5,
+ "CW-M6": 6,
+ "CCW-M6": 7,
+ }
+ self.thread = config.getchoice("screw_thread", self.threads, default="CW-M3")
# Initialize ProbePointsHelper
points = [coord for coord, name in self.screws]
- self.probe_helper = probe.ProbePointsHelper(config,
- self.probe_finalize,
- default_points=points)
+ self.probe_helper = probe.ProbePointsHelper(
+ config, self.probe_finalize, default_points=points
+ )
self.probe_helper.minimum_points(3)
# Register command
- self.gcode = self.printer.lookup_object('gcode')
- self.gcode.register_command("SCREWS_TILT_CALCULATE",
- self.cmd_SCREWS_TILT_CALCULATE,
- desc=self.cmd_SCREWS_TILT_CALCULATE_help)
- cmd_SCREWS_TILT_CALCULATE_help = "Tool to help adjust bed leveling " \
- "screws by calculating the number " \
- "of turns to level it."
+ self.gcode = self.printer.lookup_object("gcode")
+ self.gcode.register_command(
+ "SCREWS_TILT_CALCULATE",
+ self.cmd_SCREWS_TILT_CALCULATE,
+ desc=self.cmd_SCREWS_TILT_CALCULATE_help,
+ )
+
+ cmd_SCREWS_TILT_CALCULATE_help = (
+ "Tool to help adjust bed leveling "
+ "screws by calculating the number "
+ "of turns to level it."
+ )
def cmd_SCREWS_TILT_CALCULATE(self, gcmd):
self.max_diff = gcmd.get_float("MAX_DEVIATION", None)
@@ -51,52 +63,72 @@ class ScrewsTiltAdjust:
direction = gcmd.get("DIRECTION", default=None)
if direction is not None:
direction = direction.upper()
- if direction not in ('CW', 'CCW'):
+ if direction not in ("CW", "CCW"):
raise gcmd.error(
- "Error on '%s': DIRECTION must be either CW or CCW" % (
- gcmd.get_commandline(),))
+ "Error on '%s': DIRECTION must be either CW or CCW"
+ % (gcmd.get_commandline(),)
+ )
self.direction = direction
self.probe_helper.start_probe(gcmd)
def get_status(self, eventtime):
- return {'error': self.max_diff_error,
- 'max_deviation': self.max_diff,
- 'results': self.results}
+ return {
+ "error": self.max_diff_error,
+ "max_deviation": self.max_diff,
+ "results": self.results,
+ }
def probe_finalize(self, offsets, positions):
self.results = {}
self.max_diff_error = False
# Factors used for CW-M3, CCW-M3, CW-M4, CCW-M4, CW-M5, CCW-M5, CW-M6
- #and CCW-M6
- threads_factor = {0: 0.5, 1: 0.5, 2: 0.7, 3: 0.7, 4: 0.8, 5: 0.8,
- 6: 1.0, 7: 1.0}
+ # and CCW-M6
+ threads_factor = {
+ 0: 0.5,
+ 1: 0.5,
+ 2: 0.7,
+ 3: 0.7,
+ 4: 0.8,
+ 5: 0.8,
+ 6: 1.0,
+ 7: 1.0,
+ }
is_clockwise_thread = (self.thread & 1) == 0
screw_diff = []
# Process the read Z values
if self.direction is not None:
# Lowest or highest screw is the base position used for comparison
- use_max = ((is_clockwise_thread and self.direction == 'CW')
- or (not is_clockwise_thread and self.direction == 'CCW'))
+ use_max = (is_clockwise_thread and self.direction == "CW") or (
+ not is_clockwise_thread and self.direction == "CCW"
+ )
min_or_max = max if use_max else min
i_base, z_base = min_or_max(
- enumerate([pos[2] for pos in positions]), key=lambda v: v[1])
+ enumerate([pos[2] for pos in positions]), key=lambda v: v[1]
+ )
else:
# First screw is the base position used for comparison
i_base, z_base = 0, positions[0][2]
# Provide the user some information on how to read the results
- self.gcode.respond_info("01:20 means 1 full turn and 20 minutes, "
- "CW=clockwise, CCW=counter-clockwise")
+ self.gcode.respond_info(
+ "01:20 means 1 full turn and 20 minutes, "
+ "CW=clockwise, CCW=counter-clockwise"
+ )
for i, screw in enumerate(self.screws):
z = positions[i][2]
coord, name = screw
if i == i_base:
# Show the results
self.gcode.respond_info(
- "%s : x=%.1f, y=%.1f, z=%.5f" %
- (name + ' (base)', coord[0], coord[1], z))
+ "%s : x=%.1f, y=%.1f, z=%.5f"
+ % (name + " (base)", coord[0], coord[1], z)
+ )
sign = "CW" if is_clockwise_thread else "CCW"
- self.results["screw%d" % (i + 1,)] = {'z': z, 'sign': sign,
- 'adjust': '00:00', 'is_base': True}
+ self.results["screw%d" % (i + 1,)] = {
+ "z": z,
+ "sign": sign,
+ "adjust": "00:00",
+ "is_base": True,
+ }
else:
# Calculate how knob must be adjusted for other positions
diff = z_base - z
@@ -115,16 +147,22 @@ class ScrewsTiltAdjust:
minutes = round(decimal_part * 60, 0)
# Show the results
self.gcode.respond_info(
- "%s : x=%.1f, y=%.1f, z=%.5f : adjust %s %02d:%02d" %
- (name, coord[0], coord[1], z, sign, full_turns, minutes))
- self.results["screw%d" % (i + 1,)] = {'z': z, 'sign': sign,
- 'adjust':"%02d:%02d" % (full_turns, minutes),
- 'is_base': False}
+ "%s : x=%.1f, y=%.1f, z=%.5f : adjust %s %02d:%02d"
+ % (name, coord[0], coord[1], z, sign, full_turns, minutes)
+ )
+ self.results["screw%d" % (i + 1,)] = {
+ "z": z,
+ "sign": sign,
+ "adjust": "%02d:%02d" % (full_turns, minutes),
+ "is_base": False,
+ }
if self.max_diff and any((d > self.max_diff) for d in screw_diff):
self.max_diff_error = True
raise self.gcode.error(
- "bed level exceeds configured limits ({}mm)! " \
- "Adjust screws and restart print.".format(self.max_diff))
+ "bed level exceeds configured limits ({}mm)! "
+ "Adjust screws and restart print.".format(self.max_diff)
+ )
+
def load_config(config):
return ScrewsTiltAdjust(config)
diff --git a/klippy/extras/sdcard_loop.py b/klippy/extras/sdcard_loop.py
index 3cba979f..6cc182ff 100644
--- a/klippy/extras/sdcard_loop.py
+++ b/klippy/extras/sdcard_loop.py
@@ -6,40 +6,55 @@
import logging
+
class SDCardLoop:
def __init__(self, config):
printer = config.get_printer()
self.sdcard = printer.load_object(config, "virtual_sdcard")
- self.gcode = printer.lookup_object('gcode')
+ self.gcode = printer.lookup_object("gcode")
self.gcode.register_command(
- "SDCARD_LOOP_BEGIN", self.cmd_SDCARD_LOOP_BEGIN,
- desc=self.cmd_SDCARD_LOOP_BEGIN_help)
+ "SDCARD_LOOP_BEGIN",
+ self.cmd_SDCARD_LOOP_BEGIN,
+ desc=self.cmd_SDCARD_LOOP_BEGIN_help,
+ )
self.gcode.register_command(
- "SDCARD_LOOP_END", self.cmd_SDCARD_LOOP_END,
- desc=self.cmd_SDCARD_LOOP_END_help)
+ "SDCARD_LOOP_END",
+ self.cmd_SDCARD_LOOP_END,
+ desc=self.cmd_SDCARD_LOOP_END_help,
+ )
self.gcode.register_command(
- "SDCARD_LOOP_DESIST", self.cmd_SDCARD_LOOP_DESIST,
- desc=self.cmd_SDCARD_LOOP_DESIST_help)
+ "SDCARD_LOOP_DESIST",
+ self.cmd_SDCARD_LOOP_DESIST,
+ desc=self.cmd_SDCARD_LOOP_DESIST_help,
+ )
self.loop_stack = []
+
cmd_SDCARD_LOOP_BEGIN_help = "Begins a looped section in the SD file."
+
def cmd_SDCARD_LOOP_BEGIN(self, gcmd):
count = gcmd.get_int("COUNT", minval=0)
if not self.loop_begin(count):
raise gcmd.error("Only permitted in SD file.")
+
cmd_SDCARD_LOOP_END_help = "Ends a looped section in the SD file."
+
def cmd_SDCARD_LOOP_END(self, gcmd):
if not self.loop_end():
raise gcmd.error("Only permitted in SD file.")
+
cmd_SDCARD_LOOP_DESIST_help = "Stops iterating the current loop stack."
+
def cmd_SDCARD_LOOP_DESIST(self, gcmd):
if not self.loop_desist():
raise gcmd.error("Only permitted outside of a SD file.")
+
def loop_begin(self, count):
if not self.sdcard.is_cmd_from_sd():
# Can only run inside of an SD file
return False
self.loop_stack.append((count, self.sdcard.get_file_position()))
return True
+
def loop_end(self):
if not self.sdcard.is_cmd_from_sd():
# Can only run inside of an SD file
@@ -49,10 +64,10 @@ class SDCardLoop:
return True
# Get iteration count and return position
count, position = self.loop_stack.pop()
- if count == 0: # Infinite loop
+ if count == 0: # Infinite loop
self.sdcard.set_file_position(position)
self.loop_stack.append((0, position))
- elif count == 1: # Last repeat
+ elif count == 1: # Last repeat
# Nothing to do
pass
else:
@@ -61,6 +76,7 @@ class SDCardLoop:
# Decrement the count by 1, and add the position back to the stack
self.loop_stack.append((count - 1, position))
return True
+
def loop_desist(self):
if self.sdcard.is_cmd_from_sd():
# Can only run outside of an SD file
@@ -69,5 +85,6 @@ class SDCardLoop:
self.loop_stack = []
return True
+
def load_config(config):
return SDCardLoop(config)
diff --git a/klippy/extras/servo.py b/klippy/extras/servo.py
index f1ce9976..cbe98755 100644
--- a/klippy/extras/servo.py
+++ b/klippy/extras/servo.py
@@ -7,65 +7,83 @@ from . import output_pin
SERVO_SIGNAL_PERIOD = 0.020
+
class PrinterServo:
def __init__(self, config):
self.printer = config.get_printer()
- self.min_width = config.getfloat('minimum_pulse_width', .001,
- above=0., below=SERVO_SIGNAL_PERIOD)
- self.max_width = config.getfloat('maximum_pulse_width', .002,
- above=self.min_width,
- below=SERVO_SIGNAL_PERIOD)
- self.max_angle = config.getfloat('maximum_servo_angle', 180.)
+ self.min_width = config.getfloat(
+ "minimum_pulse_width", 0.001, above=0.0, below=SERVO_SIGNAL_PERIOD
+ )
+ self.max_width = config.getfloat(
+ "maximum_pulse_width",
+ 0.002,
+ above=self.min_width,
+ below=SERVO_SIGNAL_PERIOD,
+ )
+ self.max_angle = config.getfloat("maximum_servo_angle", 180.0)
self.angle_to_width = (self.max_width - self.min_width) / self.max_angle
- self.width_to_value = 1. / SERVO_SIGNAL_PERIOD
- self.last_value = 0.
- initial_pwm = 0.
- iangle = config.getfloat('initial_angle', None, minval=0., maxval=360.)
+ self.width_to_value = 1.0 / SERVO_SIGNAL_PERIOD
+ self.last_value = 0.0
+ initial_pwm = 0.0
+ iangle = config.getfloat("initial_angle", None, minval=0.0, maxval=360.0)
if iangle is not None:
initial_pwm = self._get_pwm_from_angle(iangle)
else:
- iwidth = config.getfloat('initial_pulse_width', 0.,
- minval=0., maxval=self.max_width)
+ iwidth = config.getfloat(
+ "initial_pulse_width", 0.0, minval=0.0, maxval=self.max_width
+ )
initial_pwm = self._get_pwm_from_pulse_width(iwidth)
# Setup mcu_servo pin
- ppins = self.printer.lookup_object('pins')
- self.mcu_servo = ppins.setup_pin('pwm', config.get('pin'))
- self.mcu_servo.setup_max_duration(0.)
+ ppins = self.printer.lookup_object("pins")
+ self.mcu_servo = ppins.setup_pin("pwm", config.get("pin"))
+ self.mcu_servo.setup_max_duration(0.0)
self.mcu_servo.setup_cycle_time(SERVO_SIGNAL_PERIOD)
- self.mcu_servo.setup_start_value(initial_pwm, 0.)
+ self.mcu_servo.setup_start_value(initial_pwm, 0.0)
# Create gcode request queue
self.gcrq = output_pin.GCodeRequestQueue(
- config, self.mcu_servo.get_mcu(), self._set_pwm)
+ config, self.mcu_servo.get_mcu(), self._set_pwm
+ )
# Register commands
servo_name = config.get_name().split()[1]
- gcode = self.printer.lookup_object('gcode')
- gcode.register_mux_command("SET_SERVO", "SERVO", servo_name,
- self.cmd_SET_SERVO,
- desc=self.cmd_SET_SERVO_help)
+ gcode = self.printer.lookup_object("gcode")
+ gcode.register_mux_command(
+ "SET_SERVO",
+ "SERVO",
+ servo_name,
+ self.cmd_SET_SERVO,
+ desc=self.cmd_SET_SERVO_help,
+ )
+
def get_status(self, eventtime):
- return {'value': self.last_value}
+ return {"value": self.last_value}
+
def _set_pwm(self, print_time, value):
if value == self.last_value:
- return "discard", 0.
+ return "discard", 0.0
self.last_value = value
self.mcu_servo.set_pwm(print_time, value)
+
def _get_pwm_from_angle(self, angle):
- angle = max(0., min(self.max_angle, angle))
+ angle = max(0.0, min(self.max_angle, angle))
width = self.min_width + angle * self.angle_to_width
return width * self.width_to_value
+
def _get_pwm_from_pulse_width(self, width):
if width:
width = max(self.min_width, min(self.max_width, width))
return width * self.width_to_value
+
cmd_SET_SERVO_help = "Set servo angle"
+
def cmd_SET_SERVO(self, gcmd):
- width = gcmd.get_float('WIDTH', None)
+ width = gcmd.get_float("WIDTH", None)
if width is not None:
value = self._get_pwm_from_pulse_width(width)
else:
- angle = gcmd.get_float('ANGLE')
+ angle = gcmd.get_float("ANGLE")
value = self._get_pwm_from_angle(angle)
self.gcrq.queue_gcode_request(value)
+
def load_config_prefix(config):
return PrinterServo(config)
diff --git a/klippy/extras/shaper_calibrate.py b/klippy/extras/shaper_calibrate.py
index f497171f..117409aa 100644
--- a/klippy/extras/shaper_calibrate.py
+++ b/klippy/extras/shaper_calibrate.py
@@ -4,21 +4,23 @@
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import collections, importlib, logging, math, multiprocessing, traceback
-shaper_defs = importlib.import_module('.shaper_defs', 'extras')
-MIN_FREQ = 5.
-MAX_FREQ = 200.
+shaper_defs = importlib.import_module(".shaper_defs", "extras")
+
+MIN_FREQ = 5.0
+MAX_FREQ = 200.0
WINDOW_T_SEC = 0.5
-MAX_SHAPER_FREQ = 150.
+MAX_SHAPER_FREQ = 150.0
-TEST_DAMPING_RATIOS=[0.075, 0.1, 0.15]
+TEST_DAMPING_RATIOS = [0.075, 0.1, 0.15]
-AUTOTUNE_SHAPERS = ['zv', 'mzv', 'ei', '2hump_ei', '3hump_ei']
+AUTOTUNE_SHAPERS = ["zv", "mzv", "ei", "2hump_ei", "3hump_ei"]
######################################################################
# Frequency response calculation and shaper auto-tuning
######################################################################
+
class CalibrationData:
def __init__(self, freq_bins, psd_sum, psd_x, psd_y, psd_z):
self.freq_bins = freq_bins
@@ -27,9 +29,14 @@ class CalibrationData:
self.psd_y = psd_y
self.psd_z = psd_z
self._psd_list = [self.psd_sum, self.psd_x, self.psd_y, self.psd_z]
- self._psd_map = {'x': self.psd_x, 'y': self.psd_y, 'z': self.psd_z,
- 'all': self.psd_sum}
+ self._psd_map = {
+ "x": self.psd_x,
+ "y": self.psd_y,
+ "z": self.psd_z,
+ "all": self.psd_sum,
+ }
self.data_sets = 1
+
def add_data(self, other):
np = self.numpy
joined_data_sets = self.data_sets + other.data_sets
@@ -37,45 +44,55 @@ class CalibrationData:
# `other` data may be defined at different frequency bins,
# interpolating to fix that.
other_normalized = other.data_sets * np.interp(
- self.freq_bins, other.freq_bins, other_psd)
+ self.freq_bins, other.freq_bins, other_psd
+ )
psd *= self.data_sets
- psd[:] = (psd + other_normalized) * (1. / joined_data_sets)
+ psd[:] = (psd + other_normalized) * (1.0 / joined_data_sets)
self.data_sets = joined_data_sets
+
def set_numpy(self, numpy):
self.numpy = numpy
+
def normalize_to_frequencies(self):
for psd in self._psd_list:
# Avoid division by zero errors
- psd /= self.freq_bins + .1
+ psd /= self.freq_bins + 0.1
# Remove low-frequency noise
- low_freqs = self.freq_bins < 2. * MIN_FREQ
+ low_freqs = self.freq_bins < 2.0 * MIN_FREQ
psd[low_freqs] *= self.numpy.exp(
- -(2. * MIN_FREQ / (self.freq_bins[low_freqs] + .1))**2 + 1.)
- def get_psd(self, axis='all'):
+ -((2.0 * MIN_FREQ / (self.freq_bins[low_freqs] + 0.1)) ** 2) + 1.0
+ )
+
+ def get_psd(self, axis="all"):
return self._psd_map[axis]
CalibrationResult = collections.namedtuple(
- 'CalibrationResult',
- ('name', 'freq', 'vals', 'vibrs', 'smoothing', 'score', 'max_accel'))
+ "CalibrationResult",
+ ("name", "freq", "vals", "vibrs", "smoothing", "score", "max_accel"),
+)
+
class ShaperCalibrate:
def __init__(self, printer):
self.printer = printer
self.error = printer.command_error if printer else Exception
try:
- self.numpy = importlib.import_module('numpy')
+ self.numpy = importlib.import_module("numpy")
except ImportError:
raise self.error(
- "Failed to import `numpy` module, make sure it was "
- "installed via `~/klippy-env/bin/pip install` (refer to "
- "docs/Measuring_Resonances.md for more details).")
+ "Failed to import `numpy` module, make sure it was "
+ "installed via `~/klippy-env/bin/pip install` (refer to "
+ "docs/Measuring_Resonances.md for more details)."
+ )
def background_process_exec(self, method, args):
if self.printer is None:
return method(*args)
import queuelogger
+
parent_conn, child_conn = multiprocessing.Pipe()
+
def wrapper():
queuelogger.clear_bg_logging()
try:
@@ -86,6 +103,7 @@ class ShaperCalibrate:
return
child_conn.send((False, res))
child_conn.close()
+
# Start a process to perform the calculation
calc_proc = multiprocessing.Process(target=wrapper)
calc_proc.daemon = True
@@ -95,10 +113,10 @@ class ShaperCalibrate:
gcode = self.printer.lookup_object("gcode")
eventtime = last_report_time = reactor.monotonic()
while calc_proc.is_alive():
- if eventtime > last_report_time + 5.:
+ if eventtime > last_report_time + 5.0:
last_report_time = eventtime
gcode.respond_info("Wait for calculations..", log=False)
- eventtime = reactor.pause(eventtime + .1)
+ eventtime = reactor.pause(eventtime + 0.1)
# Return results
is_err, res = parent_conn.recv()
if is_err:
@@ -115,12 +133,13 @@ class ShaperCalibrate:
shape = (window_size, n_windows)
strides = (x.strides[-1], step_between_windows * x.strides[-1])
return self.numpy.lib.stride_tricks.as_strided(
- x, shape=shape, strides=strides, writeable=False)
+ x, shape=shape, strides=strides, writeable=False
+ )
def _psd(self, x, fs, nfft):
# Calculate power spectral density (PSD) using Welch's algorithm
np = self.numpy
- window = np.kaiser(nfft, 6.)
+ window = np.kaiser(nfft, 6.0)
# Compensation for windowing loss
scale = 1.0 / (window**2).sum()
@@ -138,13 +157,13 @@ class ShaperCalibrate:
# For one-sided FFT output the response must be doubled, except
# the last point for unpaired Nyquist frequency (assuming even nfft)
# and the 'DC' term (0 Hz)
- result[1:-1,:] *= 2.
+ result[1:-1, :] *= 2.0
# Welch's algorithm: average response over windows
psd = result.real.mean(axis=-1)
# Calculate the frequency bins
- freqs = np.fft.rfftfreq(nfft, 1. / fs)
+ freqs = np.fft.rfftfreq(nfft, 1.0 / fs)
return freqs, psd
def calc_freq_response(self, raw_values):
@@ -160,7 +179,7 @@ class ShaperCalibrate:
data = np.array(samples)
N = data.shape[0]
- T = data[-1,0] - data[0,0]
+ T = data[-1, 0] - data[0, 0]
SAMPLING_FREQ = N / T
# Round up to the nearest power of 2 for faster FFT
M = 1 << int(SAMPLING_FREQ * WINDOW_T_SEC - 1).bit_length()
@@ -169,17 +188,19 @@ class ShaperCalibrate:
# Calculate PSD (power spectral density) of vibrations per
# frequency bins (the same bins for X, Y, and Z)
- fx, px = self._psd(data[:,1], SAMPLING_FREQ, M)
- fy, py = self._psd(data[:,2], SAMPLING_FREQ, M)
- fz, pz = self._psd(data[:,3], SAMPLING_FREQ, M)
- return CalibrationData(fx, px+py+pz, px, py, pz)
+ fx, px = self._psd(data[:, 1], SAMPLING_FREQ, M)
+ fy, py = self._psd(data[:, 2], SAMPLING_FREQ, M)
+ fz, pz = self._psd(data[:, 3], SAMPLING_FREQ, M)
+ return CalibrationData(fx, px + py + pz, px, py, pz)
def process_accelerometer_data(self, data):
calibration_data = self.background_process_exec(
- self.calc_freq_response, (data,))
+ self.calc_freq_response, (data,)
+ )
if calibration_data is None:
raise self.error(
- "Internal error processing accelerometer data %s" % (data,))
+ "Internal error processing accelerometer data %s" % (data,)
+ )
calibration_data.set_numpy(self.numpy)
return calibration_data
@@ -187,51 +208,59 @@ class ShaperCalibrate:
np = self.numpy
A, T = np.array(shaper[0]), np.array(shaper[1])
- inv_D = 1. / A.sum()
+ inv_D = 1.0 / A.sum()
- omega = 2. * math.pi * test_freqs
+ omega = 2.0 * math.pi * test_freqs
damping = test_damping_ratio * omega
- omega_d = omega * math.sqrt(1. - test_damping_ratio**2)
+ omega_d = omega * math.sqrt(1.0 - test_damping_ratio**2)
W = A * np.exp(np.outer(-damping, (T[-1] - T)))
S = W * np.sin(np.outer(omega_d, T))
C = W * np.cos(np.outer(omega_d, T))
- return np.sqrt(S.sum(axis=1)**2 + C.sum(axis=1)**2) * inv_D
+ return np.sqrt(S.sum(axis=1) ** 2 + C.sum(axis=1) ** 2) * inv_D
- def _estimate_remaining_vibrations(self, shaper, test_damping_ratio,
- freq_bins, psd):
+ def _estimate_remaining_vibrations(
+ self, shaper, test_damping_ratio, freq_bins, psd
+ ):
vals = self._estimate_shaper(shaper, test_damping_ratio, freq_bins)
# The input shaper can only reduce the amplitude of vibrations by
# SHAPER_VIBRATION_REDUCTION times, so all vibrations below that
# threshold can be igonred
vibr_threshold = psd.max() / shaper_defs.SHAPER_VIBRATION_REDUCTION
- remaining_vibrations = self.numpy.maximum(
- vals * psd - vibr_threshold, 0).sum()
+ remaining_vibrations = self.numpy.maximum(vals * psd - vibr_threshold, 0).sum()
all_vibrations = self.numpy.maximum(psd - vibr_threshold, 0).sum()
return (remaining_vibrations / all_vibrations, vals)
- def _get_shaper_smoothing(self, shaper, accel=5000, scv=5.):
- half_accel = accel * .5
+ def _get_shaper_smoothing(self, shaper, accel=5000, scv=5.0):
+ half_accel = accel * 0.5
A, T = shaper
- inv_D = 1. / sum(A)
+ inv_D = 1.0 / sum(A)
n = len(T)
# Calculate input shaper shift
ts = sum([A[i] * T[i] for i in range(n)]) * inv_D
# Calculate offset for 90 and 180 degrees turn
- offset_90 = offset_180 = 0.
+ offset_90 = offset_180 = 0.0
for i in range(n):
if T[i] >= ts:
# Calculate offset for one of the axes
- offset_90 += A[i] * (scv + half_accel * (T[i]-ts)) * (T[i]-ts)
- offset_180 += A[i] * half_accel * (T[i]-ts)**2
- offset_90 *= inv_D * math.sqrt(2.)
+ offset_90 += A[i] * (scv + half_accel * (T[i] - ts)) * (T[i] - ts)
+ offset_180 += A[i] * half_accel * (T[i] - ts) ** 2
+ offset_90 *= inv_D * math.sqrt(2.0)
offset_180 *= inv_D
return max(offset_90, offset_180)
- def fit_shaper(self, shaper_cfg, calibration_data, shaper_freqs,
- damping_ratio, scv, max_smoothing, test_damping_ratios,
- max_freq):
+ def fit_shaper(
+ self,
+ shaper_cfg,
+ calibration_data,
+ shaper_freqs,
+ damping_ratio,
+ scv,
+ max_smoothing,
+ test_damping_ratios,
+ max_freq,
+ ):
np = self.numpy
damping_ratio = damping_ratio or shaper_defs.DEFAULT_DAMPING_RATIO
@@ -241,9 +270,8 @@ class ShaperCalibrate:
shaper_freqs = (None, None, None)
if isinstance(shaper_freqs, tuple):
freq_end = shaper_freqs[1] or MAX_SHAPER_FREQ
- freq_start = min(shaper_freqs[0] or shaper_cfg.min_freq,
- freq_end - 1e-7)
- freq_step = shaper_freqs[2] or .2
+ freq_start = min(shaper_freqs[0] or shaper_cfg.min_freq, freq_end - 1e-7)
+ freq_step = shaper_freqs[2] or 0.2
test_freqs = np.arange(freq_start, freq_end, freq_step)
else:
test_freqs = np.array(shaper_freqs)
@@ -257,7 +285,7 @@ class ShaperCalibrate:
best_res = None
results = []
for test_freq in test_freqs[::-1]:
- shaper_vibrations = 0.
+ shaper_vibrations = 0.0
shaper_vals = np.zeros(shape=freq_bins.shape)
shaper = shaper_cfg.init_func(test_freq, damping_ratio)
shaper_smoothing = self._get_shaper_smoothing(shaper, scv=scv)
@@ -267,7 +295,8 @@ class ShaperCalibrate:
# remaining vibrations over possible damping values
for dr in test_damping_ratios:
vibrations, vals = self._estimate_remaining_vibrations(
- shaper, dr, freq_bins, psd)
+ shaper, dr, freq_bins, psd
+ )
shaper_vals = np.maximum(shaper_vals, vals)
if vibrations > shaper_vibrations:
shaper_vibrations = vibrations
@@ -275,13 +304,20 @@ class ShaperCalibrate:
# The score trying to minimize vibrations, but also accounting
# the growth of smoothing. The formula itself does not have any
# special meaning, it simply shows good results on real user data
- shaper_score = shaper_smoothing * (shaper_vibrations**1.5 +
- shaper_vibrations * .2 + .01)
+ shaper_score = shaper_smoothing * (
+ shaper_vibrations**1.5 + shaper_vibrations * 0.2 + 0.01
+ )
results.append(
- CalibrationResult(
- name=shaper_cfg.name, freq=test_freq, vals=shaper_vals,
- vibrs=shaper_vibrations, smoothing=shaper_smoothing,
- score=shaper_score, max_accel=max_accel))
+ CalibrationResult(
+ name=shaper_cfg.name,
+ freq=test_freq,
+ vals=shaper_vals,
+ vibrs=shaper_vibrations,
+ smoothing=shaper_smoothing,
+ score=shaper_score,
+ max_accel=max_accel,
+ )
+ )
if best_res is None or best_res.vibrs > results[-1].vibrs:
# The current frequency is better for the shaper.
best_res = results[-1]
@@ -294,17 +330,17 @@ class ShaperCalibrate:
return selected
def _bisect(self, func):
- left = right = 1.
+ left = right = 1.0
if not func(1e-9):
- return 0.
+ return 0.0
while not func(left):
right = left
- left *= .5
+ left *= 0.5
if right == left:
while func(right):
- right *= 2.
+ right *= 2.0
while right - left > 1e-8:
- middle = (left + right) * .5
+ middle = (left + right) * 0.5
if func(middle):
left = middle
else:
@@ -315,63 +351,99 @@ class ShaperCalibrate:
# Just some empirically chosen value which produces good projections
# for max_accel without much smoothing
TARGET_SMOOTHING = 0.12
- max_accel = self._bisect(lambda test_accel: self._get_shaper_smoothing(
- shaper, test_accel, scv) <= TARGET_SMOOTHING)
+ max_accel = self._bisect(
+ lambda test_accel: self._get_shaper_smoothing(shaper, test_accel, scv)
+ <= TARGET_SMOOTHING
+ )
return max_accel
- def find_best_shaper(self, calibration_data, shapers=None,
- damping_ratio=None, scv=None, shaper_freqs=None,
- max_smoothing=None, test_damping_ratios=None,
- max_freq=None, logger=None):
+ def find_best_shaper(
+ self,
+ calibration_data,
+ shapers=None,
+ damping_ratio=None,
+ scv=None,
+ shaper_freqs=None,
+ max_smoothing=None,
+ test_damping_ratios=None,
+ max_freq=None,
+ logger=None,
+ ):
best_shaper = None
all_shapers = []
shapers = shapers or AUTOTUNE_SHAPERS
for shaper_cfg in shaper_defs.INPUT_SHAPERS:
if shaper_cfg.name not in shapers:
continue
- shaper = self.background_process_exec(self.fit_shaper, (
- shaper_cfg, calibration_data, shaper_freqs, damping_ratio,
- scv, max_smoothing, test_damping_ratios, max_freq))
+ shaper = self.background_process_exec(
+ self.fit_shaper,
+ (
+ shaper_cfg,
+ calibration_data,
+ shaper_freqs,
+ damping_ratio,
+ scv,
+ max_smoothing,
+ test_damping_ratios,
+ max_freq,
+ ),
+ )
if logger is not None:
- logger("Fitted shaper '%s' frequency = %.1f Hz "
- "(vibrations = %.1f%%, smoothing ~= %.3f)" % (
- shaper.name, shaper.freq, shaper.vibrs * 100.,
- shaper.smoothing))
- logger("To avoid too much smoothing with '%s', suggested "
- "max_accel <= %.0f mm/sec^2" % (
- shaper.name, round(shaper.max_accel / 100.) * 100.))
+ logger(
+ "Fitted shaper '%s' frequency = %.1f Hz "
+ "(vibrations = %.1f%%, smoothing ~= %.3f)"
+ % (shaper.name, shaper.freq, shaper.vibrs * 100.0, shaper.smoothing)
+ )
+ logger(
+ "To avoid too much smoothing with '%s', suggested "
+ "max_accel <= %.0f mm/sec^2"
+ % (shaper.name, round(shaper.max_accel / 100.0) * 100.0)
+ )
all_shapers.append(shaper)
- if (best_shaper is None or shaper.score * 1.2 < best_shaper.score or
- (shaper.score * 1.05 < best_shaper.score and
- shaper.smoothing * 1.1 < best_shaper.smoothing)):
+ if (
+ best_shaper is None
+ or shaper.score * 1.2 < best_shaper.score
+ or (
+ shaper.score * 1.05 < best_shaper.score
+ and shaper.smoothing * 1.1 < best_shaper.smoothing
+ )
+ ):
# Either the shaper significantly improves the score (by 20%),
# or it improves the score and smoothing (by 5% and 10% resp.)
best_shaper = shaper
return best_shaper, all_shapers
def save_params(self, configfile, axis, shaper_name, shaper_freq):
- if axis == 'xy':
- self.save_params(configfile, 'x', shaper_name, shaper_freq)
- self.save_params(configfile, 'y', shaper_name, shaper_freq)
+ if axis == "xy":
+ self.save_params(configfile, "x", shaper_name, shaper_freq)
+ self.save_params(configfile, "y", shaper_name, shaper_freq)
else:
- configfile.set('input_shaper', 'shaper_type_'+axis, shaper_name)
- configfile.set('input_shaper', 'shaper_freq_'+axis,
- '%.1f' % (shaper_freq,))
+ configfile.set("input_shaper", "shaper_type_" + axis, shaper_name)
+ configfile.set(
+ "input_shaper", "shaper_freq_" + axis, "%.1f" % (shaper_freq,)
+ )
def apply_params(self, input_shaper, axis, shaper_name, shaper_freq):
- if axis == 'xy':
- self.apply_params(input_shaper, 'x', shaper_name, shaper_freq)
- self.apply_params(input_shaper, 'y', shaper_name, shaper_freq)
+ if axis == "xy":
+ self.apply_params(input_shaper, "x", shaper_name, shaper_freq)
+ self.apply_params(input_shaper, "y", shaper_name, shaper_freq)
return
gcode = self.printer.lookup_object("gcode")
axis = axis.upper()
- input_shaper.cmd_SET_INPUT_SHAPER(gcode.create_gcode_command(
- "SET_INPUT_SHAPER", "SET_INPUT_SHAPER", {
+ input_shaper.cmd_SET_INPUT_SHAPER(
+ gcode.create_gcode_command(
+ "SET_INPUT_SHAPER",
+ "SET_INPUT_SHAPER",
+ {
"SHAPER_TYPE_" + axis: shaper_name,
- "SHAPER_FREQ_" + axis: shaper_freq}))
+ "SHAPER_FREQ_" + axis: shaper_freq,
+ },
+ )
+ )
- def save_calibration_data(self, output, calibration_data, shapers=None,
- max_freq=None):
+ def save_calibration_data(
+ self, output, calibration_data, shapers=None, max_freq=None
+ ):
try:
max_freq = max_freq or MAX_FREQ
with open(output, "w") as csvfile:
@@ -384,12 +456,16 @@ class ShaperCalibrate:
for i in range(num_freqs):
if calibration_data.freq_bins[i] >= max_freq:
break
- csvfile.write("%.1f,%.3e,%.3e,%.3e,%.3e" % (
- calibration_data.freq_bins[i],
- calibration_data.psd_x[i],
- calibration_data.psd_y[i],
- calibration_data.psd_z[i],
- calibration_data.psd_sum[i]))
+ csvfile.write(
+ "%.1f,%.3e,%.3e,%.3e,%.3e"
+ % (
+ calibration_data.freq_bins[i],
+ calibration_data.psd_x[i],
+ calibration_data.psd_y[i],
+ calibration_data.psd_z[i],
+ calibration_data.psd_sum[i],
+ )
+ )
if shapers:
for shaper in shapers:
csvfile.write(",%.3f" % (shaper.vals[i],))
diff --git a/klippy/extras/shaper_defs.py b/klippy/extras/shaper_defs.py
index 611fed16..1cd7aeed 100644
--- a/klippy/extras/shaper_defs.py
+++ b/klippy/extras/shaper_defs.py
@@ -5,98 +5,107 @@
# This file may be distributed under the terms of the GNU GPLv3 license.
import collections, math
-SHAPER_VIBRATION_REDUCTION=20.
+SHAPER_VIBRATION_REDUCTION = 20.0
DEFAULT_DAMPING_RATIO = 0.1
InputShaperCfg = collections.namedtuple(
- 'InputShaperCfg', ('name', 'init_func', 'min_freq'))
+ "InputShaperCfg", ("name", "init_func", "min_freq")
+)
+
def get_none_shaper():
return ([], [])
+
def get_zv_shaper(shaper_freq, damping_ratio):
- df = math.sqrt(1. - damping_ratio**2)
+ df = math.sqrt(1.0 - damping_ratio**2)
K = math.exp(-damping_ratio * math.pi / df)
- t_d = 1. / (shaper_freq * df)
- A = [1., K]
- T = [0., .5*t_d]
+ t_d = 1.0 / (shaper_freq * df)
+ A = [1.0, K]
+ T = [0.0, 0.5 * t_d]
return (A, T)
+
def get_zvd_shaper(shaper_freq, damping_ratio):
- df = math.sqrt(1. - damping_ratio**2)
+ df = math.sqrt(1.0 - damping_ratio**2)
K = math.exp(-damping_ratio * math.pi / df)
- t_d = 1. / (shaper_freq * df)
- A = [1., 2.*K, K**2]
- T = [0., .5*t_d, t_d]
+ t_d = 1.0 / (shaper_freq * df)
+ A = [1.0, 2.0 * K, K**2]
+ T = [0.0, 0.5 * t_d, t_d]
return (A, T)
+
def get_mzv_shaper(shaper_freq, damping_ratio):
- df = math.sqrt(1. - damping_ratio**2)
- K = math.exp(-.75 * damping_ratio * math.pi / df)
- t_d = 1. / (shaper_freq * df)
+ df = math.sqrt(1.0 - damping_ratio**2)
+ K = math.exp(-0.75 * damping_ratio * math.pi / df)
+ t_d = 1.0 / (shaper_freq * df)
- a1 = 1. - 1. / math.sqrt(2.)
- a2 = (math.sqrt(2.) - 1.) * K
+ a1 = 1.0 - 1.0 / math.sqrt(2.0)
+ a2 = (math.sqrt(2.0) - 1.0) * K
a3 = a1 * K * K
A = [a1, a2, a3]
- T = [0., .375*t_d, .75*t_d]
+ T = [0.0, 0.375 * t_d, 0.75 * t_d]
return (A, T)
+
def get_ei_shaper(shaper_freq, damping_ratio):
- v_tol = 1. / SHAPER_VIBRATION_REDUCTION # vibration tolerance
- df = math.sqrt(1. - damping_ratio**2)
+ v_tol = 1.0 / SHAPER_VIBRATION_REDUCTION # vibration tolerance
+ df = math.sqrt(1.0 - damping_ratio**2)
K = math.exp(-damping_ratio * math.pi / df)
- t_d = 1. / (shaper_freq * df)
+ t_d = 1.0 / (shaper_freq * df)
- a1 = .25 * (1. + v_tol)
- a2 = .5 * (1. - v_tol) * K
+ a1 = 0.25 * (1.0 + v_tol)
+ a2 = 0.5 * (1.0 - v_tol) * K
a3 = a1 * K * K
A = [a1, a2, a3]
- T = [0., .5*t_d, t_d]
+ T = [0.0, 0.5 * t_d, t_d]
return (A, T)
+
def get_2hump_ei_shaper(shaper_freq, damping_ratio):
- v_tol = 1. / SHAPER_VIBRATION_REDUCTION # vibration tolerance
- df = math.sqrt(1. - damping_ratio**2)
+ v_tol = 1.0 / SHAPER_VIBRATION_REDUCTION # vibration tolerance
+ df = math.sqrt(1.0 - damping_ratio**2)
K = math.exp(-damping_ratio * math.pi / df)
- t_d = 1. / (shaper_freq * df)
+ t_d = 1.0 / (shaper_freq * df)
V2 = v_tol**2
- X = pow(V2 * (math.sqrt(1. - V2) + 1.), 1./3.)
- a1 = (3.*X*X + 2.*X + 3.*V2) / (16.*X)
- a2 = (.5 - a1) * K
+ X = pow(V2 * (math.sqrt(1.0 - V2) + 1.0), 1.0 / 3.0)
+ a1 = (3.0 * X * X + 2.0 * X + 3.0 * V2) / (16.0 * X)
+ a2 = (0.5 - a1) * K
a3 = a2 * K
a4 = a1 * K * K * K
A = [a1, a2, a3, a4]
- T = [0., .5*t_d, t_d, 1.5*t_d]
+ T = [0.0, 0.5 * t_d, t_d, 1.5 * t_d]
return (A, T)
+
def get_3hump_ei_shaper(shaper_freq, damping_ratio):
- v_tol = 1. / SHAPER_VIBRATION_REDUCTION # vibration tolerance
- df = math.sqrt(1. - damping_ratio**2)
+ v_tol = 1.0 / SHAPER_VIBRATION_REDUCTION # vibration tolerance
+ df = math.sqrt(1.0 - damping_ratio**2)
K = math.exp(-damping_ratio * math.pi / df)
- t_d = 1. / (shaper_freq * df)
+ t_d = 1.0 / (shaper_freq * df)
- K2 = K*K
- a1 = 0.0625 * (1. + 3. * v_tol + 2. * math.sqrt(2. * (v_tol + 1.) * v_tol))
- a2 = 0.25 * (1. - v_tol) * K
- a3 = (0.5 * (1. + v_tol) - 2. * a1) * K2
+ K2 = K * K
+ a1 = 0.0625 * (1.0 + 3.0 * v_tol + 2.0 * math.sqrt(2.0 * (v_tol + 1.0) * v_tol))
+ a2 = 0.25 * (1.0 - v_tol) * K
+ a3 = (0.5 * (1.0 + v_tol) - 2.0 * a1) * K2
a4 = a2 * K2
a5 = a1 * K2 * K2
A = [a1, a2, a3, a4, a5]
- T = [0., .5*t_d, t_d, 1.5*t_d, 2.*t_d]
+ T = [0.0, 0.5 * t_d, t_d, 1.5 * t_d, 2.0 * t_d]
return (A, T)
+
# min_freq for each shaper is chosen to have projected max_accel ~= 1500
INPUT_SHAPERS = [
- InputShaperCfg('zv', get_zv_shaper, min_freq=21.),
- InputShaperCfg('mzv', get_mzv_shaper, min_freq=23.),
- InputShaperCfg('zvd', get_zvd_shaper, min_freq=29.),
- InputShaperCfg('ei', get_ei_shaper, min_freq=29.),
- InputShaperCfg('2hump_ei', get_2hump_ei_shaper, min_freq=39.),
- InputShaperCfg('3hump_ei', get_3hump_ei_shaper, min_freq=48.),
+ InputShaperCfg("zv", get_zv_shaper, min_freq=21.0),
+ InputShaperCfg("mzv", get_mzv_shaper, min_freq=23.0),
+ InputShaperCfg("zvd", get_zvd_shaper, min_freq=29.0),
+ InputShaperCfg("ei", get_ei_shaper, min_freq=29.0),
+ InputShaperCfg("2hump_ei", get_2hump_ei_shaper, min_freq=39.0),
+ InputShaperCfg("3hump_ei", get_3hump_ei_shaper, min_freq=48.0),
]
diff --git a/klippy/extras/sht3x.py b/klippy/extras/sht3x.py
index c76ceb76..d6acd987 100644
--- a/klippy/extras/sht3x.py
+++ b/klippy/extras/sht3x.py
@@ -15,55 +15,57 @@ from . import bus
SHT3X_I2C_ADDR = 0x44
SHT3X_CMD = {
- 'MEASURE': {
- 'STRETCH_ENABLED': {
- 'HIGH_REP': [0x2c, 0x06], # High (15ms) repeatability measurement
- 'MED_REP': [0x2c, 0x0D], # Medium (6ms) repeatability measurement
- 'LOW_REP': [0x2c, 0x10], # Low (4ms) repeatability measurement
+ "MEASURE": {
+ "STRETCH_ENABLED": {
+ "HIGH_REP": [0x2C, 0x06], # High (15ms) repeatability measurement
+ "MED_REP": [0x2C, 0x0D], # Medium (6ms) repeatability measurement
+ "LOW_REP": [0x2C, 0x10], # Low (4ms) repeatability measurement
},
- 'STRETCH_DISABLED' : {
- 'HIGH_REP': [0x24, 0x00],
- 'MED_REP': [0x24, 0x0B],
- 'LOW_REP': [0x24, 0x16],
+ "STRETCH_DISABLED": {
+ "HIGH_REP": [0x24, 0x00],
+ "MED_REP": [0x24, 0x0B],
+ "LOW_REP": [0x24, 0x16],
},
},
- 'PERIODIC': {
- '2HZ': {
- 'HIGH_REP': [0x22, 0x36],
- 'MED_REP': [0x22, 0x20],
- 'LOW_REP': [0x22, 0x2B],
+ "PERIODIC": {
+ "2HZ": {
+ "HIGH_REP": [0x22, 0x36],
+ "MED_REP": [0x22, 0x20],
+ "LOW_REP": [0x22, 0x2B],
},
},
- 'OTHER': {
- 'STATUS': {
- 'READ': [0xF3, 0x2D],
- 'CLEAN': [0x30, 0x41],
+ "OTHER": {
+ "STATUS": {
+ "READ": [0xF3, 0x2D],
+ "CLEAN": [0x30, 0x41],
},
- 'SOFTRESET': [0x30, 0xA2], # Soft reset
- 'HEATER': {
+ "SOFTRESET": [0x30, 0xA2], # Soft reset
+ "HEATER": {
"ENABLE": [0x30, 0x6D],
"DISABLE": [0x30, 0x66],
},
- 'FETCH': [0xE0, 0x00],
- 'BREAK': [0x30, 0x93],
- }
+ "FETCH": [0xE0, 0x00],
+ "BREAK": [0x30, 0x93],
+ },
}
+
class SHT3X:
def __init__(self, config):
self.printer = config.get_printer()
self.name = config.get_name().split()[-1]
self.reactor = self.printer.get_reactor()
self.i2c = bus.MCU_I2C_from_config(
- config, default_addr=SHT3X_I2C_ADDR, default_speed=100000)
+ config, default_addr=SHT3X_I2C_ADDR, default_speed=100000
+ )
self._error = self.i2c.get_mcu().error
- self.report_time = config.getint('sht3x_report_time', 1, minval=1)
- self.deviceId = config.get('sensor_type')
- self.temp = self.min_temp = self.max_temp = self.humidity = 0.
+ self.report_time = config.getint("sht3x_report_time", 1, minval=1)
+ self.deviceId = config.get("sensor_type")
+ self.temp = self.min_temp = self.max_temp = self.humidity = 0.0
self.sample_timer = self.reactor.register_timer(self._sample_sht3x)
self.printer.add_object("sht3x " + self.name, self)
- self.printer.register_event_handler("klippy:connect",
- self.handle_connect)
+ self.printer.register_event_handler("klippy:connect", self.handle_connect)
+
def handle_connect(self):
self._init_sht3x()
self.reactor.update_timer(self.sample_timer, self.reactor.NOW)
@@ -80,15 +82,15 @@ class SHT3X:
def _init_sht3x(self):
# Device Soft Reset
- self.i2c.i2c_write_wait_ack(SHT3X_CMD['OTHER']['BREAK'])
+ self.i2c.i2c_write_wait_ack(SHT3X_CMD["OTHER"]["BREAK"])
# Break takes ~ 1ms
- self.reactor.pause(self.reactor.monotonic() + .0015)
- self.i2c.i2c_write_wait_ack(SHT3X_CMD['OTHER']['SOFTRESET'])
+ self.reactor.pause(self.reactor.monotonic() + 0.0015)
+ self.i2c.i2c_write_wait_ack(SHT3X_CMD["OTHER"]["SOFTRESET"])
# Wait <=1.5ms after reset
- self.reactor.pause(self.reactor.monotonic() + .0015)
+ self.reactor.pause(self.reactor.monotonic() + 0.0015)
- status = self.i2c.i2c_read(SHT3X_CMD['OTHER']['STATUS']['READ'], 3)
- response = bytearray(status['response'])
+ status = self.i2c.i2c_read(SHT3X_CMD["OTHER"]["STATUS"]["READ"], 3)
+ response = bytearray(status["response"])
status = response[0] << 8
status |= response[1]
checksum = response[2]
@@ -97,11 +99,9 @@ class SHT3X:
logging.warning("sht3x: Reading status - checksum error!")
# Enable periodic mode
- self.i2c.i2c_write_wait_ack(
- SHT3X_CMD['PERIODIC']['2HZ']['HIGH_REP']
- )
+ self.i2c.i2c_write_wait_ack(SHT3X_CMD["PERIODIC"]["2HZ"]["HIGH_REP"])
# Wait <=15.5ms for first measurement
- self.reactor.pause(self.reactor.monotonic() + .0155)
+ self.reactor.pause(self.reactor.monotonic() + 0.0155)
def _sample_sht3x(self, eventtime):
try:
@@ -112,27 +112,25 @@ class SHT3X:
while retries > 0 and params is None:
try:
params = self.i2c.i2c_read(
- SHT3X_CMD['OTHER']['FETCH'], 6, retry=False
+ SHT3X_CMD["OTHER"]["FETCH"], 6, retry=False
)
except self._error as e:
error = e
- self.reactor.pause(self.reactor.monotonic() + .5)
+ self.reactor.pause(self.reactor.monotonic() + 0.5)
retries -= 1
if params is None:
raise error
- response = bytearray(params['response'])
- rtemp = response[0] << 8
+ response = bytearray(params["response"])
+ rtemp = response[0] << 8
rtemp |= response[1]
if self._crc8(rtemp) != response[2]:
- logging.warning(
- "sht3x: Checksum error on Temperature reading!"
- )
+ logging.warning("sht3x: Checksum error on Temperature reading!")
else:
self.temp = -45 + (175 * rtemp / 65535)
logging.debug("sht3x: Temperature %.2f " % self.temp)
- rhumid = response[3] << 8
+ rhumid = response[3] << 8
rhumid |= response[4]
if self._crc8(rhumid) != response[5]:
logging.warning("sht3x: Checksum error on Humidity reading!")
@@ -142,13 +140,14 @@ class SHT3X:
except Exception:
logging.exception("sht3x: Error reading data")
- self.temp = self.humidity = .0
+ self.temp = self.humidity = 0.0
return self.reactor.NEVER
if self.temp < self.min_temp or self.temp > self.max_temp:
self.printer.invoke_shutdown(
"sht3x: temperature %0.1f outside range of %0.1f:%.01f"
- % (self.temp, self.min_temp, self.max_temp))
+ % (self.temp, self.min_temp, self.max_temp)
+ )
measured_time = self.reactor.monotonic()
print_time = self.i2c.get_mcu().estimated_print_time(measured_time)
@@ -158,13 +157,13 @@ class SHT3X:
def _split_bytes(self, data):
bytes = []
for i in range((data.bit_length() + 7) // 8):
- bytes.append((data >> i*8) & 0xFF)
+ bytes.append((data >> i * 8) & 0xFF)
bytes.reverse()
return bytes
def _crc8(self, data):
- #crc8 polynomial for 16bit value, CRC8 -> x^8 + x^5 + x^4 + 1
- SHT3X_CRC8_POLYNOMINAL= 0x31
+ # crc8 polynomial for 16bit value, CRC8 -> x^8 + x^5 + x^4 + 1
+ SHT3X_CRC8_POLYNOMINAL = 0x31
crc = 0xFF
data_bytes = self._split_bytes(data)
for byte in data_bytes:
@@ -178,10 +177,11 @@ class SHT3X:
def get_status(self, eventtime):
return {
- 'temperature': round(self.temp, 2),
- 'humidity': round(self.humidity, 1),
+ "temperature": round(self.temp, 2),
+ "humidity": round(self.humidity, 1),
}
+
def load_config(config):
# Register sensor
pheater = config.get_printer().lookup_object("heaters")
diff --git a/klippy/extras/skew_correction.py b/klippy/extras/skew_correction.py
index 94d8ceb7..f7346e59 100644
--- a/klippy/extras/skew_correction.py
+++ b/klippy/extras/skew_correction.py
@@ -11,10 +11,13 @@
import math
+
def calc_skew_factor(ac, bd, ad):
- side = math.sqrt(2*ac*ac + 2*bd*bd - 4*ad*ad) / 2.
- return math.tan(math.pi/2 - math.acos(
- (ac*ac - side*side - ad*ad) / (2*side*ad)))
+ side = math.sqrt(2 * ac * ac + 2 * bd * bd - 4 * ad * ad) / 2.0
+ return math.tan(
+ math.pi / 2 - math.acos((ac * ac - side * side - ad * ad) / (2 * side * ad))
+ )
+
class PrinterSkew:
def __init__(self, config):
@@ -22,82 +25,103 @@ class PrinterSkew:
self.name = config.get_name()
self.current_profile_name = ""
self.toolhead = None
- self.xy_factor = 0.
- self.xz_factor = 0.
- self.yz_factor = 0.
+ self.xy_factor = 0.0
+ self.xz_factor = 0.0
+ self.yz_factor = 0.0
self.skew_profiles = {}
self._load_storage(config)
- self.printer.register_event_handler("klippy:connect",
- self._handle_connect)
+ self.printer.register_event_handler("klippy:connect", self._handle_connect)
self.next_transform = None
- gcode = self.printer.lookup_object('gcode')
- gcode.register_command('GET_CURRENT_SKEW', self.cmd_GET_CURRENT_SKEW,
- desc=self.cmd_GET_CURRENT_SKEW_help)
- gcode.register_command('CALC_MEASURED_SKEW',
- self.cmd_CALC_MEASURED_SKEW,
- desc=self.cmd_CALC_MEASURED_SKEW_help)
- gcode.register_command('SET_SKEW', self.cmd_SET_SKEW,
- desc=self.cmd_SET_SKEW_help)
- gcode.register_command('SKEW_PROFILE', self.cmd_SKEW_PROFILE,
- desc=self.cmd_SKEW_PROFILE_help)
+ gcode = self.printer.lookup_object("gcode")
+ gcode.register_command(
+ "GET_CURRENT_SKEW",
+ self.cmd_GET_CURRENT_SKEW,
+ desc=self.cmd_GET_CURRENT_SKEW_help,
+ )
+ gcode.register_command(
+ "CALC_MEASURED_SKEW",
+ self.cmd_CALC_MEASURED_SKEW,
+ desc=self.cmd_CALC_MEASURED_SKEW_help,
+ )
+ gcode.register_command(
+ "SET_SKEW", self.cmd_SET_SKEW, desc=self.cmd_SET_SKEW_help
+ )
+ gcode.register_command(
+ "SKEW_PROFILE", self.cmd_SKEW_PROFILE, desc=self.cmd_SKEW_PROFILE_help
+ )
+
def _handle_connect(self):
- gcode_move = self.printer.lookup_object('gcode_move')
+ gcode_move = self.printer.lookup_object("gcode_move")
self.next_transform = gcode_move.set_move_transform(self, force=True)
+
def _load_storage(self, config):
stored_profs = config.get_prefix_sections(self.name)
# Remove primary skew_correction section, as it is not a stored profile
- stored_profs = [s for s in stored_profs
- if s.get_name() != self.name]
+ stored_profs = [s for s in stored_profs if s.get_name() != self.name]
for profile in stored_profs:
- name = profile.get_name().split(' ', 1)[1]
+ name = profile.get_name().split(" ", 1)[1]
self.skew_profiles[name] = {
- 'xy_skew': profile.getfloat("xy_skew"),
- 'xz_skew': profile.getfloat("xz_skew"),
- 'yz_skew': profile.getfloat("yz_skew"),
+ "xy_skew": profile.getfloat("xy_skew"),
+ "xz_skew": profile.getfloat("xz_skew"),
+ "yz_skew": profile.getfloat("yz_skew"),
}
+
def calc_skew(self, pos):
- skewed_x = pos[0] - pos[1] * self.xy_factor \
+ skewed_x = (
+ pos[0]
+ - pos[1] * self.xy_factor
- pos[2] * (self.xz_factor - (self.xy_factor * self.yz_factor))
+ )
skewed_y = pos[1] - pos[2] * self.yz_factor
return [skewed_x, skewed_y] + pos[2:]
+
def calc_unskew(self, pos):
- skewed_x = pos[0] + pos[1] * self.xy_factor \
- + pos[2] * self.xz_factor
+ skewed_x = pos[0] + pos[1] * self.xy_factor + pos[2] * self.xz_factor
skewed_y = pos[1] + pos[2] * self.yz_factor
return [skewed_x, skewed_y] + pos[2:]
+
def get_position(self):
return self.calc_unskew(self.next_transform.get_position())
+
def move(self, newpos, speed):
corrected_pos = self.calc_skew(newpos)
self.next_transform.move(corrected_pos, speed)
+
def _update_skew(self, xy_factor, xz_factor, yz_factor):
self.xy_factor = xy_factor
self.xz_factor = xz_factor
self.yz_factor = yz_factor
- gcode_move = self.printer.lookup_object('gcode_move')
+ gcode_move = self.printer.lookup_object("gcode_move")
gcode_move.reset_last_position()
+
cmd_GET_CURRENT_SKEW_help = "Report current printer skew"
+
def cmd_GET_CURRENT_SKEW(self, gcmd):
out = "Current Printer Skew:"
planes = ["XY", "XZ", "YZ"]
factors = [self.xy_factor, self.xz_factor, self.yz_factor]
for plane, fac in zip(planes, factors):
- out += '\n' + plane
- out += " Skew: %.6f radians, %.2f degrees" % (
- fac, math.degrees(fac))
+ out += "\n" + plane
+ out += " Skew: %.6f radians, %.2f degrees" % (fac, math.degrees(fac))
gcmd.respond_info(out)
+
cmd_CALC_MEASURED_SKEW_help = "Calculate skew from measured print"
+
def cmd_CALC_MEASURED_SKEW(self, gcmd):
- ac = gcmd.get_float("AC", above=0.)
- bd = gcmd.get_float("BD", above=0.)
- ad = gcmd.get_float("AD", above=0.)
+ ac = gcmd.get_float("AC", above=0.0)
+ bd = gcmd.get_float("BD", above=0.0)
+ ad = gcmd.get_float("AD", above=0.0)
factor = calc_skew_factor(ac, bd, ad)
- gcmd.respond_info("Calculated Skew: %.6f radians, %.2f degrees"
- % (factor, math.degrees(factor)))
+ gcmd.respond_info(
+ "Calculated Skew: %.6f radians, %.2f degrees"
+ % (factor, math.degrees(factor))
+ )
+
cmd_SET_SKEW_help = "Set skew based on lengths of measured object"
+
def cmd_SET_SKEW(self, gcmd):
if gcmd.get_int("CLEAR", 0):
- self._update_skew(0., 0., 0.)
+ self._update_skew(0.0, 0.0, 0.0)
return
planes = ["XY", "XZ", "YZ"]
for plane in planes:
@@ -111,57 +135,61 @@ class PrinterSkew:
except Exception:
raise gcmd.error(
"skew_correction: improperly formatted entry for "
- "plane [%s]\n%s" % (plane, gcmd.get_commandline()))
- factor = plane.lower() + '_factor'
+ "plane [%s]\n%s" % (plane, gcmd.get_commandline())
+ )
+ factor = plane.lower() + "_factor"
setattr(self, factor, calc_skew_factor(*lengths))
+
cmd_SKEW_PROFILE_help = "Profile management for skew_correction"
+
def cmd_SKEW_PROFILE(self, gcmd):
- if gcmd.get('LOAD', None) is not None:
- name = gcmd.get('LOAD')
+ if gcmd.get("LOAD", None) is not None:
+ name = gcmd.get("LOAD")
self.current_profile_name = name
prof = self.skew_profiles.get(name)
if prof is None:
gcmd.respond_info(
- "skew_correction: Load failed, unknown profile [%s]"
- % (name))
+ "skew_correction: Load failed, unknown profile [%s]" % (name)
+ )
return
- self._update_skew(prof['xy_skew'], prof['xz_skew'], prof['yz_skew'])
- elif gcmd.get('SAVE', None) is not None:
- name = gcmd.get('SAVE')
- configfile = self.printer.lookup_object('configfile')
+ self._update_skew(prof["xy_skew"], prof["xz_skew"], prof["yz_skew"])
+ elif gcmd.get("SAVE", None) is not None:
+ name = gcmd.get("SAVE")
+ configfile = self.printer.lookup_object("configfile")
cfg_name = self.name + " " + name
- configfile.set(cfg_name, 'xy_skew', self.xy_factor)
- configfile.set(cfg_name, 'xz_skew', self.xz_factor)
- configfile.set(cfg_name, 'yz_skew', self.yz_factor)
+ configfile.set(cfg_name, "xy_skew", self.xy_factor)
+ configfile.set(cfg_name, "xz_skew", self.xz_factor)
+ configfile.set(cfg_name, "yz_skew", self.yz_factor)
# Copy to local storage
self.skew_profiles[name] = {
- 'xy_skew': self.xy_factor,
- 'xz_skew': self.xz_factor,
- 'yz_skew': self.yz_factor
+ "xy_skew": self.xy_factor,
+ "xz_skew": self.xz_factor,
+ "yz_skew": self.yz_factor,
}
gcmd.respond_info(
"Skew Correction state has been saved to profile [%s]\n"
"for the current session. The SAVE_CONFIG command will\n"
- "update the printer config file and restart the printer."
- % (name))
- elif gcmd.get('REMOVE', None) is not None:
- name = gcmd.get('REMOVE')
+ "update the printer config file and restart the printer." % (name)
+ )
+ elif gcmd.get("REMOVE", None) is not None:
+ name = gcmd.get("REMOVE")
if name in self.skew_profiles:
- configfile = self.printer.lookup_object('configfile')
- configfile.remove_section('skew_correction ' + name)
+ configfile = self.printer.lookup_object("configfile")
+ configfile.remove_section("skew_correction " + name)
del self.skew_profiles[name]
gcmd.respond_info(
"Profile [%s] removed from storage for this session.\n"
"The SAVE_CONFIG command will update the printer\n"
- "configuration and restart the printer" % (name))
+ "configuration and restart the printer" % (name)
+ )
else:
gcmd.respond_info(
- "skew_correction: No profile named [%s] to remove"
- % (name))
+ "skew_correction: No profile named [%s] to remove" % (name)
+ )
+
def get_status(self, eventtime):
- return {
- 'current_profile_name': self.current_profile_name
- }
+ return {"current_profile_name": self.current_profile_name}
+
def load_config(config):
return PrinterSkew(config)
diff --git a/klippy/extras/smart_effector.py b/klippy/extras/smart_effector.py
index 3caa4e6b..2ddee5e9 100644
--- a/klippy/extras/smart_effector.py
+++ b/klippy/extras/smart_effector.py
@@ -9,28 +9,33 @@ from . import probe
# SmartEffector communication protocol implemented here originates from
# https://github.com/Duet3D/SmartEffectorFirmware
-BITS_PER_SECOND = 1000.
+BITS_PER_SECOND = 1000.0
+
class ControlPinHelper:
def __init__(self, pin_params):
- self._mcu = pin_params['chip']
- self._pin = pin_params['pin']
- self._start_value = self._invert = pin_params['invert']
+ self._mcu = pin_params["chip"]
+ self._pin = pin_params["pin"]
+ self._start_value = self._invert = pin_params["invert"]
self._oid = None
self._set_cmd = None
self._mcu.register_config_callback(self._build_config)
+
def _build_config(self):
self._mcu.request_move_queue_slot()
self._oid = self._mcu.create_oid()
self._mcu.add_config_cmd(
"config_digital_out oid=%d pin=%s value=%d default_value=%d"
- " max_duration=%d" % (self._oid, self._pin, self._start_value,
- self._start_value, 0))
+ " max_duration=%d"
+ % (self._oid, self._pin, self._start_value, self._start_value, 0)
+ )
cmd_queue = self._mcu.alloc_command_queue()
self._set_cmd = self._mcu.lookup_command(
- "queue_digital_out oid=%c clock=%u on_ticks=%u", cq=cmd_queue)
+ "queue_digital_out oid=%c clock=%u on_ticks=%u", cq=cmd_queue
+ )
+
def write_bits(self, start_time, bit_stream):
- bit_step = 1. / BITS_PER_SECOND
+ bit_step = 1.0 / BITS_PER_SECOND
last_value = self._start_value
bit_time = start_time
for b in bit_stream:
@@ -48,12 +53,13 @@ class ControlPinHelper:
bit_time += bit_step
return bit_time
+
class SmartEffectorProbe:
def __init__(self, config):
self.printer = config.get_printer()
- self.gcode = self.printer.lookup_object('gcode')
- self.probe_accel = config.getfloat('probe_accel', 0., minval=0.)
- self.recovery_time = config.getfloat('recovery_time', 0.4, minval=0.)
+ self.gcode = self.printer.lookup_object("gcode")
+ self.probe_accel = config.getfloat("probe_accel", 0.0, minval=0.0)
+ self.recovery_time = config.getfloat("recovery_time", 0.4, minval=0.0)
self.probe_wrapper = probe.ProbeEndstopWrapper(config)
# Wrappers
self.get_mcu = self.probe_wrapper.get_mcu
@@ -67,51 +73,61 @@ class SmartEffectorProbe:
self.get_position_endstop = self.probe_wrapper.get_position_endstop
# Common probe implementation helpers
self.cmd_helper = probe.ProbeCommandHelper(
- config, self, self.probe_wrapper.query_endstop)
+ config, self, self.probe_wrapper.query_endstop
+ )
self.probe_offsets = probe.ProbeOffsetsHelper(config)
self.param_helper = probe.ProbeParameterHelper(config)
- self.homing_helper = probe.HomingViaProbeHelper(config, self,
- self.param_helper)
+ self.homing_helper = probe.HomingViaProbeHelper(config, self, self.param_helper)
self.probe_session = probe.ProbeSessionHelper(
- config, self.param_helper, self.homing_helper.start_probe_session)
+ config, self.param_helper, self.homing_helper.start_probe_session
+ )
# SmartEffector control
- control_pin = config.get('control_pin', None)
+ control_pin = config.get("control_pin", None)
if control_pin:
- ppins = self.printer.lookup_object('pins')
+ ppins = self.printer.lookup_object("pins")
pin_params = ppins.lookup_pin(control_pin, can_invert=True)
self.control_pin = ControlPinHelper(pin_params)
- self.gcode.register_command("RESET_SMART_EFFECTOR",
- self.cmd_RESET_SMART_EFFECTOR,
- desc=self.cmd_RESET_SMART_EFFECTOR_help)
+ self.gcode.register_command(
+ "RESET_SMART_EFFECTOR",
+ self.cmd_RESET_SMART_EFFECTOR,
+ desc=self.cmd_RESET_SMART_EFFECTOR_help,
+ )
else:
self.control_pin = None
- self.gcode.register_command("SET_SMART_EFFECTOR",
- self.cmd_SET_SMART_EFFECTOR,
- desc=self.cmd_SET_SMART_EFFECTOR_help)
+ self.gcode.register_command(
+ "SET_SMART_EFFECTOR",
+ self.cmd_SET_SMART_EFFECTOR,
+ desc=self.cmd_SET_SMART_EFFECTOR_help,
+ )
+
def get_probe_params(self, gcmd=None):
return self.param_helper.get_probe_params(gcmd)
+
def get_offsets(self):
return self.probe_offsets.get_offsets()
+
def get_status(self, eventtime):
return self.cmd_helper.get_status(eventtime)
+
def start_probe_session(self, gcmd):
return self.probe_session.start_probe_session(gcmd)
+
def probe_prepare(self, hmove):
- toolhead = self.printer.lookup_object('toolhead')
+ toolhead = self.printer.lookup_object("toolhead")
self.probe_wrapper.probe_prepare(hmove)
if self.probe_accel:
systime = self.printer.get_reactor().monotonic()
toolhead_info = toolhead.get_status(systime)
- self.old_max_accel = toolhead_info['max_accel']
- self.gcode.run_script_from_command(
- "M204 S%.3f" % (self.probe_accel,))
+ self.old_max_accel = toolhead_info["max_accel"]
+ self.gcode.run_script_from_command("M204 S%.3f" % (self.probe_accel,))
if self.recovery_time:
toolhead.dwell(self.recovery_time)
+
def probe_finish(self, hmove):
if self.probe_accel:
- self.gcode.run_script_from_command(
- "M204 S%.3f" % (self.old_max_accel,))
+ self.gcode.run_script_from_command("M204 S%.3f" % (self.old_max_accel,))
self.probe_wrapper.probe_finish(hmove)
+
def _send_command(self, buf):
# Each byte is sent to the SmartEffector as
# [0 0 1 0 b7 b6 b5 b4 !b4 b3 b2 b1 b0 !b0]
@@ -124,7 +140,7 @@ class SmartEffectorProbe:
bit_stream.extend([b & 0x08, b & 0x04, b & 0x02, b & 0x01])
bit_stream.append((~b) & 0x01)
# Wait for previous actions to finish
- toolhead = self.printer.lookup_object('toolhead')
+ toolhead = self.printer.lookup_object("toolhead")
toolhead.wait_moves()
start_time = toolhead.get_last_move_time()
# Write generated bits to the control pin
@@ -133,9 +149,11 @@ class SmartEffectorProbe:
# with the SmartEffector programming
toolhead.dwell(end_time - start_time)
toolhead.wait_moves()
- cmd_SET_SMART_EFFECTOR_help = 'Set SmartEffector parameters'
+
+ cmd_SET_SMART_EFFECTOR_help = "Set SmartEffector parameters"
+
def cmd_SET_SMART_EFFECTOR(self, gcmd):
- sensitivity = gcmd.get_int('SENSITIVITY', None, minval=0, maxval=255)
+ sensitivity = gcmd.get_int("SENSITIVITY", None, minval=0, maxval=255)
respond_info = []
if sensitivity is not None:
if self.control_pin is not None:
@@ -143,29 +161,33 @@ class SmartEffectorProbe:
self._send_command(buf)
respond_info.append("sensitivity: %d" % (sensitivity,))
else:
- raise gcmd.error("control_pin must be set in [smart_effector] "
- "for sensitivity programming")
- self.probe_accel = gcmd.get_float('ACCEL', self.probe_accel, minval=0.)
- self.recovery_time = gcmd.get_float('RECOVERY_TIME', self.recovery_time,
- minval=0.)
+ raise gcmd.error(
+ "control_pin must be set in [smart_effector] "
+ "for sensitivity programming"
+ )
+ self.probe_accel = gcmd.get_float("ACCEL", self.probe_accel, minval=0.0)
+ self.recovery_time = gcmd.get_float(
+ "RECOVERY_TIME", self.recovery_time, minval=0.0
+ )
if self.probe_accel:
- respond_info.append(
- "probing accelartion: %.3f" % (self.probe_accel,))
+ respond_info.append("probing accelartion: %.3f" % (self.probe_accel,))
else:
respond_info.append("probing acceleration control disabled")
if self.recovery_time:
- respond_info.append(
- "probe recovery time: %.3f" % (self.recovery_time,))
+ respond_info.append("probe recovery time: %.3f" % (self.recovery_time,))
else:
respond_info.append("probe recovery time disabled")
gcmd.respond_info("SmartEffector:\n" + "\n".join(respond_info))
- cmd_RESET_SMART_EFFECTOR_help = 'Reset SmartEffector settings (sensitivity)'
+
+ cmd_RESET_SMART_EFFECTOR_help = "Reset SmartEffector settings (sensitivity)"
+
def cmd_RESET_SMART_EFFECTOR(self, gcmd):
buf = [131, 131]
self._send_command(buf)
- gcmd.respond_info('SmartEffector sensitivity was reset')
+ gcmd.respond_info("SmartEffector sensitivity was reset")
+
def load_config(config):
smart_effector = SmartEffectorProbe(config)
- config.get_printer().add_object('probe', smart_effector)
+ config.get_printer().add_object("probe", smart_effector)
return smart_effector
diff --git a/klippy/extras/sos_filter.py b/klippy/extras/sos_filter.py
index 293b2258..07517abf 100644
--- a/klippy/extras/sos_filter.py
+++ b/klippy/extras/sos_filter.py
@@ -4,26 +4,37 @@
#
# This file may be distributed under the terms of the GNU GPLv3 license.
-MAX_INT32 = (2 ** 31)
-MIN_INT32 = -(2 ** 31) - 1
+MAX_INT32 = 2**31
+MIN_INT32 = -(2**31) - 1
+
+
def assert_is_int32(value, error):
if value > MAX_INT32 or value < MIN_INT32:
raise OverflowError(error)
return value
+
# convert a floating point value to a 32 bit fixed point representation
# checks for overflow
def to_fixed_32(value, int_bits):
- fractional_bits = (32 - (1 + int_bits))
- fixed_val = int(value * (2 ** fractional_bits))
- return assert_is_int32(fixed_val, "Fixed point Q%i overflow"
- % (int_bits,))
+ fractional_bits = 32 - (1 + int_bits)
+ fixed_val = int(value * (2**fractional_bits))
+ return assert_is_int32(fixed_val, "Fixed point Q%i overflow" % (int_bits,))
# Digital filter designer and container
class DigitalFilter:
- def __init__(self, sps, cfg_error, highpass=None, highpass_order=1,
- lowpass=None, lowpass_order=1, notches=None, notch_quality=2.0):
+ def __init__(
+ self,
+ sps,
+ cfg_error,
+ highpass=None,
+ highpass_order=1,
+ lowpass=None,
+ lowpass_order=1,
+ notches=None,
+ notch_quality=2.0,
+ ):
self.filter_sections = []
self.initial_state = []
self.sample_frequency = sps
@@ -36,10 +47,10 @@ class DigitalFilter:
raise cfg_error("DigitalFilter require the SciPy module")
if highpass:
self.filter_sections.append(
- self._butter(highpass, "highpass", highpass_order))
+ self._butter(highpass, "highpass", highpass_order)
+ )
if lowpass:
- self.filter_sections.append(
- self._butter(lowpass, "lowpass", lowpass_order))
+ self.filter_sections.append(self._butter(lowpass, "lowpass", lowpass_order))
for notch_freq in notches:
self.filter_sections.append(self._notch(notch_freq, notch_quality))
if len(self.filter_sections) > 0:
@@ -47,11 +58,14 @@ class DigitalFilter:
def _butter(self, frequency, btype, order):
import scipy.signal as signal
- return signal.butter(order, Wn=frequency, btype=btype,
- fs=self.sample_frequency, output='sos')[0]
+
+ return signal.butter(
+ order, Wn=frequency, btype=btype, fs=self.sample_frequency, output="sos"
+ )[0]
def _notch(self, freq, quality):
import scipy.signal as signal
+
b, a = signal.iirnotch(freq, Q=quality, fs=self.sample_frequency)
return signal.tf2sos(b, a)[0]
@@ -61,22 +75,29 @@ class DigitalFilter:
def get_initial_state(self):
return self.initial_state
+
# container that accepts SciPy formatted SOS filter data and converts it to a
# selected fixed point representation. This data could come from DigitalFilter,
# static data, config etc.
class FixedPointSosFilter:
# filter_sections is an array of SciPy formatted SOS filter sections (sos)
# initial_state is an array of SciPy formatted SOS state sections (zi)
- def __init__(self, filter_sections=None, initial_state=None,
- coeff_int_bits=2, value_int_bits=15):
+ def __init__(
+ self,
+ filter_sections=None,
+ initial_state=None,
+ coeff_int_bits=2,
+ value_int_bits=15,
+ ):
filter_sections = [] if filter_sections is None else filter_sections
initial_state = [] if initial_state is None else initial_state
num_sections = len(filter_sections)
num_state = len(initial_state)
if num_state != num_sections:
- raise ValueError("The number of filter sections (%i) and state "
- "sections (%i) must be equal" % (
- num_sections, num_state))
+ raise ValueError(
+ "The number of filter sections (%i) and state "
+ "sections (%i) must be equal" % (num_sections, num_state)
+ )
self._coeff_int_bits = self._validate_int_bits(coeff_int_bits)
self._value_int_bits = self._validate_int_bits(value_int_bits)
self._filter = self._convert_filter(filter_sections)
@@ -99,8 +120,10 @@ class FixedPointSosFilter:
def _validate_int_bits(self, int_bits):
if int_bits < 1 or int_bits > 30:
- raise ValueError("The number of integer bits (%i) must be a"
- " value between 1 and 30" % (int_bits,))
+ raise ValueError(
+ "The number of integer bits (%i) must be a"
+ " value between 1 and 30" % (int_bits,)
+ )
return int_bits
# convert the SciPi SOS filters to fixed point format
@@ -109,16 +132,19 @@ class FixedPointSosFilter:
for section in filter_sections:
nun_coeff = len(section)
if nun_coeff != 6:
- raise ValueError("The number of filter coefficients is %i"
- ", must be 6" % (nun_coeff,))
+ raise ValueError(
+ "The number of filter coefficients is %i"
+ ", must be 6" % (nun_coeff,)
+ )
fixed_section = []
for col, coeff in enumerate(section):
if col != 3: # omit column 3
fixed_coeff = to_fixed_32(coeff, self._coeff_int_bits)
fixed_section.append(fixed_coeff)
elif coeff != 1.0: # double check column 3 is always 1.0
- raise ValueError("Coefficient 3 is expected to be 1.0"
- " but was %f" % (coeff,))
+ raise ValueError(
+ "Coefficient 3 is expected to be 1.0" " but was %f" % (coeff,)
+ )
sos_fixed.append(fixed_section)
return sos_fixed
@@ -129,8 +155,8 @@ class FixedPointSosFilter:
nun_states = len(section)
if nun_states != 2:
raise ValueError(
- "The number of state elements is %i, must be 2"
- % (nun_states,))
+ "The number of state elements is %i, must be 2" % (nun_states,)
+ )
fixed_state = []
for col, value in enumerate(section):
fixed_state.append(to_fixed_32(value, self._value_int_bits))
@@ -154,20 +180,22 @@ class SosFilter:
self._max_sections = self._filter.get_num_sections()
self._cmd_set_section = [
"sos_filter_set_section oid=%d section_idx=%d"
- " sos0=%i sos1=%i sos2=%i sos3=%i sos4=%i",
+ " sos0=%i sos1=%i sos2=%i sos3=%i sos4=%i",
"sos_filter_set_section oid=%c section_idx=%c"
- " sos0=%i sos1=%i sos2=%i sos3=%i sos4=%i"]
+ " sos0=%i sos1=%i sos2=%i sos3=%i sos4=%i",
+ ]
self._cmd_config_state = [
"sos_filter_set_state oid=%d section_idx=%d state0=%i state1=%i",
- "sos_filter_set_state oid=%c section_idx=%c state0=%i state1=%i"]
+ "sos_filter_set_state oid=%c section_idx=%c state0=%i state1=%i",
+ ]
self._cmd_activate = [
"sos_filter_set_active oid=%d n_sections=%d coeff_int_bits=%d",
- "sos_filter_set_active oid=%c n_sections=%c coeff_int_bits=%c"]
+ "sos_filter_set_active oid=%c n_sections=%c coeff_int_bits=%c",
+ ]
self._mcu.register_config_callback(self._build_config)
def _build_config(self):
- cmds = [self._cmd_set_section, self._cmd_config_state,
- self._cmd_activate]
+ cmds = [self._cmd_set_section, self._cmd_config_state, self._cmd_activate]
for cmd in cmds:
cmd.append(self._mcu.lookup_command(cmd[1], cq=self._cmd_queue))
@@ -176,8 +204,9 @@ class SosFilter:
# create an uninitialized filter object
def create_filter(self):
- self._mcu.add_config_cmd("config_sos_filter oid=%d max_sections=%d"
- % (self._oid, self._max_sections))
+ self._mcu.add_config_cmd(
+ "config_sos_filter oid=%d max_sections=%d" % (self._oid, self._max_sections)
+ )
self._configure_filter(is_init=True)
# either setup an init command or send the command based on a flag
@@ -189,8 +218,15 @@ class SosFilter:
def _set_filter_sections(self, is_init=False):
for i, section in enumerate(self._filter.get_filter_sections()):
- args = (self._oid, i, section[0], section[1], section[2],
- section[3], section[4])
+ args = (
+ self._oid,
+ i,
+ section[0],
+ section[1],
+ section[2],
+ section[3],
+ section[4],
+ )
self._cmd(self._cmd_set_section, args, is_init)
def _set_filter_state(self, is_init=False):
@@ -199,8 +235,11 @@ class SosFilter:
self._cmd(self._cmd_config_state, args, is_init)
def _activate_filter(self, is_init=False):
- args = (self._oid, self._filter.get_num_sections(),
- self._filter.get_coeff_int_bits())
+ args = (
+ self._oid,
+ self._filter.get_num_sections(),
+ self._filter.get_coeff_int_bits(),
+ )
self._cmd(self._cmd_activate, args, is_init)
# configure the filter sections on the mcu
@@ -209,13 +248,20 @@ class SosFilter:
def _configure_filter(self, is_init=False):
num_sections = self._filter.get_num_sections()
if num_sections > self._max_sections:
- raise ValueError("Too many filter sections: %i, The max is %i"
- % (num_sections, self._max_sections,))
+ raise ValueError(
+ "Too many filter sections: %i, The max is %i"
+ % (
+ num_sections,
+ self._max_sections,
+ )
+ )
# convert to fixed point to find errors
# no errors, state is accepted
# configure MCU filter and activate
self._set_filter_sections(is_init)
- self._set_filter_state(is_init,)
+ self._set_filter_state(
+ is_init,
+ )
self._activate_filter(is_init)
# Change the filter coefficients and state at runtime
diff --git a/klippy/extras/spi_temperature.py b/klippy/extras/spi_temperature.py
index 92d1b385..38d6b559 100644
--- a/klippy/extras/spi_temperature.py
+++ b/klippy/extras/spi_temperature.py
@@ -15,6 +15,7 @@ from . import bus
REPORT_TIME = 0.300
MAX_INVALID_COUNT = 3
+
class SensorBase:
def __init__(self, config, chip_type, config_cmd=None, spi_mode=1):
self.printer = config.get_printer()
@@ -23,44 +24,58 @@ class SensorBase:
self.min_sample_value = self.max_sample_value = 0
self._report_clock = 0
self.spi = bus.MCU_SPI_from_config(
- config, spi_mode, pin_option="sensor_pin", default_speed=4000000)
+ config, spi_mode, pin_option="sensor_pin", default_speed=4000000
+ )
if config_cmd is not None:
self.spi.spi_send(config_cmd)
self.mcu = mcu = self.spi.get_mcu()
# Reader chip configuration
self.oid = oid = mcu.create_oid()
- mcu.register_response(self._handle_spi_response,
- "thermocouple_result", oid)
+ mcu.register_response(self._handle_spi_response, "thermocouple_result", oid)
mcu.register_config_callback(self._build_config)
+
def setup_minmax(self, min_temp, max_temp):
adc_range = [self.calc_adc(min_temp), self.calc_adc(max_temp)]
self.min_sample_value = min(adc_range)
self.max_sample_value = max(adc_range)
+
def setup_callback(self, cb):
self._callback = cb
+
def get_report_time_delta(self):
return REPORT_TIME
+
def _build_config(self):
self.mcu.add_config_cmd(
- "config_thermocouple oid=%u spi_oid=%u thermocouple_type=%s" % (
- self.oid, self.spi.get_oid(), self.chip_type))
+ "config_thermocouple oid=%u spi_oid=%u thermocouple_type=%s"
+ % (self.oid, self.spi.get_oid(), self.chip_type)
+ )
clock = self.mcu.get_query_slot(self.oid)
self._report_clock = self.mcu.seconds_to_clock(REPORT_TIME)
self.mcu.add_config_cmd(
"query_thermocouple oid=%u clock=%u rest_ticks=%u"
- " min_value=%u max_value=%u max_invalid_count=%u" % (
- self.oid, clock, self._report_clock,
- self.min_sample_value, self.max_sample_value,
- MAX_INVALID_COUNT), is_init=True)
+ " min_value=%u max_value=%u max_invalid_count=%u"
+ % (
+ self.oid,
+ clock,
+ self._report_clock,
+ self.min_sample_value,
+ self.max_sample_value,
+ MAX_INVALID_COUNT,
+ ),
+ is_init=True,
+ )
+
def _handle_spi_response(self, params):
- if params['fault']:
- self.handle_fault(params['value'], params['fault'])
+ if params["fault"]:
+ self.handle_fault(params["value"], params["fault"])
return
- temp = self.calc_temp(params['value'])
- next_clock = self.mcu.clock32_to_clock64(params['next_clock'])
+ temp = self.calc_temp(params["value"])
+ next_clock = self.mcu.clock32_to_clock64(params["next_clock"])
last_read_clock = next_clock - self._report_clock
- last_read_time = self.mcu.clock_to_print_time(last_read_clock)
+ last_read_time = self.mcu.clock_to_print_time(last_read_clock)
self._callback(last_read_time, temp)
+
def report_fault(self, msg):
logging.warning(msg)
@@ -69,62 +84,63 @@ class SensorBase:
# MAX31856 thermocouple
######################################################################
-MAX31856_CR0_REG = 0x00
-MAX31856_CR0_AUTOCONVERT = 0x80
-MAX31856_CR0_1SHOT = 0x40
-MAX31856_CR0_OCFAULT1 = 0x20
-MAX31856_CR0_OCFAULT0 = 0x10
-MAX31856_CR0_CJ = 0x08
-MAX31856_CR0_FAULT = 0x04
-MAX31856_CR0_FAULTCLR = 0x02
-MAX31856_CR0_FILT50HZ = 0x01
-MAX31856_CR0_FILT60HZ = 0x00
+MAX31856_CR0_REG = 0x00
+MAX31856_CR0_AUTOCONVERT = 0x80
+MAX31856_CR0_1SHOT = 0x40
+MAX31856_CR0_OCFAULT1 = 0x20
+MAX31856_CR0_OCFAULT0 = 0x10
+MAX31856_CR0_CJ = 0x08
+MAX31856_CR0_FAULT = 0x04
+MAX31856_CR0_FAULTCLR = 0x02
+MAX31856_CR0_FILT50HZ = 0x01
+MAX31856_CR0_FILT60HZ = 0x00
-MAX31856_CR1_REG = 0x01
-MAX31856_CR1_AVGSEL1 = 0x00
-MAX31856_CR1_AVGSEL2 = 0x10
-MAX31856_CR1_AVGSEL4 = 0x20
-MAX31856_CR1_AVGSEL8 = 0x30
-MAX31856_CR1_AVGSEL16 = 0x70
+MAX31856_CR1_REG = 0x01
+MAX31856_CR1_AVGSEL1 = 0x00
+MAX31856_CR1_AVGSEL2 = 0x10
+MAX31856_CR1_AVGSEL4 = 0x20
+MAX31856_CR1_AVGSEL8 = 0x30
+MAX31856_CR1_AVGSEL16 = 0x70
-MAX31856_MASK_REG = 0x02
-MAX31856_MASK_COLD_JUNCTION_HIGH_FAULT = 0x20
-MAX31856_MASK_COLD_JUNCTION_LOW_FAULT = 0x10
-MAX31856_MASK_THERMOCOUPLE_HIGH_FAULT = 0x08
-MAX31856_MASK_THERMOCOUPLE_LOW_FAULT = 0x04
-MAX31856_MASK_VOLTAGE_UNDER_OVER_FAULT = 0x02
-MAX31856_MASK_THERMOCOUPLE_OPEN_FAULT = 0x01
+MAX31856_MASK_REG = 0x02
+MAX31856_MASK_COLD_JUNCTION_HIGH_FAULT = 0x20
+MAX31856_MASK_COLD_JUNCTION_LOW_FAULT = 0x10
+MAX31856_MASK_THERMOCOUPLE_HIGH_FAULT = 0x08
+MAX31856_MASK_THERMOCOUPLE_LOW_FAULT = 0x04
+MAX31856_MASK_VOLTAGE_UNDER_OVER_FAULT = 0x02
+MAX31856_MASK_THERMOCOUPLE_OPEN_FAULT = 0x01
-MAX31856_CJHF_REG = 0x03
-MAX31856_CJLF_REG = 0x04
-MAX31856_LTHFTH_REG = 0x05
-MAX31856_LTHFTL_REG = 0x06
-MAX31856_LTLFTH_REG = 0x07
-MAX31856_LTLFTL_REG = 0x08
-MAX31856_CJTO_REG = 0x09
-MAX31856_CJTH_REG = 0x0A
-MAX31856_CJTL_REG = 0x0B
-MAX31856_LTCBH_REG = 0x0C
-MAX31856_LTCBM_REG = 0x0D
-MAX31856_LTCBL_REG = 0x0E
+MAX31856_CJHF_REG = 0x03
+MAX31856_CJLF_REG = 0x04
+MAX31856_LTHFTH_REG = 0x05
+MAX31856_LTHFTL_REG = 0x06
+MAX31856_LTLFTH_REG = 0x07
+MAX31856_LTLFTL_REG = 0x08
+MAX31856_CJTO_REG = 0x09
+MAX31856_CJTH_REG = 0x0A
+MAX31856_CJTL_REG = 0x0B
+MAX31856_LTCBH_REG = 0x0C
+MAX31856_LTCBM_REG = 0x0D
+MAX31856_LTCBL_REG = 0x0E
-MAX31856_SR_REG = 0x0F
-MAX31856_FAULT_CJRANGE = 0x80 # Cold Junction out of range
-MAX31856_FAULT_TCRANGE = 0x40 # Thermocouple out of range
-MAX31856_FAULT_CJHIGH = 0x20 # Cold Junction High
-MAX31856_FAULT_CJLOW = 0x10 # Cold Junction Low
-MAX31856_FAULT_TCHIGH = 0x08 # Thermocouple Low
-MAX31856_FAULT_TCLOW = 0x04 # Thermocouple Low
-MAX31856_FAULT_OVUV = 0x02 # Under Over Voltage
-MAX31856_FAULT_OPEN = 0x01
+MAX31856_SR_REG = 0x0F
+MAX31856_FAULT_CJRANGE = 0x80 # Cold Junction out of range
+MAX31856_FAULT_TCRANGE = 0x40 # Thermocouple out of range
+MAX31856_FAULT_CJHIGH = 0x20 # Cold Junction High
+MAX31856_FAULT_CJLOW = 0x10 # Cold Junction Low
+MAX31856_FAULT_TCHIGH = 0x08 # Thermocouple Low
+MAX31856_FAULT_TCLOW = 0x04 # Thermocouple Low
+MAX31856_FAULT_OVUV = 0x02 # Under Over Voltage
+MAX31856_FAULT_OPEN = 0x01
MAX31856_SCALE = 5
MAX31856_MULT = 0.0078125
+
class MAX31856(SensorBase):
def __init__(self, config):
- SensorBase.__init__(self, config, "MAX31856",
- self.build_spi_init(config))
+ SensorBase.__init__(self, config, "MAX31856", self.build_spi_init(config))
+
def handle_fault(self, adc, fault):
if fault & MAX31856_FAULT_CJRANGE:
self.report_fault("Max31856: Cold Junction Range Fault")
@@ -142,6 +158,7 @@ class MAX31856(SensorBase):
self.report_fault("Max31856: Over/Under Voltage Fault")
if fault & MAX31856_FAULT_OPEN:
self.report_fault("Max31856: Thermocouple Open Fault")
+
def calc_temp(self, adc):
adc = adc >> MAX31856_SCALE
# Fix sign bit:
@@ -149,41 +166,45 @@ class MAX31856(SensorBase):
adc = ((adc & 0x3FFFF) + 1) * -1
temp = MAX31856_MULT * adc
return temp
+
def calc_adc(self, temp):
- adc = int( ( temp / MAX31856_MULT ) + 0.5 ) # convert to ADC value
+ adc = int((temp / MAX31856_MULT) + 0.5) # convert to ADC value
adc = max(0, min(0x3FFFF, adc)) << MAX31856_SCALE
return adc
+
def build_spi_init(self, config):
cmds = []
value = MAX31856_CR0_AUTOCONVERT
- if config.getboolean('tc_use_50Hz_filter', False):
+ if config.getboolean("tc_use_50Hz_filter", False):
value |= MAX31856_CR0_FILT50HZ
cmds.append(0x80 + MAX31856_CR0_REG)
cmds.append(value)
types = {
- "B" : 0b0000,
- "E" : 0b0001,
- "J" : 0b0010,
- "K" : 0b0011,
- "N" : 0b0100,
- "R" : 0b0101,
- "S" : 0b0110,
- "T" : 0b0111,
+ "B": 0b0000,
+ "E": 0b0001,
+ "J": 0b0010,
+ "K": 0b0011,
+ "N": 0b0100,
+ "R": 0b0101,
+ "S": 0b0110,
+ "T": 0b0111,
}
- value = config.getchoice('tc_type', types, default="K")
+ value = config.getchoice("tc_type", types, default="K")
averages = {
- 1 : MAX31856_CR1_AVGSEL1,
- 2 : MAX31856_CR1_AVGSEL2,
- 4 : MAX31856_CR1_AVGSEL4,
- 8 : MAX31856_CR1_AVGSEL8,
- 16 : MAX31856_CR1_AVGSEL16
+ 1: MAX31856_CR1_AVGSEL1,
+ 2: MAX31856_CR1_AVGSEL2,
+ 4: MAX31856_CR1_AVGSEL4,
+ 8: MAX31856_CR1_AVGSEL8,
+ 16: MAX31856_CR1_AVGSEL16,
}
- value |= config.getchoice('tc_averaging_count', averages, 1)
+ value |= config.getchoice("tc_averaging_count", averages, 1)
cmds.append(value)
- value = (MAX31856_MASK_VOLTAGE_UNDER_OVER_FAULT |
- MAX31856_MASK_THERMOCOUPLE_OPEN_FAULT)
+ value = (
+ MAX31856_MASK_VOLTAGE_UNDER_OVER_FAULT
+ | MAX31856_MASK_THERMOCOUPLE_OPEN_FAULT
+ )
cmds.append(value)
return cmds
@@ -195,9 +216,11 @@ class MAX31856(SensorBase):
MAX31855_SCALE = 18
MAX31855_MULT = 0.25
+
class MAX31855(SensorBase):
def __init__(self, config):
SensorBase.__init__(self, config, "MAX31855", spi_mode=0)
+
def handle_fault(self, adc, fault):
if fault & 0x1:
self.report_fault("MAX31855 : Open Circuit")
@@ -205,6 +228,7 @@ class MAX31855(SensorBase):
self.report_fault("MAX31855 : Short to GND")
if fault & 0x4:
self.report_fault("MAX31855 : Short to Vcc")
+
def calc_temp(self, adc):
adc = adc >> MAX31855_SCALE
# Fix sign bit:
@@ -212,8 +236,9 @@ class MAX31855(SensorBase):
adc = ((adc & 0x1FFF) + 1) * -1
temp = MAX31855_MULT * adc
return temp
+
def calc_adc(self, temp):
- adc = int( ( temp / MAX31855_MULT ) + 0.5 ) # convert to ADC value
+ adc = int((temp / MAX31855_MULT) + 0.5) # convert to ADC value
adc = max(0, min(0x1FFF, adc)) << MAX31855_SCALE
return adc
@@ -225,14 +250,17 @@ class MAX31855(SensorBase):
MAX6675_SCALE = 3
MAX6675_MULT = 0.25
+
class MAX6675(SensorBase):
def __init__(self, config):
SensorBase.__init__(self, config, "MAX6675", spi_mode=0)
+
def handle_fault(self, adc, fault):
if fault & 0x02:
self.report_fault("Max6675 : Device ID error")
if fault & 0x04:
self.report_fault("Max6675 : Thermocouple Open Fault")
+
def calc_temp(self, adc):
adc = adc >> MAX6675_SCALE
# Fix sign bit:
@@ -240,8 +268,9 @@ class MAX6675(SensorBase):
adc = ((adc & 0x1FFF) + 1) * -1
temp = MAX6675_MULT * adc
return temp
+
def calc_adc(self, temp):
- adc = int( ( temp / MAX6675_MULT ) + 0.5 ) # convert to ADC value
+ adc = int((temp / MAX6675_MULT) + 0.5) # convert to ADC value
adc = max(0, min(0x1FFF, adc)) << MAX6675_SCALE
return adc
@@ -250,43 +279,45 @@ class MAX6675(SensorBase):
# MAX31865 (RTD sensor)
######################################################################
-MAX31865_CONFIG_REG = 0x00
-MAX31865_RTDMSB_REG = 0x01
-MAX31865_RTDLSB_REG = 0x02
-MAX31865_HFAULTMSB_REG = 0x03
-MAX31865_HFAULTLSB_REG = 0x04
-MAX31865_LFAULTMSB_REG = 0x05
-MAX31865_LFAULTLSB_REG = 0x06
-MAX31865_FAULTSTAT_REG = 0x07
+MAX31865_CONFIG_REG = 0x00
+MAX31865_RTDMSB_REG = 0x01
+MAX31865_RTDLSB_REG = 0x02
+MAX31865_HFAULTMSB_REG = 0x03
+MAX31865_HFAULTLSB_REG = 0x04
+MAX31865_LFAULTMSB_REG = 0x05
+MAX31865_LFAULTLSB_REG = 0x06
+MAX31865_FAULTSTAT_REG = 0x07
-MAX31865_CONFIG_BIAS = 0x80
-MAX31865_CONFIG_MODEAUTO = 0x40
-MAX31865_CONFIG_1SHOT = 0x20
-MAX31865_CONFIG_3WIRE = 0x10
-MAX31865_CONFIG_FAULTCLEAR = 0x02
-MAX31865_CONFIG_FILT50HZ = 0x01
+MAX31865_CONFIG_BIAS = 0x80
+MAX31865_CONFIG_MODEAUTO = 0x40
+MAX31865_CONFIG_1SHOT = 0x20
+MAX31865_CONFIG_3WIRE = 0x10
+MAX31865_CONFIG_FAULTCLEAR = 0x02
+MAX31865_CONFIG_FILT50HZ = 0x01
-MAX31865_FAULT_HIGHTHRESH = 0x80
-MAX31865_FAULT_LOWTHRESH = 0x40
-MAX31865_FAULT_REFINLOW = 0x20
-MAX31865_FAULT_REFINHIGH = 0x10
-MAX31865_FAULT_RTDINLOW = 0x08
-MAX31865_FAULT_OVUV = 0x04
+MAX31865_FAULT_HIGHTHRESH = 0x80
+MAX31865_FAULT_LOWTHRESH = 0x40
+MAX31865_FAULT_REFINLOW = 0x20
+MAX31865_FAULT_REFINHIGH = 0x10
+MAX31865_FAULT_RTDINLOW = 0x08
+MAX31865_FAULT_OVUV = 0x04
-MAX31865_ADC_MAX = 1<<15
+MAX31865_ADC_MAX = 1 << 15
# Callendar-Van Dusen constants for platinum resistance thermometers (RTD)
CVD_A = 3.9083e-3
CVD_B = -5.775e-7
+
class MAX31865(SensorBase):
def __init__(self, config):
- rtd_nominal_r = config.getfloat('rtd_nominal_r', 100., above=0.)
- rtd_reference_r = config.getfloat('rtd_reference_r', 430., above=0.)
+ rtd_nominal_r = config.getfloat("rtd_nominal_r", 100.0, above=0.0)
+ rtd_reference_r = config.getfloat("rtd_reference_r", 430.0, above=0.0)
adc_to_resist = rtd_reference_r / float(MAX31865_ADC_MAX)
self.adc_to_resist_div_nominal = adc_to_resist / rtd_nominal_r
self.config_reg = self.build_spi_init(config)
SensorBase.__init__(self, config, "MAX31865", self.config_reg)
+
def handle_fault(self, adc, fault):
if fault & 0x80:
self.report_fault("Max31865 RTD input is disconnected")
@@ -294,45 +325,47 @@ class MAX31865(SensorBase):
self.report_fault("Max31865 RTD input is shorted")
if fault & 0x20:
self.report_fault(
- "Max31865 VREF- is greater than 0.85 * VBIAS, FORCE- open")
+ "Max31865 VREF- is greater than 0.85 * VBIAS, FORCE- open"
+ )
if fault & 0x10:
- self.report_fault(
- "Max31865 VREF- is less than 0.85 * VBIAS, FORCE- open")
+ self.report_fault("Max31865 VREF- is less than 0.85 * VBIAS, FORCE- open")
if fault & 0x08:
- self.report_fault(
- "Max31865 VRTD- is less than 0.85 * VBIAS, FORCE- open")
+ self.report_fault("Max31865 VRTD- is less than 0.85 * VBIAS, FORCE- open")
if fault & 0x04:
self.report_fault("Max31865 Overvoltage or undervoltage fault")
- if not fault & 0xfc:
+ if not fault & 0xFC:
self.report_fault("Max31865 Unspecified error")
# Attempt to clear the fault
self.spi.spi_send(self.config_reg)
+
def calc_temp(self, adc):
- adc = adc >> 1 # remove fault bit
+ adc = adc >> 1 # remove fault bit
R_div_nominal = adc * self.adc_to_resist_div_nominal
# Resistance (relative to rtd_nominal_r) is calculated using:
# R_div_nominal = 1. + CVD_A * temp + CVD_B * temp**2
# Solve for temp using quadratic equation:
# temp = (-b +- sqrt(b**2 - 4ac)) / 2a
- discriminant = math.sqrt(CVD_A**2 - 4. * CVD_B * (1. - R_div_nominal))
- temp = (-CVD_A + discriminant) / (2. * CVD_B)
+ discriminant = math.sqrt(CVD_A**2 - 4.0 * CVD_B * (1.0 - R_div_nominal))
+ temp = (-CVD_A + discriminant) / (2.0 * CVD_B)
return temp
+
def calc_adc(self, temp):
# Calculate relative resistance via Callendar-Van Dusen formula:
# resistance = rtd_nominal_r * (1 + CVD_A * temp + CVD_B * temp**2)
temp = min(temp, 1768.3) # Melting point of platinum
- R_div_nominal = 1. + CVD_A * temp + CVD_B * temp * temp
+ R_div_nominal = 1.0 + CVD_A * temp + CVD_B * temp * temp
adc = int(R_div_nominal / self.adc_to_resist_div_nominal + 0.5)
adc = max(0, min(MAX31865_ADC_MAX - 1, adc))
- adc = adc << 1 # Add fault bit
+ adc = adc << 1 # Add fault bit
return adc
+
def build_spi_init(self, config):
- value = (MAX31865_CONFIG_BIAS |
- MAX31865_CONFIG_MODEAUTO |
- MAX31865_CONFIG_FAULTCLEAR)
- if config.getboolean('rtd_use_50Hz_filter', False):
+ value = (
+ MAX31865_CONFIG_BIAS | MAX31865_CONFIG_MODEAUTO | MAX31865_CONFIG_FAULTCLEAR
+ )
+ if config.getboolean("rtd_use_50Hz_filter", False):
value |= MAX31865_CONFIG_FILT50HZ
- if config.getint('rtd_num_of_wires', 2) == 3:
+ if config.getint("rtd_num_of_wires", 2) == 3:
value |= MAX31865_CONFIG_3WIRE
cmd = 0x80 + MAX31865_CONFIG_REG
return [cmd, value]
@@ -349,6 +382,7 @@ Sensors = {
"MAX31865": MAX31865,
}
+
def load_config(config):
# Register sensors
pheaters = config.get_printer().load_object(config, "heaters")
diff --git a/klippy/extras/static_digital_output.py b/klippy/extras/static_digital_output.py
index 2fa0bb3f..8c39fb3c 100644
--- a/klippy/extras/static_digital_output.py
+++ b/klippy/extras/static_digital_output.py
@@ -4,16 +4,20 @@
#
# This file may be distributed under the terms of the GNU GPLv3 license.
+
class PrinterStaticDigitalOut:
def __init__(self, config):
printer = config.get_printer()
- ppins = printer.lookup_object('pins')
- pin_list = config.getlist('pins')
+ ppins = printer.lookup_object("pins")
+ pin_list = config.getlist("pins")
for pin_desc in pin_list:
pin_params = ppins.lookup_pin(pin_desc, can_invert=True)
- mcu = pin_params['chip']
- mcu.add_config_cmd("set_digital_out pin=%s value=%d"
- % (pin_params['pin'], not pin_params['invert']))
+ mcu = pin_params["chip"]
+ mcu.add_config_cmd(
+ "set_digital_out pin=%s value=%d"
+ % (pin_params["pin"], not pin_params["invert"])
+ )
+
def load_config_prefix(config):
return PrinterStaticDigitalOut(config)
diff --git a/klippy/extras/statistics.py b/klippy/extras/statistics.py
index 90cd53f8..de7302e7 100644
--- a/klippy/extras/statistics.py
+++ b/klippy/extras/statistics.py
@@ -5,11 +5,12 @@
# This file may be distributed under the terms of the GNU GPLv3 license.
import os, time, logging
+
class PrinterSysStats:
def __init__(self, config):
printer = config.get_printer()
- self.last_process_time = self.total_process_time = 0.
- self.last_load_avg = 0.
+ self.last_process_time = self.total_process_time = 0.0
+ self.last_load_avg = 0.0
self.last_mem_avail = 0
self.mem_file = None
try:
@@ -17,26 +18,30 @@ class PrinterSysStats:
except:
pass
printer.register_event_handler("klippy:disconnect", self._disconnect)
+
def _disconnect(self):
if self.mem_file is not None:
self.mem_file.close()
self.mem_file = None
+
def stats(self, eventtime):
# Get core usage stats
ptime = time.process_time()
pdiff = ptime - self.last_process_time
self.last_process_time = ptime
- if pdiff > 0.:
+ if pdiff > 0.0:
self.total_process_time += pdiff
self.last_load_avg = os.getloadavg()[0]
- msg = "sysload=%.2f cputime=%.3f" % (self.last_load_avg,
- self.total_process_time)
+ msg = "sysload=%.2f cputime=%.3f" % (
+ self.last_load_avg,
+ self.total_process_time,
+ )
# Get available system memory
if self.mem_file is not None:
try:
self.mem_file.seek(0)
data = self.mem_file.read()
- for line in data.split('\n'):
+ for line in data.split("\n"):
if line.startswith("MemAvailable:"):
self.last_mem_avail = int(line.split()[1])
msg = "%s memavail=%d" % (msg, self.last_mem_avail)
@@ -44,10 +49,14 @@ class PrinterSysStats:
except:
pass
return (False, msg)
+
def get_status(self, eventtime):
- return {'sysload': self.last_load_avg,
- 'cputime': self.total_process_time,
- 'memavail': self.last_mem_avail}
+ return {
+ "sysload": self.last_load_avg,
+ "cputime": self.total_process_time,
+ "memavail": self.last_mem_avail,
+ }
+
class PrinterStats:
def __init__(self, config):
@@ -56,19 +65,22 @@ class PrinterStats:
self.stats_timer = reactor.register_timer(self.generate_stats)
self.stats_cb = []
self.printer.register_event_handler("klippy:ready", self.handle_ready)
+
def handle_ready(self):
- self.stats_cb = [o.stats for n, o in self.printer.lookup_objects()
- if hasattr(o, 'stats')]
- if self.printer.get_start_args().get('debugoutput') is None:
+ self.stats_cb = [
+ o.stats for n, o in self.printer.lookup_objects() if hasattr(o, "stats")
+ ]
+ if self.printer.get_start_args().get("debugoutput") is None:
reactor = self.printer.get_reactor()
reactor.update_timer(self.stats_timer, reactor.NOW)
+
def generate_stats(self, eventtime):
stats = [cb(eventtime) for cb in self.stats_cb]
if max([s[0] for s in stats]):
- logging.info("Stats %.1f: %s", eventtime,
- ' '.join([s[1] for s in stats]))
- return eventtime + 1.
+ logging.info("Stats %.1f: %s", eventtime, " ".join([s[1] for s in stats]))
+ return eventtime + 1.0
+
def load_config(config):
- config.get_printer().add_object('system_stats', PrinterSysStats(config))
+ config.get_printer().add_object("system_stats", PrinterSysStats(config))
return PrinterStats(config)
diff --git a/klippy/extras/stepper_enable.py b/klippy/extras/stepper_enable.py
index 2bad7555..f4a8e29d 100644
--- a/klippy/extras/stepper_enable.py
+++ b/klippy/extras/stepper_enable.py
@@ -7,40 +7,44 @@ import logging
DISABLE_STALL_TIME = 0.100
+
# Tracking of shared stepper enable pins
class StepperEnablePin:
def __init__(self, mcu_enable, enable_count):
self.mcu_enable = mcu_enable
self.enable_count = enable_count
self.is_dedicated = True
+
def set_enable(self, print_time):
if not self.enable_count:
self.mcu_enable.set_digital(print_time, 1)
self.enable_count += 1
+
def set_disable(self, print_time):
self.enable_count -= 1
if not self.enable_count:
self.mcu_enable.set_digital(print_time, 0)
+
def setup_enable_pin(printer, pin):
if pin is None:
# No enable line (stepper always enabled)
enable = StepperEnablePin(None, 9999)
enable.is_dedicated = False
return enable
- ppins = printer.lookup_object('pins')
- pin_params = ppins.lookup_pin(pin, can_invert=True,
- share_type='stepper_enable')
- enable = pin_params.get('class')
+ ppins = printer.lookup_object("pins")
+ pin_params = ppins.lookup_pin(pin, can_invert=True, share_type="stepper_enable")
+ enable = pin_params.get("class")
if enable is not None:
# Shared enable line
enable.is_dedicated = False
return enable
- mcu_enable = pin_params['chip'].setup_pin('digital_out', pin_params)
- mcu_enable.setup_max_duration(0.)
- enable = pin_params['class'] = StepperEnablePin(mcu_enable, 0)
+ mcu_enable = pin_params["chip"].setup_pin("digital_out", pin_params)
+ mcu_enable.setup_max_duration(0.0)
+ enable = pin_params["class"] = StepperEnablePin(mcu_enable, 0)
return enable
+
# Enable line tracking for each stepper motor
class EnableTracking:
def __init__(self, stepper, enable):
@@ -49,14 +53,17 @@ class EnableTracking:
self.callbacks = []
self.is_enabled = False
self.stepper.add_active_callback(self.motor_enable)
+
def register_state_callback(self, callback):
self.callbacks.append(callback)
+
def motor_enable(self, print_time):
if not self.is_enabled:
for cb in self.callbacks:
cb(print_time, True)
self.enable.set_enable(print_time)
self.is_enabled = True
+
def motor_disable(self, print_time):
if self.is_enabled:
# Enable stepper on future stepper movement
@@ -65,31 +72,39 @@ class EnableTracking:
self.enable.set_disable(print_time)
self.is_enabled = False
self.stepper.add_active_callback(self.motor_enable)
+
def is_motor_enabled(self):
return self.is_enabled
+
def has_dedicated_enable(self):
return self.enable.is_dedicated
+
# Global stepper enable line tracking
class PrinterStepperEnable:
def __init__(self, config):
self.printer = config.get_printer()
self.enable_lines = {}
- self.printer.register_event_handler("gcode:request_restart",
- self._handle_request_restart)
+ self.printer.register_event_handler(
+ "gcode:request_restart", self._handle_request_restart
+ )
# Register M18/M84 commands
- gcode = self.printer.lookup_object('gcode')
+ gcode = self.printer.lookup_object("gcode")
gcode.register_command("M18", self.cmd_M18)
gcode.register_command("M84", self.cmd_M18)
- gcode.register_command("SET_STEPPER_ENABLE",
- self.cmd_SET_STEPPER_ENABLE,
- desc=self.cmd_SET_STEPPER_ENABLE_help)
+ gcode.register_command(
+ "SET_STEPPER_ENABLE",
+ self.cmd_SET_STEPPER_ENABLE,
+ desc=self.cmd_SET_STEPPER_ENABLE_help,
+ )
+
def register_stepper(self, config, mcu_stepper):
name = mcu_stepper.get_name()
- enable = setup_enable_pin(self.printer, config.get('enable_pin', None))
+ enable = setup_enable_pin(self.printer, config.get("enable_pin", None))
self.enable_lines[name] = EnableTracking(mcu_stepper, enable)
+
def motor_off(self):
- toolhead = self.printer.lookup_object('toolhead')
+ toolhead = self.printer.lookup_object("toolhead")
toolhead.dwell(DISABLE_STALL_TIME)
print_time = toolhead.get_last_move_time()
for el in self.enable_lines.values():
@@ -97,8 +112,9 @@ class PrinterStepperEnable:
toolhead.get_kinematics().clear_homing_state("xyz")
self.printer.send_event("stepper_enable:motor_off", print_time)
toolhead.dwell(DISABLE_STALL_TIME)
+
def motor_debug_enable(self, stepper, enable):
- toolhead = self.printer.lookup_object('toolhead')
+ toolhead = self.printer.lookup_object("toolhead")
toolhead.dwell(DISABLE_STALL_TIME)
print_time = toolhead.get_last_move_time()
el = self.enable_lines[stepper]
@@ -109,30 +125,40 @@ class PrinterStepperEnable:
el.motor_disable(print_time)
logging.info("%s has been manually disabled", stepper)
toolhead.dwell(DISABLE_STALL_TIME)
+
def get_status(self, eventtime):
- steppers = { name: et.is_motor_enabled()
- for (name, et) in self.enable_lines.items() }
- return {'steppers': steppers}
+ steppers = {
+ name: et.is_motor_enabled() for (name, et) in self.enable_lines.items()
+ }
+ return {"steppers": steppers}
+
def _handle_request_restart(self, print_time):
self.motor_off()
+
def cmd_M18(self, gcmd):
# Turn off motors
self.motor_off()
+
cmd_SET_STEPPER_ENABLE_help = "Enable/disable individual stepper by name"
+
def cmd_SET_STEPPER_ENABLE(self, gcmd):
- stepper_name = gcmd.get('STEPPER', None)
+ stepper_name = gcmd.get("STEPPER", None)
if stepper_name not in self.enable_lines:
- gcmd.respond_info('SET_STEPPER_ENABLE: Invalid stepper "%s"'
- % (stepper_name,))
+ gcmd.respond_info(
+ 'SET_STEPPER_ENABLE: Invalid stepper "%s"' % (stepper_name,)
+ )
return
- stepper_enable = gcmd.get_int('ENABLE', 1)
+ stepper_enable = gcmd.get_int("ENABLE", 1)
self.motor_debug_enable(stepper_name, stepper_enable)
+
def lookup_enable(self, name):
if name not in self.enable_lines:
raise self.printer.config_error("Unknown stepper '%s'" % (name,))
return self.enable_lines[name]
+
def get_steppers(self):
return list(self.enable_lines.keys())
+
def load_config(config):
return PrinterStepperEnable(config)
diff --git a/klippy/extras/sx1509.py b/klippy/extras/sx1509.py
index 070b9133..22a6c6a7 100644
--- a/klippy/extras/sx1509.py
+++ b/klippy/extras/sx1509.py
@@ -19,8 +19,26 @@ REG_ANALOG_DRIVER_ENABLE = 0x20
# Byte registers
-REG_I_ON = [0x2A, 0x2D, 0x30, 0x33, 0x36, 0x3B, 0x40, 0x45,
- 0x4A, 0x4D, 0x50, 0x53, 0x56, 0x5B, 0x5F, 0x65]
+REG_I_ON = [
+ 0x2A,
+ 0x2D,
+ 0x30,
+ 0x33,
+ 0x36,
+ 0x3B,
+ 0x40,
+ 0x45,
+ 0x4A,
+ 0x4D,
+ 0x50,
+ 0x53,
+ 0x56,
+ 0x5B,
+ 0x5F,
+ 0x65,
+]
+
+
class SX1509(object):
def __init__(self, config):
self._printer = config.get_printer()
@@ -33,58 +51,78 @@ class SX1509(object):
self._oid = self._i2c.get_oid()
self._last_clock = 0
# Set up registers default values
- self.reg_dict = {REG_DIR : 0xFFFF, REG_DATA : 0,
- REG_PULLUP : 0, REG_PULLDOWN : 0,
- REG_INPUT_DISABLE : 0, REG_ANALOG_DRIVER_ENABLE : 0}
- self.reg_i_on_dict = {reg : 0 for reg in REG_I_ON}
+ self.reg_dict = {
+ REG_DIR: 0xFFFF,
+ REG_DATA: 0,
+ REG_PULLUP: 0,
+ REG_PULLDOWN: 0,
+ REG_INPUT_DISABLE: 0,
+ REG_ANALOG_DRIVER_ENABLE: 0,
+ }
+ self.reg_i_on_dict = {reg: 0 for reg in REG_I_ON}
+
def _build_config(self):
# Reset the chip, Default RegClock/RegMisc 0x0
- self._mcu.add_config_cmd("i2c_write oid=%d data=%02x%02x" % (
- self._oid, REG_RESET, 0x12))
- self._mcu.add_config_cmd("i2c_write oid=%d data=%02x%02x" % (
- self._oid, REG_RESET, 0x34))
+ self._mcu.add_config_cmd(
+ "i2c_write oid=%d data=%02x%02x" % (self._oid, REG_RESET, 0x12)
+ )
+ self._mcu.add_config_cmd(
+ "i2c_write oid=%d data=%02x%02x" % (self._oid, REG_RESET, 0x34)
+ )
# Enable Oscillator
- self._mcu.add_config_cmd("i2c_write oid=%d data=%02x%02x" % (
- self._oid, REG_CLOCK, (1 << 6)))
+ self._mcu.add_config_cmd(
+ "i2c_write oid=%d data=%02x%02x" % (self._oid, REG_CLOCK, (1 << 6))
+ )
# Setup Clock Divider
- self._mcu.add_config_cmd("i2c_write oid=%d data=%02x%02x" % (
- self._oid, REG_MISC, (1 << 4)))
+ self._mcu.add_config_cmd(
+ "i2c_write oid=%d data=%02x%02x" % (self._oid, REG_MISC, (1 << 4))
+ )
# Transfer all regs with their initial cached state
for _reg, _data in self.reg_dict.items():
- self._mcu.add_config_cmd("i2c_write oid=%d data=%02x%04x" % (
- self._oid, _reg, _data), is_init=True)
+ self._mcu.add_config_cmd(
+ "i2c_write oid=%d data=%02x%04x" % (self._oid, _reg, _data),
+ is_init=True,
+ )
+
def setup_pin(self, pin_type, pin_params):
- if pin_type == 'digital_out' and pin_params['pin'][0:4] == "PIN_":
+ if pin_type == "digital_out" and pin_params["pin"][0:4] == "PIN_":
return SX1509_digital_out(self, pin_params)
- elif pin_type == 'pwm' and pin_params['pin'][0:4] == "PIN_":
+ elif pin_type == "pwm" and pin_params["pin"][0:4] == "PIN_":
return SX1509_pwm(self, pin_params)
- raise pins.error("Wrong pin or incompatible type: %s with type %s! " % (
- pin_params['pin'][0:4], pin_type))
+ raise pins.error(
+ "Wrong pin or incompatible type: %s with type %s! "
+ % (pin_params["pin"][0:4], pin_type)
+ )
+
def get_mcu(self):
return self._mcu
+
def get_oid(self):
return self._oid
+
def clear_bits_in_register(self, reg, bitmask):
if reg in self.reg_dict:
self.reg_dict[reg] &= ~(bitmask)
elif reg in self.reg_i_on_dict:
self.reg_i_on_dict[reg] &= ~(bitmask)
+
def set_bits_in_register(self, reg, bitmask):
if reg in self.reg_dict:
self.reg_dict[reg] |= bitmask
elif reg in self.reg_i_on_dict:
self.reg_i_on_dict[reg] |= bitmask
+
def set_register(self, reg, value):
if reg in self.reg_dict:
self.reg_dict[reg] = value
elif reg in self.reg_i_on_dict:
self.reg_i_on_dict[reg] = value
+
def send_register(self, reg, print_time):
data = [reg & 0xFF]
if reg in self.reg_dict:
# Word
- data += [(self.reg_dict[reg] >> 8) & 0xFF,
- self.reg_dict[reg] & 0xFF]
+ data += [(self.reg_dict[reg] >> 8) & 0xFF, self.reg_dict[reg] & 0xFF]
elif reg in self.reg_i_on_dict:
# Byte
data += [self.reg_i_on_dict[reg] & 0xFF]
@@ -92,27 +130,32 @@ class SX1509(object):
self._i2c.i2c_write(data, minclock=self._last_clock, reqclock=clock)
self._last_clock = clock
+
class SX1509_digital_out(object):
def __init__(self, sx1509, pin_params):
self._sx1509 = sx1509
self._mcu = sx1509.get_mcu()
- self._sxpin = int(pin_params['pin'].split('_')[1])
+ self._sxpin = int(pin_params["pin"].split("_")[1])
self._bitmask = 1 << self._sxpin
- self._pin = pin_params['pin']
- self._invert = pin_params['invert']
+ self._pin = pin_params["pin"]
+ self._invert = pin_params["invert"]
self._mcu.register_config_callback(self._build_config)
self._start_value = self._shutdown_value = self._invert
- self._max_duration = 2.
+ self._max_duration = 2.0
self._set_cmd = self._clear_cmd = None
# Set direction to output
self._sx1509.clear_bits_in_register(REG_DIR, self._bitmask)
+
def _build_config(self):
if self._max_duration:
raise pins.error("SX1509 pins are not suitable for heaters")
+
def get_mcu(self):
return self._mcu
+
def setup_max_duration(self, max_duration):
self._max_duration = max_duration
+
def setup_start_value(self, start_value, shutdown_value):
self._start_value = (not not start_value) ^ self._invert
self._shutdown_value = self._invert
@@ -122,70 +165,82 @@ class SX1509_digital_out(object):
self._sx1509.set_bits_in_register(REG_DATA, self._bitmask)
else:
self._sx1509.clear_bits_in_register(REG_DATA, self._bitmask)
+
def set_digital(self, print_time, value):
if int(value) ^ self._invert:
self._sx1509.set_bits_in_register(REG_DATA, self._bitmask)
else:
self._sx1509.clear_bits_in_register(REG_DATA, self._bitmask)
self._sx1509.send_register(REG_DATA, print_time)
+
def set_pwm(self, print_time, value, cycle_time=None):
self.set_digital(print_time, value >= 0.5)
+
class SX1509_pwm(object):
def __init__(self, sx1509, pin_params):
self._sx1509 = sx1509
self._mcu = sx1509.get_mcu()
- self._sxpin = int(pin_params['pin'].split('_')[1])
+ self._sxpin = int(pin_params["pin"].split("_")[1])
self._bitmask = 1 << self._sxpin
self._i_on_reg = REG_I_ON[self._sxpin]
- self._pin = pin_params['pin']
- self._invert = pin_params['invert']
+ self._pin = pin_params["pin"]
+ self._invert = pin_params["invert"]
self._mcu.register_config_callback(self._build_config)
self._start_value = self._shutdown_value = float(self._invert)
- self._max_duration = 2.
+ self._max_duration = 2.0
self._hardware_pwm = False
- self._pwm_max = 0.
+ self._pwm_max = 0.0
self._set_cmd = None
- self._cycle_time = 0.
+ self._cycle_time = 0.0
# Set required registers
self._sx1509.set_bits_in_register(REG_INPUT_DISABLE, self._bitmask)
self._sx1509.clear_bits_in_register(REG_PULLUP, self._bitmask)
self._sx1509.clear_bits_in_register(REG_DIR, self._bitmask)
- self._sx1509.set_bits_in_register(REG_ANALOG_DRIVER_ENABLE,
- self._bitmask)
+ self._sx1509.set_bits_in_register(REG_ANALOG_DRIVER_ENABLE, self._bitmask)
self._sx1509.clear_bits_in_register(REG_DATA, self._bitmask)
+
def _build_config(self):
if not self._hardware_pwm:
raise pins.error("SX1509_pwm must have hardware_pwm enabled")
if self._max_duration:
raise pins.error("SX1509 pins are not suitable for heaters")
# Send initial value
- self._sx1509.set_register(self._i_on_reg,
- ~int(255 * self._start_value) & 0xFF)
- self._mcu.add_config_cmd("i2c_write oid=%d data=%02x%02x" % (
- self._sx1509.get_oid(),
- self._i_on_reg,
- self._sx1509.reg_i_on_dict[self._i_on_reg]
+ self._sx1509.set_register(self._i_on_reg, ~int(255 * self._start_value) & 0xFF)
+ self._mcu.add_config_cmd(
+ "i2c_write oid=%d data=%02x%02x"
+ % (
+ self._sx1509.get_oid(),
+ self._i_on_reg,
+ self._sx1509.reg_i_on_dict[self._i_on_reg],
),
- is_init=True)
+ is_init=True,
+ )
+
def get_mcu(self):
return self._mcu
+
def setup_max_duration(self, max_duration):
self._max_duration = max_duration
+
def setup_cycle_time(self, cycle_time, hardware_pwm=False):
self._cycle_time = cycle_time
self._hardware_pwm = hardware_pwm
+
def setup_start_value(self, start_value, shutdown_value):
if self._invert:
- start_value = 1. - start_value
- shutdown_value = 1. - shutdown_value
- self._start_value = max(0., min(1., start_value))
- self._shutdown_value = max(0., min(1., shutdown_value))
+ start_value = 1.0 - start_value
+ shutdown_value = 1.0 - shutdown_value
+ self._start_value = max(0.0, min(1.0, start_value))
+ self._shutdown_value = max(0.0, min(1.0, shutdown_value))
+
def set_pwm(self, print_time, value):
- self._sx1509.set_register(self._i_on_reg, ~int(255 * value)
- if not self._invert
- else int(255 * value) & 0xFF)
+ self._sx1509.set_register(
+ self._i_on_reg,
+ ~int(255 * value) if not self._invert else int(255 * value) & 0xFF,
+ )
self._sx1509.send_register(self._i_on_reg, print_time)
+
def load_config_prefix(config):
return SX1509(config)
diff --git a/klippy/extras/temperature_combined.py b/klippy/extras/temperature_combined.py
index d032bce0..d8113981 100644
--- a/klippy/extras/temperature_combined.py
+++ b/klippy/extras/temperature_combined.py
@@ -14,47 +14,46 @@ class PrinterSensorCombined:
self.reactor = self.printer.get_reactor()
self.name = config.get_name().split()[-1]
# get sensor names
- self.sensor_names = config.getlist('sensor_list')
+ self.sensor_names = config.getlist("sensor_list")
# get maximum_deviation parameter from config
- self.max_deviation = config.getfloat('maximum_deviation', above=0.)
+ self.max_deviation = config.getfloat("maximum_deviation", above=0.0)
# ensure compatibility with itself
self.sensor = self
# get empty list for sensors, could be any sensor class or a heater
self.sensors = []
# get combination method to handle the different sensor values with
- algos = {'min': min, 'max': max, 'mean': mean}
- self.apply_mode = config.getchoice('combination_method', algos)
+ algos = {"min": min, "max": max, "mean": mean}
+ self.apply_mode = config.getchoice("combination_method", algos)
# set default values
self.last_temp = self.min_temp = self.max_temp = 0.0
# add object
self.printer.add_object("temperature_combined " + self.name, self)
# time-controlled sensor update
self.temperature_update_timer = self.reactor.register_timer(
- self._temperature_update_event)
- self.printer.register_event_handler('klippy:connect',
- self._handle_connect)
- self.printer.register_event_handler('klippy:ready',
- self._handle_ready)
+ self._temperature_update_event
+ )
+ self.printer.register_event_handler("klippy:connect", self._handle_connect)
+ self.printer.register_event_handler("klippy:ready", self._handle_ready)
def _handle_connect(self):
for sensor_name in self.sensor_names:
sensor = self.printer.lookup_object(sensor_name)
# check if sensor has get_status function and
# get_status has a 'temperature' value
- if not hasattr(sensor, 'get_status'):
+ if not hasattr(sensor, "get_status"):
raise self.printer.config_error(
- "'%s' does not have a status."
- % (sensor_name,))
+ "'%s' does not have a status." % (sensor_name,)
+ )
status = sensor.get_status(self.reactor.monotonic())
- if 'temperature' not in status:
+ if "temperature" not in status:
raise self.printer.config_error(
- "'%s' does not report a temperature."
- % (sensor_name,))
+ "'%s' does not report a temperature." % (sensor_name,)
+ )
# Handle temperature monitors
if status["temperature"] is None:
raise self.printer.config_error(
- "Temperature monitor '%s' is not supported"
- % (sensor_name,))
+ "Temperature monitor '%s' is not supported" % (sensor_name,)
+ )
self.sensors.append(sensor)
@@ -62,8 +61,9 @@ class PrinterSensorCombined:
# Start temperature update timer
# There is a race condition with sensors where they can be not ready,
# and return 0 or None - initialize a little bit later.
- self.reactor.update_timer(self.temperature_update_timer,
- self.reactor.monotonic() + 1.)
+ self.reactor.update_timer(
+ self.temperature_update_timer, self.reactor.monotonic() + 1.0
+ )
def setup_minmax(self, min_temp, max_temp):
self.min_temp = min_temp
@@ -79,7 +79,7 @@ class PrinterSensorCombined:
values = []
for sensor in self.sensors:
sensor_status = sensor.get_status(eventtime)
- sensor_temperature = sensor_status['temperature']
+ sensor_temperature = sensor_status["temperature"]
values.append(sensor_temperature)
# check if values are out of max_deviation range
@@ -87,18 +87,24 @@ class PrinterSensorCombined:
self.printer.invoke_shutdown(
"COMBINED SENSOR maximum deviation exceeded limit of %0.1f, "
"max sensor value %0.1f, min sensor value %0.1f."
- % (self.max_deviation, max(values), min(values),))
+ % (
+ self.max_deviation,
+ max(values),
+ min(values),
+ )
+ )
temp = self.apply_mode(values)
if temp:
self.last_temp = temp
def get_temp(self, eventtime):
- return self.last_temp, 0.
+ return self.last_temp, 0.0
def get_status(self, eventtime):
- return {'temperature': round(self.last_temp, 2),
- }
+ return {
+ "temperature": round(self.last_temp, 2),
+ }
def _temperature_update_event(self, eventtime):
# update sensor value
@@ -109,20 +115,29 @@ class PrinterSensorCombined:
self.printer.invoke_shutdown(
"COMBINED SENSOR temperature %0.1f "
"below minimum temperature of %0.1f."
- % (self.last_temp, self.min_temp,))
+ % (
+ self.last_temp,
+ self.min_temp,
+ )
+ )
if self.last_temp > self.max_temp:
self.printer.invoke_shutdown(
"COMBINED SENSOR temperature %0.1f "
"above maximum temperature of %0.1f."
- % (self.last_temp, self.max_temp,))
+ % (
+ self.last_temp,
+ self.max_temp,
+ )
+ )
# this is copied from temperature_host to enable time triggered updates
# get mcu and measured / current(?) time
- mcu = self.printer.lookup_object('mcu')
+ mcu = self.printer.lookup_object("mcu")
measured_time = self.reactor.monotonic()
# convert to print time?! for the callback???
- self.temperature_callback(mcu.estimated_print_time(measured_time),
- self.last_temp)
+ self.temperature_callback(
+ mcu.estimated_print_time(measured_time), self.last_temp
+ )
# set next update time
return measured_time + REPORT_TIME
diff --git a/klippy/extras/temperature_fan.py b/klippy/extras/temperature_fan.py
index a9aa4d0b..8ea3808f 100644
--- a/klippy/extras/temperature_fan.py
+++ b/klippy/extras/temperature_fan.py
@@ -7,85 +7,100 @@ from . import fan
KELVIN_TO_CELSIUS = -273.15
MAX_FAN_TIME = 5.0
-AMBIENT_TEMP = 25.
-PID_PARAM_BASE = 255.
+AMBIENT_TEMP = 25.0
+PID_PARAM_BASE = 255.0
+
class TemperatureFan:
def __init__(self, config):
self.name = config.get_name().split()[1]
self.printer = config.get_printer()
- self.fan = fan.Fan(config, default_shutdown_speed=1.)
- self.min_temp = config.getfloat('min_temp', minval=KELVIN_TO_CELSIUS)
- self.max_temp = config.getfloat('max_temp', above=self.min_temp)
- pheaters = self.printer.load_object(config, 'heaters')
+ self.fan = fan.Fan(config, default_shutdown_speed=1.0)
+ self.min_temp = config.getfloat("min_temp", minval=KELVIN_TO_CELSIUS)
+ self.max_temp = config.getfloat("max_temp", above=self.min_temp)
+ pheaters = self.printer.load_object(config, "heaters")
self.sensor = pheaters.setup_sensor(config)
self.sensor.setup_minmax(self.min_temp, self.max_temp)
self.sensor.setup_callback(self.temperature_callback)
pheaters.register_sensor(config, self)
self.speed_delay = self.sensor.get_report_time_delta()
- self.max_speed_conf = config.getfloat(
- 'max_speed', 1., above=0., maxval=1.)
+ self.max_speed_conf = config.getfloat("max_speed", 1.0, above=0.0, maxval=1.0)
self.max_speed = self.max_speed_conf
- self.min_speed_conf = config.getfloat(
- 'min_speed', 0.3, minval=0., maxval=1.)
+ self.min_speed_conf = config.getfloat("min_speed", 0.3, minval=0.0, maxval=1.0)
self.min_speed = self.min_speed_conf
- self.last_temp = 0.
- self.last_temp_time = 0.
+ self.last_temp = 0.0
+ self.last_temp_time = 0.0
self.target_temp_conf = config.getfloat(
- 'target_temp', 40. if self.max_temp > 40. else self.max_temp,
- minval=self.min_temp, maxval=self.max_temp)
+ "target_temp",
+ 40.0 if self.max_temp > 40.0 else self.max_temp,
+ minval=self.min_temp,
+ maxval=self.max_temp,
+ )
self.target_temp = self.target_temp_conf
- algos = {'watermark': ControlBangBang, 'pid': ControlPID}
- algo = config.getchoice('control', algos)
+ algos = {"watermark": ControlBangBang, "pid": ControlPID}
+ algo = config.getchoice("control", algos)
self.control = algo(self, config)
- self.next_speed_time = 0.
- self.last_speed_value = 0.
- gcode = self.printer.lookup_object('gcode')
+ self.next_speed_time = 0.0
+ self.last_speed_value = 0.0
+ gcode = self.printer.lookup_object("gcode")
gcode.register_mux_command(
- "SET_TEMPERATURE_FAN_TARGET", "TEMPERATURE_FAN", self.name,
+ "SET_TEMPERATURE_FAN_TARGET",
+ "TEMPERATURE_FAN",
+ self.name,
self.cmd_SET_TEMPERATURE_FAN_TARGET,
- desc=self.cmd_SET_TEMPERATURE_FAN_TARGET_help)
+ desc=self.cmd_SET_TEMPERATURE_FAN_TARGET_help,
+ )
def set_tf_speed(self, read_time, value):
- if value <= 0.:
- value = 0.
+ if value <= 0.0:
+ value = 0.0
elif value < self.min_speed:
value = self.min_speed
- if self.target_temp <= 0.:
- value = 0.
- if ((read_time < self.next_speed_time or not self.last_speed_value)
- and abs(value - self.last_speed_value) < 0.05):
+ if self.target_temp <= 0.0:
+ value = 0.0
+ if (read_time < self.next_speed_time or not self.last_speed_value) and abs(
+ value - self.last_speed_value
+ ) < 0.05:
# No significant change in value - can suppress update
return
speed_time = read_time + self.speed_delay
self.next_speed_time = speed_time + 0.75 * MAX_FAN_TIME
self.last_speed_value = value
self.fan.set_speed(value, speed_time)
+
def temperature_callback(self, read_time, temp):
self.last_temp = temp
self.control.temperature_callback(read_time, temp)
+
def get_temp(self, eventtime):
return self.last_temp, self.target_temp
+
def get_min_speed(self):
return self.min_speed
+
def get_max_speed(self):
return self.max_speed
+
def get_status(self, eventtime):
status = self.fan.get_status(eventtime)
status["temperature"] = round(self.last_temp, 2)
status["target"] = self.target_temp
return status
- cmd_SET_TEMPERATURE_FAN_TARGET_help = \
+
+ cmd_SET_TEMPERATURE_FAN_TARGET_help = (
"Sets a temperature fan target and fan speed limits"
+ )
+
def cmd_SET_TEMPERATURE_FAN_TARGET(self, gcmd):
- temp = gcmd.get_float('TARGET', self.target_temp_conf)
+ temp = gcmd.get_float("TARGET", self.target_temp_conf)
self.set_temp(temp)
- min_speed = gcmd.get_float('MIN_SPEED', self.min_speed)
- max_speed = gcmd.get_float('MAX_SPEED', self.max_speed)
+ min_speed = gcmd.get_float("MIN_SPEED", self.min_speed)
+ max_speed = gcmd.get_float("MAX_SPEED", self.max_speed)
if min_speed > max_speed:
raise self.printer.command_error(
"Requested min speed (%.1f) is greater than max speed (%.1f)"
- % (min_speed, max_speed))
+ % (min_speed, max_speed)
+ )
self.set_min_speed(min_speed)
self.set_max_speed(max_speed)
@@ -93,67 +108,73 @@ class TemperatureFan:
if degrees and (degrees < self.min_temp or degrees > self.max_temp):
raise self.printer.command_error(
"Requested temperature (%.1f) out of range (%.1f:%.1f)"
- % (degrees, self.min_temp, self.max_temp))
+ % (degrees, self.min_temp, self.max_temp)
+ )
self.target_temp = degrees
def set_min_speed(self, speed):
- if speed and (speed < 0. or speed > 1.):
+ if speed and (speed < 0.0 or speed > 1.0):
raise self.printer.command_error(
- "Requested min speed (%.1f) out of range (0.0 : 1.0)"
- % (speed))
+ "Requested min speed (%.1f) out of range (0.0 : 1.0)" % (speed)
+ )
self.min_speed = speed
def set_max_speed(self, speed):
- if speed and (speed < 0. or speed > 1.):
+ if speed and (speed < 0.0 or speed > 1.0):
raise self.printer.command_error(
- "Requested max speed (%.1f) out of range (0.0 : 1.0)"
- % (speed))
+ "Requested max speed (%.1f) out of range (0.0 : 1.0)" % (speed)
+ )
self.max_speed = speed
+
######################################################################
# Bang-bang control algo
######################################################################
+
class ControlBangBang:
def __init__(self, temperature_fan, config):
self.temperature_fan = temperature_fan
- self.max_delta = config.getfloat('max_delta', 2.0, above=0.)
+ self.max_delta = config.getfloat("max_delta", 2.0, above=0.0)
self.heating = False
+
def temperature_callback(self, read_time, temp):
current_temp, target_temp = self.temperature_fan.get_temp(read_time)
- if (self.heating
- and temp >= target_temp+self.max_delta):
+ if self.heating and temp >= target_temp + self.max_delta:
self.heating = False
- elif (not self.heating
- and temp <= target_temp-self.max_delta):
+ elif not self.heating and temp <= target_temp - self.max_delta:
self.heating = True
if self.heating:
- self.temperature_fan.set_tf_speed(read_time, 0.)
+ self.temperature_fan.set_tf_speed(read_time, 0.0)
else:
self.temperature_fan.set_tf_speed(
- read_time, self.temperature_fan.get_max_speed())
+ read_time, self.temperature_fan.get_max_speed()
+ )
+
######################################################################
# Proportional Integral Derivative (PID) control algo
######################################################################
-PID_SETTLE_DELTA = 1.
-PID_SETTLE_SLOPE = .1
+PID_SETTLE_DELTA = 1.0
+PID_SETTLE_SLOPE = 0.1
+
class ControlPID:
def __init__(self, temperature_fan, config):
self.temperature_fan = temperature_fan
- self.Kp = config.getfloat('pid_Kp') / PID_PARAM_BASE
- self.Ki = config.getfloat('pid_Ki') / PID_PARAM_BASE
- self.Kd = config.getfloat('pid_Kd') / PID_PARAM_BASE
- self.min_deriv_time = config.getfloat('pid_deriv_time', 2., above=0.)
- self.temp_integ_max = 0.
+ self.Kp = config.getfloat("pid_Kp") / PID_PARAM_BASE
+ self.Ki = config.getfloat("pid_Ki") / PID_PARAM_BASE
+ self.Kd = config.getfloat("pid_Kd") / PID_PARAM_BASE
+ self.min_deriv_time = config.getfloat("pid_deriv_time", 2.0, above=0.0)
+ self.temp_integ_max = 0.0
if self.Ki:
self.temp_integ_max = self.temperature_fan.get_max_speed() / self.Ki
self.prev_temp = AMBIENT_TEMP
- self.prev_temp_time = 0.
- self.prev_temp_deriv = 0.
- self.prev_temp_integ = 0.
+ self.prev_temp_time = 0.0
+ self.prev_temp_deriv = 0.0
+ self.prev_temp_integ = 0.0
+
def temperature_callback(self, read_time, temp):
current_temp, target_temp = self.temperature_fan.get_temp(read_time)
time_diff = read_time - self.prev_temp_time
@@ -162,18 +183,23 @@ class ControlPID:
if time_diff >= self.min_deriv_time:
temp_deriv = temp_diff / time_diff
else:
- temp_deriv = (self.prev_temp_deriv * (self.min_deriv_time-time_diff)
- + temp_diff) / self.min_deriv_time
+ temp_deriv = (
+ self.prev_temp_deriv * (self.min_deriv_time - time_diff) + temp_diff
+ ) / self.min_deriv_time
# Calculate accumulated temperature "error"
temp_err = target_temp - temp
temp_integ = self.prev_temp_integ + temp_err * time_diff
- temp_integ = max(0., min(self.temp_integ_max, temp_integ))
+ temp_integ = max(0.0, min(self.temp_integ_max, temp_integ))
# Calculate output
- co = self.Kp*temp_err + self.Ki*temp_integ - self.Kd*temp_deriv
- bounded_co = max(0., min(self.temperature_fan.get_max_speed(), co))
+ co = self.Kp * temp_err + self.Ki * temp_integ - self.Kd * temp_deriv
+ bounded_co = max(0.0, min(self.temperature_fan.get_max_speed(), co))
self.temperature_fan.set_tf_speed(
- read_time, max(self.temperature_fan.get_min_speed(),
- self.temperature_fan.get_max_speed() - bounded_co))
+ read_time,
+ max(
+ self.temperature_fan.get_min_speed(),
+ self.temperature_fan.get_max_speed() - bounded_co,
+ ),
+ )
# Store state for next measurement
self.prev_temp = temp
self.prev_temp_time = read_time
@@ -181,5 +207,6 @@ class ControlPID:
if co == bounded_co:
self.prev_temp_integ = temp_integ
+
def load_config_prefix(config):
return TemperatureFan(config)
diff --git a/klippy/extras/temperature_host.py b/klippy/extras/temperature_host.py
index 94a73603..c2e116de 100644
--- a/klippy/extras/temperature_host.py
+++ b/klippy/extras/temperature_host.py
@@ -9,6 +9,7 @@ import logging
HOST_REPORT_TIME = 1.0
RPI_PROC_TEMP_FILE = "/sys/class/thermal/thermal_zone0/temp"
+
class Temperature_HOST:
def __init__(self, config):
self.printer = config.get_printer()
@@ -19,18 +20,15 @@ class Temperature_HOST:
self.temp = self.min_temp = self.max_temp = 0.0
self.printer.add_object("temperature_host " + self.name, self)
- if self.printer.get_start_args().get('debugoutput') is not None:
+ if self.printer.get_start_args().get("debugoutput") is not None:
return
- self.sample_timer = self.reactor.register_timer(
- self._sample_pi_temperature)
+ self.sample_timer = self.reactor.register_timer(self._sample_pi_temperature)
try:
self.file_handle = open(self.path, "r")
except:
- raise config.error("Unable to open temperature file '%s'"
- % (self.path,))
+ raise config.error("Unable to open temperature file '%s'" % (self.path,))
- self.printer.register_event_handler("klippy:connect",
- self.handle_connect)
+ self.printer.register_event_handler("klippy:connect", self.handle_connect)
def handle_connect(self):
self.reactor.update_timer(self.sample_timer, self.reactor.NOW)
@@ -48,7 +46,7 @@ class Temperature_HOST:
def _sample_pi_temperature(self, eventtime):
try:
self.file_handle.seek(0)
- self.temp = float(self.file_handle.read())/1000.0
+ self.temp = float(self.file_handle.read()) / 1000.0
except Exception:
logging.exception("temperature_host: Error reading data")
self.temp = 0.0
@@ -57,20 +55,28 @@ class Temperature_HOST:
if self.temp < self.min_temp:
self.printer.invoke_shutdown(
"HOST temperature %0.1f below minimum temperature of %0.1f."
- % (self.temp, self.min_temp,))
+ % (
+ self.temp,
+ self.min_temp,
+ )
+ )
if self.temp > self.max_temp:
self.printer.invoke_shutdown(
"HOST temperature %0.1f above maximum temperature of %0.1f."
- % (self.temp, self.max_temp,))
+ % (
+ self.temp,
+ self.max_temp,
+ )
+ )
- mcu = self.printer.lookup_object('mcu')
+ mcu = self.printer.lookup_object("mcu")
measured_time = self.reactor.monotonic()
self._callback(mcu.estimated_print_time(measured_time), self.temp)
return measured_time + HOST_REPORT_TIME
def get_status(self, eventtime):
return {
- 'temperature': round(self.temp, 2),
+ "temperature": round(self.temp, 2),
}
diff --git a/klippy/extras/temperature_mcu.py b/klippy/extras/temperature_mcu.py
index 6f3386a4..1b4d983a 100644
--- a/klippy/extras/temperature_mcu.py
+++ b/klippy/extras/temperature_mcu.py
@@ -12,81 +12,101 @@ SAMPLE_COUNT = 8
REPORT_TIME = 0.300
RANGE_CHECK_COUNT = 4
+
class PrinterTemperatureMCU:
def __init__(self, config):
self.printer = config.get_printer()
self.base_temperature = self.slope = None
self.temp1 = self.adc1 = self.temp2 = self.adc2 = None
- self.min_temp = self.max_temp = 0.
+ self.min_temp = self.max_temp = 0.0
self.debug_read_cmd = None
# Read config
- mcu_name = config.get('sensor_mcu', 'mcu')
- self.temp1 = config.getfloat('sensor_temperature1', None)
+ mcu_name = config.get("sensor_mcu", "mcu")
+ self.temp1 = config.getfloat("sensor_temperature1", None)
if self.temp1 is not None:
- self.adc1 = config.getfloat('sensor_adc1', minval=0., maxval=1.)
- self.temp2 = config.getfloat('sensor_temperature2', None)
+ self.adc1 = config.getfloat("sensor_adc1", minval=0.0, maxval=1.0)
+ self.temp2 = config.getfloat("sensor_temperature2", None)
if self.temp2 is not None:
- self.adc2 = config.getfloat('sensor_adc2', minval=0., maxval=1.)
+ self.adc2 = config.getfloat("sensor_adc2", minval=0.0, maxval=1.0)
# Setup ADC port
- ppins = config.get_printer().lookup_object('pins')
- self.mcu_adc = ppins.setup_pin('adc',
- '%s:ADC_TEMPERATURE' % (mcu_name,))
+ ppins = config.get_printer().lookup_object("pins")
+ self.mcu_adc = ppins.setup_pin("adc", "%s:ADC_TEMPERATURE" % (mcu_name,))
self.mcu_adc.setup_adc_callback(REPORT_TIME, self.adc_callback)
self.diag_helper = adc_temperature.HelperTemperatureDiagnostics(
- config, self.mcu_adc, self.calc_temp)
+ config, self.mcu_adc, self.calc_temp
+ )
# Register callbacks
- if self.printer.get_start_args().get('debugoutput') is not None:
+ if self.printer.get_start_args().get("debugoutput") is not None:
self.mcu_adc.setup_adc_sample(SAMPLE_TIME, SAMPLE_COUNT)
return
- self.printer.register_event_handler("klippy:mcu_identify",
- self.handle_mcu_identify)
+ self.printer.register_event_handler(
+ "klippy:mcu_identify", self.handle_mcu_identify
+ )
+
# Temperature interface
def setup_callback(self, temperature_callback):
self.temperature_callback = temperature_callback
+
def get_report_time_delta(self):
return REPORT_TIME
+
def setup_minmax(self, min_temp, max_temp):
self.min_temp = min_temp
self.max_temp = max_temp
+
# Internal code
def adc_callback(self, read_time, read_value):
temp = self.base_temperature + read_value * self.slope
self.temperature_callback(read_time + SAMPLE_COUNT * SAMPLE_TIME, temp)
+
def calc_temp(self, adc):
return self.base_temperature + adc * self.slope
+
def calc_adc(self, temp):
return (temp - self.base_temperature) / self.slope
+
def calc_base(self, temp, adc):
return temp - adc * self.slope
+
def handle_mcu_identify(self):
# Obtain mcu information
mcu = self.mcu_adc.get_mcu()
self.debug_read_cmd = mcu.lookup_query_command(
- "debug_read order=%c addr=%u", "debug_result val=%u")
+ "debug_read order=%c addr=%u", "debug_result val=%u"
+ )
self.mcu_type = mcu.get_constants().get("MCU", "")
# Run MCU specific configuration
cfg_funcs = [
- ('rp2', self.config_rp2040),
- ('sam3', self.config_sam3), ('sam4', self.config_sam4),
- ('same70', self.config_same70), ('samd21', self.config_samd21),
- ('samd51', self.config_samd51), ('same5', self.config_samd51),
- ('stm32f1', self.config_stm32f1), ('stm32f2', self.config_stm32f2),
- ('stm32f4', self.config_stm32f4),
- ('stm32f042', self.config_stm32f0x2),
- ('stm32f070', self.config_stm32f070),
- ('stm32f072', self.config_stm32f0x2),
- ('stm32g0', self.config_stm32g0),
- ('stm32g4', self.config_stm32g0),
- ('stm32l4', self.config_stm32g0),
- ('stm32h723', self.config_stm32h723),
- ('stm32h7', self.config_stm32h7),
- ('', self.config_unknown)]
+ ("rp2", self.config_rp2040),
+ ("sam3", self.config_sam3),
+ ("sam4", self.config_sam4),
+ ("same70", self.config_same70),
+ ("samd21", self.config_samd21),
+ ("samd51", self.config_samd51),
+ ("same5", self.config_samd51),
+ ("stm32f1", self.config_stm32f1),
+ ("stm32f2", self.config_stm32f2),
+ ("stm32f4", self.config_stm32f4),
+ ("stm32f042", self.config_stm32f0x2),
+ ("stm32f070", self.config_stm32f070),
+ ("stm32f072", self.config_stm32f0x2),
+ ("stm32g0", self.config_stm32g0),
+ ("stm32g4", self.config_stm32g0),
+ ("stm32l4", self.config_stm32g0),
+ ("stm32h723", self.config_stm32h723),
+ ("stm32h7", self.config_stm32h7),
+ ("", self.config_unknown),
+ ]
for name, func in cfg_funcs:
if self.mcu_type.startswith(name):
func()
break
- logging.info("mcu_temperature '%s' nominal base=%.6f slope=%.6f",
- mcu.get_name(), self.base_temperature, self.slope)
+ logging.info(
+ "mcu_temperature '%s' nominal base=%.6f slope=%.6f",
+ mcu.get_name(),
+ self.base_temperature,
+ self.slope,
+ )
# Setup manual base/slope override
if self.temp1 is not None:
if self.temp2 is not None:
@@ -95,81 +115,106 @@ class PrinterTemperatureMCU:
# Setup min/max checks
arange = [self.calc_adc(t) for t in [self.min_temp, self.max_temp]]
min_adc, max_adc = sorted(arange)
- self.mcu_adc.setup_adc_sample(SAMPLE_TIME, SAMPLE_COUNT,
- minval=min_adc, maxval=max_adc,
- range_check_count=RANGE_CHECK_COUNT)
- self.diag_helper.setup_diag_minmax(self.min_temp, self.max_temp,
- min_adc, max_adc)
+ self.mcu_adc.setup_adc_sample(
+ SAMPLE_TIME,
+ SAMPLE_COUNT,
+ minval=min_adc,
+ maxval=max_adc,
+ range_check_count=RANGE_CHECK_COUNT,
+ )
+ self.diag_helper.setup_diag_minmax(
+ self.min_temp, self.max_temp, min_adc, max_adc
+ )
+
def config_unknown(self):
- raise self.printer.config_error("MCU temperature not supported on %s"
- % (self.mcu_type,))
+ raise self.printer.config_error(
+ "MCU temperature not supported on %s" % (self.mcu_type,)
+ )
+
def config_rp2040(self):
self.slope = 3.3 / -0.001721
- self.base_temperature = self.calc_base(27., 0.706 / 3.3)
+ self.base_temperature = self.calc_base(27.0, 0.706 / 3.3)
+
def config_sam3(self):
- self.slope = 3.3 / .002650
- self.base_temperature = self.calc_base(27., 0.8 / 3.3)
+ self.slope = 3.3 / 0.002650
+ self.base_temperature = self.calc_base(27.0, 0.8 / 3.3)
+
def config_sam4(self):
- self.slope = 3.3 / .004700
- self.base_temperature = self.calc_base(27., 1.44 / 3.3)
+ self.slope = 3.3 / 0.004700
+ self.base_temperature = self.calc_base(27.0, 1.44 / 3.3)
+
def config_same70(self):
- self.slope = 3.3 / .002330
- self.base_temperature = self.calc_base(25., 0.72 / 3.3)
+ self.slope = 3.3 / 0.002330
+ self.base_temperature = self.calc_base(25.0, 0.72 / 3.3)
+
def config_samd21(self, addr=0x00806030):
def get1v(val):
if val & 0x80:
val = 0x100 - val
- return 1. - val / 1000.
+ return 1.0 - val / 1000.0
+
cal1 = self.read32(addr)
cal2 = self.read32(addr + 4)
- room_temp = ((cal1 >> 0) & 0xff) + ((cal1 >> 8) & 0xf) / 10.
- hot_temp = ((cal1 >> 12) & 0xff) + ((cal1 >> 20) & 0xf) / 10.
- room_1v = get1v((cal1 >> 24) & 0xff)
- hot_1v = get1v((cal2 >> 0) & 0xff)
- room_adc = ((cal2 >> 8) & 0xfff) * room_1v / (3.3 * 4095.)
- hot_adc = ((cal2 >> 20) & 0xfff) * hot_1v / (3.3 * 4095.)
+ room_temp = ((cal1 >> 0) & 0xFF) + ((cal1 >> 8) & 0xF) / 10.0
+ hot_temp = ((cal1 >> 12) & 0xFF) + ((cal1 >> 20) & 0xF) / 10.0
+ room_1v = get1v((cal1 >> 24) & 0xFF)
+ hot_1v = get1v((cal2 >> 0) & 0xFF)
+ room_adc = ((cal2 >> 8) & 0xFFF) * room_1v / (3.3 * 4095.0)
+ hot_adc = ((cal2 >> 20) & 0xFFF) * hot_1v / (3.3 * 4095.0)
self.slope = (hot_temp - room_temp) / (hot_adc - room_adc)
self.base_temperature = self.calc_base(room_temp, room_adc)
+
def config_samd51(self):
self.config_samd21(addr=0x00800100)
+
def config_stm32f1(self):
- self.slope = 3.3 / -.004300
- self.base_temperature = self.calc_base(25., 1.43 / 3.3)
+ self.slope = 3.3 / -0.004300
+ self.base_temperature = self.calc_base(25.0, 1.43 / 3.3)
+
def config_stm32f2(self):
- self.slope = 3.3 / .002500
- self.base_temperature = self.calc_base(25., .76 / 3.3)
+ self.slope = 3.3 / 0.002500
+ self.base_temperature = self.calc_base(25.0, 0.76 / 3.3)
+
def config_stm32f4(self, addr1=0x1FFF7A2C, addr2=0x1FFF7A2E):
- cal_adc_30 = self.read16(addr1) / 4095.
- cal_adc_110 = self.read16(addr2) / 4095.
- self.slope = (110. - 30.) / (cal_adc_110 - cal_adc_30)
- self.base_temperature = self.calc_base(30., cal_adc_30)
+ cal_adc_30 = self.read16(addr1) / 4095.0
+ cal_adc_110 = self.read16(addr2) / 4095.0
+ self.slope = (110.0 - 30.0) / (cal_adc_110 - cal_adc_30)
+ self.base_temperature = self.calc_base(30.0, cal_adc_30)
+
def config_stm32f0x2(self):
self.config_stm32f4(addr1=0x1FFFF7B8, addr2=0x1FFFF7C2)
+
def config_stm32f070(self):
- self.slope = 3.3 / -.004300
- cal_adc_30 = self.read16(0x1FFFF7B8) / 4095.
- self.base_temperature = self.calc_base(30., cal_adc_30)
+ self.slope = 3.3 / -0.004300
+ cal_adc_30 = self.read16(0x1FFFF7B8) / 4095.0
+ self.base_temperature = self.calc_base(30.0, cal_adc_30)
+
def config_stm32g0(self):
- cal_adc_30 = self.read16(0x1FFF75A8) * 3.0 / (3.3 * 4095.)
- cal_adc_130 = self.read16(0x1FFF75CA) * 3.0 / (3.3 * 4095.)
- self.slope = (130. - 30.) / (cal_adc_130 - cal_adc_30)
- self.base_temperature = self.calc_base(30., cal_adc_30)
+ cal_adc_30 = self.read16(0x1FFF75A8) * 3.0 / (3.3 * 4095.0)
+ cal_adc_130 = self.read16(0x1FFF75CA) * 3.0 / (3.3 * 4095.0)
+ self.slope = (130.0 - 30.0) / (cal_adc_130 - cal_adc_30)
+ self.base_temperature = self.calc_base(30.0, cal_adc_30)
+
def config_stm32h723(self):
- cal_adc_30 = self.read16(0x1FF1E820) / 4095.
- cal_adc_130 = self.read16(0x1FF1E840) / 4095.
- self.slope = (130. - 30.) / (cal_adc_130 - cal_adc_30)
- self.base_temperature = self.calc_base(30., cal_adc_30)
+ cal_adc_30 = self.read16(0x1FF1E820) / 4095.0
+ cal_adc_130 = self.read16(0x1FF1E840) / 4095.0
+ self.slope = (130.0 - 30.0) / (cal_adc_130 - cal_adc_30)
+ self.base_temperature = self.calc_base(30.0, cal_adc_30)
+
def config_stm32h7(self):
- cal_adc_30 = self.read16(0x1FF1E820) / 65535.
- cal_adc_110 = self.read16(0x1FF1E840) / 65535.
- self.slope = (110. - 30.) / (cal_adc_110 - cal_adc_30)
- self.base_temperature = self.calc_base(30., cal_adc_30)
+ cal_adc_30 = self.read16(0x1FF1E820) / 65535.0
+ cal_adc_110 = self.read16(0x1FF1E840) / 65535.0
+ self.slope = (110.0 - 30.0) / (cal_adc_110 - cal_adc_30)
+ self.base_temperature = self.calc_base(30.0, cal_adc_30)
+
def read16(self, addr):
params = self.debug_read_cmd.send([1, addr])
- return params['val']
+ return params["val"]
+
def read32(self, addr):
params = self.debug_read_cmd.send([2, addr])
- return params['val']
+ return params["val"]
+
def load_config(config):
pheaters = config.get_printer().load_object(config, "heaters")
diff --git a/klippy/extras/temperature_probe.py b/klippy/extras/temperature_probe.py
index c480ddae..1d5327d7 100644
--- a/klippy/extras/temperature_probe.py
+++ b/klippy/extras/temperature_probe.py
@@ -12,6 +12,7 @@ KELVIN_TO_CELSIUS = -273.15
# Polynomial Helper Classes and Functions
######################################################################
+
def calc_determinant(matrix):
m = matrix
aei = m[0][0] * m[1][1] * m[2][2]
@@ -22,6 +23,7 @@ def calc_determinant(matrix):
afh = m[0][0] * m[2][1] * m[1][2]
return aei + bfg + cdh - ceg - bdi - afh
+
class Polynomial2d:
def __init__(self, a, b, c):
self.a = a
@@ -65,13 +67,9 @@ class Polynomial2d:
sum_x3 = sum([x**3 for x in xlist])
sum_x4 = sum([x**4 for x in xlist])
sum_xy = sum([x * y for x, y in coords])
- sum_x2y = sum([y*x**2 for x, y in coords])
+ sum_x2y = sum([y * x**2 for x, y in coords])
vector_b = [sum_y, sum_xy, sum_x2y]
- m = [
- [count, sum_x, sum_x2],
- [sum_x, sum_x2, sum_x3],
- [sum_x2, sum_x3, sum_x4]
- ]
+ m = [[count, sum_x, sum_x2], [sum_x, sum_x2, sum_x3], [sum_x2, sum_x3, sum_x4]]
m0 = [vector_b, m[1], m[2]]
m1 = [m[0], vector_b, m[2]]
m2 = [m[0], m[1], vector_b]
@@ -81,52 +79,47 @@ class Polynomial2d:
a2 = calc_determinant(m2) / det_m
return cls(a0, a1, a2)
+
class TemperatureProbe:
def __init__(self, config):
self.name = config.get_name()
self.printer = config.get_printer()
self.gcode = self.printer.lookup_object("gcode")
- self.speed = config.getfloat("speed", None, above=0.)
- self.horizontal_move_z = config.getfloat(
- "horizontal_move_z", 2., above=0.
- )
- self.resting_z = config.getfloat("resting_z", .4, above=0.)
- self.cal_pos = config.getfloatlist(
- "calibration_position", None, count=3
- )
- self.cal_bed_temp = config.getfloat(
- "calibration_bed_temp", None, above=50.
- )
+ self.speed = config.getfloat("speed", None, above=0.0)
+ self.horizontal_move_z = config.getfloat("horizontal_move_z", 2.0, above=0.0)
+ self.resting_z = config.getfloat("resting_z", 0.4, above=0.0)
+ self.cal_pos = config.getfloatlist("calibration_position", None, count=3)
+ self.cal_bed_temp = config.getfloat("calibration_bed_temp", None, above=50.0)
self.cal_extruder_temp = config.getfloat(
- "calibration_extruder_temp", None, above=50.
- )
- self.cal_extruder_z = config.getfloat(
- "extruder_heating_z", 50., above=0.
+ "calibration_extruder_temp", None, above=50.0
)
+ self.cal_extruder_z = config.getfloat("extruder_heating_z", 50.0, above=0.0)
# Setup temperature sensor
- smooth_time = config.getfloat("smooth_time", 2., above=0.)
- self.inv_smooth_time = 1. / smooth_time
+ smooth_time = config.getfloat("smooth_time", 2.0, above=0.0)
+ self.inv_smooth_time = 1.0 / smooth_time
self.min_temp = config.getfloat(
"min_temp", KELVIN_TO_CELSIUS, minval=KELVIN_TO_CELSIUS
)
- self.max_temp = config.getfloat(
- "max_temp", 99999999.9, above=self.min_temp
- )
+ self.max_temp = config.getfloat("max_temp", 99999999.9, above=self.min_temp)
pheaters = self.printer.load_object(config, "heaters")
self.sensor = pheaters.setup_sensor(config)
self.sensor.setup_minmax(self.min_temp, self.max_temp)
self.sensor.setup_callback(self._temp_callback)
pheaters.register_sensor(config, self)
- self.last_temp_read_time = 0.
- self.last_measurement = (0., 99999999., 0.,)
+ self.last_temp_read_time = 0.0
+ self.last_measurement = (
+ 0.0,
+ 99999999.0,
+ 0.0,
+ )
# Calibration State
self.cal_helper = None
- self.next_auto_temp = 99999999.
+ self.next_auto_temp = 99999999.0
self.target_temp = 0
self.expected_count = 0
self.sample_count = 0
self.in_calibration = False
- self.step = 2.
+ self.step = 2.0
self.last_zero_pos = None
self.total_expansion = 0
self.start_pos = []
@@ -134,15 +127,19 @@ class TemperatureProbe:
# Register GCode Commands
pname = self.name.split(maxsplit=1)[-1]
self.gcode.register_mux_command(
- "TEMPERATURE_PROBE_CALIBRATE", "PROBE", pname,
+ "TEMPERATURE_PROBE_CALIBRATE",
+ "PROBE",
+ pname,
self.cmd_TEMPERATURE_PROBE_CALIBRATE,
- desc=self.cmd_TEMPERATURE_PROBE_CALIBRATE_help
+ desc=self.cmd_TEMPERATURE_PROBE_CALIBRATE_help,
)
self.gcode.register_mux_command(
- "TEMPERATURE_PROBE_ENABLE", "PROBE", pname,
+ "TEMPERATURE_PROBE_ENABLE",
+ "PROBE",
+ pname,
self.cmd_TEMPERATURE_PROBE_ENABLE,
- desc=self.cmd_TEMPERATURE_PROBE_ENABLE_help
+ desc=self.cmd_TEMPERATURE_PROBE_ENABLE_help,
)
# Register Drift Compensation Helper with probe
@@ -166,20 +163,18 @@ class TemperatureProbe:
time_diff = read_time - self.last_temp_read_time
self.last_temp_read_time = read_time
temp_diff = temp - smoothed_temp
- adj_time = min(time_diff * self.inv_smooth_time, 1.)
+ adj_time = min(time_diff * self.inv_smooth_time, 1.0)
smoothed_temp += temp_diff * adj_time
measured_min = min(measured_min, smoothed_temp)
measured_max = max(measured_max, smoothed_temp)
self.last_measurement = (smoothed_temp, measured_min, measured_max)
if self.in_calibration and smoothed_temp >= self.next_auto_temp:
- self.printer.get_reactor().register_async_callback(
- self._check_kick_next
- )
+ self.printer.get_reactor().register_async_callback(self._check_kick_next)
def _check_kick_next(self, eventtime):
smoothed_temp = self.last_measurement[0]
if self.in_calibration and smoothed_temp >= self.next_auto_temp:
- self.next_auto_temp = 99999999.
+ self.next_auto_temp = 99999999.0
self.gcode.run_script("TEMPERATURE_PROBE_NEXT")
def get_temp(self, eventtime=None):
@@ -204,8 +199,9 @@ class TemperatureProbe:
# Register our own abort command now that the manual
# probe has finished and unregistered
self.gcode.register_command(
- "ABORT", self.cmd_TEMPERATURE_PROBE_ABORT,
- desc=self.cmd_TEMPERATURE_PROBE_ABORT_help
+ "ABORT",
+ self.cmd_TEMPERATURE_PROBE_ABORT,
+ desc=self.cmd_TEMPERATURE_PROBE_ABORT_help,
)
probe_speed = self._get_speeds()[1]
# Move tool down to the resting position
@@ -217,8 +213,7 @@ class TemperatureProbe:
self.next_auto_temp = last_temp + self.step
self.gcode.respond_info(
"%s: collected sample %d/%d at temp %.2fC, next sample scheduled "
- "at temp %.2fC"
- % (self.name, cnt, exp_cnt, last_temp, self.next_auto_temp)
+ "at temp %.2fC" % (self.name, cnt, exp_cnt, last_temp, self.next_auto_temp)
)
def _manual_probe_finalize(self, kin_pos):
@@ -230,8 +225,7 @@ class TemperatureProbe:
z_diff = self.last_zero_pos[2] - kin_pos[2]
self.total_expansion += z_diff
logging.info(
- "Estimated Total Thermal Expansion: %.6f"
- % (self.total_expansion,)
+ "Estimated Total Thermal Expansion: %.6f" % (self.total_expansion,)
)
self.last_zero_pos = kin_pos
toolhead = self.printer.lookup_object("toolhead")
@@ -255,11 +249,11 @@ class TemperatureProbe:
raise
def _finalize_drift_cal(self, success, msg=None):
- self.next_auto_temp = 99999999.
+ self.next_auto_temp = 99999999.0
self.target_temp = 0
self.expected_count = 0
self.sample_count = 0
- self.step = 2.
+ self.step = 2.0
self.in_calibration = False
self.last_zero_pos = None
self.total_expansion = 0
@@ -293,21 +287,19 @@ class TemperatureProbe:
toolhead = self.printer.lookup_object("toolhead")
extr_name = toolhead.get_extruder().get_name()
self.gcode.run_script_from_command(
- "SET_HEATER_TEMPERATURE HEATER=%s TARGET=%f"
- % (extr_name, temp)
+ "SET_HEATER_TEMPERATURE HEATER=%s TARGET=%f" % (extr_name, temp)
)
if wait:
self.gcode.run_script_from_command(
- "TEMPERATURE_WAIT SENSOR=%s MINIMUM=%f"
- % (extr_name, temp)
+ "TEMPERATURE_WAIT SENSOR=%s MINIMUM=%f" % (extr_name, temp)
)
+
def _set_bed_temp(self, temp):
if self.cal_bed_temp is None:
# Bed temperature not configured
return
self.gcode.run_script_from_command(
- "SET_HEATER_TEMPERATURE HEATER=heater_bed TARGET=%f"
- % (temp,)
+ "SET_HEATER_TEMPERATURE HEATER=heater_bed TARGET=%f" % (temp,)
)
def _check_homed(self):
@@ -317,9 +309,7 @@ class TemperatureProbe:
h_axes = status["homed_axes"]
for axis in "xyz":
if axis not in h_axes:
- raise self.gcode.error(
- "Printer must be homed before calibration"
- )
+ raise self.gcode.error("Printer must be homed before calibration")
def _move_to_start(self):
toolhead = self.printer.lookup_object("toolhead")
@@ -348,20 +338,17 @@ class TemperatureProbe:
cmd_TEMPERATURE_PROBE_CALIBRATE_help = (
"Calibrate probe temperature drift compensation"
)
+
def cmd_TEMPERATURE_PROBE_CALIBRATE(self, gcmd):
if self.cal_helper is None:
- raise gcmd.error(
- "No calibration helper registered for [%s]"
- % (self.name,)
- )
+ raise gcmd.error("No calibration helper registered for [%s]" % (self.name,))
self._check_homed()
probe = self._get_probe()
probe_name = probe.get_status(None)["name"]
short_name = probe_name.split(maxsplit=1)[-1]
if short_name != self.name.split(maxsplit=1)[-1]:
raise self.gcode.error(
- "[%s] not linked to registered probe [%s]."
- % (self.name, probe_name)
+ "[%s] not linked to registered probe [%s]." % (self.name, probe_name)
)
manual_probe.verify_no_manual_probe(self.printer)
if self.in_calibration:
@@ -371,25 +358,23 @@ class TemperatureProbe:
)
cur_temp = self.last_measurement[0]
target_temp = gcmd.get_float("TARGET", above=cur_temp)
- step = gcmd.get_float("STEP", 2., minval=1.0)
- expected_count = int(
- (target_temp - cur_temp) / step + .5
- )
+ step = gcmd.get_float("STEP", 2.0, minval=1.0)
+ expected_count = int((target_temp - cur_temp) / step + 0.5)
if expected_count < 3:
raise gcmd.error(
"Invalid STEP and/or TARGET parameters resulted "
- "in too few expected samples: %d"
- % (expected_count,)
+ "in too few expected samples: %d" % (expected_count,)
)
try:
self.gcode.register_command(
- "TEMPERATURE_PROBE_NEXT", self.cmd_TEMPERATURE_PROBE_NEXT,
- desc=self.cmd_TEMPERATURE_PROBE_NEXT_help
+ "TEMPERATURE_PROBE_NEXT",
+ self.cmd_TEMPERATURE_PROBE_NEXT,
+ desc=self.cmd_TEMPERATURE_PROBE_NEXT_help,
)
self.gcode.register_command(
"TEMPERATURE_PROBE_COMPLETE",
self.cmd_TEMPERATURE_PROBE_COMPLETE,
- desc=self.cmd_TEMPERATURE_PROBE_NEXT_help
+ desc=self.cmd_TEMPERATURE_PROBE_NEXT_help,
)
except self.printer.config_error:
raise gcmd.error(
@@ -411,14 +396,13 @@ class TemperatureProbe:
# Capture start position and begin initial probe
toolhead = self.printer.lookup_object("toolhead")
self.start_pos = toolhead.get_position()[:2]
- manual_probe.ManualProbeHelper(
- self.printer, gcmd, self._manual_probe_finalize
- )
+ manual_probe.ManualProbeHelper(self.printer, gcmd, self._manual_probe_finalize)
cmd_TEMPERATURE_PROBE_NEXT_help = "Sample next probe drift temperature"
+
def cmd_TEMPERATURE_PROBE_NEXT(self, gcmd):
manual_probe.verify_no_manual_probe(self.printer)
- self.next_auto_temp = 99999999.
+ self.next_auto_temp = 99999999.0
toolhead = self.printer.lookup_object("toolhead")
# Lift and Move to nozzle back to start position
curpos = toolhead.get_position()
@@ -433,22 +417,23 @@ class TemperatureProbe:
curpos[2] = start_z
toolhead.manual_move(curpos, probe_speed)
self.gcode.register_command("ABORT", None)
- manual_probe.ManualProbeHelper(
- self.printer, gcmd, self._manual_probe_finalize
- )
+ manual_probe.ManualProbeHelper(self.printer, gcmd, self._manual_probe_finalize)
cmd_TEMPERATURE_PROBE_COMPLETE_help = "Finish Probe Drift Calibration"
+
def cmd_TEMPERATURE_PROBE_COMPLETE(self, gcmd):
manual_probe.verify_no_manual_probe(self.printer)
self._finalize_drift_cal(self.sample_count >= 3)
cmd_TEMPERATURE_PROBE_ABORT_help = "Abort Probe Drift Calibration"
+
def cmd_TEMPERATURE_PROBE_ABORT(self, gcmd):
self._finalize_drift_cal(False)
cmd_TEMPERATURE_PROBE_ENABLE_help = (
"Set adjustment factor applied to drift correction"
)
+
def cmd_TEMPERATURE_PROBE_ENABLE(self, gcmd):
if self.cal_helper is not None:
self.cal_helper.set_enabled(gcmd)
@@ -467,11 +452,11 @@ class TemperatureProbe:
"measured_max_temp": round(measured_max, 2),
"in_calibration": self.in_calibration,
"estimated_expansion": self.total_expansion,
- "compensation_enabled": dcomp_enabled
+ "compensation_enabled": dcomp_enabled,
}
def stats(self, eventtime):
- return False, '%s: temp=%.1f' % (self.name, self.last_measurement[0])
+ return False, "%s: temp=%.1f" % (self.name, self.last_measurement[0])
#####################################################################
@@ -482,26 +467,23 @@ class TemperatureProbe:
DRIFT_SAMPLE_COUNT = 9
+
class EddyDriftCompensation:
def __init__(self, config, sensor):
self.printer = config.get_printer()
self.temp_sensor = sensor
self.name = config.get_name()
- self.cal_temp = config.getfloat("calibration_temp", 0.)
+ self.cal_temp = config.getfloat("calibration_temp", 0.0)
self.drift_calibration = None
self.calibration_samples = None
- self.max_valid_temp = config.getfloat("max_validation_temp", 60.)
- self.dc_min_temp = config.getfloat("drift_calibration_min_temp", 0.)
- dc = config.getlists(
- "drift_calibration", None, seps=(',', '\n'), parser=float
- )
- self.min_freq = 999999999999.
+ self.max_valid_temp = config.getfloat("max_validation_temp", 60.0)
+ self.dc_min_temp = config.getfloat("drift_calibration_min_temp", 0.0)
+ dc = config.getlists("drift_calibration", None, seps=(",", "\n"), parser=float)
+ self.min_freq = 999999999999.0
if dc is not None:
for coefs in dc:
if len(coefs) != 3:
- raise config.error(
- "Invalid polynomial in drift calibration"
- )
+ raise config.error("Invalid polynomial in drift calibration")
self.drift_calibration = [Polynomial2d(*coefs) for coefs in dc]
cal = self.drift_calibration
start_temp, end_temp = self.dc_min_temp, self.max_valid_temp
@@ -517,16 +499,14 @@ class EddyDriftCompensation:
else:
logging.info(
"%s: No drift calibration configured, disabling temperature "
- "drift compensation"
- % (self.name,)
+ "drift compensation" % (self.name,)
)
self.enabled = has_dc = self.drift_calibration is not None
if self.cal_temp < 1e-6 and has_dc:
self.enabled = False
logging.info(
"%s: No temperature saved for eddy probe calibration, "
- "disabling temperature drift compensation."
- % (self.name,)
+ "disabling temperature drift compensation." % (self.name,)
)
def is_enabled(self):
@@ -552,21 +532,20 @@ class EddyDriftCompensation:
def note_z_calibration_finish(self):
self.cal_temp = (self.cal_temp + self.get_temperature()) / 2.0
- configfile = self.printer.lookup_object('configfile')
+ configfile = self.printer.lookup_object("configfile")
configfile.set(self.name, "calibration_temp", "%.6f " % (self.cal_temp))
gcode = self.printer.lookup_object("gcode")
gcode.respond_info(
"%s: Z Calibration Temperature set to %.2f. "
"The SAVE_CONFIG command will update the printer config "
- "file and restart the printer."
- % (self.name, self.cal_temp)
+ "file and restart the printer." % (self.name, self.cal_temp)
)
def collect_sample(self, kin_pos, tool_zero_z, speeds):
if self.calibration_samples is None:
self.calibration_samples = [[] for _ in range(DRIFT_SAMPLE_COUNT)]
move_times = []
- temps = [0. for _ in range(DRIFT_SAMPLE_COUNT)]
+ temps = [0.0 for _ in range(DRIFT_SAMPLE_COUNT)]
probe_samples = [[] for _ in range(DRIFT_SAMPLE_COUNT)]
toolhead = self.printer.lookup_object("toolhead")
cur_pos = toolhead.get_position()
@@ -588,39 +567,39 @@ class EddyDriftCompensation:
temps[idx] = cur_temp
probe_samples[idx].append(sample)
return True
+
sect_name = "probe_eddy_current " + self.name.split(maxsplit=1)[-1]
self.printer.lookup_object(sect_name).add_client(_on_bulk_data_recd)
for i in range(DRIFT_SAMPLE_COUNT):
if i == 0:
# Move down to first sample location
- cur_pos[2] = tool_zero_z + .05
+ cur_pos[2] = tool_zero_z + 0.05
else:
# Sample each .5mm in z
- cur_pos[2] += 1.
+ cur_pos[2] += 1.0
toolhead.manual_move(cur_pos, lift_speed)
- cur_pos[2] -= .5
+ cur_pos[2] -= 0.5
toolhead.manual_move(cur_pos, probe_speed)
- start = toolhead.get_last_move_time() + .05
- end = start + .1
+ start = toolhead.get_last_move_time() + 0.05
+ end = start + 0.1
move_times.append((i, start, end))
- toolhead.dwell(.2)
+ toolhead.dwell(0.2)
toolhead.wait_moves()
# Wait for sample collection to finish
reactor = self.printer.get_reactor()
evttime = reactor.monotonic()
while move_times:
- evttime = reactor.pause(evttime + .1)
+ evttime = reactor.pause(evttime + 0.1)
sample_temp = sum(temps) / len(temps)
for i, data in enumerate(probe_samples):
freqs = [d[1] for d in data]
zvals = [d[2] for d in data]
avg_freq = sum(freqs) / len(freqs)
avg_z = sum(zvals) / len(zvals)
- kin_z = i * .5 + .05 + kin_pos[2]
+ kin_z = i * 0.5 + 0.05 + kin_pos[2]
logging.info(
"Probe Values at Temp %.2fC, Z %.4fmm: Avg Freq = %.6f, "
- "Avg Measured Z = %.6f"
- % (sample_temp, kin_z, avg_freq, avg_z)
+ "Avg Measured Z = %.6f" % (sample_temp, kin_z, avg_freq, avg_z)
)
self.calibration_samples[i].append((sample_temp, avg_freq))
return sample_temp
@@ -636,28 +615,25 @@ class EddyDriftCompensation:
return
gcode = self.printer.lookup_object("gcode")
if len(cal_samples) < 3:
- raise gcode.error(
- "calibration error, not enough samples"
- )
+ raise gcode.error("calibration error, not enough samples")
min_temp, _ = cal_samples[0][0]
max_temp, _ = cal_samples[-1][0]
polynomials = []
for i, coords in enumerate(cal_samples):
- height = .05 + i * .5
+ height = 0.05 + i * 0.5
poly = Polynomial2d.fit(coords)
polynomials.append(poly)
logging.info("Polynomial at Z=%.2f: %s" % (height, repr(poly)))
end_vld_temp = max(self.max_valid_temp, max_temp)
self._check_calibration(polynomials, min_temp, end_vld_temp)
coef_cfg = "\n" + "\n".join([str(p) for p in polynomials])
- configfile = self.printer.lookup_object('configfile')
+ configfile = self.printer.lookup_object("configfile")
configfile.set(self.name, "drift_calibration", coef_cfg)
configfile.set(self.name, "drift_calibration_min_temp", min_temp)
gcode.respond_info(
"%s: generated %d 2D polynomials\n"
"The SAVE_CONFIG command will update the printer config "
- "file and restart the printer."
- % (self.name, len(polynomials))
+ "file and restart the printer." % (self.name, len(polynomials))
)
def _check_calibration(self, calibration, start_temp, end_temp, error=None):
@@ -706,9 +682,9 @@ class EddyDriftCompensation:
# Frequency above max calibration value
err = poly(dest_temp) - low_freq
return freq + err
- t = min(1., max(0., (freq - low_freq) / (high_freq - low_freq)))
+ t = min(1.0, max(0.0, (freq - low_freq) / (high_freq - low_freq)))
low_tgt_freq = poly(dest_temp)
- high_tgt_freq = dc[pos-1](dest_temp)
+ high_tgt_freq = dc[pos - 1](dest_temp)
return (1 - t) * low_tgt_freq + t * high_tgt_freq
# Frequency below minimum, no correction
return freq
diff --git a/klippy/extras/temperature_sensor.py b/klippy/extras/temperature_sensor.py
index 19733731..9bde9702 100644
--- a/klippy/extras/temperature_sensor.py
+++ b/klippy/extras/temperature_sensor.py
@@ -6,37 +6,43 @@
KELVIN_TO_CELSIUS = -273.15
+
class PrinterSensorGeneric:
def __init__(self, config):
self.printer = config.get_printer()
self.name = config.get_name().split()[-1]
- pheaters = self.printer.load_object(config, 'heaters')
+ pheaters = self.printer.load_object(config, "heaters")
self.sensor = pheaters.setup_sensor(config)
- self.min_temp = config.getfloat('min_temp', KELVIN_TO_CELSIUS,
- minval=KELVIN_TO_CELSIUS)
- self.max_temp = config.getfloat('max_temp', 99999999.9,
- above=self.min_temp)
+ self.min_temp = config.getfloat(
+ "min_temp", KELVIN_TO_CELSIUS, minval=KELVIN_TO_CELSIUS
+ )
+ self.max_temp = config.getfloat("max_temp", 99999999.9, above=self.min_temp)
self.sensor.setup_minmax(self.min_temp, self.max_temp)
self.sensor.setup_callback(self.temperature_callback)
pheaters.register_sensor(config, self)
- self.last_temp = 0.
- self.measured_min = 99999999.
- self.measured_max = 0.
+ self.last_temp = 0.0
+ self.measured_min = 99999999.0
+ self.measured_max = 0.0
+
def temperature_callback(self, read_time, temp):
self.last_temp = temp
if temp:
self.measured_min = min(self.measured_min, temp)
self.measured_max = max(self.measured_max, temp)
+
def get_temp(self, eventtime):
- return self.last_temp, 0.
+ return self.last_temp, 0.0
+
def stats(self, eventtime):
- return False, '%s: temp=%.1f' % (self.name, self.last_temp)
+ return False, "%s: temp=%.1f" % (self.name, self.last_temp)
+
def get_status(self, eventtime):
return {
- 'temperature': round(self.last_temp, 2),
- 'measured_min_temp': round(self.measured_min, 2),
- 'measured_max_temp': round(self.measured_max, 2)
+ "temperature": round(self.last_temp, 2),
+ "measured_min_temp": round(self.measured_min, 2),
+ "measured_max_temp": round(self.measured_max, 2),
}
+
def load_config_prefix(config):
return PrinterSensorGeneric(config)
diff --git a/klippy/extras/thermistor.py b/klippy/extras/thermistor.py
index db26b50c..db1288e5 100644
--- a/klippy/extras/thermistor.py
+++ b/klippy/extras/thermistor.py
@@ -8,18 +8,20 @@ from . import adc_temperature
KELVIN_TO_CELSIUS = -273.15
+
# Analog voltage to temperature converter for thermistors
class Thermistor:
def __init__(self, pullup, inline_resistor):
self.pullup = pullup
self.inline_resistor = inline_resistor
- self.c1 = self.c2 = self.c3 = 0.
+ self.c1 = self.c2 = self.c3 = 0.0
+
def setup_coefficients(self, t1, r1, t2, r2, t3, r3, name=""):
# Calculate Steinhart-Hart coefficients from temp measurements.
# Arrange samples as 3 linear equations and solve for c1, c2, and c3.
- inv_t1 = 1. / (t1 - KELVIN_TO_CELSIUS)
- inv_t2 = 1. / (t2 - KELVIN_TO_CELSIUS)
- inv_t3 = 1. / (t3 - KELVIN_TO_CELSIUS)
+ inv_t1 = 1.0 / (t1 - KELVIN_TO_CELSIUS)
+ inv_t2 = 1.0 / (t2 - KELVIN_TO_CELSIUS)
+ inv_t3 = 1.0 / (t3 - KELVIN_TO_CELSIUS)
ln_r1 = math.log(r1)
ln_r2 = math.log(r2)
ln_r3 = math.log(r3)
@@ -29,79 +31,90 @@ class Thermistor:
ln_r12, ln_r13 = ln_r1 - ln_r2, ln_r1 - ln_r3
ln3_r12, ln3_r13 = ln3_r1 - ln3_r2, ln3_r1 - ln3_r3
- self.c3 = ((inv_t12 - inv_t13 * ln_r12 / ln_r13)
- / (ln3_r12 - ln3_r13 * ln_r12 / ln_r13))
- if self.c3 <= 0.:
+ self.c3 = (inv_t12 - inv_t13 * ln_r12 / ln_r13) / (
+ ln3_r12 - ln3_r13 * ln_r12 / ln_r13
+ )
+ if self.c3 <= 0.0:
beta = ln_r13 / inv_t13
- logging.warning("Using thermistor beta %.3f in heater %s",
- beta, name)
+ logging.warning("Using thermistor beta %.3f in heater %s", beta, name)
self.setup_coefficients_beta(t1, r1, beta)
return
self.c2 = (inv_t12 - self.c3 * ln3_r12) / ln_r12
self.c1 = inv_t1 - self.c2 * ln_r1 - self.c3 * ln3_r1
+
def setup_coefficients_beta(self, t1, r1, beta):
# Calculate equivalent Steinhart-Hart coefficients from beta
- inv_t1 = 1. / (t1 - KELVIN_TO_CELSIUS)
+ inv_t1 = 1.0 / (t1 - KELVIN_TO_CELSIUS)
ln_r1 = math.log(r1)
- self.c3 = 0.
- self.c2 = 1. / beta
+ self.c3 = 0.0
+ self.c2 = 1.0 / beta
self.c1 = inv_t1 - self.c2 * ln_r1
+
def calc_temp(self, adc):
# Calculate temperature from adc
- adc = max(.00001, min(.99999, adc))
+ adc = max(0.00001, min(0.99999, adc))
r = self.pullup * adc / (1.0 - adc)
ln_r = math.log(r - self.inline_resistor)
inv_t = self.c1 + self.c2 * ln_r + self.c3 * ln_r**3
- return 1.0/inv_t + KELVIN_TO_CELSIUS
+ return 1.0 / inv_t + KELVIN_TO_CELSIUS
+
def calc_adc(self, temp):
# Calculate adc reading from a temperature
if temp <= KELVIN_TO_CELSIUS:
- return 1.
- inv_t = 1. / (temp - KELVIN_TO_CELSIUS)
+ return 1.0
+ inv_t = 1.0 / (temp - KELVIN_TO_CELSIUS)
if self.c3:
# Solve for ln_r using Cardano's formula
- y = (self.c1 - inv_t) / (2. * self.c3)
- x = math.sqrt((self.c2 / (3. * self.c3))**3 + y**2)
- ln_r = math.pow(x - y, 1./3.) - math.pow(x + y, 1./3.)
+ y = (self.c1 - inv_t) / (2.0 * self.c3)
+ x = math.sqrt((self.c2 / (3.0 * self.c3)) ** 3 + y**2)
+ ln_r = math.pow(x - y, 1.0 / 3.0) - math.pow(x + y, 1.0 / 3.0)
else:
ln_r = (inv_t - self.c1) / self.c2
r = math.exp(ln_r) + self.inline_resistor
return r / (self.pullup + r)
+
# Create an ADC converter with a thermistor
def PrinterThermistor(config, params):
- pullup = config.getfloat('pullup_resistor', 4700., above=0.)
- inline_resistor = config.getfloat('inline_resistor', 0., minval=0.)
+ pullup = config.getfloat("pullup_resistor", 4700.0, above=0.0)
+ inline_resistor = config.getfloat("inline_resistor", 0.0, minval=0.0)
thermistor = Thermistor(pullup, inline_resistor)
- if 'beta' in params:
- thermistor.setup_coefficients_beta(
- params['t1'], params['r1'], params['beta'])
+ if "beta" in params:
+ thermistor.setup_coefficients_beta(params["t1"], params["r1"], params["beta"])
else:
thermistor.setup_coefficients(
- params['t1'], params['r1'], params['t2'], params['r2'],
- params['t3'], params['r3'], name=config.get_name())
+ params["t1"],
+ params["r1"],
+ params["t2"],
+ params["r2"],
+ params["t3"],
+ params["r3"],
+ name=config.get_name(),
+ )
return adc_temperature.PrinterADCtoTemperature(config, thermistor)
+
# Custom defined thermistors from the config file
class CustomThermistor:
def __init__(self, config):
self.name = " ".join(config.get_name().split()[1:])
t1 = config.getfloat("temperature1", minval=KELVIN_TO_CELSIUS)
- r1 = config.getfloat("resistance1", minval=0.)
- beta = config.getfloat("beta", None, above=0.)
+ r1 = config.getfloat("resistance1", minval=0.0)
+ beta = config.getfloat("beta", None, above=0.0)
if beta is not None:
- self.params = {'t1': t1, 'r1': r1, 'beta': beta}
+ self.params = {"t1": t1, "r1": r1, "beta": beta}
return
t2 = config.getfloat("temperature2", minval=KELVIN_TO_CELSIUS)
- r2 = config.getfloat("resistance2", minval=0.)
+ r2 = config.getfloat("resistance2", minval=0.0)
t3 = config.getfloat("temperature3", minval=KELVIN_TO_CELSIUS)
- r3 = config.getfloat("resistance3", minval=0.)
+ r3 = config.getfloat("resistance3", minval=0.0)
(t1, r1), (t2, r2), (t3, r3) = sorted([(t1, r1), (t2, r2), (t3, r3)])
- self.params = {'t1': t1, 'r1': r1, 't2': t2, 'r2': r2,
- 't3': t3, 'r3': r3}
+ self.params = {"t1": t1, "r1": r1, "t2": t2, "r2": r2, "t3": t3, "r3": r3}
+
def create(self, config):
return PrinterThermistor(config, self.params)
+
def load_config_prefix(config):
thermistor = CustomThermistor(config)
pheaters = config.get_printer().load_object(config, "heaters")
diff --git a/klippy/extras/tmc.py b/klippy/extras/tmc.py
index aad19cf4..4a7bcc9c 100644
--- a/klippy/extras/tmc.py
+++ b/klippy/extras/tmc.py
@@ -12,23 +12,29 @@ from . import bulk_sensor
# Field helpers
######################################################################
+
# Return the position of the first bit set in a mask
def ffs(mask):
return (mask & -mask).bit_length() - 1
+
class FieldHelper:
- def __init__(self, all_fields, signed_fields=[], field_formatters={},
- registers=None):
+ def __init__(
+ self, all_fields, signed_fields=[], field_formatters={}, registers=None
+ ):
self.all_fields = all_fields
self.signed_fields = {sf: 1 for sf in signed_fields}
self.field_formatters = field_formatters
self.registers = registers
if self.registers is None:
self.registers = collections.OrderedDict()
- self.field_to_register = { f: r for r, fields in self.all_fields.items()
- for f in fields }
+ self.field_to_register = {
+ f: r for r, fields in self.all_fields.items() for f in fields
+ }
+
def lookup_register(self, field_name, default=None):
return self.field_to_register.get(field_name, default)
+
def get_field(self, field_name, reg_value=None, reg_name=None):
# Returns value of the register field
if reg_name is None:
@@ -37,9 +43,10 @@ class FieldHelper:
reg_value = self.registers.get(reg_name, 0)
mask = self.all_fields[reg_name][field_name]
field_value = (reg_value & mask) >> ffs(mask)
- if field_name in self.signed_fields and ((reg_value & mask)<<1) > mask:
- field_value -= (1 << field_value.bit_length())
+ if field_name in self.signed_fields and ((reg_value & mask) << 1) > mask:
+ field_value -= 1 << field_value.bit_length()
return field_value
+
def set_field(self, field_name, field_value, reg_value=None, reg_name=None):
# Returns register value with field bits filled with supplied value
if reg_name is None:
@@ -50,6 +57,7 @@ class FieldHelper:
new_value = (reg_value & ~mask) | ((field_value << ffs(mask)) & mask)
self.registers[reg_name] = new_value
return new_value
+
def set_config_field(self, config, field_name, default):
# Allow a field to be set from the config file
config_name = "driver_" + field_name.upper()
@@ -59,11 +67,13 @@ class FieldHelper:
if maxval == 1:
val = config.getboolean(config_name, default)
elif field_name in self.signed_fields:
- val = config.getint(config_name, default,
- minval=-(maxval//2 + 1), maxval=maxval//2)
+ val = config.getint(
+ config_name, default, minval=-(maxval // 2 + 1), maxval=maxval // 2
+ )
else:
val = config.getint(config_name, default, minval=0, maxval=maxval)
return self.set_field(field_name, val)
+
def pretty_format(self, reg_name, reg_value):
# Provide a string description of a register
reg_fields = self.all_fields.get(reg_name, {})
@@ -75,22 +85,26 @@ class FieldHelper:
if sval and sval != "0":
fields.append(" %s=%s" % (field_name, sval))
return "%-11s %08x%s" % (reg_name + ":", reg_value, "".join(fields))
+
def get_reg_fields(self, reg_name, reg_value):
# Provide fields found in a register
reg_fields = self.all_fields.get(reg_name, {})
- return {field_name: self.get_field(field_name, reg_value, reg_name)
- for field_name, mask in reg_fields.items()}
+ return {
+ field_name: self.get_field(field_name, reg_value, reg_name)
+ for field_name, mask in reg_fields.items()
+ }
######################################################################
# Periodic error checking
######################################################################
+
class TMCErrorCheck:
def __init__(self, config, mcu_tmc):
self.printer = config.get_printer()
name_parts = config.get_name().split()
- self.stepper_name = ' '.join(name_parts[1:])
+ self.stepper_name = " ".join(name_parts[1:])
self.mcu_tmc = mcu_tmc
self.fields = mcu_tmc.get_fields()
self.check_timer = None
@@ -98,7 +112,7 @@ class TMCErrorCheck:
# Setup for GSTAT query
reg_name = self.fields.lookup_register("drv_err")
if reg_name is not None:
- self.gstat_reg_info = [0, reg_name, 0xffffffff, 0xffffffff, 0]
+ self.gstat_reg_info = [0, reg_name, 0xFFFFFFFF, 0xFFFFFFFF, 0]
else:
self.gstat_reg_info = None
self.clear_gstat = True
@@ -106,11 +120,11 @@ class TMCErrorCheck:
self.irun_field = "irun"
reg_name = "DRV_STATUS"
mask = err_mask = cs_actual_mask = 0
- if name_parts[0] == 'tmc2130':
+ if name_parts[0] == "tmc2130":
# TMC2130 driver quirks
self.clear_gstat = False
cs_actual_mask = self.fields.all_fields[reg_name]["cs_actual"]
- elif name_parts[0] == 'tmc2660':
+ elif name_parts[0] == "tmc2660":
# TMC2660 driver quirks
self.irun_field = "cs"
reg_name = "READRSP@RDSEL2"
@@ -127,8 +141,9 @@ class TMCErrorCheck:
self.adc_temp = None
self.adc_temp_reg = self.fields.lookup_register("adc_temp")
if self.adc_temp_reg is not None:
- pheaters = self.printer.load_object(config, 'heaters')
+ pheaters = self.printer.load_object(config, "heaters")
pheaters.register_monitor(config)
+
def _query_register(self, reg_info, try_clear=False):
last_value, reg_name, mask, err_mask, cs_actual_mask = reg_info
cleared_flags = 0
@@ -154,20 +169,21 @@ class TMCErrorCheck:
irun = self.fields.get_field(self.irun_field)
if self.check_timer is None or irun < 4:
break
- if (self.irun_field == "irun"
- and not self.fields.get_field("ihold")):
+ if self.irun_field == "irun" and not self.fields.get_field("ihold"):
break
# CS_ACTUAL field of zero - indicates a driver reset
count += 1
if count >= 3:
fmt = self.fields.pretty_format(reg_name, val)
- raise self.printer.command_error("TMC '%s' reports error: %s"
- % (self.stepper_name, fmt))
+ raise self.printer.command_error(
+ "TMC '%s' reports error: %s" % (self.stepper_name, fmt)
+ )
if try_clear and val & err_mask:
try_clear = False
cleared_flags |= val & err_mask
self.mcu_tmc.set_register(reg_name, val & err_mask)
return cleared_flags
+
def _query_temperature(self):
try:
self.adc_temp = self.mcu_tmc.get_register(self.adc_temp_reg)
@@ -175,6 +191,7 @@ class TMCErrorCheck:
# Ignore comms error for temperature
self.adc_temp = None
return
+
def _do_periodic_check(self, eventtime):
try:
self._query_register(self.drv_status_reg_info)
@@ -185,32 +202,37 @@ class TMCErrorCheck:
except self.printer.command_error as e:
self.printer.invoke_shutdown(str(e))
return self.printer.get_reactor().NEVER
- return eventtime + 1.
+ return eventtime + 1.0
+
def stop_checks(self):
if self.check_timer is None:
return
self.printer.get_reactor().unregister_timer(self.check_timer)
self.check_timer = None
+
def start_checks(self):
if self.check_timer is not None:
self.stop_checks()
cleared_flags = 0
self._query_register(self.drv_status_reg_info)
if self.gstat_reg_info is not None:
- cleared_flags = self._query_register(self.gstat_reg_info,
- try_clear=self.clear_gstat)
+ cleared_flags = self._query_register(
+ self.gstat_reg_info, try_clear=self.clear_gstat
+ )
reactor = self.printer.get_reactor()
curtime = reactor.monotonic()
- self.check_timer = reactor.register_timer(self._do_periodic_check,
- curtime + 1.)
+ self.check_timer = reactor.register_timer(
+ self._do_periodic_check, curtime + 1.0
+ )
if cleared_flags:
reset_mask = self.fields.all_fields["GSTAT"]["reset"]
if cleared_flags & reset_mask:
return True
return False
+
def get_status(self, eventtime=None):
if self.check_timer is None:
- return {'drv_status': None, 'temperature': None}
+ return {"drv_status": None, "temperature": None}
temp = None
if self.adc_temp is not None:
temp = round((self.adc_temp - 2038) / 7.7, 2)
@@ -219,16 +241,18 @@ class TMCErrorCheck:
self.last_drv_status = last_value
fields = self.fields.get_reg_fields(reg_name, last_value)
self.last_drv_fields = {n: v for n, v in fields.items() if v}
- return {'drv_status': self.last_drv_fields, 'temperature': temp}
+ return {"drv_status": self.last_drv_fields, "temperature": temp}
+
######################################################################
# Record driver status
######################################################################
+
class TMCStallguardDump:
def __init__(self, config, mcu_tmc):
self.printer = config.get_printer()
- self.stepper_name = ' '.join(config.get_name().split()[1:])
+ self.stepper_name = " ".join(config.get_name().split()[1:])
self.mcu_tmc = mcu_tmc
self.mcu = self.mcu_tmc.get_mcu()
self.fields = self.mcu_tmc.get_fields()
@@ -256,29 +280,33 @@ class TMCStallguardDump:
self.query_timer = None
self.error = None
self.batch_bulk = bulk_sensor.BatchBulkHelper(
- self.printer, self._dump, self._start, self._stop)
- api_resp = {'header': ('time', 'sg_result', 'cs_actual')}
- self.batch_bulk.add_mux_endpoint("tmc/stallguard_dump", "name",
- self.stepper_name, api_resp)
+ self.printer, self._dump, self._start, self._stop
+ )
+ api_resp = {"header": ("time", "sg_result", "cs_actual")}
+ self.batch_bulk.add_mux_endpoint(
+ "tmc/stallguard_dump", "name", self.stepper_name, api_resp
+ )
+
def _start(self):
self.error = None
status = self.mcu_tmc.get_register_raw("DRV_STATUS")
if status.get("spi_status"):
self.optimized_spi = True
reactor = self.printer.get_reactor()
- self.query_timer = reactor.register_timer(self._query_tmc,
- reactor.NOW)
+ self.query_timer = reactor.register_timer(self._query_tmc, reactor.NOW)
+
def _stop(self):
self.printer.get_reactor().unregister_timer(self.query_timer)
self.query_timer = None
self.samples = []
+
def _query_tmc(self, eventtime):
sg_result = -1
cs_actual = -1
recv_time = eventtime
try:
if self.optimized_spi or self.sg4_reg_name == "SG4_RESULT":
- #TMC2130/TMC5160/TMC2240
+ # TMC2130/TMC5160/TMC2240
status = self.mcu_tmc.get_register_raw("DRV_STATUS")
reg_val = status["data"]
cs_actual = self.fields.get_field("cs_actual", reg_val)
@@ -304,22 +332,24 @@ class TMCStallguardDump:
return eventtime + 0.001
# UART queried as fast as possible
return eventtime + 0.005
+
def _dump(self, eventtime):
- if self.error:
- raise self.error
- samples = self.samples
- self.samples = []
- return {"data": samples}
+ if self.error:
+ raise self.error
+ samples = self.samples
+ self.samples = []
+ return {"data": samples}
######################################################################
# G-Code command helpers
######################################################################
+
class TMCCommandHelper:
def __init__(self, config, mcu_tmc, current_helper):
self.printer = config.get_printer()
- self.stepper_name = ' '.join(config.get_name().split()[1:])
+ self.stepper_name = " ".join(config.get_name().split()[1:])
self.name = config.get_name().split()[-1]
self.mcu_tmc = mcu_tmc
self.current_helper = current_helper
@@ -331,69 +361,87 @@ class TMCCommandHelper:
self.mcu_phase_offset = None
self.stepper = None
self.stepper_enable = self.printer.load_object(config, "stepper_enable")
- self.printer.register_event_handler("stepper:sync_mcu_position",
- self._handle_sync_mcu_pos)
- self.printer.register_event_handler("stepper:set_sdir_inverted",
- self._handle_sync_mcu_pos)
- self.printer.register_event_handler("klippy:mcu_identify",
- self._handle_mcu_identify)
- self.printer.register_event_handler("klippy:connect",
- self._handle_connect)
+ self.printer.register_event_handler(
+ "stepper:sync_mcu_position", self._handle_sync_mcu_pos
+ )
+ self.printer.register_event_handler(
+ "stepper:set_sdir_inverted", self._handle_sync_mcu_pos
+ )
+ self.printer.register_event_handler(
+ "klippy:mcu_identify", self._handle_mcu_identify
+ )
+ self.printer.register_event_handler("klippy:connect", self._handle_connect)
# Set microstep config options
TMCMicrostepHelper(config, mcu_tmc)
# Register commands
gcode = self.printer.lookup_object("gcode")
- gcode.register_mux_command("SET_TMC_FIELD", "STEPPER", self.name,
- self.cmd_SET_TMC_FIELD,
- desc=self.cmd_SET_TMC_FIELD_help)
- gcode.register_mux_command("INIT_TMC", "STEPPER", self.name,
- self.cmd_INIT_TMC,
- desc=self.cmd_INIT_TMC_help)
- gcode.register_mux_command("SET_TMC_CURRENT", "STEPPER", self.name,
- self.cmd_SET_TMC_CURRENT,
- desc=self.cmd_SET_TMC_CURRENT_help)
+ gcode.register_mux_command(
+ "SET_TMC_FIELD",
+ "STEPPER",
+ self.name,
+ self.cmd_SET_TMC_FIELD,
+ desc=self.cmd_SET_TMC_FIELD_help,
+ )
+ gcode.register_mux_command(
+ "INIT_TMC",
+ "STEPPER",
+ self.name,
+ self.cmd_INIT_TMC,
+ desc=self.cmd_INIT_TMC_help,
+ )
+ gcode.register_mux_command(
+ "SET_TMC_CURRENT",
+ "STEPPER",
+ self.name,
+ self.cmd_SET_TMC_CURRENT,
+ desc=self.cmd_SET_TMC_CURRENT_help,
+ )
+
def _init_registers(self, print_time=None):
# Send registers
for reg_name in list(self.fields.registers.keys()):
- val = self.fields.registers[reg_name] # Val may change during loop
+ val = self.fields.registers[reg_name] # Val may change during loop
self.mcu_tmc.set_register(reg_name, val, print_time)
+
cmd_INIT_TMC_help = "Initialize TMC stepper driver registers"
+
def cmd_INIT_TMC(self, gcmd):
logging.info("INIT_TMC %s", self.name)
- print_time = self.printer.lookup_object('toolhead').get_last_move_time()
+ print_time = self.printer.lookup_object("toolhead").get_last_move_time()
self._init_registers(print_time)
+
cmd_SET_TMC_FIELD_help = "Set a register field of a TMC driver"
+
def cmd_SET_TMC_FIELD(self, gcmd):
- field_name = gcmd.get('FIELD').lower()
+ field_name = gcmd.get("FIELD").lower()
reg_name = self.fields.lookup_register(field_name, None)
if reg_name is None:
raise gcmd.error("Unknown field name '%s'" % (field_name,))
- value = gcmd.get_int('VALUE', None)
- velocity = gcmd.get_float('VELOCITY', None, minval=0.)
+ value = gcmd.get_int("VALUE", None)
+ velocity = gcmd.get_float("VELOCITY", None, minval=0.0)
if (value is None) == (velocity is None):
raise gcmd.error("Specify either VALUE or VELOCITY")
if velocity is not None:
if self.mcu_tmc.get_tmc_frequency() is None:
- raise gcmd.error(
- "VELOCITY parameter not supported by this driver")
- value = TMCtstepHelper(self.mcu_tmc, velocity,
- pstepper=self.stepper)
+ raise gcmd.error("VELOCITY parameter not supported by this driver")
+ value = TMCtstepHelper(self.mcu_tmc, velocity, pstepper=self.stepper)
reg_val = self.fields.set_field(field_name, value)
- print_time = self.printer.lookup_object('toolhead').get_last_move_time()
+ print_time = self.printer.lookup_object("toolhead").get_last_move_time()
self.mcu_tmc.set_register(reg_name, reg_val, print_time)
+
cmd_SET_TMC_CURRENT_help = "Set the current of a TMC driver"
+
def cmd_SET_TMC_CURRENT(self, gcmd):
ch = self.current_helper
prev_cur, prev_hold_cur, req_hold_cur, max_cur = ch.get_current()
- run_current = gcmd.get_float('CURRENT', None, minval=0., maxval=max_cur)
- hold_current = gcmd.get_float('HOLDCURRENT', None,
- above=0., maxval=max_cur)
+ run_current = gcmd.get_float("CURRENT", None, minval=0.0, maxval=max_cur)
+ hold_current = gcmd.get_float("HOLDCURRENT", None, above=0.0, maxval=max_cur)
if run_current is not None or hold_current is not None:
if run_current is None:
run_current = prev_cur
if hold_current is None:
hold_current = req_hold_cur
- toolhead = self.printer.lookup_object('toolhead')
+ toolhead = self.printer.lookup_object("toolhead")
print_time = toolhead.get_last_move_time()
ch.set_current(run_current, hold_current, print_time)
prev_cur, prev_hold_cur, req_hold_cur, max_cur = ch.get_current()
@@ -401,13 +449,17 @@ class TMCCommandHelper:
if prev_hold_cur is None:
gcmd.respond_info("Run Current: %0.2fA" % (prev_cur,))
else:
- gcmd.respond_info("Run Current: %0.2fA Hold Current: %0.2fA"
- % (prev_cur, prev_hold_cur))
+ gcmd.respond_info(
+ "Run Current: %0.2fA Hold Current: %0.2fA" % (prev_cur, prev_hold_cur)
+ )
+
# Stepper phase tracking
def _get_phases(self):
return (256 >> self.fields.get_field("mres")) * 4
+
def get_phase_offset(self):
return self.mcu_phase_offset, self._get_phases()
+
def _query_phase(self):
field_name = "mscnt"
if self.fields.lookup_register(field_name, None) is None:
@@ -415,6 +467,7 @@ class TMCCommandHelper:
field_name = "mstep"
reg = self.mcu_tmc.get_register(self.fields.lookup_register(field_name))
return self.fields.get_field(field_name, reg)
+
def _handle_sync_mcu_pos(self, stepper):
if stepper.get_name() != self.stepper_name:
return
@@ -430,12 +483,17 @@ class TMCCommandHelper:
if not stepper.get_dir_inverted()[0]:
driver_phase = 1023 - driver_phase
phases = self._get_phases()
- phase = int(float(driver_phase) / 1024 * phases + .5) % phases
+ phase = int(float(driver_phase) / 1024 * phases + 0.5) % phases
moff = (phase - stepper.get_mcu_position()) % phases
if self.mcu_phase_offset is not None and self.mcu_phase_offset != moff:
- logging.warning("Stepper %s phase change (was %d now %d)",
- self.stepper_name, self.mcu_phase_offset, moff)
+ logging.warning(
+ "Stepper %s phase change (was %d now %d)",
+ self.stepper_name,
+ self.mcu_phase_offset,
+ moff,
+ )
self.mcu_phase_offset = moff
+
# Stepper enable/disable tracking
def _do_enable(self, print_time):
try:
@@ -453,12 +511,14 @@ class TMCCommandHelper:
with gcode.get_mutex():
if self.mcu_phase_offset is not None:
return
- logging.info("Pausing toolhead to calculate %s phase offset",
- self.stepper_name)
- self.printer.lookup_object('toolhead').wait_moves()
+ logging.info(
+ "Pausing toolhead to calculate %s phase offset", self.stepper_name
+ )
+ self.printer.lookup_object("toolhead").wait_moves()
self._handle_sync_mcu_pos(self.stepper)
except self.printer.command_error as e:
self.printer.invoke_shutdown(str(e))
+
def _do_disable(self, print_time):
try:
if self.toff is not None:
@@ -468,18 +528,21 @@ class TMCCommandHelper:
self.echeck_helper.stop_checks()
except self.printer.command_error as e:
self.printer.invoke_shutdown(str(e))
+
def _handle_mcu_identify(self):
# Lookup stepper object
force_move = self.printer.lookup_object("force_move")
self.stepper = force_move.lookup_stepper(self.stepper_name)
# Note pulse duration and step_both_edge optimizations available
- self.stepper.setup_default_pulse_duration(.000000100, True)
+ self.stepper.setup_default_pulse_duration(0.000000100, True)
+
def _handle_stepper_enable(self, print_time, is_enable):
if is_enable:
- cb = (lambda ev: self._do_enable(print_time))
+ cb = lambda ev: self._do_enable(print_time)
else:
- cb = (lambda ev: self._do_disable(print_time))
+ cb = lambda ev: self._do_disable(print_time)
self.printer.get_reactor().register_callback(cb)
+
def _handle_connect(self):
# Check if using step on both edges optimization
pulse_duration, step_both_edge = self.stepper.get_pulse_duration()
@@ -491,37 +554,46 @@ class TMCCommandHelper:
if not enable_line.has_dedicated_enable():
self.toff = self.fields.get_field("toff")
self.fields.set_field("toff", 0)
- logging.info("Enabling TMC virtual enable for '%s'",
- self.stepper_name)
+ logging.info("Enabling TMC virtual enable for '%s'", self.stepper_name)
# Send init
try:
self._init_registers()
except self.printer.command_error as e:
logging.info("TMC %s failed to init: %s", self.name, str(e))
+
# get_status information export
def get_status(self, eventtime=None):
cpos = None
if self.stepper is not None and self.mcu_phase_offset is not None:
cpos = self.stepper.mcu_to_commanded_position(self.mcu_phase_offset)
current = self.current_helper.get_current()
- res = {'mcu_phase_offset': self.mcu_phase_offset,
- 'phase_offset_position': cpos,
- 'run_current': current[0],
- 'hold_current': current[1]}
+ res = {
+ "mcu_phase_offset": self.mcu_phase_offset,
+ "phase_offset_position": cpos,
+ "run_current": current[0],
+ "hold_current": current[1],
+ }
res.update(self.echeck_helper.get_status(eventtime))
return res
+
# DUMP_TMC support
def setup_register_dump(self, read_registers, read_translate=None):
self.read_registers = read_registers
self.read_translate = read_translate
gcode = self.printer.lookup_object("gcode")
- gcode.register_mux_command("DUMP_TMC", "STEPPER", self.name,
- self.cmd_DUMP_TMC,
- desc=self.cmd_DUMP_TMC_help)
+ gcode.register_mux_command(
+ "DUMP_TMC",
+ "STEPPER",
+ self.name,
+ self.cmd_DUMP_TMC,
+ desc=self.cmd_DUMP_TMC_help,
+ )
+
cmd_DUMP_TMC_help = "Read and display TMC stepper driver registers"
+
def cmd_DUMP_TMC(self, gcmd):
logging.info("DUMP_TMC %s", self.name)
- reg_name = gcmd.get('REGISTER', None)
+ reg_name = gcmd.get("REGISTER", None)
if reg_name is not None:
reg_name = reg_name.upper()
val = self.fields.registers.get(reg_name)
@@ -553,21 +625,22 @@ class TMCCommandHelper:
# TMC virtual pins
######################################################################
+
# Helper class for "sensorless homing"
class TMCVirtualPinHelper:
def __init__(self, config, mcu_tmc):
self.printer = config.get_printer()
self.mcu_tmc = mcu_tmc
self.fields = mcu_tmc.get_fields()
- if self.fields.lookup_register('diag0_stall') is not None:
- if config.get('diag0_pin', None) is not None:
- self.diag_pin = config.get('diag0_pin')
- self.diag_pin_field = 'diag0_stall'
+ if self.fields.lookup_register("diag0_stall") is not None:
+ if config.get("diag0_pin", None) is not None:
+ self.diag_pin = config.get("diag0_pin")
+ self.diag_pin_field = "diag0_stall"
else:
- self.diag_pin = config.get('diag1_pin', None)
- self.diag_pin_field = 'diag1_stall'
+ self.diag_pin = config.get("diag1_pin", None)
+ self.diag_pin_field = "diag1_stall"
else:
- self.diag_pin = config.get('diag_pin', None)
+ self.diag_pin = config.get("diag_pin", None)
self.diag_pin_field = None
self.mcu_endstop = None
self.en_pwm = False
@@ -576,22 +649,26 @@ class TMCVirtualPinHelper:
name_parts = config.get_name().split()
ppins = self.printer.lookup_object("pins")
ppins.register_chip("%s_%s" % (name_parts[0], name_parts[-1]), self)
+
def setup_pin(self, pin_type, pin_params):
# Validate pin
- ppins = self.printer.lookup_object('pins')
- if pin_type != 'endstop' or pin_params['pin'] != 'virtual_endstop':
+ ppins = self.printer.lookup_object("pins")
+ if pin_type != "endstop" or pin_params["pin"] != "virtual_endstop":
raise ppins.error("tmc virtual endstop only useful as endstop")
- if pin_params['invert'] or pin_params['pullup']:
+ if pin_params["invert"] or pin_params["pullup"]:
raise ppins.error("Can not pullup/invert tmc virtual pin")
if self.diag_pin is None:
raise ppins.error("tmc virtual endstop requires diag pin config")
# Setup for sensorless homing
- self.printer.register_event_handler("homing:homing_move_begin",
- self.handle_homing_move_begin)
- self.printer.register_event_handler("homing:homing_move_end",
- self.handle_homing_move_end)
- self.mcu_endstop = ppins.setup_pin('endstop', self.diag_pin)
+ self.printer.register_event_handler(
+ "homing:homing_move_begin", self.handle_homing_move_begin
+ )
+ self.printer.register_event_handler(
+ "homing:homing_move_end", self.handle_homing_move_end
+ )
+ self.mcu_endstop = ppins.setup_pin("endstop", self.diag_pin)
return self.mcu_endstop
+
def handle_homing_move_begin(self, hmove):
if self.mcu_endstop not in hmove.get_mcu_endstops():
return
@@ -613,7 +690,7 @@ class TMCVirtualPinHelper:
# Enable tcoolthrs (if not already)
self.coolthrs = self.fields.get_field("tcoolthrs")
if self.coolthrs == 0:
- tc_val = self.fields.set_field("tcoolthrs", 0xfffff)
+ tc_val = self.fields.set_field("tcoolthrs", 0xFFFFF)
self.mcu_tmc.set_register("TCOOLTHRS", tc_val)
# Disable thigh
reg = self.fields.lookup_register("thigh", None)
@@ -621,6 +698,7 @@ class TMCVirtualPinHelper:
self.thigh = self.fields.get_field("thigh")
th_val = self.fields.set_field("thigh", 0)
self.mcu_tmc.set_register(reg, th_val)
+
def handle_homing_move_end(self, hmove):
if self.mcu_endstop not in hmove.get_mcu_endstops():
return
@@ -648,6 +726,7 @@ class TMCVirtualPinHelper:
# Config reading helpers
######################################################################
+
# Helper to initialize the wave table from config or defaults
def TMCWaveTableHelper(config, mcu_tmc):
set_config_field = mcu_tmc.get_fields().set_config_field
@@ -669,6 +748,7 @@ def TMCWaveTableHelper(config, mcu_tmc):
set_config_field(config, "start_sin", 0)
set_config_field(config, "start_sin90", 247)
+
# Helper to configure the microstep settings
def TMCMicrostepHelper(config, mcu_tmc):
fields = mcu_tmc.get_fields()
@@ -676,17 +756,19 @@ def TMCMicrostepHelper(config, mcu_tmc):
if not config.has_section(stepper_name):
raise config.error(
"Could not find config section '[%s]' required by tmc driver"
- % (stepper_name,))
+ % (stepper_name,)
+ )
sconfig = config.getsection(stepper_name)
steps = {256: 0, 128: 1, 64: 2, 32: 3, 16: 4, 8: 5, 4: 6, 2: 7, 1: 8}
- mres = sconfig.getchoice('microsteps', steps)
+ mres = sconfig.getchoice("microsteps", steps)
fields.set_field("mres", mres)
fields.set_field("intpol", config.getboolean("interpolate", True))
+
# Helper for calculating TSTEP based values from velocity
def TMCtstepHelper(mcu_tmc, velocity, pstepper=None, config=None):
- if velocity <= 0.:
- return 0xfffff
+ if velocity <= 0.0:
+ return 0xFFFFF
if pstepper is not None:
step_dist = pstepper.get_step_dist()
else:
@@ -697,15 +779,16 @@ def TMCtstepHelper(mcu_tmc, velocity, pstepper=None, config=None):
mres = mcu_tmc.get_fields().get_field("mres")
step_dist_256 = step_dist / (1 << mres)
tmc_freq = mcu_tmc.get_tmc_frequency()
- threshold = int(tmc_freq * step_dist_256 / velocity + .5)
- return max(0, min(0xfffff, threshold))
+ threshold = int(tmc_freq * step_dist_256 / velocity + 0.5)
+ return max(0, min(0xFFFFF, threshold))
+
# Helper to configure stealthChop-spreadCycle transition velocity
def TMCStealthchopHelper(config, mcu_tmc):
fields = mcu_tmc.get_fields()
en_pwm_mode = False
- velocity = config.getfloat('stealthchop_threshold', None, minval=0.)
- tpwmthrs = 0xfffff
+ velocity = config.getfloat("stealthchop_threshold", None, minval=0.0)
+ tpwmthrs = 0xFFFFF
if velocity is not None:
en_pwm_mode = True
@@ -719,20 +802,22 @@ def TMCStealthchopHelper(config, mcu_tmc):
# TMC2208 uses en_spreadCycle
fields.set_field("en_spreadcycle", not en_pwm_mode)
+
# Helper to configure StallGuard and CoolStep minimum velocity
def TMCVcoolthrsHelper(config, mcu_tmc):
fields = mcu_tmc.get_fields()
- velocity = config.getfloat('coolstep_threshold', None, minval=0.)
+ velocity = config.getfloat("coolstep_threshold", None, minval=0.0)
tcoolthrs = 0
if velocity is not None:
tcoolthrs = TMCtstepHelper(mcu_tmc, velocity, config=config)
fields.set_field("tcoolthrs", tcoolthrs)
+
# Helper to configure StallGuard and CoolStep maximum velocity and
# SpreadCycle-FullStepping (High velocity) mode threshold.
def TMCVhighHelper(config, mcu_tmc):
fields = mcu_tmc.get_fields()
- velocity = config.getfloat('high_velocity_threshold', None, minval=0.)
+ velocity = config.getfloat("high_velocity_threshold", None, minval=0.0)
thigh = 0
if velocity is not None:
thigh = TMCtstepHelper(mcu_tmc, velocity, config=config)
diff --git a/klippy/extras/tmc2130.py b/klippy/extras/tmc2130.py
index 181fe07a..88c7fcf4 100644
--- a/klippy/extras/tmc2130.py
+++ b/klippy/extras/tmc2130.py
@@ -6,109 +6,183 @@
import math, logging
from . import bus, tmc
-TMC_FREQUENCY=13200000.
+TMC_FREQUENCY = 13200000.0
Registers = {
- "GCONF": 0x00, "GSTAT": 0x01, "IOIN": 0x04, "IHOLD_IRUN": 0x10,
- "TPOWERDOWN": 0x11, "TSTEP": 0x12, "TPWMTHRS": 0x13, "TCOOLTHRS": 0x14,
- "THIGH": 0x15, "XDIRECT": 0x2d, "MSLUT0": 0x60, "MSLUT1": 0x61,
- "MSLUT2": 0x62, "MSLUT3": 0x63, "MSLUT4": 0x64, "MSLUT5": 0x65,
- "MSLUT6": 0x66, "MSLUT7": 0x67, "MSLUTSEL": 0x68, "MSLUTSTART": 0x69,
- "MSCNT": 0x6a, "MSCURACT": 0x6b, "CHOPCONF": 0x6c, "COOLCONF": 0x6d,
- "DCCTRL": 0x6e, "DRV_STATUS": 0x6f, "PWMCONF": 0x70, "PWM_SCALE": 0x71,
- "ENCM_CTRL": 0x72, "LOST_STEPS": 0x73,
+ "GCONF": 0x00,
+ "GSTAT": 0x01,
+ "IOIN": 0x04,
+ "IHOLD_IRUN": 0x10,
+ "TPOWERDOWN": 0x11,
+ "TSTEP": 0x12,
+ "TPWMTHRS": 0x13,
+ "TCOOLTHRS": 0x14,
+ "THIGH": 0x15,
+ "XDIRECT": 0x2D,
+ "MSLUT0": 0x60,
+ "MSLUT1": 0x61,
+ "MSLUT2": 0x62,
+ "MSLUT3": 0x63,
+ "MSLUT4": 0x64,
+ "MSLUT5": 0x65,
+ "MSLUT6": 0x66,
+ "MSLUT7": 0x67,
+ "MSLUTSEL": 0x68,
+ "MSLUTSTART": 0x69,
+ "MSCNT": 0x6A,
+ "MSCURACT": 0x6B,
+ "CHOPCONF": 0x6C,
+ "COOLCONF": 0x6D,
+ "DCCTRL": 0x6E,
+ "DRV_STATUS": 0x6F,
+ "PWMCONF": 0x70,
+ "PWM_SCALE": 0x71,
+ "ENCM_CTRL": 0x72,
+ "LOST_STEPS": 0x73,
}
ReadRegisters = [
- "GCONF", "GSTAT", "IOIN", "TSTEP", "XDIRECT", "MSCNT", "MSCURACT",
- "CHOPCONF", "DRV_STATUS", "PWM_SCALE", "LOST_STEPS",
+ "GCONF",
+ "GSTAT",
+ "IOIN",
+ "TSTEP",
+ "XDIRECT",
+ "MSCNT",
+ "MSCURACT",
+ "CHOPCONF",
+ "DRV_STATUS",
+ "PWM_SCALE",
+ "LOST_STEPS",
]
Fields = {}
Fields["GCONF"] = {
- "i_scale_analog": 1<<0, "internal_rsense": 1<<1, "en_pwm_mode": 1<<2,
- "enc_commutation": 1<<3, "shaft": 1<<4, "diag0_error": 1<<5,
- "diag0_otpw": 1<<6, "diag0_stall": 1<<7, "diag1_stall": 1<<8,
- "diag1_index": 1<<9, "diag1_onstate": 1<<10, "diag1_steps_skipped": 1<<11,
- "diag0_int_pushpull": 1<<12, "diag1_pushpull": 1<<13,
- "small_hysteresis": 1<<14, "stop_enable": 1<<15, "direct_mode": 1<<16,
- "test_mode": 1<<17
+ "i_scale_analog": 1 << 0,
+ "internal_rsense": 1 << 1,
+ "en_pwm_mode": 1 << 2,
+ "enc_commutation": 1 << 3,
+ "shaft": 1 << 4,
+ "diag0_error": 1 << 5,
+ "diag0_otpw": 1 << 6,
+ "diag0_stall": 1 << 7,
+ "diag1_stall": 1 << 8,
+ "diag1_index": 1 << 9,
+ "diag1_onstate": 1 << 10,
+ "diag1_steps_skipped": 1 << 11,
+ "diag0_int_pushpull": 1 << 12,
+ "diag1_pushpull": 1 << 13,
+ "small_hysteresis": 1 << 14,
+ "stop_enable": 1 << 15,
+ "direct_mode": 1 << 16,
+ "test_mode": 1 << 17,
}
-Fields["GSTAT"] = { "reset": 1<<0, "drv_err": 1<<1, "uv_cp": 1<<2 }
+Fields["GSTAT"] = {"reset": 1 << 0, "drv_err": 1 << 1, "uv_cp": 1 << 2}
Fields["IOIN"] = {
- "step": 1<<0, "dir": 1<<1, "dcen_cfg4": 1<<2, "dcin_cfg5": 1<<3,
- "drv_enn_cfg6": 1<<4, "dco": 1<<5, "version": 0xff << 24
+ "step": 1 << 0,
+ "dir": 1 << 1,
+ "dcen_cfg4": 1 << 2,
+ "dcin_cfg5": 1 << 3,
+ "drv_enn_cfg6": 1 << 4,
+ "dco": 1 << 5,
+ "version": 0xFF << 24,
}
-Fields["IHOLD_IRUN"] = {
- "ihold": 0x1f << 0, "irun": 0x1f << 8, "iholddelay": 0x0f << 16
-}
-Fields["TPOWERDOWN"] = { "tpowerdown": 0xff }
-Fields["TSTEP"] = { "tstep": 0xfffff }
-Fields["TPWMTHRS"] = { "tpwmthrs": 0xfffff }
-Fields["TCOOLTHRS"] = { "tcoolthrs": 0xfffff }
-Fields["THIGH"] = { "thigh": 0xfffff }
-Fields["MSLUT0"] = { "mslut0": 0xffffffff }
-Fields["MSLUT1"] = { "mslut1": 0xffffffff }
-Fields["MSLUT2"] = { "mslut2": 0xffffffff }
-Fields["MSLUT3"] = { "mslut3": 0xffffffff }
-Fields["MSLUT4"] = { "mslut4": 0xffffffff }
-Fields["MSLUT5"] = { "mslut5": 0xffffffff }
-Fields["MSLUT6"] = { "mslut6": 0xffffffff }
-Fields["MSLUT7"] = { "mslut7": 0xffffffff }
+Fields["IHOLD_IRUN"] = {"ihold": 0x1F << 0, "irun": 0x1F << 8, "iholddelay": 0x0F << 16}
+Fields["TPOWERDOWN"] = {"tpowerdown": 0xFF}
+Fields["TSTEP"] = {"tstep": 0xFFFFF}
+Fields["TPWMTHRS"] = {"tpwmthrs": 0xFFFFF}
+Fields["TCOOLTHRS"] = {"tcoolthrs": 0xFFFFF}
+Fields["THIGH"] = {"thigh": 0xFFFFF}
+Fields["MSLUT0"] = {"mslut0": 0xFFFFFFFF}
+Fields["MSLUT1"] = {"mslut1": 0xFFFFFFFF}
+Fields["MSLUT2"] = {"mslut2": 0xFFFFFFFF}
+Fields["MSLUT3"] = {"mslut3": 0xFFFFFFFF}
+Fields["MSLUT4"] = {"mslut4": 0xFFFFFFFF}
+Fields["MSLUT5"] = {"mslut5": 0xFFFFFFFF}
+Fields["MSLUT6"] = {"mslut6": 0xFFFFFFFF}
+Fields["MSLUT7"] = {"mslut7": 0xFFFFFFFF}
Fields["MSLUTSEL"] = {
- "x3": 0xFF << 24,
- "x2": 0xFF << 16,
- "x1": 0xFF << 8,
- "w3": 0x03 << 6,
- "w2": 0x03 << 4,
- "w1": 0x03 << 2,
- "w0": 0x03 << 0,
+ "x3": 0xFF << 24,
+ "x2": 0xFF << 16,
+ "x1": 0xFF << 8,
+ "w3": 0x03 << 6,
+ "w2": 0x03 << 4,
+ "w1": 0x03 << 2,
+ "w0": 0x03 << 0,
}
Fields["MSLUTSTART"] = {
- "start_sin": 0xFF << 0,
- "start_sin90": 0xFF << 16,
+ "start_sin": 0xFF << 0,
+ "start_sin90": 0xFF << 16,
}
-Fields["MSCNT"] = { "mscnt": 0x3ff }
-Fields["MSCURACT"] = { "cur_a": 0x1ff, "cur_b": 0x1ff << 16 }
+Fields["MSCNT"] = {"mscnt": 0x3FF}
+Fields["MSCURACT"] = {"cur_a": 0x1FF, "cur_b": 0x1FF << 16}
Fields["CHOPCONF"] = {
- "toff": 0x0f, "hstrt": 0x07 << 4, "hend": 0x0f << 7, "fd3": 1<<11,
- "disfdcc": 1<<12, "rndtf": 1<<13, "chm": 1<<14, "tbl": 0x03 << 15,
- "vsense": 1<<17, "vhighfs": 1<<18, "vhighchm": 1<<19, "sync": 0x0f << 20,
- "mres": 0x0f << 24, "intpol": 1<<28, "dedge": 1<<29, "diss2g": 1<<30
+ "toff": 0x0F,
+ "hstrt": 0x07 << 4,
+ "hend": 0x0F << 7,
+ "fd3": 1 << 11,
+ "disfdcc": 1 << 12,
+ "rndtf": 1 << 13,
+ "chm": 1 << 14,
+ "tbl": 0x03 << 15,
+ "vsense": 1 << 17,
+ "vhighfs": 1 << 18,
+ "vhighchm": 1 << 19,
+ "sync": 0x0F << 20,
+ "mres": 0x0F << 24,
+ "intpol": 1 << 28,
+ "dedge": 1 << 29,
+ "diss2g": 1 << 30,
}
Fields["COOLCONF"] = {
- "semin": 0x0f, "seup": 0x03 << 5, "semax": 0x0f << 8, "sedn": 0x03 << 13,
- "seimin": 1<<15, "sgt": 0x7f << 16, "sfilt": 1<<24
+ "semin": 0x0F,
+ "seup": 0x03 << 5,
+ "semax": 0x0F << 8,
+ "sedn": 0x03 << 13,
+ "seimin": 1 << 15,
+ "sgt": 0x7F << 16,
+ "sfilt": 1 << 24,
}
Fields["DRV_STATUS"] = {
- "sg_result": 0x3ff, "fsactive": 1<<15, "cs_actual": 0x1f << 16,
- "stallguard": 1<<24, "ot": 1<<25, "otpw": 1<<26, "s2ga": 1<<27,
- "s2gb": 1<<28, "ola": 1<<29, "olb": 1<<30, "stst": 1<<31
+ "sg_result": 0x3FF,
+ "fsactive": 1 << 15,
+ "cs_actual": 0x1F << 16,
+ "stallguard": 1 << 24,
+ "ot": 1 << 25,
+ "otpw": 1 << 26,
+ "s2ga": 1 << 27,
+ "s2gb": 1 << 28,
+ "ola": 1 << 29,
+ "olb": 1 << 30,
+ "stst": 1 << 31,
}
Fields["PWMCONF"] = {
- "pwm_ampl": 0xff, "pwm_grad": 0xff << 8, "pwm_freq": 0x03 << 16,
- "pwm_autoscale": 1<<18, "pwm_symmetric": 1<<19, "freewheel": 0x03 << 20
+ "pwm_ampl": 0xFF,
+ "pwm_grad": 0xFF << 8,
+ "pwm_freq": 0x03 << 16,
+ "pwm_autoscale": 1 << 18,
+ "pwm_symmetric": 1 << 19,
+ "freewheel": 0x03 << 20,
}
-Fields["PWM_SCALE"] = { "pwm_scale": 0xff }
-Fields["LOST_STEPS"] = { "lost_steps": 0xfffff }
+Fields["PWM_SCALE"] = {"pwm_scale": 0xFF}
+Fields["LOST_STEPS"] = {"lost_steps": 0xFFFFF}
SignedFields = ["cur_a", "cur_b", "sgt"]
FieldFormatters = {
- "i_scale_analog": (lambda v: "1(ExtVREF)" if v else ""),
- "shaft": (lambda v: "1(Reverse)" if v else ""),
- "reset": (lambda v: "1(Reset)" if v else ""),
- "drv_err": (lambda v: "1(ErrorShutdown!)" if v else ""),
- "uv_cp": (lambda v: "1(Undervoltage!)" if v else ""),
- "version": (lambda v: "%#x" % v),
- "mres": (lambda v: "%d(%dusteps)" % (v, 0x100 >> v)),
- "otpw": (lambda v: "1(OvertempWarning!)" if v else ""),
- "ot": (lambda v: "1(OvertempError!)" if v else ""),
- "s2ga": (lambda v: "1(ShortToGND_A!)" if v else ""),
- "s2gb": (lambda v: "1(ShortToGND_B!)" if v else ""),
- "ola": (lambda v: "1(OpenLoad_A!)" if v else ""),
- "olb": (lambda v: "1(OpenLoad_B!)" if v else ""),
- "cs_actual": (lambda v: ("%d" % v) if v else "0(Reset?)"),
+ "i_scale_analog": (lambda v: "1(ExtVREF)" if v else ""),
+ "shaft": (lambda v: "1(Reverse)" if v else ""),
+ "reset": (lambda v: "1(Reset)" if v else ""),
+ "drv_err": (lambda v: "1(ErrorShutdown!)" if v else ""),
+ "uv_cp": (lambda v: "1(Undervoltage!)" if v else ""),
+ "version": (lambda v: "%#x" % v),
+ "mres": (lambda v: "%d(%dusteps)" % (v, 0x100 >> v)),
+ "otpw": (lambda v: "1(OvertempWarning!)" if v else ""),
+ "ot": (lambda v: "1(OvertempError!)" if v else ""),
+ "s2ga": (lambda v: "1(ShortToGND_A!)" if v else ""),
+ "s2gb": (lambda v: "1(ShortToGND_B!)" if v else ""),
+ "ola": (lambda v: "1(OpenLoad_A!)" if v else ""),
+ "olb": (lambda v: "1(OpenLoad_B!)" if v else ""),
+ "cs_actual": (lambda v: ("%d" % v) if v else "0(Reset?)"),
}
@@ -118,35 +192,39 @@ FieldFormatters = {
MAX_CURRENT = 2.000
+
class TMCCurrentHelper:
def __init__(self, config, mcu_tmc):
self.printer = config.get_printer()
self.name = config.get_name().split()[-1]
self.mcu_tmc = mcu_tmc
self.fields = mcu_tmc.get_fields()
- run_current = config.getfloat('run_current',
- above=0., maxval=MAX_CURRENT)
- hold_current = config.getfloat('hold_current', MAX_CURRENT,
- above=0., maxval=MAX_CURRENT)
+ run_current = config.getfloat("run_current", above=0.0, maxval=MAX_CURRENT)
+ hold_current = config.getfloat(
+ "hold_current", MAX_CURRENT, above=0.0, maxval=MAX_CURRENT
+ )
self.req_hold_current = hold_current
- self.sense_resistor = config.getfloat('sense_resistor', 0.110, above=0.)
+ self.sense_resistor = config.getfloat("sense_resistor", 0.110, above=0.0)
vsense, irun, ihold = self._calc_current(run_current, hold_current)
self.fields.set_field("vsense", vsense)
self.fields.set_field("ihold", ihold)
self.fields.set_field("irun", irun)
+
def _calc_current_bits(self, current, vsense):
sense_resistor = self.sense_resistor + 0.020
vref = 0.32
if vsense:
vref = 0.18
- cs = int(32. * sense_resistor * current * math.sqrt(2.) / vref + .5) - 1
+ cs = int(32.0 * sense_resistor * current * math.sqrt(2.0) / vref + 0.5) - 1
return max(0, min(31, cs))
+
def _calc_current_from_bits(self, cs, vsense):
sense_resistor = self.sense_resistor + 0.020
vref = 0.32
if vsense:
vref = 0.18
- return (cs + 1) * vref / (32. * sense_resistor * math.sqrt(2.))
+ return (cs + 1) * vref / (32.0 * sense_resistor * math.sqrt(2.0))
+
def _calc_current(self, run_current, hold_current):
vsense = True
irun = self._calc_current_bits(run_current, True)
@@ -160,6 +238,7 @@ class TMCCurrentHelper:
irun = irun2
ihold = self._calc_current_bits(min(hold_current, run_current), vsense)
return vsense, irun, ihold
+
def get_current(self):
irun = self.fields.get_field("irun")
ihold = self.fields.get_field("ihold")
@@ -167,6 +246,7 @@ class TMCCurrentHelper:
run_current = self._calc_current_from_bits(irun, vsense)
hold_current = self._calc_current_from_bits(ihold, vsense)
return run_current, hold_current, self.req_hold_current, MAX_CURRENT
+
def set_current(self, run_current, hold_current, print_time):
self.req_hold_current = hold_current
vsense, irun, ihold = self._calc_current(run_current, hold_current)
@@ -182,6 +262,7 @@ class TMCCurrentHelper:
# TMC2130 SPI
######################################################################
+
class MCU_TMC_SPI_chain:
def __init__(self, config, chain_len=1):
self.printer = config.get_printer()
@@ -190,25 +271,30 @@ class MCU_TMC_SPI_chain:
share = None
if chain_len > 1:
share = "tmc_spi_cs"
- self.spi = bus.MCU_SPI_from_config(config, 3, default_speed=4000000,
- share_type=share)
+ self.spi = bus.MCU_SPI_from_config(
+ config, 3, default_speed=4000000, share_type=share
+ )
self.taken_chain_positions = []
+
def _build_cmd(self, data, chain_pos):
- return ([0x00] * ((self.chain_len - chain_pos) * 5) +
- data + [0x00] * ((chain_pos - 1) * 5))
+ return (
+ [0x00] * ((self.chain_len - chain_pos) * 5)
+ + data
+ + [0x00] * ((chain_pos - 1) * 5)
+ )
+
def reg_read(self, reg, chain_pos):
cmd = self._build_cmd([reg, 0x00, 0x00, 0x00, 0x00], chain_pos)
self.spi.spi_send(cmd)
- if self.printer.get_start_args().get('debugoutput') is not None:
+ if self.printer.get_start_args().get("debugoutput") is not None:
return {
"spi_status": 0,
"data": 0,
- "#receive_time": .0,
+ "#receive_time": 0.0,
}
params = self.spi.spi_transfer(cmd)
- pr = bytearray(params['response'])
- pr = pr[(self.chain_len - chain_pos) * 5 :
- (self.chain_len - chain_pos + 1) * 5]
+ pr = bytearray(params["response"])
+ pr = pr[(self.chain_len - chain_pos) * 5 : (self.chain_len - chain_pos + 1) * 5]
return {
"spi_status": pr[0],
"data": (pr[1] << 24) | (pr[2] << 16) | (pr[3] << 8) | pr[4],
@@ -219,44 +305,51 @@ class MCU_TMC_SPI_chain:
minclock = 0
if print_time is not None:
minclock = self.spi.get_mcu().print_time_to_clock(print_time)
- data = [(reg | 0x80) & 0xff, (val >> 24) & 0xff, (val >> 16) & 0xff,
- (val >> 8) & 0xff, val & 0xff]
- if self.printer.get_start_args().get('debugoutput') is not None:
+ data = [
+ (reg | 0x80) & 0xFF,
+ (val >> 24) & 0xFF,
+ (val >> 16) & 0xFF,
+ (val >> 8) & 0xFF,
+ val & 0xFF,
+ ]
+ if self.printer.get_start_args().get("debugoutput") is not None:
self.spi.spi_send(self._build_cmd(data, chain_pos), minclock)
return val
write_cmd = self._build_cmd(data, chain_pos)
dummy_read = self._build_cmd([0x00, 0x00, 0x00, 0x00, 0x00], chain_pos)
- params = self.spi.spi_transfer_with_preface(write_cmd, dummy_read,
- minclock=minclock)
- pr = bytearray(params['response'])
- pr = pr[(self.chain_len - chain_pos) * 5 :
- (self.chain_len - chain_pos + 1) * 5]
+ params = self.spi.spi_transfer_with_preface(
+ write_cmd, dummy_read, minclock=minclock
+ )
+ pr = bytearray(params["response"])
+ pr = pr[(self.chain_len - chain_pos) * 5 : (self.chain_len - chain_pos + 1) * 5]
return (pr[1] << 24) | (pr[2] << 16) | (pr[3] << 8) | pr[4]
+
def get_mcu(self):
return self.spi.get_mcu()
+
# Helper to setup an spi daisy chain bus from settings in a config section
def lookup_tmc_spi_chain(config):
- chain_len = config.getint('chain_length', None, minval=2)
+ chain_len = config.getint("chain_length", None, minval=2)
if chain_len is None:
# Simple, non daisy chained SPI connection
return MCU_TMC_SPI_chain(config, 1), 1
# Shared SPI bus - lookup existing MCU_TMC_SPI_chain
ppins = config.get_printer().lookup_object("pins")
- cs_pin_params = ppins.lookup_pin(config.get('cs_pin'),
- share_type="tmc_spi_cs")
- tmc_spi = cs_pin_params.get('class')
+ cs_pin_params = ppins.lookup_pin(config.get("cs_pin"), share_type="tmc_spi_cs")
+ tmc_spi = cs_pin_params.get("class")
if tmc_spi is None:
- tmc_spi = cs_pin_params['class'] = MCU_TMC_SPI_chain(config, chain_len)
+ tmc_spi = cs_pin_params["class"] = MCU_TMC_SPI_chain(config, chain_len)
if chain_len != tmc_spi.chain_len:
raise config.error("TMC SPI chain must have same length")
- chain_pos = config.getint('chain_position', minval=1, maxval=chain_len)
+ chain_pos = config.getint("chain_position", minval=1, maxval=chain_len)
if chain_pos in tmc_spi.taken_chain_positions:
raise config.error("TMC SPI chain can not have duplicate position")
tmc_spi.taken_chain_positions.append(chain_pos)
return tmc_spi, chain_pos
+
# Helper code for working with TMC devices via SPI
class MCU_TMC_SPI:
def __init__(self, config, name_to_reg, fields, tmc_frequency):
@@ -267,22 +360,27 @@ class MCU_TMC_SPI:
self.name_to_reg = name_to_reg
self.fields = fields
self.tmc_frequency = tmc_frequency
+
def get_fields(self):
return self.fields
+
def get_register_raw(self, reg_name):
reg = self.name_to_reg[reg_name]
with self.mutex:
resp = self.tmc_spi.reg_read(reg, self.chain_pos)
return resp
+
def decode_spi_status(spi_status):
return {
"standstill": spi_status >> 3 & 0x1,
"sg2": spi_status >> 2 & 0x1,
"driver_error": spi_status >> 1 & 0x1,
- "reset_flag": spi_status & 0x1
+ "reset_flag": spi_status & 0x1,
}
+
def get_register(self, reg_name):
return self.get_register_raw(reg_name)["data"]
+
def set_register(self, reg_name, val, print_time=None):
reg = self.name_to_reg[reg_name]
with self.mutex:
@@ -291,9 +389,12 @@ class MCU_TMC_SPI:
if v == val:
return
raise self.printer.command_error(
- "Unable to write tmc spi '%s' register %s" % (self.name, reg_name))
+ "Unable to write tmc spi '%s' register %s" % (self.name, reg_name)
+ )
+
def get_tmc_frequency(self):
return self.tmc_frequency
+
def get_mcu(self):
return self.tmc_spi.get_mcu()
@@ -302,12 +403,12 @@ class MCU_TMC_SPI:
# TMC2130 printer object
######################################################################
+
class TMC2130:
def __init__(self, config):
# Setup mcu communication
self.fields = tmc.FieldHelper(Fields, SignedFields, FieldFormatters)
- self.mcu_tmc = MCU_TMC_SPI(config, Registers, self.fields,
- TMC_FREQUENCY)
+ self.mcu_tmc = MCU_TMC_SPI(config, Registers, self.fields, TMC_FREQUENCY)
# Allow virtual pins to be created
tmc.TMCVirtualPinHelper(config, self.mcu_tmc)
# Register commands
@@ -349,5 +450,6 @@ class TMC2130:
# TPOWERDOWN
set_config_field(config, "tpowerdown", 0)
+
def load_config_prefix(config):
return TMC2130(config)
diff --git a/klippy/extras/tmc2208.py b/klippy/extras/tmc2208.py
index 39bd8737..fcf057ce 100644
--- a/klippy/extras/tmc2208.py
+++ b/klippy/extras/tmc2208.py
@@ -6,188 +6,180 @@
import logging
from . import tmc, tmc_uart, tmc2130
-TMC_FREQUENCY=12000000.
+TMC_FREQUENCY = 12000000.0
Registers = {
- "GCONF": 0x00, "GSTAT": 0x01, "IFCNT": 0x02, "SLAVECONF": 0x03,
- "OTP_PROG": 0x04, "OTP_READ": 0x05, "IOIN": 0x06, "FACTORY_CONF": 0x07,
- "IHOLD_IRUN": 0x10, "TPOWERDOWN": 0x11, "TSTEP": 0x12, "TPWMTHRS": 0x13,
- "VACTUAL": 0x22, "MSCNT": 0x6a, "MSCURACT": 0x6b, "CHOPCONF": 0x6c,
- "DRV_STATUS": 0x6f, "PWMCONF": 0x70, "PWM_SCALE": 0x71, "PWM_AUTO": 0x72
+ "GCONF": 0x00,
+ "GSTAT": 0x01,
+ "IFCNT": 0x02,
+ "SLAVECONF": 0x03,
+ "OTP_PROG": 0x04,
+ "OTP_READ": 0x05,
+ "IOIN": 0x06,
+ "FACTORY_CONF": 0x07,
+ "IHOLD_IRUN": 0x10,
+ "TPOWERDOWN": 0x11,
+ "TSTEP": 0x12,
+ "TPWMTHRS": 0x13,
+ "VACTUAL": 0x22,
+ "MSCNT": 0x6A,
+ "MSCURACT": 0x6B,
+ "CHOPCONF": 0x6C,
+ "DRV_STATUS": 0x6F,
+ "PWMCONF": 0x70,
+ "PWM_SCALE": 0x71,
+ "PWM_AUTO": 0x72,
}
ReadRegisters = [
- "GCONF", "GSTAT", "IFCNT", "OTP_READ", "IOIN", "FACTORY_CONF", "TSTEP",
- "MSCNT", "MSCURACT", "CHOPCONF", "DRV_STATUS",
- "PWMCONF", "PWM_SCALE", "PWM_AUTO"
+ "GCONF",
+ "GSTAT",
+ "IFCNT",
+ "OTP_READ",
+ "IOIN",
+ "FACTORY_CONF",
+ "TSTEP",
+ "MSCNT",
+ "MSCURACT",
+ "CHOPCONF",
+ "DRV_STATUS",
+ "PWMCONF",
+ "PWM_SCALE",
+ "PWM_AUTO",
]
Fields = {}
Fields["GCONF"] = {
- "i_scale_analog": 0x01,
- "internal_rsense": 0x01 << 1,
- "en_spreadcycle": 0x01 << 2,
- "shaft": 0x01 << 3,
- "index_otpw": 0x01 << 4,
- "index_step": 0x01 << 5,
- "pdn_disable": 0x01 << 6,
- "mstep_reg_select": 0x01 << 7,
- "multistep_filt": 0x01 << 8,
- "test_mode": 0x01 << 9
-}
-Fields["GSTAT"] = {
- "reset": 0x01,
- "drv_err": 0x01 << 1,
- "uv_cp": 0x01 << 2
-}
-Fields["IFCNT"] = {
- "ifcnt": 0xff
-}
-Fields["SLAVECONF"] = {
- "senddelay": 0x0f << 8
-}
-Fields["OTP_PROG"] = {
- "otpbit": 0x07,
- "otpbyte": 0x03 << 4,
- "otpmagic": 0xff << 8
+ "i_scale_analog": 0x01,
+ "internal_rsense": 0x01 << 1,
+ "en_spreadcycle": 0x01 << 2,
+ "shaft": 0x01 << 3,
+ "index_otpw": 0x01 << 4,
+ "index_step": 0x01 << 5,
+ "pdn_disable": 0x01 << 6,
+ "mstep_reg_select": 0x01 << 7,
+ "multistep_filt": 0x01 << 8,
+ "test_mode": 0x01 << 9,
}
+Fields["GSTAT"] = {"reset": 0x01, "drv_err": 0x01 << 1, "uv_cp": 0x01 << 2}
+Fields["IFCNT"] = {"ifcnt": 0xFF}
+Fields["SLAVECONF"] = {"senddelay": 0x0F << 8}
+Fields["OTP_PROG"] = {"otpbit": 0x07, "otpbyte": 0x03 << 4, "otpmagic": 0xFF << 8}
Fields["OTP_READ"] = {
- "otp_fclktrim": 0x1f,
- "otp_ottrim": 0x01 << 5,
- "otp_internalrsense": 0x01 << 6,
- "otp_tbl": 0x01 << 7,
- "otp_pwm_grad": 0x0f << 8,
- "otp_pwm_autograd": 0x01 << 12,
- "otp_tpwmthrs": 0x07 << 13,
- "otp_pwm_ofs": 0x01 << 16,
- "otp_pwm_reg": 0x01 << 17,
- "otp_pwm_freq": 0x01 << 18,
- "otp_iholddelay": 0x03 << 19,
- "otp_ihold": 0x03 << 21,
- "otp_en_spreadcycle": 0x01 << 23
+ "otp_fclktrim": 0x1F,
+ "otp_ottrim": 0x01 << 5,
+ "otp_internalrsense": 0x01 << 6,
+ "otp_tbl": 0x01 << 7,
+ "otp_pwm_grad": 0x0F << 8,
+ "otp_pwm_autograd": 0x01 << 12,
+ "otp_tpwmthrs": 0x07 << 13,
+ "otp_pwm_ofs": 0x01 << 16,
+ "otp_pwm_reg": 0x01 << 17,
+ "otp_pwm_freq": 0x01 << 18,
+ "otp_iholddelay": 0x03 << 19,
+ "otp_ihold": 0x03 << 21,
+ "otp_en_spreadcycle": 0x01 << 23,
}
# IOIN mapping depends on the driver type (SEL_A field)
# TMC222x (SEL_A == 0)
Fields["IOIN@TMC222x"] = {
- "pdn_uart": 0x01 << 1,
- "spread": 0x01 << 2,
- "dir": 0x01 << 3,
- "enn": 0x01 << 4,
- "step": 0x01 << 5,
- "ms1": 0x01 << 6,
- "ms2": 0x01 << 7,
- "sel_a": 0x01 << 8,
- "version": 0xff << 24
+ "pdn_uart": 0x01 << 1,
+ "spread": 0x01 << 2,
+ "dir": 0x01 << 3,
+ "enn": 0x01 << 4,
+ "step": 0x01 << 5,
+ "ms1": 0x01 << 6,
+ "ms2": 0x01 << 7,
+ "sel_a": 0x01 << 8,
+ "version": 0xFF << 24,
}
# TMC220x (SEL_A == 1)
Fields["IOIN@TMC220x"] = {
- "enn": 0x01,
- "ms1": 0x01 << 2,
- "ms2": 0x01 << 3,
- "diag": 0x01 << 4,
- "pdn_uart": 0x01 << 6,
- "step": 0x01 << 7,
- "sel_a": 0x01 << 8,
- "dir": 0x01 << 9,
- "version": 0xff << 24,
-}
-Fields["FACTORY_CONF"] = {
- "fclktrim": 0x1f,
- "ottrim": 0x03 << 8
-}
-Fields["IHOLD_IRUN"] = {
- "ihold": 0x1f,
- "irun": 0x1f << 8,
- "iholddelay": 0x0f << 16
-}
-Fields["TPOWERDOWN"] = {
- "tpowerdown": 0xff
-}
-Fields["TSTEP"] = {
- "tstep": 0xfffff
-}
-Fields["TPWMTHRS"] = {
- "tpwmthrs": 0xfffff
-}
-Fields["VACTUAL"] = {
- "vactual": 0xffffff
-}
-Fields["MSCNT"] = {
- "mscnt": 0x3ff
-}
-Fields["MSCURACT"] = {
- "cur_a": 0x1ff,
- "cur_b": 0x1ff << 16
+ "enn": 0x01,
+ "ms1": 0x01 << 2,
+ "ms2": 0x01 << 3,
+ "diag": 0x01 << 4,
+ "pdn_uart": 0x01 << 6,
+ "step": 0x01 << 7,
+ "sel_a": 0x01 << 8,
+ "dir": 0x01 << 9,
+ "version": 0xFF << 24,
}
+Fields["FACTORY_CONF"] = {"fclktrim": 0x1F, "ottrim": 0x03 << 8}
+Fields["IHOLD_IRUN"] = {"ihold": 0x1F, "irun": 0x1F << 8, "iholddelay": 0x0F << 16}
+Fields["TPOWERDOWN"] = {"tpowerdown": 0xFF}
+Fields["TSTEP"] = {"tstep": 0xFFFFF}
+Fields["TPWMTHRS"] = {"tpwmthrs": 0xFFFFF}
+Fields["VACTUAL"] = {"vactual": 0xFFFFFF}
+Fields["MSCNT"] = {"mscnt": 0x3FF}
+Fields["MSCURACT"] = {"cur_a": 0x1FF, "cur_b": 0x1FF << 16}
Fields["CHOPCONF"] = {
- "toff": 0x0f,
- "hstrt": 0x07 << 4,
- "hend": 0x0f << 7,
- "tbl": 0x03 << 15,
- "vsense": 0x01 << 17,
- "mres": 0x0f << 24,
- "intpol": 0x01 << 28,
- "dedge": 0x01 << 29,
- "diss2g": 0x01 << 30,
- "diss2vs": 0x01 << 31
+ "toff": 0x0F,
+ "hstrt": 0x07 << 4,
+ "hend": 0x0F << 7,
+ "tbl": 0x03 << 15,
+ "vsense": 0x01 << 17,
+ "mres": 0x0F << 24,
+ "intpol": 0x01 << 28,
+ "dedge": 0x01 << 29,
+ "diss2g": 0x01 << 30,
+ "diss2vs": 0x01 << 31,
}
Fields["DRV_STATUS"] = {
- "otpw": 0x01,
- "ot": 0x01 << 1,
- "s2ga": 0x01 << 2,
- "s2gb": 0x01 << 3,
- "s2vsa": 0x01 << 4,
- "s2vsb": 0x01 << 5,
- "ola": 0x01 << 6,
- "olb": 0x01 << 7,
- "t120": 0x01 << 8,
- "t143": 0x01 << 9,
- "t150": 0x01 << 10,
- "t157": 0x01 << 11,
- "cs_actual": 0x1f << 16,
- "stealth": 0x01 << 30,
- "stst": 0x01 << 31
+ "otpw": 0x01,
+ "ot": 0x01 << 1,
+ "s2ga": 0x01 << 2,
+ "s2gb": 0x01 << 3,
+ "s2vsa": 0x01 << 4,
+ "s2vsb": 0x01 << 5,
+ "ola": 0x01 << 6,
+ "olb": 0x01 << 7,
+ "t120": 0x01 << 8,
+ "t143": 0x01 << 9,
+ "t150": 0x01 << 10,
+ "t157": 0x01 << 11,
+ "cs_actual": 0x1F << 16,
+ "stealth": 0x01 << 30,
+ "stst": 0x01 << 31,
}
Fields["PWMCONF"] = {
- "pwm_ofs": 0xff,
- "pwm_grad": 0xff << 8,
- "pwm_freq": 0x03 << 16,
- "pwm_autoscale": 0x01 << 18,
- "pwm_autograd": 0x01 << 19,
- "freewheel": 0x03 << 20,
- "pwm_reg": 0xf << 24,
- "pwm_lim": 0xf << 28
-}
-Fields["PWM_SCALE"] = {
- "pwm_scale_sum": 0xff,
- "pwm_scale_auto": 0x1ff << 16
-}
-Fields["PWM_AUTO"] = {
- "pwm_ofs_auto": 0xff,
- "pwm_grad_auto": 0xff << 16
+ "pwm_ofs": 0xFF,
+ "pwm_grad": 0xFF << 8,
+ "pwm_freq": 0x03 << 16,
+ "pwm_autoscale": 0x01 << 18,
+ "pwm_autograd": 0x01 << 19,
+ "freewheel": 0x03 << 20,
+ "pwm_reg": 0xF << 24,
+ "pwm_lim": 0xF << 28,
}
+Fields["PWM_SCALE"] = {"pwm_scale_sum": 0xFF, "pwm_scale_auto": 0x1FF << 16}
+Fields["PWM_AUTO"] = {"pwm_ofs_auto": 0xFF, "pwm_grad_auto": 0xFF << 16}
SignedFields = ["cur_a", "cur_b", "pwm_scale_auto"]
FieldFormatters = dict(tmc2130.FieldFormatters)
-FieldFormatters.update({
- "sel_a": (lambda v: "%d(%s)" % (v, ["TMC222x", "TMC220x"][v])),
- "s2vsa": (lambda v: "1(ShortToSupply_A!)" if v else ""),
- "s2vsb": (lambda v: "1(ShortToSupply_B!)" if v else ""),
-})
+FieldFormatters.update(
+ {
+ "sel_a": (lambda v: "%d(%s)" % (v, ["TMC222x", "TMC220x"][v])),
+ "s2vsa": (lambda v: "1(ShortToSupply_A!)" if v else ""),
+ "s2vsb": (lambda v: "1(ShortToSupply_B!)" if v else ""),
+ }
+)
######################################################################
# TMC2208 printer object
######################################################################
+
class TMC2208:
def __init__(self, config):
# Setup mcu communication
self.fields = tmc.FieldHelper(Fields, SignedFields, FieldFormatters)
- self.mcu_tmc = tmc_uart.MCU_TMC_uart(config, Registers, self.fields, 0,
- TMC_FREQUENCY)
+ self.mcu_tmc = tmc_uart.MCU_TMC_uart(
+ config, Registers, self.fields, 0, TMC_FREQUENCY
+ )
self.fields.set_field("pdn_disable", True)
# Register commands
current_helper = tmc2130.TMCCurrentHelper(config, self.mcu_tmc)
@@ -220,11 +212,13 @@ class TMC2208:
set_config_field(config, "pwm_lim", 12)
# TPOWERDOWN
set_config_field(config, "tpowerdown", 20)
+
def read_translate(self, reg_name, val):
if reg_name == "IOIN":
drv_type = self.fields.get_field("sel_a", val)
reg_name = "IOIN@TMC220x" if drv_type else "IOIN@TMC222x"
return reg_name, val
+
def load_config_prefix(config):
return TMC2208(config)
diff --git a/klippy/extras/tmc2209.py b/klippy/extras/tmc2209.py
index 96b7424d..5fc3ae17 100644
--- a/klippy/extras/tmc2209.py
+++ b/klippy/extras/tmc2209.py
@@ -5,46 +5,37 @@
# This file may be distributed under the terms of the GNU GPLv3 license.
from . import tmc2208, tmc2130, tmc, tmc_uart
-TMC_FREQUENCY=12000000.
+TMC_FREQUENCY = 12000000.0
Registers = dict(tmc2208.Registers)
-Registers.update({
- "TCOOLTHRS": 0x14,
- "COOLCONF": 0x42,
- "SGTHRS": 0x40,
- "SG_RESULT": 0x41
-})
+Registers.update(
+ {"TCOOLTHRS": 0x14, "COOLCONF": 0x42, "SGTHRS": 0x40, "SG_RESULT": 0x41}
+)
ReadRegisters = tmc2208.ReadRegisters + ["SG_RESULT"]
Fields = dict(tmc2208.Fields)
Fields["COOLCONF"] = {
- "semin": 0x0F << 0,
- "seup": 0x03 << 5,
- "semax": 0x0F << 8,
- "sedn": 0x03 << 13,
- "seimin": 0x01 << 15
+ "semin": 0x0F << 0,
+ "seup": 0x03 << 5,
+ "semax": 0x0F << 8,
+ "sedn": 0x03 << 13,
+ "seimin": 0x01 << 15,
}
Fields["IOIN"] = {
- "enn": 0x01 << 0,
- "ms1": 0x01 << 2,
- "ms2": 0x01 << 3,
- "diag": 0x01 << 4,
- "pdn_uart": 0x01 << 6,
- "step": 0x01 << 7,
- "spread_en": 0x01 << 8,
- "dir": 0x01 << 9,
- "version": 0xff << 24
-}
-Fields["SGTHRS"] = {
- "sgthrs": 0xFF << 0
-}
-Fields["SG_RESULT"] = {
- "sg_result": 0x3FF << 0
-}
-Fields["TCOOLTHRS"] = {
- "tcoolthrs": 0xfffff
+ "enn": 0x01 << 0,
+ "ms1": 0x01 << 2,
+ "ms2": 0x01 << 3,
+ "diag": 0x01 << 4,
+ "pdn_uart": 0x01 << 6,
+ "step": 0x01 << 7,
+ "spread_en": 0x01 << 8,
+ "dir": 0x01 << 9,
+ "version": 0xFF << 24,
}
+Fields["SGTHRS"] = {"sgthrs": 0xFF << 0}
+Fields["SG_RESULT"] = {"sg_result": 0x3FF << 0}
+Fields["TCOOLTHRS"] = {"tcoolthrs": 0xFFFFF}
FieldFormatters = dict(tmc2208.FieldFormatters)
@@ -53,16 +44,17 @@ FieldFormatters = dict(tmc2208.FieldFormatters)
# TMC2209 printer object
######################################################################
+
class TMC2209:
def __init__(self, config):
# Setup mcu communication
- self.fields = tmc.FieldHelper(Fields, tmc2208.SignedFields,
- FieldFormatters)
- self.mcu_tmc = tmc_uart.MCU_TMC_uart(config, Registers, self.fields, 3,
- TMC_FREQUENCY)
+ self.fields = tmc.FieldHelper(Fields, tmc2208.SignedFields, FieldFormatters)
+ self.mcu_tmc = tmc_uart.MCU_TMC_uart(
+ config, Registers, self.fields, 3, TMC_FREQUENCY
+ )
# Setup fields for UART
self.fields.set_field("pdn_disable", True)
- self.fields.set_field("senddelay", 2) # Avoid tx errors on shared uart
+ self.fields.set_field("senddelay", 2) # Avoid tx errors on shared uart
# Allow virtual pins to be created
tmc.TMCVirtualPinHelper(config, self.mcu_tmc)
# Register commands
@@ -106,5 +98,6 @@ class TMC2209:
# SGTHRS
set_config_field(config, "sgthrs", 0)
+
def load_config_prefix(config):
return TMC2209(config)
diff --git a/klippy/extras/tmc2240.py b/klippy/extras/tmc2240.py
index 214896e7..b6d13935 100644
--- a/klippy/extras/tmc2240.py
+++ b/klippy/extras/tmc2240.py
@@ -7,279 +7,269 @@
import math, logging
from . import bus, tmc, tmc2130, tmc_uart
-TMC_FREQUENCY=12500000.
+TMC_FREQUENCY = 12500000.0
Registers = {
- "GCONF": 0x00,
- "GSTAT": 0x01,
- "IFCNT": 0x02,
- "NODECONF": 0x03,
- "IOIN": 0x04,
- "DRV_CONF": 0x0A,
- "GLOBALSCALER": 0x0B,
- "IHOLD_IRUN": 0x10,
- "TPOWERDOWN": 0x11,
- "TSTEP": 0x12,
- "TPWMTHRS": 0x13,
- "TCOOLTHRS": 0x14,
- "THIGH": 0x15,
- "DIRECT_MODE": 0x2D,
- "ENCMODE": 0x38,
- "X_ENC": 0x39,
- "ENC_CONST": 0x3A,
- "ENC_STATUS": 0x3B,
- "ENC_LATCH": 0x3C,
- "ADC_VSUPPLY_AIN": 0x50,
- "ADC_TEMP": 0x51,
- "OTW_OV_VTH": 0x52,
- "MSLUT0": 0x60,
- "MSLUT1": 0x61,
- "MSLUT2": 0x62,
- "MSLUT3": 0x63,
- "MSLUT4": 0x64,
- "MSLUT5": 0x65,
- "MSLUT6": 0x66,
- "MSLUT7": 0x67,
- "MSLUTSEL": 0x68,
- "MSLUTSTART": 0x69,
- "MSCNT": 0x6A,
- "MSCURACT": 0x6B,
- "CHOPCONF": 0x6C,
- "COOLCONF": 0x6D,
- "DRV_STATUS": 0x6F,
- "PWMCONF": 0x70,
- "PWM_SCALE": 0x71,
- "PWM_AUTO": 0x72,
- "SG4_THRS": 0x74,
- "SG4_RESULT": 0x75,
- "SG4_IND": 0x76,
+ "GCONF": 0x00,
+ "GSTAT": 0x01,
+ "IFCNT": 0x02,
+ "NODECONF": 0x03,
+ "IOIN": 0x04,
+ "DRV_CONF": 0x0A,
+ "GLOBALSCALER": 0x0B,
+ "IHOLD_IRUN": 0x10,
+ "TPOWERDOWN": 0x11,
+ "TSTEP": 0x12,
+ "TPWMTHRS": 0x13,
+ "TCOOLTHRS": 0x14,
+ "THIGH": 0x15,
+ "DIRECT_MODE": 0x2D,
+ "ENCMODE": 0x38,
+ "X_ENC": 0x39,
+ "ENC_CONST": 0x3A,
+ "ENC_STATUS": 0x3B,
+ "ENC_LATCH": 0x3C,
+ "ADC_VSUPPLY_AIN": 0x50,
+ "ADC_TEMP": 0x51,
+ "OTW_OV_VTH": 0x52,
+ "MSLUT0": 0x60,
+ "MSLUT1": 0x61,
+ "MSLUT2": 0x62,
+ "MSLUT3": 0x63,
+ "MSLUT4": 0x64,
+ "MSLUT5": 0x65,
+ "MSLUT6": 0x66,
+ "MSLUT7": 0x67,
+ "MSLUTSEL": 0x68,
+ "MSLUTSTART": 0x69,
+ "MSCNT": 0x6A,
+ "MSCURACT": 0x6B,
+ "CHOPCONF": 0x6C,
+ "COOLCONF": 0x6D,
+ "DRV_STATUS": 0x6F,
+ "PWMCONF": 0x70,
+ "PWM_SCALE": 0x71,
+ "PWM_AUTO": 0x72,
+ "SG4_THRS": 0x74,
+ "SG4_RESULT": 0x75,
+ "SG4_IND": 0x76,
}
ReadRegisters = [
- "GCONF", "GSTAT", "IOIN", "DRV_CONF", "GLOBALSCALER", "IHOLD_IRUN",
- "TPOWERDOWN", "TSTEP", "TPWMTHRS", "TCOOLTHRS", "THIGH", "ADC_VSUPPLY_AIN",
- "ADC_TEMP", "MSCNT", "MSCURACT", "CHOPCONF", "COOLCONF", "DRV_STATUS",
- "PWMCONF", "PWM_SCALE", "PWM_AUTO", "SG4_THRS", "SG4_RESULT", "SG4_IND"
+ "GCONF",
+ "GSTAT",
+ "IOIN",
+ "DRV_CONF",
+ "GLOBALSCALER",
+ "IHOLD_IRUN",
+ "TPOWERDOWN",
+ "TSTEP",
+ "TPWMTHRS",
+ "TCOOLTHRS",
+ "THIGH",
+ "ADC_VSUPPLY_AIN",
+ "ADC_TEMP",
+ "MSCNT",
+ "MSCURACT",
+ "CHOPCONF",
+ "COOLCONF",
+ "DRV_STATUS",
+ "PWMCONF",
+ "PWM_SCALE",
+ "PWM_AUTO",
+ "SG4_THRS",
+ "SG4_RESULT",
+ "SG4_IND",
]
Fields = {}
Fields["COOLCONF"] = {
- "semin": 0x0F << 0,
- "seup": 0x03 << 5,
- "semax": 0x0F << 8,
- "sedn": 0x03 << 13,
- "seimin": 0x01 << 15,
- "sgt": 0x7F << 16,
- "sfilt": 0x01 << 24
+ "semin": 0x0F << 0,
+ "seup": 0x03 << 5,
+ "semax": 0x0F << 8,
+ "sedn": 0x03 << 13,
+ "seimin": 0x01 << 15,
+ "sgt": 0x7F << 16,
+ "sfilt": 0x01 << 24,
}
Fields["CHOPCONF"] = {
- "toff": 0x0F << 0,
- "hstrt": 0x07 << 4,
- "hend": 0x0F << 7,
- "fd3": 0x01 << 11,
- "disfdcc": 0x01 << 12,
- "chm": 0x01 << 14,
- "tbl": 0x03 << 15,
- "vhighfs": 0x01 << 18,
- "vhighchm": 0x01 << 19,
- "tpfd": 0x0F << 20, # midrange resonances
- "mres": 0x0F << 24,
- "intpol": 0x01 << 28,
- "dedge": 0x01 << 29,
- "diss2g": 0x01 << 30,
- "diss2vs": 0x01 << 31
+ "toff": 0x0F << 0,
+ "hstrt": 0x07 << 4,
+ "hend": 0x0F << 7,
+ "fd3": 0x01 << 11,
+ "disfdcc": 0x01 << 12,
+ "chm": 0x01 << 14,
+ "tbl": 0x03 << 15,
+ "vhighfs": 0x01 << 18,
+ "vhighchm": 0x01 << 19,
+ "tpfd": 0x0F << 20, # midrange resonances
+ "mres": 0x0F << 24,
+ "intpol": 0x01 << 28,
+ "dedge": 0x01 << 29,
+ "diss2g": 0x01 << 30,
+ "diss2vs": 0x01 << 31,
}
Fields["DRV_STATUS"] = {
- "sg_result": 0x3FF << 0,
- "s2vsa": 0x01 << 12,
- "s2vsb": 0x01 << 13,
- "stealth": 0x01 << 14,
- "fsactive": 0x01 << 15,
- "cs_actual": 0x1F << 16,
- "stallguard": 0x01 << 24,
- "ot": 0x01 << 25,
- "otpw": 0x01 << 26,
- "s2ga": 0x01 << 27,
- "s2gb": 0x01 << 28,
- "ola": 0x01 << 29,
- "olb": 0x01 << 30,
- "stst": 0x01 << 31
+ "sg_result": 0x3FF << 0,
+ "s2vsa": 0x01 << 12,
+ "s2vsb": 0x01 << 13,
+ "stealth": 0x01 << 14,
+ "fsactive": 0x01 << 15,
+ "cs_actual": 0x1F << 16,
+ "stallguard": 0x01 << 24,
+ "ot": 0x01 << 25,
+ "otpw": 0x01 << 26,
+ "s2ga": 0x01 << 27,
+ "s2gb": 0x01 << 28,
+ "ola": 0x01 << 29,
+ "olb": 0x01 << 30,
+ "stst": 0x01 << 31,
}
Fields["GCONF"] = {
- "faststandstill": 0x01 << 1,
- "en_pwm_mode": 0x01 << 2,
- "multistep_filt": 0x01 << 3,
- "shaft": 0x01 << 4,
- "diag0_error": 0x01 << 5,
- "diag0_otpw": 0x01 << 6,
- "diag0_stall": 0x01 << 7,
- "diag1_stall": 0x01 << 8,
- "diag1_index": 0x01 << 9,
- "diag1_onstate": 0x01 << 10,
- "diag0_pushpull": 0x01 << 12,
- "diag1_pushpull": 0x01 << 13,
- "small_hysteresis": 0x01 << 14,
- "stop_enable": 0x01 << 15,
- "direct_mode": 0x01 << 16
+ "faststandstill": 0x01 << 1,
+ "en_pwm_mode": 0x01 << 2,
+ "multistep_filt": 0x01 << 3,
+ "shaft": 0x01 << 4,
+ "diag0_error": 0x01 << 5,
+ "diag0_otpw": 0x01 << 6,
+ "diag0_stall": 0x01 << 7,
+ "diag1_stall": 0x01 << 8,
+ "diag1_index": 0x01 << 9,
+ "diag1_onstate": 0x01 << 10,
+ "diag0_pushpull": 0x01 << 12,
+ "diag1_pushpull": 0x01 << 13,
+ "small_hysteresis": 0x01 << 14,
+ "stop_enable": 0x01 << 15,
+ "direct_mode": 0x01 << 16,
}
Fields["GSTAT"] = {
- "reset": 0x01 << 0,
- "drv_err": 0x01 << 1,
- "uv_cp": 0x01 << 2,
- "register_reset": 0x01 << 3,
- "vm_uvlo": 0x01 << 4
-}
-Fields["GLOBALSCALER"] = {
- "globalscaler": 0xFF << 0
+ "reset": 0x01 << 0,
+ "drv_err": 0x01 << 1,
+ "uv_cp": 0x01 << 2,
+ "register_reset": 0x01 << 3,
+ "vm_uvlo": 0x01 << 4,
}
+Fields["GLOBALSCALER"] = {"globalscaler": 0xFF << 0}
Fields["IHOLD_IRUN"] = {
- "ihold": 0x1F << 0,
- "irun": 0x1F << 8,
- "iholddelay": 0x0F << 16,
- "irundelay": 0x0F << 24
+ "ihold": 0x1F << 0,
+ "irun": 0x1F << 8,
+ "iholddelay": 0x0F << 16,
+ "irundelay": 0x0F << 24,
}
Fields["IOIN"] = {
- "step": 0x01 << 0,
- "dir": 0x01 << 1,
- "encb": 0x01 << 2,
- "enca": 0x01 << 3,
- "drv_enn": 0x01 << 4,
- "encn": 0x01 << 5,
- "uart_en": 0x01 << 6,
- "comp_a": 0x01 << 8,
- "comp_b": 0x01 << 9,
- "comp_a1_a2": 0x01 << 10,
- "comp_b1_b2": 0x01 << 11,
- "output": 0x01 << 12,
- "ext_res_det": 0x01 << 13,
- "ext_clk": 0x01 << 14,
- "adc_err": 0x01 << 15,
- "silicon_rv": 0x07 << 16,
- "version": 0xFF << 24
+ "step": 0x01 << 0,
+ "dir": 0x01 << 1,
+ "encb": 0x01 << 2,
+ "enca": 0x01 << 3,
+ "drv_enn": 0x01 << 4,
+ "encn": 0x01 << 5,
+ "uart_en": 0x01 << 6,
+ "comp_a": 0x01 << 8,
+ "comp_b": 0x01 << 9,
+ "comp_a1_a2": 0x01 << 10,
+ "comp_b1_b2": 0x01 << 11,
+ "output": 0x01 << 12,
+ "ext_res_det": 0x01 << 13,
+ "ext_clk": 0x01 << 14,
+ "adc_err": 0x01 << 15,
+ "silicon_rv": 0x07 << 16,
+ "version": 0xFF << 24,
}
-Fields["MSLUT0"] = { "mslut0": 0xffffffff }
-Fields["MSLUT1"] = { "mslut1": 0xffffffff }
-Fields["MSLUT2"] = { "mslut2": 0xffffffff }
-Fields["MSLUT3"] = { "mslut3": 0xffffffff }
-Fields["MSLUT4"] = { "mslut4": 0xffffffff }
-Fields["MSLUT5"] = { "mslut5": 0xffffffff }
-Fields["MSLUT6"] = { "mslut6": 0xffffffff }
-Fields["MSLUT7"] = { "mslut7": 0xffffffff }
+Fields["MSLUT0"] = {"mslut0": 0xFFFFFFFF}
+Fields["MSLUT1"] = {"mslut1": 0xFFFFFFFF}
+Fields["MSLUT2"] = {"mslut2": 0xFFFFFFFF}
+Fields["MSLUT3"] = {"mslut3": 0xFFFFFFFF}
+Fields["MSLUT4"] = {"mslut4": 0xFFFFFFFF}
+Fields["MSLUT5"] = {"mslut5": 0xFFFFFFFF}
+Fields["MSLUT6"] = {"mslut6": 0xFFFFFFFF}
+Fields["MSLUT7"] = {"mslut7": 0xFFFFFFFF}
Fields["MSLUTSEL"] = {
- "x3": 0xFF << 24,
- "x2": 0xFF << 16,
- "x1": 0xFF << 8,
- "w3": 0x03 << 6,
- "w2": 0x03 << 4,
- "w1": 0x03 << 2,
- "w0": 0x03 << 0,
+ "x3": 0xFF << 24,
+ "x2": 0xFF << 16,
+ "x1": 0xFF << 8,
+ "w3": 0x03 << 6,
+ "w2": 0x03 << 4,
+ "w1": 0x03 << 2,
+ "w0": 0x03 << 0,
}
Fields["MSLUTSTART"] = {
- "start_sin": 0xFF << 0,
- "start_sin90": 0xFF << 16,
- "offset_sin90": 0xFF << 24,
-}
-Fields["MSCNT"] = {
- "mscnt": 0x3ff << 0
-}
-Fields["MSCURACT"] = {
- "cur_a": 0x1ff << 0,
- "cur_b": 0x1ff << 16
-}
-Fields["PWM_AUTO"] = {
- "pwm_ofs_auto": 0xff << 0,
- "pwm_grad_auto": 0xff << 16
+ "start_sin": 0xFF << 0,
+ "start_sin90": 0xFF << 16,
+ "offset_sin90": 0xFF << 24,
}
+Fields["MSCNT"] = {"mscnt": 0x3FF << 0}
+Fields["MSCURACT"] = {"cur_a": 0x1FF << 0, "cur_b": 0x1FF << 16}
+Fields["PWM_AUTO"] = {"pwm_ofs_auto": 0xFF << 0, "pwm_grad_auto": 0xFF << 16}
Fields["PWMCONF"] = {
- "pwm_ofs": 0xFF << 0,
- "pwm_grad": 0xFF << 8,
- "pwm_freq": 0x03 << 16,
- "pwm_autoscale": 0x01 << 18,
- "pwm_autograd": 0x01 << 19,
- "freewheel": 0x03 << 20,
- "pwm_meas_sd_enable": 0x01 << 22,
- "pwm_dis_reg_stst": 0x01 << 23,
- "pwm_reg": 0x0F << 24,
- "pwm_lim": 0x0F << 28
-}
-Fields["PWM_SCALE"] = {
- "pwm_scale_sum": 0x3ff << 0,
- "pwm_scale_auto": 0x1ff << 16
-}
-Fields["TPOWERDOWN"] = {
- "tpowerdown": 0xff << 0
-}
-Fields["TPWMTHRS"] = {
- "tpwmthrs": 0xfffff << 0
-}
-Fields["TCOOLTHRS"] = {
- "tcoolthrs": 0xfffff << 0
-}
-Fields["TSTEP"] = {
- "tstep": 0xfffff << 0
-}
-Fields["THIGH"] = {
- "thigh": 0xfffff << 0
-}
-Fields["DRV_CONF"] = {
- "current_range": 0x03 << 0,
- "slope_control": 0x03 << 4
-}
-Fields["ADC_VSUPPLY_AIN"] = {
- "adc_vsupply": 0x1fff << 0,
- "adc_ain": 0x1fff << 16
-}
-Fields["ADC_TEMP"] = {
- "adc_temp": 0x1fff << 0
+ "pwm_ofs": 0xFF << 0,
+ "pwm_grad": 0xFF << 8,
+ "pwm_freq": 0x03 << 16,
+ "pwm_autoscale": 0x01 << 18,
+ "pwm_autograd": 0x01 << 19,
+ "freewheel": 0x03 << 20,
+ "pwm_meas_sd_enable": 0x01 << 22,
+ "pwm_dis_reg_stst": 0x01 << 23,
+ "pwm_reg": 0x0F << 24,
+ "pwm_lim": 0x0F << 28,
}
+Fields["PWM_SCALE"] = {"pwm_scale_sum": 0x3FF << 0, "pwm_scale_auto": 0x1FF << 16}
+Fields["TPOWERDOWN"] = {"tpowerdown": 0xFF << 0}
+Fields["TPWMTHRS"] = {"tpwmthrs": 0xFFFFF << 0}
+Fields["TCOOLTHRS"] = {"tcoolthrs": 0xFFFFF << 0}
+Fields["TSTEP"] = {"tstep": 0xFFFFF << 0}
+Fields["THIGH"] = {"thigh": 0xFFFFF << 0}
+Fields["DRV_CONF"] = {"current_range": 0x03 << 0, "slope_control": 0x03 << 4}
+Fields["ADC_VSUPPLY_AIN"] = {"adc_vsupply": 0x1FFF << 0, "adc_ain": 0x1FFF << 16}
+Fields["ADC_TEMP"] = {"adc_temp": 0x1FFF << 0}
Fields["OTW_OV_VTH"] = {
- "overvoltage_vth": 0x1fff << 0,
- "overtempprewarning_vth": 0x1fff << 16
+ "overvoltage_vth": 0x1FFF << 0,
+ "overtempprewarning_vth": 0x1FFF << 16,
}
Fields["SG4_THRS"] = {
- "sg4_thrs": 0xFF << 0,
- "sg4_filt_en": 0x01 << 8,
- "sg4_angle_offset": 0x01 << 9
-}
-Fields["SG4_RESULT"] = {
- "sg4_result": 0x3FF << 0
+ "sg4_thrs": 0xFF << 0,
+ "sg4_filt_en": 0x01 << 8,
+ "sg4_angle_offset": 0x01 << 9,
}
+Fields["SG4_RESULT"] = {"sg4_result": 0x3FF << 0}
Fields["SG4_IND"] = {
- "sg4_ind_0": 0xFF << 0,
- "sg4_ind_1": 0xFF << 8,
- "sg4_ind_2": 0xFF << 16,
- "sg4_ind_3": 0xFF << 24
+ "sg4_ind_0": 0xFF << 0,
+ "sg4_ind_1": 0xFF << 8,
+ "sg4_ind_2": 0xFF << 16,
+ "sg4_ind_3": 0xFF << 24,
}
SignedFields = ["cur_a", "cur_b", "sgt", "pwm_scale_auto", "offset_sin90"]
FieldFormatters = dict(tmc2130.FieldFormatters)
-FieldFormatters.update({
- "s2vsa": (lambda v: "1(ShortToSupply_A!)" if v else ""),
- "s2vsb": (lambda v: "1(ShortToSupply_B!)" if v else ""),
- "adc_temp": (lambda v: "0x%04x(%.1fC)" % (v, ((v - 2038) / 7.7))),
- "adc_vsupply": (lambda v: "0x%04x(%.3fV)" % (v, v * 0.009732)),
- "adc_ain": (lambda v: "0x%04x(%.3fmV)" % (v, v * 0.3052)),
-})
+FieldFormatters.update(
+ {
+ "s2vsa": (lambda v: "1(ShortToSupply_A!)" if v else ""),
+ "s2vsb": (lambda v: "1(ShortToSupply_B!)" if v else ""),
+ "adc_temp": (lambda v: "0x%04x(%.1fC)" % (v, ((v - 2038) / 7.7))),
+ "adc_vsupply": (lambda v: "0x%04x(%.3fV)" % (v, v * 0.009732)),
+ "adc_ain": (lambda v: "0x%04x(%.3fmV)" % (v, v * 0.3052)),
+ }
+)
######################################################################
# TMC stepper current config helper
######################################################################
+
class TMC2240CurrentHelper:
def __init__(self, config, mcu_tmc):
self.printer = config.get_printer()
self.name = config.get_name().split()[-1]
self.mcu_tmc = mcu_tmc
self.fields = mcu_tmc.get_fields()
- self.Rref = config.getfloat('rref', 12000.,
- minval=12000., maxval=60000.)
+ self.Rref = config.getfloat("rref", 12000.0, minval=12000.0, maxval=60000.0)
max_cur = self._get_ifs_rms(3)
- run_current = config.getfloat('run_current', above=0., maxval=max_cur)
- hold_current = config.getfloat('hold_current', max_cur,
- above=0., maxval=max_cur)
+ run_current = config.getfloat("run_current", above=0.0, maxval=max_cur)
+ hold_current = config.getfloat(
+ "hold_current", max_cur, above=0.0, maxval=max_cur
+ )
self.req_hold_current = hold_current
current_range = self._calc_current_range(run_current)
self.fields.set_field("current_range", current_range)
@@ -287,46 +277,54 @@ class TMC2240CurrentHelper:
self.fields.set_field("globalscaler", gscaler)
self.fields.set_field("ihold", ihold)
self.fields.set_field("irun", irun)
+
def _get_ifs_rms(self, current_range=None):
if current_range is None:
current_range = self.fields.get_field("current_range")
- KIFS = [11750., 24000., 36000., 36000.]
- return (KIFS[current_range] / self.Rref) / math.sqrt(2.)
+ KIFS = [11750.0, 24000.0, 36000.0, 36000.0]
+ return (KIFS[current_range] / self.Rref) / math.sqrt(2.0)
+
def _calc_current_range(self, current):
for current_range in range(4):
if current <= self._get_ifs_rms(current_range):
break
return current_range
+
def _calc_globalscaler(self, current):
ifs_rms = self._get_ifs_rms()
- globalscaler = int(((current * 256.) / ifs_rms) + .5)
+ globalscaler = int(((current * 256.0) / ifs_rms) + 0.5)
globalscaler = max(32, globalscaler)
if globalscaler >= 256:
globalscaler = 0
return globalscaler
+
def _calc_current_bits(self, current, globalscaler):
ifs_rms = self._get_ifs_rms()
if not globalscaler:
globalscaler = 256
- cs = int((current * 256. * 32.) / (globalscaler * ifs_rms) - 1. + .5)
+ cs = int((current * 256.0 * 32.0) / (globalscaler * ifs_rms) - 1.0 + 0.5)
return max(0, min(31, cs))
+
def _calc_current(self, run_current, hold_current):
gscaler = self._calc_globalscaler(run_current)
irun = self._calc_current_bits(run_current, gscaler)
ihold = self._calc_current_bits(min(hold_current, run_current), gscaler)
return gscaler, irun, ihold
+
def _calc_current_from_field(self, field_name):
ifs_rms = self._get_ifs_rms()
globalscaler = self.fields.get_field("globalscaler")
if not globalscaler:
globalscaler = 256
bits = self.fields.get_field(field_name)
- return globalscaler * (bits + 1) * ifs_rms / (256. * 32.)
+ return globalscaler * (bits + 1) * ifs_rms / (256.0 * 32.0)
+
def get_current(self):
ifs_rms = self._get_ifs_rms()
run_current = self._calc_current_from_field("irun")
hold_current = self._calc_current_from_field("ihold")
return (run_current, hold_current, self.req_hold_current, ifs_rms)
+
def set_current(self, run_current, hold_current, print_time):
self.req_hold_current = hold_current
gscaler, irun, ihold = self._calc_current(run_current, hold_current)
@@ -341,18 +339,21 @@ class TMC2240CurrentHelper:
# TMC2240 printer object
######################################################################
+
class TMC2240:
def __init__(self, config):
# Setup mcu communication
self.fields = tmc.FieldHelper(Fields, SignedFields, FieldFormatters)
if config.get("uart_pin", None) is not None:
# use UART for communication
- self.mcu_tmc = tmc_uart.MCU_TMC_uart(config, Registers, self.fields,
- 7, TMC_FREQUENCY)
+ self.mcu_tmc = tmc_uart.MCU_TMC_uart(
+ config, Registers, self.fields, 7, TMC_FREQUENCY
+ )
else:
# Use SPI bus for communication
- self.mcu_tmc = tmc2130.MCU_TMC_SPI(config, Registers, self.fields,
- TMC_FREQUENCY)
+ self.mcu_tmc = tmc2130.MCU_TMC_SPI(
+ config, Registers, self.fields, TMC_FREQUENCY
+ )
# Allow virtual pins to be created
tmc.TMCVirtualPinHelper(config, self.mcu_tmc)
# Register commands
@@ -411,5 +412,6 @@ class TMC2240:
# DRV_CONF
set_config_field(config, "slope_control", 0)
+
def load_config_prefix(config):
return TMC2240(config)
diff --git a/klippy/extras/tmc2660.py b/klippy/extras/tmc2660.py
index c97f1eff..03b8c12c 100644
--- a/klippy/extras/tmc2660.py
+++ b/klippy/extras/tmc2660.py
@@ -8,43 +8,42 @@ import math, logging
from . import bus, tmc, tmc2130
Registers = {
- "DRVCONF": 0xE, "SGCSCONF": 0xC, "SMARTEN": 0xA,
- "CHOPCONF": 0x8, "DRVCTRL": 0x0
+ "DRVCONF": 0xE,
+ "SGCSCONF": 0xC,
+ "SMARTEN": 0xA,
+ "CHOPCONF": 0x8,
+ "DRVCTRL": 0x0,
}
-ReadRegisters = [ "READRSP@RDSEL0", "READRSP@RDSEL1", "READRSP@RDSEL2" ]
+ReadRegisters = ["READRSP@RDSEL0", "READRSP@RDSEL1", "READRSP@RDSEL2"]
Fields = {}
Fields["DRVCTRL"] = {
- "mres": 0x0f,
+ "mres": 0x0F,
"dedge": 0x01 << 8,
"intpol": 0x01 << 9,
}
Fields["CHOPCONF"] = {
- "toff": 0x0f,
+ "toff": 0x0F,
"hstrt": 0x7 << 4,
- "hend": 0x0f << 7,
+ "hend": 0x0F << 7,
"hdec": 0x03 << 11,
"rndtf": 0x01 << 13,
"chm": 0x01 << 14,
- "tbl": 0x03 << 15
+ "tbl": 0x03 << 15,
}
Fields["SMARTEN"] = {
- "semin" : 0x0f,
+ "semin": 0x0F,
"seup": 0x03 << 5,
- "semax": 0x0f << 8,
+ "semax": 0x0F << 8,
"sedn": 0x03 << 13,
- "seimin": 0x01 << 15
+ "seimin": 0x01 << 15,
}
-Fields["SGCSCONF"] = {
- "cs": 0x1f,
- "sgt": 0x7F << 8,
- "sfilt": 0x01 << 16
-}
+Fields["SGCSCONF"] = {"cs": 0x1F, "sgt": 0x7F << 8, "sfilt": 0x01 << 16}
Fields["DRVCONF"] = {
"rdsel": 0x03 << 4,
@@ -54,7 +53,7 @@ Fields["DRVCONF"] = {
"diss2g": 0x01 << 10,
"slpl": 0x03 << 12,
"slph": 0x03 << 14,
- "tst": 0x01 << 16
+ "tst": 0x01 << 16,
}
Fields["READRSP@RDSEL0"] = {
@@ -66,7 +65,7 @@ Fields["READRSP@RDSEL0"] = {
"ola": 0x01 << 9,
"olb": 0x01 << 10,
"stst": 0x01 << 11,
- "mstep": 0x3ff << 14
+ "mstep": 0x3FF << 14,
}
Fields["READRSP@RDSEL1"] = {
@@ -78,7 +77,7 @@ Fields["READRSP@RDSEL1"] = {
"ola": 0x01 << 9,
"olb": 0x01 << 10,
"stst": 0x01 << 11,
- "sg_result": 0x3ff << 14
+ "sg_result": 0x3FF << 14,
}
Fields["READRSP@RDSEL2"] = {
@@ -90,20 +89,22 @@ Fields["READRSP@RDSEL2"] = {
"ola": 0x01 << 9,
"olb": 0x01 << 10,
"stst": 0x01 << 11,
- "se": 0x1f << 14,
- "sg_result@rdsel2": 0x1f << 19
+ "se": 0x1F << 14,
+ "sg_result@rdsel2": 0x1F << 19,
}
SignedFields = ["sgt"]
FieldFormatters = dict(tmc2130.FieldFormatters)
-FieldFormatters.update({
- "chm": (lambda v: "1(constant toff)" if v else "0(spreadCycle)"),
- "vsense": (lambda v: "1(165mV)" if v else "0(305mV)"),
- "sdoff": (lambda v: "1(Step/Dir disabled!)" if v else ""),
- "diss2g": (lambda v: "1(Short to GND disabled!)" if v else ""),
- "se": (lambda v: ("%d" % v) if v else "0(Reset?)"),
-})
+FieldFormatters.update(
+ {
+ "chm": (lambda v: "1(constant toff)" if v else "0(spreadCycle)"),
+ "vsense": (lambda v: "1(165mV)" if v else "0(305mV)"),
+ "sdoff": (lambda v: "1(Step/Dir disabled!)" if v else ""),
+ "diss2g": (lambda v: "1(Short to GND disabled!)" if v else ""),
+ "se": (lambda v: ("%d" % v) if v else "0(Reset?)"),
+ }
+)
######################################################################
@@ -112,37 +113,40 @@ FieldFormatters.update({
MAX_CURRENT = 2.400
+
class TMC2660CurrentHelper:
def __init__(self, config, mcu_tmc):
self.printer = config.get_printer()
self.name = config.get_name().split()[-1]
self.mcu_tmc = mcu_tmc
self.fields = mcu_tmc.get_fields()
- self.current = config.getfloat('run_current', minval=0.1,
- maxval=MAX_CURRENT)
- self.sense_resistor = config.getfloat('sense_resistor')
+ self.current = config.getfloat("run_current", minval=0.1, maxval=MAX_CURRENT)
+ self.sense_resistor = config.getfloat("sense_resistor")
vsense, cs = self._calc_current(self.current)
self.fields.set_field("cs", cs)
self.fields.set_field("vsense", vsense)
# Register ready/printing handlers
self.idle_current_percentage = config.getint(
- 'idle_current_percent', default=100, minval=0, maxval=100)
+ "idle_current_percent", default=100, minval=0, maxval=100
+ )
if self.idle_current_percentage < 100:
- self.printer.register_event_handler("idle_timeout:printing",
- self._handle_printing)
- self.printer.register_event_handler("idle_timeout:ready",
- self._handle_ready)
+ self.printer.register_event_handler(
+ "idle_timeout:printing", self._handle_printing
+ )
+ self.printer.register_event_handler(
+ "idle_timeout:ready", self._handle_ready
+ )
def _calc_current_bits(self, current, vsense):
vref = 0.165 if vsense else 0.310
sr = self.sense_resistor
- cs = int(32. * sr * current * math.sqrt(2.) / vref + .5) - 1
+ cs = int(32.0 * sr * current * math.sqrt(2.0) / vref + 0.5) - 1
return max(0, min(31, cs))
def _calc_current_from_bits(self, cs, vsense):
vref = 0.165 if vsense else 0.310
- return (cs + 1) * vref / (32. * self.sense_resistor * math.sqrt(2.))
+ return (cs + 1) * vref / (32.0 * self.sense_resistor * math.sqrt(2.0))
def _calc_current(self, run_current):
vsense = True
@@ -158,14 +162,16 @@ class TMC2660CurrentHelper:
return vsense, irun
def _handle_printing(self, print_time):
- print_time -= 0.100 # Schedule slightly before deadline
+ print_time -= 0.100 # Schedule slightly before deadline
self.printer.get_reactor().register_callback(
- (lambda ev: self._update_current(self.current, print_time)))
+ (lambda ev: self._update_current(self.current, print_time))
+ )
def _handle_ready(self, print_time):
- current = self.current * float(self.idle_current_percentage) / 100.
+ current = self.current * float(self.idle_current_percentage) / 100.0
self.printer.get_reactor().register_callback(
- (lambda ev: self._update_current(current, print_time)))
+ (lambda ev: self._update_current(current, print_time))
+ )
def _update_current(self, current, print_time):
vsense, cs = self._calc_current(current)
@@ -188,6 +194,7 @@ class TMC2660CurrentHelper:
# TMC2660 SPI
######################################################################
+
# Helper code for working with TMC2660 devices via SPI
class MCU_TMC2660_SPI:
def __init__(self, config, name_to_reg, fields):
@@ -196,41 +203,47 @@ class MCU_TMC2660_SPI:
self.spi = bus.MCU_SPI_from_config(config, 0, default_speed=4000000)
self.name_to_reg = name_to_reg
self.fields = fields
+
def get_fields(self):
return self.fields
+
def get_register_raw(self, reg_name):
new_rdsel = ReadRegisters.index(reg_name)
reg = self.name_to_reg["DRVCONF"]
- if self.printer.get_start_args().get('debugoutput') is not None:
+ if self.printer.get_start_args().get("debugoutput") is not None:
return {
- 'data': 0,
- '#receive_time': .0,
+ "data": 0,
+ "#receive_time": 0.0,
}
with self.mutex:
old_rdsel = self.fields.get_field("rdsel")
val = self.fields.set_field("rdsel", new_rdsel)
- msg = [((val >> 16) | reg) & 0xff, (val >> 8) & 0xff, val & 0xff]
+ msg = [((val >> 16) | reg) & 0xFF, (val >> 8) & 0xFF, val & 0xFF]
if new_rdsel != old_rdsel:
# Must set RDSEL value first
self.spi.spi_send(msg)
params = self.spi.spi_transfer(msg)
- pr = bytearray(params['response'])
+ pr = bytearray(params["response"])
return {
- 'data': (pr[0] << 16) | (pr[1] << 8) | pr[2],
- '#receive_time': params['#receive_time'],
+ "data": (pr[0] << 16) | (pr[1] << 8) | pr[2],
+ "#receive_time": params["#receive_time"],
}
+
def get_register(self, reg_name):
- return self.get_register_raw(reg_name)['data']
+ return self.get_register_raw(reg_name)["data"]
+
def set_register(self, reg_name, val, print_time=None):
minclock = 0
if print_time is not None:
minclock = self.spi.get_mcu().print_time_to_clock(print_time)
reg = self.name_to_reg[reg_name]
- msg = [((val >> 16) | reg) & 0xff, (val >> 8) & 0xff, val & 0xff]
+ msg = [((val >> 16) | reg) & 0xFF, (val >> 8) & 0xFF, val & 0xFF]
with self.mutex:
self.spi.spi_send(msg, minclock)
+
def get_tmc_frequency(self):
return None
+
def get_mcu(self):
return self.spi.get_mcu()
@@ -239,11 +252,12 @@ class MCU_TMC2660_SPI:
# TMC2660 printer object
######################################################################
+
class TMC2660:
def __init__(self, config):
# Setup mcu communication
self.fields = tmc.FieldHelper(Fields, SignedFields, FieldFormatters)
- self.fields.set_field("sdoff", 0) # Access DRVCTRL in step/dir mode
+ self.fields.set_field("sdoff", 0) # Access DRVCTRL in step/dir mode
self.mcu_tmc = MCU_TMC2660_SPI(config, Registers, self.fields)
# Register commands
current_helper = TMC2660CurrentHelper(config, self.mcu_tmc)
@@ -262,8 +276,7 @@ class TMC2660:
set_config_field(config, "hstrt", 3)
set_config_field(config, "toff", 4)
if not self.fields.get_field("chm"):
- if (self.fields.get_field("hstrt") +
- self.fields.get_field("hend")) > 15:
+ if (self.fields.get_field("hstrt") + self.fields.get_field("hend")) > 15:
raise config.error("driver_HEND + driver_HSTRT must be <= 15")
# SMARTEN
set_config_field(config, "seimin", 0)
@@ -282,5 +295,6 @@ class TMC2660:
set_config_field(config, "diss2g", 0)
set_config_field(config, "ts2g", 3)
+
def load_config_prefix(config):
return TMC2660(config)
diff --git a/klippy/extras/tmc5160.py b/klippy/extras/tmc5160.py
index b773135c..42161d8a 100644
--- a/klippy/extras/tmc5160.py
+++ b/klippy/extras/tmc5160.py
@@ -6,253 +6,230 @@
import math, logging
from . import bus, tmc, tmc2130
-TMC_FREQUENCY=12000000.
+TMC_FREQUENCY = 12000000.0
Registers = {
- "GCONF": 0x00,
- "GSTAT": 0x01,
- "IFCNT": 0x02,
- "SLAVECONF": 0x03,
- "IOIN": 0x04,
- "X_COMPARE": 0x05,
- "OTP_READ": 0x07,
- "FACTORY_CONF": 0x08,
- "SHORT_CONF": 0x09,
- "DRV_CONF": 0x0A,
- "GLOBALSCALER": 0x0B,
- "OFFSET_READ": 0x0C,
- "IHOLD_IRUN": 0x10,
- "TPOWERDOWN": 0x11,
- "TSTEP": 0x12,
- "TPWMTHRS": 0x13,
- "TCOOLTHRS": 0x14,
- "THIGH": 0x15,
- "RAMPMODE": 0x20,
- "XACTUAL": 0x21,
- "VACTUAL": 0x22,
- "VSTART": 0x23,
- "A1": 0x24,
- "V1": 0x25,
- "AMAX": 0x26,
- "VMAX": 0x27,
- "DMAX": 0x28,
- "D1": 0x2A,
- "VSTOP": 0x2B,
- "TZEROWAIT": 0x2C,
- "XTARGET": 0x2D,
- "VDCMIN": 0x33,
- "SW_MODE": 0x34,
- "RAMP_STAT": 0x35,
- "XLATCH": 0x36,
- "ENCMODE": 0x38,
- "X_ENC": 0x39,
- "ENC_CONST": 0x3A,
- "ENC_STATUS": 0x3B,
- "ENC_LATCH": 0x3C,
- "ENC_DEVIATION": 0x3D,
- "MSLUT0": 0x60,
- "MSLUT1": 0x61,
- "MSLUT2": 0x62,
- "MSLUT3": 0x63,
- "MSLUT4": 0x64,
- "MSLUT5": 0x65,
- "MSLUT6": 0x66,
- "MSLUT7": 0x67,
- "MSLUTSEL": 0x68,
- "MSLUTSTART": 0x69,
- "MSCNT": 0x6A,
- "MSCURACT": 0x6B,
- "CHOPCONF": 0x6C,
- "COOLCONF": 0x6D,
- "DCCTRL": 0x6E,
- "DRV_STATUS": 0x6F,
- "PWMCONF": 0x70,
- "PWM_SCALE": 0x71,
- "PWM_AUTO": 0x72,
- "LOST_STEPS": 0x73,
+ "GCONF": 0x00,
+ "GSTAT": 0x01,
+ "IFCNT": 0x02,
+ "SLAVECONF": 0x03,
+ "IOIN": 0x04,
+ "X_COMPARE": 0x05,
+ "OTP_READ": 0x07,
+ "FACTORY_CONF": 0x08,
+ "SHORT_CONF": 0x09,
+ "DRV_CONF": 0x0A,
+ "GLOBALSCALER": 0x0B,
+ "OFFSET_READ": 0x0C,
+ "IHOLD_IRUN": 0x10,
+ "TPOWERDOWN": 0x11,
+ "TSTEP": 0x12,
+ "TPWMTHRS": 0x13,
+ "TCOOLTHRS": 0x14,
+ "THIGH": 0x15,
+ "RAMPMODE": 0x20,
+ "XACTUAL": 0x21,
+ "VACTUAL": 0x22,
+ "VSTART": 0x23,
+ "A1": 0x24,
+ "V1": 0x25,
+ "AMAX": 0x26,
+ "VMAX": 0x27,
+ "DMAX": 0x28,
+ "D1": 0x2A,
+ "VSTOP": 0x2B,
+ "TZEROWAIT": 0x2C,
+ "XTARGET": 0x2D,
+ "VDCMIN": 0x33,
+ "SW_MODE": 0x34,
+ "RAMP_STAT": 0x35,
+ "XLATCH": 0x36,
+ "ENCMODE": 0x38,
+ "X_ENC": 0x39,
+ "ENC_CONST": 0x3A,
+ "ENC_STATUS": 0x3B,
+ "ENC_LATCH": 0x3C,
+ "ENC_DEVIATION": 0x3D,
+ "MSLUT0": 0x60,
+ "MSLUT1": 0x61,
+ "MSLUT2": 0x62,
+ "MSLUT3": 0x63,
+ "MSLUT4": 0x64,
+ "MSLUT5": 0x65,
+ "MSLUT6": 0x66,
+ "MSLUT7": 0x67,
+ "MSLUTSEL": 0x68,
+ "MSLUTSTART": 0x69,
+ "MSCNT": 0x6A,
+ "MSCURACT": 0x6B,
+ "CHOPCONF": 0x6C,
+ "COOLCONF": 0x6D,
+ "DCCTRL": 0x6E,
+ "DRV_STATUS": 0x6F,
+ "PWMCONF": 0x70,
+ "PWM_SCALE": 0x71,
+ "PWM_AUTO": 0x72,
+ "LOST_STEPS": 0x73,
}
ReadRegisters = [
- "GCONF", "CHOPCONF", "GSTAT", "DRV_STATUS", "FACTORY_CONF", "IOIN",
- "LOST_STEPS", "MSCNT", "MSCURACT", "OTP_READ", "PWM_SCALE",
- "PWM_AUTO", "TSTEP"
+ "GCONF",
+ "CHOPCONF",
+ "GSTAT",
+ "DRV_STATUS",
+ "FACTORY_CONF",
+ "IOIN",
+ "LOST_STEPS",
+ "MSCNT",
+ "MSCURACT",
+ "OTP_READ",
+ "PWM_SCALE",
+ "PWM_AUTO",
+ "TSTEP",
]
Fields = {}
Fields["COOLCONF"] = {
- "semin": 0x0F << 0,
- "seup": 0x03 << 5,
- "semax": 0x0F << 8,
- "sedn": 0x03 << 13,
- "seimin": 0x01 << 15,
- "sgt": 0x7F << 16,
- "sfilt": 0x01 << 24
+ "semin": 0x0F << 0,
+ "seup": 0x03 << 5,
+ "semax": 0x0F << 8,
+ "sedn": 0x03 << 13,
+ "seimin": 0x01 << 15,
+ "sgt": 0x7F << 16,
+ "sfilt": 0x01 << 24,
}
Fields["CHOPCONF"] = {
- "toff": 0x0F << 0,
- "hstrt": 0x07 << 4,
- "hend": 0x0F << 7,
- "fd3": 0x01 << 11,
- "disfdcc": 0x01 << 12,
- "chm": 0x01 << 14,
- "tbl": 0x03 << 15,
- "vhighfs": 0x01 << 18,
- "vhighchm": 0x01 << 19,
- "tpfd": 0x0F << 20, # midrange resonances
- "mres": 0x0F << 24,
- "intpol": 0x01 << 28,
- "dedge": 0x01 << 29,
- "diss2g": 0x01 << 30,
- "diss2vs": 0x01 << 31
+ "toff": 0x0F << 0,
+ "hstrt": 0x07 << 4,
+ "hend": 0x0F << 7,
+ "fd3": 0x01 << 11,
+ "disfdcc": 0x01 << 12,
+ "chm": 0x01 << 14,
+ "tbl": 0x03 << 15,
+ "vhighfs": 0x01 << 18,
+ "vhighchm": 0x01 << 19,
+ "tpfd": 0x0F << 20, # midrange resonances
+ "mres": 0x0F << 24,
+ "intpol": 0x01 << 28,
+ "dedge": 0x01 << 29,
+ "diss2g": 0x01 << 30,
+ "diss2vs": 0x01 << 31,
}
Fields["DRV_CONF"] = {
- "bbmtime": 0x1F << 0,
- "bbmclks": 0x0F << 8,
- "otselect": 0x03 << 16,
- "drvstrength": 0x03 << 18,
- "filt_isense": 0x03 << 20,
+ "bbmtime": 0x1F << 0,
+ "bbmclks": 0x0F << 8,
+ "otselect": 0x03 << 16,
+ "drvstrength": 0x03 << 18,
+ "filt_isense": 0x03 << 20,
}
Fields["DRV_STATUS"] = {
- "sg_result": 0x3FF << 0,
- "s2vsa": 0x01 << 12,
- "s2vsb": 0x01 << 13,
- "stealth": 0x01 << 14,
- "fsactive": 0x01 << 15,
- "cs_actual": 0x1F << 16,
- "stallguard": 0x01 << 24,
- "ot": 0x01 << 25,
- "otpw": 0x01 << 26,
- "s2ga": 0x01 << 27,
- "s2gb": 0x01 << 28,
- "ola": 0x01 << 29,
- "olb": 0x01 << 30,
- "stst": 0x01 << 31
-}
-Fields["FACTORY_CONF"] = {
- "factory_conf": 0x1F << 0
+ "sg_result": 0x3FF << 0,
+ "s2vsa": 0x01 << 12,
+ "s2vsb": 0x01 << 13,
+ "stealth": 0x01 << 14,
+ "fsactive": 0x01 << 15,
+ "cs_actual": 0x1F << 16,
+ "stallguard": 0x01 << 24,
+ "ot": 0x01 << 25,
+ "otpw": 0x01 << 26,
+ "s2ga": 0x01 << 27,
+ "s2gb": 0x01 << 28,
+ "ola": 0x01 << 29,
+ "olb": 0x01 << 30,
+ "stst": 0x01 << 31,
}
+Fields["FACTORY_CONF"] = {"factory_conf": 0x1F << 0}
Fields["GCONF"] = {
- "recalibrate": 0x01 << 0,
- "faststandstill": 0x01 << 1,
- "en_pwm_mode": 0x01 << 2,
- "multistep_filt": 0x01 << 3,
- "shaft": 0x01 << 4,
- "diag0_error": 0x01 << 5,
- "diag0_otpw": 0x01 << 6,
- "diag0_stall": 0x01 << 7,
- "diag1_stall": 0x01 << 8,
- "diag1_index": 0x01 << 9,
- "diag1_onstate": 0x01 << 10,
- "diag1_steps_skipped": 0x01 << 11,
- "diag0_int_pushpull": 0x01 << 12,
- "diag1_poscomp_pushpull": 0x01 << 13,
- "small_hysteresis": 0x01 << 14,
- "stop_enable": 0x01 << 15,
- "direct_mode": 0x01 << 16,
- "test_mode": 0x01 << 17
-}
-Fields["GSTAT"] = {
- "reset": 0x01 << 0,
- "drv_err": 0x01 << 1,
- "uv_cp": 0x01 << 2
-}
-Fields["GLOBALSCALER"] = {
- "globalscaler": 0xFF << 0
-}
-Fields["IHOLD_IRUN"] = {
- "ihold": 0x1F << 0,
- "irun": 0x1F << 8,
- "iholddelay": 0x0F << 16
+ "recalibrate": 0x01 << 0,
+ "faststandstill": 0x01 << 1,
+ "en_pwm_mode": 0x01 << 2,
+ "multistep_filt": 0x01 << 3,
+ "shaft": 0x01 << 4,
+ "diag0_error": 0x01 << 5,
+ "diag0_otpw": 0x01 << 6,
+ "diag0_stall": 0x01 << 7,
+ "diag1_stall": 0x01 << 8,
+ "diag1_index": 0x01 << 9,
+ "diag1_onstate": 0x01 << 10,
+ "diag1_steps_skipped": 0x01 << 11,
+ "diag0_int_pushpull": 0x01 << 12,
+ "diag1_poscomp_pushpull": 0x01 << 13,
+ "small_hysteresis": 0x01 << 14,
+ "stop_enable": 0x01 << 15,
+ "direct_mode": 0x01 << 16,
+ "test_mode": 0x01 << 17,
}
+Fields["GSTAT"] = {"reset": 0x01 << 0, "drv_err": 0x01 << 1, "uv_cp": 0x01 << 2}
+Fields["GLOBALSCALER"] = {"globalscaler": 0xFF << 0}
+Fields["IHOLD_IRUN"] = {"ihold": 0x1F << 0, "irun": 0x1F << 8, "iholddelay": 0x0F << 16}
Fields["IOIN"] = {
- "refl_step": 0x01 << 0,
- "refr_dir": 0x01 << 1,
- "encb_dcen_cfg4": 0x01 << 2,
- "enca_dcin_cfg5": 0x01 << 3,
- "drv_enn": 0x01 << 4,
- "enc_n_dco_cfg6": 0x01 << 5,
- "sd_mode": 0x01 << 6,
- "swcomp_in": 0x01 << 7,
- "version": 0xFF << 24
-}
-Fields["LOST_STEPS"] = {
- "lost_steps": 0xfffff << 0
+ "refl_step": 0x01 << 0,
+ "refr_dir": 0x01 << 1,
+ "encb_dcen_cfg4": 0x01 << 2,
+ "enca_dcin_cfg5": 0x01 << 3,
+ "drv_enn": 0x01 << 4,
+ "enc_n_dco_cfg6": 0x01 << 5,
+ "sd_mode": 0x01 << 6,
+ "swcomp_in": 0x01 << 7,
+ "version": 0xFF << 24,
}
-Fields["MSLUT0"] = { "mslut0": 0xffffffff }
-Fields["MSLUT1"] = { "mslut1": 0xffffffff }
-Fields["MSLUT2"] = { "mslut2": 0xffffffff }
-Fields["MSLUT3"] = { "mslut3": 0xffffffff }
-Fields["MSLUT4"] = { "mslut4": 0xffffffff }
-Fields["MSLUT5"] = { "mslut5": 0xffffffff }
-Fields["MSLUT6"] = { "mslut6": 0xffffffff }
-Fields["MSLUT7"] = { "mslut7": 0xffffffff }
+Fields["LOST_STEPS"] = {"lost_steps": 0xFFFFF << 0}
+Fields["MSLUT0"] = {"mslut0": 0xFFFFFFFF}
+Fields["MSLUT1"] = {"mslut1": 0xFFFFFFFF}
+Fields["MSLUT2"] = {"mslut2": 0xFFFFFFFF}
+Fields["MSLUT3"] = {"mslut3": 0xFFFFFFFF}
+Fields["MSLUT4"] = {"mslut4": 0xFFFFFFFF}
+Fields["MSLUT5"] = {"mslut5": 0xFFFFFFFF}
+Fields["MSLUT6"] = {"mslut6": 0xFFFFFFFF}
+Fields["MSLUT7"] = {"mslut7": 0xFFFFFFFF}
Fields["MSLUTSEL"] = {
- "x3": 0xFF << 24,
- "x2": 0xFF << 16,
- "x1": 0xFF << 8,
- "w3": 0x03 << 6,
- "w2": 0x03 << 4,
- "w1": 0x03 << 2,
- "w0": 0x03 << 0,
+ "x3": 0xFF << 24,
+ "x2": 0xFF << 16,
+ "x1": 0xFF << 8,
+ "w3": 0x03 << 6,
+ "w2": 0x03 << 4,
+ "w1": 0x03 << 2,
+ "w0": 0x03 << 0,
}
Fields["MSLUTSTART"] = {
- "start_sin": 0xFF << 0,
- "start_sin90": 0xFF << 16,
-}
-Fields["MSCNT"] = {
- "mscnt": 0x3ff << 0
-}
-Fields["MSCURACT"] = {
- "cur_a": 0x1ff << 0,
- "cur_b": 0x1ff << 16
+ "start_sin": 0xFF << 0,
+ "start_sin90": 0xFF << 16,
}
+Fields["MSCNT"] = {"mscnt": 0x3FF << 0}
+Fields["MSCURACT"] = {"cur_a": 0x1FF << 0, "cur_b": 0x1FF << 16}
Fields["OTP_READ"] = {
- "otp_fclktrim": 0x1f << 0,
- "otp_s2_level": 0x01 << 5,
- "otp_bbm": 0x01 << 6,
- "otp_tbl": 0x01 << 7
-}
-Fields["PWM_AUTO"] = {
- "pwm_ofs_auto": 0xff << 0,
- "pwm_grad_auto": 0xff << 16
+ "otp_fclktrim": 0x1F << 0,
+ "otp_s2_level": 0x01 << 5,
+ "otp_bbm": 0x01 << 6,
+ "otp_tbl": 0x01 << 7,
}
+Fields["PWM_AUTO"] = {"pwm_ofs_auto": 0xFF << 0, "pwm_grad_auto": 0xFF << 16}
Fields["PWMCONF"] = {
- "pwm_ofs": 0xFF << 0,
- "pwm_grad": 0xFF << 8,
- "pwm_freq": 0x03 << 16,
- "pwm_autoscale": 0x01 << 18,
- "pwm_autograd": 0x01 << 19,
- "freewheel": 0x03 << 20,
- "pwm_reg": 0x0F << 24,
- "pwm_lim": 0x0F << 28
-}
-Fields["PWM_SCALE"] = {
- "pwm_scale_sum": 0xff << 0,
- "pwm_scale_auto": 0x1ff << 16
-}
-Fields["TPOWERDOWN"] = {
- "tpowerdown": 0xff << 0
-}
-Fields["TPWMTHRS"] = {
- "tpwmthrs": 0xfffff << 0
-}
-Fields["TCOOLTHRS"] = {
- "tcoolthrs": 0xfffff << 0
-}
-Fields["TSTEP"] = {
- "tstep": 0xfffff << 0
-}
-Fields["THIGH"] = {
- "thigh": 0xfffff << 0
+ "pwm_ofs": 0xFF << 0,
+ "pwm_grad": 0xFF << 8,
+ "pwm_freq": 0x03 << 16,
+ "pwm_autoscale": 0x01 << 18,
+ "pwm_autograd": 0x01 << 19,
+ "freewheel": 0x03 << 20,
+ "pwm_reg": 0x0F << 24,
+ "pwm_lim": 0x0F << 28,
}
+Fields["PWM_SCALE"] = {"pwm_scale_sum": 0xFF << 0, "pwm_scale_auto": 0x1FF << 16}
+Fields["TPOWERDOWN"] = {"tpowerdown": 0xFF << 0}
+Fields["TPWMTHRS"] = {"tpwmthrs": 0xFFFFF << 0}
+Fields["TCOOLTHRS"] = {"tcoolthrs": 0xFFFFF << 0}
+Fields["TSTEP"] = {"tstep": 0xFFFFF << 0}
+Fields["THIGH"] = {"thigh": 0xFFFFF << 0}
SignedFields = ["cur_a", "cur_b", "sgt", "xactual", "vactual", "pwm_scale_auto"]
FieldFormatters = dict(tmc2130.FieldFormatters)
-FieldFormatters.update({
- "s2vsa": (lambda v: "1(ShortToSupply_A!)" if v else ""),
- "s2vsb": (lambda v: "1(ShortToSupply_B!)" if v else ""),
-})
+FieldFormatters.update(
+ {
+ "s2vsa": (lambda v: "1(ShortToSupply_A!)" if v else ""),
+ "s2vsb": (lambda v: "1(ShortToSupply_B!)" if v else ""),
+ }
+)
######################################################################
@@ -260,7 +237,8 @@ FieldFormatters.update({
######################################################################
VREF = 0.325
-MAX_CURRENT = 10.000 # Maximum dependent on board, but 10 is safe sanity check
+MAX_CURRENT = 10.000 # Maximum dependent on board, but 10 is safe sanity check
+
class TMC5160CurrentHelper:
def __init__(self, config, mcu_tmc):
@@ -268,46 +246,60 @@ class TMC5160CurrentHelper:
self.name = config.get_name().split()[-1]
self.mcu_tmc = mcu_tmc
self.fields = mcu_tmc.get_fields()
- run_current = config.getfloat('run_current',
- above=0., maxval=MAX_CURRENT)
- hold_current = config.getfloat('hold_current', MAX_CURRENT,
- above=0., maxval=MAX_CURRENT)
+ run_current = config.getfloat("run_current", above=0.0, maxval=MAX_CURRENT)
+ hold_current = config.getfloat(
+ "hold_current", MAX_CURRENT, above=0.0, maxval=MAX_CURRENT
+ )
self.req_hold_current = hold_current
- self.sense_resistor = config.getfloat('sense_resistor', 0.075, above=0.)
+ self.sense_resistor = config.getfloat("sense_resistor", 0.075, above=0.0)
gscaler, irun, ihold = self._calc_current(run_current, hold_current)
self.fields.set_field("globalscaler", gscaler)
self.fields.set_field("ihold", ihold)
self.fields.set_field("irun", irun)
+
def _calc_globalscaler(self, current):
- globalscaler = int((current * 256. * math.sqrt(2.)
- * self.sense_resistor / VREF) + .5)
+ globalscaler = int(
+ (current * 256.0 * math.sqrt(2.0) * self.sense_resistor / VREF) + 0.5
+ )
globalscaler = max(32, globalscaler)
if globalscaler >= 256:
globalscaler = 0
return globalscaler
+
def _calc_current_bits(self, current, globalscaler):
if not globalscaler:
globalscaler = 256
- cs = int((current * 256. * 32. * math.sqrt(2.) * self.sense_resistor)
- / (globalscaler * VREF)
- - 1. + .5)
+ cs = int(
+ (current * 256.0 * 32.0 * math.sqrt(2.0) * self.sense_resistor)
+ / (globalscaler * VREF)
+ - 1.0
+ + 0.5
+ )
return max(0, min(31, cs))
+
def _calc_current(self, run_current, hold_current):
gscaler = self._calc_globalscaler(run_current)
irun = self._calc_current_bits(run_current, gscaler)
ihold = self._calc_current_bits(min(hold_current, run_current), gscaler)
return gscaler, irun, ihold
+
def _calc_current_from_field(self, field_name):
globalscaler = self.fields.get_field("globalscaler")
if not globalscaler:
globalscaler = 256
bits = self.fields.get_field(field_name)
- return (globalscaler * (bits + 1) * VREF
- / (256. * 32. * math.sqrt(2.) * self.sense_resistor))
+ return (
+ globalscaler
+ * (bits + 1)
+ * VREF
+ / (256.0 * 32.0 * math.sqrt(2.0) * self.sense_resistor)
+ )
+
def get_current(self):
run_current = self._calc_current_from_field("irun")
hold_current = self._calc_current_from_field("ihold")
return run_current, hold_current, self.req_hold_current, MAX_CURRENT
+
def set_current(self, run_current, hold_current, print_time):
self.req_hold_current = hold_current
gscaler, irun, ihold = self._calc_current(run_current, hold_current)
@@ -322,12 +314,14 @@ class TMC5160CurrentHelper:
# TMC5160 printer object
######################################################################
+
class TMC5160:
def __init__(self, config):
# Setup mcu communication
self.fields = tmc.FieldHelper(Fields, SignedFields, FieldFormatters)
- self.mcu_tmc = tmc2130.MCU_TMC_SPI(config, Registers, self.fields,
- TMC_FREQUENCY)
+ self.mcu_tmc = tmc2130.MCU_TMC_SPI(
+ config, Registers, self.fields, TMC_FREQUENCY
+ )
# Allow virtual pins to be created
tmc.TMCVirtualPinHelper(config, self.mcu_tmc)
# Register commands
@@ -359,7 +353,7 @@ class TMC5160:
set_config_field(config, "diss2g", 0)
set_config_field(config, "diss2vs", 0)
# COOLCONF
- set_config_field(config, "semin", 0) # page 52
+ set_config_field(config, "semin", 0) # page 52
set_config_field(config, "seup", 0)
set_config_field(config, "semax", 0)
set_config_field(config, "sedn", 0)
@@ -385,5 +379,6 @@ class TMC5160:
# TPOWERDOWN
set_config_field(config, "tpowerdown", 10)
+
def load_config_prefix(config):
return TMC5160(config)
diff --git a/klippy/extras/tmc_uart.py b/klippy/extras/tmc_uart.py
index fa0d6262..141b8965 100644
--- a/klippy/extras/tmc_uart.py
+++ b/klippy/extras/tmc_uart.py
@@ -10,38 +10,48 @@ import logging
# TMC uart analog mux support
######################################################################
+
class MCU_analog_mux:
def __init__(self, mcu, cmd_queue, select_pins_desc):
self.mcu = mcu
self.cmd_queue = cmd_queue
ppins = mcu.get_printer().lookup_object("pins")
- select_pin_params = [ppins.lookup_pin(spd, can_invert=True)
- for spd in select_pins_desc]
+ select_pin_params = [
+ ppins.lookup_pin(spd, can_invert=True) for spd in select_pins_desc
+ ]
self.oids = [self.mcu.create_oid() for pp in select_pin_params]
- self.pins = [pp['pin'] for pp in select_pin_params]
+ self.pins = [pp["pin"] for pp in select_pin_params]
self.pin_values = tuple([-1 for pp in select_pin_params])
for oid, pin in zip(self.oids, self.pins):
- self.mcu.add_config_cmd("config_digital_out oid=%d pin=%s"
- " value=0 default_value=0 max_duration=0"
- % (oid, pin))
+ self.mcu.add_config_cmd(
+ "config_digital_out oid=%d pin=%s"
+ " value=0 default_value=0 max_duration=0" % (oid, pin)
+ )
self.update_pin_cmd = None
self.mcu.register_config_callback(self.build_config)
+
def build_config(self):
self.update_pin_cmd = self.mcu.lookup_command(
- "update_digital_out oid=%c value=%c", cq=self.cmd_queue)
+ "update_digital_out oid=%c value=%c", cq=self.cmd_queue
+ )
+
def get_instance_id(self, select_pins_desc):
ppins = self.mcu.get_printer().lookup_object("pins")
- select_pin_params = [ppins.parse_pin(spd, can_invert=True)
- for spd in select_pins_desc]
+ select_pin_params = [
+ ppins.parse_pin(spd, can_invert=True) for spd in select_pins_desc
+ ]
for pin_params in select_pin_params:
- if pin_params['chip'] != self.mcu:
+ if pin_params["chip"] != self.mcu:
raise self.mcu.get_printer().config_error(
- "TMC mux pins must be on the same mcu")
- pins = [pp['pin'] for pp in select_pin_params]
+ "TMC mux pins must be on the same mcu"
+ )
+ pins = [pp["pin"] for pp in select_pin_params]
if pins != self.pins:
raise self.mcu.get_printer().config_error(
- "All TMC mux instances must use identical pins")
- return tuple([not pp['invert'] for pp in select_pin_params])
+ "All TMC mux instances must use identical pins"
+ )
+ return tuple([not pp["invert"] for pp in select_pin_params])
+
def activate(self, instance_id):
for oid, old, new in zip(self.oids, self.pin_values, instance_id):
if old != new:
@@ -53,71 +63,85 @@ class MCU_analog_mux:
# TMC uart communication
######################################################################
+
# Share mutexes so only one active tmc_uart command on a single mcu at
# a time. This helps limit cpu usage on slower micro-controllers.
class PrinterTMCUartMutexes:
def __init__(self):
self.mcu_to_mutex = {}
+
+
def lookup_tmc_uart_mutex(mcu):
printer = mcu.get_printer()
- pmutexes = printer.lookup_object('tmc_uart', None)
+ pmutexes = printer.lookup_object("tmc_uart", None)
if pmutexes is None:
pmutexes = PrinterTMCUartMutexes()
- printer.add_object('tmc_uart', pmutexes)
+ printer.add_object("tmc_uart", pmutexes)
mutex = pmutexes.mcu_to_mutex.get(mcu)
if mutex is None:
mutex = printer.get_reactor().mutex()
pmutexes.mcu_to_mutex[mcu] = mutex
return mutex
+
TMC_BAUD_RATE = 40000
TMC_BAUD_RATE_AVR = 9000
+
# Code for sending messages on a TMC uart
class MCU_TMC_uart_bitbang:
def __init__(self, rx_pin_params, tx_pin_params, select_pins_desc):
- self.mcu = rx_pin_params['chip']
+ self.mcu = rx_pin_params["chip"]
self.mutex = lookup_tmc_uart_mutex(self.mcu)
- self.pullup = rx_pin_params['pullup']
- self.rx_pin = rx_pin_params['pin']
- self.tx_pin = tx_pin_params['pin']
+ self.pullup = rx_pin_params["pullup"]
+ self.rx_pin = rx_pin_params["pin"]
+ self.tx_pin = tx_pin_params["pin"]
self.oid = self.mcu.create_oid()
self.cmd_queue = self.mcu.alloc_command_queue()
self.analog_mux = None
if select_pins_desc is not None:
- self.analog_mux = MCU_analog_mux(self.mcu, self.cmd_queue,
- select_pins_desc)
+ self.analog_mux = MCU_analog_mux(self.mcu, self.cmd_queue, select_pins_desc)
self.instances = {}
self.tmcuart_send_cmd = None
self.mcu.register_config_callback(self.build_config)
+
def build_config(self):
baud = TMC_BAUD_RATE
mcu_type = self.mcu.get_constants().get("MCU", "")
if mcu_type.startswith("atmega") or mcu_type.startswith("at90usb"):
baud = TMC_BAUD_RATE_AVR
- bit_ticks = self.mcu.seconds_to_clock(1. / baud)
+ bit_ticks = self.mcu.seconds_to_clock(1.0 / baud)
self.mcu.add_config_cmd(
"config_tmcuart oid=%d rx_pin=%s pull_up=%d tx_pin=%s bit_time=%d"
- % (self.oid, self.rx_pin, self.pullup, self.tx_pin, bit_ticks))
+ % (self.oid, self.rx_pin, self.pullup, self.tx_pin, bit_ticks)
+ )
self.tmcuart_send_cmd = self.mcu.lookup_query_command(
"tmcuart_send oid=%c write=%*s read=%c",
- "tmcuart_response oid=%c read=%*s", oid=self.oid,
- cq=self.cmd_queue, is_async=True)
- def register_instance(self, rx_pin_params, tx_pin_params,
- select_pins_desc, addr):
- if (rx_pin_params['pin'] != self.rx_pin
- or tx_pin_params['pin'] != self.tx_pin
- or (select_pins_desc is None) != (self.analog_mux is None)):
+ "tmcuart_response oid=%c read=%*s",
+ oid=self.oid,
+ cq=self.cmd_queue,
+ is_async=True,
+ )
+
+ def register_instance(self, rx_pin_params, tx_pin_params, select_pins_desc, addr):
+ if (
+ rx_pin_params["pin"] != self.rx_pin
+ or tx_pin_params["pin"] != self.tx_pin
+ or (select_pins_desc is None) != (self.analog_mux is None)
+ ):
raise self.mcu.get_printer().config_error(
- "Shared TMC uarts must use the same pins")
+ "Shared TMC uarts must use the same pins"
+ )
instance_id = None
if self.analog_mux is not None:
instance_id = self.analog_mux.get_instance_id(select_pins_desc)
if (instance_id, addr) in self.instances:
raise self.mcu.get_printer().config_error(
- "Shared TMC uarts need unique address or select_pins polarity")
+ "Shared TMC uarts need unique address or select_pins polarity"
+ )
self.instances[(instance_id, addr)] = True
return instance_id
+
def _calc_crc8(self, data):
# Generate a CRC8-ATM value for a bytearray
crc = 0
@@ -126,33 +150,46 @@ class MCU_TMC_uart_bitbang:
if (crc >> 7) ^ (b & 0x01):
crc = (crc << 1) ^ 0x07
else:
- crc = (crc << 1)
- crc &= 0xff
+ crc = crc << 1
+ crc &= 0xFF
b >>= 1
return crc
+
def _add_serial_bits(self, data):
# Add serial start and stop bits to a message in a bytearray
out = 0
pos = 0
for d in data:
b = (d << 1) | 0x200
- out |= (b << pos)
+ out |= b << pos
pos += 10
res = bytearray()
- for i in range((pos+7)//8):
- res.append((out >> (i*8)) & 0xff)
+ for i in range((pos + 7) // 8):
+ res.append((out >> (i * 8)) & 0xFF)
return res
+
def _encode_read(self, sync, addr, reg):
# Generate a uart read register message
msg = bytearray([sync, addr, reg])
msg.append(self._calc_crc8(msg))
return self._add_serial_bits(msg)
+
def _encode_write(self, sync, addr, reg, val):
# Generate a uart write register message
- msg = bytearray([sync, addr, reg, (val >> 24) & 0xff,
- (val >> 16) & 0xff, (val >> 8) & 0xff, val & 0xff])
+ msg = bytearray(
+ [
+ sync,
+ addr,
+ reg,
+ (val >> 24) & 0xFF,
+ (val >> 16) & 0xFF,
+ (val >> 8) & 0xFF,
+ val & 0xFF,
+ ]
+ )
msg.append(self._calc_crc8(msg))
return self._add_serial_bits(msg)
+
def _decode_read(self, reg, data):
# Extract a uart read response message
if len(data) != 10:
@@ -163,56 +200,66 @@ class MCU_TMC_uart_bitbang:
mval |= d << pos
pos += 8
# Extract register value
- val = ((((mval >> 31) & 0xff) << 24) | (((mval >> 41) & 0xff) << 16)
- | (((mval >> 51) & 0xff) << 8) | ((mval >> 61) & 0xff))
+ val = (
+ (((mval >> 31) & 0xFF) << 24)
+ | (((mval >> 41) & 0xFF) << 16)
+ | (((mval >> 51) & 0xFF) << 8)
+ | ((mval >> 61) & 0xFF)
+ )
# Verify start/stop bits and crc
- encoded_data = self._encode_write(0x05, 0xff, reg, val)
+ encoded_data = self._encode_write(0x05, 0xFF, reg, val)
if data != encoded_data:
return None
return val
+
def reg_read(self, instance_id, addr, reg):
if self.analog_mux is not None:
self.analog_mux.activate(instance_id)
- msg = self._encode_read(0xf5, addr, reg)
+ msg = self._encode_read(0xF5, addr, reg)
params = self.tmcuart_send_cmd.send([self.oid, msg, 10])
return {
- 'data': self._decode_read(reg, params['read']),
- '#receive_time': params['#receive_time']
+ "data": self._decode_read(reg, params["read"]),
+ "#receive_time": params["#receive_time"],
}
+
def reg_write(self, instance_id, addr, reg, val, print_time=None):
minclock = 0
if print_time is not None:
minclock = self.mcu.print_time_to_clock(print_time)
if self.analog_mux is not None:
self.analog_mux.activate(instance_id)
- msg = self._encode_write(0xf5, addr, reg | 0x80, val)
+ msg = self._encode_write(0xF5, addr, reg | 0x80, val)
self.tmcuart_send_cmd.send([self.oid, msg, 0], minclock=minclock)
+
def get_mcu(self):
return self.mcu
+
# Lookup a (possibly shared) tmc uart
def lookup_tmc_uart_bitbang(config, max_addr):
ppins = config.get_printer().lookup_object("pins")
- rx_pin_params = ppins.lookup_pin(config.get('uart_pin'), can_pullup=True,
- share_type="tmc_uart_rx")
- tx_pin_desc = config.get('tx_pin', None)
+ rx_pin_params = ppins.lookup_pin(
+ config.get("uart_pin"), can_pullup=True, share_type="tmc_uart_rx"
+ )
+ tx_pin_desc = config.get("tx_pin", None)
if tx_pin_desc is None:
tx_pin_params = rx_pin_params
else:
tx_pin_params = ppins.lookup_pin(tx_pin_desc, share_type="tmc_uart_tx")
- if rx_pin_params['chip'] is not tx_pin_params['chip']:
+ if rx_pin_params["chip"] is not tx_pin_params["chip"]:
raise ppins.error("TMC uart rx and tx pins must be on the same mcu")
- select_pins_desc = config.getlist('select_pins', None)
- addr = config.getint('uart_address', 0, minval=0, maxval=max_addr)
- mcu_uart = rx_pin_params.get('class')
+ select_pins_desc = config.getlist("select_pins", None)
+ addr = config.getint("uart_address", 0, minval=0, maxval=max_addr)
+ mcu_uart = rx_pin_params.get("class")
if mcu_uart is None:
- mcu_uart = MCU_TMC_uart_bitbang(rx_pin_params, tx_pin_params,
- select_pins_desc)
- rx_pin_params['class'] = mcu_uart
- instance_id = mcu_uart.register_instance(rx_pin_params, tx_pin_params,
- select_pins_desc, addr)
+ mcu_uart = MCU_TMC_uart_bitbang(rx_pin_params, tx_pin_params, select_pins_desc)
+ rx_pin_params["class"] = mcu_uart
+ instance_id = mcu_uart.register_instance(
+ rx_pin_params, tx_pin_params, select_pins_desc, addr
+ )
return instance_id, addr, mcu_uart
+
# Helper code for communicating via TMC uart
class MCU_TMC_uart:
def __init__(self, config, name_to_reg, fields, max_addr, tmc_frequency):
@@ -222,46 +269,54 @@ class MCU_TMC_uart:
self.fields = fields
self.ifcnt = None
self.instance_id, self.addr, self.mcu_uart = lookup_tmc_uart_bitbang(
- config, max_addr)
+ config, max_addr
+ )
self.mutex = self.mcu_uart.mutex
self.tmc_frequency = tmc_frequency
+
def get_fields(self):
return self.fields
+
def _do_get_register(self, reg_name):
reg = self.name_to_reg[reg_name]
- if self.printer.get_start_args().get('debugoutput') is not None:
- return {
- 'data': 0,
- '#receive_time': 0.
- }
+ if self.printer.get_start_args().get("debugoutput") is not None:
+ return {"data": 0, "#receive_time": 0.0}
for retry in range(5):
val = self.mcu_uart.reg_read(self.instance_id, self.addr, reg)
- if val['data'] is not None:
+ if val["data"] is not None:
return val
raise self.printer.command_error(
- "Unable to read tmc uart '%s' register %s" % (self.name, reg_name))
+ "Unable to read tmc uart '%s' register %s" % (self.name, reg_name)
+ )
+
def get_register_raw(self, reg_name):
with self.mutex:
return self._do_get_register(reg_name)
+
def get_register(self, reg_name):
- return self.get_register_raw(reg_name)['data']
+ return self.get_register_raw(reg_name)["data"]
+
def set_register(self, reg_name, val, print_time=None):
reg = self.name_to_reg[reg_name]
- if self.printer.get_start_args().get('debugoutput') is not None:
+ if self.printer.get_start_args().get("debugoutput") is not None:
return
with self.mutex:
for retry in range(5):
ifcnt = self.ifcnt
if ifcnt is None:
- self.ifcnt = ifcnt = self._do_get_register("IFCNT")['data']
- self.mcu_uart.reg_write(self.instance_id, self.addr, reg, val,
- print_time)
- self.ifcnt = self._do_get_register("IFCNT")['data']
- if self.ifcnt == (ifcnt + 1) & 0xff:
+ self.ifcnt = ifcnt = self._do_get_register("IFCNT")["data"]
+ self.mcu_uart.reg_write(
+ self.instance_id, self.addr, reg, val, print_time
+ )
+ self.ifcnt = self._do_get_register("IFCNT")["data"]
+ if self.ifcnt == (ifcnt + 1) & 0xFF:
return
raise self.printer.command_error(
- "Unable to write tmc uart '%s' register %s" % (self.name, reg_name))
+ "Unable to write tmc uart '%s' register %s" % (self.name, reg_name)
+ )
+
def get_tmc_frequency(self):
return self.tmc_frequency
+
def get_mcu(self):
return self.mcu_uart.get_mcu()
diff --git a/klippy/extras/tsl1401cl_filament_width_sensor.py b/klippy/extras/tsl1401cl_filament_width_sensor.py
index 83480f46..72ddc423 100644
--- a/klippy/extras/tsl1401cl_filament_width_sensor.py
+++ b/klippy/extras/tsl1401cl_filament_width_sensor.py
@@ -9,20 +9,19 @@ ADC_SAMPLE_TIME = 0.001
ADC_SAMPLE_COUNT = 8
MEASUREMENT_INTERVAL_MM = 10
+
class FilamentWidthSensor:
def __init__(self, config):
self.printer = config.get_printer()
self.reactor = self.printer.get_reactor()
- self.pin = config.get('pin')
+ self.pin = config.get("pin")
self.nominal_filament_dia = config.getfloat(
- 'default_nominal_filament_diameter', above=1.0)
- self.measurement_delay = config.getfloat('measurement_delay', above=0.)
- self.measurement_max_difference = config.getfloat('max_difference',
- above=0.)
- self.max_diameter = (self.nominal_filament_dia
- + self.measurement_max_difference)
- self.min_diameter = (self.nominal_filament_dia
- - self.measurement_max_difference)
+ "default_nominal_filament_diameter", above=1.0
+ )
+ self.measurement_delay = config.getfloat("measurement_delay", above=0.0)
+ self.measurement_max_difference = config.getfloat("max_difference", above=0.0)
+ self.max_diameter = self.nominal_filament_dia + self.measurement_max_difference
+ self.min_diameter = self.nominal_filament_dia - self.measurement_max_difference
self.is_active = True
# filament array [position, filamentWidth]
self.filament_array = []
@@ -31,31 +30,30 @@ class FilamentWidthSensor:
self.toolhead = self.ppins = self.mcu_adc = None
self.printer.register_event_handler("klippy:ready", self.handle_ready)
# Start adc
- self.ppins = self.printer.lookup_object('pins')
- self.mcu_adc = self.ppins.setup_pin('adc', self.pin)
+ self.ppins = self.printer.lookup_object("pins")
+ self.mcu_adc = self.ppins.setup_pin("adc", self.pin)
self.mcu_adc.setup_adc_sample(ADC_SAMPLE_TIME, ADC_SAMPLE_COUNT)
self.mcu_adc.setup_adc_callback(ADC_REPORT_TIME, self.adc_callback)
# extrude factor updating
self.extrude_factor_update_timer = self.reactor.register_timer(
- self.extrude_factor_update_event)
+ self.extrude_factor_update_event
+ )
# Register commands
- self.gcode = self.printer.lookup_object('gcode')
- self.gcode.register_command('QUERY_FILAMENT_WIDTH', self.cmd_M407)
- self.gcode.register_command('RESET_FILAMENT_WIDTH_SENSOR',
- self.cmd_ClearFilamentArray)
- self.gcode.register_command('DISABLE_FILAMENT_WIDTH_SENSOR',
- self.cmd_M406)
- self.gcode.register_command('ENABLE_FILAMENT_WIDTH_SENSOR',
- self.cmd_M405)
+ self.gcode = self.printer.lookup_object("gcode")
+ self.gcode.register_command("QUERY_FILAMENT_WIDTH", self.cmd_M407)
+ self.gcode.register_command(
+ "RESET_FILAMENT_WIDTH_SENSOR", self.cmd_ClearFilamentArray
+ )
+ self.gcode.register_command("DISABLE_FILAMENT_WIDTH_SENSOR", self.cmd_M406)
+ self.gcode.register_command("ENABLE_FILAMENT_WIDTH_SENSOR", self.cmd_M405)
# Initialization
def handle_ready(self):
# Load printer objects
- self.toolhead = self.printer.lookup_object('toolhead')
+ self.toolhead = self.printer.lookup_object("toolhead")
# Start extrude factor update timer
- self.reactor.update_timer(self.extrude_factor_update_timer,
- self.reactor.NOW)
+ self.reactor.update_timer(self.extrude_factor_update_timer, self.reactor.NOW)
def adc_callback(self, read_time, read_value):
# read sensor value
@@ -66,15 +64,16 @@ class FilamentWidthSensor:
if len(self.filament_array) > 0:
# Get last reading position in array & calculate next
# reading position
- next_reading_position = (self.filament_array[-1][0]
- + MEASUREMENT_INTERVAL_MM)
+ next_reading_position = self.filament_array[-1][0] + MEASUREMENT_INTERVAL_MM
if next_reading_position <= (last_epos + self.measurement_delay):
- self.filament_array.append([last_epos + self.measurement_delay,
- self.lastFilamentWidthReading])
+ self.filament_array.append(
+ [last_epos + self.measurement_delay, self.lastFilamentWidthReading]
+ )
else:
# add first item to array
- self.filament_array.append([self.measurement_delay + last_epos,
- self.lastFilamentWidthReading])
+ self.filament_array.append(
+ [self.measurement_delay + last_epos, self.lastFilamentWidthReading]
+ )
def extrude_factor_update_event(self, eventtime):
# Update extrude factor
@@ -91,10 +90,12 @@ class FilamentWidthSensor:
# Get first item in filament_array queue
item = self.filament_array.pop(0)
filament_width = item[1]
- if ((filament_width <= self.max_diameter)
- and (filament_width >= self.min_diameter)):
- percentage = round(self.nominal_filament_dia**2
- / filament_width**2 * 100)
+ if (filament_width <= self.max_diameter) and (
+ filament_width >= self.min_diameter
+ ):
+ percentage = round(
+ self.nominal_filament_dia**2 / filament_width**2 * 100
+ )
self.gcode.run_script("M221 S" + str(percentage))
else:
self.gcode.run_script("M221 S100")
@@ -110,8 +111,9 @@ class FilamentWidthSensor:
def cmd_M407(self, gcmd):
response = ""
if self.lastFilamentWidthReading > 0:
- response += ("Filament dia (measured mm): "
- + str(self.lastFilamentWidthReading))
+ response += "Filament dia (measured mm): " + str(
+ self.lastFilamentWidthReading
+ )
else:
response += "Filament NOT present"
gcmd.respond_info(response)
@@ -129,8 +131,9 @@ class FilamentWidthSensor:
else:
self.is_active = True
# Start extrude factor update timer
- self.reactor.update_timer(self.extrude_factor_update_timer,
- self.reactor.NOW)
+ self.reactor.update_timer(
+ self.extrude_factor_update_timer, self.reactor.NOW
+ )
gcmd.respond_info(response)
def cmd_M406(self, gcmd):
@@ -140,13 +143,15 @@ class FilamentWidthSensor:
else:
self.is_active = False
# Stop extrude factor update timer
- self.reactor.update_timer(self.extrude_factor_update_timer,
- self.reactor.NEVER)
+ self.reactor.update_timer(
+ self.extrude_factor_update_timer, self.reactor.NEVER
+ )
# Clear filament array
self.filament_array = []
# Set extrude multiplier to 100%
self.gcode.run_script_from_command("M221 S100")
gcmd.respond_info(response)
+
def load_config(config):
return FilamentWidthSensor(config)
diff --git a/klippy/extras/tuning_tower.py b/klippy/extras/tuning_tower.py
index 4fec5b1b..7d54cbb5 100644
--- a/klippy/extras/tuning_tower.py
+++ b/klippy/extras/tuning_tower.py
@@ -5,38 +5,41 @@
# This file may be distributed under the terms of the GNU GPLv3 license.
import math, logging
-CANCEL_Z_DELTA=2.0
+CANCEL_Z_DELTA = 2.0
+
class TuningTower:
def __init__(self, config):
self.printer = config.get_printer()
self.normal_transform = None
- self.last_position = [0., 0., 0., 0.]
- self.last_z = self.start = self.factor = self.band = 0.
+ self.last_position = [0.0, 0.0, 0.0, 0.0]
+ self.last_z = self.start = self.factor = self.band = 0.0
self.last_command_value = None
self.command_fmt = ""
self.gcode_move = self.printer.load_object(config, "gcode_move")
# Register command
self.gcode = self.printer.lookup_object("gcode")
- self.gcode.register_command("TUNING_TOWER", self.cmd_TUNING_TOWER,
- desc=self.cmd_TUNING_TOWER_help)
+ self.gcode.register_command(
+ "TUNING_TOWER", self.cmd_TUNING_TOWER, desc=self.cmd_TUNING_TOWER_help
+ )
+
cmd_TUNING_TOWER_help = "Tool to adjust a parameter at each Z height"
+
def cmd_TUNING_TOWER(self, gcmd):
if self.normal_transform is not None:
self.end_test()
# Get parameters
- command = gcmd.get('COMMAND')
- parameter = gcmd.get('PARAMETER')
- self.start = gcmd.get_float('START', 0.)
- self.factor = gcmd.get_float('FACTOR', 0.)
- self.band = gcmd.get_float('BAND', 0., minval=0.)
- self.step_delta = gcmd.get_float('STEP_DELTA', 0.)
- self.step_height = gcmd.get_float('STEP_HEIGHT', 0., minval=0.)
- self.skip = gcmd.get_float('SKIP', 0., minval=0.)
+ command = gcmd.get("COMMAND")
+ parameter = gcmd.get("PARAMETER")
+ self.start = gcmd.get_float("START", 0.0)
+ self.factor = gcmd.get_float("FACTOR", 0.0)
+ self.band = gcmd.get_float("BAND", 0.0, minval=0.0)
+ self.step_delta = gcmd.get_float("STEP_DELTA", 0.0)
+ self.step_height = gcmd.get_float("STEP_HEIGHT", 0.0, minval=0.0)
+ self.skip = gcmd.get_float("SKIP", 0.0, minval=0.0)
if self.factor and (self.step_height or self.step_delta):
- raise gcmd.error(
- "Cannot specify both FACTOR and STEP_DELTA/STEP_HEIGHT")
- if (self.step_delta != 0.) != (self.step_height != 0.):
+ raise gcmd.error("Cannot specify both FACTOR and STEP_DELTA/STEP_HEIGHT")
+ if (self.step_delta != 0.0) != (self.step_height != 0.0):
raise gcmd.error("Must specify both STEP_DELTA and STEP_HEIGHT")
# Enable test mode
if self.gcode.is_traditional_gcode(command):
@@ -59,25 +62,29 @@ class TuningTower:
message_parts.append("step_height=%.6f" % (self.step_height,))
if self.skip:
message_parts.append("skip=%.6f" % (self.skip,))
- gcmd.respond_info(
- "Starting tuning test (" + " ".join(message_parts) + ")")
+ gcmd.respond_info("Starting tuning test (" + " ".join(message_parts) + ")")
+
def get_position(self):
pos = self.normal_transform.get_position()
self.last_position = list(pos)
return pos
+
def calc_value(self, z):
if self.skip:
- z = max(0., z - self.skip)
+ z = max(0.0, z - self.skip)
if self.step_height:
- return self.start + \
- self.step_delta * math.floor(z / self.step_height)
+ return self.start + self.step_delta * math.floor(z / self.step_height)
if self.band:
- z = (math.floor(z / self.band) + .5) * self.band
+ z = (math.floor(z / self.band) + 0.5) * self.band
return self.start + z * self.factor
+
def move(self, newpos, speed):
normal_transform = self.normal_transform
- if (newpos[3] > self.last_position[3] and newpos[2] != self.last_z
- and newpos[:3] != self.last_position[:3]):
+ if (
+ newpos[3] > self.last_position[3]
+ and newpos[2] != self.last_z
+ and newpos[:3] != self.last_position[:3]
+ ):
# Extrusion move at new z height
z = newpos[2]
if z < self.last_z - CANCEL_Z_DELTA:
@@ -85,22 +92,24 @@ class TuningTower:
self.end_test()
else:
# Process update
- gcode_z = self.gcode_move.get_status()['gcode_position'].z
+ gcode_z = self.gcode_move.get_status()["gcode_position"].z
newval = self.calc_value(gcode_z)
self.last_z = z
if newval != self.last_command_value:
self.last_command_value = newval
- self.gcode.run_script_from_command(self.command_fmt
- % (newval,))
+ self.gcode.run_script_from_command(self.command_fmt % (newval,))
# Forward move to actual handler
self.last_position[:] = newpos
normal_transform.move(newpos, speed)
+
def end_test(self):
self.gcode.respond_info("Ending tuning test mode")
self.gcode_move.set_move_transform(self.normal_transform, force=True)
self.normal_transform = None
+
def is_active(self):
return self.normal_transform is not None
+
def load_config(config):
return TuningTower(config)
diff --git a/klippy/extras/verify_heater.py b/klippy/extras/verify_heater.py
index 6726e479..0937b638 100644
--- a/klippy/extras/verify_heater.py
+++ b/klippy/extras/verify_heater.py
@@ -10,58 +10,61 @@ See the 'verify_heater' section in docs/Config_Reference.md
for the parameters that control this check.
"""
+
class HeaterCheck:
def __init__(self, config):
self.printer = config.get_printer()
- self.printer.register_event_handler("klippy:connect",
- self.handle_connect)
- self.printer.register_event_handler("klippy:shutdown",
- self.handle_shutdown)
+ self.printer.register_event_handler("klippy:connect", self.handle_connect)
+ self.printer.register_event_handler("klippy:shutdown", self.handle_shutdown)
self.heater_name = config.get_name().split()[1]
self.heater = None
- self.hysteresis = config.getfloat('hysteresis', 5., minval=0.)
- self.max_error = config.getfloat('max_error', 120., minval=0.)
- self.heating_gain = config.getfloat('heating_gain', 2., above=0.)
- default_gain_time = 20.
- if self.heater_name == 'heater_bed':
- default_gain_time = 60.
+ self.hysteresis = config.getfloat("hysteresis", 5.0, minval=0.0)
+ self.max_error = config.getfloat("max_error", 120.0, minval=0.0)
+ self.heating_gain = config.getfloat("heating_gain", 2.0, above=0.0)
+ default_gain_time = 20.0
+ if self.heater_name == "heater_bed":
+ default_gain_time = 60.0
self.check_gain_time = config.getfloat(
- 'check_gain_time', default_gain_time, minval=1.)
+ "check_gain_time", default_gain_time, minval=1.0
+ )
self.approaching_target = self.starting_approach = False
- self.last_target = self.goal_temp = self.error = 0.
+ self.last_target = self.goal_temp = self.error = 0.0
self.goal_systime = self.printer.get_reactor().NEVER
self.check_timer = None
+
def handle_connect(self):
- if self.printer.get_start_args().get('debugoutput') is not None:
+ if self.printer.get_start_args().get("debugoutput") is not None:
# Disable verify_heater if outputting to a debug file
return
- pheaters = self.printer.lookup_object('heaters')
+ pheaters = self.printer.lookup_object("heaters")
self.heater = pheaters.lookup_heater(self.heater_name)
logging.info("Starting heater checks for %s", self.heater_name)
reactor = self.printer.get_reactor()
self.check_timer = reactor.register_timer(self.check_event, reactor.NOW)
+
def handle_shutdown(self):
if self.check_timer is not None:
reactor = self.printer.get_reactor()
reactor.update_timer(self.check_timer, reactor.NEVER)
+
def check_event(self, eventtime):
temp, target = self.heater.get_temp(eventtime)
- if temp >= target - self.hysteresis or target <= 0.:
+ if temp >= target - self.hysteresis or target <= 0.0:
# Temperature near target - reset checks
if self.approaching_target and target:
- logging.info("Heater %s within range of %.3f",
- self.heater_name, target)
+ logging.info("Heater %s within range of %.3f", self.heater_name, target)
self.approaching_target = self.starting_approach = False
if temp <= target + self.hysteresis:
- self.error = 0.
+ self.error = 0.0
self.last_target = target
- return eventtime + 1.
+ return eventtime + 1.0
self.error += (target - self.hysteresis) - temp
if not self.approaching_target:
if target != self.last_target:
# Target changed - reset checks
- logging.info("Heater %s approaching new target of %.3f",
- self.heater_name, target)
+ logging.info(
+ "Heater %s approaching new target of %.3f", self.heater_name, target
+ )
self.approaching_target = self.starting_approach = True
self.goal_temp = temp + self.heating_gain
self.goal_systime = eventtime + self.check_gain_time
@@ -71,23 +74,26 @@ class HeaterCheck:
elif temp >= self.goal_temp:
# Temperature approaching target - reset checks
self.starting_approach = False
- self.error = 0.
+ self.error = 0.0
self.goal_temp = temp + self.heating_gain
self.goal_systime = eventtime + self.check_gain_time
elif eventtime >= self.goal_systime:
# Temperature is no longer approaching target
self.approaching_target = False
- logging.info("Heater %s no longer approaching target %.3f",
- self.heater_name, target)
+ logging.info(
+ "Heater %s no longer approaching target %.3f", self.heater_name, target
+ )
elif self.starting_approach:
self.goal_temp = min(self.goal_temp, temp + self.heating_gain)
self.last_target = target
- return eventtime + 1.
+ return eventtime + 1.0
+
def heater_fault(self):
msg = "Heater %s not heating at expected rate" % (self.heater_name,)
logging.error(msg)
self.printer.invoke_shutdown(msg + HINT_THERMAL)
return self.printer.get_reactor().NEVER
+
def load_config_prefix(config):
return HeaterCheck(config)
diff --git a/klippy/extras/virtual_sdcard.py b/klippy/extras/virtual_sdcard.py
index 6dc49e2f..aca257ea 100644
--- a/klippy/extras/virtual_sdcard.py
+++ b/klippy/extras/virtual_sdcard.py
@@ -5,7 +5,7 @@
# This file may be distributed under the terms of the GNU GPLv3 license.
import os, sys, logging, io
-VALID_GCODE_EXTS = ['gcode', 'g', 'gco']
+VALID_GCODE_EXTS = ["gcode", "g", "gco"]
DEFAULT_ERROR_GCODE = """
{% if 'heaters' in printer %}
@@ -13,39 +13,45 @@ DEFAULT_ERROR_GCODE = """
{% endif %}
"""
+
class VirtualSD:
def __init__(self, config):
self.printer = config.get_printer()
- self.printer.register_event_handler("klippy:shutdown",
- self.handle_shutdown)
+ self.printer.register_event_handler("klippy:shutdown", self.handle_shutdown)
# sdcard state
- sd = config.get('path')
+ sd = config.get("path")
self.sdcard_dirname = os.path.normpath(os.path.expanduser(sd))
self.current_file = None
self.file_position = self.file_size = 0
# Print Stat Tracking
- self.print_stats = self.printer.load_object(config, 'print_stats')
+ self.print_stats = self.printer.load_object(config, "print_stats")
# Work timer
self.reactor = self.printer.get_reactor()
self.must_pause_work = self.cmd_from_sd = False
self.next_file_position = 0
self.work_timer = None
# Error handling
- gcode_macro = self.printer.load_object(config, 'gcode_macro')
+ gcode_macro = self.printer.load_object(config, "gcode_macro")
self.on_error_gcode = gcode_macro.load_template(
- config, 'on_error_gcode', DEFAULT_ERROR_GCODE)
+ config, "on_error_gcode", DEFAULT_ERROR_GCODE
+ )
# Register commands
- self.gcode = self.printer.lookup_object('gcode')
- for cmd in ['M20', 'M21', 'M23', 'M24', 'M25', 'M26', 'M27']:
- self.gcode.register_command(cmd, getattr(self, 'cmd_' + cmd))
- for cmd in ['M28', 'M29', 'M30']:
+ self.gcode = self.printer.lookup_object("gcode")
+ for cmd in ["M20", "M21", "M23", "M24", "M25", "M26", "M27"]:
+ self.gcode.register_command(cmd, getattr(self, "cmd_" + cmd))
+ for cmd in ["M28", "M29", "M30"]:
self.gcode.register_command(cmd, self.cmd_error)
self.gcode.register_command(
- "SDCARD_RESET_FILE", self.cmd_SDCARD_RESET_FILE,
- desc=self.cmd_SDCARD_RESET_FILE_help)
+ "SDCARD_RESET_FILE",
+ self.cmd_SDCARD_RESET_FILE,
+ desc=self.cmd_SDCARD_RESET_FILE_help,
+ )
self.gcode.register_command(
- "SDCARD_PRINT_FILE", self.cmd_SDCARD_PRINT_FILE,
- desc=self.cmd_SDCARD_PRINT_FILE_help)
+ "SDCARD_PRINT_FILE",
+ self.cmd_SDCARD_PRINT_FILE,
+ desc=self.cmd_SDCARD_PRINT_FILE_help,
+ )
+
def handle_shutdown(self):
if self.work_timer is not None:
self.must_pause_work = True
@@ -57,24 +63,29 @@ class VirtualSD:
except:
logging.exception("virtual_sdcard shutdown read")
return
- logging.info("Virtual sdcard (%d): %s\nUpcoming (%d): %s",
- readpos, repr(data[:readcount]),
- self.file_position, repr(data[readcount:]))
+ logging.info(
+ "Virtual sdcard (%d): %s\nUpcoming (%d): %s",
+ readpos,
+ repr(data[:readcount]),
+ self.file_position,
+ repr(data[readcount:]),
+ )
+
def stats(self, eventtime):
if self.work_timer is None:
return False, ""
return True, "sd_pos=%d" % (self.file_position,)
+
def get_file_list(self, check_subdirs=False):
if check_subdirs:
flist = []
- for root, dirs, files in os.walk(
- self.sdcard_dirname, followlinks=True):
+ for root, dirs, files in os.walk(self.sdcard_dirname, followlinks=True):
for name in files:
- ext = name[name.rfind('.')+1:]
+ ext = name[name.rfind(".") + 1 :]
if ext not in VALID_GCODE_EXTS:
continue
full_path = os.path.join(root, name)
- r_path = full_path[len(self.sdcard_dirname) + 1:]
+ r_path = full_path[len(self.sdcard_dirname) + 1 :]
size = os.path.getsize(full_path)
flist.append((r_path, size))
return sorted(flist, key=lambda f: f[0].lower())
@@ -82,43 +93,53 @@ class VirtualSD:
dname = self.sdcard_dirname
try:
filenames = os.listdir(self.sdcard_dirname)
- return [(fname, os.path.getsize(os.path.join(dname, fname)))
- for fname in sorted(filenames, key=str.lower)
- if not fname.startswith('.')
- and os.path.isfile((os.path.join(dname, fname)))]
+ return [
+ (fname, os.path.getsize(os.path.join(dname, fname)))
+ for fname in sorted(filenames, key=str.lower)
+ if not fname.startswith(".")
+ and os.path.isfile((os.path.join(dname, fname)))
+ ]
except:
logging.exception("virtual_sdcard get_file_list")
raise self.gcode.error("Unable to get file list")
+
def get_status(self, eventtime):
return {
- 'file_path': self.file_path(),
- 'progress': self.progress(),
- 'is_active': self.is_active(),
- 'file_position': self.file_position,
- 'file_size': self.file_size,
+ "file_path": self.file_path(),
+ "progress": self.progress(),
+ "is_active": self.is_active(),
+ "file_position": self.file_position,
+ "file_size": self.file_size,
}
+
def file_path(self):
if self.current_file:
return self.current_file.name
return None
+
def progress(self):
if self.file_size:
return float(self.file_position) / self.file_size
else:
- return 0.
+ return 0.0
+
def is_active(self):
return self.work_timer is not None
+
def do_pause(self):
if self.work_timer is not None:
self.must_pause_work = True
while self.work_timer is not None and not self.cmd_from_sd:
- self.reactor.pause(self.reactor.monotonic() + .001)
+ self.reactor.pause(self.reactor.monotonic() + 0.001)
+
def do_resume(self):
if self.work_timer is not None:
raise self.gcode.error("SD busy")
self.must_pause_work = False
self.work_timer = self.reactor.register_timer(
- self.work_handler, self.reactor.NOW)
+ self.work_handler, self.reactor.NOW
+ )
+
def do_cancel(self):
if self.current_file is not None:
self.do_pause()
@@ -126,9 +147,11 @@ class VirtualSD:
self.current_file = None
self.print_stats.note_cancel()
self.file_position = self.file_size = 0
+
# G-Code commands
def cmd_error(self, gcmd):
raise gcmd.error("SD write not supported")
+
def _reset_file(self):
if self.current_file is not None:
self.do_pause()
@@ -137,24 +160,30 @@ class VirtualSD:
self.file_position = self.file_size = 0
self.print_stats.reset()
self.printer.send_event("virtual_sdcard:reset_file")
- cmd_SDCARD_RESET_FILE_help = "Clears a loaded SD File. Stops the print "\
- "if necessary"
+
+ cmd_SDCARD_RESET_FILE_help = (
+ "Clears a loaded SD File. Stops the print " "if necessary"
+ )
+
def cmd_SDCARD_RESET_FILE(self, gcmd):
if self.cmd_from_sd:
- raise gcmd.error(
- "SDCARD_RESET_FILE cannot be run from the sdcard")
+ raise gcmd.error("SDCARD_RESET_FILE cannot be run from the sdcard")
self._reset_file()
- cmd_SDCARD_PRINT_FILE_help = "Loads a SD file and starts the print. May "\
- "include files in subdirectories."
+
+ cmd_SDCARD_PRINT_FILE_help = (
+ "Loads a SD file and starts the print. May " "include files in subdirectories."
+ )
+
def cmd_SDCARD_PRINT_FILE(self, gcmd):
if self.work_timer is not None:
raise gcmd.error("SD busy")
self._reset_file()
filename = gcmd.get("FILENAME")
- if filename[0] == '/':
+ if filename[0] == "/":
filename = filename[1:]
self._load_file(gcmd, filename, check_subdirs=True)
self.do_resume()
+
def cmd_M20(self, gcmd):
# List SD card
files = self.get_file_list()
@@ -162,28 +191,31 @@ class VirtualSD:
for fname, fsize in files:
gcmd.respond_raw("%s %d" % (fname, fsize))
gcmd.respond_raw("End file list")
+
def cmd_M21(self, gcmd):
# Initialize SD card
gcmd.respond_raw("SD card ok")
+
def cmd_M23(self, gcmd):
# Select SD file
if self.work_timer is not None:
raise gcmd.error("SD busy")
self._reset_file()
filename = gcmd.get_raw_command_parameters().strip()
- if filename.startswith('/'):
+ if filename.startswith("/"):
filename = filename[1:]
self._load_file(gcmd, filename)
+
def _load_file(self, gcmd, filename, check_subdirs=False):
files = self.get_file_list(check_subdirs)
flist = [f[0] for f in files]
- files_by_lower = { fname.lower(): fname for fname, fsize in files }
+ files_by_lower = {fname.lower(): fname for fname, fsize in files}
fname = filename
try:
if fname not in flist:
fname = files_by_lower[fname.lower()]
fname = os.path.join(self.sdcard_dirname, fname)
- f = io.open(fname, 'r', newline='')
+ f = io.open(fname, "r", newline="")
f.seek(0, os.SEEK_END)
fsize = f.tell()
f.seek(0)
@@ -196,31 +228,40 @@ class VirtualSD:
self.file_position = 0
self.file_size = fsize
self.print_stats.set_current_file(filename)
+
def cmd_M24(self, gcmd):
# Start/resume SD print
self.do_resume()
+
def cmd_M25(self, gcmd):
# Pause SD print
self.do_pause()
+
def cmd_M26(self, gcmd):
# Set SD position
if self.work_timer is not None:
raise gcmd.error("SD busy")
- pos = gcmd.get_int('S', minval=0)
+ pos = gcmd.get_int("S", minval=0)
self.file_position = pos
+
def cmd_M27(self, gcmd):
# Report SD print status
if self.current_file is None:
gcmd.respond_raw("Not SD printing.")
return
- gcmd.respond_raw("SD printing byte %d/%d"
- % (self.file_position, self.file_size))
+ gcmd.respond_raw(
+ "SD printing byte %d/%d" % (self.file_position, self.file_size)
+ )
+
def get_file_position(self):
return self.next_file_position
+
def set_file_position(self, pos):
self.next_file_position = pos
+
def is_cmd_from_sd(self):
return self.cmd_from_sd
+
# Background work timer
def work_handler(self, eventtime):
logging.info("Starting SD card print (position %d)", self.file_position)
@@ -251,7 +292,7 @@ class VirtualSD:
logging.info("Finished SD card print")
self.gcode.respond_raw("Done printing file")
break
- lines = data.split('\n')
+ lines = data.split("\n")
lines[0] = partial_input + lines[0]
partial_input = lines.pop()
lines.reverse()
@@ -304,5 +345,6 @@ class VirtualSD:
self.print_stats.note_complete()
return self.reactor.NEVER
+
def load_config(config):
return VirtualSD(config)
diff --git a/klippy/extras/z_thermal_adjust.py b/klippy/extras/z_thermal_adjust.py
index a3a5997e..d33d367a 100644
--- a/klippy/extras/z_thermal_adjust.py
+++ b/klippy/extras/z_thermal_adjust.py
@@ -11,87 +11,89 @@ import threading
KELVIN_TO_CELSIUS = -273.15
+
class ZThermalAdjuster:
def __init__(self, config):
self.printer = config.get_printer()
- self.gcode = self.printer.lookup_object('gcode')
+ self.gcode = self.printer.lookup_object("gcode")
self.lock = threading.Lock()
# Get config parameters, convert to SI units where necessary
- self.temp_coeff = config.getfloat('temp_coeff', minval=-1, maxval=1,
- default=0)
- self.off_above_z = config.getfloat('z_adjust_off_above', 99999999.)
- self.max_z_adjust_mm = config.getfloat('max_z_adjustment', 99999999.)
+ self.temp_coeff = config.getfloat("temp_coeff", minval=-1, maxval=1, default=0)
+ self.off_above_z = config.getfloat("z_adjust_off_above", 99999999.0)
+ self.max_z_adjust_mm = config.getfloat("max_z_adjustment", 99999999.0)
# Register printer events
- self.printer.register_event_handler("klippy:connect",
- self.handle_connect)
- self.printer.register_event_handler("homing:home_rails_end",
- self.handle_homing_move_end)
+ self.printer.register_event_handler("klippy:connect", self.handle_connect)
+ self.printer.register_event_handler(
+ "homing:home_rails_end", self.handle_homing_move_end
+ )
# Setup temperature sensor
- self.smooth_time = config.getfloat('smooth_time', 2., above=0.)
- self.inv_smooth_time = 1. / self.smooth_time
- self.min_temp = config.getfloat('min_temp', minval=KELVIN_TO_CELSIUS)
- self.max_temp = config.getfloat('max_temp', above=self.min_temp)
- pheaters = self.printer.load_object(config, 'heaters')
+ self.smooth_time = config.getfloat("smooth_time", 2.0, above=0.0)
+ self.inv_smooth_time = 1.0 / self.smooth_time
+ self.min_temp = config.getfloat("min_temp", minval=KELVIN_TO_CELSIUS)
+ self.max_temp = config.getfloat("max_temp", above=self.min_temp)
+ pheaters = self.printer.load_object(config, "heaters")
self.sensor = pheaters.setup_sensor(config)
self.sensor.setup_minmax(self.min_temp, self.max_temp)
self.sensor.setup_callback(self.temperature_callback)
pheaters.register_sensor(config, self)
- self.last_temp = 0.
- self.measured_min = self.measured_max = 0.
- self.smoothed_temp = 0.
- self.last_temp_time = 0.
- self.ref_temperature = 0.
+ self.last_temp = 0.0
+ self.measured_min = self.measured_max = 0.0
+ self.smoothed_temp = 0.0
+ self.last_temp_time = 0.0
+ self.ref_temperature = 0.0
self.ref_temp_override = False
# Z transformation
- self.z_adjust_mm = 0.
- self.last_z_adjust_mm = 0.
+ self.z_adjust_mm = 0.0
+ self.last_z_adjust_mm = 0.0
self.adjust_enable = True
- self.last_position = [0., 0., 0., 0.]
+ self.last_position = [0.0, 0.0, 0.0, 0.0]
self.next_transform = None
# Register gcode commands
- self.gcode.register_command('SET_Z_THERMAL_ADJUST',
- self.cmd_SET_Z_THERMAL_ADJUST,
- desc=self.cmd_SET_Z_THERMAL_ADJUST_help)
+ self.gcode.register_command(
+ "SET_Z_THERMAL_ADJUST",
+ self.cmd_SET_Z_THERMAL_ADJUST,
+ desc=self.cmd_SET_Z_THERMAL_ADJUST_help,
+ )
def handle_connect(self):
- 'Called after all printer objects are instantiated'
- self.toolhead = self.printer.lookup_object('toolhead')
- gcode_move = self.printer.lookup_object('gcode_move')
+ "Called after all printer objects are instantiated"
+ self.toolhead = self.printer.lookup_object("toolhead")
+ gcode_move = self.printer.lookup_object("gcode_move")
# Register move transformation
self.next_transform = gcode_move.set_move_transform(self, force=True)
# Pull Z step distance for minimum adjustment increment
- kin = self.printer.lookup_object('toolhead').get_kinematics()
+ kin = self.printer.lookup_object("toolhead").get_kinematics()
steppers = [s.get_name() for s in kin.get_steppers()]
z_stepper = kin.get_steppers()[steppers.index("stepper_z")]
self.z_step_dist = z_stepper.get_step_dist()
def get_status(self, eventtime):
return {
- 'temperature': self.smoothed_temp,
- 'measured_min_temp': round(self.measured_min, 2),
- 'measured_max_temp': round(self.measured_max, 2),
- 'current_z_adjust': self.z_adjust_mm,
- 'z_adjust_ref_temperature': self.ref_temperature,
- 'enabled': self.adjust_enable
+ "temperature": self.smoothed_temp,
+ "measured_min_temp": round(self.measured_min, 2),
+ "measured_max_temp": round(self.measured_max, 2),
+ "current_z_adjust": self.z_adjust_mm,
+ "z_adjust_ref_temperature": self.ref_temperature,
+ "enabled": self.adjust_enable,
}
def handle_homing_move_end(self, homing_state, rails):
- 'Set reference temperature after Z homing.'
+ "Set reference temperature after Z homing."
if 2 in homing_state.get_axes():
self.ref_temperature = self.smoothed_temp
self.ref_temp_override = False
- self.z_adjust_mm = 0.
+ self.z_adjust_mm = 0.0
def calc_adjust(self, pos):
- 'Z adjustment calculation'
+ "Z adjustment calculation"
if pos[2] < self.off_above_z:
delta_t = self.smoothed_temp - self.ref_temperature
@@ -99,12 +101,11 @@ class ZThermalAdjuster:
adjust = -1 * self.temp_coeff * delta_t
# compute sign (+1 or -1) for maximum offset setting
- sign = 1 - (adjust <= 0)*2
+ sign = 1 - (adjust <= 0) * 2
# Don't apply adjustments smaller than step distance
if abs(adjust - self.z_adjust_mm) > self.z_step_dist:
- self.z_adjust_mm = min([self.max_z_adjust_mm*sign,
- adjust], key=abs)
+ self.z_adjust_mm = min([self.max_z_adjust_mm * sign, adjust], key=abs)
# Apply Z adjustment
new_z = pos[2] + self.z_adjust_mm
@@ -112,7 +113,7 @@ class ZThermalAdjuster:
return [pos[0], pos[1], new_z] + pos[3:]
def calc_unadjust(self, pos):
- 'Remove Z adjustment'
+ "Remove Z adjustment"
unadjusted_z = pos[2] - self.z_adjust_mm
return [pos[0], pos[1], unadjusted_z] + pos[3:]
@@ -133,27 +134,27 @@ class ZThermalAdjuster:
self.last_position[:] = newpos
def temperature_callback(self, read_time, temp):
- 'Called everytime the Z adjust thermistor is read'
+ "Called everytime the Z adjust thermistor is read"
with self.lock:
time_diff = read_time - self.last_temp_time
self.last_temp = temp
self.last_temp_time = read_time
temp_diff = temp - self.smoothed_temp
- adj_time = min(time_diff * self.inv_smooth_time, 1.)
+ adj_time = min(time_diff * self.inv_smooth_time, 1.0)
self.smoothed_temp += temp_diff * adj_time
self.measured_min = min(self.measured_min, self.smoothed_temp)
self.measured_max = max(self.measured_max, self.smoothed_temp)
def get_temp(self, eventtime):
- return self.smoothed_temp, 0.
+ return self.smoothed_temp, 0.0
def stats(self, eventtime):
- return False, '%s: temp=%.1f' % ("z_thermal_adjust", self.smoothed_temp)
+ return False, "%s: temp=%.1f" % ("z_thermal_adjust", self.smoothed_temp)
def cmd_SET_Z_THERMAL_ADJUST(self, gcmd):
- enable = gcmd.get_int('ENABLE', None, minval=0, maxval=1)
- coeff = gcmd.get_float('TEMP_COEFF', None, minval=-1, maxval=1)
- ref_temp = gcmd.get_float('REF_TEMP', None, minval=KELVIN_TO_CELSIUS)
+ enable = gcmd.get_int("ENABLE", None, minval=0, maxval=1)
+ coeff = gcmd.get_float("TEMP_COEFF", None, minval=-1, maxval=1)
+ ref_temp = gcmd.get_float("REF_TEMP", None, minval=KELVIN_TO_CELSIUS)
if ref_temp is not None:
self.ref_temperature = ref_temp
@@ -163,26 +164,31 @@ class ZThermalAdjuster:
if enable is not None:
if enable != self.adjust_enable:
self.adjust_enable = True if enable else False
- gcode_move = self.printer.lookup_object('gcode_move')
+ gcode_move = self.printer.lookup_object("gcode_move")
gcode_move.reset_last_position()
- state = '1 (enabled)' if self.adjust_enable else '0 (disabled)'
- override = ' (manual)' if self.ref_temp_override else ''
- msg = ("enable: %s\n"
- "temp_coeff: %f mm/degC\n"
- "ref_temp: %.2f degC%s\n"
- "-------------------\n"
- "Current Z temp: %.2f degC\n"
- "Applied Z adjustment: %.4f mm"
- % (state,
- self.temp_coeff,
- self.ref_temperature, override,
- self.smoothed_temp,
- self.z_adjust_mm)
+ state = "1 (enabled)" if self.adjust_enable else "0 (disabled)"
+ override = " (manual)" if self.ref_temp_override else ""
+ msg = (
+ "enable: %s\n"
+ "temp_coeff: %f mm/degC\n"
+ "ref_temp: %.2f degC%s\n"
+ "-------------------\n"
+ "Current Z temp: %.2f degC\n"
+ "Applied Z adjustment: %.4f mm"
+ % (
+ state,
+ self.temp_coeff,
+ self.ref_temperature,
+ override,
+ self.smoothed_temp,
+ self.z_adjust_mm,
+ )
)
gcmd.respond_info(msg)
- cmd_SET_Z_THERMAL_ADJUST_help = 'Set/query Z Thermal Adjust parameters.'
+ cmd_SET_Z_THERMAL_ADJUST_help = "Set/query Z Thermal Adjust parameters."
+
def load_config(config):
return ZThermalAdjuster(config)
diff --git a/klippy/extras/z_tilt.py b/klippy/extras/z_tilt.py
index 9f5ea0b9..f000370c 100644
--- a/klippy/extras/z_tilt.py
+++ b/klippy/extras/z_tilt.py
@@ -7,32 +7,37 @@ import logging
import mathutil
from . import probe
+
class ZAdjustHelper:
def __init__(self, config, z_count):
self.printer = config.get_printer()
self.name = config.get_name()
self.z_count = z_count
self.z_steppers = []
- self.printer.register_event_handler("klippy:connect",
- self.handle_connect)
+ self.printer.register_event_handler("klippy:connect", self.handle_connect)
+
def handle_connect(self):
- kin = self.printer.lookup_object('toolhead').get_kinematics()
- z_steppers = [s for s in kin.get_steppers() if s.is_active_axis('z')]
+ kin = self.printer.lookup_object("toolhead").get_kinematics()
+ z_steppers = [s for s in kin.get_steppers() if s.is_active_axis("z")]
if len(z_steppers) != self.z_count:
raise self.printer.config_error(
- "%s z_positions needs exactly %d items" % (
- self.name, len(z_steppers)))
+ "%s z_positions needs exactly %d items" % (self.name, len(z_steppers))
+ )
if len(z_steppers) < 2:
raise self.printer.config_error(
- "%s requires multiple z steppers" % (self.name,))
+ "%s requires multiple z steppers" % (self.name,)
+ )
self.z_steppers = z_steppers
+
def adjust_steppers(self, adjustments, speed):
- toolhead = self.printer.lookup_object('toolhead')
- gcode = self.printer.lookup_object('gcode')
+ toolhead = self.printer.lookup_object("toolhead")
+ gcode = self.printer.lookup_object("gcode")
curpos = toolhead.get_position()
# Report on movements
- stepstrs = ["%s = %.6f" % (s.get_name(), a)
- for s, a in zip(self.z_steppers, adjustments)]
+ stepstrs = [
+ "%s = %.6f" % (s.get_name(), a)
+ for s, a in zip(self.z_steppers, adjustments)
+ ]
msg = "Making the following Z adjustments:\n%s" % ("\n".join(stepstrs),)
gcode.respond_info(msg)
# Disable Z stepper movements
@@ -44,9 +49,9 @@ class ZAdjustHelper:
positions.sort(key=(lambda k: k[0]))
first_stepper_offset, first_stepper = positions[0]
z_low = curpos[2] - first_stepper_offset
- for i in range(len(positions)-1):
+ for i in range(len(positions) - 1):
stepper_offset, stepper = positions[i]
- next_stepper_offset, next_stepper = positions[i+1]
+ next_stepper_offset, next_stepper = positions[i + 1]
toolhead.flush_step_generation()
stepper.set_trapq(toolhead.get_trapq())
curpos[2] = z_low + next_stepper_offset
@@ -66,39 +71,48 @@ class ZAdjustHelper:
curpos[2] += first_stepper_offset
toolhead.set_position(curpos)
+
class ZAdjustStatus:
def __init__(self, printer):
self.applied = False
- printer.register_event_handler("stepper_enable:motor_off",
- self._motor_off)
+ printer.register_event_handler("stepper_enable:motor_off", self._motor_off)
+
def check_retry_result(self, retry_result):
if retry_result == "done":
self.applied = True
return retry_result
+
def reset(self):
self.applied = False
+
def get_status(self, eventtime):
- return {'applied': self.applied}
+ return {"applied": self.applied}
+
def _motor_off(self, print_time):
self.reset()
+
class RetryHelper:
- def __init__(self, config, error_msg_extra = ""):
- self.gcode = config.get_printer().lookup_object('gcode')
+ def __init__(self, config, error_msg_extra=""):
+ self.gcode = config.get_printer().lookup_object("gcode")
self.default_max_retries = config.getint("retries", 0, minval=0)
- self.default_retry_tolerance = \
- config.getfloat("retry_tolerance", 0., above=0.)
+ self.default_retry_tolerance = config.getfloat(
+ "retry_tolerance", 0.0, above=0.0
+ )
self.value_label = "Probed points range"
self.error_msg_extra = error_msg_extra
+
def start(self, gcmd):
- self.max_retries = gcmd.get_int('RETRIES', self.default_max_retries,
- minval=0, maxval=30)
- self.retry_tolerance = gcmd.get_float('RETRY_TOLERANCE',
- self.default_retry_tolerance,
- minval=0.0, maxval=1.0)
+ self.max_retries = gcmd.get_int(
+ "RETRIES", self.default_max_retries, minval=0, maxval=30
+ )
+ self.retry_tolerance = gcmd.get_float(
+ "RETRY_TOLERANCE", self.default_retry_tolerance, minval=0.0, maxval=1.0
+ )
self.current_retry = 0
self.previous = None
self.increasing = 0
+
def check_increase(self, error):
if self.previous and error > self.previous + 0.0000001:
self.increasing += 1
@@ -106,17 +120,26 @@ class RetryHelper:
self.increasing -= 1
self.previous = error
return self.increasing > 1
+
def check_retry(self, z_positions):
if self.max_retries == 0:
return "done"
- error = round(max(z_positions) - min(z_positions),6)
+ error = round(max(z_positions) - min(z_positions), 6)
self.gcode.respond_info(
- "Retries: %d/%d %s: %0.6f tolerance: %0.6f" % (
- self.current_retry, self.max_retries, self.value_label,
- error, self.retry_tolerance))
+ "Retries: %d/%d %s: %0.6f tolerance: %0.6f"
+ % (
+ self.current_retry,
+ self.max_retries,
+ self.value_label,
+ error,
+ self.retry_tolerance,
+ )
+ )
if self.check_increase(error):
- raise self.gcode.error("Retries aborting: %s is increasing. %s"
- % (self.value_label, self.error_msg_extra))
+ raise self.gcode.error(
+ "Retries aborting: %s is increasing. %s"
+ % (self.value_label, self.error_msg_extra)
+ )
if error <= self.retry_tolerance:
return "done"
self.current_retry += 1
@@ -124,56 +147,73 @@ class RetryHelper:
raise self.gcode.error("Too many retries")
return "retry"
+
class ZTilt:
def __init__(self, config):
self.printer = config.get_printer()
- self.z_positions = config.getlists('z_positions', seps=(',', '\n'),
- parser=float, count=2)
+ self.z_positions = config.getlists(
+ "z_positions", seps=(",", "\n"), parser=float, count=2
+ )
self.retry_helper = RetryHelper(config)
self.probe_helper = probe.ProbePointsHelper(config, self.probe_finalize)
self.probe_helper.minimum_points(2)
self.z_status = ZAdjustStatus(self.printer)
self.z_helper = ZAdjustHelper(config, len(self.z_positions))
# Register Z_TILT_ADJUST command
- gcode = self.printer.lookup_object('gcode')
- gcode.register_command('Z_TILT_ADJUST', self.cmd_Z_TILT_ADJUST,
- desc=self.cmd_Z_TILT_ADJUST_help)
+ gcode = self.printer.lookup_object("gcode")
+ gcode.register_command(
+ "Z_TILT_ADJUST", self.cmd_Z_TILT_ADJUST, desc=self.cmd_Z_TILT_ADJUST_help
+ )
+
cmd_Z_TILT_ADJUST_help = "Adjust the Z tilt"
+
def cmd_Z_TILT_ADJUST(self, gcmd):
self.z_status.reset()
self.retry_helper.start(gcmd)
self.probe_helper.start_probe(gcmd)
+
def probe_finalize(self, offsets, positions):
# Setup for coordinate descent analysis
z_offset = offsets[2]
logging.info("Calculating bed tilt with: %s", positions)
- params = { 'x_adjust': 0., 'y_adjust': 0., 'z_adjust': z_offset }
+ params = {"x_adjust": 0.0, "y_adjust": 0.0, "z_adjust": z_offset}
+
# Perform coordinate descent
def adjusted_height(pos, params):
x, y, z = pos
- return (z - x*params['x_adjust'] - y*params['y_adjust']
- - params['z_adjust'])
+ return (
+ z - x * params["x_adjust"] - y * params["y_adjust"] - params["z_adjust"]
+ )
+
def errorfunc(params):
- total_error = 0.
+ total_error = 0.0
for pos in positions:
- total_error += adjusted_height(pos, params)**2
+ total_error += adjusted_height(pos, params) ** 2
return total_error
- new_params = mathutil.coordinate_descent(
- params.keys(), params, errorfunc)
+
+ new_params = mathutil.coordinate_descent(params.keys(), params, errorfunc)
# Apply results
speed = self.probe_helper.get_lift_speed()
logging.info("Calculated bed tilt parameters: %s", new_params)
- x_adjust = new_params['x_adjust']
- y_adjust = new_params['y_adjust']
- z_adjust = (new_params['z_adjust'] - z_offset
- - x_adjust * offsets[0] - y_adjust * offsets[1])
- adjustments = [x*x_adjust + y*y_adjust + z_adjust
- for x, y in self.z_positions]
+ x_adjust = new_params["x_adjust"]
+ y_adjust = new_params["y_adjust"]
+ z_adjust = (
+ new_params["z_adjust"]
+ - z_offset
+ - x_adjust * offsets[0]
+ - y_adjust * offsets[1]
+ )
+ adjustments = [
+ x * x_adjust + y * y_adjust + z_adjust for x, y in self.z_positions
+ ]
self.z_helper.adjust_steppers(adjustments, speed)
return self.z_status.check_retry_result(
- self.retry_helper.check_retry([p[2] for p in positions]))
+ self.retry_helper.check_retry([p[2] for p in positions])
+ )
+
def get_status(self, eventtime):
- return self.z_status.get_status(eventtime)
+ return self.z_status.get_status(eventtime)
+
def load_config(config):
return ZTilt(config)